From 6fa0c37c39b98fd72a648fd222862fb1dc74e822 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 18 Nov 2025 16:14:51 +0100 Subject: [PATCH 1/5] cdp: add getTargets --- src/cdp/domains/target.zig | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index fcee29d35..41d5dcd38 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -24,6 +24,7 @@ const LOADER_ID = "LOADERID42AA389647D702B4D805F49A"; pub fn processMessage(cmd: anytype) !void { const action = std.meta.stringToEnum(enum { + getTargets, attachToTarget, closeTarget, createBrowserContext, @@ -38,6 +39,7 @@ pub fn processMessage(cmd: anytype) !void { }, cmd.input.action) orelse return error.UnknownMethod; switch (action) { + .getTargets => return getTargets(cmd), .attachToTarget => return attachToTarget(cmd), .closeTarget => return closeTarget(cmd), .createBrowserContext => return createBrowserContext(cmd), @@ -52,6 +54,31 @@ pub fn processMessage(cmd: anytype) !void { } } +fn getTargets(cmd: anytype) !void { + // Some clients like Stagehand expects to have an existing context. + const bc = cmd.browser_context orelse cmd.createBrowserContext() catch |err| switch (err) { + error.AlreadyExists => unreachable, + else => return err, + }; + + const target_id = bc.target_id orelse { + return cmd.sendResult(.{ + .targetInfos = [_]TargetInfo{}, + }, .{ .include_session_id = false }); + }; + + return cmd.sendResult(.{ + .targetInfos = [_]TargetInfo{.{ + .targetId = target_id, + .type = "page", + .title = "", + .url = "", + .attached = true, + .canAccessOpener = false, + }}, + }, .{ .include_session_id = false }); +} + fn getBrowserContexts(cmd: anytype) !void { var browser_context_ids: []const []const u8 = undefined; if (cmd.browser_context) |bc| { From 0f3ce851526e156a831bfed5a9feb40536f17ced Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 18 Nov 2025 18:19:21 +0100 Subject: [PATCH 2/5] cdp: force traget creation on setAutoAttach --- src/cdp/domains/target.zig | 51 +++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index 41d5dcd38..7edcde574 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -166,13 +166,25 @@ fn createTarget(cmd: anytype) !void { // if target_id is null, we should never have a session_id std.debug.assert(bc.session_id == null); - const target_id = cmd.cdp.target_id_gen.next(); + const target_id = try doCreateTarget(cmd, params.url); + + try cmd.sendResult(.{ + .targetId = target_id, + }, .{}); +} +fn doCreateTarget(cmd: anytype, url: []const u8) ![]const u8 { + const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; + const target_id = cmd.cdp.target_id_gen.next(); bc.target_id = target_id; var page = try bc.session.createPage(); { - const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id}); + const aux_data = try std.fmt.allocPrint( + cmd.arena, + "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", + .{target_id}, + ); bc.inspector.contextCreated( page.js, "", @@ -205,15 +217,13 @@ fn createTarget(cmd: anytype) !void { try doAttachtoTarget(cmd, target_id); } - if (!std.mem.eql(u8, "about:blank", params.url)) { - try page.navigate(params.url, .{ + if (!std.mem.eql(u8, "about:blank", url)) { + try page.navigate(url, .{ .reason = .address_bar, }); } - try cmd.sendResult(.{ - .targetId = target_id, - }, .{}); + return target_id; } fn attachToTarget(cmd: anytype) !void { @@ -415,6 +425,13 @@ fn setAutoAttach(cmd: anytype) !void { return; } + _ = cmd.createBrowserContext() catch |err| switch (err) { + error.AlreadyExists => unreachable, + else => return err, + }; + + _ = try doCreateTarget(cmd, "about:blank"); + // This is a hack. Puppeteer, and probably others, expect the Browser to // automatically started creating targets. Things like an empty tab, or // a blank page. And they block until this happens. So we send an event @@ -423,16 +440,16 @@ fn setAutoAttach(cmd: anytype) !void { // there. // This hack requires the main cdp dispatch handler to special case // messages from this "STARTUP" session. - try cmd.sendEvent("Target.attachedToTarget", AttachToTarget{ - .sessionId = "STARTUP", - .targetInfo = TargetInfo{ - .type = "page", - .targetId = "TID-STARTUP-P", - .title = "New Private Tab", - .url = "chrome://newtab/", - .browserContextId = "BID-STARTUP", - }, - }, .{}); + // try cmd.sendEvent("Target.attachedToTarget", AttachToTarget{ + // .sessionId = "STARTUP", + // .targetInfo = TargetInfo{ + // .type = "page", + // .targetId = "TID-STARTUP-P", + // .title = "New Private Tab", + // .url = "chrome://newtab/", + // .browserContextId = "BID-STARTUP", + // }, + // }, .{}); } fn doAttachtoTarget(cmd: anytype, target_id: []const u8) !void { From 4d5ae111a65cb96853925968b3363dd6d4c05667 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 18 Nov 2025 18:23:53 +0100 Subject: [PATCH 3/5] cdp: accept multiple attachToTarget calls --- src/cdp/domains/target.zig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index 7edcde574..17448c360 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -238,12 +238,10 @@ fn attachToTarget(cmd: anytype) !void { return error.UnknownTargetId; } - if (bc.session_id != null) { - return error.SessionAlreadyLoaded; + if (bc.session_id == null) { + try doAttachtoTarget(cmd, target_id); } - try doAttachtoTarget(cmd, target_id); - return cmd.sendResult( .{ .sessionId = bc.session_id }, .{ .include_session_id = false }, From dcd1d090dcc08c40647f2ab1bd9a331fe9e1a53d Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 19 Nov 2025 10:57:48 +0100 Subject: [PATCH 4/5] cdp: use default true value for grantUniveralAccess In createIsolatedWorld, we set a default value to false for optional grantUniveralAccess parameter. --- src/cdp/domains/page.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index f1aa83e9d..93cf5a26e 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -20,6 +20,7 @@ const std = @import("std"); const Page = @import("../../browser/page.zig").Page; const timestampF = @import("../../datetime.zig").timestamp; const Notification = @import("../../notification.zig").Notification; +const log = @import("../../log.zig"); const Allocator = std.mem.Allocator; @@ -133,10 +134,10 @@ fn createIsolatedWorld(cmd: anytype) !void { const params = (try cmd.params(struct { frameId: []const u8, worldName: []const u8, - grantUniveralAccess: bool, + grantUniveralAccess: bool = false, })) orelse return error.InvalidParams; if (!params.grantUniveralAccess) { - std.debug.print("grantUniveralAccess == false is not yet implemented", .{}); + log.warn(.cdp, "not implemented", .{ .feature = "grantUniveralAccess == false is not yet implemented" }); // When grantUniveralAccess == false and the client attempts to resolve // or otherwise access a DOM or other JS Object from another context that should fail. } From 12ead4dbfe1dfdceb80551a5197febd043e25db5 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Wed, 19 Nov 2025 12:07:42 +0100 Subject: [PATCH 5/5] cdp: add accessibility domain --- src/cdp/cdp.zig | 5 ++++ src/cdp/domains/accessibility.zig | 38 +++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/cdp/domains/accessibility.zig diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index da1447547..def567730 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -230,6 +230,11 @@ pub fn CDPT(comptime TypeProvider: type) type { asUint(u88, "Performance") => return @import("domains/performance.zig").processMessage(command), else => {}, }, + 13 => switch (@as(u104, @bitCast(domain[0..13].*))) { + asUint(u104, "Accessibility") => return @import("domains/accessibility.zig").processMessage(command), + else => {}, + }, + else => {}, } diff --git a/src/cdp/domains/accessibility.zig b/src/cdp/domains/accessibility.zig new file mode 100644 index 000000000..a2132f43d --- /dev/null +++ b/src/cdp/domains/accessibility.zig @@ -0,0 +1,38 @@ +// Copyright (C) 2023-2024 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +const std = @import("std"); + +pub fn processMessage(cmd: anytype) !void { + const action = std.meta.stringToEnum(enum { + enable, + disable, + }, cmd.input.action) orelse return error.UnknownMethod; + + switch (action) { + .enable => return enable(cmd), + .disable => return disable(cmd), + } +} +fn enable(cmd: anytype) !void { + return cmd.sendResult(null, .{}); +} + +fn disable(cmd: anytype) !void { + return cmd.sendResult(null, .{}); +}