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)
pub const Writer = struct {
opts: Opts,
node: *const Node,
depth: i32,
exclude_root: bool,
root: *const Node,
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 {
self.toJSON(w) catch |err| {
if (self.exclude_root) {
_ = self.writeChildren(self.root, 1, w) catch |err| {
log.err(.cdp, "node writeChildren", .{ .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, "json stringify", .{ .err = err });
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 self.writeCommon(self.node, false, w);
try self.writeCommon(node, false, w);
{
try w.objectField("children");
const child_count = try self.writeChildren(node, depth, w);
try w.objectField("childNodeCount");
try w.write(child_count);
try w.endObject();
}
fn writeChildren(self: *const Writer, node: *const Node, depth: usize, w: anytype) anyerror!usize {
var registry = self.registry;
const child_nodes = try parser.nodeGetChildNodes(self.node._node);
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.objectField("children");
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 self.writeCommon(child_node, true, w);
try w.endObject();
}
i += 1;
}
try w.endArray();
try w.objectField("childNodeCount");
try w.write(i);
}
try w.endObject();
return i;
}
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);
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();
{
const node = try registry.register(doc.asNode());
const json = try std.json.stringifyAlloc(testing.allocator, Writer{
.node = node,
.opts = .{},
.root = node,
.depth = 0,
.exclude_root = false,
.registry = &registry,
}, .{});
defer testing.allocator.free(json);
@@ -445,8 +466,9 @@ test "cdp Node: Writer" {
{
const node = registry.lookup_by_id.get(1).?;
const json = try std.json.stringifyAlloc(testing.allocator, Writer{
.node = node,
.opts = .{},
.root = node,
.depth = 1,
.exclude_root = false,
.registry = &registry,
}, .{});
defer testing.allocator.free(json);
@@ -495,4 +517,61 @@ test "cdp Node: Writer" {
} },
}, 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.?;
}
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 .{
.node = node,
.opts = opts,
.root = root,
.depth = opts.depth,
.exclude_root = opts.exclude_root,
.registry = &self.node_registry,
};
}

View File

@@ -38,6 +38,7 @@ pub fn processMessage(cmd: anytype) !void {
scrollIntoViewIfNeeded,
getContentQuads,
getBoxModel,
requestChildNodes,
}, cmd.input.action) orelse return error.UnknownMethod;
switch (action) {
@@ -53,6 +54,7 @@ pub fn processMessage(cmd: anytype) !void {
.scrollIntoViewIfNeeded => return scrollIntoViewIfNeeded(cmd),
.getContentQuads => return getContentQuads(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");
test "cdp.dom: getSearchResults unknown search id" {