1use crate::{BaseDocument, Node as BlitzDomNode, local_name};
2use accesskit::{Node as AccessKitNode, NodeId, Role, Tree, TreeId, 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 tree_id: TreeId::ROOT,
29 nodes,
30 tree: Some(tree),
31 focus: NodeId(self.focus_node_id.map(|id| id.as_u64()).unwrap_or(u64::MAX)),
32 }
33 }
34
35 fn build_accessibility_node(
36 &self,
37 node: &BlitzDomNode,
38 parent: &mut AccessKitNode,
39 ) -> (NodeId, AccessKitNode) {
40 let id = NodeId(node.id.as_u64());
41
42 let mut builder = AccessKitNode::default();
43 if node.parent.is_none() {
44 builder.set_role(Role::Window)
45 } else if let Some(element_data) = node.element_data() {
46 let name = element_data.name.local.to_string();
47
48 let role = match &*name {
50 "article" => Role::Article,
52 "aside" => Role::Complementary,
53 "footer" => Role::Footer,
54 "header" => Role::Header,
55 "main" => Role::Main,
56 "nav" => Role::Navigation,
57 "search" => Role::Search,
58 "section" => Role::Section,
59 "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => Role::Heading,
60 "p" => Role::Paragraph,
61 "blockquote" => Role::Blockquote,
62 "figure" => Role::Figure,
63 "figcaption" | "caption" => Role::Caption,
64 "hr" => Role::Splitter,
65
66 "ul" | "ol" | "menu" => Role::List,
68 "li" => Role::ListItem,
69 "dl" => Role::DescriptionList,
70 "dt" => Role::Term,
71 "dd" => Role::Definition,
72 "dialog" => Role::Dialog,
73 "fieldset" => Role::Group,
74 "form" => Role::Form,
75 "div" => Role::GenericContainer,
76
77 "table" => Role::Table,
79 "thead" | "tbody" | "tfoot" => Role::RowGroup,
80 "tr" => Role::Row,
81 "td" => Role::Cell,
82 "th" => match element_data.attr(local_name!("scope")) {
83 Some("row") | Some("rowgroup") => Role::RowHeader,
84 _ => Role::ColumnHeader,
85 },
86
87 "a" => match element_data.attr(local_name!("href")) {
90 Some(_) => Role::Link,
91 None => Role::GenericContainer,
92 },
93 "button" => Role::Button,
94 "label" => Role::Label,
95 "legend" => Role::Label,
96 "select" => match element_data.attr(local_name!("multiple")) {
97 Some(_) => Role::ListBox,
98 None => Role::ComboBox,
99 },
100 "option" => Role::ListBoxOption,
101 "textarea" => Role::MultilineTextInput,
102 "progress" => Role::ProgressIndicator,
103 "meter" => Role::Meter,
104 "output" => Role::Status,
105 "summary" => Role::DisclosureTriangle,
106
107 "code" => Role::Code,
109 "em" => Role::Emphasis,
110 "strong" => Role::Strong,
111 "mark" => Role::Mark,
112 "time" => Role::Time,
113 "img" => Role::Image,
114 "iframe" => Role::Iframe,
115
116 "input" => {
117 let ty = element_data.attr(local_name!("type")).unwrap_or("text");
118 match ty {
119 "button" | "submit" | "reset" => Role::Button,
120 "checkbox" => Role::CheckBox,
121 "color" => Role::ColorWell,
122 "date" => Role::DateInput,
123 "datetime-local" => Role::DateTimeInput,
124 "email" => Role::EmailInput,
125 "number" => Role::NumberInput,
126 "password" => Role::PasswordInput,
127 "radio" => Role::RadioButton,
128 "range" => Role::Slider,
129 "search" => Role::SearchInput,
130 "tel" => Role::PhoneNumberInput,
131 "time" => Role::TimeInput,
132 _ => Role::TextInput,
133 }
134 }
135 _ => Role::Unknown,
136 };
137
138 builder.set_role(role);
139 builder.set_html_tag(name);
140 } else if node.is_text_node() {
141 builder.set_role(Role::TextRun);
142 builder.set_value(node.text_content());
143 parent.push_labelled_by(id)
144 }
145
146 parent.push_child(id);
147
148 (id, builder)
149 }
150}