Skip to content

Commit 8eadccd

Browse files
Merge pull request #587 from lightpanda-io/dom-setchildnodes
cdp: dispatch DOM.setChildNodes event for search results
2 parents b328392 + 2402443 commit 8eadccd

File tree

6 files changed

+104
-21
lines changed

6 files changed

+104
-21
lines changed

src/browser/dom/element.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ pub const Element = struct {
9999
}
100100

101101
pub fn get_attributes(self: *parser.Element) !*parser.NamedNodeMap {
102-
return try parser.nodeGetAttributes(parser.elementToNode(self));
102+
// An element must have non-nil attributes.
103+
return try parser.nodeGetAttributes(parser.elementToNode(self)) orelse unreachable;
103104
}
104105

105106
pub fn get_innerHTML(self: *parser.Element, state: *SessionState) ![]const u8 {

src/browser/dump.zig

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,18 @@ pub fn writeNode(node: *parser.Node, writer: anytype) anyerror!void {
3838
try writer.writeAll(tag);
3939

4040
// write the attributes
41-
const map = try parser.nodeGetAttributes(node);
42-
const ln = try parser.namedNodeMapGetLength(map);
43-
var i: u32 = 0;
44-
while (i < ln) {
45-
const attr = try parser.namedNodeMapItem(map, i) orelse break;
46-
try writer.writeAll(" ");
47-
try writer.writeAll(try parser.attributeGetName(attr));
48-
try writer.writeAll("=\"");
49-
const attribute_value = try parser.attributeGetValue(attr) orelse "";
50-
try writeEscapedAttributeValue(writer, attribute_value);
51-
try writer.writeAll("\"");
52-
i += 1;
41+
const _map = try parser.nodeGetAttributes(node);
42+
if (_map) |map| {
43+
const ln = try parser.namedNodeMapGetLength(map);
44+
for (0..ln) |i| {
45+
const attr = try parser.namedNodeMapItem(map, @intCast(i)) orelse break;
46+
try writer.writeAll(" ");
47+
try writer.writeAll(try parser.attributeGetName(attr));
48+
try writer.writeAll("=\"");
49+
const attribute_value = try parser.attributeGetValue(attr) orelse "";
50+
try writeEscapedAttributeValue(writer, attribute_value);
51+
try writer.writeAll("\"");
52+
}
5353
}
5454

5555
try writer.writeAll(">");

src/browser/netsurf.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,11 +1250,11 @@ pub fn nodeHasAttributes(node: *Node) !bool {
12501250
return res;
12511251
}
12521252

1253-
pub fn nodeGetAttributes(node: *Node) !*NamedNodeMap {
1253+
pub fn nodeGetAttributes(node: *Node) !?*NamedNodeMap {
12541254
var res: ?*NamedNodeMap = undefined;
12551255
const err = nodeVtable(node).dom_node_get_attributes.?(node, &res);
12561256
try DOMErr(err);
1257-
return res.?;
1257+
return res;
12581258
}
12591259

12601260
pub fn nodeGetNamespace(node: *Node) !?[]const u8 {

src/cdp/Node.zig

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const Node = @This();
2929

3030
id: Id,
3131
_node: *parser.Node,
32+
set_child_nodes_event: bool,
3233

3334
// Whenever we send a node to the client, we register it here for future lookup.
3435
// We maintain a node -> id and id -> node lookup.
@@ -85,6 +86,7 @@ pub const Registry = struct {
8586
node.* = .{
8687
._node = n,
8788
.id = id,
89+
.set_child_nodes_event = false,
8890
};
8991

9092
node_lookup_gop.value_ptr.* = node;
@@ -218,7 +220,7 @@ pub const Writer = struct {
218220

219221
fn toJSON(self: *const Writer, w: anytype) !void {
220222
try w.beginObject();
221-
try writeCommon(self.node, false, w);
223+
try self.writeCommon(self.node, false, w);
222224

223225
{
224226
var registry = self.registry;
@@ -232,7 +234,7 @@ pub const Writer = struct {
232234
const child = (try parser.nodeListItem(child_nodes, @intCast(i))) orelse break;
233235
const child_node = try registry.register(child);
234236
try w.beginObject();
235-
try writeCommon(child_node, true, w);
237+
try self.writeCommon(child_node, true, w);
236238
try w.endObject();
237239
i += 1;
238240
}
@@ -245,7 +247,7 @@ pub const Writer = struct {
245247
try w.endObject();
246248
}
247249

248-
fn writeCommon(node: *const Node, include_child_count: bool, w: anytype) !void {
250+
fn writeCommon(self: *const Writer, node: *const Node, include_child_count: bool, w: anytype) !void {
249251
try w.objectField("nodeId");
250252
try w.write(node.id);
251253

@@ -254,9 +256,24 @@ pub const Writer = struct {
254256

255257
const n = node._node;
256258

257-
// TODO:
258-
// try w.objectField("parentId");
259-
// try w.write(pid);
259+
if (try parser.nodeParentNode(n)) |p| {
260+
const parent_node = try self.registry.register(p);
261+
try w.objectField("parentId");
262+
try w.write(parent_node.id);
263+
}
264+
265+
const _map = try parser.nodeGetAttributes(n);
266+
if (_map) |map| {
267+
const attr_count = try parser.namedNodeMapGetLength(map);
268+
try w.objectField("attributes");
269+
try w.beginArray();
270+
for (0..attr_count) |i| {
271+
const attr = try parser.namedNodeMapItem(map, @intCast(i)) orelse continue;
272+
try w.write(try parser.attributeGetName(attr));
273+
try w.write(try parser.attributeGetValue(attr) orelse continue);
274+
}
275+
try w.endArray();
276+
}
260277

261278
try w.objectField("nodeType");
262279
try w.write(@intFromEnum(try parser.nodeType(n)));
@@ -461,6 +478,7 @@ test "cdp Node: Writer" {
461478
.xmlVersion = "",
462479
.compatibilityMode = "NoQuirksMode",
463480
.isScrollable = false,
481+
.parentId = 1,
464482
}, .{
465483
.nodeId = 3,
466484
.backendNodeId = 3,
@@ -474,6 +492,7 @@ test "cdp Node: Writer" {
474492
.xmlVersion = "",
475493
.compatibilityMode = "NoQuirksMode",
476494
.isScrollable = false,
495+
.parentId = 1,
477496
} },
478497
}, json);
479498
}

src/cdp/domains/dom.zig

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,74 @@ fn performSearch(cmd: anytype) !void {
7676

7777
const search = try bc.node_search_list.create(list.nodes.items);
7878

79+
// dispatch setChildNodesEvents to inform the client of the subpart of node
80+
// tree covering the results.
81+
try dispatchSetChildNodes(cmd, list.nodes.items);
82+
7983
return cmd.sendResult(.{
8084
.searchId = search.name,
8185
.resultCount = @as(u32, @intCast(search.node_ids.len)),
8286
}, .{});
8387
}
8488

89+
// dispatchSetChildNodes send the setChildNodes event for the whole DOM tree
90+
// hierarchy of each nodes.
91+
// We dispatch event in the reverse order: from the top level to the direct parents.
92+
// We should dispatch a node only if it has never been sent.
93+
fn dispatchSetChildNodes(cmd: anytype, nodes: []*parser.Node) !void {
94+
const arena = cmd.arena;
95+
const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded;
96+
const session_id = bc.session_id orelse return error.SessionIdNotLoaded;
97+
98+
var parents: std.ArrayListUnmanaged(*Node) = .{};
99+
for (nodes) |_n| {
100+
var n = _n;
101+
while (true) {
102+
const p = try parser.nodeParentNode(n) orelse break;
103+
104+
// Register the node.
105+
const node = try bc.node_registry.register(p);
106+
if (node.set_child_nodes_event) break;
107+
try parents.append(arena, node);
108+
n = p;
109+
}
110+
}
111+
112+
const plen = parents.items.len;
113+
if (plen == 0) return;
114+
115+
var i: usize = plen;
116+
// We're going to iterate in reverse order from how we added them.
117+
// This ensures that we're emitting the tree of nodes top-down.
118+
while (i > 0) {
119+
i -= 1;
120+
const node = parents.items[i];
121+
// Although our above loop won't add an already-sent node to `parents`
122+
// this can still be true because two nodes can share the same parent node
123+
// so we might have just sent the node a previous iteration of this loop
124+
if (node.set_child_nodes_event) continue;
125+
126+
node.set_child_nodes_event = true;
127+
128+
// If the node has no parent, it's the root node.
129+
// We don't dispatch event for it because we assume the root node is
130+
// dispatched via the DOM.getDocument command.
131+
const p = try parser.nodeParentNode(node._node) orelse {
132+
continue;
133+
};
134+
135+
// Retrieve the parent from the registry.
136+
const parent_node = try bc.node_registry.register(p);
137+
138+
try cmd.sendEvent("DOM.setChildNodes", .{
139+
.parentId = parent_node.id,
140+
.nodes = .{bc.nodeWriter(node, .{})},
141+
}, .{
142+
.session_id = session_id,
143+
});
144+
}
145+
}
146+
85147
// https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-discardSearchResults
86148
fn discardSearchResults(cmd: anytype) !void {
87149
const params = (try cmd.params(struct {

src/cdp/testing.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ const TestContext = struct {
120120
}
121121

122122
if (opts.html) |html| {
123+
if (bc.session_id == null) bc.session_id = "SID-X";
123124
parser.deinit();
124125
try parser.init();
125126
const page = try bc.session.createPage();

0 commit comments

Comments
 (0)