@@ -37,6 +37,7 @@ pub fn processMessage(cmd: anytype) !void {
3737 getFrameTree ,
3838 setLifecycleEventsEnabled ,
3939 addScriptToEvaluateOnNewDocument ,
40+ removeScriptToEvaluateOnNewDocument ,
4041 createIsolatedWorld ,
4142 navigate ,
4243 reload ,
@@ -51,6 +52,7 @@ pub fn processMessage(cmd: anytype) !void {
5152 .getFrameTree = > return getFrameTree (cmd ),
5253 .setLifecycleEventsEnabled = > return setLifecycleEventsEnabled (cmd ),
5354 .addScriptToEvaluateOnNewDocument = > return addScriptToEvaluateOnNewDocument (cmd ),
55+ .removeScriptToEvaluateOnNewDocument = > return removeScriptToEvaluateOnNewDocument (cmd ),
5456 .createIsolatedWorld = > return createIsolatedWorld (cmd ),
5557 .navigate = > return navigate (cmd ),
5658 .reload = > return doReload (cmd ),
@@ -147,22 +149,55 @@ fn setLifecycleEventsEnabled(cmd: anytype) !void {
147149 return cmd .sendResult (null , .{});
148150}
149151
150- // TODO: hard coded method
151- // With the command we receive a script we need to store and run for each new document.
152- // Note that the worldName refers to the name given to the isolated world.
153152fn addScriptToEvaluateOnNewDocument (cmd : anytype ) ! void {
154- // const params = (try cmd.params(struct {
155- // source: []const u8,
156- // worldName: ?[]const u8 = null,
157- // includeCommandLineAPI: bool = false,
158- // runImmediately: bool = false,
159- // })) orelse return error.InvalidParams;
153+ const params = (try cmd .params (struct {
154+ source : []const u8 ,
155+ worldName : ? []const u8 = null ,
156+ includeCommandLineAPI : bool = false ,
157+ runImmediately : bool = false ,
158+ })) orelse return error .InvalidParams ;
159+
160+ const bc = cmd .browser_context orelse return error .BrowserContextNotLoaded ;
160161
162+ if (params .runImmediately ) {
163+ log .warn (.not_implemented , "addScriptOnNewDocument" , .{ .param = "runImmediately" });
164+ }
165+
166+ const script_id = bc .next_script_id ;
167+ bc .next_script_id += 1 ;
168+
169+ const source_dupe = try bc .arena .dupe (u8 , params .source );
170+ try bc .scripts_on_new_document .append (bc .arena , .{
171+ .identifier = script_id ,
172+ .source = source_dupe ,
173+ });
174+
175+ var id_buf : [16 ]u8 = undefined ;
176+ const id_str = std .fmt .bufPrint (& id_buf , "{d}" , .{script_id }) catch "1" ;
161177 return cmd .sendResult (.{
162- .identifier = "1" ,
178+ .identifier = id_str ,
163179 }, .{});
164180}
165181
182+ fn removeScriptToEvaluateOnNewDocument (cmd : anytype ) ! void {
183+ const params = (try cmd .params (struct {
184+ identifier : []const u8 ,
185+ })) orelse return error .InvalidParams ;
186+
187+ const bc = cmd .browser_context orelse return error .BrowserContextNotLoaded ;
188+
189+ const target_id = std .fmt .parseInt (u32 , params .identifier , 10 ) catch
190+ return cmd .sendResult (null , .{});
191+
192+ for (bc .scripts_on_new_document .items , 0.. ) | script , i | {
193+ if (script .identifier == target_id ) {
194+ _ = bc .scripts_on_new_document .orderedRemove (i );
195+ break ;
196+ }
197+ }
198+ return cmd .sendResult (null , .{});
199+ }
200+
166201fn close (cmd : anytype ) ! void {
167202 const bc = cmd .browser_context orelse return error .BrowserContextNotLoaded ;
168203
@@ -482,6 +517,27 @@ pub fn pageNavigated(arena: Allocator, bc: anytype, event: *const Notification.P
482517 );
483518 }
484519
520+ // Evaluate scripts registered via Page.addScriptToEvaluateOnNewDocument.
521+ // Must run after the execution context is created but before the client
522+ // receives frameNavigated/loadEventFired so polyfills are available for
523+ // subsequent CDP commands.
524+ if (bc .scripts_on_new_document .items .len > 0 ) {
525+ var ls : js.Local.Scope = undefined ;
526+ page .js .localScope (& ls );
527+ defer ls .deinit ();
528+
529+ for (bc .scripts_on_new_document .items ) | script | {
530+ var try_catch : lp.js.TryCatch = undefined ;
531+ try_catch .init (& ls .local );
532+ defer try_catch .deinit ();
533+
534+ ls .local .eval (script .source , null ) catch | err | {
535+ const caught = try_catch .caughtOrError (arena , err );
536+ log .warn (.cdp , "script on new doc" , .{ .caught = caught });
537+ };
538+ }
539+ }
540+
485541 // frameNavigated event
486542 try cdp .sendEvent ("Page.frameNavigated" , .{
487543 .type = "Navigation" ,
@@ -840,3 +896,55 @@ test "cdp.page: reload" {
840896 try ctx .processMessage (.{ .id = 32 , .method = "Page.reload" , .params = .{ .ignoreCache = true } });
841897 }
842898}
899+
900+ test "cdp.page: addScriptToEvaluateOnNewDocument" {
901+ var ctx = try testing .context ();
902+ defer ctx .deinit ();
903+
904+ var bc = try ctx .loadBrowserContext (.{ .id = "BID-9" , .url = "hi.html" , .target_id = "FID-000000000X" .* });
905+
906+ {
907+ // Register a script — should return unique identifier "1"
908+ try ctx .processMessage (.{ .id = 20 , .method = "Page.addScriptToEvaluateOnNewDocument" , .params = .{ .source = "window.__test = 1" } });
909+ try ctx .expectSentResult (.{
910+ .identifier = "1" ,
911+ }, .{ .id = 20 });
912+ }
913+
914+ {
915+ // Register another script — should return identifier "2"
916+ try ctx .processMessage (.{ .id = 21 , .method = "Page.addScriptToEvaluateOnNewDocument" , .params = .{ .source = "window.__test2 = 2" } });
917+ try ctx .expectSentResult (.{
918+ .identifier = "2" ,
919+ }, .{ .id = 21 });
920+ }
921+
922+ {
923+ // Remove the first script — should succeed
924+ try ctx .processMessage (.{ .id = 22 , .method = "Page.removeScriptToEvaluateOnNewDocument" , .params = .{ .identifier = "1" } });
925+ try ctx .expectSentResult (null , .{ .id = 22 });
926+ }
927+
928+ {
929+ // Remove a non-existent identifier — should succeed silently
930+ try ctx .processMessage (.{ .id = 23 , .method = "Page.removeScriptToEvaluateOnNewDocument" , .params = .{ .identifier = "999" } });
931+ try ctx .expectSentResult (null , .{ .id = 23 });
932+ }
933+
934+ {
935+ try ctx .processMessage (.{ .id = 34 , .method = "Page.reload" });
936+ // wait for this event, which is sent after we've run the registered scripts
937+ try ctx .expectSentEvent ("Page.frameNavigated" , .{
938+ .frame = .{ .loaderId = "LID-0000000002" },
939+ }, .{});
940+
941+ const page = bc .session .currentPage () orelse unreachable ;
942+
943+ var ls : js.Local.Scope = undefined ;
944+ page .js .localScope (& ls );
945+ defer ls .deinit ();
946+
947+ const test_val = try ls .local .exec ("window.__test2" , null );
948+ try testing .expectEqual (2 , try test_val .toI32 ());
949+ }
950+ }
0 commit comments