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/. */
4
5use accesskit::Role;
6use layout_api::LayoutNode;
7use log::trace;
8use rustc_hash::FxHashMap;
9use script::layout_dom::ServoLayoutNode;
10use servo_base::Epoch;
11use style::dom::{NodeInfo, OpaqueNode};
12
13struct AccessibilityUpdate {
14    accesskit_update: accesskit::TreeUpdate,
15}
16
17#[derive(Debug)]
18struct AccessibilityNode {
19    id: accesskit::NodeId,
20    accesskit_node: accesskit::Node,
21}
22
23#[derive(Debug)]
24pub struct AccessibilityTree {
25    nodes: FxHashMap<accesskit::NodeId, AccessibilityNode>,
26    accesskit_tree: accesskit::Tree,
27    tree_id: accesskit::TreeId,
28    epoch: Epoch,
29}
30
31impl AccessibilityUpdate {
32    fn new(tree: accesskit::Tree, tree_id: accesskit::TreeId) -> Self {
33        Self {
34            accesskit_update: accesskit::TreeUpdate {
35                nodes: Default::default(),
36                tree: Some(tree),
37                focus: accesskit::NodeId(1),
38                tree_id,
39            },
40        }
41    }
42
43    fn add(&mut self, node: &AccessibilityNode) {
44        self.accesskit_update
45            .nodes
46            .push((node.id, node.accesskit_node.clone()));
47    }
48}
49
50impl AccessibilityTree {
51    const ROOT_NODE_ID: accesskit::NodeId = accesskit::NodeId(0);
52
53    pub(super) fn new(tree_id: accesskit::TreeId, epoch: Epoch) -> Self {
54        // The root node doesn't correspond to a DOM node, but contains the root DOM node.
55        let mut root_node = AccessibilityNode::new(AccessibilityTree::ROOT_NODE_ID);
56        root_node
57            .accesskit_node
58            .set_role(accesskit::Role::RootWebArea);
59        root_node
60            .accesskit_node
61            .add_action(accesskit::Action::Focus);
62
63        let mut tree = Self {
64            nodes: Default::default(),
65            accesskit_tree: accesskit::Tree::new(root_node.id),
66            tree_id,
67            epoch,
68        };
69        tree.nodes.insert(root_node.id, root_node);
70
71        tree
72    }
73
74    pub(super) fn update_tree(
75        &mut self,
76        root_dom_node: &ServoLayoutNode<'_>,
77    ) -> Option<accesskit::TreeUpdate> {
78        let mut tree_update = AccessibilityUpdate::new(self.accesskit_tree.clone(), self.tree_id);
79
80        let root_dom_node_id = Self::to_accesskit_id(&root_dom_node.opaque());
81        let root_node = self
82            .nodes
83            .get_mut(&AccessibilityTree::ROOT_NODE_ID)
84            .unwrap();
85        root_node
86            .accesskit_node
87            .set_children(vec![root_dom_node_id]);
88
89        tree_update.add(root_node);
90
91        self.update_node_and_children(root_dom_node, &mut tree_update);
92        Some(tree_update.accesskit_update)
93    }
94
95    fn update_node_and_children(
96        &mut self,
97        dom_node: &ServoLayoutNode<'_>,
98        tree_update: &mut AccessibilityUpdate,
99    ) {
100        // TODO: read accessibility damage from dom_node (right now, assume damage is complete)
101
102        let node = self.get_or_create_node_mut(dom_node);
103        let accesskit_node = &mut node.accesskit_node;
104
105        let mut new_children: Vec<accesskit::NodeId> = vec![];
106        for dom_child in dom_node.flat_tree_children() {
107            let child_id = Self::to_accesskit_id(&dom_child.opaque());
108            new_children.push(child_id);
109        }
110        if new_children != accesskit_node.children() {
111            accesskit_node.set_children(new_children);
112        }
113
114        if dom_node.is_text_node() {
115            accesskit_node.set_role(Role::TextRun);
116            let text_content = dom_node.text_content();
117            trace!("node text content = {text_content:?}");
118            // FIXME: this should take into account editing selection units (grapheme clusters?)
119            accesskit_node.set_value(&*text_content);
120        } else if dom_node.as_element().is_some() {
121            accesskit_node.set_role(Role::GenericContainer);
122        }
123
124        tree_update.add(node);
125
126        for dom_child in dom_node.flat_tree_children() {
127            self.update_node_and_children(&dom_child, tree_update);
128        }
129    }
130
131    fn get_or_create_node_mut(&mut self, dom_node: &ServoLayoutNode<'_>) -> &mut AccessibilityNode {
132        let id = Self::to_accesskit_id(&dom_node.opaque());
133
134        self.nodes
135            .entry(id)
136            .or_insert_with(|| AccessibilityNode::new(id))
137    }
138
139    fn to_accesskit_id(opaque: &OpaqueNode) -> accesskit::NodeId {
140        accesskit::NodeId(opaque.0 as u64)
141    }
142
143    pub(crate) fn epoch(&self) -> Epoch {
144        self.epoch
145    }
146}
147
148impl AccessibilityNode {
149    fn new(id: accesskit::NodeId) -> Self {
150        Self {
151            id,
152            accesskit_node: accesskit::Node::new(Role::Unknown),
153        }
154    }
155}