servoshell: Activate accessibility in all webviews (#43558)

this patch plumbs the webview accessibility trees (#43029, #43556) into
servoshell. we add a global flag in servoshell, which is set when the
platform activates accessibility and cleared when the platform
deactivates accessibility. the flag in turn [activates
accessibility](https://doc.servo.org/servo/struct.WebView.html#method.set_accessibility_active)
in existing and new webviews.

Testing: none in this patch, but will be covered by end-to-end platform
a11y tests in WPT
Fixes: part of #4344, extracted from our work in #42338

Signed-off-by: delan azabani <dazabani@igalia.com>
Co-authored-by: Luke Warlow <lwarlow@igalia.com>
Co-authored-by: Alice Boxhall <alice@igalia.com>
This commit is contained in:
shuppy
2026-04-01 17:46:48 +08:00
committed by GitHub
parent fdadd1d31d
commit 805519deac
5 changed files with 74 additions and 6 deletions

View File

@@ -214,7 +214,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(&window, app_event);
headed_window.handle_winit_app_event(state.clone(), app_event);
}
}

View File

@@ -59,6 +59,10 @@ pub struct Gui {
///
/// These need to be cached across egui draw calls.
favicon_textures: HashMap<WebViewId, (egui::TextureHandle, egui::load::SizedTexture)>,
/// AccessKit tree updates pending the next egui tick.
/// This allows us to ensure that graft nodes are sent before the subtrees they graft.
pending_accesskit_updates: Vec<accesskit::TreeUpdate>,
}
fn truncate_with_ellipsis(input: &str, max_length: usize) -> String {
@@ -124,6 +128,7 @@ impl Gui {
can_go_back: false,
can_go_forward: false,
favicon_textures: Default::default(),
pending_accesskit_updates: vec![],
}
}
@@ -472,6 +477,16 @@ 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();
// Build a graft node for each WebView.
for (webview_id, webview) in window.webviews() {
if let Some(tree_id) = webview.accesskit_tree_id() {
let id = egui::Id::new(webview_id);
ctx.accesskit_node_builder(id, |node| {
node.set_tree_id(tree_id);
});
}
}
let size = Size2D::new(rect.width(), rect.height()) * scale;
if let Some(webview) = window.active_webview() &&
size != webview.size()
@@ -509,6 +524,16 @@ impl Gui {
});
}
});
let adapter = self
.context
.egui_winit
.accesskit
.as_mut()
.expect("guaranteed by Gui::new()");
for tree_update in self.pending_accesskit_updates.drain(..) {
adapter.update_if_active(|| tree_update);
}
}
/// Paint the GUI, as of the last update.
@@ -618,8 +643,8 @@ impl Gui {
self.context.egui_ctx.set_zoom_factor(factor);
}
pub(crate) fn notify_accessibility_tree_update(&mut self, _tree_update: accesskit::TreeUpdate) {
// TODO(#41930): Forward this update to `self.context.egui_winit.accesskit`
pub(crate) fn notify_accessibility_tree_update(&mut self, tree_update: accesskit::TreeUpdate) {
self.pending_accesskit_updates.push(tree_update);
}
}

View File

@@ -761,9 +761,21 @@ impl HeadedWindow {
}
}
pub(crate) fn handle_winit_app_event(&self, _window: &ServoShellWindow, app_event: AppEvent) {
pub(crate) fn handle_winit_app_event(&self, state: Rc<RunningAppState>, app_event: AppEvent) {
if let AppEvent::Accessibility(ref event) = app_event {
// TODO(#41930): Forward accesskit_winit::WindowEvent events to Servo where appropriate
match &event.window_event {
egui_winit::accesskit_winit::WindowEvent::InitialTreeRequested => {
state.set_accessibility_active(true);
},
egui_winit::accesskit_winit::WindowEvent::ActionRequested(req) => {
if req.target_tree != accesskit::TreeId::ROOT {
// TODO(#4344): Forward action to Servo
}
},
egui_winit::accesskit_winit::WindowEvent::AccessibilityDeactivated => {
state.set_accessibility_active(false);
},
}
if self
.gui

View File

@@ -216,6 +216,13 @@ pub(crate) struct RunningAppState {
/// The currently focused [`ServoShellWindow`], if one is focused.
focused_window: RefCell<Option<Rc<ServoShellWindow>>>,
/// Whether accessibility is active in servoshell.
///
/// Set by the platform via AccessKit, and forwarded to existing and new WebViews via
/// [`WebView::set_accessibility_active()`], in [`Self::set_accessibility_active()`] and
/// and [`ServoShellWindow::create_toplevel_webview()`].
accessibility_active: Cell<bool>,
}
impl RunningAppState {
@@ -265,6 +272,7 @@ impl RunningAppState {
exit_scheduled: Default::default(),
user_content_manager,
experimental_preferences_enabled,
accessibility_active: Cell::new(false),
}
}
@@ -274,10 +282,10 @@ impl RunningAppState {
initial_url: Url,
) -> Rc<ServoShellWindow> {
let window = Rc::new(ServoShellWindow::new(platform_window.clone()));
window.create_and_activate_toplevel_webview(self.clone(), initial_url);
self.windows
.borrow_mut()
.insert(window.id(), window.clone());
window.create_and_activate_toplevel_webview(self.clone(), initial_url);
// If the window already has platform focus, mark it as focused in our application state.
if platform_window.has_platform_focus() {
@@ -661,6 +669,23 @@ impl RunningAppState {
});
}
}
pub(crate) fn set_accessibility_active(&self, active: bool) {
let was_active = self.accessibility_active.replace(active);
if active == was_active {
return;
}
for window in self.windows().values() {
for (_, webview) in window.webviews() {
webview.set_accessibility_active(active);
}
}
}
pub(crate) fn accessibility_active(&self) -> bool {
self.accessibility_active.get()
}
}
impl WebViewDelegate for RunningAppState {

View File

@@ -74,6 +74,7 @@ impl ServoShellWindow {
self.platform_window().id()
}
/// Must be called *after* `self` is in `state.windows`, otherwise it will panic.
pub(crate) fn create_and_activate_toplevel_webview(
&self,
state: Rc<RunningAppState>,
@@ -84,6 +85,7 @@ impl ServoShellWindow {
webview
}
/// Must be called *after* `self` is in `state.windows`, otherwise it will panic.
pub(crate) fn create_toplevel_webview(&self, state: Rc<RunningAppState>, url: Url) -> WebView {
let mut webview_builder =
WebViewBuilder::new(state.servo(), self.platform_window.rendering_context())
@@ -103,6 +105,10 @@ impl ServoShellWindow {
let webview = webview_builder.build();
webview.notify_theme_change(self.platform_window.theme());
self.add_webview(webview.clone());
// If `self` is not in `state.windows`, our notify_accessibility_tree_update() will panic.
if state.accessibility_active() {
webview.set_accessibility_active(true);
}
webview
}