mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-30 09:08:55 +00:00
CDP: implement Page.addScriptToEvaluateOnNewDocument
Replace the hardcoded stub with a working implementation that stores registered scripts and evaluates them in each new document. Changes: - Add ScriptOnNewDocument struct and storage list on BrowserContext - Store scripts with unique identifiers when addScript is called - Evaluate all registered scripts in pageNavigated, after the execution context is created but before frameNavigated/loadEventFired events are sent to the CDP client - Add removeScriptToEvaluateOnNewDocument for cleanup - Return unique identifiers per the CDP spec (was hardcoded to "1") Scripts are evaluated with error suppression (warns on failure) to avoid breaking navigation if a script has issues. This unblocks CDP clients that rely on auto-injected scripts (polyfills, monitoring, test helpers) persisting across navigations. Previously clients had to manually re-inject after every Page.navigate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -365,6 +365,11 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
|||||||
inspector_session: *js.Inspector.Session,
|
inspector_session: *js.Inspector.Session,
|
||||||
isolated_worlds: std.ArrayList(*IsolatedWorld),
|
isolated_worlds: std.ArrayList(*IsolatedWorld),
|
||||||
|
|
||||||
|
// Scripts registered via Page.addScriptToEvaluateOnNewDocument.
|
||||||
|
// Evaluated in each new document after navigation completes.
|
||||||
|
scripts_on_new_document: std.ArrayList(ScriptOnNewDocument) = .empty,
|
||||||
|
next_script_id: u32 = 1,
|
||||||
|
|
||||||
http_proxy_changed: bool = false,
|
http_proxy_changed: bool = false,
|
||||||
|
|
||||||
// Extra headers to add to all requests.
|
// Extra headers to add to all requests.
|
||||||
@@ -764,6 +769,11 @@ pub fn BrowserContext(comptime CDP_T: type) type {
|
|||||||
/// Clients create this to be able to create variables and run code without interfering with the
|
/// Clients create this to be able to create variables and run code without interfering with the
|
||||||
/// normal namespace and values of the webpage. Similar to the main context we need to pretend to recreate it after
|
/// normal namespace and values of the webpage. Similar to the main context we need to pretend to recreate it after
|
||||||
/// a executionContextsCleared event which happens when navigating to a new page. A client can have a command be executed
|
/// a executionContextsCleared event which happens when navigating to a new page. A client can have a command be executed
|
||||||
|
const ScriptOnNewDocument = struct {
|
||||||
|
identifier: u32,
|
||||||
|
source: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
/// in the isolated world by using its Context ID or the worldName.
|
/// in the isolated world by using its Context ID or the worldName.
|
||||||
/// grantUniveralAccess Indecated whether the isolated world can reference objects like the DOM or other JS Objects.
|
/// grantUniveralAccess Indecated whether the isolated world can reference objects like the DOM or other JS Objects.
|
||||||
/// An isolated world has it's own instance of globals like Window.
|
/// An isolated world has it's own instance of globals like Window.
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ pub fn processMessage(cmd: anytype) !void {
|
|||||||
getFrameTree,
|
getFrameTree,
|
||||||
setLifecycleEventsEnabled,
|
setLifecycleEventsEnabled,
|
||||||
addScriptToEvaluateOnNewDocument,
|
addScriptToEvaluateOnNewDocument,
|
||||||
|
removeScriptToEvaluateOnNewDocument,
|
||||||
createIsolatedWorld,
|
createIsolatedWorld,
|
||||||
navigate,
|
navigate,
|
||||||
reload,
|
reload,
|
||||||
@@ -51,6 +52,7 @@ pub fn processMessage(cmd: anytype) !void {
|
|||||||
.getFrameTree => return getFrameTree(cmd),
|
.getFrameTree => return getFrameTree(cmd),
|
||||||
.setLifecycleEventsEnabled => return setLifecycleEventsEnabled(cmd),
|
.setLifecycleEventsEnabled => return setLifecycleEventsEnabled(cmd),
|
||||||
.addScriptToEvaluateOnNewDocument => return addScriptToEvaluateOnNewDocument(cmd),
|
.addScriptToEvaluateOnNewDocument => return addScriptToEvaluateOnNewDocument(cmd),
|
||||||
|
.removeScriptToEvaluateOnNewDocument => return removeScriptToEvaluateOnNewDocument(cmd),
|
||||||
.createIsolatedWorld => return createIsolatedWorld(cmd),
|
.createIsolatedWorld => return createIsolatedWorld(cmd),
|
||||||
.navigate => return navigate(cmd),
|
.navigate => return navigate(cmd),
|
||||||
.reload => return doReload(cmd),
|
.reload => return doReload(cmd),
|
||||||
@@ -147,22 +149,52 @@ fn setLifecycleEventsEnabled(cmd: anytype) !void {
|
|||||||
return cmd.sendResult(null, .{});
|
return cmd.sendResult(null, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: hard coded method
|
|
||||||
// With the command we receive a script we need to store and run for each new document.
|
|
||||||
// Note that the worldName refers to the name given to the isolated world.
|
|
||||||
fn addScriptToEvaluateOnNewDocument(cmd: anytype) !void {
|
fn addScriptToEvaluateOnNewDocument(cmd: anytype) !void {
|
||||||
// const params = (try cmd.params(struct {
|
const params = (try cmd.params(struct {
|
||||||
// source: []const u8,
|
source: []const u8,
|
||||||
// worldName: ?[]const u8 = null,
|
worldName: ?[]const u8 = null,
|
||||||
// includeCommandLineAPI: bool = false,
|
includeCommandLineAPI: bool = false,
|
||||||
// runImmediately: bool = false,
|
runImmediately: bool = false,
|
||||||
// })) orelse return error.InvalidParams;
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
|
||||||
|
const script_id = bc.next_script_id;
|
||||||
|
bc.next_script_id += 1;
|
||||||
|
|
||||||
|
const source_dupe = try bc.arena.dupe(u8, params.source);
|
||||||
|
try bc.scripts_on_new_document.append(bc.arena, .{
|
||||||
|
.identifier = script_id,
|
||||||
|
.source = source_dupe,
|
||||||
|
});
|
||||||
|
|
||||||
|
var id_buf: [16]u8 = undefined;
|
||||||
|
const id_str = std.fmt.bufPrint(&id_buf, "{d}", .{script_id}) catch "1";
|
||||||
return cmd.sendResult(.{
|
return cmd.sendResult(.{
|
||||||
.identifier = "1",
|
.identifier = id_str,
|
||||||
}, .{});
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn removeScriptToEvaluateOnNewDocument(cmd: anytype) !void {
|
||||||
|
const params = (try cmd.params(struct {
|
||||||
|
identifier: []const u8,
|
||||||
|
})) orelse return error.InvalidParams;
|
||||||
|
|
||||||
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
|
||||||
|
const target_id = std.fmt.parseInt(u32, params.identifier, 10) catch
|
||||||
|
return cmd.sendResult(null, .{});
|
||||||
|
|
||||||
|
for (bc.scripts_on_new_document.items, 0..) |script, i| {
|
||||||
|
if (script.identifier == target_id) {
|
||||||
|
_ = bc.scripts_on_new_document.orderedRemove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.sendResult(null, .{});
|
||||||
|
}
|
||||||
|
|
||||||
fn close(cmd: anytype) !void {
|
fn close(cmd: anytype) !void {
|
||||||
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
|
||||||
|
|
||||||
@@ -482,6 +514,22 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Evaluate scripts registered via Page.addScriptToEvaluateOnNewDocument.
|
||||||
|
// Must run after the execution context is created but before the client
|
||||||
|
// receives frameNavigated/loadEventFired so polyfills are available for
|
||||||
|
// subsequent CDP commands.
|
||||||
|
if (bc.scripts_on_new_document.items.len > 0) {
|
||||||
|
var ls: js.Local.Scope = undefined;
|
||||||
|
page.js.localScope(&ls);
|
||||||
|
defer ls.deinit();
|
||||||
|
|
||||||
|
for (bc.scripts_on_new_document.items) |script| {
|
||||||
|
ls.local.eval(script.source, null) catch |err| {
|
||||||
|
log.warn(.cdp, "script on new doc failed", .{ .err = err });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// frameNavigated event
|
// frameNavigated event
|
||||||
try cdp.sendEvent("Page.frameNavigated", .{
|
try cdp.sendEvent("Page.frameNavigated", .{
|
||||||
.type = "Navigation",
|
.type = "Navigation",
|
||||||
@@ -840,3 +888,38 @@ test "cdp.page: reload" {
|
|||||||
try ctx.processMessage(.{ .id = 32, .method = "Page.reload", .params = .{ .ignoreCache = true } });
|
try ctx.processMessage(.{ .id = 32, .method = "Page.reload", .params = .{ .ignoreCache = true } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "cdp.page: addScriptToEvaluateOnNewDocument" {
|
||||||
|
var ctx = try testing.context();
|
||||||
|
defer ctx.deinit();
|
||||||
|
|
||||||
|
_ = try ctx.loadBrowserContext(.{ .id = "BID-9", .url = "hi.html", .target_id = "FID-000000000X".* });
|
||||||
|
|
||||||
|
{
|
||||||
|
// Register a script — should return unique identifier "1"
|
||||||
|
try ctx.processMessage(.{ .id = 20, .method = "Page.addScriptToEvaluateOnNewDocument", .params = .{ .source = "window.__test = 1" } });
|
||||||
|
try ctx.expectSentResult(.{
|
||||||
|
.identifier = "1",
|
||||||
|
}, .{ .id = 20 });
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Register another script — should return identifier "2"
|
||||||
|
try ctx.processMessage(.{ .id = 21, .method = "Page.addScriptToEvaluateOnNewDocument", .params = .{ .source = "window.__test2 = 2" } });
|
||||||
|
try ctx.expectSentResult(.{
|
||||||
|
.identifier = "2",
|
||||||
|
}, .{ .id = 21 });
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Remove the first script — should succeed
|
||||||
|
try ctx.processMessage(.{ .id = 22, .method = "Page.removeScriptToEvaluateOnNewDocument", .params = .{ .identifier = "1" } });
|
||||||
|
try ctx.expectSentResult(null, .{ .id = 22 });
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Remove a non-existent identifier — should succeed silently
|
||||||
|
try ctx.processMessage(.{ .id = 23, .method = "Page.removeScriptToEvaluateOnNewDocument", .params = .{ .identifier = "999" } });
|
||||||
|
try ctx.expectSentResult(null, .{ .id = 23 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user