From 8930e2f06e599906768c0633f54b9391402196d9 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Fri, 2 May 2025 10:56:25 +0200 Subject: [PATCH] isolated polyfill + create when needed --- src/browser/browser.zig | 5 +++++ src/cdp/cdp.zig | 45 ++++++++++++++++++++++++++++---------- src/cdp/domains/dom.zig | 2 +- src/cdp/domains/page.zig | 32 +++++++++++++++++++++------ src/cdp/domains/target.zig | 2 -- src/notification.zig | 6 +++++ 6 files changed, 70 insertions(+), 22 deletions(-) diff --git a/src/browser/browser.zig b/src/browser/browser.zig index 3d35b603..6d1ea5c0 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -163,11 +163,16 @@ pub const Session = struct { // start JS env log.debug("start new js scope", .{}); + // Inform CDP the main page has been created such that additional context for other Worlds can be created as well + self.browser.notification.dispatch(.page_created, page); return page; } pub fn removePage(self: *Session) void { + // Inform CDP the page is going to be removed, allowing other worlds to remove themselves before the main one + self.browser.notification.dispatch(.page_remove, .{}); + std.debug.assert(self.page != null); // Reset all existing callbacks. self.browser.app.loop.resetJS(); diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index d0a551a4..c70afcc0 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -340,6 +340,8 @@ pub fn BrowserContext(comptime CDP_T: type) type { self.node_search_list = Node.Search.List.init(allocator, &self.node_registry); errdefer self.deinit(); + try cdp.browser.notification.register(.page_remove, self, onPageRemove); + try cdp.browser.notification.register(.page_created, self, onPageCreated); try cdp.browser.notification.register(.page_navigate, self, onPageNavigate); try cdp.browser.notification.register(.page_navigated, self, onPageNavigated); } @@ -365,7 +367,7 @@ pub fn BrowserContext(comptime CDP_T: type) type { self.node_search_list.reset(); } - pub fn createIsolatedWorld(self: *Self, page: *Page) !void { + pub fn createIsolatedWorld(self: *Self, page: *Page, world_name: []const u8, grant_universal_access: bool) !*IsolatedWorld { if (self.isolated_world != null) { return error.CurrentlyOnly1IsolatedWorldSupported; } @@ -374,19 +376,14 @@ pub fn BrowserContext(comptime CDP_T: type) type { errdefer executor.deinit(); self.isolated_world = .{ - .name = "", + .name = try self.arena.dupe(u8, world_name), .scope = undefined, .executor = executor, - .grant_universal_access = true, + .grant_universal_access = grant_universal_access, }; - var world = &self.isolated_world.?; - - // The isolate world must share at least some of the state with the related page, specifically the DocumentHTML - // (assuming grantUniveralAccess will be set to True!). - // We just created the world and the page. The page's state lives in the session, but is update on navigation. - // This also means this pointer becomes invalid after removePage untill a new page is created. - // Currently we have only 1 page/frame and thus also only 1 state in the isolate world. - world.scope = try world.executor.startScope(&page.window, &page.state, {}, false); + const world = &self.isolated_world.?; + try world.createContext(page); + return world; } pub fn nodeWriter(self: *Self, node: *const Node, opts: Node.Writer.Opts) Node.Writer { @@ -403,6 +400,16 @@ pub fn BrowserContext(comptime CDP_T: type) type { return if (raw_url.len == 0) null else raw_url; } + pub fn onPageRemove(ctx: *anyopaque, _: Notification.PageRemove) !void { + const self: *Self = @alignCast(@ptrCast(ctx)); + return @import("domains/page.zig").pageRemove(self); + } + + pub fn onPageCreated(ctx: *anyopaque, page: *Page) !void { + const self: *Self = @alignCast(@ptrCast(ctx)); + return @import("domains/page.zig").pageCreated(self, page); + } + pub fn onPageNavigate(ctx: *anyopaque, data: *const Notification.PageNavigate) !void { const self: *Self = @alignCast(@ptrCast(ctx)); return @import("domains/page.zig").pageNavigate(self, data); @@ -506,12 +513,26 @@ pub fn BrowserContext(comptime CDP_T: type) type { /// An object id is unique across all contexts, different object ids can refer to the same Node in different contexts. const IsolatedWorld = struct { name: []const u8, - scope: *Env.Scope, + scope: ?*Env.Scope, executor: Env.Executor, grant_universal_access: bool, pub fn deinit(self: *IsolatedWorld) void { self.executor.deinit(); + self.scope = null; + } + pub fn removeContext(self: *IsolatedWorld) void { + self.executor.endScope(); + self.scope = null; + } + + // The isolate world must share at least some of the state with the related page, specifically the DocumentHTML + // (assuming grantUniveralAccess will be set to True!). + // We just created the world and the page. The page's state lives in the session, but is update on navigation. + // This also means this pointer becomes invalid after removePage untill a new page is created. + // Currently we have only 1 page/frame and thus also only 1 state in the isolate world. + pub fn createContext(self: *IsolatedWorld, page: *Page) !void { + self.scope = try self.executor.startScope(&page.window, &page.state, {}, false); } }; diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig index ceb9bc93..14a3db37 100644 --- a/src/cdp/domains/dom.zig +++ b/src/cdp/domains/dom.zig @@ -135,7 +135,7 @@ fn resolveNode(cmd: anytype) !void { if (params.executionContextId) |context_id| { if (scope.context.debugContextId() != context_id) { const isolated_world = bc.isolated_world orelse return error.ContextNotFound; - scope = isolated_world.scope; + scope = isolated_world.scope orelse return error.ContextNotFound; if (scope.context.debugContextId() != context_id) return error.ContextNotFound; } diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index 676db373..892360be 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -20,6 +20,7 @@ const std = @import("std"); const runtime = @import("runtime.zig"); const URL = @import("../../url.zig").URL; const Notification = @import("../../notification.zig").Notification; +const Page = @import("../../browser/browser.zig").Page; pub fn processMessage(cmd: anytype) !void { const action = std.meta.stringToEnum(enum { @@ -112,15 +113,16 @@ fn createIsolatedWorld(cmd: anytype) !void { } const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; - const world = &bc.isolated_world.?; - world.name = try bc.arena.dupe(u8, params.worldName); - world.grant_universal_access = params.grantUniveralAccess; + const page = bc.session.currentPage().?; + const world = try bc.createIsolatedWorld(page, params.worldName, params.grantUniveralAccess); + const scope = world.scope.?; + // Create the auxdata json for the contextCreated event // Calling contextCreated will assign a Id to the context and send the contextCreated event const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{params.frameId}); - bc.inspector.contextCreated(world.scope, world.name, "", aux_data, false); + bc.inspector.contextCreated(scope, world.name, "", aux_data, false); - return cmd.sendResult(.{ .executionContextId = world.scope.context.debugContextId() }, .{}); + return cmd.sendResult(.{ .executionContextId = scope.context.debugContextId() }, .{}); } fn navigate(cmd: anytype) !void { @@ -230,12 +232,11 @@ pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void true, ); } - if (bc.isolated_world) |*isolated_world| { const aux_json = try std.fmt.bufPrint(&buffer, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{target_id}); // Calling contextCreated will assign a new Id to the context and send the contextCreated event bc.inspector.contextCreated( - isolated_world.scope, + isolated_world.scope.?, isolated_world.name, "://", aux_json, @@ -244,6 +245,23 @@ pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void } } +pub fn pageRemove(bc: anytype) !void { + // The main page is going to be removed, we need to remove contexts from other worlds first. + if (bc.isolated_world) |*isolated_world| { + isolated_world.removeContext(); + } +} + +pub fn pageCreated(bc: anytype, page: *Page) !void { + if (bc.isolated_world) |*isolated_world| { + // We need to recreate the isolated world context + try isolated_world.createContext(page); + + const polyfill = @import("../../browser/polyfill/polyfill.zig"); + try polyfill.load(bc.arena, isolated_world.scope.?); + } +} + pub fn pageNavigated(bc: anytype, event: *const Notification.PageNavigated) !void { // I don't think it's possible that we get these notifications and don't // have these things setup. diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index 9df99bf0..fca78351 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -125,8 +125,6 @@ fn createTarget(cmd: anytype) !void { bc.target_id = target_id; var page = try bc.session.createPage(); - try bc.createIsolatedWorld(page); - { const aux_data = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":true,\"type\":\"default\",\"frameId\":\"{s}\"}}", .{target_id}); bc.inspector.contextCreated( diff --git a/src/notification.zig b/src/notification.zig index 5d655348..ab8c2511 100644 --- a/src/notification.zig +++ b/src/notification.zig @@ -55,18 +55,24 @@ pub const Notification = struct { node_pool: std.heap.MemoryPool(Node), const EventListeners = struct { + page_remove: List = .{}, + page_created: List = .{}, page_navigate: List = .{}, page_navigated: List = .{}, notification_created: List = .{}, }; const Events = union(enum) { + page_remove: PageRemove, + page_created: *browser.Page, page_navigate: *const PageNavigate, page_navigated: *const PageNavigated, notification_created: *Notification, }; const EventType = std.meta.FieldEnum(Events); + pub const PageRemove = struct {}; + pub const PageNavigate = struct { timestamp: u32, url: *const URL,