summaryrefslogtreecommitdiffstats
path: root/accessible/android/AccessibleWrap.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--accessible/android/AccessibleWrap.cpp679
1 files changed, 679 insertions, 0 deletions
diff --git a/accessible/android/AccessibleWrap.cpp b/accessible/android/AccessibleWrap.cpp
new file mode 100644
index 0000000000..0b9fda291d
--- /dev/null
+++ b/accessible/android/AccessibleWrap.cpp
@@ -0,0 +1,679 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AccessibleWrap.h"
+
+#include "JavaBuiltins.h"
+#include "LocalAccessible-inl.h"
+#include "HyperTextAccessible-inl.h"
+#include "AccAttributes.h"
+#include "AccEvent.h"
+#include "AndroidInputType.h"
+#include "DocAccessibleWrap.h"
+#include "SessionAccessibility.h"
+#include "TextLeafAccessible.h"
+#include "TraversalRule.h"
+#include "Pivot.h"
+#include "Platform.h"
+#include "nsAccessibilityService.h"
+#include "nsEventShell.h"
+#include "nsIAccessibleAnnouncementEvent.h"
+#include "nsAccUtils.h"
+#include "nsTextEquivUtils.h"
+#include "nsWhitespaceTokenizer.h"
+#include "RootAccessible.h"
+
+#include "mozilla/a11y/PDocAccessibleChild.h"
+#include "mozilla/jni/GeckoBundleUtils.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+
+// icu TRUE conflicting with java::sdk::Boolean::TRUE()
+// https://searchfox.org/mozilla-central/rev/ce02064d8afc8673cef83c92896ee873bd35e7ae/intl/icu/source/common/unicode/umachine.h#265
+// https://searchfox.org/mozilla-central/source/__GENERATED__/widget/android/bindings/JavaBuiltins.h#78
+#ifdef TRUE
+# undef TRUE
+#endif
+
+using namespace mozilla::a11y;
+
+//-----------------------------------------------------
+// construction
+//-----------------------------------------------------
+AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
+ : LocalAccessible(aContent, aDoc), mID(SessionAccessibility::kUnsetID) {
+ if (!IPCAccessibilityActive()) {
+ MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
+ SessionAccessibility::RegisterAccessible(this);
+ }
+}
+
+//-----------------------------------------------------
+// destruction
+//-----------------------------------------------------
+AccessibleWrap::~AccessibleWrap() {}
+
+nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
+ auto accessible = static_cast<AccessibleWrap*>(aEvent->GetAccessible());
+ NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
+ DocAccessibleWrap* doc =
+ static_cast<DocAccessibleWrap*>(accessible->Document());
+ if (doc) {
+ switch (aEvent->GetEventType()) {
+ case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
+ if (accessible != aEvent->Document() && !aEvent->IsFromUserInput()) {
+ AccCaretMoveEvent* caretEvent = downcast_accEvent(aEvent);
+ HyperTextAccessible* ht = AsHyperText();
+ // Pivot to the caret's position if it has an expanded selection.
+ // This is used mostly for find in page.
+ if ((ht && ht->SelectionCount())) {
+ DOMPoint point =
+ AsHyperText()->OffsetToDOMPoint(caretEvent->GetCaretOffset());
+ if (LocalAccessible* newPos =
+ doc->GetAccessibleOrContainer(point.node)) {
+ static_cast<AccessibleWrap*>(newPos)->PivotTo(
+ java::SessionAccessibility::HTML_GRANULARITY_DEFAULT, true,
+ true);
+ }
+ }
+ }
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_SCROLLING_START: {
+ accessible->PivotTo(
+ java::SessionAccessibility::HTML_GRANULARITY_DEFAULT, true, true);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ nsresult rv = LocalAccessible::HandleAccEvent(aEvent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ accessible->HandleLiveRegionEvent(aEvent);
+
+ if (IPCAccessibilityActive()) {
+ return NS_OK;
+ }
+
+ // The accessible can become defunct if we have an xpcom event listener
+ // which decides it would be fun to change the DOM and flush layout.
+ if (accessible->IsDefunct() || !accessible->IsBoundToParent()) {
+ return NS_OK;
+ }
+
+ if (doc) {
+ if (!doc->DocumentNode()->IsContentDocument()) {
+ return NS_OK;
+ }
+ }
+
+ RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(accessible);
+ if (!sessionAcc) {
+ return NS_OK;
+ }
+
+ switch (aEvent->GetEventType()) {
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ sessionAcc->SendFocusEvent(accessible);
+ break;
+ case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
+ AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
+ if (!vcEvent->IsFromUserInput()) {
+ break;
+ }
+
+ RefPtr<AccessibleWrap> newPosition =
+ static_cast<AccessibleWrap*>(vcEvent->NewAccessible());
+ if (sessionAcc && newPosition) {
+ if (vcEvent->Reason() == nsIAccessiblePivot::REASON_POINT) {
+ sessionAcc->SendHoverEnterEvent(newPosition);
+ } else if (vcEvent->BoundaryType() == nsIAccessiblePivot::NO_BOUNDARY) {
+ sessionAcc->SendAccessibilityFocusedEvent(newPosition);
+ }
+
+ if (vcEvent->BoundaryType() != nsIAccessiblePivot::NO_BOUNDARY) {
+ sessionAcc->SendTextTraversedEvent(
+ newPosition, vcEvent->NewStartOffset(), vcEvent->NewEndOffset());
+ }
+ }
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
+ AccCaretMoveEvent* event = downcast_accEvent(aEvent);
+ sessionAcc->SendTextSelectionChangedEvent(accessible,
+ event->GetCaretOffset());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
+ case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
+ AccTextChangeEvent* event = downcast_accEvent(aEvent);
+ sessionAcc->SendTextChangedEvent(
+ accessible, event->ModifiedText(), event->GetStartOffset(),
+ event->GetLength(), event->IsTextInserted(),
+ event->IsFromUserInput());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
+ AccStateChangeEvent* event = downcast_accEvent(aEvent);
+ auto state = event->GetState();
+ if (state & states::CHECKED) {
+ sessionAcc->SendClickedEvent(
+ accessible, java::SessionAccessibility::FLAG_CHECKABLE |
+ (event->IsStateEnabled()
+ ? java::SessionAccessibility::FLAG_CHECKED
+ : 0));
+ }
+
+ if (state & states::EXPANDED) {
+ sessionAcc->SendClickedEvent(
+ accessible, java::SessionAccessibility::FLAG_EXPANDABLE |
+ (event->IsStateEnabled()
+ ? java::SessionAccessibility::FLAG_EXPANDED
+ : 0));
+ }
+
+ if (state & states::SELECTED) {
+ sessionAcc->SendSelectedEvent(accessible, event->IsStateEnabled());
+ }
+
+ if (state & states::BUSY) {
+ sessionAcc->SendWindowStateChangedEvent(accessible);
+ }
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_SCROLLING: {
+ AccScrollingEvent* event = downcast_accEvent(aEvent);
+ sessionAcc->SendScrollingEvent(accessible, event->ScrollX(),
+ event->ScrollY(), event->MaxScrollX(),
+ event->MaxScrollY());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: {
+ AccAnnouncementEvent* event = downcast_accEvent(aEvent);
+ sessionAcc->SendAnnouncementEvent(accessible, event->Announcement(),
+ event->Priority());
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_REORDER: {
+ sessionAcc->SendWindowContentChangedEvent();
+ break;
+ }
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+void AccessibleWrap::Shutdown() {
+ if (!IPCAccessibilityActive()) {
+ MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
+ SessionAccessibility::UnregisterAccessible(this);
+ }
+ LocalAccessible::Shutdown();
+}
+
+bool AccessibleWrap::DoAction(uint8_t aIndex) const {
+ if (ActionCount()) {
+ return LocalAccessible::DoAction(aIndex);
+ }
+
+ if (mContent) {
+ // We still simulate a click on an accessible even if there is no
+ // known actions. For the sake of bad markup.
+ DoCommand();
+ return true;
+ }
+
+ return false;
+}
+
+Accessible* AccessibleWrap::DoPivot(Accessible* aAccessible,
+ int32_t aGranularity, bool aForward,
+ bool aInclusive) {
+ Accessible* pivotRoot = nullptr;
+ if (aAccessible->IsRemote()) {
+ // If this is a remote accessible provide the top level
+ // remote doc as the pivot root for thread safety reasons.
+ DocAccessibleParent* doc = aAccessible->AsRemote()->Document();
+ while (doc && !doc->IsTopLevel()) {
+ doc = doc->ParentDoc();
+ }
+ MOZ_ASSERT(doc, "Failed to get top level DocAccessibleParent");
+ pivotRoot = doc;
+ }
+ a11y::Pivot pivot(pivotRoot);
+ // Depending on the start accessible, the pivot rule will either traverse
+ // local or remote accessibles exclusively.
+ TraversalRule rule(aGranularity, aAccessible->IsLocal());
+ Accessible* result = aForward ? pivot.Next(aAccessible, rule, aInclusive)
+ : pivot.Prev(aAccessible, rule, aInclusive);
+
+ if (result && (result != aAccessible || aInclusive)) {
+ return result;
+ }
+
+ return nullptr;
+}
+
+bool AccessibleWrap::PivotTo(int32_t aGranularity, bool aForward,
+ bool aInclusive) {
+ Accessible* result = DoPivot(this, aGranularity, aForward, aInclusive);
+ if (result) {
+ MOZ_ASSERT(result->IsLocal());
+ // Dispatch a virtual cursor change event that will be turned into an
+ // android accessibility focused changed event in the parent.
+ PivotMoveReason reason = aForward ? nsIAccessiblePivot::REASON_NEXT
+ : nsIAccessiblePivot::REASON_PREV;
+ LocalAccessible* localResult = result->AsLocal();
+ RefPtr<AccEvent> event = new AccVCChangeEvent(
+ localResult->Document(), this, -1, -1, localResult, -1, -1, reason,
+ nsIAccessiblePivot::NO_BOUNDARY, eFromUserInput);
+ nsEventShell::FireEvent(event);
+
+ return true;
+ }
+
+ return false;
+}
+
+void AccessibleWrap::ExploreByTouch(float aX, float aY) {
+ a11y::Pivot pivot(RootAccessible());
+ TraversalRule rule;
+
+ Accessible* maybeResult = pivot.AtPoint(aX, aY, rule);
+ LocalAccessible* result = maybeResult ? maybeResult->AsLocal() : nullptr;
+
+ if (result && result != this) {
+ RefPtr<AccEvent> event =
+ new AccVCChangeEvent(result->Document(), this, -1, -1, result, -1, -1,
+ nsIAccessiblePivot::REASON_POINT,
+ nsIAccessiblePivot::NO_BOUNDARY, eFromUserInput);
+ nsEventShell::FireEvent(event);
+ }
+}
+
+void AccessibleWrap::NavigateText(int32_t aGranularity, int32_t aStartOffset,
+ int32_t aEndOffset, bool aForward,
+ bool aSelect) {
+ a11y::Pivot pivot(RootAccessible());
+
+ HyperTextAccessible* editable =
+ (State() & states::EDITABLE) != 0 ? AsHyperText() : nullptr;
+
+ int32_t start = aStartOffset, end = aEndOffset;
+ // If the accessible is an editable, set the virtual cursor position
+ // to its caret offset. Otherwise use the document's virtual cursor
+ // position as a starting offset.
+ if (editable) {
+ start = end = editable->CaretOffset();
+ }
+
+ uint16_t pivotGranularity = nsIAccessiblePivot::LINE_BOUNDARY;
+ switch (aGranularity) {
+ case 1: // MOVEMENT_GRANULARITY_CHARACTER
+ pivotGranularity = nsIAccessiblePivot::CHAR_BOUNDARY;
+ break;
+ case 2: // MOVEMENT_GRANULARITY_WORD
+ pivotGranularity = nsIAccessiblePivot::WORD_BOUNDARY;
+ break;
+ default:
+ break;
+ }
+
+ int32_t newOffset;
+ Accessible* newAnchorBase = nullptr;
+ if (aForward) {
+ newAnchorBase = pivot.NextText(this, &start, &end, pivotGranularity);
+ newOffset = end;
+ } else {
+ newAnchorBase = pivot.PrevText(this, &start, &end, pivotGranularity);
+ newOffset = start;
+ }
+ LocalAccessible* newAnchor =
+ newAnchorBase ? newAnchorBase->AsLocal() : nullptr;
+
+ if (newAnchor && (start != aStartOffset || end != aEndOffset)) {
+ if (IsTextLeaf() && newAnchor == LocalParent()) {
+ // For paragraphs, divs, spans, etc., we put a11y focus on the text leaf
+ // node instead of the HyperTextAccessible. However, Pivot will always
+ // return a HyperTextAccessible. Android doesn't support text navigation
+ // landing on an accessible which is different to the originating
+ // accessible. Therefore, if we're still within the same text leaf,
+ // translate the offsets to the text leaf.
+ int32_t thisChild = IndexInParent();
+ HyperTextAccessible* newHyper = newAnchor->AsHyperText();
+ MOZ_ASSERT(newHyper);
+ int32_t startChild = newHyper->GetChildIndexAtOffset(start);
+ // We use end - 1 because the end offset is exclusive, so end itself
+ // might be associated with the next child.
+ int32_t endChild = newHyper->GetChildIndexAtOffset(end - 1);
+ if (startChild == thisChild && endChild == thisChild) {
+ // We've landed within the same text leaf.
+ newAnchor = this;
+ int32_t thisOffset = newHyper->GetChildOffset(thisChild);
+ start -= thisOffset;
+ end -= thisOffset;
+ }
+ }
+ RefPtr<AccEvent> event = new AccVCChangeEvent(
+ newAnchor->Document(), this, aStartOffset, aEndOffset, newAnchor, start,
+ end, nsIAccessiblePivot::REASON_NONE, pivotGranularity, eFromUserInput);
+ nsEventShell::FireEvent(event);
+ }
+
+ // If we are in an editable, move the caret to the new virtual cursor
+ // offset.
+ if (editable) {
+ if (aSelect) {
+ int32_t anchor = editable->CaretOffset();
+ if (editable->SelectionCount()) {
+ int32_t startSel, endSel;
+ GetSelectionOrCaret(&startSel, &endSel);
+ anchor = startSel == anchor ? endSel : startSel;
+ }
+ editable->SetSelectionBoundsAt(0, anchor, newOffset);
+ } else {
+ editable->SetCaretOffset(newOffset);
+ }
+ }
+}
+
+void AccessibleWrap::SetSelection(int32_t aStart, int32_t aEnd) {
+ if (HyperTextAccessible* textAcc = AsHyperText()) {
+ if (aStart == aEnd) {
+ textAcc->SetCaretOffset(aStart);
+ } else {
+ textAcc->SetSelectionBoundsAt(0, aStart, aEnd);
+ }
+ }
+}
+
+void AccessibleWrap::Cut() {
+ if ((State() & states::EDITABLE) == 0) {
+ return;
+ }
+
+ if (HyperTextAccessible* textAcc = AsHyperText()) {
+ int32_t startSel, endSel;
+ GetSelectionOrCaret(&startSel, &endSel);
+ textAcc->CutText(startSel, endSel);
+ }
+}
+
+void AccessibleWrap::Copy() {
+ if (HyperTextAccessible* textAcc = AsHyperText()) {
+ int32_t startSel, endSel;
+ GetSelectionOrCaret(&startSel, &endSel);
+ textAcc->CopyText(startSel, endSel);
+ }
+}
+
+void AccessibleWrap::Paste() {
+ if ((State() & states::EDITABLE) == 0) {
+ return;
+ }
+
+ if (IsHyperText()) {
+ RefPtr<HyperTextAccessible> textAcc = AsHyperText();
+ int32_t startSel, endSel;
+ GetSelectionOrCaret(&startSel, &endSel);
+ if (startSel != endSel) {
+ textAcc->DeleteText(startSel, endSel);
+ }
+ textAcc->PasteText(startSel);
+ }
+}
+
+void AccessibleWrap::GetSelectionOrCaret(int32_t* aStartOffset,
+ int32_t* aEndOffset) {
+ *aStartOffset = *aEndOffset = -1;
+ if (HyperTextAccessible* textAcc = AsHyperText()) {
+ if (!textAcc->SelectionBoundsAt(0, aStartOffset, aEndOffset)) {
+ *aStartOffset = *aEndOffset = textAcc->CaretOffset();
+ }
+ }
+}
+
+uint32_t AccessibleWrap::GetFlags(role aRole, uint64_t aState,
+ uint8_t aActionCount) {
+ uint32_t flags = 0;
+ if (aState & states::CHECKABLE) {
+ flags |= java::SessionAccessibility::FLAG_CHECKABLE;
+ }
+
+ if (aState & states::CHECKED) {
+ flags |= java::SessionAccessibility::FLAG_CHECKED;
+ }
+
+ if (aState & states::INVALID) {
+ flags |= java::SessionAccessibility::FLAG_CONTENT_INVALID;
+ }
+
+ if (aState & states::EDITABLE) {
+ flags |= java::SessionAccessibility::FLAG_EDITABLE;
+ }
+
+ if (aActionCount && aRole != roles::TEXT_LEAF) {
+ flags |= java::SessionAccessibility::FLAG_CLICKABLE;
+ }
+
+ if (aState & states::ENABLED) {
+ flags |= java::SessionAccessibility::FLAG_ENABLED;
+ }
+
+ if (aState & states::FOCUSABLE) {
+ flags |= java::SessionAccessibility::FLAG_FOCUSABLE;
+ }
+
+ if (aState & states::FOCUSED) {
+ flags |= java::SessionAccessibility::FLAG_FOCUSED;
+ }
+
+ if (aState & states::MULTI_LINE) {
+ flags |= java::SessionAccessibility::FLAG_MULTI_LINE;
+ }
+
+ if (aState & states::SELECTABLE) {
+ flags |= java::SessionAccessibility::FLAG_SELECTABLE;
+ }
+
+ if (aState & states::SELECTED) {
+ flags |= java::SessionAccessibility::FLAG_SELECTED;
+ }
+
+ if (aState & states::EXPANDABLE) {
+ flags |= java::SessionAccessibility::FLAG_EXPANDABLE;
+ }
+
+ if (aState & states::EXPANDED) {
+ flags |= java::SessionAccessibility::FLAG_EXPANDED;
+ }
+
+ if ((aState & (states::INVISIBLE | states::OFFSCREEN)) == 0) {
+ flags |= java::SessionAccessibility::FLAG_VISIBLE_TO_USER;
+ }
+
+ if (aRole == roles::PASSWORD_TEXT) {
+ flags |= java::SessionAccessibility::FLAG_PASSWORD;
+ }
+
+ return flags;
+}
+
+void AccessibleWrap::GetRoleDescription(role aRole, AccAttributes* aAttributes,
+ nsAString& aGeckoRole,
+ nsAString& aRoleDescription) {
+ if (aRole == roles::HEADING && aAttributes) {
+ // The heading level is an attribute, so we need that.
+ nsAutoString headingLevel;
+ if (aAttributes->GetAttribute(nsGkAtoms::level, headingLevel)) {
+ nsAutoString token(u"heading-");
+ token.Append(headingLevel);
+ if (LocalizeString(token, aRoleDescription)) {
+ return;
+ }
+ }
+ }
+
+ if ((aRole == roles::LANDMARK || aRole == roles::REGION) && aAttributes) {
+ nsAutoString xmlRoles;
+ if (aAttributes->GetAttribute(nsGkAtoms::xmlroles, xmlRoles)) {
+ nsWhitespaceTokenizer tokenizer(xmlRoles);
+ while (tokenizer.hasMoreTokens()) {
+ if (LocalizeString(tokenizer.nextToken(), aRoleDescription)) {
+ return;
+ }
+ }
+ }
+ }
+
+ GetAccService()->GetStringRole(aRole, aGeckoRole);
+ LocalizeString(aGeckoRole, aRoleDescription);
+}
+
+int32_t AccessibleWrap::AndroidClass(Accessible* aAccessible) {
+ return GetVirtualViewID(aAccessible) == SessionAccessibility::kNoID
+ ? java::SessionAccessibility::CLASSNAME_WEBVIEW
+ : GetAndroidClass(aAccessible->Role());
+}
+
+int32_t AccessibleWrap::GetVirtualViewID(Accessible* aAccessible) {
+ if (aAccessible->IsLocal()) {
+ return static_cast<AccessibleWrap*>(aAccessible)->mID;
+ }
+
+ return static_cast<int32_t>(aAccessible->AsRemote()->GetWrapper());
+}
+
+void AccessibleWrap::SetVirtualViewID(Accessible* aAccessible,
+ int32_t aVirtualViewID) {
+ if (aAccessible->IsLocal()) {
+ static_cast<AccessibleWrap*>(aAccessible)->mID = aVirtualViewID;
+ } else {
+ aAccessible->AsRemote()->SetWrapper(static_cast<uintptr_t>(aVirtualViewID));
+ }
+}
+
+int32_t AccessibleWrap::GetAndroidClass(role aRole) {
+#define ROLE(geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
+ msaaRole, ia2Role, androidClass, nameRule) \
+ case roles::geckoRole: \
+ return androidClass;
+
+ switch (aRole) {
+#include "RoleMap.h"
+ default:
+ return java::SessionAccessibility::CLASSNAME_VIEW;
+ }
+
+#undef ROLE
+}
+
+int32_t AccessibleWrap::GetInputType(const nsString& aInputTypeAttr) {
+ if (aInputTypeAttr.EqualsIgnoreCase("email")) {
+ return java::sdk::InputType::TYPE_CLASS_TEXT |
+ java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+ }
+
+ if (aInputTypeAttr.EqualsIgnoreCase("number")) {
+ return java::sdk::InputType::TYPE_CLASS_NUMBER;
+ }
+
+ if (aInputTypeAttr.EqualsIgnoreCase("password")) {
+ return java::sdk::InputType::TYPE_CLASS_TEXT |
+ java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_PASSWORD;
+ }
+
+ if (aInputTypeAttr.EqualsIgnoreCase("tel")) {
+ return java::sdk::InputType::TYPE_CLASS_PHONE;
+ }
+
+ if (aInputTypeAttr.EqualsIgnoreCase("text")) {
+ return java::sdk::InputType::TYPE_CLASS_TEXT |
+ java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
+ }
+
+ if (aInputTypeAttr.EqualsIgnoreCase("url")) {
+ return java::sdk::InputType::TYPE_CLASS_TEXT |
+ java::sdk::InputType::TYPE_TEXT_VARIATION_URI;
+ }
+
+ return 0;
+}
+
+void AccessibleWrap::GetTextEquiv(nsString& aText) {
+ if (nsTextEquivUtils::HasNameRule(this, eNameFromSubtreeIfReqRule)) {
+ // This is an accessible that normally doesn't get its name from its
+ // subtree, so we collect the text equivalent explicitly.
+ nsTextEquivUtils::GetTextEquivFromSubtree(this, aText);
+ } else {
+ Name(aText);
+ }
+}
+
+bool AccessibleWrap::HandleLiveRegionEvent(AccEvent* aEvent) {
+ auto eventType = aEvent->GetEventType();
+ if (eventType != nsIAccessibleEvent::EVENT_TEXT_INSERTED &&
+ eventType != nsIAccessibleEvent::EVENT_NAME_CHANGE) {
+ // XXX: Right now only announce text inserted events. aria-relevant=removals
+ // is potentially on the chopping block[1]. We also don't support editable
+ // text because we currently can't descern the source of the change[2].
+ // 1. https://github.com/w3c/aria/issues/712
+ // 2. https://bugzilla.mozilla.org/show_bug.cgi?id=1531189
+ return false;
+ }
+
+ if (aEvent->IsFromUserInput()) {
+ return false;
+ }
+
+ RefPtr<AccAttributes> attributes = new AccAttributes();
+ nsAccUtils::SetLiveContainerAttributes(attributes, this);
+ nsString live;
+ if (!attributes->GetAttribute(nsGkAtoms::containerLive, live)) {
+ return false;
+ }
+
+ uint16_t priority = live.EqualsIgnoreCase("assertive")
+ ? nsIAccessibleAnnouncementEvent::ASSERTIVE
+ : nsIAccessibleAnnouncementEvent::POLITE;
+
+ Maybe<bool> atomic =
+ attributes->GetAttribute<bool>(nsGkAtoms::containerAtomic);
+ LocalAccessible* announcementTarget = this;
+ nsAutoString announcement;
+ if (atomic && *atomic) {
+ LocalAccessible* atomicAncestor = nullptr;
+ for (LocalAccessible* parent = announcementTarget; parent;
+ parent = parent->LocalParent()) {
+ dom::Element* element = parent->Elm();
+ if (element &&
+ nsAccUtils::ARIAAttrValueIs(element, nsGkAtoms::aria_atomic,
+ nsGkAtoms::_true, eCaseMatters)) {
+ atomicAncestor = parent;
+ break;
+ }
+ }
+
+ if (atomicAncestor) {
+ announcementTarget = atomicAncestor;
+ static_cast<AccessibleWrap*>(atomicAncestor)->GetTextEquiv(announcement);
+ }
+ } else {
+ GetTextEquiv(announcement);
+ }
+
+ announcement.CompressWhitespace();
+ if (announcement.IsEmpty()) {
+ return false;
+ }
+
+ announcementTarget->Announce(announcement, priority);
+ return true;
+}