diff --git a/src/browser/browser.zig b/src/browser/browser.zig index a357c7da..0af2b423 100644 --- a/src/browser/browser.zig +++ b/src/browser/browser.zig @@ -309,7 +309,7 @@ pub const Session = struct { fn contextCreated(self: *Session, page: *Page) void { log.debug("inspector context created", .{}); - self.inspector.contextCreated(self.executor, "", (page.origin() catch "://") orelse "://", self.aux_data); + self.inspector.contextCreated(self.executor, "", (page.origin() catch "://") orelse "://", aux_data, true); } fn notify(self: *const Session, notification: *const Notification) void { diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig index 146fc84e..a32e61ae 100644 --- a/src/cdp/cdp.zig +++ b/src/cdp/cdp.zig @@ -306,6 +306,26 @@ pub fn BrowserContext(comptime CDP_T: type) type { node_registry: Node.Registry, node_search_list: Node.Search.List, + isolated_world: ?IsolatedWorld, + + pub fn createIsolatedWorld( + self: *Self, + world_name: []const u8, + grant_universal_access: bool, + ) !void { + if (self.isolated_world != null) return error.AlreadyExists; + + const executor = try self.cdp.browser.env.startExecutor(@import("../browser/html/window.zig").Window, &self.session.state, self.session); + errdefer self.cdp.browser.env.stopExecutor(executor); + executor.context.exit(); + + self.isolated_world = .{ + .name = try self.session.arena.allocator().dupe(u8, world_name), // TODO allocator + .grant_universal_access = grant_universal_access, + .executor = executor, + }; + } + const Self = @This(); fn init(self: *Self, id: []const u8, cdp: *CDP_T) !void { @@ -326,6 +346,7 @@ pub fn BrowserContext(comptime CDP_T: type) type { .page_life_cycle_events = false, // TODO; Target based value .node_registry = registry, .node_search_list = undefined, + .isolated_world = null, }; self.node_search_list = Node.Search.List.init(allocator, &self.node_registry); } @@ -437,6 +458,20 @@ pub fn BrowserContext(comptime CDP_T: type) type { }; } +/// The current understanding. An isolated world lives in the same isolate, but a separated context. +/// Clients creates 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 +/// a executionContextsCleared event which happens when navigating to a new page. A client can have a command be executed +/// in the isolated world by using its Context ID or the worldName. +/// grantUniveralAccess Indecated whether the isolated world has access to objects like the DOM or other JS Objects. +/// Generally the client needs to resolve a node into the isolated world to be able to work with it. +/// An object id is unique across all contexts, different object ids can refer to the same Node in different contexts. +pub const IsolatedWorld = struct { + name: []const u8, + grant_universal_access: bool, + executor: *@import("../browser/env.zig").Env.Executor, +}; + // This is a generic because when we send a result we have two different // behaviors. Normally, we're sending the result to the client. But in some cases // we want to capture the result. So we want the command.sendResult to be diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig index a51a4059..110f11c5 100644 --- a/src/cdp/domains/dom.zig +++ b/src/cdp/domains/dom.zig @@ -127,12 +127,18 @@ fn resolveNode(cmd: anytype) !void { objectGroup: ?[]const u8 = null, executionContextId: ?u32 = null, })) orelse return error.InvalidParams; - if (params.nodeId == null or params.backendNodeId != null or params.executionContextId != null) { - return error.NotYetImplementedParams; - } - const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; - const node = bc.node_registry.lookup_by_id.get(params.nodeId.?) orelse return error.UnknownNode; + + var executor = bc.session.executor; + if (params.executionContextId) |context_id| { + if (executor.context.debugContextId() != context_id) { + const isolated_world = bc.isolated_world orelse return error.ContextNotFound; + executor = isolated_world.executor; + if (executor.context.debugContextId() != context_id) return error.ContextNotFound; + } + } + const input_node_id = if (params.nodeId) |node_id| node_id else params.backendNodeId orelse return error.InvalidParams; + const node = bc.node_registry.lookup_by_id.get(input_node_id) orelse return error.UnknownNode; // node._node is a *parser.Node we need this to be able to find its most derived type e.g. Node -> Element -> HTMLElement // So we use the Node.Union when retrieve the value from the environment diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index 94c569ab..868b5499 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -97,36 +97,27 @@ fn addScriptToEvaluateOnNewDocument(cmd: anytype) !void { }, .{}); } -// TODO: hard coded method fn createIsolatedWorld(cmd: anytype) !void { - _ = cmd.browser_context orelse return error.BrowserContextNotLoaded; - - const session_id = cmd.input.session_id orelse return error.SessionIdRequired; - const params = (try cmd.params(struct { frameId: []const u8, worldName: []const u8, grantUniveralAccess: bool, })) orelse return error.InvalidParams; + if (!params.grantUniveralAccess) { + std.debug.print("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. + } + const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; - // noop executionContextCreated event - try cmd.sendEvent("Runtime.executionContextCreated", .{ - .context = runtime.ExecutionContextCreated{ - .id = 0, - .origin = "", - .name = params.worldName, - // TODO: hard coded ID - .uniqueId = "7102379147004877974.3265385113993241162", - .auxData = .{ - .isDefault = false, - .type = "isolated", - .frameId = params.frameId, - }, - }, - }, .{ .session_id = session_id }); + try bc.createIsolatedWorld(params.worldName, params.grantUniveralAccess); // orelse return error.IsolatedWorldAlreadyExists; + + // Create the auxdata json from + const aux_json = try std.fmt.allocPrint(cmd.arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{params.frameId}); + bc.session.inspector.contextCreated(bc.isolated_world.?.executor, bc.isolated_world.?.name, "", aux_json, false); return cmd.sendResult(.{ - .executionContextId = 0, + .executionContextId = bc.isolated_world.?.executor.context.debugContextId(), }, .{}); } @@ -222,7 +213,24 @@ pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void // Send Runtime.executionContextsCleared event // TODO: noop event, we have no env context at this point, is it necesarry? + // When we actually recreated the context we should have the inspector send this event, see: resetContextGroup try cdp.sendEvent("Runtime.executionContextsCleared", null, .{ .session_id = session_id }); + + if (bc.isolated_world != null) { + const aux_json = try std.fmt.allocPrint( + bc.session.arena.allocator(), // TODO change this + "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", + .{bc.target_id.?}, // TODO check this + ); + + bc.session.inspector.contextCreated( + bc.isolated_world.?.executor, + bc.isolated_world.?.name, + "://", + aux_json, + false, + ); + } } pub fn pageNavigated(bc: anytype, event: *const Notification.PageNavigated) !void { diff --git a/src/runtime/js.zig b/src/runtime/js.zig index b9869067..4d7770e6 100644 --- a/src/runtime/js.zig +++ b/src/runtime/js.zig @@ -1359,14 +1359,23 @@ pub fn Env(comptime S: type, comptime types: anytype) type { self.session.dispatchProtocolMessage(self.isolate, msg); } + // From CDP docs + // https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-ExecutionContextDescription + // ---- + // - name: Human readable name describing given context. + // - origin: Execution context origin (ie. URL who initialised the request) + // - auxData: Embedder-specific auxiliary data likely matching + // {isDefault: boolean, type: 'default'|'isolated'|'worker', frameId: string} + // - is_default_context: Whether the execution context is default, should match the auxData pub fn contextCreated( self: *const Inspector, executor: *const Executor, name: []const u8, origin: []const u8, aux_data: ?[]const u8, + is_default_context: bool, ) void { - self.inner.contextCreated(executor.context, name, origin, aux_data); + self.inner.contextCreated(executor.context, name, origin, aux_data, is_default_context); } // Retrieves the RemoteObject for a given value.