Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/cdp/cdp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {},
}

Expand Down
38 changes: 38 additions & 0 deletions src/cdp/domains/accessibility.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// 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 <https://www.gnu.org/licenses/>.

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, .{});
}
5 changes: 3 additions & 2 deletions src/cdp/domains/page.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
}
Expand Down
84 changes: 63 additions & 21 deletions src/cdp/domains/target.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const LOADER_ID = "LOADERID42AA389647D702B4D805F49A";

pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum {
getTargets,
attachToTarget,
closeTarget,
createBrowserContext,
Expand All @@ -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),
Expand All @@ -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| {
Expand Down Expand Up @@ -139,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,
"",
Expand Down Expand Up @@ -178,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 {
Expand All @@ -201,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 },
Expand Down Expand Up @@ -388,6 +423,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
Expand All @@ -396,16 +438,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 {
Expand Down