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