From eee232c12c20c4c6928da2e1154b02901d3b972b Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 3 Mar 2026 12:06:56 +0100 Subject: [PATCH 1/5] cdp: allow multiple calls to attachToTarget Playwright, when creating a new CDPSession, sends an attachToBrowserTarget followed by another attachToTarget to re-attach itself to the existing target. see playwright/axtree.js from demo/ repository. --- src/cdp/domains/target.zig | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index 243ddc8f..41fdd18b 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -243,9 +243,7 @@ fn attachToTarget(cmd: anytype) !void { return error.UnknownTargetId; } - if (bc.session_id == null) { - try doAttachtoTarget(cmd, target_id); - } + try doAttachtoTarget(cmd, target_id); return cmd.sendResult( .{ .sessionId = bc.session_id }, @@ -451,22 +449,23 @@ fn setAutoAttach(cmd: anytype) !void { fn doAttachtoTarget(cmd: anytype, target_id: []const u8) !void { const bc = cmd.browser_context.?; - lp.assert(bc.session_id == null, "CDP.target.doAttachtoTarget not null session_id", .{}); - const session_id = cmd.cdp.session_id_gen.next(); + const session_id = bc.session_id orelse 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.extra_headers.clearRetainingCapacity(); + if (bc.session_id == null) { + // 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, .targetInfo = TargetInfo{ .targetId = target_id, - .title = "about:blank", - .url = "chrome://newtab/", + .title = bc.getTitle() orelse "about:blank", + .url = bc.getURL() orelse "chrome://newtab/", .browserContextId = bc.id, }, - }, .{}); + }, .{ .session_id = bc.session_id }); bc.session_id = session_id; } From 14b58e806260a54201461a6338bc1f6fcac0e0c1 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 3 Mar 2026 12:54:30 +0100 Subject: [PATCH 2/5] add target.attachToBrowserTarget --- src/cdp/domains/target.zig | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index 41fdd18b..ff7e0b47 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -31,6 +31,7 @@ pub fn processMessage(cmd: anytype) !void { const action = std.meta.stringToEnum(enum { getTargets, attachToTarget, + attachToBrowserTarget, closeTarget, createBrowserContext, createTarget, @@ -47,6 +48,7 @@ pub fn processMessage(cmd: anytype) !void { switch (action) { .getTargets => return getTargets(cmd), .attachToTarget => return attachToTarget(cmd), + .attachToBrowserTarget => return attachToBrowserTarget(cmd), .closeTarget => return closeTarget(cmd), .createBrowserContext => return createBrowserContext(cmd), .createTarget => return createTarget(cmd), @@ -251,6 +253,28 @@ fn attachToTarget(cmd: anytype) !void { ); } +fn attachToBrowserTarget(cmd: anytype) !void { + const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; + + const session_id = bc.session_id orelse cmd.cdp.session_id_gen.next(); + + try cmd.sendEvent("Target.attachedToTarget", AttachToTarget{ + .sessionId = session_id, + .targetInfo = TargetInfo{ + .targetId = bc.id, // We use the browser context is as browser's target id. + .title = "", + .url = "", + .type = "browser", + // Chrome doesn't send a browserContextId in this case. + .browserContextId = null, + }, + }, .{}); + + bc.session_id = session_id; + + return cmd.sendResult(null, .{}); +} + fn closeTarget(cmd: anytype) !void { const params = (try cmd.params(struct { targetId: []const u8, From 06ef6d3e6a61f4aaa4ca2e3f5e575d851e4f4be2 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 3 Mar 2026 12:54:55 +0100 Subject: [PATCH 3/5] cdp: attachToTarget must add the session id --- src/cdp/domains/target.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index ff7e0b47..634fd49a 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -249,7 +249,7 @@ fn attachToTarget(cmd: anytype) !void { return cmd.sendResult( .{ .sessionId = bc.session_id }, - .{ .include_session_id = false }, + .{}, ); } From 56cc881ac04e32d9b213ee91bbfde351e7348b4f Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 3 Mar 2026 15:01:53 +0100 Subject: [PATCH 4/5] Fcdp: fix attachtToTarget and attachToBrowserTarget resp --- src/cdp/domains/target.zig | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index 634fd49a..37d6377b 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -247,10 +247,7 @@ fn attachToTarget(cmd: anytype) !void { try doAttachtoTarget(cmd, target_id); - return cmd.sendResult( - .{ .sessionId = bc.session_id }, - .{}, - ); + return cmd.sendResult(.{ .sessionId = bc.session_id }, .{}); } fn attachToBrowserTarget(cmd: anytype) !void { @@ -272,7 +269,7 @@ fn attachToBrowserTarget(cmd: anytype) !void { bc.session_id = session_id; - return cmd.sendResult(null, .{}); + return cmd.sendResult(.{ .sessionId = bc.session_id }, .{}); } fn closeTarget(cmd: anytype) !void { From 9ca5188e12680006736ccd14bc657152a505c16d Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 3 Mar 2026 17:24:08 +0100 Subject: [PATCH 5/5] cdp: set consistent target's default with about:blank for url and empty title. --- src/cdp/domains/target.zig | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index 37d6377b..b1e2be42 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -81,7 +81,7 @@ fn getTargets(cmd: anytype) !void { .targetInfos = [_]TargetInfo{.{ .targetId = target_id, .type = "page", - .title = bc.getTitle() orelse "about:blank", + .title = bc.getTitle() orelse "", .url = bc.getURL() orelse "about:blank", .attached = true, .canAccessOpener = false, @@ -209,7 +209,7 @@ fn createTarget(cmd: anytype) !void { .targetInfo = TargetInfo{ .attached = false, .targetId = target_id, - .title = "about:blank", + .title = "", .browserContextId = bc.id, .url = "about:blank", }, @@ -330,7 +330,7 @@ fn getTargetInfo(cmd: anytype) !void { .targetInfo = TargetInfo{ .targetId = target_id, .type = "page", - .title = bc.getTitle() orelse "about:blank", + .title = bc.getTitle() orelse "", .url = bc.getURL() orelse "about:blank", .attached = true, .canAccessOpener = false, @@ -342,7 +342,7 @@ fn getTargetInfo(cmd: anytype) !void { .targetInfo = TargetInfo{ .targetId = "TID-STARTUP-B", .type = "browser", - .title = "about:blank", + .title = "", .url = "about:blank", .attached = true, .canAccessOpener = false, @@ -461,8 +461,8 @@ fn setAutoAttach(cmd: anytype) !void { .targetInfo = TargetInfo{ .type = "page", .targetId = "TID-STARTUP-P", - .title = "New Private Tab", - .url = "chrome://newtab/", + .title = "", + .url = "about:blank", .browserContextId = "BID-STARTUP", }, }, .{}); @@ -482,8 +482,8 @@ fn doAttachtoTarget(cmd: anytype, target_id: []const u8) !void { .sessionId = session_id, .targetInfo = TargetInfo{ .targetId = target_id, - .title = bc.getTitle() orelse "about:blank", - .url = bc.getURL() orelse "chrome://newtab/", + .title = bc.getTitle() orelse "", + .url = bc.getURL() orelse "about:blank", .browserContextId = bc.id, }, }, .{ .session_id = bc.session_id }); @@ -588,7 +588,7 @@ test "cdp.target: createTarget" { // should create a browser context const bc = ctx.cdp().browser_context.?; - try ctx.expectSentEvent("Target.targetCreated", .{ .targetInfo = .{ .url = "about:blank", .title = "about:blank", .attached = false, .type = "page", .canAccessOpener = false, .browserContextId = bc.id, .targetId = bc.target_id.? } }, .{}); + try ctx.expectSentEvent("Target.targetCreated", .{ .targetInfo = .{ .url = "about:blank", .title = "", .attached = false, .type = "page", .canAccessOpener = false, .browserContextId = bc.id, .targetId = bc.target_id.? } }, .{}); } { @@ -600,8 +600,8 @@ test "cdp.target: createTarget" { // should create a browser context const bc = ctx.cdp().browser_context.?; - try ctx.expectSentEvent("Target.targetCreated", .{ .targetInfo = .{ .url = "about:blank", .title = "about:blank", .attached = false, .type = "page", .canAccessOpener = false, .browserContextId = bc.id, .targetId = bc.target_id.? } }, .{}); - try ctx.expectSentEvent("Target.attachedToTarget", .{ .sessionId = bc.session_id.?, .targetInfo = .{ .url = "chrome://newtab/", .title = "about:blank", .attached = true, .type = "page", .canAccessOpener = false, .browserContextId = bc.id, .targetId = bc.target_id.? } }, .{}); + try ctx.expectSentEvent("Target.targetCreated", .{ .targetInfo = .{ .url = "about:blank", .title = "", .attached = false, .type = "page", .canAccessOpener = false, .browserContextId = bc.id, .targetId = bc.target_id.? } }, .{}); + try ctx.expectSentEvent("Target.attachedToTarget", .{ .sessionId = bc.session_id.?, .targetInfo = .{ .url = "about:blank", .title = "", .attached = true, .type = "page", .canAccessOpener = false, .browserContextId = bc.id, .targetId = bc.target_id.? } }, .{}); } var ctx = testing.context(); @@ -616,7 +616,7 @@ test "cdp.target: createTarget" { try ctx.processMessage(.{ .id = 10, .method = "Target.createTarget", .params = .{ .browserContextId = "BID-9" } }); try testing.expectEqual(true, bc.target_id != null); try ctx.expectSentResult(.{ .targetId = bc.target_id.? }, .{ .id = 10 }); - try ctx.expectSentEvent("Target.targetCreated", .{ .targetInfo = .{ .url = "about:blank", .title = "about:blank", .attached = false, .type = "page", .canAccessOpener = false, .browserContextId = "BID-9", .targetId = bc.target_id.? } }, .{}); + try ctx.expectSentEvent("Target.targetCreated", .{ .targetInfo = .{ .url = "about:blank", .title = "", .attached = false, .type = "page", .canAccessOpener = false, .browserContextId = "BID-9", .targetId = bc.target_id.? } }, .{}); } } @@ -678,7 +678,7 @@ test "cdp.target: attachToTarget" { try ctx.processMessage(.{ .id = 11, .method = "Target.attachToTarget", .params = .{ .targetId = "TID-000000000B" } }); const session_id = bc.session_id.?; try ctx.expectSentResult(.{ .sessionId = session_id }, .{ .id = 11 }); - try ctx.expectSentEvent("Target.attachedToTarget", .{ .sessionId = session_id, .targetInfo = .{ .url = "chrome://newtab/", .title = "about:blank", .attached = true, .type = "page", .canAccessOpener = false, .browserContextId = "BID-9", .targetId = bc.target_id.? } }, .{}); + try ctx.expectSentEvent("Target.attachedToTarget", .{ .sessionId = session_id, .targetInfo = .{ .url = "about:blank", .title = "", .attached = true, .type = "page", .canAccessOpener = false, .browserContextId = "BID-9", .targetId = bc.target_id.? } }, .{}); } } @@ -691,7 +691,7 @@ test "cdp.target: getTargetInfo" { try ctx.expectSentResult(.{ .targetInfo = .{ .type = "browser", - .title = "about:blank", + .title = "", .url = "about:blank", .attached = true, .canAccessOpener = false,