mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 09:35:59 -04:00
Replace active page on synthetic root navigation (about:blank, blob:)
Page.navigate("about:blank") (and blob:) issued against a non-blank tab
routed through Session.initiateRootNavigation, which always allocated a
pending Page. A pending Page is promoted to active only by
frameHeaderDoneCallback when HTTP response headers arrive — but synthetic
navigations ZIGFLAGS= make no HTTP request, so the pending Page was never committed
and the previous document stayed active (window.location.href, document.URL
and Page.getFrameTree all kept reporting the old page).
Route synthetic URLs through the existing immediate-swap path
(replaceRootImmediate) from initiateRootNavigation, mirroring what
processRootQueuedNavigation already does for JS-initiated synthetic
navigations. replaceRootImmediate now takes (frame_id, url, opts) so both
call sites share it.
Fixes #2363
This commit is contained in:
@@ -489,7 +489,8 @@ fn processRootQueuedNavigation(self: *Session) !void {
|
||||
const is_synthetic = qn.is_about_blank or std.mem.startsWith(u8, qn.url, "blob:");
|
||||
|
||||
if (is_synthetic) {
|
||||
return self.replaceRootImmediate(current_frame._frame_id, qn);
|
||||
defer self.arena_pool.release(qn.arena);
|
||||
return self.replaceRootImmediate(current_frame._frame_id, qn.url, qn.opts);
|
||||
}
|
||||
|
||||
// The qn arena is consumed here regardless of success — frame.navigate
|
||||
@@ -500,18 +501,18 @@ fn processRootQueuedNavigation(self: *Session) !void {
|
||||
return self.initiateRootNavigation(current_frame._frame_id, qn.url, qn.opts);
|
||||
}
|
||||
|
||||
// Legacy immediate-swap path: tear down the active page and create a new one
|
||||
// in its place before issuing the navigation. Used for synthetic navigations
|
||||
// (about:blank, blob:) where there is no in-flight HTTP and therefore no
|
||||
// "pending" window to span.
|
||||
fn replaceRootImmediate(self: *Session, frame_id: u32, qn: *QueuedNavigation) !void {
|
||||
defer self.arena_pool.release(qn.arena);
|
||||
|
||||
// Immediate-swap path for synthetic navigations (about:blank, blob:): there is
|
||||
// no in-flight HTTP and therefore no "pending" window to span — and no
|
||||
// frameHeaderDoneCallback to commit a pending Page. Tear down the active page
|
||||
// and create a new one in its place, then navigate it. Reached from both the
|
||||
// queued-navigation path (processRootQueuedNavigation) and the CDP entry point
|
||||
// (initiateRootNavigation); each caller owns any arena tied to `url`/`opts`.
|
||||
fn replaceRootImmediate(self: *Session, frame_id: u32, url: [:0]const u8, opts: Frame.NavigateOpts) !void {
|
||||
self.tearDownActivePage();
|
||||
const new_frame = try self.installNewActivePage(frame_id);
|
||||
|
||||
new_frame.navigate(qn.url, qn.opts) catch |err| {
|
||||
log.err(.browser, "queued navigation error", .{ .err = err });
|
||||
new_frame.navigate(url, opts) catch |err| {
|
||||
log.err(.browser, "synthetic navigation error", .{ .err = err, .url = url });
|
||||
return err;
|
||||
};
|
||||
}
|
||||
@@ -524,6 +525,14 @@ fn replaceRootImmediate(self: *Session, frame_id: u32, qn: *QueuedNavigation) !v
|
||||
pub fn initiateRootNavigation(self: *Session, frame_id: u32, url: [:0]const u8, opts: Frame.NavigateOpts) !void {
|
||||
self.discardPendingPage();
|
||||
|
||||
// Synthetic navigations (about:blank, blob:) have no HTTP round-trip and
|
||||
// therefore no frameHeaderDoneCallback to commit a pending Page. Swap the
|
||||
// active Page immediately instead of allocating a pending one that would
|
||||
// never be promoted, leaving the previous document in place (issue #2363).
|
||||
if (std.mem.eql(u8, "about:blank", url) or std.mem.startsWith(u8, url, "blob:")) {
|
||||
return self.replaceRootImmediate(frame_id, url, opts);
|
||||
}
|
||||
|
||||
const page = try self.allocatePage(frame_id);
|
||||
errdefer self.destroyPage(page);
|
||||
|
||||
|
||||
@@ -1309,6 +1309,42 @@ test "cdp.frame: navigate inherits original fragment across redirect" {
|
||||
}
|
||||
}
|
||||
|
||||
test "cdp.frame: navigate to about:blank replaces a non-blank document" {
|
||||
// Regression test for #2363. Page.navigate("about:blank") issued against a
|
||||
// tab that already holds a real document must replace the active document
|
||||
// with a fresh about:blank page — not leave the previous page in place.
|
||||
// A synthetic (no-HTTP) navigation has no response-headers callback to
|
||||
// commit a pending Page, so it must swap the active Page immediately.
|
||||
var ctx = try testing.context();
|
||||
defer ctx.deinit();
|
||||
|
||||
var bc = try ctx.loadBrowserContext(.{ .id = "BID-AB", .url = "hi.html", .target_id = "TID-AB-0000000".* });
|
||||
|
||||
// Precondition: the tab is on a non-blank document.
|
||||
{
|
||||
const frame = bc.session.currentFrame() orelse unreachable;
|
||||
try testing.expect(std.mem.endsWith(u8, frame.url, "/hi.html"));
|
||||
}
|
||||
|
||||
try ctx.processMessage(.{ .id = 70, .method = "Page.navigate", .params = .{ .url = "about:blank" } });
|
||||
{
|
||||
var runner = try bc.session.runner(.{});
|
||||
try runner.wait(.{ .ms = 2000 });
|
||||
}
|
||||
|
||||
// The active frame must now point at the replaced about:blank document.
|
||||
const frame = bc.session.currentFrame() orelse unreachable;
|
||||
try testing.expectEqualSlices(u8, "about:blank", frame.url);
|
||||
|
||||
// ...and the active page's JS context must agree — the exact symptom in the
|
||||
// bug report was window.location.href staying on the previous URL.
|
||||
var ls: js.Local.Scope = undefined;
|
||||
frame.js.localScope(&ls);
|
||||
defer ls.deinit();
|
||||
const v = try ls.local.exec("window.location.href === 'about:blank'", null);
|
||||
try testing.expect(v.toBool());
|
||||
}
|
||||
|
||||
test "cdp.frame: anchor click sends Referer matching the originating page" {
|
||||
// HTML Living Standard "navigate" algorithm + Fetch §4.5 "request's referrer":
|
||||
// when a navigation is initiated by a hyperlink click (or form submit, or
|
||||
|
||||
Reference in New Issue
Block a user