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; +}