Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
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 "AccessibleWrap.h"
7
8
#include "Accessible-inl.h"
9
#include "AccEvent.h"
10
#include "AndroidInputType.h"
11
#include "DocAccessibleWrap.h"
12
#include "IDSet.h"
13
#include "JavaBuiltins.h"
14
#include "SessionAccessibility.h"
15
#include "TraversalRule.h"
16
#include "Pivot.h"
17
#include "nsAccessibilityService.h"
18
#include "nsEventShell.h"
19
#include "nsPersistentProperties.h"
20
#include "nsIAccessibleAnnouncementEvent.h"
21
#include "nsIStringBundle.h"
22
#include "nsAccUtils.h"
23
#include "nsTextEquivUtils.h"
24
#include "RootAccessible.h"
25
26
#include "mozilla/a11y/PDocAccessibleChild.h"
27
#include "mozilla/jni/GeckoBundleUtils.h"
28
29
#define ROLE_STRINGS_URL "chrome://global/locale/AccessFu.properties"
30
31
using namespace mozilla::a11y;
32
33
// IDs should be a positive 32bit integer.
34
IDSet sIDSet(31UL);
35
36
//-----------------------------------------------------
37
// construction
38
//-----------------------------------------------------
39
AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
40
: Accessible(aContent, aDoc) {
41
if (aDoc) {
42
mID = AcquireID();
43
DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc);
44
doc->AddID(mID, this);
45
}
46
}
47
48
//-----------------------------------------------------
49
// destruction
50
//-----------------------------------------------------
51
AccessibleWrap::~AccessibleWrap() {}
52
53
nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
54
auto accessible = static_cast<AccessibleWrap*>(aEvent->GetAccessible());
55
NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
56
DocAccessibleWrap* doc =
57
static_cast<DocAccessibleWrap*>(accessible->Document());
58
if (doc) {
59
switch (aEvent->GetEventType()) {
60
case nsIAccessibleEvent::EVENT_FOCUS: {
61
if (DocAccessibleWrap* topContentDoc =
62
doc->GetTopLevelContentDoc(accessible)) {
63
topContentDoc->CacheFocusPath(accessible);
64
}
65
break;
66
}
67
case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
68
AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
69
auto newPosition =
70
static_cast<AccessibleWrap*>(vcEvent->NewAccessible());
71
if (newPosition) {
72
if (DocAccessibleWrap* topContentDoc =
73
doc->GetTopLevelContentDoc(accessible)) {
74
topContentDoc->CacheFocusPath(newPosition);
75
}
76
}
77
break;
78
}
79
case nsIAccessibleEvent::EVENT_SHOW:
80
case nsIAccessibleEvent::EVENT_HIDE: {
81
if (DocAccessibleWrap* topContentDoc =
82
doc->GetTopLevelContentDoc(accessible)) {
83
topContentDoc->CacheViewport();
84
}
85
break;
86
}
87
case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
88
if (accessible != aEvent->Document() && !aEvent->IsFromUserInput()) {
89
AccCaretMoveEvent* caretEvent = downcast_accEvent(aEvent);
90
if (IsHyperText()) {
91
DOMPoint point =
92
AsHyperText()->OffsetToDOMPoint(caretEvent->GetCaretOffset());
93
if (Accessible* newPos =
94
doc->GetAccessibleOrContainer(point.node)) {
95
static_cast<AccessibleWrap*>(newPos)->Pivot(
96
java::SessionAccessibility::HTML_GRANULARITY_DEFAULT, true,
97
true);
98
}
99
}
100
}
101
break;
102
}
103
case nsIAccessibleEvent::EVENT_SCROLLING_START: {
104
accessible->Pivot(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT,
105
true, true);
106
break;
107
}
108
default:
109
break;
110
}
111
}
112
113
nsresult rv = Accessible::HandleAccEvent(aEvent);
114
NS_ENSURE_SUCCESS(rv, rv);
115
116
accessible->HandleLiveRegionEvent(aEvent);
117
118
if (IPCAccessibilityActive()) {
119
return NS_OK;
120
}
121
122
// The accessible can become defunct if we have an xpcom event listener
123
// which decides it would be fun to change the DOM and flush layout.
124
if (accessible->IsDefunct() || !accessible->IsBoundToParent()) {
125
return NS_OK;
126
}
127
128
if (doc) {
129
if (!nsCoreUtils::IsContentDocument(doc->DocumentNode())) {
130
return NS_OK;
131
}
132
}
133
134
RefPtr<SessionAccessibility> sessionAcc =
135
SessionAccessibility::GetInstanceFor(accessible);
136
if (!sessionAcc) {
137
return NS_OK;
138
}
139
140
switch (aEvent->GetEventType()) {
141
case nsIAccessibleEvent::EVENT_FOCUS:
142
sessionAcc->SendFocusEvent(accessible);
143
break;
144
case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
145
AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
146
if (!vcEvent->IsFromUserInput()) {
147
break;
148
}
149
150
RefPtr<AccessibleWrap> newPosition =
151
static_cast<AccessibleWrap*>(vcEvent->NewAccessible());
152
if (sessionAcc && newPosition) {
153
if (vcEvent->Reason() == nsIAccessiblePivot::REASON_POINT) {
154
sessionAcc->SendHoverEnterEvent(newPosition);
155
} else {
156
sessionAcc->SendAccessibilityFocusedEvent(newPosition);
157
}
158
159
if (vcEvent->BoundaryType() != nsIAccessiblePivot::NO_BOUNDARY) {
160
sessionAcc->SendTextTraversedEvent(
161
newPosition, vcEvent->NewStartOffset(), vcEvent->NewEndOffset());
162
}
163
}
164
break;
165
}
166
case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
167
AccCaretMoveEvent* event = downcast_accEvent(aEvent);
168
sessionAcc->SendTextSelectionChangedEvent(accessible,
169
event->GetCaretOffset());
170
break;
171
}
172
case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
173
case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
174
AccTextChangeEvent* event = downcast_accEvent(aEvent);
175
sessionAcc->SendTextChangedEvent(
176
accessible, event->ModifiedText(), event->GetStartOffset(),
177
event->GetLength(), event->IsTextInserted(),
178
event->IsFromUserInput());
179
break;
180
}
181
case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
182
AccStateChangeEvent* event = downcast_accEvent(aEvent);
183
auto state = event->GetState();
184
if (state & states::CHECKED) {
185
sessionAcc->SendClickedEvent(accessible, event->IsStateEnabled());
186
}
187
188
if (state & states::SELECTED) {
189
sessionAcc->SendSelectedEvent(accessible, event->IsStateEnabled());
190
}
191
192
if (state & states::BUSY) {
193
sessionAcc->SendWindowStateChangedEvent(accessible);
194
}
195
break;
196
}
197
case nsIAccessibleEvent::EVENT_SCROLLING: {
198
AccScrollingEvent* event = downcast_accEvent(aEvent);
199
sessionAcc->SendScrollingEvent(accessible, event->ScrollX(),
200
event->ScrollY(), event->MaxScrollX(),
201
event->MaxScrollY());
202
break;
203
}
204
case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: {
205
AccAnnouncementEvent* event = downcast_accEvent(aEvent);
206
sessionAcc->SendAnnouncementEvent(accessible, event->Announcement(),
207
event->Priority());
208
break;
209
}
210
default:
211
break;
212
}
213
214
return NS_OK;
215
}
216
217
void AccessibleWrap::Shutdown() {
218
if (mDoc) {
219
if (mID > 0) {
220
if (auto doc = static_cast<DocAccessibleWrap*>(mDoc.get())) {
221
doc->RemoveID(mID);
222
}
223
ReleaseID(mID);
224
mID = 0;
225
}
226
}
227
228
Accessible::Shutdown();
229
}
230
231
bool AccessibleWrap::DoAction(uint8_t aIndex) const {
232
if (ActionCount()) {
233
return Accessible::DoAction(aIndex);
234
}
235
236
if (mContent) {
237
// We still simulate a click on an accessible even if there is no
238
// known actions. For the sake of bad markup.
239
DoCommand();
240
return true;
241
}
242
243
return false;
244
}
245
246
int32_t AccessibleWrap::AcquireID() { return sIDSet.GetID(); }
247
248
void AccessibleWrap::ReleaseID(int32_t aID) { sIDSet.ReleaseID(aID); }
249
250
void AccessibleWrap::SetTextContents(const nsAString& aText) {
251
if (IsHyperText()) {
252
AsHyperText()->ReplaceText(aText);
253
}
254
}
255
256
void AccessibleWrap::GetTextContents(nsAString& aText) {
257
// For now it is a simple wrapper for getting entire range of TextSubstring.
258
// In the future this may be smarter and retrieve a flattened string.
259
if (IsHyperText()) {
260
AsHyperText()->TextSubstring(0, -1, aText);
261
}
262
}
263
264
bool AccessibleWrap::GetSelectionBounds(int32_t* aStartOffset,
265
int32_t* aEndOffset) {
266
if (IsHyperText()) {
267
return AsHyperText()->SelectionBoundsAt(0, aStartOffset, aEndOffset);
268
}
269
270
return false;
271
}
272
273
void AccessibleWrap::Pivot(int32_t aGranularity, bool aForward,
274
bool aInclusive) {
275
a11y::Pivot pivot(RootAccessible());
276
TraversalRule rule(aGranularity);
277
Accessible* result = aForward ? pivot.Next(this, rule, aInclusive)
278
: pivot.Prev(this, rule, aInclusive);
279
if (result && (result != this || aInclusive)) {
280
PivotMoveReason reason = aForward ? nsIAccessiblePivot::REASON_NEXT
281
: nsIAccessiblePivot::REASON_PREV;
282
RefPtr<AccEvent> event = new AccVCChangeEvent(
283
result->Document(), this, -1, -1, result, -1, -1, reason,
284
nsIAccessiblePivot::NO_BOUNDARY, eFromUserInput);
285
nsEventShell::FireEvent(event);
286
}
287
}
288
289
void AccessibleWrap::ExploreByTouch(float aX, float aY) {
290
a11y::Pivot pivot(RootAccessible());
291
TraversalRule rule;
292
293
Accessible* result = pivot.AtPoint(aX, aY, rule);
294
295
if (result && result != this) {
296
RefPtr<AccEvent> event =
297
new AccVCChangeEvent(result->Document(), this, -1, -1, result, -1, -1,
298
nsIAccessiblePivot::REASON_POINT,
299
nsIAccessiblePivot::NO_BOUNDARY, eFromUserInput);
300
nsEventShell::FireEvent(event);
301
}
302
}
303
304
void AccessibleWrap::NavigateText(int32_t aGranularity, int32_t aStartOffset,
305
int32_t aEndOffset, bool aForward,
306
bool aSelect) {
307
a11y::Pivot pivot(RootAccessible());
308
309
HyperTextAccessible* editable =
310
(State() & states::EDITABLE) != 0 ? AsHyperText() : nullptr;
311
312
int32_t start = aStartOffset, end = aEndOffset;
313
// If the accessible is an editable, set the virtual cursor position
314
// to its caret offset. Otherwise use the document's virtual cursor
315
// position as a starting offset.
316
if (editable) {
317
start = end = editable->CaretOffset();
318
}
319
320
uint16_t pivotGranularity = nsIAccessiblePivot::LINE_BOUNDARY;
321
switch (aGranularity) {
322
case 1: // MOVEMENT_GRANULARITY_CHARACTER
323
pivotGranularity = nsIAccessiblePivot::CHAR_BOUNDARY;
324
break;
325
case 2: // MOVEMENT_GRANULARITY_WORD
326
pivotGranularity = nsIAccessiblePivot::WORD_BOUNDARY;
327
break;
328
default:
329
break;
330
}
331
332
int32_t newOffset;
333
Accessible* newAnchor = nullptr;
334
if (aForward) {
335
newAnchor = pivot.NextText(this, &start, &end, pivotGranularity);
336
newOffset = end;
337
} else {
338
newAnchor = pivot.PrevText(this, &start, &end, pivotGranularity);
339
newOffset = start;
340
}
341
342
if (newAnchor && (start != aStartOffset || end != aEndOffset)) {
343
RefPtr<AccEvent> event = new AccVCChangeEvent(
344
newAnchor->Document(), this, aStartOffset, aEndOffset, newAnchor, start,
345
end, nsIAccessiblePivot::REASON_NONE, pivotGranularity, eFromUserInput);
346
nsEventShell::FireEvent(event);
347
}
348
349
// If we are in an editable, move the caret to the new virtual cursor
350
// offset.
351
if (editable) {
352
if (aSelect) {
353
int32_t anchor = editable->CaretOffset();
354
if (editable->SelectionCount()) {
355
int32_t startSel, endSel;
356
GetSelectionOrCaret(&startSel, &endSel);
357
anchor = startSel == anchor ? endSel : startSel;
358
}
359
editable->SetSelectionBoundsAt(0, anchor, newOffset);
360
} else {
361
editable->SetCaretOffset(newOffset);
362
}
363
}
364
}
365
366
void AccessibleWrap::SetSelection(int32_t aStart, int32_t aEnd) {
367
if (HyperTextAccessible* textAcc = AsHyperText()) {
368
if (aStart == aEnd) {
369
textAcc->SetCaretOffset(aStart);
370
} else {
371
textAcc->SetSelectionBoundsAt(0, aStart, aEnd);
372
}
373
}
374
}
375
376
void AccessibleWrap::Cut() {
377
if ((State() & states::EDITABLE) == 0) {
378
return;
379
}
380
381
if (HyperTextAccessible* textAcc = AsHyperText()) {
382
int32_t startSel, endSel;
383
GetSelectionOrCaret(&startSel, &endSel);
384
textAcc->CutText(startSel, endSel);
385
}
386
}
387
388
void AccessibleWrap::Copy() {
389
if (HyperTextAccessible* textAcc = AsHyperText()) {
390
int32_t startSel, endSel;
391
GetSelectionOrCaret(&startSel, &endSel);
392
textAcc->CopyText(startSel, endSel);
393
}
394
}
395
396
void AccessibleWrap::Paste() {
397
if ((State() & states::EDITABLE) == 0) {
398
return;
399
}
400
401
if (IsHyperText()) {
402
RefPtr<HyperTextAccessible> textAcc = AsHyperText();
403
int32_t startSel, endSel;
404
GetSelectionOrCaret(&startSel, &endSel);
405
if (startSel != endSel) {
406
textAcc->DeleteText(startSel, endSel);
407
}
408
textAcc->PasteText(startSel);
409
}
410
}
411
412
void AccessibleWrap::GetSelectionOrCaret(int32_t* aStartOffset,
413
int32_t* aEndOffset) {
414
*aStartOffset = *aEndOffset = -1;
415
if (HyperTextAccessible* textAcc = AsHyperText()) {
416
if (!textAcc->SelectionBoundsAt(0, aStartOffset, aEndOffset)) {
417
*aStartOffset = *aEndOffset = textAcc->CaretOffset();
418
}
419
}
420
}
421
422
uint32_t AccessibleWrap::GetFlags(role aRole, uint64_t aState,
423
uint8_t aActionCount) {
424
uint32_t flags = 0;
425
if (aState & states::CHECKABLE) {
426
flags |= java::SessionAccessibility::FLAG_CHECKABLE;
427
}
428
429
if (aState & states::CHECKED) {
430
flags |= java::SessionAccessibility::FLAG_CHECKED;
431
}
432
433
if (aState & states::INVALID) {
434
flags |= java::SessionAccessibility::FLAG_CONTENT_INVALID;
435
}
436
437
if (aState & states::EDITABLE) {
438
flags |= java::SessionAccessibility::FLAG_EDITABLE;
439
}
440
441
if (aActionCount && aRole != roles::TEXT_LEAF) {
442
flags |= java::SessionAccessibility::FLAG_CLICKABLE;
443
}
444
445
if (aState & states::ENABLED) {
446
flags |= java::SessionAccessibility::FLAG_ENABLED;
447
}
448
449
if (aState & states::FOCUSABLE) {
450
flags |= java::SessionAccessibility::FLAG_FOCUSABLE;
451
}
452
453
if (aState & states::FOCUSED) {
454
flags |= java::SessionAccessibility::FLAG_FOCUSED;
455
}
456
457
if (aState & states::MULTI_LINE) {
458
flags |= java::SessionAccessibility::FLAG_MULTI_LINE;
459
}
460
461
if (aState & states::SELECTABLE) {
462
flags |= java::SessionAccessibility::FLAG_SELECTABLE;
463
}
464
465
if (aState & states::SELECTED) {
466
flags |= java::SessionAccessibility::FLAG_SELECTED;
467
}
468
469
if ((aState & (states::INVISIBLE | states::OFFSCREEN)) == 0) {
470
flags |= java::SessionAccessibility::FLAG_VISIBLE_TO_USER;
471
}
472
473
if (aRole == roles::PASSWORD_TEXT) {
474
flags |= java::SessionAccessibility::FLAG_PASSWORD;
475
}
476
477
return flags;
478
}
479
480
void AccessibleWrap::GetRoleDescription(role aRole,
481
nsIPersistentProperties* aAttributes,
482
nsAString& aGeckoRole,
483
nsAString& aRoleDescription) {
484
nsresult rv = NS_OK;
485
486
nsCOMPtr<nsIStringBundleService> sbs =
487
do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
488
if (NS_FAILED(rv)) {
489
NS_WARNING("Failed to get string bundle service");
490
return;
491
}
492
493
nsCOMPtr<nsIStringBundle> bundle;
494
rv = sbs->CreateBundle(ROLE_STRINGS_URL, getter_AddRefs(bundle));
495
if (NS_FAILED(rv)) {
496
NS_WARNING("Failed to get string bundle");
497
return;
498
}
499
500
if (aRole == roles::HEADING && aAttributes) {
501
// The heading level is an attribute, so we need that.
502
AutoTArray<nsString, 1> formatString;
503
rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("level"),
504
*formatString.AppendElement());
505
if (NS_SUCCEEDED(rv)) {
506
rv = bundle->FormatStringFromName("headingLevel", formatString,
507
aRoleDescription);
508
if (NS_SUCCEEDED(rv)) {
509
return;
510
}
511
}
512
}
513
514
GetAccService()->GetStringRole(aRole, aGeckoRole);
515
rv = bundle->GetStringFromName(NS_ConvertUTF16toUTF8(aGeckoRole).get(),
516
aRoleDescription);
517
if (NS_FAILED(rv)) {
518
aRoleDescription.AssignLiteral("");
519
}
520
}
521
522
already_AddRefed<nsIPersistentProperties>
523
AccessibleWrap::AttributeArrayToProperties(
524
const nsTArray<Attribute>& aAttributes) {
525
RefPtr<nsPersistentProperties> props = new nsPersistentProperties();
526
nsAutoString unused;
527
528
for (size_t i = 0; i < aAttributes.Length(); i++) {
529
props->SetStringProperty(aAttributes.ElementAt(i).Name(),
530
aAttributes.ElementAt(i).Value(), unused);
531
}
532
533
return props.forget();
534
}
535
536
int32_t AccessibleWrap::GetAndroidClass(role aRole) {
537
#define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, \
538
androidClass, nameRule) \
539
case roles::geckoRole: \
540
return androidClass;
541
542
switch (aRole) {
543
#include "RoleMap.h"
544
default:
545
return java::SessionAccessibility::CLASSNAME_VIEW;
546
}
547
548
#undef ROLE
549
}
550
551
int32_t AccessibleWrap::GetInputType(const nsString& aInputTypeAttr) {
552
if (aInputTypeAttr.EqualsIgnoreCase("email")) {
553
return java::sdk::InputType::TYPE_CLASS_TEXT |
554
java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
555
}
556
557
if (aInputTypeAttr.EqualsIgnoreCase("number")) {
558
return java::sdk::InputType::TYPE_CLASS_NUMBER;
559
}
560
561
if (aInputTypeAttr.EqualsIgnoreCase("password")) {
562
return java::sdk::InputType::TYPE_CLASS_TEXT |
563
java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_PASSWORD;
564
}
565
566
if (aInputTypeAttr.EqualsIgnoreCase("tel")) {
567
return java::sdk::InputType::TYPE_CLASS_PHONE;
568
}
569
570
if (aInputTypeAttr.EqualsIgnoreCase("text")) {
571
return java::sdk::InputType::TYPE_CLASS_TEXT |
572
java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
573
}
574
575
if (aInputTypeAttr.EqualsIgnoreCase("url")) {
576
return java::sdk::InputType::TYPE_CLASS_TEXT |
577
java::sdk::InputType::TYPE_TEXT_VARIATION_URI;
578
}
579
580
return 0;
581
}
582
583
void AccessibleWrap::WrapperDOMNodeID(nsString& aDOMNodeID) {
584
if (mContent) {
585
nsAtom* id = mContent->GetID();
586
if (id) {
587
id->ToString(aDOMNodeID);
588
}
589
}
590
}
591
592
bool AccessibleWrap::WrapperRangeInfo(double* aCurVal, double* aMinVal,
593
double* aMaxVal, double* aStep) {
594
if (HasNumericValue()) {
595
*aCurVal = CurValue();
596
*aMinVal = MinValue();
597
*aMaxVal = MaxValue();
598
*aStep = Step();
599
return true;
600
}
601
602
return false;
603
}
604
605
mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToBundle(bool aSmall) {
606
nsAutoString name;
607
Name(name);
608
nsAutoString textValue;
609
Value(textValue);
610
nsAutoString nodeID;
611
WrapperDOMNodeID(nodeID);
612
nsAutoString description;
613
Description(description);
614
615
if (aSmall) {
616
return ToBundle(State(), Bounds(), ActionCount(), name, textValue, nodeID,
617
description);
618
}
619
620
double curValue = UnspecifiedNaN<double>();
621
double minValue = UnspecifiedNaN<double>();
622
double maxValue = UnspecifiedNaN<double>();
623
double step = UnspecifiedNaN<double>();
624
WrapperRangeInfo(&curValue, &minValue, &maxValue, &step);
625
626
nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
627
628
return ToBundle(State(), Bounds(), ActionCount(), name, textValue, nodeID,
629
description, curValue, minValue, maxValue, step, attributes);
630
}
631
632
mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToBundle(
633
const uint64_t aState, const nsIntRect& aBounds, const uint8_t aActionCount,
634
const nsString& aName, const nsString& aTextValue,
635
const nsString& aDOMNodeID, const nsString& aDescription,
636
const double& aCurVal, const double& aMinVal, const double& aMaxVal,
637
const double& aStep, nsIPersistentProperties* aAttributes) {
638
if (!IsProxy() && IsDefunct()) {
639
return nullptr;
640
}
641
642
GECKOBUNDLE_START(nodeInfo);
643
GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
644
645
AccessibleWrap* parent = WrapperParent();
646
GECKOBUNDLE_PUT(
647
nodeInfo, "parentId",
648
java::sdk::Integer::ValueOf(parent ? parent->VirtualViewID() : 0));
649
650
role role = WrapperRole();
651
uint32_t flags = GetFlags(role, aState, aActionCount);
652
GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags));
653
GECKOBUNDLE_PUT(nodeInfo, "className",
654
java::sdk::Integer::ValueOf(AndroidClass()));
655
656
if (aState & states::EDITABLE) {
657
nsAutoString hint(aName);
658
if (!aDescription.IsEmpty()) {
659
hint.AppendLiteral(" ");
660
hint.Append(aDescription);
661
}
662
GECKOBUNDLE_PUT(nodeInfo, "hint", jni::StringParam(hint));
663
GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aTextValue));
664
} else {
665
GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aName));
666
if (!aDescription.IsEmpty()) {
667
GECKOBUNDLE_PUT(nodeInfo, "hint", jni::StringParam(aDescription));
668
}
669
}
670
671
nsAutoString geckoRole;
672
nsAutoString roleDescription;
673
if (VirtualViewID() != kNoID) {
674
GetRoleDescription(role, aAttributes, geckoRole, roleDescription);
675
}
676
677
GECKOBUNDLE_PUT(nodeInfo, "roleDescription",
678
jni::StringParam(roleDescription));
679
GECKOBUNDLE_PUT(nodeInfo, "geckoRole", jni::StringParam(geckoRole));
680
681
GECKOBUNDLE_PUT(nodeInfo, "roleDescription",
682
jni::StringParam(roleDescription));
683
GECKOBUNDLE_PUT(nodeInfo, "geckoRole", jni::StringParam(geckoRole));
684
685
if (!aDOMNodeID.IsEmpty()) {
686
GECKOBUNDLE_PUT(nodeInfo, "viewIdResourceName",
687
jni::StringParam(aDOMNodeID));
688
}
689
690
const int32_t data[4] = {aBounds.x, aBounds.y, aBounds.x + aBounds.width,
691
aBounds.y + aBounds.height};
692
GECKOBUNDLE_PUT(nodeInfo, "bounds", jni::IntArray::New(data, 4));
693
694
if (HasNumericValue()) {
695
GECKOBUNDLE_START(rangeInfo);
696
if (aMaxVal == 1 && aMinVal == 0) {
697
GECKOBUNDLE_PUT(rangeInfo, "type",
698
java::sdk::Integer::ValueOf(2)); // percent
699
} else if (std::round(aStep) != aStep) {
700
GECKOBUNDLE_PUT(rangeInfo, "type",
701
java::sdk::Integer::ValueOf(1)); // float
702
} else {
703
GECKOBUNDLE_PUT(rangeInfo, "type",
704
java::sdk::Integer::ValueOf(0)); // integer
705
}
706
707
if (!IsNaN(aCurVal)) {
708
GECKOBUNDLE_PUT(rangeInfo, "current", java::sdk::Double::New(aCurVal));
709
}
710
if (!IsNaN(aMinVal)) {
711
GECKOBUNDLE_PUT(rangeInfo, "min", java::sdk::Double::New(aMinVal));
712
}
713
if (!IsNaN(aMaxVal)) {
714
GECKOBUNDLE_PUT(rangeInfo, "max", java::sdk::Double::New(aMaxVal));
715
}
716
717
GECKOBUNDLE_FINISH(rangeInfo);
718
GECKOBUNDLE_PUT(nodeInfo, "rangeInfo", rangeInfo);
719
}
720
721
if (aAttributes) {
722
nsString inputTypeAttr;
723
nsAccUtils::GetAccAttr(aAttributes, nsGkAtoms::textInputType,
724
inputTypeAttr);
725
int32_t inputType = GetInputType(inputTypeAttr);
726
if (inputType) {
727
GECKOBUNDLE_PUT(nodeInfo, "inputType",
728
java::sdk::Integer::ValueOf(inputType));
729
}
730
731
nsString posinset;
732
nsresult rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("posinset"),
733
posinset);
734
if (NS_SUCCEEDED(rv)) {
735
int32_t rowIndex;
736
if (sscanf(NS_ConvertUTF16toUTF8(posinset).get(), "%d", &rowIndex) > 0) {
737
GECKOBUNDLE_START(collectionItemInfo);
738
GECKOBUNDLE_PUT(collectionItemInfo, "rowIndex",
739
java::sdk::Integer::ValueOf(rowIndex));
740
GECKOBUNDLE_PUT(collectionItemInfo, "columnIndex",
741
java::sdk::Integer::ValueOf(0));
742
GECKOBUNDLE_PUT(collectionItemInfo, "rowSpan",
743
java::sdk::Integer::ValueOf(1));
744
GECKOBUNDLE_PUT(collectionItemInfo, "columnSpan",
745
java::sdk::Integer::ValueOf(1));
746
GECKOBUNDLE_FINISH(collectionItemInfo);
747
748
GECKOBUNDLE_PUT(nodeInfo, "collectionItemInfo", collectionItemInfo);
749
}
750
}
751
752
nsString colSize;
753
rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("child-item-count"),
754
colSize);
755
if (NS_SUCCEEDED(rv)) {
756
int32_t rowCount;
757
if (sscanf(NS_ConvertUTF16toUTF8(colSize).get(), "%d", &rowCount) > 0) {
758
GECKOBUNDLE_START(collectionInfo);
759
GECKOBUNDLE_PUT(collectionInfo, "rowCount",
760
java::sdk::Integer::ValueOf(rowCount));
761
GECKOBUNDLE_PUT(collectionInfo, "columnCount",
762
java::sdk::Integer::ValueOf(1));
763
764
nsString unused;
765
rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("hierarchical"),
766
unused);
767
if (NS_SUCCEEDED(rv)) {
768
GECKOBUNDLE_PUT(collectionInfo, "isHierarchical",
769
java::sdk::Boolean::TRUE());
770
}
771
772
if (IsSelect()) {
773
int32_t selectionMode = (aState & states::MULTISELECTABLE) ? 2 : 1;
774
GECKOBUNDLE_PUT(collectionInfo, "selectionMode",
775
java::sdk::Integer::ValueOf(selectionMode));
776
}
777
778
GECKOBUNDLE_FINISH(collectionInfo);
779
GECKOBUNDLE_PUT(nodeInfo, "collectionInfo", collectionInfo);
780
}
781
}
782
}
783
784
bool mustPrune =
785
IsProxy() ? nsAccUtils::MustPrune(Proxy()) : nsAccUtils::MustPrune(this);
786
if (!mustPrune) {
787
auto childCount = ChildCount();
788
nsTArray<int32_t> children(childCount);
789
for (uint32_t i = 0; i < childCount; i++) {
790
auto child = static_cast<AccessibleWrap*>(GetChildAt(i));
791
children.AppendElement(child->VirtualViewID());
792
}
793
794
GECKOBUNDLE_PUT(nodeInfo, "children",
795
jni::IntArray::New(children.Elements(), children.Length()));
796
}
797
798
GECKOBUNDLE_FINISH(nodeInfo);
799
800
return nodeInfo;
801
}
802
803
void AccessibleWrap::GetTextEquiv(nsString& aText) {
804
if (nsTextEquivUtils::HasNameRule(this, eNameFromSubtreeIfReqRule)) {
805
// This is an accessible that normally doesn't get its name from its
806
// subtree, so we collect the text equivalent explicitly.
807
nsTextEquivUtils::GetTextEquivFromSubtree(this, aText);
808
} else {
809
Name(aText);
810
}
811
}
812
813
bool AccessibleWrap::HandleLiveRegionEvent(AccEvent* aEvent) {
814
auto eventType = aEvent->GetEventType();
815
if (eventType != nsIAccessibleEvent::EVENT_TEXT_INSERTED &&
816
eventType != nsIAccessibleEvent::EVENT_NAME_CHANGE) {
817
// XXX: Right now only announce text inserted events. aria-relevant=removals
818
// is potentially on the chopping block[1]. We also don't support editable
819
// text because we currently can't descern the source of the change[2].
822
return false;
823
}
824
825
if (aEvent->IsFromUserInput()) {
826
return false;
827
}
828
829
nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
830
nsString live;
831
nsresult rv =
832
attributes->GetStringProperty(NS_LITERAL_CSTRING("container-live"), live);
833
if (!NS_SUCCEEDED(rv)) {
834
return false;
835
}
836
837
uint16_t priority = live.EqualsIgnoreCase("assertive")
838
? nsIAccessibleAnnouncementEvent::ASSERTIVE
839
: nsIAccessibleAnnouncementEvent::POLITE;
840
841
nsString atomic;
842
rv = attributes->GetStringProperty(NS_LITERAL_CSTRING("container-atomic"),
843
atomic);
844
845
Accessible* announcementTarget = this;
846
nsAutoString announcement;
847
if (atomic.EqualsIgnoreCase("true")) {
848
Accessible* atomicAncestor = nullptr;
849
for (Accessible* parent = announcementTarget; parent;
850
parent = parent->Parent()) {
851
Element* element = parent->Elm();
852
if (element &&
853
element->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_atomic,
854
nsGkAtoms::_true, eCaseMatters)) {
855
atomicAncestor = parent;
856
break;
857
}
858
}
859
860
if (atomicAncestor) {
861
announcementTarget = atomicAncestor;
862
static_cast<AccessibleWrap*>(atomicAncestor)->GetTextEquiv(announcement);
863
}
864
} else {
865
GetTextEquiv(announcement);
866
}
867
868
announcement.CompressWhitespace();
869
if (announcement.IsEmpty()) {
870
return false;
871
}
872
873
announcementTarget->Announce(announcement, priority);
874
return true;
875
}