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}