@@ -44,67 +44,27 @@ const CompatibilityMode = enum {
4444 NoQuirksMode ,
4545};
4646
47- pub fn jsonStringify (self : * const Node , writer : anytype ) ! void {
48- try writer .beginObject ();
49- try writer .objectField ("nodeId" );
50- try writer .write (self .id );
51-
52- try writer .objectField ("parentId" );
53- try writer .write (self .parent_id );
54-
55- try writer .objectField ("backendNodeId" );
56- try writer .write (self .backend_node_id );
57-
58- try writer .objectField ("nodeType" );
59- try writer .write (self .node_type );
60-
61- try writer .objectField ("nodeName" );
62- try writer .write (self .node_name );
63-
64- try writer .objectField ("localName" );
65- try writer .write (self .local_name );
66-
67- try writer .objectField ("nodeValue" );
68- try writer .write (self .node_value );
69-
70- try writer .objectField ("childNodeCount" );
71- try writer .write (self .child_node_count );
72-
73- try writer .objectField ("children" );
74- try writer .write (self .children );
75-
76- try writer .objectField ("documentURL" );
77- try writer .write (self .document_url );
78-
79- try writer .objectField ("baseURL" );
80- try writer .write (self .base_url );
81-
82- try writer .objectField ("xmlVersion" );
83- try writer .write (self .xml_version );
84-
85- try writer .objectField ("compatibilityMode" );
86- try writer .write (self .compatibility_mode );
87-
88- try writer .objectField ("isScrollable" );
89- try writer .write (self .is_scrollable );
90- try writer .endObject ();
47+ pub fn writer (self : * const Node , opts : Writer.Opts ) Writer {
48+ return .{ .node = self , .opts = opts };
9149}
9250
9351// Whenever we send a node to the client, we register it here for future lookup.
9452// We maintain a node -> id and id -> node lookup.
9553pub const Registry = struct {
9654 node_id : u32 ,
9755 allocator : Allocator ,
56+ arena : std.heap.ArenaAllocator ,
9857 node_pool : std .heap .MemoryPool (Node ),
9958 lookup_by_id : std .AutoHashMapUnmanaged (Id , * Node ),
10059 lookup_by_node : std .HashMapUnmanaged (* parser.Node , * Node , NodeContext , std .hash_map .default_max_load_percentage ),
10160
10261 pub fn init (allocator : Allocator ) Registry {
10362 return .{
10463 .node_id = 0 ,
105- .allocator = allocator ,
10664 .lookup_by_id = .{},
10765 .lookup_by_node = .{},
66+ .allocator = allocator ,
67+ .arena = std .heap .ArenaAllocator .init (allocator ),
10868 .node_pool = std .heap .MemoryPool (Node ).init (allocator ),
10969 };
11070 }
@@ -114,12 +74,14 @@ pub const Registry = struct {
11474 self .lookup_by_id .deinit (allocator );
11575 self .lookup_by_node .deinit (allocator );
11676 self .node_pool .deinit ();
77+ self .arena .deinit ();
11778 }
11879
11980 pub fn reset (self : * Registry ) void {
12081 self .lookup_by_id .clearRetainingCapacity ();
12182 self .lookup_by_node .clearRetainingCapacity ();
122- _ = self .node_pool .reset (.{ .retain_capacity = {} });
83+ _ = self .arena .reset (.{ .retain_with_limit = 1024 });
84+ _ = self .node_pool .reset (.{ .retain_with_limit = 1024 });
12385 }
12486
12587 pub fn register (self : * Registry , n : * parser.Node ) ! * Node {
@@ -132,11 +94,10 @@ pub const Registry = struct {
13294 // but, just in case, let's try to keep things tidy.
13395 errdefer _ = self .lookup_by_node .remove (n );
13496
135- const children = try parser .nodeGetChildNodes (n );
136- const children_count = try parser .nodeListLength (children );
137-
13897 const id = self .node_id ;
139- defer self .node_id = id + 1 ;
98+ self .node_id = id + 1 ;
99+
100+ const child_nodes = try self .registerChildNodes (n );
140101
141102 const node = try self .node_pool .create ();
142103 errdefer self .node_pool .destroy (node );
@@ -146,12 +107,12 @@ pub const Registry = struct {
146107 .id = id ,
147108 .parent_id = null , // TODO
148109 .backend_node_id = id , // ??
149- .node_name = try parser .nodeName (n ),
150- .local_name = try parser .nodeLocalName (n ),
151- .node_value = try parser .nodeValue (n ) orelse "" ,
152- .node_type = @intFromEnum (try parser .nodeType (n )),
153- .child_node_count = children_count ,
154- .children = &.{}, // TODO
110+ .node_name = parser .nodeName (n ) catch return error . NodeNameError ,
111+ .local_name = parser .nodeLocalName (n ) catch return error . NodeLocalNameError ,
112+ .node_value = ( parser .nodeValue (n ) catch return error . NameValueError ) orelse "" ,
113+ .node_type = @intFromEnum (parser .nodeType (n ) catch return error . NodeTypeError ),
114+ .child_node_count = @intCast ( child_nodes . len ) ,
115+ .children = child_nodes ,
155116 .document_url = null ,
156117 .base_url = null ,
157118 .xml_version = "" ,
@@ -168,6 +129,31 @@ pub const Registry = struct {
168129 try self .lookup_by_id .putNoClobber (self .allocator , id , node );
169130 return node ;
170131 }
132+
133+ pub fn registerChildNodes (self : * Registry , n : * parser.Node ) RegisterError ! []* Node {
134+ const node_list = parser .nodeGetChildNodes (n ) catch return error .GetChildNodeError ;
135+ const count = parser .nodeListLength (node_list ) catch return error .NodeListLengthError ;
136+
137+ var arr = try self .arena .allocator ().alloc (* Node , count );
138+ var i : usize = 0 ;
139+ for (0.. count ) | _ | {
140+ const child = (parser .nodeListItem (node_list , @intCast (i )) catch return error .NodeListItemError ) orelse continue ;
141+ arr [i ] = try self .register (child );
142+ i += 1 ;
143+ }
144+ return arr [0.. i ];
145+ }
146+ };
147+
148+ const RegisterError = error {
149+ OutOfMemory ,
150+ GetChildNodeError ,
151+ NodeListLengthError ,
152+ NodeListItemError ,
153+ NodeNameError ,
154+ NodeLocalNameError ,
155+ NameValueError ,
156+ NodeTypeError ,
171157};
172158
173159const NodeContext = struct {
@@ -271,8 +257,76 @@ pub const Search = struct {
271257 };
272258};
273259
260+ // Need a custom writer, because we can't just serialize the node as-is.
261+ // Sometimes we want to serializ the node without chidren, sometimes with just
262+ // its direct children, and sometimes the entire tree.
263+ // (For now, we only support direct children)
264+ pub const Writer = struct {
265+ opts : Opts ,
266+ node : * const Node ,
267+
268+ pub const Opts = struct {};
269+
270+ pub fn jsonStringify (self : * const Writer , w : anytype ) ! void {
271+ try w .beginObject ();
272+ try writeCommon (self .node , w );
273+ try w .objectField ("children" );
274+ try w .beginArray ();
275+ for (self .node .children ) | node | {
276+ try w .beginObject ();
277+ try writeCommon (node , w );
278+ try w .endObject ();
279+ }
280+ try w .endArray ();
281+ try w .endObject ();
282+ }
283+
284+ fn writeCommon (node : * const Node , w : anytype ) ! void {
285+ try w .objectField ("nodeId" );
286+ try w .write (node .id );
287+
288+ if (node .parent_id ) | pid | {
289+ try w .objectField ("parentId" );
290+ try w .write (pid );
291+ }
292+
293+ try w .objectField ("backendNodeId" );
294+ try w .write (node .backend_node_id );
295+
296+ try w .objectField ("nodeType" );
297+ try w .write (node .node_type );
298+
299+ try w .objectField ("nodeName" );
300+ try w .write (node .node_name );
301+
302+ try w .objectField ("localName" );
303+ try w .write (node .local_name );
304+
305+ try w .objectField ("nodeValue" );
306+ try w .write (node .node_value );
307+
308+ try w .objectField ("childNodeCount" );
309+ try w .write (node .child_node_count );
310+
311+ try w .objectField ("documentURL" );
312+ try w .write (node .document_url );
313+
314+ try w .objectField ("baseURL" );
315+ try w .write (node .base_url );
316+
317+ try w .objectField ("xmlVersion" );
318+ try w .write (node .xml_version );
319+
320+ try w .objectField ("compatibilityMode" );
321+ try w .write (node .compatibility_mode );
322+
323+ try w .objectField ("isScrollable" );
324+ try w .write (node .is_scrollable );
325+ }
326+ };
327+
274328const testing = @import ("testing.zig" );
275- test "CDP Node: Registry register" {
329+ test "cdp Node: Registry register" {
276330 var registry = Registry .init (testing .allocator );
277331 defer registry .deinit ();
278332
@@ -298,7 +352,8 @@ test "CDP Node: Registry register" {
298352 try testing .expectEqual ("a" , node .local_name );
299353 try testing .expectEqual ("" , node .node_value );
300354 try testing .expectEqual (1 , node .child_node_count );
301- try testing .expectEqual (0 , node .children .len );
355+ try testing .expectEqual (1 , node .children .len );
356+ try testing .expectEqual (1 , node .children [0 ].id );
302357 try testing .expectEqual (null , node .document_url );
303358 try testing .expectEqual (null , node .base_url );
304359 try testing .expectEqual ("" , node .xml_version );
@@ -310,20 +365,21 @@ test "CDP Node: Registry register" {
310365 {
311366 const n = (try doc .querySelector ("p" )).? ;
312367 const node = try registry .register (n );
313- const n1b = registry .lookup_by_id .get (1 ).? ;
368+ const n1b = registry .lookup_by_id .get (2 ).? ;
314369 const n1c = registry .lookup_by_node .get (node ._node ).? ;
315370 try testing .expectEqual (node , n1b );
316371 try testing .expectEqual (node , n1c );
317372
318- try testing .expectEqual (1 , node .id );
373+ try testing .expectEqual (2 , node .id );
319374 try testing .expectEqual (null , node .parent_id );
320375 try testing .expectEqual (1 , node .node_type );
321- try testing .expectEqual (1 , node .backend_node_id );
376+ try testing .expectEqual (2 , node .backend_node_id );
322377 try testing .expectEqual ("P" , node .node_name );
323378 try testing .expectEqual ("p" , node .local_name );
324379 try testing .expectEqual ("" , node .node_value );
325380 try testing .expectEqual (1 , node .child_node_count );
326- try testing .expectEqual (0 , node .children .len );
381+ try testing .expectEqual (1 , node .children .len );
382+ try testing .expectEqual (3 , node .children [0 ].id );
327383 try testing .expectEqual (null , node .document_url );
328384 try testing .expectEqual (null , node .base_url );
329385 try testing .expectEqual ("" , node .xml_version );
@@ -333,7 +389,7 @@ test "CDP Node: Registry register" {
333389 }
334390}
335391
336- test "CDP Node: search list" {
392+ test "cdp Node: search list" {
337393 var registry = Registry .init (testing .allocator );
338394 defer registry .deinit ();
339395
@@ -383,3 +439,27 @@ test "CDP Node: search list" {
383439 try testing .expectEqual (2 , registry .lookup_by_node .count ());
384440 }
385441}
442+
443+ test "cdp Node: Writer" {
444+ var registry = Registry .init (testing .allocator );
445+ defer registry .deinit ();
446+
447+ var doc = try testing .Document .init ("<a id=a1></a><a id=a2></a>" );
448+ defer doc .deinit ();
449+
450+ {
451+ const node = try registry .register (doc .asNode ());
452+ const json = try std .json .stringifyAlloc (testing .allocator , node .writer (.{}), .{});
453+ defer testing .allocator .free (json );
454+
455+ try testing .expectJson (.{ .nodeId = 0 , .backendNodeId = 0 , .nodeType = 9 , .nodeName = "#document" , .localName = "" , .nodeValue = "" , .documentURL = null , .baseURL = null , .xmlVersion = "" , .isScrollable = false , .compatibilityMode = "NoQuirksMode" , .childNodeCount = 1 , .children = &.{.{ .nodeId = 1 , .backendNodeId = 1 , .nodeType = 1 , .nodeName = "HTML" , .localName = "html" , .nodeValue = "" , .childNodeCount = 2 , .documentURL = null , .baseURL = null , .xmlVersion = "" , .compatibilityMode = "NoQuirksMode" , .isScrollable = false }} }, json );
456+ }
457+
458+ {
459+ const node = registry .lookup_by_id .get (1 ).? ;
460+ const json = try std .json .stringifyAlloc (testing .allocator , node .writer (.{}), .{});
461+ defer testing .allocator .free (json );
462+
463+ try testing .expectJson (.{ .nodeId = 1 , .backendNodeId = 1 , .nodeType = 1 , .nodeName = "HTML" , .localName = "html" , .nodeValue = "" , .childNodeCount = 2 , .documentURL = null , .baseURL = null , .xmlVersion = "" , .compatibilityMode = "NoQuirksMode" , .isScrollable = false , .children = &.{ .{ .nodeId = 2 , .backendNodeId = 2 , .nodeType = 1 , .nodeName = "HEAD" , .localName = "head" , .nodeValue = "" , .childNodeCount = 0 , .documentURL = null , .baseURL = null , .xmlVersion = "" , .compatibilityMode = "NoQuirksMode" , .isScrollable = false }, .{ .nodeId = 3 , .backendNodeId = 3 , .nodeType = 1 , .nodeName = "BODY" , .localName = "body" , .nodeValue = "" , .childNodeCount = 2 , .documentURL = null , .baseURL = null , .xmlVersion = "" , .compatibilityMode = "NoQuirksMode" , .isScrollable = false } } }, json );
464+ }
465+ }
0 commit comments