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