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::cell::RefCell;
5use std::collections::VecDeque;
6use std::fmt::Debug;
7use std::iter::repeat;
8use std::sync::atomic::AtomicU64;
9use std::sync::{LazyLock, atomic};
10
11use accesskit::{Affine, NodeId, Role};
12use app_units::Au;
13use bitflags::bitflags;
14use euclid::Rect;
15use layout_api::{
16    AccessibilityDamage, BoxAreaType, LayoutElement, LayoutNode, LayoutNodeType,
17    node_id_from_scroll_id,
18};
19use log::trace;
20use num_traits::ToPrimitive;
21use paint_api::display_list::SpatialTreeNodeInfo;
22use rustc_hash::{FxHashMap, FxHashSet};
23use script::layout_dom::{ServoLayoutElement, ServoLayoutNode};
24use servo_base::Epoch;
25use servo_base::print_tree::PrintTree;
26use servo_config::opts::{self, DiagnosticsLogging, DiagnosticsLoggingOption};
27use servo_config::pref;
28use style::Atom;
29use style::dom::OpaqueNode;
30use style_traits::CSSPixel;
31use web_atoms::{LocalName, local_name, ns};
32use webrender_api::ExternalScrollId;
33use webrender_api::units::LayoutVector2D;
34
35use crate::ArcRefCell;
36use crate::cell::WeakRefCell;
37use crate::display_list::StackingContextTree;
38use crate::layout_impl::LayoutThread;
39use crate::query::process_box_area_request;
40
41bitflags! {
42    /// Damage which was caused by changes to the accessibility tree. These changes can cause other
43    /// properties to need to be re-computed based on the updated values, either on the same node or
44    /// on other nodes.
45    #[derive(Clone, Copy, Default, Debug, Eq, PartialEq)]
46    struct LocalAccessibilityDamage: u16 {
47        /// This node's children changed, and/or any node in its subtree changed.
48        const SubtreeChanged = 0b0001;
49        /// This node's computed role changed.
50        const RoleChanged = 0b0010;
51        /// This node's computed label or text value (for a text node) changed.
52        const TextChanged = 0b0100;
53    }
54}
55
56/// Everything the accessibility tree needs from layout in order to compute node bounds during an
57/// update.
58pub(super) struct AccessibilityContext<'update> {
59    pub(super) layout_thread: &'update LayoutThread,
60    pub(super) stacking_context_tree: &'update StackingContextTree,
61}
62
63/// All the [`AccessibilityDamage`] which comes from outside the accessibility tree itself.
64pub(super) type AccessibilityDamageMap<'a> =
65    FxHashMap<OpaqueNode, (ServoLayoutNode<'a>, AccessibilityDamage)>;
66
67/// Convert a rectangle as layout reports it into the one [`accesskit`] wants.
68fn au_rect_to_accesskit_rect(rect: Rect<Au, CSSPixel>) -> accesskit::Rect {
69    accesskit::Rect::new(
70        rect.min_x().to_f64_px(),
71        rect.min_y().to_f64_px(),
72        rect.max_x().to_f64_px(),
73        rect.max_y().to_f64_px(),
74    )
75}
76
77fn scroll_offset_to_affine(layout_vector: LayoutVector2D) -> Affine {
78    Affine::translate((
79        -layout_vector.x.to_f64().unwrap_or(0.),
80        -layout_vector.y.to_f64().unwrap_or(0.),
81    ))
82}
83
84/// Changes which have occurred during the current update, and data required to process the update.
85struct AccessibilityUpdate<'update> {
86    /// Nodes whose internal data has changed within the current update.
87    changed_nodes: FxHashSet<NodeId>,
88    /// Nodes that changed their relation to the tree within the current update.
89    tree_changes: FxHashMap<NodeId, TreeChange>,
90    /// Counters to track how many nodes we've checked for changes or updated in this tree update.
91    counters: UpdateCounters,
92
93    /// Map of [`NodeId`] to the [`AccessibilityDamage`] which was passed in for that node.
94    damage_map: FxHashMap<NodeId, AccessibilityDamage>,
95    /// Map of [`NodeId`] to the corresponding [`ServoLayoutNode`]. This is populated for nodes
96    /// which have damage, including nodes which are newly added to the accessibility tree.
97    dom_node_map: RefCell<FxHashMap<NodeId, ServoLayoutNode<'update>>>,
98
99    /// Nodes which were removed from the DOM tree since the last reflow, which were rooted in
100    /// `AccessibilityData`. Only set if `pref::expensive_accessibility_test_assertions_enabled`
101    /// is set.
102    rooted_nodes: Option<FxHashSet<OpaqueNode>>,
103}
104
105#[derive(Debug, Default)]
106pub struct UpdateCounters {
107    pub nodes_updated_from_dom: u32,
108    pub nodes_updated_from_tree: u32,
109    pub nodes_updated_bounds: u32,
110    pub nodes_in_tree_update: u32,
111}
112
113bitflags! {
114    /// Flags tracking an [`AccessibilityNode`]'s dirty state during an update. All flags which are
115    /// set during the update should be unset by the end of the update.
116    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
117    struct DirtyState : u16 {
118        /// At least one descendant of this node has unresolved damage from the DOM tree.
119        const DescendantHasDamage = 0b0001;
120        /// This node has unresolved damage from the DOM tree.
121        const HasDamage = 0b0010;
122        /// This node's data changed, but it hasn't yet been added to the [`AccessibilityUpdate`].
123        const Updated = 0b0100;
124    }
125}
126
127struct AccessibilityNode {
128    /// The unique ID for the node. This is used both as a key in [`AccessibilityTree`]'s cache of
129    /// nodes, and as an identifier in [`accesskit`] datastructures: [`accesskit::Node`]s,
130    /// [`accesskit::TreeUpdate`]s and [`accesskit::ActionRequest`]s.
131    id: NodeId,
132    /// The computed [`accesskit::Node`] data. This will be copied and serialized into a
133    /// [`accesskit::TreeUpdate`] whenever it is changed during an update.
134    accesskit_node: accesskit::Node,
135    /// This node's parent, if any.
136    parent_node: Option<WeakRefCell<AccessibilityNode>>,
137    /// All this node's children.
138    child_nodes: Vec<ArcRefCell<AccessibilityNode>>,
139    /// The [`OpaqueNode`] for the DOM node which corresponds to this accessibility node, if any.
140    /// An accessibility node may not correspond to a DOM node if it corresponds to a
141    /// pseudo-element, or in a test.
142    opaque_node: Option<OpaqueNode>,
143    /// This node's scroll offset, if it is a scroll container which has scrolled. This is used to
144    /// translate this node's children.
145    scroll_offset: Option<LayoutVector2D>,
146    /// Any dirty state for the current update.
147    dirty_state: DirtyState,
148}
149
150/// A retained, internal representation of the accessibility tree for a document.
151///
152/// [`accesskit`] only provides interchange types for tree updates and action requests, so we need
153/// to define our own representation for incremental tree building.
154#[derive(Debug)]
155pub struct AccessibilityTree {
156    /// All nodes currently in the tree as of the most recent update. New nodes are added and stale
157    /// nodes are pruned during [`AccessibilityTree::update_tree()`].
158    nodes: FxHashMap<NodeId, ArcRefCell<AccessibilityNode>>,
159    /// A map to allow retrieving the [`AccessibilityNode`] which corresponds to a particular DOM
160    /// node, if any.
161    ///
162    /// This must be kept in sync with [`Self::id_to_opaque_node`].
163    opaque_node_to_id: FxHashMap<OpaqueNode, NodeId>,
164    /// A map to retrieve the `OpaqueNode` corresponding to a particular [`AccessibilityNode`], if
165    /// any.
166    ///
167    /// This must be kept in sync with [`Self::opaque_node_to_id`].
168    id_to_opaque_node: FxHashMap<NodeId, OpaqueNode>,
169    /// Sent with each [`accesskit::TreeUpdate`]. This allows this tree to be
170    /// [grafted](https://docs.rs/accesskit/latest/accesskit/struct.Node.html#method.tree_id) into
171    /// an application's tree.
172    tree_id: accesskit::TreeId,
173    /// This node's ID is sent with each [`accesskit::TreeUpdate`] to identify the root node.
174    /// Also used for any complete tree walk, such as in [`Self::assert_integrity()`] and
175    /// [`Self::print()`].
176    root_node: Option<ArcRefCell<AccessibilityNode>>,
177    /// If any nodes were scrolled since the last update, they are tracked here so that the next
178    /// update can update the tree accordingly.
179    pending_scroll_updates: FxHashMap<ExternalScrollId, LayoutVector2D>,
180    /// Sent to the embedder alongside each [`accesskit::TreeUpdate`], so that the embedder can
181    /// drop updates from documents which have been navigated away from.
182    embedder_epoch: Epoch,
183    /// Debug options, copied from configuration to this `AccessibilityTree` in order
184    /// to avoid having to constantly access the thread-safe global options.
185    debug: DiagnosticsLogging,
186}
187
188/// Tracks changes to a node's relation to the tree within an update.
189///
190/// This is used to remove nodes from the accessibility tree's cache when they are no longer in the
191/// tree.
192#[derive(Debug, PartialEq, Copy, Clone)]
193enum TreeChange {
194    /// The node was newly created in this update.
195    New,
196
197    /// The node has been re-parented in this update.
198    Moved,
199
200    /// The node has been added to its new parent, but not yet removed from its old
201    /// parent.
202    ///
203    /// When a node is moved within the tree, it must be both removed from its old parent
204    /// and added to its new parent within the same update. This may happen in either
205    /// order, depending on the relative positions of the node before and after it moves.
206    ///
207    /// - If a node's new parent is updated before its old parent, the node will be in a
208    ///   `TreeChange::PendingMove` state until its old parent is updated. We expect that it
209    ///   must later be removed from its old parent, at which point its state will be updated to
210    ///   `TreeChange::Moved`.
211    /// - If a node's old parent is updated before its new parent, the node will be first
212    ///   `TreeChange::Removed` and then `TreeChange::Moved`.
213    ///
214    /// At the end of the update, we assert that there are no pending moves remaining.
215    PendingMove,
216
217    /// The node is no longer a child of its previous parent.
218    Removed,
219}
220
221impl AccessibilityTree {
222    /// See [`Self::tree_id`] and [`Self::embedder_epoch`] for explanations of the parameters.
223    pub(super) fn new(tree_id: accesskit::TreeId, embedder_epoch: Epoch) -> Self {
224        Self {
225            nodes: FxHashMap::default(),
226            opaque_node_to_id: FxHashMap::default(),
227            id_to_opaque_node: FxHashMap::default(),
228            tree_id,
229            root_node: None,
230            pending_scroll_updates: FxHashMap::default(),
231            embedder_epoch,
232            debug: opts::get().debug.clone(),
233        }
234    }
235
236    /// Update this tree based on the current state of the given DOM tree, and if anything changed,
237    /// return an [`accesskit::TreeUpdate`] representing what changed.
238    pub(super) fn update_tree<'update>(
239        &mut self,
240        root_dom_node: &ServoLayoutNode<'update>,
241        damage_from_dom: AccessibilityDamageMap<'update>,
242        context: AccessibilityContext<'update>,
243        rooted_nodes: Option<FxHashSet<OpaqueNode>>,
244    ) -> (Option<accesskit::TreeUpdate>, UpdateCounters) {
245        let mut update = AccessibilityUpdate::new(damage_from_dom, rooted_nodes, self);
246
247        self.ensure_root_node(root_dom_node, &context, &mut update);
248
249        self.apply_changes_from_dom_tree(&context, &mut update);
250
251        self.handle_pending_scroll_updates(&mut update);
252
253        update.finalize(self)
254    }
255
256    /// Add all given scroll updates to [`Self::pending_scroll_updates`].
257    /// See [`Self::handle_pending_scroll_updates()`].
258    pub(super) fn add_pending_scroll_updates(
259        &mut self,
260        scroll_states: FxHashMap<ExternalScrollId, LayoutVector2D>,
261    ) {
262        self.pending_scroll_updates.extend(scroll_states);
263    }
264
265    /// Add the given scroll update to [`Self::pending_scroll_updates`].
266    /// See [`Self::handle_pending_scroll_updates()`].
267    pub(super) fn add_pending_scroll_update(
268        &mut self,
269        external_scroll_id: ExternalScrollId,
270        offset: LayoutVector2D,
271    ) {
272        self.pending_scroll_updates
273            .insert(external_scroll_id, offset);
274    }
275
276    /// Get the node corresponding to the root DOM node, and set it as this tree's root. If the root
277    /// node is newly created, which probably means this accessibility tree is newly created, append
278    /// an `AccessibilityDamage::Rebuild` value for it to `damage_from_dom`.
279    fn ensure_root_node<'update>(
280        &mut self,
281        root_dom_node: &ServoLayoutNode<'update>,
282        context: &AccessibilityContext<'update>,
283        update: &mut AccessibilityUpdate<'update>,
284    ) {
285        let (root_id, root_node) = self.get_or_create_node(root_dom_node, update);
286        if update.is_new(&root_id) {
287            // We're going to rebuild the whole tree, so ignore any incoming damage.
288            update.clear_damage();
289            update.insert_damage(root_id, AccessibilityDamage::Rebuild);
290            update.insert_dom_node(root_id, *root_dom_node);
291            self.populate_pending_scroll_updates_from_scroll_tree(context);
292        } else {
293            // TODO(#47161) This hack is necessary because we don't collect accessibility damage
294            // from layout.
295            update.insert_damage(root_id, AccessibilityDamage::Subtree);
296            update.insert_dom_node(root_id, *root_dom_node);
297        }
298
299        self.root_node = Some(root_node);
300    }
301
302    /// Update all nodes with damage tracked in `update` based on their `AccessibilityDamage`. If
303    /// any [`LocalAccessibilityDamage`] results from the update, propagate
304    /// [`LocalAccessibilityDamage::SubtreeChanged`] to its ancestors.
305    fn apply_changes_from_dom_tree(
306        &mut self,
307        context: &AccessibilityContext,
308        update: &mut AccessibilityUpdate,
309    ) {
310        let Some(damage_root_id) = self.mark_nodes_and_ancestors_dirty(update) else {
311            return;
312        };
313        let damage_root = self.assert_node_for_id(&damage_root_id);
314        let local_damage =
315            damage_root
316                .borrow_mut()
317                .update_subtree(damage_root.clone(), context, self, update);
318
319        damage_root.borrow().update_ancestors(local_damage, update);
320    }
321
322    /// Read all scroll offsets directly from the scroll tree, and use them to populate
323    /// [`Self::pending_scroll_updates`].
324    /// This will clear any previous pending scroll updates, as the scroll tree contains all scroll
325    /// information.
326    fn populate_pending_scroll_updates_from_scroll_tree(&mut self, context: &AccessibilityContext) {
327        let scroll_tree = &context.stacking_context_tree.paint_info.scroll_tree;
328        let scroll_updates = scroll_tree
329            .nodes
330            .iter()
331            .filter_map(|node| match node.info {
332                SpatialTreeNodeInfo::Scroll(ref info) => {
333                    let offset = info.offset;
334                    Some((info.external_id, offset))
335                },
336                _ => None,
337            })
338            .collect();
339        self.pending_scroll_updates = scroll_updates;
340    }
341
342    /// For each entry in [`Self::pending_scroll_updates`], set the scroll offset on the
343    /// [`AccessibilityNode`] corresponding to its [`ExternalScrollId`], if any.
344    /// This sets a transformation on every direct child of the scrolled node.
345    ///
346    /// This should be called after the tree has been updated, so that we can be sure not to miss
347    /// any newly-added nodes.
348    fn handle_pending_scroll_updates(&mut self, update: &mut AccessibilityUpdate) {
349        let pending_scroll_updates = std::mem::take(&mut self.pending_scroll_updates);
350        for (opaque, offset) in
351            pending_scroll_updates
352                .into_iter()
353                .filter_map(|(scroll_id, translate)| {
354                    if scroll_id.is_root() {
355                        let root_node_opaque =
356                            self.root_node.as_ref()?.clone().borrow().opaque_node?;
357                        return Some((root_node_opaque, translate));
358                    }
359                    let node_id = node_id_from_scroll_id(scroll_id.0 as usize);
360                    let opaque = OpaqueNode(node_id);
361                    Some((opaque, translate))
362                })
363        {
364            let Some(node) = self.node_for_opaque(opaque) else {
365                continue;
366            };
367            node.borrow_mut().set_scroll_offset(offset, update);
368        }
369    }
370
371    /// Given an iterator of `NodeId`s corresponding to nodes which have received some damage from
372    /// the DOM:
373    /// - mark each node as `dirty`;
374    /// - mark all of each node's ancestors as `has_dirty_descendants`;
375    /// - return the lowest common ancestor node of all the damaged nodes.
376    fn mark_nodes_and_ancestors_dirty(
377        &mut self,
378        update: &mut AccessibilityUpdate,
379    ) -> Option<NodeId> {
380        let mut dirty_node_ids = update.damage_map.keys();
381
382        // An ordered list of common ancestors for the nodes seen so far, from shallowest to
383        // deepest. At the end of the loop, the lowest common ancestor is the last node in this vec.
384        let mut common_ancestors: Vec<NodeId> = Vec::new();
385
386        {
387            // Initialize the list of potential common ancestors.
388            let node_id = dirty_node_ids.next()?;
389            update.collect_dom_node_ancestors(node_id, self);
390            let first_node = self.assert_node_for_id(node_id);
391            let mut first_node = first_node.borrow_mut();
392            first_node.dirty_state |= DirtyState::HasDamage;
393            common_ancestors.push(first_node.id);
394            common_ancestors.extend(first_node.ancestors().map(|ancestor| {
395                let mut ancestor = ancestor.borrow_mut();
396                ancestor.dirty_state |= DirtyState::DescendantHasDamage;
397                ancestor.id
398            }));
399            common_ancestors.reverse();
400        }
401
402        let mut truncate_ancestors = |node: &AccessibilityNode| -> bool {
403            if node.dirty_state.descendant_has_damage() {
404                if let Some(pos) = common_ancestors.iter().position(|&id| id == node.id) {
405                    common_ancestors.truncate(pos + 1);
406                }
407                return true;
408            }
409            false
410        };
411
412        for node_id in dirty_node_ids {
413            let node = self.assert_node_for_id(node_id);
414            let mut node = node.borrow_mut();
415            node.dirty_state |= DirtyState::HasDamage;
416
417            if truncate_ancestors(&node) {
418                continue;
419            }
420
421            for ancestor in node.ancestors() {
422                let mut ancestor = ancestor.borrow_mut();
423
424                // If we find an ancestor we've already seen, discard any potential ancestors deeper
425                // than this one, and go on to the next dirty node.
426                if truncate_ancestors(&ancestor) {
427                    break;
428                }
429
430                ancestor.dirty_state |= DirtyState::DescendantHasDamage;
431            }
432        }
433
434        common_ancestors.pop()
435    }
436
437    /// Get the [`AccessibilityNode`] corresponding to the given DOM node.
438    /// If there is no existing [`AccessibilityNode`] for this DOM node, it will be created and
439    /// marked as having [`AccessibilityDamage::Rebuild`] in `update`.
440    fn get_or_create_node(
441        &mut self,
442        dom_node: &ServoLayoutNode<'_>,
443        update: &mut AccessibilityUpdate,
444    ) -> (NodeId, ArcRefCell<AccessibilityNode>) {
445        let id = self.get_or_create_id_for_opaque(dom_node.opaque());
446        let node_ref = self.get_or_create_node_with_id(id, update);
447
448        if update.is_new(&id) {
449            let mut node = node_ref.borrow_mut();
450            node.opaque_node = Some(dom_node.opaque());
451            if let Some(dom_element) = dom_node.as_element() {
452                let local_name = dom_element.local_name().to_ascii_lowercase();
453                node.set_html_tag(&local_name);
454            }
455            update.insert_damage(id, AccessibilityDamage::Rebuild);
456            node.dirty_state |= DirtyState::HasDamage;
457        }
458
459        (id, node_ref)
460    }
461
462    fn get_or_create_node_with_id(
463        &mut self,
464        id: NodeId,
465        update: &mut AccessibilityUpdate,
466    ) -> ArcRefCell<AccessibilityNode> {
467        if let Some(node) = self.nodes.get(&id) {
468            return node.clone();
469        }
470
471        let node = ArcRefCell::new(AccessibilityNode::new(id));
472        update.set_tree_state_change(id, TreeChange::New);
473        self.nodes.insert(id, node.clone());
474
475        node
476    }
477
478    fn node_for_id(&self, id: NodeId) -> Option<ArcRefCell<AccessibilityNode>> {
479        self.nodes.get(&id).cloned()
480    }
481
482    fn assert_node_for_id(&self, id: &NodeId) -> ArcRefCell<AccessibilityNode> {
483        let Some(node) = self.nodes.get(id) else {
484            panic!("{id:?} does not exist in tree");
485        };
486        node.clone()
487    }
488
489    fn node_for_opaque(&self, opaque: OpaqueNode) -> Option<ArcRefCell<AccessibilityNode>> {
490        self.nodes
491            .get(&self.existing_id_for_opaque(opaque)?)
492            .cloned()
493    }
494
495    /// Consume the [`AccessibilityUpdate`] by deleting all nodes it detected as being removed from
496    /// the tree.
497    fn drop_removed_nodes(&mut self, mut update: AccessibilityUpdate) {
498        let mut rooted_nodes = std::mem::take(&mut update.rooted_nodes);
499        if let Some(rooted_nodes) = rooted_nodes.as_mut() {
500            self.assert_removed_nodes_were_rooted(&update, rooted_nodes);
501        }
502
503        let mut ids_to_remove: Vec<_> = update
504            .tree_changes
505            .iter()
506            .filter_map(|(id, change)| match change {
507                TreeChange::Removed => Some(id),
508                TreeChange::PendingMove => None,
509                TreeChange::New => None,
510                TreeChange::Moved => None,
511            })
512            .cloned()
513            .collect();
514
515        while let Some(id) = ids_to_remove.pop() {
516            if update.tree_changes.get(&id) == Some(&TreeChange::PendingMove) {
517                // Mark the move as completed by marking the node as removed from its old position.
518                update.set_tree_state_change(id, TreeChange::Removed);
519
520                // Since this node is actually moved, don't continue removing its subtree.
521                continue;
522            }
523
524            if let Some(opaque_node) = self.id_to_opaque_node.remove(&id) {
525                self.opaque_node_to_id.remove(&opaque_node);
526            }
527            let node = self.nodes.remove(&id).expect("Node {id:?} already removed");
528            ids_to_remove.extend(node.borrow().child_ids());
529        }
530
531        update
532            .tree_changes
533            .drain()
534            .for_each(|(id, change)| match change {
535                TreeChange::PendingMove => unreachable!(
536                    "Pending move found for node id {id:?} when draining tree state changes"
537                ),
538                TreeChange::Removed => (),
539                TreeChange::New => (),
540                TreeChange::Moved => (),
541            });
542
543        if let Some(rooted_nodes) = rooted_nodes {
544            self.assert_remaining_rooted_nodes_not_in_tree(rooted_nodes);
545        }
546
547        if self
548            .debug
549            .is_enabled(DiagnosticsLoggingOption::AccessibilityTree)
550        {
551            self.print();
552        }
553
554        if pref!(expensive_accessibility_test_assertions_enabled) {
555            self.assert_integrity();
556        }
557    }
558
559    /// If we got `rooted_nodes` from the document's `AccessibilityData`, assert that every node we
560    /// marked as `TreeChange::Removed` during this update was rooted.
561    fn assert_removed_nodes_were_rooted(
562        &mut self,
563        update: &AccessibilityUpdate,
564        rooted_nodes: &mut FxHashSet<OpaqueNode>,
565    ) {
566        debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
567        for (id, change) in update.tree_changes.iter() {
568            if change == &TreeChange::Removed {
569                let Some(&opaque_node) = self.id_to_opaque_node.get(id) else {
570                    panic!("No opaque node found for removed node: id {id:?}");
571                };
572                assert!(
573                    rooted_nodes.remove(&opaque_node),
574                    "Node removed from accessibility tree wasn't rooted: id {id:?}"
575                );
576            };
577        }
578    }
579
580    /// If we got `rooted_nodes` from the document's `AccessibilityData`, assert that any nodes
581    /// which were rooted but not marked as `TreeChange::Removed` are no longer in the tree after
582    /// dropping all nodes which were removed from the tree. They may have been part of a subtree
583    /// which was marked `TreeChange::Removed` on an ancestor node, or may have never made it into
584    /// the accessibility tree to begin with.
585    fn assert_remaining_rooted_nodes_not_in_tree(&self, rooted_nodes: FxHashSet<OpaqueNode>) {
586        for leftover_node in rooted_nodes {
587            assert!(
588                !self.opaque_node_to_id.contains_key(&leftover_node),
589                "Found node removed from DOM tree but not accessibility tree: {:#x}",
590                leftover_node.0
591            );
592        }
593    }
594
595    fn get_or_create_id_for_opaque(&mut self, opaque: OpaqueNode) -> NodeId {
596        let id = self.opaque_node_to_id.entry(opaque).or_insert_with(|| {
597            static LAST_ID: AtomicU64 = AtomicU64::new(0);
598            let id = LAST_ID.fetch_add(1, atomic::Ordering::SeqCst).into();
599            self.id_to_opaque_node.insert(id, opaque);
600            id
601        });
602        *id
603    }
604
605    fn existing_id_for_opaque(&self, opaque: OpaqueNode) -> Option<NodeId> {
606        self.opaque_node_to_id.get(&opaque).cloned()
607    }
608
609    pub(crate) fn embedder_epoch(&self) -> Epoch {
610        self.embedder_epoch
611    }
612
613    /// Assert that the tree is a tree without any dangling references or orphaned nodes.
614    ///
615    /// For accessibility tests only, because it’s expensive.
616    fn assert_integrity(&self) {
617        debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
618        let Some(root_node) = self.root_node.clone() else {
619            return;
620        };
621
622        // Traverse the tree from the given root.
623        // `nodes` is a Vec of pairs of nodes and their expected parents.
624        let mut nodes = vec![(root_node, None)];
625        let mut seen_node_ids = FxHashSet::default();
626        while let Some((node, expected_parent)) = nodes.pop() {
627            let node = node.borrow();
628
629            // If this fails, then the tree is not a tree at all.
630            assert!(
631                seen_node_ids.insert(node.id),
632                "Tree contains {:?} in multiple places",
633                node.id
634            );
635
636            node.assert_integrity(expected_parent);
637
638            // assert_node_for_id() here double-checks that the node hasn't been incorrectly evicted
639            // from the map while it's still retained as a child node.
640            let weak_node = Some(self.assert_node_for_id(&node.id).downgrade());
641            nodes.extend(node.children().cloned().zip(repeat(weak_node)));
642        }
643
644        // If this fails, then the tree has orphaned nodes (a leak).
645        // If a node has been incorrectly removed from the map, that will be caught above.
646        assert_eq!(seen_node_ids, self.nodes.keys().copied().collect());
647    }
648
649    fn print(&self) {
650        let Some(root_node) = self.root_node.clone() else {
651            return;
652        };
653
654        let mut print_tree = PrintTree::new("Accessibility Tree");
655        root_node.borrow().print(&mut print_tree);
656        print_tree.end_level();
657    }
658}
659
660/// <https://w3c.github.io/aria/#host_general_role>
661fn role_from_role_attribute(dom_element: &ServoLayoutElement<'_>) -> Option<Role> {
662    let role_attribute = dom_element.attribute(&ns!(), &local_name!("role"))?;
663    role_attribute
664        .as_tokens()
665        .iter()
666        .filter_map(|role_name_in_attribute| SUPPORTED_ARIA_ROLES.get(role_name_in_attribute))
667        .next()
668        .cloned()
669}
670
671fn role_from_dom_node(dom_node: &ServoLayoutNode<'_>) -> Role {
672    if let Some(dom_element) = dom_node.as_element() {
673        role_from_role_attribute(&dom_element).unwrap_or_else(|| {
674            let local_name = dom_element.local_name().to_ascii_lowercase();
675            *HTML_ELEMENT_ROLE_MAPPINGS
676                .get(&local_name)
677                .unwrap_or(&Role::GenericContainer)
678        })
679    } else if dom_node.type_id() == Some(LayoutNodeType::Text) {
680        Role::TextRun
681    } else {
682        Role::GenericContainer
683    }
684}
685
686struct AccessibilityNodeIterator<I>
687where
688    I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
689{
690    next_value: Option<ArcRefCell<AccessibilityNode>>,
691    next_fn: I,
692}
693
694impl<I> AccessibilityNodeIterator<I>
695where
696    I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
697{
698    fn new(next_value: Option<ArcRefCell<AccessibilityNode>>, next_fn: I) -> Self {
699        AccessibilityNodeIterator {
700            next_value,
701            next_fn,
702        }
703    }
704}
705
706impl<I> Iterator for AccessibilityNodeIterator<I>
707where
708    I: Fn(&AccessibilityNode) -> Option<ArcRefCell<AccessibilityNode>>,
709{
710    type Item = ArcRefCell<AccessibilityNode>;
711
712    fn next(&mut self) -> Option<Self::Item> {
713        let next_value = self.next_value.take();
714        self.next_value = next_value
715            .as_ref()
716            .and_then(|node| (self.next_fn)(&node.borrow()));
717        next_value
718    }
719}
720
721impl AccessibilityNode {
722    fn new(id: NodeId) -> Self {
723        Self::new_with_role(id, Role::Unknown)
724    }
725
726    fn new_with_role(id: NodeId, role: Role) -> Self {
727        Self {
728            id,
729            accesskit_node: accesskit::Node::new(role),
730            parent_node: None,
731            child_nodes: vec![],
732            opaque_node: None,
733            scroll_offset: None,
734            dirty_state: DirtyState::empty(),
735        }
736    }
737
738    /// Update this node and its subtree based on damage from the DOM.
739    ///
740    /// - First, if this node has damage from the DOM to be resolved, update the node from the DOM
741    ///   tree, recursively populating any new children.
742    /// - Next, recursively call this method for any children which are dirty, or have dirty
743    ///   descendants.
744    /// - Finally, update any properties on this node which are may have changed due to other
745    ///   changes in the tree.
746    ///
747    /// At the end of this method, both `has_dirty_descendants` and `is_dirty` should be false for
748    /// this node and all its descendants.
749    fn update_subtree<'update>(
750        &mut self,
751        ref_self: ArcRefCell<Self>,
752        context: &AccessibilityContext,
753        tree: &mut AccessibilityTree,
754        update: &mut AccessibilityUpdate<'update>,
755    ) -> LocalAccessibilityDamage {
756        let mut local_damage = LocalAccessibilityDamage::empty();
757
758        let damage = self.compute_damage(update);
759
760        if let Some(dom_node) = update.take_dom_node(&self.id) {
761            // TODO(#47162, #47161): Once we handle scrolling properly and have a way of tracking
762            // damage from layout, we won't need to update every node.
763            local_damage.insert(self.update_properties_and_children_from_dom_node(
764                ref_self, &dom_node, damage, tree, update,
765            ));
766            self.update_bounds_from_dom_node(&dom_node, context, update);
767
768            if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) &&
769                let Some(scroll_offset) = self.scroll_offset
770            {
771                // If children have changed, re-set the scroll transforms on all children.
772                self.set_scroll_offset(scroll_offset, update);
773            }
774
775            self.dirty_state -= DirtyState::HasDamage;
776        }
777
778        for child_node in self.children() {
779            let child_node_ref = child_node.clone();
780            let mut child_node = child_node.borrow_mut();
781            let child_local_damage =
782                child_node.update_subtree(child_node_ref, context, tree, update);
783            if !child_local_damage.is_empty() {
784                local_damage.insert(LocalAccessibilityDamage::SubtreeChanged);
785            }
786        }
787        self.dirty_state -= DirtyState::DescendantHasDamage;
788
789        local_damage.insert(self.update_node_local(local_damage, update));
790
791        if self.dirty_state.updated() {
792            update.add(self);
793        }
794
795        local_damage
796    }
797
798    /// Update each of this node's ancestors based on changes which have already been applied in the
799    /// tree.
800    fn update_ancestors(
801        &self,
802        local_damage: LocalAccessibilityDamage,
803        update: &mut AccessibilityUpdate,
804    ) {
805        if local_damage.is_empty() {
806            return;
807        }
808        for node in self.ancestors() {
809            let mut node = node.borrow_mut();
810            node.update_node_local(LocalAccessibilityDamage::SubtreeChanged, update);
811            node.dirty_state -= DirtyState::DescendantHasDamage;
812            if node.dirty_state.updated() {
813                update.add(&mut node);
814            }
815        }
816    }
817
818    /// Update the given [`AccessibilityNode`] from its corresponding DOM node and
819    /// [`AccessibilityDamage`].
820    /// If it has new children, those will be created here, but not yet populated.
821    // Any changed nodes will be added to the given [`AccessibilityUpdate`].
822    fn update_properties_and_children_from_dom_node<'update>(
823        &mut self,
824        ref_self: ArcRefCell<Self>,
825        dom_node: &ServoLayoutNode<'update>,
826        dom_damage: AccessibilityDamage,
827        tree: &mut AccessibilityTree,
828        update: &mut AccessibilityUpdate<'update>,
829    ) -> LocalAccessibilityDamage {
830        let mut local_damage = LocalAccessibilityDamage::empty();
831
832        // TODO(#47162): We currently need to walk the children each time so that we always find the
833        // DOM node for each accessibility node, since we update bounds on every node.
834        // Once this is no longer true, we can check dom_damage and potentially early return here.
835
836        update.counters.nodes_updated_from_dom += 1;
837
838        local_damage.insert(self.update_properties_from_dom_node(dom_node, dom_damage));
839        local_damage.insert(
840            self.update_children_from_dom_node(ref_self, dom_node, dom_damage, tree, update),
841        );
842
843        local_damage
844    }
845
846    /// Update this node's [`Self::children`] from its corresponding DOM node.
847    /// If it has new children, those will be created here, but not yet populated.
848    fn update_children_from_dom_node<'update>(
849        &mut self,
850        ref_self: ArcRefCell<AccessibilityNode>,
851        dom_node: &ServoLayoutNode<'update>,
852        _dom_damage: AccessibilityDamage,
853        tree: &mut AccessibilityTree,
854        update: &mut AccessibilityUpdate<'update>,
855    ) -> LocalAccessibilityDamage {
856        // TODO(#47162): We currently need to walk the children each time so that we always find the
857        // DOM node for each accessibility node, since we update bounds on every node.
858        // Once this is no longer true, we can check _dom_damage and potentially early return here.
859
860        let mut remaining_dom_children = dom_node.flat_tree_children().peekable();
861        let mut old_child_ids = self.child_ids().iter().peekable();
862        let mut unchanged_count = 0usize;
863
864        // Iterate over existing children and DOM children while they match. No action is necessary
865        // for these nodes.
866        while let Some(&old_id) = old_child_ids.peek() &&
867            let Some(dom_child) = remaining_dom_children.peek()
868        {
869            if tree.existing_id_for_opaque(dom_child.opaque()) == Some(*old_id) {
870                update.insert_dom_node(*old_id, *dom_child);
871                unchanged_count += 1;
872                old_child_ids.next();
873                remaining_dom_children.next();
874            } else {
875                break;
876            }
877        }
878
879        // If we iterated over all the DOM children without finding any changes, we're done.
880        if old_child_ids.peek().is_none() && remaining_dom_children.peek().is_none() {
881            return LocalAccessibilityDamage::empty();
882        }
883
884        // Remove all child nodes after the first `unchanged_count`.
885        self.child_nodes.truncate(unchanged_count);
886        let mut new_child_ids = Vec::from(self.child_ids());
887        for removed_child_id in new_child_ids.split_off(unchanged_count) {
888            update.set_tree_state_change(removed_child_id, TreeChange::Removed);
889        }
890
891        // Then, (re-)add all the remaining DOM children. Note that this means that some children
892        // may end up being "Moved" even though they haven't changed parents, and may even be in the
893        // same position as previously.
894        let weak_self = ref_self.downgrade();
895        for dom_child in remaining_dom_children {
896            let (child_id, child_ref) = tree.get_or_create_node(&dom_child, update);
897            // TODO(#47162): Since we need to update bounds for all nodes, we need to ensure every
898            // AccessibilityNode has a corresponding DOM node available to be retrieved from the
899            // AccessibilityUpdate. Once we no longer update bounds on all nodes, we won't need to
900            // add all nodes like this.
901            update.insert_dom_node(child_id, dom_child);
902
903            // Update self.child_nodes in place.
904            self.child_nodes.push(child_ref.clone());
905            new_child_ids.push(child_id);
906
907            let mut child = child_ref.borrow_mut();
908            child.parent_node = Some(weak_self.clone());
909
910            if update.is_new(&child_id) {
911                self.dirty_state |= DirtyState::DescendantHasDamage;
912            } else {
913                update.set_tree_state_change(child_id, TreeChange::PendingMove);
914            }
915
916            self.dirty_state
917                .propagate_descendant_has_damage(child.dirty_state);
918        }
919
920        // We can't update the AccessKit node's `children` in place, so we build up the full list
921        // and then set it here.
922        self.accesskit_node.set_children(new_child_ids);
923        self.dirty_state |= DirtyState::Updated;
924
925        LocalAccessibilityDamage::SubtreeChanged
926    }
927
928    /// Update this node's properties from its corresponding DOM node.
929    fn update_properties_from_dom_node(
930        &mut self,
931        dom_node: &ServoLayoutNode,
932        dom_damage: AccessibilityDamage,
933    ) -> LocalAccessibilityDamage {
934        let mut local_damage = LocalAccessibilityDamage::empty();
935        if !dom_damage.contains(AccessibilityDamage::Node) {
936            return local_damage;
937        }
938        local_damage.insert(self.set_role(role_from_dom_node(dom_node)));
939        if dom_node.type_id() == Some(LayoutNodeType::Text) {
940            let text_content = dom_node.text_content();
941            trace!("node text content = {text_content:?}");
942            // FIXME: this should take into account editing selection units (grapheme clusters?)
943            local_damage.insert(self.set_value(&text_content));
944        }
945
946        local_damage
947    }
948
949    /// Update this node's bounds from the current layout geometry.
950    fn update_bounds_from_dom_node(
951        &mut self,
952        dom_node: &ServoLayoutNode,
953        context: &AccessibilityContext,
954        update: &mut AccessibilityUpdate,
955    ) {
956        update.counters.nodes_updated_bounds += 1;
957
958        // Border box without transforms. Bounds are in CSS pixels, relative to the document origin;
959        // scroll containers set translations on their child nodes, and the embedder's graft node
960        // carries the transform that composes them into AccessKit's coordinate space (see the
961        // "Coordinates" section of
962        // <https://docs.rs/accesskit/latest/accesskit/struct.Node.html>).
963        // TODO(#47166): This doesn't take any CSS transforms into account.
964        let bounds = process_box_area_request(
965            context.layout_thread,
966            context.stacking_context_tree,
967            *dom_node,
968            BoxAreaType::Border,
969            true, /* exclude_transform_and_inline */
970        )
971        .map(au_rect_to_accesskit_rect);
972
973        // For now only nodes with a box of their own get bounds; anything else, including
974        // `display: none` content, gets its bounds cleared. That leaves two kinds of nodes
975        // without geometry which assistive technology would like to have some:
976        //
977        // TODO(#47164): A text node never has bounds of its own: `LayoutBox::Text` has no
978        // `LayoutBoxBase`, and `Fragment::Text` has no box area, so the query above always returns
979        // `None` for one. Text nodes should get the union of the rectangles of their own
980        // `Fragment::Text` fragments, once `cumulative_box_area_rect()` can handle those.
981        //
982        // TODO(#47163): A `display: contents` element generates no box either. Other
983        // engines (Blink, WebKit, Gecko) compute its bounds as the union of the bounding boxes of
984        // its rendered descendants.
985        match bounds {
986            Some(bounds) => self.set_bounds(bounds),
987            None => self.clear_bounds(),
988        }
989    }
990
991    /// Update this node's properties based on changes already made to the accessibility tree.
992    /// For example, if there were nodes added or removed in its subtree, its computed text may have
993    /// changed, so that will be recomputed here.
994    /// If any changes are made, add this node to the given [`AccessibilityUpdate`].
995    fn update_node_local(
996        &mut self,
997        local_damage: LocalAccessibilityDamage,
998        update: &mut AccessibilityUpdate,
999    ) -> LocalAccessibilityDamage {
1000        let mut new_damage = LocalAccessibilityDamage::empty();
1001        if local_damage.is_empty() {
1002            return new_damage;
1003        }
1004        update.counters.nodes_updated_from_tree += 1;
1005
1006        if local_damage.contains(LocalAccessibilityDamage::SubtreeChanged) ||
1007            local_damage.contains(LocalAccessibilityDamage::RoleChanged)
1008        {
1009            if let Some(text) = self.label_from_descendants() {
1010                new_damage.insert(self.set_label(text.as_str()));
1011            } else {
1012                new_damage.insert(self.clear_label());
1013            }
1014        }
1015
1016        new_damage
1017    }
1018
1019    fn label_from_descendants(&self) -> Option<String> {
1020        if !NAME_FROM_CONTENTS_ROLES.contains(&self.role()) {
1021            return None;
1022        }
1023        let mut children = VecDeque::from_iter(self.children().cloned());
1024        let mut text = String::new();
1025        while let Some(child) = children.pop_front() {
1026            let child = child.borrow();
1027            match child.role() {
1028                Role::TextRun => {
1029                    if let Some(child_text) = child.value() {
1030                        text.push_str(child_text);
1031                    }
1032                },
1033                _ => {
1034                    for node in child.children().rev() {
1035                        children.push_front(node.clone());
1036                    }
1037                },
1038            }
1039        }
1040        Some(text.trim().to_owned())
1041    }
1042
1043    fn print(&self, print_tree: &mut PrintTree) {
1044        if self.child_nodes.is_empty() {
1045            print_tree.add_item(format!("{self:?}"));
1046            return;
1047        }
1048
1049        print_tree.new_level(format!("{self:?}"));
1050
1051        for child in self.children() {
1052            child.borrow().print(print_tree);
1053        }
1054        print_tree.end_level();
1055    }
1056
1057    fn parent(&self) -> Option<ArcRefCell<AccessibilityNode>> {
1058        self.parent_node.as_ref().and_then(|weak| weak.upgrade())
1059    }
1060
1061    fn children(&self) -> impl DoubleEndedIterator<Item = &ArcRefCell<AccessibilityNode>> {
1062        self.child_nodes.iter()
1063    }
1064
1065    fn ancestors(&self) -> impl Iterator<Item = ArcRefCell<AccessibilityNode>> {
1066        AccessibilityNodeIterator::new(self.parent(), |node| node.parent_node.clone()?.upgrade())
1067    }
1068
1069    fn child_ids(&self) -> &[NodeId] {
1070        self.accesskit_node.children()
1071    }
1072
1073    fn set_scroll_offset(&mut self, offset: LayoutVector2D, update: &mut AccessibilityUpdate) {
1074        self.scroll_offset = Some(offset);
1075        let transform = scroll_offset_to_affine(offset);
1076        for child in self.children() {
1077            let mut child = child.borrow_mut();
1078            child.set_transform(transform);
1079            if child.dirty_state.updated() {
1080                update.add(&mut child);
1081            }
1082        }
1083    }
1084
1085    // TODO: use macros to generate getter/setter methods.
1086
1087    fn role(&self) -> Role {
1088        self.accesskit_node.role()
1089    }
1090
1091    fn set_role(&mut self, role: Role) -> LocalAccessibilityDamage {
1092        if role == self.accesskit_node.role() {
1093            return LocalAccessibilityDamage::empty();
1094        }
1095        self.accesskit_node.set_role(role);
1096        self.dirty_state |= DirtyState::Updated;
1097        LocalAccessibilityDamage::RoleChanged
1098    }
1099
1100    fn label(&self) -> Option<&str> {
1101        self.accesskit_node.label()
1102    }
1103
1104    fn set_label(&mut self, label: &str) -> LocalAccessibilityDamage {
1105        if Some(label) == self.accesskit_node.label() {
1106            return LocalAccessibilityDamage::empty();
1107        }
1108        self.accesskit_node.set_label(label);
1109        self.dirty_state |= DirtyState::Updated;
1110        LocalAccessibilityDamage::TextChanged
1111    }
1112
1113    fn clear_label(&mut self) -> LocalAccessibilityDamage {
1114        if self.accesskit_node.label().is_none() {
1115            return LocalAccessibilityDamage::empty();
1116        }
1117        self.accesskit_node.clear_label();
1118        self.dirty_state |= DirtyState::Updated;
1119        LocalAccessibilityDamage::TextChanged
1120    }
1121
1122    fn html_tag(&self) -> Option<&str> {
1123        self.accesskit_node.html_tag()
1124    }
1125
1126    fn set_html_tag(&mut self, html_tag: &str) {
1127        if Some(html_tag) == self.accesskit_node.html_tag() {
1128            return;
1129        }
1130        self.accesskit_node.set_html_tag(html_tag);
1131        self.dirty_state |= DirtyState::Updated;
1132    }
1133
1134    fn value(&self) -> Option<&str> {
1135        self.accesskit_node.value()
1136    }
1137
1138    fn set_value(&mut self, value: &str) -> LocalAccessibilityDamage {
1139        if Some(value) == self.accesskit_node.value() {
1140            return LocalAccessibilityDamage::empty();
1141        }
1142        self.accesskit_node.set_value(value);
1143        self.dirty_state |= DirtyState::Updated;
1144        LocalAccessibilityDamage::TextChanged
1145    }
1146
1147    fn bounds(&self) -> Option<accesskit::Rect> {
1148        self.accesskit_node.bounds()
1149    }
1150
1151    fn set_bounds(&mut self, bounds: accesskit::Rect) {
1152        if Some(bounds) == self.accesskit_node.bounds() {
1153            return;
1154        }
1155        self.accesskit_node.set_bounds(bounds);
1156        self.dirty_state |= DirtyState::Updated;
1157    }
1158
1159    fn clear_bounds(&mut self) {
1160        if self.accesskit_node.bounds().is_none() {
1161            return;
1162        }
1163        self.accesskit_node.clear_bounds();
1164        self.dirty_state |= DirtyState::Updated;
1165    }
1166
1167    fn set_transform(&mut self, transform: Affine) {
1168        // TODO(#47166): Right now a node will only ever have a single transform from a scroll
1169        // container, if any. Once we correctly support CSS transforms, a node may have multiple
1170        // transforms, which we'll need to be able to combine.
1171        if self.accesskit_node.transform() == Some(&transform) {
1172            return;
1173        }
1174        if transform == Affine::IDENTITY {
1175            self.clear_transform();
1176            return;
1177        }
1178        self.accesskit_node.set_transform(transform);
1179        self.dirty_state |= DirtyState::Updated;
1180    }
1181
1182    fn clear_transform(&mut self) {
1183        if self.accesskit_node.transform().is_none() {
1184            return;
1185        }
1186        self.accesskit_node.clear_transform();
1187        self.dirty_state |= DirtyState::Updated;
1188    }
1189
1190    fn assert_integrity(&self, expected_parent: Option<WeakRefCell<AccessibilityNode>>) {
1191        debug_assert!(pref!(expensive_accessibility_test_assertions_enabled));
1192
1193        if let Some(actual_parent) = &self.parent_node {
1194            let expected = expected_parent.expect("Actual parent but no expected parent");
1195            let expected = expected.upgrade().expect("Expected parent was dropped");
1196            let actual = actual_parent.upgrade().expect("Actual parent was dropped");
1197            assert!(actual.ptr_eq(&expected));
1198        } else {
1199            assert!(
1200                expected_parent.is_none(),
1201                "Expected parent but no actual parent"
1202            );
1203        }
1204
1205        assert!(
1206            self.dirty_state.is_empty(),
1207            "{self:?} has dirty state {:?}",
1208            self.dirty_state
1209        );
1210
1211        let children_ids: Vec<_> = self.children().map(|child| child.borrow().id).collect();
1212        assert_eq!(
1213            children_ids,
1214            self.child_ids(),
1215            "children() IDs didn't match child_ids() for {self:?}"
1216        );
1217    }
1218
1219    fn compute_damage(&self, update: &mut AccessibilityUpdate) -> AccessibilityDamage {
1220        let mut damage = AccessibilityDamage::empty();
1221
1222        if self.dirty_state.has_damage() {
1223            damage |= update.take_damage(&self.id);
1224        }
1225
1226        damage
1227    }
1228}
1229
1230impl Debug for AccessibilityNode {
1231    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1232        write!(f, "{:?}: {:?}", self.id, self.role())?;
1233        if let Some(html_tag) = self.html_tag() {
1234            write!(f, " (html_tag: {html_tag:?})")?;
1235        }
1236        if let Some(label) = self.label() {
1237            write!(f, "\nlabel: {label:?}")?;
1238        }
1239        if let Some(bounds) = self.bounds() {
1240            write!(f, "\nbounds: {bounds:?}")?;
1241        }
1242        if !self.child_ids().is_empty() {
1243            write!(f, "\nchildren: {:?}", self.child_ids())?;
1244        }
1245        Ok(())
1246    }
1247}
1248
1249impl<'update> AccessibilityUpdate<'update> {
1250    fn new(
1251        dom_damage: AccessibilityDamageMap<'update>,
1252        rooted_nodes: Option<FxHashSet<OpaqueNode>>,
1253        tree: &AccessibilityTree,
1254    ) -> Self {
1255        let damage_map = dom_damage
1256            .iter()
1257            .filter_map(|(&opaque, &(_dom_node, damage))| {
1258                let id = tree.existing_id_for_opaque(opaque)?;
1259                Some((id, damage))
1260            })
1261            .collect();
1262        let dom_node_map = dom_damage
1263            .into_iter()
1264            .filter_map(|(opaque, (dom_node, _damage))| {
1265                let id = tree.existing_id_for_opaque(opaque)?;
1266                Some((id, dom_node))
1267            })
1268            .collect();
1269        Self {
1270            changed_nodes: FxHashSet::default(),
1271            tree_changes: FxHashMap::default(),
1272            counters: UpdateCounters::default(),
1273            damage_map,
1274            dom_node_map: RefCell::new(dom_node_map),
1275            rooted_nodes,
1276        }
1277    }
1278
1279    fn add(&mut self, node: &mut AccessibilityNode) {
1280        self.changed_nodes.insert(node.id);
1281        node.dirty_state -= DirtyState::Updated;
1282    }
1283
1284    fn set_tree_state_change(&mut self, node_id: NodeId, change: TreeChange) {
1285        let old_change = self.tree_changes.get(&node_id);
1286
1287        assert!(
1288            change != TreeChange::Moved,
1289            "Incoming change must never be Moved"
1290        );
1291
1292        let resolved_change = old_change
1293            .map(|old_change| match (old_change, change) {
1294                (TreeChange::PendingMove, TreeChange::Removed) => TreeChange::Moved,
1295                (TreeChange::Removed, TreeChange::PendingMove) => TreeChange::Moved,
1296                _ => {
1297                    unreachable!("Logically impossible state change: {old_change:?} → {change:?}")
1298                },
1299            })
1300            .unwrap_or(change);
1301
1302        self.tree_changes.insert(node_id, resolved_change);
1303    }
1304
1305    fn is_new(&mut self, node_id: &NodeId) -> bool {
1306        self.tree_changes.get(node_id) == Some(&TreeChange::New)
1307    }
1308
1309    /// Consume this `AccessibilityUpdate`, producing an [`accesskit::TreeUpdate`] if there have
1310    /// been any changes to `tree`.
1311    /// This will pass `self` into [`AccessibilityTree::remove_stale_nodes()`] to consume
1312    /// [`Self::tree_changes`].
1313    fn finalize(
1314        mut self,
1315        tree: &mut AccessibilityTree,
1316    ) -> (Option<accesskit::TreeUpdate>, UpdateCounters) {
1317        let root_node_id = tree
1318            .root_node
1319            .clone()
1320            .expect("AccessibilityUpdate::finalize() called but no root_node set in tree")
1321            .borrow()
1322            .id;
1323
1324        if self.changed_nodes.is_empty() {
1325            assert!(self.tree_changes.is_empty());
1326            return (None, self.counters);
1327        }
1328
1329        let changed_nodes = std::mem::take(&mut self.changed_nodes);
1330        let mut counters = std::mem::take(&mut self.counters);
1331
1332        tree.drop_removed_nodes(self);
1333
1334        // Filter out any nodes which were both changed and removed.
1335        let changed_nodes: Vec<_> = changed_nodes
1336            .into_iter()
1337            .filter_map(|id| Some((id, tree.node_for_id(id)?.borrow().accesskit_node.clone())))
1338            .collect();
1339
1340        counters.nodes_in_tree_update = changed_nodes.len().try_into().unwrap_or_default();
1341
1342        let accesskit_tree = accesskit::Tree::new(root_node_id);
1343        let tree_update = accesskit::TreeUpdate {
1344            nodes: changed_nodes,
1345            tree: Some(accesskit_tree),
1346            focus: NodeId(1),
1347            tree_id: tree.tree_id,
1348        };
1349
1350        (Some(tree_update), counters)
1351    }
1352
1353    fn clear_damage(&mut self) {
1354        self.damage_map.clear();
1355    }
1356
1357    fn insert_damage(&mut self, node_id: NodeId, damage: AccessibilityDamage) {
1358        self.damage_map.insert(node_id, damage);
1359    }
1360
1361    fn insert_dom_node(&self, node_id: NodeId, dom_node: ServoLayoutNode<'update>) {
1362        self.dom_node_map.borrow_mut().insert(node_id, dom_node);
1363    }
1364
1365    fn take_damage(&mut self, node_id: &NodeId) -> AccessibilityDamage {
1366        self.damage_map
1367            .remove(node_id)
1368            .unwrap_or(AccessibilityDamage::empty())
1369    }
1370
1371    fn take_dom_node(&mut self, node_id: &NodeId) -> Option<ServoLayoutNode<'update>> {
1372        self.dom_node_map.borrow_mut().remove(node_id)
1373    }
1374
1375    #[expect(unsafe_code)]
1376    fn collect_dom_node_ancestors(&self, node_id: &NodeId, tree: &AccessibilityTree) {
1377        let mut dom_node_map = self.dom_node_map.borrow_mut();
1378        let dom_node = dom_node_map
1379            .get(node_id)
1380            .expect("collect_dom_node_ancestors should be called for a known DOM node");
1381        let mut parent = unsafe { dom_node.dangerous_flat_tree_parent() };
1382        while let Some(node) = parent {
1383            if let Some(node_id) = tree.existing_id_for_opaque(node.opaque()) {
1384                dom_node_map.insert(node_id, node);
1385            }
1386            parent = unsafe { node.dangerous_flat_tree_parent() };
1387        }
1388    }
1389}
1390
1391impl DirtyState {
1392    fn updated(&self) -> bool {
1393        self.contains(DirtyState::Updated)
1394    }
1395
1396    fn has_damage(&self) -> bool {
1397        self.contains(DirtyState::HasDamage)
1398    }
1399
1400    fn descendant_has_damage(&self) -> bool {
1401        self.contains(DirtyState::DescendantHasDamage)
1402    }
1403
1404    fn propagate_descendant_has_damage(&mut self, child_dirty_state: DirtyState) {
1405        if child_dirty_state.self_or_descendant_has_damage() {
1406            self.insert(DirtyState::DescendantHasDamage)
1407        }
1408    }
1409
1410    fn self_or_descendant_has_damage(&self) -> bool {
1411        self.intersects(DirtyState::HasDamage | DirtyState::DescendantHasDamage)
1412    }
1413}
1414
1415#[cfg(test)]
1416#[test]
1417fn test_accessibility_update_add_some_nodes_twice() {
1418    let mut tree = AccessibilityTree::new(accesskit::TreeId::ROOT, Epoch::default());
1419    let mut root_update = AccessibilityUpdate::new(AccessibilityDamageMap::default(), None, &tree);
1420
1421    let root_node = tree.get_or_create_node_with_id(NodeId(2), &mut root_update);
1422    tree.root_node = Some(root_node.clone());
1423
1424    let nodes: Vec<_> = [
1425        (3, Role::GenericContainer),
1426        (4, Role::Heading),
1427        (5, Role::Paragraph),
1428    ]
1429    .into_iter()
1430    .map(|(id, role)| {
1431        let id = NodeId(id);
1432        let node = tree.get_or_create_node_with_id(id, &mut root_update);
1433        node.borrow_mut().set_role(role);
1434        (id, node)
1435    })
1436    .collect();
1437
1438    {
1439        let (child_node_ids, child_nodes): (Vec<_>, Vec<_>) = nodes.iter().cloned().unzip();
1440        let mut root_node = root_node.borrow_mut();
1441        root_node.accesskit_node.set_children(child_node_ids);
1442        root_node.child_nodes = child_nodes;
1443    }
1444
1445    let mut update = AccessibilityUpdate::new(AccessibilityDamageMap::default(), None, &tree);
1446
1447    {
1448        let node_3 = tree.assert_node_for_id(&NodeId(3));
1449        let mut node_3 = node_3.borrow_mut();
1450        let node_4 = tree.assert_node_for_id(&NodeId(4));
1451        let mut node_4 = node_4.borrow_mut();
1452        let node_5 = tree.assert_node_for_id(&NodeId(5));
1453        let mut node_5 = node_5.borrow_mut();
1454
1455        update.add(&mut node_5);
1456        update.add(&mut node_3);
1457        update.add(&mut node_4);
1458        update.add(&mut node_4);
1459
1460        node_3.set_role(Role::ScrollView);
1461        update.add(&mut node_3);
1462    }
1463
1464    let (tree_update, _) = update.finalize(&mut tree);
1465    let mut tree_update = tree_update.expect("finalize should produce a tree update");
1466    tree_update.nodes.sort_by_key(|(node_id, _node)| *node_id);
1467    assert_eq!(
1468        tree_update,
1469        accesskit::TreeUpdate {
1470            nodes: vec![
1471                (NodeId(3), accesskit::Node::new(Role::ScrollView)),
1472                (NodeId(4), accesskit::Node::new(Role::Heading)),
1473                (NodeId(5), accesskit::Node::new(Role::Paragraph)),
1474            ],
1475            tree: Some(accesskit::Tree {
1476                root: NodeId(2),
1477                toolkit_name: None,
1478                toolkit_version: None
1479            }),
1480            tree_id: accesskit::TreeId::ROOT,
1481            focus: NodeId(1),
1482        }
1483    );
1484}
1485
1486static HTML_ELEMENT_ROLE_MAPPINGS: LazyLock<FxHashMap<LocalName, Role>> = LazyLock::new(|| {
1487    [
1488        (local_name!("article"), Role::Article),
1489        (local_name!("aside"), Role::Complementary),
1490        (local_name!("body"), Role::RootWebArea),
1491        (local_name!("footer"), Role::ContentInfo),
1492        (local_name!("h1"), Role::Heading),
1493        (local_name!("h2"), Role::Heading),
1494        (local_name!("h3"), Role::Heading),
1495        (local_name!("h4"), Role::Heading),
1496        (local_name!("h5"), Role::Heading),
1497        (local_name!("h6"), Role::Heading),
1498        (local_name!("header"), Role::Banner),
1499        (local_name!("hr"), Role::Splitter),
1500        (local_name!("main"), Role::Main),
1501        (local_name!("nav"), Role::Navigation),
1502        (local_name!("p"), Role::Paragraph),
1503    ]
1504    .into_iter()
1505    .collect()
1506});
1507
1508/// A map from role names allowed in the 'role' attribute of an HTML element to the corresponding
1509/// [`Role`] in AccessKit.
1510///
1511/// This is currently just the roles that don't have any [supported][1] or [required][2] properties
1512/// and also don't require an [accessible name][3].
1513/// [1]: https://w3c.github.io/aria/#supportedState
1514/// [2]: https://w3c.github.io/aria/#requiredState
1515/// [3]: https://w3c.github.io/aria/#namefromauthor
1516static SUPPORTED_ARIA_ROLES: LazyLock<FxHashMap<Atom, Role>> = LazyLock::new(|| {
1517    [
1518        (Atom::from("alert"), Role::Alert),
1519        (Atom::from("banner"), Role::Banner),
1520        (Atom::from("blockquote"), Role::Blockquote),
1521        (Atom::from("caption"), Role::Caption),
1522        (Atom::from("code"), Role::Code),
1523        (Atom::from("complementary"), Role::Complementary),
1524        (Atom::from("contentinfo"), Role::ContentInfo),
1525        (Atom::from("definition"), Role::Definition),
1526        (Atom::from("deletion"), Role::ContentDeletion),
1527        (Atom::from("directory"), Role::Unknown),
1528        (Atom::from("document"), Role::Document),
1529        (Atom::from("emphasis"), Role::Emphasis),
1530        (Atom::from("feed"), Role::Feed),
1531        (Atom::from("figure"), Role::Figure),
1532        (Atom::from("generic"), Role::GenericContainer),
1533        (Atom::from("insertion"), Role::ContentInsertion),
1534        (Atom::from("list"), Role::List),
1535        (Atom::from("log"), Role::Log),
1536        (Atom::from("main"), Role::Main),
1537        (Atom::from("math"), Role::Math),
1538        (Atom::from("navigation"), Role::Navigation),
1539        (Atom::from("none"), Role::GenericContainer),
1540        (Atom::from("note"), Role::Note),
1541        (Atom::from("paragraph"), Role::Paragraph),
1542        (Atom::from("presentation"), Role::GenericContainer),
1543        (Atom::from("rowgroup"), Role::RowGroup),
1544        (Atom::from("search"), Role::Search),
1545        (Atom::from("status"), Role::Status),
1546        (Atom::from("strong"), Role::Strong),
1547        // (Atom::from("subscript"), Role::Subscript), // no corresponding accesskit role.
1548        // (Atom::from("superscript"), Role::Superscript), // no corresponding accesskit role.
1549        (Atom::from("term"), Role::Term),
1550        (Atom::from("time"), Role::Time),
1551        (Atom::from("timer"), Role::Timer),
1552    ]
1553    .into_iter()
1554    .collect()
1555});
1556
1557/// <https://w3c.github.io/aria/#namefromcontent>
1558static NAME_FROM_CONTENTS_ROLES: LazyLock<FxHashSet<Role>> =
1559    LazyLock::new(|| [(Role::Heading)].into_iter().collect());