Note: The following steps represent my current understanding of how Chrome’s internal accessibility pipeline works. This overview is based on information from the Chromium open-source codebase, public documentation, and discussions from Chromium’s accessibility components. Any errors are my own.
Over the past weeks, I’ve written a series of articles that explain how events and the accessibility tree work at a high level. These include:
- How Chrome builds the accessibility tree — start to finish
- What happens when an accessibility event occurs? — the quick answer
In those articles, we follow a clean, simple pipeline:
DOM mutation
↓
AXEvent
↓
Accessibility tree
↓
OS accessibility API
↓
Screen reader
This model is correct. But there is a lot more that happens between the DOM mutation and the moment an event appears in the accessibility tree.
And here’s the strange part: Chrome doesn’t have one accessibility tree, it has two!
These are:
- The hidden internal tree in Blink.
- The public-facing platform-neutral tree you normally interact with.
This article traces the full lifecycle of an accessibility event. From the moment the DOM changes, through Blink’s internal systems, through its internal accessibility tree, and finally back out into the BrowserAccessibility tree that screen readers query.
This is a highly technical look at how Chrome actually works.
Strap in. This is going to be a deep, wild ride.
Chrome’s accessibility event lifecycle
1. A DOM mutation occurs
A DOM mutation means something changes in the DOM: text, attributes, structure, visibility, layout, state, or content. These changes may happen with or without JavaScript.
Note: Steps 2-5 occur inside Blink’s internal accessibility tree.
2. Blink evaluates whether the mutation affects accessibility
Blink (Chrome’s rendering engine) uses its internal accessibility tree to check whether the mutation affects semantics such as:
- accessible name
- role
- state
- relationships
- geometry
- tree structure
If the change has no accessibility impact, the process stops here.
3. If it does affect accessibility, Blink marks the corresponding AX node as “dirty”
AX nodes belong to Blink’s internal accessibility tree. They represent semantic objects such as:
- buttons
- headings
- links
- list items
- text fields
- static text
AX nodes are not DOM nodes — they are a separate structure derived from the DOM.
A node becomes dirty when its accessible representation must be refreshed, such as when:
- its accessible name changes
- its role changes
- its state changes
- its text content changes
- its child list changes
- its geometry changes
Dirty nodes tell Blink which AX nodes must be re-serialized. This means that Blink generates a fresh AXNodeData snapshot for the node with all its current properties.
4. Blink creates an AXEvent describing what kind of accessibility change occurred
AXEvents are internal Blink signals that never leave the renderer process.
AXEvents label the semantic type of change and Blink sends these event types to the browser process, which uses them to determine which OS accessibility events to fire.
They are not JavaScript events and not OS events.
5. Blink re-serializes dirty AX nodes into AXNodeData to capture what actually changed
Each dirty AX node is converted into AXNodeData.
An AXNodeData is platform-neutral snapshot that includes:
- role
- name
- description
- value
- states
- relations
- bounding box
- text content
- ARIA attributes
- child node IDs
AXNodeData is a snapshot of a Blink AX node, converted into a simple data structure so it can be sent from the renderer process to the browser process using Chrome’s internal messaging system (IPC).
6. Blink sends AXNodeData to the browser process, which delivers them to BrowserAccessibilityManager
This is the moment when data leaves Blink’s AX tree and enters Chrome’s BrowserAccessibility tree in the browser process.
Blink sends the updated AXNodeData and a list of event types to fire.
Blink’s role ends here.
Note: Steps 7–10 occur inside the BrowserAccessibility tree.
7. BrowserAccessibilityManager updates Chrome’s platform-neutral accessibility tree
BrowserAccessibilityManager maintains Chrome’s second accessibility tree - the BrowserAccessibility tree.
Using AXNodeData, it:
- updates BrowserAccessibility nodes
- adjusts roles, names, states, geometry
- updates parent/child relationships
- invalidates caches
- decides whether the reported event type should result in a platform accessibility event
This is the accessibility tree Chrome exposes to operating systems.
8. Each BrowserAccessibility node has a platform-specific accessibility wrapper
Every node in the BrowserAccessibility tree is paired with a persistent platform wrapper, such as:
- macOS:
BrowserAccessibilityCocoa - Windows:
BrowserAccessibilityWin - Linux:
BrowserAccessibilityAtk - Android:
BrowserAccessibilityAndroid
These wrappers translate Chrome’s platform-neutral accessibility data into the specific OS accessibility API.
9. The platform wrapper is notified and emits the corresponding platform accessibility event
The wrapper translates the event into the correct platform-specific accessibility event and fires it through the OS API.
Accessibiltiy events could include:
NSAccessibilityPostNotification(macOS)UiaRaiseAutomationEvent(Windows)object:state-changed(Linux AT-SPI)- Android
AccessibilityEventobjects
These are the events assistive technologies actually receive.
10. Screen readers query the OS accessibility API, which retrieves information from Chrome’s BrowserAccessibility tree
Screen readers such as VoiceOver, NVDA, JAWS, Narrator, and TalkBack:
- listen for the OS accessibility event
- query the BrowserAccessibility tree via the OS accessibility API
- retrieve the updated node information
- generate speech, braille, or focus movement
This is where the user finally experiences the result of the original DOM mutation.
So the more detailed diagram is now:
DOM mutation
↓
Blink AX tree
↓
Blink accessibility updates (AXEvents + AXNodeData)
↓
BrowserAccessibility tree
↓
Platform wrappers
↓
OS API
↓
Screen readers
We are done!
What’s the difference between the trees?
Blink AX Tree (internal engine tree)
This is Blink’s own accessibility representation of the DOM. It is tightly connected to:
- DOM nodes
- layout
- styles
- ARIA attributes
- computed semantics
It’s used to detect accessibility-relevant changes and produce AXNodeData.
Key features:
- It lives inside the renderer process
- It is part of Blink (Chrome’s rendering engine).
- Because the renderer is sandboxed, it is not exposed outside Blink and cannot be accessed by assistive technologies.
BrowserAccessibility Tree (platform-neutral browser tree)
This is Chrome’s OS-facing accessibility tree. It stores:
- roles
- names
- states
- geometry
- relationships
- platform IDs and caches
This tree is what Chrome exposes to operating systems such as:
- macOS (AX API)
- Windows (UIA)
- Linux (AT-SPI)
- Android (AccessibilityNodeInfo)
Key features
- It lives inside the browser process.
- It is managed by BrowserAccessibilityManager
- Updated via serialized AXNodeData sent from Blink
- Holds platform-specific wrapper objects (Win, Cocoa, ATK, Android)
This tree has permission to communicate with OS accessibility APIs.
Which tree is used by assistive technologies?
Screen readers only interact with the BrowserAccessibility tree.
But everything they read originates in Blink’s AX tree. The data simply travels through AXNodeData to reach the browser tree.
So, both trees are essential. They just play completely different roles.
- Blink AX tree: computation, semantics, detection
- BrowserAccessibility tree: exposure, platform events, screen reader queries
Conclusion
Every time a screen reader announces something, it’s the result of two accessibility trees working together behind the scenes.
Note: Chrome’s accessibility architecture is complex and always evolving. If you have corrections or additional insight, I’d be grateful to hear from you.