diff --git a/src/browser/dom/element.zig b/src/browser/dom/element.zig index 8984d251..26c4ab12 100644 --- a/src/browser/dom/element.zig +++ b/src/browser/dom/element.zig @@ -99,7 +99,8 @@ pub const Element = struct { } pub fn get_attributes(self: *parser.Element) !*parser.NamedNodeMap { - return try parser.nodeGetAttributes(parser.elementToNode(self)); + // An element must have non-nil attributes. + return try parser.nodeGetAttributes(parser.elementToNode(self)) orelse unreachable; } pub fn get_innerHTML(self: *parser.Element, state: *SessionState) ![]const u8 { diff --git a/src/browser/dump.zig b/src/browser/dump.zig index 4c4e7996..01669036 100644 --- a/src/browser/dump.zig +++ b/src/browser/dump.zig @@ -38,18 +38,18 @@ pub fn writeNode(node: *parser.Node, writer: anytype) anyerror!void { try writer.writeAll(tag); // write the attributes - const map = try parser.nodeGetAttributes(node); - const ln = try parser.namedNodeMapGetLength(map); - var i: u32 = 0; - while (i < ln) { - const attr = try parser.namedNodeMapItem(map, i) orelse break; - try writer.writeAll(" "); - try writer.writeAll(try parser.attributeGetName(attr)); - try writer.writeAll("=\""); - const attribute_value = try parser.attributeGetValue(attr) orelse ""; - try writeEscapedAttributeValue(writer, attribute_value); - try writer.writeAll("\""); - i += 1; + const _map = try parser.nodeGetAttributes(node); + if (_map) |map| { + const ln = try parser.namedNodeMapGetLength(map); + for (0..ln) |i| { + const attr = try parser.namedNodeMapItem(map, @intCast(i)) orelse break; + try writer.writeAll(" "); + try writer.writeAll(try parser.attributeGetName(attr)); + try writer.writeAll("=\""); + const attribute_value = try parser.attributeGetValue(attr) orelse ""; + try writeEscapedAttributeValue(writer, attribute_value); + try writer.writeAll("\""); + } } try writer.writeAll(">"); diff --git a/src/browser/netsurf.zig b/src/browser/netsurf.zig index 263c204a..b5204f25 100644 --- a/src/browser/netsurf.zig +++ b/src/browser/netsurf.zig @@ -1250,11 +1250,11 @@ pub fn nodeHasAttributes(node: *Node) !bool { return res; } -pub fn nodeGetAttributes(node: *Node) !*NamedNodeMap { +pub fn nodeGetAttributes(node: *Node) !?*NamedNodeMap { var res: ?*NamedNodeMap = undefined; const err = nodeVtable(node).dom_node_get_attributes.?(node, &res); try DOMErr(err); - return res.?; + return res; } pub fn nodeGetNamespace(node: *Node) !?[]const u8 { diff --git a/src/cdp/Node.zig b/src/cdp/Node.zig index a7a6b658..44b8c9f2 100644 --- a/src/cdp/Node.zig +++ b/src/cdp/Node.zig @@ -29,6 +29,7 @@ const Node = @This(); id: Id, _node: *parser.Node, +set_child_nodes_event: bool, // Whenever we send a node to the client, we register it here for future lookup. // We maintain a node -> id and id -> node lookup. @@ -85,6 +86,7 @@ pub const Registry = struct { node.* = .{ ._node = n, .id = id, + .set_child_nodes_event = false, }; node_lookup_gop.value_ptr.* = node; @@ -218,7 +220,7 @@ pub const Writer = struct { fn toJSON(self: *const Writer, w: anytype) !void { try w.beginObject(); - try writeCommon(self.node, false, w); + try self.writeCommon(self.node, false, w); { var registry = self.registry; @@ -232,7 +234,7 @@ pub const Writer = struct { const child = (try parser.nodeListItem(child_nodes, @intCast(i))) orelse break; const child_node = try registry.register(child); try w.beginObject(); - try writeCommon(child_node, true, w); + try self.writeCommon(child_node, true, w); try w.endObject(); i += 1; } @@ -245,7 +247,7 @@ pub const Writer = struct { try w.endObject(); } - fn writeCommon(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 { try w.objectField("nodeId"); try w.write(node.id); @@ -254,9 +256,24 @@ pub const Writer = struct { const n = node._node; - // TODO: - // try w.objectField("parentId"); - // try w.write(pid); + if (try parser.nodeParentNode(n)) |p| { + const parent_node = try self.registry.register(p); + try w.objectField("parentId"); + try w.write(parent_node.id); + } + + const _map = try parser.nodeGetAttributes(n); + if (_map) |map| { + const attr_count = try parser.namedNodeMapGetLength(map); + try w.objectField("attributes"); + try w.beginArray(); + for (0..attr_count) |i| { + const attr = try parser.namedNodeMapItem(map, @intCast(i)) orelse continue; + try w.write(try parser.attributeGetName(attr)); + try w.write(try parser.attributeGetValue(attr) orelse continue); + } + try w.endArray(); + } try w.objectField("nodeType"); try w.write(@intFromEnum(try parser.nodeType(n))); @@ -461,6 +478,7 @@ test "cdp Node: Writer" { .xmlVersion = "", .compatibilityMode = "NoQuirksMode", .isScrollable = false, + .parentId = 1, }, .{ .nodeId = 3, .backendNodeId = 3, @@ -474,6 +492,7 @@ test "cdp Node: Writer" { .xmlVersion = "", .compatibilityMode = "NoQuirksMode", .isScrollable = false, + .parentId = 1, } }, }, json); } diff --git a/src/cdp/domains/dom.zig b/src/cdp/domains/dom.zig index ceb9bc93..c37f59cb 100644 --- a/src/cdp/domains/dom.zig +++ b/src/cdp/domains/dom.zig @@ -76,12 +76,74 @@ fn performSearch(cmd: anytype) !void { const search = try bc.node_search_list.create(list.nodes.items); + // dispatch setChildNodesEvents to inform the client of the subpart of node + // tree covering the results. + try dispatchSetChildNodes(cmd, list.nodes.items); + return cmd.sendResult(.{ .searchId = search.name, .resultCount = @as(u32, @intCast(search.node_ids.len)), }, .{}); } +// dispatchSetChildNodes send the setChildNodes event for the whole DOM tree +// hierarchy of each nodes. +// We dispatch event in the reverse order: from the top level to the direct parents. +// We should dispatch a node only if it has never been sent. +fn dispatchSetChildNodes(cmd: anytype, nodes: []*parser.Node) !void { + const arena = cmd.arena; + const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; + const session_id = bc.session_id orelse return error.SessionIdNotLoaded; + + var parents: std.ArrayListUnmanaged(*Node) = .{}; + for (nodes) |_n| { + var n = _n; + while (true) { + const p = try parser.nodeParentNode(n) orelse break; + + // Register the node. + const node = try bc.node_registry.register(p); + if (node.set_child_nodes_event) break; + try parents.append(arena, node); + n = p; + } + } + + const plen = parents.items.len; + if (plen == 0) return; + + var i: usize = plen; + // We're going to iterate in reverse order from how we added them. + // This ensures that we're emitting the tree of nodes top-down. + while (i > 0) { + i -= 1; + const node = parents.items[i]; + // Although our above loop won't add an already-sent node to `parents` + // this can still be true because two nodes can share the same parent node + // so we might have just sent the node a previous iteration of this loop + if (node.set_child_nodes_event) continue; + + node.set_child_nodes_event = true; + + // 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(node._node) orelse { + continue; + }; + + // Retrieve the parent from the registry. + const parent_node = try bc.node_registry.register(p); + + try cmd.sendEvent("DOM.setChildNodes", .{ + .parentId = parent_node.id, + .nodes = .{bc.nodeWriter(node, .{})}, + }, .{ + .session_id = session_id, + }); + } +} + // https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-discardSearchResults fn discardSearchResults(cmd: anytype) !void { const params = (try cmd.params(struct { diff --git a/src/cdp/testing.zig b/src/cdp/testing.zig index 045fa7ee..fc19d3af 100644 --- a/src/cdp/testing.zig +++ b/src/cdp/testing.zig @@ -120,6 +120,7 @@ const TestContext = struct { } if (opts.html) |html| { + if (bc.session_id == null) bc.session_id = "SID-X"; parser.deinit(); try parser.init(); const page = try bc.session.createPage();