diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 32b8c232..fd42b083 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -142,6 +142,11 @@ _queued_navigation: ?QueuedNavigation = null, // The URL of the current page url: [:0]const u8, +// The base url specifies the base URL used to resolve the relative urls. +// It is set by a tag. +// If null the url must be used. +base_url: ?[:0]const u8, + // Arbitrary buffer. Need to temporarily lowercase a value? Use this. No lifetime // guarantee - it's valid until someone else uses it. buf: [BUF_SIZE]u8, @@ -220,6 +225,7 @@ fn reset(self: *Page, comptime initializing: bool) !void { self.version = 0; self.url = "about:blank"; + self.base_url = null; self.document = (try self._factory.document(Node.Document.HTMLDocument{ ._proto = undefined })).asDocument(); @@ -274,6 +280,10 @@ fn reset(self: *Page, comptime initializing: bool) !void { try self.registerBackgroundTasks(); } +pub fn base(self: *const Page) [:0]const u8 { + return self.base_url orelse self.url; +} + fn registerBackgroundTasks(self: *Page) !void { if (comptime builtin.is_test) { // HTML test runner manually calls these as necessary @@ -424,7 +434,7 @@ pub fn scheduleNavigation(self: *Page, request_url: []const u8, opts: NavigateOp const resolved_url = try URL.resolve( session.transfer_arena, - self.url, + self.base(), request_url, .{ .always_dupe = true }, ); @@ -1421,6 +1431,24 @@ pub fn createElement(self: *Page, ns_: ?[]const u8, name: []const u8, attribute_ attribute_iterator, .{ ._proto = undefined }, ), + asUint("base") => { + const n = try self.createHtmlElementT( + Element.Html.Generic, + namespace, + attribute_iterator, + .{ ._proto = undefined, ._tag_name = String.init(undefined, "base", .{}) catch unreachable, ._tag = .base }, + ); + + // If page's base url is not already set, fill it with the base + // tag. + if (self.base_url == null) { + if (n.as(Element).getAttributeSafe("href")) |href| { + self.base_url = try URL.resolve(self.arena, self.url, href, .{}); + } + } + + return n; + }, else => {}, }, 5 => switch (@as(u40, @bitCast(name[0..5].*))) { diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index 1cebeeb5..5f55952f 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -190,11 +190,12 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e const page = self.page; var source: Script.Source = undefined; var remote_url: ?[:0]const u8 = null; + const base_url = page.base(); if (element.getAttributeSafe("src")) |src| { if (try parseDataURI(page.arena, src)) |data_uri| { source = .{ .@"inline" = data_uri }; } else { - remote_url = try URL.resolve(page.arena, page.url, src, .{}); + remote_url = try URL.resolve(page.arena, base_url, src, .{}); source = .{ .remote = .{} }; } } else { @@ -215,7 +216,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e .script_element = script_element, .complete = is_inline, .status = if (is_inline) 200 else 0, - .url = remote_url orelse page.url, + .url = remote_url orelse base_url, .mode = blk: { if (source == .@"inline") { break :blk if (kind == .module) .@"defer" else .normal; @@ -568,7 +569,7 @@ fn parseImportmap(self: *ScriptManager, script: *const Script) !void { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#importing_modules_using_import_maps const resolved_url = try URL.resolve( self.page.arena, - self.page.url, + self.page.base(), entry.value_ptr.*, .{}, ); diff --git a/src/browser/dump.zig b/src/browser/dump.zig index 77a75c88..bc4d5431 100644 --- a/src/browser/dump.zig +++ b/src/browser/dump.zig @@ -55,7 +55,7 @@ pub fn root(doc: *Node.Document, opts: RootOpts, writer: *std.Io.Writer, page: * if (opts.with_base) { const parent = if (html_doc.getHead()) |head| head.asNode() else doc.asNode(); const base = try doc.createElement("base", null, page); - try base.setAttributeSafe("base", page.url, page); + try base.setAttributeSafe("base", page.base(), page); _ = try parent.insertBefore(base.asNode(), parent.firstChild(), page); } } diff --git a/src/browser/tests/legacy/dom/node.html b/src/browser/tests/legacy/dom/node.html index ae9b8a3e..c391022a 100644 --- a/src/browser/tests/legacy/dom/node.html +++ b/src/browser/tests/legacy/dom/node.html @@ -218,7 +218,7 @@ let first_child = content.firstChild.nextSibling; // nextSibling because of line diff --git a/src/browser/tests/node/base_uri.html b/src/browser/tests/node/base_uri.html new file mode 100644 index 00000000..122d7d13 --- /dev/null +++ b/src/browser/tests/node/base_uri.html @@ -0,0 +1,14 @@ + + + + +foo + + + diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index fe5effac..a3a542a8 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -1178,6 +1178,7 @@ pub const Tag = enum { body, br, button, + base, canvas, circle, custom, diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index 41357bcc..1149ef44 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -874,6 +874,11 @@ pub const JsApi = struct { fn _toString(self: *const Node) []const u8 { return self.className(); } + + fn _baseURI(_: *Node, page: *const Page) []const u8 { + return page.base(); + } + pub const baseURI = bridge.accessor(_baseURI, null, .{}); }; pub const Build = struct { diff --git a/src/browser/webapi/element/html/Anchor.zig b/src/browser/webapi/element/html/Anchor.zig index 75e61c20..44e24823 100644 --- a/src/browser/webapi/element/html/Anchor.zig +++ b/src/browser/webapi/element/html/Anchor.zig @@ -44,7 +44,7 @@ pub fn getHref(self: *Anchor, page: *Page) ![]const u8 { if (href.len == 0) { return ""; } - return URL.resolve(page.call_arena, page.url, href, .{}); + return URL.resolve(page.call_arena, page.base(), href, .{}); } pub fn setHref(self: *Anchor, value: []const u8, page: *Page) !void { @@ -195,7 +195,7 @@ fn getResolvedHref(self: *Anchor, page: *Page) !?[:0]const u8 { if (href.len == 0) { return null; } - return try URL.resolve(page.call_arena, page.url, href, .{}); + return try URL.resolve(page.call_arena, page.base(), href, .{}); } pub const JsApi = struct { diff --git a/src/browser/webapi/element/html/Image.zig b/src/browser/webapi/element/html/Image.zig index affaaba0..aa6cdd04 100644 --- a/src/browser/webapi/element/html/Image.zig +++ b/src/browser/webapi/element/html/Image.zig @@ -42,7 +42,7 @@ pub fn getSrc(self: *const Image, page: *Page) ![]const u8 { } // Always resolve the src against the page URL - return URL.resolve(page.call_arena, page.url, src, .{}); + return URL.resolve(page.call_arena, page.base(), src, .{}); } pub fn setSrc(self: *Image, value: []const u8, page: *Page) !void { diff --git a/src/browser/webapi/element/html/Link.zig b/src/browser/webapi/element/html/Link.zig index e381e227..f8607ca8 100644 --- a/src/browser/webapi/element/html/Link.zig +++ b/src/browser/webapi/element/html/Link.zig @@ -42,7 +42,7 @@ pub fn getHref(self: *Link, page: *Page) ![]const u8 { } // Always resolve the href against the page URL - return URL.resolve(page.call_arena, page.url, href, .{}); + return URL.resolve(page.call_arena, page.base(), href, .{}); } pub fn setHref(self: *Link, value: []const u8, page: *Page) !void { diff --git a/src/browser/webapi/element/html/Media.zig b/src/browser/webapi/element/html/Media.zig index dc29e160..eca130d0 100644 --- a/src/browser/webapi/element/html/Media.zig +++ b/src/browser/webapi/element/html/Media.zig @@ -224,7 +224,7 @@ pub fn getSrc(self: *const Media, page: *Page) ![]const u8 { return ""; } const URL = @import("../../URL.zig"); - return URL.resolve(page.call_arena, page.url, src, .{}); + return URL.resolve(page.call_arena, page.base(), src, .{}); } pub fn setSrc(self: *Media, value: []const u8, page: *Page) !void { diff --git a/src/browser/webapi/element/html/Video.zig b/src/browser/webapi/element/html/Video.zig index cfe19da0..dda22eb2 100644 --- a/src/browser/webapi/element/html/Video.zig +++ b/src/browser/webapi/element/html/Video.zig @@ -59,7 +59,7 @@ pub fn getPoster(self: *const Video, page: *Page) ![]const u8 { } const URL = @import("../../URL.zig"); - return URL.resolve(page.call_arena, page.url, poster, .{}); + return URL.resolve(page.call_arena, page.base(), poster, .{}); } pub fn setPoster(self: *Video, value: []const u8, page: *Page) !void { diff --git a/src/browser/webapi/navigation/NavigationHistoryEntry.zig b/src/browser/webapi/navigation/NavigationHistoryEntry.zig index 2411a741..5e1a98a8 100644 --- a/src/browser/webapi/navigation/NavigationHistoryEntry.zig +++ b/src/browser/webapi/navigation/NavigationHistoryEntry.zig @@ -69,7 +69,7 @@ pub fn key(self: *const NavigationHistoryEntry) []const u8 { pub fn sameDocument(self: *const NavigationHistoryEntry, page: *Page) bool { const got_url = self._url orelse return false; - return URL.eqlDocument(got_url, page.url); + return URL.eqlDocument(got_url, page.base()); } pub fn url(self: *const NavigationHistoryEntry) ?[:0]const u8 { diff --git a/src/browser/webapi/net/Request.zig b/src/browser/webapi/net/Request.zig index 13c7a977..46cad2a1 100644 --- a/src/browser/webapi/net/Request.zig +++ b/src/browser/webapi/net/Request.zig @@ -69,7 +69,7 @@ const Cache = enum { pub fn init(input: Input, opts_: ?InitOpts, page: *Page) !*Request { const arena = page.arena; const url = switch (input) { - .url => |u| try URL.resolve(arena, page.url, u, .{ .always_dupe = true }), + .url => |u| try URL.resolve(arena, page.base(), u, .{ .always_dupe = true }), .request => |r| try arena.dupeZ(u8, r._url), }; diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index c0837680..a29476a6 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -130,7 +130,7 @@ pub fn open(self: *XMLHttpRequest, method_: []const u8, url: [:0]const u8) !void self._request_body = null; self._method = try parseMethod(method_); - self._url = try URL.resolve(self._arena, self._page.url, url, .{ .always_dupe = true }); + self._url = try URL.resolve(self._arena, self._page.base(), url, .{ .always_dupe = true }); try self.stateChanged(.opened, self._page); }