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 unresolved_local_damage: FxHashMap<NodeId, LocalAccessibilityDamage>,
49 rooted_nodes: Option<FxHashSet<OpaqueNode>>,
53}
54
55struct AccessibilityNode {
56 id: NodeId,
60 accesskit_node: accesskit::Node,
63 parent_node: Option<WeakRefCell<AccessibilityNode>>,
65 child_nodes: Vec<ArcRefCell<AccessibilityNode>>,
67 opaque_node: Option<OpaqueNode>,
71 updated: bool,
74}
75
76#[derive(Debug)]
81pub struct AccessibilityTree {
82 nodes: FxHashMap<NodeId, ArcRefCell<AccessibilityNode>>,
85 opaque_node_to_id: FxHashMap<OpaqueNode, NodeId>,
90 id_to_opaque_node: FxHashMap<NodeId, OpaqueNode>,
95 tree_id: accesskit::TreeId,
99 root_node: Option<ArcRefCell<AccessibilityNode>>,
103 embedder_epoch: Epoch,
106 debug: DiagnosticsLogging,
109}
110
111#[derive(Debug, PartialEq, Copy, Clone)]
116enum TreeChange {
117 New,
119
120 Moved,
122
123 PendingMove,
139
140 Removed,
142}
143
144impl AccessibilityTree {
145 pub(super) fn new(tree_id: accesskit::TreeId, embedder_epoch: Epoch) -> Self {
147 Self {
148 nodes: FxHashMap::default(),
149 opaque_node_to_id: FxHashMap::default(),
150 id_to_opaque_node: FxHashMap::default(),
151 tree_id,
152 root_node: None,
153 embedder_epoch,
154 debug: opts::get().debug.clone(),
155 }
156 }
157
158 pub(super) fn update_tree<'dom>(
161 &mut self,
162 root_dom_node: &ServoLayoutNode<'dom>,
163 mut damage_from_dom: VecDeque<(ServoLayoutNode<'dom>, AccessibilityDamage)>,
164 rooted_nodes: Option<FxHashSet<OpaqueNode>>,
165 ) -> Option<accesskit::TreeUpdate> {
166 let mut update = AccessibilityUpdate::new(rooted_nodes);
167
168 self.ensure_root_node(root_dom_node, &mut damage_from_dom, &mut update);
169
170 self.apply_changes_from_dom_tree(damage_from_dom, &mut update);
171
172 self.resolve_local_damage_for_node_and_subtree(self.assert_root_node(), &mut update);
175
176 update.finalize(self)
177 }
178
179 fn ensure_root_node<'dom>(
183 &mut self,
184 root_dom_node: &ServoLayoutNode<'dom>,
185 damage_from_dom: &mut VecDeque<(ServoLayoutNode<'dom>, AccessibilityDamage)>,
186 update: &mut AccessibilityUpdate,
187 ) {
188 let (root_id, root_node) = self.get_or_create_node(root_dom_node, update);
189 if update.is_new(&root_id) {
190 damage_from_dom.push_front((*root_dom_node, AccessibilityDamage::Rebuild));
191 }
192 self.root_node = Some(root_node);
193 }
194
195 fn assert_root_node(&self) -> ArcRefCell<AccessibilityNode> {
197 self.root_node.clone().expect("Root node was asserted")
198 }
199
200 fn apply_changes_from_dom_tree<'dom>(
204 &mut self,
205 mut damage_from_dom: VecDeque<(ServoLayoutNode<'dom>, AccessibilityDamage)>,
206 update: &mut AccessibilityUpdate,
207 ) {
208 while let Some((dom_node, dom_node_damage)) = damage_from_dom.pop_front() {
209 let Some((_, node)) = self.node_for_dom_node(&dom_node) else {
210 continue;
213 };
214 self.update_node_and_descendants_from_dom_node(
215 node.clone(),
216 &dom_node,
217 dom_node_damage,
218 update,
219 );
220 }
221
222 self.propagate_subtree_damage_to_ancestors(update);
223 }
224
225 fn propagate_subtree_damage_to_ancestors(&mut self, update: &mut AccessibilityUpdate) {
228 for (node_id, damage) in update.unresolved_local_damage.clone() {
229 if damage.is_empty() {
230 continue;
231 }
232 let node = self.assert_node_for_id(&node_id);
233
234 let mut parent_node = node.borrow().parent();
235 while let Some(node) = parent_node {
236 let node = node.borrow();
237 let existing_damage = update.unresolved_local_damage.entry(node.id).or_default();
238
239 if existing_damage.contains(LocalAccessibilityDamage::SubtreeChanged) {
242 break;
243 }
244
245 existing_damage.insert(LocalAccessibilityDamage::SubtreeChanged);
246 parent_node = node.parent();
247 }
248 }
249 }
250
251 fn update_node_and_descendants_from_dom_node(
256 &mut self,
257 node: ArcRefCell<AccessibilityNode>,
258 dom_node: &ServoLayoutNode<'_>,
259 dom_damage: AccessibilityDamage,
260 update: &mut AccessibilityUpdate,
261 ) -> LocalAccessibilityDamage {
262 let weak_node = node.downgrade();
263 let mut node = node.borrow_mut();
264 let mut local_damage = LocalAccessibilityDamage::empty();
265
266 local_damage.insert(node.update_node_from_dom_node(dom_node, dom_damage));
267 local_damage.insert(
268 node.update_descendants_from_dom_node(weak_node, dom_node, dom_damage, self, update),
269 );
270
271 if node.updated {
272 update.add(&mut node);
273 }
274
275 if !local_damage.is_empty() {
276 update
277 .unresolved_local_damage
278 .entry(node.id)
279 .or_default()
280 .insert(local_damage);
281 }
282
283 local_damage
284 }
285
286 fn resolve_local_damage_for_node_and_subtree(
293 &mut self,
294 node: ArcRefCell<AccessibilityNode>,
295 update: &mut AccessibilityUpdate,
296 ) {
297 let mut node = node.borrow_mut();
298 let Some(&local_damage) = update.unresolved_local_damage.get(&node.id) else {
299 return;
300 };
301
302 node.update_node_local(local_damage);
303
304 if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) {
305 for child in node.children() {
306 self.resolve_local_damage_for_node_and_subtree(child.clone(), update);
307 }
308 }
309
310 if node.updated {
311 update.add(&mut node);
312 }
313
314 update.unresolved_local_damage.remove(&node.id);
315 }
316
317 fn get_or_create_node(
318 &mut self,
319 dom_node: &ServoLayoutNode<'_>,
320 update: &mut AccessibilityUpdate,
321 ) -> (NodeId, ArcRefCell<AccessibilityNode>) {
322 let id = self.get_or_create_id_for_opaque(dom_node.opaque());
323 let node = self.get_or_create_node_with_id(id, update);
324
325 {
326 let mut node = node.borrow_mut();
327 node.opaque_node = Some(dom_node.opaque());
328 if let Some(dom_element) = dom_node.as_element() {
329 let local_name = dom_element.local_name().to_ascii_lowercase();
330 node.set_html_tag(&local_name);
331 }
332 }
333
334 (id, node)
335 }
336
337 fn get_or_create_node_with_id(
338 &mut self,
339 id: NodeId,
340 update: &mut AccessibilityUpdate,
341 ) -> ArcRefCell<AccessibilityNode> {
342 if let Some(node) = self.nodes.get(&id) {
343 return node.clone();
344 }
345
346 let node = ArcRefCell::new(AccessibilityNode::new(id));
347 update.set_tree_state_change(id, TreeChange::New);
348 self.nodes.insert(id, node.clone());
349
350 node
351 }
352
353 fn node_for_id(&self, id: NodeId) -> Option<ArcRefCell<AccessibilityNode>> {
354 self.nodes.get(&id).cloned()
355 }
356
357 fn node_for_dom_node(
358 &self,
359 dom_node: &ServoLayoutNode<'_>,
360 ) -> Option<(NodeId, ArcRefCell<AccessibilityNode>)> {
361 let id = self.existing_id_for_opaque(dom_node.opaque())?;
362 Some((id, self.node_for_id(id)?))
363 }
364
365 fn assert_node_for_id(&self, id: &NodeId) -> ArcRefCell<AccessibilityNode> {
366 let Some(node) = self.nodes.get(id) else {
367 panic!("{id:?} does not exist in tree");
368 };
369 node.clone()
370 }
371
372 fn remove_stale_nodes(&mut self, mut update: AccessibilityUpdate) {
375 if let Some(rooted_nodes) = std::mem::take(&mut update.rooted_nodes) {
376 self.assert_removed_nodes_were_rooted(&update, rooted_nodes);
377 }
378
379 for id in update
380 .tree_changes
381 .drain()
382 .filter_map(|(id, change)| match change {
383 TreeChange::PendingMove => {
384 unreachable!(
385 "Pending move found for node id {id:?} when draining tree state changes"
386 );
387 },
388 TreeChange::Removed => Some(id),
389 _ => None,
390 })
391 {
392 let _node = self.nodes.remove(&id);
393 {
394 #[cfg(debug_assertions)]
395 assert!(_node.is_some(), "Node for id {id:?} was already removed");
396 }
397 if let Some(opaque_node) = self.id_to_opaque_node.remove(&id) {
398 self.opaque_node_to_id.remove(&opaque_node);
399 }
400 }
401
402 if self
403 .debug
404 .is_enabled(DiagnosticsLoggingOption::AccessibilityTree)
405 {
406 self.print();
407 }
408
409 if pref!(expensive_accessibility_test_assertions_enabled) {
410 self.assert_integrity();
411 }
412 }
413
414 fn assert_removed_nodes_were_rooted(
418 &mut self,
419 update: &AccessibilityUpdate,
420 mut rooted_nodes: 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 for leftover_node in rooted_nodes {
436 assert!(
437 !self.opaque_node_to_id.contains_key(&leftover_node),
438 "Found node removed from DOM tree but not accessibility tree"
439 );
440 }
441 }
442
443 fn get_or_create_id_for_opaque(&mut self, opaque: OpaqueNode) -> NodeId {
444 let id = self.opaque_node_to_id.entry(opaque).or_insert_with(|| {
445 static LAST_ID: AtomicU64 = AtomicU64::new(0);
446 let id = LAST_ID.fetch_add(1, atomic::Ordering::SeqCst).into();
447 self.id_to_opaque_node.insert(id, opaque);
448 id
449 });
450 *id
451 }
452
453 fn existing_id_for_opaque(&self, opaque: OpaqueNode) -> Option<NodeId> {
454 self.opaque_node_to_id.get(&opaque).cloned()
455 }
456
457 pub(crate) fn embedder_epoch(&self) -> Epoch {
458 self.embedder_epoch
459 }
460
461 fn assert_integrity(&self) {
465 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
466 let Some(root_node) = self.root_node.clone() else {
467 return;
468 };
469
470 let mut nodes = vec![(root_node, None)];
473 let mut seen_node_ids = FxHashSet::default();
474 while let Some((node, expected_parent)) = nodes.pop() {
475 let node = node.borrow();
476
477 assert!(
479 seen_node_ids.insert(node.id),
480 "Tree contains {:?} in multiple places",
481 node.id
482 );
483
484 node.assert_integrity(expected_parent);
485
486 let weak_node = Some(self.assert_node_for_id(&node.id).downgrade());
489 nodes.extend(node.children().iter().cloned().zip(repeat(weak_node)));
490 }
491
492 assert_eq!(seen_node_ids, self.nodes.keys().copied().collect());
495 }
496
497 fn print(&self) {
498 let Some(root_node) = self.root_node.clone() else {
499 return;
500 };
501
502 let mut print_tree = PrintTree::new("Accessibility Tree");
503 root_node.borrow().print(&mut print_tree);
504 print_tree.end_level();
505 }
506}
507
508fn role_from_dom_node(dom_node: &ServoLayoutNode<'_>) -> Role {
509 if let Some(dom_element) = dom_node.as_element() {
510 let local_name = dom_element.local_name().to_ascii_lowercase();
511 *HTML_ELEMENT_ROLE_MAPPINGS
512 .get(&local_name)
513 .unwrap_or(&Role::GenericContainer)
514 } else if dom_node.type_id() == Some(LayoutNodeType::Text) {
515 Role::TextRun
516 } else {
517 Role::GenericContainer
518 }
519}
520
521impl AccessibilityNode {
522 fn new(id: NodeId) -> Self {
523 Self::new_with_role(id, Role::Unknown)
524 }
525
526 fn new_with_role(id: NodeId, role: Role) -> Self {
527 Self {
528 id,
529 accesskit_node: accesskit::Node::new(role),
530 parent_node: None,
531 child_nodes: vec![],
532 opaque_node: None,
533 updated: true,
534 }
535 }
536
537 fn update_descendants_from_dom_node<'dom>(
540 &mut self,
541 weak_self: WeakRefCell<Self>,
542 dom_node: &ServoLayoutNode<'dom>,
543 dom_damage: AccessibilityDamage,
544 tree: &mut AccessibilityTree,
545 update: &mut AccessibilityUpdate,
546 ) -> LocalAccessibilityDamage {
547 let mut local_damage = LocalAccessibilityDamage::empty();
548 if !dom_damage.contains(AccessibilityDamage::Children) {
549 return local_damage;
550 }
551
552 let dom_children: Vec<ServoLayoutNode> = dom_node.flat_tree_children().collect();
553
554 let mut damage_from_children = LocalAccessibilityDamage::empty();
555 let mut new_child_ids = vec![];
556 let mut new_child_nodes = vec![];
557 for dom_child in dom_children {
558 let (child_id, child_node) = tree.get_or_create_node(&dom_child, update);
559 if update.is_new(&child_id) {
560 let child_damage = tree.update_node_and_descendants_from_dom_node(
561 child_node.clone(),
562 &dom_child,
563 AccessibilityDamage::Rebuild,
564 update,
565 );
566 damage_from_children.insert(child_damage);
567 }
568 new_child_ids.push(child_id);
569 new_child_nodes.push(child_node);
570 }
571 if !damage_from_children.is_empty() {
572 local_damage.insert(LocalAccessibilityDamage::SubtreeChanged);
573 }
574
575 local_damage.insert(self.set_children(weak_self, new_child_ids, new_child_nodes, update));
576 local_damage
577 }
578
579 fn set_subtree_state_change(&self, change: TreeChange, update: &mut AccessibilityUpdate) {
591 assert!(
592 change != TreeChange::New,
593 "New shouldn't be set recursively"
594 );
595
596 update.set_tree_state_change(self.id, change);
597
598 for child in self.children().iter() {
599 child.borrow().set_subtree_state_change(change, update);
601 }
602 }
603
604 fn update_node_from_dom_node(
606 &mut self,
607 dom_node: &ServoLayoutNode<'_>,
608 dom_damage: AccessibilityDamage,
609 ) -> LocalAccessibilityDamage {
610 let mut local_damage = LocalAccessibilityDamage::empty();
611 if !dom_damage.contains(AccessibilityDamage::Text) {
612 return local_damage;
613 }
614 local_damage.insert(self.set_role(role_from_dom_node(dom_node)));
615 if dom_node.type_id() == Some(LayoutNodeType::Text) {
616 let text_content = dom_node.text_content();
617 trace!("node text content = {text_content:?}");
618 local_damage.insert(self.set_value(&text_content));
620 }
621
622 local_damage
623 }
624
625 fn update_node_local(
630 &mut self,
631 local_damage: LocalAccessibilityDamage,
632 ) -> LocalAccessibilityDamage {
633 let mut new_damage = LocalAccessibilityDamage::empty();
634 if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) ||
635 local_damage.contains(LocalAccessibilityDamage::RoleChanged)
636 {
637 if let Some(text) = self.label_from_descendants() {
638 new_damage.insert(self.set_label(text.as_str()));
639 } else {
640 new_damage.insert(self.clear_label());
641 }
642 }
643
644 new_damage
645 }
646
647 fn label_from_descendants(&self) -> Option<String> {
648 if !NAME_FROM_CONTENTS_ROLES.contains(&self.role()) {
649 return None;
650 }
651 let mut children = VecDeque::from_iter(self.children().iter().cloned());
652 let mut text = String::new();
653 while let Some(child) = children.pop_front() {
654 let child = child.borrow();
655 match child.role() {
656 Role::TextRun => {
657 if let Some(child_text) = child.value() {
658 text.push_str(child_text);
659 }
660 },
661 _ => {
662 for node in child.children().iter().rev() {
663 children.push_front(node.clone());
664 }
665 },
666 }
667 }
668 Some(text.trim().to_owned())
669 }
670
671 fn print(&self, print_tree: &mut PrintTree) {
672 if self.children().is_empty() {
673 print_tree.add_item(format!("{self:?}"));
674 return;
675 }
676
677 print_tree.new_level(format!("{self:?}"));
678
679 for child in self.children() {
680 child.borrow().print(print_tree);
681 }
682 print_tree.end_level();
683 }
684
685 fn parent(&self) -> Option<ArcRefCell<AccessibilityNode>> {
686 self.parent_node.as_ref().and_then(|weak| weak.upgrade())
687 }
688
689 fn children(&self) -> &Vec<ArcRefCell<AccessibilityNode>> {
692 &self.child_nodes
693 }
694
695 fn child_ids(&self) -> &[NodeId] {
696 self.accesskit_node.children()
697 }
698
699 fn set_children(
702 &mut self,
703 weak_self: WeakRefCell<Self>,
704 new_child_ids: Vec<NodeId>,
705 new_child_nodes: Vec<ArcRefCell<AccessibilityNode>>,
706 update: &mut AccessibilityUpdate,
707 ) -> LocalAccessibilityDamage {
708 if new_child_ids == self.child_ids() {
709 return LocalAccessibilityDamage::empty();
710 }
711
712 let old_child_ids = self.child_ids();
713
714 for (old_id, old_child) in old_child_ids.iter().zip(self.children().iter()) {
715 if !new_child_ids.contains(old_id) {
716 let mut removed_child = old_child.borrow_mut();
717 removed_child.set_subtree_state_change(TreeChange::Removed, update);
718 if let Some(parent_node) = removed_child.parent_node.clone() &&
719 parent_node.ptr_eq(&weak_self)
720 {
721 removed_child.parent_node = None;
722 }
723 }
724 }
725
726 for (new_id, new_child) in new_child_ids.iter().zip(new_child_nodes.iter()) {
727 if !old_child_ids.contains(new_id) {
728 let mut new_child = new_child.borrow_mut();
729 new_child.parent_node = Some(weak_self.clone());
730 if !update.is_new(new_id) {
731 new_child.set_subtree_state_change(TreeChange::PendingMove, update);
732 }
733 }
734 }
735
736 self.child_nodes = new_child_nodes;
737 self.accesskit_node.set_children(new_child_ids);
738 self.updated = true;
739
740 LocalAccessibilityDamage::SubtreeChanged
741 }
742
743 fn role(&self) -> Role {
744 self.accesskit_node.role()
745 }
746
747 fn set_role(&mut self, role: Role) -> LocalAccessibilityDamage {
748 if role == self.accesskit_node.role() {
749 return LocalAccessibilityDamage::empty();
750 }
751 self.accesskit_node.set_role(role);
752 self.updated = true;
753 LocalAccessibilityDamage::RoleChanged
754 }
755
756 fn label(&self) -> Option<&str> {
757 self.accesskit_node.label()
758 }
759
760 fn set_label(&mut self, label: &str) -> LocalAccessibilityDamage {
761 if Some(label) == self.accesskit_node.label() {
762 return LocalAccessibilityDamage::empty();
763 }
764 self.accesskit_node.set_label(label);
765 self.updated = true;
766 LocalAccessibilityDamage::TextChanged
767 }
768
769 fn clear_label(&mut self) -> LocalAccessibilityDamage {
770 if self.accesskit_node.label().is_none() {
771 return LocalAccessibilityDamage::empty();
772 }
773 self.accesskit_node.clear_label();
774 self.updated = true;
775 LocalAccessibilityDamage::TextChanged
776 }
777
778 fn html_tag(&self) -> Option<&str> {
779 self.accesskit_node.html_tag()
780 }
781
782 fn set_html_tag(&mut self, html_tag: &str) {
783 if Some(html_tag) == self.accesskit_node.html_tag() {
784 return;
785 }
786 self.accesskit_node.set_html_tag(html_tag);
787 self.updated = true;
788 }
789
790 fn value(&self) -> Option<&str> {
791 self.accesskit_node.value()
792 }
793
794 fn set_value(&mut self, value: &str) -> LocalAccessibilityDamage {
795 if Some(value) == self.accesskit_node.value() {
796 return LocalAccessibilityDamage::empty();
797 }
798 self.accesskit_node.set_value(value);
799 self.updated = true;
800 LocalAccessibilityDamage::TextChanged
801 }
802
803 fn assert_integrity(&self, expected_parent: Option<WeakRefCell<AccessibilityNode>>) {
804 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
805
806 if let Some(actual_parent) = &self.parent_node {
807 let expected = expected_parent.expect("Actual parent but no expected parent");
808 let expected = expected.upgrade().expect("Expected parent was dropped");
809 let actual = actual_parent.upgrade().expect("Actual parent was dropped");
810 assert!(actual.ptr_eq(&expected));
811 } else {
812 assert!(
813 expected_parent.is_none(),
814 "Expected parent but no actual parent"
815 );
816 }
817
818 let children_ids: Vec<_> = self
819 .children()
820 .iter()
821 .map(|child| child.borrow().id)
822 .collect();
823 assert_eq!(
824 children_ids,
825 self.child_ids(),
826 "children() IDs didn't match child_ids() for {self:?}"
827 );
828 }
829}
830
831impl Debug for AccessibilityNode {
832 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
833 write!(f, "{:?}: {:?}", self.id, self.role())?;
834 if let Some(html_tag) = self.html_tag() {
835 write!(f, " (html_tag: {html_tag:?})")?;
836 }
837 if let Some(label) = self.label() {
838 write!(f, "\nlabel: {label:?}")?;
839 }
840 if !self.child_ids().is_empty() {
841 write!(f, "\nchildren: {:?}", self.child_ids())?;
842 }
843 Ok(())
844 }
845}
846
847impl AccessibilityUpdate {
848 fn new(rooted_nodes: Option<FxHashSet<OpaqueNode>>) -> Self {
849 Self {
850 changed_nodes: FxHashSet::default(),
851 tree_changes: FxHashMap::default(),
852 unresolved_local_damage: FxHashMap::default(),
853 rooted_nodes,
854 }
855 }
856
857 fn add(&mut self, node: &mut AccessibilityNode) {
858 self.changed_nodes.insert(node.id);
859
860 node.updated = false;
861 }
862
863 fn set_tree_state_change(&mut self, node_id: NodeId, change: TreeChange) {
864 let old_change = self.tree_changes.get(&node_id);
865
866 assert!(
867 change != TreeChange::Moved,
868 "Incoming change must never be Moved"
869 );
870
871 let resolved_change = old_change
872 .map(|old_change| match (old_change, change) {
873 (TreeChange::PendingMove, TreeChange::Removed) => TreeChange::Moved,
874 (TreeChange::Removed, TreeChange::PendingMove) => TreeChange::Moved,
875 _ => {
876 unreachable!("Logically impossible state change: {old_change:?} → {change:?}")
877 },
878 })
879 .unwrap_or(change);
880
881 self.tree_changes.insert(node_id, resolved_change);
882 }
883
884 fn is_new(&mut self, node_id: &NodeId) -> bool {
885 self.tree_changes.get(node_id) == Some(&TreeChange::New)
886 }
887
888 fn finalize(mut self, tree: &mut AccessibilityTree) -> Option<accesskit::TreeUpdate> {
893 let root_node_id = tree
894 .root_node
895 .clone()
896 .expect("AccessibilityUpdate::finalize() called but no root_node set in tree")
897 .borrow()
898 .id;
899
900 debug_assert!(self.unresolved_local_damage.is_empty());
901
902 if self.changed_nodes.is_empty() {
903 assert!(self.tree_changes.is_empty());
904 return None;
905 }
906
907 let accesskit_tree = accesskit::Tree::new(root_node_id);
908 let tree_update = accesskit::TreeUpdate {
909 nodes: std::mem::take(&mut self.changed_nodes)
910 .into_iter()
911 .map(|id| {
912 (
913 id,
914 tree.assert_node_for_id(&id).borrow().accesskit_node.clone(),
915 )
916 })
917 .collect(),
918 tree: Some(accesskit_tree),
919 focus: NodeId(1),
920 tree_id: tree.tree_id,
921 };
922
923 tree.remove_stale_nodes(self);
924
925 Some(tree_update)
926 }
927}
928
929#[cfg(test)]
930#[test]
931fn test_accessibility_update_add_some_nodes_twice() {
932 let mut tree = AccessibilityTree::new(accesskit::TreeId::ROOT, Epoch::default());
933 let mut root_update = AccessibilityUpdate::new(None);
934
935 let root_node = tree.get_or_create_node_with_id(NodeId(2), &mut root_update);
936 tree.root_node = Some(root_node.clone());
937
938 let nodes: Vec<_> = [
939 (3, Role::GenericContainer),
940 (4, Role::Heading),
941 (5, Role::Paragraph),
942 ]
943 .into_iter()
944 .map(|(id, role)| {
945 let id = NodeId(id);
946 let node = tree.get_or_create_node_with_id(id, &mut root_update);
947 node.borrow_mut().set_role(role);
948 (id, node)
949 })
950 .collect();
951 let (child_node_ids, child_nodes) = nodes.iter().cloned().unzip();
952 root_node.borrow_mut().set_children(
953 root_node.downgrade(),
954 child_node_ids,
955 child_nodes,
956 &mut root_update,
957 );
958
959 let mut update = AccessibilityUpdate::new(None);
960
961 {
962 let node_3 = tree.assert_node_for_id(&NodeId(3));
963 let mut node_3 = node_3.borrow_mut();
964 let node_4 = tree.assert_node_for_id(&NodeId(4));
965 let mut node_4 = node_4.borrow_mut();
966 let node_5 = tree.assert_node_for_id(&NodeId(5));
967 let mut node_5 = node_5.borrow_mut();
968
969 update.add(&mut node_5);
970 update.add(&mut node_3);
971 update.add(&mut node_4);
972 update.add(&mut node_4);
973
974 node_3.set_role(Role::ScrollView);
975 update.add(&mut node_3);
976 }
977
978 let mut tree_update = update
979 .finalize(&mut tree)
980 .expect("finalize should produce a tree update");
981 tree_update.nodes.sort_by_key(|(node_id, _node)| *node_id);
982 assert_eq!(
983 tree_update,
984 accesskit::TreeUpdate {
985 nodes: vec![
986 (NodeId(3), accesskit::Node::new(Role::ScrollView)),
987 (NodeId(4), accesskit::Node::new(Role::Heading)),
988 (NodeId(5), accesskit::Node::new(Role::Paragraph)),
989 ],
990 tree: Some(accesskit::Tree {
991 root: NodeId(2),
992 toolkit_name: None,
993 toolkit_version: None
994 }),
995 tree_id: accesskit::TreeId::ROOT,
996 focus: NodeId(1),
997 }
998 );
999}
1000
1001static HTML_ELEMENT_ROLE_MAPPINGS: LazyLock<FxHashMap<LocalName, Role>> = LazyLock::new(|| {
1002 [
1003 (local_name!("article"), Role::Article),
1004 (local_name!("aside"), Role::Complementary),
1005 (local_name!("body"), Role::RootWebArea),
1006 (local_name!("footer"), Role::ContentInfo),
1007 (local_name!("h1"), Role::Heading),
1008 (local_name!("h2"), Role::Heading),
1009 (local_name!("h3"), Role::Heading),
1010 (local_name!("h4"), Role::Heading),
1011 (local_name!("h5"), Role::Heading),
1012 (local_name!("h6"), Role::Heading),
1013 (local_name!("header"), Role::Banner),
1014 (local_name!("hr"), Role::Splitter),
1015 (local_name!("main"), Role::Main),
1016 (local_name!("nav"), Role::Navigation),
1017 (local_name!("p"), Role::Paragraph),
1018 ]
1019 .into_iter()
1020 .collect()
1021});
1022
1023static NAME_FROM_CONTENTS_ROLES: LazyLock<FxHashSet<Role>> =
1025 LazyLock::new(|| [(Role::Heading)].into_iter().collect());