cdp: dispatch Page.navigate response after navigation

This commit is contained in:
Pierre Tachoire
2025-12-11 17:51:17 +01:00
parent 34518dfa98
commit e2682ab9fe
4 changed files with 66 additions and 33 deletions

View File

@@ -83,6 +83,7 @@ pub const Page = struct {
// indicates intention to navigate to another page on the next loop execution.
delayed_navigation: bool = false,
navigated_options: ?NavigatedOpts = null,
state_pool: *std.heap.MemoryPool(State),
@@ -574,6 +575,11 @@ pub const Page = struct {
});
self.session.browser.notification.dispatch(.page_navigated, &.{
.opts = .{
.cdp_id = opts.cdp_id,
.reason = opts.reason,
.method = opts.method,
},
.url = request_url,
.timestamp = timestamp(),
});
@@ -584,6 +590,12 @@ pub const Page = struct {
const owned_url = try self.arena.dupeZ(u8, request_url);
self.url = try URL.parse(owned_url, null);
self.navigated_options = .{
.cdp_id = opts.cdp_id,
.reason = opts.reason,
.method = opts.method,
};
var headers = try self.http_client.newHeaders();
if (opts.header) |hdr| try headers.add(hdr);
try self.requestCookie(.{ .is_navigation = true }).headersForRequest(self.arena, owned_url, &headers);
@@ -656,7 +668,9 @@ pub const Page = struct {
log.err(.browser, "document is complete", .{ .err = err });
};
std.debug.assert(self.navigated_options != null);
self.session.browser.notification.dispatch(.page_navigated, &.{
.opts = self.navigated_options.?,
.url = self.url.raw,
.timestamp = timestamp(),
});
@@ -1264,6 +1278,12 @@ pub const NavigateOpts = struct {
force: bool = false,
};
pub const NavigatedOpts = struct {
cdp_id: ?i64 = null,
reason: NavigateReason = .address_bar,
method: Http.Method = .GET,
};
const IdleNotification = union(enum) {
// hasn't started yet.
init,

View File

@@ -545,13 +545,13 @@ pub fn BrowserContext(comptime CDP_T: type) type {
pub fn onPageNavigate(ctx: *anyopaque, msg: *const Notification.PageNavigate) !void {
const self: *Self = @ptrCast(@alignCast(ctx));
defer self.resetNotificationArena();
return @import("domains/page.zig").pageNavigate(self.notification_arena, self, msg);
return @import("domains/page.zig").pageNavigate(self, msg);
}
pub fn onPageNavigated(ctx: *anyopaque, msg: *const Notification.PageNavigated) !void {
const self: *Self = @ptrCast(@alignCast(ctx));
return @import("domains/page.zig").pageNavigated(self, msg);
defer self.resetNotificationArena();
return @import("domains/page.zig").pageNavigated(self.notification_arena, self, msg);
}
pub fn onPageNetworkIdle(ctx: *anyopaque, msg: *const Notification.PageNetworkIdle) !void {

View File

@@ -184,7 +184,7 @@ fn navigate(cmd: anytype) !void {
});
}
pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.PageNavigate) !void {
pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
@@ -234,6 +234,30 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
try cdp.sendEvent("Page.frameStartedLoading", .{
.frameId = target_id,
}, .{ .session_id = session_id });
}
pub fn pageRemove(bc: anytype) !void {
// The main page is going to be removed, we need to remove contexts from other worlds first.
for (bc.isolated_worlds.items) |*isolated_world| {
try isolated_world.removeContext();
}
}
pub fn pageCreated(bc: anytype, page: *Page) !void {
for (bc.isolated_worlds.items) |*isolated_world| {
try isolated_world.createContextAndLoadPolyfills(bc.arena, page);
}
}
pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.PageNavigated) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
const loader_id = bc.loader_id;
const target_id = bc.target_id orelse unreachable;
const timestamp = event.timestamp;
var cdp = bc.cdp;
// Drivers are sensitive to the order of events. Some more than others.
// The result for the Page.navigate seems like it _must_ come after
@@ -260,6 +284,17 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
}, .{ .session_id = session_id });
}
const reason_: ?[]const u8 = switch (event.opts.reason) {
.anchor => "anchorClick",
.script, .history, .navigation => "scriptInitiated",
.form => switch (event.opts.method) {
.GET => "formSubmissionGet",
.POST => "formSubmissionPost",
else => unreachable,
},
.address_bar => null,
};
if (reason_ != null) {
try cdp.sendEvent("Page.frameClearedScheduledNavigation", .{
.frameId = target_id,
@@ -293,30 +328,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
false,
);
}
}
pub fn pageRemove(bc: anytype) !void {
// The main page is going to be removed, we need to remove contexts from other worlds first.
for (bc.isolated_worlds.items) |*isolated_world| {
try isolated_world.removeContext();
}
}
pub fn pageCreated(bc: anytype, page: *Page) !void {
for (bc.isolated_worlds.items) |*isolated_world| {
try isolated_world.createContextAndLoadPolyfills(bc.arena, page);
}
}
pub fn pageNavigated(bc: anytype, event: *const Notification.PageNavigated) !void {
// detachTarget could be called, in which case, we still have a page doing
// things, but no session.
const session_id = bc.session_id orelse return;
const loader_id = bc.loader_id;
const target_id = bc.target_id orelse unreachable;
const timestamp = event.timestamp;
var cdp = bc.cdp;
// frameNavigated event
try cdp.sendEvent("Page.frameNavigated", .{
.type = "Navigation",

View File

@@ -98,6 +98,7 @@ pub const Notification = struct {
pub const PageNavigated = struct {
timestamp: u32,
url: []const u8,
opts: page.NavigatedOpts,
};
pub const PageNetworkIdle = struct {
@@ -326,7 +327,7 @@ test "Notification" {
.url = undefined,
.opts = .{},
});
notifier.dispatch(.page_navigated, &.{ .timestamp = 6, .url = undefined });
notifier.dispatch(.page_navigated, &.{ .timestamp = 6, .url = undefined, .opts = .{} });
try testing.expectEqual(14, tc.page_navigate);
try testing.expectEqual(6, tc.page_navigated);
@@ -336,7 +337,7 @@ test "Notification" {
.url = undefined,
.opts = .{},
});
notifier.dispatch(.page_navigated, &.{ .timestamp = 100, .url = undefined });
notifier.dispatch(.page_navigated, &.{ .timestamp = 100, .url = undefined, .opts = .{} });
try testing.expectEqual(14, tc.page_navigate);
try testing.expectEqual(6, tc.page_navigated);
@@ -345,26 +346,26 @@ test "Notification" {
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(1006, tc.page_navigated);
notifier.unregister(.page_navigate, &tc);
notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated);
notifier.unregister(.page_navigated, &tc);
notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated);
// already unregistered, try anyways
notifier.unregister(.page_navigated, &tc);
notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated);
}