1use std::collections::VecDeque;
6use std::fmt::Debug;
7use std::sync::LazyLock;
8
9use accesskit::{Affine, NodeId, Role};
10use app_units::Au;
11use bitflags::bitflags;
12use euclid::Rect;
13use layout_api::{
14 ARIA_ROLE_TO_ACCESSKIT_ROLE, AccessibilityDamage, BoxAreaType, LayoutElement, LayoutNode,
15 LayoutNodeType, SUPPORTED_ARIA_ROLES,
16};
17use log::trace;
18use num_traits::ToPrimitive;
19use rustc_hash::{FxHashMap, FxHashSet};
20use script::layout_dom::{ServoLayoutElement, ServoLayoutNode};
21use servo_base::print_tree::PrintTree;
22use servo_config::pref;
23use style::dom::{NodeInfo, OpaqueNode};
24use style_traits::CSSPixel;
25use web_atoms::{LocalName, local_name, ns};
26use webrender_api::units::LayoutVector2D;
27
28use super::accessibility_tree::{AccessibilityTree, AccessibilityUpdate, TreeChange};
29use crate::ArcRefCell;
30use crate::accessibility::AccessibilityContext;
31use crate::cell::WeakRefCell;
32use crate::query::{BoxAreaInclusion, process_box_area_request};
33
34fn au_rect_to_accesskit_rect(rect: Rect<Au, CSSPixel>) -> accesskit::Rect {
36 accesskit::Rect::new(
37 rect.min_x().to_f64_px(),
38 rect.min_y().to_f64_px(),
39 rect.max_x().to_f64_px(),
40 rect.max_y().to_f64_px(),
41 )
42}
43
44fn scroll_offset_to_affine(layout_vector: LayoutVector2D) -> Affine {
45 Affine::translate((
46 -layout_vector.x.to_f64().unwrap_or(0.),
47 -layout_vector.y.to_f64().unwrap_or(0.),
48 ))
49}
50
51bitflags! {
52 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
55 pub(super) struct DirtyState : u16 {
56 const DescendantHasDamage = 0b0001;
58 const HasDamage = 0b0010;
60 const Updated = 0b0100;
62 }
63}
64
65bitflags! {
66 #[derive(Clone, Copy, Default, Debug, Eq, PartialEq)]
70 pub(super) struct LocalAccessibilityDamage: u16 {
71 const SubtreeChanged = 0b0001;
73 const RoleChanged = 0b0010;
75 const TextChanged = 0b0100;
77 const VisibilityChanged = 0b1000;
79 }
80}
81
82pub(super) struct AccessibilityNode {
83 id: NodeId,
87 accesskit_node: accesskit::Node,
90 parent_node: Option<WeakRefCell<AccessibilityNode>>,
92 child_nodes: Vec<ArcRefCell<AccessibilityNode>>,
94 opaque_node: Option<OpaqueNode>,
98 scroll_offset: Option<LayoutVector2D>,
101 dirty_state: DirtyState,
103}
104
105impl AccessibilityNode {
106 pub(super) fn new(id: NodeId) -> Self {
107 Self::new_with_role(id, Role::Unknown)
108 }
109
110 pub(super) fn new_with_role(id: NodeId, role: Role) -> Self {
111 Self {
112 id,
113 accesskit_node: accesskit::Node::new(role),
114 parent_node: None,
115 child_nodes: vec![],
116 opaque_node: None,
117 scroll_offset: None,
118 dirty_state: DirtyState::empty(),
119 }
120 }
121
122 pub(super) fn update_subtree<'update>(
134 &mut self,
135 ref_self: ArcRefCell<Self>,
136 damage_from_parent: AccessibilityDamage,
137 hidden: bool,
138 context: &AccessibilityContext,
139 tree: &mut AccessibilityTree,
140 update: &mut AccessibilityUpdate<'update>,
141 ) -> LocalAccessibilityDamage {
142 let mut local_damage = LocalAccessibilityDamage::empty();
143
144 let dom_node = update.take_dom_node(&self.id);
145 let damage = update.take_damage(&self.id) | damage_from_parent;
146 let mut children_changed = false;
147
148 if let Some(dom_node) = dom_node {
149 local_damage.insert(self.update_properties_and_children_from_dom_node(
150 &ref_self, &dom_node, damage, tree, update,
151 ));
152 local_damage
153 .insert(self.update_node_from_layout(&dom_node, damage, hidden, context, update));
154
155 if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) {
156 children_changed = true;
157 }
158
159 self.dirty_state -= DirtyState::HasDamage;
160 }
161
162 let layout_damage = damage & AccessibilityDamage::Layout;
163 if self.dirty_state.descendant_has_damage() || !layout_damage.is_empty() {
164 for child_node in self.children() {
165 let child_node_ref = child_node.clone();
166 let mut child_node = child_node.borrow_mut();
167 let child_local_damage = child_node.update_subtree(
168 child_node_ref,
169 layout_damage,
170 hidden || self.is_hidden(),
171 context,
172 tree,
173 update,
174 );
175 if !child_local_damage.is_empty() {
176 local_damage.insert(LocalAccessibilityDamage::SubtreeChanged);
177 if child_local_damage.contains(LocalAccessibilityDamage::VisibilityChanged) {
178 children_changed = true;
179 }
180 }
181 }
182 }
183 self.dirty_state -= DirtyState::DescendantHasDamage;
184
185 if children_changed && let Some(scroll_offset) = self.scroll_offset {
186 self.set_scroll_offset(scroll_offset, update);
188 }
189
190 local_damage.insert(self.update_node_local(local_damage, update));
191
192 if self.dirty_state.updated() {
193 update.add(self);
194 }
195
196 local_damage
197 }
198
199 pub(super) fn update_ancestors(
202 &self,
203 local_damage: LocalAccessibilityDamage,
204 update: &mut AccessibilityUpdate,
205 ) {
206 if local_damage.is_empty() {
207 return;
208 }
209 for node in self.ancestors() {
210 let mut node = node.borrow_mut();
211 node.update_node_local(LocalAccessibilityDamage::SubtreeChanged, update);
212 node.dirty_state -= DirtyState::DescendantHasDamage;
213 if node.dirty_state.updated() {
214 update.add(&mut node);
215 }
216 }
217 }
218
219 pub(super) fn initialize_from_dom_node(&mut self, dom_node: &ServoLayoutNode) {
220 self.opaque_node = Some(dom_node.opaque());
221 if let Some(dom_element) = dom_node.as_element() {
222 let local_name = dom_element.local_name().to_ascii_lowercase();
223 self.set_html_tag(&local_name);
224 }
225 }
226
227 fn update_properties_and_children_from_dom_node<'update>(
232 &mut self,
233 ref_self: &ArcRefCell<Self>,
234 dom_node: &ServoLayoutNode<'update>,
235 dom_damage: AccessibilityDamage,
236 tree: &mut AccessibilityTree,
237 update: &mut AccessibilityUpdate<'update>,
238 ) -> LocalAccessibilityDamage {
239 let mut local_damage = LocalAccessibilityDamage::empty();
240
241 if !dom_damage.intersects(
242 AccessibilityDamage::Node | AccessibilityDamage::Children | AccessibilityDamage::Layout,
243 ) {
244 return local_damage;
245 }
246
247 if dom_damage == AccessibilityDamage::Layout && dom_node.is_text_node() {
251 return local_damage;
252 }
253
254 update.counters.nodes_updated_from_dom += 1;
255
256 if dom_damage.intersects(AccessibilityDamage::Node) {
257 local_damage.insert(self.update_properties_from_dom_node(dom_node));
258 }
259
260 if dom_damage.intersects(AccessibilityDamage::Children | AccessibilityDamage::Layout) {
261 local_damage
264 .insert(self.update_children_from_dom_node(ref_self, dom_node, tree, update));
265 }
266
267 local_damage
268 }
269
270 fn update_children_from_dom_node<'update>(
273 &mut self,
274 ref_self: &ArcRefCell<AccessibilityNode>,
275 dom_node: &ServoLayoutNode<'update>,
276 tree: &mut AccessibilityTree,
277 update: &mut AccessibilityUpdate<'update>,
278 ) -> LocalAccessibilityDamage {
279 let mut remaining_dom_children = dom_node.flat_tree_children().peekable();
280 let mut old_child_ids = self.child_ids().iter().peekable();
281
282 let mut unchanged_count = 0usize;
285 while let Some(&old_id) = old_child_ids.peek() &&
286 let Some(dom_child) = remaining_dom_children.peek()
287 {
288 if tree.existing_id_for_opaque(dom_child.opaque()) == Some(*old_id) {
289 update.insert_dom_node(*old_id, *dom_child);
290 unchanged_count += 1;
291 old_child_ids.next();
292 remaining_dom_children.next();
293 } else {
294 break;
295 }
296 }
297
298 if old_child_ids.peek().is_none() && remaining_dom_children.peek().is_none() {
300 return LocalAccessibilityDamage::empty();
301 }
302
303 self.child_nodes.truncate(unchanged_count);
305 let mut new_child_ids = Vec::from(self.child_ids());
306 for removed_child_id in new_child_ids.split_off(unchanged_count) {
307 update.set_tree_state_change(removed_child_id, TreeChange::Removed);
308 }
309
310 let weak_self = ref_self.downgrade();
314 for dom_child in remaining_dom_children {
315 let (child_id, child_ref) = tree.get_or_create_node(&dom_child, update);
316 update.insert_dom_node(child_id, dom_child);
321
322 self.child_nodes.push(child_ref.clone());
324 new_child_ids.push(child_id);
325
326 let mut child = child_ref.borrow_mut();
327 child.parent_node = Some(weak_self.clone());
328
329 if update.is_new(&child_id) {
330 self.dirty_state |= DirtyState::DescendantHasDamage;
331 } else {
332 update.set_tree_state_change(child_id, TreeChange::PendingMove);
333 }
334
335 self.dirty_state
336 .propagate_descendant_has_damage(child.dirty_state);
337 }
338
339 self.accesskit_node.set_children(new_child_ids);
342 self.dirty_state |= DirtyState::Updated;
343
344 LocalAccessibilityDamage::SubtreeChanged
345 }
346
347 fn update_properties_from_dom_node(
349 &mut self,
350 dom_node: &ServoLayoutNode,
351 ) -> LocalAccessibilityDamage {
352 let mut local_damage = LocalAccessibilityDamage::empty();
353 local_damage.insert(self.set_role(role_from_dom_node(dom_node)));
354 if dom_node.type_id() == Some(LayoutNodeType::Text) {
355 let text_content = dom_node.text_content();
356 trace!("node text content = {text_content:?}");
357 local_damage.insert(self.set_value(&text_content));
359 }
360
361 local_damage
362 }
363
364 fn update_node_from_layout(
366 &mut self,
367 dom_node: &ServoLayoutNode<'_>,
368 layout_damage: AccessibilityDamage,
369 hidden: bool,
370 context: &AccessibilityContext,
371 update: &mut AccessibilityUpdate,
372 ) -> LocalAccessibilityDamage {
373 let mut local_damage = LocalAccessibilityDamage::empty();
374
375 if hidden || !layout_damage.intersects(AccessibilityDamage::Layout) {
377 return local_damage;
378 }
379
380 if let Some(dom_element) = dom_node.as_element() &&
381 dom_element.style_data().is_some()
382 {
383 let data = dom_element.element_data();
384 let style = data.styles.primary();
385 if style.get_display().is_none() {
386 self.clear_bounds();
387 local_damage.insert(self.set_hidden());
388 } else {
389 local_damage.insert(self.clear_hidden());
390 }
391 }
392
393 if self.is_hidden() {
394 return local_damage;
395 }
396
397 update.counters.nodes_updated_bounds += 1;
398
399 let bounds = process_box_area_request(
406 context.layout_thread,
407 context.stacking_context_tree,
408 *dom_node,
409 BoxAreaType::Border,
410 BoxAreaInclusion::Inlines,
411 )
412 .map(au_rect_to_accesskit_rect);
413
414 match bounds {
427 Some(bounds) => self.set_bounds(bounds),
428 None => self.clear_bounds(),
429 }
430 local_damage
431 }
432
433 fn update_node_local(
438 &mut self,
439 local_damage: LocalAccessibilityDamage,
440 update: &mut AccessibilityUpdate,
441 ) -> LocalAccessibilityDamage {
442 let mut new_damage = LocalAccessibilityDamage::empty();
443 if local_damage.is_empty() {
444 return new_damage;
445 }
446 update.counters.nodes_updated_from_tree += 1;
447
448 if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) ||
449 local_damage.contains(LocalAccessibilityDamage::RoleChanged)
450 {
451 if let Some(text) = self.label_from_descendants() {
452 new_damage.insert(self.set_label(text.as_str()));
453 } else {
454 new_damage.insert(self.clear_label());
455 }
456 }
457
458 new_damage
459 }
460
461 fn label_from_descendants(&self) -> Option<String> {
462 if !NAME_FROM_CONTENTS_ROLES.contains(&self.role()) {
463 return None;
464 }
465 let mut children = VecDeque::from_iter(self.children().cloned());
466 let mut text = String::new();
467 while let Some(child) = children.pop_front() {
468 let child = child.borrow();
469 if child.is_hidden() {
470 continue;
471 }
472 match child.role() {
473 Role::TextRun => {
474 if let Some(child_text) = child.value() {
475 text.push_str(child_text);
476 }
477 },
478 _ => {
479 for node in child.children().rev() {
480 children.push_front(node.clone());
481 }
482 },
483 }
484 }
485 Some(text.trim().to_owned())
486 }
487
488 pub(super) fn print(&self, print_tree: &mut PrintTree, tree: &AccessibilityTree) {
489 let focused = if tree.focused_node_id() == Some(self.id) {
490 "[focused] "
491 } else {
492 ""
493 };
494 let node_string = format!("{focused}{self:?}");
495
496 if self.child_nodes.is_empty() {
497 print_tree.add_item(node_string);
498 return;
499 }
500
501 print_tree.new_level(node_string);
502
503 for child in self.children() {
504 child.borrow().print(print_tree, tree);
505 }
506 print_tree.end_level();
507 }
508
509 pub(super) fn clone_accesskit_node(&self) -> accesskit::Node {
510 self.accesskit_node.clone()
511 }
512
513 pub(super) fn id(&self) -> NodeId {
514 self.id
515 }
516
517 pub(super) fn opaque_node(&self) -> Option<OpaqueNode> {
518 self.opaque_node
519 }
520
521 pub(super) fn dirty_state(&self) -> DirtyState {
522 self.dirty_state
523 }
524
525 pub(super) fn add_dirty_state(&mut self, state: DirtyState) {
526 self.dirty_state |= state;
527 }
528
529 pub(super) fn remove_dirty_state(&mut self, state: DirtyState) {
530 self.dirty_state -= state;
531 }
532
533 pub(super) fn parent(&self) -> Option<ArcRefCell<AccessibilityNode>> {
534 self.parent_node.as_ref().and_then(|weak| weak.upgrade())
535 }
536
537 pub(super) fn children(
538 &self,
539 ) -> impl DoubleEndedIterator<Item = &ArcRefCell<AccessibilityNode>> {
540 self.child_nodes.iter()
541 }
542
543 #[cfg(test)]
544 pub(super) fn set_children_for_testing(
545 &mut self,
546 child_ids: Vec<NodeId>,
547 child_nodes: Vec<ArcRefCell<AccessibilityNode>>,
548 ) {
549 self.accesskit_node.set_children(child_ids);
550 self.child_nodes = child_nodes;
551 }
552
553 pub(super) fn ancestors(&self) -> impl Iterator<Item = ArcRefCell<AccessibilityNode>> {
554 AccessibilityNodeIterator::new(self.parent(), |node| node.parent_node.clone()?.upgrade())
555 }
556
557 pub(super) fn child_ids(&self) -> &[NodeId] {
558 self.accesskit_node.children()
559 }
560
561 pub(super) fn set_scroll_offset(
562 &mut self,
563 offset: LayoutVector2D,
564 update: &mut AccessibilityUpdate,
565 ) {
566 self.scroll_offset = Some(offset);
567 let transform = scroll_offset_to_affine(offset);
568 for child in self.children() {
569 let mut child = child.borrow_mut();
570 if child.is_hidden() {
571 continue;
572 }
573 child.set_transform(transform);
574 if child.dirty_state.updated() {
575 update.add(&mut child);
576 }
577 }
578 }
579
580 pub(super) fn role(&self) -> Role {
581 self.accesskit_node.role()
582 }
583
584 pub(super) fn set_role(&mut self, role: Role) -> LocalAccessibilityDamage {
585 if role == self.accesskit_node.role() {
586 return LocalAccessibilityDamage::empty();
587 }
588 self.accesskit_node.set_role(role);
589 self.dirty_state |= DirtyState::Updated;
590 LocalAccessibilityDamage::RoleChanged
591 }
592
593 fn label(&self) -> Option<&str> {
594 self.accesskit_node.label()
595 }
596
597 fn set_label(&mut self, label: &str) -> LocalAccessibilityDamage {
598 if Some(label) == self.accesskit_node.label() {
599 return LocalAccessibilityDamage::empty();
600 }
601 self.accesskit_node.set_label(label);
602 self.dirty_state |= DirtyState::Updated;
603 LocalAccessibilityDamage::TextChanged
604 }
605
606 fn clear_label(&mut self) -> LocalAccessibilityDamage {
607 if self.accesskit_node.label().is_none() {
608 return LocalAccessibilityDamage::empty();
609 }
610 self.accesskit_node.clear_label();
611 self.dirty_state |= DirtyState::Updated;
612 LocalAccessibilityDamage::TextChanged
613 }
614
615 fn html_tag(&self) -> Option<&str> {
616 self.accesskit_node.html_tag()
617 }
618
619 fn set_html_tag(&mut self, html_tag: &str) {
620 if Some(html_tag) == self.accesskit_node.html_tag() {
621 return;
622 }
623 self.accesskit_node.set_html_tag(html_tag);
624 self.dirty_state |= DirtyState::Updated;
625 }
626
627 fn value(&self) -> Option<&str> {
628 self.accesskit_node.value()
629 }
630
631 fn set_value(&mut self, value: &str) -> LocalAccessibilityDamage {
632 if Some(value) == self.accesskit_node.value() {
633 return LocalAccessibilityDamage::empty();
634 }
635 self.accesskit_node.set_value(value);
636 self.dirty_state |= DirtyState::Updated;
637 LocalAccessibilityDamage::TextChanged
638 }
639
640 fn is_hidden(&self) -> bool {
641 self.accesskit_node.is_hidden()
642 }
643
644 fn set_hidden(&mut self) -> LocalAccessibilityDamage {
645 if self.is_hidden() {
646 return LocalAccessibilityDamage::empty();
647 }
648 self.accesskit_node.set_hidden();
649 self.dirty_state |= DirtyState::Updated;
650 LocalAccessibilityDamage::VisibilityChanged
651 }
652
653 fn clear_hidden(&mut self) -> LocalAccessibilityDamage {
654 if !self.is_hidden() {
655 return LocalAccessibilityDamage::empty();
656 }
657
658 self.accesskit_node.clear_hidden();
659 self.dirty_state |= DirtyState::Updated;
660 LocalAccessibilityDamage::VisibilityChanged
661 }
662
663 fn bounds(&self) -> Option<accesskit::Rect> {
664 self.accesskit_node.bounds()
665 }
666
667 fn set_bounds(&mut self, bounds: accesskit::Rect) {
668 if Some(bounds) == self.accesskit_node.bounds() {
669 return;
670 }
671 self.accesskit_node.set_bounds(bounds);
672 self.dirty_state |= DirtyState::Updated;
673 }
674
675 fn clear_bounds(&mut self) {
676 if self.accesskit_node.bounds().is_none() {
677 return;
678 }
679 self.accesskit_node.clear_bounds();
680 self.dirty_state |= DirtyState::Updated;
681 }
682
683 fn set_transform(&mut self, transform: Affine) {
684 if self.accesskit_node.transform() == Some(&transform) {
688 return;
689 }
690 if transform == Affine::IDENTITY {
691 self.clear_transform();
692 return;
693 }
694 self.accesskit_node.set_transform(transform);
695 self.dirty_state |= DirtyState::Updated;
696 }
697
698 fn clear_transform(&mut self) {
699 if self.accesskit_node.transform().is_none() {
700 return;
701 }
702 self.accesskit_node.clear_transform();
703 self.dirty_state |= DirtyState::Updated;
704 }
705
706 pub(super) fn assert_integrity(&self, expected_parent: Option<WeakRefCell<AccessibilityNode>>) {
707 debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
708
709 if let Some(actual_parent) = &self.parent_node {
710 let expected = expected_parent.expect("Actual parent but no expected parent");
711 let expected = expected.upgrade().expect("Expected parent was dropped");
712 let actual = actual_parent.upgrade().expect("Actual parent was dropped");
713 assert!(actual.ptr_eq(&expected));
714 } else {
715 assert!(
716 expected_parent.is_none(),
717 "Expected parent but no actual parent"
718 );
719 }
720
721 assert!(
722 self.dirty_state.is_empty(),
723 "{self:?} has dirty state {:?}",
724 self.dirty_state
725 );
726
727 let children_ids: Vec<_> = self.children().map(|child| child.borrow().id).collect();
728 assert_eq!(
729 children_ids,
730 self.child_ids(),
731 "children() IDs didn't match child_ids() for {self:?}"
732 );
733 }
734}
735
736impl Debug for AccessibilityNode {
737 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
738 if self.is_hidden() {
739 write!(f, "[hidden] ")?;
740 }
741 write!(f, "{:?}: {:?}", self.id, self.role())?;
742 if let Some(html_tag) = self.html_tag() {
743 write!(f, " (html_tag: {html_tag:?})")?;
744 }
745 if let Some(label) = self.label() {
746 write!(f, "\nlabel: {label:?}")?;
747 }
748 if let Some(bounds) = self.bounds() {
749 write!(f, "\nbounds: {bounds:?}")?;
750 }
751 if !self.child_ids().is_empty() {
752 write!(f, "\nchildren: {:?}", self.child_ids())?;
753 }
754 Ok(())
755 }
756}
757
758fn role_from_role_attribute(dom_element: &ServoLayoutElement<'_>) -> Option<Role> {
760 let role_attribute = dom_element.attribute(&ns!(), &local_name!("role"))?;
761 role_attribute
762 .as_tokens()
763 .iter()
764 .filter_map(|role_name_in_attribute| SUPPORTED_ARIA_ROLES.get(role_name_in_attribute))
765 .next()
766 .and_then(|aria_role| ARIA_ROLE_TO_ACCESSKIT_ROLE.get(aria_role))
767 .cloned()
768}
769
770fn role_from_dom_node(dom_node: &ServoLayoutNode<'_>) -> Role {
771 if let Some(dom_element) = dom_node.as_element() {
772 role_from_role_attribute(&dom_element).unwrap_or_else(|| {
773 let local_name = dom_element.local_name().to_ascii_lowercase();
774 *HTML_ELEMENT_ROLE_MAPPINGS
775 .get(&local_name)
776 .unwrap_or(&Role::GenericContainer)
777 })
778 } else if dom_node.type_id() == Some(LayoutNodeType::Text) {
779 Role::TextRun
780 } else {
781 Role::GenericContainer
782 }
783}
784
785struct AccessibilityNodeIterator<I>
786where
787 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
788{
789 next_value: Option<ArcRefCell<AccessibilityNode>>,
790 next_fn: I,
791}
792
793impl<I> AccessibilityNodeIterator<I>
794where
795 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
796{
797 fn new(next_value: Option<ArcRefCell<AccessibilityNode>>, next_fn: I) -> Self {
798 AccessibilityNodeIterator {
799 next_value,
800 next_fn,
801 }
802 }
803}
804
805impl<I> Iterator for AccessibilityNodeIterator<I>
806where
807 I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
808{
809 type Item = ArcRefCell<AccessibilityNode>;
810
811 fn next(&mut self) -> Option<Self::Item> {
812 let next_value = self.next_value.take();
813 self.next_value = next_value
814 .as_ref()
815 .and_then(|node| (self.next_fn)(&node.borrow()));
816 next_value
817 }
818}
819
820impl DirtyState {
821 pub(super) fn updated(&self) -> bool {
822 self.contains(DirtyState::Updated)
823 }
824
825 pub(super) fn descendant_has_damage(&self) -> bool {
826 self.contains(DirtyState::DescendantHasDamage)
827 }
828
829 pub(super) fn propagate_descendant_has_damage(&mut self, child_dirty_state: DirtyState) {
830 if child_dirty_state.self_or_descendant_has_damage() {
831 self.insert(DirtyState::DescendantHasDamage)
832 }
833 }
834
835 pub(super) fn self_or_descendant_has_damage(&self) -> bool {
836 self.intersects(DirtyState::HasDamage | DirtyState::DescendantHasDamage)
837 }
838}
839
840static HTML_ELEMENT_ROLE_MAPPINGS: LazyLock<FxHashMap<LocalName, Role>> = LazyLock::new(|| {
841 [
842 (local_name!("a"), Role::Link),
844 (local_name!("article"), Role::Article),
845 (local_name!("aside"), Role::Complementary),
846 (local_name!("body"), Role::RootWebArea),
847 (local_name!("footer"), Role::ContentInfo),
848 (local_name!("h1"), Role::Heading),
849 (local_name!("h2"), Role::Heading),
850 (local_name!("h3"), Role::Heading),
851 (local_name!("h4"), Role::Heading),
852 (local_name!("h5"), Role::Heading),
853 (local_name!("h6"), Role::Heading),
854 (local_name!("header"), Role::Banner),
855 (local_name!("hr"), Role::Splitter),
856 (local_name!("main"), Role::Main),
857 (local_name!("nav"), Role::Navigation),
858 (local_name!("p"), Role::Paragraph),
859 ]
860 .into_iter()
861 .collect()
862});
863
864static NAME_FROM_CONTENTS_ROLES: LazyLock<FxHashSet<Role>> =
866 LazyLock::new(|| [(Role::Heading), (Role::Link)].into_iter().collect());