mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
gpui: Application::inaccessible() (#57954)
Provide a way to prevent GPUI from creating AccessKit adapters, and enable this in Zed. This will allow us to test AccessKit support in Zed without rolling it out more broadly, while we gain confidence in the implementation in GPUI. I've also added a log statement ## Motivation (i.e. a mini post-mortem about the #56065 panics) Merging #56065 caused some nasty panics in nightly. This was caused by a bug in the logic for selecting a focus node for a `TreeUpdate`. AccessKit panics when an invalid `TreeUpdate` is provided. My assumption was that, since Zed uses no a11y APIs, and also that essentially 0 zed users would have AT apps running, that merging this PR would have no effect on the behaviour of Zed itself. However, two issues combined to cause the panics: - It seems like many people (everyone?) on mac gets the activation callback called by accesskit_macos. A quick search suggests this might be due to password managers searching for password fields, but not sure how true that is. - The bug in question related to *forgetting to check* whether a node used a11y APIs, so we *were* pushing non-empty `TreeUpdate`s As a (probably temporary) defensive measure, I added a function to try to detect the bad cases and fix them. But it would be lovely if this could live in AccessKit itself, since it would mean we wouldn't have to do the check twice (once in GPUI, once in AccessKit). This would also help prevent drift when updating accesskit versions if new invariants are added. We also cannot protect against this with `catch_unwind`, since we use `panic=abort`. So our only option unfortunately is to temporarily disable AccessKit until we know our implementation is stable. Release Notes: - N/A or Added/Fixed/Improved ...
This commit is contained in:
parent
b8c853a63d
commit
4d6a3c7e11
4 changed files with 55 additions and 8 deletions
|
|
@ -151,6 +151,21 @@ impl Application {
|
|||
))
|
||||
}
|
||||
|
||||
/// Builds an app with accessibility (AccessKit) integration forcibly
|
||||
/// disabled.
|
||||
///
|
||||
/// In this mode, accessibility APIs (e.g.
|
||||
/// [`div().role()`][crate::StatefulInteractiveElement::role]) silently
|
||||
/// no-op.
|
||||
///
|
||||
/// See the [accessibility guide](crate::_accessibility) for an overview of
|
||||
/// the features this disables.
|
||||
pub fn new_inaccessible(platform: Rc<dyn Platform>) -> Self {
|
||||
let this = Self::with_platform(platform);
|
||||
this.0.borrow_mut().accessibility_force_disabled = true;
|
||||
this
|
||||
}
|
||||
|
||||
/// Assigns the source of assets for the application.
|
||||
pub fn with_assets(self, asset_source: impl AssetSource) -> Self {
|
||||
let mut context_lock = self.0.borrow_mut();
|
||||
|
|
@ -666,6 +681,9 @@ pub struct App {
|
|||
pub(crate) window_update_stack: Vec<WindowId>,
|
||||
pub(crate) mode: GpuiMode,
|
||||
pub(crate) cursor_hide_mode: CursorHideMode,
|
||||
/// Whether the app was created by [`Application::new_inaccessible`]. No
|
||||
/// accesskit APIs will be called when this flag is set.
|
||||
pub(crate) accessibility_force_disabled: bool,
|
||||
flushing_effects: bool,
|
||||
pending_updates: usize,
|
||||
quit_mode: QuitMode,
|
||||
|
|
@ -755,6 +773,7 @@ impl App {
|
|||
quit_mode: QuitMode::default(),
|
||||
quitting: false,
|
||||
cursor_hide_mode: CursorHideMode::default(),
|
||||
accessibility_force_disabled: false,
|
||||
|
||||
#[cfg(any(test, feature = "test-support", debug_assertions))]
|
||||
name: None,
|
||||
|
|
|
|||
|
|
@ -1330,10 +1330,11 @@ impl Window {
|
|||
WindowBounds::Windowed(_) => {}
|
||||
}
|
||||
|
||||
let accessibility_force_disabled = cx.accessibility_force_disabled;
|
||||
let a11y_active_flag = Arc::new(AtomicBool::new(false));
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
{
|
||||
if !accessibility_force_disabled {
|
||||
let initial_tree = accesskit::TreeUpdate {
|
||||
nodes: vec![(ROOT_NODE_ID, accesskit::Node::new(accesskit::Role::Window))],
|
||||
tree: Some(accesskit::Tree::new(ROOT_NODE_ID)),
|
||||
|
|
@ -1717,7 +1718,7 @@ impl Window {
|
|||
captured_hitbox: None,
|
||||
#[cfg(any(feature = "inspector", debug_assertions))]
|
||||
inspector: None,
|
||||
a11y: A11y::new(a11y_active_flag),
|
||||
a11y: A11y::new(a11y_active_flag, accessibility_force_disabled),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ pub(crate) type A11yActionListener =
|
|||
/// Manages the AccessKit tree that is built each frame and the mappings
|
||||
/// needed to dispatch incoming action requests back to the right elements.
|
||||
pub(crate) struct A11y {
|
||||
/// Whether accessibility has been [forcibly disabled] for this window.
|
||||
///
|
||||
/// [forcibly disabled]: crate::Application::new_inaccessible
|
||||
force_disabled: bool,
|
||||
/// Whether a11y features have been requested by the system.
|
||||
///
|
||||
/// Updated by AccessKit using callbacks provided to the adapter. Can change
|
||||
|
|
@ -131,8 +135,9 @@ pub(crate) struct A11y {
|
|||
}
|
||||
|
||||
impl A11y {
|
||||
pub(crate) fn new(active_flag: Arc<AtomicBool>) -> Self {
|
||||
pub(crate) fn new(active_flag: Arc<AtomicBool>, force_disabled: bool) -> Self {
|
||||
Self {
|
||||
force_disabled,
|
||||
active_flag,
|
||||
active_this_frame: false,
|
||||
nodes: A11yNodeBuilder::new(),
|
||||
|
|
@ -147,7 +152,7 @@ impl A11y {
|
|||
/// See the docs for [`Self::active_flag`] and [`Self::active_this_frame`]
|
||||
/// for more commentary.
|
||||
pub(crate) fn sync_active_flag(&mut self) {
|
||||
self.active_this_frame = self.active_flag.load(Ordering::SeqCst);
|
||||
self.active_this_frame = !self.force_disabled && self.active_flag.load(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub(crate) fn is_active(&self) -> bool {
|
||||
|
|
@ -164,7 +169,21 @@ impl A11y {
|
|||
|
||||
/// Finalize the tree and produce a [`TreeUpdate`] for the platform adapter.
|
||||
pub(crate) fn end_frame(&mut self) -> TreeUpdate {
|
||||
self.nodes.finalize()
|
||||
let tree_update = self.nodes.finalize();
|
||||
|
||||
// Zed currently doesn't set any a11y APIs on *any* UI elements, so a
|
||||
// tree with nodes other than the root indicates a bug in the
|
||||
// `TreeUpdate`-producing logic.
|
||||
//
|
||||
// Remove this when adding aria attributes.
|
||||
if tree_update.nodes.len() > 1 {
|
||||
log::warn!(
|
||||
"expected an empty a11y tree update (only the root node), but got {} nodes; Zed has no accessible UI elements yet",
|
||||
tree_update.nodes.len()
|
||||
);
|
||||
}
|
||||
|
||||
tree_update
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,15 @@ use crate::zed::{CrashHandler, OpenRequestKind, eager_load_active_theme_and_icon
|
|||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
fn build_application() -> Application {
|
||||
let platform = gpui_platform::current_platform(false);
|
||||
if std::env::var("ZED_EXPERIMENTAL_A11Y").as_deref() == Ok("1") {
|
||||
Application::with_platform(platform)
|
||||
} else {
|
||||
Application::new_inaccessible(platform)
|
||||
}
|
||||
}
|
||||
|
||||
fn files_not_created_on_launch(errors: HashMap<io::ErrorKind, Vec<&Path>>) {
|
||||
let message = "Zed failed to launch";
|
||||
let error_details = errors
|
||||
|
|
@ -113,7 +122,7 @@ fn files_not_created_on_launch(errors: HashMap<io::ErrorKind, Vec<&Path>>) {
|
|||
.collect::<Vec<_>>().join("\n\n");
|
||||
|
||||
eprintln!("{message}: {error_details}");
|
||||
Application::with_platform(gpui_platform::current_platform(false))
|
||||
build_application()
|
||||
.with_quit_mode(QuitMode::Explicit)
|
||||
.run(move |cx| {
|
||||
if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |_, cx| {
|
||||
|
|
@ -338,8 +347,7 @@ fn main() {
|
|||
#[cfg(windows)]
|
||||
check_for_conpty_dll();
|
||||
|
||||
let app =
|
||||
Application::with_platform(gpui_platform::current_platform(false)).with_assets(Assets);
|
||||
let app = build_application().with_assets(Assets);
|
||||
|
||||
let app_db = db::AppDatabase::new();
|
||||
let system_id = app.background_executor().spawn(system_id());
|
||||
|
|
|
|||
Loading…
Reference in a new issue