Bug 1738926 Part 1: Check if sublayers need to be rebuilt. r=mstange
[gecko.git] / accessible / mac / Platform.mm
blob514e12705dce0926673903c5b4f5c6c4704a9a59
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 "MOXWebAreaAccessible.h"
18 #include "nsAppShell.h"
19 #include "mozilla/Telemetry.h"
21 // Available from 10.13 onwards; test availability at runtime before using
22 @interface NSWorkspace (AvailableSinceHighSierra)
23 @property(readonly) BOOL isVoiceOverEnabled;
24 @property(readonly) BOOL isSwitchControlEnabled;
25 @end
27 namespace mozilla {
28 namespace a11y {
30 // Mac a11y whitelisting
31 static bool sA11yShouldBeEnabled = false;
33 bool ShouldA11yBeEnabled() {
34   EPlatformDisabledState disabledState = PlatformDisabledState();
35   return (disabledState == ePlatformIsForceEnabled) ||
36          ((disabledState == ePlatformIsEnabled) && sA11yShouldBeEnabled);
39 void PlatformInit() {}
41 void PlatformShutdown() {}
43 void ProxyCreated(RemoteAccessible* aProxy) {
44   if (aProxy->Role() == roles::WHITESPACE) {
45     // We don't create a native object if we're child of a "flat" accessible;
46     // for example, on OS X buttons shouldn't have any children, because that
47     // makes the OS confused. We also don't create accessibles for <br>
48     // (whitespace) elements.
49     return;
50   }
52   // Pass in dummy state for now as retrieving proxy state requires IPC.
53   // Note that we can use RemoteAccessible::IsTable* functions here because they
54   // do not use IPC calls but that might change after bug 1210477.
55   Class type;
56   if (aProxy->IsTable()) {
57     type = [mozTableAccessible class];
58   } else if (aProxy->IsTableRow()) {
59     type = [mozTableRowAccessible class];
60   } else if (aProxy->IsTableCell()) {
61     type = [mozTableCellAccessible class];
62   } else if (aProxy->IsDoc()) {
63     type = [MOXWebAreaAccessible class];
64   } else {
65     type = GetTypeFromRole(aProxy->Role());
66   }
68   mozAccessible* mozWrapper = [[type alloc] initWithAccessible:aProxy];
69   aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
72 void ProxyDestroyed(RemoteAccessible* aProxy) {
73   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
74   [wrapper expire];
75   [wrapper release];
76   aProxy->SetWrapper(0);
78   if (aProxy->IsDoc()) {
79     [MOXTextMarkerDelegate destroyForDoc:aProxy];
80   }
83 void ProxyEvent(RemoteAccessible* aProxy, uint32_t aEventType) {
84   // Ignore event that we don't escape below, they aren't yet supported.
85   if (aEventType != nsIAccessibleEvent::EVENT_ALERT &&
86       aEventType != nsIAccessibleEvent::EVENT_FOCUS &&
87       aEventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
88       aEventType != nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE &&
89       aEventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
90       aEventType != nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE &&
91       aEventType != nsIAccessibleEvent::EVENT_REORDER &&
92       aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED &&
93       aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED &&
94       aEventType != nsIAccessibleEvent::EVENT_NAME_CHANGE &&
95       aEventType != nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED &&
96       aEventType != nsIAccessibleEvent::EVENT_TABLE_STYLING_CHANGED)
97     return;
99   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
100   if (wrapper) {
101     [wrapper handleAccessibleEvent:aEventType];
102   }
105 void ProxyStateChangeEvent(RemoteAccessible* aProxy, uint64_t aState,
106                            bool aEnabled) {
107   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
108   if (wrapper) {
109     [wrapper stateChanged:aState isEnabled:aEnabled];
110   }
113 void ProxyCaretMoveEvent(RemoteAccessible* aTarget, int32_t aOffset,
114                          bool aIsSelectionCollapsed) {
115   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
116   MOXTextMarkerDelegate* delegate =
117       [MOXTextMarkerDelegate getOrCreateForDoc:aTarget->Document()];
118   [delegate setCaretOffset:aTarget at:aOffset];
119   if (aIsSelectionCollapsed) {
120     // If selection is collapsed, invalidate selection.
121     [delegate setSelectionFrom:aTarget at:aOffset to:aTarget at:aOffset];
122   }
124   if (wrapper) {
125     if (mozTextAccessible* textAcc =
126             static_cast<mozTextAccessible*>([wrapper moxEditableAncestor])) {
127       [textAcc
128           handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
129     } else {
130       [wrapper
131           handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
132     }
133   }
136 void ProxyTextChangeEvent(RemoteAccessible* aTarget, const nsString& aStr,
137                           int32_t aStart, uint32_t aLen, bool aIsInsert,
138                           bool aFromUser) {
139   RemoteAccessible* acc = aTarget;
140   // If there is a text input ancestor, use it as the event source.
141   while (acc && GetTypeFromRole(acc->Role()) != [mozTextAccessible class]) {
142     acc = acc->RemoteParent();
143   }
144   mozAccessible* wrapper = GetNativeFromGeckoAccessible(acc ? acc : aTarget);
145   [wrapper handleAccessibleTextChangeEvent:nsCocoaUtils::ToNSString(aStr)
146                                   inserted:aIsInsert
147                                inContainer:aTarget
148                                         at:aStart];
151 void ProxyShowHideEvent(RemoteAccessible*, RemoteAccessible*, bool, bool) {}
153 void ProxySelectionEvent(RemoteAccessible* aTarget, RemoteAccessible* aWidget,
154                          uint32_t aEventType) {
155   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aWidget);
156   if (wrapper) {
157     [wrapper handleAccessibleEvent:aEventType];
158   }
161 void ProxyTextSelectionChangeEvent(RemoteAccessible* aTarget,
162                                    const nsTArray<TextRangeData>& aSelection) {
163   if (aSelection.Length()) {
164     MOXTextMarkerDelegate* delegate =
165         [MOXTextMarkerDelegate getOrCreateForDoc:aTarget->Document()];
166     DocAccessibleParent* doc = aTarget->Document();
167     RemoteAccessible* startContainer =
168         doc->GetAccessible(aSelection[0].StartID());
169     RemoteAccessible* endContainer = doc->GetAccessible(aSelection[0].EndID());
170     // Cache the selection.
171     [delegate setSelectionFrom:startContainer
172                             at:aSelection[0].StartOffset()
173                             to:endContainer
174                             at:aSelection[0].EndOffset()];
175   }
177   mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
178   if (wrapper) {
179     [wrapper
180         handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED];
181   }
184 void ProxyRoleChangedEvent(RemoteAccessible* aTarget, const a11y::role& aRole) {
185   if (mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget)) {
186     [wrapper handleRoleChanged:aRole];
187   }
190 }  // namespace a11y
191 }  // namespace mozilla
193 @interface GeckoNSApplication (a11y)
194 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
195 @end
197 @implementation GeckoNSApplication (a11y)
199 - (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
200   if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) {
201     mozilla::a11y::sA11yShouldBeEnabled = ([value intValue] == 1);
202     if (sA11yShouldBeEnabled) {
203       // If accessibility should be enabled, log the appropriate client
204       nsAutoString client;
205       if ([[NSWorkspace sharedWorkspace]
206               respondsToSelector:@selector(isVoiceOverEnabled)] &&
207           [[NSWorkspace sharedWorkspace] isVoiceOverEnabled]) {
208         client.Assign(u"VoiceOver"_ns);
209       } else if ([[NSWorkspace sharedWorkspace]
210                      respondsToSelector:@selector(isSwitchControlEnabled)] &&
211                  [[NSWorkspace sharedWorkspace] isSwitchControlEnabled]) {
212         client.Assign(u"SwitchControl"_ns);
213       } else {
214         client.Assign(u"Unknown"_ns);
215       }
217 #if defined(MOZ_TELEMETRY_REPORTING)
218       Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INSTANTIATORS, client);
219 #endif  // defined(MOZ_TELEMETRY_REPORTING)
220       CrashReporter::AnnotateCrashReport(
221           CrashReporter::Annotation::AccessibilityClient,
222           NS_ConvertUTF16toUTF8(client));
223     }
224   }
226   return [super accessibilitySetValue:value forAttribute:attribute];
229 @end