dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 1 | # Accessibility Overview |
| 2 | |
| 3 | Accessibility means ensuring that all users, including users with disabilities, |
| 4 | have equal access to software. One piece of this involves basic design |
| 5 | principles such as using appropriate font sizes and color contrast, |
| 6 | avoiding using color to convey important information, and providing keyboard |
| 7 | alternatives for anything that is normally accomplished with a pointing device. |
| 8 | However, when you see the word "accessibility" in a directory name in Chromium, |
| 9 | that code's purpose is to provide full access to Chromium's UI via external |
| 10 | accessibility APIs that are utilized by assistive technology. |
| 11 | |
| 12 | **Assistive technology** here refers to software or hardware which |
| 13 | makes use of these APIs to create an alternative interface for the user to |
| 14 | accommodate some specific needs, for example: |
| 15 | |
| 16 | Assistive technology includes: |
| 17 | |
| 18 | * Screen readers for blind users that describe the screen using |
| 19 | synthesized speech or braille |
| 20 | * Voice control applications that let you speak to the computer, |
Anastasia Helfinstein | 4cc18b2d | 2019-11-12 18:22:00 | [diff] [blame] | 21 | * Switch Access that lets you control the computer with a small number |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 22 | of physical switches, |
| 23 | * Magnifiers that magnify a portion of the screen, and often highlight the |
| 24 | cursor and caret for easier viewing, and |
| 25 | * Assistive learning and literacy software that helps users who have a hard |
| 26 | time reading print, by highlighting and/or speaking selected text |
| 27 | |
| 28 | In addition, because accessibility APIs provide a convenient and universal |
| 29 | way to explore and control applications, they're often used for automated |
| 30 | testing scripts, and UI automation software like password managers. |
| 31 | |
| 32 | Web browsers play an important role in this ecosystem because they need |
| 33 | to not only provide access to their own UI, but also provide access to |
| 34 | all of the content of the web. |
| 35 | |
| 36 | Each operating system has its own native accessibility API. While the |
| 37 | core APIs tend to be well-documented, it's unfortunately common for |
| 38 | screen readers in particular to depend on additional undocumented or |
| 39 | vendor-specific APIs in order to fully function, especially with web |
| 40 | browsers, because the standard APIs are insufficient to handle the |
| 41 | complexity of the web. |
| 42 | |
| 43 | Chromium needs to support all of these operating system and |
| 44 | vendor-specific accessibility APIs in order to be usable with the full |
| 45 | ecosystem of assistive technology on all platforms. Just like Chromium |
| 46 | sometimes mimics the quirks and bugs of older browsers, Chromium often |
| 47 | needs to mimic the quirks and bugs of other browsers' implementation |
| 48 | of accessibility APIs, too. |
| 49 | |
| 50 | ## Concepts |
| 51 | |
| 52 | While each operating system and vendor accessibility API is different, |
| 53 | there are some concepts all of them share. |
| 54 | |
| 55 | 1. The *tree*, which models the entire interface as a tree of objects, exposed |
| 56 | to assistive technology via accessibility APIs; |
| 57 | 2. *Events*, which let assistive technology know that a part of the tree has |
| 58 | changed somehow; |
| 59 | 3. *Actions*, which come from assistive technology and ask the interface to |
| 60 | change. |
| 61 | |
| 62 | Consider the following small HTML file: |
| 63 | |
| 64 | ``` |
| 65 | <html> |
| 66 | <head> |
| 67 | <title>How old are you?</title> |
| 68 | </head> |
| 69 | <body> |
| 70 | <label for="age">Age</label> |
| 71 | <input id="age" type="number" name="age" value="42"> |
| 72 | <div> |
| 73 | <button>Back</button> |
| 74 | <button>Next</button> |
| 75 | </div> |
| 76 | </body> |
| 77 | </html> |
| 78 | ``` |
| 79 | |
| 80 | ### The Accessibility Tree and Accessibility Attributes |
| 81 | |
| 82 | Internally, Chromium represents the accessibility tree for that web page |
| 83 | using a data structure something like this: |
| 84 | |
| 85 | ``` |
| 86 | id=1 role=WebArea name="How old are you?" |
| 87 | id=2 role=Label name="Age" |
| 88 | id=3 role=TextField labelledByIds=[2] value="42" |
| 89 | id=4 role=Group |
| 90 | id=5 role=Button name="Back" |
| 91 | id=6 role=Button name="Next" |
| 92 | ``` |
| 93 | |
| 94 | Note that the tree structure closely resembles the structure of the |
| 95 | HTML elements, but slightly simplified. Each node in the accessibility |
| 96 | tree has an ID and a role. Many have a name. The text field has a value, |
| 97 | and instead of a name it has labelledByIds, which indicates that its |
| 98 | accessible name comes from another node in the tree, the label node |
| 99 | with id=2. |
| 100 | |
| 101 | On a particular platform, each node in the accessibility tree is implemented |
| 102 | by an object that conforms to a particular protocol. |
| 103 | |
| 104 | On Windows, the root node implements the IAccessible protocol and |
| 105 | if you call IAccessible::get_accRole, it returns ROLE_SYSTEM_DOCUMENT, |
| 106 | and if you call IAccessible::get_accName, it returns "How old are you?". |
| 107 | Other methods let you walk the tree. |
| 108 | |
Jacobo Aragunde Pérez | 0af076da | 2022-01-13 15:38:55 | [diff] [blame] | 109 | The Linux accessibility API, [ATK](https://gnome.pages.gitlab.gnome.org/atk/), |
| 110 | is similar to [IAccessible2](https://wiki.linuxfoundation.org/accessibility/iaccessible2/start), |
Aaron Leventhal | 6d0183a | 2021-12-20 19:59:40 | [diff] [blame] | 111 | aka IA2. Historical note: IA2 was developed to extend MSAA/IAccessible to add |
| 112 | richer document support, in a way that was harmonious with ATK, in order to |
| 113 | simplify implementing them both within the same product. Both APIs are |
| 114 | maintained by the Linux Foundation. |
| 115 | |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 116 | On macOS, the root node implements the NSAccessibility protocol and |
| 117 | if you call [NSAccessibility accessibilityRole], it returns @"AXWebArea", |
| 118 | and if you call [NSAccessibility accessibilityLabel], it returns |
| 119 | "How old are you?". |
| 120 | |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 121 | The Android accessibility API is of course based on Java. The main |
| 122 | data structure is AccessibilityNodeInfo. It doesn't have a role, but |
| 123 | if you call AccessibilityNodeInfo.getClassName() on the root node |
| 124 | it returns "android.webkit.WebView", and if you call |
| 125 | AccessibilityNodeInfo.getContentDescription() it returns "How old are you?". |
| 126 | |
| 127 | On Chrome OS, we use our own accessibility API that closely maps to |
| 128 | Chrome's internal accessibility API. |
| 129 | |
| 130 | So while the details of the interface vary, the underlying concepts are |
| 131 | similar. Both IAccessible and NSAccessibility have a concept of a role, |
| 132 | but IAccessible uses a role of "document" for a web page, while NSAccessibility |
| 133 | uses a role of "web area". Both IAccessible and NSAccessibility have a |
| 134 | concept of the primary accessible text for a node, but IAccessible calls |
| 135 | it the "name" while NSAccessibility calls it the "label", and Android |
| 136 | calls it a "content description". |
| 137 | |
| 138 | **Historical note:** The internal names of roles and attributes in |
| 139 | Chrome often tend to most closely match the macOS accessibility API |
| 140 | because Chromium was originally based on WebKit, where most of the |
| 141 | accessibility code was written by Apple. Over time we're slowly |
| 142 | migrating internal names to match what those roles and attributes are |
| 143 | called in web accessibility standards, like ARIA. |
| 144 | |
| 145 | ### Accessibility Events |
| 146 | |
| 147 | In Chromium's internal terminology, an Accessibility Event always represents |
| 148 | communication from the app to the assistive technology, indicating that the |
| 149 | accessibility tree changed in some way. |
| 150 | |
| 151 | As an example, if the user were to press the Tab key and the text |
| 152 | field from the example above became focused, Chromium would fire a |
| 153 | "focus" accessibility event that assistive technology could listen |
| 154 | to. A screen reader might then announce the name and current value of |
| 155 | the text field. A magnifier might zoom the screen to its bounding |
| 156 | box. If the user types some text into the text field, Chromium would |
| 157 | fire a "value changed" accessibility event. |
| 158 | |
| 159 | As with nodes in the accessibility tree, each platform has a slightly different |
| 160 | API for accessibility events. On Windows we'd fire EVENT_OBJECT_FOCUS for |
| 161 | a focus change, and on Mac we'd fire @"AXFocusedUIElementChanged". |
| 162 | Those are pretty similar. Sometimes they're quite different - to support |
| 163 | live regions (notifications that certain key parts of a web page have changed), |
| 164 | on Mac we simply fire @"AXLiveRegionChanged", but on Windows we need to |
| 165 | fire IA2_EVENT_TEXT_INSERTED and IA2_EVENT_TEXT_REMOVED events individually |
| 166 | on each affected node within the changed region, with additional attributes |
| 167 | like "container-live:polite" to indicate that the affected node was part of |
| 168 | a live region. This discussion is not meant to explain all of the technical |
| 169 | details but just to illustrate that the concepts are similar, |
| 170 | but the details of notifying software on each platform about changes can |
| 171 | vary quite a bit. |
| 172 | |
| 173 | ### Accessibility Actions |
| 174 | |
| 175 | Each native object that implements a platform's native accessibility API |
| 176 | supports a number of actions, which are requests from the assistive |
| 177 | technology to control or change the UI. This is the opposite of events, |
| 178 | which are messages from Chromium to the assistive technology. |
| 179 | |
| 180 | For example, if the user had a voice control application running, such as |
| 181 | Voice Access on Android, the user could just speak the name of one of the |
| 182 | buttons on the page, like "Next". Upon recognizing that text and finding |
| 183 | that it matches one of the UI elements on the page, the voice control |
| 184 | app executes the action to click the button id=6 in Chromium's accessibility |
| 185 | tree. Internally we call that action "do default" rather than click, since |
| 186 | it represents the default action for any type of control. |
| 187 | |
| 188 | Other examples of actions include setting focus, changing the value of |
| 189 | a control, and scrolling the page. |
| 190 | |
| 191 | ### Parameterized attributes |
| 192 | |
| 193 | In addition to accessibility attributes, events, and actions, native |
| 194 | accessibility APIs often have so-called "parameterized attributes". |
| 195 | The most common example of this is for text - for example there may be |
| 196 | a function to retrieve the bounding box for a range of text, or a |
| 197 | function to retrieve the text properties (font family, font size, |
| 198 | weight, etc.) at a specific character position. |
| 199 | |
| 200 | Parameterized attributes are particularly tricky to implement because |
Katie Dektar | 4a165e1b | 2017-09-27 16:15:13 | [diff] [blame] | 201 | of Chromium's multi-process architecture. More on this below. |
| 202 | |
| 203 | ### Tools for inspecting the Accessibility tree |
| 204 | |
| 205 | Developers can inspect the accessibility tree in several ways: |
| 206 | |
| 207 | * By navigating to [chrome://accessibility/](chrome://accessibility) |
| 208 | and inspecting a tree directly. Note that you may want to enable the |
| 209 | 'Internal' option. Click 'show accessibility tree' for a particular tab, |
| 210 | then click again to refresh that tree. |
Jacobo Aragunde Pérez | 0af076da | 2022-01-13 15:38:55 | [diff] [blame] | 211 | * Using the [Automation API]( |
| 212 | https://developer.chrome.com/extensions/automation). |
| 213 | * Installing the [Automation Inspector Chrome extension]( |
| 214 | https://github.com/google/automation-inspector). |
David Tseng | 50445dd | 2022-04-12 19:42:02 | [diff] [blame] | 215 | * Building and using [ax_dump_tree or ax_dump_events](../../tools/accessibility/inspect/README.md). |
Aaron Leventhal | 6d0183a | 2021-12-20 19:59:40 | [diff] [blame] | 216 | These can be used to view accessibility trees and events from any application on |
| 217 | Windows, Mac or Linux. |
Katie Dektar | 4a165e1b | 2017-09-27 16:15:13 | [diff] [blame] | 218 | * Or by using native tools: |
| 219 | |
| 220 | - Android: UIAutomatorViewer |
| 221 | - macOS: Accessibility Inspector |
| 222 | - Windows: Inspect, AViewer, accProbe (and many others) |
| 223 | |
Amanda Lin Dietz | 375d922c9 | 2023-04-17 18:28:48 | [diff] [blame] | 224 | ### Command Line Options |
| 225 | |
| 226 | Accessibility features in Chrome are off by default and enabled automatically |
| 227 | on-demand. When running Chrome from the command line, certain options can be |
| 228 | used to impact the behavior of the Chrome accessibility engine at launch. |
| 229 | |
| 230 | * `--force-renderer-accessibility=[basic|form-controls|complete]`: Force |
| 231 | accessibility to be enabled, with optional parameter to force the AXMode |
Amanda Lin Dietz | 62d7a52 | 2023-04-18 21:16:13 | [diff] [blame] | 232 | to one of the predefined bundles during the entire execution. If the optional |
| 233 | parameter is invalid, then the default AXMode will be `complete`. If the |
| 234 | optional parameter is missing, then the AXMode will initially default to |
| 235 | `complete` but allow changes to the mode during execution. |
Amanda Lin Dietz | 375d922c9 | 2023-04-17 18:28:48 | [diff] [blame] | 236 | * `--disable-renderer-accessibility`: Disable accessibility |
| 237 | |
Kevin Babbitt | 805b781 | 2021-06-14 18:18:16 | [diff] [blame] | 238 | ### Supported Platforms and APIs |
| 239 | |
| 240 | * Windows: IAccessible (also known as Microsoft Active Accessibility or MSAA), |
David Tseng | 7f8ddfb | 2021-11-01 19:32:59 | [diff] [blame] | 241 | IAccessible2, [UI Automation](browser/uiautomation.md). Chromium also supports |
| 242 | [mapping between IAccessible2 and UI Automation nodes](browser/ia2_to_uia.md). |
Kevin Babbitt | 805b781 | 2021-06-14 18:18:16 | [diff] [blame] | 243 | * Mac: NSAccessibility |
| 244 | * Linux: ATK |
David Tseng | 7f8ddfb | 2021-11-01 19:32:59 | [diff] [blame] | 245 | * Android: [AccessibilityNodeInfo and AccessibilityNodeProvider](browser/android.md) |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 246 | |
| 247 | ## Chromium's multi-process architecture |
| 248 | |
| 249 | Native accessibility APIs tend to have a *functional* interface, where |
| 250 | Chromium implements an interface for a canonical accessible object that |
| 251 | includes methods to return various attributes, walk the tree, or perform |
| 252 | an action like click(), focus(), or setValue(...). |
| 253 | |
| 254 | In contrast, the web has a largely *declarative* interface. The shape |
| 255 | of the accessibility tree is determined by the DOM tree (occasionally |
| 256 | influenced by CSS), and the accessible semantics of a DOM element can |
| 257 | be modified by adding ARIA attributes. |
| 258 | |
| 259 | One important complication is that all of these native accessibility APIs |
| 260 | are *synchronous*, while Chromium is multi-process, with the contents of |
| 261 | each web page living in a different process than the process that |
| 262 | implements Chromium's UI and the native accessibility APIs. Furthermore, |
| 263 | the renderer processes are *sandboxed*, so they can't implement |
| 264 | operating system APIs directly. |
| 265 | |
| 266 | If you're unfamiliar with Chrome's multi-process architecture, see |
| 267 | [this blog post introducing the concept]( |
| 268 | https://blog.chromium.org/2008/09/multi-process-architecture.html) or |
| 269 | [the design doc on chromium.org]( |
| 270 | https://www.chromium.org/developers/design-documents/multi-process-architecture) |
| 271 | for an intro. |
| 272 | |
| 273 | Chromium's multi-process architecture means that we can't implement |
| 274 | accessibility APIs the same way that a single-process browser can - |
| 275 | namely, by calling directly into the DOM to compute the result of each |
| 276 | API call. For example, on some operating systems there might be an API |
| 277 | to get the bounding box for a particular range of characters on the |
| 278 | page. In other browsers, this might be implemented by creating a DOM |
| 279 | selection object and asking for its bounding box. |
| 280 | |
| 281 | That implementation would be impossible in Chromium because it'd require |
| 282 | blocking the main thread while waiting for a response from the renderer |
| 283 | process that implements that web page's DOM. (Not only is blocking the |
| 284 | main thread strictly disallowed, but the latency of doing this for every |
| 285 | API call makes it prohibitively slow anyway.) Instead, Chromium takes an |
| 286 | approach where a representation of the entire accessibility tree is |
| 287 | cached in the main process. Great care needs to be taken to ensure that |
| 288 | this representation is as concise as possible. |
| 289 | |
| 290 | In Chromium, we build a data structure representing all of the |
| 291 | information for a web page's accessibility tree, send the data |
| 292 | structure from the renderer process to the main browser process, cache |
| 293 | it in the main browser process, and implement native accessibility |
| 294 | APIs using solely the information in that cache. |
| 295 | |
| 296 | As the accessibility tree changes, tree updates and accessibility events |
| 297 | get sent from the renderer process to the browser process. The browser |
| 298 | cache is updated atomically in the main thread, so whenever an external |
| 299 | client (like assistive technology) calls an accessibility API function, |
| 300 | we're always returning something from a complete and consistent snapshot |
| 301 | of the accessibility tree. From time to time, the cache may lag what's |
| 302 | in the renderer process by a fraction of a second. |
| 303 | |
| 304 | Here are some of the specific challenges faced by this approach and |
| 305 | how we've addressed them. |
| 306 | |
| 307 | ### Sparse data |
| 308 | |
| 309 | There are a *lot* of possible accessibility attributes for any given |
| 310 | node in an accessibility tree. For example, there are more than 150 |
| 311 | unique accessibility API methods that Chrome implements on the Windows |
| 312 | platform alone. We need to implement all of those APIs, many of which |
| 313 | request rather rare or obscure attributes, but storing all possible |
| 314 | attribute values in a single struct would be quite wasteful. |
| 315 | |
| 316 | To avoid each accessible node object containing hundreds of fields the |
| 317 | data for each accessibility node is stored in a relatively compact |
| 318 | data structure, ui::AXNodeData. Every AXNodeData has an integer ID, a |
| 319 | role enum, and a couple of other mandatory fields, but everything else |
| 320 | is stored in attribute arrays, one for each major data type. |
| 321 | |
| 322 | ``` |
| 323 | struct AXNodeData { |
| 324 | int32_t id; |
Dominic Mazzoni | dcef1b73 | 2018-01-26 17:57:04 | [diff] [blame] | 325 | ax::mojom::Role role; |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 326 | ... |
Dominic Mazzoni | dcef1b73 | 2018-01-26 17:57:04 | [diff] [blame] | 327 | std::vector<std::pair<ax::mojom::StringAttribute, std::string>> string_attributes; |
| 328 | std::vector<std::pair<ax::mojom::IntAttribute, int32_t>> int_attributes; |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 329 | ... |
| 330 | } |
| 331 | ``` |
| 332 | |
| 333 | So if a text field has a placeholder attribute, we can store |
| 334 | that by adding an entry to `string_attributes` with an attribute |
Dominic Mazzoni | dcef1b73 | 2018-01-26 17:57:04 | [diff] [blame] | 335 | of ax::mojom::StringAttribute::kPlaceholder and the placeholder string as the value. |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 336 | |
| 337 | ### Incremental tree updates |
| 338 | |
| 339 | Web pages change frequently. It'd be terribly inefficient to send a |
| 340 | new copy of the accessibility tree every time any part of it changes. |
| 341 | However, the accessibility tree can change shape in complicated ways - |
| 342 | for example, whole subtrees can be reparented dynamically. |
| 343 | |
| 344 | Rather than writing code to deal with every possible way the |
| 345 | accessibility tree could be modified, Chromium has a general-purpose |
| 346 | tree serializer class that's designed to send small incremental |
| 347 | updates of a tree from one process to another. The tree serializer has |
| 348 | just a few requirements: |
| 349 | |
| 350 | * Every node in the tree must have a unique integer ID. |
| 351 | * The tree must be acyclic. |
| 352 | * The tree serializer must be notified when a node's data changes. |
| 353 | * The tree serializer must be notified when the list of child IDs of a |
| 354 | node changes. |
| 355 | |
| 356 | The tree serializer doesn't know anything about accessibility attributes. |
| 357 | It keeps track of the previous state of the tree, and every time the tree |
| 358 | structure changes (based on notifications of a node changing or a node's |
| 359 | children changing), it walks the tree and builds up an incremental tree |
| 360 | update that serializes as few nodes as possible. |
| 361 | |
| 362 | In the other process, the Unserialization code applies the incremental |
| 363 | tree update atomically. |
| 364 | |
| 365 | ### Text bounding boxes |
| 366 | |
| 367 | One challenge faced by Chromium is that accessibility clients want to be |
| 368 | able to query the bounding box of an arbitrary range of text - not necessarily |
| 369 | just the current cursor position or selection. As discussed above, it's |
| 370 | not possible to block Chromium's main browser process while waiting for this |
| 371 | information from Blink, so instead we cache enough information to satisfy these |
| 372 | queries in the accessibility tree. |
| 373 | |
| 374 | To compactly store the bounding box of every character on the page, we |
| 375 | split the text into *inline text boxes*, sometimes called *text runs*. |
| 376 | For example, in a typical paragraph, each line of text would be its own |
Quinten Yearsley | 317532d | 2021-10-20 17:10:31 | [diff] [blame] | 377 | inline text box. In general, an inline text box or text run contains a |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 378 | sequence of text characters that are all oriented in the same direction, |
| 379 | in a line, with the same font, size, and style. |
| 380 | |
| 381 | Each inline text box stores its own bounding box, and then the relative |
| 382 | x-coordinate of each character in its text (assuming left-to-right). |
| 383 | From that it's possible to compute the bounding box |
| 384 | of any individual character. |
| 385 | |
| 386 | The inline text boxes are part of Chromium's internal accessibility tree. |
| 387 | They're used purely internally and aren't ever exposed directly via any |
| 388 | native accessibility APIs. |
| 389 | |
| 390 | For example, suppose that a document contains a text field with the text |
| 391 | "Hello world", but the field is narrow, so "Hello" is on the first line and |
| 392 | "World" is on the second line. Internally Chromium's accessibility tree |
| 393 | might look like this: |
| 394 | |
| 395 | ``` |
| 396 | staticText location=(8, 8) size=(38, 36) name='Hello world' |
| 397 | inlineTextBox location=(0, 0) size=(36, 18) name='Hello ' characterOffsets=12,19,23,28,36 |
| 398 | inlineTextBox location=(0, 18) size=(38, 18) name='world' characterOffsets=12,20,25,29,37 |
| 399 | ``` |
| 400 | |
| 401 | ### Scrolling, transformations, and animation |
| 402 | |
| 403 | Native accessibility APIs typically want the bounding box of every element in the |
| 404 | tree, either in window coordinates or global screen coordinates. If we |
| 405 | stored the global screen coordinates for every node, we'd be constantly |
| 406 | re-serializing the whole tree every time the user scrolls or drags the |
| 407 | window. |
| 408 | |
| 409 | Instead, we store the bounding box of each node in the accessibility tree |
| 410 | relative to its *offset container*, which can be any ancestor. If no offset |
| 411 | container is specified, it's assumed to be the root of the tree. |
| 412 | |
| 413 | In addition, any offset container can contain scroll offsets, which can be |
| 414 | used to scroll the bounding boxes of anything in that subtree. |
| 415 | |
| 416 | Finally, any offset container can also include an arbitrary 4x4 transformation |
| 417 | matrix, which can be used to represent arbitrary 3-D rotations, translations, and |
| 418 | scaling, and more. The transformation matrix applies to the whole subtree. |
| 419 | |
| 420 | Storing coordinates this way means that any time an object scrolls, moves, or |
| 421 | animates its position and scale, only the root of the scrolling or animation |
| 422 | needs to post updates to the accessibility tree. Everything in the subtree |
| 423 | remains valid relative to that offset container. |
| 424 | |
| 425 | Computing the global screen coordinates for an object in the accessibility |
| 426 | tree just means walking up its ancestor chain and applying offsets and |
| 427 | occasionally multiplying by a 4x4 matrix. |
| 428 | |
| 429 | ### Site isolation / out-of-process iframes |
| 430 | |
| 431 | At one point in time, all of the content of a single Tab or other web view |
| 432 | was contained in the same Blink process, and it was possible to serialize |
| 433 | the accessibility tree for a whole frame tree in a single pass. |
| 434 | |
| 435 | Today the situation is a bit more complicated, as Chromium supports |
| 436 | out-of-process iframes. (It also supports "browser plugins" such as |
| 437 | the `<webview>` tag in Chrome packaged apps, which embeds a whole |
| 438 | browser inside a browser, but for the purposes of accessibility this |
| 439 | is handled the same as frames.) |
| 440 | |
| 441 | Rather than a mix of in-process and out-of-process frames that are handled |
| 442 | differently, Chromium builds a separate independent accessibility tree |
| 443 | for each frame. Each frame gets its own tree ID, and it keeps track of |
| 444 | the tree ID of its parent frame (if any) and any child frames. |
| 445 | |
| 446 | In Chrome's main browser process, the accessibility trees for each frame |
| 447 | are cached separately, and when an accessibility client (assistive |
| 448 | technology) walks the accessibility tree, Chromium dynamically composes |
| 449 | all of the frames into a single virtual accessibility tree on the fly, |
| 450 | using those aforementioned tree IDs. |
| 451 | |
| 452 | The node IDs for accessibility trees only need to be unique within a |
| 453 | single frame. Where necessary, separate unique IDs are used within |
| 454 | Chrome's main browser process. In Chromium accessibility, a "node ID" |
| 455 | always means that ID that's only unique within a frame, and a "unique ID" |
| 456 | means an ID that's globally unique. |
| 457 | |
| 458 | ## Blink |
| 459 | |
| 460 | Blink constructs an accessibility tree (a hierarchy of [WebAXObject]s) from the |
sashab | b498325a | 2017-05-17 01:18:46 | [diff] [blame] | 461 | page it is rendering. WebAXObject is the public API wrapper around [AXObject], |
| 462 | which is the core class of Blink's accessibility tree. AXObject is an abstract |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 463 | class; the most commonly used concrete subclass of it is [AXNodeObject], which |
| 464 | wraps a [Node]. In turn, most AXNodeObjects are actually [AXLayoutObject]s, |
| 465 | which wrap both a [Node] and a [LayoutObject]. Access to the LayoutObject is |
sashab | b498325a | 2017-05-17 01:18:46 | [diff] [blame] | 466 | important because some elements are only in the AXObject tree depending on their |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 467 | visibility, geometry, linewrapping, and so on. There are some subclasses of |
| 468 | AXLayoutObject that implement special-case logic for specific types of Node. |
sashab | b498325a | 2017-05-17 01:18:46 | [diff] [blame] | 469 | There are also other subclasses of AXObject, which are mostly used for testing. |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 470 | |
| 471 | Note that not all AXLayoutObjects correspond to actual Nodes; some are synthetic |
| 472 | layout objects which group related inline elements or similar. |
| 473 | |
| 474 | The central class responsible for dealing with accessibility events in Blink is |
| 475 | [AXObjectCacheImpl], which is responsible for caching the corresponding |
| 476 | AXObjects for Nodes or LayoutObjects. This class has many methods named |
| 477 | `handleFoo`, which are called throughout Blink to notify the AXObjectCacheImpl |
| 478 | that it may need to update its tree. Since this class is already aware of all |
| 479 | accessibility events in Blink, it is also responsible for relaying accessibility |
| 480 | events from Blink to the embedding content layer. |
| 481 | |
| 482 | ## The content layer |
| 483 | |
| 484 | The content layer lives on both sides of the renderer/browser split. The content |
Dan Clark | 2256222 | 2021-08-24 22:39:50 | [diff] [blame] | 485 | layer translates WebAXObjects into [ui::AXNodeData]. The ui::AXNodeData class |
| 486 | and related classes are Chromium's cross-platform accessibility tree. The |
| 487 | translation is implemented in [BlinkAXTreeSource]. This translation happens on |
| 488 | the renderer side, so the ui::AXNodeData tree now needs to be sent to the |
| 489 | browser, which is done by calling the remote method |
| 490 | [ax.mojom.RenderAccessibilityHost::HandleAXEvents()] with the payload being |
| 491 | serialized delta-updates to the tree, so that changes that happen on the |
| 492 | renderer side can be reflected on the browser side. |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 493 | |
| 494 | On the browser side, these IPCs are received by [RenderFrameHostImpl], and then |
| 495 | usually forwarded to [BrowserAccessibilityManager] which is responsible for: |
| 496 | |
| 497 | 1. Merging AXNodeData trees into one tree of [BrowserAccessibility] objects, |
| 498 | by linking to other BrowserAccessibilityManagers. This is important because |
| 499 | each page has its own accessibility tree, but each Chromium *window* must |
| 500 | have only one accessibility tree, so trees from multiple pages need to be |
| 501 | combined (possibly also with trees from Views UI). |
| 502 | 2. Dispatching outgoing accessibility events to the platform's accessibility |
| 503 | APIs. This is done in the platform-specific subclasses of |
| 504 | BrowserAccessibilityManager, in a method named `NotifyAccessibilityEvent`. |
| 505 | 3. Dispatching incoming accessibility actions to the appropriate recipient, via |
Nektarios Paisios | 5f9a4d9a | 2022-10-11 16:37:26 | [diff] [blame] | 506 | [AXPlatformTreeManagerDelegate]. For messages destined for a renderer, |
Jacques Newman | ab0be72 | 2024-03-08 23:14:04 | [diff] [blame] | 507 | [RenderFrameHostImpl] is responsible for calling the remote method |
Mario Sanchez Prada | 52f04e7 | 2020-04-22 15:34:21 | [diff] [blame] | 508 | [ax.mojom.RenderAccessibility.PerformAction()], implemented by the renderer, |
| 509 | with the appropriate payload (of type [ax.mojom.AXActionData]). This IPC call |
| 510 | will be received by [RenderAccessibilityManager], which will then relay on |
| 511 | the [RenderAccessibilityImpl] where the actual logic is implemented. |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 512 | |
| 513 | On Chrome OS, RenderFrameHostImpl does not route events to |
| 514 | BrowserAccessibilityManager at all, since there is no platform screenreader |
| 515 | outside Chromium to integrate with. |
| 516 | |
| 517 | ## Views |
| 518 | |
David Tseng | 0fa4a63 | 2019-05-23 23:57:46 | [diff] [blame] | 519 | Views generates a [ViewAccessibility] for each View, which is used as the |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 520 | delegate for an [AXPlatformNode] representing that View. This part is relatively |
| 521 | straightforward, but then the generated tree must be combined with the web |
| 522 | accessibility tree, which is handled by BrowserAccessibilityManager. |
| 523 | |
| 524 | ## WebUI |
| 525 | |
| 526 | Since WebUI surfaces have renderer processes as normal, WebUI accessibility goes |
| 527 | through the blink-to-content-to-platform pipeline described above. Accessibility |
| 528 | for WebUI is largely implemented in JavaScript in [webui-js]; these classes take |
| 529 | care of adding ARIA attributes and so on to DOM nodes as needed. |
| 530 | |
| 531 | ## The Chrome OS layer |
| 532 | |
| 533 | The accessibility tree is also exposed via the [chrome.automation API], which |
| 534 | gives extension JavaScript access to the accessibility tree, events, and |
| 535 | actions. This API is implemented in C++ by [AutomationInternalCustomBindings], |
| 536 | which is renderer-side code, and in JavaScript by the [automation API]. The API |
| 537 | is defined by [automation.idl], which must be kept synchronized with |
Chris Hall | dd6f87c | 2019-10-14 05:20:47 | [diff] [blame] | 538 | [ax_enums.mojom]. |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 539 | |
Dominic Mazzoni | 7821334d | 2021-09-01 20:41:40 | [diff] [blame] | 540 | ## Further reading |
| 541 | |
David Tseng | 7f8ddfb | 2021-11-01 19:32:59 | [diff] [blame] | 542 | For more detail on Chrome web contents and platform accessibility, read [How Chrome Accessibility Works](browser/how_a11y_works.md). |
| 543 | |
David Tseng | b2b326c | 2022-02-09 15:41:31 | [diff] [blame] | 544 | For more detail on Chrome OS accessibility, read [How Chrome OS Accessibility Works](os/how_a11y_works.md). |
Dominic Mazzoni | 7821334d | 2021-09-01 20:41:40 | [diff] [blame] | 545 | |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 546 | [ax.mojom.AXActionData]: https://source.chromium.org/chromium/chromium/src/+/main:ui/accessibility/mojom/ax_action_data.mojom;l=13 |
| 547 | [ax.mojom.RenderAccessibilityHost::HandleAXEvents()]: https://source.chromium.org/chromium/chromium/src/+/main:content/common/render_accessibility.mojom;l=47 |
| 548 | [ax.mojom.RenderAccessibility.PerformAction()]: https://source.chromium.org/chromium/chromium/src/+/main:content/common/render_accessibility.mojom;l=86 |
Katie D | e5c2c70 | 2019-06-26 17:54:50 | [diff] [blame] | 549 | [AutomationInternalCustomBindings]: https://cs.chromium.org/chromium/src/extensions/renderer/api/automation/automation_internal_custom_bindings.h |
Kent Tamura | 6943cf79 | 2018-04-09 05:24:54 | [diff] [blame] | 550 | [AXLayoutObject]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/accessibility/ax_layout_object.h |
| 551 | [AXNodeObject]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/accessibility/ax_node_object.h |
Sharon Yang | 8c9500e | 2019-06-14 17:41:40 | [diff] [blame] | 552 | [AXObject]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/accessibility/ax_object.h |
Kent Tamura | 6943cf79 | 2018-04-09 05:24:54 | [diff] [blame] | 553 | [AXObjectImpl]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/accessibility/ax_object_impl.h |
| 554 | [AXObjectCacheImpl]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 555 | [AXPlatformNode]: https://cs.chromium.org/chromium/src/ui/accessibility/platform/ax_platform_node.h |
| 556 | [AXTreeSerializer]: https://cs.chromium.org/chromium/src/ui/accessibility/ax_tree_serializer.h |
| 557 | [BlinkAXTreeSource]: https://cs.chromium.org/chromium/src/content/renderer/accessibility/blink_ax_tree_source.h |
Jacques Newman | 6f230d78 | 2024-08-26 19:13:34 | [diff] [blame] | 558 | [BrowserAccessibility]: https://cs.chromium.org/chromium/src/ui/accessibility/platform/browser_accessibility.h |
| 559 | [BrowserAccessibilityManager]: https://cs.chromium.org/chromium/src/ui/accessibility/platform/browser_accessibility_manager.h |
Kent Tamura | 6943cf79 | 2018-04-09 05:24:54 | [diff] [blame] | 560 | [LayoutObject]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/layout/layout_object.h |
Nektarios Paisios | 5f9a4d9a | 2022-10-11 16:37:26 | [diff] [blame] | 561 | [AXPlatformTreeManagerDelegate]: https://cs.chromium.org/chromium/src/ui/accessibility/platform/ax_platform_tree_manager_delegate.h |
David Tseng | 0fa4a63 | 2019-05-23 23:57:46 | [diff] [blame] | 562 | [ViewAccessibility]: https://cs.chromium.org/chromium/src/ui/views/accessibility/view_accessibility.h |
Sharon Yang | 8c9500e | 2019-06-14 17:41:40 | [diff] [blame] | 563 | [Node]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/dom/node.h |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 564 | [RenderAccessibilityImpl]: https://cs.chromium.org/chromium/src/content/renderer/accessibility/render_accessibility_impl.h |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 565 | [RenderAccessibilityManager]: https://source.chromium.org/chromium/chromium/src/+/main:content/renderer/accessibility/render_accessibility_manager.h |
danakj | d3baa13 | 2020-09-10 15:33:56 | [diff] [blame] | 566 | [RenderFrameHostImpl]: https://cs.chromium.org/chromium/src/content/browser/renderer_host/render_frame_host_impl.h |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 567 | [ui::AXNodeData]: https://cs.chromium.org/chromium/src/ui/accessibility/ax_node_data.h |
Kent Tamura | 6943cf79 | 2018-04-09 05:24:54 | [diff] [blame] | 568 | [WebAXObject]: https://cs.chromium.org/chromium/src/third_party/blink/public/web/web_ax_object.h |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 569 | [automation API]: https://cs.chromium.org/chromium/src/chrome/renderer/resources/extensions/automation |
Katie D | e5c2c70 | 2019-06-26 17:54:50 | [diff] [blame] | 570 | [automation.idl]: https://cs.chromium.org/chromium/src/extensions/common/api/automation.idl |
Chris Hall | dd6f87c | 2019-10-14 05:20:47 | [diff] [blame] | 571 | [ax_enums.mojom]: https://cs.chromium.org/chromium/src/ui/accessibility/ax_enums.mojom |
dmazzoni | 2f48975 | 2017-02-16 03:39:16 | [diff] [blame] | 572 | [chrome.automation API]: https://developer.chrome.com/extensions/automation |
| 573 | [webui-js]: https://cs.chromium.org/chromium/src/ui/webui/resources/js/cr/ui/ |