mirror of
				https://github.com/lightpanda-io/browser.git
				synced 2025-10-29 15:13:28 +00:00 
			
		
		
		
	Add support for CDP's DOM.requestChildNodes
https://github.com/lightpanda-io/browser/issues/866
This commit is contained in:
		
							
								
								
									
										143
									
								
								src/cdp/Node.zig
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								src/cdp/Node.zig
									
									
									
									
									
								
							| @@ -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| { | ||||
|             // 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 { | ||||
|             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; | ||||
|  | ||||
|         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 { | ||||
| @@ -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 = ®istry, | ||||
|         }, .{}); | ||||
|         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 = 0, | ||||
|             .exclude_root = false, | ||||
|             .registry = ®istry, | ||||
|         }, .{}); | ||||
|         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 = ®istry, | ||||
|         }, .{}); | ||||
|         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); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
|             }; | ||||
|         } | ||||
|   | ||||
| @@ -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,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, | ||||
|     })) 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" { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Karl Seguin
					Karl Seguin