diff --git a/src/server/cdp/AXNode.zig b/src/server/cdp/AXNode.zig index af0fc17e0..b336bd9cf 100644 --- a/src/server/cdp/AXNode.zig +++ b/src/server/cdp/AXNode.zig @@ -80,15 +80,17 @@ pub const Writer = struct { } fn toJSON(self: *const Writer, w: anytype) !void { + 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, false, w); + try self.walkQuery(self.root.dom, &ignore_cache, w); } else { const root = AXNode.fromNode(self.root.dom); const root_hidden = if (self.root.dom.is(DOMNode.Element)) |el| isHidden(el, self.frame, .{}) else false; - if (try self.writeNode(self.root.id, root, false, root_hidden, w)) { - try self.writeNodeChildren(root, false, w); - } + try self.writeTree(root, root_hidden, &ignore_cache, w); } return w.endArray(); } @@ -118,28 +120,17 @@ pub const Writer = struct { try w.write(s); } - fn writeNodeChildren(self: *const Writer, parent: AXNode, in_aria_hidden: bool, w: anytype) !void { - // Add ListMarker for listitem elements - if (parent.dom.is(DOMNode.Element)) |parent_el| { - if (parent_el.getTag() == .li) { - try self.writeListMarker(parent.dom, w); - } - } - - const child_in_aria_hidden = in_aria_hidden or blk: { - const parent_el = parent.dom.is(DOMNode.Element) orelse break :blk false; - break :blk hasAriaHiddenTrue(parent_el); - }; - - var it = parent.dom.childrenIterator(); - const ignore_text = ignoreText(parent.dom); - while (it.next()) |dom_node| { + fn writeTree(self: *const Writer, root: AXNode, root_hidden: bool, ignore_cache: *IgnoreCache, w: anytype) !void { + var walker: Walker = .init(root.dom); + var descend = try self.writeNode(self.root.id, root, false, root_hidden, ignore_cache, w); + while (walker.next(descend)) |dom_node| { + descend = false; switch (dom_node._type) { .cdata => { if (dom_node.is(DOMNode.CData.Text) == null) { continue; } - if (ignore_text) { + if (ignoreText(dom_node._parent.?)) { continue; } }, @@ -147,8 +138,8 @@ pub const Writer = struct { // Prune hidden subtrees entirely (display:none, // visibility:hidden, aria-hidden, hidden, inert). Matches // Chromium: these elements aren't exposed to the AX tree. - const child_el = dom_node.as(DOMNode.Element); - if (child_in_aria_hidden or isHidden(child_el, self.frame, .{ .ancestors = false })) { + const el = dom_node.as(DOMNode.Element); + if (walker.inAriaHidden() or isHidden(el, self.frame, .{ .ancestors = false })) { continue; } }, @@ -156,10 +147,7 @@ pub const Writer = struct { } const node = try self.registry.register(dom_node); - const axn = AXNode.fromNode(node.dom); - if (try self.writeNode(node.id, axn, child_in_aria_hidden, false, w)) { - try self.writeNodeChildren(axn, child_in_aria_hidden, w); - } + descend = try self.writeNode(node.id, .fromNode(dom_node), walker.inAriaHidden(), false, ignore_cache, w); } } @@ -495,8 +483,7 @@ 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, w: anytype) !bool { - // ignore empty texts + fn writeNode(self: *const Writer, id: u32, axn: AXNode, in_aria_hidden: bool, hidden: bool, ignore_cache: *IgnoreCache, w: anytype) !bool { try w.beginObject(); try w.objectField("nodeId"); @@ -511,7 +498,7 @@ pub const Writer = struct { try w.objectField("role"); try self.writeAXValue(.{ .role = resolved.role }, w); - const ignore = axn.isIgnore(self.frame, in_aria_hidden, hidden); + const ignore = try axn.isIgnore(self.frame, in_aria_hidden, hidden, ignore_cache); try w.objectField("ignored"); try w.write(ignore); @@ -591,7 +578,7 @@ pub const Writer = struct { } // Skip hidden element children so childIds matches the - // subtree-pruning done in writeNodeChildren. + // subtree-pruning done in writeTree. if (child.is(DOMNode.Element)) |child_el| { if (child_in_aria_hidden or isHidden(child_el, self.frame, .{ .ancestors = false })) { continue; @@ -606,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; } @@ -648,25 +643,21 @@ pub const Writer = struct { // Query-mode walk. Visits every node under `root` (including AX-ignored // ones, per the queryAXTree spec) and defers emission to emitMatch. - fn walkQuery(self: *const Writer, node: *DOMNode, in_aria_hidden: bool, w: anytype) !void { - const axn = AXNode.fromNode(node); - try self.emitMatch(axn, in_aria_hidden, w); - - //
,