Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ download-libiconv:
ifeq ("$(wildcard vendor/libiconv/libiconv-1.17)","")
@mkdir -p vendor/libiconv
@cd vendor/libiconv && \
curl https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.17.tar.gz | tar -xvzf -
curl https://ftpmirror.gnu.org/libiconv/libiconv-1.17.tar.gz | tar -xvzf -
endif

install-libiconv: download-libiconv clean-libiconv
Expand Down
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
.hash = "tigerbeetle_io-0.0.0-ViLgxpyRBAB5BMfIcj3KMXfbJzwARs9uSl8aRy2OXULd",
},
.v8 = .{
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/5790c80fcd12dec64e596f6f66f09de567020e8a.tar.gz",
.hash = "v8-0.0.0-xddH66roIAAdXNJpBKN_NO8zBz2H8b9moUzshBCfns2p",
.url = "https://github.com/lightpanda-io/zig-v8-fork/archive/97bcfb61da8c97de1321d677a6727a927a9db9a4.tar.gz",
.hash = "v8-0.0.0-xddH69DoIADZ8YXZ_EIx_tKdQKEoGsgob_3_ZIi0O_nV",
},
//.v8 = .{ .path = "../zig-v8-fork" },
//.tigerbeetle_io = .{ .path = "../tigerbeetle-io" },
Expand Down
6 changes: 4 additions & 2 deletions src/browser/browser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub const Browser = struct {
http_client: *http.Client,
session_pool: SessionPool,
page_arena: std.heap.ArenaAllocator,
pub const EnvType = Env;

const SessionPool = std.heap.MemoryPool(Session);

Expand Down Expand Up @@ -126,6 +127,7 @@ pub const Session = struct {
//
// The arena is initialised with self.alloc allocator.
// all others Session deps use directly self.alloc and not the arena.
// The arena is also used in the BrowserContext
arena: std.heap.ArenaAllocator,

window: Window,
Expand Down Expand Up @@ -191,7 +193,7 @@ pub const Session = struct {
self.state.cookie_jar = &self.cookie_jar;
errdefer self.arena.deinit();

self.executor = try browser.env.startExecutor(Window, &self.state, self);
self.executor = try browser.env.startExecutor(Window, &self.state, self, .main);
errdefer browser.env.stopExecutor(self.executor);
self.inspector = try Env.Inspector.init(self.arena.allocator(), self.executor, ctx);

Expand Down Expand Up @@ -309,7 +311,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 "://", self.aux_data, true);
}

fn notify(self: *const Session, notification: *const Notification) void {
Expand Down
54 changes: 53 additions & 1 deletion src/cdp/cdp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ pub fn BrowserContext(comptime CDP_T: type) type {
// RELATION TO SESSION_ID
session: *CDP_T.Session,

// Points to the session arena
arena: Allocator,

// Maps to our Page. (There are other types of targets, but we only
// deal with "pages" for now). Since we only allow 1 open page at a
// time, we only have 1 target_id.
Expand All @@ -306,6 +309,8 @@ pub fn BrowserContext(comptime CDP_T: type) type {
node_registry: Node.Registry,
node_search_list: Node.Search.List,

isolated_world: ?IsolatedWorld(CDP_T.Browser.EnvType),

const Self = @This();

fn init(self: *Self, id: []const u8, cdp: *CDP_T) !void {
Expand All @@ -314,6 +319,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
var registry = Node.Registry.init(allocator);
errdefer registry.deinit();

const session = try cdp.browser.newSession(self);
self.* = .{
.id = id,
.cdp = cdp,
Expand All @@ -322,15 +328,22 @@ pub fn BrowserContext(comptime CDP_T: type) type {
.security_origin = URL_BASE,
.secure_context_type = "Secure", // TODO = enum
.loader_id = LOADER_ID,
.session = try cdp.browser.newSession(self),
.session = session,
.arena = session.arena.allocator(),
.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);
}

pub fn deinit(self: *Self) void {
if (self.isolated_world) |isolated_world| {
isolated_world.executor.endScope();
self.cdp.browser.env.stopExecutor(isolated_world.executor);
self.isolated_world = null;
}
self.node_registry.deinit();
self.node_search_list.deinit();
}
Expand All @@ -340,6 +353,27 @@ pub fn BrowserContext(comptime CDP_T: type) type {
self.node_search_list.reset();
}

pub fn createIsolatedWorld(
self: *Self,
world_name: []const u8,
grant_universal_access: bool,
) !void {
if (self.isolated_world != null) return error.CurrentlyOnly1IsolatedWorldSupported;

const executor = try self.cdp.browser.env.startExecutor(@import("../browser/html/window.zig").Window, &self.session.state, self.session, .isolated);
errdefer self.cdp.browser.env.stopExecutor(executor);

// TBD should we endScope on removePage and re-startScope on createPage?
// Window will be refactored into the executor so we leave it ugly here for now as a reminder.
try executor.startScope(@import("../browser/html/window.zig").Window{});

self.isolated_world = .{
.name = try self.arena.dupe(u8, world_name),
.grant_universal_access = grant_universal_access,
.executor = executor,
};
}

pub fn nodeWriter(self: *Self, node: *const Node, opts: Node.Writer.Opts) Node.Writer {
return .{
.node = node,
Expand Down Expand Up @@ -437,6 +471,24 @@ pub fn BrowserContext(comptime CDP_T: type) type {
};
}

/// see: https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md#world
/// The current understanding. An isolated world lives in the same isolate, but a separated context.
/// 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
/// 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 can reference objects like the DOM or other JS Objects.
/// An isolated world has it's own instance of globals like Window.
/// 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 fn IsolatedWorld(comptime E: type) type {
return struct {
name: []const u8,
grant_universal_access: bool,
executor: *E.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
Expand Down
19 changes: 13 additions & 6 deletions src/cdp/domains/dom.zig
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,24 @@ 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
const remote_object = try bc.session.inspector.getRemoteObject(
bc.session.executor,
executor,
params.objectGroup orelse "",
try dom_node.Node.toInterface(node._node),
);
Expand Down
58 changes: 33 additions & 25 deletions src/cdp/domains/page.zig
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ fn setLifecycleEventsEnabled(cmd: anytype) !void {
}

// 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 {
// const params = (try cmd.params(struct {
// source: []const u8,
Expand All @@ -97,37 +99,28 @@ 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);
const world = &bc.isolated_world.?;

return cmd.sendResult(.{
.executionContextId = 0,
}, .{});
// 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.session.inspector.contextCreated(world.executor, world.name, "", aux_data, false);

return cmd.sendResult(.{ .executionContextId = world.executor.context.debugContextId() }, .{});
}

fn navigate(cmd: anytype) !void {
Expand Down Expand Up @@ -220,9 +213,24 @@ pub fn pageNavigate(bc: anytype, event: *const Notification.PageNavigate) !void
}, .{ .session_id = session_id });
}

// 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
// Sending this event will tell the client that the context ids they had are invalid and the context shouls be dropped
// The client will expect us to send new contextCreated events, such that the client has new id's for the active contexts.
try cdp.sendEvent("Runtime.executionContextsCleared", null, .{ .session_id = session_id });

if (bc.isolated_world) |*isolated_world| {
var buffer: [256]u8 = undefined;
const aux_json = try std.fmt.bufPrint(&buffer, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{bc.target_id.?});

// Calling contextCreated will assign a new Id to the context and send the contextCreated event
bc.session.inspector.contextCreated(
isolated_world.executor,
isolated_world.name,
"://",
aux_json,
false,
);
}
}

pub fn pageNavigated(bc: anytype, event: *const Notification.PageNavigated) !void {
Expand Down
14 changes: 0 additions & 14 deletions src/cdp/domains/runtime.zig
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,6 @@ fn sendInspector(cmd: anytype, action: anytype) !void {
cmd.cdp.browser.runMicrotasks();
}

pub const ExecutionContextCreated = struct {
id: u64,
origin: []const u8,
name: []const u8,
uniqueId: []const u8,
auxData: ?AuxData = null,

pub const AuxData = struct {
isDefault: bool = true,
type: []const u8 = "default",
frameId: []const u8,
};
};

fn logInspector(cmd: anytype, action: anytype) !void {
const script = switch (action) {
.evaluate => blk: {
Expand Down
Loading
Loading