From e0caf02bea2de5c39e133e76086d508a04365001 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Tue, 20 May 2025 16:45:02 +0200 Subject: [PATCH 1/2] dom.querySelector --- src/cdp/domains/dom.zig | 134 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig index 79410c97f..78d86ae2d 100644 --- a/src/cdp/domains/dom.zig +++ b/src/cdp/domains/dom.zig @@ -31,6 +31,8 @@ pub fn processMessage(cmd: anytype) !void { performSearch, getSearchResults, discardSearchResults, + querySelector, + querySelectorAll, resolveNode, describeNode, scrollIntoViewIfNeeded, @@ -43,6 +45,8 @@ pub fn processMessage(cmd: anytype) !void { .performSearch => return performSearch(cmd), .getSearchResults => return getSearchResults(cmd), .discardSearchResults => return discardSearchResults(cmd), + .querySelector => return querySelector(cmd), + .querySelectorAll => return querySelectorAll(cmd), .resolveNode => return resolveNode(cmd), .describeNode => return describeNode(cmd), .scrollIntoViewIfNeeded => return scrollIntoViewIfNeeded(cmd), @@ -188,6 +192,61 @@ fn getSearchResults(cmd: anytype) !void { return cmd.sendResult(.{ .nodeIds = node_ids[params.fromIndex..params.toIndex] }, .{}); } +fn querySelector(cmd: anytype) !void { + const params = (try cmd.params(struct { + nodeId: Node.Id, + selector: []const u8, + })) orelse return error.InvalidParams; + + 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 selected_node = try css.querySelector( + cmd.arena, + node._node, + params.selector, + ) orelse return error.NodeNotFoundForGivenId; + + const registered_node = try bc.node_registry.register(selected_node); + + // Dispatch setChildNodesEvents to inform the client of the subpart of node tree covering the results. + var array = [1]*parser.Node{selected_node}; + try dispatchSetChildNodes(cmd, array[0..]); + + return cmd.sendResult(.{ + .nodeId = registered_node.id, + }, .{}); +} + +fn querySelectorAll(cmd: anytype) !void { + const params = (try cmd.params(struct { + nodeId: Node.Id, + selector: []const u8, + })) orelse return error.InvalidParams; + + 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 arena = cmd.arena; + var selected_nodes = try css.querySelectorAll(arena, node._node, params.selector); + defer selected_nodes.deinit(arena); + + const nodes = selected_nodes.nodes.items; + const node_ids = try arena.alloc(Node.Id, nodes.len); + for (nodes, node_ids) |selected_node, *node_id| { + node_id.* = (try bc.node_registry.register(selected_node)).id; + } + + // Dispatch setChildNodesEvents to inform the client of the subpart of node tree covering the results. + try dispatchSetChildNodes(cmd, nodes); + + return cmd.sendResult(.{ + .nodeIds = node_ids, + }, .{}); +} + fn resolveNode(cmd: anytype) !void { const params = (try cmd.params(struct { nodeId: ?Node.Id = null, @@ -399,3 +458,78 @@ test "cdp.dom: search flow" { .params = .{ .searchId = "0", .fromIndex = 0, .toIndex = 1 }, })); } + +test "cdp.dom: querySelector unknown search id" { + var ctx = testing.context(); + defer ctx.deinit(); + + _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "

1

2

" }); + + try testing.expectError(error.UnknownNode, ctx.processMessage(.{ + .id = 9, + .method = "DOM.querySelector", + .params = .{ .nodeId = 99, .selector = "" }, + })); + try testing.expectError(error.UnknownNode, ctx.processMessage(.{ + .id = 9, + .method = "DOM.querySelectorAll", + .params = .{ .nodeId = 99, .selector = "" }, + })); +} + +test "cdp.dom: querySelector Node not found" { + var ctx = testing.context(); + defer ctx.deinit(); + + _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "

1

2

" }); + + try ctx.processMessage(.{ // Hacky way to make sure nodeId 0 exists in the registry + .id = 3, + .method = "DOM.performSearch", + .params = .{ .query = "p" }, + }); + try ctx.expectSentResult(.{ .searchId = "0", .resultCount = 2 }, .{ .id = 3 }); + + try testing.expectError(error.NodeNotFoundForGivenId, ctx.processMessage(.{ + .id = 4, + .method = "DOM.querySelector", + .params = .{ .nodeId = 0, .selector = "a" }, + })); + + try ctx.processMessage(.{ + .id = 5, + .method = "DOM.querySelectorAll", + .params = .{ .nodeId = 0, .selector = "a" }, + }); + try ctx.expectSentResult(.{ .nodeIds = &[_]u32{} }, .{ .id = 5 }); +} + +test "cdp.dom: querySelector Nodes found" { + var ctx = testing.context(); + defer ctx.deinit(); + + _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .html = "

2

" }); + + try ctx.processMessage(.{ // Hacky way to make sure nodeId 0 exists in the registry + .id = 3, + .method = "DOM.performSearch", + .params = .{ .query = "div" }, + }); + try ctx.expectSentResult(.{ .searchId = "0", .resultCount = 1 }, .{ .id = 3 }); + + try ctx.processMessage(.{ + .id = 4, + .method = "DOM.querySelector", + .params = .{ .nodeId = 0, .selector = "p" }, + }); + // TODO Check 1 or more "DOM.setChildNodes" was send + try ctx.expectSentResult(.{ .nodeId = 5 }, .{ .id = 4 }); + + try ctx.processMessage(.{ + .id = 5, + .method = "DOM.querySelectorAll", + .params = .{ .nodeId = 0, .selector = "p" }, + }); + // TODO Check 1 or more "DOM.setChildNodes" was send + try ctx.expectSentResult(.{ .nodeIds = &.{5} }, .{ .id = 5 }); +} From dc8ef824c84ccb0742d93e8c93739616b5f95400 Mon Sep 17 00:00:00 2001 From: sjorsdonkers <72333389+sjorsdonkers@users.noreply.github.com> Date: Wed, 21 May 2025 10:11:54 +0200 Subject: [PATCH 2/2] setChildNodes checks --- src/cdp/domains/dom.zig | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig index 78d86ae2d..d2e8cdb5b 100644 --- a/src/cdp/domains/dom.zig +++ b/src/cdp/domains/dom.zig @@ -230,10 +230,9 @@ fn querySelectorAll(cmd: anytype) !void { const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse return error.UnknownNode; const arena = cmd.arena; - var selected_nodes = try css.querySelectorAll(arena, node._node, params.selector); - defer selected_nodes.deinit(arena); - + const selected_nodes = try css.querySelectorAll(arena, node._node, params.selector); const nodes = selected_nodes.nodes.items; + const node_ids = try arena.alloc(Node.Id, nodes.len); for (nodes, node_ids) |selected_node, *node_id| { node_id.* = (try bc.node_registry.register(selected_node)).id; @@ -522,7 +521,7 @@ test "cdp.dom: querySelector Nodes found" { .method = "DOM.querySelector", .params = .{ .nodeId = 0, .selector = "p" }, }); - // TODO Check 1 or more "DOM.setChildNodes" was send + try ctx.expectSentEvent("DOM.setChildNodes", null, .{}); try ctx.expectSentResult(.{ .nodeId = 5 }, .{ .id = 4 }); try ctx.processMessage(.{ @@ -530,6 +529,6 @@ test "cdp.dom: querySelector Nodes found" { .method = "DOM.querySelectorAll", .params = .{ .nodeId = 0, .selector = "p" }, }); - // TODO Check 1 or more "DOM.setChildNodes" was send + try ctx.expectSentEvent("DOM.setChildNodes", null, .{}); try ctx.expectSentResult(.{ .nodeIds = &.{5} }, .{ .id = 5 }); }