diff --git a/src/browser/css/parser.zig b/src/browser/css/parser.zig
index 63b863d8..d93fdb80 100644
--- a/src/browser/css/parser.zig
+++ b/src/browser/css/parser.zig
@@ -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
@@ -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é" },
@@ -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| {
diff --git a/src/browser/css/selector.zig b/src/browser/css/selector.zig
index 00ef1558..a6402efc 100644
--- a/src/browser/css/selector.zig
+++ b/src/browser/css/selector.zig
@@ -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" } } },
diff --git a/src/cdp/cdp.zig b/src/cdp/cdp.zig
index 3fcf14dc..3373d51d 100644
--- a/src/cdp/cdp.zig
+++ b/src/cdp/cdp.zig
@@ -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;
};
}
@@ -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,
@@ -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),
@@ -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);
@@ -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 {
@@ -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,
@@ -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
@@ -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,
});
}
diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig
index 64f24fdf..035918e8 100644
--- a/src/cdp/domains/dom.zig
+++ b/src/cdp/domains/dom.zig
@@ -17,6 +17,7 @@
// along with this program. If not, see
1
2
" }); - 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" { diff --git a/src/cdp/domains/network.zig b/src/cdp/domains/network.zig index c8a1788d..83e2b8f8 100644 --- a/src/cdp/domains/network.zig +++ b/src/cdp/domains/network.zig @@ -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 { diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index 4495a474..978ae7a8 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -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 @@ -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( @@ -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); } } diff --git a/src/cdp/domains/runtime.zig b/src/cdp/domains/runtime.zig index 707b5912..138155db 100644 --- a/src/cdp/domains/runtime.zig +++ b/src/cdp/domains/runtime.zig @@ -27,6 +27,7 @@ pub fn processMessage(cmd: anytype) !void { addBinding, callFunctionOn, releaseObject, + getProperties, }, cmd.input.action) orelse return error.UnknownMethod; switch (action) { diff --git a/src/cdp/domains/target.zig b/src/cdp/domains/target.zig index 87fe4324..b3b839ad 100644 --- a/src/cdp/domains/target.zig +++ b/src/cdp/domains/target.zig @@ -79,7 +79,7 @@ fn createBrowserContext(cmd: anytype) !void { } const bc = cmd.createBrowserContext() catch |err| switch (err) { - error.AlreadyExists => return cmd.sendError(-32000, "Cannot have more than one browser context at a time"), + error.AlreadyExists => return cmd.sendError(-32000, "Cannot have more than one browser context at a time", .{}), else => return err, }; @@ -102,7 +102,7 @@ fn disposeBrowserContext(cmd: anytype) !void { })) orelse return error.InvalidParams; if (cmd.cdp.disposeBrowserContext(params.browserContextId) == false) { - return cmd.sendError(-32602, "No browser context with the given id found"); + return cmd.sendError(-32602, "No browser context with the given id found", .{}); } try cmd.sendResult(null, .{}); } @@ -241,10 +241,10 @@ fn closeTarget(cmd: anytype) !void { } bc.session.removePage(); - if (bc.isolated_world) |*world| { + for (bc.isolated_worlds.items) |*world| { world.deinit(); - bc.isolated_world = null; } + bc.isolated_worlds.clearRetainingCapacity(); bc.target_id = null; }