Skip to content

Commit 2af71a6

Browse files
karlseguinsjorsdonkers
authored andcommitted
Lazily load nodes
Node registry now only tracks the node id (which we need to be consistent) and the underlying parser.Node. All other data is loaded on-demand (i.e. when we serialize the node). This allows us to serialize node values as they appear when they are serialized, as opposed to when they are registered.
1 parent 85a1cd7 commit 2af71a6

File tree

3 files changed

+154
-132
lines changed

3 files changed

+154
-132
lines changed

src/cdp/Node.zig

Lines changed: 143 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -22,32 +22,13 @@ const Allocator = std.mem.Allocator;
2222

2323
pub const Id = u32;
2424

25+
const log = std.log.scoped(.cdp_node);
26+
2527
const Node = @This();
2628

2729
id: Id,
28-
parent_id: ?Id = null,
29-
node_type: u32,
30-
backend_node_id: Id,
31-
node_name: []const u8,
32-
local_name: []const u8,
33-
node_value: []const u8,
34-
child_node_count: u32,
35-
children: []const *Node,
36-
document_url: ?[]const u8,
37-
base_url: ?[]const u8,
38-
xml_version: []const u8,
39-
compatibility_mode: CompatibilityMode,
40-
is_scrollable: bool,
4130
_node: *parser.Node,
4231

43-
const CompatibilityMode = enum {
44-
NoQuirksMode,
45-
};
46-
47-
pub fn writer(self: *const Node, opts: Writer.Opts) Writer {
48-
return .{ .node = self, .opts = opts };
49-
}
50-
5132
// Whenever we send a node to the client, we register it here for future lookup.
5233
// We maintain a node -> id and id -> node lookup.
5334
pub const Registry = struct {
@@ -94,66 +75,21 @@ pub const Registry = struct {
9475
// but, just in case, let's try to keep things tidy.
9576
errdefer _ = self.lookup_by_node.remove(n);
9677

97-
const id = self.node_id;
98-
self.node_id = id + 1;
99-
100-
const child_nodes = try self.registerChildNodes(n);
101-
10278
const node = try self.node_pool.create();
10379
errdefer self.node_pool.destroy(node);
10480

81+
const id = self.node_id;
82+
self.node_id = id + 1;
83+
10584
node.* = .{
10685
._node = n,
10786
.id = id,
108-
.parent_id = null, // TODO
109-
.backend_node_id = id, // ??
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,
116-
.document_url = null,
117-
.base_url = null,
118-
.xml_version = "",
119-
.compatibility_mode = .NoQuirksMode,
120-
.is_scrollable = false,
12187
};
12288

123-
// if (try parser.nodeParentNode(n)) |pn| {
124-
// _ = pn;
125-
// // TODO
126-
// }
127-
12889
node_lookup_gop.value_ptr.* = node;
12990
try self.lookup_by_id.putNoClobber(self.allocator, id, node);
13091
return node;
13192
}
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,
15793
};
15894

15995
const NodeContext = struct {
@@ -261,67 +197,98 @@ pub const Search = struct {
261197
// Sometimes we want to serializ the node without chidren, sometimes with just
262198
// its direct children, and sometimes the entire tree.
263199
// (For now, we only support direct children)
200+
264201
pub const Writer = struct {
265202
opts: Opts,
266203
node: *const Node,
204+
registry: *Registry,
267205

268206
pub const Opts = struct {};
269207

270208
pub fn jsonStringify(self: *const Writer, w: anytype) !void {
209+
self.toJSON(w) catch |err| {
210+
// The only error our jsonStringify method can return is
211+
// @TypeOf(w).Error. In other words, our code can't return its own
212+
// error, we can only return a writer error. Kinda sucks.
213+
log.err("json stringify: {}", .{err});
214+
return error.OutOfMemory;
215+
};
216+
}
217+
218+
fn toJSON(self: *const Writer, w: anytype) !void {
271219
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();
220+
try writeCommon(self.node, false, w);
221+
222+
{
223+
var registry = self.registry;
224+
const child_nodes = try parser.nodeGetChildNodes(self.node._node);
225+
const child_count = try parser.nodeListLength(child_nodes);
226+
227+
var i: usize = 0;
228+
try w.objectField("children");
229+
try w.beginArray();
230+
for (0..child_count) |_| {
231+
const child = (try parser.nodeListItem(child_nodes, @intCast(i))) orelse continue;
232+
const child_node = try registry.register(child);
233+
try w.beginObject();
234+
try writeCommon(child_node, true, w);
235+
try w.endObject();
236+
i += 1;
237+
}
238+
try w.endArray();
239+
240+
try w.objectField("childNodeCount");
241+
try w.write(i);
279242
}
280-
try w.endArray();
243+
281244
try w.endObject();
282245
}
283246

284-
fn writeCommon(node: *const Node, w: anytype) !void {
247+
fn writeCommon(node: *const Node, include_child_count: bool, w: anytype) !void {
285248
try w.objectField("nodeId");
286249
try w.write(node.id);
287250

288-
if (node.parent_id) |pid| {
289-
try w.objectField("parentId");
290-
try w.write(pid);
291-
}
292-
293251
try w.objectField("backendNodeId");
294-
try w.write(node.backend_node_id);
252+
try w.write(node.id);
253+
254+
const n = node._node;
255+
256+
// TODO:
257+
// try w.objectField("parentId");
258+
// try w.write(pid);
295259

296260
try w.objectField("nodeType");
297-
try w.write(node.node_type);
261+
try w.write(@intFromEnum(try parser.nodeType(n)));
298262

299263
try w.objectField("nodeName");
300-
try w.write(node.node_name);
264+
try w.write(try parser.nodeName(n));
301265

302266
try w.objectField("localName");
303-
try w.write(node.local_name);
267+
try w.write(try parser.nodeLocalName(n));
304268

305269
try w.objectField("nodeValue");
306-
try w.write(node.node_value);
270+
try w.write((try parser.nodeValue(n)) orelse "");
307271

308-
try w.objectField("childNodeCount");
309-
try w.write(node.child_node_count);
272+
if (include_child_count) {
273+
try w.objectField("childNodeCount");
274+
const child_nodes = try parser.nodeGetChildNodes(n);
275+
try w.write(try parser.nodeListLength(child_nodes));
276+
}
310277

311278
try w.objectField("documentURL");
312-
try w.write(node.document_url);
279+
try w.write(null);
313280

314281
try w.objectField("baseURL");
315-
try w.write(node.base_url);
282+
try w.write(null);
316283

317284
try w.objectField("xmlVersion");
318-
try w.write(node.xml_version);
285+
try w.write("");
319286

320287
try w.objectField("compatibilityMode");
321-
try w.write(node.compatibility_mode);
288+
try w.write("NoQuirksMode");
322289

323290
try w.objectField("isScrollable");
324-
try w.write(node.is_scrollable);
291+
try w.write(false);
325292
}
326293
};
327294

@@ -345,46 +312,18 @@ test "cdp Node: Registry register" {
345312
try testing.expectEqual(node, n1c);
346313

347314
try testing.expectEqual(0, node.id);
348-
try testing.expectEqual(null, node.parent_id);
349-
try testing.expectEqual(1, node.node_type);
350-
try testing.expectEqual(0, node.backend_node_id);
351-
try testing.expectEqual("A", node.node_name);
352-
try testing.expectEqual("a", node.local_name);
353-
try testing.expectEqual("", node.node_value);
354-
try testing.expectEqual(1, node.child_node_count);
355-
try testing.expectEqual(1, node.children.len);
356-
try testing.expectEqual(1, node.children[0].id);
357-
try testing.expectEqual(null, node.document_url);
358-
try testing.expectEqual(null, node.base_url);
359-
try testing.expectEqual("", node.xml_version);
360-
try testing.expectEqual(.NoQuirksMode, node.compatibility_mode);
361-
try testing.expectEqual(false, node.is_scrollable);
362315
try testing.expectEqual(n, node._node);
363316
}
364317

365318
{
366319
const n = (try doc.querySelector("p")).?;
367320
const node = try registry.register(n);
368-
const n1b = registry.lookup_by_id.get(2).?;
321+
const n1b = registry.lookup_by_id.get(1).?;
369322
const n1c = registry.lookup_by_node.get(node._node).?;
370323
try testing.expectEqual(node, n1b);
371324
try testing.expectEqual(node, n1c);
372325

373-
try testing.expectEqual(2, node.id);
374-
try testing.expectEqual(null, node.parent_id);
375-
try testing.expectEqual(1, node.node_type);
376-
try testing.expectEqual(2, node.backend_node_id);
377-
try testing.expectEqual("P", node.node_name);
378-
try testing.expectEqual("p", node.local_name);
379-
try testing.expectEqual("", node.node_value);
380-
try testing.expectEqual(1, node.child_node_count);
381-
try testing.expectEqual(1, node.children.len);
382-
try testing.expectEqual(3, node.children[0].id);
383-
try testing.expectEqual(null, node.document_url);
384-
try testing.expectEqual(null, node.base_url);
385-
try testing.expectEqual("", node.xml_version);
386-
try testing.expectEqual(.NoQuirksMode, node.compatibility_mode);
387-
try testing.expectEqual(false, node.is_scrollable);
326+
try testing.expectEqual(1, node.id);
388327
try testing.expectEqual(n, node._node);
389328
}
390329
}
@@ -449,17 +388,92 @@ test "cdp Node: Writer" {
449388

450389
{
451390
const node = try registry.register(doc.asNode());
452-
const json = try std.json.stringifyAlloc(testing.allocator, node.writer(.{}), .{});
391+
const json = try std.json.stringifyAlloc(testing.allocator, Writer{
392+
.node = node,
393+
.opts = .{},
394+
.registry = &registry,
395+
}, .{});
453396
defer testing.allocator.free(json);
454397

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);
398+
try testing.expectJson(.{
399+
.nodeId = 0,
400+
.backendNodeId = 0,
401+
.nodeType = 9,
402+
.nodeName = "#document",
403+
.localName = "",
404+
.nodeValue = "",
405+
.documentURL = null,
406+
.baseURL = null,
407+
.xmlVersion = "",
408+
.isScrollable = false,
409+
.compatibilityMode = "NoQuirksMode",
410+
.childNodeCount = 1,
411+
.children = &.{.{
412+
.nodeId = 1,
413+
.backendNodeId = 1,
414+
.nodeType = 1,
415+
.nodeName = "HTML",
416+
.localName = "html",
417+
.nodeValue = "",
418+
.childNodeCount = 2,
419+
.documentURL = null,
420+
.baseURL = null,
421+
.xmlVersion = "",
422+
.compatibilityMode = "NoQuirksMode",
423+
.isScrollable = false,
424+
}},
425+
}, json);
456426
}
457427

458428
{
459429
const node = registry.lookup_by_id.get(1).?;
460-
const json = try std.json.stringifyAlloc(testing.allocator, node.writer(.{}), .{});
430+
const json = try std.json.stringifyAlloc(testing.allocator, Writer{
431+
.node = node,
432+
.opts = .{},
433+
.registry = &registry,
434+
}, .{});
461435
defer testing.allocator.free(json);
462436

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);
437+
try testing.expectJson(.{
438+
.nodeId = 1,
439+
.backendNodeId = 1,
440+
.nodeType = 1,
441+
.nodeName = "HTML",
442+
.localName = "html",
443+
.nodeValue = "",
444+
.childNodeCount = 2,
445+
.documentURL = null,
446+
.baseURL = null,
447+
.xmlVersion = "",
448+
.compatibilityMode = "NoQuirksMode",
449+
.isScrollable = false,
450+
.children = &.{ .{
451+
.nodeId = 2,
452+
.backendNodeId = 2,
453+
.nodeType = 1,
454+
.nodeName = "HEAD",
455+
.localName = "head",
456+
.nodeValue = "",
457+
.childNodeCount = 0,
458+
.documentURL = null,
459+
.baseURL = null,
460+
.xmlVersion = "",
461+
.compatibilityMode = "NoQuirksMode",
462+
.isScrollable = false,
463+
}, .{
464+
.nodeId = 3,
465+
.backendNodeId = 3,
466+
.nodeType = 1,
467+
.nodeName = "BODY",
468+
.localName = "body",
469+
.nodeValue = "",
470+
.childNodeCount = 2,
471+
.documentURL = null,
472+
.baseURL = null,
473+
.xmlVersion = "",
474+
.compatibilityMode = "NoQuirksMode",
475+
.isScrollable = false,
476+
} },
477+
}, json);
464478
}
465479
}

src/cdp/cdp.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,14 @@ pub fn BrowserContext(comptime CDP_T: type) type {
325325
self.node_search_list.reset();
326326
}
327327

328+
pub fn nodeWriter(self: *Self, node: *const Node, opts: Node.Writer.Opts) Node.Writer {
329+
return .{
330+
.node = node,
331+
.opts = opts,
332+
.registry = &self.node_registry,
333+
};
334+
}
335+
328336
pub fn onInspectorResponse(ctx: *anyopaque, _: u32, msg: []const u8) void {
329337
if (std.log.defaultLogEnabled(.debug)) {
330338
// msg should be {"id":<id>,...

0 commit comments

Comments
 (0)