From bfe2065b9f93b180343d8b9e79608eb5dd7dec66 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 6 Mar 2026 16:54:16 +0800 Subject: [PATCH] Add target-aware(ish) navigation All inner navigations have an originator and a target. Consider this: ```js aframe.contentDocument.querySelector('#link').click(); ``` The originator is the context in which this JavaScript is called, the target is `aframe. Importantly, relative URLs are resolved based on the originator. This commit adds that. This is only a first step, there are other aspect to this relationship that isn't addressed yet, like differences in behavior if the originator and target are on different origins, and specific target targetting via the things like the "target" attribute. What this commit does though is address the normal / common case. It builds on top of https://github.com/lightpanda-io/browser/pull/1720 --- src/browser/Page.zig | 119 +++++++++++------- src/browser/Session.zig | 45 +++---- src/browser/js/Context.zig | 2 +- src/browser/tests/frames/frames.html | 43 +++---- .../tests/frames/support/with_link.html | 2 +- src/browser/webapi/HTMLDocument.zig | 4 +- src/browser/webapi/Location.zig | 8 +- src/browser/webapi/Window.zig | 4 +- src/browser/webapi/element/html/IFrame.zig | 6 +- src/browser/webapi/navigation/Navigation.zig | 8 +- 10 files changed, 129 insertions(+), 112 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index a8d063b4..7c6bcac4 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -69,6 +69,7 @@ const ArenaPool = App.ArenaPool; const timestamp = @import("../datetime.zig").timestamp; const milliTimestamp = @import("../datetime.zig").milliTimestamp; +const IFrame = Element.Html.IFrame; const WebApiURL = @import("webapi/URL.zig"); const GlobalEventHandlersLookup = @import("webapi/global_event_handlers.zig").Lookup; @@ -223,7 +224,7 @@ _arena_pool_leak_track: (if (IS_DEBUG) std.AutoHashMapUnmanaged(usize, struct { parent: ?*Page, window: *Window, document: *Document, -iframe: ?*Element.Html.IFrame = null, +iframe: ?*IFrame = null, frames: std.ArrayList(*Page) = .{}, frames_sorted: bool = true, @@ -566,62 +567,76 @@ pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts) !voi }; } -// 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. -pub fn scheduleNavigation(self: *Page, request_url: []const u8, opts: NavigateOpts, priority: NavigationPriority) !void { - if (self.canScheduleNavigation(priority) == false) { +// Navigation can happen in many places, such as executing a - - - + diff --git a/src/browser/tests/frames/support/with_link.html b/src/browser/tests/frames/support/with_link.html index 2fd9ca77..bc31b190 100644 --- a/src/browser/tests/frames/support/with_link.html +++ b/src/browser/tests/frames/support/with_link.html @@ -1,2 +1,2 @@ -a link +a link diff --git a/src/browser/webapi/HTMLDocument.zig b/src/browser/webapi/HTMLDocument.zig index 9efde318..15ba610b 100644 --- a/src/browser/webapi/HTMLDocument.zig +++ b/src/browser/webapi/HTMLDocument.zig @@ -180,8 +180,8 @@ pub fn getLocation(self: *const HTMLDocument) ?*@import("Location.zig") { return self._proto._location; } -pub fn setLocation(_: *const HTMLDocument, url: [:0]const u8, page: *Page) !void { - return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .script); +pub fn setLocation(self: *HTMLDocument, url: [:0]const u8, page: *Page) !void { + return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._proto._page }); } pub fn getAll(self: *HTMLDocument, page: *Page) !*collections.HTMLAllCollection { diff --git a/src/browser/webapi/Location.zig b/src/browser/webapi/Location.zig index dbda1e51..9055abbb 100644 --- a/src/browser/webapi/Location.zig +++ b/src/browser/webapi/Location.zig @@ -83,19 +83,19 @@ pub fn setHash(_: *const Location, hash: []const u8, page: *Page) !void { return page.scheduleNavigation(normalized_hash, .{ .reason = .script, .kind = .{ .replace = null }, - }, .script); + }, .{ .script = page }); } pub fn assign(_: *const Location, url: [:0]const u8, page: *Page) !void { - return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .script); + return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = page }); } pub fn replace(_: *const Location, url: [:0]const u8, page: *Page) !void { - return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .replace = null } }, .script); + return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .replace = null } }, .{ .script = page }); } pub fn reload(_: *const Location, page: *Page) !void { - return page.scheduleNavigation(page.url, .{ .reason = .script, .kind = .reload }, .script); + return page.scheduleNavigation(page.url, .{ .reason = .script, .kind = .reload }, .{ .script = page }); } pub fn toString(self: *const Location, page: *const Page) ![:0]const u8 { diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 91f55b60..2c3e3cd0 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -160,8 +160,8 @@ pub fn getSelection(self: *const Window) *Selection { return &self._document._selection; } -pub fn setLocation(_: *const Window, url: [:0]const u8, page: *Page) !void { - return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .script); +pub fn setLocation(self: *Window, url: [:0]const u8, page: *Page) !void { + return page.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._page }); } pub fn getHistory(_: *Window, page: *Page) *History { diff --git a/src/browser/webapi/element/html/IFrame.zig b/src/browser/webapi/element/html/IFrame.zig index e9cffc6e..d912dd41 100644 --- a/src/browser/webapi/element/html/IFrame.zig +++ b/src/browser/webapi/element/html/IFrame.zig @@ -30,7 +30,7 @@ const IFrame = @This(); _proto: *HtmlElement, _src: []const u8 = "", _executed: bool = false, -_content_window: ?*Window = null, +_window: ?*Window = null, pub fn asElement(self: *IFrame) *Element { return self._proto._proto; @@ -40,11 +40,11 @@ pub fn asNode(self: *IFrame) *Node { } pub fn getContentWindow(self: *const IFrame) ?*Window { - return self._content_window; + return self._window; } pub fn getContentDocument(self: *const IFrame) ?*Document { - const window = self._content_window orelse return null; + const window = self._window orelse return null; return window._document; } diff --git a/src/browser/webapi/navigation/Navigation.zig b/src/browser/webapi/navigation/Navigation.zig index e81ff46e..447f5e00 100644 --- a/src/browser/webapi/navigation/Navigation.zig +++ b/src/browser/webapi/navigation/Navigation.zig @@ -308,7 +308,7 @@ pub fn navigateInner( _ = try self.pushEntry(url, .{ .source = .navigation, .value = state }, page, true); } else { - try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script); + try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .{ .script = page }); } }, .replace => |state| { @@ -321,7 +321,7 @@ pub fn navigateInner( _ = try self.replaceEntry(url, .{ .source = .navigation, .value = state }, page, true); } else { - try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script); + try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .{ .script = page }); } }, .traverse => |index| { @@ -334,11 +334,11 @@ pub fn navigateInner( // todo: Fire navigate event finished.resolve("navigation traverse", {}); } else { - try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script); + try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .{ .script = page }); } }, .reload => { - try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .script); + try page.scheduleNavigation(url, .{ .reason = .navigation, .kind = kind }, .{ .script = page }); }, }