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