Skip to content

a11y-macos: every checkbox, radio and switch reads AXValue twice #501

Description

@xxx

Every checkbox, radio button and switch in a macOS snapshot pays two cross-process AX round-trips for one attribute.

walk reads AXValue as a string for AxNode.value:

// crates/glass-a11y-macos/src/reader.rs:477
let value = ffi::attribute_string(el, attr::VALUE);

and gather_states, called two lines later, reads the same attribute as an integer to decide checked:

// crates/glass-a11y-macos/src/reader.rs:622
let (checkable, checked) = if mapping::role_carries_checked(role) {
    let value = ffi::attribute_i64(el, attr::VALUE);

Both go through ffi::copy_attribute (ffi.rs:99) — attribute_string at ffi.rs:222, attribute_i64 at ffi.rs:256 — so each is its own AXUIElementCopyAttributeValue call. attribute_i64 then does value.downcast_ref::<CFNumber>(), which means one copy_attribute plus a branch on CFString/CFNumber would serve both callers.

Only the three roles mapping::role_carries_checked admits pay it, so this is not every node — but it is every interactive toggle, which is a large share of the controls an agent actually drives.

Worth fixing because this crate already treats a per-node round-trip as a cost worth designing around: mapping::subrole_matters exists solely to avoid one, and its doc comment carries a measurement to justify the gate ("48 of 359 nodes gated, walk wall-clock 42.8ms to 45.4ms"). A duplicate read of the same attribute is the same cost with no gate in front of it.

Care needed on two points. The two reads have different failure semantics today — attribute_string is error-blind while the checked path deliberately logs an unreadable value (reader.rs:624-632, the #170 invariant) — so a shared read must keep the "no readable numeric value claims neither checked nor unchecked" behaviour rather than folding a missing CFNumber into a successful string read. And AXValue on a checkbox is a CFNumber, so the string read returns None for it today; a shared read must not start populating AxNode.value for toggles as a side effect.

Surfaced by the pre-merge review panel on #498 and verified by reading; out of scope there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions