Merge pull request #1963 from lightpanda-io/nested_navigation

Use double-queue to better support recursive navigation
This commit is contained in:
Karl Seguin
2026-03-23 18:13:18 +08:00
committed by GitHub
2 changed files with 30 additions and 16 deletions

View File

@@ -77,7 +77,15 @@ arena_pool: *ArenaPool,
page: ?Page, page: ?Page,
queued_navigation: std.ArrayList(*Page), // Double buffer so that, as we process one list of queued navigations, new entries
// are added to the separate buffer. This ensures that we don't end up with
// endless navigation loops AND that we don't invalidate the list while iterating
// if a new entry gets appended
queued_navigation_1: std.ArrayList(*Page),
queued_navigation_2: std.ArrayList(*Page),
// pointer to either queued_navigation_1 or queued_navigation_2
queued_navigation: *std.ArrayList(*Page),
// Temporary buffer for about:blank navigations during processing. // Temporary buffer for about:blank navigations during processing.
// We process async navigations first (safe from re-entrance), then sync // We process async navigations first (safe from re-entrance), then sync
// about:blank navigations (which may add to queued_navigation). // about:blank navigations (which may add to queued_navigation).
@@ -107,11 +115,14 @@ pub fn init(self: *Session, browser: *Browser, notification: *Notification) !voi
.navigation = .{ ._proto = undefined }, .navigation = .{ ._proto = undefined },
.storage_shed = .{}, .storage_shed = .{},
.browser = browser, .browser = browser,
.queued_navigation = .{}, .queued_navigation = undefined,
.queued_navigation_1 = .{},
.queued_navigation_2 = .{},
.queued_queued_navigation = .{}, .queued_queued_navigation = .{},
.notification = notification, .notification = notification,
.cookie_jar = storage.Cookie.Jar.init(allocator), .cookie_jar = storage.Cookie.Jar.init(allocator),
}; };
self.queued_navigation = &self.queued_navigation_1;
} }
pub fn deinit(self: *Session) void { pub fn deinit(self: *Session) void {
@@ -284,7 +295,7 @@ pub fn runner(self: *Session, opts: Runner.Opts) !Runner {
} }
pub fn scheduleNavigation(self: *Session, page: *Page) !void { pub fn scheduleNavigation(self: *Session, page: *Page) !void {
const list = &self.queued_navigation; const list = self.queued_navigation;
// Check if page is already queued // Check if page is already queued
for (list.items) |existing| { for (list.items) |existing| {
@@ -298,7 +309,12 @@ pub fn scheduleNavigation(self: *Session, page: *Page) !void {
} }
pub fn processQueuedNavigation(self: *Session) !void { pub fn processQueuedNavigation(self: *Session) !void {
const navigations = &self.queued_navigation; const navigations = self.queued_navigation;
if (self.queued_navigation == &self.queued_navigation_1) {
self.queued_navigation = &self.queued_navigation_2;
} else {
self.queued_navigation = &self.queued_navigation_1;
}
if (self.page.?._queued_navigation != null) { if (self.page.?._queued_navigation != null) {
// This is both an optimization and a simplification of sorts. If the // This is both an optimization and a simplification of sorts. If the
@@ -314,7 +330,6 @@ pub fn processQueuedNavigation(self: *Session) !void {
defer about_blank_queue.clearRetainingCapacity(); defer about_blank_queue.clearRetainingCapacity();
// First pass: process async navigations (non-about:blank) // First pass: process async navigations (non-about:blank)
// These cannot cause re-entrant navigation scheduling
for (navigations.items) |page| { for (navigations.items) |page| {
const qn = page._queued_navigation.?; const qn = page._queued_navigation.?;
@@ -329,7 +344,6 @@ pub fn processQueuedNavigation(self: *Session) !void {
}; };
} }
// Clear the queue after first pass
navigations.clearRetainingCapacity(); navigations.clearRetainingCapacity();
// Second pass: process synchronous navigations (about:blank) // Second pass: process synchronous navigations (about:blank)
@@ -339,15 +353,17 @@ pub fn processQueuedNavigation(self: *Session) !void {
try self.processFrameNavigation(page, qn); try self.processFrameNavigation(page, qn);
} }
// Safety: Remove any about:blank navigations that were queued during the // Safety: Remove any about:blank navigations that were queued during
// second pass to prevent infinite loops // processing to prevent infinite loops. New navigations have been queued
// in the other buffer.
const new_navigations = self.queued_navigation;
var i: usize = 0; var i: usize = 0;
while (i < navigations.items.len) { while (i < new_navigations.items.len) {
const page = navigations.items[i]; const page = new_navigations.items[i];
if (page._queued_navigation) |qn| { if (page._queued_navigation) |qn| {
if (qn.is_about_blank) { if (qn.is_about_blank) {
log.warn(.page, "recursive about blank", .{}); log.warn(.page, "recursive about blank", .{});
_ = navigations.swapRemove(i); _ = self.queued_navigation.swapRemove(i);
continue; continue;
} }
} }

View File

@@ -118,8 +118,6 @@
} }
</script> </script>
<!--
This test is flaky. Temporarily disabled until Runner can handle more advanced cases.
<script id=link_click> <script id=link_click>
testing.async(async (restore) => { testing.async(async (restore) => {
await new Promise((resolve) => { await new Promise((resolve) => {
@@ -139,9 +137,9 @@ This test is flaky. Temporarily disabled until Runner can handle more advanced c
restore(); restore();
testing.expectEqual("<html><head></head><body>It was clicked!\n</body></html>", f6.contentDocument.documentElement.outerHTML); testing.expectEqual("<html><head></head><body>It was clicked!\n</body></html>", f6.contentDocument.documentElement.outerHTML);
}); });
</script> --> </script>
<script id=count> <script id=about_blank_nav>
{ {
let i = document.createElement('iframe'); let i = document.createElement('iframe');
document.documentElement.appendChild(i); document.documentElement.appendChild(i);