Chromium's AXNodeData: Storing Roles and ARIA Attributes
This article provides a technical breakdown of the
AXNodeData structure within the Chromium web browser
codebase. It explains the core purpose of AXNodeData in
bridging the gap between the Blink rendering engine and assistive
technologies, followed by a detailed look at how the structure
efficiently stores semantic roles, states, and various ARIA attributes
using strongly-typed enums, key-value vectors, and bitmasks.
What is AXNodeData?
In the Chromium codebase, AXNodeData is a fundamental
C++ struct defined in ui/accessibility/ax_node_data.h. It
serves as the serializable representation of a single node's
accessibility information within the accessibility tree
(AXTree).
Chromium uses a multi-process architecture. The web page is parsed
and rendered in the sandboxed Blink renderer process, while
platform-specific accessibility APIs (such as UI Automation on Windows
or NSAccessibility on macOS) live in the main browser process.
AXNodeData packages the computed accessibility properties
of a DOM element so they can be sent across Inter-Process Communication
(IPC) via Mojo from the renderer to the browser process.
Storing Semantic Roles
Every accessible node requires a semantic role to inform assistive
technologies, like screen readers, what the element actually is.
AXNodeData stores this information directly in a dedicated
member variable:
ax::mojom::Role role;Instead of storing the role as a raw string (like
"button" or "navigation"), Chromium uses the
ax::mojom::Role enumeration.
When Blink constructs the accessibility tree, it evaluates both the
native HTML tag (such as <button> or
<nav>) and any explicit role ARIA
attribute present in the DOM. Blink resolves conflicts and computes the
final semantic meaning, mapping it to a specific enum value like
ax::mojom::Role::kButton or
ax::mojom::Role::kNavigation. This single enum is then
assigned to the role field in AXNodeData.
Storing ARIA Attributes
A naive implementation of an accessibility node might store all ARIA attributes in a single string-to-string hash map. However, because a complex web page can have tens of thousands of DOM nodes, this approach would consume too much memory and slow down IPC serialization.
To optimize for memory and performance, AXNodeData
normalizes ARIA attributes (along with native HTML attributes) and
splits them into strongly-typed
std::vector<std::pair<Enum, Value>> collections
based on the underlying data type of the attribute's value.
Here is how AXNodeData categorizes and stores these
attributes:
1. String Attributes
std::vector<std::pair<ax::mojom::StringAttribute, std::string>> string_attributes;ARIA attributes that resolve to text are stored here. For example,
aria-placeholder, aria-roledescription, and
aria-keyshortcuts are mapped to their respective
ax::mojom::StringAttribute enum keys. Computed accessible
names and descriptions (derived from aria-label or
aria-description) are also stored in this vector under
kName and kDescription.
2. Integer Attributes
std::vector<std::pair<ax::mojom::IntAttribute, int32_t>> int_attributes;This vector handles numerical ARIA attributes and single-node
relationships. Attributes like aria-posinset,
aria-setsize, aria-colindex, and
aria-rowindex are stored here. Additionally, ARIA
attributes that reference a single other element—such as
aria-activedescendant or
aria-errormessage—have their target DOM IDs resolved to
internal Chromium AXNodeID integers and are stored in this
vector.
3. Float Attributes
std::vector<std::pair<ax::mojom::FloatAttribute, float>> float_attributes;Range-based ARIA attributes that require decimal precision are placed
in the float vector. This includes aria-valuenow,
aria-valuemin, and aria-valuemax, which are
commonly used on sliders, progress bars, and spinbuttons.
4. Boolean Attributes
std::vector<std::pair<ax::mojom::BoolAttribute, bool>> bool_attributes;True/false ARIA properties that are not frequent enough to warrant a
bitmask slot are placed here. Examples include aria-modal,
aria-busy, and aria-atomic.
5. IntList Attributes
std::vector<std::pair<ax::mojom::IntListAttribute, std::vector<int32_t>>> intlist_attributes;Many ARIA attributes establish relationships between one element and
multiple other elements by taking a space-separated list of HTML IDs.
Chromium resolves these string IDs into a list of integer
AXNodeIDs. Attributes like aria-controls,
aria-flowto, aria-owns,
aria-labelledby, and aria-describedby (when
referencing other elements) are stored in this vector.
Storing ARIA States via Bitmasks
Not all ARIA attributes are placed into the attribute vectors. To
further reduce memory overhead, extremely common boolean-like ARIA
states are packed into a 32-bit integer bitmask on the
AXNodeData struct:
uint32_t state;This bitmask uses the ax::mojom::State enum. When Blink
processes attributes such as aria-disabled="true",
aria-expanded="true",
aria-multiselectable="true", or
aria-required="true", it flips the corresponding bit in the
state field (e.g., kDisabled,
kExpanded, kMultiselectable,
kRequired). Checking a state in the browser process then
becomes a fast bitwise operation rather than an array lookup.
Note that aria-checked and aria-pressed are
exceptions to the bitmask rule. Because they support a "mixed" tristate
value in addition to true and false, they are stored in
int_attributes under kCheckedState mapped to
the ax::mojom::CheckedState enum.