Skip to content

Commit b47b829

Browse files
Merge pull request #1021 from lightpanda-io/patchright
Patchright compatibility
2 parents b7d26cf + 5d1e17c commit b47b829

File tree

8 files changed

+111
-44
lines changed

8 files changed

+111
-44
lines changed

src/browser/css/parser.zig

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -821,7 +821,8 @@ pub const Parser = struct {
821821
// nameStart returns whether c can be the first character of an identifier
822822
// (not counting an initial hyphen, or an escape sequence).
823823
fn nameStart(c: u8) bool {
824-
return 'a' <= c and c <= 'z' or 'A' <= c and c <= 'Z' or c == '_' or c > 127;
824+
return 'a' <= c and c <= 'z' or 'A' <= c and c <= 'Z' or c == '_' or c > 127 or
825+
'0' <= c and c <= '9';
825826
}
826827

827828
// nameChar returns whether c can be a character within an identifier
@@ -890,7 +891,7 @@ test "parser.parseIdentifier" {
890891
err: bool = false,
891892
}{
892893
.{ .s = "x", .exp = "x" },
893-
.{ .s = "96", .exp = "", .err = true },
894+
.{ .s = "96", .exp = "96", .err = false },
894895
.{ .s = "-x", .exp = "-x" },
895896
.{ .s = "r\\e9 sumé", .exp = "résumé" },
896897
.{ .s = "r\\0000e9 sumé", .exp = "résumé" },
@@ -975,6 +976,7 @@ test "parser.parse" {
975976
.{ .s = ":root", .exp = .{ .pseudo_class = .root } },
976977
.{ .s = ".\\:bar", .exp = .{ .class = ":bar" } },
977978
.{ .s = ".foo\\:bar", .exp = .{ .class = "foo:bar" } },
979+
.{ .s = "[class=75c0fa18a94b9e3a6b8e14d6cbe688a27f5da10a]", .exp = .{ .attribute = .{ .key = "class", .val = "75c0fa18a94b9e3a6b8e14d6cbe688a27f5da10a", .op = .eql } } },
978980
};
979981

980982
for (testcases) |tc| {

src/browser/css/selector.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,11 @@ test "Browser.CSS.Selector: matchFirst" {
993993
.n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } },
994994
.exp = 0,
995995
},
996+
.{
997+
.q = "[foo=1baz]",
998+
.n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } },
999+
.exp = 0,
1000+
},
9961001
.{
9971002
.q = "[foo!=bar]",
9981003
.n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "bar" } } },

src/cdp/cdp.zig

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,18 @@ pub fn CDPT(comptime TypeProvider: type) type {
151151
if (std.mem.eql(u8, input_session_id, "STARTUP")) {
152152
is_startup = true;
153153
} else if (self.isValidSessionId(input_session_id) == false) {
154-
return command.sendError(-32001, "Unknown sessionId");
154+
return command.sendError(-32001, "Unknown sessionId", .{});
155155
}
156156
}
157157

158158
if (is_startup) {
159159
dispatchStartupCommand(&command) catch |err| {
160-
command.sendError(-31999, @errorName(err)) catch {};
160+
command.sendError(-31999, @errorName(err), .{}) catch {};
161161
return err;
162162
};
163163
} else {
164164
dispatchCommand(&command, input.method) catch |err| {
165-
command.sendError(-31998, @errorName(err)) catch {};
165+
command.sendError(-31998, @errorName(err), .{}) catch {};
166166
return err;
167167
};
168168
}
@@ -331,7 +331,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
331331
node_search_list: Node.Search.List,
332332

333333
inspector: Inspector,
334-
isolated_world: ?IsolatedWorld,
334+
isolated_worlds: std.ArrayListUnmanaged(IsolatedWorld),
335335

336336
http_proxy_changed: bool = false,
337337

@@ -375,7 +375,7 @@ pub fn BrowserContext(comptime CDP_T: type) type {
375375
.page_life_cycle_events = false, // TODO; Target based value
376376
.node_registry = registry,
377377
.node_search_list = undefined,
378-
.isolated_world = null,
378+
.isolated_worlds = .empty,
379379
.inspector = inspector,
380380
.notification_arena = cdp.notification_arena.allocator(),
381381
.intercept_state = try InterceptState.init(allocator),
@@ -404,9 +404,10 @@ pub fn BrowserContext(comptime CDP_T: type) type {
404404
// so we need to shutdown the page one first.
405405
self.cdp.browser.closeSession();
406406

407-
if (self.isolated_world) |*world| {
407+
for (self.isolated_worlds.items) |*world| {
408408
world.deinit();
409409
}
410+
self.isolated_worlds.clearRetainingCapacity();
410411
self.node_registry.deinit();
411412
self.node_search_list.deinit();
412413
self.cdp.browser.notification.unregisterAll(self);
@@ -427,19 +428,19 @@ pub fn BrowserContext(comptime CDP_T: type) type {
427428
}
428429

429430
pub fn createIsolatedWorld(self: *Self, world_name: []const u8, grant_universal_access: bool) !*IsolatedWorld {
430-
if (self.isolated_world != null) {
431-
return error.CurrentlyOnly1IsolatedWorldSupported;
432-
}
433-
434431
var executor = try self.cdp.browser.env.newExecutionWorld();
435432
errdefer executor.deinit();
436433

437-
self.isolated_world = .{
438-
.name = try self.arena.dupe(u8, world_name),
434+
const owned_name = try self.arena.dupe(u8, world_name);
435+
const world = try self.isolated_worlds.addOne(self.arena);
436+
437+
world.* = .{
438+
.name = owned_name,
439439
.executor = executor,
440440
.grant_universal_access = grant_universal_access,
441441
};
442-
return &self.isolated_world.?;
442+
443+
return world;
443444
}
444445

445446
pub fn nodeWriter(self: *Self, root: *const Node, opts: Node.Writer.Opts) Node.Writer {
@@ -682,7 +683,14 @@ const IsolatedWorld = struct {
682683
// This also means this pointer becomes invalid after removePage untill a new page is created.
683684
// Currently we have only 1 page/frame and thus also only 1 state in the isolate world.
684685
pub fn createContext(self: *IsolatedWorld, page: *Page) !void {
685-
if (self.executor.js_context != null) return error.Only1IsolatedContextSupported;
686+
// if (self.executor.js_context != null) return error.Only1IsolatedContextSupported;
687+
if (self.executor.js_context != null) {
688+
log.warn(.cdp, "not implemented", .{
689+
.feature = "createContext: Not implemented second isolated context creation",
690+
.info = "reuse existing context",
691+
});
692+
return;
693+
}
686694
_ = try self.executor.createJsContext(
687695
&page.window,
688696
page,
@@ -691,6 +699,14 @@ const IsolatedWorld = struct {
691699
Env.GlobalMissingCallback.init(&self.polyfill_loader),
692700
);
693701
}
702+
703+
pub fn createContextAndLoadPolyfills(self: *IsolatedWorld, arena: Allocator, page: *Page) !void {
704+
// We need to recreate the isolated world context
705+
try self.createContext(page);
706+
707+
const loader = @import("../browser/polyfill/polyfill.zig");
708+
try loader.preload(arena, &self.executor.js_context.?);
709+
}
694710
};
695711

696712
// 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 {
757773
return self.cdp.sendEvent(method, p, opts);
758774
}
759775

760-
pub fn sendError(self: *Self, code: i32, message: []const u8) !void {
776+
const SendErrorOpts = struct {
777+
include_session_id: bool = true,
778+
};
779+
pub fn sendError(self: *Self, code: i32, message: []const u8, opts: SendErrorOpts) !void {
761780
return self.sender.sendJSON(.{
762781
.id = self.input.id,
763782
.@"error" = .{ .code = code, .message = message },
783+
.sessionId = if (opts.include_session_id) self.input.session_id else null,
764784
});
765785
}
766786

src/cdp/domains/dom.zig

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

1919
const std = @import("std");
20+
const log = @import("../../log.zig");
2021
const Allocator = std.mem.Allocator;
2122
const Node = @import("../Node.zig");
2223
const css = @import("../../browser/dom/css.zig");
@@ -39,6 +40,7 @@ pub fn processMessage(cmd: anytype) !void {
3940
getContentQuads,
4041
getBoxModel,
4142
requestChildNodes,
43+
getFrameOwner,
4244
}, cmd.input.action) orelse return error.UnknownMethod;
4345

4446
switch (action) {
@@ -55,6 +57,7 @@ pub fn processMessage(cmd: anytype) !void {
5557
.getContentQuads => return getContentQuads(cmd),
5658
.getBoxModel => return getBoxModel(cmd),
5759
.requestChildNodes => return requestChildNodes(cmd),
60+
.getFrameOwner => return getFrameOwner(cmd),
5861
}
5962
}
6063

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

73+
if (params.pierce) {
74+
log.warn(.cdp, "not implemented", .{ .feature = "DOM.getDocument: Not implemented pierce parameter" });
75+
}
76+
7077
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
7178
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
7279
const doc = parser.documentHTMLToDocument(page.window.document);
@@ -206,7 +213,9 @@ fn querySelector(cmd: anytype) !void {
206213

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

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

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

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

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

238249
const arena = cmd.arena;
239250
const selected_nodes = try css.querySelectorAll(arena, node._node, params.selector);
@@ -266,10 +277,12 @@ fn resolveNode(cmd: anytype) !void {
266277
var js_context = page.main_context;
267278
if (params.executionContextId) |context_id| {
268279
if (js_context.v8_context.debugContextId() != context_id) {
269-
var isolated_world = bc.isolated_world orelse return error.ContextNotFound;
270-
js_context = &(isolated_world.executor.js_context orelse return error.ContextNotFound);
271-
272-
if (js_context.v8_context.debugContextId() != context_id) return error.ContextNotFound;
280+
for (bc.isolated_worlds.items) |*isolated_world| {
281+
js_context = &(isolated_world.executor.js_context orelse return error.ContextNotFound);
282+
if (js_context.v8_context.debugContextId() == context_id) {
283+
break;
284+
}
285+
} else return error.ContextNotFound;
273286
}
274287
}
275288

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

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

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

312-
return cmd.sendResult(.{ .node = bc.nodeWriter(node, .{}) }, .{});
327+
return cmd.sendResult(.{ .node = bc.nodeWriter(node, .{ .depth = params.depth }) }, .{});
313328
}
314329

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

479+
fn getFrameOwner(cmd: anytype) !void {
480+
const params = (try cmd.params(struct {
481+
frameId: []const u8,
482+
})) orelse return error.InvalidParams;
483+
484+
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
485+
const target_id = bc.target_id orelse return error.TargetNotLoaded;
486+
if (std.mem.eql(u8, target_id, params.frameId) == false) {
487+
return cmd.sendError(-32000, "Frame with the given id does not belong to the target.", .{});
488+
}
489+
490+
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
491+
const doc = parser.documentHTMLToDocument(page.window.document);
492+
493+
const node = try bc.node_registry.register(parser.documentToNode(doc));
494+
return cmd.sendResult(.{ .nodeId = node.id, .backendNodeId = node.id }, .{});
495+
}
496+
464497
const testing = @import("../testing.zig");
465498

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

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

537-
try testing.expectError(error.UnknownNode, ctx.processMessage(.{
570+
try ctx.processMessage(.{
538571
.id = 9,
539572
.method = "DOM.querySelector",
540573
.params = .{ .nodeId = 99, .selector = "" },
541-
}));
542-
try testing.expectError(error.UnknownNode, ctx.processMessage(.{
574+
});
575+
try ctx.expectSentError(-32000, "Could not find node with given id", .{});
576+
577+
try ctx.processMessage(.{
543578
.id = 9,
544579
.method = "DOM.querySelectorAll",
545580
.params = .{ .nodeId = 99, .selector = "" },
546-
}));
581+
});
582+
try ctx.expectSentError(-32000, "Could not find node with given id", .{});
547583
}
548584

549585
test "cdp.dom: querySelector Node not found" {

src/cdp/domains/network.zig

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,14 @@ pub fn httpRequestStart(arena: Allocator, bc: anytype, msg: *const Notification.
244244

245245
const transfer = msg.transfer;
246246
// We're missing a bunch of fields, but, for now, this seems like enough
247-
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 });
247+
try bc.cdp.sendEvent("Network.requestWillBeSent", .{
248+
.requestId = try std.fmt.allocPrint(arena, "REQ-{d}", .{transfer.id}),
249+
.frameId = target_id,
250+
.loaderId = bc.loader_id,
251+
.documentUrl = DocumentUrlWriter.init(&page.url.uri),
252+
.request = TransferAsRequestWriter.init(transfer),
253+
.initiator = .{ .type = "other" },
254+
}, .{ .session_id = session_id });
248255
}
249256

250257
pub fn httpResponseHeaderDone(arena: Allocator, bc: anytype, msg: *const Notification.ResponseHeaderDone) !void {

src/cdp/domains/page.zig

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ fn createIsolatedWorld(cmd: anytype) !void {
122122

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

128128
// Create the auxdata json for the contextCreated event
@@ -259,7 +259,7 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
259259
true,
260260
);
261261
}
262-
if (bc.isolated_world) |*isolated_world| {
262+
for (bc.isolated_worlds.items) |*isolated_world| {
263263
const aux_json = try std.fmt.allocPrint(arena, "{{\"isDefault\":false,\"type\":\"isolated\",\"frameId\":\"{s}\"}}", .{target_id});
264264
// Calling contextCreated will assign a new Id to the context and send the contextCreated event
265265
bc.inspector.contextCreated(
@@ -274,18 +274,14 @@ pub fn pageNavigate(arena: Allocator, bc: anytype, event: *const Notification.Pa
274274

275275
pub fn pageRemove(bc: anytype) !void {
276276
// The main page is going to be removed, we need to remove contexts from other worlds first.
277-
if (bc.isolated_world) |*isolated_world| {
277+
for (bc.isolated_worlds.items) |*isolated_world| {
278278
try isolated_world.removeContext();
279279
}
280280
}
281281

282282
pub fn pageCreated(bc: anytype, page: *Page) !void {
283-
if (bc.isolated_world) |*isolated_world| {
284-
// We need to recreate the isolated world context
285-
try isolated_world.createContext(page);
286-
287-
const polyfill = @import("../../browser/polyfill/polyfill.zig");
288-
try polyfill.preload(bc.arena, &isolated_world.executor.js_context.?);
283+
for (bc.isolated_worlds.items) |*isolated_world| {
284+
try isolated_world.createContextAndLoadPolyfills(bc.arena, page);
289285
}
290286
}
291287

src/cdp/domains/runtime.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub fn processMessage(cmd: anytype) !void {
2727
addBinding,
2828
callFunctionOn,
2929
releaseObject,
30+
getProperties,
3031
}, cmd.input.action) orelse return error.UnknownMethod;
3132

3233
switch (action) {

0 commit comments

Comments
 (0)