Skip to main content

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