cdp: use same value for requestId and loaderId

For all events regarding an HTTP request, the values of requestId
and loaderId must be the same.
This commit is contained in:
Pierre Tachoire
2025-12-12 17:04:18 +01:00
parent e2682ab9fe
commit 26827efe34
6 changed files with 55 additions and 23 deletions

View File

@@ -83,6 +83,7 @@ pub const Page = struct {
// indicates intention to navigate to another page on the next loop execution. // indicates intention to navigate to another page on the next loop execution.
delayed_navigation: bool = false, delayed_navigation: bool = false,
req_id: ?usize = null,
navigated_options: ?NavigatedOpts = null, navigated_options: ?NavigatedOpts = null,
state_pool: *std.heap.MemoryPool(State), state_pool: *std.heap.MemoryPool(State),
@@ -547,11 +548,14 @@ pub const Page = struct {
try self.reset(); try self.reset();
} }
const req_id = self.http_client.nextReqId();
log.info(.http, "navigate", .{ log.info(.http, "navigate", .{
.url = request_url, .url = request_url,
.method = opts.method, .method = opts.method,
.reason = opts.reason, .reason = opts.reason,
.body = opts.body != null, .body = opts.body != null,
.req_id = req_id,
}); });
// if the url is about:blank, we load an empty HTML document in the // if the url is about:blank, we load an empty HTML document in the
@@ -569,12 +573,14 @@ pub const Page = struct {
self.documentIsComplete(); self.documentIsComplete();
self.session.browser.notification.dispatch(.page_navigate, &.{ self.session.browser.notification.dispatch(.page_navigate, &.{
.req_id = req_id,
.opts = opts, .opts = opts,
.url = request_url, .url = request_url,
.timestamp = timestamp(), .timestamp = timestamp(),
}); });
self.session.browser.notification.dispatch(.page_navigated, &.{ self.session.browser.notification.dispatch(.page_navigated, &.{
.req_id = req_id,
.opts = .{ .opts = .{
.cdp_id = opts.cdp_id, .cdp_id = opts.cdp_id,
.reason = opts.reason, .reason = opts.reason,
@@ -584,12 +590,16 @@ pub const Page = struct {
.timestamp = timestamp(), .timestamp = timestamp(),
}); });
// force next request id manually b/c we won't create a real req.
_ = self.http_client.incrReqId();
return; return;
} }
const owned_url = try self.arena.dupeZ(u8, request_url); const owned_url = try self.arena.dupeZ(u8, request_url);
self.url = try URL.parse(owned_url, null); self.url = try URL.parse(owned_url, null);
self.req_id = req_id;
self.navigated_options = .{ self.navigated_options = .{
.cdp_id = opts.cdp_id, .cdp_id = opts.cdp_id,
.reason = opts.reason, .reason = opts.reason,
@@ -603,6 +613,7 @@ pub const Page = struct {
// We dispatch page_navigate event before sending the request. // We dispatch page_navigate event before sending the request.
// It ensures the event page_navigated is not dispatched before this one. // It ensures the event page_navigated is not dispatched before this one.
self.session.browser.notification.dispatch(.page_navigate, &.{ self.session.browser.notification.dispatch(.page_navigate, &.{
.req_id = req_id,
.opts = opts, .opts = opts,
.url = owned_url, .url = owned_url,
.timestamp = timestamp(), .timestamp = timestamp(),
@@ -668,8 +679,10 @@ pub const Page = struct {
log.err(.browser, "document is complete", .{ .err = err }); log.err(.browser, "document is complete", .{ .err = err });
}; };
std.debug.assert(self.req_id != null);
std.debug.assert(self.navigated_options != null); std.debug.assert(self.navigated_options != null);
self.session.browser.notification.dispatch(.page_navigated, &.{ self.session.browser.notification.dispatch(.page_navigated, &.{
.req_id = self.req_id.?,
.opts = self.navigated_options.?, .opts = self.navigated_options.?,
.url = self.url.raw, .url = self.url.raw,
.timestamp = timestamp(), .timestamp = timestamp(),

View File

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

View File

@@ -243,11 +243,12 @@ pub fn httpRequestStart(arena: Allocator, bc: anytype, msg: *const Notification.
} }
const transfer = msg.transfer; const transfer = msg.transfer;
const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id});
// We're missing a bunch of fields, but, for now, this seems like enough // We're missing a bunch of fields, but, for now, this seems like enough
try bc.cdp.sendEvent("Network.requestWillBeSent", .{ try bc.cdp.sendEvent("Network.requestWillBeSent", .{
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}), .requestId = loader_id,
.frameId = target_id, .frameId = target_id,
.loaderId = bc.loader_id, .loaderId = loader_id,
.type = msg.transfer.req.resource_type.string(), .type = msg.transfer.req.resource_type.string(),
.documentURL = DocumentUrlWriter.init(&page.url.uri), .documentURL = DocumentUrlWriter.init(&page.url.uri),
.request = TransferAsRequestWriter.init(transfer), .request = TransferAsRequestWriter.init(transfer),
@@ -263,11 +264,14 @@ pub fn httpResponseHeaderDone(arena: Allocator, bc: anytype, msg: *const Notific
const session_id = bc.session_id orelse return; const session_id = bc.session_id orelse return;
const target_id = bc.target_id orelse unreachable; const target_id = bc.target_id orelse unreachable;
const transfer = msg.transfer;
const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id});
// We're missing a bunch of fields, but, for now, this seems like enough // We're missing a bunch of fields, but, for now, this seems like enough
try bc.cdp.sendEvent("Network.responseReceived", .{ try bc.cdp.sendEvent("Network.responseReceived", .{
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{msg.transfer.id}), .requestId = loader_id,
.loaderId = bc.loader_id,
.frameId = target_id, .frameId = target_id,
.loaderId = loader_id,
.response = TransferAsResponseWriter.init(arena, msg.transfer), .response = TransferAsResponseWriter.init(arena, msg.transfer),
.hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo .hasExtraInfo = false, // TODO change after adding Network.responseReceivedExtraInfo
}, .{ .session_id = session_id }); }, .{ .session_id = session_id });

View File

@@ -176,7 +176,6 @@ fn navigate(cmd: anytype) !void {
} }
var page = bc.session.currentPage() orelse return error.PageNotLoaded; var page = bc.session.currentPage() orelse return error.PageNotLoaded;
bc.loader_id = bc.cdp.loader_id_gen.next();
try page.navigate(params.url, .{ try page.navigate(params.url, .{
.reason = .address_bar, .reason = .address_bar,
@@ -184,13 +183,12 @@ fn navigate(cmd: anytype) !void {
}); });
} }
pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void { pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.PageNavigate) !void {
// detachTarget could be called, in which case, we still have a page doing // detachTarget could be called, in which case, we still have a page doing
// things, but no session. // things, but no session.
const session_id = bc.session_id orelse return; const session_id = bc.session_id orelse return;
bc.loader_id = bc.cdp.loader_id_gen.next(); const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{event.req_id});
const loader_id = bc.loader_id;
const target_id = bc.target_id orelse unreachable; const target_id = bc.target_id orelse unreachable;
bc.reset(); bc.reset();
@@ -253,7 +251,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
// detachTarget could be called, in which case, we still have a page doing // detachTarget could be called, in which case, we still have a page doing
// things, but no session. // things, but no session.
const session_id = bc.session_id orelse return; const session_id = bc.session_id orelse return;
const loader_id = bc.loader_id; const loader_id = try std.fmt.allocPrint(arena, "REQ-{d}", .{event.req_id});
const target_id = bc.target_id orelse unreachable; const target_id = bc.target_id orelse unreachable;
const timestamp = event.timestamp; const timestamp = event.timestamp;
@@ -335,7 +333,7 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
.frame = Frame{ .frame = Frame{
.id = target_id, .id = target_id,
.url = event.url, .url = event.url,
.loaderId = bc.loader_id, .loaderId = loader_id,
.securityOrigin = bc.security_origin, .securityOrigin = bc.security_origin,
.secureContextType = bc.secure_context_type, .secureContextType = bc.secure_context_type,
}, },

View File

@@ -261,6 +261,16 @@ pub fn fulfillTransfer(self: *Client, transfer: *Transfer, status: u16, headers:
return transfer.fulfill(status, headers, body); return transfer.fulfill(status, headers, body);
} }
pub fn nextReqId(self: *Client) usize {
return self.next_request_id + 1;
}
pub fn incrReqId(self: *Client) usize {
const id = self.next_request_id + 1;
self.next_request_id = id;
return id;
}
fn makeTransfer(self: *Client, req: Request) !*Transfer { fn makeTransfer(self: *Client, req: Request) !*Transfer {
errdefer req.headers.deinit(); errdefer req.headers.deinit();
@@ -273,8 +283,7 @@ fn makeTransfer(self: *Client, req: Request) !*Transfer {
const transfer = try self.transfer_pool.create(); const transfer = try self.transfer_pool.create();
errdefer self.transfer_pool.destroy(transfer); errdefer self.transfer_pool.destroy(transfer);
const id = self.next_request_id + 1; const id = self.incrReqId();
self.next_request_id = id;
transfer.* = .{ transfer.* = .{
.arena = ArenaAllocator.init(self.allocator), .arena = ArenaAllocator.init(self.allocator),
.id = id, .id = id,

View File

@@ -90,12 +90,14 @@ pub const Notification = struct {
pub const PageRemove = struct {}; pub const PageRemove = struct {};
pub const PageNavigate = struct { pub const PageNavigate = struct {
req_id: usize,
timestamp: u32, timestamp: u32,
url: []const u8, url: []const u8,
opts: page.NavigateOpts, opts: page.NavigateOpts,
}; };
pub const PageNavigated = struct { pub const PageNavigated = struct {
req_id: usize,
timestamp: u32, timestamp: u32,
url: []const u8, url: []const u8,
opts: page.NavigatedOpts, opts: page.NavigatedOpts,
@@ -297,6 +299,7 @@ test "Notification" {
// noop // noop
notifier.dispatch(.page_navigate, &.{ notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 4, .timestamp = 4,
.url = undefined, .url = undefined,
.opts = .{}, .opts = .{},
@@ -306,6 +309,7 @@ test "Notification" {
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate); try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
notifier.dispatch(.page_navigate, &.{ notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 4, .timestamp = 4,
.url = undefined, .url = undefined,
.opts = .{}, .opts = .{},
@@ -314,6 +318,7 @@ test "Notification" {
notifier.unregisterAll(&tc); notifier.unregisterAll(&tc);
notifier.dispatch(.page_navigate, &.{ notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 10, .timestamp = 10,
.url = undefined, .url = undefined,
.opts = .{}, .opts = .{},
@@ -323,21 +328,23 @@ test "Notification" {
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate); try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated); try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
notifier.dispatch(.page_navigate, &.{ notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 10, .timestamp = 10,
.url = undefined, .url = undefined,
.opts = .{}, .opts = .{},
}); });
notifier.dispatch(.page_navigated, &.{ .timestamp = 6, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 6, .url = undefined, .opts = .{} });
try testing.expectEqual(14, tc.page_navigate); try testing.expectEqual(14, tc.page_navigate);
try testing.expectEqual(6, tc.page_navigated); try testing.expectEqual(6, tc.page_navigated);
notifier.unregisterAll(&tc); notifier.unregisterAll(&tc);
notifier.dispatch(.page_navigate, &.{ notifier.dispatch(.page_navigate, &.{
.req_id = 1,
.timestamp = 100, .timestamp = 100,
.url = undefined, .url = undefined,
.opts = .{}, .opts = .{},
}); });
notifier.dispatch(.page_navigated, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
try testing.expectEqual(14, tc.page_navigate); try testing.expectEqual(14, tc.page_navigate);
try testing.expectEqual(6, tc.page_navigated); try testing.expectEqual(6, tc.page_navigated);
@@ -345,27 +352,27 @@ test "Notification" {
// unregister // unregister
try notifier.register(.page_navigate, &tc, TestClient.pageNavigate); try notifier.register(.page_navigate, &tc, TestClient.pageNavigate);
try notifier.register(.page_navigated, &tc, TestClient.pageNavigated); try notifier.register(.page_navigated, &tc, TestClient.pageNavigated);
notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate); try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(1006, tc.page_navigated); try testing.expectEqual(1006, tc.page_navigated);
notifier.unregister(.page_navigate, &tc); notifier.unregister(.page_navigate, &tc);
notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate); try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated); try testing.expectEqual(2006, tc.page_navigated);
notifier.unregister(.page_navigated, &tc); notifier.unregister(.page_navigated, &tc);
notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate); try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated); try testing.expectEqual(2006, tc.page_navigated);
// already unregistered, try anyways // already unregistered, try anyways
notifier.unregister(.page_navigated, &tc); notifier.unregister(.page_navigated, &tc);
notifier.dispatch(.page_navigate, &.{ .timestamp = 100, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigate, &.{ .req_id = 1, .timestamp = 100, .url = undefined, .opts = .{} });
notifier.dispatch(.page_navigated, &.{ .timestamp = 1000, .url = undefined, .opts = .{} }); notifier.dispatch(.page_navigated, &.{ .req_id = 1, .timestamp = 1000, .url = undefined, .opts = .{} });
try testing.expectEqual(114, tc.page_navigate); try testing.expectEqual(114, tc.page_navigate);
try testing.expectEqual(2006, tc.page_navigated); try testing.expectEqual(2006, tc.page_navigated);
} }