Skip to main content

layout/accessibility/
accessibility_node.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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
34/// Convert a rectangle as layout reports it into the one [`accesskit`] wants.
35fn 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    /// Flags tracking an [`AccessibilityNode`]'s dirty state during an update. All flags which are
53    /// set during the update should be unset by the end of the update.
54    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
55    pub(super) struct DirtyState : u16 {
56        /// At least one descendant of this node has unresolved damage from the DOM tree.
57        const DescendantHasDamage = 0b0001;
58        /// This node has unresolved damage from the DOM tree.
59        const HasDamage = 0b0010;
60        /// This node's data changed, but it hasn't yet been added to the [`AccessibilityUpdate`].
61        const Updated = 0b0100;
62    }
63}
64
65bitflags! {
66    /// Damage which was caused by changes to the accessibility tree. These changes can cause other
67    /// properties to need to be re-computed based on the updated values, either on the same node or
68    /// on other nodes.
69    #[derive(Clone, Copy, Default, Debug, Eq, PartialEq)]
70    pub(super) struct LocalAccessibilityDamage: u16 {
71        /// This node's children changed, and/or any node in its subtree changed.
72        const SubtreeChanged = 0b0001;
73        /// This node's computed role changed.
74        const RoleChanged = 0b0010;
75        /// This node's computed label or text value (for a text node) changed.
76        const TextChanged = 0b0100;
77        /// This node's visibility changed.
78        const VisibilityChanged = 0b1000;
79    }
80}
81
82pub(super) struct AccessibilityNode {
83    /// The unique ID for the node. This is used both as a key in [`AccessibilityTree`]'s cache of
84    /// nodes, and as an identifier in [`accesskit`] datastructures: [`accesskit::Node`]s,
85    /// [`accesskit::TreeUpdate`]s and [`accesskit::ActionRequest`]s.
86    id: NodeId,
87    /// The computed [`accesskit::Node`] data. This will be copied and serialized into a
88    /// [`accesskit::TreeUpdate`] whenever it is changed during an update.
89    accesskit_node: accesskit::Node,
90    /// This node's parent, if any.
91    parent_node: Option<WeakRefCell<AccessibilityNode>>,
92    /// All this node's children.
93    child_nodes: Vec<ArcRefCell<AccessibilityNode>>,
94    /// The [`OpaqueNode`] for the DOM node which corresponds to this accessibility node, if any.
95    /// An accessibility node may not correspond to a DOM node if it corresponds to a
96    /// pseudo-element, or in a test.
97    opaque_node: Option<OpaqueNode>,
98    /// This node's scroll offset, if it is a scroll container which has scrolled. This is used to
99    /// translate this node's children.
100    scroll_offset: Option<LayoutVector2D>,
101    /// Any dirty state for the current update.
102    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    /// Update this node and its subtree based on damage from the DOM.
123    ///
124    /// - First, if this node has damage from the DOM to be resolved, update the node from the DOM
125    ///   tree, recursively populating any new children.
126    /// - Next, recursively call this method for any children which are dirty, or have dirty
127    ///   descendants.
128    /// - Finally, update any properties on this node which are may have changed due to other
129    ///   changes in the tree.
130    ///
131    /// At the end of this method, both `has_dirty_descendants` and `is_dirty` should be false for
132    /// this node and all its descendants.
133    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            // If this node may have new, or newly-visible, children, update their scroll offsets.
187            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    /// Update each of this node's ancestors based on changes which have already been applied in the
200    /// tree.
201    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    /// Update the given [`AccessibilityNode`] from its corresponding DOM node and
228    /// [`AccessibilityDamage`].
229    /// If it has new children, those will be created here, but not yet populated.
230    // Any changed nodes will be added to the given [`AccessibilityUpdate`].
231    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        // We check for layout damage here because we need to walk the DOM children of nodes with
248        // layout damage in order to be able to recompute their bounds. Text nodes have neither
249        // bounds nor child nodes, so if the only damage is layout, we can early return here.
250        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            // If this node has damage from layout, this ensures that all of its children have
262            // their corresponding DOM nodes in `update`.
263            local_damage
264                .insert(self.update_children_from_dom_node(ref_self, dom_node, tree, update));
265        }
266
267        local_damage
268    }
269
270    /// Update this node's [`Self::children`] from its corresponding DOM node.
271    /// If it has new children, those will be created here, but not yet populated.
272    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        // Iterate over existing children and DOM children while they match. No action is necessary
283        // for these nodes.
284        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 we iterated over all the DOM children without finding any changes, we're done.
299        if old_child_ids.peek().is_none() && remaining_dom_children.peek().is_none() {
300            return LocalAccessibilityDamage::empty();
301        }
302
303        // Remove all child nodes after the first `unchanged_count`.
304        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        // Then, (re-)add all the remaining DOM children. Note that this means that some children
311        // may end up being "Moved" even though they haven't changed parents, and may even be in the
312        // same position as previously.
313        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            // TODO(#47162): Since we need to update bounds for all nodes, we need to ensure every
317            // AccessibilityNode has a corresponding DOM node available to be retrieved from the
318            // AccessibilityUpdate. Once we no longer update bounds on all nodes, we won't need to
319            // add all nodes like this.
320            update.insert_dom_node(child_id, dom_child);
321
322            // Update self.child_nodes in place.
323            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        // We can't update the AccessKit node's `children` in place, so we build up the full list
340        // and then set it here.
341        self.accesskit_node.set_children(new_child_ids);
342        self.dirty_state |= DirtyState::Updated;
343
344        LocalAccessibilityDamage::SubtreeChanged
345    }
346
347    /// Update this node's properties from its corresponding DOM node.
348    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            // FIXME: this should take into account editing selection units (grapheme clusters?)
358            local_damage.insert(self.set_value(&text_content));
359        }
360
361        local_damage
362    }
363
364    /// Update this node's bounds from the current layout geometry.
365    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        // Don't update bounds on nodes in hidden subtrees.
376        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        // Border box without transforms. Bounds are in CSS pixels, relative to the document origin;
400        // scroll containers set translations on their child nodes, and the embedder's graft node
401        // carries the transform that composes them into AccessKit's coordinate space (see the
402        // "Coordinates" section of
403        // <https://docs.rs/accesskit/latest/accesskit/struct.Node.html>).
404        // TODO(#47166): This doesn't take any CSS transforms into account.
405        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        // For now only nodes with a box of their own get bounds; anything else, including
415        // `display: none` content, gets its bounds cleared. That leaves two kinds of nodes
416        // without geometry which assistive technology would like to have some:
417        //
418        // TODO(#47164): A text node never has bounds of its own: `LayoutBox::Text` has no
419        // `LayoutBoxBase`, and `Fragment::Text` has no box area, so the query above always returns
420        // `None` for one. Text nodes should get the union of the rectangles of their own
421        // `Fragment::Text` fragments, once `cumulative_box_area_rect()` can handle those.
422        //
423        // TODO(#47163): A `display: contents` element generates no box either. Other
424        // engines (Blink, WebKit, Gecko) compute its bounds as the union of the bounding boxes of
425        // its rendered descendants.
426        match bounds {
427            Some(bounds) => self.set_bounds(bounds),
428            None => self.clear_bounds(),
429        }
430        local_damage
431    }
432
433    /// Update this node's properties based on changes already made to the accessibility tree.
434    /// For example, if there were nodes added or removed in its subtree, its computed text may have
435    /// changed, so that will be recomputed here.
436    /// If any changes are made, add this node to the given [`AccessibilityUpdate`].
437    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        // TODO(#47166): Right now a node will only ever have a single transform from a scroll
685        // container, if any. Once we correctly support CSS transforms, a node may have multiple
686        // transforms, which we'll need to be able to combine.
687        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
758/// <https://w3c.github.io/aria/#host_general_role>
759fn 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        // FIXME: only a with href!
843        (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
864/// <https://w3c.github.io/aria/#namefromcontent>
865static NAME_FROM_CONTENTS_ROLES: LazyLock<FxHashSet<Role>> =
866    LazyLock::new(|| [(Role::Heading), (Role::Link)].into_iter().collect());