1use std::cell::RefCell;
5use std::collections::VecDeque;
6use std::fmt::Debug;
7use std::iter::repeat;
8use std::sync::atomic::AtomicU64;
9use std::sync::{LazyLock, atomic};
10
11use accesskit::{Affine, NodeId, Role};
12use app_units::Au;
13use bitflags::bitflags;
14use euclid::Rect;
15use layout_api::{
16 AccessibilityDamage, BoxAreaType, LayoutElement, LayoutNode, LayoutNodeType,
17 node_id_from_scroll_id,
18};
19use log::trace;
20use num_traits::ToPrimitive;
21use paint_api::display_list::SpatialTreeNodeInfo;
22use rustc_hash::{FxHashMap, FxHashSet};
23use script::layout_dom::{ServoLayoutElement, ServoLayoutNode};
24use servo_base::Epoch;
25use servo_base::print_tree::PrintTree;
26use servo_config::opts::{self, DiagnosticsLogging, DiagnosticsLoggingOption};
27use servo_config::pref;
28use style::Atom;
29use style::dom::OpaqueNode;
30use style_traits::CSSPixel;
31use web_atoms::{LocalName, local_name, ns};
32use webrender_api::ExternalScrollId;
33use webrender_api::units::LayoutVector2D;
34
35use crate::ArcRefCell;
36use crate::cell::WeakRefCell;
37use crate::display_list::StackingContextTree;
38use crate::layout_impl::LayoutThread;
39use crate::query::process_box_area_request;
40
41bitflags! {
42 #[derive(Clone, Copy, Default, Debug, Eq, PartialEq)]
46 struct LocalAccessibilityDamage: u16 {
47 const SubtreeChanged = 0b0001;
49 const RoleChanged = 0b0010;
51 const TextChanged = 0b0100;
53 }
54}
55
56pub(super) struct AccessibilityContext<'update> {
59 pub(super) layout_thread: &'update LayoutThread,
60 pub(super) stacking_context_tree: &'update StackingContextTree,
61}
62
63pub(super) type AccessibilityDamageMap<'a> =
65 FxHashMap<OpaqueNode, (ServoLayoutNode<'a>, AccessibilityDamage)>;
66
67fn au_rect_to_accesskit_rect(rect: Rect<Au, CSSPixel>) -> accesskit::Rect {
69 accesskit::Rect::new(
70 rect.min_x().to_f64_px(),
71 rect.min_y().to_f64_px(),
72 rect.max_x().to_f64_px(),
73 rect.max_y().to_f64_px(),
74 )
75}
76
77fn scroll_offset_to_affine(layout_vector: LayoutVector2D) -> Affine {
78 Affine::translate((
79 -layout_vector.x.to_f64().unwrap_or(0.),
80 -layout_vector.y.to_f64().unwrap_or(0.),
81 ))
82}
83
84struct AccessibilityUpdate<'update> {
86 changed_nodes: FxHashSet<NodeId>,
88 tree_changes: FxHashMap<NodeId, TreeChange>,
90 counters: UpdateCounters,
92
93 damage_map: FxHashMap<NodeId, AccessibilityDamage>,
95 dom_node_map: RefCell<FxHashMap<NodeId, ServoLayoutNode<'update>>>,
98
99 rooted_nodes: Option<FxHashSet<OpaqueNode>>,
103}
104
105#[derive(Debug, Default)]
106pub struct UpdateCounters {
107 pub nodes_updated_from_dom: u32,
108 pub nodes_updated_from_tree: u32,
109 pub nodes_updated_bounds: u32,
110 pub nodes_in_tree_update: u32,
111}
112
113bitflags! {
114 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
117 struct DirtyState : u16 {
118 const DescendantHasDamage = 0b0001;
120 const HasDamage = 0b0010;
122 const Updated = 0b0100;
124 }
125}
126
127struct AccessibilityNode {
128 id: NodeId,
132 accesskit_node: accesskit::Node,
135 parent_node: Option<WeakRefCell<AccessibilityNode>>,
137 child_nodes: Vec<ArcRefCell<AccessibilityNode>>,
139 opaque_node: Option<OpaqueNode>,
143 scroll_offset: Option<LayoutVector2D>,
146 dirty_state: DirtyState,
148}
149
150#[derive(Debug)]
155pub struct AccessibilityTree {
156 nodes: FxHashMap<NodeId, ArcRefCell<AccessibilityNode>>,
159 opaque_node_to_id: FxHashMap<OpaqueNode, NodeId>,
164 id_to_opaque_node: FxHashMap<NodeId, OpaqueNode>,
169 tree_id: accesskit::TreeId,
173 root_node: Option<ArcRefCell<AccessibilityNode>>,
177 pending_scroll_updates: FxHashMap<ExternalScrollId, LayoutVector2D>,
180 embedder_epoch: Epoch,
183 debug: DiagnosticsLogging,
186}
187
188#[derive(Debug, PartialEq, Copy, Clone)]
193enum TreeChange {
194 New,
196
197 Moved,
199
200 PendingMove,
216
217 Removed,
219}
220
221impl AccessibilityTree {
222 pub(super) fn new(tree_id: accesskit::TreeId, embedder_epoch: Epoch) -> Self {
224 Self {
225 nodes: FxHashMap::default(),
226 opaque_node_to_id: FxHashMap::default(),
227 id_to_opaque_node: FxHashMap::default(),
228 tree_id,
229 root_node: None,
230 pending_scroll_updates: FxHashMap::default(),
231 embedder_epoch,
232 debug: opts::get().debug.clone(),
233 }
234 }
235
236 pub(super) fn update_tree<'update>(
239 &mut self,
240 root_dom_node: &ServoLayoutNode<'update>,
241 damage_from_dom: AccessibilityDamageMap<'update>,
242 context: AccessibilityContext<'update>,
243 rooted_nodes: Option<FxHashSet<OpaqueNode>>,
244 ) -> (Option<accesskit::TreeUpdate>, UpdateCounters) {
245 let mut update = AccessibilityUpdate::new(damage_from_dom, rooted_nodes, self);
246
247 self.ensure_root_node(root_dom_node, &context, &mut update);
248
249 self.apply_changes_from_dom_tree(&context, &mut update);
250
251 self.handle_pending_scroll_updates(&mut update);
252
253 update.finalize(self)
254 }
255
256 pub(super) fn add_pending_scroll_updates(
259 &mut self,
260 scroll_states: FxHashMap<ExternalScrollId, LayoutVector2D>,
261 ) {
262 self.pending_scroll_updates.extend(scroll_states);
263 }
264
265 pub(super) fn add_pending_scroll_update(
268 &mut self,
269 external_scroll_id: ExternalScrollId,
270 offset: LayoutVector2D,
271 ) {
272 self.pending_scroll_updates
273 .insert(external_scroll_id, offset);
274 }
275
276 fn ensure_root_node<'update>(
280 &mut self,
281 root_dom_node: &ServoLayoutNode<'update>,
282 context: &AccessibilityContext<'update>,
283 update: &mut AccessibilityUpdate<'update>,
284 ) {
285 let (root_id, root_node) = self.get_or_create_node(root_dom_node, update);
286 if update.is_new(&root_id) {
287 update.clear_damage();
289 update.insert_damage(root_id, AccessibilityDamage::Rebuild);
290 update.insert_dom_node(root_id, *root_dom_node);
291 self.populate_pending_scroll_updates_from_scroll_tree(context);
292 } else {
293 update.insert_damage(root_id, AccessibilityDamage::Subtree);
296 update.insert_dom_node(root_id, *root_dom_node);
297 }
298
299 self.root_node = Some(root_node);
300 }
301
302 fn apply_changes_from_dom_tree(
306 &mut self,
307 context: &AccessibilityContext,
308 update: &mut AccessibilityUpdate,
309 ) {
310 let Some(damage_root_id) = self.mark_nodes_and_ancestors_dirty(update) else {
311 return;
312 };
313 let damage_root = self.assert_node_for_id(&damage_root_id);
314 let local_damage =
315 damage_root
316 .borrow_mut()
317 .update_subtree(damage_root.clone(), context, self, update);
318
319 damage_root.borrow().update_ancestors(local_damage, update);
320 }
321
322 fn populate_pending_scroll_updates_from_scroll_tree(&mut self, context: &AccessibilityContext) {
327 let scroll_tree = &context.stacking_context_tree.paint_info.scroll_tree;
328 let scroll_updates = scroll_tree
329 .nodes
330 .iter()
331 .filter_map(|node| match node.info {
332 SpatialTreeNodeInfo::Scroll(ref info) => {
333 let offset = info.offset;
334 Some((info.external_id, offset))
335 },
336 _ => None,
337 })
338 .collect();
339 self.pending_scroll_updates = scroll_updates;
340 }
341
342 fn handle_pending_scroll_updates(&mut self, update: &mut AccessibilityUpdate) {
349 let pending_scroll_updates = std::mem::take(&mut self.pending_scroll_updates);
350 for (opaque, offset) in
351 pending_scroll_updates
352 .into_iter()
353 .filter_map(|(scroll_id, translate)| {
354 if scroll_id.is_root() {
355 let root_node_opaque =
356 self.root_node.as_ref()?.clone().borrow().opaque_node?;
357 return Some((root_node_opaque, translate));
358 }
359 let node_id = node_id_from_scroll_id(scroll_id.0 as usize);
360 let opaque = OpaqueNode(node_id);
361 Some((opaque, translate))
362 })
363 {
364 let Some(node) = self.node_for_opaque(opaque) else {
365 continue;
366 };
367 node.borrow_mut().set_scroll_offset(offset, update);
368 }
369 }
370
371 fn mark_nodes_and_ancestors_dirty(
377 &mut self,
378 update: &mut AccessibilityUpdate,
379 ) -> Option<NodeId> {
380 let mut dirty_node_ids = update.damage_map.keys();
381
382 let mut common_ancestors: Vec<NodeId> = Vec::new();
385
386 {
387 let node_id = dirty_node_ids.next()?;
389 update.collect_dom_node_ancestors(node_id, self);
390 let first_node = self.assert_node_for_id(node_id);
391 let mut first_node = first_node.borrow_mut();
392 first_node.dirty_state |= DirtyState::HasDamage;
393 common_ancestors.push(first_node.id);
394 common_ancestors.extend(first_node.ancestors().map(|ancestor| {
395 let mut ancestor = ancestor.borrow_mut();
396 ancestor.dirty_state |= DirtyState::DescendantHasDamage;
397 ancestor.id
398 }));
399 common_ancestors.reverse();
400 }
401
402 let mut truncate_ancestors = |node: &AccessibilityNode| -> bool {
403 if node.dirty_state.descendant_has_damage() {
404 if let Some(pos) = common_ancestors.iter().position(|&id| id == node.id) {
405 common_ancestors.truncate(pos + 1);
406 }
407 return true;
408 }
409 false
410 };
411
412 for node_id in dirty_node_ids {
413 let node = self.assert_node_for_id(node_id);
414 let mut node = node.borrow_mut();
415 node.dirty_state |= DirtyState::HasDamage;
416
417 if truncate_ancestors(&node) {
418 continue;
419 }
420
421 for ancestor in node.ancestors() {
422 let mut ancestor = ancestor.borrow_mut();
423
424 if truncate_ancestors(&ancestor) {
427 break;
428 }
429
430 ancestor.dirty_state |= DirtyState::DescendantHasDamage;
431 }
432 }
433
434 common_ancestors.pop()
435 }
436
437 fn get_or_create_node(
441 &mut self,
442 dom_node: &ServoLayoutNode<'_>,
443 update: &mut AccessibilityUpdate,
444 ) -> (NodeId, ArcRefCell<AccessibilityNode>) {
445 let id = self.get_or_create_id_for_opaque(dom_node.opaque());
446 let node_ref = self.get_or_create_node_with_id(id, update);
447
448 if update.is_new(&id) {
449 let mut node = node_ref.borrow_mut();
450 node.opaque_node = Some(dom_node.opaque());
451 if let Some(dom_element) = dom_node.as_element() {
452 let local_name = dom_element.local_name().to_ascii_lowercase();
453 node.set_html_tag(&local_name);
454 }
455 update.insert_damage(id, AccessibilityDamage::Rebuild);
456 node.dirty_state |= DirtyState::HasDamage;
457 }
458
459 (id, node_ref)
460 }
461
462 fn get_or_create_node_with_id(
463 &mut self,
464 id: NodeId,
465 update: &mut AccessibilityUpdate,
466 ) -> ArcRefCell<AccessibilityNode> {
467 if let Some(node) = self.nodes.get(&id) {
468 return node.clone();
469 }
470
471 let node = ArcRefCell::new(AccessibilityNode::new(id));
472 update.set_tree_state_change(id, TreeChange::New);
473 self.nodes.insert(id, node.clone());
474
475 node
476 }
477
478 fn node_for_id(&self, id: NodeId) -> Option<ArcRefCell<AccessibilityNode>> {
479 self.nodes.get(&id).cloned()
480 }
481
482 fn assert_node_for_id(&self, id: &NodeId) -> ArcRefCell<AccessibilityNode> {
483 let Some(node) = self.nodes.get(id) else {
484 panic!("{id:?} does not exist in tree");
485 };
486 node.clone()
487 }
488
489 fn node_for_opaque(&self, opaque: OpaqueNode) -> Option<ArcRefCell<AccessibilityNode>> {
490 self.nodes
491 .get(&self.existing_id_for_opaque(opaque)?)
492 .cloned()
493 }
494
495 fn drop_removed_nodes(&mut self, mut update: AccessibilityUpdate) {
498 let mut rooted_nodes = std::mem::take(&mut update.rooted_nodes);
499 if let Some(rooted_nodes) = rooted_nodes.as_mut() {
500 self.assert_removed_nodes_were_rooted(&update, rooted_nodes);
501 }
502
503 let mut ids_to_remove: Vec<_> = update
504 .tree_changes
505 .iter()
506 .filter_map(|(id, change)| match change {
507 TreeChange::Removed => Some(id),
508 TreeChange::PendingMove => None,
509 TreeChange::New => None,
510 TreeChange::Moved => None,
511 })
512 .cloned()
513 .collect();
514
515 while let Some(id) = ids_to_remove.pop() {
516 if update.tree_changes.get(&id) == Some(&TreeChange::PendingMove) {
517 update.set_tree_state_change(id, TreeChange::Removed);
519
520 continue;
522 }
523
524 if let Some(opaque_node) = self.id_to_opaque_node.remove(&id) {
525 self.opaque_node_to_id.remove(&opaque_node);
526 }
527 let node = self.nodes.remove(&id).expect("Node {id:?} already removed");
528 ids_to_remove.extend(node.borrow().child_ids());
529 }
530
531 update
532 .tree_changes
533 .drain()
534 .for_each(|(id, change)| match change {
535 TreeChange::PendingMove => unreachable!(
536 "Pending move found for node id {id:?} when draining tree state changes"
537 ),
538 TreeChange::Removed => (),
539 TreeChange::New => (),
540 TreeChange::Moved => (),
541 });
542
543 if let Some(rooted_nodes) = rooted_nodes {
544 self.assert_remaining_rooted_nodes_not_in_tree(rooted_nodes);
545 }
546
547 if self
548 .debug
549 .is_enabled(DiagnosticsLoggingOption::AccessibilityTree)
550 {
551 self.print();
552 }
553
554 if pref!(expensive_accessibility_test_assertions_enabled) {
555 self.assert_integrity();
556 }
557 }
558
559 fn assert_removed_nodes_were_rooted(
562 &mut self,
563 update: &AccessibilityUpdate,
564 rooted_nodes: &mut FxHashSet<OpaqueNode>,
565 ) {
566 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
567 for (id, change) in update.tree_changes.iter() {
568 if change == &TreeChange::Removed {
569 let Some(&opaque_node) = self.id_to_opaque_node.get(id) else {
570 panic!("No opaque node found for removed node: id {id:?}");
571 };
572 assert!(
573 rooted_nodes.remove(&opaque_node),
574 "Node removed from accessibility tree wasn't rooted: id {id:?}"
575 );
576 };
577 }
578 }
579
580 fn assert_remaining_rooted_nodes_not_in_tree(&self, rooted_nodes: FxHashSet<OpaqueNode>) {
586 for leftover_node in rooted_nodes {
587 assert!(
588 !self.opaque_node_to_id.contains_key(&leftover_node),
589 "Found node removed from DOM tree but not accessibility tree: {:#x}",
590 leftover_node.0
591 );
592 }
593 }
594
595 fn get_or_create_id_for_opaque(&mut self, opaque: OpaqueNode) -> NodeId {
596 let id = self.opaque_node_to_id.entry(opaque).or_insert_with(|| {
597 static LAST_ID: AtomicU64 = AtomicU64::new(0);
598 let id = LAST_ID.fetch_add(1, atomic::Ordering::SeqCst).into();
599 self.id_to_opaque_node.insert(id, opaque);
600 id
601 });
602 *id
603 }
604
605 fn existing_id_for_opaque(&self, opaque: OpaqueNode) -> Option<NodeId> {
606 self.opaque_node_to_id.get(&opaque).cloned()
607 }
608
609 pub(crate) fn embedder_epoch(&self) -> Epoch {
610 self.embedder_epoch
611 }
612
613 fn assert_integrity(&self) {
617 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
618 let Some(root_node) = self.root_node.clone() else {
619 return;
620 };
621
622 let mut nodes = vec![(root_node, None)];
625 let mut seen_node_ids = FxHashSet::default();
626 while let Some((node, expected_parent)) = nodes.pop() {
627 let node = node.borrow();
628
629 assert!(
631 seen_node_ids.insert(node.id),
632 "Tree contains {:?} in multiple places",
633 node.id
634 );
635
636 node.assert_integrity(expected_parent);
637
638 let weak_node = Some(self.assert_node_for_id(&node.id).downgrade());
641 nodes.extend(node.children().cloned().zip(repeat(weak_node)));
642 }
643
644 assert_eq!(seen_node_ids, self.nodes.keys().copied().collect());
647 }
648
649 fn print(&self) {
650 let Some(root_node) = self.root_node.clone() else {
651 return;
652 };
653
654 let mut print_tree = PrintTree::new("Accessibility Tree");
655 root_node.borrow().print(&mut print_tree);
656 print_tree.end_level();
657 }
658}
659
660fn role_from_role_attribute(dom_element: &ServoLayoutElement<'_>) -> Option<Role> {
662 let role_attribute = dom_element.attribute(&ns!(), &local_name!("role"))?;
663 role_attribute
664 .as_tokens()
665 .iter()
666 .filter_map(|role_name_in_attribute| SUPPORTED_ARIA_ROLES.get(role_name_in_attribute))
667 .next()
668 .cloned()
669}
670
671fn role_from_dom_node(dom_node: &ServoLayoutNode<'_>) -> Role {
672 if let Some(dom_element) = dom_node.as_element() {
673 role_from_role_attribute(&dom_element).unwrap_or_else(|| {
674 let local_name = dom_element.local_name().to_ascii_lowercase();
675 *HTML_ELEMENT_ROLE_MAPPINGS
676 .get(&local_name)
677 .unwrap_or(&Role::GenericContainer)
678 })
679 } else if dom_node.type_id() == Some(LayoutNodeType::Text) {
680 Role::TextRun
681 } else {
682 Role::GenericContainer
683 }
684}
685
686struct AccessibilityNodeIterator<I>
687where
688 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
689{
690 next_value: Option<ArcRefCell<AccessibilityNode>>,
691 next_fn: I,
692}
693
694impl<I> AccessibilityNodeIterator<I>
695where
696 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
697{
698 fn new(next_value: Option<ArcRefCell<AccessibilityNode>>, next_fn: I) -> Self {
699 AccessibilityNodeIterator {
700 next_value,
701 next_fn,
702 }
703 }
704}
705
706impl<I> Iterator for AccessibilityNodeIterator<I>
707where
708 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
709{
710 type Item = ArcRefCell<AccessibilityNode>;
711
712 fn next(&mut self) -> Option<Self::Item> {
713 let next_value = self.next_value.take();
714 self.next_value = next_value
715 .as_ref()
716 .and_then(|node| (self.next_fn)(&node.borrow()));
717 next_value
718 }
719}
720
721impl AccessibilityNode {
722 fn new(id: NodeId) -> Self {
723 Self::new_with_role(id, Role::Unknown)
724 }
725
726 fn new_with_role(id: NodeId, role: Role) -> Self {
727 Self {
728 id,
729 accesskit_node: accesskit::Node::new(role),
730 parent_node: None,
731 child_nodes: vec![],
732 opaque_node: None,
733 scroll_offset: None,
734 dirty_state: DirtyState::empty(),
735 }
736 }
737
738 fn update_subtree<'update>(
750 &mut self,
751 ref_self: ArcRefCell<Self>,
752 context: &AccessibilityContext,
753 tree: &mut AccessibilityTree,
754 update: &mut AccessibilityUpdate<'update>,
755 ) -> LocalAccessibilityDamage {
756 let mut local_damage = LocalAccessibilityDamage::empty();
757
758 let damage = self.compute_damage(update);
759
760 if let Some(dom_node) = update.take_dom_node(&self.id) {
761 local_damage.insert(self.update_properties_and_children_from_dom_node(
764 ref_self, &dom_node, damage, tree, update,
765 ));
766 self.update_bounds_from_dom_node(&dom_node, context, update);
767
768 if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) &&
769 let Some(scroll_offset) = self.scroll_offset
770 {
771 self.set_scroll_offset(scroll_offset, update);
773 }
774
775 self.dirty_state -= DirtyState::HasDamage;
776 }
777
778 for child_node in self.children() {
779 let child_node_ref = child_node.clone();
780 let mut child_node = child_node.borrow_mut();
781 let child_local_damage =
782 child_node.update_subtree(child_node_ref, context, tree, update);
783 if !child_local_damage.is_empty() {
784 local_damage.insert(LocalAccessibilityDamage::SubtreeChanged);
785 }
786 }
787 self.dirty_state -= DirtyState::DescendantHasDamage;
788
789 local_damage.insert(self.update_node_local(local_damage, update));
790
791 if self.dirty_state.updated() {
792 update.add(self);
793 }
794
795 local_damage
796 }
797
798 fn update_ancestors(
801 &self,
802 local_damage: LocalAccessibilityDamage,
803 update: &mut AccessibilityUpdate,
804 ) {
805 if local_damage.is_empty() {
806 return;
807 }
808 for node in self.ancestors() {
809 let mut node = node.borrow_mut();
810 node.update_node_local(LocalAccessibilityDamage::SubtreeChanged, update);
811 node.dirty_state -= DirtyState::DescendantHasDamage;
812 if node.dirty_state.updated() {
813 update.add(&mut node);
814 }
815 }
816 }
817
818 fn update_properties_and_children_from_dom_node<'update>(
823 &mut self,
824 ref_self: ArcRefCell<Self>,
825 dom_node: &ServoLayoutNode<'update>,
826 dom_damage: AccessibilityDamage,
827 tree: &mut AccessibilityTree,
828 update: &mut AccessibilityUpdate<'update>,
829 ) -> LocalAccessibilityDamage {
830 let mut local_damage = LocalAccessibilityDamage::empty();
831
832 update.counters.nodes_updated_from_dom += 1;
837
838 local_damage.insert(self.update_properties_from_dom_node(dom_node, dom_damage));
839 local_damage.insert(
840 self.update_children_from_dom_node(ref_self, dom_node, dom_damage, tree, update),
841 );
842
843 local_damage
844 }
845
846 fn update_children_from_dom_node<'update>(
849 &mut self,
850 ref_self: ArcRefCell<AccessibilityNode>,
851 dom_node: &ServoLayoutNode<'update>,
852 _dom_damage: AccessibilityDamage,
853 tree: &mut AccessibilityTree,
854 update: &mut AccessibilityUpdate<'update>,
855 ) -> LocalAccessibilityDamage {
856 let mut remaining_dom_children = dom_node.flat_tree_children().peekable();
861 let mut old_child_ids = self.child_ids().iter().peekable();
862 let mut unchanged_count = 0usize;
863
864 while let Some(&old_id) = old_child_ids.peek() &&
867 let Some(dom_child) = remaining_dom_children.peek()
868 {
869 if tree.existing_id_for_opaque(dom_child.opaque()) == Some(*old_id) {
870 update.insert_dom_node(*old_id, *dom_child);
871 unchanged_count += 1;
872 old_child_ids.next();
873 remaining_dom_children.next();
874 } else {
875 break;
876 }
877 }
878
879 if old_child_ids.peek().is_none() && remaining_dom_children.peek().is_none() {
881 return LocalAccessibilityDamage::empty();
882 }
883
884 self.child_nodes.truncate(unchanged_count);
886 let mut new_child_ids = Vec::from(self.child_ids());
887 for removed_child_id in new_child_ids.split_off(unchanged_count) {
888 update.set_tree_state_change(removed_child_id, TreeChange::Removed);
889 }
890
891 let weak_self = ref_self.downgrade();
895 for dom_child in remaining_dom_children {
896 let (child_id, child_ref) = tree.get_or_create_node(&dom_child, update);
897 update.insert_dom_node(child_id, dom_child);
902
903 self.child_nodes.push(child_ref.clone());
905 new_child_ids.push(child_id);
906
907 let mut child = child_ref.borrow_mut();
908 child.parent_node = Some(weak_self.clone());
909
910 if update.is_new(&child_id) {
911 self.dirty_state |= DirtyState::DescendantHasDamage;
912 } else {
913 update.set_tree_state_change(child_id, TreeChange::PendingMove);
914 }
915
916 self.dirty_state
917 .propagate_descendant_has_damage(child.dirty_state);
918 }
919
920 self.accesskit_node.set_children(new_child_ids);
923 self.dirty_state |= DirtyState::Updated;
924
925 LocalAccessibilityDamage::SubtreeChanged
926 }
927
928 fn update_properties_from_dom_node(
930 &mut self,
931 dom_node: &ServoLayoutNode,
932 dom_damage: AccessibilityDamage,
933 ) -> LocalAccessibilityDamage {
934 let mut local_damage = LocalAccessibilityDamage::empty();
935 if !dom_damage.contains(AccessibilityDamage::Node) {
936 return local_damage;
937 }
938 local_damage.insert(self.set_role(role_from_dom_node(dom_node)));
939 if dom_node.type_id() == Some(LayoutNodeType::Text) {
940 let text_content = dom_node.text_content();
941 trace!("node text content = {text_content:?}");
942 local_damage.insert(self.set_value(&text_content));
944 }
945
946 local_damage
947 }
948
949 fn update_bounds_from_dom_node(
951 &mut self,
952 dom_node: &ServoLayoutNode,
953 context: &AccessibilityContext,
954 update: &mut AccessibilityUpdate,
955 ) {
956 update.counters.nodes_updated_bounds += 1;
957
958 let bounds = process_box_area_request(
965 context.layout_thread,
966 context.stacking_context_tree,
967 *dom_node,
968 BoxAreaType::Border,
969 true, )
971 .map(au_rect_to_accesskit_rect);
972
973 match bounds {
986 Some(bounds) => self.set_bounds(bounds),
987 None => self.clear_bounds(),
988 }
989 }
990
991 fn update_node_local(
996 &mut self,
997 local_damage: LocalAccessibilityDamage,
998 update: &mut AccessibilityUpdate,
999 ) -> LocalAccessibilityDamage {
1000 let mut new_damage = LocalAccessibilityDamage::empty();
1001 if local_damage.is_empty() {
1002 return new_damage;
1003 }
1004 update.counters.nodes_updated_from_tree += 1;
1005
1006 if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) ||
1007 local_damage.contains(LocalAccessibilityDamage::RoleChanged)
1008 {
1009 if let Some(text) = self.label_from_descendants() {
1010 new_damage.insert(self.set_label(text.as_str()));
1011 } else {
1012 new_damage.insert(self.clear_label());
1013 }
1014 }
1015
1016 new_damage
1017 }
1018
1019 fn label_from_descendants(&self) -> Option<String> {
1020 if !NAME_FROM_CONTENTS_ROLES.contains(&self.role()) {
1021 return None;
1022 }
1023 let mut children = VecDeque::from_iter(self.children().cloned());
1024 let mut text = String::new();
1025 while let Some(child) = children.pop_front() {
1026 let child = child.borrow();
1027 match child.role() {
1028 Role::TextRun => {
1029 if let Some(child_text) = child.value() {
1030 text.push_str(child_text);
1031 }
1032 },
1033 _ => {
1034 for node in child.children().rev() {
1035 children.push_front(node.clone());
1036 }
1037 },
1038 }
1039 }
1040 Some(text.trim().to_owned())
1041 }
1042
1043 fn print(&self, print_tree: &mut PrintTree) {
1044 if self.child_nodes.is_empty() {
1045 print_tree.add_item(format!("{self:?}"));
1046 return;
1047 }
1048
1049 print_tree.new_level(format!("{self:?}"));
1050
1051 for child in self.children() {
1052 child.borrow().print(print_tree);
1053 }
1054 print_tree.end_level();
1055 }
1056
1057 fn parent(&self) -> Option<ArcRefCell<AccessibilityNode>> {
1058 self.parent_node.as_ref().and_then(|weak| weak.upgrade())
1059 }
1060
1061 fn children(&self) -> impl DoubleEndedIterator<Item = &ArcRefCell<AccessibilityNode>> {
1062 self.child_nodes.iter()
1063 }
1064
1065 fn ancestors(&self) -> impl Iterator<Item = ArcRefCell<AccessibilityNode>> {
1066 AccessibilityNodeIterator::new(self.parent(), |node| node.parent_node.clone()?.upgrade())
1067 }
1068
1069 fn child_ids(&self) -> &[NodeId] {
1070 self.accesskit_node.children()
1071 }
1072
1073 fn set_scroll_offset(&mut self, offset: LayoutVector2D, update: &mut AccessibilityUpdate) {
1074 self.scroll_offset = Some(offset);
1075 let transform = scroll_offset_to_affine(offset);
1076 for child in self.children() {
1077 let mut child = child.borrow_mut();
1078 child.set_transform(transform);
1079 if child.dirty_state.updated() {
1080 update.add(&mut child);
1081 }
1082 }
1083 }
1084
1085 fn role(&self) -> Role {
1088 self.accesskit_node.role()
1089 }
1090
1091 fn set_role(&mut self, role: Role) -> LocalAccessibilityDamage {
1092 if role == self.accesskit_node.role() {
1093 return LocalAccessibilityDamage::empty();
1094 }
1095 self.accesskit_node.set_role(role);
1096 self.dirty_state |= DirtyState::Updated;
1097 LocalAccessibilityDamage::RoleChanged
1098 }
1099
1100 fn label(&self) -> Option<&str> {
1101 self.accesskit_node.label()
1102 }
1103
1104 fn set_label(&mut self, label: &str) -> LocalAccessibilityDamage {
1105 if Some(label) == self.accesskit_node.label() {
1106 return LocalAccessibilityDamage::empty();
1107 }
1108 self.accesskit_node.set_label(label);
1109 self.dirty_state |= DirtyState::Updated;
1110 LocalAccessibilityDamage::TextChanged
1111 }
1112
1113 fn clear_label(&mut self) -> LocalAccessibilityDamage {
1114 if self.accesskit_node.label().is_none() {
1115 return LocalAccessibilityDamage::empty();
1116 }
1117 self.accesskit_node.clear_label();
1118 self.dirty_state |= DirtyState::Updated;
1119 LocalAccessibilityDamage::TextChanged
1120 }
1121
1122 fn html_tag(&self) -> Option<&str> {
1123 self.accesskit_node.html_tag()
1124 }
1125
1126 fn set_html_tag(&mut self, html_tag: &str) {
1127 if Some(html_tag) == self.accesskit_node.html_tag() {
1128 return;
1129 }
1130 self.accesskit_node.set_html_tag(html_tag);
1131 self.dirty_state |= DirtyState::Updated;
1132 }
1133
1134 fn value(&self) -> Option<&str> {
1135 self.accesskit_node.value()
1136 }
1137
1138 fn set_value(&mut self, value: &str) -> LocalAccessibilityDamage {
1139 if Some(value) == self.accesskit_node.value() {
1140 return LocalAccessibilityDamage::empty();
1141 }
1142 self.accesskit_node.set_value(value);
1143 self.dirty_state |= DirtyState::Updated;
1144 LocalAccessibilityDamage::TextChanged
1145 }
1146
1147 fn bounds(&self) -> Option<accesskit::Rect> {
1148 self.accesskit_node.bounds()
1149 }
1150
1151 fn set_bounds(&mut self, bounds: accesskit::Rect) {
1152 if Some(bounds) == self.accesskit_node.bounds() {
1153 return;
1154 }
1155 self.accesskit_node.set_bounds(bounds);
1156 self.dirty_state |= DirtyState::Updated;
1157 }
1158
1159 fn clear_bounds(&mut self) {
1160 if self.accesskit_node.bounds().is_none() {
1161 return;
1162 }
1163 self.accesskit_node.clear_bounds();
1164 self.dirty_state |= DirtyState::Updated;
1165 }
1166
1167 fn set_transform(&mut self, transform: Affine) {
1168 if self.accesskit_node.transform() == Some(&transform) {
1172 return;
1173 }
1174 if transform == Affine::IDENTITY {
1175 self.clear_transform();
1176 return;
1177 }
1178 self.accesskit_node.set_transform(transform);
1179 self.dirty_state |= DirtyState::Updated;
1180 }
1181
1182 fn clear_transform(&mut self) {
1183 if self.accesskit_node.transform().is_none() {
1184 return;
1185 }
1186 self.accesskit_node.clear_transform();
1187 self.dirty_state |= DirtyState::Updated;
1188 }
1189
1190 fn assert_integrity(&self, expected_parent: Option<WeakRefCell<AccessibilityNode>>) {
1191 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
1192
1193 if let Some(actual_parent) = &self.parent_node {
1194 let expected = expected_parent.expect("Actual parent but no expected parent");
1195 let expected = expected.upgrade().expect("Expected parent was dropped");
1196 let actual = actual_parent.upgrade().expect("Actual parent was dropped");
1197 assert!(actual.ptr_eq(&expected));
1198 } else {
1199 assert!(
1200 expected_parent.is_none(),
1201 "Expected parent but no actual parent"
1202 );
1203 }
1204
1205 assert!(
1206 self.dirty_state.is_empty(),
1207 "{self:?} has dirty state {:?}",
1208 self.dirty_state
1209 );
1210
1211 let children_ids: Vec<_> = self.children().map(|child| child.borrow().id).collect();
1212 assert_eq!(
1213 children_ids,
1214 self.child_ids(),
1215 "children() IDs didn't match child_ids() for {self:?}"
1216 );
1217 }
1218
1219 fn compute_damage(&self, update: &mut AccessibilityUpdate) -> AccessibilityDamage {
1220 let mut damage = AccessibilityDamage::empty();
1221
1222 if self.dirty_state.has_damage() {
1223 damage |= update.take_damage(&self.id);
1224 }
1225
1226 damage
1227 }
1228}
1229
1230impl Debug for AccessibilityNode {
1231 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1232 write!(f, "{:?}: {:?}", self.id, self.role())?;
1233 if let Some(html_tag) = self.html_tag() {
1234 write!(f, " (html_tag: {html_tag:?})")?;
1235 }
1236 if let Some(label) = self.label() {
1237 write!(f, "\nlabel: {label:?}")?;
1238 }
1239 if let Some(bounds) = self.bounds() {
1240 write!(f, "\nbounds: {bounds:?}")?;
1241 }
1242 if !self.child_ids().is_empty() {
1243 write!(f, "\nchildren: {:?}", self.child_ids())?;
1244 }
1245 Ok(())
1246 }
1247}
1248
1249impl<'update> AccessibilityUpdate<'update> {
1250 fn new(
1251 dom_damage: AccessibilityDamageMap<'update>,
1252 rooted_nodes: Option<FxHashSet<OpaqueNode>>,
1253 tree: &AccessibilityTree,
1254 ) -> Self {
1255 let damage_map = dom_damage
1256 .iter()
1257 .filter_map(|(&opaque, &(_dom_node, damage))| {
1258 let id = tree.existing_id_for_opaque(opaque)?;
1259 Some((id, damage))
1260 })
1261 .collect();
1262 let dom_node_map = dom_damage
1263 .into_iter()
1264 .filter_map(|(opaque, (dom_node, _damage))| {
1265 let id = tree.existing_id_for_opaque(opaque)?;
1266 Some((id, dom_node))
1267 })
1268 .collect();
1269 Self {
1270 changed_nodes: FxHashSet::default(),
1271 tree_changes: FxHashMap::default(),
1272 counters: UpdateCounters::default(),
1273 damage_map,
1274 dom_node_map: RefCell::new(dom_node_map),
1275 rooted_nodes,
1276 }
1277 }
1278
1279 fn add(&mut self, node: &mut AccessibilityNode) {
1280 self.changed_nodes.insert(node.id);
1281 node.dirty_state -= DirtyState::Updated;
1282 }
1283
1284 fn set_tree_state_change(&mut self, node_id: NodeId, change: TreeChange) {
1285 let old_change = self.tree_changes.get(&node_id);
1286
1287 assert!(
1288 change != TreeChange::Moved,
1289 "Incoming change must never be Moved"
1290 );
1291
1292 let resolved_change = old_change
1293 .map(|old_change| match (old_change, change) {
1294 (TreeChange::PendingMove, TreeChange::Removed) => TreeChange::Moved,
1295 (TreeChange::Removed, TreeChange::PendingMove) => TreeChange::Moved,
1296 _ => {
1297 unreachable!("Logically impossible state change: {old_change:?} → {change:?}")
1298 },
1299 })
1300 .unwrap_or(change);
1301
1302 self.tree_changes.insert(node_id, resolved_change);
1303 }
1304
1305 fn is_new(&mut self, node_id: &NodeId) -> bool {
1306 self.tree_changes.get(node_id) == Some(&TreeChange::New)
1307 }
1308
1309 fn finalize(
1314 mut self,
1315 tree: &mut AccessibilityTree,
1316 ) -> (Option<accesskit::TreeUpdate>, UpdateCounters) {
1317 let root_node_id = tree
1318 .root_node
1319 .clone()
1320 .expect("AccessibilityUpdate::finalize() called but no root_node set in tree")
1321 .borrow()
1322 .id;
1323
1324 if self.changed_nodes.is_empty() {
1325 assert!(self.tree_changes.is_empty());
1326 return (None, self.counters);
1327 }
1328
1329 let changed_nodes = std::mem::take(&mut self.changed_nodes);
1330 let mut counters = std::mem::take(&mut self.counters);
1331
1332 tree.drop_removed_nodes(self);
1333
1334 let changed_nodes: Vec<_> = changed_nodes
1336 .into_iter()
1337 .filter_map(|id| Some((id, tree.node_for_id(id)?.borrow().accesskit_node.clone())))
1338 .collect();
1339
1340 counters.nodes_in_tree_update = changed_nodes.len().try_into().unwrap_or_default();
1341
1342 let accesskit_tree = accesskit::Tree::new(root_node_id);
1343 let tree_update = accesskit::TreeUpdate {
1344 nodes: changed_nodes,
1345 tree: Some(accesskit_tree),
1346 focus: NodeId(1),
1347 tree_id: tree.tree_id,
1348 };
1349
1350 (Some(tree_update), counters)
1351 }
1352
1353 fn clear_damage(&mut self) {
1354 self.damage_map.clear();
1355 }
1356
1357 fn insert_damage(&mut self, node_id: NodeId, damage: AccessibilityDamage) {
1358 self.damage_map.insert(node_id, damage);
1359 }
1360
1361 fn insert_dom_node(&self, node_id: NodeId, dom_node: ServoLayoutNode<'update>) {
1362 self.dom_node_map.borrow_mut().insert(node_id, dom_node);
1363 }
1364
1365 fn take_damage(&mut self, node_id: &NodeId) -> AccessibilityDamage {
1366 self.damage_map
1367 .remove(node_id)
1368 .unwrap_or(AccessibilityDamage::empty())
1369 }
1370
1371 fn take_dom_node(&mut self, node_id: &NodeId) -> Option<ServoLayoutNode<'update>> {
1372 self.dom_node_map.borrow_mut().remove(node_id)
1373 }
1374
1375 #[expect(unsafe_code)]
1376 fn collect_dom_node_ancestors(&self, node_id: &NodeId, tree: &AccessibilityTree) {
1377 let mut dom_node_map = self.dom_node_map.borrow_mut();
1378 let dom_node = dom_node_map
1379 .get(node_id)
1380 .expect("collect_dom_node_ancestors should be called for a known DOM node");
1381 let mut parent = unsafe { dom_node.dangerous_flat_tree_parent() };
1382 while let Some(node) = parent {
1383 if let Some(node_id) = tree.existing_id_for_opaque(node.opaque()) {
1384 dom_node_map.insert(node_id, node);
1385 }
1386 parent = unsafe { node.dangerous_flat_tree_parent() };
1387 }
1388 }
1389}
1390
1391impl DirtyState {
1392 fn updated(&self) -> bool {
1393 self.contains(DirtyState::Updated)
1394 }
1395
1396 fn has_damage(&self) -> bool {
1397 self.contains(DirtyState::HasDamage)
1398 }
1399
1400 fn descendant_has_damage(&self) -> bool {
1401 self.contains(DirtyState::DescendantHasDamage)
1402 }
1403
1404 fn propagate_descendant_has_damage(&mut self, child_dirty_state: DirtyState) {
1405 if child_dirty_state.self_or_descendant_has_damage() {
1406 self.insert(DirtyState::DescendantHasDamage)
1407 }
1408 }
1409
1410 fn self_or_descendant_has_damage(&self) -> bool {
1411 self.intersects(DirtyState::HasDamage | DirtyState::DescendantHasDamage)
1412 }
1413}
1414
1415#[cfg(test)]
1416#[test]
1417fn test_accessibility_update_add_some_nodes_twice() {
1418 let mut tree = AccessibilityTree::new(accesskit::TreeId::ROOT, Epoch::default());
1419 let mut root_update = AccessibilityUpdate::new(AccessibilityDamageMap::default(), None, &tree);
1420
1421 let root_node = tree.get_or_create_node_with_id(NodeId(2), &mut root_update);
1422 tree.root_node = Some(root_node.clone());
1423
1424 let nodes: Vec<_> = [
1425 (3, Role::GenericContainer),
1426 (4, Role::Heading),
1427 (5, Role::Paragraph),
1428 ]
1429 .into_iter()
1430 .map(|(id, role)| {
1431 let id = NodeId(id);
1432 let node = tree.get_or_create_node_with_id(id, &mut root_update);
1433 node.borrow_mut().set_role(role);
1434 (id, node)
1435 })
1436 .collect();
1437
1438 {
1439 let (child_node_ids, child_nodes): (Vec<_>, Vec<_>) = nodes.iter().cloned().unzip();
1440 let mut root_node = root_node.borrow_mut();
1441 root_node.accesskit_node.set_children(child_node_ids);
1442 root_node.child_nodes = child_nodes;
1443 }
1444
1445 let mut update = AccessibilityUpdate::new(AccessibilityDamageMap::default(), None, &tree);
1446
1447 {
1448 let node_3 = tree.assert_node_for_id(&NodeId(3));
1449 let mut node_3 = node_3.borrow_mut();
1450 let node_4 = tree.assert_node_for_id(&NodeId(4));
1451 let mut node_4 = node_4.borrow_mut();
1452 let node_5 = tree.assert_node_for_id(&NodeId(5));
1453 let mut node_5 = node_5.borrow_mut();
1454
1455 update.add(&mut node_5);
1456 update.add(&mut node_3);
1457 update.add(&mut node_4);
1458 update.add(&mut node_4);
1459
1460 node_3.set_role(Role::ScrollView);
1461 update.add(&mut node_3);
1462 }
1463
1464 let (tree_update, _) = update.finalize(&mut tree);
1465 let mut tree_update = tree_update.expect("finalize should produce a tree update");
1466 tree_update.nodes.sort_by_key(|(node_id, _node)| *node_id);
1467 assert_eq!(
1468 tree_update,
1469 accesskit::TreeUpdate {
1470 nodes: vec![
1471 (NodeId(3), accesskit::Node::new(Role::ScrollView)),
1472 (NodeId(4), accesskit::Node::new(Role::Heading)),
1473 (NodeId(5), accesskit::Node::new(Role::Paragraph)),
1474 ],
1475 tree: Some(accesskit::Tree {
1476 root: NodeId(2),
1477 toolkit_name: None,
1478 toolkit_version: None
1479 }),
1480 tree_id: accesskit::TreeId::ROOT,
1481 focus: NodeId(1),
1482 }
1483 );
1484}
1485
1486static HTML_ELEMENT_ROLE_MAPPINGS: LazyLock<FxHashMap<LocalName, Role>> = LazyLock::new(|| {
1487 [
1488 (local_name!("article"), Role::Article),
1489 (local_name!("aside"), Role::Complementary),
1490 (local_name!("body"), Role::RootWebArea),
1491 (local_name!("footer"), Role::ContentInfo),
1492 (local_name!("h1"), Role::Heading),
1493 (local_name!("h2"), Role::Heading),
1494 (local_name!("h3"), Role::Heading),
1495 (local_name!("h4"), Role::Heading),
1496 (local_name!("h5"), Role::Heading),
1497 (local_name!("h6"), Role::Heading),
1498 (local_name!("header"), Role::Banner),
1499 (local_name!("hr"), Role::Splitter),
1500 (local_name!("main"), Role::Main),
1501 (local_name!("nav"), Role::Navigation),
1502 (local_name!("p"), Role::Paragraph),
1503 ]
1504 .into_iter()
1505 .collect()
1506});
1507
1508static SUPPORTED_ARIA_ROLES: LazyLock<FxHashMap<Atom, Role>> = LazyLock::new(|| {
1517 [
1518 (Atom::from("alert"), Role::Alert),
1519 (Atom::from("banner"), Role::Banner),
1520 (Atom::from("blockquote"), Role::Blockquote),
1521 (Atom::from("caption"), Role::Caption),
1522 (Atom::from("code"), Role::Code),
1523 (Atom::from("complementary"), Role::Complementary),
1524 (Atom::from("contentinfo"), Role::ContentInfo),
1525 (Atom::from("definition"), Role::Definition),
1526 (Atom::from("deletion"), Role::ContentDeletion),
1527 (Atom::from("directory"), Role::Unknown),
1528 (Atom::from("document"), Role::Document),
1529 (Atom::from("emphasis"), Role::Emphasis),
1530 (Atom::from("feed"), Role::Feed),
1531 (Atom::from("figure"), Role::Figure),
1532 (Atom::from("generic"), Role::GenericContainer),
1533 (Atom::from("insertion"), Role::ContentInsertion),
1534 (Atom::from("list"), Role::List),
1535 (Atom::from("log"), Role::Log),
1536 (Atom::from("main"), Role::Main),
1537 (Atom::from("math"), Role::Math),
1538 (Atom::from("navigation"), Role::Navigation),
1539 (Atom::from("none"), Role::GenericContainer),
1540 (Atom::from("note"), Role::Note),
1541 (Atom::from("paragraph"), Role::Paragraph),
1542 (Atom::from("presentation"), Role::GenericContainer),
1543 (Atom::from("rowgroup"), Role::RowGroup),
1544 (Atom::from("search"), Role::Search),
1545 (Atom::from("status"), Role::Status),
1546 (Atom::from("strong"), Role::Strong),
1547 (Atom::from("term"), Role::Term),
1550 (Atom::from("time"), Role::Time),
1551 (Atom::from("timer"), Role::Timer),
1552 ]
1553 .into_iter()
1554 .collect()
1555});
1556
1557static NAME_FROM_CONTENTS_ROLES: LazyLock<FxHashSet<Role>> =
1559 LazyLock::new(|| [(Role::Heading)].into_iter().collect());