Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f263008
feat(a11y): add AxRole::Document and its role-support row
xxx Aug 24, 2026
3a2c8b6
feat(a11y-linux): map AT-SPI DocumentWeb and DocumentFrame to Document
xxx Aug 24, 2026
80483b0
test(a11y-windows): note the Document/TextArea trade the matrix defers
xxx Aug 24, 2026
c9a6550
feat(a11y): map AXWebArea to Document on macOS and iOS
xxx Aug 24, 2026
faa3304
fix(android a11y): a WebView is a Document, not an opaque Group (glas…
xxx Aug 24, 2026
fb4a596
feat(a11y): disclose a childless Document with the pixel path
xxx Aug 24, 2026
7990665
feat(mcp): surface the unpublished-Document guidance beside the trunc…
xxx Aug 24, 2026
b71d6a8
docs: describe the Document role and its disclosure
xxx Aug 24, 2026
99ee192
fix(a11y): a bounded walk no longer blames a childless Document on th…
xxx Aug 24, 2026
d1560bd
refactor(a11y): render AxRect bounds through one Display impl
xxx Aug 24, 2026
436146b
test(a11y): pin Document's classification, its outline line, and the …
xxx Aug 24, 2026
d441885
docs(a11y): the Windows Document cell and the web-area comments say w…
xxx Aug 24, 2026
08ded4e
docs: scope the Document role by platform and name the notice's id
xxx Aug 24, 2026
1889de2
docs: tighten the comments this fix wave added
xxx Aug 24, 2026
4c05388
fix(a11y): leave the max_nodes recourse to the notice that knows the …
xxx Aug 24, 2026
8640900
docs: fix the Document hedge trigger and drop the unread Windows claim
xxx Aug 24, 2026
c305654
refactor: terse comments
xxx Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ internal refactors, CI, or test-only changes.

## [Unreleased]

### Added
- `Document` accessibility role: a browser page or embedded web view (AT-SPI `document web`/`document frame`, `AXWebArea`, Android `WebView`) now reads as a `Document` whose children are the page's elements, on Linux, macOS, Android and iOS.

### Fixed
- A web view whose content the platform has not published is disclosed in the snapshot with its id, bounds and the pixel path, instead of arriving as an indistinguishable empty group.

## [1.5.0] - 2026-08-22

### Added
Expand Down
13 changes: 13 additions & 0 deletions crates/glass-a11y-linux/src/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ pub(crate) fn map_role(role: Role) -> AxRole {
Role::ToolBar => AxRole::Toolbar,
Role::StatusBar => AxRole::StatusBar,
Role::Heading => AxRole::Heading,
Role::DocumentWeb | Role::DocumentFrame => AxRole::Document,
_ => AxRole::Other,
}
}
Expand Down Expand Up @@ -108,6 +109,17 @@ mod tests {
assert_eq!(map_role(Role::Calendar), AxRole::Other);
}

#[test]
fn web_documents_map_to_document() {
// A browser's page root and an ARIA role=document region are both web documents;
// a text document (DocumentText) is a text area and stays one.
assert_eq!(map_role(Role::DocumentWeb), AxRole::Document);
assert_eq!(map_role(Role::DocumentFrame), AxRole::Document);
assert_eq!(map_role(Role::DocumentText), AxRole::TextArea);
// An embedded object (<embed>, <object>) is not a document.
assert_eq!(map_role(Role::Embedded), AxRole::Other);
}

#[test]
fn states_map_to_flags() {
let s =
Expand Down Expand Up @@ -177,6 +189,7 @@ mod tests {
(Role::ToolBar, AxRole::Toolbar),
(Role::StatusBar, AxRole::StatusBar),
(Role::Heading, AxRole::Heading),
(Role::DocumentWeb, AxRole::Document),
];

#[test]
Expand Down
7 changes: 7 additions & 0 deletions crates/glass-a11y-macos/src/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub const ROLE_TOKENS: &[(&str, AxRole)] = &[
("AXSplitter", AxRole::Separator),
("AXHeading", AxRole::Heading),
("AXMenuButton", AxRole::Button),
// The root of a web engine's subtree.
("AXWebArea", AxRole::Document),
];

/// Subroles that decide a role, and the base roles that can carry one.
Expand Down Expand Up @@ -231,6 +233,11 @@ mod tests {
assert_eq!(map_role("", None), AxRole::Other);
}

#[test]
fn a_web_area_is_a_document() {
assert_eq!(map_role("AXWebArea", None), AxRole::Document);
}

#[test]
fn a_switch_is_a_togglebutton_whichever_base_role_carries_it() {
// `AXToggle` is deliberately absent: AppKit documents it for on/off *buttons*, and no probe
Expand Down
3 changes: 2 additions & 1 deletion crates/glass-a11y-windows/src/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ mod tests {
#[test]
fn document_maps_from_an_observed_token() {
// Observed on a stock text editor — see the probe test in
// crates/glass-windows/tests/onbox.rs.
// crates/glass-windows/tests/onbox.rs. What a web document reports here is unread, so
// the role-support matrix records the Document cell as a gap until one is read.
assert_eq!(map_role(50030, false), AxRole::TextArea);
}

Expand Down
5 changes: 1 addition & 4 deletions crates/glass-android/src/a11y_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,10 +987,7 @@ fn movement_candidates<'a>(tree: &'a AxTree, target: &AxTarget) -> Vec<&'a AxNod

/// A rectangle as `(x,y wxh)`.
fn rect(b: Option<AxRect>) -> String {
b.map_or_else(
|| "(no bounds)".to_string(),
|r| format!("({},{} {}x{})", r.x, r.y, r.width, r.height),
)
b.map_or_else(|| "(no bounds)".to_string(), |r| r.to_string())
}

/// The refusal for a target more than one node now matches on everything but position, so its id
Expand Down
9 changes: 8 additions & 1 deletion crates/glass-android/src/axmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub const CLASS_TOKENS: &[(&str, AxRole)] = &[
("RecyclerView", AxRole::List),
("ListView", AxRole::List),
("GridView", AxRole::List),
("WebView", AxRole::Group),
("WebView", AxRole::Document),
// Containers the leaf-suffix rule below cannot catch, each observed in a real app's
// tree: the AndroidX card container, the AppCompat linear layout (shipped under two
// package names), the view that hosts a Compose hierarchy, and a swipe-paged container.
Expand Down Expand Up @@ -629,6 +629,13 @@ mod tests {
}
}

#[test]
fn a_webview_is_a_document_not_a_group() {
// glass#506: as a Group, a WebView whose content the reader could not enter was
// indistinguishable from an empty container.
assert_eq!(class_to_role("android.webkit.WebView"), AxRole::Document);
}

#[test]
fn class_tokens_have_no_duplicates() {
for (i, (leaf, _)) in CLASS_TOKENS.iter().enumerate() {
Expand Down
Loading