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.