From e33018f40ef2300f4094eb38935ad85efe06a01d Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 28 Apr 2026 21:44:22 +0200 Subject: [PATCH] discardPendingPage on Session.initiateRootNavigation --- src/browser/Frame.zig | 5 +++-- src/browser/HttpClient.zig | 35 +++++++++++++++++++++++------------ src/browser/Session.zig | 11 +++++++++-- src/browser/webapi/Worker.zig | 3 ++- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index ac065dfd..3998117d 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -413,7 +413,8 @@ pub fn deinit(self: *Frame) void { self._script_manager.base.shutdown = true; - browser.http_client.abortFrame(self._frame_id); + // don't abort pending frames. + browser.http_client.abortFrame(self._frame_id, .{}); self._script_manager.deinit(); self._style_manager.deinit(); @@ -758,7 +759,7 @@ fn scheduleNavigationWithArena(originator: *Frame, arena: Allocator, request_url .type = target._type, }); - session.browser.http_client.abortFrame(target._frame_id); + session.browser.http_client.abortFrame(target._frame_id, .{}); // Capture the originating frame's URL as the Referer for this // navigation. The originator's frame may be torn down before navigate() diff --git a/src/browser/HttpClient.zig b/src/browser/HttpClient.zig index 28acecd2..a68134ed 100644 --- a/src/browser/HttpClient.zig +++ b/src/browser/HttpClient.zig @@ -304,19 +304,25 @@ pub fn getUserAgent(self: *const Client) [:0]const u8 { return self.user_agent_override orelse self.network.config.http_headers.user_agent; } +const AbortOpts = struct { + scope: enum { normal, full } = .normal, +}; + pub fn abort(self: *Client) void { - self._abort(true, 0); + self._abort(true, 0, .{ .scope = .full }); } -pub fn abortFrame(self: *Client, frame_id: u32) void { - self._abort(false, frame_id); +// abortFrame with .normal doesn't abort protect_from_abort requests. +// .full abort all relqtive requests. +pub fn abortFrame(self: *Client, frame_id: u32, opts: AbortOpts) void { + self._abort(false, frame_id, opts); } // Written this way so that both abort and abortFrame can share the same code // but abort can avoid the frame_id check at comptime. -fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void { - abortConnections(self.in_use, abort_all, frame_id); - abortConnections(self.ready_queue, abort_all, frame_id); +fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32, opts: AbortOpts) void { + abortConnections(self.in_use, abort_all, frame_id, opts); + abortConnections(self.ready_queue, abort_all, frame_id, opts); { var q = &self.queue; @@ -327,9 +333,11 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void { const params = transfer.req.params; if (comptime abort_all) { transfer.kill(); - } else if (params.frame_id == frame_id and !params.protect_from_abort) { - q.remove(node); - transfer.kill(); + } else if (params.frame_id == frame_id) { + if (opts.scope == .full or !params.protect_from_abort) { + q.remove(node); + transfer.kill(); + } } } } @@ -355,7 +363,7 @@ fn _abort(self: *Client, comptime abort_all: bool, frame_id: u32) void { } } -fn abortConnections(list: std.DoublyLinkedList, comptime abort_all: bool, frame_id: u32) void { +fn abortConnections(list: std.DoublyLinkedList, comptime abort_all: bool, frame_id: u32, opts: AbortOpts) void { var n = list.first; while (n) |node| { n = node.next; @@ -363,9 +371,12 @@ fn abortConnections(list: std.DoublyLinkedList, comptime abort_all: bool, frame_ switch (conn.transport) { .http => |transfer| { const params = transfer.req.params; - const matches = (comptime abort_all) or (params.frame_id == frame_id and !params.protect_from_abort); - if (matches) { + if (comptime abort_all) { transfer.kill(); + } else if (params.frame_id == frame_id) { + if (opts.scope == .full or !params.protect_from_abort) { + transfer.kill(); + } } }, .websocket => |ws| { diff --git a/src/browser/Session.zig b/src/browser/Session.zig index 052648c0..1c97cc9d 100644 --- a/src/browser/Session.zig +++ b/src/browser/Session.zig @@ -534,7 +534,9 @@ fn replaceRootImmediate(self: *Session, frame_id: u32, qn: *QueuedNavigation) !v // trip — Runtime.evaluate, DOM.*, etc. continue to operate on the OLD page // until commitPendingPage swaps the pointer when response headers arrive. pub fn initiateRootNavigation(self: *Session, frame_id: u32, url: [:0]const u8, opts: Frame.NavigateOpts) !void { - lp.assert(self._pending_idx == null, "Session.initiateRootNavigation - pending already set", .{}); + if (self._pending_idx != null) { + self.discardPendingPage(); + } // Pick the slot NOT occupied by the active page. const slot = try self.findFreeSlot(); @@ -643,8 +645,13 @@ pub fn discardPendingPage(self: *Session) void { log.debug(.browser, "discard pending page", .{}); } + const pending_page = &self._pages[idx].?; + + // Force abort all inflight queries. + self.browser.http_client.abortFrame(pending_page.frame._frame_id, .{ .scope = .full }); + self._pending_idx = null; - self._pages[idx].?.deinit(); + pending_page.deinit(); self.freeSlot(idx); } diff --git a/src/browser/webapi/Worker.zig b/src/browser/webapi/Worker.zig index aed9ad16..56f40413 100644 --- a/src/browser/webapi/Worker.zig +++ b/src/browser/webapi/Worker.zig @@ -121,7 +121,8 @@ pub fn init(url: []const u8, exec: *Execution) !*Worker { // Called from Frame.deinit when the frame is destroyed, so we don't need to // remove from the frame's worker list. pub fn deinit(self: *Worker) void { - self._frame._session.browser.http_client.abortFrame(self._frame_id); + // No pending frame for workers, so we can abort all frames. + self._frame._session.browser.http_client.abortFrame(self._frame_id, .{ .scope = .full }); if (self._http_response) |res| { res.abort(error.Abort); self._http_response = null;