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