Skip to content

Commit 09ca0e6

Browse files
committed
Add support for CDP's DOM.requestChildNodes
#866
1 parent d35a3ea commit 09ca0e6

File tree

3 files changed

+140
-35
lines changed

3 files changed

+140
-35
lines changed

src/cdp/Node.zig

Lines changed: 111 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -201,49 +201,69 @@ pub const Search = struct {
201201
// (For now, we only support direct children)
202202

203203
pub const Writer = struct {
204-
opts: Opts,
205-
node: *const Node,
204+
depth: i32,
205+
exclude_root: bool,
206+
root: *const Node,
206207
registry: *Registry,
207208

208-
pub const Opts = struct {};
209+
pub const Opts = struct {
210+
depth: i32 = 0,
211+
exclude_root: bool = false,
212+
};
209213

210214
pub fn jsonStringify(self: *const Writer, w: anytype) !void {
211-
self.toJSON(w) catch |err| {
212-
// The only error our jsonStringify method can return is
213-
// @TypeOf(w).Error. In other words, our code can't return its own
214-
// error, we can only return a writer error. Kinda sucks.
215-
log.err(.cdp, "json stringify", .{ .err = err });
216-
return error.OutOfMemory;
217-
};
215+
if (self.exclude_root) {
216+
_ = self.writeChildren(self.root, 0, w) catch |err| {
217+
log.err(.cdp, "node writeChildren", .{ .err = err });
218+
return error.OutOfMemory;
219+
};
220+
} else {
221+
self.toJSON(self.root, 0, w) catch |err| {
222+
// The only error our jsonStringify method can return is
223+
// @TypeOf(w).Error. In other words, our code can't return its own
224+
// error, we can only return a writer error. Kinda sucks.
225+
log.err(.cdp, "node toJSON stringify", .{ .err = err });
226+
return error.OutOfMemory;
227+
};
228+
}
218229
}
219230

220-
fn toJSON(self: *const Writer, w: anytype) !void {
231+
fn toJSON(self: *const Writer, node: *const Node, depth: usize, w: anytype) !void {
221232
try w.beginObject();
222-
try self.writeCommon(self.node, false, w);
233+
try self.writeCommon(node, false, w);
223234

224-
{
225-
var registry = self.registry;
226-
const child_nodes = try parser.nodeGetChildNodes(self.node._node);
227-
const child_count = try parser.nodeListLength(child_nodes);
235+
try w.objectField("children");
236+
const child_count = try self.writeChildren(node, depth, w);
237+
try w.objectField("childNodeCount");
238+
try w.write(child_count);
228239

229-
var i: usize = 0;
230-
try w.objectField("children");
231-
try w.beginArray();
232-
for (0..child_count) |_| {
233-
const child = (try parser.nodeListItem(child_nodes, @intCast(i))) orelse break;
234-
const child_node = try registry.register(child);
240+
try w.endObject();
241+
}
242+
243+
fn writeChildren(self: *const Writer, node: *const Node, depth: usize, w: anytype) anyerror!usize {
244+
var registry = self.registry;
245+
const child_nodes = try parser.nodeGetChildNodes(node._node);
246+
const child_count = try parser.nodeListLength(child_nodes);
247+
const full_child = self.depth < 0 or self.depth < depth;
248+
249+
var i: usize = 0;
250+
try w.beginArray();
251+
for (0..child_count) |_| {
252+
const child = (try parser.nodeListItem(child_nodes, @intCast(i))) orelse break;
253+
const child_node = try registry.register(child);
254+
if (full_child) {
255+
try self.toJSON(child_node, depth + 1, w);
256+
} else {
235257
try w.beginObject();
236258
try self.writeCommon(child_node, true, w);
237259
try w.endObject();
238-
i += 1;
239260
}
240-
try w.endArray();
241261

242-
try w.objectField("childNodeCount");
243-
try w.write(i);
262+
i += 1;
244263
}
264+
try w.endArray();
245265

246-
try w.endObject();
266+
return i;
247267
}
248268

249269
fn writeCommon(self: *const Writer, node: *const Node, include_child_count: bool, w: anytype) !void {
@@ -400,14 +420,15 @@ test "cdp Node: Writer" {
400420
var registry = Registry.init(testing.allocator);
401421
defer registry.deinit();
402422

403-
var doc = try testing.Document.init("<a id=a1></a><a id=a2></a>");
423+
var doc = try testing.Document.init("<a id=a1></a><div id=d2><a id=a2></a></div>");
404424
defer doc.deinit();
405425

406426
{
407427
const node = try registry.register(doc.asNode());
408428
const json = try std.json.stringifyAlloc(testing.allocator, Writer{
409-
.node = node,
410-
.opts = .{},
429+
.root = node,
430+
.depth = 0,
431+
.exclude_root = false,
411432
.registry = &registry,
412433
}, .{});
413434
defer testing.allocator.free(json);
@@ -445,8 +466,9 @@ test "cdp Node: Writer" {
445466
{
446467
const node = registry.lookup_by_id.get(1).?;
447468
const json = try std.json.stringifyAlloc(testing.allocator, Writer{
448-
.node = node,
449-
.opts = .{},
469+
.root = node,
470+
.depth = 0,
471+
.exclude_root = false,
450472
.registry = &registry,
451473
}, .{});
452474
defer testing.allocator.free(json);
@@ -495,4 +517,61 @@ test "cdp Node: Writer" {
495517
} },
496518
}, json);
497519
}
520+
521+
{
522+
const node = registry.lookup_by_id.get(1).?;
523+
const json = try std.json.stringifyAlloc(testing.allocator, Writer{
524+
.root = node,
525+
.depth = -1,
526+
.exclude_root = true,
527+
.registry = &registry,
528+
}, .{});
529+
defer testing.allocator.free(json);
530+
531+
try testing.expectJson(&.{ .{
532+
.nodeId = 2,
533+
.backendNodeId = 2,
534+
.nodeType = 1,
535+
.nodeName = "HEAD",
536+
.localName = "head",
537+
.nodeValue = "",
538+
.childNodeCount = 0,
539+
.documentURL = null,
540+
.baseURL = null,
541+
.xmlVersion = "",
542+
.compatibilityMode = "NoQuirksMode",
543+
.isScrollable = false,
544+
.parentId = 1,
545+
}, .{
546+
.nodeId = 3,
547+
.backendNodeId = 3,
548+
.nodeType = 1,
549+
.nodeName = "BODY",
550+
.localName = "body",
551+
.nodeValue = "",
552+
.childNodeCount = 2,
553+
.documentURL = null,
554+
.baseURL = null,
555+
.xmlVersion = "",
556+
.compatibilityMode = "NoQuirksMode",
557+
.isScrollable = false,
558+
.children = &.{ .{
559+
.nodeId = 4,
560+
.localName = "a",
561+
.childNodeCount = 0,
562+
.parentId = 3,
563+
}, .{
564+
.nodeId = 5,
565+
.localName = "div",
566+
.childNodeCount = 1,
567+
.parentId = 3,
568+
.children = &.{ .{
569+
.nodeId = 6,
570+
.localName = "a",
571+
.childNodeCount = 0,
572+
.parentId = 5,
573+
}}
574+
}
575+
} } }, json);
576+
}
498577
}

src/cdp/cdp.zig

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,10 +402,11 @@ pub fn BrowserContext(comptime CDP_T: type) type {
402402
return &self.isolated_world.?;
403403
}
404404

405-
pub fn nodeWriter(self: *Self, node: *const Node, opts: Node.Writer.Opts) Node.Writer {
405+
pub fn nodeWriter(self: *Self, root: *const Node, opts: Node.Writer.Opts) Node.Writer {
406406
return .{
407-
.node = node,
408-
.opts = opts,
407+
.root = root,
408+
.depth = opts.depth,
409+
.exclude_root = opts.exclude_root,
409410
.registry = &self.node_registry,
410411
};
411412
}

src/cdp/domains/dom.zig

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub fn processMessage(cmd: anytype) !void {
3838
scrollIntoViewIfNeeded,
3939
getContentQuads,
4040
getBoxModel,
41+
requestChildNodes,
4142
}, cmd.input.action) orelse return error.UnknownMethod;
4243

4344
switch (action) {
@@ -53,6 +54,7 @@ pub fn processMessage(cmd: anytype) !void {
5354
.scrollIntoViewIfNeeded => return scrollIntoViewIfNeeded(cmd),
5455
.getContentQuads => return getContentQuads(cmd),
5556
.getBoxModel => return getBoxModel(cmd),
57+
.requestChildNodes => return requestChildNodes(cmd),
5658
}
5759
}
5860

@@ -433,6 +435,29 @@ fn getBoxModel(cmd: anytype) !void {
433435
} }, .{});
434436
}
435437

438+
fn requestChildNodes(cmd: anytype) !void {
439+
const params = (try cmd.params(struct {
440+
nodeId: Node.Id,
441+
depth: i32 = 1,
442+
pierce: bool = false,
443+
})) orelse return error.InvalidParams;
444+
445+
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
446+
const session_id = bc.session_id orelse return error.SessionIdNotLoaded;
447+
const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
448+
return error.InvalidNode;
449+
};
450+
451+
try cmd.sendEvent("DOM.setChildNodes", .{
452+
.parentId = node.id,
453+
.nodes = bc.nodeWriter(node, .{.depth = params.depth, .exclude_root = true}),
454+
}, .{
455+
.session_id = session_id,
456+
});
457+
458+
return cmd.sendResult(null, .{});
459+
}
460+
436461
const testing = @import("../testing.zig");
437462

438463
test "cdp.dom: getSearchResults unknown search id" {

0 commit comments

Comments
 (0)