mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
Compare commits
7 Commits
8ced3d1b8e
...
hacky-acce
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64041ee394 | ||
|
|
6bab93ae4c | ||
|
|
2b0b180eb2 | ||
|
|
84798a5ee6 | ||
|
|
08965f2149 | ||
|
|
eb90fcc8f9 | ||
|
|
463a1fc631 |
116
Cargo.lock
generated
116
Cargo.lock
generated
@@ -18,45 +18,60 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
|
||||
[[package]]
|
||||
name = "accessibility_traits"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"base",
|
||||
"crossbeam-channel",
|
||||
"ipc-channel",
|
||||
"malloc_size_of_derive",
|
||||
"rustc-hash 2.1.1",
|
||||
"serde",
|
||||
"servo_malloc_size_of",
|
||||
"strum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf203f9d3bd8f29f98833d1fbef628df18f759248a547e7e01cfbf63cda36a99"
|
||||
source = "git+https://github.com/AccessKit/accesskit.git?rev=783c1e8fea164f337a0cd62739fb6e59f159d77c#783c1e8fea164f337a0cd62739fb6e59f159d77c"
|
||||
dependencies = [
|
||||
"enumn",
|
||||
"serde",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_atspi_common"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "890d241cf51fc784f0ac5ac34dfc847421f8d39da6c7c91a0fcc987db62a8267"
|
||||
source = "git+https://github.com/AccessKit/accesskit.git?rev=783c1e8fea164f337a0cd62739fb6e59f159d77c#783c1e8fea164f337a0cd62739fb6e59f159d77c"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
"atspi-common",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_consumer"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db81010a6895d8707f9072e6ce98070579b43b717193d2614014abd5cb17dd43"
|
||||
source = "git+https://github.com/AccessKit/accesskit.git?rev=783c1e8fea164f337a0cd62739fb6e59f159d77c#783c1e8fea164f337a0cd62739fb6e59f159d77c"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_macos"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0089e5c0ac0ca281e13ea374773898d9354cc28d15af9f0f7394d44a495b575"
|
||||
source = "git+https://github.com/AccessKit/accesskit.git?rev=783c1e8fea164f337a0cd62739fb6e59f159d77c#783c1e8fea164f337a0cd62739fb6e59f159d77c"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
@@ -65,8 +80,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "accesskit_unix"
|
||||
version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301e55b39cfc15d9c48943ce5f572204a551646700d0e8efa424585f94fec528"
|
||||
source = "git+https://github.com/AccessKit/accesskit.git?rev=783c1e8fea164f337a0cd62739fb6e59f159d77c#783c1e8fea164f337a0cd62739fb6e59f159d77c"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_atspi_common",
|
||||
@@ -83,12 +97,11 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "accesskit_windows"
|
||||
version = "0.29.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d63dd5041e49c363d83f5419a896ecb074d309c414036f616dc0b04faca971"
|
||||
source = "git+https://github.com/AccessKit/accesskit.git?rev=783c1e8fea164f337a0cd62739fb6e59f159d77c#783c1e8fea164f337a0cd62739fb6e59f159d77c"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"static_assertions",
|
||||
"windows 0.61.3",
|
||||
"windows-core 0.61.2",
|
||||
@@ -631,20 +644,19 @@ checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c"
|
||||
|
||||
[[package]]
|
||||
name = "atspi"
|
||||
version = "0.25.0"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c83247582e7508838caf5f316c00791eee0e15c0bf743e6880585b867e16815c"
|
||||
checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d"
|
||||
dependencies = [
|
||||
"atspi-common",
|
||||
"atspi-connection",
|
||||
"atspi-proxies",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atspi-common"
|
||||
version = "0.9.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33dfc05e7cdf90988a197803bf24f5788f94f7c94a69efa95683e8ffe76cfdfb"
|
||||
checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"serde",
|
||||
@@ -656,23 +668,11 @@ dependencies = [
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atspi-connection"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4193d51303d8332304056ae0004714256b46b6635a5c556109b319c0d3784938"
|
||||
dependencies = [
|
||||
"atspi-common",
|
||||
"atspi-proxies",
|
||||
"futures-lite",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atspi-proxies"
|
||||
version = "0.9.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2eebcb9e7e76f26d0bcfd6f0295e1cd1e6f33bedbc5698a971db8dc43d7751c"
|
||||
checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc"
|
||||
dependencies = [
|
||||
"atspi-common",
|
||||
"serde",
|
||||
@@ -2353,8 +2353,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "ecolor"
|
||||
version = "0.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71ddb8ac7643d1dba1bb02110e804406dd459a838efcb14011ced10556711a8e"
|
||||
source = "git+https://github.com/emilk/egui.git?rev=82199c1c449ec58aca3e8f0252e2d68f2ea5f5da#82199c1c449ec58aca3e8f0252e2d68f2ea5f5da"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"emath",
|
||||
@@ -2363,8 +2362,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a9b567d356674e9a5121ed3fedfb0a7c31e059fe71f6972b691bcd0bfc284e3"
|
||||
source = "git+https://github.com/emilk/egui.git?rev=82199c1c449ec58aca3e8f0252e2d68f2ea5f5da#82199c1c449ec58aca3e8f0252e2d68f2ea5f5da"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"ahash",
|
||||
@@ -2394,8 +2392,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec6687e5bb551702f4ad10ac428bab12acf9d53047ebb1082d4a0ed8c6251a29"
|
||||
source = "git+https://github.com/emilk/egui.git?rev=82199c1c449ec58aca3e8f0252e2d68f2ea5f5da#82199c1c449ec58aca3e8f0252e2d68f2ea5f5da"
|
||||
dependencies = [
|
||||
"accesskit_winit",
|
||||
"arboard",
|
||||
@@ -2460,8 +2457,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "491bdf728bf25ddd9ad60d4cf1c48588fa82c013a2440b91aa7fc43e34a07c32"
|
||||
source = "git+https://github.com/emilk/egui.git?rev=82199c1c449ec58aca3e8f0252e2d68f2ea5f5da#82199c1c449ec58aca3e8f0252e2d68f2ea5f5da"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
@@ -2470,6 +2466,7 @@ dependencies = [
|
||||
name = "embedder_traits"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"accessibility_traits",
|
||||
"base",
|
||||
"bitflags 2.10.0",
|
||||
"cookie 0.18.1",
|
||||
@@ -2552,6 +2549,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumn"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.4"
|
||||
@@ -2588,8 +2596,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "009d0dd3c2163823a0abdb899451ecbc78798dec545ee91b43aff1fa790bab62"
|
||||
source = "git+https://github.com/emilk/egui.git?rev=82199c1c449ec58aca3e8f0252e2d68f2ea5f5da#82199c1c449ec58aca3e8f0252e2d68f2ea5f5da"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"ahash",
|
||||
@@ -2606,8 +2613,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "epaint_default_fonts"
|
||||
version = "0.33.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c4fbe202b6578d3d56428fa185cdf114a05e49da05f477b3c7f0fbb221f1862"
|
||||
source = "git+https://github.com/emilk/egui.git?rev=82199c1c449ec58aca3e8f0252e2d68f2ea5f5da#82199c1c449ec58aca3e8f0252e2d68f2ea5f5da"
|
||||
|
||||
[[package]]
|
||||
name = "equator"
|
||||
@@ -2808,6 +2814,12 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "font-types"
|
||||
version = "0.10.1"
|
||||
@@ -3804,7 +3816,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
"foldhash 0.1.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -3813,6 +3825,9 @@ name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
dependencies = [
|
||||
"foldhash 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
@@ -4973,6 +4988,8 @@ dependencies = [
|
||||
name = "layout"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"accessibility_traits",
|
||||
"accesskit",
|
||||
"app_units",
|
||||
"atomic_refcell",
|
||||
"base",
|
||||
@@ -5133,6 +5150,7 @@ dependencies = [
|
||||
name = "libservo"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"accessibility_traits",
|
||||
"arboard",
|
||||
"background_hang_monitor",
|
||||
"base",
|
||||
@@ -7876,6 +7894,7 @@ dependencies = [
|
||||
name = "script_traits"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"accessibility_traits",
|
||||
"background_hang_monitor_api",
|
||||
"base",
|
||||
"bluetooth_traits",
|
||||
@@ -8399,6 +8418,7 @@ dependencies = [
|
||||
name = "servoshell"
|
||||
version = "0.0.4"
|
||||
dependencies = [
|
||||
"accessibility_traits",
|
||||
"android_logger",
|
||||
"backtrace",
|
||||
"base",
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -1,6 +1,7 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"components/shared/accessibility",
|
||||
"components/xpath",
|
||||
"ports/servoshell",
|
||||
"tests/unit/*",
|
||||
@@ -23,6 +24,8 @@ publish = false
|
||||
rust-version = "1.86.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
accessibility_traits = { path = "components/shared/accessibility" }
|
||||
accesskit = "0.21.1"
|
||||
accountable-refcell = "0.2.2"
|
||||
aes = "0.8.4"
|
||||
aes-gcm = "0.10.3"
|
||||
@@ -277,6 +280,14 @@ lto = "thin"
|
||||
codegen-units = 1
|
||||
|
||||
[patch.crates-io]
|
||||
accesskit = { git = "https://github.com/AccessKit/accesskit.git", rev = "783c1e8fea164f337a0cd62739fb6e59f159d77c" }
|
||||
accesskit_atspi_common = { git = "https://github.com/AccessKit/accesskit.git", rev = "783c1e8fea164f337a0cd62739fb6e59f159d77c" }
|
||||
accesskit_consumer = { git = "https://github.com/AccessKit/accesskit.git", rev = "783c1e8fea164f337a0cd62739fb6e59f159d77c" }
|
||||
accesskit_unix = { git = "https://github.com/AccessKit/accesskit.git", rev = "783c1e8fea164f337a0cd62739fb6e59f159d77c" }
|
||||
accesskit_macos = { git = "https://github.com/AccessKit/accesskit.git", rev = "783c1e8fea164f337a0cd62739fb6e59f159d77c" }
|
||||
accesskit_windows = { git = "https://github.com/AccessKit/accesskit.git", rev = "783c1e8fea164f337a0cd62739fb6e59f159d77c" }
|
||||
egui = { git = "https://github.com/emilk/egui.git", rev = "82199c1c449ec58aca3e8f0252e2d68f2ea5f5da" }
|
||||
egui-winit = { git = "https://github.com/emilk/egui.git", rev = "82199c1c449ec58aca3e8f0252e2d68f2ea5f5da" }
|
||||
# If you need to temporarily test Servo with a local fork of some upstream
|
||||
# crate, add that here. Use the form:
|
||||
#
|
||||
|
||||
@@ -17,6 +17,8 @@ doctest = false
|
||||
tracing = ["dep:tracing"]
|
||||
|
||||
[dependencies]
|
||||
accessibility_traits = { workspace = true }
|
||||
accesskit = { workspace = true }
|
||||
app_units = { workspace = true }
|
||||
atomic_refcell = { workspace = true }
|
||||
base = { workspace = true }
|
||||
|
||||
200
components/layout/accessibility_tree.rs
Normal file
200
components/layout/accessibility_tree.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
pub(crate) use accessibility_traits::AccessibilityTree;
|
||||
use accesskit::{Action, Node as AxNode, NodeId as AxNodeId, Role, Tree as AxTree};
|
||||
use html5ever::{LocalName, local_name};
|
||||
use layout_api::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
|
||||
use log::trace;
|
||||
use rustc_hash::FxHashMap;
|
||||
use script::layout_dom::ServoLayoutDocument;
|
||||
use style::dom::{NodeInfo, TDocument, TElement, TNode};
|
||||
|
||||
use crate::FragmentTree;
|
||||
|
||||
// #[derive(MallocSizeOf)]
|
||||
pub(crate) struct AccessibilityTreeCalculator {}
|
||||
|
||||
impl AccessibilityTreeCalculator {
|
||||
pub(crate) fn construct(
|
||||
document: ServoLayoutDocument<'_>,
|
||||
fragment_tree: Rc<FragmentTree>,
|
||||
) -> AccessibilityTree {
|
||||
let mut ax_nodes: FxHashMap<AxNodeId, AxNode> = FxHashMap::default();
|
||||
let ax_document_id = AxNodeId(document.as_node().opaque().0 as u64);
|
||||
let ax_document = AxNode::new(Role::Document);
|
||||
ax_nodes.insert(ax_document_id, ax_document);
|
||||
let dom_root = document.root_element().unwrap().as_node();
|
||||
let mut dom_queue = vec![dom_root];
|
||||
'outer: loop {
|
||||
// find the next node in the queue that is not ‘display: none’.
|
||||
let dom_next = loop {
|
||||
let Some(next) = dom_queue.pop() else {
|
||||
break 'outer;
|
||||
};
|
||||
if next
|
||||
.style_data()
|
||||
.is_none_or(|style| !style.element_data.borrow().styles.is_display_none())
|
||||
{
|
||||
break next;
|
||||
}
|
||||
};
|
||||
// push its children for later traversal.
|
||||
// reverse order ensures that the first child will get popped first.
|
||||
for child in RevDomChildren::of(dom_next) {
|
||||
dom_queue.push(child);
|
||||
}
|
||||
// now process the node.
|
||||
trace!("DOM node: {dom_next:?}");
|
||||
let dom_parent = dom_next
|
||||
.parent_node()
|
||||
.expect("Even the root element must have a parent");
|
||||
let ax_parent_id = AxNodeId(dom_parent.opaque().0 as u64);
|
||||
let ax_next_id = AxNodeId(dom_next.opaque().0 as u64);
|
||||
let ax_parent = ax_nodes.get_mut(&ax_parent_id).expect("Guaranteed by us");
|
||||
ax_parent.push_child(ax_next_id);
|
||||
let mut ax_next = AxNode::default();
|
||||
if dom_next.is_text_node() {
|
||||
let text_content = dom_next.to_threadsafe().text_content();
|
||||
trace!("node text content = {:?}", text_content);
|
||||
ax_next.set_value(&*text_content);
|
||||
// FIXME: this should take into account editing selection units (grapheme clusters?)
|
||||
ax_next.set_character_lengths(
|
||||
text_content
|
||||
.chars()
|
||||
.map(|c| c.len_utf8() as u8)
|
||||
.collect::<Box<[u8]>>(),
|
||||
);
|
||||
ax_next.set_role(Role::TextRun);
|
||||
} else if let Some(element) = dom_next.as_element() {
|
||||
if element.is_html_element() {
|
||||
if let Some(role) = HTML_ELEMENT_ROLE_MAPPINGS.get(element.local_name()) {
|
||||
ax_next.set_role(*role);
|
||||
}
|
||||
}
|
||||
}
|
||||
ax_next.add_action(Action::Click);
|
||||
ax_nodes.insert(ax_next_id, ax_next);
|
||||
}
|
||||
AccessibilityTree {
|
||||
ax_nodes,
|
||||
ax_tree: AxTree {
|
||||
root: ax_document_id,
|
||||
toolkit_name: None,
|
||||
toolkit_version: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`style::dom::DomChildren`], but reversed.
|
||||
///
|
||||
/// Do not use the tuple constructor; use [`Self::of`].
|
||||
pub(crate) struct RevDomChildren<N>(Option<N>);
|
||||
impl<N: TNode> RevDomChildren<N> {
|
||||
fn of(node: N) -> Self {
|
||||
Self(node.last_child())
|
||||
}
|
||||
}
|
||||
impl<N: TNode> Iterator for RevDomChildren<N> {
|
||||
type Item = N;
|
||||
fn next(&mut self) -> Option<N> {
|
||||
let n = self.0.take()?;
|
||||
self.0 = n.prev_sibling();
|
||||
Some(n)
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://www.w3.org/TR/html-aam-1.0/#html-element-role-mappings>
|
||||
///
|
||||
/// FIXME: converted mechanically for now, so this will have many errors
|
||||
static HTML_ELEMENT_ROLE_MAPPINGS: LazyLock<FxHashMap<LocalName, Role>> = LazyLock::new(|| {
|
||||
[
|
||||
(local_name!("a"), Role::Link),
|
||||
(local_name!("address"), Role::Group),
|
||||
(local_name!("area"), Role::Link),
|
||||
(local_name!("area"), Role::GenericContainer),
|
||||
(local_name!("article"), Role::Article),
|
||||
(local_name!("aside"), Role::Complementary),
|
||||
(local_name!("b"), Role::GenericContainer),
|
||||
(local_name!("bdi"), Role::GenericContainer),
|
||||
(local_name!("bdo"), Role::GenericContainer),
|
||||
(local_name!("blockquote"), Role::Blockquote),
|
||||
(local_name!("body"), Role::GenericContainer),
|
||||
(local_name!("button"), Role::Button),
|
||||
(local_name!("caption"), Role::Caption),
|
||||
(local_name!("code"), Role::Code),
|
||||
(local_name!("data"), Role::GenericContainer),
|
||||
(local_name!("datalist"), Role::ListBox),
|
||||
(local_name!("dd"), Role::Definition),
|
||||
(local_name!("del"), Role::ContentDeletion),
|
||||
(local_name!("details"), Role::Group),
|
||||
(local_name!("dfn"), Role::Term),
|
||||
(local_name!("dialog"), Role::Dialog),
|
||||
(local_name!("dir"), Role::List),
|
||||
(local_name!("div"), Role::GenericContainer),
|
||||
(local_name!("dl"), Role::List),
|
||||
(local_name!("dt"), Role::Term),
|
||||
(local_name!("em"), Role::Emphasis),
|
||||
(local_name!("fieldset"), Role::Group),
|
||||
(local_name!("figcaption"), Role::Caption),
|
||||
(local_name!("figure"), Role::Figure),
|
||||
(local_name!("footer"), Role::ContentInfo),
|
||||
(local_name!("form"), Role::Form),
|
||||
(local_name!("h1"), Role::Heading),
|
||||
(local_name!("h2"), Role::Heading),
|
||||
(local_name!("h3"), Role::Heading),
|
||||
(local_name!("h4"), Role::Heading),
|
||||
(local_name!("h5"), Role::Heading),
|
||||
(local_name!("h6"), Role::Heading),
|
||||
(local_name!("header"), Role::Banner),
|
||||
(local_name!("hgroup"), Role::Group),
|
||||
(local_name!("hr"), Role::Splitter),
|
||||
(local_name!("html"), Role::GenericContainer),
|
||||
(local_name!("i"), Role::GenericContainer),
|
||||
(local_name!("img"), Role::Image),
|
||||
(local_name!("ins"), Role::ContentInsertion),
|
||||
(local_name!("li"), Role::ListItem),
|
||||
(local_name!("main"), Role::Main),
|
||||
(local_name!("mark"), Role::Mark),
|
||||
(local_name!("menu"), Role::List),
|
||||
(local_name!("meter"), Role::Meter),
|
||||
(local_name!("nav"), Role::Navigation),
|
||||
(local_name!("ol"), Role::List),
|
||||
(local_name!("optgroup"), Role::Group),
|
||||
(local_name!("option"), Role::ListBoxOption),
|
||||
(local_name!("output"), Role::Status),
|
||||
(local_name!("p"), Role::Paragraph),
|
||||
(local_name!("pre"), Role::GenericContainer),
|
||||
(local_name!("progress"), Role::ProgressIndicator),
|
||||
(local_name!("q"), Role::GenericContainer),
|
||||
(local_name!("s"), Role::ContentDeletion),
|
||||
(local_name!("samp"), Role::GenericContainer),
|
||||
(local_name!("search"), Role::Search),
|
||||
(local_name!("section"), Role::Region),
|
||||
(local_name!("select"), Role::ListBox),
|
||||
(local_name!("select"), Role::ComboBox),
|
||||
(local_name!("small"), Role::GenericContainer),
|
||||
(local_name!("span"), Role::GenericContainer),
|
||||
(local_name!("strong"), Role::Strong),
|
||||
(local_name!("table"), Role::Table),
|
||||
(local_name!("tbody"), Role::RowGroup),
|
||||
(local_name!("td"), Role::Cell),
|
||||
(local_name!("textarea"), Role::TextInput),
|
||||
(local_name!("tfoot"), Role::RowGroup),
|
||||
(local_name!("th"), Role::Cell),
|
||||
(local_name!("th"), Role::ColumnHeader),
|
||||
(local_name!("th"), Role::RowHeader),
|
||||
(local_name!("thead"), Role::RowGroup),
|
||||
(local_name!("time"), Role::Time),
|
||||
(local_name!("tr"), Role::Row),
|
||||
(local_name!("u"), Role::GenericContainer),
|
||||
(local_name!("ul"), Role::List),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
});
|
||||
@@ -86,6 +86,7 @@ use url::Url;
|
||||
use webrender_api::ExternalScrollId;
|
||||
use webrender_api::units::{DevicePixel, LayoutVector2D};
|
||||
|
||||
use crate::accessibility_tree::{AccessibilityTree, AccessibilityTreeCalculator};
|
||||
use crate::context::{CachedImageOrError, ImageResolver, LayoutContext};
|
||||
use crate::display_list::{
|
||||
DisplayListBuilder, HitTest, LargestContentfulPaintCandidateCollector, StackingContextTree,
|
||||
@@ -180,6 +181,7 @@ pub struct LayoutThread {
|
||||
|
||||
/// The fragment tree.
|
||||
fragment_tree: RefCell<Option<Rc<FragmentTree>>>,
|
||||
accessibility_tree: RefCell<Option<AccessibilityTree>>,
|
||||
|
||||
/// The [`StackingContextTree`] cached from previous layouts.
|
||||
stacking_context_tree: RefCell<Option<StackingContextTree>>,
|
||||
@@ -749,6 +751,7 @@ impl LayoutThread {
|
||||
need_new_stacking_context_tree: Cell::new(false),
|
||||
box_tree: Default::default(),
|
||||
fragment_tree: Default::default(),
|
||||
accessibility_tree: Default::default(),
|
||||
stacking_context_tree: Default::default(),
|
||||
paint_api: config.paint_api,
|
||||
stylist: Stylist::new(device, QuirksMode::NoQuirks),
|
||||
@@ -1150,6 +1153,15 @@ impl LayoutThread {
|
||||
run_layout()
|
||||
});
|
||||
|
||||
let accessibility_tree =
|
||||
AccessibilityTreeCalculator::construct(document, fragment_tree.clone());
|
||||
self.script_chan
|
||||
.send(ScriptThreadMessage::HackySendAccessibilityTree(
|
||||
self.webview_id,
|
||||
accessibility_tree,
|
||||
))
|
||||
.expect("TODO: panic message");
|
||||
|
||||
*self.fragment_tree.borrow_mut() = Some(fragment_tree);
|
||||
|
||||
if self.debug.style_tree {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
//! Layout. Performs layout on the DOM, builds display lists and sends them to be
|
||||
//! painted.
|
||||
|
||||
mod accessibility_tree;
|
||||
mod cell;
|
||||
mod context;
|
||||
mod display_list;
|
||||
|
||||
@@ -104,6 +104,7 @@ impl MixedMessage {
|
||||
ScriptThreadMessage::EmbedderControlResponse(id, _) => Some(id.pipeline_id),
|
||||
ScriptThreadMessage::SetUserContents(..) => None,
|
||||
ScriptThreadMessage::DestroyUserContentManager(..) => None,
|
||||
ScriptThreadMessage::HackySendAccessibilityTree(..) => None,
|
||||
},
|
||||
MixedMessage::FromScript(inner_msg) => match inner_msg {
|
||||
MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, _, pipeline_id, _)) => {
|
||||
|
||||
@@ -1923,6 +1923,15 @@ impl ScriptThread {
|
||||
.borrow_mut()
|
||||
.remove(&user_content_manager_id);
|
||||
},
|
||||
ScriptThreadMessage::HackySendAccessibilityTree(webview_id, accessibility_tree) => {
|
||||
self.senders
|
||||
.pipeline_to_embedder_sender
|
||||
.send(EmbedderMsg::HackyAccessibilityTreeUpdate(
|
||||
webview_id,
|
||||
accessibility_tree,
|
||||
))
|
||||
.expect("TODO: panic message");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ webxr = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
accessibility_traits = { workspace = true }
|
||||
background_hang_monitor = { path = "../background_hang_monitor" }
|
||||
base = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
|
||||
@@ -638,6 +638,13 @@ impl ServoInner {
|
||||
warn!("Failed to respond to GetScreenMetrics: {error}");
|
||||
}
|
||||
},
|
||||
EmbedderMsg::HackyAccessibilityTreeUpdate(webview_id, accessibility_tree) => {
|
||||
if let Some(webview) = self.get_webview_handle(webview_id) {
|
||||
webview
|
||||
.delegate()
|
||||
.hacky_accessibility_tree_update(webview, accessibility_tree);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use accessibility_traits::AccessibilityTree;
|
||||
use base::generic_channel::GenericSender;
|
||||
use base::id::PipelineId;
|
||||
use compositing_traits::rendering_context::RenderingContext;
|
||||
@@ -974,6 +975,13 @@ pub trait WebViewDelegate {
|
||||
/// A console message was logged by content in this [`WebView`].
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/API/Console_API>
|
||||
fn show_console_message(&self, _webview: WebView, _level: ConsoleLogLevel, _message: String) {}
|
||||
|
||||
fn hacky_accessibility_tree_update(
|
||||
&self,
|
||||
_webview: WebView,
|
||||
_accessibility_tree: AccessibilityTree,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DefaultWebViewDelegate;
|
||||
|
||||
23
components/shared/accessibility/Cargo.toml
Normal file
23
components/shared/accessibility/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "accessibility_traits"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "accessibility_traits"
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
accesskit = { workspace = true, features = ["serde"] }
|
||||
rustc-hash = { workspace = true }
|
||||
base = { workspace = true }
|
||||
crossbeam-channel = { workspace = true }
|
||||
ipc-channel = { workspace = true }
|
||||
malloc_size_of = { workspace = true }
|
||||
malloc_size_of_derive = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
34
components/shared/accessibility/lib.rs
Normal file
34
components/shared/accessibility/lib.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use accesskit::{Node as AxNode, NodeId as AxNodeId, Tree as AxTree};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct AccessibilityTree {
|
||||
pub ax_nodes: FxHashMap<AxNodeId, AxNode>,
|
||||
pub ax_tree: AxTree,
|
||||
}
|
||||
|
||||
impl AccessibilityTree {
|
||||
pub fn descendants(&self) -> AxDescendants<'_> {
|
||||
AxDescendants(self, vec![self.ax_tree.root])
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AxDescendants<'tree>(&'tree AccessibilityTree, Vec<AxNodeId>);
|
||||
impl<'tree> Iterator for AxDescendants<'tree> {
|
||||
type Item = (AxNodeId, &'tree AxNode);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let Some(result_id) = self.1.pop() else {
|
||||
return None;
|
||||
};
|
||||
let result_node = self.0.ax_nodes.get(&result_id).unwrap();
|
||||
for child_id in result_node.children().iter().rev() {
|
||||
self.1.push(*child_id);
|
||||
}
|
||||
Some((result_id, result_node))
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ baked-default-resources = []
|
||||
gamepad = []
|
||||
|
||||
[dependencies]
|
||||
accessibility_traits = { workspace = true }
|
||||
base = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
cookie = { workspace = true }
|
||||
|
||||
@@ -22,6 +22,7 @@ use std::ops::Range;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use accessibility_traits::AccessibilityTree;
|
||||
use base::generic_channel::{GenericCallback, GenericSender, GenericSharedMemory, SendResult};
|
||||
use base::id::{PipelineId, WebViewId};
|
||||
use crossbeam_channel::Sender;
|
||||
@@ -530,6 +531,8 @@ pub enum EmbedderMsg {
|
||||
/// Inform the embedding layer that a particular `InputEvent` was handled by Servo
|
||||
/// and the embedder can continue processing it, if necessary.
|
||||
InputEventHandled(WebViewId, InputEventId, InputEventResult),
|
||||
/// Placeholder
|
||||
HackyAccessibilityTreeUpdate(WebViewId, AccessibilityTree),
|
||||
}
|
||||
|
||||
impl Debug for EmbedderMsg {
|
||||
|
||||
@@ -16,6 +16,7 @@ bluetooth = ["bluetooth_traits"]
|
||||
webgpu = ["webgpu_traits"]
|
||||
|
||||
[dependencies]
|
||||
accessibility_traits = { workspace = true }
|
||||
background_hang_monitor_api = { workspace = true }
|
||||
base = { workspace = true }
|
||||
bluetooth_traits = { workspace = true, optional = true }
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use accessibility_traits::AccessibilityTree;
|
||||
use base::cross_process_instant::CrossProcessInstant;
|
||||
use base::generic_channel::{GenericCallback, GenericReceiver, GenericSender};
|
||||
use base::id::{
|
||||
@@ -305,6 +306,8 @@ pub enum ScriptThreadMessage {
|
||||
/// Release all data for the given `UserContentManagerId` from the `ScriptThread`'s
|
||||
/// `user_contents_for_manager_id` map.
|
||||
DestroyUserContentManager(UserContentManagerId),
|
||||
/// Placeholder
|
||||
HackySendAccessibilityTree(WebViewId, AccessibilityTree),
|
||||
}
|
||||
|
||||
impl fmt::Debug for ScriptThreadMessage {
|
||||
|
||||
@@ -56,6 +56,7 @@ webgpu = ["libservo/webgpu"]
|
||||
webxr = ["libservo/webxr"]
|
||||
|
||||
[dependencies]
|
||||
accessibility_traits = { workspace = true }
|
||||
bpaf = { version = "0.9.20", features = ["derive"] }
|
||||
cfg-if = { workspace = true }
|
||||
crossbeam-channel = { workspace = true }
|
||||
|
||||
@@ -213,7 +213,7 @@ impl ApplicationHandler<AppEvent> for App {
|
||||
.and_then(|window_id| state.window(ServoShellWindowId::from(u64::from(window_id))))
|
||||
{
|
||||
if let Some(headed_window) = window.platform_window().as_headed_window() {
|
||||
headed_window.handle_winit_app_event(app_event);
|
||||
headed_window.handle_winit_app_event(&window, app_event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dpi::PhysicalSize;
|
||||
use egui::accesskit::{Role, TreeId, TreeUpdate, Uuid};
|
||||
use egui::text::{CCursor, CCursorRange};
|
||||
use egui::text_edit::TextEditState;
|
||||
use egui::{
|
||||
Button, Key, Label, LayerId, Modifiers, PaintCallback, TopBottomPanel, Vec2, WidgetInfo,
|
||||
WidgetType, pos2,
|
||||
WidgetType, accesskit, pos2,
|
||||
};
|
||||
use egui_glow::{CallbackFn, EguiGlow};
|
||||
use egui_winit::EventResponse;
|
||||
@@ -464,6 +465,18 @@ impl Gui {
|
||||
// If the top parts of the GUI changed size, then update the size of the WebView and also
|
||||
// the size of its RenderingContext.
|
||||
let rect = ctx.available_rect();
|
||||
let tree_id = TreeId(Uuid::from_bytes([1; 16]));
|
||||
let id = egui::Id::new("webview");
|
||||
ctx.accesskit_node_builder(id, |node| {
|
||||
node.set_role(Role::Group);
|
||||
node.set_tree_id(tree_id);
|
||||
node.set_bounds(accesskit::Rect {
|
||||
x0: rect.left() as f64,
|
||||
y0: rect.top() as f64,
|
||||
x1: rect.right() as f64,
|
||||
y1: rect.bottom() as f64,
|
||||
});
|
||||
});
|
||||
let size = Size2D::new(rect.width(), rect.height()) * scale;
|
||||
if let Some(webview) = window.active_webview() &&
|
||||
size != webview.size()
|
||||
@@ -608,6 +621,12 @@ impl Gui {
|
||||
pub(crate) fn set_zoom_factor(&self, factor: f32) {
|
||||
self.context.egui_ctx.set_zoom_factor(factor);
|
||||
}
|
||||
|
||||
pub(crate) fn hacky_accessibility_tree_update(&mut self, updater: impl FnOnce() -> TreeUpdate) {
|
||||
if let Some(adapter) = self.context.egui_winit.accesskit.as_mut() {
|
||||
adapter.update_if_active(updater);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn embedder_image_to_egui_image(image: &Image) -> egui::ColorImage {
|
||||
|
||||
@@ -13,6 +13,8 @@ use std::env;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use accessibility_traits::AccessibilityTree;
|
||||
use egui::accesskit::{Node, NodeId, TreeId, TreeUpdate, Uuid};
|
||||
use euclid::{Angle, Length, Point2D, Rect, Rotation3D, Scale, Size2D, UnknownUnit, Vector3D};
|
||||
use keyboard_types::ShortcutMatcher;
|
||||
use log::{debug, info};
|
||||
@@ -756,8 +758,21 @@ impl HeadedWindow {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn handle_winit_app_event(&self, app_event: AppEvent) {
|
||||
pub(crate) fn handle_winit_app_event(&self, _window: &ServoShellWindow, app_event: AppEvent) {
|
||||
if let AppEvent::Accessibility(ref event) = app_event {
|
||||
match &event.window_event {
|
||||
egui_winit::accesskit_winit::WindowEvent::InitialTreeRequested => {
|
||||
// TODO get the initial tree from servo
|
||||
},
|
||||
egui_winit::accesskit_winit::WindowEvent::ActionRequested(req) => {
|
||||
// TODO use window to get active webview, then do something with action request.
|
||||
if req.target_tree != TreeId::ROOT {
|
||||
println!("{:?}", req);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
if self
|
||||
.gui
|
||||
.borrow_mut()
|
||||
@@ -1131,6 +1146,26 @@ impl PlatformWindow for HeadedWindow {
|
||||
println!("{message}");
|
||||
log::log!(level.into(), "{message}");
|
||||
}
|
||||
|
||||
fn hacky_accessibility_tree_update(
|
||||
&self,
|
||||
_webview: WebView,
|
||||
accessibility_tree: AccessibilityTree,
|
||||
) {
|
||||
let nodes: Vec<(NodeId, Node)> = accessibility_tree
|
||||
.ax_nodes
|
||||
.iter()
|
||||
.map(|(k, v)| (*k, v.clone()))
|
||||
.collect();
|
||||
self.gui
|
||||
.borrow_mut()
|
||||
.hacky_accessibility_tree_update(|| TreeUpdate {
|
||||
nodes,
|
||||
tree: Some(accessibility_tree.ax_tree),
|
||||
tree_id: TreeId(Uuid::from_bytes([1; 16])),
|
||||
focus: NodeId(1),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn winit_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType {
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
use accessibility_traits::AccessibilityTree;
|
||||
use base::generic_channel::GenericCallback;
|
||||
use dpi::PhysicalSize;
|
||||
use euclid::{Rect, Scale};
|
||||
use keyboard_types::{CompositionEvent, CompositionState, Key, KeyState, NamedKey};
|
||||
use log::{info, warn};
|
||||
use log::{info, trace, warn};
|
||||
use raw_window_handle::{RawWindowHandle, WindowHandle};
|
||||
use servo::{
|
||||
DeviceIndependentIntRect, DeviceIndependentPixel, DeviceIntSize, DevicePixel, DevicePoint,
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::rc::Rc;
|
||||
|
||||
use accessibility_traits::AccessibilityTree;
|
||||
use crossbeam_channel::{Receiver, Sender, unbounded};
|
||||
use euclid::Rect;
|
||||
use image::{DynamicImage, ImageFormat, RgbaImage};
|
||||
@@ -819,6 +820,15 @@ impl WebViewDelegate for RunningAppState {
|
||||
self.platform_window_for_webview_id(webview.id())
|
||||
.show_console_message(level, &message);
|
||||
}
|
||||
|
||||
fn hacky_accessibility_tree_update(
|
||||
&self,
|
||||
webview: WebView,
|
||||
accessibility_tree: AccessibilityTree,
|
||||
) {
|
||||
self.platform_window_for_webview_id(webview.id())
|
||||
.hacky_accessibility_tree_update(webview, accessibility_tree);
|
||||
}
|
||||
}
|
||||
|
||||
struct ServoShellServoDelegate;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
use accessibility_traits::AccessibilityTree;
|
||||
use euclid::Scale;
|
||||
use log::warn;
|
||||
use servo::{
|
||||
@@ -433,4 +434,6 @@ pub(crate) trait PlatformWindow {
|
||||
fn as_headed_window(&self) -> Option<&crate::egl::app::EmbeddedPlatformWindow> {
|
||||
None
|
||||
}
|
||||
|
||||
fn hacky_accessibility_tree_update(&self, _: WebView, _: AccessibilityTree) {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user