From 09300ea5d8364766d0c3baf99f7a4b9f5440af06 Mon Sep 17 00:00:00 2001 From: cameron Date: Mon, 16 Mar 2026 16:47:03 +0000 Subject: [PATCH] only maintain a11y tree when requested --- crates/gpui/src/element.rs | 3 ++ crates/gpui/src/elements/div.rs | 24 +++++---- crates/gpui/src/platform.rs | 6 +++ crates/gpui/src/window.rs | 29 ++++++++--- crates/gpui_linux/src/linux/wayland/window.rs | 40 +++++++++++++-- crates/gpui_linux/src/linux/x11/window.rs | 50 ++++++++++++++++--- 6 files changed, 123 insertions(+), 29 deletions(-) diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index 16bd18fd15578a8088dee1d583b75abeef0f0c63..38e1d612e629c210c92d1334ef9a9fd3b377f2cc 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -431,6 +431,9 @@ impl Drawable { } fn push_a11y_node(&self, global_id: &GlobalElementId, window: &mut Window) -> bool { + if !window.is_a11y_active() { + return false; + } if let Some(role) = self.element.a11y_role() { let global_id = global_id.accesskit_node_id(); diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 4e9c7993550dec3ff913a35a45f41a1362664280..8cc4d08fc2e4534bbc79eb64ff9be80b6b59e318 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1968,10 +1968,12 @@ impl Interactivity { if let Some(focus_handle) = self.tracked_focus_handle.as_ref() { window.set_focus_handle(focus_handle, cx); - if let Some(global_id) = global_id { - window - .a11y_focus_ids - .insert(global_id.accesskit_node_id(), focus_handle.id); + if window.is_a11y_active() { + if let Some(global_id) = global_id { + window + .a11y_focus_ids + .insert(global_id.accesskit_node_id(), focus_handle.id); + } } } window.with_optional_element_state::( @@ -2465,12 +2467,14 @@ impl Interactivity { let aux_click_listeners = mem::take(&mut self.aux_click_listeners); let can_drop_predicate = mem::take(&mut self.can_drop_predicate); - if let Some(global_id) = global_id { - if !click_listeners.is_empty() { - let node_id = global_id.accesskit_node_id(); - window - .a11y_click_listeners - .insert(node_id, click_listeners.clone()); + if window.is_a11y_active() { + if let Some(global_id) = global_id { + if !click_listeners.is_empty() { + let node_id = global_id.accesskit_node_id(); + window + .a11y_click_listeners + .insert(node_id, click_listeners.clone()); + } } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index e07b2ab1bf698e1a406e31c92b8810c6dde688f6..ac12680d78580045012c9bf82f3f1b3b53398ace 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -508,6 +508,12 @@ pub trait PlatformWindow: HasWindowHandle + HasDisplayHandle { /// Inform the accesskit adapter of the bounds of the window. fn a11y_update_window_bounds(&self); + /// Whether the platform's accessibility adapter is currently active. Always + /// returns false on platforms that don't support accesskit. + fn is_a11y_active(&self) -> bool { + false + } + // macOS specific methods fn get_title(&self) -> String { String::new() diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index ee4e7f377fbb5ce82dcd9f3b333a779030b8352a..c313656786c4fa4b1c558ae8fd7218d63155dffa 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2427,8 +2427,11 @@ impl Window { fn draw_roots(&mut self, cx: &mut App) { self.invalidator.set_phase(DrawPhase::Prepaint); self.tooltip_bounds.take(); - self.a11y_click_listeners.clear(); - self.a11y_focus_ids.clear(); + + if self.is_a11y_active() { + self.a11y_click_listeners.clear(); + self.a11y_focus_ids.clear(); + } let _inspector_width: Pixels = rems(30.0).to_pixels(self.rem_size()); let root_size = { @@ -2449,7 +2452,9 @@ impl Window { }; // Layout all root elements. - self.a11y_nodes.push_root(); + if self.is_a11y_active() { + self.a11y_nodes.push_root(); + } let mut root_element = self.root.as_ref().unwrap().clone().into_any(); root_element.prepaint_as_root(Point::default(), root_size.into(), self, cx); @@ -2501,8 +2506,16 @@ impl Window { #[cfg(any(feature = "inspector", debug_assertions))] self.paint_inspector_hitbox(cx); - let tree_update = self.a11y_nodes.finalize(); - self.platform_window.a11y_tree_update(tree_update); + if self.is_a11y_active() { + let tree_update = self.a11y_nodes.finalize(); + self.platform_window.a11y_tree_update(tree_update); + } + } + + /// Whether accessibility information is currently being requested by an + /// a11y tool. If false, we can skip building the a11y tree altogether. + pub fn is_a11y_active(&self) -> bool { + self.platform_window.is_a11y_active() } fn handle_a11y_action(&mut self, request: ActionRequest, cx: &mut App) { @@ -3844,8 +3857,10 @@ impl Window { self.invalidator.debug_assert_prepaint(); if focus_handle.is_focused(self) { self.next_frame.focus = Some(focus_handle.id); - if let Some(node_id) = self.a11y_nodes.current_node_id() { - self.a11y_nodes.set_focused(node_id); + if self.is_a11y_active() { + if let Some(node_id) = self.a11y_nodes.current_node_id() { + self.a11y_nodes.set_focused(node_id); + } } } self.next_frame.dispatch_tree.set_focus_id(focus_handle.id); diff --git a/crates/gpui_linux/src/linux/wayland/window.rs b/crates/gpui_linux/src/linux/wayland/window.rs index c19c056046c3f978157bdca01f8d38a967da536c..4cda257e10e8e18dcdc452a8054c2b65bbf0a713 100644 --- a/crates/gpui_linux/src/linux/wayland/window.rs +++ b/crates/gpui_linux/src/linux/wayland/window.rs @@ -3,7 +3,10 @@ use std::{ ffi::c_void, ptr::NonNull, rc::Rc, - sync::Arc, + sync::{ + Arc, + atomic::{AtomicBool, Ordering::SeqCst}, + }, }; use accesskit::TreeUpdate; @@ -34,7 +37,8 @@ use gpui::{ AnyWindowHandle, Bounds, Capslock, Decorations, DevicePixels, GpuSpecs, Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, Scene, Size, Tiling, - WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowControls, + TrivialActivationHandler, TrivialDeactivationHandler, WindowAppearance, + WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowControls, WindowDecorations, WindowKind, WindowParams, layer_shell::LayerShellNotSupportedError, px, size, }; @@ -120,7 +124,7 @@ pub struct WaylandWindowState { in_progress_window_controls: Option, window_controls: WindowControls, client_inset: Option, - // todo! document initialization reqs + a11y_active: Arc, accesskit: Option, } @@ -393,6 +397,7 @@ impl WaylandWindowState { in_progress_window_controls: None, window_controls: WindowControls::default(), client_inset: None, + a11y_active: Arc::new(AtomicBool::new(false)), accesskit: None, }) } @@ -1481,10 +1486,31 @@ impl PlatformWindow for WaylandWindow { panic!("cannot initialize accesskit twice"); } + let original_activation = callbacks.activation; + let activation = TrivialActivationHandler(Box::new({ + let a11y_active = state.a11y_active.clone(); + move || { + let tree = (original_activation.0)(); + if tree.is_some() { + a11y_active.store(true, SeqCst); + } + tree + } + })); + + let original_deactivation = callbacks.deactivation; + let deactivation = TrivialDeactivationHandler(Box::new({ + let a11y_active = state.a11y_active.clone(); + move || { + a11y_active.store(false, SeqCst); + (original_deactivation.0)(); + } + })); + state.accesskit = Some(accesskit_unix::Adapter::new( - callbacks.activation, + activation, callbacks.action, - callbacks.deactivation, + deactivation, )); } @@ -1501,6 +1527,10 @@ impl PlatformWindow for WaylandWindow { fn a11y_update_window_bounds(&self) { // no-op - wayland does not let us get bounds for our window } + + fn is_a11y_active(&self) -> bool { + self.0.state.borrow().a11y_active.load(SeqCst) + } } fn update_window(mut state: RefMut) { diff --git a/crates/gpui_linux/src/linux/x11/window.rs b/crates/gpui_linux/src/linux/x11/window.rs index ec65e09ff43fb15353bd67c551035a5025d5077a..d02ffbfe09f791e7d754b911eb8e01065e741829 100644 --- a/crates/gpui_linux/src/linux/x11/window.rs +++ b/crates/gpui_linux/src/linux/x11/window.rs @@ -7,8 +7,9 @@ use gpui::{ AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size, - Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea, - WindowDecorations, WindowKind, WindowParams, px, + Tiling, TrivialActivationHandler, TrivialDeactivationHandler, WindowAppearance, + WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowDecorations, WindowKind, + WindowParams, px, }; use gpui_wgpu::{CompositorGpuHint, WgpuRenderer, WgpuSurfaceConfig}; @@ -30,7 +31,16 @@ use x11rb::{ }; use std::{ - cell::RefCell, ffi::c_void, fmt::Display, num::NonZeroU32, ptr::NonNull, rc::Rc, sync::Arc, + cell::RefCell, + ffi::c_void, + fmt::Display, + num::NonZeroU32, + ptr::NonNull, + rc::Rc, + sync::{ + Arc, + atomic::{AtomicBool, Ordering::SeqCst}, + }, }; use super::{X11Display, XINPUT_ALL_DEVICE_GROUPS, XINPUT_ALL_DEVICES}; @@ -282,7 +292,7 @@ pub struct X11WindowState { edge_constraints: Option, pub handle: AnyWindowHandle, last_insets: [u32; 4], - // todo! document initialization reqs + a11y_active: Arc, accesskit: Option, } @@ -755,6 +765,7 @@ impl X11WindowState { edge_constraints: None, counter_id: sync_request_counter, last_sync_counter: None, + a11y_active: Arc::new(AtomicBool::new(false)), accesskit: None, }) }); @@ -1834,11 +1845,32 @@ impl PlatformWindow for X11Window { panic!("cannot initialize accesskit twice"); } + let original_activation = callbacks.activation; + let activation = TrivialActivationHandler(Box::new({ + let a11y_active = state.a11y_active.clone(); + move || { + let tree = (original_activation.0)(); + if tree.is_some() { + a11y_active.store(true, SeqCst); + } + tree + } + })); + + let original_deactivation = callbacks.deactivation; + let deactivation = TrivialDeactivationHandler(Box::new({ + let a11y_active = state.a11y_active.clone(); + move || { + a11y_active.store(false, SeqCst); + (original_deactivation.0)(); + } + })); + state.accesskit = Some(accesskit_unix::Adapter::new( - callbacks.activation, + activation, callbacks.action, - callbacks.deactivation, - )) + deactivation, + )); } fn a11y_tree_update(&mut self, tree_update: TreeUpdate) { @@ -1851,6 +1883,10 @@ impl PlatformWindow for X11Window { adapter.update_if_active(|| tree_update); } + fn is_a11y_active(&self) -> bool { + self.0.state.borrow().a11y_active.load(SeqCst) + } + fn a11y_update_window_bounds(&self) { let mut state = self.0.state.borrow_mut();