From 8b9084cb73a8b81f803966655635d6596fb8b918 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Thu, 1 May 2025 19:42:04 +0200 Subject: [PATCH] cdp: dispatch the correct dom hierarchy wit setChildNodes --- src/cdp/domains/dom.zig | 89 +++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 16 deletions(-) diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig index 6870ce76..91b4a0d5 100644 --- a/src/cdp/domains/dom.zig +++ b/src/cdp/domains/dom.zig @@ -78,22 +78,7 @@ fn performSearch(cmd: anytype) !void { // dispatch setChildNodesEvents to inform the client of the subpart of node // tree covering the results. - for (list.nodes.items) |n| { - // retrieve the node's parent - if (try parser.nodeParentNode(n)) |p| { - // Register the parent and send the node. - const parent_node = try bc.node_registry.register(p); - const node = bc.node_registry.lookup_by_node.get(n) orelse unreachable; - // Should-we return one DOM.setChildNodes event per parentId - // containing all its children in the nodes array? - try cmd.sendEvent("DOM.setChildNodes", .{ - .parentId = parent_node.id, - .nodes = .{bc.nodeWriter(node, .{})}, - }, .{ - .session_id = bc.session_id.?, - }); - } - } + try dispatchSetChildNodes(cmd, list.nodes.items); return cmd.sendResult(.{ .searchId = search.name, @@ -101,6 +86,78 @@ fn performSearch(cmd: anytype) !void { }, .{}); } +// dispatchSetChildNodes send the setChildNodes event for the whole DOM tree +// hierarchy of each nodes. +// If a parent has already been registred, we don't re-send a setChildNodes +// event anymore. +// We dispatch event in the reverse order: from the top level to the direct parents. +fn dispatchSetChildNodes(cmd: anytype, nodes: []*parser.Node) !void { + const arena = cmd.arena; + const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; + + var parents: std.ArrayListUnmanaged(*parser.Node) = .{}; + for (nodes) |_n| { + var n = _n; + while (true) { + const p = try parser.nodeParentNode(n) orelse break; + // If the parent exists in he registry, don't send the event. + // In this case we stop browsing the tree, b/c parents must have + // been sent already. + if (bc.node_registry.lookup_by_node.contains(p)) { + break; + } + try parents.append(arena, p); + + n = p; + } + } + + const plen = parents.items.len; + if (plen == 0) return; + + var i: usize = plen; + while (i > 0) { + i -= 1; + const n = parents.items[i]; + // If the parent exists in he registry, don't send the event. + // Indeed the parent can be twice in the array. + if (bc.node_registry.lookup_by_node.contains(n)) { + continue; + } + + // If the node has no parent, it's the root node. + // We don't dispatch event for it because we assume the root node is + // dispatched via the DOM.getDocument command. + const p = try parser.nodeParentNode(n) orelse break; + // Register the node. + const node = try bc.node_registry.register(n); + // Retrieve the parent from the registry. + const parent_node = bc.node_registry.lookup_by_node.get(p) orelse unreachable; + + try cmd.sendEvent("DOM.setChildNodes", .{ + .parentId = parent_node.id, + .nodes = .{bc.nodeWriter(node, .{})}, + }, .{ + .session_id = bc.session_id.?, + }); + } + + // now dispatch the event for the node list. + for (nodes) |n| { + const node = bc.node_registry.lookup_by_node.get(n) orelse unreachable; + const p = try parser.nodeParentNode(n) orelse continue; + // Retrieve the parent from the registry. + const parent_node = bc.node_registry.lookup_by_node.get(p) orelse unreachable; + + try cmd.sendEvent("DOM.setChildNodes", .{ + .parentId = parent_node.id, + .nodes = .{bc.nodeWriter(node, .{})}, + }, .{ + .session_id = bc.session_id.?, + }); + } +} + // https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-discardSearchResults fn discardSearchResults(cmd: anytype) !void { const params = (try cmd.params(struct {