Skip to content

Commit 8ea99da

Browse files
alicedelan
authored andcommitted
WIP: Begin fleshing out accessibility design doc
Signed-off-by: Alice Boxhall <alice@igalia.com>
1 parent abb01de commit 8ea99da

2 files changed

Lines changed: 167 additions & 17 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Diagnosing Errors
Lines changed: 166 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,179 @@
11
# Accessibility
22

3-
## Central cache
3+
## Background: AccessKit concepts
44

5-
Applications using Servo must retain a central cache of the whole accessibility tree in the main process ([embedder process](architecture.md)).
6-
This is the same basic principle as the accessibility architecture of Chromium ([docs](https://chromium.googlesource.com/chromium/src/+/d779ec8c0ed366ee689e3a30132b9b8c98a9a941/docs/accessibility/browser/how_a11y_works_2.md)), Firefox ([Cache the World](https://www.jantrid.net/2022/12/22/Cache-the-World/)), and [AccessKit](https://accesskit.dev) ([how it works](https://accesskit.dev/how-it-works/)).
7-
Since assistive technologies need to query the tree frequently and synchronously, caching the tree centrally allows them to do so without incurring IPC latency or interrupting web content processes.
5+
[AccessKit](https://accesskit.dev) provides a platform-independent schema for exposing information about the application's UI to assistive technology APIs.
6+
7+
### Accessibility tree
8+
9+
Accessibility information is provided to platform accessibility APIs as a tree of nodes, representing the different parts of the UI - for example, a node representing a toolbar may contain nodes representing buttons.
10+
11+
Platform APIs allow assistive technologies like screen readers to present an alternative user interface (for example, a speech- or braille-based interface) to users, and allow those interfaces to be interacted with via the assistive technology by allowing the assistive technology to relay user interactions back to the application.
12+
13+
A browser's accessibility tree combines the accessibility tree for its own UI (the address bar, and so on) with the accessibility trees for any active documents, so that users can use assistive technology to interact with web pages being shown in the browser.
14+
15+
### Central cache
16+
17+
Since assistive technologies need to query the tree frequently and synchronously, multi-process browsers typically cache the tree centrally to allow them to do so without incurring IPC latency or interrupting web content processes.
18+
19+
This principle is behind the accessibility architecture of Chromium ([docs](https://chromium.googlesource.com/chromium/src/+/d779ec8c0ed366ee689e3a30132b9b8c98a9a941/docs/accessibility/browser/how_a11y_works_2.md)) and Firefox ([Cache the World](https://www.jantrid.net/2022/12/22/Cache-the-World/)). In these architectures, the main browser process retains an in-memory accessibility tree composed of the tree representing the browser UI plus the sub-trees for any web contents being shown in the browser (including tabs which are currently not showing). Each renderer process is responsible for communicating _updates_ to its accessibility tree to the main process for incorporation into the aggregated tree. The accessibility trees are all represented internally in a platform-independent manner, and mapped to the respective platform APIs for the platform the browser is running on.
20+
21+
The [basic design of AccessKit](https://accesskit.dev/how-it-works/) is based on Chromium's original multi-process accessibility architecture, which also influenced Firefox's "Cache the World" design. It provides a platform-independent, serializable schema centered on the concept of tree updates, as well as an API to allow consuming those updates to create an in-memory tree which can be mapped to platform APIs. Typically, application developers only need to be concerned with producing the tree updates; AccessKit provides "adapters" which consume the updates, retain the cached full tree, and communicate with platform APIs.
22+
23+
### AccessKit data types
24+
25+
#### `Node`
26+
27+
AccessKit provides a [`Node`](https://docs.rs/accesskit/0.24.0/accesskit/struct.Node.html) type to represent a node in the accessibility tree. A `Node` _must_ have a [`Role`](https://docs.rs/accesskit/0.24.0/accesskit/enum.Role.html) value, and may have many other properties, including [`children`](https://docs.rs/accesskit/0.24.0/accesskit/struct.Node.html#method.children).
28+
29+
`Node` is designed to be serializable, so it can be easily passed between processes.
30+
31+
#### `NodeId`
32+
33+
Each `Node` is associated with a [`NodeId`](https://docs.rs/accesskit/0.24.0/accesskit/struct.NodeId.html), which must be unique within the node's tree. Properties which refer to other nodes in the tree, including `children`, refer to nodes by their `NodeId`s.
34+
35+
`Node` doesn't have an ID property; rather, the mechanism for associating a `Node` with a `NodeId` is via `TreeUpdate`.
36+
37+
#### `TreeUpdate`
38+
39+
[`TreeUpdate`](https://docs.rs/accesskit/0.24.0/accesskit/struct.TreeUpdate.html) represents a _change_ to an accessibility tree. The initial full tree for an application or subtree is sent as a `TreeUpdate` with all known nodes, and the necessary metadata for the tree; subsequent `TreeUpdate`s need only include nodes which have changed and the tree's `TreeId`. Any node which is added or changed in any way, including adding or removing child nodes, must be included in the `TreeUpdate` in full (i.e. not only changed properties for the node).
40+
41+
Somewhat counter-intuitively, AccessKit doesn't provide a schema for an accessibility tree data structure to be used as a "source" for `TreeUpdate`s - it's up to the application to produce `TreeUpdate`s based on any UI changes in any way it sees fit.
42+
43+
The bulk of each `TreeUpdate` is a vector of `(NodeId, Node)` pairs; each `NodeId` must be unique to the [`TreeId`](https://docs.rs/accesskit/0.24.0/accesskit/struct.TreeId.html) that the `TreeUpdate` refers to. The rest of the `TreeUpdate` consists of the `TreeId` for the tree, the `NodeId` of the currently focused node, and optionally some metadata about the tree (required if this is the first `TreeUpdate` for this `TreeId`).
44+
45+
Each `TreeUpdate` can only have one `TreeId`; this determines the ID space for the `NodeId`s in the update.
46+
47+
### Subtrees
48+
49+
AccessKit allows applications to nest separate trees to avoid needing to maintain global uniqueness of NodeIds.
50+
51+
Nesting a tree as a subtree of another tree is a two-step process, where the order is critical:
52+
53+
1. Send a `TreeUpdate` for the _parent_ tree which includes a [`Node`](https://docs.rs/accesskit/0.24.0/accesskit/struct.Node.html) with a [`tree_id`](https://docs.rs/accesskit/0.24.0/accesskit/struct.Node.html#method.tree_id) value equal to the `TreeId` of the _child_ tree. This `Node` becomes a _graft node_ for the subtree.
54+
2. Send a `TreeUpdate` for the _child_ tree with the matching [`tree_id`](https://docs.rs/accesskit/0.24.0/accesskit/struct.TreeUpdate.html#structfield.tree_id) value.
55+
56+
Any `TreeUpdate` with a `tree_id` value other than [`TreeId::ROOT`](https://docs.rs/accesskit/0.24.0/accesskit/struct.TreeId.html#associatedconstant.ROOT) MUST be preceded by a `TreeUpdate` containing a `Node` with the same `tree_id` value; otherwise, the AccessKit adapter consuming the `TreeUpdate` will panic.
57+
58+
### Adapters
59+
60+
`Node`s and `TreeUpdate`s allow an application to describe a platform-independent accessibility tree. Adapters map between this platform-independent representation and the various platform-specific accessibility APIs.
61+
62+
Typically, an adapter will provide a method which takes a `TreeUpdate`, and uses the [`accesskit_consumer` API](https://docs.rs/accesskit_consumer/latest/accesskit_consumer/index.html) to update an in-memory tree which can be queried via platform APIs, triggering the appropriate notifications to the API in the process.
63+
64+
---
865

966
We do this by sending AccessKit [tree updates](https://docs.rs/accesskit/0.23.0/accesskit/struct.TreeUpdate.html) from layout (in web content processes) to an embedder-provided AccessKit adapter (in the main process), such as [accesskit_winit](https://docs.rs/accesskit_winit/0.31.1/accesskit_winit/struct.Adapter.html).
1067
Internally the adapter uses [accesskit_consumer](https://docs.rs/accesskit_consumer/0.33.1/accesskit_consumer/) to retain the tree.
1168

12-
## Subtrees
69+
([embedder process](architecture.md)).
70+
71+
---
72+
73+
74+
### Actions
75+
76+
Finally, AccessKit provides an [`ActionRequest`](https://docs.rs/accesskit/0.24.0/accesskit/struct.ActionRequest.html) type for relaying user actions from assistive technology back to the application in a platform-independent way. Adapters provide hooks for the application to be notified of action requests to be able to handle user actions.
77+
78+
## Servo accessibility for embedders
79+
80+
While the system is being developed, the [`accessibility_enabled`](https://doc.servo.org/servo/prefs/struct.Preferences.html#structfield.accessibility_enabled) pref must be set in order to enable the accessibility code to run.
81+
82+
The entry point for enabling accessibility is [`WebView::set_accessibility_active()`](https://doc.servo.org/servo/webview/struct.WebView.html#method.set_accessibility_active). This will return a randomly-generated `TreeId`, which will remain stable for the lifetime of the WebView. The WebView's `TreeId` can also be accessed via the [`accesskit_tree_id()`](https://doc.servo.org/servo/webview/struct.WebView.html#method.accesskit_tree_id) method.
83+
84+
Once accessibility is active for the WebView, it will begin to emit `TreeUpdate`s via the [`WebViewDelegate::notify_accessibility_tree_update()`](https://doc.servo.org/servo/trait.WebViewDelegate.html#method.notify_accessibility_tree_update) method. These updates include the AccessKit `TreeId` for the WebView, so they must be preceded by a `TreeUpdate` for the [root tree](https://docs.rs/accesskit/struct.TreeId.html#associatedconstant.ROOT) for the application which includes a [graft node](https://docs.rs/accesskit/struct.Node.html#method.tree_id) for the `WebView`'s subtree.
85+
86+
The `WebView` will continue to emit `TreeUpdate`s for any change to its accessibility tree until either its `set_accessibility_active()` method is used to deactivate the accessibility tree, or its lifetime ends. Accessibility tree changes will be triggered by navigations within the webview, as well as any changes to the currently active document. Servo manages subtrees within the `WebView`'s accessibility tree; the embedder only needs to ensure that there is a graft node for the `WebView` in its top-level tree, and that Servo's `TreeUpdate`s are sent to the adapter in the order in which they are emitted from Servo.
87+
88+
```
89+
TODO: diagram
90+
```
91+
92+
Still to come: embedder API for forwarding actions to the appropriate WebView.
93+
94+
## Servo accessibility tree internal design
95+
96+
![](https://notes.igalia.com/uploads/5d85e981-4c2b-41a9-875c-226781663a6d.png)
97+
By @delan in https://github.com/servo/servo/pull/43012
98+
99+
### WebView subtree
100+
101+
Each WebView has a minimal tree consisting of a [`ScrollView`](https://docs.rs/accesskit/0.24.0/accesskit/enum.Role.html#variant.ScrollView) and a graft node for the top-level pipeline (i.e. the top-level document).
102+
103+
When accessibility is activated, and when the top-level pipeline changes, the constellation sends an `EmbedderMsg::AccessibilityTreeIdChanged()` message with the new top-level `accesskit::TreeId`. This triggers the WebView to emit an `accesskit::TreeUpdate` updating the graft node with the new `TreeId`.
104+
105+
```
106+
// TODO: diagram
107+
```
108+
```
109+
// TODO: redundant with below, need to edit
110+
```
111+
112+
### Accesibility state for ConstellationWebView and Pipelines
113+
114+
When accessibility is activated for a WebView, the WebView notifies the constellation via `EmbedderToConstellationMessage::SetAccessibilityActive` that accessibility should be activated for the corresponding `ConstellationWebView.`
115+
116+
This sets the `accessibility_active` flag on the ConstellationWebView, and sends a `ScriptThreadMessage::SetAccessibilityActive` message to all active pipelines for the WebView.
117+
118+
The Constellation also immediately sends back a `EmbedderMsg::AccessibilityTreeIdChanged` message with the pipeline ID for the top-level pipeline for the WebView, so that the WebView can create a `TreeUpdate` for the graft node.
119+
120+
121+
### Deterministic `TreeId` generation for `Pipeline`s
122+
123+
[#43012](https://github.com/servo/servo/pull/43012)
124+
125+
To allow synchronous generation of the `TreeUpdate` updating the graft node's `TreeId`, we have a deterministic mapping from a `PipelineId` to `accesskit::TreeId`. This means we don't need to wait for the `Pipeline` to generate a random ID and notify the `WebView` of its value, so that the `TreeUpdate` with the graft node can be sent before the `TreeUpdate` with the document's initial tree.
126+
127+
This is implemented using the [`Uuid::new_v5()`](https://docs.rs/uuid/latest/uuid/struct.Uuid.html#method.new_v5) method, using a static namespace value combined with the pipeline ID.
128+
129+
130+
### Generating `TreeUpdate`s in `layout`
131+
132+
When the ScriptThread receives the `ScriptThreadMessage::SetAccessibilityActive` message for a pipeline, it locates the Document for that pipeline, and calls the `set_accessibility_active()` method on its LayoutThread.
133+
134+
This causes the LayoutThread to:
135+
- create an instance of `AccessibilityTree` which is stored in its `accessibility_tree` property and acts as a flag for its accessibility activation;
136+
- sets the `needs_accessibility_update` flag for the layout, which marks it as needing the accessibility tree to be updated in the next reflow.
137+
138+
If no reflow is otherwise required, having the `needs_accessibility_update` flag will ensure a rendering update occurs, as it is checked in Document's [`needs_rendering_update()`](https://doc.servo.org/script/dom/document/document/struct.Document.html#method.needs_rendering_update) method, and LayoutThread's [`can_skip_reflow_request_entirely()`](https://doc.servo.org/layout/layout_impl/struct.LayoutThread.html#method.can_skip_reflow_request_entirely) method.
139+
140+
When the next reflow occurs, if the LayoutThread's `accessibility_tree` property is set, the accessibility tree update occurs as a phase after all the other phases in [`handle_reflow()`](https://doc.servo.org/layout/layout_impl/struct.LayoutThread.html#method.handle_reflow). This primarily consists of calling the `update_tree()` method on `AccessibilityTree`, which returns a `TreeUpdate`. The `TreeUpdate` is emitted back to the embedder via [`ScriptThreadMessage::AccessibilityTreeUpdate`](https://doc.servo.org/script_traits/enum.ScriptThreadMessage.html#variant.AccessibilityTreeUpdate), [`EmbedderMessage::AccessibilityTreeUpdate`](https://doc.servo.org/servo/enum.EmbedderMsg.html#variant.AccessibilityTreeUpdate) and finally [`WebViewDelegate::notify_accessibility_tree_update()`](https://doc.servo.org/servo/trait.WebViewDelegate.html#method.notify_accessibility_tree_update).
141+
142+
### `AccessibilityTree`
143+
144+
```
145+
// TODO: link to generated docs once landed
146+
// Something like https://doc.servo.org/layout/accessibility_tree
147+
```
148+
149+
`AccessibilityTree` represents the accessibility tree for a particular Document.
150+
151+
```
152+
// TODO: finish
153+
```
154+
155+
156+
## Servo accessibility tree testing
157+
158+
- Using the `accesskit_consumer` API to generate an in-memory tree from Servo `TreeUpdates`
13159

14-
That whole accessibility tree is made up of subtrees that are built by different parties.
15-
The subtrees for each webview and document within that webview are built by the layout engine for that document, and the subtrees for any surrounding UI elements are built by the embedder.
160+
```
161+
// TODO: finish
162+
```
16163

17-
This creates some problems: we want to allow these different parties to build and update their subtrees independently of one another, on their own schedules, and use whatever [NodeId](https://docs.rs/accesskit/0.23.0/accesskit/struct.NodeId.html) values they like without worrying about conflicts.
18164

19-
Ad-hoc schemes for avoiding NodeId conflicts are problematic here.
20-
The type is only 64 bits wide, which is wide enough to avoid needing to [handle overflow](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc;l=2187-2215;drc=068b7e346d819075983f831a853bdf1da287973c), but not wide enough to [reliably avoid collisions between random values](https://en.wikipedia.org/wiki/Birthday_attack).
21-
If we partition the bits of NodeId into a “subtree id” and “local node id”, even in the most balanced case with 32 bits for each, both parts now need to worry about handling overflow.
165+
## Servoshell accessibility tree integration
22166

23-
In our initial proposal ([accesskit#641](https://github.com/AccessKit/accesskit/pull/641)), we considered adding support for subtrees at the adapter level, introducing a “wrapper” or “meta” adapter that statefully remaps every NodeId to another globally unique NodeId, using hash maps to guarantee uniqueness.
24-
This was cumbersome and unlikely to be performant.
167+
```
168+
// TODO: write
169+
```
25170

26-
The subtree feature that shipped ([accesskit#655](https://github.com/AccessKit/accesskit/pull/655)) solves these problems at the schema level by allowing tree updates to [specify](https://docs.rs/accesskit/0.23.0/accesskit/struct.TreeUpdate.html#structfield.tree_id) a 128-bit [TreeId](https://docs.rs/accesskit/0.23.0/accesskit/struct.TreeId.html), such that each node is uniquely identified by the tuple ([TreeId](https://docs.rs/accesskit/0.23.0/accesskit/struct.TreeId.html), [NodeId](https://docs.rs/accesskit/0.23.0/accesskit/struct.NodeId.html)).
27-
To configure how the subtrees are combined, a [Node](https://docs.rs/accesskit/0.23.0/accesskit/struct.Node.html) can be [designated](https://docs.rs/accesskit/0.23.0/accesskit/struct.Node.html#method.tree_id) as a graft node for some other subtree.
171+
# TODOs
28172

29-
> [!NOTE]
30-
> The adapter [internally maps this](https://docs.rs/accesskit_consumer/0.33.1/src/accesskit_consumer/node.rs.html#29-51) to another tuple (32-bit tree index, NodeId), forming a 96-bit value that gets exposed to the platform, but uniqueness of tree indices is managed by the adapter, so this is mostly an implementation detail that limits us to <math><msup><mn>2</mn><mn>32</mn></msup></math> concurrent subtrees.
173+
- Adapters typically use accesskit_consumer to ingest tree updates; consumer can also be useful in other ways
174+
- Add section on `accesskit_consumer`?
175+
- Future directions for testing
176+
- WebDriver methods
177+
- platform API testing using WPT integration (requires mapping DOM ID through to platform APIs which is not yet implemented in AccessKit)
178+
- Link to PRs introducing various concepts?
179+
- Maybe just a standalone paragraph with the GitHub PR number as a link

0 commit comments

Comments
 (0)