mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user