Skip to content

Commit 4c8e2a1

Browse files
committed
Setting anchor href should consider document.url
1 parent a482d59 commit 4c8e2a1

File tree

2 files changed

+117
-66
lines changed

2 files changed

+117
-66
lines changed

src/browser/html/elements.zig

Lines changed: 69 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,10 @@ pub const HTMLAnchorElement = struct {
194194
return try parser.anchorGetHref(self);
195195
}
196196

197-
pub fn set_href(self: *parser.Anchor, href: []const u8) !void {
198-
return try parser.anchorSetHref(self, href);
197+
pub fn set_href(self: *parser.Anchor, href: []const u8, page: *const Page) !void {
198+
const stitch = @import("../../url.zig").stitch;
199+
const full = try stitch(page.call_arena, href, page.url.raw, .{});
200+
return try parser.anchorSetHref(self, full);
199201
}
200202

201203
pub fn get_hreflang(self: *parser.Anchor) ![]const u8 {
@@ -289,10 +291,9 @@ pub const HTMLAnchorElement = struct {
289291
try parser.anchorSetHref(self, href);
290292
}
291293

292-
// TODO return a disposable string
293294
pub fn get_hostname(self: *parser.Anchor, page: *Page) ![]const u8 {
294295
var u = try url(self, page);
295-
return try page.arena.dupe(u8, u.get_hostname());
296+
return u.get_hostname();
296297
}
297298

298299
pub fn set_hostname(self: *parser.Anchor, v: []const u8, page: *Page) !void {
@@ -326,7 +327,7 @@ pub const HTMLAnchorElement = struct {
326327
// TODO return a disposable string
327328
pub fn get_username(self: *parser.Anchor, page: *Page) ![]const u8 {
328329
var u = try url(self, page);
329-
return try page.arena.dupe(u8, u.get_username());
330+
return u.get_username();
330331
}
331332

332333
pub fn set_username(self: *parser.Anchor, v: ?[]const u8, page: *Page) !void {
@@ -366,7 +367,7 @@ pub const HTMLAnchorElement = struct {
366367
// TODO return a disposable string
367368
pub fn get_pathname(self: *parser.Anchor, page: *Page) ![]const u8 {
368369
var u = try url(self, page);
369-
return try page.arena.dupe(u8, u.get_pathname());
370+
return u.get_pathname();
370371
}
371372

372373
pub fn set_pathname(self: *parser.Anchor, v: []const u8, page: *Page) !void {
@@ -1056,62 +1057,62 @@ test "Browser.HTML.Element" {
10561057
defer runner.deinit();
10571058

10581059
try runner.testCases(&.{
1059-
.{ "let a = document.getElementById('link')", "undefined" },
1060-
.{ "a.target", "" },
1061-
.{ "a.target = '_blank'", "_blank" },
1062-
.{ "a.target", "_blank" },
1063-
.{ "a.target = ''", "" },
1064-
1065-
.{ "a.href", "foo" },
1066-
.{ "a.href = 'https://lightpanda.io/'", "https://lightpanda.io/" },
1067-
.{ "a.href", "https://lightpanda.io/" },
1068-
1069-
.{ "a.origin", "https://lightpanda.io" },
1070-
1071-
.{ "a.host = 'lightpanda.io:443'", "lightpanda.io:443" },
1072-
.{ "a.host", "lightpanda.io:443" },
1073-
.{ "a.port", "443" },
1074-
.{ "a.hostname", "lightpanda.io" },
1075-
1076-
.{ "a.host = 'lightpanda.io'", "lightpanda.io" },
1077-
.{ "a.host", "lightpanda.io" },
1078-
.{ "a.port", "" },
1079-
.{ "a.hostname", "lightpanda.io" },
1080-
1081-
.{ "a.host", "lightpanda.io" },
1082-
.{ "a.hostname", "lightpanda.io" },
1083-
.{ "a.hostname = 'foo.bar'", "foo.bar" },
1084-
.{ "a.href", "https://foo.bar/" },
1085-
1086-
.{ "a.search", "" },
1087-
.{ "a.search = 'q=bar'", "q=bar" },
1088-
.{ "a.search", "?q=bar" },
1089-
.{ "a.href", "https://foo.bar/?q=bar" },
1090-
1091-
.{ "a.hash", "" },
1092-
.{ "a.hash = 'frag'", "frag" },
1093-
.{ "a.hash", "#frag" },
1094-
.{ "a.href", "https://foo.bar/?q=bar#frag" },
1095-
1096-
.{ "a.port", "" },
1097-
.{ "a.port = '443'", "443" },
1098-
.{ "a.host", "foo.bar:443" },
1099-
.{ "a.hostname", "foo.bar" },
1100-
.{ "a.href", "https://foo.bar:443/?q=bar#frag" },
1101-
.{ "a.port = null", "null" },
1102-
.{ "a.href", "https://foo.bar/?q=bar#frag" },
1103-
1104-
.{ "a.href = 'foo'", "foo" },
1105-
1106-
.{ "a.type", "" },
1107-
.{ "a.type = 'text/html'", "text/html" },
1108-
.{ "a.type", "text/html" },
1109-
.{ "a.type = ''", "" },
1110-
1111-
.{ "a.text", "OK" },
1112-
.{ "a.text = 'foo'", "foo" },
1113-
.{ "a.text", "foo" },
1114-
.{ "a.text = 'OK'", "OK" },
1060+
.{ "let link = document.getElementById('link')", "undefined" },
1061+
.{ "link.target", "" },
1062+
.{ "link.target = '_blank'", "_blank" },
1063+
.{ "link.target", "_blank" },
1064+
.{ "link.target = ''", "" },
1065+
1066+
.{ "link.href", "foo" },
1067+
.{ "link.href = 'https://lightpanda.io/'", "https://lightpanda.io/" },
1068+
.{ "link.href", "https://lightpanda.io/" },
1069+
1070+
.{ "link.origin", "https://lightpanda.io" },
1071+
1072+
.{ "link.host = 'lightpanda.io:443'", "lightpanda.io:443" },
1073+
.{ "link.host", "lightpanda.io:443" },
1074+
.{ "link.port", "443" },
1075+
.{ "link.hostname", "lightpanda.io" },
1076+
1077+
.{ "link.host = 'lightpanda.io'", "lightpanda.io" },
1078+
.{ "link.host", "lightpanda.io" },
1079+
.{ "link.port", "" },
1080+
.{ "link.hostname", "lightpanda.io" },
1081+
1082+
.{ "link.host", "lightpanda.io" },
1083+
.{ "link.hostname", "lightpanda.io" },
1084+
.{ "link.hostname = 'foo.bar'", "foo.bar" },
1085+
.{ "link.href", "https://foo.bar/" },
1086+
1087+
.{ "link.search", "" },
1088+
.{ "link.search = 'q=bar'", "q=bar" },
1089+
.{ "link.search", "?q=bar" },
1090+
.{ "link.href", "https://foo.bar/?q=bar" },
1091+
1092+
.{ "link.hash", "" },
1093+
.{ "link.hash = 'frag'", "frag" },
1094+
.{ "link.hash", "#frag" },
1095+
.{ "link.href", "https://foo.bar/?q=bar#frag" },
1096+
1097+
.{ "link.port", "" },
1098+
.{ "link.port = '443'", "443" },
1099+
.{ "link.host", "foo.bar:443" },
1100+
.{ "link.hostname", "foo.bar" },
1101+
.{ "link.href", "https://foo.bar:443/?q=bar#frag" },
1102+
.{ "link.port = null", "null" },
1103+
.{ "link.href", "https://foo.bar/?q=bar#frag" },
1104+
1105+
.{ "link.href = 'foo'", "foo" },
1106+
1107+
.{ "link.type", "" },
1108+
.{ "link.type = 'text/html'", "text/html" },
1109+
.{ "link.type", "text/html" },
1110+
.{ "link.type = ''", "" },
1111+
1112+
.{ "link.text", "OK" },
1113+
.{ "link.text = 'foo'", "foo" },
1114+
.{ "link.text", "foo" },
1115+
.{ "link.text = 'OK'", "OK" },
11151116
}, .{});
11161117

11171118
try runner.testCases(&.{
@@ -1174,4 +1175,10 @@ test "Browser.HTML.Element" {
11741175
.{ "lyric.src = 15", "15" },
11751176
.{ "lyric.src", "15" },
11761177
}, .{});
1178+
1179+
try runner.testCases(&.{
1180+
.{ "let a = document.createElement('a');", null },
1181+
.{ "a.href = 'about'", null },
1182+
.{ "a.href", "https://lightpanda.io/opensource-browser/about" },
1183+
}, .{});
11771184
}

src/url.zig

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const Uri = std.Uri;
44
const Allocator = std.mem.Allocator;
55
const WebApiURL = @import("browser/url/url.zig").URL;
66

7+
pub const stitch = URL.stitch;
8+
79
pub const URL = struct {
810
uri: Uri,
911
raw: []const u8,
@@ -91,6 +93,7 @@ pub const URL = struct {
9193
if_needed,
9294
};
9395
};
96+
9497
/// Properly stitches two URL fragments together.
9598
///
9699
/// For URLs with a path, it will replace the last entry with the src.
@@ -101,7 +104,7 @@ pub const URL = struct {
101104
base: []const u8,
102105
opts: StitchOpts,
103106
) ![]const u8 {
104-
if (base.len == 0) {
107+
if (base.len == 0 or isURL(src)) {
105108
if (opts.alloc == .always) {
106109
return allocator.dupe(u8, src);
107110
}
@@ -154,7 +157,41 @@ pub const URL = struct {
154157
}
155158
};
156159

157-
test "Url resolve size" {
160+
fn isURL(url: []const u8) bool {
161+
if (std.mem.startsWith(u8, url, "://")) {
162+
return true;
163+
}
164+
165+
if (url.len < 8) {
166+
return false;
167+
}
168+
169+
if (!std.ascii.startsWithIgnoreCase(url, "http")) {
170+
return false;
171+
}
172+
173+
var pos: usize = 4;
174+
if (url[4] == 's' or url[4] == 'S') {
175+
pos = 5;
176+
}
177+
return std.mem.startsWith(u8, url[pos..], "://");
178+
}
179+
180+
const testing = @import("testing.zig");
181+
test "URL: isURL" {
182+
try testing.expectEqual(true, isURL("://lightpanda.io"));
183+
try testing.expectEqual(true, isURL("://lightpanda.io/about"));
184+
try testing.expectEqual(true, isURL("http://lightpanda.io/about"));
185+
try testing.expectEqual(true, isURL("HttP://lightpanda.io/about"));
186+
try testing.expectEqual(true, isURL("httpS://lightpanda.io/about"));
187+
try testing.expectEqual(true, isURL("HTTPs://lightpanda.io/about"));
188+
189+
try testing.expectEqual(false, isURL("/lightpanda.io"));
190+
try testing.expectEqual(false, isURL("../../about"));
191+
try testing.expectEqual(false, isURL("about"));
192+
}
193+
194+
test "URL: resolve size" {
158195
const base = "https://www.lightpande.io";
159196
const url = try URL.parse(base, null);
160197

@@ -170,8 +207,6 @@ test "Url resolve size" {
170207
try std.testing.expectEqualStrings(out_url.raw[26..], &url_string);
171208
}
172209

173-
const testing = @import("testing.zig");
174-
175210
test "URL: Stitching Base & Src URLs (Basic)" {
176211
const allocator = testing.allocator;
177212

@@ -212,6 +247,15 @@ test "URL: Stiching Base & Src URLs (Both Local)" {
212247
try testing.expectString("./abcdef/something.js", result);
213248
}
214249

250+
test "URL: Stiching src as full path" {
251+
const allocator = testing.allocator;
252+
253+
const base = "https://www.lightpanda.io/";
254+
const src = "https://lightpanda.io/something.js";
255+
const result = try URL.stitch(allocator, src, base, .{});
256+
try testing.expectString("https://lightpanda.io/something.js", result);
257+
}
258+
215259
test "URL: concatQueryString" {
216260
defer testing.reset();
217261
const arena = testing.arena_allocator;

0 commit comments

Comments
 (0)