From e6bd65626e876f65a55c57acc25a0c92f30b67ce Mon Sep 17 00:00:00 2001 From: Boyd Ebsworthy <5266759+bebsworthy@users.noreply.github.com> Date: Fri, 26 Jun 2026 13:59:54 +0200 Subject: [PATCH] cdp: Fix Page.frameAttached params double-wrapping that broke sub-frame interception MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit frameChildFrameCreated emitted Page.frameAttached with its payload wrapped in an extra `.{ .params = ... }`. CDP.sendEvent already places the payload into the event's `params` field, so this produced a malformed wire event with double-nested params (`params.params.frameId`) — unlike every sibling frame event in the same function, which passes its fields flat. CDP clients (e.g. Playwright via connectOverCDP + page.route) parse frameId/parentFrameId at the top level of params. With the fields one level too deep the client never registers the child frame, so when that frame's Fetch.requestPaused arrives it is bare-continued instead of matched against a route. The result: request interception (page.route / Fetch) silently does not apply to iframe (sub-frame) document navigations — the iframe loads from the real network. The interception layer itself was never at fault; it does emit requestPaused for sub-frames. Drop the extra wrapper so the payload is flat. Add a regression test that dispatches frame_child_frame_created and asserts Page.frameAttached carries frameId/parentFrameId directly under params (fails on the double-nested shape). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cdp/domains/page.zig | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index 155dc2d4d..eae8128eb 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -585,10 +585,10 @@ pub fn frameChildFrameCreated(bc: *CDP.BrowserContext, event: *const Notificatio const cdp = bc.cdp; const frame_id = &id.toFrameId(event.frame_id); - try cdp.sendEvent("Page.frameAttached", .{ .params = .{ + try cdp.sendEvent("Page.frameAttached", .{ .frameId = frame_id, .parentFrameId = &id.toFrameId(event.parent_id), - } }, .{ .session_id = session_id }); + }, .{ .session_id = session_id }); if (bc.page_life_cycle_events) { try cdp.sendEvent("Page.lifecycleEvent", LifecycleEvent{ @@ -1055,6 +1055,25 @@ test "cdp.frame: getFrameTree" { } } +test "cdp.frame: frameAttached" { + var ctx = try testing.context(); + defer ctx.deinit(); + + const bc = try ctx.loadBrowserContext(.{ .session_id = "SID-X" }); + + bc.notification.dispatch(.frame_child_frame_created, &.{ + .frame_id = 2, + .parent_id = 1, + .loader_id = 7, + .timestamp = 0, + }); + + try ctx.expectSentEvent("Page.frameAttached", .{ + .frameId = "FID-0000000002", + .parentFrameId = "FID-0000000001", + }, .{ .session_id = "SID-X" }); +} + test "cdp.frame: captureScreenshot" { const LogFilter = @import("../../testing.zig").LogFilter; const filter: LogFilter = .init(&.{.not_implemented});