How Chromium Builds the Accessibility Tree
This article provides an overview of the architectural process Chromium uses to transform a web page's Document Object Model (DOM) into an internal Accessibility Tree, bridging the gap between raw web code and assistive technologies like NVDA and JAWS. It covers how the Blink rendering engine extracts semantic data from the DOM and CSS, how this data crosses security boundaries via inter-process communication, and how the main browser process exposes the final tree to operating system accessibility APIs.
1. Extracting Semantics in the Renderer Process
The construction of the Accessibility Tree begins inside Chromium’s sandboxed Renderer Process, managed by the Blink rendering engine. Chromium does not build the accessibility tree from the DOM alone; it combines the DOM tree with the Layout Tree (which includes computed CSS styles).
CSS directly impacts accessibility. For example, elements styled with
display: none or visibility: hidden are
excluded from the accessibility tree, while CSS pseudo-elements
(::before and ::after) are included.
Blink traverses the DOM and Layout trees simultaneously to create a
parallel structure of AXObject instances. During this
phase, Blink performs two critical actions:
- Filtering: Not every DOM node becomes an
AXObject. Purely visual or structural containers, such as a generic<div>or<span>used only for styling, are ignored to keep the tree clean and navigable. - Computation: For every relevant node, Blink
calculates its core accessibility properties: Role
(what the element is, e.g., button, heading), Name (its
accessible label), State (e.g., checked, disabled,
expanded), and Value. This calculation follows strict
precedence rules, combining native HTML semantics with WAI-ARIA
attributes (such as
aria-labelorrole).
2. Serialization and Inter-Process Communication (IPC)
Because Chromium uses a multi-process architecture, the Renderer Process is heavily sandboxed for security. Screen readers like NVDA and JAWS run at the operating system level and cannot directly query the Renderer Process. Furthermore, screen readers require fast, synchronous responses to user commands; querying the Renderer directly could cause the screen reader to freeze if the web page's main thread is busy running heavy JavaScript.
To solve this, Chromium caches a copy of the accessibility tree in the main Browser Process.
Whenever the DOM or layout changes in a way that affects
accessibility, Blink batches these modifications and serializes the
affected AXObject nodes into a data structure called
AXTreeUpdate. Chromium then sends this serialized data
across process boundaries to the Browser Process using Mojo, Chromium's
inter-process communication (IPC) system.
3. Reconstructing the Tree in the Browser Process
Once the AXTreeUpdate message arrives in the unsandboxed
Browser Process, it is deserialized to construct or update a
platform-independent representation of the tree known as
ui::AXTree, which is composed of ui::AXNode
instances.
A component called the BrowserAccessibilityManager
oversees this tree. For every ui::AXNode, the manager
creates a platform-specific wrapper object called
BrowserAccessibility. This separation allows Chromium to
maintain one core logic system for tree management while adapting the
output to fit the specific requirements of Windows, macOS, Linux,
Android, or ChromeOS.
4. Exposing Content to NVDA and JAWS
The final step is translating the BrowserAccessibility
objects into formats that Windows-based screen readers like NVDA and
JAWS can understand.
On Windows, assistive technologies rely on specific Component Object Model (COM) APIs to interact with applications. Chromium supports the following APIs for Windows screen readers:
- Microsoft Active Accessibility (MSAA): A legacy API still used for basic compatibility.
- IAccessible2 (IA2): An API that extends MSAA, heavily relied upon by both NVDA and JAWS for rich web content.
- UI Automation (UIA): Microsoft's modern accessibility framework.
The BrowserAccessibility wrappers in Chromium implement
the interfaces for these exact APIs (such as IAccessible2
on Windows). When a user navigates a page using NVDA or JAWS, the screen
reader makes API calls directly to the Chromium Browser Process. The
Browser Process immediately answers these queries using its cached
ui::AXTree without waiting on the Renderer.
Similarly, when a web page dynamically updates—such as a modal
opening or an aria-live region changing—Blink detects the
change, sends an AXTreeUpdate and an associated event to
the Browser Process, and the Browser Process fires a native Windows OS
event. NVDA or JAWS intercepts this event, queries the updated node in
the ui::AXTree, and announces the change to the user.