only maintain a11y tree when requested

cameron created

Change summary

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(-)

Detailed changes

crates/gpui/src/element.rs 🔗

@@ -431,6 +431,9 @@ impl<E: Element> Drawable<E> {
     }
 
     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();
 

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::<InteractiveElementState, _>(
@@ -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());
+                }
             }
         }
 

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()

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);

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<WindowControls>,
     window_controls: WindowControls,
     client_inset: Option<Pixels>,
-    // todo! document initialization reqs
+    a11y_active: Arc<AtomicBool>,
     accesskit: Option<accesskit_unix::Adapter>,
 }
 
@@ -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<WaylandWindowState>) {

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<EdgeConstraints>,
     pub handle: AnyWindowHandle,
     last_insets: [u32; 4],
-    // todo! document initialization reqs
+    a11y_active: Arc<AtomicBool>,
     accesskit: Option<accesskit_unix::Adapter>,
 }
 
@@ -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();