1use std::collections::VecDeque;
5use std::fmt::Debug;
6use std::sync::atomic::AtomicU64;
7use std::sync::{LazyLock, atomic};
8
9use accesskit::{NodeId, Role};
10use layout_api::{LayoutElement, LayoutNode, LayoutNodeType};
11use log::trace;
12use rustc_hash::{FxHashMap, FxHashSet};
13use script::layout_dom::ServoLayoutNode;
14use servo_base::Epoch;
15use servo_base::print_tree::PrintTree;
16use servo_config::opts::{self, DiagnosticsLogging, DiagnosticsLoggingOption};
17use servo_config::pref;
18use style::dom::{NodeInfo, OpaqueNode};
19use web_atoms::{LocalName, local_name};
20
21use crate::ArcRefCell;
22
23struct AccessibilityUpdate {
25 changed_nodes: FxHashSet<NodeId>,
27 tree_changes: FxHashMap<NodeId, TreeChange>,
29}
30
31struct AccessibilityNode {
32 id: NodeId,
36 accesskit_node: accesskit::Node,
39 opaque_node: Option<OpaqueNode>,
43 updated: bool,
46}
47
48#[derive(Debug)]
53pub struct AccessibilityTree {
54 nodes: FxHashMap<NodeId, ArcRefCell<AccessibilityNode>>,
57 opaque_node_to_id: FxHashMap<OpaqueNode, NodeId>,
60 tree_id: accesskit::TreeId,
64 root_node_id: Option<accesskit::NodeId>,
67 embedder_epoch: Epoch,
70 debug: DiagnosticsLogging,
73}
74
75#[derive(Debug, PartialEq, Copy, Clone)]
80enum TreeChange {
81 New,
83
84 Moved,
86
87 PendingMove,
103
104 Removed,
106}
107
108impl AccessibilityTree {
109 pub(super) fn new(tree_id: accesskit::TreeId, embedder_epoch: Epoch) -> Self {
111 Self {
112 nodes: FxHashMap::default(),
113 opaque_node_to_id: FxHashMap::default(),
114 tree_id,
115 root_node_id: None,
116 embedder_epoch,
117 debug: opts::get().debug.clone(),
118 }
119 }
120
121 pub(super) fn update_tree(
124 &mut self,
125 root_dom_node: &ServoLayoutNode<'_>,
126 ) -> Option<accesskit::TreeUpdate> {
127 let mut update = AccessibilityUpdate::new();
128 let root_node = self.get_or_create_node(root_dom_node, &mut update);
129 let root_node_id = root_node.borrow().id;
130 self.root_node_id = Some(root_node_id);
131
132 let any_node_updated = self.update_node_and_descendants(root_dom_node, &mut update);
133
134 if !any_node_updated {
135 assert!(update.tree_changes.is_empty());
136 return None;
137 }
138
139 let accesskit_update = update.finalize(self)?;
140
141 if self
142 .debug
143 .is_enabled(DiagnosticsLoggingOption::AccessibilityTree)
144 {
145 self.print();
146 }
147
148 if pref!(expensive_accessibility_test_assertions_enabled) {
149 self.assert_integrity();
150 }
151
152 Some(accesskit_update)
153 }
154
155 fn update_node_and_descendants(
158 &mut self,
159 dom_node: &ServoLayoutNode<'_>,
160 update: &mut AccessibilityUpdate,
161 ) -> bool {
162 let node = self.assert_node_for_dom_node(dom_node);
163 let mut node = node.borrow_mut();
164
165 let any_descendant_updated = node.update_descendants(dom_node, self, update);
167
168 node.update_node(dom_node, self, any_descendant_updated);
169
170 if node.updated {
171 update.add(&mut node);
172 return true;
173 }
174
175 any_descendant_updated
176 }
177
178 fn get_or_create_node(
179 &mut self,
180 dom_node: &ServoLayoutNode<'_>,
181 update: &mut AccessibilityUpdate,
182 ) -> ArcRefCell<AccessibilityNode> {
183 let id = self.id_for_opaque(dom_node.opaque());
184
185 let node = self.nodes.entry(id).or_insert_with(|| {
186 update.tree_changes.insert(id, TreeChange::New);
187 ArcRefCell::new(AccessibilityNode::new(id))
188 });
189
190 let mut new_node = node.borrow_mut();
191
192 new_node.opaque_node = Some(dom_node.opaque());
193 if let Some(dom_element) = dom_node.as_element() {
194 let local_name = dom_element.local_name().to_ascii_lowercase();
195 new_node.set_html_tag(&local_name);
196 }
197
198 node.clone()
199 }
200
201 fn node_for_dom_node(
202 &mut self,
203 dom_node: &ServoLayoutNode<'_>,
204 ) -> Option<ArcRefCell<AccessibilityNode>> {
205 let id = self.id_for_opaque(dom_node.opaque());
206 self.nodes.get(&id).cloned()
207 }
208
209 fn assert_node_for_dom_node(
210 &mut self,
211 dom_node: &ServoLayoutNode<'_>,
212 ) -> ArcRefCell<AccessibilityNode> {
213 let id = self.id_for_opaque(dom_node.opaque());
214 let node = self.assert_node_for_id(id);
215 assert!(node.borrow().opaque_node == Some(dom_node.opaque()));
216 node
217 }
218
219 fn assert_node_for_id(&self, id: NodeId) -> ArcRefCell<AccessibilityNode> {
220 let Some(node) = self.nodes.get(&id) else {
221 panic!("{id:?} does not exist in tree");
222 };
223 node.clone()
224 }
225
226 fn remove_node(&mut self, id: &NodeId) {
228 let Some(node) = self.nodes.remove(id) else {
229 return;
230 };
231 if let Some(opaque_node) = node.borrow().opaque_node {
232 self.opaque_node_to_id.remove(&opaque_node);
233 }
234 }
235
236 fn id_for_opaque(&mut self, opaque: OpaqueNode) -> NodeId {
237 let id = self.opaque_node_to_id.entry(opaque).or_insert_with(|| {
238 static LAST_ID: AtomicU64 = AtomicU64::new(0);
239 LAST_ID.fetch_add(1, atomic::Ordering::SeqCst).into()
240 });
241 *id
242 }
243
244 pub(crate) fn embedder_epoch(&self) -> Epoch {
245 self.embedder_epoch
246 }
247
248 fn assert_integrity(&self) {
252 let Some(root_node_id) = self.root_node_id else {
253 return;
254 };
255
256 assert!(pref!(expensive_accessibility_test_assertions_enabled));
257 let mut node_ids = vec![root_node_id];
259 let mut seen_node_ids = FxHashSet::default();
260 while let Some(node_id) = node_ids.pop() {
261 assert!(
263 seen_node_ids.insert(node_id),
264 "Tree contains {node_id:?} in multiple places"
265 );
266 let node = self.assert_node_for_id(node_id);
268 let node = node.borrow();
269 node_ids.extend(node.children().iter().rev());
270 }
271 assert_eq!(seen_node_ids, self.nodes.keys().copied().collect());
274 }
275
276 fn print(&self) {
277 let Some(root_node_id) = self.root_node_id else {
278 return;
279 };
280
281 let mut print_tree = PrintTree::new("Accessibility Tree");
282 let node = self.assert_node_for_id(root_node_id);
283 node.borrow().print(self, &mut print_tree);
284 print_tree.end_level();
285 }
286}
287
288fn role_from_dom_node(dom_node: &ServoLayoutNode<'_>) -> Role {
289 if let Some(dom_element) = dom_node.as_element() {
290 let local_name = dom_element.local_name().to_ascii_lowercase();
291 *HTML_ELEMENT_ROLE_MAPPINGS
292 .get(&local_name)
293 .unwrap_or(&Role::GenericContainer)
294 } else if dom_node.type_id() == Some(LayoutNodeType::Text) {
295 Role::TextRun
296 } else {
297 Role::GenericContainer
298 }
299}
300
301impl AccessibilityNode {
302 fn new(id: NodeId) -> Self {
303 Self::new_with_role(id, Role::Unknown)
304 }
305
306 fn new_with_role(id: NodeId, role: Role) -> Self {
307 Self {
308 id,
309 accesskit_node: accesskit::Node::new(role),
310 opaque_node: None,
311 updated: true,
312 }
313 }
314
315 fn update_descendants<'dom>(
316 &mut self,
317 dom_node: &ServoLayoutNode<'dom>,
318 tree: &mut AccessibilityTree,
319 update: &mut AccessibilityUpdate,
320 ) -> bool {
321 let mut any_descendant_updated = false;
322 let mut newly_created = FxHashSet::default();
323 let new_children: Vec<_> = dom_node
324 .flat_tree_children()
325 .map(|dom_child| {
326 let child_node_id = match tree.node_for_dom_node(&dom_child) {
327 Some(child_node) => child_node.borrow().id,
328 None => {
329 let new_child = tree.get_or_create_node(&dom_child, update);
330 let child_node_id = new_child.borrow().id;
331 newly_created.insert(child_node_id);
332 child_node_id
333 },
334 };
335
336 any_descendant_updated |= tree.update_node_and_descendants(&dom_child, update);
339
340 child_node_id
341 })
342 .collect();
343
344 if new_children != self.children() {
345 let old_children = self.children();
346 for old_child_id in old_children {
347 if !new_children.contains(old_child_id) {
348 let removed_child = tree.assert_node_for_id(*old_child_id);
349 removed_child.borrow().set_subtree_state_change(
350 TreeChange::Removed,
351 tree,
352 update,
353 );
354 }
355 }
356 for new_child_id in new_children.iter() {
357 if !newly_created.contains(new_child_id) && !old_children.contains(new_child_id) {
358 let moved_child = tree.assert_node_for_id(*new_child_id);
359 moved_child.borrow().set_subtree_state_change(
360 TreeChange::PendingMove,
361 tree,
362 update,
363 );
364 }
365 }
366 self.set_children(new_children);
367 }
368
369 any_descendant_updated
370 }
371
372 fn set_subtree_state_change(
384 &self,
385 change: TreeChange,
386 tree: &mut AccessibilityTree,
387 update: &mut AccessibilityUpdate,
388 ) {
389 let old_change = update.tree_changes.get(&self.id);
390
391 assert!(
392 change != TreeChange::New,
393 "New shouldn't be set recursively"
394 );
395 assert!(
396 change != TreeChange::Moved,
397 "Incoming change must never be Moved"
398 );
399
400 let new_change = old_change
401 .map(|old_change| match (old_change, change) {
402 (TreeChange::PendingMove, TreeChange::Removed) => TreeChange::Moved,
403 (TreeChange::Removed, TreeChange::PendingMove) => TreeChange::Moved,
404 _ => {
405 unreachable!("Logically impossible state change: {old_change:?} → {change:?}")
406 },
407 })
408 .unwrap_or(change);
409
410 update.tree_changes.insert(self.id, new_change);
411
412 for child_id in self.children().to_vec() {
413 let child = tree.assert_node_for_id(child_id);
414 child
416 .borrow()
417 .set_subtree_state_change(change, tree, update);
418 }
419 }
420
421 fn update_node(
422 &mut self,
423 dom_node: &ServoLayoutNode<'_>,
424 tree: &mut AccessibilityTree,
425 any_descendant_updated: bool,
426 ) {
427 self.set_role(role_from_dom_node(dom_node));
428 if dom_node.is_element() {
429 if any_descendant_updated && let Some(text) = self.label_from_descendants(tree) {
430 self.set_label(text.as_str());
431 }
432 } else if dom_node.type_id() == Some(LayoutNodeType::Text) {
433 let text_content = dom_node.text_content();
434 trace!("node text content = {text_content:?}");
435 self.set_value(&text_content);
437 }
438 }
439
440 fn label_from_descendants(&self, tree: &AccessibilityTree) -> Option<String> {
441 if !NAME_FROM_CONTENTS_ROLES.contains(&self.role()) {
442 return None;
443 }
444 let mut children = VecDeque::from_iter(self.children().iter().copied());
445 let mut text = String::new();
446 while let Some(child_id) = children.pop_front() {
447 let child = tree.assert_node_for_id(child_id);
448 let child = child.borrow();
449 match child.role() {
450 Role::TextRun => {
451 if let Some(child_text) = child.value() {
452 text.push_str(child_text);
453 }
454 },
455 _ => {
456 for id in child.children().iter().rev() {
457 children.push_front(*id);
458 }
459 },
460 }
461 }
462 Some(text.trim().to_owned())
463 }
464
465 fn print(&self, tree: &AccessibilityTree, print_tree: &mut PrintTree) {
466 if self.children().is_empty() {
467 print_tree.add_item(format!("{self:?}"));
468 return;
469 }
470
471 print_tree.new_level(format!("{self:?}"));
472
473 for child_id in self.children() {
474 let child = tree.assert_node_for_id(*child_id);
475 child.borrow().print(tree, print_tree);
476 }
477 print_tree.end_level();
478 }
479
480 fn children(&self) -> &[NodeId] {
483 self.accesskit_node.children()
484 }
485
486 fn set_children(&mut self, children: Vec<NodeId>) {
487 self.accesskit_node.set_children(children);
488 self.updated = true;
489 }
490
491 fn role(&self) -> Role {
492 self.accesskit_node.role()
493 }
494
495 fn set_role(&mut self, role: Role) {
496 if role == self.accesskit_node.role() {
497 return;
498 }
499 self.accesskit_node.set_role(role);
500 self.updated = true;
501 }
502
503 fn label(&self) -> Option<&str> {
504 self.accesskit_node.label()
505 }
506
507 fn set_label(&mut self, label: &str) {
508 if Some(label) == self.accesskit_node.label() {
509 return;
510 }
511 self.accesskit_node.set_label(label);
512 self.updated = true;
513 }
514
515 fn html_tag(&self) -> Option<&str> {
516 self.accesskit_node.html_tag()
517 }
518
519 fn set_html_tag(&mut self, html_tag: &str) {
520 if Some(html_tag) == self.accesskit_node.html_tag() {
521 return;
522 }
523 self.accesskit_node.set_html_tag(html_tag);
524 self.updated = true;
525 }
526
527 fn value(&self) -> Option<&str> {
528 self.accesskit_node.value()
529 }
530
531 fn set_value(&mut self, value: &str) {
532 if Some(value) == self.accesskit_node.value() {
533 return;
534 }
535 self.accesskit_node.set_value(value);
536 self.updated = true;
537 }
538}
539
540impl Debug for AccessibilityNode {
541 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
542 write!(f, "{:?}: {:?}", self.id, self.role())?;
543 if let Some(html_tag) = self.html_tag() {
544 write!(f, " (html_tag: {html_tag:?})")?;
545 }
546 if let Some(label) = self.label() {
547 write!(f, "\nlabel: {label:?}")?;
548 }
549 if !self.children().is_empty() {
550 write!(f, "\nchildren: {:?}", self.children())?;
551 }
552 Ok(())
553 }
554}
555
556impl AccessibilityUpdate {
557 fn new() -> Self {
558 Self {
559 changed_nodes: FxHashSet::default(),
560 tree_changes: FxHashMap::default(),
561 }
562 }
563
564 fn add(&mut self, node: &mut AccessibilityNode) {
565 self.changed_nodes.insert(node.id);
566
567 node.updated = false;
568 }
569
570 fn finalize(mut self, tree: &mut AccessibilityTree) -> Option<accesskit::TreeUpdate> {
571 for id in self
572 .tree_changes
573 .drain()
574 .filter_map(|(id, change)| match change {
575 TreeChange::PendingMove => {
576 unreachable!(
577 "Pending move found for node id {id:?} when draining tree state changes"
578 );
579 },
580 TreeChange::Removed => Some(id),
581 _ => None,
582 })
583 {
584 tree.remove_node(&id);
585 }
586
587 let accesskit_tree = accesskit::Tree::new(tree.root_node_id?);
588 Some(accesskit::TreeUpdate {
589 nodes: self
590 .changed_nodes
591 .into_iter()
592 .map(|id| {
593 (
594 id,
595 tree.assert_node_for_id(id).borrow().accesskit_node.clone(),
596 )
597 })
598 .collect(),
599 tree: Some(accesskit_tree),
600 focus: accesskit::NodeId(1),
601 tree_id: tree.tree_id,
602 })
603 }
604}
605
606#[cfg(test)]
607#[test]
608fn test_accessibility_update_add_some_nodes_twice() {
609 let mut tree = AccessibilityTree::new(accesskit::TreeId::ROOT, Epoch::default());
610 tree.root_node_id = Some(accesskit::NodeId(2));
611
612 for (id, role) in [
613 (3, Role::GenericContainer),
614 (4, Role::Heading),
615 (5, Role::Paragraph),
616 ] {
617 let id = accesskit::NodeId(id);
618 tree.nodes.insert(
619 id,
620 ArcRefCell::new(AccessibilityNode::new_with_role(id, role)),
621 );
622 }
623
624 let mut update = AccessibilityUpdate::new();
625
626 {
627 let node_3 = tree.assert_node_for_id(accesskit::NodeId(3));
628 let mut node_3 = node_3.borrow_mut();
629 let node_4 = tree.assert_node_for_id(accesskit::NodeId(4));
630 let mut node_4 = node_4.borrow_mut();
631 let node_5 = tree.assert_node_for_id(accesskit::NodeId(5));
632 let mut node_5 = node_5.borrow_mut();
633
634 update.add(&mut node_5);
635 update.add(&mut node_3);
636 update.add(&mut node_4);
637 update.add(&mut node_4);
638
639 node_3.set_role(Role::ScrollView);
640 update.add(&mut node_3);
641 }
642
643 let mut tree_update = update
644 .finalize(&mut tree)
645 .expect("finalize should produce a tree update");
646 tree_update.nodes.sort_by_key(|(node_id, _node)| *node_id);
647 assert_eq!(
648 tree_update,
649 accesskit::TreeUpdate {
650 nodes: vec![
651 (NodeId(3), accesskit::Node::new(Role::ScrollView)),
652 (NodeId(4), accesskit::Node::new(Role::Heading)),
653 (NodeId(5), accesskit::Node::new(Role::Paragraph)),
654 ],
655 tree: Some(accesskit::Tree {
656 root: NodeId(2),
657 toolkit_name: None,
658 toolkit_version: None
659 }),
660 tree_id: accesskit::TreeId::ROOT,
661 focus: NodeId(1),
662 }
663 );
664}
665
666static HTML_ELEMENT_ROLE_MAPPINGS: LazyLock<FxHashMap<LocalName, Role>> = LazyLock::new(|| {
667 [
668 (local_name!("article"), Role::Article),
669 (local_name!("aside"), Role::Complementary),
670 (local_name!("body"), Role::RootWebArea),
671 (local_name!("footer"), Role::ContentInfo),
672 (local_name!("h1"), Role::Heading),
673 (local_name!("h2"), Role::Heading),
674 (local_name!("h3"), Role::Heading),
675 (local_name!("h4"), Role::Heading),
676 (local_name!("h5"), Role::Heading),
677 (local_name!("h6"), Role::Heading),
678 (local_name!("header"), Role::Banner),
679 (local_name!("hr"), Role::Splitter),
680 (local_name!("main"), Role::Main),
681 (local_name!("nav"), Role::Navigation),
682 (local_name!("p"), Role::Paragraph),
683 ]
684 .into_iter()
685 .collect()
686});
687
688static NAME_FROM_CONTENTS_ROLES: LazyLock<FxHashSet<Role>> =
690 LazyLock::new(|| [(Role::Heading)].into_iter().collect());