Skip to main content

layout/
accessibility_tree.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/. */
4use std::collections::VecDeque;
5use std::fmt::Debug;
6use std::iter::repeat;
7use std::sync::atomic::AtomicU64;
8use std::sync::{LazyLock, atomic};
9
10use accesskit::{NodeId, Role};
11use bitflags::bitflags;
12use layout_api::{AccessibilityDamage, LayoutElement, LayoutNode, LayoutNodeType};
13use log::trace;
14use rustc_hash::{FxHashMap, FxHashSet};
15use script::layout_dom::ServoLayoutNode;
16use servo_base::Epoch;
17use servo_base::print_tree::PrintTree;
18use servo_config::opts::{self, DiagnosticsLogging, DiagnosticsLoggingOption};
19use servo_config::pref;
20use style::dom::OpaqueNode;
21use web_atoms::{LocalName, local_name};
22
23use crate::ArcRefCell;
24use crate::cell::WeakRefCell;
25
26bitflags! {
27    /// Damage which was caused by changes to the accessibility tree. These changes can cause other
28    /// properties to need to be re-computed based on the updated values, either on the same node or
29    /// on other nodes.
30    #[derive(Clone, Copy, Default, Debug, Eq, PartialEq)]
31    struct LocalAccessibilityDamage: u16 {
32        /// This node's children changed, and/or any node in its subtree changed.
33        const SubtreeChanged = 0b0001;
34        /// This node's computed role changed.
35        const RoleChanged = 0b0010;
36        /// This node's computed label or text value (for a text node) changed.
37        const TextChanged = 0b0100;
38    }
39}
40
41/// Changes which have occurred during the current update.
42struct AccessibilityUpdate {
43    /// Nodes whose internal data has changed within the current update.
44    changed_nodes: FxHashSet<NodeId>,
45    /// Nodes that changed their relation to the tree within the current update.
46    tree_changes: FxHashMap<NodeId, TreeChange>,
47    /// Counters to track how many nodes we've checked for changes or updated in this tree update.
48    counters: UpdateCounters,
49    /// Nodes which were removed from the DOM tree since the last reflow, which were rooted in
50    /// `AccessibilityData`. Only set if `pref::expensive_accessibility_test_assertions_enabled`
51    /// is set.
52    rooted_nodes: Option<FxHashSet<OpaqueNode>>,
53}
54
55#[derive(Debug, Default)]
56pub struct UpdateCounters {
57    pub nodes_updated_from_dom: u32,
58    pub nodes_updated_from_tree: u32,
59    pub nodes_in_tree_update: u32,
60}
61
62bitflags! {
63    /// Flags tracking an [`AccessibilityNode`]'s dirty state during an update. All flags which are
64    /// set during the update should be unset by the end of the update.
65    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
66    struct DirtyState : u16 {
67        /// At least one descendant of this node has unresolved damage from the DOM tree.
68        const DescendantHasDamage = 0b0001;
69        /// This node has unresolved damage from the DOM tree.
70        const HasDamage = 0b0010;
71        /// This node's data changed, but it hasn't yet been added to the [`AccessibilityUpdate`].
72        const Updated = 0b0100;
73    }
74}
75
76struct AccessibilityNode {
77    /// The unique ID for the node. This is used both as a key in [`AccessibilityTree`]'s cache of
78    /// nodes, and as an identifier in [`accesskit`] datastructures: [`accesskit::Node`]s,
79    /// [`accesskit::TreeUpdate`]s and [`accesskit::ActionRequest`]s.
80    id: NodeId,
81    /// The computed [`accesskit::Node`] data. This will be copied and serialized into a
82    /// [`accesskit::TreeUpdate`] whenever it is changed during an update.
83    accesskit_node: accesskit::Node,
84    /// This node's parent, if any.
85    parent_node: Option<WeakRefCell<AccessibilityNode>>,
86    /// All this node's children.
87    child_nodes: Vec<ArcRefCell<AccessibilityNode>>,
88    /// The [`OpaqueNode`] for the DOM node which corresponds to this accessibility node, if any.
89    /// An accessibility node may not correspond to a DOM node if it corresponds to a
90    /// pseudo-element, or in a test.
91    opaque_node: Option<OpaqueNode>,
92    /// Any dirty state for the current update.
93    dirty_state: DirtyState,
94}
95
96/// A retained, internal representation of the accessibility tree for a document.
97///
98/// [`accesskit`] only provides interchange types for tree updates and action requests, so we need
99/// to define our own representation for incremental tree building.
100#[derive(Debug)]
101pub struct AccessibilityTree {
102    /// All nodes currently in the tree as of the most recent update. New nodes are added and stale
103    /// nodes are pruned during [`AccessibilityTree::update_tree()`].
104    nodes: FxHashMap<NodeId, ArcRefCell<AccessibilityNode>>,
105    /// A map to allow retrieving the [`AccessibilityNode`] which corresponds to a particular DOM
106    /// node, if any.
107    ///
108    /// This must be kept in sync with [`Self::id_to_opaque_node`].
109    opaque_node_to_id: FxHashMap<OpaqueNode, NodeId>,
110    /// A map to retrieve the `OpaqueNode` corresponding to a particular [`AccessibilityNode`], if
111    /// any.
112    ///
113    /// This must be kept in sync with [`Self::opaque_node_to_id`].
114    id_to_opaque_node: FxHashMap<NodeId, OpaqueNode>,
115    /// Sent with each [`accesskit::TreeUpdate`]. This allows this tree to be
116    /// [grafted](https://docs.rs/accesskit/latest/accesskit/struct.Node.html#method.tree_id) into
117    /// an application's tree.
118    tree_id: accesskit::TreeId,
119    /// This node's ID is sent with each [`accesskit::TreeUpdate`] to identify the root node.
120    /// Also used for any complete tree walk, such as in [`Self::assert_integrity()`] and
121    /// [`Self::print()`].
122    root_node: Option<ArcRefCell<AccessibilityNode>>,
123    /// Sent to the embedder alongside each [`accesskit::TreeUpdate`], so that the embedder can
124    /// drop updates from documents which have been navigated away from.
125    embedder_epoch: Epoch,
126    /// Debug options, copied from configuration to this `AccessibilityTree` in order
127    /// to avoid having to constantly access the thread-safe global options.
128    debug: DiagnosticsLogging,
129}
130
131/// Tracks changes to a node's relation to the tree within an update.
132///
133/// This is used to remove nodes from the accessibility tree's cache when they are no longer in the
134/// tree.
135#[derive(Debug, PartialEq, Copy, Clone)]
136enum TreeChange {
137    /// The node was newly created in this update.
138    New,
139
140    /// The node has been re-parented in this update.
141    Moved,
142
143    /// The node has been added to its new parent, but not yet removed from its old
144    /// parent.
145    ///
146    /// When a node is moved within the tree, it must be both removed from its old parent
147    /// and added to its new parent within the same update. This may happen in either
148    /// order, depending on the relative positions of the node before and after it moves.
149    ///
150    /// - If a node's new parent is updated before its old parent, the node will be in a
151    ///   `TreeChange::PendingMove` state until its old parent is updated. We expect that it
152    ///   must later be removed from its old parent, at which point its state will be updated to
153    ///   `TreeChange::Moved`.
154    /// - If a node's old parent is updated before its new parent, the node will be first
155    ///   `TreeChange::Removed` and then `TreeChange::Moved`.
156    ///
157    /// At the end of the update, we assert that there are no pending moves remaining.
158    PendingMove,
159
160    /// The node is no longer a child of its previous parent.
161    Removed,
162}
163
164impl AccessibilityTree {
165    /// See [`Self::tree_id`] and [`Self::embedder_epoch`] for explanations of the parameters.
166    pub(super) fn new(tree_id: accesskit::TreeId, embedder_epoch: Epoch) -> Self {
167        Self {
168            nodes: FxHashMap::default(),
169            opaque_node_to_id: FxHashMap::default(),
170            id_to_opaque_node: FxHashMap::default(),
171            tree_id,
172            root_node: None,
173            embedder_epoch,
174            debug: opts::get().debug.clone(),
175        }
176    }
177
178    /// Update this tree based on the current state of the given DOM tree, and if anything changed,
179    /// return an [`accesskit::TreeUpdate`] representing what changed.
180    pub(super) fn update_tree<'dom>(
181        &mut self,
182        root_dom_node: &ServoLayoutNode<'dom>,
183        mut damage_from_dom: VecDeque<(ServoLayoutNode<'dom>, AccessibilityDamage)>,
184        rooted_nodes: Option<FxHashSet<OpaqueNode>>,
185    ) -> (Option<accesskit::TreeUpdate>, UpdateCounters) {
186        let mut update = AccessibilityUpdate::new(rooted_nodes);
187
188        self.ensure_root_node(root_dom_node, &mut damage_from_dom, &mut update);
189
190        self.apply_changes_from_dom_tree(damage_from_dom, &mut update);
191
192        update.finalize(self)
193    }
194
195    /// Get the node corresponding to the root DOM node, and set it as this tree's root. If the root
196    /// node is newly created, which probably means this accessibility tree is newly created, append
197    /// an `AccessibilityDamage::REBUILD` value for it to `damage_from_dom`.
198    fn ensure_root_node<'dom>(
199        &mut self,
200        root_dom_node: &ServoLayoutNode<'dom>,
201        damage_from_dom: &mut VecDeque<(ServoLayoutNode<'dom>, AccessibilityDamage)>,
202        update: &mut AccessibilityUpdate,
203    ) {
204        let (root_id, root_node) = self.get_or_create_node(root_dom_node, update);
205        if update.is_new(&root_id) {
206            damage_from_dom.push_front((*root_dom_node, AccessibilityDamage::Rebuild));
207        }
208        self.root_node = Some(root_node);
209    }
210
211    /// For each DOM node in `damage_from_dom`, update the corresponding accessibility node based on
212    /// its `AccessibilityDamage`. If any [`LocalAccessibilityDamage`] results from the update,
213    /// propagate [`LocalAccessibilityDamage::SubtreeChanged`] to its ancestors.
214    fn apply_changes_from_dom_tree<'dom>(
215        &mut self,
216        damage_from_dom: VecDeque<(ServoLayoutNode<'dom>, AccessibilityDamage)>,
217        update: &mut AccessibilityUpdate,
218    ) {
219        let mut dom_damage_map = FxHashMap::from(
220            damage_from_dom
221                .into_iter()
222                .filter_map(|(dom_node, dom_node_damage)| {
223                    let id = self.existing_id_for_opaque(dom_node.opaque())?;
224                    Some((id, (dom_node, dom_node_damage)))
225                })
226                .collect(),
227        );
228        let damage_root = self.mark_nodes_and_ancestors_dirty(dom_damage_map.keys().cloned());
229        let Some(damage_root) = damage_root else {
230            return;
231        };
232        let local_damage = damage_root.borrow_mut().update_subtree(
233            damage_root.clone(),
234            &mut dom_damage_map,
235            self,
236            update,
237        );
238
239        damage_root.borrow().update_ancestors(local_damage, update);
240    }
241
242    /// Given an iterator of `NodeId`s corresponding to nodes which have received some damage from
243    /// the DOM:
244    /// - mark each node as `dirty`;
245    /// - mark all of each node's ancestors as `has_dirty_descendants`;
246    /// - return the lowest common ancestor node of all the damaged nodes.
247    fn mark_nodes_and_ancestors_dirty(
248        &mut self,
249        mut dirty_node_ids: impl Iterator<Item = NodeId>,
250    ) -> Option<ArcRefCell<AccessibilityNode>> {
251        // An ordered list of common ancestors for the nodes seen so far, from shallowest to
252        // deepest. At the end of the loop, the lowest common ancestor is the last node in this vec.
253        let mut common_ancestors: Vec<NodeId> = Vec::new();
254
255        {
256            // Initialize the list of potential common ancestors.
257            let first_node = self.assert_node_for_id(&dirty_node_ids.next()?);
258            let mut first_node = first_node.borrow_mut();
259            first_node.dirty_state |= DirtyState::HasDamage;
260            common_ancestors.push(first_node.id);
261            common_ancestors.extend(first_node.ancestors().map(|ancestor| {
262                let mut ancestor = ancestor.borrow_mut();
263                ancestor.dirty_state |= DirtyState::DescendantHasDamage;
264                ancestor.id
265            }));
266            common_ancestors.reverse();
267        }
268
269        let mut truncate_ancestors = |node: &AccessibilityNode| -> bool {
270            if node.dirty_state.descendant_has_damage() {
271                if let Some(pos) = common_ancestors.iter().position(|&id| id == node.id) {
272                    common_ancestors.truncate(pos + 1);
273                }
274                return true;
275            }
276            false
277        };
278
279        for node_id in dirty_node_ids {
280            let node = self.assert_node_for_id(&node_id);
281            let mut node = node.borrow_mut();
282            node.dirty_state |= DirtyState::HasDamage;
283
284            if truncate_ancestors(&node) {
285                continue;
286            }
287
288            for ancestor in node.ancestors() {
289                let mut ancestor = ancestor.borrow_mut();
290
291                // If we find an ancestor we've already seen, discard any potential ancestors deeper
292                // than this one, and go on to the next dirty node.
293                if truncate_ancestors(&ancestor) {
294                    break;
295                }
296
297                ancestor.dirty_state |= DirtyState::DescendantHasDamage;
298            }
299        }
300
301        self.nodes.get(common_ancestors.last()?).cloned()
302    }
303
304    fn get_or_create_node(
305        &mut self,
306        dom_node: &ServoLayoutNode<'_>,
307        update: &mut AccessibilityUpdate,
308    ) -> (NodeId, ArcRefCell<AccessibilityNode>) {
309        let id = self.get_or_create_id_for_opaque(dom_node.opaque());
310        let node_ref = self.get_or_create_node_with_id(id, update);
311
312        if update.is_new(&id) {
313            let mut node = node_ref.borrow_mut();
314            node.opaque_node = Some(dom_node.opaque());
315            if let Some(dom_element) = dom_node.as_element() {
316                let local_name = dom_element.local_name().to_ascii_lowercase();
317                node.set_html_tag(&local_name);
318            }
319        }
320
321        (id, node_ref)
322    }
323
324    fn get_or_create_node_with_id(
325        &mut self,
326        id: NodeId,
327        update: &mut AccessibilityUpdate,
328    ) -> ArcRefCell<AccessibilityNode> {
329        if let Some(node) = self.nodes.get(&id) {
330            return node.clone();
331        }
332
333        let node = ArcRefCell::new(AccessibilityNode::new(id));
334        update.set_tree_state_change(id, TreeChange::New);
335        self.nodes.insert(id, node.clone());
336
337        node
338    }
339
340    fn node_for_id(&self, id: NodeId) -> Option<ArcRefCell<AccessibilityNode>> {
341        self.nodes.get(&id).cloned()
342    }
343
344    fn assert_node_for_id(&self, id: &NodeId) -> ArcRefCell<AccessibilityNode> {
345        let Some(node) = self.nodes.get(id) else {
346            panic!("{id:?} does not exist in tree");
347        };
348        node.clone()
349    }
350
351    /// Consume the [`AccessibilityUpdate`] by deleting all nodes it detected as being removed from
352    /// the tree.
353    fn drop_removed_nodes(&mut self, mut update: AccessibilityUpdate) {
354        let mut rooted_nodes = std::mem::take(&mut update.rooted_nodes);
355        if let Some(rooted_nodes) = rooted_nodes.as_mut() {
356            self.assert_removed_nodes_were_rooted(&update, rooted_nodes);
357        }
358
359        let mut ids_to_remove: Vec<_> = update
360            .tree_changes
361            .iter()
362            .filter_map(|(id, change)| match change {
363                TreeChange::Removed => Some(id),
364                TreeChange::PendingMove => None,
365                TreeChange::New => None,
366                TreeChange::Moved => None,
367            })
368            .cloned()
369            .collect();
370
371        while let Some(id) = ids_to_remove.pop() {
372            if update.tree_changes.get(&id) == Some(&TreeChange::PendingMove) {
373                // Mark the move as completed by marking the node as removed from its old position.
374                update.set_tree_state_change(id, TreeChange::Removed);
375
376                // Since this node is actually moved, don't continue removing its subtree.
377                continue;
378            }
379
380            if let Some(opaque_node) = self.id_to_opaque_node.remove(&id) {
381                self.opaque_node_to_id.remove(&opaque_node);
382            }
383            let node = self.nodes.remove(&id).expect("Node {id:?} already removed");
384            ids_to_remove.extend(node.borrow().child_ids());
385        }
386
387        update
388            .tree_changes
389            .drain()
390            .for_each(|(id, change)| match change {
391                TreeChange::PendingMove => unreachable!(
392                    "Pending move found for node id {id:?} when draining tree state changes"
393                ),
394                TreeChange::Removed => (),
395                TreeChange::New => (),
396                TreeChange::Moved => (),
397            });
398
399        if let Some(rooted_nodes) = rooted_nodes {
400            self.assert_remaining_rooted_nodes_not_in_tree(rooted_nodes);
401        }
402
403        if self
404            .debug
405            .is_enabled(DiagnosticsLoggingOption::AccessibilityTree)
406        {
407            self.print();
408        }
409
410        if pref!(expensive_accessibility_test_assertions_enabled) {
411            self.assert_integrity();
412        }
413    }
414
415    /// If we got `rooted_nodes` from the document's `AccessibilityData`, assert that every node we
416    /// marked as `TreeChange::Removed` during this update was rooted.
417    fn assert_removed_nodes_were_rooted(
418        &mut self,
419        update: &AccessibilityUpdate,
420        rooted_nodes: &mut FxHashSet<OpaqueNode>,
421    ) {
422        debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
423        for (id, change) in update.tree_changes.iter() {
424            if change == &TreeChange::Removed {
425                let Some(&opaque_node) = self.id_to_opaque_node.get(id) else {
426                    panic!("No opaque node found for removed node: id {id:?}");
427                };
428                assert!(
429                    rooted_nodes.remove(&opaque_node),
430                    "Node removed from accessibility tree wasn't rooted: id {id:?}"
431                );
432            };
433        }
434    }
435
436    /// If we got `rooted_nodes` from the document's `AccessibilityData`, assert that any nodes
437    /// which were rooted but not marked as `TreeChange::Removed` are no longer in the tree after
438    /// dropping all nodes which were removed from the tree. They may have been part of a subtree
439    /// which was marked `TreeChange::Removed` on an ancestor node, or may have never made it into
440    /// the accessibility tree to begin with.
441    fn assert_remaining_rooted_nodes_not_in_tree(&self, rooted_nodes: FxHashSet<OpaqueNode>) {
442        for leftover_node in rooted_nodes {
443            assert!(
444                !self.opaque_node_to_id.contains_key(&leftover_node),
445                "Found node removed from DOM tree but not accessibility tree: {:#x}",
446                leftover_node.0
447            );
448        }
449    }
450
451    fn get_or_create_id_for_opaque(&mut self, opaque: OpaqueNode) -> NodeId {
452        let id = self.opaque_node_to_id.entry(opaque).or_insert_with(|| {
453            static LAST_ID: AtomicU64 = AtomicU64::new(0);
454            let id = LAST_ID.fetch_add(1, atomic::Ordering::SeqCst).into();
455            self.id_to_opaque_node.insert(id, opaque);
456            id
457        });
458        *id
459    }
460
461    fn existing_id_for_opaque(&self, opaque: OpaqueNode) -> Option<NodeId> {
462        self.opaque_node_to_id.get(&opaque).cloned()
463    }
464
465    pub(crate) fn embedder_epoch(&self) -> Epoch {
466        self.embedder_epoch
467    }
468
469    /// Assert that the tree is a tree without any dangling references or orphaned nodes.
470    ///
471    /// For accessibility tests only, because it’s expensive.
472    fn assert_integrity(&self) {
473        debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
474        let Some(root_node) = self.root_node.clone() else {
475            return;
476        };
477
478        // Traverse the tree from the given root.
479        // `nodes` is a Vec of pairs of nodes and their expected parents.
480        let mut nodes = vec![(root_node, None)];
481        let mut seen_node_ids = FxHashSet::default();
482        while let Some((node, expected_parent)) = nodes.pop() {
483            let node = node.borrow();
484
485            // If this fails, then the tree is not a tree at all.
486            assert!(
487                seen_node_ids.insert(node.id),
488                "Tree contains {:?} in multiple places",
489                node.id
490            );
491
492            node.assert_integrity(expected_parent);
493
494            // assert_node_for_id() here double-checks that the node hasn't been incorrectly evicted
495            // from the map while it's still retained as a child node.
496            let weak_node = Some(self.assert_node_for_id(&node.id).downgrade());
497            nodes.extend(node.children().cloned().zip(repeat(weak_node)));
498        }
499
500        // If this fails, then the tree has orphaned nodes (a leak).
501        // If a node has been incorrectly removed from the map, that will be caught above.
502        assert_eq!(seen_node_ids, self.nodes.keys().copied().collect());
503    }
504
505    fn print(&self) {
506        let Some(root_node) = self.root_node.clone() else {
507            return;
508        };
509
510        let mut print_tree = PrintTree::new("Accessibility Tree");
511        root_node.borrow().print(&mut print_tree);
512        print_tree.end_level();
513    }
514}
515
516fn role_from_dom_node(dom_node: &ServoLayoutNode<'_>) -> Role {
517    if let Some(dom_element) = dom_node.as_element() {
518        let local_name = dom_element.local_name().to_ascii_lowercase();
519        *HTML_ELEMENT_ROLE_MAPPINGS
520            .get(&local_name)
521            .unwrap_or(&Role::GenericContainer)
522    } else if dom_node.type_id() == Some(LayoutNodeType::Text) {
523        Role::TextRun
524    } else {
525        Role::GenericContainer
526    }
527}
528
529struct AccessibilityNodeIterator<I>
530where
531    I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
532{
533    next_value: Option<ArcRefCell<AccessibilityNode>>,
534    next_fn: I,
535}
536
537impl<I> AccessibilityNodeIterator<I>
538where
539    I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
540{
541    fn new(next_value: Option<ArcRefCell<AccessibilityNode>>, next_fn: I) -> Self {
542        AccessibilityNodeIterator {
543            next_value,
544            next_fn,
545        }
546    }
547}
548
549impl<I> Iterator for AccessibilityNodeIterator<I>
550where
551    I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
552{
553    type Item = ArcRefCell<AccessibilityNode>;
554
555    fn next(&mut self) -> Option<Self::Item> {
556        let next_value = self.next_value.take();
557        self.next_value = next_value
558            .as_ref()
559            .and_then(|node| (self.next_fn)(&node.borrow()));
560        next_value
561    }
562}
563
564impl AccessibilityNode {
565    fn new(id: NodeId) -> Self {
566        Self::new_with_role(id, Role::Unknown)
567    }
568
569    fn new_with_role(id: NodeId, role: Role) -> Self {
570        Self {
571            id,
572            accesskit_node: accesskit::Node::new(role),
573            parent_node: None,
574            child_nodes: vec![],
575            opaque_node: None,
576            dirty_state: DirtyState::empty(),
577        }
578    }
579
580    /// Update this node and its subtree based on damage from the DOM.
581    ///
582    /// - First, if this node has damage from the DOM to be resolved, update the node from the DOM
583    ///   tree, recursively populating any new children.
584    /// - Next, recursively call this method for any children which are dirty, or have dirty
585    ///   descendants.
586    /// - Finally, update any properties on this node which are may have changed due to other
587    ///   changes in the tree.
588    ///
589    /// At the end of this method, both `has_dirty_descendants` and `is_dirty` should be false for
590    /// this node and all its descendants.
591    fn update_subtree<'dom>(
592        &mut self,
593        ref_self: ArcRefCell<Self>,
594        dom_damage_map: &mut FxHashMap<NodeId, (ServoLayoutNode<'dom>, AccessibilityDamage)>,
595        tree: &mut AccessibilityTree,
596        update: &mut AccessibilityUpdate,
597    ) -> LocalAccessibilityDamage {
598        let mut local_damage = LocalAccessibilityDamage::empty();
599
600        if let Some((dom_node, dom_damage)) = dom_damage_map.get(&self.id) {
601            local_damage.insert(self.update_node_and_populate_new_descendants_from_dom_node(
602                ref_self,
603                dom_node,
604                *dom_damage,
605                tree,
606                update,
607            ));
608
609            self.dirty_state -= DirtyState::HasDamage;
610        }
611
612        if self.dirty_state.descendant_has_damage() {
613            for child_node in self.children() {
614                let strong_child_node = child_node.clone();
615                let mut child_node = child_node.borrow_mut();
616                if !child_node.dirty_state.self_or_descendant_has_damage() {
617                    continue;
618                }
619                let child_damage =
620                    child_node.update_subtree(strong_child_node, dom_damage_map, tree, update);
621                if !child_damage.is_empty() {
622                    local_damage.insert(LocalAccessibilityDamage::SubtreeChanged);
623                }
624            }
625
626            self.dirty_state -= DirtyState::DescendantHasDamage;
627        }
628
629        local_damage.insert(self.update_node_local(local_damage, update));
630
631        if self.dirty_state.updated() {
632            update.add(self);
633        }
634
635        local_damage
636    }
637
638    /// Update each of this node's ancestors based on changes which have already been applied in the
639    /// tree.
640    fn update_ancestors(
641        &self,
642        local_damage: LocalAccessibilityDamage,
643        update: &mut AccessibilityUpdate,
644    ) {
645        if local_damage.is_empty() {
646            return;
647        }
648        for node in self.ancestors() {
649            let mut node = node.borrow_mut();
650            node.update_node_local(LocalAccessibilityDamage::SubtreeChanged, update);
651            node.dirty_state -= DirtyState::DescendantHasDamage;
652            if node.dirty_state.updated() {
653                update.add(&mut node);
654            }
655        }
656    }
657
658    /// Update the given [`AccessibilityNode`] from its corresponding DOM node and
659    /// [`AccessibilityDamage`].
660    /// If it has new children, those will be recursively populated here.
661    // Any changed nodes will be added to the given [`AccessibilityUpdate`].
662    fn update_node_and_populate_new_descendants_from_dom_node<'dom>(
663        &mut self,
664        ref_self: ArcRefCell<Self>,
665        dom_node: &ServoLayoutNode<'dom>,
666        dom_damage: AccessibilityDamage,
667        tree: &mut AccessibilityTree,
668        update: &mut AccessibilityUpdate,
669    ) -> LocalAccessibilityDamage {
670        update.counters.nodes_updated_from_dom += 1;
671
672        let mut local_damage = LocalAccessibilityDamage::empty();
673
674        local_damage.insert(self.update_properties_from_dom_node(dom_node, dom_damage));
675        local_damage.insert(
676            self.update_children_and_populate_new_descendants_from_dom_node(
677                ref_self, dom_node, dom_damage, tree, update,
678            ),
679        );
680
681        local_damage
682    }
683
684    /// Update this node's [`Self::children`] from its corresponding DOM node. If any children are
685    /// newly added to the tree, populate them and recursively populate their children.
686    fn update_children_and_populate_new_descendants_from_dom_node<'dom>(
687        &mut self,
688        ref_self: ArcRefCell<AccessibilityNode>,
689        dom_node: &ServoLayoutNode<'dom>,
690        dom_damage: AccessibilityDamage,
691        tree: &mut AccessibilityTree,
692        update: &mut AccessibilityUpdate,
693    ) -> LocalAccessibilityDamage {
694        if !dom_damage.contains(AccessibilityDamage::Children) {
695            return LocalAccessibilityDamage::empty();
696        }
697
698        let mut remaining_dom_children = dom_node.flat_tree_children().peekable();
699        let mut old_child_ids = self.child_ids().iter().peekable();
700        let mut unchanged_count = 0usize;
701
702        // Iterate over existing children and DOM children while they match. No action is necessary
703        // for these nodes.
704        while let Some(&old_id) = old_child_ids.peek() &&
705            let Some(dom_child) = remaining_dom_children.peek()
706        {
707            if tree.existing_id_for_opaque(dom_child.opaque()) == Some(*old_id) {
708                unchanged_count += 1;
709                old_child_ids.next();
710                remaining_dom_children.next();
711            } else {
712                break;
713            }
714        }
715
716        // If we iterated over all the DOM children without finding any changes, we're done.
717        if old_child_ids.peek().is_none() && remaining_dom_children.peek().is_none() {
718            return LocalAccessibilityDamage::empty();
719        }
720
721        // Remove all child nodes after the first `unchanged_count`.
722        self.child_nodes.truncate(unchanged_count);
723        let mut new_child_ids = Vec::from(self.child_ids());
724        for removed_child_id in new_child_ids.split_off(unchanged_count) {
725            update.set_tree_state_change(removed_child_id, TreeChange::Removed);
726        }
727
728        // Then, (re-)add all the remaining DOM children. Note that this means that some children
729        // may end up being "Moved" even though they haven't changed parents, and may even be in the
730        // same position as previously.
731        let weak_self = ref_self.downgrade();
732        for dom_child in remaining_dom_children {
733            let (child_id, child_ref) = tree.get_or_create_node(&dom_child, update);
734
735            // Update self.child_nodes in place.
736            self.child_nodes.push(child_ref.clone());
737            new_child_ids.push(child_id);
738
739            let mut child = child_ref.borrow_mut();
740            child.parent_node = Some(weak_self.clone());
741
742            if update.is_new(&child_id) {
743                let child_damage = child.update_node_and_populate_new_descendants_from_dom_node(
744                    child_ref.clone(),
745                    &dom_child,
746                    AccessibilityDamage::Rebuild,
747                    tree,
748                    update,
749                );
750                child.update_node_local(child_damage, update);
751                update.add(&mut child);
752            } else {
753                update.set_tree_state_change(child_id, TreeChange::PendingMove);
754            }
755
756            self.dirty_state
757                .propagate_descendant_has_damage(child.dirty_state);
758        }
759
760        // We can't update the AccessKit node's `children` in place, so we build up the full list
761        // and then set it here.
762        self.accesskit_node.set_children(new_child_ids);
763        self.dirty_state |= DirtyState::Updated;
764
765        LocalAccessibilityDamage::SubtreeChanged
766    }
767
768    /// Update this node's properties from its corresponding DOM node.
769    fn update_properties_from_dom_node(
770        &mut self,
771        dom_node: &ServoLayoutNode<'_>,
772        dom_damage: AccessibilityDamage,
773    ) -> LocalAccessibilityDamage {
774        let mut local_damage = LocalAccessibilityDamage::empty();
775        if !dom_damage.contains(AccessibilityDamage::Text) {
776            return local_damage;
777        }
778        local_damage.insert(self.set_role(role_from_dom_node(dom_node)));
779        if dom_node.type_id() == Some(LayoutNodeType::Text) {
780            let text_content = dom_node.text_content();
781            trace!("node text content = {text_content:?}");
782            // FIXME: this should take into account editing selection units (grapheme clusters?)
783            local_damage.insert(self.set_value(&text_content));
784        }
785
786        local_damage
787    }
788
789    /// Update this node's properties based on changes already made to the accessibility tree.
790    /// For example, if there were nodes added or removed in its subtree, its computed text may have
791    /// changed, so that will be recomputed here.
792    /// If any changes are made, add this node to the given [`AccessibilityUpdate`].
793    fn update_node_local(
794        &mut self,
795        local_damage: LocalAccessibilityDamage,
796        update: &mut AccessibilityUpdate,
797    ) -> LocalAccessibilityDamage {
798        update.counters.nodes_updated_from_tree += 1;
799
800        let mut new_damage = LocalAccessibilityDamage::empty();
801        if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) ||
802            local_damage.contains(LocalAccessibilityDamage::RoleChanged)
803        {
804            if let Some(text) = self.label_from_descendants() {
805                new_damage.insert(self.set_label(text.as_str()));
806            } else {
807                new_damage.insert(self.clear_label());
808            }
809        }
810
811        new_damage
812    }
813
814    fn label_from_descendants(&self) -> Option<String> {
815        if !NAME_FROM_CONTENTS_ROLES.contains(&self.role()) {
816            return None;
817        }
818        let mut children = VecDeque::from_iter(self.children().cloned());
819        let mut text = String::new();
820        while let Some(child) = children.pop_front() {
821            let child = child.borrow();
822            match child.role() {
823                Role::TextRun => {
824                    if let Some(child_text) = child.value() {
825                        text.push_str(child_text);
826                    }
827                },
828                _ => {
829                    for node in child.children().rev() {
830                        children.push_front(node.clone());
831                    }
832                },
833            }
834        }
835        Some(text.trim().to_owned())
836    }
837
838    fn print(&self, print_tree: &mut PrintTree) {
839        if self.child_nodes.is_empty() {
840            print_tree.add_item(format!("{self:?}"));
841            return;
842        }
843
844        print_tree.new_level(format!("{self:?}"));
845
846        for child in self.children() {
847            child.borrow().print(print_tree);
848        }
849        print_tree.end_level();
850    }
851
852    fn parent(&self) -> Option<ArcRefCell<AccessibilityNode>> {
853        self.parent_node.as_ref().and_then(|weak| weak.upgrade())
854    }
855
856    // TODO: use macros to generate getter/setter methods.
857
858    fn children(&self) -> impl DoubleEndedIterator<Item = &ArcRefCell<AccessibilityNode>> {
859        self.child_nodes.iter()
860    }
861
862    fn ancestors(&self) -> impl Iterator<Item = ArcRefCell<AccessibilityNode>> {
863        AccessibilityNodeIterator::new(self.parent(), |node| node.parent_node.clone()?.upgrade())
864    }
865
866    fn child_ids(&self) -> &[NodeId] {
867        self.accesskit_node.children()
868    }
869
870    fn role(&self) -> Role {
871        self.accesskit_node.role()
872    }
873
874    fn set_role(&mut self, role: Role) -> LocalAccessibilityDamage {
875        if role == self.accesskit_node.role() {
876            return LocalAccessibilityDamage::empty();
877        }
878        self.accesskit_node.set_role(role);
879        self.dirty_state |= DirtyState::Updated;
880        LocalAccessibilityDamage::RoleChanged
881    }
882
883    fn label(&self) -> Option<&str> {
884        self.accesskit_node.label()
885    }
886
887    fn set_label(&mut self, label: &str) -> LocalAccessibilityDamage {
888        if Some(label) == self.accesskit_node.label() {
889            return LocalAccessibilityDamage::empty();
890        }
891        self.accesskit_node.set_label(label);
892        self.dirty_state |= DirtyState::Updated;
893        LocalAccessibilityDamage::TextChanged
894    }
895
896    fn clear_label(&mut self) -> LocalAccessibilityDamage {
897        if self.accesskit_node.label().is_none() {
898            return LocalAccessibilityDamage::empty();
899        }
900        self.accesskit_node.clear_label();
901        self.dirty_state |= DirtyState::Updated;
902        LocalAccessibilityDamage::TextChanged
903    }
904
905    fn html_tag(&self) -> Option<&str> {
906        self.accesskit_node.html_tag()
907    }
908
909    fn set_html_tag(&mut self, html_tag: &str) {
910        if Some(html_tag) == self.accesskit_node.html_tag() {
911            return;
912        }
913        self.accesskit_node.set_html_tag(html_tag);
914        self.dirty_state |= DirtyState::Updated;
915    }
916
917    fn value(&self) -> Option<&str> {
918        self.accesskit_node.value()
919    }
920
921    fn set_value(&mut self, value: &str) -> LocalAccessibilityDamage {
922        if Some(value) == self.accesskit_node.value() {
923            return LocalAccessibilityDamage::empty();
924        }
925        self.accesskit_node.set_value(value);
926        self.dirty_state |= DirtyState::Updated;
927        LocalAccessibilityDamage::TextChanged
928    }
929
930    fn assert_integrity(&self, expected_parent: Option<WeakRefCell<AccessibilityNode>>) {
931        debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
932
933        if let Some(actual_parent) = &self.parent_node {
934            let expected = expected_parent.expect("Actual parent but no expected parent");
935            let expected = expected.upgrade().expect("Expected parent was dropped");
936            let actual = actual_parent.upgrade().expect("Actual parent was dropped");
937            assert!(actual.ptr_eq(&expected));
938        } else {
939            assert!(
940                expected_parent.is_none(),
941                "Expected parent but no actual parent"
942            );
943        }
944
945        assert!(
946            self.dirty_state.is_empty(),
947            "{self:?} has dirty state {:?}",
948            self.dirty_state
949        );
950
951        let children_ids: Vec<_> = self.children().map(|child| child.borrow().id).collect();
952        assert_eq!(
953            children_ids,
954            self.child_ids(),
955            "children() IDs didn't match child_ids() for {self:?}"
956        );
957    }
958}
959
960impl Debug for AccessibilityNode {
961    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
962        write!(f, "{:?}: {:?}", self.id, self.role())?;
963        if let Some(html_tag) = self.html_tag() {
964            write!(f, " (html_tag: {html_tag:?})")?;
965        }
966        if let Some(label) = self.label() {
967            write!(f, "\nlabel: {label:?}")?;
968        }
969        if !self.child_ids().is_empty() {
970            write!(f, "\nchildren: {:?}", self.child_ids())?;
971        }
972        Ok(())
973    }
974}
975
976impl AccessibilityUpdate {
977    fn new(rooted_nodes: Option<FxHashSet<OpaqueNode>>) -> Self {
978        Self {
979            changed_nodes: FxHashSet::default(),
980            tree_changes: FxHashMap::default(),
981            counters: UpdateCounters::default(),
982            rooted_nodes,
983        }
984    }
985
986    fn add(&mut self, node: &mut AccessibilityNode) {
987        self.changed_nodes.insert(node.id);
988        node.dirty_state -= DirtyState::Updated;
989    }
990
991    fn set_tree_state_change(&mut self, node_id: NodeId, change: TreeChange) {
992        let old_change = self.tree_changes.get(&node_id);
993
994        assert!(
995            change != TreeChange::Moved,
996            "Incoming change must never be Moved"
997        );
998
999        let resolved_change = old_change
1000            .map(|old_change| match (old_change, change) {
1001                (TreeChange::PendingMove, TreeChange::Removed) => TreeChange::Moved,
1002                (TreeChange::Removed, TreeChange::PendingMove) => TreeChange::Moved,
1003                _ => {
1004                    unreachable!("Logically impossible state change: {old_change:?} → {change:?}")
1005                },
1006            })
1007            .unwrap_or(change);
1008
1009        self.tree_changes.insert(node_id, resolved_change);
1010    }
1011
1012    fn is_new(&mut self, node_id: &NodeId) -> bool {
1013        self.tree_changes.get(node_id) == Some(&TreeChange::New)
1014    }
1015
1016    /// Consume this `AccessibilityUpdate`, producing an [`accesskit::TreeUpdate`] if there have
1017    /// been any changes to `tree`.
1018    /// This will pass `self` into [`AccessibilityTree::remove_stale_nodes()`] to consume
1019    /// [`Self::tree_changes`].
1020    fn finalize(
1021        mut self,
1022        tree: &mut AccessibilityTree,
1023    ) -> (Option<accesskit::TreeUpdate>, UpdateCounters) {
1024        let root_node_id = tree
1025            .root_node
1026            .clone()
1027            .expect("AccessibilityUpdate::finalize() called but no root_node set in tree")
1028            .borrow()
1029            .id;
1030
1031        if self.changed_nodes.is_empty() {
1032            assert!(self.tree_changes.is_empty());
1033            return (None, self.counters);
1034        }
1035
1036        let changed_nodes = std::mem::take(&mut self.changed_nodes);
1037        let mut counters = std::mem::take(&mut self.counters);
1038
1039        tree.drop_removed_nodes(self);
1040
1041        let changed_nodes: Vec<_> = changed_nodes
1042            .into_iter()
1043            .filter_map(|id| Some((id, tree.node_for_id(id)?.borrow().accesskit_node.clone())))
1044            .collect();
1045
1046        counters.nodes_in_tree_update = changed_nodes.len().try_into().unwrap_or_default();
1047
1048        let accesskit_tree = accesskit::Tree::new(root_node_id);
1049        let tree_update = accesskit::TreeUpdate {
1050            // Filter out any nodes which were both changed and removed.
1051            nodes: changed_nodes,
1052            tree: Some(accesskit_tree),
1053            focus: NodeId(1),
1054            tree_id: tree.tree_id,
1055        };
1056
1057        (Some(tree_update), counters)
1058    }
1059}
1060
1061impl DirtyState {
1062    fn updated(&self) -> bool {
1063        self.contains(DirtyState::Updated)
1064    }
1065
1066    fn descendant_has_damage(&self) -> bool {
1067        self.contains(DirtyState::DescendantHasDamage)
1068    }
1069
1070    fn propagate_descendant_has_damage(&mut self, child_dirty_state: DirtyState) {
1071        if child_dirty_state.self_or_descendant_has_damage() {
1072            self.insert(DirtyState::DescendantHasDamage)
1073        }
1074    }
1075
1076    fn self_or_descendant_has_damage(&self) -> bool {
1077        self.intersects(DirtyState::HasDamage | DirtyState::DescendantHasDamage)
1078    }
1079}
1080
1081#[cfg(test)]
1082#[test]
1083fn test_accessibility_update_add_some_nodes_twice() {
1084    let mut tree = AccessibilityTree::new(accesskit::TreeId::ROOT, Epoch::default());
1085    let mut root_update = AccessibilityUpdate::new(None);
1086
1087    let root_node = tree.get_or_create_node_with_id(NodeId(2), &mut root_update);
1088    tree.root_node = Some(root_node.clone());
1089
1090    let nodes: Vec<_> = [
1091        (3, Role::GenericContainer),
1092        (4, Role::Heading),
1093        (5, Role::Paragraph),
1094    ]
1095    .into_iter()
1096    .map(|(id, role)| {
1097        let id = NodeId(id);
1098        let node = tree.get_or_create_node_with_id(id, &mut root_update);
1099        node.borrow_mut().set_role(role);
1100        (id, node)
1101    })
1102    .collect();
1103
1104    {
1105        let (child_node_ids, child_nodes): (Vec<_>, Vec<_>) = nodes.iter().cloned().unzip();
1106        let mut root_node = root_node.borrow_mut();
1107        root_node.accesskit_node.set_children(child_node_ids);
1108        root_node.child_nodes = child_nodes;
1109    }
1110
1111    let mut update = AccessibilityUpdate::new(None);
1112
1113    {
1114        let node_3 = tree.assert_node_for_id(&NodeId(3));
1115        let mut node_3 = node_3.borrow_mut();
1116        let node_4 = tree.assert_node_for_id(&NodeId(4));
1117        let mut node_4 = node_4.borrow_mut();
1118        let node_5 = tree.assert_node_for_id(&NodeId(5));
1119        let mut node_5 = node_5.borrow_mut();
1120
1121        update.add(&mut node_5);
1122        update.add(&mut node_3);
1123        update.add(&mut node_4);
1124        update.add(&mut node_4);
1125
1126        node_3.set_role(Role::ScrollView);
1127        update.add(&mut node_3);
1128    }
1129
1130    let (tree_update, _) = update.finalize(&mut tree);
1131    let mut tree_update = tree_update.expect("finalize should produce a tree update");
1132    tree_update.nodes.sort_by_key(|(node_id, _node)| *node_id);
1133    assert_eq!(
1134        tree_update,
1135        accesskit::TreeUpdate {
1136            nodes: vec![
1137                (NodeId(3), accesskit::Node::new(Role::ScrollView)),
1138                (NodeId(4), accesskit::Node::new(Role::Heading)),
1139                (NodeId(5), accesskit::Node::new(Role::Paragraph)),
1140            ],
1141            tree: Some(accesskit::Tree {
1142                root: NodeId(2),
1143                toolkit_name: None,
1144                toolkit_version: None
1145            }),
1146            tree_id: accesskit::TreeId::ROOT,
1147            focus: NodeId(1),
1148        }
1149    );
1150}
1151
1152static HTML_ELEMENT_ROLE_MAPPINGS: LazyLock<FxHashMap<LocalName, Role>> = LazyLock::new(|| {
1153    [
1154        (local_name!("article"), Role::Article),
1155        (local_name!("aside"), Role::Complementary),
1156        (local_name!("body"), Role::RootWebArea),
1157        (local_name!("footer"), Role::ContentInfo),
1158        (local_name!("h1"), Role::Heading),
1159        (local_name!("h2"), Role::Heading),
1160        (local_name!("h3"), Role::Heading),
1161        (local_name!("h4"), Role::Heading),
1162        (local_name!("h5"), Role::Heading),
1163        (local_name!("h6"), Role::Heading),
1164        (local_name!("header"), Role::Banner),
1165        (local_name!("hr"), Role::Splitter),
1166        (local_name!("main"), Role::Main),
1167        (local_name!("nav"), Role::Navigation),
1168        (local_name!("p"), Role::Paragraph),
1169    ]
1170    .into_iter()
1171    .collect()
1172});
1173
1174/// <https://w3c.github.io/aria/#namefromcontent>
1175static NAME_FROM_CONTENTS_ROLES: LazyLock<FxHashSet<Role>> =
1176    LazyLock::new(|| [(Role::Heading)].into_iter().collect());