From 211012d367e5d703b3b2168dd9308866eff77ede Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Mon, 18 Aug 2025 13:23:17 +0800 Subject: [PATCH] move intercept_state and extra_headers from CDP instance to BrowserContext --- src/cdp/cdp.zig | 18 ++++++++------ src/cdp/domains/fetch.zig | 48 ++++++++++++++++++++++++++++++------- src/cdp/domains/network.zig | 8 +++---- src/cdp/domains/target.zig | 5 ++-- 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 23259ecc..8e693718 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -74,11 +74,6 @@ pub fn CDPT(comptime TypeProvider: type) type { // Used for processing notifications within a browser context. notification_arena: std.heap.ArenaAllocator, - // Extra headers to add to all requests. TBD under which conditions this should be reset. - extra_headers: std.ArrayListUnmanaged([*c]const u8) = .empty, - - intercept_state: InterceptState, - const Self = @This(); pub fn init(app: *App, client: TypeProvider.Client) !Self { @@ -93,7 +88,6 @@ pub fn CDPT(comptime TypeProvider: type) type { .browser_context = null, .message_arena = std.heap.ArenaAllocator.init(allocator), .notification_arena = std.heap.ArenaAllocator.init(allocator), - .intercept_state = try InterceptState.init(allocator), // TBD or browser session arena? }; } @@ -101,7 +95,6 @@ pub fn CDPT(comptime TypeProvider: type) type { if (self.browser_context) |*bc| { bc.deinit(); } - self.intercept_state.deinit(); // TBD Should this live in BC? self.browser.deinit(); self.message_arena.deinit(); self.notification_arena.deinit(); @@ -346,6 +339,11 @@ pub fn BrowserContext(comptime CDP_T: type) type { http_proxy_changed: bool = false, + // Extra headers to add to all requests. + extra_headers: std.ArrayListUnmanaged([*c]const u8) = .empty, + + intercept_state: InterceptState, + const Self = @This(); fn init(self: *Self, id: []const u8, cdp: *CDP_T) !void { @@ -375,6 +373,7 @@ pub fn BrowserContext(comptime CDP_T: type) type { .isolated_world = null, .inspector = inspector, .notification_arena = cdp.notification_arena.allocator(), + .intercept_state = try InterceptState.init(allocator), }; self.node_search_list = Node.Search.List.init(allocator, &self.node_registry); errdefer self.deinit(); @@ -407,6 +406,11 @@ pub fn BrowserContext(comptime CDP_T: type) type { log.warn(.http, "restoreOriginalProxy", .{ .err = err }); }; } + + for (self.intercept_state.pendingTransfers()) |transfer| { + transfer.abort(); + } + self.intercept_state.deinit(); } pub fn reset(self: *Self) void { diff --git a/src/cdp/domains/fetch.zig b/src/cdp/domains/fetch.zig index 5b8d3c41..b13adc29 100644 --- a/src/cdp/domains/fetch.zig +++ b/src/cdp/domains/fetch.zig @@ -70,6 +70,10 @@ pub const InterceptState = struct { pub fn deinit(self: *InterceptState) void { self.waiting.deinit(self.allocator); } + + pub fn pendingTransfers(self: *const InterceptState) []*Transfer { + return self.waiting.values(); + } }; const RequestPattern = struct { @@ -134,11 +138,13 @@ fn disable(cmd: anytype) !void { fn enable(cmd: anytype) !void { const params = (try cmd.params(EnableParam)) orelse EnableParam{}; - if (params.patterns.len != 0) { - log.warn(.cdp, "not implemented", .{ .feature = "Fetch.enable No patterns yet" }); + if (!arePatternsSupported(params.patterns)) { + log.warn(.cdp, "not implemented", .{ .feature = "Fetch.enable advanced patterns are not" }); + return cmd.sendResult(null, .{}); } + if (params.handleAuthRequests) { - log.warn(.cdp, "not implemented", .{ .feature = "Fetch.enable No auth yet" }); + log.warn(.cdp, "not implemented", .{ .feature = "Fetch.enable handleAuthRequests is not supported yet" }); } const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; @@ -147,9 +153,33 @@ fn enable(cmd: anytype) !void { return cmd.sendResult(null, .{}); } -pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notification.RequestIntercept) !void { - var cdp = bc.cdp; +fn arePatternsSupported(patterns: []RequestPattern) bool { + if (patterns.len == 0) { + return true; + } + if (patterns.len > 1) { + return false; + } + // While we don't support patterns, yet, both Playwright and Puppeteer send + // a default pattern which happens to be what we support: + // [{"urlPattern":"*","requestStage":"Request"}] + // So, rather than erroring on this case because we don't support patterns, + // we'll allow it, because this pattern is how it works as-is. + const pattern = patterns[0]; + if (!std.mem.eql(u8, pattern.urlPattern, "*")) { + return false; + } + if (pattern.resourceType != null) { + return false; + } + if (pattern.requestStage != .Request) { + return false; + } + return true; +} + +pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notification.RequestIntercept) !void { // unreachable because we _have_ to have a page. const session_id = bc.session_id orelse unreachable; const target_id = bc.target_id orelse unreachable; @@ -160,9 +190,9 @@ pub fn requestIntercept(arena: Allocator, bc: anytype, intercept: *const Notific // TODO: What to do when receiving replies for a previous page's requests? const transfer = intercept.transfer; - try cdp.intercept_state.put(transfer); + try bc.intercept_state.put(transfer); - try cdp.sendEvent("Fetch.requestPaused", .{ + try bc.cdp.sendEvent("Fetch.requestPaused", .{ .requestId = try std.fmt.allocPrint(arena, "INTERCEPT-{d}", .{transfer.id}), .request = network.TransferAsRequestWriter.init(transfer), .frameId = target_id, @@ -202,7 +232,7 @@ fn continueRequest(cmd: anytype) !void { const page = bc.session.currentPage() orelse return error.PageNotLoaded; - var intercept_state = &bc.cdp.intercept_state; + var intercept_state = &bc.intercept_state; const request_id = try idFromRequestId(params.requestId); const transfer = intercept_state.remove(request_id) orelse return error.RequestNotFound; @@ -238,7 +268,7 @@ fn failRequest(cmd: anytype) !void { const page = bc.session.currentPage() orelse return error.PageNotLoaded; - var intercept_state = &bc.cdp.intercept_state; + var intercept_state = &bc.intercept_state; const request_id = try idFromRequestId(params.requestId); const transfer = intercept_state.remove(request_id) orelse return error.RequestNotFound; diff --git a/src/cdp/domains/network.zig b/src/cdp/domains/network.zig index 99e43a6e..7efb6cf6 100644 --- a/src/cdp/domains/network.zig +++ b/src/cdp/domains/network.zig @@ -83,7 +83,7 @@ fn setExtraHTTPHeaders(cmd: anytype) !void { // Copy the headers onto the browser context arena const arena = bc.arena; - const extra_headers = &bc.cdp.extra_headers; + const extra_headers = &bc.extra_headers; extra_headers.clearRetainingCapacity(); try extra_headers.ensureTotalCapacity(arena, params.headers.map.count()); @@ -235,7 +235,7 @@ pub fn httpRequestStart(arena: Allocator, bc: anytype, data: *const Notification const page = bc.session.currentPage() orelse unreachable; // Modify request with extra CDP headers - for (cdp.extra_headers.items) |extra| { + for (bc.extra_headers.items) |extra| { try data.transfer.req.headers.add(extra); } @@ -429,10 +429,10 @@ test "cdp.network setExtraHTTPHeaders" { }); const bc = ctx.cdp().browser_context.?; - try testing.expectEqual(bc.cdp.extra_headers.items.len, 1); + try testing.expectEqual(bc.extra_headers.items.len, 1); try ctx.processMessage(.{ .id = 5, .method = "Target.attachToTarget", .params = .{ .targetId = bc.target_id.? } }); - try testing.expectEqual(bc.cdp.extra_headers.items.len, 0); + try testing.expectEqual(bc.extra_headers.items.len, 0); } test "cdp.Network: cookies" { diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index ced51f74..fdf01be4 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -409,8 +409,9 @@ fn doAttachtoTarget(cmd: anytype, target_id: []const u8) !void { std.debug.assert(bc.session_id == null); const session_id = cmd.cdp.session_id_gen.next(); - // extra_headers should not be kept on a new page or tab, currently we have only 1 page, we clear it just in case - bc.cdp.extra_headers.clearRetainingCapacity(); + // extra_headers should not be kept on a new page or tab, + // currently we have only 1 page, we clear it just in case + bc.extra_headers.clearRetainingCapacity(); try cmd.sendEvent("Target.attachedToTarget", AttachToTarget{ .sessionId = session_id,