Skip to main content

gpui/window/
a11y.rs

1//! Accessibility support, provided by [AccessKit][accesskit].
2//!
3//! There are user-facing guide-level docs [here](crate::_documentation::guides::accessibility).
4//!
5//! ## Architecture
6//!
7//! ```text
8//!                              ┌────────────────────────────────┐   ┌─────────────────────┐
9//!                           ┌─▶│ AccessKit Adapter (MacOS)      │◀─▶│ MacOS System APIs   │
10//!                           │  └────────────────────────────────┘   └─────────────────────┘
11//!                           │
12//! ┌──────┐   ┌───────────┐  │  ┌────────────────────────────────┐   ┌─────────────────────┐
13//! │ GPUI │◀─▶│ AccessKit │◀─┼─▶│ AccessKit Adapter (Windows)    │◀─▶│ Windows System APIs │
14//! └──────┘   └───────────┘  │  └────────────────────────────────┘   └─────────────────────┘
15//!                           │
16//!                           │  ┌────────────────────────────────┐   ┌─────────────────────┐
17//!                           └─▶│ AccessKit Adapter (Linux)      │◀─▶│ dbus                │
18//!                              └────────────────────────────────┘   └─────────────────────┘
19//! ```
20//!
21//! To work with assistive technology, a GPUI app must:
22//! - report meaningful UI changes, including new, removed and changed nodes;
23//! - describe each node's role, value and capabilities;
24//! - dispatch actions from assistive technology to nodes; and
25//! - activate or deactivate accessibility when the system requests it.
26//!
27//! AccessKit has its own [`Action`] type, separate from GPUI's [`crate::Action`]
28//! trait.
29//!
30//! The state for both lives in the [`A11y`] struct in this module.
31//!
32//! ### Reporting UI changes
33//!
34//! While assistive technology is active, we build a [`TreeUpdate`] every frame
35//! and send it to the platform-specific adapter. A [`TreeUpdate`] represents a
36//! subset of the UI tree.
37//! When the adapter receives the update, it diffs it against the previous
38//! update, and calls platform-specific APIs to inform screen readers about the
39//! changes. Nodes may have been created, destroyed, or updated.
40//!
41//! Each node has an ID, and this ID *should* be stable across frames. If a
42//! node's ID changes, then, from AccessKit's point of view, it is a different
43//! node.
44//!
45//! We derive the node ID from the [`GlobalElementId`] in
46//! [`GlobalElementId::accesskit_node_id`]. Nodes without [`GlobalElementId`]s
47//! cannot produce an AccessKit [`NodeId`], and so are not included in the
48//! accessibility tree. When a focused element has no node, we log that once per
49//! focus change (see [`A11y::note_focus_without_node`]).
50//!
51//! This all happens in [`Drawable::prepaint`]. The [`A11y`] struct maintains a
52//! stack of nodes during prepainting, which we can use to calculate the
53//! [`NodeId`]s, and record parent-child relationships. Once all [`Element`]s in
54//! a frame have been prepainted, we send the resulting [`TreeUpdate`] object to
55//! the adapter and the screen reader can announce the changes.
56//!
57//! #### Synthetic children
58//!
59//! Additionally, some nodes can register "synthetic children" using
60//! [`Element::a11y_synthetic_children`]. Normally, one accesskit node is pushed
61//! for every [`Element`] with a role and id. However, sometimes a single
62//! element may want to produce many accesskit nodes. These extra nodes are
63//! referred to as "synthetic children" of the element providing a non-default
64//! [`Element::a11y_synthetic_children`] implementation.
65//!
66//! [`A11ySubtreeBuilder`] lets an element add children to its current node and
67//! modify that node.
68//!
69//! GPUI calls this callback *after* prepainting (and just before popping the
70//! corresponding element), since this step may need prepaint information to be
71//! available. In the future, we may want to add prepaint information more
72//! generally to [`Element::write_a11y_info`], but for now that's not necessary.
73//!
74//! ### Responding to actions
75//!
76//! When the adapter is created, GPUI gives it a callback for dispatching actions.
77//! The callback looks up handlers in [`A11y::action_listeners`], which maps
78//! [`NodeId`]s to boxed callbacks.
79//!
80//! This is populated in:
81//! - [`Window::on_a11y_action`], which is called by:
82//! - [`Interactivity::paint`], which is called by:
83//! - [`StatefulInteractiveElement::on_a11y_action`], which is a public-facing API
84//!
85//! These are cleared at the start of a frame, and re-populated during painting.
86//!
87//! [`NodeId`]: accesskit::NodeId
88
89use crate::*;
90
91pub(crate) mod debug;
92
93use crate::{App, Bounds, FocusId, Pixels, SharedString, Window};
94use accesskit::{Action, NodeId, TreeUpdate};
95use collections::{FxHashMap, FxHashSet};
96use smallvec::SmallVec;
97use std::hash::{Hash, Hasher};
98use std::sync::{
99    Arc,
100    atomic::{AtomicBool, Ordering},
101};
102
103/// The fixed AccessKit node ID used for the root of every window's a11y tree.
104pub(crate) const ROOT_NODE_ID: NodeId = NodeId(0);
105
106/// A listener for an accessibility action on a specific node.
107pub(crate) type A11yActionListener =
108    Box<dyn FnMut(Option<&accesskit::ActionData>, &mut Window, &mut App) + 'static>;
109
110/// Per-window accessibility state.
111///
112/// Manages the AccessKit tree that is built each frame and the mappings
113/// needed to dispatch incoming action requests back to the right elements.
114pub(crate) struct A11y {
115    /// Whether accessibility has been [forcibly disabled] for this window.
116    ///
117    /// [forcibly disabled]: crate::Application::new_inaccessible
118    force_disabled: bool,
119    /// Whether the tree is built every frame even with no assistive
120    /// technology connected. Set through [`crate::Application::with_accessibility_forced`]
121    /// or [`crate::Window::set_a11y_forced`]; for automation and tree inspection.
122    /// [`Self::force_disabled`] still wins.
123    forced: bool,
124    /// Whether a11y features have been requested by the system.
125    ///
126    /// Updated by AccessKit using callbacks provided to the adapter. Can change
127    /// halfway through a frame.
128    active_flag: Arc<AtomicBool>,
129    /// Whether a11y features are active for *this specific frame*.
130    ///
131    /// At the start of each frame, we load [`Self::active_flag`] (using
132    /// [`Self::sync_active_flag`]) and use this to determine whether we
133    /// should construct a [`TreeUpdate`] for this frame. It's important that
134    /// this value is stable within a frame, because the builder API exposed by
135    /// this type maintains a stack of nodes and each must be pushed and popped
136    /// exactly once.
137    ///
138    /// At the end of the frame, we re-call [`Self::sync_active_flag`] to
139    /// determine whether we should actually send the finished [`TreeUpdate`].
140    active_this_frame: bool,
141    pub(crate) nodes: A11yNodeBuilder,
142    pub(crate) focus_ids: FxHashMap<NodeId, FocusId>,
143    pub(crate) node_bounds: FxHashMap<NodeId, Bounds<Pixels>>,
144    pub(crate) action_listeners: FxHashMap<NodeId, Vec<(Action, A11yActionListener)>>,
145    /// Last frame's action listeners, which reused views carry into this frame.
146    previous_action_listeners: FxHashMap<NodeId, Vec<(Action, A11yActionListener)>>,
147    /// The window's title, used to label the root node so assistive
148    /// technology can tell windows apart.
149    window_title: Option<SharedString>,
150    /// The focus id we most recently reported as having no accessibility node,
151    /// used to log at most once per focus change rather than every frame.
152    last_focus_without_node: Option<FocusId>,
153    /// Retains the last tree update (and, in debug builds, per-node provenance)
154    /// so it can be dumped via [`crate::Window::debug_a11y_tree_json`].
155    debug: debug::A11yDebug,
156    /// Maps a view's [`EntityId`] to its `Render` type name
157    #[cfg(debug_assertions)]
158    pub(crate) view_type_names: FxHashMap<EntityId, &'static str>,
159}
160
161impl A11y {
162    pub(crate) fn new(
163        active_flag: Arc<AtomicBool>,
164        force_disabled: bool,
165        forced: bool,
166        window_title: Option<SharedString>,
167    ) -> Self {
168        Self {
169            force_disabled,
170            forced,
171            active_flag,
172            active_this_frame: false,
173            nodes: A11yNodeBuilder::new(),
174            focus_ids: FxHashMap::default(),
175            node_bounds: FxHashMap::default(),
176            action_listeners: FxHashMap::default(),
177            previous_action_listeners: FxHashMap::default(),
178            window_title,
179            last_focus_without_node: None,
180            debug: debug::A11yDebug::default(),
181            #[cfg(debug_assertions)]
182            view_type_names: FxHashMap::default(),
183        }
184    }
185
186    /// Logs (once per focus change) that the focused element is not exposed to
187    /// assistive technology because it has no accessibility node. When this
188    /// happens, screen readers fall back to announcing the whole window instead
189    /// of the focused element. The fix is to give the element both an
190    /// `.id(...)` and a `.role(...)`.
191    pub(crate) fn note_focus_without_node(&mut self, focus_id: FocusId, reason: &str) {
192        if self.last_focus_without_node != Some(focus_id) {
193            self.last_focus_without_node = Some(focus_id);
194            log::info!(
195                "a11y: focused element ({focus_id:?}) has no accessibility node \
196                 ({reason}); assistive technology will announce the whole window \
197                 instead. Give it both an `.id(...)` and a `.role(...)` to expose it."
198            );
199        }
200    }
201
202    pub(crate) fn set_window_title(&mut self, title: impl Into<SharedString>) {
203        self.window_title = Some(title.into());
204    }
205
206    /// Ensures that [`Self::is_active`] returns up to date information.
207    ///
208    /// See the docs for [`Self::active_flag`] and [`Self::active_this_frame`]
209    /// for more commentary.
210    pub(crate) fn sync_active_flag(&mut self) {
211        self.active_this_frame =
212            !self.force_disabled && (self.forced || self.active_flag.load(Ordering::SeqCst));
213    }
214
215    /// Build the tree every frame regardless of whether assistive technology
216    /// is connected. Takes effect on the next frame.
217    pub(crate) fn set_forced(&mut self, forced: bool) {
218        self.forced = forced;
219    }
220
221    pub(crate) fn is_enabled(&self) -> bool {
222        !self.force_disabled
223    }
224
225    pub(crate) fn is_active(&self) -> bool {
226        self.active_this_frame
227    }
228
229    pub(crate) fn set_focusable(&mut self, node_id: NodeId, focus_id: FocusId) {
230        self.focus_ids.insert(node_id, focus_id);
231    }
232
233    /// Report `node_id` as the currently-focused node, if it is present in the
234    /// tree.
235    ///
236    /// Must only be called once per frame.
237    pub(crate) fn set_focus(&mut self, node_id: NodeId) {
238        // A focused node must have been registered as focusable this frame.
239        if !self.focus_ids.contains_key(&node_id) {
240            if cfg!(debug_assertions) {
241                panic!("set_focus called for a node that was not registered with set_focusable");
242            } else {
243                log::warn!(
244                    "a11y: set_focus called for a node that was not registered with \
245                     set_focusable ({node_id:?})"
246                );
247            }
248        }
249        if self.nodes.has_node(node_id) {
250            // The focused element is properly exposed; reset the dedup so a
251            // later focus on a node-less element logs again.
252            self.last_focus_without_node = None;
253            self.nodes.set_focus(node_id);
254        } else {
255            // The element registered a focus handle and an id, but never got a
256            // node because it has no role.
257            if let Some(focus_id) = self.focus_ids.get(&node_id).copied() {
258                self.note_focus_without_node(focus_id, "it has an id but no role");
259            }
260        }
261    }
262
263    pub(crate) fn set_active_descendant(&mut self, node_id: NodeId) {
264        // The active descendant must be a descendant of the focused container,
265        // not the focused node itself.
266        if self.nodes.node_is_focused(node_id) {
267            if cfg!(debug_assertions) {
268                panic!("set_active_descendant called on the focused node");
269            } else {
270                log::warn!("a11y: set_active_descendant called on the focused node ({node_id:?})");
271            }
272            return;
273        }
274        if self.nodes.has_node(node_id) && self.nodes.focus_is_ancestor_of_current() {
275            self.nodes.set_active_descendant(node_id);
276        }
277    }
278
279    /// Clear per-frame state and push the root node to start a new frame.
280    pub(crate) fn begin_frame(&mut self) {
281        self.focus_ids.clear();
282        self.node_bounds.clear();
283        self.previous_action_listeners = std::mem::take(&mut self.action_listeners);
284        self.nodes.begin_frame(self.window_title.as_ref());
285    }
286
287    /// Marks where a view starts adding nodes, for [`Self::record`].
288    pub(crate) fn mark(&self) -> A11yMark {
289        A11yMark {
290            nodes: self.nodes.all_nodes.len(),
291            children: self
292                .nodes
293                .nodes_stack
294                .last()
295                .map_or(0, |node| node.children().len()),
296            focus: self.nodes.focus,
297            active_descendant: self.nodes.active_descendant,
298        }
299    }
300
301    /// Copies what was added to the tree since `mark`, so a reused view can [replay](Self::replay)
302    /// it without running its prepaint again.
303    pub(crate) fn record(&self, mark: A11yMark) -> A11yRecording {
304        let nodes = self.nodes.all_nodes[mark.nodes..].to_vec();
305        let children = self
306            .nodes
307            .nodes_stack
308            .last()
309            .map_or(&[][..], |node| &node.children()[mark.children..])
310            .to_vec();
311        fn lookup<T: Copy>(
312            nodes: &[(NodeId, accesskit::Node)],
313            map: &FxHashMap<NodeId, T>,
314        ) -> Vec<(NodeId, T)> {
315            nodes
316                .iter()
317                .filter_map(|(id, _)| Some((*id, *map.get(id)?)))
318                .collect()
319        }
320        A11yRecording {
321            bounds: lookup(&nodes, &self.node_bounds),
322            focusable: lookup(&nodes, &self.focus_ids),
323            focus: self.nodes.focus.filter(|_| mark.focus.is_none()),
324            active_descendant: self
325                .nodes
326                .active_descendant
327                .filter(|_| mark.active_descendant.is_none()),
328            #[cfg(debug_assertions)]
329            info: nodes
330                .iter()
331                .filter_map(|(id, _)| Some((*id, self.nodes.node_info.get(id)?.clone())))
332                .collect(),
333            nodes,
334            children,
335        }
336    }
337
338    /// Adds a recorded view's nodes back under the open node, with their bounds, focus and action
339    /// listeners.
340    pub(crate) fn replay(&mut self, recording: &A11yRecording) {
341        for (id, node) in &recording.nodes {
342            if self.nodes.can_push(*id) {
343                self.nodes.all_nodes.push((*id, node.clone()));
344            }
345            if let Some(listeners) = self.previous_action_listeners.remove(id) {
346                self.action_listeners.insert(*id, listeners);
347            }
348        }
349        if let Some(parent) = self.nodes.nodes_stack.last_mut() {
350            for id in &recording.children {
351                parent.push_child(*id);
352            }
353        }
354        self.node_bounds.extend(recording.bounds.iter().copied());
355        self.focus_ids.extend(recording.focusable.iter().copied());
356        if let Some(id) = recording.focus {
357            self.nodes.set_focus(id);
358        }
359        if let Some(id) = recording.active_descendant {
360            self.nodes.set_active_descendant(id);
361        }
362        #[cfg(debug_assertions)]
363        self.nodes.node_info.extend(recording.info.iter().cloned());
364    }
365
366    /// Finalize the tree and produce a [`TreeUpdate`] for the platform adapter.
367    pub(crate) fn end_frame(&mut self, frame: debug::FrameDebugInfo) -> TreeUpdate {
368        let update = self.nodes.finalize();
369        self.debug.capture(
370            &update,
371            self.nodes.focus,
372            self.nodes.active_descendant,
373            self.window_title.as_ref(),
374            frame,
375            &self.node_bounds,
376        );
377        #[cfg(debug_assertions)]
378        self.debug.capture_node_info(&self.nodes.node_info);
379        update
380    }
381
382    pub(crate) fn debug_tree_json(&self) -> Option<String> {
383        self.debug.to_json()
384    }
385
386    pub(crate) fn last_tree_update(&self) -> Option<&TreeUpdate> {
387        self.debug.last_tree_update()
388    }
389
390    pub(crate) fn last_node_bounds(&self, id: NodeId) -> Option<Bounds<Pixels>> {
391        self.debug.node_bounds(id)
392    }
393
394    pub(crate) fn frame_number(&self) -> u64 {
395        self.debug.frame_number()
396    }
397}
398
399/// Adds accessibility nodes for things an element draws but doesn't model as elements.
400///
401/// GPUI passes a builder to [`Element::a11y_synthetic_children`] (or to the closure given to
402/// [`StatefulInteractiveElement::a11y_synthetic_children`]) after the element is prepainted. The
403/// builder exists only for that call. Nodes you add become children of the element's node. Use
404/// it for things like the lines of a text editor, which users can read and select but which
405/// aren't separate elements. See the
406/// [accessibility guide](crate::_documentation::guides::accessibility#expose-elements-that-gpui-doesnt-draw-as-nodes).
407pub struct A11ySubtreeBuilder<'a> {
408    parent_id: NodeId,
409    nodes: &'a mut A11yNodeBuilder,
410    /// Provenance of the real element whose `a11y_synthetic_children` is
411    /// running.
412    #[cfg(debug_assertions)]
413    creator: debug::NodeCreator,
414}
415
416impl<'a> A11ySubtreeBuilder<'a> {
417    pub(crate) fn new(parent_id: NodeId, nodes: &'a mut A11yNodeBuilder) -> Self {
418        Self {
419            parent_id,
420            nodes,
421            #[cfg(debug_assertions)]
422            creator: debug::NodeCreator::default(),
423        }
424    }
425
426    #[cfg(debug_assertions)]
427    pub(crate) fn with_creator(mut self, creator: debug::NodeCreator) -> Self {
428        self.creator = creator;
429        self
430    }
431
432    /// Returns a stable node id for a synthetic child, derived from `key`.
433    ///
434    /// The id is a hash of `key` and the parent node's id. Keys must be unique within one
435    /// [`Element::a11y_synthetic_children`] call. The same key in a later frame gives the same id,
436    /// so assistive technology sees the same node. Use [`Self::push_child`] with the returned id.
437    ///
438    /// # Examples
439    ///
440    /// ```
441    /// use gpui::{Role, accesskit, div, prelude::*};
442    ///
443    /// // Two runs of text in one text field, each with its own stable node id.
444    /// let field = div()
445    ///     .id("field")
446    ///     .role(Role::TextInput)
447    ///     .a11y_synthetic_children(|builder| {
448    ///         for (index, text) in ["Hello", "world"].into_iter().enumerate() {
449    ///             let mut run = accesskit::Node::new(Role::TextRun);
450    ///             run.set_value(text);
451    ///             builder.push_child(builder.synthetic_node_id(index), run);
452    ///         }
453    ///     });
454    /// # let _ = field;
455    /// ```
456    pub fn synthetic_node_id(&self, key: impl Hash) -> NodeId {
457        let mut hasher = std::hash::DefaultHasher::default();
458        self.parent_id.0.hash(&mut hasher);
459        key.hash(&mut hasher);
460        NodeId(hasher.finish())
461    }
462
463    /// Adds `node` with the given `id` as a leaf child of this element's node.
464    ///
465    /// Returns `true` if the node was added. A node id can appear only once in a frame's tree.
466    /// If `id` is already used, the new node is dropped and this returns `false`. In debug builds,
467    /// a duplicate id panics instead. Use [`Self::synthetic_node_id`] to get an id that stays the
468    /// same between frames.
469    pub fn push_child(&mut self, id: NodeId, node: accesskit::Node) -> bool {
470        let pushed = self.nodes.push_leaf(id, node);
471        #[cfg(debug_assertions)]
472        if pushed {
473            self.nodes.record_node_info(
474                id,
475                debug::NodeDebugInfo {
476                    synthetic: true,
477                    view: self.creator.view,
478                    element_id: self.creator.element_id.clone(),
479                    source_location: self.creator.source_location,
480                },
481            );
482        }
483        pushed
484    }
485
486    /// Returns a mutable reference to this element's own node, so you can set its properties.
487    ///
488    /// Use it to set properties such as the value or the label of the element from inside
489    /// [`Element::a11y_synthetic_children`]. The reference is valid only for that call.
490    pub fn parent_node(&mut self) -> &mut accesskit::Node {
491        self.nodes
492            .current_node_mut()
493            .expect("A11ySubtreeBuilder exists only while its element's node is on the stack")
494    }
495}
496
497/// Where the tree stood when a view started drawing. See [`A11y::mark`].
498pub(crate) struct A11yMark {
499    nodes: usize,
500    children: usize,
501    focus: Option<NodeId>,
502    active_descendant: Option<NodeId>,
503}
504
505/// The nodes one view added to the tree in a frame. See [`A11y::record`].
506pub(crate) struct A11yRecording {
507    nodes: Vec<(NodeId, accesskit::Node)>,
508    /// The nodes added directly under the node that was open when the view drew.
509    children: Vec<NodeId>,
510    bounds: Vec<(NodeId, Bounds<Pixels>)>,
511    focusable: Vec<(NodeId, FocusId)>,
512    focus: Option<NodeId>,
513    active_descendant: Option<NodeId>,
514    #[cfg(debug_assertions)]
515    info: Vec<(NodeId, debug::NodeDebugInfo)>,
516}
517
518pub(crate) struct A11yNodeBuilder {
519    ids_stack: SmallVec<[NodeId; 16]>,
520    nodes_stack: SmallVec<[accesskit::Node; 16]>,
521    /// This is the exact type required by accesskit, so we can't just make it a
522    /// `HashMap<NodeId, Node>` to remove the need for `seen_ids`
523    all_nodes: Vec<(NodeId, accesskit::Node)>,
524    seen_ids: FxHashSet<NodeId>,
525    /// The node that GPUI considers focused. Note that this may be different to
526    /// what is reported to accesskit - see [`Self::active_descendant`]
527    focus: Option<NodeId>,
528    /// If a node calls `.aria_active_descendant()`, AND an ancestor is focused,
529    /// override it as the focused node. This supports the "active descendant"
530    /// pattern, which allows a focused container to act as if a descendant is
531    /// focused.
532    active_descendant: Option<NodeId>,
533    #[cfg(debug_assertions)]
534    node_info: FxHashMap<NodeId, debug::NodeDebugInfo>,
535}
536
537impl A11yNodeBuilder {
538    fn new() -> Self {
539        Self {
540            ids_stack: SmallVec::new(),
541            nodes_stack: SmallVec::new(),
542            all_nodes: Vec::new(),
543            seen_ids: FxHashSet::default(),
544            focus: None,
545            active_descendant: None,
546            #[cfg(debug_assertions)]
547            node_info: FxHashMap::default(),
548        }
549    }
550
551    /// Records provenance for a node already pushed this frame. Debug builds only.
552    #[cfg(debug_assertions)]
553    pub(crate) fn record_node_info(&mut self, id: NodeId, info: debug::NodeDebugInfo) {
554        self.node_info.insert(id, info);
555    }
556
557    #[must_use]
558    fn can_push(&mut self, id: NodeId) -> bool {
559        debug_assert!(!self.ids_stack.is_empty(), "node pushed before push_root");
560
561        if !self.seen_ids.insert(id) {
562            debug_assert!(
563                false,
564                "Duplicate a11y node id: {id:?}. In a release build, this node would be silently discarded from the a11y tree."
565            );
566            return false;
567        }
568
569        true
570    }
571
572    /// Push a new node onto the stack. It becomes a child of the current
573    /// top-of-stack node.
574    ///
575    /// Returns `true` if the node was successfully pushed.
576    pub(crate) fn push(&mut self, id: NodeId, node: accesskit::Node) -> bool {
577        if !self.can_push(id) {
578            return false;
579        }
580
581        if let Some(parent) = self.nodes_stack.last_mut() {
582            parent.push_child(id);
583        }
584        self.ids_stack.push(id);
585        self.nodes_stack.push(node);
586        true
587    }
588
589    /// Add a leaf node as a child of the current top-of-stack node, without
590    /// pushing it onto the stack. Semantically equivalent to a [`Self::push`]
591    /// followed by a [`Self::pop`].
592    ///
593    /// Returns `true` if the node was successfully pushed.
594    pub(crate) fn push_leaf(&mut self, id: NodeId, node: accesskit::Node) -> bool {
595        if !self.can_push(id) {
596            return false;
597        }
598
599        if let Some(parent) = self.nodes_stack.last_mut() {
600            parent.push_child(id);
601        }
602        self.all_nodes.push((id, node));
603        true
604    }
605
606    pub(crate) fn current_node_mut(&mut self) -> Option<&mut accesskit::Node> {
607        self.nodes_stack.last_mut()
608    }
609
610    /// Pop the current node off the stack and finalize it into the all_nodes
611    /// list.
612    pub(crate) fn pop(&mut self) {
613        debug_assert!(self.ids_stack.len() > 1, "pop would remove the root node");
614
615        if let (Some(id), Some(node)) = (self.ids_stack.pop(), self.nodes_stack.pop()) {
616            self.all_nodes.push((id, node));
617        }
618    }
619
620    /// Push the root node to start a new frame.
621    fn begin_frame(&mut self, window_title: Option<&SharedString>) {
622        self.all_nodes.clear();
623        self.ids_stack.clear();
624        self.nodes_stack.clear();
625        self.seen_ids.clear();
626        #[cfg(debug_assertions)]
627        self.node_info.clear();
628        let mut root_node = accesskit::Node::new(accesskit::Role::Window);
629        if let Some(title) = window_title {
630            root_node.set_label(title.to_string());
631        }
632
633        self.ids_stack.push(ROOT_NODE_ID);
634        self.nodes_stack.push(root_node);
635        self.focus = None;
636        self.active_descendant = None;
637    }
638
639    /// Returns whether a node with the given ID has been pushed in this frame.
640    pub(crate) fn has_node(&self, id: NodeId) -> bool {
641        id == ROOT_NODE_ID || self.seen_ids.contains(&id)
642    }
643
644    /// Returns whether `id` is the node currently reported as focused.
645    pub(crate) fn node_is_focused(&self, id: NodeId) -> bool {
646        self.focus == Some(id)
647    }
648
649    pub(crate) fn focus_is_ancestor_of_current(&self) -> bool {
650        let Some(focus) = self.focus else {
651            return false;
652        };
653
654        // The current node is on top of the stack; everything below it is an
655        // ancestor.
656        let ancestor_count = self.ids_stack.len().saturating_sub(1);
657        self.ids_stack[..ancestor_count].contains(&focus)
658    }
659
660    pub(crate) fn set_active_descendant(&mut self, id: NodeId) {
661        if self
662            .active_descendant
663            .is_some_and(|existing| existing != id)
664        {
665            if cfg!(debug_assertions) {
666                panic!("active descendant claimed by multiple nodes in one frame");
667            } else {
668                log::warn!(
669                    "a11y: multiple nodes claimed the active descendant this frame; \
670                     using last-wins ({id:?})"
671                );
672            }
673        }
674        self.active_descendant = Some(id);
675    }
676
677    pub(crate) fn set_focus(&mut self, id: NodeId) {
678        if self.focus.is_some() {
679            if cfg!(debug_assertions) {
680                panic!("set_focus called more than once in a single frame");
681            } else {
682                log::warn!(
683                    "a11y: set_focus called more than once in a single frame; \
684                     using last-wins ({id:?})"
685                );
686            }
687        }
688        self.focus = Some(id);
689    }
690
691    fn finalize(&mut self) -> TreeUpdate {
692        // Stack should contain only the root node
693        debug_assert_eq!(self.ids_stack.len(), 1);
694        debug_assert_eq!(self.ids_stack[0], ROOT_NODE_ID);
695
696        if self.ids_stack.len() != 1 {
697            log::error!(
698                "a11y: Stack imbalance at end of frame: expected 1 (root), got {}. \
699                 Some elements may have pushed without popping.",
700                self.ids_stack.len()
701            );
702        }
703
704        // Pop remaining nodes (should just be the root).
705        while !self.ids_stack.is_empty() {
706            if let (Some(id), Some(node)) = (self.ids_stack.pop(), self.nodes_stack.pop()) {
707                self.all_nodes.push((id, node));
708            }
709        }
710
711        let focus = match self.active_descendant {
712            Some(id) if self.has_node(id) => id,
713            Some(id) => {
714                if cfg!(debug_assertions) {
715                    panic!("active_descendant set to {id:?}, which is not in the tree");
716                } else {
717                    log::warn!("active_descendant set to {id:?}, which is not in the tree");
718                    self.focus.unwrap_or(ROOT_NODE_ID)
719                }
720            }
721
722            _ => self.focus.unwrap_or(ROOT_NODE_ID),
723        };
724
725        let nodes = std::mem::take(&mut self.all_nodes);
726        let update = TreeUpdate {
727            nodes,
728            tree: Some(accesskit::Tree::new(ROOT_NODE_ID)),
729            tree_id: accesskit::TreeId::ROOT,
730            focus,
731        };
732
733        Self::repair_tree_update(update)
734    }
735
736    /// Accesskit panics on invalid [`TreeUpdate`]s. This function defensively
737    /// checks invariants that accesskit panics on, and tries to fix them.
738    fn repair_tree_update(mut update: TreeUpdate) -> TreeUpdate {
739        let node_ids: FxHashSet<NodeId> = update.nodes.iter().map(|(id, _)| *id).collect();
740
741        // Focus must point to a node in the tree.
742        if !node_ids.contains(&update.focus) {
743            log::error!(
744                "a11y: Focused node {:?} is not in the tree ({} nodes). \
745                 Falling back to root. This is a bug in the a11y tree builder.",
746                update.focus,
747                update.nodes.len()
748            );
749            update.focus = ROOT_NODE_ID;
750        }
751
752        // Every child reference must point to a node in the update.
753        for (id, node) in &mut update.nodes {
754            let has_invalid_child = node
755                .children()
756                .iter()
757                .any(|child_id| !node_ids.contains(child_id));
758            if has_invalid_child {
759                let children = node.children();
760                let invalid_count = children
761                    .iter()
762                    .filter(|child_id| !node_ids.contains(child_id))
763                    .count();
764                log::error!(
765                    "a11y: Node {:?} references {} children not present in the tree. \
766                     Stripping invalid child references.",
767                    id,
768                    invalid_count
769                );
770                let valid: Vec<NodeId> = children
771                    .iter()
772                    .copied()
773                    .filter(|child_id| node_ids.contains(child_id))
774                    .collect();
775                node.set_children(valid);
776            }
777        }
778
779        update
780    }
781}
782
783#[cfg(test)]
784mod tests {
785    // Import specific items rather than glob-importing `super`, which would pull
786    // in gpui's own `test` attribute macro and shadow the standard one.
787    use super::{A11y, A11yNodeBuilder, ROOT_NODE_ID};
788    use crate::FocusId;
789    use accesskit::{NodeId, Role, TreeUpdate};
790    use std::sync::{Arc, atomic::AtomicBool};
791
792    fn test_node() -> accesskit::Node {
793        accesskit::Node::new(Role::GenericContainer)
794    }
795
796    fn new_builder() -> A11yNodeBuilder {
797        let mut builder = A11yNodeBuilder::new();
798        builder.begin_frame(None);
799        builder
800    }
801
802    fn new_a11y() -> A11y {
803        let mut a11y = A11y::new(Arc::new(AtomicBool::new(true)), false, false, None);
804        a11y.begin_frame();
805        a11y
806    }
807
808    #[test]
809    fn forced_builds_the_tree_without_an_assistive_client() {
810        // No assistive technology has activated the platform adapter.
811        let mut a11y = A11y::new(Arc::new(AtomicBool::new(false)), false, true, None);
812        a11y.sync_active_flag();
813        assert!(a11y.is_active());
814
815        a11y.set_forced(false);
816        a11y.sync_active_flag();
817        assert!(!a11y.is_active());
818    }
819
820    #[test]
821    fn force_disabled_wins_over_forced() {
822        let mut a11y = A11y::new(Arc::new(AtomicBool::new(true)), true, true, None);
823        a11y.sync_active_flag();
824        assert!(!a11y.is_active());
825    }
826
827    #[test]
828    fn accessibility_enabled_is_independent_of_activation() {
829        for force_disabled in [false, true] {
830            for active in [false, true] {
831                let mut a11y = A11y::new(
832                    Arc::new(AtomicBool::new(active)),
833                    force_disabled,
834                    false,
835                    None,
836                );
837                a11y.sync_active_flag();
838
839                assert_eq!(a11y.is_enabled(), !force_disabled);
840                assert_eq!(a11y.is_active(), !force_disabled && active);
841            }
842        }
843    }
844
845    #[test]
846    fn active_descendant_honored_when_container_focused() {
847        let mut builder = new_builder();
848        let container = NodeId(1);
849        let item = NodeId(2);
850
851        assert!(builder.push(container, test_node()));
852        builder.set_focus(container);
853        assert!(builder.push(item, test_node()));
854
855        // The item is on top of the stack; the focused container is its
856        // ancestor, so the claim is honored.
857        assert!(builder.focus_is_ancestor_of_current());
858        builder.set_active_descendant(item);
859
860        builder.pop(); // item
861        builder.pop(); // container
862        let update = builder.finalize();
863        assert_eq!(update.focus, item);
864    }
865
866    #[test]
867    fn active_descendant_honored_for_deep_descendant() {
868        let mut builder = new_builder();
869        let container = NodeId(1);
870        let group = NodeId(2);
871        let item = NodeId(3);
872
873        assert!(builder.push(container, test_node()));
874        builder.set_focus(container);
875        assert!(builder.push(group, test_node()));
876        assert!(builder.push(item, test_node()));
877
878        // The item is a grandchild of the focused container; depth doesn't
879        // matter, the focused ancestor is still on the stack.
880        assert!(builder.focus_is_ancestor_of_current());
881        builder.set_active_descendant(item);
882
883        builder.pop(); // item
884        builder.pop(); // group
885        builder.pop(); // container
886        let update = builder.finalize();
887        assert_eq!(update.focus, item);
888    }
889
890    #[test]
891    fn active_descendant_ignored_when_focus_in_other_subtree() {
892        let mut builder = new_builder();
893        let focused_container = NodeId(1);
894        let focused_leaf = NodeId(2);
895        let other_container = NodeId(3);
896        let other_item = NodeId(4);
897
898        // First subtree holds real focus.
899        assert!(builder.push(focused_container, test_node()));
900        assert!(builder.push(focused_leaf, test_node()));
901        builder.set_focus(focused_leaf);
902        builder.pop(); // focused_leaf
903        builder.pop(); // focused_container
904
905        // Second subtree: its item would claim the active descendant, but the
906        // focus is not on any of its ancestors, so the gate rejects it.
907        assert!(builder.push(other_container, test_node()));
908        assert!(builder.push(other_item, test_node()));
909        assert!(!builder.focus_is_ancestor_of_current());
910        builder.pop(); // other_item
911        builder.pop(); // other_container
912
913        let update = builder.finalize();
914        assert_eq!(update.focus, focused_leaf);
915    }
916
917    #[test]
918    fn active_descendant_ignored_when_nothing_focused() {
919        let mut builder = new_builder();
920        let container = NodeId(1);
921        let item = NodeId(2);
922
923        assert!(builder.push(container, test_node()));
924        assert!(builder.push(item, test_node()));
925
926        // Nothing is focused (focus defaults to the root window node), so the
927        // gate rejects the claim.
928        assert!(!builder.focus_is_ancestor_of_current());
929        builder.pop();
930        builder.pop();
931
932        let update = builder.finalize();
933        assert_eq!(update.focus, ROOT_NODE_ID);
934    }
935
936    #[test]
937    fn regular_focus_used_when_no_active_descendant() {
938        let mut builder = new_builder();
939        let focused = NodeId(1);
940
941        assert!(builder.push(focused, test_node()));
942        builder.set_focus(focused);
943        builder.pop();
944
945        let update = builder.finalize();
946        assert_eq!(update.focus, focused);
947    }
948
949    #[test]
950    fn focus_is_ancestor_excludes_self_and_non_ancestors() {
951        let mut builder = new_builder();
952        let container = NodeId(1);
953        let item = NodeId(2);
954
955        assert!(builder.push(container, test_node()));
956        builder.set_focus(container);
957
958        // With the focused container itself on top, it is not its own (strict)
959        // ancestor, so the gate is false.
960        assert!(!builder.focus_is_ancestor_of_current());
961
962        assert!(builder.push(item, test_node()));
963        // Now the focused container is a strict ancestor of the item on top.
964        assert!(builder.focus_is_ancestor_of_current());
965
966        builder.pop();
967        builder.pop();
968    }
969
970    // The double-claim guard panics only in debug builds; in release it falls
971    // back to last-wins with a warning.
972    #[test]
973    #[cfg_attr(
974        debug_assertions,
975        should_panic(expected = "active descendant claimed by multiple nodes")
976    )]
977    fn multiple_active_descendant_claims_panic_in_debug() {
978        let mut builder = new_builder();
979        builder.set_active_descendant(NodeId(1));
980        builder.set_active_descendant(NodeId(2));
981    }
982
983    // Setting focus twice in one frame means two elements both claimed window
984    // focus; that panics in debug and falls back to last-wins in release.
985    #[test]
986    #[cfg_attr(
987        debug_assertions,
988        should_panic(expected = "set_focus called more than once")
989    )]
990    fn setting_focus_twice_panics_in_debug() {
991        let mut builder = new_builder();
992        builder.set_focus(NodeId(1));
993        builder.set_focus(NodeId(2));
994    }
995
996    // Focusing a node that was never registered as focusable is a bug: panic in
997    // debug, warn in release.
998    #[test]
999    #[cfg_attr(
1000        debug_assertions,
1001        should_panic(expected = "was not registered with set_focusable")
1002    )]
1003    fn set_focus_without_set_focusable() {
1004        let mut a11y = new_a11y();
1005        let node = NodeId(1);
1006        assert!(a11y.nodes.push(node, test_node()));
1007        // set_focusable was never called for `node`.
1008        a11y.set_focus(node);
1009    }
1010
1011    // The focused node cannot also be its own active descendant: panic in
1012    // debug, warn in release.
1013    #[test]
1014    #[cfg_attr(debug_assertions, should_panic(expected = "on the focused node"))]
1015    fn set_active_descendant_on_focused_node() {
1016        let mut a11y = new_a11y();
1017        let node = NodeId(1);
1018        assert!(a11y.nodes.push(node, test_node()));
1019        a11y.set_focusable(node, FocusId::default());
1020        a11y.set_focus(node);
1021        a11y.set_active_descendant(node);
1022    }
1023
1024    // Two sibling children of a focused container both claim the active
1025    // descendant (both pass the focus gate). The second claim is a bug: panic
1026    // in debug, last-wins + warn in release.
1027    #[test]
1028    #[cfg_attr(
1029        debug_assertions,
1030        should_panic(expected = "active descendant claimed by multiple nodes")
1031    )]
1032    fn two_siblings_claiming_active_descendant() {
1033        let mut a11y = new_a11y();
1034        let container = NodeId(1);
1035        let first = NodeId(2);
1036        let second = NodeId(3);
1037
1038        assert!(a11y.nodes.push(container, test_node()));
1039        a11y.set_focusable(container, FocusId::default());
1040        a11y.set_focus(container);
1041
1042        assert!(a11y.nodes.push(first, test_node()));
1043        a11y.set_active_descendant(first);
1044        a11y.nodes.pop(); // first
1045
1046        assert!(a11y.nodes.push(second, test_node()));
1047        a11y.set_active_descendant(second);
1048        a11y.nodes.pop(); // second
1049
1050        a11y.nodes.pop(); // container
1051    }
1052
1053    // Node A is focused; node C (a child of the unfocused node B) claims the
1054    // active descendant. The final tree must still report A as focused.
1055    #[test]
1056    fn active_descendant_in_unfocused_subtree_keeps_real_focus() {
1057        let mut a11y = new_a11y();
1058        let a = NodeId(1);
1059        let b = NodeId(2);
1060        let c = NodeId(3);
1061
1062        assert!(a11y.nodes.push(a, test_node()));
1063        a11y.set_focusable(a, FocusId::default());
1064        a11y.set_focus(a);
1065        a11y.nodes.pop(); // a
1066
1067        assert!(a11y.nodes.push(b, test_node()));
1068        assert!(a11y.nodes.push(c, test_node()));
1069        a11y.set_active_descendant(c);
1070        a11y.nodes.pop(); // c
1071        a11y.nodes.pop(); // b
1072
1073        let update = a11y.end_frame(Default::default());
1074        assert_eq!(update.focus, a);
1075    }
1076
1077    #[gpui::test]
1078    fn reused_views_keep_their_nodes_focus_and_listeners(cx: &mut crate::TestAppContext) {
1079        use crate::{
1080            AnyWindowHandle, AppContext, Context, Entity, FocusHandle, InteractiveElement,
1081            IntoElement, ParentElement, Render, StatefulInteractiveElement, Styled, Window, div,
1082            px,
1083        };
1084        use std::{cell::Cell, rc::Rc};
1085
1086        struct Button {
1087            label: &'static str,
1088            focus: FocusHandle,
1089            renders: Rc<Cell<usize>>,
1090            presses: Rc<Cell<usize>>,
1091        }
1092
1093        impl Render for Button {
1094            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
1095                self.renders.set(self.renders.get() + 1);
1096                let presses = self.presses.clone();
1097                div()
1098                    .id("button")
1099                    .role(Role::Button)
1100                    .aria_label(self.label)
1101                    .track_focus(&self.focus)
1102                    .size(px(10.))
1103                    .on_a11y_action(accesskit::Action::Click, move |_, _, _| {
1104                        presses.set(presses.get() + 1)
1105                    })
1106            }
1107        }
1108
1109        struct Host(Entity<Button>, Entity<Button>);
1110
1111        impl Render for Host {
1112            fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
1113                div()
1114                    .id("host")
1115                    .role(Role::Group)
1116                    .child(self.0.clone())
1117                    .child(self.1.clone())
1118            }
1119        }
1120
1121        let renders = Rc::new(Cell::new(0));
1122        let presses = Rc::new(Cell::new(0));
1123        let button = |label, cx: &mut crate::TestAppContext| {
1124            let (renders, presses) = (renders.clone(), presses.clone());
1125            cx.new(|cx| Button {
1126                label,
1127                focus: cx.focus_handle(),
1128                renders,
1129                presses,
1130            })
1131        };
1132        let (first, second) = (button("first", cx), button("second", cx));
1133        let window = cx.add_window({
1134            let (first, second) = (first.clone(), second.clone());
1135            move |_, _| Host(first, second)
1136        });
1137        let window = AnyWindowHandle::from(window);
1138        let draw = |cx: &mut crate::TestAppContext| {
1139            cx.update_window(window, |_, window, cx| {
1140                window.draw(cx).clear(cx);
1141                window.a11y_tree().unwrap().clone()
1142            })
1143            .unwrap()
1144        };
1145        let find = |tree: &TreeUpdate, label: &str| {
1146            tree.nodes
1147                .iter()
1148                .find(|(_, node)| node.label() == Some(label))
1149                .map(|(id, _)| *id)
1150                .unwrap()
1151        };
1152
1153        cx.update_window(window, |_, window, cx| {
1154            window.set_a11y_forced(true);
1155            let focus = second.read(cx).focus.clone();
1156            focus.focus(window, cx);
1157        })
1158        .unwrap();
1159        let before = draw(cx);
1160        let second_id = find(&before, "second");
1161        assert_eq!(before.focus, second_id);
1162
1163        let renders_before = renders.get();
1164        first.update(cx, |button, cx| {
1165            button.label = "changed";
1166            cx.notify();
1167        });
1168        let after = draw(cx);
1169        assert_eq!(
1170            renders.get(),
1171            renders_before + 1,
1172            "only the changed view renders"
1173        );
1174
1175        let mut expected = before.clone();
1176        let first_id = find(&before, "first");
1177        for (id, node) in &mut expected.nodes {
1178            if *id == first_id {
1179                node.set_label("changed");
1180            }
1181        }
1182        assert_eq!(
1183            after, expected,
1184            "the reused view's nodes and focus are unchanged"
1185        );
1186
1187        cx.update_window(window, |_, window, cx| {
1188            window.perform_a11y_action(second_id, accesskit::Action::Click, None, cx)
1189        })
1190        .unwrap();
1191        assert_eq!(
1192            presses.get(),
1193            1,
1194            "the reused view's action listener survives"
1195        );
1196    }
1197}