backport a variety of smaller CDP changes

This commit is contained in:
Karl Seguin
2025-12-19 10:31:07 +08:00
parent a087386af3
commit bb1ea39c54
4 changed files with 99 additions and 14 deletions

View File

@@ -283,6 +283,13 @@ fn registerBackgroundTasks(self: *Page) !void {
}.runMessageLoop, 250, .{ .name = "page.messageLoop" }); }.runMessageLoop, 250, .{ .name = "page.messageLoop" });
} }
pub fn getTitle(self: *Page) !?[]const u8 {
if (self.window._document.is(Document.HTMLDocument)) |html_doc| {
return try html_doc.getTitle(self);
}
return null;
}
pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts, kind: NavigationKind) !void { pub fn navigate(self: *Page, request_url: [:0]const u8, opts: NavigateOpts, kind: NavigationKind) !void {
const session = self._session; const session = self._session;

View File

@@ -469,6 +469,14 @@ pub fn BrowserContext(comptime CDP_T: type) type {
return if (url.len == 0) null else url; return if (url.len == 0) null else url;
} }
pub fn getTitle(self: *const Self) ?[]const u8 {
const page = self.session.currentPage() orelse return null;
return page.getTitle() catch |err| {
log.err(.cdp, "page title", .{ .err = err });
return null;
};
}
pub fn networkEnable(self: *Self) !void { pub fn networkEnable(self: *Self) !void {
try self.cdp.browser.notification.register(.http_request_fail, self, onHttpRequestFail); try self.cdp.browser.notification.register(.http_request_fail, self, onHttpRequestFail);
try self.cdp.browser.notification.register(.http_request_start, self, onHttpRequestStart); try self.cdp.browser.notification.register(.http_request_start, self, onHttpRequestStart);

View File

@@ -33,6 +33,7 @@ pub fn processMessage(cmd: anytype) !void {
createIsolatedWorld, createIsolatedWorld,
navigate, navigate,
stopLoading, stopLoading,
close,
}, cmd.input.action) orelse return error.UnknownMethod; }, cmd.input.action) orelse return error.UnknownMethod;
switch (action) { switch (action) {
@@ -43,6 +44,7 @@ pub fn processMessage(cmd: anytype) !void {
.createIsolatedWorld => return createIsolatedWorld(cmd), .createIsolatedWorld => return createIsolatedWorld(cmd),
.navigate => return navigate(cmd), .navigate => return navigate(cmd),
.stopLoading => return cmd.sendResult(null, .{}), .stopLoading => return cmd.sendResult(null, .{}),
.close => return close(cmd),
} }
} }
@@ -133,6 +135,43 @@ fn addScriptToEvaluateOnNewDocument(cmd: anytype) !void {
}, .{}); }, .{});
} }
fn close(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const target_id = bc.target_id orelse return error.TargetNotLoaded;
// can't be null if we have a target_id
std.debug.assert(bc.session.page != null);
try cmd.sendResult(.{}, .{});
// Following code is similar to target.closeTarget
//
// could be null, created but never attached
if (bc.session_id) |session_id| {
// Inspector.detached event
try cmd.sendEvent("Inspector.detached", .{
.reason = "Render process gone.",
}, .{ .session_id = session_id });
// detachedFromTarget event
try cmd.sendEvent("Target.detachedFromTarget", .{
.targetId = target_id,
.sessionId = session_id,
.reason = "Render process gone.",
}, .{});
bc.session_id = null;
}
bc.session.removePage();
for (bc.isolated_worlds.items) |*world| {
world.deinit();
}
bc.isolated_worlds.clearRetainingCapacity();
bc.target_id = null;
}
fn createIsolatedWorld(cmd: anytype) !void { fn createIsolatedWorld(cmd: anytype) !void {
const params = (try cmd.params(struct { const params = (try cmd.params(struct {
frameId: []const u8, frameId: []const u8,

View File

@@ -24,6 +24,7 @@ const LOADER_ID = "LOADERID42AA389647D702B4D805F49A";
pub fn processMessage(cmd: anytype) !void { pub fn processMessage(cmd: anytype) !void {
const action = std.meta.stringToEnum(enum { const action = std.meta.stringToEnum(enum {
getTargets,
attachToTarget, attachToTarget,
closeTarget, closeTarget,
createBrowserContext, createBrowserContext,
@@ -38,6 +39,7 @@ pub fn processMessage(cmd: anytype) !void {
}, cmd.input.action) orelse return error.UnknownMethod; }, cmd.input.action) orelse return error.UnknownMethod;
switch (action) { switch (action) {
.getTargets => return getTargets(cmd),
.attachToTarget => return attachToTarget(cmd), .attachToTarget => return attachToTarget(cmd),
.closeTarget => return closeTarget(cmd), .closeTarget => return closeTarget(cmd),
.createBrowserContext => return createBrowserContext(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 = bc.getTitle() orelse "about:blank",
.url = bc.getURL() orelse "about:blank",
.attached = true,
.canAccessOpener = false,
}},
}, .{ .include_session_id = false });
}
fn getBrowserContexts(cmd: anytype) !void { fn getBrowserContexts(cmd: anytype) !void {
var browser_context_ids: []const []const u8 = undefined; var browser_context_ids: []const []const u8 = undefined;
if (cmd.browser_context) |bc| { if (cmd.browser_context) |bc| {
@@ -168,7 +195,7 @@ fn createTarget(cmd: anytype) !void {
.targetInfo = TargetInfo{ .targetInfo = TargetInfo{
.attached = false, .attached = false,
.targetId = target_id, .targetId = target_id,
.title = params.url, .title = "about:blank",
.browserContextId = bc.id, .browserContextId = bc.id,
.url = "about:blank", .url = "about:blank",
}, },
@@ -179,11 +206,13 @@ fn createTarget(cmd: anytype) !void {
try doAttachtoTarget(cmd, target_id); try doAttachtoTarget(cmd, target_id);
} }
if (!std.mem.eql(u8, "about:blank", params.url)) {
try page.navigate( try page.navigate(
params.url, params.url,
.{ .reason = .address_bar }, .{ .reason = .address_bar },
.{ .push = null }, .{ .push = null },
); );
}
try cmd.sendResult(.{ try cmd.sendResult(.{
.targetId = target_id, .targetId = target_id,
@@ -206,7 +235,9 @@ fn attachToTarget(cmd: anytype) !void {
return error.SessionAlreadyLoaded; return error.SessionAlreadyLoaded;
} }
if (bc.session_id == null) {
try doAttachtoTarget(cmd, target_id); try doAttachtoTarget(cmd, target_id);
}
return cmd.sendResult( return cmd.sendResult(
.{ .sessionId = bc.session_id }, .{ .sessionId = bc.session_id },
@@ -272,8 +303,8 @@ fn getTargetInfo(cmd: anytype) !void {
.targetInfo = TargetInfo{ .targetInfo = TargetInfo{
.targetId = target_id, .targetId = target_id,
.type = "page", .type = "page",
.title = "", .title = bc.getTitle() orelse "about:blank",
.url = "", .url = bc.getURL() orelse "about:blank",
.attached = true, .attached = true,
.canAccessOpener = false, .canAccessOpener = false,
}, },
@@ -284,8 +315,8 @@ fn getTargetInfo(cmd: anytype) !void {
.targetInfo = TargetInfo{ .targetInfo = TargetInfo{
.targetId = "TID-STARTUP-B", .targetId = "TID-STARTUP-B",
.type = "browser", .type = "browser",
.title = "", .title = "about:blank",
.url = "", .url = "about:blank",
.attached = true, .attached = true,
.canAccessOpener = false, .canAccessOpener = false,
}, },
@@ -631,8 +662,8 @@ test "cdp.target: getTargetInfo" {
try ctx.expectSentResult(.{ try ctx.expectSentResult(.{
.targetInfo = .{ .targetInfo = .{
.type = "browser", .type = "browser",
.title = "", .title = "about:blank",
.url = "", .url = "about:blank",
.attached = true, .attached = true,
.canAccessOpener = false, .canAccessOpener = false,
}, },
@@ -665,7 +696,7 @@ test "cdp.target: getTargetInfo" {
.targetId = "TID-A", .targetId = "TID-A",
.type = "page", .type = "page",
.title = "", .title = "",
.url = "", .url = "about:blank",
.attached = true, .attached = true,
.canAccessOpener = false, .canAccessOpener = false,
}, },