@@ -31,6 +31,8 @@ pub fn processMessage(cmd: anytype) !void {
3131 performSearch ,
3232 getSearchResults ,
3333 discardSearchResults ,
34+ querySelector ,
35+ querySelectorAll ,
3436 resolveNode ,
3537 describeNode ,
3638 scrollIntoViewIfNeeded ,
@@ -43,6 +45,8 @@ pub fn processMessage(cmd: anytype) !void {
4345 .performSearch = > return performSearch (cmd ),
4446 .getSearchResults = > return getSearchResults (cmd ),
4547 .discardSearchResults = > return discardSearchResults (cmd ),
48+ .querySelector = > return querySelector (cmd ),
49+ .querySelectorAll = > return querySelectorAll (cmd ),
4650 .resolveNode = > return resolveNode (cmd ),
4751 .describeNode = > return describeNode (cmd ),
4852 .scrollIntoViewIfNeeded = > return scrollIntoViewIfNeeded (cmd ),
@@ -188,6 +192,61 @@ fn getSearchResults(cmd: anytype) !void {
188192 return cmd .sendResult (.{ .nodeIds = node_ids [params .fromIndex .. params .toIndex ] }, .{});
189193}
190194
195+ fn querySelector (cmd : anytype ) ! void {
196+ const params = (try cmd .params (struct {
197+ nodeId : Node.Id ,
198+ selector : []const u8 ,
199+ })) orelse return error .InvalidParams ;
200+
201+ const bc = cmd .browser_context orelse return error .BrowserContextNotLoaded ;
202+
203+ const node = bc .node_registry .lookup_by_id .get (params .nodeId ) orelse return error .UnknownNode ;
204+
205+ const selected_node = try css .querySelector (
206+ cmd .arena ,
207+ node ._node ,
208+ params .selector ,
209+ ) orelse return error .NodeNotFoundForGivenId ;
210+
211+ const registered_node = try bc .node_registry .register (selected_node );
212+
213+ // Dispatch setChildNodesEvents to inform the client of the subpart of node tree covering the results.
214+ var array = [1 ]* parser.Node {selected_node };
215+ try dispatchSetChildNodes (cmd , array [0.. ]);
216+
217+ return cmd .sendResult (.{
218+ .nodeId = registered_node .id ,
219+ }, .{});
220+ }
221+
222+ fn querySelectorAll (cmd : anytype ) ! void {
223+ const params = (try cmd .params (struct {
224+ nodeId : Node.Id ,
225+ selector : []const u8 ,
226+ })) orelse return error .InvalidParams ;
227+
228+ const bc = cmd .browser_context orelse return error .BrowserContextNotLoaded ;
229+
230+ const node = bc .node_registry .lookup_by_id .get (params .nodeId ) orelse return error .UnknownNode ;
231+
232+ const arena = cmd .arena ;
233+ var selected_nodes = try css .querySelectorAll (arena , node ._node , params .selector );
234+ defer selected_nodes .deinit (arena );
235+
236+ const nodes = selected_nodes .nodes .items ;
237+ const node_ids = try arena .alloc (Node .Id , nodes .len );
238+ for (nodes , node_ids ) | selected_node , * node_id | {
239+ node_id .* = (try bc .node_registry .register (selected_node )).id ;
240+ }
241+
242+ // Dispatch setChildNodesEvents to inform the client of the subpart of node tree covering the results.
243+ try dispatchSetChildNodes (cmd , nodes );
244+
245+ return cmd .sendResult (.{
246+ .nodeIds = node_ids ,
247+ }, .{});
248+ }
249+
191250fn resolveNode (cmd : anytype ) ! void {
192251 const params = (try cmd .params (struct {
193252 nodeId : ? Node.Id = null ,
@@ -399,3 +458,78 @@ test "cdp.dom: search flow" {
399458 .params = .{ .searchId = "0" , .fromIndex = 0 , .toIndex = 1 },
400459 }));
401460}
461+
462+ test "cdp.dom: querySelector unknown search id" {
463+ var ctx = testing .context ();
464+ defer ctx .deinit ();
465+
466+ _ = try ctx .loadBrowserContext (.{ .id = "BID-A" , .html = "<p>1</p> <p>2</p>" });
467+
468+ try testing .expectError (error .UnknownNode , ctx .processMessage (.{
469+ .id = 9 ,
470+ .method = "DOM.querySelector" ,
471+ .params = .{ .nodeId = 99 , .selector = "" },
472+ }));
473+ try testing .expectError (error .UnknownNode , ctx .processMessage (.{
474+ .id = 9 ,
475+ .method = "DOM.querySelectorAll" ,
476+ .params = .{ .nodeId = 99 , .selector = "" },
477+ }));
478+ }
479+
480+ test "cdp.dom: querySelector Node not found" {
481+ var ctx = testing .context ();
482+ defer ctx .deinit ();
483+
484+ _ = try ctx .loadBrowserContext (.{ .id = "BID-A" , .html = "<p>1</p> <p>2</p>" });
485+
486+ try ctx .processMessage (.{ // Hacky way to make sure nodeId 0 exists in the registry
487+ .id = 3 ,
488+ .method = "DOM.performSearch" ,
489+ .params = .{ .query = "p" },
490+ });
491+ try ctx .expectSentResult (.{ .searchId = "0" , .resultCount = 2 }, .{ .id = 3 });
492+
493+ try testing .expectError (error .NodeNotFoundForGivenId , ctx .processMessage (.{
494+ .id = 4 ,
495+ .method = "DOM.querySelector" ,
496+ .params = .{ .nodeId = 0 , .selector = "a" },
497+ }));
498+
499+ try ctx .processMessage (.{
500+ .id = 5 ,
501+ .method = "DOM.querySelectorAll" ,
502+ .params = .{ .nodeId = 0 , .selector = "a" },
503+ });
504+ try ctx .expectSentResult (.{ .nodeIds = &[_ ]u32 {} }, .{ .id = 5 });
505+ }
506+
507+ test "cdp.dom: querySelector Nodes found" {
508+ var ctx = testing .context ();
509+ defer ctx .deinit ();
510+
511+ _ = try ctx .loadBrowserContext (.{ .id = "BID-A" , .html = "<div><p>2</p></div>" });
512+
513+ try ctx .processMessage (.{ // Hacky way to make sure nodeId 0 exists in the registry
514+ .id = 3 ,
515+ .method = "DOM.performSearch" ,
516+ .params = .{ .query = "div" },
517+ });
518+ try ctx .expectSentResult (.{ .searchId = "0" , .resultCount = 1 }, .{ .id = 3 });
519+
520+ try ctx .processMessage (.{
521+ .id = 4 ,
522+ .method = "DOM.querySelector" ,
523+ .params = .{ .nodeId = 0 , .selector = "p" },
524+ });
525+ // TODO Check 1 or more "DOM.setChildNodes" was send
526+ try ctx .expectSentResult (.{ .nodeId = 5 }, .{ .id = 4 });
527+
528+ try ctx .processMessage (.{
529+ .id = 5 ,
530+ .method = "DOM.querySelectorAll" ,
531+ .params = .{ .nodeId = 0 , .selector = "p" },
532+ });
533+ // TODO Check 1 or more "DOM.setChildNodes" was send
534+ try ctx .expectSentResult (.{ .nodeIds = &.{5 } }, .{ .id = 5 });
535+ }
0 commit comments