Bug 1884547 - Skip Snap QA because of CAPCTHAs r=mboldan
[gecko.git] / accessible / mac / Platform.mm
blob9f9738ab36fe52f9941fc5ae94f4652e45c2ab04
1 /* clang-format off */
2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 /* clang-format on */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #import <Cocoa/Cocoa.h>
10 #import "MOXTextMarkerDelegate.h"
12 #include "Platform.h"
13 #include "RemoteAccessible.h"
14 #include "DocAccessibleParent.h"
15 #include "mozTableAccessible.h"
16 #include "mozTextAccessible.h"
17 #include "MOXWebAreaAccessible.h"
18 #include "nsAccUtils.h"
19 #include "TextRange.h"
21 #include "nsAppShell.h"
22 #include "nsCocoaUtils.h"
23 #include "mozilla/Telemetry.h"
25 // Available from 10.13 onwards; test availability at runtime before using
26 @interface NSWorkspace (AvailableSinceHighSierra)
27 @property(readonly) BOOL isVoiceOverEnabled;
28 @property(readonly) BOOL isSwitchControlEnabled;
29 @end
31 namespace mozilla {
32 namespace a11y {
34 // Mac a11y whitelisting
35 static bool sA11yShouldBeEnabled = false;
37 bool ShouldA11yBeEnabled() {
38   EPlatformDisabledState disabledState = PlatformDisabledState();
39   return (disabledState == ePlatformIsForceEnabled) ||
40          ((disabledState == ePlatformIsEnabled) && sA11yShouldBeEnabled);
43 void PlatformInit() {}
45 void PlatformShutdown() {}
47 void ProxyCreated(RemoteAccessible* aProxy) {
48   if (aProxy->Role() == roles::WHITESPACE) {
49     // We don't create a native object if we're child of a "flat" accessible;
50     // for example, on OS X buttons shouldn't have any children, because that
51     // makes the OS confused. We also don't create accessibles for <br>
52     // (whitespace) elements.
53     return;
54   }
56   // Pass in dummy state for now as retrieving proxy state requires IPC.
57   // Note that we can use RemoteAccessible::IsTable* functions here because they
58   // do not use IPC calls but that might change after bug 1210477.
59   Class type;
60   if (aProxy->IsTable()) {
61     type = [mozTableAccessible class];
62   } else if (aProxy->IsTableRow()) {
63     type = [mozTableRowAccessible class];
64   } else if (aProxy->IsTableCell()) {
65     type = [mozTableCellAccessible class];
66   } else if (aProxy->IsDoc()) {
67     type = [MOXWebAreaAccessible class];
68   } else {
69     type = GetTypeFromRole(aProxy->Role());
70   }
72   mozAccessible* mozWrapper = [[type alloc] initWithAccessible:aProxy];
73   aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
76 void ProxyDestroyed(RemoteAccessible* aProxy) {
77   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
78   [wrapper expire];
79   [wrapper release];
80   aProxy->SetWrapper(0);
82   if (aProxy->IsDoc()) {
83     [MOXTextMarkerDelegate destroyForDoc:aProxy];
84   }
87 void PlatformEvent(Accessible* aTarget, uint32_t aEventType) {
88   // Ignore event that we don't escape below, they aren't yet supported.
89   if (aEventType != nsIAccessibleEvent::EVENT_ALERT &&
90       aEventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
91       aEventType != nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE &&
92       aEventType != nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE &&
93       aEventType != nsIAccessibleEvent::EVENT_REORDER &&
94       aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED &&
95       aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED &&
96       aEventType != nsIAccessibleEvent::EVENT_NAME_CHANGE &&
97       aEventType != nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED) {
98     return;
99   }
101   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
102   if (wrapper) {
103     [wrapper handleAccessibleEvent:aEventType];
104   }
107 void PlatformStateChangeEvent(Accessible* aTarget, uint64_t aState,
108                               bool aEnabled) {
109   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
110   if (wrapper) {
111     [wrapper stateChanged:aState isEnabled:aEnabled];
112   }
115 void PlatformFocusEvent(Accessible* aTarget,
116                         const LayoutDeviceIntRect& aCaretRect) {
117   if (mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget)) {
118     [wrapper handleAccessibleEvent:nsIAccessibleEvent::EVENT_FOCUS];
119   }
122 void PlatformCaretMoveEvent(Accessible* aTarget, int32_t aOffset,
123                             bool aIsSelectionCollapsed, int32_t aGranularity,
124                             const LayoutDeviceIntRect& aCaretRect,
125                             bool aFromUser) {
126   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
127   MOXTextMarkerDelegate* delegate = [MOXTextMarkerDelegate
128       getOrCreateForDoc:nsAccUtils::DocumentFor(aTarget)];
129   [delegate setCaretOffset:aTarget at:aOffset moveGranularity:aGranularity];
130   if (aIsSelectionCollapsed) {
131     // If selection is collapsed, invalidate selection.
132     [delegate setSelectionFrom:aTarget at:aOffset to:aTarget at:aOffset];
133   }
135   if (wrapper) {
136     if (mozTextAccessible* textAcc =
137             static_cast<mozTextAccessible*>([wrapper moxEditableAncestor])) {
138       [textAcc
139           handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
140     } else {
141       [wrapper
142           handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
143     }
144   }
147 void PlatformTextChangeEvent(Accessible* aTarget, const nsAString& aStr,
148                              int32_t aStart, uint32_t aLen, bool aIsInsert,
149                              bool aFromUser) {
150   Accessible* acc = aTarget;
151   // If there is a text input ancestor, use it as the event source.
152   while (acc && GetTypeFromRole(acc->Role()) != [mozTextAccessible class]) {
153     acc = acc->Parent();
154   }
155   mozAccessible* wrapper = GetNativeFromGeckoAccessible(acc ? acc : aTarget);
156   [wrapper handleAccessibleTextChangeEvent:nsCocoaUtils::ToNSString(aStr)
157                                   inserted:aIsInsert
158                                inContainer:aTarget
159                                         at:aStart];
162 void PlatformShowHideEvent(Accessible*, Accessible*, bool, bool) {}
164 void PlatformSelectionEvent(Accessible* aTarget, Accessible* aWidget,
165                             uint32_t aEventType) {
166   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aWidget);
167   if (wrapper) {
168     [wrapper handleAccessibleEvent:aEventType];
169   }
172 void PlatformTextSelectionChangeEvent(Accessible* aTarget,
173                                       const nsTArray<TextRange>& aSelection) {
174   if (aSelection.Length()) {
175     MOXTextMarkerDelegate* delegate = [MOXTextMarkerDelegate
176         getOrCreateForDoc:nsAccUtils::DocumentFor(aTarget)];
177     // Cache the selection.
178     [delegate setSelectionFrom:aSelection[0].StartContainer()
179                             at:aSelection[0].StartOffset()
180                             to:aSelection[0].EndContainer()
181                             at:aSelection[0].EndOffset()];
182   }
184   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
185   if (wrapper) {
186     [wrapper
187         handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED];
188   }
191 void PlatformRoleChangedEvent(Accessible* aTarget, const a11y::role& aRole,
192                               uint8_t aRoleMapEntryIndex) {
193   if (mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget)) {
194     [wrapper handleRoleChanged:aRole];
195   }
198 }  // namespace a11y
199 }  // namespace mozilla
201 @interface GeckoNSApplication (a11y)
202 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
203 @end
205 @implementation GeckoNSApplication (a11y)
207 - (NSAccessibilityRole)accessibilityRole {
208   // For ATs that don't request `AXEnhancedUserInterface` we need to enable
209   // accessibility when a role is fetched. Not ideal, but this is needed
210   // for such services as Voice Control.
211   if (!mozilla::a11y::sA11yShouldBeEnabled) {
212     [self accessibilitySetValue:@YES forAttribute:@"AXEnhancedUserInterface"];
213   }
214   return [super accessibilityRole];
217 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
218   if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) {
219     mozilla::a11y::sA11yShouldBeEnabled = ([value intValue] == 1);
220     if (sA11yShouldBeEnabled) {
221       // If accessibility should be enabled, log the appropriate client
222       nsAutoString client;
223       if ([[NSWorkspace sharedWorkspace]
224               respondsToSelector:@selector(isVoiceOverEnabled)] &&
225           [[NSWorkspace sharedWorkspace] isVoiceOverEnabled]) {
226         client.Assign(u"VoiceOver"_ns);
227       } else if ([[NSWorkspace sharedWorkspace]
228                      respondsToSelector:@selector(isSwitchControlEnabled)] &&
229                  [[NSWorkspace sharedWorkspace] isSwitchControlEnabled]) {
230         client.Assign(u"SwitchControl"_ns);
231       } else {
232         // This is more complicated than the NSWorkspace queries above
233         // because (a) there is no "full keyboard access" query for NSWorkspace
234         // and (b) the [NSApplication fullKeyboardAccessEnabled] query checks
235         // the pre-Monterey version of full keyboard access, which is not what
236         // we're looking for here. For more info, see bug 1772375 comment 7.
237         Boolean exists;
238         int val = CFPreferencesGetAppIntegerValue(
239             CFSTR("FullKeyboardAccessEnabled"),
240             CFSTR("com.apple.Accessibility"), &exists);
241         if (exists && val == 1) {
242           client.Assign(u"FullKeyboardAccess"_ns);
243         } else {
244           val = CFPreferencesGetAppIntegerValue(
245               CFSTR("CommandAndControlEnabled"),
246               CFSTR("com.apple.Accessibility"), &exists);
247           if (exists && val == 1) {
248             client.Assign(u"VoiceControl"_ns);
249           } else {
250             client.Assign(u"Unknown"_ns);
251           }
252         }
253       }
255 #if defined(MOZ_TELEMETRY_REPORTING)
256       mozilla::Telemetry::ScalarSet(
257           mozilla::Telemetry::ScalarID::A11Y_INSTANTIATORS, client);
258 #endif  // defined(MOZ_TELEMETRY_REPORTING)
259       CrashReporter::RecordAnnotationNSCString(
260           CrashReporter::Annotation::AccessibilityClient,
261           NS_ConvertUTF16toUTF8(client));
262     }
263   }
265   return [super accessibilitySetValue:value forAttribute:attribute];
268 @end