1use 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
103pub(crate) const ROOT_NODE_ID: NodeId = NodeId(0);
105
106pub(crate) type A11yActionListener =
108 Box<dyn FnMut(Option<&accesskit::ActionData>, &mut Window, &mut App) + 'static>;
109
110pub(crate) struct A11y {
115 force_disabled: bool,
119 forced: bool,
124 active_flag: Arc<AtomicBool>,
129 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 previous_action_listeners: FxHashMap<NodeId, Vec<(Action, A11yActionListener)>>,
147 window_title: Option<SharedString>,
150 last_focus_without_node: Option<FocusId>,
153 debug: debug::A11yDebug,
156 #[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 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 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 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 pub(crate) fn set_focus(&mut self, node_id: NodeId) {
238 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 self.last_focus_without_node = None;
253 self.nodes.set_focus(node_id);
254 } else {
255 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 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 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 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 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 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 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
399pub struct A11ySubtreeBuilder<'a> {
408 parent_id: NodeId,
409 nodes: &'a mut A11yNodeBuilder,
410 #[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 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 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 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
497pub(crate) struct A11yMark {
499 nodes: usize,
500 children: usize,
501 focus: Option<NodeId>,
502 active_descendant: Option<NodeId>,
503}
504
505pub(crate) struct A11yRecording {
507 nodes: Vec<(NodeId, accesskit::Node)>,
508 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 all_nodes: Vec<(NodeId, accesskit::Node)>,
524 seen_ids: FxHashSet<NodeId>,
525 focus: Option<NodeId>,
528 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 #[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 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 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 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 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 pub(crate) fn has_node(&self, id: NodeId) -> bool {
641 id == ROOT_NODE_ID || self.seen_ids.contains(&id)
642 }
643
644 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 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 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 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 fn repair_tree_update(mut update: TreeUpdate) -> TreeUpdate {
739 let node_ids: FxHashSet<NodeId> = update.nodes.iter().map(|(id, _)| *id).collect();
740
741 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 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 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 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 assert!(builder.focus_is_ancestor_of_current());
858 builder.set_active_descendant(item);
859
860 builder.pop(); builder.pop(); 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 assert!(builder.focus_is_ancestor_of_current());
881 builder.set_active_descendant(item);
882
883 builder.pop(); builder.pop(); builder.pop(); 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 assert!(builder.push(focused_container, test_node()));
900 assert!(builder.push(focused_leaf, test_node()));
901 builder.set_focus(focused_leaf);
902 builder.pop(); builder.pop(); 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(); builder.pop(); 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 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 assert!(!builder.focus_is_ancestor_of_current());
961
962 assert!(builder.push(item, test_node()));
963 assert!(builder.focus_is_ancestor_of_current());
965
966 builder.pop();
967 builder.pop();
968 }
969
970 #[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 #[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 #[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 a11y.set_focus(node);
1009 }
1010
1011 #[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 #[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(); assert!(a11y.nodes.push(second, test_node()));
1047 a11y.set_active_descendant(second);
1048 a11y.nodes.pop(); a11y.nodes.pop(); }
1052
1053 #[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(); 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(); a11y.nodes.pop(); 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}