From d75ef37d4203246d4228aaf0e99070b5f1f8afea Mon Sep 17 00:00:00 2001 From: Rohit <71192000+rohitsux@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:36:20 +0530 Subject: [PATCH 1/2] feat(cdp): implement Accessibility.getPartialAXTree The Accessibility domain handled getFullAXTree and queryAXTree but not getPartialAXTree, so a client asking for the accessibility subtree rooted at a specific node got UnknownMethod. Tools like Playwright/Puppeteer and axe-core use getPartialAXTree for scoped a11y snapshots. Add it by mirroring the existing methods: resolve the node via dom.getNode (like queryAXTree) and emit its unfiltered AX subtree via bc.axnodeWriter (like getFullAXTree). fetchRelatives is accepted for protocol compatibility; subtree emission matches queryAXTree's scope. Adds tests for the missing-id and unknown-id error paths. --- src/cdp/domains/accessibility.zig | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/cdp/domains/accessibility.zig b/src/cdp/domains/accessibility.zig index 3ecf397b9..7120ad333 100644 --- a/src/cdp/domains/accessibility.zig +++ b/src/cdp/domains/accessibility.zig @@ -26,6 +26,7 @@ pub fn processMessage(cmd: *CDP.Command) !void { enable, disable, getFullAXTree, + getPartialAXTree, queryAXTree, }, cmd.input.action) orelse return error.UnknownMethod; @@ -33,6 +34,7 @@ pub fn processMessage(cmd: *CDP.Command) !void { .enable => return enable(cmd), .disable => return disable(cmd), .getFullAXTree => return getFullAXTree(cmd), + .getPartialAXTree => return getPartialAXTree(cmd), .queryAXTree => return queryAXTree(cmd), } } @@ -96,6 +98,29 @@ fn queryAXTree(cmd: *CDP.Command) !void { }) }, .{}); } +fn getPartialAXTree(cmd: *CDP.Command) !void { + const params = (try cmd.params(struct { + nodeId: ?u32 = null, + backendNodeId: ?u32 = null, + objectId: ?[]const u8 = null, + // Accepted for Chrome protocol compatibility. The ancestor chain is + // not yet emitted; this returns the subtree rooted at the resolved + // node, matching the scope of queryAXTree. + fetchRelatives: ?bool = true, + })) orelse return error.InvalidParams; + + const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; + const node = try dom.getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId); + + const frame = bc.mainFrame() orelse return error.FrameNotLoaded; + const temp_arena = try frame.getArena(.medium, "AXNode"); + defer frame.releaseArena(temp_arena); + + // No filter: emit the full accessibility subtree rooted at the resolved + // node, the same shape getFullAXTree produces for the document root. + return cmd.sendResult(.{ .nodes = try bc.axnodeWriter(temp_arena, node, .{}) }, .{}); +} + const testing = @import("../testing.zig"); test "cdp.accessibility: queryAXTree requires nodeId, backendNodeId or objectId" { @@ -126,3 +151,32 @@ test "cdp.accessibility: queryAXTree with unknown nodeId returns error" { }); try ctx.expectSentError(-31998, "NodeNotFound", .{ .id = 1 }); } + +test "cdp.accessibility: getPartialAXTree requires nodeId, backendNodeId or objectId" { + var ctx = try testing.context(); + defer ctx.deinit(); + + _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/ax_tree.html" }); + + // Params present but no node identifier — dom.getNode returns MissingParams. + try ctx.processMessage(.{ + .id = 1, + .method = "Accessibility.getPartialAXTree", + .params = .{ .fetchRelatives = true }, + }); + try ctx.expectSentError(-31998, "MissingParams", .{ .id = 1 }); +} + +test "cdp.accessibility: getPartialAXTree with unknown nodeId returns error" { + var ctx = try testing.context(); + defer ctx.deinit(); + + _ = try ctx.loadBrowserContext(.{ .id = "BID-A", .url = "cdp/ax_tree.html" }); + + try ctx.processMessage(.{ + .id = 1, + .method = "Accessibility.getPartialAXTree", + .params = .{ .nodeId = 99999 }, + }); + try ctx.expectSentError(-31998, "NodeNotFound", .{ .id = 1 }); +} From aa8bcb67e1fa2f1140ce17724339155458d493e1 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 16 Jul 2026 07:53:01 +0800 Subject: [PATCH 2/2] log not implemented on fetchRelatives == true (default) --- src/cdp/domains/accessibility.zig | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/cdp/domains/accessibility.zig b/src/cdp/domains/accessibility.zig index 7120ad333..861205c5b 100644 --- a/src/cdp/domains/accessibility.zig +++ b/src/cdp/domains/accessibility.zig @@ -17,10 +17,15 @@ // along with this program. If not, see . const std = @import("std"); +const lp = @import("lightpanda"); + const id = @import("../id.zig"); const CDP = @import("../CDP.zig"); + const dom = @import("dom.zig"); +const log = lp.log; + pub fn processMessage(cmd: *CDP.Command) !void { const action = std.meta.stringToEnum(enum { enable, @@ -106,9 +111,18 @@ fn getPartialAXTree(cmd: *CDP.Command) !void { // Accepted for Chrome protocol compatibility. The ancestor chain is // not yet emitted; this returns the subtree rooted at the resolved // node, matching the scope of queryAXTree. - fetchRelatives: ?bool = true, + fetchRelatives: ?bool = null, })) orelse return error.InvalidParams; + if (params.fetchRelatives orelse true) { + // orelse true, because that's what Chrome defaults too, and if people + // aren't setting it, then they're expecting true. + log.warn(.not_implemented, "getPartialAXTree", .{ + .cdp_cmd = "Accessibility.getPartialAXTree", + .param = "fetchRelatives", + }); + } + const bc = cmd.browser_context orelse return error.BrowserContextNotLoaded; const node = try dom.getNode(cmd.arena, bc, params.nodeId, params.backendNodeId, params.objectId); @@ -162,7 +176,7 @@ test "cdp.accessibility: getPartialAXTree requires nodeId, backendNodeId or obje try ctx.processMessage(.{ .id = 1, .method = "Accessibility.getPartialAXTree", - .params = .{ .fetchRelatives = true }, + .params = .{ .fetchRelatives = false }, }); try ctx.expectSentError(-31998, "MissingParams", .{ .id = 1 }); } @@ -176,7 +190,7 @@ test "cdp.accessibility: getPartialAXTree with unknown nodeId returns error" { try ctx.processMessage(.{ .id = 1, .method = "Accessibility.getPartialAXTree", - .params = .{ .nodeId = 99999 }, + .params = .{ .nodeId = 99999, .fetchRelatives = false }, }); try ctx.expectSentError(-31998, "NodeNotFound", .{ .id = 1 }); }