Skip to main content

script/dom/document/
accessibility_data.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use js::context::NoGC;
6use rustc_hash::FxHashSet;
7use script_bindings::root::Dom;
8use servo_config::pref;
9
10use crate::dom::Node;
11
12#[derive(Clone, Default, JSTraceable, MallocSizeOf)]
13#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
14pub(crate) struct AccessibilityData {
15    /// Nodes which have been unbound from the DOM but may not yet have been removed from the
16    /// accessibility tree. This is cleared after each reflow.
17    rooted_nodes: FxHashSet<Dom<Node>>,
18}
19
20impl AccessibilityData {
21    /// Root a node which has been removed from the DOM but which may still have an associated
22    /// accessibility tree node. It will be unrooted after the next reflow, since the accessibility
23    /// tree is updated as part of the reflow process.
24    ///
25    /// Longer explanation:
26    /// - The accessibility tree doesn't hold strong references to DOM nodes, but uses
27    ///   [`OpaqueNode`]s as a way of mapping from an incoming DOM node to an existing accessibility
28    ///   tree node. This allows us to cache previously computed accessibility data, and update it
29    ///   based on the current DOM node state, which is passed in to the update function.
30    /// - If a DOM node is garbage collected before its corresponding node is removed from the
31    ///   accessibility tree, there is a risk that another new DOM node may be created at the same
32    ///   memory address, causing it to have an identical `OpaqueNode`. If this `OpaqueNode` was
33    ///   used to look up a node in the accessibility tree, we would get the stale accessibility
34    ///   node corresponding to the node which was removed.
35    /// - A DOM node is prevented from being garbage collected while it's connected to the document;
36    ///   it's kept alive by strong references in its parent, child and/or sibling [`Node`]s (and in
37    ///   the case of the document itself, by a strong reference in the [`Window`]). See
38    ///   [`Node::first_child`], [`Node::next_sibling`], etc.
39    /// - After a node is removed from the tree, those strong references are removed, and it _may_
40    ///   become a candidate for GC if its DOM object isn't held (directly or indirectly) in script
41    ///   and it isn't immediately inserted elsewhere in the DOM.
42    /// - To make sure the node isn't GCed before the next accessibility update occurs, we
43    ///   temporarily root it here in between its removal from the tree and the subsequent reflow.
44    /// - During reflow, the accessibility tree is updated, and all stale accessibility nodes are
45    ///   removed.
46    /// - After reflow, we can safely un-root these nodes by dropping all the strong references
47    ///   being held here, and allow them to potentially be GCed.
48    ///   See [`Self::unroot_all_removed_nodes()`].
49    pub(crate) fn root_removed_node(&mut self, _no_gc: &NoGC, node_to_root: &Node) {
50        debug_assert!(pref!(accessibility_enabled));
51
52        self.rooted_nodes.insert(Dom::from_ref(node_to_root));
53    }
54
55    /// Clear all nodes which were rooted using [`Self::root_removed_node()`].
56    /// This should be called at the end of reflow.
57    pub(crate) fn unroot_all_removed_nodes(&mut self) {
58        self.rooted_nodes.clear();
59    }
60}