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::{ActionRequest, Affine, NodeId, Role};
12use app_units::Au;
13use bitflags::bitflags;
14use euclid::Rect;
15use layout_api::{
16 AccessibilityActionRequest, AccessibilityDamage, BoxAreaType, LayoutElement, LayoutNode,
17 LayoutNodeType, 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::{NodeInfo, 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::{BoxAreaInclusion, 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 const VisibilityChanged = 0b1000;
55 }
56}
57
58pub(super) struct AccessibilityContext<'update> {
61 pub(super) layout_thread: &'update LayoutThread,
62 pub(super) stacking_context_tree: &'update StackingContextTree,
63 pub(super) rooted_nodes_for_integrity_check: Option<FxHashSet<OpaqueNode>>,
64}
65
66pub(super) type AccessibilityDamageMap<'a> =
68 FxHashMap<OpaqueNode, (ServoLayoutNode<'a>, AccessibilityDamage)>;
69
70fn au_rect_to_accesskit_rect(rect: Rect<Au, CSSPixel>) -> accesskit::Rect {
72 accesskit::Rect::new(
73 rect.min_x().to_f64_px(),
74 rect.min_y().to_f64_px(),
75 rect.max_x().to_f64_px(),
76 rect.max_y().to_f64_px(),
77 )
78}
79
80fn scroll_offset_to_affine(layout_vector: LayoutVector2D) -> Affine {
81 Affine::translate((
82 -layout_vector.x.to_f64().unwrap_or(0.),
83 -layout_vector.y.to_f64().unwrap_or(0.),
84 ))
85}
86
87struct AccessibilityUpdate<'update> {
89 changed_nodes: FxHashSet<NodeId>,
91 accesskit_tree: Option<accesskit::Tree>,
95 tree_changes: FxHashMap<NodeId, TreeChange>,
97 counters: UpdateCounters,
99
100 damage_map: FxHashMap<NodeId, AccessibilityDamage>,
102 dom_node_map: RefCell<FxHashMap<NodeId, ServoLayoutNode<'update>>>,
105}
106
107#[derive(Debug, Default)]
108pub struct UpdateCounters {
109 pub nodes_updated_from_dom: u32,
110 pub nodes_updated_from_tree: u32,
111 pub nodes_updated_bounds: u32,
112 pub nodes_in_tree_update: u32,
113}
114
115bitflags! {
116 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
119 struct DirtyState : u16 {
120 const DescendantHasDamage = 0b0001;
122 const HasDamage = 0b0010;
124 const Updated = 0b0100;
126 }
127}
128
129struct AccessibilityNode {
130 id: NodeId,
134 accesskit_node: accesskit::Node,
137 parent_node: Option<WeakRefCell<AccessibilityNode>>,
139 child_nodes: Vec<ArcRefCell<AccessibilityNode>>,
141 opaque_node: Option<OpaqueNode>,
145 scroll_offset: Option<LayoutVector2D>,
148 dirty_state: DirtyState,
150}
151
152#[derive(Debug)]
157pub struct AccessibilityTree {
158 nodes: FxHashMap<NodeId, ArcRefCell<AccessibilityNode>>,
161 opaque_node_to_id: FxHashMap<OpaqueNode, NodeId>,
166 id_to_opaque_node: FxHashMap<NodeId, OpaqueNode>,
171 tree_id: accesskit::TreeId,
175 root_node: Option<ArcRefCell<AccessibilityNode>>,
179 pending_scroll_updates: FxHashMap<ExternalScrollId, LayoutVector2D>,
182 embedder_epoch: Epoch,
185 pending_actions: Vec<AccessibilityActionRequest>,
191 debug: DiagnosticsLogging,
194}
195
196#[derive(Debug, PartialEq, Copy, Clone)]
201enum TreeChange {
202 New,
204
205 Moved,
207
208 PendingMove,
224
225 Removed,
227}
228
229impl AccessibilityTree {
230 pub(super) fn new(tree_id: accesskit::TreeId, embedder_epoch: Epoch) -> Self {
232 Self {
233 nodes: FxHashMap::default(),
234 opaque_node_to_id: FxHashMap::default(),
235 id_to_opaque_node: FxHashMap::default(),
236 tree_id,
237 root_node: None,
238 pending_scroll_updates: FxHashMap::default(),
239 embedder_epoch,
240 pending_actions: vec![],
241 debug: opts::get().debug.clone(),
242 }
243 }
244
245 pub(super) fn update_tree<'update>(
248 &mut self,
249 root_dom_node: &ServoLayoutNode<'update>,
250 damage_from_dom: AccessibilityDamageMap<'update>,
251 action_requests: Vec<ActionRequest>,
252 context: AccessibilityContext<'update>,
253 ) -> (Option<accesskit::TreeUpdate>, UpdateCounters) {
254 let mut update = AccessibilityUpdate::new(damage_from_dom, self);
255
256 self.ensure_root_node(root_dom_node, &context, &mut update);
257
258 self.apply_changes_from_dom_tree(&context, &mut update);
259
260 self.handle_pending_scroll_updates(&mut update);
261
262 update.finalize(
263 self,
264 context.rooted_nodes_for_integrity_check,
265 action_requests,
266 )
267 }
268
269 pub(super) fn add_pending_scroll_updates(
272 &mut self,
273 scroll_states: FxHashMap<ExternalScrollId, LayoutVector2D>,
274 ) {
275 self.pending_scroll_updates.extend(scroll_states);
276 }
277
278 pub(super) fn add_pending_scroll_update(
281 &mut self,
282 external_scroll_id: ExternalScrollId,
283 offset: LayoutVector2D,
284 ) {
285 self.pending_scroll_updates
286 .insert(external_scroll_id, offset);
287 }
288
289 fn ensure_root_node<'update>(
293 &mut self,
294 root_dom_node: &ServoLayoutNode<'update>,
295 context: &AccessibilityContext<'update>,
296 update: &mut AccessibilityUpdate<'update>,
297 ) {
298 let (root_id, root_node) = self.get_or_create_node(root_dom_node, update);
299 if update.is_new(&root_id) {
300 update.clear_damage();
302 update.insert_damage(root_id, AccessibilityDamage::Rebuild);
303 update.insert_dom_node(root_id, *root_dom_node);
304 self.populate_pending_scroll_updates_from_scroll_tree(context);
305
306 update.accesskit_tree = Some(accesskit::Tree::new(root_id));
307 }
308
309 self.root_node = Some(root_node);
310 }
311
312 fn apply_changes_from_dom_tree(
316 &mut self,
317 context: &AccessibilityContext,
318 update: &mut AccessibilityUpdate,
319 ) {
320 let Some(damage_root_id) = self.mark_nodes_and_ancestors_dirty(update) else {
321 return;
322 };
323 let damage_root = self.assert_node_for_id(&damage_root_id);
324 let hidden = false;
325 let local_damage = damage_root.borrow_mut().update_subtree(
326 damage_root.clone(),
327 AccessibilityDamage::empty(),
328 hidden,
329 context,
330 self,
331 update,
332 );
333
334 damage_root.borrow().update_ancestors(local_damage, update);
335 }
336
337 fn populate_pending_scroll_updates_from_scroll_tree(&mut self, context: &AccessibilityContext) {
342 let scroll_tree = &context.stacking_context_tree.paint_info.scroll_tree;
343 let scroll_updates = scroll_tree
344 .nodes
345 .iter()
346 .filter_map(|node| match node.info {
347 SpatialTreeNodeInfo::Scroll(ref info) => {
348 let offset = info.offset;
349 Some((info.external_id, offset))
350 },
351 _ => None,
352 })
353 .collect();
354 self.pending_scroll_updates = scroll_updates;
355 }
356
357 fn handle_pending_scroll_updates(&mut self, update: &mut AccessibilityUpdate) {
364 let pending_scroll_updates = std::mem::take(&mut self.pending_scroll_updates);
365 for (opaque, offset) in
366 pending_scroll_updates
367 .into_iter()
368 .filter_map(|(scroll_id, translate)| {
369 if scroll_id.is_root() {
370 let root_node_opaque =
371 self.root_node.as_ref()?.clone().borrow().opaque_node?;
372 return Some((root_node_opaque, translate));
373 }
374 let node_id = node_id_from_scroll_id(scroll_id.0 as usize);
375 let opaque = OpaqueNode(node_id);
376 Some((opaque, translate))
377 })
378 {
379 let Some(node) = self.node_for_opaque(opaque) else {
380 continue;
381 };
382 node.borrow_mut().set_scroll_offset(offset, update);
383 }
384 }
385
386 fn mark_nodes_and_ancestors_dirty(
395 &mut self,
396 update: &mut AccessibilityUpdate,
397 ) -> Option<NodeId> {
398 let mut dirty_node_ids = update.damage_map.keys();
399
400 let mut common_ancestors: Vec<NodeId> = Vec::new();
403
404 {
405 let node_id = dirty_node_ids.next()?;
407 update.collect_dom_node_ancestors(node_id, self);
408 let first_node = self.assert_node_for_id(node_id);
409 let mut first_node = first_node.borrow_mut();
410 first_node.dirty_state |= DirtyState::HasDamage;
411 common_ancestors.push(first_node.id);
412 common_ancestors.extend(first_node.ancestors().map(|ancestor| {
413 let mut ancestor = ancestor.borrow_mut();
414 ancestor.dirty_state |= DirtyState::DescendantHasDamage;
415 ancestor.id
416 }));
417 common_ancestors.reverse();
418 }
419
420 let mut truncate_ancestors = |node: &AccessibilityNode| -> bool {
421 if node.dirty_state.descendant_has_damage() {
422 if let Some(pos) = common_ancestors.iter().position(|&id| id == node.id) {
423 common_ancestors.truncate(pos + 1);
424 }
425 return true;
426 }
427 false
428 };
429
430 for node_id in dirty_node_ids {
431 let node = self.assert_node_for_id(node_id);
432 let mut node = node.borrow_mut();
433 node.dirty_state |= DirtyState::HasDamage;
434
435 if truncate_ancestors(&node) {
436 continue;
437 }
438
439 for ancestor in node.ancestors() {
440 let mut ancestor = ancestor.borrow_mut();
441
442 if truncate_ancestors(&ancestor) {
445 break;
446 }
447
448 ancestor.dirty_state |= DirtyState::DescendantHasDamage;
449 }
450 }
451
452 let lowest_common_ancestor = common_ancestors.pop();
453
454 for ancestor_id in common_ancestors {
455 let ancestor = self.assert_node_for_id(&ancestor_id);
456 ancestor.borrow_mut().dirty_state -= DirtyState::DescendantHasDamage;
457 }
458
459 lowest_common_ancestor
460 }
461
462 fn get_or_create_node(
466 &mut self,
467 dom_node: &ServoLayoutNode<'_>,
468 update: &mut AccessibilityUpdate,
469 ) -> (NodeId, ArcRefCell<AccessibilityNode>) {
470 let id = self.get_or_create_id_for_opaque(dom_node.opaque());
471 let node_ref = self.get_or_create_node_with_id(id, update);
472
473 if update.is_new(&id) {
474 let mut node = node_ref.borrow_mut();
475 node.opaque_node = Some(dom_node.opaque());
476 if let Some(dom_element) = dom_node.as_element() {
477 let local_name = dom_element.local_name().to_ascii_lowercase();
478 node.set_html_tag(&local_name);
479 }
480 update.insert_damage(id, AccessibilityDamage::Rebuild);
481 node.dirty_state |= DirtyState::HasDamage;
482 }
483
484 (id, node_ref)
485 }
486
487 fn get_or_create_node_with_id(
488 &mut self,
489 id: NodeId,
490 update: &mut AccessibilityUpdate,
491 ) -> ArcRefCell<AccessibilityNode> {
492 if let Some(node) = self.nodes.get(&id) {
493 return node.clone();
494 }
495
496 let node = ArcRefCell::new(AccessibilityNode::new(id));
497 update.set_tree_state_change(id, TreeChange::New);
498 self.nodes.insert(id, node.clone());
499
500 node
501 }
502
503 fn node_for_id(&self, id: NodeId) -> Option<ArcRefCell<AccessibilityNode>> {
504 self.nodes.get(&id).cloned()
505 }
506
507 fn assert_node_for_id(&self, id: &NodeId) -> ArcRefCell<AccessibilityNode> {
508 let Some(node) = self.nodes.get(id) else {
509 panic!("{id:?} does not exist in tree");
510 };
511 node.clone()
512 }
513
514 fn node_for_opaque(&self, opaque: OpaqueNode) -> Option<ArcRefCell<AccessibilityNode>> {
515 self.nodes
516 .get(&self.existing_id_for_opaque(opaque)?)
517 .cloned()
518 }
519
520 fn drop_removed_nodes(
523 &mut self,
524 mut update: AccessibilityUpdate,
525 mut rooted_nodes_for_integrity_check: Option<FxHashSet<OpaqueNode>>,
526 ) {
527 if let Some(rooted_nodes) = rooted_nodes_for_integrity_check.as_mut() {
528 self.assert_removed_nodes_were_rooted(&update, rooted_nodes);
529 }
530
531 let mut ids_to_remove: Vec<_> = update
532 .tree_changes
533 .iter()
534 .filter_map(|(id, change)| match change {
535 TreeChange::Removed => Some(id),
536 TreeChange::PendingMove => None,
537 TreeChange::New => None,
538 TreeChange::Moved => None,
539 })
540 .cloned()
541 .collect();
542
543 while let Some(id) = ids_to_remove.pop() {
544 if update.tree_changes.get(&id) == Some(&TreeChange::PendingMove) {
545 update.set_tree_state_change(id, TreeChange::Removed);
547
548 continue;
550 }
551
552 if let Some(opaque_node) = self.id_to_opaque_node.remove(&id) {
553 self.opaque_node_to_id.remove(&opaque_node);
554 }
555 let node = self.nodes.remove(&id).expect("Node {id:?} already removed");
556 ids_to_remove.extend(node.borrow().child_ids());
557 }
558
559 update
560 .tree_changes
561 .drain()
562 .for_each(|(id, change)| match change {
563 TreeChange::PendingMove => unreachable!(
564 "Pending move found for node id {id:?} when draining tree state changes"
565 ),
566 TreeChange::Removed => (),
567 TreeChange::New => (),
568 TreeChange::Moved => (),
569 });
570
571 if let Some(rooted_nodes) = rooted_nodes_for_integrity_check {
572 self.assert_remaining_rooted_nodes_not_in_tree(rooted_nodes);
573 }
574
575 if self
576 .debug
577 .is_enabled(DiagnosticsLoggingOption::AccessibilityTree)
578 {
579 self.print();
580 }
581
582 if pref!(expensive_accessibility_test_assertions_enabled) {
583 self.assert_integrity();
584 }
585 }
586
587 fn assert_removed_nodes_were_rooted(
590 &mut self,
591 update: &AccessibilityUpdate,
592 rooted_nodes: &mut FxHashSet<OpaqueNode>,
593 ) {
594 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
595 for (id, change) in update.tree_changes.iter() {
596 if change == &TreeChange::Removed {
597 let Some(&opaque_node) = self.id_to_opaque_node.get(id) else {
598 panic!("No opaque node found for removed node: id {id:?}");
599 };
600 assert!(
601 rooted_nodes.remove(&opaque_node),
602 "Node removed from accessibility tree wasn't rooted: id {id:?}"
603 );
604 };
605 }
606 }
607
608 fn assert_remaining_rooted_nodes_not_in_tree(&self, rooted_nodes: FxHashSet<OpaqueNode>) {
614 for leftover_node in rooted_nodes {
615 assert!(
616 !self.opaque_node_to_id.contains_key(&leftover_node),
617 "Found node removed from DOM tree but not accessibility tree: {:#x}",
618 leftover_node.0
619 );
620 }
621 }
622
623 fn get_or_create_id_for_opaque(&mut self, opaque: OpaqueNode) -> NodeId {
624 let id = self.opaque_node_to_id.entry(opaque).or_insert_with(|| {
625 static LAST_ID: AtomicU64 = AtomicU64::new(0);
626 let id = LAST_ID.fetch_add(1, atomic::Ordering::SeqCst).into();
627 self.id_to_opaque_node.insert(id, opaque);
628 id
629 });
630 *id
631 }
632
633 fn existing_id_for_opaque(&self, opaque: OpaqueNode) -> Option<NodeId> {
634 self.opaque_node_to_id.get(&opaque).cloned()
635 }
636
637 pub(crate) fn embedder_epoch(&self) -> Epoch {
638 self.embedder_epoch
639 }
640
641 pub(crate) fn take_pending_actions(&mut self) -> Vec<AccessibilityActionRequest> {
642 std::mem::take(&mut self.pending_actions)
643 }
644
645 fn assert_integrity(&self) {
649 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
650 let Some(root_node) = self.root_node.clone() else {
651 return;
652 };
653
654 let mut nodes = vec![(root_node, None)];
657 let mut seen_node_ids = FxHashSet::default();
658 while let Some((node, expected_parent)) = nodes.pop() {
659 let node = node.borrow();
660
661 assert!(
663 seen_node_ids.insert(node.id),
664 "Tree contains {:?} in multiple places",
665 node.id
666 );
667
668 node.assert_integrity(expected_parent);
669
670 let weak_node = Some(self.assert_node_for_id(&node.id).downgrade());
673 nodes.extend(node.children().cloned().zip(repeat(weak_node)));
674 }
675
676 assert_eq!(seen_node_ids, self.nodes.keys().copied().collect());
679 }
680
681 fn print(&self) {
682 let Some(root_node) = self.root_node.clone() else {
683 return;
684 };
685
686 let mut print_tree = PrintTree::new("Accessibility Tree");
687 root_node.borrow().print(&mut print_tree);
688 print_tree.end_level();
689 }
690}
691
692fn role_from_role_attribute(dom_element: &ServoLayoutElement<'_>) -> Option<Role> {
694 let role_attribute = dom_element.attribute(&ns!(), &local_name!("role"))?;
695 role_attribute
696 .as_tokens()
697 .iter()
698 .filter_map(|role_name_in_attribute| SUPPORTED_ARIA_ROLES.get(role_name_in_attribute))
699 .next()
700 .cloned()
701}
702
703fn role_from_dom_node(dom_node: &ServoLayoutNode<'_>) -> Role {
704 if let Some(dom_element) = dom_node.as_element() {
705 role_from_role_attribute(&dom_element).unwrap_or_else(|| {
706 let local_name = dom_element.local_name().to_ascii_lowercase();
707 *HTML_ELEMENT_ROLE_MAPPINGS
708 .get(&local_name)
709 .unwrap_or(&Role::GenericContainer)
710 })
711 } else if dom_node.type_id() == Some(LayoutNodeType::Text) {
712 Role::TextRun
713 } else {
714 Role::GenericContainer
715 }
716}
717
718struct AccessibilityNodeIterator<I>
719where
720 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
721{
722 next_value: Option<ArcRefCell<AccessibilityNode>>,
723 next_fn: I,
724}
725
726impl<I> AccessibilityNodeIterator<I>
727where
728 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
729{
730 fn new(next_value: Option<ArcRefCell<AccessibilityNode>>, next_fn: I) -> Self {
731 AccessibilityNodeIterator {
732 next_value,
733 next_fn,
734 }
735 }
736}
737
738impl<I> Iterator for AccessibilityNodeIterator<I>
739where
740 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
741{
742 type Item = ArcRefCell<AccessibilityNode>;
743
744 fn next(&mut self) -> Option<Self::Item> {
745 let next_value = self.next_value.take();
746 self.next_value = next_value
747 .as_ref()
748 .and_then(|node| (self.next_fn)(&node.borrow()));
749 next_value
750 }
751}
752
753impl AccessibilityNode {
754 fn new(id: NodeId) -> Self {
755 Self::new_with_role(id, Role::Unknown)
756 }
757
758 fn new_with_role(id: NodeId, role: Role) -> Self {
759 Self {
760 id,
761 accesskit_node: accesskit::Node::new(role),
762 parent_node: None,
763 child_nodes: vec![],
764 opaque_node: None,
765 scroll_offset: None,
766 dirty_state: DirtyState::empty(),
767 }
768 }
769
770 fn update_subtree<'update>(
782 &mut self,
783 ref_self: ArcRefCell<Self>,
784 damage_from_parent: AccessibilityDamage,
785 hidden: bool,
786 context: &AccessibilityContext,
787 tree: &mut AccessibilityTree,
788 update: &mut AccessibilityUpdate<'update>,
789 ) -> LocalAccessibilityDamage {
790 let mut local_damage = LocalAccessibilityDamage::empty();
791
792 let dom_node = update.take_dom_node(&self.id);
793 let damage = update.take_damage(&self.id) | damage_from_parent;
794 let mut children_changed = false;
795
796 if let Some(dom_node) = dom_node {
797 local_damage.insert(self.update_properties_and_children_from_dom_node(
798 &ref_self, &dom_node, damage, tree, update,
799 ));
800 local_damage
801 .insert(self.update_node_from_layout(&dom_node, damage, hidden, context, update));
802
803 if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) {
804 children_changed = true;
805 }
806
807 self.dirty_state -= DirtyState::HasDamage;
808 }
809
810 let layout_damage = damage & AccessibilityDamage::Layout;
811 if self.dirty_state.descendant_has_damage() || !layout_damage.is_empty() {
812 for child_node in self.children() {
813 let child_node_ref = child_node.clone();
814 let mut child_node = child_node.borrow_mut();
815 let child_local_damage = child_node.update_subtree(
816 child_node_ref,
817 layout_damage,
818 hidden || self.is_hidden(),
819 context,
820 tree,
821 update,
822 );
823 if !child_local_damage.is_empty() {
824 local_damage.insert(LocalAccessibilityDamage::SubtreeChanged);
825 if child_local_damage.contains(LocalAccessibilityDamage::VisibilityChanged) {
826 children_changed = true;
827 }
828 }
829 }
830 }
831 self.dirty_state -= DirtyState::DescendantHasDamage;
832
833 if children_changed && let Some(scroll_offset) = self.scroll_offset {
834 self.set_scroll_offset(scroll_offset, update);
836 }
837
838 local_damage.insert(self.update_node_local(local_damage, update));
839
840 if self.dirty_state.updated() {
841 update.add(self);
842 }
843
844 local_damage
845 }
846
847 fn update_ancestors(
850 &self,
851 local_damage: LocalAccessibilityDamage,
852 update: &mut AccessibilityUpdate,
853 ) {
854 if local_damage.is_empty() {
855 return;
856 }
857 for node in self.ancestors() {
858 let mut node = node.borrow_mut();
859 node.update_node_local(LocalAccessibilityDamage::SubtreeChanged, update);
860 node.dirty_state -= DirtyState::DescendantHasDamage;
861 if node.dirty_state.updated() {
862 update.add(&mut node);
863 }
864 }
865 }
866
867 fn update_properties_and_children_from_dom_node<'update>(
872 &mut self,
873 ref_self: &ArcRefCell<Self>,
874 dom_node: &ServoLayoutNode<'update>,
875 dom_damage: AccessibilityDamage,
876 tree: &mut AccessibilityTree,
877 update: &mut AccessibilityUpdate<'update>,
878 ) -> LocalAccessibilityDamage {
879 let mut local_damage = LocalAccessibilityDamage::empty();
880
881 if !dom_damage.intersects(
882 AccessibilityDamage::Node | AccessibilityDamage::Children | AccessibilityDamage::Layout,
883 ) {
884 return local_damage;
885 }
886
887 if dom_damage == AccessibilityDamage::Layout && dom_node.is_text_node() {
891 return local_damage;
892 }
893
894 update.counters.nodes_updated_from_dom += 1;
895
896 if dom_damage.intersects(AccessibilityDamage::Node) {
897 local_damage.insert(self.update_properties_from_dom_node(dom_node));
898 }
899
900 if dom_damage.intersects(AccessibilityDamage::Children | AccessibilityDamage::Layout) {
901 local_damage
904 .insert(self.update_children_from_dom_node(ref_self, dom_node, tree, update));
905 }
906
907 local_damage
908 }
909
910 fn update_children_from_dom_node<'update>(
913 &mut self,
914 ref_self: &ArcRefCell<AccessibilityNode>,
915 dom_node: &ServoLayoutNode<'update>,
916 tree: &mut AccessibilityTree,
917 update: &mut AccessibilityUpdate<'update>,
918 ) -> LocalAccessibilityDamage {
919 let mut remaining_dom_children = dom_node.flat_tree_children().peekable();
920 let mut old_child_ids = self.child_ids().iter().peekable();
921
922 let mut unchanged_count = 0usize;
925 while let Some(&old_id) = old_child_ids.peek() &&
926 let Some(dom_child) = remaining_dom_children.peek()
927 {
928 if tree.existing_id_for_opaque(dom_child.opaque()) == Some(*old_id) {
929 update.insert_dom_node(*old_id, *dom_child);
930 unchanged_count += 1;
931 old_child_ids.next();
932 remaining_dom_children.next();
933 } else {
934 break;
935 }
936 }
937
938 if old_child_ids.peek().is_none() && remaining_dom_children.peek().is_none() {
940 return LocalAccessibilityDamage::empty();
941 }
942
943 self.child_nodes.truncate(unchanged_count);
945 let mut new_child_ids = Vec::from(self.child_ids());
946 for removed_child_id in new_child_ids.split_off(unchanged_count) {
947 update.set_tree_state_change(removed_child_id, TreeChange::Removed);
948 }
949
950 let weak_self = ref_self.downgrade();
954 for dom_child in remaining_dom_children {
955 let (child_id, child_ref) = tree.get_or_create_node(&dom_child, update);
956 update.insert_dom_node(child_id, dom_child);
961
962 self.child_nodes.push(child_ref.clone());
964 new_child_ids.push(child_id);
965
966 let mut child = child_ref.borrow_mut();
967 child.parent_node = Some(weak_self.clone());
968
969 if update.is_new(&child_id) {
970 self.dirty_state |= DirtyState::DescendantHasDamage;
971 } else {
972 update.set_tree_state_change(child_id, TreeChange::PendingMove);
973 }
974
975 self.dirty_state
976 .propagate_descendant_has_damage(child.dirty_state);
977 }
978
979 self.accesskit_node.set_children(new_child_ids);
982 self.dirty_state |= DirtyState::Updated;
983
984 LocalAccessibilityDamage::SubtreeChanged
985 }
986
987 fn update_properties_from_dom_node(
989 &mut self,
990 dom_node: &ServoLayoutNode,
991 ) -> LocalAccessibilityDamage {
992 let mut local_damage = LocalAccessibilityDamage::empty();
993 local_damage.insert(self.set_role(role_from_dom_node(dom_node)));
994 if dom_node.type_id() == Some(LayoutNodeType::Text) {
995 let text_content = dom_node.text_content();
996 trace!("node text content = {text_content:?}");
997 local_damage.insert(self.set_value(&text_content));
999 }
1000
1001 local_damage
1002 }
1003
1004 fn update_node_from_layout(
1006 &mut self,
1007 dom_node: &ServoLayoutNode<'_>,
1008 layout_damage: AccessibilityDamage,
1009 hidden: bool,
1010 context: &AccessibilityContext,
1011 update: &mut AccessibilityUpdate,
1012 ) -> LocalAccessibilityDamage {
1013 let mut local_damage = LocalAccessibilityDamage::empty();
1014
1015 if hidden || !layout_damage.intersects(AccessibilityDamage::Layout) {
1017 return local_damage;
1018 }
1019
1020 if let Some(dom_element) = dom_node.as_element() &&
1021 dom_element.style_data().is_some()
1022 {
1023 let data = dom_element.element_data();
1024 let style = data.styles.primary();
1025 if style.get_display().is_none() {
1026 self.clear_bounds();
1027 local_damage.insert(self.set_hidden());
1028 } else {
1029 local_damage.insert(self.clear_hidden());
1030 }
1031 }
1032
1033 if self.is_hidden() {
1034 return local_damage;
1035 }
1036
1037 update.counters.nodes_updated_bounds += 1;
1038
1039 let bounds = process_box_area_request(
1046 context.layout_thread,
1047 context.stacking_context_tree,
1048 *dom_node,
1049 BoxAreaType::Border,
1050 BoxAreaInclusion::Inlines,
1051 )
1052 .map(au_rect_to_accesskit_rect);
1053
1054 match bounds {
1067 Some(bounds) => self.set_bounds(bounds),
1068 None => self.clear_bounds(),
1069 }
1070 local_damage
1071 }
1072
1073 fn update_node_local(
1078 &mut self,
1079 local_damage: LocalAccessibilityDamage,
1080 update: &mut AccessibilityUpdate,
1081 ) -> LocalAccessibilityDamage {
1082 let mut new_damage = LocalAccessibilityDamage::empty();
1083 if local_damage.is_empty() {
1084 return new_damage;
1085 }
1086 update.counters.nodes_updated_from_tree += 1;
1087
1088 if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) ||
1089 local_damage.contains(LocalAccessibilityDamage::RoleChanged)
1090 {
1091 if let Some(text) = self.label_from_descendants() {
1092 new_damage.insert(self.set_label(text.as_str()));
1093 } else {
1094 new_damage.insert(self.clear_label());
1095 }
1096 }
1097
1098 new_damage
1099 }
1100
1101 fn label_from_descendants(&self) -> Option<String> {
1102 if !NAME_FROM_CONTENTS_ROLES.contains(&self.role()) {
1103 return None;
1104 }
1105 let mut children = VecDeque::from_iter(self.children().cloned());
1106 let mut text = String::new();
1107 while let Some(child) = children.pop_front() {
1108 let child = child.borrow();
1109 if child.is_hidden() {
1110 continue;
1111 }
1112 match child.role() {
1113 Role::TextRun => {
1114 if let Some(child_text) = child.value() {
1115 text.push_str(child_text);
1116 }
1117 },
1118 _ => {
1119 for node in child.children().rev() {
1120 children.push_front(node.clone());
1121 }
1122 },
1123 }
1124 }
1125 Some(text.trim().to_owned())
1126 }
1127
1128 fn print(&self, print_tree: &mut PrintTree) {
1129 if self.child_nodes.is_empty() {
1130 print_tree.add_item(format!("{self:?}"));
1131 return;
1132 }
1133
1134 print_tree.new_level(format!("{self:?}"));
1135
1136 for child in self.children() {
1137 child.borrow().print(print_tree);
1138 }
1139 print_tree.end_level();
1140 }
1141
1142 fn parent(&self) -> Option<ArcRefCell<AccessibilityNode>> {
1143 self.parent_node.as_ref().and_then(|weak| weak.upgrade())
1144 }
1145
1146 fn children(&self) -> impl DoubleEndedIterator<Item = &ArcRefCell<AccessibilityNode>> {
1147 self.child_nodes.iter()
1148 }
1149
1150 fn ancestors(&self) -> impl Iterator<Item = ArcRefCell<AccessibilityNode>> {
1151 AccessibilityNodeIterator::new(self.parent(), |node| node.parent_node.clone()?.upgrade())
1152 }
1153
1154 fn child_ids(&self) -> &[NodeId] {
1155 self.accesskit_node.children()
1156 }
1157
1158 fn set_scroll_offset(&mut self, offset: LayoutVector2D, update: &mut AccessibilityUpdate) {
1159 self.scroll_offset = Some(offset);
1160 let transform = scroll_offset_to_affine(offset);
1161 for child in self.children() {
1162 let mut child = child.borrow_mut();
1163 if child.is_hidden() {
1164 continue;
1165 }
1166 child.set_transform(transform);
1167 if child.dirty_state.updated() {
1168 update.add(&mut child);
1169 }
1170 }
1171 }
1172
1173 fn role(&self) -> Role {
1176 self.accesskit_node.role()
1177 }
1178
1179 fn set_role(&mut self, role: Role) -> LocalAccessibilityDamage {
1180 if role == self.accesskit_node.role() {
1181 return LocalAccessibilityDamage::empty();
1182 }
1183 self.accesskit_node.set_role(role);
1184 self.dirty_state |= DirtyState::Updated;
1185 LocalAccessibilityDamage::RoleChanged
1186 }
1187
1188 fn label(&self) -> Option<&str> {
1189 self.accesskit_node.label()
1190 }
1191
1192 fn set_label(&mut self, label: &str) -> LocalAccessibilityDamage {
1193 if Some(label) == self.accesskit_node.label() {
1194 return LocalAccessibilityDamage::empty();
1195 }
1196 self.accesskit_node.set_label(label);
1197 self.dirty_state |= DirtyState::Updated;
1198 LocalAccessibilityDamage::TextChanged
1199 }
1200
1201 fn clear_label(&mut self) -> LocalAccessibilityDamage {
1202 if self.accesskit_node.label().is_none() {
1203 return LocalAccessibilityDamage::empty();
1204 }
1205 self.accesskit_node.clear_label();
1206 self.dirty_state |= DirtyState::Updated;
1207 LocalAccessibilityDamage::TextChanged
1208 }
1209
1210 fn html_tag(&self) -> Option<&str> {
1211 self.accesskit_node.html_tag()
1212 }
1213
1214 fn set_html_tag(&mut self, html_tag: &str) {
1215 if Some(html_tag) == self.accesskit_node.html_tag() {
1216 return;
1217 }
1218 self.accesskit_node.set_html_tag(html_tag);
1219 self.dirty_state |= DirtyState::Updated;
1220 }
1221
1222 fn value(&self) -> Option<&str> {
1223 self.accesskit_node.value()
1224 }
1225
1226 fn set_value(&mut self, value: &str) -> LocalAccessibilityDamage {
1227 if Some(value) == self.accesskit_node.value() {
1228 return LocalAccessibilityDamage::empty();
1229 }
1230 self.accesskit_node.set_value(value);
1231 self.dirty_state |= DirtyState::Updated;
1232 LocalAccessibilityDamage::TextChanged
1233 }
1234
1235 fn is_hidden(&self) -> bool {
1236 self.accesskit_node.is_hidden()
1237 }
1238
1239 fn set_hidden(&mut self) -> LocalAccessibilityDamage {
1240 if self.is_hidden() {
1241 return LocalAccessibilityDamage::empty();
1242 }
1243 self.accesskit_node.set_hidden();
1244 self.dirty_state |= DirtyState::Updated;
1245 LocalAccessibilityDamage::VisibilityChanged
1246 }
1247
1248 fn clear_hidden(&mut self) -> LocalAccessibilityDamage {
1249 if !self.is_hidden() {
1250 return LocalAccessibilityDamage::empty();
1251 }
1252
1253 self.accesskit_node.clear_hidden();
1254 self.dirty_state |= DirtyState::Updated;
1255 LocalAccessibilityDamage::VisibilityChanged
1256 }
1257
1258 fn bounds(&self) -> Option<accesskit::Rect> {
1259 self.accesskit_node.bounds()
1260 }
1261
1262 fn set_bounds(&mut self, bounds: accesskit::Rect) {
1263 if Some(bounds) == self.accesskit_node.bounds() {
1264 return;
1265 }
1266 self.accesskit_node.set_bounds(bounds);
1267 self.dirty_state |= DirtyState::Updated;
1268 }
1269
1270 fn clear_bounds(&mut self) {
1271 if self.accesskit_node.bounds().is_none() {
1272 return;
1273 }
1274 self.accesskit_node.clear_bounds();
1275 self.dirty_state |= DirtyState::Updated;
1276 }
1277
1278 fn set_transform(&mut self, transform: Affine) {
1279 if self.accesskit_node.transform() == Some(&transform) {
1283 return;
1284 }
1285 if transform == Affine::IDENTITY {
1286 self.clear_transform();
1287 return;
1288 }
1289 self.accesskit_node.set_transform(transform);
1290 self.dirty_state |= DirtyState::Updated;
1291 }
1292
1293 fn clear_transform(&mut self) {
1294 if self.accesskit_node.transform().is_none() {
1295 return;
1296 }
1297 self.accesskit_node.clear_transform();
1298 self.dirty_state |= DirtyState::Updated;
1299 }
1300
1301 fn assert_integrity(&self, expected_parent: Option<WeakRefCell<AccessibilityNode>>) {
1302 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
1303
1304 if let Some(actual_parent) = &self.parent_node {
1305 let expected = expected_parent.expect("Actual parent but no expected parent");
1306 let expected = expected.upgrade().expect("Expected parent was dropped");
1307 let actual = actual_parent.upgrade().expect("Actual parent was dropped");
1308 assert!(actual.ptr_eq(&expected));
1309 } else {
1310 assert!(
1311 expected_parent.is_none(),
1312 "Expected parent but no actual parent"
1313 );
1314 }
1315
1316 assert!(
1317 self.dirty_state.is_empty(),
1318 "{self:?} has dirty state {:?}",
1319 self.dirty_state
1320 );
1321
1322 let children_ids: Vec<_> = self.children().map(|child| child.borrow().id).collect();
1323 assert_eq!(
1324 children_ids,
1325 self.child_ids(),
1326 "children() IDs didn't match child_ids() for {self:?}"
1327 );
1328 }
1329}
1330
1331impl Debug for AccessibilityNode {
1332 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1333 if self.is_hidden() {
1334 write!(f, "[hidden] ")?;
1335 }
1336 write!(f, "{:?}: {:?}", self.id, self.role())?;
1337 if let Some(html_tag) = self.html_tag() {
1338 write!(f, " (html_tag: {html_tag:?})")?;
1339 }
1340 if let Some(label) = self.label() {
1341 write!(f, "\nlabel: {label:?}")?;
1342 }
1343 if let Some(bounds) = self.bounds() {
1344 write!(f, "\nbounds: {bounds:?}")?;
1345 }
1346 if !self.child_ids().is_empty() {
1347 write!(f, "\nchildren: {:?}", self.child_ids())?;
1348 }
1349 Ok(())
1350 }
1351}
1352
1353impl<'update> AccessibilityUpdate<'update> {
1354 fn new(dom_damage: AccessibilityDamageMap<'update>, tree: &AccessibilityTree) -> Self {
1355 let damage_map = dom_damage
1356 .iter()
1357 .filter_map(|(&opaque, &(_dom_node, damage))| {
1358 let id = tree.existing_id_for_opaque(opaque)?;
1359 Some((id, damage))
1360 })
1361 .collect();
1362 let dom_node_map = dom_damage
1363 .into_iter()
1364 .filter_map(|(opaque, (dom_node, _damage))| {
1365 let id = tree.existing_id_for_opaque(opaque)?;
1366 Some((id, dom_node))
1367 })
1368 .collect();
1369 Self {
1370 changed_nodes: FxHashSet::default(),
1371 accesskit_tree: None,
1372 tree_changes: FxHashMap::default(),
1373 counters: UpdateCounters::default(),
1374 damage_map,
1375 dom_node_map: RefCell::new(dom_node_map),
1376 }
1377 }
1378
1379 fn add(&mut self, node: &mut AccessibilityNode) {
1380 self.changed_nodes.insert(node.id);
1381 node.dirty_state -= DirtyState::Updated;
1382 }
1383
1384 fn set_tree_state_change(&mut self, node_id: NodeId, change: TreeChange) {
1385 let old_change = self.tree_changes.get(&node_id);
1386
1387 assert!(
1388 change != TreeChange::Moved,
1389 "Incoming change must never be Moved"
1390 );
1391
1392 let resolved_change = old_change
1393 .map(|old_change| match (old_change, change) {
1394 (TreeChange::PendingMove, TreeChange::Removed) => TreeChange::Moved,
1395 (TreeChange::Removed, TreeChange::PendingMove) => TreeChange::Moved,
1396 _ => {
1397 unreachable!("Logically impossible state change: {old_change:?} → {change:?}")
1398 },
1399 })
1400 .unwrap_or(change);
1401
1402 self.tree_changes.insert(node_id, resolved_change);
1403 }
1404
1405 fn is_new(&mut self, node_id: &NodeId) -> bool {
1406 self.tree_changes.get(node_id) == Some(&TreeChange::New)
1407 }
1408
1409 fn finalize(
1414 mut self,
1415 tree: &mut AccessibilityTree,
1416 rooted_nodes_for_integrity_check: Option<FxHashSet<OpaqueNode>>,
1417 action_requests: Vec<ActionRequest>,
1418 ) -> (Option<accesskit::TreeUpdate>, UpdateCounters) {
1419 let mut tree_update = None;
1420 let mut counters = std::mem::take(&mut self.counters);
1421 if !self.changed_nodes.is_empty() || self.accesskit_tree.is_some() {
1422 let changed_nodes = std::mem::take(&mut self.changed_nodes);
1423 let accesskit_tree = std::mem::take(&mut self.accesskit_tree);
1424
1425 tree.drop_removed_nodes(self, rooted_nodes_for_integrity_check);
1426
1427 let changed_nodes: Vec<_> = changed_nodes
1428 .into_iter()
1429 .filter_map(|id| Some((id, tree.node_for_id(id)?.borrow().accesskit_node.clone())))
1430 .collect();
1431
1432 counters.nodes_in_tree_update = changed_nodes.len().try_into().unwrap_or_default();
1433
1434 tree_update = Some(accesskit::TreeUpdate {
1435 nodes: changed_nodes,
1437 tree: accesskit_tree,
1438 focus: NodeId(1),
1439 tree_id: tree.tree_id,
1440 });
1441 } else {
1442 assert!(self.tree_changes.is_empty());
1443 }
1444
1445 for action in action_requests {
1446 assert_eq!(
1447 action.target_tree, tree.tree_id,
1448 "Got action with wrong tree ID: {action:?}"
1449 );
1450 let Some(&opaque) = tree.id_to_opaque_node.get(&action.target_node) else {
1451 continue;
1453 };
1454 let dom_action_request = AccessibilityActionRequest {
1455 action: action.action,
1456 target: opaque,
1457 data: action.data,
1458 };
1459 tree.pending_actions.push(dom_action_request);
1460 }
1461
1462 (tree_update, counters)
1463 }
1464
1465 fn clear_damage(&mut self) {
1466 self.damage_map.clear();
1467 }
1468
1469 fn insert_damage(&mut self, node_id: NodeId, damage: AccessibilityDamage) {
1470 self.damage_map.insert(node_id, damage);
1471 }
1472
1473 fn insert_dom_node(&self, node_id: NodeId, dom_node: ServoLayoutNode<'update>) {
1474 self.dom_node_map.borrow_mut().insert(node_id, dom_node);
1475 }
1476
1477 fn take_damage(&mut self, node_id: &NodeId) -> AccessibilityDamage {
1478 self.damage_map
1479 .remove(node_id)
1480 .unwrap_or(AccessibilityDamage::empty())
1481 }
1482
1483 fn take_dom_node(&mut self, node_id: &NodeId) -> Option<ServoLayoutNode<'update>> {
1484 self.dom_node_map.borrow_mut().remove(node_id)
1485 }
1486
1487 #[expect(unsafe_code)]
1488 fn collect_dom_node_ancestors(&self, node_id: &NodeId, tree: &AccessibilityTree) {
1489 let mut dom_node_map = self.dom_node_map.borrow_mut();
1490 let dom_node = dom_node_map
1491 .get(node_id)
1492 .expect("collect_dom_node_ancestors should be called for a known DOM node");
1493 let mut parent = unsafe { dom_node.dangerous_flat_tree_parent() };
1494 while let Some(node) = parent {
1495 if let Some(node_id) = tree.existing_id_for_opaque(node.opaque()) {
1496 dom_node_map.insert(node_id, node);
1497 }
1498 parent = unsafe { node.dangerous_flat_tree_parent() };
1499 }
1500 }
1501}
1502
1503impl DirtyState {
1504 fn updated(&self) -> bool {
1505 self.contains(DirtyState::Updated)
1506 }
1507
1508 fn descendant_has_damage(&self) -> bool {
1509 self.contains(DirtyState::DescendantHasDamage)
1510 }
1511
1512 fn propagate_descendant_has_damage(&mut self, child_dirty_state: DirtyState) {
1513 if child_dirty_state.self_or_descendant_has_damage() {
1514 self.insert(DirtyState::DescendantHasDamage)
1515 }
1516 }
1517
1518 fn self_or_descendant_has_damage(&self) -> bool {
1519 self.intersects(DirtyState::HasDamage | DirtyState::DescendantHasDamage)
1520 }
1521}
1522
1523#[cfg(test)]
1524#[test]
1525fn test_accessibility_update_add_some_nodes_twice() {
1526 let mut tree = AccessibilityTree::new(accesskit::TreeId::ROOT, Epoch::default());
1527 let mut root_update = AccessibilityUpdate::new(AccessibilityDamageMap::default(), &tree);
1528
1529 let root_node = tree.get_or_create_node_with_id(NodeId(2), &mut root_update);
1530 tree.root_node = Some(root_node.clone());
1531
1532 let nodes: Vec<_> = [
1533 (3, Role::GenericContainer),
1534 (4, Role::Heading),
1535 (5, Role::Paragraph),
1536 ]
1537 .into_iter()
1538 .map(|(id, role)| {
1539 let id = NodeId(id);
1540 let node = tree.get_or_create_node_with_id(id, &mut root_update);
1541 node.borrow_mut().set_role(role);
1542 (id, node)
1543 })
1544 .collect();
1545
1546 {
1547 let (child_node_ids, child_nodes): (Vec<_>, Vec<_>) = nodes.iter().cloned().unzip();
1548 let mut root_node = root_node.borrow_mut();
1549 root_node.accesskit_node.set_children(child_node_ids);
1550 root_node.child_nodes = child_nodes;
1551 }
1552
1553 let mut update = AccessibilityUpdate::new(AccessibilityDamageMap::default(), &tree);
1554
1555 {
1556 let node_3 = tree.assert_node_for_id(&NodeId(3));
1557 let mut node_3 = node_3.borrow_mut();
1558 let node_4 = tree.assert_node_for_id(&NodeId(4));
1559 let mut node_4 = node_4.borrow_mut();
1560 let node_5 = tree.assert_node_for_id(&NodeId(5));
1561 let mut node_5 = node_5.borrow_mut();
1562
1563 update.add(&mut node_5);
1564 update.add(&mut node_3);
1565 update.add(&mut node_4);
1566 update.add(&mut node_4);
1567
1568 node_3.set_role(Role::ScrollView);
1569 update.add(&mut node_3);
1570 }
1571
1572 let (tree_update, _) = update.finalize(&mut tree, None, vec![]);
1573 let mut tree_update = tree_update.expect("finalize should produce a tree update");
1574 tree_update.nodes.sort_by_key(|(node_id, _node)| *node_id);
1575 assert_eq!(
1576 tree_update,
1577 accesskit::TreeUpdate {
1578 nodes: vec![
1579 (NodeId(3), accesskit::Node::new(Role::ScrollView)),
1580 (NodeId(4), accesskit::Node::new(Role::Heading)),
1581 (NodeId(5), accesskit::Node::new(Role::Paragraph)),
1582 ],
1583 tree: None,
1584 tree_id: accesskit::TreeId::ROOT,
1585 focus: NodeId(1),
1586 }
1587 );
1588}
1589
1590static HTML_ELEMENT_ROLE_MAPPINGS: LazyLock<FxHashMap<LocalName, Role>> = LazyLock::new(|| {
1591 [
1592 (local_name!("a"), Role::Link),
1594 (local_name!("article"), Role::Article),
1595 (local_name!("aside"), Role::Complementary),
1596 (local_name!("body"), Role::RootWebArea),
1597 (local_name!("footer"), Role::ContentInfo),
1598 (local_name!("h1"), Role::Heading),
1599 (local_name!("h2"), Role::Heading),
1600 (local_name!("h3"), Role::Heading),
1601 (local_name!("h4"), Role::Heading),
1602 (local_name!("h5"), Role::Heading),
1603 (local_name!("h6"), Role::Heading),
1604 (local_name!("header"), Role::Banner),
1605 (local_name!("hr"), Role::Splitter),
1606 (local_name!("main"), Role::Main),
1607 (local_name!("nav"), Role::Navigation),
1608 (local_name!("p"), Role::Paragraph),
1609 ]
1610 .into_iter()
1611 .collect()
1612});
1613
1614static SUPPORTED_ARIA_ROLES: LazyLock<FxHashMap<Atom, Role>> = LazyLock::new(|| {
1623 [
1624 (Atom::from("alert"), Role::Alert),
1625 (Atom::from("banner"), Role::Banner),
1626 (Atom::from("blockquote"), Role::Blockquote),
1627 (Atom::from("caption"), Role::Caption),
1628 (Atom::from("code"), Role::Code),
1629 (Atom::from("complementary"), Role::Complementary),
1630 (Atom::from("contentinfo"), Role::ContentInfo),
1631 (Atom::from("definition"), Role::Definition),
1632 (Atom::from("deletion"), Role::ContentDeletion),
1633 (Atom::from("directory"), Role::Unknown),
1634 (Atom::from("document"), Role::Document),
1635 (Atom::from("emphasis"), Role::Emphasis),
1636 (Atom::from("feed"), Role::Feed),
1637 (Atom::from("figure"), Role::Figure),
1638 (Atom::from("generic"), Role::GenericContainer),
1639 (Atom::from("insertion"), Role::ContentInsertion),
1640 (Atom::from("list"), Role::List),
1641 (Atom::from("log"), Role::Log),
1642 (Atom::from("main"), Role::Main),
1643 (Atom::from("math"), Role::Math),
1644 (Atom::from("navigation"), Role::Navigation),
1645 (Atom::from("none"), Role::GenericContainer),
1646 (Atom::from("note"), Role::Note),
1647 (Atom::from("paragraph"), Role::Paragraph),
1648 (Atom::from("presentation"), Role::GenericContainer),
1649 (Atom::from("rowgroup"), Role::RowGroup),
1650 (Atom::from("search"), Role::Search),
1651 (Atom::from("status"), Role::Status),
1652 (Atom::from("strong"), Role::Strong),
1653 (Atom::from("term"), Role::Term),
1656 (Atom::from("time"), Role::Time),
1657 (Atom::from("timer"), Role::Timer),
1658 ]
1659 .into_iter()
1660 .collect()
1661});
1662
1663static NAME_FROM_CONTENTS_ROLES: LazyLock<FxHashSet<Role>> =
1665 LazyLock::new(|| [(Role::Heading), (Role::Link)].into_iter().collect());