diff --git a/Cargo.lock b/Cargo.lock
index 09d30e63a..bb1fc2713 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1099,6 +1099,7 @@ dependencies = [
"markup5ever",
"stylo",
"taffy",
+ "test-that",
"usvg",
]
@@ -8017,6 +8018,27 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "253bcead4f3aa96243b0f8fa46f9010e87ca23bd5d0c723d474ff1d2417bbdf8"
+[[package]]
+name = "test-that"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291f228058142bae421e0b4b014a7b18edd6137629550cc15da37feed66a7db6"
+dependencies = [
+ "num-traits",
+ "regex",
+ "test-that-macro",
+]
+
+[[package]]
+name = "test-that-macro"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6453b0f4c66f6ed93ac9ababa97e347a22f5556180d19808bb6d796522eef5d"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
+
[[package]]
name = "thin-vec"
version = "0.2.19"
diff --git a/Cargo.toml b/Cargo.toml
index 7a9eb0fdd..6a5a2bd97 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -188,6 +188,7 @@ smol_str = "0.3"
bitflags = "2.8.0"
bytemuck = "1"
rayon = "1"
+test-that = "0.5.2"
thread_local = "1"
[profile.profile]
diff --git a/packages/blitz-dom/src/accessibility.rs b/packages/blitz-dom/src/accessibility.rs
index e7e8b55fd..1b239c5a7 100644
--- a/packages/blitz-dom/src/accessibility.rs
+++ b/packages/blitz-dom/src/accessibility.rs
@@ -1,12 +1,23 @@
use crate::{BaseDocument, ElementData, Node as BlitzDomNode, local_name};
use accesskit::{Node as AccessKitNode, NodeId, Role, Tree, TreeId, TreeUpdate};
+use style::properties::longhands::visibility;
impl BaseDocument {
pub fn build_accessibility_tree(&self) -> TreeUpdate {
let mut nodes = std::collections::HashMap::new();
let mut window = AccessKitNode::new(Role::Window);
+ let mut hidden_nodes = std::collections::HashSet::new();
self.visit(|node_id, node| {
+ if node.is_hidden_from_accessibility_tree()
+ || node
+ .parent
+ .map(|p| hidden_nodes.contains(&p))
+ .unwrap_or(false)
+ {
+ hidden_nodes.insert(node_id);
+ return;
+ }
let parent = node
.parent
.and_then(|parent_id| nodes.get_mut(&parent_id))
@@ -55,6 +66,11 @@ impl BaseDocument {
builder.set_role(role);
builder.set_html_tag(name);
+
+ // https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion
+ if element_data.attr(local_name!("aria-hidden")) == Some("true") {
+ builder.set_hidden();
+ }
} else if node.is_text_node() {
builder.set_role(Role::TextRun);
builder.set_value(node.text_content());
@@ -67,6 +83,21 @@ impl BaseDocument {
}
}
+impl BlitzDomNode {
+ // https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion
+ fn is_hidden_from_accessibility_tree(&self) -> bool {
+ self.try_stylo_element_data()
+ .as_ref()
+ .and_then(|s| s.get())
+ .map(|s| {
+ s.styles.is_display_none()
+ || s.styles.primary().clone_visibility()
+ == visibility::computed_value::T::Hidden
+ })
+ .unwrap_or(false)
+ }
+}
+
fn role_from_name(name: &str) -> Option