1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
|
/* clang-format off */
/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* clang-format on */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#import <Cocoa/Cocoa.h>
#import "MOXTextMarkerDelegate.h"
#include "Platform.h"
#include "ProxyAccessible.h"
#include "AccessibleOrProxy.h"
#include "DocAccessibleParent.h"
#include "mozTableAccessible.h"
#include "MOXWebAreaAccessible.h"
#include "nsAppShell.h"
#include "mozilla/Telemetry.h"
// Available from 10.13 onwards; test availability at runtime before using
@interface NSWorkspace (AvailableSinceHighSierra)
@property(readonly) BOOL isVoiceOverEnabled;
@end
namespace mozilla {
namespace a11y {
// Mac a11y whitelisting
static bool sA11yShouldBeEnabled = false;
bool ShouldA11yBeEnabled() {
EPlatformDisabledState disabledState = PlatformDisabledState();
return (disabledState == ePlatformIsForceEnabled) ||
((disabledState == ePlatformIsEnabled) && sA11yShouldBeEnabled);
}
void PlatformInit() {}
void PlatformShutdown() {}
void ProxyCreated(ProxyAccessible* aProxy, uint32_t) {
if (aProxy->Role() == roles::WHITESPACE) {
// We don't create a native object if we're child of a "flat" accessible;
// for example, on OS X buttons shouldn't have any children, because that
// makes the OS confused. We also don't create accessibles for <br>
// (whitespace) elements.
return;
}
// Pass in dummy state for now as retrieving proxy state requires IPC.
// Note that we can use ProxyAccessible::IsTable* functions here because they
// do not use IPC calls but that might change after bug 1210477.
Class type;
if (aProxy->IsTable()) {
type = [mozTableAccessible class];
} else if (aProxy->IsTableRow()) {
type = [mozTableRowAccessible class];
} else if (aProxy->IsTableCell()) {
type = [mozTableCellAccessible class];
} else if (aProxy->IsDoc()) {
type = [MOXWebAreaAccessible class];
} else {
type = GetTypeFromRole(aProxy->Role());
}
mozAccessible* mozWrapper = [[type alloc] initWithAccessible:aProxy];
aProxy->SetWrapper(reinterpret_cast<uintptr_t>(mozWrapper));
}
void ProxyDestroyed(ProxyAccessible* aProxy) {
mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
[wrapper expire];
[wrapper release];
aProxy->SetWrapper(0);
if (aProxy->IsDoc()) {
[MOXTextMarkerDelegate destroyForDoc:aProxy];
}
}
void ProxyEvent(ProxyAccessible* aProxy, uint32_t aEventType) {
// Ignore event that we don't escape below, they aren't yet supported.
if (aEventType != nsIAccessibleEvent::EVENT_FOCUS &&
aEventType != nsIAccessibleEvent::EVENT_VALUE_CHANGE &&
aEventType != nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE &&
aEventType != nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED &&
aEventType != nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE &&
aEventType != nsIAccessibleEvent::EVENT_REORDER &&
aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_ADDED &&
aEventType != nsIAccessibleEvent::EVENT_LIVE_REGION_REMOVED &&
aEventType != nsIAccessibleEvent::EVENT_NAME_CHANGE)
return;
mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
if (wrapper) {
[wrapper handleAccessibleEvent:aEventType];
}
}
void ProxyStateChangeEvent(ProxyAccessible* aProxy, uint64_t aState,
bool aEnabled) {
mozAccessible* wrapper = GetNativeFromGeckoAccessible(aProxy);
if (wrapper) {
[wrapper stateChanged:aState isEnabled:aEnabled];
}
}
void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset,
bool aIsSelectionCollapsed) {
mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
MOXTextMarkerDelegate* delegate =
[MOXTextMarkerDelegate getOrCreateForDoc:aTarget->Document()];
[delegate setCaretOffset:aTarget at:aOffset];
if (aIsSelectionCollapsed) {
// If selection is collapsed, invalidate selection.
[delegate setSelectionFrom:aTarget at:aOffset to:aTarget at:aOffset];
}
if (wrapper) {
if (mozTextAccessible* textAcc =
static_cast<mozTextAccessible*>([wrapper moxEditableAncestor])) {
[textAcc
handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
} else {
[wrapper
handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED];
}
}
}
void ProxyTextChangeEvent(ProxyAccessible* aTarget, const nsString& aStr,
int32_t aStart, uint32_t aLen, bool aIsInsert,
bool aFromUser) {
ProxyAccessible* acc = aTarget;
// If there is a text input ancestor, use it as the event source.
while (acc && GetTypeFromRole(acc->Role()) != [mozTextAccessible class]) {
acc = acc->Parent();
}
mozAccessible* wrapper = GetNativeFromGeckoAccessible(acc ? acc : aTarget);
[wrapper handleAccessibleTextChangeEvent:nsCocoaUtils::ToNSString(aStr)
inserted:aIsInsert
inContainer:aTarget
at:aStart];
}
void ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool) {}
void ProxySelectionEvent(ProxyAccessible* aTarget, ProxyAccessible* aWidget,
uint32_t aEventType) {
mozAccessible* wrapper = GetNativeFromGeckoAccessible(aWidget);
if (wrapper) {
[wrapper handleAccessibleEvent:aEventType];
}
}
void ProxyTextSelectionChangeEvent(ProxyAccessible* aTarget,
const nsTArray<TextRangeData>& aSelection) {
if (aSelection.Length()) {
MOXTextMarkerDelegate* delegate =
[MOXTextMarkerDelegate getOrCreateForDoc:aTarget->Document()];
DocAccessibleParent* doc = aTarget->Document();
ProxyAccessible* startContainer =
doc->GetAccessible(aSelection[0].StartID());
ProxyAccessible* endContainer = doc->GetAccessible(aSelection[0].EndID());
// Cache the selection.
[delegate setSelectionFrom:startContainer
at:aSelection[0].StartOffset()
to:endContainer
at:aSelection[0].EndOffset()];
}
mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget);
if (wrapper) {
[wrapper
handleAccessibleEvent:nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED];
}
}
void ProxyRoleChangedEvent(ProxyAccessible* aTarget, const a11y::role& aRole) {
if (mozAccessible* wrapper = GetNativeFromGeckoAccessible(aTarget)) {
[wrapper handleRoleChanged:aRole];
}
}
} // namespace a11y
} // namespace mozilla
@interface GeckoNSApplication (a11y)
- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
@end
@implementation GeckoNSApplication (a11y)
- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
if ([attribute isEqualToString:@"AXEnhancedUserInterface"]) {
mozilla::a11y::sA11yShouldBeEnabled = ([value intValue] == 1);
#if defined(MOZ_TELEMETRY_REPORTING)
if ([[NSWorkspace sharedWorkspace]
respondsToSelector:@selector(isVoiceOverEnabled)] &&
[[NSWorkspace sharedWorkspace] isVoiceOverEnabled]) {
Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INSTANTIATORS,
u"VoiceOver"_ns);
}
#endif // defined(MOZ_TELEMETRY_REPORTING)
}
return [super accessibilitySetValue:value forAttribute:attribute];
}
@end
|