From 0d3dfa50f8b969f5a8b0a6ba2665836f8a6648bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Thu, 24 Sep 2026 16:16:25 +0200 Subject: [PATCH] AXNode: simplify iterative walks and scope the ignore cache to one call The list marker is written by writeNode, so writeTree and walkQuery share one loop shape. IgnoreCache lives on its own pooled arena for the call instead of frame.call_arena, which only resets on JS calls. The scan no longer caches its root, which is never asked about again. --- src/server/cdp/AXNode.zig | 166 ++++++++++++++++++-------------------- 1 file changed, 77 insertions(+), 89 deletions(-) diff --git a/src/server/cdp/AXNode.zig b/src/server/cdp/AXNode.zig index 8b0447a25..b336bd9cf 100644 --- a/src/server/cdp/AXNode.zig +++ b/src/server/cdp/AXNode.zig @@ -80,7 +80,10 @@ pub const Writer = struct { } fn toJSON(self: *const Writer, w: anytype) !void { - var ignore_cache: IgnoreCache = .empty; + const cache_arena = try self.frame.getArena(.medium, "AXNode.IgnoreCache"); + defer cache_arena.release(); + var ignore_cache: IgnoreCache = .{ .allocator = cache_arena.allocator() }; + try w.beginArray(); if (self.filter != null) { try self.walkQuery(self.root.dom, &ignore_cache, w); @@ -119,18 +122,8 @@ pub const Writer = struct { fn writeTree(self: *const Writer, root: AXNode, root_hidden: bool, ignore_cache: *IgnoreCache, w: anytype) !void { var walker: Walker = .init(root.dom); - var axn = root; var descend = try self.writeNode(self.root.id, root, false, root_hidden, ignore_cache, w); - while (true) { - if (descend) { - if (axn.dom.is(DOMNode.Element)) |el| { - if (el.getTag() == .li) { - try self.writeListMarker(axn.dom, w); - } - } - } - - const dom_node = walker.next(descend) orelse return; + while (walker.next(descend)) |dom_node| { descend = false; switch (dom_node._type) { .cdata => { @@ -154,8 +147,7 @@ pub const Writer = struct { } const node = try self.registry.register(dom_node); - axn = AXNode.fromNode(node.dom); - descend = try self.writeNode(node.id, axn, walker.inAriaHidden(), false, ignore_cache, w); + descend = try self.writeNode(node.id, .fromNode(dom_node), walker.inAriaHidden(), false, ignore_cache, w); } } @@ -492,7 +484,6 @@ pub const Writer = struct { // write a node. returns true if children must be written. fn writeNode(self: *const Writer, id: u32, axn: AXNode, in_aria_hidden: bool, hidden: bool, ignore_cache: *IgnoreCache, w: anytype) !bool { - // ignore empty texts try w.beginObject(); try w.objectField("nodeId"); @@ -602,6 +593,14 @@ pub const Writer = struct { try w.endObject(); + if (write_children) { + if (n.is(DOMNode.Element)) |el| { + if (el.getTag() == .li) { + try self.writeListMarker(n, w); + } + } + } + return write_children; } @@ -646,19 +645,19 @@ pub const Writer = struct { // ones, per the queryAXTree spec) and defers emission to emitMatch. fn walkQuery(self: *const Writer, root: *DOMNode, ignore_cache: *IgnoreCache, w: anytype) !void { var walker: Walker = .init(root); - var node = root; - while (true) { - const axn = AXNode.fromNode(node); + const root_axn: AXNode = .fromNode(root); + try self.emitMatch(root_axn, false, ignore_cache, w); + // ,