Special attribute for macOS accessibility#10305
Conversation
In the linked issue we were discussing that Electron apps are inaccessible unless VoiceOver is enabled. While it's a working solution for users with vision impairment, all other users and apps that require accessibility can't interact with Electron-based software because they don't keep VoiceOver running. I suggest adding `AXManualAccessibility` for programmatically enabling it in Electron apps. The reason for a new attribute is that `AXEnhancedUserInterface` is already reserved by VoiceOver. Adding this attribute will allow both Electron developers and 3rd party developers to enable and disable accessibility from their code by calling `accessibilitySetValue:forAttribute:` on the application. It will be also possible to create a small utility app to switch accessibility in Electron-based apps until there's a native UI solution (like the accessibility settings page in Chrome).
|
💖 Thanks for opening this pull request! 💖 Here is a list of things that will help get it across the finish line:
|
|
Thanks, @ivmirx. Can you add some API documentation for this? |
|
@zeke thank you for looking into this. I added the macOS case to the accessibility tutorial. The example code will work from Cocoa apps and should also work via NodObjC for Electron apps. But maybe I'm missing an easier way for Electron apps to switch their own accessibility. |
Yeah this would be ideal. End-users of Electron should generally not be exposed to C++ code, but should rather use a JavaScript API that hooks into the underlying C++ implementation. |
|
I added an accessibility setter to the app bindings and updated docs with a warning about performance. Overall I haven't experienced noticeable performance issues when running many Chrome tabs with accessibility enabled (Mac Mini 2012) but there's some overhead according to dev tools (depending on DOM complexity and update frequency). |
| https://www.chromium.org/developers/design-documents/accessibility for more | ||
| details. | ||
|
|
||
| ### `app.setAccessibilitySupportEnabled(value)` _macOS_ _Windows_ |
There was a problem hiding this comment.
I would suggest using a more descriptive name than value for the boolean. Something like enabled would be more consistent with other APIs that accept boolean arguments:
win.setResizable(resizable)win.setMovable(movable)win.setMinimizable(minimizable)
There was a problem hiding this comment.
Oh, it was enabled initially but then I decided to follow the app.commandLine.appendArgument(value) nearby. Fixed!
zeke
left a comment
There was a problem hiding this comment.
From an user perspective, this is looking great. Left a few comments on documentation and expected behavior.
|
|
||
| ### `app.setAccessibilitySupportEnabled(enabled)` _macOS_ _Windows_ | ||
|
|
||
| * `enabled` Boolean - Enable or disable accessibility tree rendering |
There was a problem hiding this comment.
Is accessibility support disabled by default? If so, these docs should probably mention that.
For folks who don't know what the "accessibility tree rendering" is, a link would be helpful. Is this the right URL? https://developers.google.com/web/fundamentals/accessibility/semantics-builtin/the-accessibility-tree
There was a problem hiding this comment.
Yes, it's disabled by default both in Chrome and Electron. The link is super helpful, adding it.
|
|
||
| ### Assistive Technology | ||
|
|
||
| Electron application will enable accessibility automatically when it detects assistive technology (Windows) or VoiceOver (macOS). See Chrome's [accessibility documentation](https://www.chromium.org/developers/design-documents/accessibility#TOC-How-Chrome-detects-the-presence-of-Assistive-Technology) for more details. |
There was a problem hiding this comment.
"Electron application will enable accessibility automatically" -- just so I understand correctly: does this mean the presence of these assistive features will automatically cause accessibility to be enabled, regardless of the current setting?
Or does it only mean that these features will be used if setAccessibilitySupportEnabled(true) was explicitly called in the app?
There was a problem hiding this comment.
The first one – system assistive utilities have a priority and will enable accessibility regardless of the setAccessibilitySupportEnabled(). So an Electron app's developer can be unaware of this method and accessibility will still work for many users who have VoiceOver on.
For users who need accessibility without VoiceOver there are 3 options:
- ask an Electron app's developer to expose accessibility switch in settings using
setAccessibilitySupportEnabled(true) - ask their assistive software developer to start applying the
AXManualAccessibilityattribute on Electron apps - use some custom utility to apply
AXManualAccessibilityon Electron apps (I'm going to create an open-source one later)
|
|
||
| Electron application will enable accessibility automatically when it detects assistive technology (Windows) or VoiceOver (macOS). See Chrome's [accessibility documentation](https://www.chromium.org/developers/design-documents/accessibility#TOC-How-Chrome-detects-the-presence-of-Assistive-Technology) for more details. | ||
|
|
||
| On macOS 3rd party assistive technology can switch accessibility inside Electron applications by setting the attribute `AXManualAccessibility` programmatically: |
|
Congrats on merging your first pull request! 🎉🎉🎉 |
Quitting the recorder (e.g. from the tray) could inject ghost keystrokes into the focused app — typed text coming back mangled or with garbage appended (e.g. "hello hello" → "ello ell ell"), most visibly in Chromium/Electron apps like Discord. Root causes fixed: - Probe tap on every permission poll: removed probe_listen_event_tap(). Permission checks now read INPUT_MONITORING_GROUND_TRUTH (populated by the real capture tap) falling back to CGPreflightListenEventAccess. Modelled after keycastr/keycastr@1025e8f - Shutdown race: tap callback now checks state.stop (Acquire) first and drops events immediately when teardown begins. - InstalledTap RAII guard: idempotent teardown — set_enabled(false) → remove_src → invalidate src → invalidate tap → drain run loop. Drop impl as safety net against panics. - TAP_DISABLED_BY_TIMEOUT replay burst: re-enable tap immediately via tap_ptr; use try_lock in callback so a slow flush can't stall past the kernel timeout. - Chromium AX replay: remove AXObserver notifications before teardown, clear AXManualAccessibility to signal graceful disconnect, drain run loop 3× so Chromium flushes buffered keystrokes instead of replaying. Ref: electron/electron#10305 - Memory ordering: Relaxed → Acquire on all stop-flag reads. - TapState Box::leak reclaimed on stop (was a per-session leak). Regression tests: test_no_ghost_keystrokes_after_stop, test_stop_flag_concurrent. UI input-monitoring card updated to reflect the no-probe model. Closes screenpipe#3789
…raceful shutdown (#3916) * fix(a11y): eliminate ghost keystrokes on macOS recorder shutdown Quitting the recorder (e.g. from the tray) could inject ghost keystrokes into the focused app — typed text coming back mangled or with garbage appended (e.g. "hello hello" → "ello ell ell"), most visibly in Chromium/Electron apps like Discord. Root causes fixed: - Probe tap on every permission poll: removed probe_listen_event_tap(). Permission checks now read INPUT_MONITORING_GROUND_TRUTH (populated by the real capture tap) falling back to CGPreflightListenEventAccess. Modelled after keycastr/keycastr@1025e8f - Shutdown race: tap callback now checks state.stop (Acquire) first and drops events immediately when teardown begins. - InstalledTap RAII guard: idempotent teardown — set_enabled(false) → remove_src → invalidate src → invalidate tap → drain run loop. Drop impl as safety net against panics. - TAP_DISABLED_BY_TIMEOUT replay burst: re-enable tap immediately via tap_ptr; use try_lock in callback so a slow flush can't stall past the kernel timeout. - Chromium AX replay: remove AXObserver notifications before teardown, clear AXManualAccessibility to signal graceful disconnect, drain run loop 3× so Chromium flushes buffered keystrokes instead of replaying. Ref: electron/electron#10305 - Memory ordering: Relaxed → Acquire on all stop-flag reads. - TapState Box::leak reclaimed on stop (was a per-session leak). Regression tests: test_no_ghost_keystrokes_after_stop, test_stop_flag_concurrent. UI input-monitoring card updated to reflect the no-probe model. Closes #3789 * style(a11y): apply rustfmt to shutdown changes Fixes the cargo fmt --all --check failure (the only red required check). Pure formatting, no logic change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Louis Beaumont <louis@screenpi.pe> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
In the issue #7206 we were discussing that Electron apps are inaccessible unless VoiceOver is enabled. While it's a working solution for users with vision impairment, all other users and apps that require accessibility can't interact with Electron-based software because they don't keep VoiceOver running.
I suggest adding
AXManualAccessibilityfor programmatically enabling it in Electron apps. The reason for a new attribute is thatAXEnhancedUserInterfaceis already reserved by VoiceOver.Adding this attribute will allow both Electron developers and 3rd party developers to enable and disable accessibility from their code by calling
accessibilitySetValue:forAttribute:on the application.It will be also possible to create a small utility app to switch accessibility in Electron-based apps until there's a native UI solution (like the accessibility settings page in Chrome).