1use std::collections::VecDeque;
5use std::fmt::Debug;
6use std::iter::repeat;
7use std::sync::atomic::AtomicU64;
8use std::sync::{LazyLock, atomic};
9
10use accesskit::{NodeId, Role};
11use bitflags::bitflags;
12use layout_api::{AccessibilityDamage, LayoutElement, LayoutNode, LayoutNodeType};
13use log::trace;
14use rustc_hash::{FxHashMap, FxHashSet};
15use script::layout_dom::{ServoLayoutElement, ServoLayoutNode};
16use servo_base::Epoch;
17use servo_base::print_tree::PrintTree;
18use servo_config::opts::{self, DiagnosticsLogging, DiagnosticsLoggingOption};
19use servo_config::pref;
20use style::Atom;
21use style::dom::OpaqueNode;
22use web_atoms::{LocalName, local_name, ns};
23
24use crate::ArcRefCell;
25use crate::cell::WeakRefCell;
26
27bitflags! {
28 #[derive(Clone, Copy, Default, Debug, Eq, PartialEq)]
32 struct LocalAccessibilityDamage: u16 {
33 const SubtreeChanged = 0b0001;
35 const RoleChanged = 0b0010;
37 const TextChanged = 0b0100;
39 }
40}
41
42struct AccessibilityUpdate {
44 changed_nodes: FxHashSet<NodeId>,
46 tree_changes: FxHashMap<NodeId, TreeChange>,
48 counters: UpdateCounters,
50 rooted_nodes: Option<FxHashSet<OpaqueNode>>,
54}
55
56#[derive(Debug, Default)]
57pub struct UpdateCounters {
58 pub nodes_updated_from_dom: u32,
59 pub nodes_updated_from_tree: u32,
60 pub nodes_in_tree_update: u32,
61}
62
63bitflags! {
64 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
67 struct DirtyState : u16 {
68 const DescendantHasDamage = 0b0001;
70 const HasDamage = 0b0010;
72 const Updated = 0b0100;
74 }
75}
76
77struct AccessibilityNode {
78 id: NodeId,
82 accesskit_node: accesskit::Node,
85 parent_node: Option<WeakRefCell<AccessibilityNode>>,
87 child_nodes: Vec<ArcRefCell<AccessibilityNode>>,
89 opaque_node: Option<OpaqueNode>,
93 dirty_state: DirtyState,
95}
96
97#[derive(Debug)]
102pub struct AccessibilityTree {
103 nodes: FxHashMap<NodeId, ArcRefCell<AccessibilityNode>>,
106 opaque_node_to_id: FxHashMap<OpaqueNode, NodeId>,
111 id_to_opaque_node: FxHashMap<NodeId, OpaqueNode>,
116 tree_id: accesskit::TreeId,
120 root_node: Option<ArcRefCell<AccessibilityNode>>,
124 embedder_epoch: Epoch,
127 debug: DiagnosticsLogging,
130}
131
132#[derive(Debug, PartialEq, Copy, Clone)]
137enum TreeChange {
138 New,
140
141 Moved,
143
144 PendingMove,
160
161 Removed,
163}
164
165impl AccessibilityTree {
166 pub(super) fn new(tree_id: accesskit::TreeId, embedder_epoch: Epoch) -> Self {
168 Self {
169 nodes: FxHashMap::default(),
170 opaque_node_to_id: FxHashMap::default(),
171 id_to_opaque_node: FxHashMap::default(),
172 tree_id,
173 root_node: None,
174 embedder_epoch,
175 debug: opts::get().debug.clone(),
176 }
177 }
178
179 pub(super) fn update_tree<'dom>(
182 &mut self,
183 root_dom_node: &ServoLayoutNode<'dom>,
184 mut damage_from_dom: VecDeque<(ServoLayoutNode<'dom>, AccessibilityDamage)>,
185 rooted_nodes: Option<FxHashSet<OpaqueNode>>,
186 ) -> (Option<accesskit::TreeUpdate>, UpdateCounters) {
187 let mut update = AccessibilityUpdate::new(rooted_nodes);
188
189 self.ensure_root_node(root_dom_node, &mut damage_from_dom, &mut update);
190
191 self.apply_changes_from_dom_tree(damage_from_dom, &mut update);
192
193 update.finalize(self)
194 }
195
196 fn ensure_root_node<'dom>(
200 &mut self,
201 root_dom_node: &ServoLayoutNode<'dom>,
202 damage_from_dom: &mut VecDeque<(ServoLayoutNode<'dom>, AccessibilityDamage)>,
203 update: &mut AccessibilityUpdate,
204 ) {
205 let (root_id, root_node) = self.get_or_create_node(root_dom_node, update);
206 if update.is_new(&root_id) {
207 damage_from_dom.push_front((*root_dom_node, AccessibilityDamage::Rebuild));
208 }
209 self.root_node = Some(root_node);
210 }
211
212 fn apply_changes_from_dom_tree<'dom>(
216 &mut self,
217 damage_from_dom: VecDeque<(ServoLayoutNode<'dom>, AccessibilityDamage)>,
218 update: &mut AccessibilityUpdate,
219 ) {
220 let mut dom_damage_map = FxHashMap::from(
221 damage_from_dom
222 .into_iter()
223 .filter_map(|(dom_node, dom_node_damage)| {
224 let id = self.existing_id_for_opaque(dom_node.opaque())?;
225 Some((id, (dom_node, dom_node_damage)))
226 })
227 .collect(),
228 );
229 let damage_root = self.mark_nodes_and_ancestors_dirty(dom_damage_map.keys().cloned());
230 let Some(damage_root) = damage_root else {
231 return;
232 };
233 let local_damage = damage_root.borrow_mut().update_subtree(
234 damage_root.clone(),
235 &mut dom_damage_map,
236 self,
237 update,
238 );
239
240 damage_root.borrow().update_ancestors(local_damage, update);
241 }
242
243 fn mark_nodes_and_ancestors_dirty(
249 &mut self,
250 mut dirty_node_ids: impl Iterator<Item = NodeId>,
251 ) -> Option<ArcRefCell<AccessibilityNode>> {
252 let mut common_ancestors: Vec<NodeId> = Vec::new();
255
256 {
257 let first_node = self.assert_node_for_id(&dirty_node_ids.next()?);
259 let mut first_node = first_node.borrow_mut();
260 first_node.dirty_state |= DirtyState::HasDamage;
261 common_ancestors.push(first_node.id);
262 common_ancestors.extend(first_node.ancestors().map(|ancestor| {
263 let mut ancestor = ancestor.borrow_mut();
264 ancestor.dirty_state |= DirtyState::DescendantHasDamage;
265 ancestor.id
266 }));
267 common_ancestors.reverse();
268 }
269
270 let mut truncate_ancestors = |node: &AccessibilityNode| -> bool {
271 if node.dirty_state.descendant_has_damage() {
272 if let Some(pos) = common_ancestors.iter().position(|&id| id == node.id) {
273 common_ancestors.truncate(pos + 1);
274 }
275 return true;
276 }
277 false
278 };
279
280 for node_id in dirty_node_ids {
281 let node = self.assert_node_for_id(&node_id);
282 let mut node = node.borrow_mut();
283 node.dirty_state |= DirtyState::HasDamage;
284
285 if truncate_ancestors(&node) {
286 continue;
287 }
288
289 for ancestor in node.ancestors() {
290 let mut ancestor = ancestor.borrow_mut();
291
292 if truncate_ancestors(&ancestor) {
295 break;
296 }
297
298 ancestor.dirty_state |= DirtyState::DescendantHasDamage;
299 }
300 }
301
302 self.nodes.get(common_ancestors.last()?).cloned()
303 }
304
305 fn get_or_create_node(
306 &mut self,
307 dom_node: &ServoLayoutNode<'_>,
308 update: &mut AccessibilityUpdate,
309 ) -> (NodeId, ArcRefCell<AccessibilityNode>) {
310 let id = self.get_or_create_id_for_opaque(dom_node.opaque());
311 let node_ref = self.get_or_create_node_with_id(id, update);
312
313 if update.is_new(&id) {
314 let mut node = node_ref.borrow_mut();
315 node.opaque_node = Some(dom_node.opaque());
316 if let Some(dom_element) = dom_node.as_element() {
317 let local_name = dom_element.local_name().to_ascii_lowercase();
318 node.set_html_tag(&local_name);
319 }
320 }
321
322 (id, node_ref)
323 }
324
325 fn get_or_create_node_with_id(
326 &mut self,
327 id: NodeId,
328 update: &mut AccessibilityUpdate,
329 ) -> ArcRefCell<AccessibilityNode> {
330 if let Some(node) = self.nodes.get(&id) {
331 return node.clone();
332 }
333
334 let node = ArcRefCell::new(AccessibilityNode::new(id));
335 update.set_tree_state_change(id, TreeChange::New);
336 self.nodes.insert(id, node.clone());
337
338 node
339 }
340
341 fn node_for_id(&self, id: NodeId) -> Option<ArcRefCell<AccessibilityNode>> {
342 self.nodes.get(&id).cloned()
343 }
344
345 fn assert_node_for_id(&self, id: &NodeId) -> ArcRefCell<AccessibilityNode> {
346 let Some(node) = self.nodes.get(id) else {
347 panic!("{id:?} does not exist in tree");
348 };
349 node.clone()
350 }
351
352 fn drop_removed_nodes(&mut self, mut update: AccessibilityUpdate) {
355 let mut rooted_nodes = std::mem::take(&mut update.rooted_nodes);
356 if let Some(rooted_nodes) = rooted_nodes.as_mut() {
357 self.assert_removed_nodes_were_rooted(&update, rooted_nodes);
358 }
359
360 let mut ids_to_remove: Vec<_> = update
361 .tree_changes
362 .iter()
363 .filter_map(|(id, change)| match change {
364 TreeChange::Removed => Some(id),
365 TreeChange::PendingMove => None,
366 TreeChange::New => None,
367 TreeChange::Moved => None,
368 })
369 .cloned()
370 .collect();
371
372 while let Some(id) = ids_to_remove.pop() {
373 if update.tree_changes.get(&id) == Some(&TreeChange::PendingMove) {
374 update.set_tree_state_change(id, TreeChange::Removed);
376
377 continue;
379 }
380
381 if let Some(opaque_node) = self.id_to_opaque_node.remove(&id) {
382 self.opaque_node_to_id.remove(&opaque_node);
383 }
384 let node = self.nodes.remove(&id).expect("Node {id:?} already removed");
385 ids_to_remove.extend(node.borrow().child_ids());
386 }
387
388 update
389 .tree_changes
390 .drain()
391 .for_each(|(id, change)| match change {
392 TreeChange::PendingMove => unreachable!(
393 "Pending move found for node id {id:?} when draining tree state changes"
394 ),
395 TreeChange::Removed => (),
396 TreeChange::New => (),
397 TreeChange::Moved => (),
398 });
399
400 if let Some(rooted_nodes) = rooted_nodes {
401 self.assert_remaining_rooted_nodes_not_in_tree(rooted_nodes);
402 }
403
404 if self
405 .debug
406 .is_enabled(DiagnosticsLoggingOption::AccessibilityTree)
407 {
408 self.print();
409 }
410
411 if pref!(expensive_accessibility_test_assertions_enabled) {
412 self.assert_integrity();
413 }
414 }
415
416 fn assert_removed_nodes_were_rooted(
419 &mut self,
420 update: &AccessibilityUpdate,
421 rooted_nodes: &mut FxHashSet<OpaqueNode>,
422 ) {
423 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
424 for (id, change) in update.tree_changes.iter() {
425 if change == &TreeChange::Removed {
426 let Some(&opaque_node) = self.id_to_opaque_node.get(id) else {
427 panic!("No opaque node found for removed node: id {id:?}");
428 };
429 assert!(
430 rooted_nodes.remove(&opaque_node),
431 "Node removed from accessibility tree wasn't rooted: id {id:?}"
432 );
433 };
434 }
435 }
436
437 fn assert_remaining_rooted_nodes_not_in_tree(&self, rooted_nodes: FxHashSet<OpaqueNode>) {
443 for leftover_node in rooted_nodes {
444 assert!(
445 !self.opaque_node_to_id.contains_key(&leftover_node),
446 "Found node removed from DOM tree but not accessibility tree: {:#x}",
447 leftover_node.0
448 );
449 }
450 }
451
452 fn get_or_create_id_for_opaque(&mut self, opaque: OpaqueNode) -> NodeId {
453 let id = self.opaque_node_to_id.entry(opaque).or_insert_with(|| {
454 static LAST_ID: AtomicU64 = AtomicU64::new(0);
455 let id = LAST_ID.fetch_add(1, atomic::Ordering::SeqCst).into();
456 self.id_to_opaque_node.insert(id, opaque);
457 id
458 });
459 *id
460 }
461
462 fn existing_id_for_opaque(&self, opaque: OpaqueNode) -> Option<NodeId> {
463 self.opaque_node_to_id.get(&opaque).cloned()
464 }
465
466 pub(crate) fn embedder_epoch(&self) -> Epoch {
467 self.embedder_epoch
468 }
469
470 fn assert_integrity(&self) {
474 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
475 let Some(root_node) = self.root_node.clone() else {
476 return;
477 };
478
479 let mut nodes = vec![(root_node, None)];
482 let mut seen_node_ids = FxHashSet::default();
483 while let Some((node, expected_parent)) = nodes.pop() {
484 let node = node.borrow();
485
486 assert!(
488 seen_node_ids.insert(node.id),
489 "Tree contains {:?} in multiple places",
490 node.id
491 );
492
493 node.assert_integrity(expected_parent);
494
495 let weak_node = Some(self.assert_node_for_id(&node.id).downgrade());
498 nodes.extend(node.children().cloned().zip(repeat(weak_node)));
499 }
500
501 assert_eq!(seen_node_ids, self.nodes.keys().copied().collect());
504 }
505
506 fn print(&self) {
507 let Some(root_node) = self.root_node.clone() else {
508 return;
509 };
510
511 let mut print_tree = PrintTree::new("Accessibility Tree");
512 root_node.borrow().print(&mut print_tree);
513 print_tree.end_level();
514 }
515}
516
517fn role_from_role_attribute(dom_element: &ServoLayoutElement<'_>) -> Option<Role> {
519 let role_attribute = dom_element.attribute(&ns!(), &local_name!("role"))?;
520 role_attribute
521 .as_tokens()
522 .iter()
523 .filter_map(|role_name_in_attribute| SUPPORTED_ARIA_ROLES.get(role_name_in_attribute))
524 .next()
525 .cloned()
526}
527
528fn role_from_dom_node(dom_node: &ServoLayoutNode<'_>) -> Role {
529 if let Some(dom_element) = dom_node.as_element() {
530 role_from_role_attribute(&dom_element).unwrap_or_else(|| {
531 let local_name = dom_element.local_name().to_ascii_lowercase();
532 *HTML_ELEMENT_ROLE_MAPPINGS
533 .get(&local_name)
534 .unwrap_or(&Role::GenericContainer)
535 })
536 } else if dom_node.type_id() == Some(LayoutNodeType::Text) {
537 Role::TextRun
538 } else {
539 Role::GenericContainer
540 }
541}
542
543struct AccessibilityNodeIterator<I>
544where
545 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
546{
547 next_value: Option<ArcRefCell<AccessibilityNode>>,
548 next_fn: I,
549}
550
551impl<I> AccessibilityNodeIterator<I>
552where
553 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
554{
555 fn new(next_value: Option<ArcRefCell<AccessibilityNode>>, next_fn: I) -> Self {
556 AccessibilityNodeIterator {
557 next_value,
558 next_fn,
559 }
560 }
561}
562
563impl<I> Iterator for AccessibilityNodeIterator<I>
564where
565 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
566{
567 type Item = ArcRefCell<AccessibilityNode>;
568
569 fn next(&mut self) -> Option<Self::Item> {
570 let next_value = self.next_value.take();
571 self.next_value = next_value
572 .as_ref()
573 .and_then(|node| (self.next_fn)(&node.borrow()));
574 next_value
575 }
576}
577
578impl AccessibilityNode {
579 fn new(id: NodeId) -> Self {
580 Self::new_with_role(id, Role::Unknown)
581 }
582
583 fn new_with_role(id: NodeId, role: Role) -> Self {
584 Self {
585 id,
586 accesskit_node: accesskit::Node::new(role),
587 parent_node: None,
588 child_nodes: vec![],
589 opaque_node: None,
590 dirty_state: DirtyState::empty(),
591 }
592 }
593
594 fn update_subtree<'dom>(
606 &mut self,
607 ref_self: ArcRefCell<Self>,
608 dom_damage_map: &mut FxHashMap<NodeId, (ServoLayoutNode<'dom>, AccessibilityDamage)>,
609 tree: &mut AccessibilityTree,
610 update: &mut AccessibilityUpdate,
611 ) -> LocalAccessibilityDamage {
612 let mut local_damage = LocalAccessibilityDamage::empty();
613
614 if let Some((dom_node, dom_damage)) = dom_damage_map.get(&self.id) {
615 local_damage.insert(self.update_node_and_populate_new_descendants_from_dom_node(
616 ref_self,
617 dom_node,
618 *dom_damage,
619 tree,
620 update,
621 ));
622
623 self.dirty_state -= DirtyState::HasDamage;
624 }
625
626 if self.dirty_state.descendant_has_damage() {
627 for child_node in self.children() {
628 let strong_child_node = child_node.clone();
629 let mut child_node = child_node.borrow_mut();
630 if !child_node.dirty_state.self_or_descendant_has_damage() {
631 continue;
632 }
633 let child_damage =
634 child_node.update_subtree(strong_child_node, dom_damage_map, tree, update);
635 if !child_damage.is_empty() {
636 local_damage.insert(LocalAccessibilityDamage::SubtreeChanged);
637 }
638 }
639
640 self.dirty_state -= DirtyState::DescendantHasDamage;
641 }
642
643 local_damage.insert(self.update_node_local(local_damage, update));
644
645 if self.dirty_state.updated() {
646 update.add(self);
647 }
648
649 local_damage
650 }
651
652 fn update_ancestors(
655 &self,
656 local_damage: LocalAccessibilityDamage,
657 update: &mut AccessibilityUpdate,
658 ) {
659 if local_damage.is_empty() {
660 return;
661 }
662 for node in self.ancestors() {
663 let mut node = node.borrow_mut();
664 node.update_node_local(LocalAccessibilityDamage::SubtreeChanged, update);
665 node.dirty_state -= DirtyState::DescendantHasDamage;
666 if node.dirty_state.updated() {
667 update.add(&mut node);
668 }
669 }
670 }
671
672 fn update_node_and_populate_new_descendants_from_dom_node<'dom>(
677 &mut self,
678 ref_self: ArcRefCell<Self>,
679 dom_node: &ServoLayoutNode<'dom>,
680 dom_damage: AccessibilityDamage,
681 tree: &mut AccessibilityTree,
682 update: &mut AccessibilityUpdate,
683 ) -> LocalAccessibilityDamage {
684 update.counters.nodes_updated_from_dom += 1;
685
686 let mut local_damage = LocalAccessibilityDamage::empty();
687
688 local_damage.insert(self.update_properties_from_dom_node(dom_node, dom_damage));
689 local_damage.insert(
690 self.update_children_and_populate_new_descendants_from_dom_node(
691 ref_self, dom_node, dom_damage, tree, update,
692 ),
693 );
694
695 local_damage
696 }
697
698 fn update_children_and_populate_new_descendants_from_dom_node<'dom>(
701 &mut self,
702 ref_self: ArcRefCell<AccessibilityNode>,
703 dom_node: &ServoLayoutNode<'dom>,
704 dom_damage: AccessibilityDamage,
705 tree: &mut AccessibilityTree,
706 update: &mut AccessibilityUpdate,
707 ) -> LocalAccessibilityDamage {
708 if !dom_damage.contains(AccessibilityDamage::Children) {
709 return LocalAccessibilityDamage::empty();
710 }
711
712 let mut remaining_dom_children = dom_node.flat_tree_children().peekable();
713 let mut old_child_ids = self.child_ids().iter().peekable();
714 let mut unchanged_count = 0usize;
715
716 while let Some(&old_id) = old_child_ids.peek() &&
719 let Some(dom_child) = remaining_dom_children.peek()
720 {
721 if tree.existing_id_for_opaque(dom_child.opaque()) == Some(*old_id) {
722 unchanged_count += 1;
723 old_child_ids.next();
724 remaining_dom_children.next();
725 } else {
726 break;
727 }
728 }
729
730 if old_child_ids.peek().is_none() && remaining_dom_children.peek().is_none() {
732 return LocalAccessibilityDamage::empty();
733 }
734
735 self.child_nodes.truncate(unchanged_count);
737 let mut new_child_ids = Vec::from(self.child_ids());
738 for removed_child_id in new_child_ids.split_off(unchanged_count) {
739 update.set_tree_state_change(removed_child_id, TreeChange::Removed);
740 }
741
742 let weak_self = ref_self.downgrade();
746 for dom_child in remaining_dom_children {
747 let (child_id, child_ref) = tree.get_or_create_node(&dom_child, update);
748
749 self.child_nodes.push(child_ref.clone());
751 new_child_ids.push(child_id);
752
753 let mut child = child_ref.borrow_mut();
754 child.parent_node = Some(weak_self.clone());
755
756 if update.is_new(&child_id) {
757 let child_damage = child.update_node_and_populate_new_descendants_from_dom_node(
758 child_ref.clone(),
759 &dom_child,
760 AccessibilityDamage::Rebuild,
761 tree,
762 update,
763 );
764 child.update_node_local(child_damage, update);
765 update.add(&mut child);
766 } else {
767 update.set_tree_state_change(child_id, TreeChange::PendingMove);
768 }
769
770 self.dirty_state
771 .propagate_descendant_has_damage(child.dirty_state);
772 }
773
774 self.accesskit_node.set_children(new_child_ids);
777 self.dirty_state |= DirtyState::Updated;
778
779 LocalAccessibilityDamage::SubtreeChanged
780 }
781
782 fn update_properties_from_dom_node(
784 &mut self,
785 dom_node: &ServoLayoutNode<'_>,
786 dom_damage: AccessibilityDamage,
787 ) -> LocalAccessibilityDamage {
788 let mut local_damage = LocalAccessibilityDamage::empty();
789 if !dom_damage.contains(AccessibilityDamage::Node) {
790 return local_damage;
791 }
792 local_damage.insert(self.set_role(role_from_dom_node(dom_node)));
793 if dom_node.type_id() == Some(LayoutNodeType::Text) {
794 let text_content = dom_node.text_content();
795 trace!("node text content = {text_content:?}");
796 local_damage.insert(self.set_value(&text_content));
798 }
799
800 local_damage
801 }
802
803 fn update_node_local(
808 &mut self,
809 local_damage: LocalAccessibilityDamage,
810 update: &mut AccessibilityUpdate,
811 ) -> LocalAccessibilityDamage {
812 update.counters.nodes_updated_from_tree += 1;
813
814 let mut new_damage = LocalAccessibilityDamage::empty();
815 if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) ||
816 local_damage.contains(LocalAccessibilityDamage::RoleChanged)
817 {
818 if let Some(text) = self.label_from_descendants() {
819 new_damage.insert(self.set_label(text.as_str()));
820 } else {
821 new_damage.insert(self.clear_label());
822 }
823 }
824
825 new_damage
826 }
827
828 fn label_from_descendants(&self) -> Option<String> {
829 if !NAME_FROM_CONTENTS_ROLES.contains(&self.role()) {
830 return None;
831 }
832 let mut children = VecDeque::from_iter(self.children().cloned());
833 let mut text = String::new();
834 while let Some(child) = children.pop_front() {
835 let child = child.borrow();
836 match child.role() {
837 Role::TextRun => {
838 if let Some(child_text) = child.value() {
839 text.push_str(child_text);
840 }
841 },
842 _ => {
843 for node in child.children().rev() {
844 children.push_front(node.clone());
845 }
846 },
847 }
848 }
849 Some(text.trim().to_owned())
850 }
851
852 fn print(&self, print_tree: &mut PrintTree) {
853 if self.child_nodes.is_empty() {
854 print_tree.add_item(format!("{self:?}"));
855 return;
856 }
857
858 print_tree.new_level(format!("{self:?}"));
859
860 for child in self.children() {
861 child.borrow().print(print_tree);
862 }
863 print_tree.end_level();
864 }
865
866 fn parent(&self) -> Option<ArcRefCell<AccessibilityNode>> {
867 self.parent_node.as_ref().and_then(|weak| weak.upgrade())
868 }
869
870 fn children(&self) -> impl DoubleEndedIterator<Item = &ArcRefCell<AccessibilityNode>> {
873 self.child_nodes.iter()
874 }
875
876 fn ancestors(&self) -> impl Iterator<Item = ArcRefCell<AccessibilityNode>> {
877 AccessibilityNodeIterator::new(self.parent(), |node| node.parent_node.clone()?.upgrade())
878 }
879
880 fn child_ids(&self) -> &[NodeId] {
881 self.accesskit_node.children()
882 }
883
884 fn role(&self) -> Role {
885 self.accesskit_node.role()
886 }
887
888 fn set_role(&mut self, role: Role) -> LocalAccessibilityDamage {
889 if role == self.accesskit_node.role() {
890 return LocalAccessibilityDamage::empty();
891 }
892 self.accesskit_node.set_role(role);
893 self.dirty_state |= DirtyState::Updated;
894 LocalAccessibilityDamage::RoleChanged
895 }
896
897 fn label(&self) -> Option<&str> {
898 self.accesskit_node.label()
899 }
900
901 fn set_label(&mut self, label: &str) -> LocalAccessibilityDamage {
902 if Some(label) == self.accesskit_node.label() {
903 return LocalAccessibilityDamage::empty();
904 }
905 self.accesskit_node.set_label(label);
906 self.dirty_state |= DirtyState::Updated;
907 LocalAccessibilityDamage::TextChanged
908 }
909
910 fn clear_label(&mut self) -> LocalAccessibilityDamage {
911 if self.accesskit_node.label().is_none() {
912 return LocalAccessibilityDamage::empty();
913 }
914 self.accesskit_node.clear_label();
915 self.dirty_state |= DirtyState::Updated;
916 LocalAccessibilityDamage::TextChanged
917 }
918
919 fn html_tag(&self) -> Option<&str> {
920 self.accesskit_node.html_tag()
921 }
922
923 fn set_html_tag(&mut self, html_tag: &str) {
924 if Some(html_tag) == self.accesskit_node.html_tag() {
925 return;
926 }
927 self.accesskit_node.set_html_tag(html_tag);
928 self.dirty_state |= DirtyState::Updated;
929 }
930
931 fn value(&self) -> Option<&str> {
932 self.accesskit_node.value()
933 }
934
935 fn set_value(&mut self, value: &str) -> LocalAccessibilityDamage {
936 if Some(value) == self.accesskit_node.value() {
937 return LocalAccessibilityDamage::empty();
938 }
939 self.accesskit_node.set_value(value);
940 self.dirty_state |= DirtyState::Updated;
941 LocalAccessibilityDamage::TextChanged
942 }
943
944 fn assert_integrity(&self, expected_parent: Option<WeakRefCell<AccessibilityNode>>) {
945 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
946
947 if let Some(actual_parent) = &self.parent_node {
948 let expected = expected_parent.expect("Actual parent but no expected parent");
949 let expected = expected.upgrade().expect("Expected parent was dropped");
950 let actual = actual_parent.upgrade().expect("Actual parent was dropped");
951 assert!(actual.ptr_eq(&expected));
952 } else {
953 assert!(
954 expected_parent.is_none(),
955 "Expected parent but no actual parent"
956 );
957 }
958
959 assert!(
960 self.dirty_state.is_empty(),
961 "{self:?} has dirty state {:?}",
962 self.dirty_state
963 );
964
965 let children_ids: Vec<_> = self.children().map(|child| child.borrow().id).collect();
966 assert_eq!(
967 children_ids,
968 self.child_ids(),
969 "children() IDs didn't match child_ids() for {self:?}"
970 );
971 }
972}
973
974impl Debug for AccessibilityNode {
975 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
976 write!(f, "{:?}: {:?}", self.id, self.role())?;
977 if let Some(html_tag) = self.html_tag() {
978 write!(f, " (html_tag: {html_tag:?})")?;
979 }
980 if let Some(label) = self.label() {
981 write!(f, "\nlabel: {label:?}")?;
982 }
983 if !self.child_ids().is_empty() {
984 write!(f, "\nchildren: {:?}", self.child_ids())?;
985 }
986 Ok(())
987 }
988}
989
990impl AccessibilityUpdate {
991 fn new(rooted_nodes: Option<FxHashSet<OpaqueNode>>) -> Self {
992 Self {
993 changed_nodes: FxHashSet::default(),
994 tree_changes: FxHashMap::default(),
995 counters: UpdateCounters::default(),
996 rooted_nodes,
997 }
998 }
999
1000 fn add(&mut self, node: &mut AccessibilityNode) {
1001 self.changed_nodes.insert(node.id);
1002 node.dirty_state -= DirtyState::Updated;
1003 }
1004
1005 fn set_tree_state_change(&mut self, node_id: NodeId, change: TreeChange) {
1006 let old_change = self.tree_changes.get(&node_id);
1007
1008 assert!(
1009 change != TreeChange::Moved,
1010 "Incoming change must never be Moved"
1011 );
1012
1013 let resolved_change = old_change
1014 .map(|old_change| match (old_change, change) {
1015 (TreeChange::PendingMove, TreeChange::Removed) => TreeChange::Moved,
1016 (TreeChange::Removed, TreeChange::PendingMove) => TreeChange::Moved,
1017 _ => {
1018 unreachable!("Logically impossible state change: {old_change:?} → {change:?}")
1019 },
1020 })
1021 .unwrap_or(change);
1022
1023 self.tree_changes.insert(node_id, resolved_change);
1024 }
1025
1026 fn is_new(&mut self, node_id: &NodeId) -> bool {
1027 self.tree_changes.get(node_id) == Some(&TreeChange::New)
1028 }
1029
1030 fn finalize(
1035 mut self,
1036 tree: &mut AccessibilityTree,
1037 ) -> (Option<accesskit::TreeUpdate>, UpdateCounters) {
1038 let root_node_id = tree
1039 .root_node
1040 .clone()
1041 .expect("AccessibilityUpdate::finalize() called but no root_node set in tree")
1042 .borrow()
1043 .id;
1044
1045 if self.changed_nodes.is_empty() {
1046 assert!(self.tree_changes.is_empty());
1047 return (None, self.counters);
1048 }
1049
1050 let changed_nodes = std::mem::take(&mut self.changed_nodes);
1051 let mut counters = std::mem::take(&mut self.counters);
1052
1053 tree.drop_removed_nodes(self);
1054
1055 let changed_nodes: Vec<_> = changed_nodes
1056 .into_iter()
1057 .filter_map(|id| Some((id, tree.node_for_id(id)?.borrow().accesskit_node.clone())))
1058 .collect();
1059
1060 counters.nodes_in_tree_update = changed_nodes.len().try_into().unwrap_or_default();
1061
1062 let accesskit_tree = accesskit::Tree::new(root_node_id);
1063 let tree_update = accesskit::TreeUpdate {
1064 nodes: changed_nodes,
1066 tree: Some(accesskit_tree),
1067 focus: NodeId(1),
1068 tree_id: tree.tree_id,
1069 };
1070
1071 (Some(tree_update), counters)
1072 }
1073}
1074
1075impl DirtyState {
1076 fn updated(&self) -> bool {
1077 self.contains(DirtyState::Updated)
1078 }
1079
1080 fn descendant_has_damage(&self) -> bool {
1081 self.contains(DirtyState::DescendantHasDamage)
1082 }
1083
1084 fn propagate_descendant_has_damage(&mut self, child_dirty_state: DirtyState) {
1085 if child_dirty_state.self_or_descendant_has_damage() {
1086 self.insert(DirtyState::DescendantHasDamage)
1087 }
1088 }
1089
1090 fn self_or_descendant_has_damage(&self) -> bool {
1091 self.intersects(DirtyState::HasDamage | DirtyState::DescendantHasDamage)
1092 }
1093}
1094
1095#[cfg(test)]
1096#[test]
1097fn test_accessibility_update_add_some_nodes_twice() {
1098 let mut tree = AccessibilityTree::new(accesskit::TreeId::ROOT, Epoch::default());
1099 let mut root_update = AccessibilityUpdate::new(None);
1100
1101 let root_node = tree.get_or_create_node_with_id(NodeId(2), &mut root_update);
1102 tree.root_node = Some(root_node.clone());
1103
1104 let nodes: Vec<_> = [
1105 (3, Role::GenericContainer),
1106 (4, Role::Heading),
1107 (5, Role::Paragraph),
1108 ]
1109 .into_iter()
1110 .map(|(id, role)| {
1111 let id = NodeId(id);
1112 let node = tree.get_or_create_node_with_id(id, &mut root_update);
1113 node.borrow_mut().set_role(role);
1114 (id, node)
1115 })
1116 .collect();
1117
1118 {
1119 let (child_node_ids, child_nodes): (Vec<_>, Vec<_>) = nodes.iter().cloned().unzip();
1120 let mut root_node = root_node.borrow_mut();
1121 root_node.accesskit_node.set_children(child_node_ids);
1122 root_node.child_nodes = child_nodes;
1123 }
1124
1125 let mut update = AccessibilityUpdate::new(None);
1126
1127 {
1128 let node_3 = tree.assert_node_for_id(&NodeId(3));
1129 let mut node_3 = node_3.borrow_mut();
1130 let node_4 = tree.assert_node_for_id(&NodeId(4));
1131 let mut node_4 = node_4.borrow_mut();
1132 let node_5 = tree.assert_node_for_id(&NodeId(5));
1133 let mut node_5 = node_5.borrow_mut();
1134
1135 update.add(&mut node_5);
1136 update.add(&mut node_3);
1137 update.add(&mut node_4);
1138 update.add(&mut node_4);
1139
1140 node_3.set_role(Role::ScrollView);
1141 update.add(&mut node_3);
1142 }
1143
1144 let (tree_update, _) = update.finalize(&mut tree);
1145 let mut tree_update = tree_update.expect("finalize should produce a tree update");
1146 tree_update.nodes.sort_by_key(|(node_id, _node)| *node_id);
1147 assert_eq!(
1148 tree_update,
1149 accesskit::TreeUpdate {
1150 nodes: vec![
1151 (NodeId(3), accesskit::Node::new(Role::ScrollView)),
1152 (NodeId(4), accesskit::Node::new(Role::Heading)),
1153 (NodeId(5), accesskit::Node::new(Role::Paragraph)),
1154 ],
1155 tree: Some(accesskit::Tree {
1156 root: NodeId(2),
1157 toolkit_name: None,
1158 toolkit_version: None
1159 }),
1160 tree_id: accesskit::TreeId::ROOT,
1161 focus: NodeId(1),
1162 }
1163 );
1164}
1165
1166static HTML_ELEMENT_ROLE_MAPPINGS: LazyLock<FxHashMap<LocalName, Role>> = LazyLock::new(|| {
1167 [
1168 (local_name!("article"), Role::Article),
1169 (local_name!("aside"), Role::Complementary),
1170 (local_name!("body"), Role::RootWebArea),
1171 (local_name!("footer"), Role::ContentInfo),
1172 (local_name!("h1"), Role::Heading),
1173 (local_name!("h2"), Role::Heading),
1174 (local_name!("h3"), Role::Heading),
1175 (local_name!("h4"), Role::Heading),
1176 (local_name!("h5"), Role::Heading),
1177 (local_name!("h6"), Role::Heading),
1178 (local_name!("header"), Role::Banner),
1179 (local_name!("hr"), Role::Splitter),
1180 (local_name!("main"), Role::Main),
1181 (local_name!("nav"), Role::Navigation),
1182 (local_name!("p"), Role::Paragraph),
1183 ]
1184 .into_iter()
1185 .collect()
1186});
1187
1188static SUPPORTED_ARIA_ROLES: LazyLock<FxHashMap<Atom, Role>> = LazyLock::new(|| {
1197 [
1198 (Atom::from("alert"), Role::Alert),
1199 (Atom::from("banner"), Role::Banner),
1200 (Atom::from("blockquote"), Role::Blockquote),
1201 (Atom::from("caption"), Role::Caption),
1202 (Atom::from("code"), Role::Code),
1203 (Atom::from("complementary"), Role::Complementary),
1204 (Atom::from("contentinfo"), Role::ContentInfo),
1205 (Atom::from("definition"), Role::Definition),
1206 (Atom::from("deletion"), Role::ContentDeletion),
1207 (Atom::from("directory"), Role::Unknown),
1208 (Atom::from("document"), Role::Document),
1209 (Atom::from("emphasis"), Role::Emphasis),
1210 (Atom::from("feed"), Role::Feed),
1211 (Atom::from("figure"), Role::Figure),
1212 (Atom::from("generic"), Role::GenericContainer),
1213 (Atom::from("insertion"), Role::ContentInsertion),
1214 (Atom::from("list"), Role::List),
1215 (Atom::from("log"), Role::Log),
1216 (Atom::from("main"), Role::Main),
1217 (Atom::from("math"), Role::Math),
1218 (Atom::from("navigation"), Role::Navigation),
1219 (Atom::from("none"), Role::GenericContainer),
1220 (Atom::from("note"), Role::Note),
1221 (Atom::from("paragraph"), Role::Paragraph),
1222 (Atom::from("presentation"), Role::GenericContainer),
1223 (Atom::from("rowgroup"), Role::RowGroup),
1224 (Atom::from("search"), Role::Search),
1225 (Atom::from("status"), Role::Status),
1226 (Atom::from("strong"), Role::Strong),
1227 (Atom::from("term"), Role::Term),
1230 (Atom::from("time"), Role::Time),
1231 (Atom::from("timer"), Role::Timer),
1232 ]
1233 .into_iter()
1234 .collect()
1235});
1236
1237static NAME_FROM_CONTENTS_ROLES: LazyLock<FxHashSet<Role>> =
1239 LazyLock::new(|| [(Role::Heading)].into_iter().collect());