Source code

Revision control

Other Tools

1
/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
2
* This Source Code Form is subject to the terms of the Mozilla Public
3
* License, v. 2.0. If a copy of the MPL was not distributed with this
4
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "SessionAccessibility.h"
7
#include "AndroidUiThread.h"
8
#include "DocAccessibleParent.h"
9
#include "nsThreadUtils.h"
10
#include "AccessibilityEvent.h"
11
#include "HyperTextAccessible.h"
12
#include "JavaBuiltins.h"
13
#include "RootAccessibleWrap.h"
14
#include "nsAccessibilityService.h"
15
#include "nsViewManager.h"
16
#include "nsIPersistentProperties2.h"
17
18
#include "mozilla/PresShell.h"
19
#include "mozilla/dom/BrowserParent.h"
20
#include "mozilla/a11y/DocAccessibleParent.h"
21
#include "mozilla/a11y/DocManager.h"
22
#include "mozilla/jni/GeckoBundleUtils.h"
23
24
#ifdef DEBUG
25
# include <android/log.h>
26
# define AALOG(args...) \
27
__android_log_print(ANDROID_LOG_INFO, "GeckoAccessibilityNative", ##args)
28
#else
29
# define AALOG(args...) \
30
do { \
31
} while (0)
32
#endif
33
34
template <>
35
const char nsWindow::NativePtr<mozilla::a11y::SessionAccessibility>::sName[] =
36
"SessionAccessibility";
37
38
using namespace mozilla::a11y;
39
40
class Settings final
41
: public mozilla::java::SessionAccessibility::Settings::Natives<Settings> {
42
public:
43
static void ToggleNativeAccessibility(bool aEnable) {
44
if (aEnable) {
45
GetOrCreateAccService();
46
} else {
47
MaybeShutdownAccService(nsAccessibilityService::ePlatformAPI);
48
}
49
}
50
};
51
52
void SessionAccessibility::SetAttached(bool aAttached,
53
already_AddRefed<Runnable> aRunnable) {
54
if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
55
uiThread->Dispatch(NS_NewRunnableFunction(
56
"SessionAccessibility::Attach",
57
[aAttached,
58
sa = java::SessionAccessibility::NativeProvider::GlobalRef(
59
mSessionAccessibility),
60
runnable = RefPtr<Runnable>(aRunnable)] {
61
sa->SetAttached(aAttached);
62
if (runnable) {
63
runnable->Run();
64
}
65
}));
66
}
67
}
68
69
void SessionAccessibility::Init() {
70
java::SessionAccessibility::NativeProvider::Natives<
71
SessionAccessibility>::Init();
72
Settings::Init();
73
}
74
75
mozilla::jni::Object::LocalRef SessionAccessibility::GetNodeInfo(int32_t aID) {
76
java::GeckoBundle::GlobalRef ret = nullptr;
77
RefPtr<SessionAccessibility> self(this);
78
nsAppShell::SyncRunEvent([this, self, aID, &ret] {
79
if (RootAccessibleWrap* rootAcc = GetRoot()) {
80
AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
81
if (acc) {
82
ret = acc->ToBundle();
83
} else {
84
AALOG("oops, nothing for %d", aID);
85
}
86
}
87
});
88
89
return mozilla::jni::Object::Ref::From(ret);
90
}
91
92
RootAccessibleWrap* SessionAccessibility::GetRoot() {
93
if (!mWindow) {
94
return nullptr;
95
}
96
97
return static_cast<RootAccessibleWrap*>(mWindow->GetRootAccessible());
98
}
99
100
void SessionAccessibility::SetText(int32_t aID, jni::String::Param aText) {
101
if (RootAccessibleWrap* rootAcc = GetRoot()) {
102
AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
103
if (!acc) {
104
return;
105
}
106
107
acc->SetTextContents(aText->ToString());
108
}
109
}
110
111
void SessionAccessibility::Click(int32_t aID) {
112
if (RootAccessibleWrap* rootAcc = GetRoot()) {
113
AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
114
if (!acc) {
115
return;
116
}
117
118
acc->DoAction(0);
119
}
120
}
121
122
SessionAccessibility* SessionAccessibility::GetInstanceFor(
123
ProxyAccessible* aAccessible) {
124
auto tab =
125
static_cast<dom::BrowserParent*>(aAccessible->Document()->Manager());
126
dom::Element* frame = tab->GetOwnerElement();
127
MOZ_ASSERT(frame);
128
if (!frame) {
129
return nullptr;
130
}
131
132
Accessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc());
133
return chromeDoc ? GetInstanceFor(chromeDoc) : nullptr;
134
}
135
136
SessionAccessibility* SessionAccessibility::GetInstanceFor(
137
Accessible* aAccessible) {
138
RootAccessible* rootAcc = aAccessible->RootAccessible();
139
nsViewManager* vm = rootAcc->PresShellPtr()->GetViewManager();
140
if (!vm) {
141
return nullptr;
142
}
143
144
nsCOMPtr<nsIWidget> rootWidget;
145
vm->GetRootWidget(getter_AddRefs(rootWidget));
146
// `rootWidget` can be one of several types. Here we make sure it is an
147
// android nsWindow.
148
if (RefPtr<nsWindow> window = nsWindow::From(rootWidget)) {
149
return window->GetSessionAccessibility();
150
}
151
152
return nullptr;
153
}
154
155
void SessionAccessibility::SendAccessibilityFocusedEvent(
156
AccessibleWrap* aAccessible) {
157
mSessionAccessibility->SendEvent(
158
java::sdk::AccessibilityEvent::TYPE_VIEW_ACCESSIBILITY_FOCUSED,
159
aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr);
160
aAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
161
}
162
163
void SessionAccessibility::SendHoverEnterEvent(AccessibleWrap* aAccessible) {
164
mSessionAccessibility->SendEvent(
165
java::sdk::AccessibilityEvent::TYPE_VIEW_HOVER_ENTER,
166
aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr);
167
}
168
169
void SessionAccessibility::SendFocusEvent(AccessibleWrap* aAccessible) {
170
// Suppress focus events from about:blank pages.
171
// This is important for tests.
172
if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
173
return;
174
}
175
176
mSessionAccessibility->SendEvent(
177
java::sdk::AccessibilityEvent::TYPE_VIEW_FOCUSED,
178
aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr);
179
}
180
181
void SessionAccessibility::SendScrollingEvent(AccessibleWrap* aAccessible,
182
int32_t aScrollX,
183
int32_t aScrollY,
184
int32_t aMaxScrollX,
185
int32_t aMaxScrollY) {
186
int32_t virtualViewId = aAccessible->VirtualViewID();
187
188
if (virtualViewId != AccessibleWrap::kNoID) {
189
// XXX: Support scrolling in subframes
190
return;
191
}
192
193
GECKOBUNDLE_START(eventInfo);
194
GECKOBUNDLE_PUT(eventInfo, "scrollX", java::sdk::Integer::ValueOf(aScrollX));
195
GECKOBUNDLE_PUT(eventInfo, "scrollY", java::sdk::Integer::ValueOf(aScrollY));
196
GECKOBUNDLE_PUT(eventInfo, "maxScrollX",
197
java::sdk::Integer::ValueOf(aMaxScrollX));
198
GECKOBUNDLE_PUT(eventInfo, "maxScrollY",
199
java::sdk::Integer::ValueOf(aMaxScrollY));
200
GECKOBUNDLE_FINISH(eventInfo);
201
202
mSessionAccessibility->SendEvent(
203
java::sdk::AccessibilityEvent::TYPE_VIEW_SCROLLED, virtualViewId,
204
aAccessible->AndroidClass(), eventInfo);
205
}
206
207
void SessionAccessibility::SendWindowContentChangedEvent() {
208
mSessionAccessibility->SendEvent(
209
java::sdk::AccessibilityEvent::TYPE_WINDOW_CONTENT_CHANGED,
210
AccessibleWrap::kNoID, java::SessionAccessibility::CLASSNAME_WEBVIEW,
211
nullptr);
212
}
213
214
void SessionAccessibility::SendWindowStateChangedEvent(
215
AccessibleWrap* aAccessible) {
216
// Suppress window state changed events from about:blank pages.
217
// This is important for tests.
218
if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
219
return;
220
}
221
222
mSessionAccessibility->SendEvent(
223
java::sdk::AccessibilityEvent::TYPE_WINDOW_STATE_CHANGED,
224
aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr);
225
}
226
227
void SessionAccessibility::SendTextSelectionChangedEvent(
228
AccessibleWrap* aAccessible, int32_t aCaretOffset) {
229
int32_t fromIndex = aCaretOffset;
230
int32_t startSel = -1;
231
int32_t endSel = -1;
232
if (aAccessible->GetSelectionBounds(&startSel, &endSel)) {
233
fromIndex = startSel == aCaretOffset ? endSel : startSel;
234
}
235
236
GECKOBUNDLE_START(eventInfo);
237
GECKOBUNDLE_PUT(eventInfo, "fromIndex",
238
java::sdk::Integer::ValueOf(fromIndex));
239
GECKOBUNDLE_PUT(eventInfo, "toIndex",
240
java::sdk::Integer::ValueOf(aCaretOffset));
241
GECKOBUNDLE_FINISH(eventInfo);
242
243
mSessionAccessibility->SendEvent(
244
java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_SELECTION_CHANGED,
245
aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo);
246
}
247
248
void SessionAccessibility::SendTextChangedEvent(AccessibleWrap* aAccessible,
249
const nsString& aStr,
250
int32_t aStart, uint32_t aLen,
251
bool aIsInsert,
252
bool aFromUser) {
253
if (!aFromUser) {
254
// Only dispatch text change events from users, for now.
255
return;
256
}
257
258
nsAutoString text;
259
aAccessible->GetTextContents(text);
260
nsAutoString beforeText(text);
261
if (aIsInsert) {
262
beforeText.Cut(aStart, aLen);
263
} else {
264
beforeText.Insert(aStr, aStart);
265
}
266
267
GECKOBUNDLE_START(eventInfo);
268
GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
269
GECKOBUNDLE_PUT(eventInfo, "beforeText", jni::StringParam(beforeText));
270
GECKOBUNDLE_PUT(eventInfo, "addedCount",
271
java::sdk::Integer::ValueOf(aIsInsert ? aLen : 0));
272
GECKOBUNDLE_PUT(eventInfo, "removedCount",
273
java::sdk::Integer::ValueOf(aIsInsert ? 0 : aLen));
274
GECKOBUNDLE_FINISH(eventInfo);
275
276
mSessionAccessibility->SendEvent(
277
java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_CHANGED,
278
aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo);
279
}
280
281
void SessionAccessibility::SendTextTraversedEvent(AccessibleWrap* aAccessible,
282
int32_t aStartOffset,
283
int32_t aEndOffset) {
284
nsAutoString text;
285
aAccessible->GetTextContents(text);
286
287
GECKOBUNDLE_START(eventInfo);
288
GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
289
GECKOBUNDLE_PUT(eventInfo, "fromIndex",
290
java::sdk::Integer::ValueOf(aStartOffset));
291
GECKOBUNDLE_PUT(eventInfo, "toIndex",
292
java::sdk::Integer::ValueOf(aEndOffset));
293
GECKOBUNDLE_FINISH(eventInfo);
294
295
mSessionAccessibility->SendEvent(
296
java::sdk::AccessibilityEvent::
297
TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
298
aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo);
299
}
300
301
void SessionAccessibility::SendClickedEvent(AccessibleWrap* aAccessible,
302
bool aChecked) {
303
GECKOBUNDLE_START(eventInfo);
304
// Boolean::FALSE/TRUE gets clobbered by a macro, so ugh.
305
GECKOBUNDLE_PUT(eventInfo, "checked",
306
java::sdk::Integer::ValueOf(aChecked ? 1 : 0));
307
GECKOBUNDLE_FINISH(eventInfo);
308
309
mSessionAccessibility->SendEvent(
310
java::sdk::AccessibilityEvent::TYPE_VIEW_CLICKED,
311
aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo);
312
}
313
314
void SessionAccessibility::SendSelectedEvent(AccessibleWrap* aAccessible,
315
bool aSelected) {
316
GECKOBUNDLE_START(eventInfo);
317
// Boolean::FALSE/TRUE gets clobbered by a macro, so ugh.
318
GECKOBUNDLE_PUT(eventInfo, "selected",
319
java::sdk::Integer::ValueOf(aSelected ? 1 : 0));
320
GECKOBUNDLE_FINISH(eventInfo);
321
322
mSessionAccessibility->SendEvent(
323
java::sdk::AccessibilityEvent::TYPE_VIEW_SELECTED,
324
aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo);
325
}
326
327
void SessionAccessibility::SendAnnouncementEvent(AccessibleWrap* aAccessible,
328
const nsString& aAnnouncement,
329
uint16_t aPriority) {
330
GECKOBUNDLE_START(eventInfo);
331
GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(aAnnouncement));
332
GECKOBUNDLE_FINISH(eventInfo);
333
334
// Announcements should have the root as their source, so we ignore the
335
// accessible of the event.
336
mSessionAccessibility->SendEvent(
337
java::sdk::AccessibilityEvent::TYPE_ANNOUNCEMENT, AccessibleWrap::kNoID,
338
java::SessionAccessibility::CLASSNAME_WEBVIEW, eventInfo);
339
}
340
341
void SessionAccessibility::ReplaceViewportCache(
342
const nsTArray<AccessibleWrap*>& aAccessibles,
343
const nsTArray<BatchData>& aData) {
344
auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length());
345
for (size_t i = 0; i < aAccessibles.Length(); i++) {
346
AccessibleWrap* acc = aAccessibles.ElementAt(i);
347
if (!acc) {
348
MOZ_ASSERT_UNREACHABLE("Updated accessible is gone.");
349
continue;
350
}
351
352
if (aData.Length() == aAccessibles.Length()) {
353
const BatchData& data = aData.ElementAt(i);
354
auto bundle = acc->ToBundle(
355
data.State(), data.Bounds(), data.ActionCount(), data.Name(),
356
data.TextValue(), data.DOMNodeID(), data.Description());
357
infos->SetElement(i, bundle);
358
} else {
359
infos->SetElement(i, acc->ToBundle(true));
360
}
361
}
362
363
mSessionAccessibility->ReplaceViewportCache(infos);
364
SendWindowContentChangedEvent();
365
}
366
367
void SessionAccessibility::ReplaceFocusPathCache(
368
const nsTArray<AccessibleWrap*>& aAccessibles,
369
const nsTArray<BatchData>& aData) {
370
auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length());
371
for (size_t i = 0; i < aAccessibles.Length(); i++) {
372
AccessibleWrap* acc = aAccessibles.ElementAt(i);
373
if (!acc) {
374
MOZ_ASSERT_UNREACHABLE("Updated accessible is gone.");
375
continue;
376
}
377
378
if (aData.Length() == aAccessibles.Length()) {
379
const BatchData& data = aData.ElementAt(i);
380
nsCOMPtr<nsIPersistentProperties> props =
381
AccessibleWrap::AttributeArrayToProperties(data.Attributes());
382
auto bundle =
383
acc->ToBundle(data.State(), data.Bounds(), data.ActionCount(),
384
data.Name(), data.TextValue(), data.DOMNodeID(),
385
data.Description(), data.CurValue(), data.MinValue(),
386
data.MaxValue(), data.Step(), props);
387
infos->SetElement(i, bundle);
388
} else {
389
infos->SetElement(i, acc->ToBundle());
390
}
391
}
392
393
mSessionAccessibility->ReplaceFocusPathCache(infos);
394
}
395
396
void SessionAccessibility::UpdateCachedBounds(
397
const nsTArray<AccessibleWrap*>& aAccessibles,
398
const nsTArray<BatchData>& aData) {
399
auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length());
400
for (size_t i = 0; i < aAccessibles.Length(); i++) {
401
AccessibleWrap* acc = aAccessibles.ElementAt(i);
402
if (!acc) {
403
MOZ_ASSERT_UNREACHABLE("Updated accessible is gone.");
404
continue;
405
}
406
407
if (aData.Length() == aAccessibles.Length()) {
408
const BatchData& data = aData.ElementAt(i);
409
auto bundle = acc->ToBundle(
410
data.State(), data.Bounds(), data.ActionCount(), data.Name(),
411
data.TextValue(), data.DOMNodeID(), data.Description());
412
infos->SetElement(i, bundle);
413
} else {
414
infos->SetElement(i, acc->ToBundle(true));
415
}
416
}
417
418
mSessionAccessibility->UpdateCachedBounds(infos);
419
SendWindowContentChangedEvent();
420
}