Merge pull request #740 from lightpanda-io/fix_anchor_href

Fix anchor href
This commit is contained in:
Karl Seguin
2025-06-03 10:47:25 +08:00
committed by GitHub
2 changed files with 106 additions and 55 deletions

View File

@@ -194,8 +194,10 @@ pub const HTMLAnchorElement = struct {
return try parser.anchorGetHref(self);
}
pub fn set_href(self: *parser.Anchor, href: []const u8) !void {
return try parser.anchorSetHref(self, href);
pub fn set_href(self: *parser.Anchor, href: []const u8, page: *const Page) !void {
const stitch = @import("../../url.zig").stitch;
const full = try stitch(page.call_arena, href, page.url.raw, .{});
return try parser.anchorSetHref(self, full);
}
pub fn get_hreflang(self: *parser.Anchor) ![]const u8 {
@@ -289,10 +291,9 @@ pub const HTMLAnchorElement = struct {
try parser.anchorSetHref(self, href);
}
// TODO return a disposable string
pub fn get_hostname(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try page.arena.dupe(u8, u.get_hostname());
return u.get_hostname();
}
pub fn set_hostname(self: *parser.Anchor, v: []const u8, page: *Page) !void {
@@ -326,7 +327,7 @@ pub const HTMLAnchorElement = struct {
// TODO return a disposable string
pub fn get_username(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try page.arena.dupe(u8, u.get_username());
return u.get_username();
}
pub fn set_username(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
@@ -366,7 +367,7 @@ pub const HTMLAnchorElement = struct {
// TODO return a disposable string
pub fn get_pathname(self: *parser.Anchor, page: *Page) ![]const u8 {
var u = try url(self, page);
return try page.arena.dupe(u8, u.get_pathname());
return u.get_pathname();
}
pub fn set_pathname(self: *parser.Anchor, v: []const u8, page: *Page) !void {
@@ -1056,62 +1057,62 @@ test "Browser.HTML.Element" {
defer runner.deinit();
try runner.testCases(&.{
.{ "let a = document.getElementById('link')", "undefined" },
.{ "a.target", "" },
.{ "a.target = '_blank'", "_blank" },
.{ "a.target", "_blank" },
.{ "a.target = ''", "" },
.{ "let link = document.getElementById('link')", "undefined" },
.{ "link.target", "" },
.{ "link.target = '_blank'", "_blank" },
.{ "link.target", "_blank" },
.{ "link.target = ''", "" },
.{ "a.href", "foo" },
.{ "a.href = 'https://lightpanda.io/'", "https://lightpanda.io/" },
.{ "a.href", "https://lightpanda.io/" },
.{ "link.href", "foo" },
.{ "link.href = 'https://lightpanda.io/'", "https://lightpanda.io/" },
.{ "link.href", "https://lightpanda.io/" },
.{ "a.origin", "https://lightpanda.io" },
.{ "link.origin", "https://lightpanda.io" },
.{ "a.host = 'lightpanda.io:443'", "lightpanda.io:443" },
.{ "a.host", "lightpanda.io:443" },
.{ "a.port", "443" },
.{ "a.hostname", "lightpanda.io" },
.{ "link.host = 'lightpanda.io:443'", "lightpanda.io:443" },
.{ "link.host", "lightpanda.io:443" },
.{ "link.port", "443" },
.{ "link.hostname", "lightpanda.io" },
.{ "a.host = 'lightpanda.io'", "lightpanda.io" },
.{ "a.host", "lightpanda.io" },
.{ "a.port", "" },
.{ "a.hostname", "lightpanda.io" },
.{ "link.host = 'lightpanda.io'", "lightpanda.io" },
.{ "link.host", "lightpanda.io" },
.{ "link.port", "" },
.{ "link.hostname", "lightpanda.io" },
.{ "a.host", "lightpanda.io" },
.{ "a.hostname", "lightpanda.io" },
.{ "a.hostname = 'foo.bar'", "foo.bar" },
.{ "a.href", "https://foo.bar/" },
.{ "link.host", "lightpanda.io" },
.{ "link.hostname", "lightpanda.io" },
.{ "link.hostname = 'foo.bar'", "foo.bar" },
.{ "link.href", "https://foo.bar/" },
.{ "a.search", "" },
.{ "a.search = 'q=bar'", "q=bar" },
.{ "a.search", "?q=bar" },
.{ "a.href", "https://foo.bar/?q=bar" },
.{ "link.search", "" },
.{ "link.search = 'q=bar'", "q=bar" },
.{ "link.search", "?q=bar" },
.{ "link.href", "https://foo.bar/?q=bar" },
.{ "a.hash", "" },
.{ "a.hash = 'frag'", "frag" },
.{ "a.hash", "#frag" },
.{ "a.href", "https://foo.bar/?q=bar#frag" },
.{ "link.hash", "" },
.{ "link.hash = 'frag'", "frag" },
.{ "link.hash", "#frag" },
.{ "link.href", "https://foo.bar/?q=bar#frag" },
.{ "a.port", "" },
.{ "a.port = '443'", "443" },
.{ "a.host", "foo.bar:443" },
.{ "a.hostname", "foo.bar" },
.{ "a.href", "https://foo.bar:443/?q=bar#frag" },
.{ "a.port = null", "null" },
.{ "a.href", "https://foo.bar/?q=bar#frag" },
.{ "link.port", "" },
.{ "link.port = '443'", "443" },
.{ "link.host", "foo.bar:443" },
.{ "link.hostname", "foo.bar" },
.{ "link.href", "https://foo.bar:443/?q=bar#frag" },
.{ "link.port = null", "null" },
.{ "link.href", "https://foo.bar/?q=bar#frag" },
.{ "a.href = 'foo'", "foo" },
.{ "link.href = 'foo'", "foo" },
.{ "a.type", "" },
.{ "a.type = 'text/html'", "text/html" },
.{ "a.type", "text/html" },
.{ "a.type = ''", "" },
.{ "link.type", "" },
.{ "link.type = 'text/html'", "text/html" },
.{ "link.type", "text/html" },
.{ "link.type = ''", "" },
.{ "a.text", "OK" },
.{ "a.text = 'foo'", "foo" },
.{ "a.text", "foo" },
.{ "a.text = 'OK'", "OK" },
.{ "link.text", "OK" },
.{ "link.text = 'foo'", "foo" },
.{ "link.text", "foo" },
.{ "link.text = 'OK'", "OK" },
}, .{});
try runner.testCases(&.{
@@ -1174,4 +1175,10 @@ test "Browser.HTML.Element" {
.{ "lyric.src = 15", "15" },
.{ "lyric.src", "15" },
}, .{});
try runner.testCases(&.{
.{ "let a = document.createElement('a');", null },
.{ "a.href = 'about'", null },
.{ "a.href", "https://lightpanda.io/opensource-browser/about" },
}, .{});
}

View File

@@ -4,6 +4,8 @@ const Uri = std.Uri;
const Allocator = std.mem.Allocator;
const WebApiURL = @import("browser/url/url.zig").URL;
pub const stitch = URL.stitch;
pub const URL = struct {
uri: Uri,
raw: []const u8,
@@ -91,6 +93,7 @@ pub const URL = struct {
if_needed,
};
};
/// Properly stitches two URL fragments together.
///
/// For URLs with a path, it will replace the last entry with the src.
@@ -101,7 +104,7 @@ pub const URL = struct {
base: []const u8,
opts: StitchOpts,
) ![]const u8 {
if (base.len == 0) {
if (base.len == 0 or isURL(src)) {
if (opts.alloc == .always) {
return allocator.dupe(u8, src);
}
@@ -154,7 +157,41 @@ pub const URL = struct {
}
};
test "Url resolve size" {
fn isURL(url: []const u8) bool {
if (std.mem.startsWith(u8, url, "://")) {
return true;
}
if (url.len < 8) {
return false;
}
if (!std.ascii.startsWithIgnoreCase(url, "http")) {
return false;
}
var pos: usize = 4;
if (url[4] == 's' or url[4] == 'S') {
pos = 5;
}
return std.mem.startsWith(u8, url[pos..], "://");
}
const testing = @import("testing.zig");
test "URL: isURL" {
try testing.expectEqual(true, isURL("://lightpanda.io"));
try testing.expectEqual(true, isURL("://lightpanda.io/about"));
try testing.expectEqual(true, isURL("http://lightpanda.io/about"));
try testing.expectEqual(true, isURL("HttP://lightpanda.io/about"));
try testing.expectEqual(true, isURL("httpS://lightpanda.io/about"));
try testing.expectEqual(true, isURL("HTTPs://lightpanda.io/about"));
try testing.expectEqual(false, isURL("/lightpanda.io"));
try testing.expectEqual(false, isURL("../../about"));
try testing.expectEqual(false, isURL("about"));
}
test "URL: resolve size" {
const base = "https://www.lightpande.io";
const url = try URL.parse(base, null);
@@ -170,8 +207,6 @@ test "Url resolve size" {
try std.testing.expectEqualStrings(out_url.raw[26..], &url_string);
}
const testing = @import("testing.zig");
test "URL: Stitching Base & Src URLs (Basic)" {
const allocator = testing.allocator;
@@ -212,6 +247,15 @@ test "URL: Stiching Base & Src URLs (Both Local)" {
try testing.expectString("./abcdef/something.js", result);
}
test "URL: Stiching src as full path" {
const allocator = testing.allocator;
const base = "https://www.lightpanda.io/";
const src = "https://lightpanda.io/something.js";
const result = try URL.stitch(allocator, src, base, .{});
try testing.expectString("https://lightpanda.io/something.js", result);
}
test "URL: concatQueryString" {
defer testing.reset();
const arena = testing.arena_allocator;