From 96df3b08943517ad846d87e9b1b18aa5e5f879af Mon Sep 17 00:00:00 2001 From: Nathan LaPre Date: Mon, 9 Sep 2024 23:02:19 +0000 Subject: [PATCH] Bug 1794974: Part 1: Add improved client detection, automatic domain setting, r=Jamie This revision is a step towards ensuring we get the right domains for known accessibility instantiators. We want to anticipate the needs of known clients and make sure we have all the cache domains ready, especially for heavy-use clients like screen readers. The domains we'll need are a bit of a work in progress, but can be updated easily. For now, err on the side of caution. This revision also implements better client detection on macOS, so we can handle multiple clients. Differential Revision: https://phabricator.services.mozilla.com/D220035 --- accessible/android/Platform.cpp | 8 ++ accessible/atk/Platform.cpp | 4 + accessible/base/Platform.h | 6 + accessible/base/nsAccessibilityService.cpp | 9 ++ accessible/ios/Platform.mm | 4 + accessible/mac/Platform.mm | 139 +++++++++++++++------ accessible/windows/msaa/Compatibility.h | 8 ++ accessible/windows/msaa/Platform.cpp | 9 ++ 8 files changed, 151 insertions(+), 36 deletions(-) diff --git a/accessible/android/Platform.cpp b/accessible/android/Platform.cpp index c3435bff8f9c..15321ac06aea 100644 --- a/accessible/android/Platform.cpp +++ b/accessible/android/Platform.cpp @@ -232,3 +232,11 @@ bool a11y::LocalizeString(const nsAString& aToken, nsAString& aLocalized) { return !!str; } + +uint64_t a11y::GetCacheDomainsForKnownClients(uint64_t aCacheDomains) { + Unused << aCacheDomains; + + // XXX: Respond to clients such as TalkBack. For now, be safe and default to + // caching all domains. + return CacheDomain::All; +} diff --git a/accessible/atk/Platform.cpp b/accessible/atk/Platform.cpp index 19c63fb9af77..99fa809baf15 100644 --- a/accessible/atk/Platform.cpp +++ b/accessible/atk/Platform.cpp @@ -277,3 +277,7 @@ bool a11y::ShouldA11yBeEnabled() { return false; } + +uint64_t a11y::GetCacheDomainsForKnownClients(uint64_t aCacheDomains) { + return aCacheDomains; +} diff --git a/accessible/base/Platform.h b/accessible/base/Platform.h index 68529d176a1d..edd3a1281662 100644 --- a/accessible/base/Platform.h +++ b/accessible/base/Platform.h @@ -130,6 +130,12 @@ void PlatformRoleChangedEvent(Accessible* aTarget, const a11y::role& aRole, uint8_t aRoleMapEntryIndex); #endif +// Get the cache domains needed by any known clients interacting with Gecko. If +// any known clients are found, the return value is aCacheDomains bitwise OR'd +// with the required cache domains for those clients. Otherwise, the return +// value is aCacheDomains unaltered. +uint64_t GetCacheDomainsForKnownClients(uint64_t aCacheDomains); + } // namespace a11y } // namespace mozilla diff --git a/accessible/base/nsAccessibilityService.cpp b/accessible/base/nsAccessibilityService.cpp index 08e53f3182dd..42ff9657cb33 100644 --- a/accessible/base/nsAccessibilityService.cpp +++ b/accessible/base/nsAccessibilityService.cpp @@ -340,6 +340,15 @@ LocalAccessible* CreateMenupopupAccessible(Element* aElement, return new XULMenupopupAccessible(aElement, aContext->Document()); } +static uint64_t GetCacheDomainsForKnownClients(uint64_t aCacheDomains) { + // Only check clients in the parent process. + if (!XRE_IsParentProcess()) { + return aCacheDomains; + } + + return a11y::GetCacheDomainsForKnownClients(aCacheDomains); +} + //////////////////////////////////////////////////////////////////////////////// // LocalAccessible constructors diff --git a/accessible/ios/Platform.mm b/accessible/ios/Platform.mm index b4342ec5559c..93df6c99f484 100644 --- a/accessible/ios/Platform.mm +++ b/accessible/ios/Platform.mm @@ -53,5 +53,9 @@ void PlatformShowHideEvent(Accessible*, Accessible*, bool, bool) {} void PlatformSelectionEvent(Accessible*, Accessible*, uint32_t) {} +uint64_t GetCacheDomainsForKnownClients(uint64_t aCacheDomains) { + return aCacheDomains; +} + } // namespace a11y } // namespace mozilla diff --git a/accessible/mac/Platform.mm b/accessible/mac/Platform.mm index c9a5af37df8c..ac8efbfe42b8 100644 --- a/accessible/mac/Platform.mm +++ b/accessible/mac/Platform.mm @@ -21,6 +21,7 @@ #include "nsAppShell.h" #include "nsCocoaUtils.h" +#include "mozilla/EnumSet.h" #include "mozilla/Telemetry.h" // Available from 10.13 onwards; test availability at runtime before using @@ -200,6 +201,102 @@ void PlatformRoleChangedEvent(Accessible* aTarget, const a11y::role& aRole, } } +// This enum lists possible assistive technology clients. It's intended for use +// in an EnumSet since there can be multiple ATs active at once. +enum class Client : uint64_t { + Unknown, + VoiceOver, + SwitchControl, + FullKeyboardAccess, + VoiceControl +}; + +// Get the set of currently-active clients and the client to log. +// XXX: We should log all clients, but default to the first one encountered. +std::pair, Client> GetClients() { + EnumSet clients; + std::optional clientToLog; + auto AddClient = [&clients, &clientToLog](Client client) { + clients += client; + if (!clientToLog.has_value()) { + clientToLog = client; + } + }; + if ([[NSWorkspace sharedWorkspace] + respondsToSelector:@selector(isVoiceOverEnabled)] && + [[NSWorkspace sharedWorkspace] isVoiceOverEnabled]) { + AddClient(Client::VoiceOver); + } else if ([[NSWorkspace sharedWorkspace] + respondsToSelector:@selector(isSwitchControlEnabled)] && + [[NSWorkspace sharedWorkspace] isSwitchControlEnabled]) { + AddClient(Client::SwitchControl); + } else { + // This is more complicated than the NSWorkspace queries above + // because (a) there is no "full keyboard access" query for NSWorkspace + // and (b) the [NSApplication fullKeyboardAccessEnabled] query checks + // the pre-Monterey version of full keyboard access, which is not what + // we're looking for here. For more info, see bug 1772375 comment 7. + Boolean exists; + int val = CFPreferencesGetAppIntegerValue( + CFSTR("FullKeyboardAccessEnabled"), CFSTR("com.apple.Accessibility"), + &exists); + if (exists && val == 1) { + AddClient(Client::FullKeyboardAccess); + } else { + val = CFPreferencesGetAppIntegerValue(CFSTR("CommandAndControlEnabled"), + CFSTR("com.apple.Accessibility"), + &exists); + if (exists && val == 1) { + AddClient(Client::VoiceControl); + } else { + AddClient(Client::Unknown); + } + } + } + return std::make_pair(clients, clientToLog.value()); +} + +// Expects a single client, returns a string representation of that client. +constexpr const char* GetStringForClient(Client aClient) { + switch (aClient) { + case Client::Unknown: + return "Unknown"; + case Client::VoiceOver: + return "VoiceOver"; + case Client::SwitchControl: + return "SwitchControl"; + case Client::FullKeyboardAccess: + return "FullKeyboardAccess"; + case Client::VoiceControl: + return "VoiceControl"; + default: + break; + } + MOZ_ASSERT_UNREACHABLE("Unknown Client enum value!"); + return ""; +} + +uint64_t GetCacheDomainsForKnownClients(uint64_t aCacheDomains) { + auto [clients, _] = GetClients(); + // We expect VoiceOver will require all information we have. + if (clients.contains(Client::VoiceOver)) { + return CacheDomain::All; + } + if (clients.contains(Client::FullKeyboardAccess)) { + aCacheDomains |= CacheDomain::Bounds; + } + if (clients.contains(Client::SwitchControl)) { + // XXX: Find minimum set of domains required for SwitchControl. + // SwitchControl can give up if we don't furnish it certain information. + return CacheDomain::All; + } + if (clients.contains(Client::VoiceControl)) { + // XXX: Find minimum set of domains required for VoiceControl. + return CacheDomain::All; + } + return aCacheDomains; +} + } // namespace a11y } // namespace mozilla @@ -224,46 +321,16 @@ void PlatformRoleChangedEvent(Accessible* aTarget, const a11y::role& aRole, mozilla::a11y::sA11yShouldBeEnabled = ([value intValue] == 1); if (sA11yShouldBeEnabled) { // If accessibility should be enabled, log the appropriate client - nsAutoString client; - if ([[NSWorkspace sharedWorkspace] - respondsToSelector:@selector(isVoiceOverEnabled)] && - [[NSWorkspace sharedWorkspace] isVoiceOverEnabled]) { - client.Assign(u"VoiceOver"_ns); - } else if ([[NSWorkspace sharedWorkspace] - respondsToSelector:@selector(isSwitchControlEnabled)] && - [[NSWorkspace sharedWorkspace] isSwitchControlEnabled]) { - client.Assign(u"SwitchControl"_ns); - } else { - // This is more complicated than the NSWorkspace queries above - // because (a) there is no "full keyboard access" query for NSWorkspace - // and (b) the [NSApplication fullKeyboardAccessEnabled] query checks - // the pre-Monterey version of full keyboard access, which is not what - // we're looking for here. For more info, see bug 1772375 comment 7. - Boolean exists; - int val = CFPreferencesGetAppIntegerValue( - CFSTR("FullKeyboardAccessEnabled"), - CFSTR("com.apple.Accessibility"), &exists); - if (exists && val == 1) { - client.Assign(u"FullKeyboardAccess"_ns); - } else { - val = CFPreferencesGetAppIntegerValue( - CFSTR("CommandAndControlEnabled"), - CFSTR("com.apple.Accessibility"), &exists); - if (exists && val == 1) { - client.Assign(u"VoiceControl"_ns); - } else { - client.Assign(u"Unknown"_ns); - } - } - } + auto [_, clientToLog] = GetClients(); + const char* client = GetStringForClient(clientToLog); #if defined(MOZ_TELEMETRY_REPORTING) mozilla::Telemetry::ScalarSet( - mozilla::Telemetry::ScalarID::A11Y_INSTANTIATORS, client); + mozilla::Telemetry::ScalarID::A11Y_INSTANTIATORS, + NS_ConvertASCIItoUTF16(client)); #endif // defined(MOZ_TELEMETRY_REPORTING) - CrashReporter::RecordAnnotationNSCString( - CrashReporter::Annotation::AccessibilityClient, - NS_ConvertUTF16toUTF8(client)); + CrashReporter::RecordAnnotationCString( + CrashReporter::Annotation::AccessibilityClient, client); } } diff --git a/accessible/windows/msaa/Compatibility.h b/accessible/windows/msaa/Compatibility.h index 05d2e82400ff..8b1c30fdc52b 100644 --- a/accessible/windows/msaa/Compatibility.h +++ b/accessible/windows/msaa/Compatibility.h @@ -55,6 +55,14 @@ class Compatibility { */ static bool IsVisperoShared() { return !!(sConsumers & VISPEROSHARED); } + /* + * Returns true if the instantiator is a known screen reader. + */ + static bool IsKnownScreenReader() { + return IsJAWS() || IsDolphin() || IsVisperoShared() || + !!(sConsumers & NVDA); + } + /** * Return a string describing sConsumers suitable for about:support. * Exposed through nsIXULRuntime.accessibilityInstantiator. diff --git a/accessible/windows/msaa/Platform.cpp b/accessible/windows/msaa/Platform.cpp index 59a0021b8978..54cd19063ba0 100644 --- a/accessible/windows/msaa/Platform.cpp +++ b/accessible/windows/msaa/Platform.cpp @@ -271,3 +271,12 @@ bool a11y::GetInstantiator(nsIFile** aOutInstantiator) { return NS_SUCCEEDED(gInstantiator->Clone(aOutInstantiator)); } + +uint64_t a11y::GetCacheDomainsForKnownClients(uint64_t aCacheDomains) { + // If we're instantiating because of a screen reader, enable all cache + // domains. We expect that demanding ATs will need all information we have. + if (Compatibility::IsKnownScreenReader()) { + return CacheDomain::All; + } + return aCacheDomains; +}