@@ -44,67 +44,27 @@ const CompatibilityMode = enum {
44
44
NoQuirksMode ,
45
45
};
46
46
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 };
91
49
}
92
50
93
51
// Whenever we send a node to the client, we register it here for future lookup.
94
52
// We maintain a node -> id and id -> node lookup.
95
53
pub const Registry = struct {
96
54
node_id : u32 ,
97
55
allocator : Allocator ,
56
+ arena : std.heap.ArenaAllocator ,
98
57
node_pool : std .heap .MemoryPool (Node ),
99
58
lookup_by_id : std .AutoHashMapUnmanaged (Id , * Node ),
100
59
lookup_by_node : std .HashMapUnmanaged (* parser.Node , * Node , NodeContext , std .hash_map .default_max_load_percentage ),
101
60
102
61
pub fn init (allocator : Allocator ) Registry {
103
62
return .{
104
63
.node_id = 0 ,
105
- .allocator = allocator ,
106
64
.lookup_by_id = .{},
107
65
.lookup_by_node = .{},
66
+ .allocator = allocator ,
67
+ .arena = std .heap .ArenaAllocator .init (allocator ),
108
68
.node_pool = std .heap .MemoryPool (Node ).init (allocator ),
109
69
};
110
70
}
@@ -114,12 +74,14 @@ pub const Registry = struct {
114
74
self .lookup_by_id .deinit (allocator );
115
75
self .lookup_by_node .deinit (allocator );
116
76
self .node_pool .deinit ();
77
+ self .arena .deinit ();
117
78
}
118
79
119
80
pub fn reset (self : * Registry ) void {
120
81
self .lookup_by_id .clearRetainingCapacity ();
121
82
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 });
123
85
}
124
86
125
87
pub fn register (self : * Registry , n : * parser.Node ) ! * Node {
@@ -132,11 +94,10 @@ pub const Registry = struct {
132
94
// but, just in case, let's try to keep things tidy.
133
95
errdefer _ = self .lookup_by_node .remove (n );
134
96
135
- const children = try parser .nodeGetChildNodes (n );
136
- const children_count = try parser .nodeListLength (children );
137
-
138
97
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 );
140
101
141
102
const node = try self .node_pool .create ();
142
103
errdefer self .node_pool .destroy (node );
@@ -146,12 +107,12 @@ pub const Registry = struct {
146
107
.id = id ,
147
108
.parent_id = null , // TODO
148
109
.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 ,
155
116
.document_url = null ,
156
117
.base_url = null ,
157
118
.xml_version = "" ,
@@ -168,6 +129,31 @@ pub const Registry = struct {
168
129
try self .lookup_by_id .putNoClobber (self .allocator , id , node );
169
130
return node ;
170
131
}
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 ,
171
157
};
172
158
173
159
const NodeContext = struct {
@@ -271,8 +257,76 @@ pub const Search = struct {
271
257
};
272
258
};
273
259
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
+
274
328
const testing = @import ("testing.zig" );
275
- test "CDP Node: Registry register" {
329
+ test "cdp Node: Registry register" {
276
330
var registry = Registry .init (testing .allocator );
277
331
defer registry .deinit ();
278
332
@@ -298,7 +352,8 @@ test "CDP Node: Registry register" {
298
352
try testing .expectEqual ("a" , node .local_name );
299
353
try testing .expectEqual ("" , node .node_value );
300
354
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 );
302
357
try testing .expectEqual (null , node .document_url );
303
358
try testing .expectEqual (null , node .base_url );
304
359
try testing .expectEqual ("" , node .xml_version );
@@ -310,20 +365,21 @@ test "CDP Node: Registry register" {
310
365
{
311
366
const n = (try doc .querySelector ("p" )).? ;
312
367
const node = try registry .register (n );
313
- const n1b = registry .lookup_by_id .get (1 ).? ;
368
+ const n1b = registry .lookup_by_id .get (2 ).? ;
314
369
const n1c = registry .lookup_by_node .get (node ._node ).? ;
315
370
try testing .expectEqual (node , n1b );
316
371
try testing .expectEqual (node , n1c );
317
372
318
- try testing .expectEqual (1 , node .id );
373
+ try testing .expectEqual (2 , node .id );
319
374
try testing .expectEqual (null , node .parent_id );
320
375
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 );
322
377
try testing .expectEqual ("P" , node .node_name );
323
378
try testing .expectEqual ("p" , node .local_name );
324
379
try testing .expectEqual ("" , node .node_value );
325
380
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 );
327
383
try testing .expectEqual (null , node .document_url );
328
384
try testing .expectEqual (null , node .base_url );
329
385
try testing .expectEqual ("" , node .xml_version );
@@ -333,7 +389,7 @@ test "CDP Node: Registry register" {
333
389
}
334
390
}
335
391
336
- test "CDP Node: search list" {
392
+ test "cdp Node: search list" {
337
393
var registry = Registry .init (testing .allocator );
338
394
defer registry .deinit ();
339
395
@@ -383,3 +439,27 @@ test "CDP Node: search list" {
383
439
try testing .expectEqual (2 , registry .lookup_by_node .count ());
384
440
}
385
441
}
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