blitz_dom/
accessibility.rs

1use crate::{BaseDocument, Node as BlitzDomNode, local_name};
2use accesskit::{Node as AccessKitNode, NodeId, Role, Tree, TreeUpdate};
3
4impl BaseDocument {
5    pub fn build_accessibility_tree(&self) -> TreeUpdate {
6        let mut nodes = std::collections::HashMap::new();
7        let mut window = AccessKitNode::new(Role::Window);
8
9        self.visit(|node_id, node| {
10            let parent = node
11                .parent
12                .and_then(|parent_id| nodes.get_mut(&parent_id))
13                .map(|(_, parent)| parent)
14                .unwrap_or(&mut window);
15            let (id, builder) = self.build_accessibility_node(node, parent);
16
17            nodes.insert(node_id, (id, builder));
18        });
19
20        let mut nodes: Vec<_> = nodes
21            .into_iter()
22            .map(|(_, (id, node))| (id, node))
23            .collect();
24        nodes.push((NodeId(u64::MAX), window));
25
26        let tree = Tree::new(NodeId(u64::MAX));
27        TreeUpdate {
28            nodes,
29            tree: Some(tree),
30            focus: NodeId(self.focus_node_id.map(|id| id as u64).unwrap_or(u64::MAX)),
31        }
32    }
33
34    fn build_accessibility_node(
35        &self,
36        node: &BlitzDomNode,
37        parent: &mut AccessKitNode,
38    ) -> (NodeId, AccessKitNode) {
39        let id = NodeId(node.id as u64);
40
41        let mut builder = AccessKitNode::default();
42        if node.id == 0 {
43            builder.set_role(Role::Window)
44        } else if let Some(element_data) = node.element_data() {
45            let name = element_data.name.local.to_string();
46
47            // TODO match more roles
48            let role = match &*name {
49                "button" => Role::Button,
50                "div" => Role::GenericContainer,
51                "header" => Role::Header,
52                "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => Role::Heading,
53                "p" => Role::Paragraph,
54                "section" => Role::Section,
55                "input" => {
56                    let ty = element_data.attr(local_name!("type")).unwrap_or("text");
57                    match ty {
58                        "number" => Role::NumberInput,
59                        "checkbox" => Role::CheckBox,
60                        _ => Role::TextInput,
61                    }
62                }
63                _ => Role::Unknown,
64            };
65
66            builder.set_role(role);
67            builder.set_html_tag(name);
68        } else if node.is_text_node() {
69            builder.set_role(Role::TextRun);
70            builder.set_value(node.text_content());
71            parent.push_labelled_by(id)
72        }
73
74        parent.push_child(id);
75
76        (id, builder)
77    }
78}