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 "Accessible-inl.h"
10 #include "HyperTextAccessible-inl.h"
12 #include "AndroidInputType.h"
13 #include "DocAccessibleWrap.h"
15 #include "SessionAccessibility.h"
16 #include "TextLeafAccessible.h"
17 #include "TraversalRule.h"
20 #include "nsAccessibilityService.h"
21 #include "nsEventShell.h"
22 #include "nsPersistentProperties.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 : Accessible(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 (Accessible
* newPos
=
104 doc
->GetAccessibleOrContainer(point
.node
)) {
105 static_cast<AccessibleWrap
*>(newPos
)->Pivot(
106 java::SessionAccessibility::HTML_GRANULARITY_DEFAULT
, true,
113 case nsIAccessibleEvent::EVENT_SCROLLING_START
: {
114 accessible
->Pivot(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT
,
123 nsresult rv
= Accessible::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 (!nsCoreUtils::IsContentDocument(doc
->DocumentNode())) {
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 Accessible::Shutdown();
253 bool AccessibleWrap::DoAction(uint8_t aIndex
) const {
255 return Accessible::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::Pivot(int32_t aGranularity
, bool aForward
,
299 a11y::Pivot
pivot(RootAccessible());
300 TraversalRule
rule(aGranularity
);
301 Accessible
* result
= aForward
? pivot
.Next(this, rule
, aInclusive
)
302 : pivot
.Prev(this, rule
, aInclusive
);
303 if (result
&& (result
!= this || aInclusive
)) {
304 PivotMoveReason reason
= aForward
? nsIAccessiblePivot::REASON_NEXT
305 : nsIAccessiblePivot::REASON_PREV
;
306 RefPtr
<AccEvent
> event
= new AccVCChangeEvent(
307 result
->Document(), this, -1, -1, result
, -1, -1, reason
,
308 nsIAccessiblePivot::NO_BOUNDARY
, eFromUserInput
);
309 nsEventShell::FireEvent(event
);
313 void AccessibleWrap::ExploreByTouch(float aX
, float aY
) {
314 a11y::Pivot
pivot(RootAccessible());
317 Accessible
* result
= pivot
.AtPoint(aX
, aY
, rule
);
319 if (result
&& result
!= this) {
320 RefPtr
<AccEvent
> event
=
321 new AccVCChangeEvent(result
->Document(), this, -1, -1, result
, -1, -1,
322 nsIAccessiblePivot::REASON_POINT
,
323 nsIAccessiblePivot::NO_BOUNDARY
, eFromUserInput
);
324 nsEventShell::FireEvent(event
);
328 void AccessibleWrap::NavigateText(int32_t aGranularity
, int32_t aStartOffset
,
329 int32_t aEndOffset
, bool aForward
,
331 a11y::Pivot
pivot(RootAccessible());
333 HyperTextAccessible
* editable
=
334 (State() & states::EDITABLE
) != 0 ? AsHyperText() : nullptr;
336 int32_t start
= aStartOffset
, end
= aEndOffset
;
337 // If the accessible is an editable, set the virtual cursor position
338 // to its caret offset. Otherwise use the document's virtual cursor
339 // position as a starting offset.
341 start
= end
= editable
->CaretOffset();
344 uint16_t pivotGranularity
= nsIAccessiblePivot::LINE_BOUNDARY
;
345 switch (aGranularity
) {
346 case 1: // MOVEMENT_GRANULARITY_CHARACTER
347 pivotGranularity
= nsIAccessiblePivot::CHAR_BOUNDARY
;
349 case 2: // MOVEMENT_GRANULARITY_WORD
350 pivotGranularity
= nsIAccessiblePivot::WORD_BOUNDARY
;
357 Accessible
* newAnchor
= nullptr;
359 newAnchor
= pivot
.NextText(this, &start
, &end
, pivotGranularity
);
362 newAnchor
= pivot
.PrevText(this, &start
, &end
, pivotGranularity
);
366 if (newAnchor
&& (start
!= aStartOffset
|| end
!= aEndOffset
)) {
367 if (IsTextLeaf() && newAnchor
== Parent()) {
368 // For paragraphs, divs, spans, etc., we put a11y focus on the text leaf
369 // node instead of the HyperTextAccessible. However, Pivot will always
370 // return a HyperTextAccessible. Android doesn't support text navigation
371 // landing on an accessible which is different to the originating
372 // accessible. Therefore, if we're still within the same text leaf,
373 // translate the offsets to the text leaf.
374 int32_t thisChild
= IndexInParent();
375 HyperTextAccessible
* newHyper
= newAnchor
->AsHyperText();
376 MOZ_ASSERT(newHyper
);
377 int32_t startChild
= newHyper
->GetChildIndexAtOffset(start
);
378 // We use end - 1 because the end offset is exclusive, so end itself
379 // might be associated with the next child.
380 int32_t endChild
= newHyper
->GetChildIndexAtOffset(end
- 1);
381 if (startChild
== thisChild
&& endChild
== thisChild
) {
382 // We've landed within the same text leaf.
384 int32_t thisOffset
= newHyper
->GetChildOffset(thisChild
);
389 RefPtr
<AccEvent
> event
= new AccVCChangeEvent(
390 newAnchor
->Document(), this, aStartOffset
, aEndOffset
, newAnchor
, start
,
391 end
, nsIAccessiblePivot::REASON_NONE
, pivotGranularity
, eFromUserInput
);
392 nsEventShell::FireEvent(event
);
395 // If we are in an editable, move the caret to the new virtual cursor
399 int32_t anchor
= editable
->CaretOffset();
400 if (editable
->SelectionCount()) {
401 int32_t startSel
, endSel
;
402 GetSelectionOrCaret(&startSel
, &endSel
);
403 anchor
= startSel
== anchor
? endSel
: startSel
;
405 editable
->SetSelectionBoundsAt(0, anchor
, newOffset
);
407 editable
->SetCaretOffset(newOffset
);
412 void AccessibleWrap::SetSelection(int32_t aStart
, int32_t aEnd
) {
413 if (HyperTextAccessible
* textAcc
= AsHyperText()) {
414 if (aStart
== aEnd
) {
415 textAcc
->SetCaretOffset(aStart
);
417 textAcc
->SetSelectionBoundsAt(0, aStart
, aEnd
);
422 void AccessibleWrap::Cut() {
423 if ((State() & states::EDITABLE
) == 0) {
427 if (HyperTextAccessible
* textAcc
= AsHyperText()) {
428 int32_t startSel
, endSel
;
429 GetSelectionOrCaret(&startSel
, &endSel
);
430 textAcc
->CutText(startSel
, endSel
);
434 void AccessibleWrap::Copy() {
435 if (HyperTextAccessible
* textAcc
= AsHyperText()) {
436 int32_t startSel
, endSel
;
437 GetSelectionOrCaret(&startSel
, &endSel
);
438 textAcc
->CopyText(startSel
, endSel
);
442 void AccessibleWrap::Paste() {
443 if ((State() & states::EDITABLE
) == 0) {
448 RefPtr
<HyperTextAccessible
> textAcc
= AsHyperText();
449 int32_t startSel
, endSel
;
450 GetSelectionOrCaret(&startSel
, &endSel
);
451 if (startSel
!= endSel
) {
452 textAcc
->DeleteText(startSel
, endSel
);
454 textAcc
->PasteText(startSel
);
458 void AccessibleWrap::GetSelectionOrCaret(int32_t* aStartOffset
,
459 int32_t* aEndOffset
) {
460 *aStartOffset
= *aEndOffset
= -1;
461 if (HyperTextAccessible
* textAcc
= AsHyperText()) {
462 if (!textAcc
->SelectionBoundsAt(0, aStartOffset
, aEndOffset
)) {
463 *aStartOffset
= *aEndOffset
= textAcc
->CaretOffset();
468 uint32_t AccessibleWrap::GetFlags(role aRole
, uint64_t aState
,
469 uint8_t aActionCount
) {
471 if (aState
& states::CHECKABLE
) {
472 flags
|= java::SessionAccessibility::FLAG_CHECKABLE
;
475 if (aState
& states::CHECKED
) {
476 flags
|= java::SessionAccessibility::FLAG_CHECKED
;
479 if (aState
& states::INVALID
) {
480 flags
|= java::SessionAccessibility::FLAG_CONTENT_INVALID
;
483 if (aState
& states::EDITABLE
) {
484 flags
|= java::SessionAccessibility::FLAG_EDITABLE
;
487 if (aActionCount
&& aRole
!= roles::TEXT_LEAF
) {
488 flags
|= java::SessionAccessibility::FLAG_CLICKABLE
;
491 if (aState
& states::ENABLED
) {
492 flags
|= java::SessionAccessibility::FLAG_ENABLED
;
495 if (aState
& states::FOCUSABLE
) {
496 flags
|= java::SessionAccessibility::FLAG_FOCUSABLE
;
499 if (aState
& states::FOCUSED
) {
500 flags
|= java::SessionAccessibility::FLAG_FOCUSED
;
503 if (aState
& states::MULTI_LINE
) {
504 flags
|= java::SessionAccessibility::FLAG_MULTI_LINE
;
507 if (aState
& states::SELECTABLE
) {
508 flags
|= java::SessionAccessibility::FLAG_SELECTABLE
;
511 if (aState
& states::SELECTED
) {
512 flags
|= java::SessionAccessibility::FLAG_SELECTED
;
515 if (aState
& states::EXPANDABLE
) {
516 flags
|= java::SessionAccessibility::FLAG_EXPANDABLE
;
519 if (aState
& states::EXPANDED
) {
520 flags
|= java::SessionAccessibility::FLAG_EXPANDED
;
523 if ((aState
& (states::INVISIBLE
| states::OFFSCREEN
)) == 0) {
524 flags
|= java::SessionAccessibility::FLAG_VISIBLE_TO_USER
;
527 if (aRole
== roles::PASSWORD_TEXT
) {
528 flags
|= java::SessionAccessibility::FLAG_PASSWORD
;
534 void AccessibleWrap::GetRoleDescription(role aRole
,
535 nsIPersistentProperties
* aAttributes
,
536 nsAString
& aGeckoRole
,
537 nsAString
& aRoleDescription
) {
538 if (aRole
== roles::HEADING
&& aAttributes
) {
539 // The heading level is an attribute, so we need that.
540 AutoTArray
<nsString
, 1> formatString
;
541 nsresult rv
= aAttributes
->GetStringProperty(NS_LITERAL_CSTRING("level"),
542 *formatString
.AppendElement());
543 if (NS_SUCCEEDED(rv
) &&
544 LocalizeString("headingLevel", aRoleDescription
, formatString
)) {
549 if ((aRole
== roles::LANDMARK
|| aRole
== roles::REGION
) && aAttributes
) {
550 nsAutoString xmlRoles
;
551 if (NS_SUCCEEDED(aAttributes
->GetStringProperty(
552 NS_LITERAL_CSTRING("xml-roles"), xmlRoles
))) {
553 nsWhitespaceTokenizer
tokenizer(xmlRoles
);
554 while (tokenizer
.hasMoreTokens()) {
555 if (LocalizeString(NS_ConvertUTF16toUTF8(tokenizer
.nextToken()).get(),
563 GetAccService()->GetStringRole(aRole
, aGeckoRole
);
564 LocalizeString(NS_ConvertUTF16toUTF8(aGeckoRole
).get(), aRoleDescription
);
567 already_AddRefed
<nsIPersistentProperties
>
568 AccessibleWrap::AttributeArrayToProperties(
569 const nsTArray
<Attribute
>& aAttributes
) {
570 RefPtr
<nsPersistentProperties
> props
= new nsPersistentProperties();
573 for (size_t i
= 0; i
< aAttributes
.Length(); i
++) {
574 props
->SetStringProperty(aAttributes
.ElementAt(i
).Name(),
575 aAttributes
.ElementAt(i
).Value(), unused
);
578 return props
.forget();
581 int32_t AccessibleWrap::GetAndroidClass(role aRole
) {
582 #define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, \
583 androidClass, nameRule) \
584 case roles::geckoRole: \
590 return java::SessionAccessibility::CLASSNAME_VIEW
;
596 int32_t AccessibleWrap::GetInputType(const nsString
& aInputTypeAttr
) {
597 if (aInputTypeAttr
.EqualsIgnoreCase("email")) {
598 return java::sdk::InputType::TYPE_CLASS_TEXT
|
599 java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS
;
602 if (aInputTypeAttr
.EqualsIgnoreCase("number")) {
603 return java::sdk::InputType::TYPE_CLASS_NUMBER
;
606 if (aInputTypeAttr
.EqualsIgnoreCase("password")) {
607 return java::sdk::InputType::TYPE_CLASS_TEXT
|
608 java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_PASSWORD
;
611 if (aInputTypeAttr
.EqualsIgnoreCase("tel")) {
612 return java::sdk::InputType::TYPE_CLASS_PHONE
;
615 if (aInputTypeAttr
.EqualsIgnoreCase("text")) {
616 return java::sdk::InputType::TYPE_CLASS_TEXT
|
617 java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
;
620 if (aInputTypeAttr
.EqualsIgnoreCase("url")) {
621 return java::sdk::InputType::TYPE_CLASS_TEXT
|
622 java::sdk::InputType::TYPE_TEXT_VARIATION_URI
;
628 void AccessibleWrap::WrapperDOMNodeID(nsString
& aDOMNodeID
) {
630 nsAtom
* id
= mContent
->GetID();
632 id
->ToString(aDOMNodeID
);
637 bool AccessibleWrap::WrapperRangeInfo(double* aCurVal
, double* aMinVal
,
638 double* aMaxVal
, double* aStep
) {
639 if (HasNumericValue()) {
640 *aCurVal
= CurValue();
641 *aMinVal
= MinValue();
642 *aMaxVal
= MaxValue();
650 mozilla::java::GeckoBundle::LocalRef
AccessibleWrap::ToBundle(bool aSmall
) {
653 nsAutoString textValue
;
656 WrapperDOMNodeID(nodeID
);
657 nsAutoString description
;
658 Description(description
);
661 return ToBundle(State(), Bounds(), ActionCount(), name
, textValue
, nodeID
,
665 double curValue
= UnspecifiedNaN
<double>();
666 double minValue
= UnspecifiedNaN
<double>();
667 double maxValue
= UnspecifiedNaN
<double>();
668 double step
= UnspecifiedNaN
<double>();
669 WrapperRangeInfo(&curValue
, &minValue
, &maxValue
, &step
);
671 nsCOMPtr
<nsIPersistentProperties
> attributes
= Attributes();
673 return ToBundle(State(), Bounds(), ActionCount(), name
, textValue
, nodeID
,
674 description
, curValue
, minValue
, maxValue
, step
, attributes
);
677 mozilla::java::GeckoBundle::LocalRef
AccessibleWrap::ToBundle(
678 const uint64_t aState
, const nsIntRect
& aBounds
, const uint8_t aActionCount
,
679 const nsString
& aName
, const nsString
& aTextValue
,
680 const nsString
& aDOMNodeID
, const nsString
& aDescription
,
681 const double& aCurVal
, const double& aMinVal
, const double& aMaxVal
,
682 const double& aStep
, nsIPersistentProperties
* aAttributes
) {
683 if (!IsProxy() && IsDefunct()) {
687 GECKOBUNDLE_START(nodeInfo
);
688 GECKOBUNDLE_PUT(nodeInfo
, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
690 AccessibleWrap
* parent
= WrapperParent();
692 nodeInfo
, "parentId",
693 java::sdk::Integer::ValueOf(parent
? parent
->VirtualViewID() : 0));
695 role role
= WrapperRole();
696 if (role
== roles::LINK
&& !(aState
& states::LINKED
)) {
697 // A link without the linked state (<a> with no href) shouldn't be presented
702 uint32_t flags
= GetFlags(role
, aState
, aActionCount
);
703 GECKOBUNDLE_PUT(nodeInfo
, "flags", java::sdk::Integer::ValueOf(flags
));
704 GECKOBUNDLE_PUT(nodeInfo
, "className",
705 java::sdk::Integer::ValueOf(AndroidClass()));
708 if (aState
& states::EDITABLE
) {
709 // An editable field's name is populated in the hint.
711 GECKOBUNDLE_PUT(nodeInfo
, "text", jni::StringParam(aTextValue
));
713 if (role
== roles::LINK
|| role
== roles::HEADING
) {
714 GECKOBUNDLE_PUT(nodeInfo
, "description", jni::StringParam(aName
));
716 GECKOBUNDLE_PUT(nodeInfo
, "text", jni::StringParam(aName
));
720 if (!aDescription
.IsEmpty()) {
721 if (!hint
.IsEmpty()) {
722 // If this is an editable, the description is concatenated with a
723 // whitespace directly after the name.
724 hint
.AppendLiteral(" ");
726 hint
.Append(aDescription
);
729 if ((aState
& states::REQUIRED
) != 0) {
730 nsAutoString requiredString
;
731 if (LocalizeString("stateRequired", requiredString
)) {
732 if (!hint
.IsEmpty()) {
733 // If the hint is non-empty, concatenate with a comma for a brief pause.
734 hint
.AppendLiteral(", ");
736 hint
.Append(requiredString
);
740 if (!hint
.IsEmpty()) {
741 GECKOBUNDLE_PUT(nodeInfo
, "hint", jni::StringParam(hint
));
744 nsAutoString geckoRole
;
745 nsAutoString roleDescription
;
746 if (VirtualViewID() != kNoID
) {
747 GetRoleDescription(role
, aAttributes
, geckoRole
, roleDescription
);
750 GECKOBUNDLE_PUT(nodeInfo
, "roleDescription",
751 jni::StringParam(roleDescription
));
752 GECKOBUNDLE_PUT(nodeInfo
, "geckoRole", jni::StringParam(geckoRole
));
754 if (!aDOMNodeID
.IsEmpty()) {
755 GECKOBUNDLE_PUT(nodeInfo
, "viewIdResourceName",
756 jni::StringParam(aDOMNodeID
));
759 const int32_t data
[4] = {aBounds
.x
, aBounds
.y
, aBounds
.x
+ aBounds
.width
,
760 aBounds
.y
+ aBounds
.height
};
761 GECKOBUNDLE_PUT(nodeInfo
, "bounds", jni::IntArray::New(data
, 4));
763 if (HasNumericValue()) {
764 GECKOBUNDLE_START(rangeInfo
);
765 if (aMaxVal
== 1 && aMinVal
== 0) {
766 GECKOBUNDLE_PUT(rangeInfo
, "type",
767 java::sdk::Integer::ValueOf(2)); // percent
768 } else if (std::round(aStep
) != aStep
) {
769 GECKOBUNDLE_PUT(rangeInfo
, "type",
770 java::sdk::Integer::ValueOf(1)); // float
772 GECKOBUNDLE_PUT(rangeInfo
, "type",
773 java::sdk::Integer::ValueOf(0)); // integer
776 if (!IsNaN(aCurVal
)) {
777 GECKOBUNDLE_PUT(rangeInfo
, "current", java::sdk::Double::New(aCurVal
));
779 if (!IsNaN(aMinVal
)) {
780 GECKOBUNDLE_PUT(rangeInfo
, "min", java::sdk::Double::New(aMinVal
));
782 if (!IsNaN(aMaxVal
)) {
783 GECKOBUNDLE_PUT(rangeInfo
, "max", java::sdk::Double::New(aMaxVal
));
786 GECKOBUNDLE_FINISH(rangeInfo
);
787 GECKOBUNDLE_PUT(nodeInfo
, "rangeInfo", rangeInfo
);
791 nsString inputTypeAttr
;
792 nsAccUtils::GetAccAttr(aAttributes
, nsGkAtoms::textInputType
,
794 int32_t inputType
= GetInputType(inputTypeAttr
);
796 GECKOBUNDLE_PUT(nodeInfo
, "inputType",
797 java::sdk::Integer::ValueOf(inputType
));
801 nsresult rv
= aAttributes
->GetStringProperty(NS_LITERAL_CSTRING("posinset"),
803 if (NS_SUCCEEDED(rv
)) {
805 if (sscanf(NS_ConvertUTF16toUTF8(posinset
).get(), "%d", &rowIndex
) > 0) {
806 GECKOBUNDLE_START(collectionItemInfo
);
807 GECKOBUNDLE_PUT(collectionItemInfo
, "rowIndex",
808 java::sdk::Integer::ValueOf(rowIndex
));
809 GECKOBUNDLE_PUT(collectionItemInfo
, "columnIndex",
810 java::sdk::Integer::ValueOf(0));
811 GECKOBUNDLE_PUT(collectionItemInfo
, "rowSpan",
812 java::sdk::Integer::ValueOf(1));
813 GECKOBUNDLE_PUT(collectionItemInfo
, "columnSpan",
814 java::sdk::Integer::ValueOf(1));
815 GECKOBUNDLE_FINISH(collectionItemInfo
);
817 GECKOBUNDLE_PUT(nodeInfo
, "collectionItemInfo", collectionItemInfo
);
822 rv
= aAttributes
->GetStringProperty(NS_LITERAL_CSTRING("child-item-count"),
824 if (NS_SUCCEEDED(rv
)) {
826 if (sscanf(NS_ConvertUTF16toUTF8(colSize
).get(), "%d", &rowCount
) > 0) {
827 GECKOBUNDLE_START(collectionInfo
);
828 GECKOBUNDLE_PUT(collectionInfo
, "rowCount",
829 java::sdk::Integer::ValueOf(rowCount
));
830 GECKOBUNDLE_PUT(collectionInfo
, "columnCount",
831 java::sdk::Integer::ValueOf(1));
834 rv
= aAttributes
->GetStringProperty(NS_LITERAL_CSTRING("hierarchical"),
836 if (NS_SUCCEEDED(rv
)) {
837 GECKOBUNDLE_PUT(collectionInfo
, "isHierarchical",
838 java::sdk::Boolean::TRUE());
842 int32_t selectionMode
= (aState
& states::MULTISELECTABLE
) ? 2 : 1;
843 GECKOBUNDLE_PUT(collectionInfo
, "selectionMode",
844 java::sdk::Integer::ValueOf(selectionMode
));
847 GECKOBUNDLE_FINISH(collectionInfo
);
848 GECKOBUNDLE_PUT(nodeInfo
, "collectionInfo", collectionInfo
);
854 IsProxy() ? nsAccUtils::MustPrune(Proxy()) : nsAccUtils::MustPrune(this);
856 auto childCount
= ChildCount();
857 nsTArray
<int32_t> children(childCount
);
858 for (uint32_t i
= 0; i
< childCount
; i
++) {
859 auto child
= static_cast<AccessibleWrap
*>(GetChildAt(i
));
860 children
.AppendElement(child
->VirtualViewID());
863 GECKOBUNDLE_PUT(nodeInfo
, "children",
864 jni::IntArray::New(children
.Elements(), children
.Length()));
867 GECKOBUNDLE_FINISH(nodeInfo
);
872 void AccessibleWrap::GetTextEquiv(nsString
& aText
) {
873 if (nsTextEquivUtils::HasNameRule(this, eNameFromSubtreeIfReqRule
)) {
874 // This is an accessible that normally doesn't get its name from its
875 // subtree, so we collect the text equivalent explicitly.
876 nsTextEquivUtils::GetTextEquivFromSubtree(this, aText
);
882 bool AccessibleWrap::HandleLiveRegionEvent(AccEvent
* aEvent
) {
883 auto eventType
= aEvent
->GetEventType();
884 if (eventType
!= nsIAccessibleEvent::EVENT_TEXT_INSERTED
&&
885 eventType
!= nsIAccessibleEvent::EVENT_NAME_CHANGE
) {
886 // XXX: Right now only announce text inserted events. aria-relevant=removals
887 // is potentially on the chopping block[1]. We also don't support editable
888 // text because we currently can't descern the source of the change[2].
889 // 1. https://github.com/w3c/aria/issues/712
890 // 2. https://bugzilla.mozilla.org/show_bug.cgi?id=1531189
894 if (aEvent
->IsFromUserInput()) {
898 nsCOMPtr
<nsIPersistentProperties
> attributes
= Attributes();
901 attributes
->GetStringProperty(NS_LITERAL_CSTRING("container-live"), live
);
902 if (!NS_SUCCEEDED(rv
)) {
906 uint16_t priority
= live
.EqualsIgnoreCase("assertive")
907 ? nsIAccessibleAnnouncementEvent::ASSERTIVE
908 : nsIAccessibleAnnouncementEvent::POLITE
;
911 rv
= attributes
->GetStringProperty(NS_LITERAL_CSTRING("container-atomic"),
914 Accessible
* announcementTarget
= this;
915 nsAutoString announcement
;
916 if (atomic
.EqualsIgnoreCase("true")) {
917 Accessible
* atomicAncestor
= nullptr;
918 for (Accessible
* parent
= announcementTarget
; parent
;
919 parent
= parent
->Parent()) {
920 dom::Element
* element
= parent
->Elm();
922 element
->AttrValueIs(kNameSpaceID_None
, nsGkAtoms::aria_atomic
,
923 nsGkAtoms::_true
, eCaseMatters
)) {
924 atomicAncestor
= parent
;
929 if (atomicAncestor
) {
930 announcementTarget
= atomicAncestor
;
931 static_cast<AccessibleWrap
*>(atomicAncestor
)->GetTextEquiv(announcement
);
934 GetTextEquiv(announcement
);
937 announcement
.CompressWhitespace();
938 if (announcement
.IsEmpty()) {
942 announcementTarget
->Announce(announcement
, priority
);