For typically name-less elements, check the "role" attribute to see if we should
fallback to the content.
Improves a handful of WPT tests, e.g.: /accname/name/comp_text_node.html
(It seems impossible to get 100% on these tests without knowing what is and
isn't a block-level element which would require more knowledge in the
StyleManager).
Introduces `Input.getRedactedValue` to mask password values in
LLM-facing dumps (semantic tree, forms, AXNode) instead of
exposing raw values or using ad-hoc checks.
Fold QueryWriter into Writer behind an Opts.filter. Tree mode is unchanged
(filter=null); query mode walks the full subtree (including AX-ignored
nodes per the queryAXTree spec) and emits the flat-match shape. Shared
resolveRole helper handles label-promotion for both paths so the two
can't drift.
Drop the "objectId not yet supported" carve-out: queryAXTree now reuses
dom.getNode, which already resolves nodeId/backendNodeId/objectId.
Match Writer.writeNode (line 488-499): when a <label> targets a CSS-hidden
checkbox/radio (toggle-switch / CSS-only radio pattern), emit the label
with the input's role so clients searching by role land on the label's
clickable backendDOMNodeId rather than the hidden input.
Without this, queryAXTree(role="checkbox", name=...) on a toggle-switch
returns the display:none input — a non-actionable target.
Adds Accessibility.queryAXTree for finding AX nodes by role and/or
accessible name without serializing the full tree. Motivated by
agentic / MCP automation workloads where getFullAXTree round-trips
multi-MB JSON on complex pages.
QueryWriter walks the DOM subtree rooted at the requested node,
reuses the existing role + name resolution + ignore logic from
AXNode, and emits a flat array of matches. Reuses the same
VisibilityCache + LabelByForIndex + temp arena pattern as
axnodeWriter, so no extra retained state.
MVP limitations, each a small follow-up PR:
- objectId param returns a specific not-yet-supported error
- matches emit empty properties/childIds and no parentId
- frameId not parsed; queries the current frame
Follow up to https://github.com/lightpanda-io/browser/pull/2200
This change is actually pretty mundane, but a bunch of files that used to
take a *Session (e.g. every WebAPI releaseRef and deinit) now take a *Page.
This aims to separate the 2 lifetimes currently managed by Session by moving
the "Page" lifetime to a dedicated container: Page. Ultimately, the goal is to
remove the 1-page-per-session limit of the current design. Not to explicitly
support multiple pages per session (though, that's more possible now), but
in order to better emulate Chrome where, during a navigation event, the old and
new page both exist.
Chromium's accessibility tree prunes elements hidden via `display:none`,
`visibility:hidden`, the `hidden` attribute, `inert`, or `aria-hidden="true"`
and we match this behavior. It's correct for most elements, but it breaks
a common form pattern: CSS-only toggle switches and custom radio groups
hide the real `<input>` and style the `<label>` as the interactive surface.
Before, an agent walking the AX tree for a toggle switch saw:
{role: "none", name: "Enable feature", ignored: false}
a generic element with no indication it's interactive and no exposure of
the underlying `checked` state. The input was pruned and the label had no
intrinsic role.
Now, when a `<label>` is associated (via `for=` or by wrapping) with a
hidden checkbox or radio input, the label is promoted to the input's role
and state:
{role: "checkbox", name: "Enable feature",
properties: [..., {checked: "true"}, {focusable: true}, ...]}
Native browsers already forward label clicks to the associated input, so
the label's `backendDOMNodeId` remains a valid click target — no action-
side changes needed.
Scope is intentionally narrow:
- Only checkbox and radio inputs. Other labelable controls (textarea,
select, etc.) don't have the "visible label as interactive surface"
idiom.
- Skipped when the label has an explicit `role=` attribute, to respect
the page's declared semantics.
- Skipped when the input is visible: normal AX tree flow already
surfaces it, and promoting would double-count.
The fixture `src/browser/tests/cdp/ax_tree.html` covers:
- `display:none` checkbox, checked, referenced by `<label for=id>`
- `display:none` radio, checked, referenced by `<label for=id>`
- `visibility:hidden` radio, unchecked, referenced by `<label for=id>`
- Wrapping `<label>` containing a `display:none` checkbox
This is to pave the way for introducing a new "Page" container, which will take
over the page lifecycle currently burdening Session. The ultimate goal of that
is to allow the Session to have multiple pages (mostly for better transitions
between pages), which is hard to do now since the Session has so much state.
This rename was aggressive, e.g. currentPage() -> currentFrame() so that, when
the new Page container is added, you won't see "currentPage()" and wonder:
"Does 'currentPage' mean the new Page container, or the Frame (which
used to be called Page)".
@import("lightpanda") where needed.
Would also like to do this for String, Page, Session and js which all stand out
as types that are use across the codebase.
I know that a few devs are doing this in new work and I haven't heard anyone
voice an objection.
Prunes hidden subtrees from the accessibility tree and implements
accessible name resolution via labels. Adds the `labels` property
to labellable HTML elements.
These new optional parameter run AFTER --wait-until, allowing the (imo) useful
combination of `--wait-until load --wait-script "report.complete === true"`.
However, if `--wait-until` IS NOT specified but `--wait-selector/script` IS,
then there is no default wait and it'll just check the selector/script. If
neither `--wait-selector` or `--wait-script/--wait-script-file` are specified
then `--wait-until` continues to default to `done`.
These waiters were added to the Runner, and the existing Action.waitForSelector
now uses the runner's version. Selector querying has been split into distinct
parse and query functions, so that we can parse once, and query on every tick.
We could potentially optimize --wait-script to compile the script once and call
it on each tick, but we'd have to detect page navigation to recompile the script
in the new context. Something I'd rather optimize separately.
The CDP Accessibility spec defines AXValue.value as always being a
string, but integer values were serialized as JSON numbers. This
breaks CDP clients with strict deserialization (e.g., Rust serde).
Fixes#1822
When calculating accessible names for elements without explicit labels, multiple descendant text nodes were previously concatenated directly together. This adds a space between distinct text node contents to prevent words from sticking together.
- Use `checkVisibility` for more accurate element visibility detection.
- Add support for color, date, file, and month AX roles.
- Optimize XPath generation by tracking sibling indices during the walk.
- Refine interactivity detection for form elements.
- Add more structural roles (banner, navigation, main, list, etc.).
- Implement fallback for accessible names (SVG titles, image alt text).
- Skip children for leaf-like semantic nodes to reduce redundancy.
- Disable pruning in the default semantic tree view.