Skip to content
Merged
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
6 changes: 4 additions & 2 deletions src/browser/css/parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,8 @@ pub const Parser = struct {
// nameStart returns whether c can be the first character of an identifier
// (not counting an initial hyphen, or an escape sequence).
fn nameStart(c: u8) bool {
return 'a' <= c and c <= 'z' or 'A' <= c and c <= 'Z' or c == '_' or c > 127;
return 'a' <= c and c <= 'z' or 'A' <= c and c <= 'Z' or c == '_' or c > 127 or
'0' <= c and c <= '9';
}

// nameChar returns whether c can be a character within an identifier
Expand Down Expand Up @@ -890,7 +891,7 @@ test "parser.parseIdentifier" {
err: bool = false,
}{
.{ .s = "x", .exp = "x" },
.{ .s = "96", .exp = "", .err = true },
.{ .s = "96", .exp = "96", .err = false },
.{ .s = "-x", .exp = "-x" },
.{ .s = "r\\e9 sumé", .exp = "résumé" },
.{ .s = "r\\0000e9 sumé", .exp = "résumé" },
Expand Down Expand Up @@ -975,6 +976,7 @@ test "parser.parse" {
.{ .s = ":root", .exp = .{ .pseudo_class = .root } },
.{ .s = ".\\:bar", .exp = .{ .class = ":bar" } },
.{ .s = ".foo\\:bar", .exp = .{ .class = "foo:bar" } },
.{ .s = "[class=75c0fa18a94b9e3a6b8e14d6cbe688a27f5da10a]", .exp = .{ .attribute = .{ .key = "class", .val = "75c0fa18a94b9e3a6b8e14d6cbe688a27f5da10a", .op = .eql } } },
};

for (testcases) |tc| {
Expand Down
5 changes: 5 additions & 0 deletions src/browser/css/selector.zig
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,11 @@ test "Browser.CSS.Selector: matchFirst" {
.n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } },
.exp = 0,
},
.{
.q = "[foo=1baz]",
.n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } },
.exp = 0,
},
.{
.q = "[foo!=bar]",
.n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } },
Expand Down
50 changes: 35 additions & 15 deletions src/cdp/cdp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,18 @@ pub fn CDPT(comptime TypeProvider: type) type {
if (std.mem.eql(u8, input_session_id, "STARTUP")) {
is_startup = true;
} else if (self.isValidSessionId(input_session_id) == false) {
return command.sendError(-32001, "Unknown sessionId");
return command.sendError(-32001, "Unknown sessionId", .{});
}
}

if (is_startup) {
dispatchStartupCommand(&command) catch |err| {
command.sendError(-31999, @errorName(err)) catch {};
command.sendError(-31999, @errorName(err), .{}) catch {};
return err;
};
} else {
dispatchCommand(&command, input.method) catch |err| {
command.sendError(-31998, @errorName(err)) catch {};
command.sendError(-31998, @errorName(err), .{}) catch {};
return err;
};
}
Expand Down Expand Up @@ -331,7 +331,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
node_search_list: Node.Search.List,

inspector: Inspector,
isolated_world: ?IsolatedWorld,
isolated_worlds: std.ArrayListUnmanaged(IsolatedWorld),

http_proxy_changed: bool = false,

Expand Down Expand Up @@ -375,7 +375,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,
.isolated_worlds = .empty,
.inspector = inspector,
.notification_arena = cdp.notification_arena.allocator(),
.intercept_state = try InterceptState.init(allocator),
Expand Down Expand Up @@ -404,9 +404,10 @@ pub fn BrowserContext(comptime CDP_T: type) type {
// so we need to shutdown the page one first.
self.cdp.browser.closeSession();

if (self.isolated_world) |*world| {
for (self.isolated_worlds.items) |*world| {
world.deinit();
}
self.isolated_worlds.clearRetainingCapacity();
self.node_registry.deinit();
self.node_search_list.deinit();
self.cdp.browser.notification.unregisterAll(self);
Expand All @@ -427,19 +428,19 @@ pub fn BrowserContext(comptime CDP_T: type) type {
}

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

var executor = try self.cdp.browser.env.newExecutionWorld();
errdefer executor.deinit();

self.isolated_world = .{
.name = try self.arena.dupe(u8, world_name),
const owned_name = try self.arena.dupe(u8, world_name);
const world = try self.isolated_worlds.addOne(self.arena);

world.* = .{
.name = owned_name,
.executor = executor,
.grant_universal_access = grant_universal_access,
};
return &self.isolated_world.?;

return world;
}

pub fn nodeWriter(self: *Self, root: *const Node, opts: Node.Writer.Opts) Node.Writer {
Expand Down Expand Up @@ -682,7 +683,14 @@ const IsolatedWorld = struct {
// 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 {
if (self.executor.js_context != null) return error.Only1IsolatedContextSupported;
// if (self.executor.js_context != null) return error.Only1IsolatedContextSupported;
if (self.executor.js_context != null) {
log.warn(.cdp, "not implemented", .{
.feature = "createContext: Not implemented second isolated context creation",
.info = "reuse existing context",
});
return;
}
_ = try self.executor.createJsContext(
&page.window,
page,
Expand All @@ -691,6 +699,14 @@ const IsolatedWorld = struct {
Env.GlobalMissingCallback.init(&self.polyfill_loader),
);
}

pub fn createContextAndLoadPolyfills(self: *IsolatedWorld, arena: Allocator, page: *Page) !void {
// We need to recreate the isolated world context
try self.createContext(page);

const loader = @import("../browser/polyfill/polyfill.zig");
try loader.preload(arena, &self.executor.js_context.?);
}
};

// This is a generic because when we send a result we have two different
Expand Down Expand Up @@ -757,10 +773,14 @@ pub fn Command(comptime CDP_T: type, comptime Sender: type) type {
return self.cdp.sendEvent(method, p, opts);
}

pub fn sendError(self: *Self, code: i32, message: []const u8) !void {
const SendErrorOpts = struct {
include_session_id: bool = true,
};
pub fn sendError(self: *Self, code: i32, message: []const u8, opts: SendErrorOpts) !void {
return self.sender.sendJSON(.{
.id = self.input.id,
.@"error" = .{ .code = code, .message = message },
.sessionId = if (opts.include_session_id) self.input.session_id else null,
});
}

Expand Down
62 changes: 49 additions & 13 deletions src/cdp/domains/dom.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

const std = @import("std");
const log = @import("../../log.zig");
const Allocator = std.mem.Allocator;
const Node = @import("../Node.zig");
const css = @import("../../browser/dom/css.zig");
Expand All @@ -39,6 +40,7 @@ pub fn processMessage(cmd: anytype) !void {
getContentQuads,
getBoxModel,
requestChildNodes,
getFrameOwner,
}, cmd.input.action) orelse return error.UnknownMethod;

switch (action) {
Expand All @@ -55,6 +57,7 @@ pub fn processMessage(cmd: anytype) !void {
.getContentQuads => return getContentQuads(cmd),
.getBoxModel => return getBoxModel(cmd),
.requestChildNodes => return requestChildNodes(cmd),
.getFrameOwner => return getFrameOwner(cmd),
}
}

Expand All @@ -67,6 +70,10 @@ fn getDocument(cmd: anytype) !void {
};
const params = try cmd.params(Params) orelse Params{};

if (params.pierce) {
log.warn(.cdp, "not implemented", .{ .feature = "DOM.getDocument: Not implemented pierce parameter" });
}

const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const doc = parser.documentHTMLToDocument(page.window.document);
Expand Down Expand Up @@ -206,7 +213,9 @@ fn querySelector(cmd: anytype) !void {

const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;

const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse return error.UnknownNode;
const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
return cmd.sendError(-32000, "Could not find node with given id", .{});
};

const selected_node = try css.querySelector(
cmd.arena,
Expand All @@ -233,7 +242,9 @@ fn querySelectorAll(cmd: anytype) !void {

const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;

const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse return error.UnknownNode;
const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
return cmd.sendError(-32000, "Could not find node with given id", .{});
};

const arena = cmd.arena;
const selected_nodes = try css.querySelectorAll(arena, node._node, params.selector);
Expand Down Expand Up @@ -266,10 +277,12 @@ fn resolveNode(cmd: anytype) !void {
var js_context = page.main_context;
if (params.executionContextId) |context_id| {
if (js_context.v8_context.debugContextId() != context_id) {
var isolated_world = bc.isolated_world orelse return error.ContextNotFound;
js_context = &(isolated_world.executor.js_context orelse return error.ContextNotFound);

if (js_context.v8_context.debugContextId() != context_id) return error.ContextNotFound;
for (bc.isolated_worlds.items) |*isolated_world| {
js_context = &(isolated_world.executor.js_context orelse return error.ContextNotFound);
if (js_context.v8_context.debugContextId() == context_id) {
break;
}
} else return error.ContextNotFound;
}
}

Expand Down Expand Up @@ -300,16 +313,18 @@ fn describeNode(cmd: anytype) !void {
nodeId: ?Node.Id = null,
backendNodeId: ?Node.Id = null,
objectId: ?[]const u8 = null,
depth: u32 = 1,
depth: i32 = 1,
pierce: bool = false,
})) orelse return error.InvalidParams;

if (params.depth != 1 or params.pierce) return error.NotImplemented;
if (params.pierce) {
log.warn(.cdp, "not implemented", .{ .feature = "DOM.describeNode: Not implemented pierce parameter" });
}
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;

const node = try getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId);

return cmd.sendResult(.{ .node = bc.nodeWriter(node, .{}) }, .{});
return cmd.sendResult(.{ .node = bc.nodeWriter(node, .{ .depth = params.depth }) }, .{});
}

// An array of quad vertices, x immediately followed by y for each point, points clock-wise.
Expand Down Expand Up @@ -461,6 +476,24 @@ fn requestChildNodes(cmd: anytype) !void {
return cmd.sendResult(null, .{});
}

fn getFrameOwner(cmd: anytype) !void {
const params = (try cmd.params(struct {
frameId: []const u8,
})) orelse return error.InvalidParams;

const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const target_id = bc.target_id orelse return error.TargetNotLoaded;
if (std.mem.eql(u8, target_id, params.frameId) == false) {
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
}

const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const doc = parser.documentHTMLToDocument(page.window.document);

const node = try bc.node_registry.register(parser.documentToNode(doc));
return cmd.sendResult(.{ .nodeId = node.id, .backendNodeId = node.id }, .{});
}

const testing = @import("../testing.zig");

test "cdp.dom: getSearchResults unknown search id" {
Expand Down Expand Up @@ -534,16 +567,19 @@ test "cdp.dom: querySelector unknown search id" {

_ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "<p>1</p> <p>2</p>" });

try testing.expectError(error.UnknownNode, ctx.processMessage(.{
try ctx.processMessage(.{
.id = 9,
.method = "DOM.querySelector",
.params = .{ .nodeId = 99, .selector = "" },
}));
try testing.expectError(error.UnknownNode, ctx.processMessage(.{
});
try ctx.expectSentError(-32000, "Could not find node with given id", .{});

try ctx.processMessage(.{
.id = 9,
.method = "DOM.querySelectorAll",
.params = .{ .nodeId = 99, .selector = "" },
}));
});
try ctx.expectSentError(-32000, "Could not find node with given id", .{});
}

test "cdp.dom: querySelector Node not found" {
Expand Down
9 changes: 8 additions & 1 deletion src/cdp/domains/network.zig
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,14 @@ pub fn httpRequestStart(arena: Allocator, bc: anytype, msg: *const Notification.

const transfer = msg.transfer;
// We're missing a bunch of fields, but, for now, this seems like enough
try bc.cdp.sendEvent("Network.requestWillBeSent", .{ .requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}), .frameId = target_id, .loaderId = bc.loader_id, .documentUrl = DocumentUrlWriter.init(&page.url.uri), .request = TransferAsRequestWriter.init(transfer) }, .{ .session_id = session_id });
try bc.cdp.sendEvent("Network.requestWillBeSent", .{
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
.frameId = target_id,
.loaderId = bc.loader_id,
.documentUrl = DocumentUrlWriter.init(&page.url.uri),
.request = TransferAsRequestWriter.init(transfer),
.initiator = .{ .type = "other" },
}, .{ .session_id = session_id });
}

pub fn httpResponseHeaderDone(arena: Allocator, bc: anytype, msg: *const Notification.ResponseHeaderDone) !void {
Expand Down
14 changes: 5 additions & 9 deletions src/cdp/domains/page.zig
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ fn createIsolatedWorld(cmd: anytype) !void {

const world = try bc.createIsolatedWorld(params.worldName, params.grantUniveralAccess);
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
try pageCreated(bc, page);
try world.createContextAndLoadPolyfills(bc.arena, page);
const js_context = &world.executor.js_context.?;

// Create the auxdata json for the contextCreated event
Expand Down Expand Up @@ -259,7 +259,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
true,
);
}
if (bc.isolated_world) |*isolated_world| {
for (bc.isolated_worlds.items) |*isolated_world| {
const aux_json = try std.fmt.allocPrint(arena, "{{\"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(
Expand All @@ -274,18 +274,14 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa

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| {
for (bc.isolated_worlds.items) |*isolated_world| {
try 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.preload(bc.arena, &isolated_world.executor.js_context.?);
for (bc.isolated_worlds.items) |*isolated_world| {
try isolated_world.createContextAndLoadPolyfills(bc.arena, page);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/cdp/domains/runtime.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub fn processMessage(cmd: anytype) !void {
addBinding,
callFunctionOn,
releaseObject,
getProperties,
}, cmd.input.action) orelse return error.UnknownMethod;

switch (action) {
Expand Down
Loading