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/. */
6 #include "AccessibleWrap.h"
8 #include "JavaBuiltins.h"
9 #include "LocalAccessible-inl.h"
10 #include "HyperTextAccessible-inl.h"
11 #include "AccAttributes.h"
13 #include "AndroidInputType.h"
14 #include "DocAccessibleWrap.h"
16 #include "SessionAccessibility.h"
17 #include "TextLeafAccessible.h"
18 #include "TraversalRule.h"
21 #include "nsAccessibilityService.h"
22 #include "nsEventShell.h"
23 #include "nsIAccessibleAnnouncementEvent.h"
24 #include "nsAccUtils.h"
25 #include "nsTextEquivUtils.h"
26 #include "nsWhitespaceTokenizer.h"
27 #include "RootAccessible.h"
29 #include "mozilla/a11y/PDocAccessibleChild.h"
30 #include "mozilla/jni/GeckoBundleUtils.h"
32 // icu TRUE conflicting with java::sdk::Boolean::TRUE()
33 // https://searchfox.org/mozilla-central/rev/ce02064d8afc8673cef83c92896ee873bd35e7ae/intl/icu/source/common/unicode/umachine.h#265
34 // https://searchfox.org/mozilla-central/source/__GENERATED__/widget/android/bindings/JavaBuiltins.h#78
39 using namespace mozilla::a11y
;
41 // IDs should be a positive 32bit integer.
44 //-----------------------------------------------------
46 //-----------------------------------------------------
47 AccessibleWrap::AccessibleWrap(nsIContent
* aContent
, DocAccessible
* aDoc
)
48 : LocalAccessible(aContent
, aDoc
) {
51 DocAccessibleWrap
* doc
= static_cast<DocAccessibleWrap
*>(aDoc
);
52 doc
->AddID(mID
, this);
56 //-----------------------------------------------------
58 //-----------------------------------------------------
59 AccessibleWrap::~AccessibleWrap() {}
61 nsresult
AccessibleWrap::HandleAccEvent(AccEvent
* aEvent
) {
62 auto accessible
= static_cast<AccessibleWrap
*>(aEvent
->GetAccessible());
63 NS_ENSURE_TRUE(accessible
, NS_ERROR_FAILURE
);
64 DocAccessibleWrap
* doc
=
65 static_cast<DocAccessibleWrap
*>(accessible
->Document());
67 switch (aEvent
->GetEventType()) {
68 case nsIAccessibleEvent::EVENT_FOCUS
: {
69 if (DocAccessibleWrap
* topContentDoc
=
70 doc
->GetTopLevelContentDoc(accessible
)) {
71 topContentDoc
->CacheFocusPath(accessible
);
75 case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED
: {
76 AccVCChangeEvent
* vcEvent
= downcast_accEvent(aEvent
);
78 static_cast<AccessibleWrap
*>(vcEvent
->NewAccessible());
80 if (DocAccessibleWrap
* topContentDoc
=
81 doc
->GetTopLevelContentDoc(accessible
)) {
82 topContentDoc
->CacheFocusPath(newPosition
);
87 case nsIAccessibleEvent::EVENT_REORDER
: {
88 if (DocAccessibleWrap
* topContentDoc
=
89 doc
->GetTopLevelContentDoc(accessible
)) {
90 topContentDoc
->CacheViewport(true);
94 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED
: {
95 if (accessible
!= aEvent
->Document() && !aEvent
->IsFromUserInput()) {
96 AccCaretMoveEvent
* caretEvent
= downcast_accEvent(aEvent
);
97 HyperTextAccessible
* ht
= AsHyperText();
98 // Pivot to the caret's position if it has an expanded selection.
99 // This is used mostly for find in page.
100 if ((ht
&& ht
->SelectionCount())) {
102 AsHyperText()->OffsetToDOMPoint(caretEvent
->GetCaretOffset());
103 if (LocalAccessible
* newPos
=
104 doc
->GetAccessibleOrContainer(point
.node
)) {
105 static_cast<AccessibleWrap
*>(newPos
)->PivotTo(
106 java::SessionAccessibility::HTML_GRANULARITY_DEFAULT
, true,
113 case nsIAccessibleEvent::EVENT_SCROLLING_START
: {
115 java::SessionAccessibility::HTML_GRANULARITY_DEFAULT
, true, true);
123 nsresult rv
= LocalAccessible::HandleAccEvent(aEvent
);
124 NS_ENSURE_SUCCESS(rv
, rv
);
126 accessible
->HandleLiveRegionEvent(aEvent
);
128 if (IPCAccessibilityActive()) {
132 // The accessible can become defunct if we have an xpcom event listener
133 // which decides it would be fun to change the DOM and flush layout.
134 if (accessible
->IsDefunct() || !accessible
->IsBoundToParent()) {
139 if (!doc
->DocumentNode()->IsContentDocument()) {
144 RefPtr
<SessionAccessibility
> sessionAcc
=
145 SessionAccessibility::GetInstanceFor(accessible
);
150 switch (aEvent
->GetEventType()) {
151 case nsIAccessibleEvent::EVENT_FOCUS
:
152 sessionAcc
->SendFocusEvent(accessible
);
154 case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED
: {
155 AccVCChangeEvent
* vcEvent
= downcast_accEvent(aEvent
);
156 if (!vcEvent
->IsFromUserInput()) {
160 RefPtr
<AccessibleWrap
> newPosition
=
161 static_cast<AccessibleWrap
*>(vcEvent
->NewAccessible());
162 if (sessionAcc
&& newPosition
) {
163 if (vcEvent
->Reason() == nsIAccessiblePivot::REASON_POINT
) {
164 sessionAcc
->SendHoverEnterEvent(newPosition
);
165 } else if (vcEvent
->BoundaryType() == nsIAccessiblePivot::NO_BOUNDARY
) {
166 sessionAcc
->SendAccessibilityFocusedEvent(newPosition
);
169 if (vcEvent
->BoundaryType() != nsIAccessiblePivot::NO_BOUNDARY
) {
170 sessionAcc
->SendTextTraversedEvent(
171 newPosition
, vcEvent
->NewStartOffset(), vcEvent
->NewEndOffset());
176 case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED
: {
177 AccCaretMoveEvent
* event
= downcast_accEvent(aEvent
);
178 sessionAcc
->SendTextSelectionChangedEvent(accessible
,
179 event
->GetCaretOffset());
182 case nsIAccessibleEvent::EVENT_TEXT_INSERTED
:
183 case nsIAccessibleEvent::EVENT_TEXT_REMOVED
: {
184 AccTextChangeEvent
* event
= downcast_accEvent(aEvent
);
185 sessionAcc
->SendTextChangedEvent(
186 accessible
, event
->ModifiedText(), event
->GetStartOffset(),
187 event
->GetLength(), event
->IsTextInserted(),
188 event
->IsFromUserInput());
191 case nsIAccessibleEvent::EVENT_STATE_CHANGE
: {
192 AccStateChangeEvent
* event
= downcast_accEvent(aEvent
);
193 auto state
= event
->GetState();
194 if (state
& states::CHECKED
) {
195 sessionAcc
->SendClickedEvent(
196 accessible
, java::SessionAccessibility::FLAG_CHECKABLE
|
197 (event
->IsStateEnabled()
198 ? java::SessionAccessibility::FLAG_CHECKED
202 if (state
& states::EXPANDED
) {
203 sessionAcc
->SendClickedEvent(
204 accessible
, java::SessionAccessibility::FLAG_EXPANDABLE
|
205 (event
->IsStateEnabled()
206 ? java::SessionAccessibility::FLAG_EXPANDED
210 if (state
& states::SELECTED
) {
211 sessionAcc
->SendSelectedEvent(accessible
, event
->IsStateEnabled());
214 if (state
& states::BUSY
) {
215 sessionAcc
->SendWindowStateChangedEvent(accessible
);
219 case nsIAccessibleEvent::EVENT_SCROLLING
: {
220 AccScrollingEvent
* event
= downcast_accEvent(aEvent
);
221 sessionAcc
->SendScrollingEvent(accessible
, event
->ScrollX(),
222 event
->ScrollY(), event
->MaxScrollX(),
223 event
->MaxScrollY());
226 case nsIAccessibleEvent::EVENT_ANNOUNCEMENT
: {
227 AccAnnouncementEvent
* event
= downcast_accEvent(aEvent
);
228 sessionAcc
->SendAnnouncementEvent(accessible
, event
->Announcement(),
239 void AccessibleWrap::Shutdown() {
242 if (auto doc
= static_cast<DocAccessibleWrap
*>(mDoc
.get())) {
250 LocalAccessible::Shutdown();
253 bool AccessibleWrap::DoAction(uint8_t aIndex
) const {
255 return LocalAccessible::DoAction(aIndex
);
259 // We still simulate a click on an accessible even if there is no
260 // known actions. For the sake of bad markup.
268 int32_t AccessibleWrap::AcquireID() { return sIDSet
.GetID(); }
270 void AccessibleWrap::ReleaseID(int32_t aID
) { sIDSet
.ReleaseID(aID
); }
272 void AccessibleWrap::SetTextContents(const nsAString
& aText
) {
274 AsHyperText()->ReplaceText(aText
);
278 void AccessibleWrap::GetTextContents(nsAString
& aText
) {
279 // For now it is a simple wrapper for getting entire range of TextSubstring.
280 // In the future this may be smarter and retrieve a flattened string.
282 AsHyperText()->TextSubstring(0, -1, aText
);
283 } else if (IsTextLeaf()) {
284 aText
= AsTextLeaf()->Text();
288 bool AccessibleWrap::GetSelectionBounds(int32_t* aStartOffset
,
289 int32_t* aEndOffset
) {
291 return AsHyperText()->SelectionBoundsAt(0, aStartOffset
, aEndOffset
);
297 void AccessibleWrap::PivotTo(int32_t aGranularity
, bool aForward
,
299 a11y::Pivot
pivot(RootAccessible());
300 TraversalRule
rule(aGranularity
);
301 Accessible
* maybeResult
= aForward
? pivot
.Next(this, rule
, aInclusive
)
302 : pivot
.Prev(this, rule
, aInclusive
);
303 LocalAccessible
* result
= maybeResult
? maybeResult
->AsLocal() : nullptr;
304 if (result
&& (result
!= this || aInclusive
)) {
305 PivotMoveReason reason
= aForward
? nsIAccessiblePivot::REASON_NEXT
306 : nsIAccessiblePivot::REASON_PREV
;
307 RefPtr
<AccEvent
> event
= new AccVCChangeEvent(
308 result
->Document(), this, -1, -1, result
, -1, -1, reason
,
309 nsIAccessiblePivot::NO_BOUNDARY
, eFromUserInput
);
310 nsEventShell::FireEvent(event
);
314 void AccessibleWrap::ExploreByTouch(float aX
, float aY
) {
315 a11y::Pivot
pivot(RootAccessible());
318 Accessible
* maybeResult
= pivot
.AtPoint(aX
, aY
, rule
);
319 LocalAccessible
* result
= maybeResult
? maybeResult
->AsLocal() : nullptr;
321 if (result
&& result
!= this) {
322 RefPtr
<AccEvent
> event
=
323 new AccVCChangeEvent(result
->Document(), this, -1, -1, result
, -1, -1,
324 nsIAccessiblePivot::REASON_POINT
,
325 nsIAccessiblePivot::NO_BOUNDARY
, eFromUserInput
);
326 nsEventShell::FireEvent(event
);
330 void AccessibleWrap::NavigateText(int32_t aGranularity
, int32_t aStartOffset
,
331 int32_t aEndOffset
, bool aForward
,
333 a11y::Pivot
pivot(RootAccessible());
335 HyperTextAccessible
* editable
=
336 (State() & states::EDITABLE
) != 0 ? AsHyperText() : nullptr;
338 int32_t start
= aStartOffset
, end
= aEndOffset
;
339 // If the accessible is an editable, set the virtual cursor position
340 // to its caret offset. Otherwise use the document's virtual cursor
341 // position as a starting offset.
343 start
= end
= editable
->CaretOffset();
346 uint16_t pivotGranularity
= nsIAccessiblePivot::LINE_BOUNDARY
;
347 switch (aGranularity
) {
348 case 1: // MOVEMENT_GRANULARITY_CHARACTER
349 pivotGranularity
= nsIAccessiblePivot::CHAR_BOUNDARY
;
351 case 2: // MOVEMENT_GRANULARITY_WORD
352 pivotGranularity
= nsIAccessiblePivot::WORD_BOUNDARY
;
359 LocalAccessible
* newAnchor
= nullptr;
361 newAnchor
= pivot
.NextText(this, &start
, &end
, pivotGranularity
);
364 newAnchor
= pivot
.PrevText(this, &start
, &end
, pivotGranularity
);
368 if (newAnchor
&& (start
!= aStartOffset
|| end
!= aEndOffset
)) {
369 if (IsTextLeaf() && newAnchor
== LocalParent()) {
370 // For paragraphs, divs, spans, etc., we put a11y focus on the text leaf
371 // node instead of the HyperTextAccessible. However, Pivot will always
372 // return a HyperTextAccessible. Android doesn't support text navigation
373 // landing on an accessible which is different to the originating
374 // accessible. Therefore, if we're still within the same text leaf,
375 // translate the offsets to the text leaf.
376 int32_t thisChild
= IndexInParent();
377 HyperTextAccessible
* newHyper
= newAnchor
->AsHyperText();
378 MOZ_ASSERT(newHyper
);
379 int32_t startChild
= newHyper
->GetChildIndexAtOffset(start
);
380 // We use end - 1 because the end offset is exclusive, so end itself
381 // might be associated with the next child.
382 int32_t endChild
= newHyper
->GetChildIndexAtOffset(end
- 1);
383 if (startChild
== thisChild
&& endChild
== thisChild
) {
384 // We've landed within the same text leaf.
386 int32_t thisOffset
= newHyper
->GetChildOffset(thisChild
);
391 RefPtr
<AccEvent
> event
= new AccVCChangeEvent(
392 newAnchor
->Document(), this, aStartOffset
, aEndOffset
, newAnchor
, start
,
393 end
, nsIAccessiblePivot::REASON_NONE
, pivotGranularity
, eFromUserInput
);
394 nsEventShell::FireEvent(event
);
397 // If we are in an editable, move the caret to the new virtual cursor
401 int32_t anchor
= editable
->CaretOffset();
402 if (editable
->SelectionCount()) {
403 int32_t startSel
, endSel
;
404 GetSelectionOrCaret(&startSel
, &endSel
);
405 anchor
= startSel
== anchor
? endSel
: startSel
;
407 editable
->SetSelectionBoundsAt(0, anchor
, newOffset
);
409 editable
->SetCaretOffset(newOffset
);
414 void AccessibleWrap::SetSelection(int32_t aStart
, int32_t aEnd
) {
415 if (HyperTextAccessible
* textAcc
= AsHyperText()) {
416 if (aStart
== aEnd
) {
417 textAcc
->SetCaretOffset(aStart
);
419 textAcc
->SetSelectionBoundsAt(0, aStart
, aEnd
);
424 void AccessibleWrap::Cut() {
425 if ((State() & states::EDITABLE
) == 0) {
429 if (HyperTextAccessible
* textAcc
= AsHyperText()) {
430 int32_t startSel
, endSel
;
431 GetSelectionOrCaret(&startSel
, &endSel
);
432 textAcc
->CutText(startSel
, endSel
);
436 void AccessibleWrap::Copy() {
437 if (HyperTextAccessible
* textAcc
= AsHyperText()) {
438 int32_t startSel
, endSel
;
439 GetSelectionOrCaret(&startSel
, &endSel
);
440 textAcc
->CopyText(startSel
, endSel
);
444 void AccessibleWrap::Paste() {
445 if ((State() & states::EDITABLE
) == 0) {
450 RefPtr
<HyperTextAccessible
> textAcc
= AsHyperText();
451 int32_t startSel
, endSel
;
452 GetSelectionOrCaret(&startSel
, &endSel
);
453 if (startSel
!= endSel
) {
454 textAcc
->DeleteText(startSel
, endSel
);
456 textAcc
->PasteText(startSel
);
460 void AccessibleWrap::GetSelectionOrCaret(int32_t* aStartOffset
,
461 int32_t* aEndOffset
) {
462 *aStartOffset
= *aEndOffset
= -1;
463 if (HyperTextAccessible
* textAcc
= AsHyperText()) {
464 if (!textAcc
->SelectionBoundsAt(0, aStartOffset
, aEndOffset
)) {
465 *aStartOffset
= *aEndOffset
= textAcc
->CaretOffset();
470 uint32_t AccessibleWrap::GetFlags(role aRole
, uint64_t aState
,
471 uint8_t aActionCount
) {
473 if (aState
& states::CHECKABLE
) {
474 flags
|= java::SessionAccessibility::FLAG_CHECKABLE
;
477 if (aState
& states::CHECKED
) {
478 flags
|= java::SessionAccessibility::FLAG_CHECKED
;
481 if (aState
& states::INVALID
) {
482 flags
|= java::SessionAccessibility::FLAG_CONTENT_INVALID
;
485 if (aState
& states::EDITABLE
) {
486 flags
|= java::SessionAccessibility::FLAG_EDITABLE
;
489 if (aActionCount
&& aRole
!= roles::TEXT_LEAF
) {
490 flags
|= java::SessionAccessibility::FLAG_CLICKABLE
;
493 if (aState
& states::ENABLED
) {
494 flags
|= java::SessionAccessibility::FLAG_ENABLED
;
497 if (aState
& states::FOCUSABLE
) {
498 flags
|= java::SessionAccessibility::FLAG_FOCUSABLE
;
501 if (aState
& states::FOCUSED
) {
502 flags
|= java::SessionAccessibility::FLAG_FOCUSED
;
505 if (aState
& states::MULTI_LINE
) {
506 flags
|= java::SessionAccessibility::FLAG_MULTI_LINE
;
509 if (aState
& states::SELECTABLE
) {
510 flags
|= java::SessionAccessibility::FLAG_SELECTABLE
;
513 if (aState
& states::SELECTED
) {
514 flags
|= java::SessionAccessibility::FLAG_SELECTED
;
517 if (aState
& states::EXPANDABLE
) {
518 flags
|= java::SessionAccessibility::FLAG_EXPANDABLE
;
521 if (aState
& states::EXPANDED
) {
522 flags
|= java::SessionAccessibility::FLAG_EXPANDED
;
525 if ((aState
& (states::INVISIBLE
| states::OFFSCREEN
)) == 0) {
526 flags
|= java::SessionAccessibility::FLAG_VISIBLE_TO_USER
;
529 if (aRole
== roles::PASSWORD_TEXT
) {
530 flags
|= java::SessionAccessibility::FLAG_PASSWORD
;
536 void AccessibleWrap::GetRoleDescription(role aRole
, AccAttributes
* aAttributes
,
537 nsAString
& aGeckoRole
,
538 nsAString
& aRoleDescription
) {
539 if (aRole
== roles::HEADING
&& aAttributes
) {
540 // The heading level is an attribute, so we need that.
541 AutoTArray
<nsString
, 1> formatString
;
542 if (aAttributes
->GetAttribute(nsGkAtoms::level
,
543 *formatString
.AppendElement()) &&
544 LocalizeString("headingLevel", aRoleDescription
, formatString
)) {
549 if ((aRole
== roles::LANDMARK
|| aRole
== roles::REGION
) && aAttributes
) {
550 nsAutoString xmlRoles
;
551 if (aAttributes
->GetAttribute(nsGkAtoms::xmlroles
, xmlRoles
)) {
552 nsWhitespaceTokenizer
tokenizer(xmlRoles
);
553 while (tokenizer
.hasMoreTokens()) {
554 if (LocalizeString(NS_ConvertUTF16toUTF8(tokenizer
.nextToken()).get(),
562 GetAccService()->GetStringRole(aRole
, aGeckoRole
);
563 LocalizeString(NS_ConvertUTF16toUTF8(aGeckoRole
).get(), aRoleDescription
);
566 int32_t AccessibleWrap::GetAndroidClass(role aRole
) {
567 #define ROLE(geckoRole, stringRole, atkRole, macRole, macSubrole, msaaRole, \
568 ia2Role, androidClass, nameRule) \
569 case roles::geckoRole: \
575 return java::SessionAccessibility::CLASSNAME_VIEW
;
581 int32_t AccessibleWrap::GetInputType(const nsString
& aInputTypeAttr
) {
582 if (aInputTypeAttr
.EqualsIgnoreCase("email")) {
583 return java::sdk::InputType::TYPE_CLASS_TEXT
|
584 java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS
;
587 if (aInputTypeAttr
.EqualsIgnoreCase("number")) {
588 return java::sdk::InputType::TYPE_CLASS_NUMBER
;
591 if (aInputTypeAttr
.EqualsIgnoreCase("password")) {
592 return java::sdk::InputType::TYPE_CLASS_TEXT
|
593 java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_PASSWORD
;
596 if (aInputTypeAttr
.EqualsIgnoreCase("tel")) {
597 return java::sdk::InputType::TYPE_CLASS_PHONE
;
600 if (aInputTypeAttr
.EqualsIgnoreCase("text")) {
601 return java::sdk::InputType::TYPE_CLASS_TEXT
|
602 java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
;
605 if (aInputTypeAttr
.EqualsIgnoreCase("url")) {
606 return java::sdk::InputType::TYPE_CLASS_TEXT
|
607 java::sdk::InputType::TYPE_TEXT_VARIATION_URI
;
613 void AccessibleWrap::WrapperDOMNodeID(nsString
& aDOMNodeID
) {
615 nsAtom
* id
= mContent
->GetID();
617 id
->ToString(aDOMNodeID
);
622 bool AccessibleWrap::WrapperRangeInfo(double* aCurVal
, double* aMinVal
,
623 double* aMaxVal
, double* aStep
) {
624 if (HasNumericValue()) {
625 *aCurVal
= CurValue();
626 *aMinVal
= MinValue();
627 *aMaxVal
= MaxValue();
635 mozilla::java::GeckoBundle::LocalRef
AccessibleWrap::ToBundle(bool aSmall
) {
638 nsAutoString textValue
;
641 WrapperDOMNodeID(nodeID
);
642 nsAutoString description
;
643 Description(description
);
646 return ToBundle(State(), Bounds(), ActionCount(), name
, textValue
, nodeID
,
650 double curValue
= UnspecifiedNaN
<double>();
651 double minValue
= UnspecifiedNaN
<double>();
652 double maxValue
= UnspecifiedNaN
<double>();
653 double step
= UnspecifiedNaN
<double>();
654 WrapperRangeInfo(&curValue
, &minValue
, &maxValue
, &step
);
656 RefPtr
<AccAttributes
> attributes
= Attributes();
658 return ToBundle(State(), Bounds(), ActionCount(), name
, textValue
, nodeID
,
659 description
, curValue
, minValue
, maxValue
, step
, attributes
);
662 mozilla::java::GeckoBundle::LocalRef
AccessibleWrap::ToBundle(
663 const uint64_t aState
, const nsIntRect
& aBounds
, const uint8_t aActionCount
,
664 const nsString
& aName
, const nsString
& aTextValue
,
665 const nsString
& aDOMNodeID
, const nsString
& aDescription
,
666 const double& aCurVal
, const double& aMinVal
, const double& aMaxVal
,
667 const double& aStep
, AccAttributes
* aAttributes
) {
668 if (!IsProxy() && IsDefunct()) {
672 GECKOBUNDLE_START(nodeInfo
);
673 GECKOBUNDLE_PUT(nodeInfo
, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
675 AccessibleWrap
* parent
= WrapperParent();
677 nodeInfo
, "parentId",
678 java::sdk::Integer::ValueOf(parent
? parent
->VirtualViewID() : 0));
680 role role
= WrapperRole();
681 if (role
== roles::LINK
&& !(aState
& states::LINKED
)) {
682 // A link without the linked state (<a> with no href) shouldn't be presented
687 uint32_t flags
= GetFlags(role
, aState
, aActionCount
);
688 GECKOBUNDLE_PUT(nodeInfo
, "flags", java::sdk::Integer::ValueOf(flags
));
689 GECKOBUNDLE_PUT(nodeInfo
, "className",
690 java::sdk::Integer::ValueOf(AndroidClass()));
693 if (aState
& states::EDITABLE
) {
694 // An editable field's name is populated in the hint.
696 GECKOBUNDLE_PUT(nodeInfo
, "text", jni::StringParam(aTextValue
));
698 if (role
== roles::LINK
|| role
== roles::HEADING
) {
699 GECKOBUNDLE_PUT(nodeInfo
, "description", jni::StringParam(aName
));
701 GECKOBUNDLE_PUT(nodeInfo
, "text", jni::StringParam(aName
));
705 if (!aDescription
.IsEmpty()) {
706 if (!hint
.IsEmpty()) {
707 // If this is an editable, the description is concatenated with a
708 // whitespace directly after the name.
709 hint
.AppendLiteral(" ");
711 hint
.Append(aDescription
);
714 if ((aState
& states::REQUIRED
) != 0) {
715 nsAutoString requiredString
;
716 if (LocalizeString("stateRequired", requiredString
)) {
717 if (!hint
.IsEmpty()) {
718 // If the hint is non-empty, concatenate with a comma for a brief pause.
719 hint
.AppendLiteral(", ");
721 hint
.Append(requiredString
);
725 if (!hint
.IsEmpty()) {
726 GECKOBUNDLE_PUT(nodeInfo
, "hint", jni::StringParam(hint
));
729 nsAutoString geckoRole
;
730 nsAutoString roleDescription
;
731 if (VirtualViewID() != kNoID
) {
732 GetRoleDescription(role
, aAttributes
, geckoRole
, roleDescription
);
735 GECKOBUNDLE_PUT(nodeInfo
, "roleDescription",
736 jni::StringParam(roleDescription
));
737 GECKOBUNDLE_PUT(nodeInfo
, "geckoRole", jni::StringParam(geckoRole
));
739 if (!aDOMNodeID
.IsEmpty()) {
740 GECKOBUNDLE_PUT(nodeInfo
, "viewIdResourceName",
741 jni::StringParam(aDOMNodeID
));
744 const int32_t data
[4] = {aBounds
.x
, aBounds
.y
, aBounds
.x
+ aBounds
.width
,
745 aBounds
.y
+ aBounds
.height
};
746 GECKOBUNDLE_PUT(nodeInfo
, "bounds", jni::IntArray::New(data
, 4));
748 if (HasNumericValue()) {
749 GECKOBUNDLE_START(rangeInfo
);
750 if (aMaxVal
== 1 && aMinVal
== 0) {
751 GECKOBUNDLE_PUT(rangeInfo
, "type",
752 java::sdk::Integer::ValueOf(2)); // percent
753 } else if (std::round(aStep
) != aStep
) {
754 GECKOBUNDLE_PUT(rangeInfo
, "type",
755 java::sdk::Integer::ValueOf(1)); // float
757 GECKOBUNDLE_PUT(rangeInfo
, "type",
758 java::sdk::Integer::ValueOf(0)); // integer
761 if (!IsNaN(aCurVal
)) {
762 GECKOBUNDLE_PUT(rangeInfo
, "current", java::sdk::Double::New(aCurVal
));
764 if (!IsNaN(aMinVal
)) {
765 GECKOBUNDLE_PUT(rangeInfo
, "min", java::sdk::Double::New(aMinVal
));
767 if (!IsNaN(aMaxVal
)) {
768 GECKOBUNDLE_PUT(rangeInfo
, "max", java::sdk::Double::New(aMaxVal
));
771 GECKOBUNDLE_FINISH(rangeInfo
);
772 GECKOBUNDLE_PUT(nodeInfo
, "rangeInfo", rangeInfo
);
776 nsString inputTypeAttr
;
777 aAttributes
->GetAttribute(nsGkAtoms::textInputType
, inputTypeAttr
);
778 int32_t inputType
= GetInputType(inputTypeAttr
);
780 GECKOBUNDLE_PUT(nodeInfo
, "inputType",
781 java::sdk::Integer::ValueOf(inputType
));
784 Maybe
<int32_t> rowIndex
=
785 aAttributes
->GetAttribute
<int32_t>(nsGkAtoms::posinset
);
787 GECKOBUNDLE_START(collectionItemInfo
);
788 GECKOBUNDLE_PUT(collectionItemInfo
, "rowIndex",
789 java::sdk::Integer::ValueOf(*rowIndex
));
790 GECKOBUNDLE_PUT(collectionItemInfo
, "columnIndex",
791 java::sdk::Integer::ValueOf(0));
792 GECKOBUNDLE_PUT(collectionItemInfo
, "rowSpan",
793 java::sdk::Integer::ValueOf(1));
794 GECKOBUNDLE_PUT(collectionItemInfo
, "columnSpan",
795 java::sdk::Integer::ValueOf(1));
796 GECKOBUNDLE_FINISH(collectionItemInfo
);
798 GECKOBUNDLE_PUT(nodeInfo
, "collectionItemInfo", collectionItemInfo
);
801 Maybe
<int32_t> rowCount
=
802 aAttributes
->GetAttribute
<int32_t>(nsGkAtoms::child_item_count
);
804 GECKOBUNDLE_START(collectionInfo
);
805 GECKOBUNDLE_PUT(collectionInfo
, "rowCount",
806 java::sdk::Integer::ValueOf(*rowCount
));
807 GECKOBUNDLE_PUT(collectionInfo
, "columnCount",
808 java::sdk::Integer::ValueOf(1));
810 if (aAttributes
->HasAttribute(nsGkAtoms::tree
)) {
811 GECKOBUNDLE_PUT(collectionInfo
, "isHierarchical",
812 java::sdk::Boolean::TRUE());
816 int32_t selectionMode
= (aState
& states::MULTISELECTABLE
) ? 2 : 1;
817 GECKOBUNDLE_PUT(collectionInfo
, "selectionMode",
818 java::sdk::Integer::ValueOf(selectionMode
));
821 GECKOBUNDLE_FINISH(collectionInfo
);
822 GECKOBUNDLE_PUT(nodeInfo
, "collectionInfo", collectionInfo
);
827 IsProxy() ? nsAccUtils::MustPrune(Proxy()) : nsAccUtils::MustPrune(this);
829 auto childCount
= ChildCount();
830 nsTArray
<int32_t> children(childCount
);
831 for (uint32_t i
= 0; i
< childCount
; i
++) {
832 auto child
= static_cast<AccessibleWrap
*>(LocalChildAt(i
));
833 children
.AppendElement(child
->VirtualViewID());
836 GECKOBUNDLE_PUT(nodeInfo
, "children",
837 jni::IntArray::New(children
.Elements(), children
.Length()));
840 GECKOBUNDLE_FINISH(nodeInfo
);
845 void AccessibleWrap::GetTextEquiv(nsString
& aText
) {
846 if (nsTextEquivUtils::HasNameRule(this, eNameFromSubtreeIfReqRule
)) {
847 // This is an accessible that normally doesn't get its name from its
848 // subtree, so we collect the text equivalent explicitly.
849 nsTextEquivUtils::GetTextEquivFromSubtree(this, aText
);
855 bool AccessibleWrap::HandleLiveRegionEvent(AccEvent
* aEvent
) {
856 auto eventType
= aEvent
->GetEventType();
857 if (eventType
!= nsIAccessibleEvent::EVENT_TEXT_INSERTED
&&
858 eventType
!= nsIAccessibleEvent::EVENT_NAME_CHANGE
) {
859 // XXX: Right now only announce text inserted events. aria-relevant=removals
860 // is potentially on the chopping block[1]. We also don't support editable
861 // text because we currently can't descern the source of the change[2].
862 // 1. https://github.com/w3c/aria/issues/712
863 // 2. https://bugzilla.mozilla.org/show_bug.cgi?id=1531189
867 if (aEvent
->IsFromUserInput()) {
871 RefPtr
<AccAttributes
> attributes
= Attributes();
873 if (!attributes
->GetAttribute(nsGkAtoms::containerLive
, live
)) {
877 uint16_t priority
= live
.EqualsIgnoreCase("assertive")
878 ? nsIAccessibleAnnouncementEvent::ASSERTIVE
879 : nsIAccessibleAnnouncementEvent::POLITE
;
882 attributes
->GetAttribute
<bool>(nsGkAtoms::containerAtomic
);
883 LocalAccessible
* announcementTarget
= this;
884 nsAutoString announcement
;
885 if (atomic
&& *atomic
) {
886 LocalAccessible
* atomicAncestor
= nullptr;
887 for (LocalAccessible
* parent
= announcementTarget
; parent
;
888 parent
= parent
->LocalParent()) {
889 dom::Element
* element
= parent
->Elm();
891 element
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::aria_atomic
,
892 nsGkAtoms::_true
, eCaseMatters
)) {
893 atomicAncestor
= parent
;
898 if (atomicAncestor
) {
899 announcementTarget
= atomicAncestor
;
900 static_cast<AccessibleWrap
*>(atomicAncestor
)->GetTextEquiv(announcement
);
903 GetTextEquiv(announcement
);
906 announcement
.CompressWhitespace();
907 if (announcement
.IsEmpty()) {
911 announcementTarget
->Announce(announcement
, priority
);