Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 111 additions & 32 deletions src/cdp/Node.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DOM.getDocument I believe uses the undocumented value 3 by default. So may be good to update the other users in this pr as well, and connect the parameters.
For DOM.describeNode the default is 1, however it would be good to wire it in.
performSearch and querySelector most likely use the default, but I did not check to confirm

depth: i32 = 0,
exclude_root: bool = false,
};

pub fn jsonStringify(self: *const Writer, w: anytype) !void {
self.toJSON(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 });
return error.OutOfMemory;
};
if (self.exclude_root) {
_ = self.writeChildren(self.root, 0, w) catch |err| {
log.err(.cdp, "node writeChildren", .{ .err = err });
return error.OutOfMemory;
};
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assume that the meaning of the depth value 1, 2, 3 has been confirmed for both cases

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 self.writeCommon(self.node, false, w);
try self.writeCommon(node, false, w);

{
var registry = self.registry;
const child_nodes = try parser.nodeGetChildNodes(self.node._node);
const child_count = try parser.nodeListLength(child_nodes);
try w.objectField("children");
const child_count = try self.writeChildren(node, depth, w);
try w.objectField("childNodeCount");
try w.write(child_count);

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);
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(node._node);
const child_count = try parser.nodeListLength(child_nodes);
const full_child = self.depth < 0 or self.depth < depth;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const full_child = self.depth < 0 or self.depth < depth;
const full_child = self.depth < 0 or depth < self.depth;

My understanding is that if self.depth is 1 we only get one layer of nodes.
So in the exclude_root == true case that would be a single list of nodes without any of their children populated.
So I would expect this function to be called with the parameter depth: usize set to 1 (now called with 0).

In the exclude_root == false I would expect writeChildren not to be called at all as just the root node would be written. So we'd need this if statement also in the toJSON function.

I would not be surpised if I'm off by one tho.


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 self.writeCommon(child_node, true, w);
try w.endObject();
i += 1;
}
try w.endArray();

try w.objectField("childNodeCount");
try w.write(i);
i += 1;
}
try w.endArray();

try w.endObject();
return i;
}

fn writeCommon(self: *const Writer, node: *const Node, include_child_count: bool, w: anytype) !void {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 = 0,
.exclude_root = false,
.registry = &registry,
}, .{});
defer testing.allocator.free(json);
Expand Down Expand Up @@ -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);
}
}
7 changes: 4 additions & 3 deletions src/cdp/cdp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -402,10 +402,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,
};
}
Expand Down
25 changes: 25 additions & 0 deletions src/cdp/domains/dom.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub fn processMessage(cmd: anytype) !void {
scrollIntoViewIfNeeded,
getContentQuads,
getBoxModel,
requestChildNodes,
}, cmd.input.action) orelse return error.UnknownMethod;

switch (action) {
Expand All @@ -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),
}
}

Expand Down Expand Up @@ -433,6 +435,29 @@ 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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want to warn or so when pierce is set to true that this is not implemented yet

})) orelse 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" {
Expand Down