From 58215a470b5cf252a30aff697c9666e9b0ecaa60 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Wed, 28 May 2025 16:24:28 +0800 Subject: [PATCH] Implement location.reload(), location.assign() and location setter I'm not sure that _any_ location instance should be able to change the page URL. But you can't create a new location (i.e. new Location() isn't valid), and the only two ways I know of are via `window.location` and `document.location` both of which _should_ alter the location of the window/document. --- src/browser/html/document.zig | 4 ++++ src/browser/html/location.zig | 15 +++++++------- src/browser/html/window.zig | 4 ++++ src/browser/page.zig | 37 ++++++++++++++++++----------------- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/browser/html/document.zig b/src/browser/html/document.zig index f0b533d4..d5432036 100644 --- a/src/browser/html/document.zig +++ b/src/browser/html/document.zig @@ -174,6 +174,10 @@ pub const HTMLDocument = struct { return try parser.documentHTMLGetLocation(Location, self); } + pub fn set_location(_: *const parser.DocumentHTML, url: []const u8, page: *Page) !void { + return page.navigateFromWebAPI(url); + } + pub fn get_designMode(_: *parser.DocumentHTML) []const u8 { return "off"; } diff --git a/src/browser/html/location.zig b/src/browser/html/location.zig index 604b9622..5fd2fb9f 100644 --- a/src/browser/html/location.zig +++ b/src/browser/html/location.zig @@ -69,18 +69,17 @@ pub const Location = struct { return ""; } - // TODO - pub fn _assign(_: *Location, url: []const u8) !void { - _ = url; + pub fn _assign(_: *const Location, url: []const u8, page: *Page) !void { + return page.navigateFromWebAPI(url); } - // TODO - pub fn _replace(_: *Location, url: []const u8) !void { - _ = url; + pub fn _replace(_: *const Location, url: []const u8, page: *Page) !void { + return page.navigateFromWebAPI(url); } - // TODO - pub fn _reload(_: *Location) !void {} + pub fn _reload(_: *const Location, page: *Page) !void { + return page.navigateFromWebAPI(page.url.raw); + } pub fn _toString(self: *Location, page: *Page) ![]const u8 { return try self.get_href(page); diff --git a/src/browser/html/window.zig b/src/browser/html/window.zig index b58f7cbc..c0c2b98f 100644 --- a/src/browser/html/window.zig +++ b/src/browser/html/window.zig @@ -100,6 +100,10 @@ pub const Window = struct { return &self.location; } + pub fn set_location(_: *const Window, url: []const u8, page: *Page) !void { + return page.navigateFromWebAPI(url); + } + pub fn get_console(self: *Window) *Console { return &self.console; } diff --git a/src/browser/page.zig b/src/browser/page.zig index 54639b0d..2a18777c 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -541,23 +541,25 @@ pub const Page = struct { .a => { const element: *parser.Element = @ptrCast(node); const href = (try parser.elementGetAttribute(element, "href")) orelse return; - - // We cannot navigate immediately as navigating will delete the DOM tree, which holds this event's node. - // As such we schedule the function to be called as soon as possible. - // NOTE Using the page.arena assumes that the scheduling loop does use this object after invoking the callback - // If that changes we may want to consider storing DelayedNavigation in the session instead. - const arena = self.arena; - const navi = try arena.create(DelayedNavigation); - navi.* = .{ - .session = self.session, - .href = try arena.dupe(u8, href), - }; - _ = try self.loop.timeout(0, &navi.navigate_node); + try self.navigateFromWebAPI(href); }, else => {}, } } + // As such we schedule the function to be called as soon as possible. + // The page.arena is safe to use here, but the transfer_arena exists + // specifically for this type of lifetime. + pub fn navigateFromWebAPI(self: *Page, url: []const u8) !void { + const arena = self.session.transfer_arena; + const navi = try arena.create(DelayedNavigation); + navi.* = .{ + .session = self.session, + .url = try arena.dupe(u8, url), + }; + _ = try self.loop.timeout(0, &navi.navigate_node); + } + pub fn getOrCreateNodeWrapper(self: *Page, comptime T: type, node: *parser.Node) !*T { if (try self.getNodeWrapper(T, node)) |wrap| { return wrap; @@ -579,16 +581,15 @@ pub const Page = struct { }; const DelayedNavigation = struct { - navigate_node: Loop.CallbackNode = .{ .func = DelayedNavigation.delay_navigate }, + url: []const u8, session: *Session, - href: []const u8, + navigate_node: Loop.CallbackNode = .{ .func = delayNavigate }, - fn delay_navigate(node: *Loop.CallbackNode, repeat_delay: *?u63) void { + fn delayNavigate(node: *Loop.CallbackNode, repeat_delay: *?u63) void { _ = repeat_delay; const self: *DelayedNavigation = @fieldParentPtr("navigate_node", node); - self.session.pageNavigate(self.href) catch |err| { - // TODO: should we trigger a specific event here? - log.err(.page, "delayed navigation error", .{ .err = err }); + self.session.pageNavigate(self.url) catch |err| { + log.err(.page, "delayed navigation error", .{ .err = err, .url = self.url }); }; } };