Merge pull request #888 from lightpanda-io/cdp_dom_requestChildNodes
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled

Add support for CDP's DOM.requestChildNodes
This commit is contained in:
Karl Seguin
2025-07-17 10:48:24 +08:00
committed by GitHub
3 changed files with 141 additions and 35 deletions

View File

@@ -201,49 +201,69 @@ pub const Search = struct {
// (For now, we only support direct children) // (For now, we only support direct children)
pub const Writer = struct { pub const Writer = struct {
opts: Opts, depth: i32,
node: *const Node, exclude_root: bool,
root: *const Node,
registry: *Registry, registry: *Registry,
pub const Opts = struct {}; pub const Opts = struct {
depth: i32 = 0,
exclude_root: bool = false,
};
pub fn jsonStringify(self: *const Writer, w: anytype) !void { pub fn jsonStringify(self: *const Writer, w: anytype) !void {
self.toJSON(w) catch |err| { if (self.exclude_root) {
// The only error our jsonStringify method can return is _ = self.writeChildren(self.root, 1, w) catch |err| {
// @TypeOf(w).Error. In other words, our code can't return its own log.err(.cdp, "node writeChildren", .{ .err = err });
// error, we can only return a writer error. Kinda sucks. return error.OutOfMemory;
log.err(.cdp, "json stringify", .{ .err = err }); };
return error.OutOfMemory; } else {
}; self.toJSON(self.root, 0, w) catch |err| {
// The only error our jsonStringify method can return is
// @TypeOf(w).Error. In other words, our code can't return its own
// error, we can only return a writer error. Kinda sucks.
log.err(.cdp, "node toJSON stringify", .{ .err = err });
return error.OutOfMemory;
};
}
} }
fn toJSON(self: *const Writer, w: anytype) !void { fn toJSON(self: *const Writer, node: *const Node, depth: usize, w: anytype) !void {
try w.beginObject(); try w.beginObject();
try self.writeCommon(self.node, false, w); try self.writeCommon(node, false, w);
{ try w.objectField("children");
var registry = self.registry; const child_count = try self.writeChildren(node, depth, w);
const child_nodes = try parser.nodeGetChildNodes(self.node._node); try w.objectField("childNodeCount");
const child_count = try parser.nodeListLength(child_nodes); try w.write(child_count);
var i: usize = 0; try w.endObject();
try w.objectField("children"); }
try w.beginArray();
for (0..child_count) |_| { fn writeChildren(self: *const Writer, node: *const Node, depth: usize, w: anytype) anyerror!usize {
const child = (try parser.nodeListItem(child_nodes, @intCast(i))) orelse break; var registry = self.registry;
const child_node = try registry.register(child); const child_nodes = try parser.nodeGetChildNodes(node._node);
const child_count = try parser.nodeListLength(child_nodes);
const full_child = self.depth < 0 or self.depth < depth;
var i: usize = 0;
try w.beginArray();
for (0..child_count) |_| {
const child = (try parser.nodeListItem(child_nodes, @intCast(i))) orelse break;
const child_node = try registry.register(child);
if (full_child) {
try self.toJSON(child_node, depth + 1, w);
} else {
try w.beginObject(); try w.beginObject();
try self.writeCommon(child_node, true, w); try self.writeCommon(child_node, true, w);
try w.endObject(); try w.endObject();
i += 1;
} }
try w.endArray();
try w.objectField("childNodeCount"); i += 1;
try w.write(i);
} }
try w.endArray();
try w.endObject(); return i;
} }
fn writeCommon(self: *const Writer, node: *const Node, include_child_count: bool, w: anytype) !void { fn writeCommon(self: *const Writer, node: *const Node, include_child_count: bool, w: anytype) !void {
@@ -400,14 +420,15 @@ test "cdp Node: Writer" {
var registry = Registry.init(testing.allocator); var registry = Registry.init(testing.allocator);
defer registry.deinit(); defer registry.deinit();
var doc = try testing.Document.init("<a id=a1></a><a id=a2></a>"); var doc = try testing.Document.init("<a id=a1></a><div id=d2><a id=a2></a></div>");
defer doc.deinit(); defer doc.deinit();
{ {
const node = try registry.register(doc.asNode()); const node = try registry.register(doc.asNode());
const json = try std.json.stringifyAlloc(testing.allocator, Writer{ const json = try std.json.stringifyAlloc(testing.allocator, Writer{
.node = node, .root = node,
.opts = .{}, .depth = 0,
.exclude_root = false,
.registry = &registry, .registry = &registry,
}, .{}); }, .{});
defer testing.allocator.free(json); defer testing.allocator.free(json);
@@ -445,8 +466,9 @@ test "cdp Node: Writer" {
{ {
const node = registry.lookup_by_id.get(1).?; const node = registry.lookup_by_id.get(1).?;
const json = try std.json.stringifyAlloc(testing.allocator, Writer{ const json = try std.json.stringifyAlloc(testing.allocator, Writer{
.node = node, .root = node,
.opts = .{}, .depth = 1,
.exclude_root = false,
.registry = &registry, .registry = &registry,
}, .{}); }, .{});
defer testing.allocator.free(json); defer testing.allocator.free(json);
@@ -495,4 +517,61 @@ test "cdp Node: Writer" {
} }, } },
}, json); }, json);
} }
{
const node = registry.lookup_by_id.get(1).?;
const json = try std.json.stringifyAlloc(testing.allocator, Writer{
.root = node,
.depth = -1,
.exclude_root = true,
.registry = &registry,
}, .{});
defer testing.allocator.free(json);
try testing.expectJson(&.{ .{
.nodeId = 2,
.backendNodeId = 2,
.nodeType = 1,
.nodeName = "HEAD",
.localName = "head",
.nodeValue = "",
.childNodeCount = 0,
.documentURL = null,
.baseURL = null,
.xmlVersion = "",
.compatibilityMode = "NoQuirksMode",
.isScrollable = false,
.parentId = 1,
}, .{
.nodeId = 3,
.backendNodeId = 3,
.nodeType = 1,
.nodeName = "BODY",
.localName = "body",
.nodeValue = "",
.childNodeCount = 2,
.documentURL = null,
.baseURL = null,
.xmlVersion = "",
.compatibilityMode = "NoQuirksMode",
.isScrollable = false,
.children = &.{ .{
.nodeId = 4,
.localName = "a",
.childNodeCount = 0,
.parentId = 3,
}, .{
.nodeId = 5,
.localName = "div",
.childNodeCount = 1,
.parentId = 3,
.children = &.{ .{
.nodeId = 6,
.localName = "a",
.childNodeCount = 0,
.parentId = 5,
}}
}
} } }, json);
}
} }

View File

@@ -406,10 +406,11 @@ pub fn BrowserContext(comptime CDP_T: type) type {
return &self.isolated_world.?; return &self.isolated_world.?;
} }
pub fn nodeWriter(self: *Self, node: *const Node, opts: Node.Writer.Opts) Node.Writer { pub fn nodeWriter(self: *Self, root: *const Node, opts: Node.Writer.Opts) Node.Writer {
return .{ return .{
.node = node, .root = root,
.opts = opts, .depth = opts.depth,
.exclude_root = opts.exclude_root,
.registry = &self.node_registry, .registry = &self.node_registry,
}; };
} }

View File

@@ -38,6 +38,7 @@ pub fn processMessage(cmd: anytype) !void {
scrollIntoViewIfNeeded, scrollIntoViewIfNeeded,
getContentQuads, getContentQuads,
getBoxModel, getBoxModel,
requestChildNodes,
}, cmd.input.action) orelse return error.UnknownMethod; }, cmd.input.action) orelse return error.UnknownMethod;
switch (action) { switch (action) {
@@ -53,6 +54,7 @@ pub fn processMessage(cmd: anytype) !void {
.scrollIntoViewIfNeeded => return scrollIntoViewIfNeeded(cmd), .scrollIntoViewIfNeeded => return scrollIntoViewIfNeeded(cmd),
.getContentQuads => return getContentQuads(cmd), .getContentQuads => return getContentQuads(cmd),
.getBoxModel => return getBoxModel(cmd), .getBoxModel => return getBoxModel(cmd),
.requestChildNodes => return requestChildNodes(cmd),
} }
} }
@@ -433,6 +435,30 @@ fn getBoxModel(cmd: anytype) !void {
} }, .{}); } }, .{});
} }
fn requestChildNodes(cmd: anytype) !void {
const params = (try cmd.params(struct {
nodeId: Node.Id,
depth: i32 = 1,
pierce: bool = false,
})) orelse return error.InvalidParams;
if (params.depth == 0) return error.InvalidParams;
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
const session_id = bc.session_id orelse return error.SessionIdNotLoaded;
const node = bc.node_registry.lookup_by_id.get(params.nodeId) orelse {
return error.InvalidNode;
};
try cmd.sendEvent("DOM.setChildNodes", .{
.parentId = node.id,
.nodes = bc.nodeWriter(node, .{.depth = params.depth, .exclude_root = true}),
}, .{
.session_id = session_id,
});
return cmd.sendResult(null, .{});
}
const testing = @import("../testing.zig"); const testing = @import("../testing.zig");
test "cdp.dom: getSearchResults unknown search id" { test "cdp.dom: getSearchResults unknown search id" {