Dominic Mazzoni | b64f0fb3 | 2021-08-18 04:49:00 | [diff] [blame] | 1 | # How Chrome Accessibility Works |
| 2 | |
| 3 | This document explains the technical details behind Chrome accessibility |
| 4 | code by starting at a high level and progressively adding more levels of |
| 5 | detail. |
| 6 | |
David Tseng | 7f8ddfb | 2021-11-01 19:32:59 | [diff] [blame] | 7 | Please read the accessibility [overview](../overview.md) first. |
Dominic Mazzoni | b64f0fb3 | 2021-08-18 04:49:00 | [diff] [blame] | 8 | |
| 9 | [TOC] |
| 10 | |
| 11 | ## Accessibility for a simple (non-browser) application |
| 12 | |
David Tseng | 7f8ddfb | 2021-11-01 19:32:59 | [diff] [blame] | 13 | As described in the [overview](../overview.md), every platform has its own |
Dominic Mazzoni | b64f0fb3 | 2021-08-18 04:49:00 | [diff] [blame] | 14 | accessibility APIs that are used by both assistive technology and sometimes |
| 15 | by automation software. To better understand the challenges of accessibility |
| 16 | support in Chromium, let's first explore what it's like to build an |
| 17 | accessible application using a standard UI toolkit. |
| 18 | |
| 19 | Examples of standard toolkits would be Win32 UI controls or .NET components |
| 20 | on Windows, or Cocoa NSViews on macOS, or Android Views via Java or Kotlin. |
| 21 | When you use such a toolkit to build an application, a lot of accessibility |
| 22 | comes for free. |
| 23 | |
| 24 | Typically every UI element - including containers and grouping elements - gets |
| 25 | its own corresponding accessibility element, implementing that platform's |
| 26 | accessibility API. So if you add a button, checkbox, scroll container, or |
| 27 | text field to your application, you don't need to do any work to make it |
| 28 | accessible - the built-in UI toolkit has provided all of that already. |
| 29 | |
| 30 |  |
| 31 | |
| 32 | ### Changing accessibility properties |
| 33 | |
| 34 | There are some cases where a bit of extra information may be required on the |
| 35 | part of the app author. If you add an image button, you may need to ensure |
| 36 | you provide an accessible label ("alt text"). Other common simple modifications |
| 37 | might include hiding UI elements from accessibility because they're primarily |
| 38 | decorative, or marking a UI element as an alert to ensure it's announced |
| 39 | to screen reader users when it appears. However, none of these require |
| 40 | writing more than a few lines of code. |
| 41 | |
| 42 | #### Windows |
| 43 | |
| 44 | The [dynamic annotation API](https://docs.microsoft.com/en-us/windows/win32/winauto/dynamic-annotation-api) |
| 45 | lets you change any accessibility property of a UI element represented by an HWND. Here's a tiny code |
| 46 | snippet showing how you could override the accessible name property (PROPID_ACC_NAME) to be the |
| 47 | text "Accessible Name": |
| 48 | |
| 49 | ```C++ |
| 50 | CComPtr<IAccPropServices> pAccPropSrv; |
| 51 | pAccPropSrv.CoCreateInstance(CLSID_AccPropServices); |
| 52 | COleVariant varName("Accessible Name"); |
| 53 | pAccPropSrv->SetHwndProp(hwnd, OBJID_CLIENT, 0, PROPID_ACC_NAME, varName); |
| 54 | ``` |
| 55 | |
| 56 | #### Android |
| 57 | |
| 58 | On Android, if you don't want to subclass a View you can just set its accessibility delegate |
| 59 | as an easy way to override one or more accessibility attributes. In this case we override |
| 60 | the content description to say "Accessible Name". |
| 61 | |
| 62 | ```Java |
| 63 | view.setAccessibilityDelegate(new AccessibilityDelegate() { |
| 64 | public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { |
| 65 | super.onInitializeAccessibilityNodeInfo(host, info); |
| 66 | info.setContentDescription("Accessible Name"); |
| 67 | } |
| 68 | }); |
| 69 | ``` |
| 70 | |
| 71 | ### Custom controls and subclassing |
| 72 | |
| 73 | When building a custom control, more work is often required. As an example, |
| 74 | suppose an application is implementing a "cover flow" image picker, such as the |
| 75 | one found in the original iTunes album picker. Conceptually the user is picking |
| 76 | one album from a large list, but visually it's shown as a horizontally scrolling |
| 77 | list of album cover images that fly by in three dimensions. |
| 78 | |
| 79 |  |
| 80 | |
| 81 | While this control may be implemented as a single UI element, from the |
| 82 | perspective of accessibility APIs it would typically be represented as |
| 83 | a parent element with a "list box" role and children with "option" roles. |
| 84 | This can't be done using the code snippets above, which are only sufficient |
| 85 | for overriding some simple accessibility properties. A more complex custom |
| 86 | control like this requires subclassing. |
| 87 | |
| 88 | To make the CoverFlow accessible, we would need to first need to subclass the |
| 89 | appropriate accessibility interface, such as IAccessible on Windows or |
| 90 | AccessibilityDelegate on Android. In addition to setting properties such as the |
| 91 | name and role (like ComboBox on Windows, or Choice on Android), we'd want to |
| 92 | override any methods that are used to walk the tree, in order to give this |
| 93 | object accessible children - one for each option the user can pick. |
| 94 | |
| 95 |  |
| 97 | |
| 98 | Each option would be another instance of our subclass of an accessibility |
| 99 | interface. Each one might need to implement a role, name, and very |
| 100 | importantly, a bounding box representing the position of that option |
| 101 | within the view's bounds. |
| 102 | |
| 103 | ## A single-process browser for basic HTML only |
| 104 | |
| 105 | Let's next discuss what's needed in order to make a single-process web |
| 106 | browser accessible. At the time Chrome was first released, this is |
| 107 | more or less how other browsers such as Safari and Firefox worked. |
| 108 | However, let's start with the simplifying assumption that we're dealing |
| 109 | with basic HTML only, and no CSS or ARIA. |
| 110 | |
| 111 | From the perspective of accessibility APIs, the web contents is one |
| 112 | big custom control. Modern web browsers do not build their UI using |
| 113 | platform UI elements. |
| 114 | |
| 115 | *** note |
| 116 | Years ago, some browsers may have rendered web pages using |
| 117 | platform UI elements, but this doesn't scale well as most UI frameworks |
| 118 | get bogged down after thousands of UI elements, whereas many web pages |
| 119 | have hundreds of thousands of elements. |
| 120 | |
| 121 | In addition, note that web form controls support CSS effects |
| 122 | including unusual ones like 3-D transformations, whereas most |
| 123 | platform UI controls don't support, e.g. a 45-degree rotated |
| 124 | text box. |
| 125 | *** |
| 126 | |
| 127 | The web contents can be represented as a tree of elements. |
| 128 | For the moment let's ignore details between the DOM and the |
| 129 | final rendered layout, and let's just assume that every DOM node |
| 130 | corresponds perfectly to one node in the accessibility tree. |
| 131 | |
| 132 | Essentially the web browser needs to create one accessible |
| 133 | object for every DOM node. The accessible object subclasses |
| 134 | that platform's accessibility interface, and its accessible |
| 135 | role, name, and other properties can all be computed by |
| 136 | querying the corresponding DOM node. When DOM nodes are created |
| 137 | or destroyed, the corresponding accessible objects need to |
| 138 | be updated accordingly. |
| 139 | |
| 140 | Here's an example of a DOM tree and a corresponding accessibility |
| 141 | tree. There's a 1:1 correspondence in this example. |
| 142 | |
| 143 |  |
| 146 | |
| 147 | Objects in the accessibility tree are sometimes called "wrappers" in this |
| 148 | design. Each accessible object "wraps" its corresponding DOM element. The DOM |
| 149 | element contains all of the state, and the accessible object just implements the |
| 150 | accessibility API based on the DOM element. This design avoids the need for DOM |
| 151 | code to need to be tightly bound to accessibility code. |
| 152 | |
| 153 | For efficiency, these accessible objects are sometimes built |
| 154 | lazily. Initially there could just be one accessible object |
| 155 | for the root of the web contents. If that object is ever |
| 156 | queried and asked for its children, the accessible objects |
| 157 | corresponding to the children of the root DOM element could |
| 158 | be created on-demand, and so on. This is useful because |
| 159 | many users don't have any features enabled that use |
| 160 | accessibility APIs, so very little work would be done |
| 161 | unless they're utilized. |
| 162 | |
| 163 | The overall system so far is pretty simple - the DOM tree is used to |
| 164 | build the accessibility tree, which is used to communicate with the |
| 165 | platform accessibility APIs. |
| 166 | |
| 167 |  |
| 171 | |
| 172 | ## A single-process browser with CSS and ARIA |
| 173 | |
| 174 | In the previous example we assumed that there was a 1:1 correspondence |
| 175 | between a DOM node and an accessible node. Unfortunately that doesn't |
| 176 | hold true. Here are some examples of where that assumption doesn't work. |
| 177 | |
| 178 | * CSS can have generated content, like list item markers (bullets or numbers) |
| 179 | or text inserted before or after an element with pseudoselectors |
| 180 | like ::before and ::after. |
| 181 | * Some DOM subtrees are never displayed (like <head>) or are hidden using |
| 182 | display:none |
| 183 | * Some DOM nodes are hidden using visibility:hidden, but it's possible for |
| 184 | some of their descendant nodes to be visible |
| 185 | * aria-hidden can hide a subtree from the accessibility tree even if it's |
| 186 | displayed visually |
| 187 | * ARIA role=none or role=presentation hides a node but not its subtree |
| 188 | * aria-owns can be used to reparent a node within the accessibility tree |
| 189 | |
| 190 | So in practice, the accessibility tree is similar to the DOM tree, but with |
| 191 | important differences. Objects in the accessibility tree usually wrap DOM |
| 192 | nodes, but occasionally they wrap pseudoelements (like list markers or CSS |
| 193 | generated content) or other layout objects with no corresponding node (such as |
| 194 | images that are children of pseudoelements). |
| 195 | |
| 196 | Here's an example where the DOM tree has list items and the corresponding |
| 197 | accessibility tree has nodes for the list item markers: |
| 198 | |
| 199 |  |
| 202 | |
| 203 | Here's a different example where some of the content is excluded from the |
| 204 | accessibility tree using aria-hidden: |
| 205 | |
| 206 |  |
| 210 | |
| 211 | This new system diagram reflects the complexity of the system when |
| 212 | you consider both HTML, CSS, and ARIA all affecting the accessibility |
| 213 | tree. |
| 214 | |
| 215 |  |
| 220 | |
| 221 | ## A multi-process browser |
| 222 | |
| 223 | In the next section we'll explore how the system diagram needs to change |
| 224 | in order to support a multi-process browser. |
| 225 | |
| 226 | See [How Chrome Accessibility Works, Part 2](how_a11y_works_2.md) |