summaryrefslogtreecommitdiffstats
path: root/accessible/android
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /accessible/android
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/android')
-rw-r--r--accessible/android/ARIAGridAccessibleWrap.h22
-rw-r--r--accessible/android/AccessibleWrap.cpp942
-rw-r--r--accessible/android/AccessibleWrap.h122
-rw-r--r--accessible/android/ApplicationAccessibleWrap.h20
-rw-r--r--accessible/android/DocAccessibleWrap.cpp301
-rw-r--r--accessible/android/DocAccessibleWrap.h69
-rw-r--r--accessible/android/HTMLTableAccessibleWrap.h23
-rw-r--r--accessible/android/HyperTextAccessibleWrap.h19
-rw-r--r--accessible/android/ImageAccessibleWrap.h21
-rw-r--r--accessible/android/Platform.cpp256
-rw-r--r--accessible/android/ProxyAccessibleWrap.cpp172
-rw-r--r--accessible/android/ProxyAccessibleWrap.h153
-rw-r--r--accessible/android/RootAccessibleWrap.cpp98
-rw-r--r--accessible/android/RootAccessibleWrap.h39
-rw-r--r--accessible/android/SessionAccessibility.cpp467
-rw-r--r--accessible/android/SessionAccessibility.h118
-rw-r--r--accessible/android/TextLeafAccessibleWrap.h19
-rw-r--r--accessible/android/TraversalRule.cpp292
-rw-r--r--accessible/android/TraversalRule.h56
-rw-r--r--accessible/android/XULListboxAccessibleWrap.h20
-rw-r--r--accessible/android/XULMenuAccessibleWrap.h19
-rw-r--r--accessible/android/XULTreeGridAccessibleWrap.h20
-rw-r--r--accessible/android/moz.build42
23 files changed, 3310 insertions, 0 deletions
diff --git a/accessible/android/ARIAGridAccessibleWrap.h b/accessible/android/ARIAGridAccessibleWrap.h
new file mode 100644
index 0000000000..f6380f2b2e
--- /dev/null
+++ b/accessible/android/ARIAGridAccessibleWrap.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#ifndef MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H
+#define MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H
+
+#include "ARIAGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ARIAGridAccessible ARIAGridAccessibleWrap;
+typedef class ARIAGridCellAccessible ARIAGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/AccessibleWrap.cpp b/accessible/android/AccessibleWrap.cpp
new file mode 100644
index 0000000000..dc82e85e56
--- /dev/null
+++ b/accessible/android/AccessibleWrap.cpp
@@ -0,0 +1,942 @@
+/* -*- 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 "Accessible-inl.h"
+#include "HyperTextAccessible-inl.h"
+#include "AccEvent.h"
+#include "AndroidInputType.h"
+#include "DocAccessibleWrap.h"
+#include "IDSet.h"
+#include "SessionAccessibility.h"
+#include "TextLeafAccessible.h"
+#include "TraversalRule.h"
+#include "Pivot.h"
+#include "Platform.h"
+#include "nsAccessibilityService.h"
+#include "nsEventShell.h"
+#include "nsPersistentProperties.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"
+
+// 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;
+
+// IDs should be a positive 32bit integer.
+IDSet sIDSet(31UL);
+
+//-----------------------------------------------------
+// construction
+//-----------------------------------------------------
+AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
+ : Accessible(aContent, aDoc) {
+ if (aDoc) {
+ mID = AcquireID();
+ DocAccessibleWrap* doc = static_cast<DocAccessibleWrap*>(aDoc);
+ doc->AddID(mID, 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_FOCUS: {
+ if (DocAccessibleWrap* topContentDoc =
+ doc->GetTopLevelContentDoc(accessible)) {
+ topContentDoc->CacheFocusPath(accessible);
+ }
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
+ AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
+ auto newPosition =
+ static_cast<AccessibleWrap*>(vcEvent->NewAccessible());
+ if (newPosition) {
+ if (DocAccessibleWrap* topContentDoc =
+ doc->GetTopLevelContentDoc(accessible)) {
+ topContentDoc->CacheFocusPath(newPosition);
+ }
+ }
+ break;
+ }
+ case nsIAccessibleEvent::EVENT_REORDER: {
+ if (DocAccessibleWrap* topContentDoc =
+ doc->GetTopLevelContentDoc(accessible)) {
+ topContentDoc->CacheViewport(true);
+ }
+ break;
+ }
+ 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 (Accessible* 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 = Accessible::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 (!nsCoreUtils::IsContentDocument(doc->DocumentNode())) {
+ 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;
+ }
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+void AccessibleWrap::Shutdown() {
+ if (mDoc) {
+ if (mID > 0) {
+ if (auto doc = static_cast<DocAccessibleWrap*>(mDoc.get())) {
+ doc->RemoveID(mID);
+ }
+ ReleaseID(mID);
+ mID = 0;
+ }
+ }
+
+ Accessible::Shutdown();
+}
+
+bool AccessibleWrap::DoAction(uint8_t aIndex) const {
+ if (ActionCount()) {
+ return Accessible::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;
+}
+
+int32_t AccessibleWrap::AcquireID() { return sIDSet.GetID(); }
+
+void AccessibleWrap::ReleaseID(int32_t aID) { sIDSet.ReleaseID(aID); }
+
+void AccessibleWrap::SetTextContents(const nsAString& aText) {
+ if (IsHyperText()) {
+ AsHyperText()->ReplaceText(aText);
+ }
+}
+
+void AccessibleWrap::GetTextContents(nsAString& aText) {
+ // For now it is a simple wrapper for getting entire range of TextSubstring.
+ // In the future this may be smarter and retrieve a flattened string.
+ if (IsHyperText()) {
+ AsHyperText()->TextSubstring(0, -1, aText);
+ } else if (IsTextLeaf()) {
+ aText = AsTextLeaf()->Text();
+ }
+}
+
+bool AccessibleWrap::GetSelectionBounds(int32_t* aStartOffset,
+ int32_t* aEndOffset) {
+ if (IsHyperText()) {
+ return AsHyperText()->SelectionBoundsAt(0, aStartOffset, aEndOffset);
+ }
+
+ return false;
+}
+
+void AccessibleWrap::PivotTo(int32_t aGranularity, bool aForward,
+ bool aInclusive) {
+ AccessibleOrProxy accOrProxyRoot = AccessibleOrProxy(RootAccessible());
+ a11y::Pivot pivot(accOrProxyRoot);
+ TraversalRule rule(aGranularity);
+ AccessibleOrProxy accOrProxy = AccessibleOrProxy(this);
+ Accessible* result =
+ aForward ? pivot.Next(accOrProxy, rule, aInclusive).AsAccessible()
+ : pivot.Prev(accOrProxy, rule, aInclusive).AsAccessible();
+ if (result && (result != this || aInclusive)) {
+ PivotMoveReason reason = aForward ? nsIAccessiblePivot::REASON_NEXT
+ : nsIAccessiblePivot::REASON_PREV;
+ RefPtr<AccEvent> event = new AccVCChangeEvent(
+ result->Document(), this, -1, -1, result, -1, -1, reason,
+ nsIAccessiblePivot::NO_BOUNDARY, eFromUserInput);
+ nsEventShell::FireEvent(event);
+ }
+}
+
+void AccessibleWrap::ExploreByTouch(float aX, float aY) {
+ a11y::Pivot pivot(RootAccessible());
+ TraversalRule rule;
+
+ Accessible* result = pivot.AtPoint(aX, aY, rule).AsAccessible();
+
+ 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* newAnchor = nullptr;
+ if (aForward) {
+ newAnchor = pivot.NextText(this, &start, &end, pivotGranularity);
+ newOffset = end;
+ } else {
+ newAnchor = pivot.PrevText(this, &start, &end, pivotGranularity);
+ newOffset = start;
+ }
+
+ if (newAnchor && (start != aStartOffset || end != aEndOffset)) {
+ if (IsTextLeaf() && newAnchor == Parent()) {
+ // 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,
+ nsIPersistentProperties* aAttributes,
+ nsAString& aGeckoRole,
+ nsAString& aRoleDescription) {
+ if (aRole == roles::HEADING && aAttributes) {
+ // The heading level is an attribute, so we need that.
+ AutoTArray<nsString, 1> formatString;
+ nsresult rv = aAttributes->GetStringProperty("level"_ns,
+ *formatString.AppendElement());
+ if (NS_SUCCEEDED(rv) &&
+ LocalizeString("headingLevel", aRoleDescription, formatString)) {
+ return;
+ }
+ }
+
+ if ((aRole == roles::LANDMARK || aRole == roles::REGION) && aAttributes) {
+ nsAutoString xmlRoles;
+ if (NS_SUCCEEDED(
+ aAttributes->GetStringProperty("xml-roles"_ns, xmlRoles))) {
+ nsWhitespaceTokenizer tokenizer(xmlRoles);
+ while (tokenizer.hasMoreTokens()) {
+ if (LocalizeString(NS_ConvertUTF16toUTF8(tokenizer.nextToken()).get(),
+ aRoleDescription)) {
+ return;
+ }
+ }
+ }
+ }
+
+ GetAccService()->GetStringRole(aRole, aGeckoRole);
+ LocalizeString(NS_ConvertUTF16toUTF8(aGeckoRole).get(), aRoleDescription);
+}
+
+already_AddRefed<nsIPersistentProperties>
+AccessibleWrap::AttributeArrayToProperties(
+ const nsTArray<Attribute>& aAttributes) {
+ RefPtr<nsPersistentProperties> props = new nsPersistentProperties();
+ nsAutoString unused;
+
+ for (size_t i = 0; i < aAttributes.Length(); i++) {
+ props->SetStringProperty(aAttributes.ElementAt(i).Name(),
+ aAttributes.ElementAt(i).Value(), unused);
+ }
+
+ return props.forget();
+}
+
+int32_t AccessibleWrap::GetAndroidClass(role aRole) {
+#define ROLE(geckoRole, stringRole, 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::WrapperDOMNodeID(nsString& aDOMNodeID) {
+ if (mContent) {
+ nsAtom* id = mContent->GetID();
+ if (id) {
+ id->ToString(aDOMNodeID);
+ }
+ }
+}
+
+bool AccessibleWrap::WrapperRangeInfo(double* aCurVal, double* aMinVal,
+ double* aMaxVal, double* aStep) {
+ if (HasNumericValue()) {
+ *aCurVal = CurValue();
+ *aMinVal = MinValue();
+ *aMaxVal = MaxValue();
+ *aStep = Step();
+ return true;
+ }
+
+ return false;
+}
+
+mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToBundle(bool aSmall) {
+ nsAutoString name;
+ Name(name);
+ nsAutoString textValue;
+ Value(textValue);
+ nsAutoString nodeID;
+ WrapperDOMNodeID(nodeID);
+ nsAutoString description;
+ Description(description);
+
+ if (aSmall) {
+ return ToBundle(State(), Bounds(), ActionCount(), name, textValue, nodeID,
+ description);
+ }
+
+ double curValue = UnspecifiedNaN<double>();
+ double minValue = UnspecifiedNaN<double>();
+ double maxValue = UnspecifiedNaN<double>();
+ double step = UnspecifiedNaN<double>();
+ WrapperRangeInfo(&curValue, &minValue, &maxValue, &step);
+
+ nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
+
+ return ToBundle(State(), Bounds(), ActionCount(), name, textValue, nodeID,
+ description, curValue, minValue, maxValue, step, attributes);
+}
+
+mozilla::java::GeckoBundle::LocalRef AccessibleWrap::ToBundle(
+ const uint64_t aState, const nsIntRect& aBounds, const uint8_t aActionCount,
+ const nsString& aName, const nsString& aTextValue,
+ const nsString& aDOMNodeID, const nsString& aDescription,
+ const double& aCurVal, const double& aMinVal, const double& aMaxVal,
+ const double& aStep, nsIPersistentProperties* aAttributes) {
+ if (!IsProxy() && IsDefunct()) {
+ return nullptr;
+ }
+
+ GECKOBUNDLE_START(nodeInfo);
+ GECKOBUNDLE_PUT(nodeInfo, "id", java::sdk::Integer::ValueOf(VirtualViewID()));
+
+ AccessibleWrap* parent = WrapperParent();
+ GECKOBUNDLE_PUT(
+ nodeInfo, "parentId",
+ java::sdk::Integer::ValueOf(parent ? parent->VirtualViewID() : 0));
+
+ role role = WrapperRole();
+ if (role == roles::LINK && !(aState & states::LINKED)) {
+ // A link without the linked state (<a> with no href) shouldn't be presented
+ // as a link.
+ role = roles::TEXT;
+ }
+
+ uint32_t flags = GetFlags(role, aState, aActionCount);
+ GECKOBUNDLE_PUT(nodeInfo, "flags", java::sdk::Integer::ValueOf(flags));
+ GECKOBUNDLE_PUT(nodeInfo, "className",
+ java::sdk::Integer::ValueOf(AndroidClass()));
+
+ nsAutoString hint;
+ if (aState & states::EDITABLE) {
+ // An editable field's name is populated in the hint.
+ hint.Assign(aName);
+ GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aTextValue));
+ } else {
+ if (role == roles::LINK || role == roles::HEADING) {
+ GECKOBUNDLE_PUT(nodeInfo, "description", jni::StringParam(aName));
+ } else {
+ GECKOBUNDLE_PUT(nodeInfo, "text", jni::StringParam(aName));
+ }
+ }
+
+ if (!aDescription.IsEmpty()) {
+ if (!hint.IsEmpty()) {
+ // If this is an editable, the description is concatenated with a
+ // whitespace directly after the name.
+ hint.AppendLiteral(" ");
+ }
+ hint.Append(aDescription);
+ }
+
+ if ((aState & states::REQUIRED) != 0) {
+ nsAutoString requiredString;
+ if (LocalizeString("stateRequired", requiredString)) {
+ if (!hint.IsEmpty()) {
+ // If the hint is non-empty, concatenate with a comma for a brief pause.
+ hint.AppendLiteral(", ");
+ }
+ hint.Append(requiredString);
+ }
+ }
+
+ if (!hint.IsEmpty()) {
+ GECKOBUNDLE_PUT(nodeInfo, "hint", jni::StringParam(hint));
+ }
+
+ nsAutoString geckoRole;
+ nsAutoString roleDescription;
+ if (VirtualViewID() != kNoID) {
+ GetRoleDescription(role, aAttributes, geckoRole, roleDescription);
+ }
+
+ GECKOBUNDLE_PUT(nodeInfo, "roleDescription",
+ jni::StringParam(roleDescription));
+ GECKOBUNDLE_PUT(nodeInfo, "geckoRole", jni::StringParam(geckoRole));
+
+ if (!aDOMNodeID.IsEmpty()) {
+ GECKOBUNDLE_PUT(nodeInfo, "viewIdResourceName",
+ jni::StringParam(aDOMNodeID));
+ }
+
+ const int32_t data[4] = {aBounds.x, aBounds.y, aBounds.x + aBounds.width,
+ aBounds.y + aBounds.height};
+ GECKOBUNDLE_PUT(nodeInfo, "bounds", jni::IntArray::New(data, 4));
+
+ if (HasNumericValue()) {
+ GECKOBUNDLE_START(rangeInfo);
+ if (aMaxVal == 1 && aMinVal == 0) {
+ GECKOBUNDLE_PUT(rangeInfo, "type",
+ java::sdk::Integer::ValueOf(2)); // percent
+ } else if (std::round(aStep) != aStep) {
+ GECKOBUNDLE_PUT(rangeInfo, "type",
+ java::sdk::Integer::ValueOf(1)); // float
+ } else {
+ GECKOBUNDLE_PUT(rangeInfo, "type",
+ java::sdk::Integer::ValueOf(0)); // integer
+ }
+
+ if (!IsNaN(aCurVal)) {
+ GECKOBUNDLE_PUT(rangeInfo, "current", java::sdk::Double::New(aCurVal));
+ }
+ if (!IsNaN(aMinVal)) {
+ GECKOBUNDLE_PUT(rangeInfo, "min", java::sdk::Double::New(aMinVal));
+ }
+ if (!IsNaN(aMaxVal)) {
+ GECKOBUNDLE_PUT(rangeInfo, "max", java::sdk::Double::New(aMaxVal));
+ }
+
+ GECKOBUNDLE_FINISH(rangeInfo);
+ GECKOBUNDLE_PUT(nodeInfo, "rangeInfo", rangeInfo);
+ }
+
+ if (aAttributes) {
+ nsString inputTypeAttr;
+ nsAccUtils::GetAccAttr(aAttributes, nsGkAtoms::textInputType,
+ inputTypeAttr);
+ int32_t inputType = GetInputType(inputTypeAttr);
+ if (inputType) {
+ GECKOBUNDLE_PUT(nodeInfo, "inputType",
+ java::sdk::Integer::ValueOf(inputType));
+ }
+
+ nsString posinset;
+ nsresult rv = aAttributes->GetStringProperty("posinset"_ns, posinset);
+ if (NS_SUCCEEDED(rv)) {
+ int32_t rowIndex;
+ if (sscanf(NS_ConvertUTF16toUTF8(posinset).get(), "%d", &rowIndex) > 0) {
+ GECKOBUNDLE_START(collectionItemInfo);
+ GECKOBUNDLE_PUT(collectionItemInfo, "rowIndex",
+ java::sdk::Integer::ValueOf(rowIndex));
+ GECKOBUNDLE_PUT(collectionItemInfo, "columnIndex",
+ java::sdk::Integer::ValueOf(0));
+ GECKOBUNDLE_PUT(collectionItemInfo, "rowSpan",
+ java::sdk::Integer::ValueOf(1));
+ GECKOBUNDLE_PUT(collectionItemInfo, "columnSpan",
+ java::sdk::Integer::ValueOf(1));
+ GECKOBUNDLE_FINISH(collectionItemInfo);
+
+ GECKOBUNDLE_PUT(nodeInfo, "collectionItemInfo", collectionItemInfo);
+ }
+ }
+
+ nsString colSize;
+ rv = aAttributes->GetStringProperty("child-item-count"_ns, colSize);
+ if (NS_SUCCEEDED(rv)) {
+ int32_t rowCount;
+ if (sscanf(NS_ConvertUTF16toUTF8(colSize).get(), "%d", &rowCount) > 0) {
+ GECKOBUNDLE_START(collectionInfo);
+ GECKOBUNDLE_PUT(collectionInfo, "rowCount",
+ java::sdk::Integer::ValueOf(rowCount));
+ GECKOBUNDLE_PUT(collectionInfo, "columnCount",
+ java::sdk::Integer::ValueOf(1));
+
+ nsString unused;
+ rv = aAttributes->GetStringProperty("hierarchical"_ns, unused);
+ if (NS_SUCCEEDED(rv)) {
+ GECKOBUNDLE_PUT(collectionInfo, "isHierarchical",
+ java::sdk::Boolean::TRUE());
+ }
+
+ if (IsSelect()) {
+ int32_t selectionMode = (aState & states::MULTISELECTABLE) ? 2 : 1;
+ GECKOBUNDLE_PUT(collectionInfo, "selectionMode",
+ java::sdk::Integer::ValueOf(selectionMode));
+ }
+
+ GECKOBUNDLE_FINISH(collectionInfo);
+ GECKOBUNDLE_PUT(nodeInfo, "collectionInfo", collectionInfo);
+ }
+ }
+ }
+
+ bool mustPrune =
+ IsProxy() ? nsAccUtils::MustPrune(Proxy()) : nsAccUtils::MustPrune(this);
+ if (!mustPrune) {
+ auto childCount = ChildCount();
+ nsTArray<int32_t> children(childCount);
+ for (uint32_t i = 0; i < childCount; i++) {
+ auto child = static_cast<AccessibleWrap*>(GetChildAt(i));
+ children.AppendElement(child->VirtualViewID());
+ }
+
+ GECKOBUNDLE_PUT(nodeInfo, "children",
+ jni::IntArray::New(children.Elements(), children.Length()));
+ }
+
+ GECKOBUNDLE_FINISH(nodeInfo);
+
+ return nodeInfo;
+}
+
+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;
+ }
+
+ nsCOMPtr<nsIPersistentProperties> attributes = Attributes();
+ nsString live;
+ nsresult rv = attributes->GetStringProperty("container-live"_ns, live);
+ if (!NS_SUCCEEDED(rv)) {
+ return false;
+ }
+
+ uint16_t priority = live.EqualsIgnoreCase("assertive")
+ ? nsIAccessibleAnnouncementEvent::ASSERTIVE
+ : nsIAccessibleAnnouncementEvent::POLITE;
+
+ nsString atomic;
+ rv = attributes->GetStringProperty("container-atomic"_ns, atomic);
+
+ Accessible* announcementTarget = this;
+ nsAutoString announcement;
+ if (atomic.EqualsIgnoreCase("true")) {
+ Accessible* atomicAncestor = nullptr;
+ for (Accessible* parent = announcementTarget; parent;
+ parent = parent->Parent()) {
+ dom::Element* element = parent->Elm();
+ if (element &&
+ element->AttrValueIs(kNameSpaceID_None, 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;
+}
diff --git a/accessible/android/AccessibleWrap.h b/accessible/android/AccessibleWrap.h
new file mode 100644
index 0000000000..7e621177e4
--- /dev/null
+++ b/accessible/android/AccessibleWrap.h
@@ -0,0 +1,122 @@
+/* -*- 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/. */
+
+#ifndef mozilla_a11y_AccessibleWrap_h_
+#define mozilla_a11y_AccessibleWrap_h_
+
+#include "Accessible.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+#include "mozilla/java/GeckoBundleWrappers.h"
+#include "mozilla/java/SessionAccessibilityWrappers.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleWrap : public Accessible {
+ public:
+ AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
+ virtual ~AccessibleWrap();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY // TODO: Mark this as MOZ_CAN_RUN_SCRIPT
+ virtual nsresult
+ HandleAccEvent(AccEvent* aEvent) override;
+
+ virtual void Shutdown() override;
+
+ virtual bool DoAction(uint8_t aIndex) const override;
+
+ int32_t VirtualViewID() const { return mID; }
+
+ virtual void SetTextContents(const nsAString& aText);
+
+ virtual void GetTextContents(nsAString& aText);
+
+ virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset);
+
+ virtual void PivotTo(int32_t aGranularity, bool aForward, bool aInclusive);
+
+ virtual void ExploreByTouch(float aX, float aY);
+
+ virtual void NavigateText(int32_t aGranularity, int32_t aStartOffset,
+ int32_t aEndOffset, bool aForward, bool aSelect);
+
+ virtual void SetSelection(int32_t aStart, int32_t aEnd);
+
+ virtual void Cut();
+
+ virtual void Copy();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual void Paste();
+
+ mozilla::java::GeckoBundle::LocalRef ToBundle(bool aSmall = false);
+
+ mozilla::java::GeckoBundle::LocalRef ToBundle(
+ const uint64_t aState, const nsIntRect& aBounds,
+ const uint8_t aActionCount, const nsString& aName,
+ const nsString& aTextValue, const nsString& aDOMNodeID,
+ const nsString& aDescription,
+ const double& aCurVal = UnspecifiedNaN<double>(),
+ const double& aMinVal = UnspecifiedNaN<double>(),
+ const double& aMaxVal = UnspecifiedNaN<double>(),
+ const double& aStep = UnspecifiedNaN<double>(),
+ nsIPersistentProperties* aAttributes = nullptr);
+
+ virtual void WrapperDOMNodeID(nsString& aDOMNodeID);
+
+ int32_t AndroidClass() {
+ return mID == kNoID ? java::SessionAccessibility::CLASSNAME_WEBVIEW
+ : GetAndroidClass(WrapperRole());
+ }
+
+ static already_AddRefed<nsIPersistentProperties> AttributeArrayToProperties(
+ const nsTArray<Attribute>& aAttributes);
+
+ static const int32_t kNoID = -1;
+
+ protected:
+ // IDs should be a positive 32bit integer.
+ static int32_t AcquireID();
+ static void ReleaseID(int32_t aID);
+
+ static int32_t GetAndroidClass(role aRole);
+
+ static int32_t GetInputType(const nsString& aInputTypeAttr);
+
+ int32_t mID;
+
+ private:
+ virtual AccessibleWrap* WrapperParent() {
+ return static_cast<AccessibleWrap*>(Parent());
+ }
+
+ virtual bool WrapperRangeInfo(double* aCurVal, double* aMinVal,
+ double* aMaxVal, double* aStep);
+
+ virtual role WrapperRole() { return Role(); }
+
+ void GetTextEquiv(nsString& aText);
+
+ bool HandleLiveRegionEvent(AccEvent* aEvent);
+
+ void GetSelectionOrCaret(int32_t* aStartOffset, int32_t* aEndOffset);
+
+ static void GetRoleDescription(role aRole,
+ nsIPersistentProperties* aAttributes,
+ nsAString& aGeckoRole,
+ nsAString& aRoleDescription);
+
+ static uint32_t GetFlags(role aRole, uint64_t aState, uint8_t aActionCount);
+};
+
+static inline AccessibleWrap* WrapperFor(const ProxyAccessible* aProxy) {
+ return reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/ApplicationAccessibleWrap.h b/accessible/android/ApplicationAccessibleWrap.h
new file mode 100644
index 0000000000..89b07916c9
--- /dev/null
+++ b/accessible/android/ApplicationAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* 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/. */
+
+#ifndef mozilla_a11y_ApplicationAccessibleWrap_h__
+#define mozilla_a11y_ApplicationAccessibleWrap_h__
+
+#include "ApplicationAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef ApplicationAccessible ApplicationAccessibleWrap;
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/DocAccessibleWrap.cpp b/accessible/android/DocAccessibleWrap.cpp
new file mode 100644
index 0000000000..a9652c1e1b
--- /dev/null
+++ b/accessible/android/DocAccessibleWrap.cpp
@@ -0,0 +1,301 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "Accessible-inl.h"
+#include "AccessibleOrProxy.h"
+#include "DocAccessibleChild.h"
+#include "DocAccessibleWrap.h"
+#include "nsIDocShell.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsAccessibilityService.h"
+#include "nsAccUtils.h"
+#include "nsIPersistentProperties2.h"
+#include "Pivot.h"
+#include "SessionAccessibility.h"
+#include "TraversalRule.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/a11y/DocAccessiblePlatformExtChild.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+const uint32_t kCacheRefreshInterval = 500;
+
+#define UNIQUE_ID(acc) \
+ !acc || (acc->IsDoc() && acc->AsDoc()->IPCDoc()) \
+ ? 0 \
+ : reinterpret_cast<uint64_t>(acc->UniqueID())
+
+////////////////////////////////////////////////////////////////////////////////
+// DocAccessibleWrap
+////////////////////////////////////////////////////////////////////////////////
+
+DocAccessibleWrap::DocAccessibleWrap(Document* aDocument, PresShell* aPresShell)
+ : DocAccessible(aDocument, aPresShell) {
+ if (aDocument->GetBrowsingContext()->IsTopContent()) {
+ // The top-level content document gets this special ID.
+ mID = kNoID;
+ } else {
+ mID = AcquireID();
+ }
+}
+
+DocAccessibleWrap::~DocAccessibleWrap() {}
+
+AccessibleWrap* DocAccessibleWrap::GetAccessibleByID(int32_t aID) const {
+ if (AccessibleWrap* acc = mIDToAccessibleMap.Get(aID)) {
+ return acc;
+ }
+
+ // If the ID is not in the hash table, check the IDs of the child docs.
+ for (uint32_t i = 0; i < ChildDocumentCount(); i++) {
+ auto childDoc = reinterpret_cast<AccessibleWrap*>(GetChildDocumentAt(i));
+ if (childDoc->VirtualViewID() == aID) {
+ return childDoc;
+ }
+ }
+
+ return nullptr;
+}
+
+void DocAccessibleWrap::DoInitialUpdate() {
+ DocAccessible::DoInitialUpdate();
+ CacheViewport(true);
+}
+
+nsresult DocAccessibleWrap::HandleAccEvent(AccEvent* aEvent) {
+ switch (aEvent->GetEventType()) {
+ case nsIAccessibleEvent::EVENT_SCROLLING_END:
+ CacheViewport(false);
+ break;
+ case nsIAccessibleEvent::EVENT_SCROLLING:
+ UpdateFocusPathBounds();
+ break;
+ default:
+ break;
+ }
+
+ return DocAccessible::HandleAccEvent(aEvent);
+}
+
+void DocAccessibleWrap::CacheViewportCallback(nsITimer* aTimer,
+ void* aDocAccParam) {
+ RefPtr<DocAccessibleWrap> docAcc(
+ dont_AddRef(reinterpret_cast<DocAccessibleWrap*>(aDocAccParam)));
+ if (!docAcc || docAcc->HasShutdown()) {
+ return;
+ }
+
+ PresShell* presShell = docAcc->PresShellPtr();
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (!rootFrame) {
+ return;
+ }
+
+ nsTArray<nsIFrame*> frames;
+ nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
+ nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect();
+
+ nsLayoutUtils::GetFramesForArea(
+ RelativeTo{presShell->GetRootFrame()}, scrollPort, frames,
+ {nsLayoutUtils::FrameForPointOption::OnlyVisible});
+ AccessibleHashtable inViewAccs;
+ for (size_t i = 0; i < frames.Length(); i++) {
+ nsIContent* content = frames.ElementAt(i)->GetContent();
+ if (!content) {
+ continue;
+ }
+
+ Accessible* visibleAcc = docAcc->GetAccessibleOrContainer(content);
+ if (!visibleAcc) {
+ continue;
+ }
+
+ for (Accessible* acc = visibleAcc; acc && acc != docAcc->Parent();
+ acc = acc->Parent()) {
+ if (inViewAccs.Contains(acc->UniqueID())) {
+ break;
+ }
+ inViewAccs.Put(acc->UniqueID(), RefPtr{acc});
+ }
+ }
+
+ if (IPCAccessibilityActive()) {
+ DocAccessibleChild* ipcDoc = docAcc->IPCDoc();
+ nsTArray<BatchData> cacheData(inViewAccs.Count());
+ for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) {
+ Accessible* accessible = iter.Data();
+ nsAutoString name;
+ accessible->Name(name);
+ nsAutoString textValue;
+ accessible->Value(textValue);
+ nsAutoString nodeID;
+ static_cast<AccessibleWrap*>(accessible)->WrapperDOMNodeID(nodeID);
+ nsAutoString description;
+ accessible->Description(description);
+
+ cacheData.AppendElement(BatchData(
+ accessible->Document()->IPCDoc(), UNIQUE_ID(accessible),
+ accessible->State(), accessible->Bounds(), accessible->ActionCount(),
+ name, textValue, nodeID, description, UnspecifiedNaN<double>(),
+ UnspecifiedNaN<double>(), UnspecifiedNaN<double>(),
+ UnspecifiedNaN<double>(), nsTArray<Attribute>()));
+ }
+
+ ipcDoc->SendBatch(eBatch_Viewport, cacheData);
+ } else if (RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(docAcc)) {
+ nsTArray<AccessibleWrap*> accessibles(inViewAccs.Count());
+ for (auto iter = inViewAccs.Iter(); !iter.Done(); iter.Next()) {
+ accessibles.AppendElement(
+ static_cast<AccessibleWrap*>(iter.Data().get()));
+ }
+
+ sessionAcc->ReplaceViewportCache(accessibles);
+ }
+
+ if (docAcc->mCachePivotBoundaries) {
+ AccessibleOrProxy accOrProxy = AccessibleOrProxy(docAcc);
+ a11y::Pivot pivot(accOrProxy);
+ TraversalRule rule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT);
+ Accessible* first = pivot.First(rule).AsAccessible();
+ Accessible* last = pivot.Last(rule).AsAccessible();
+
+ // If first/last are null, pass the root document as pivot boundary.
+ if (IPCAccessibilityActive()) {
+ DocAccessibleChild* ipcDoc = docAcc->IPCDoc();
+ DocAccessibleChild* firstDoc =
+ first ? first->Document()->IPCDoc() : ipcDoc;
+ DocAccessibleChild* lastDoc = last ? last->Document()->IPCDoc() : ipcDoc;
+ if (ipcDoc && firstDoc && lastDoc) {
+ // One or more of the documents may not have recieved an IPC doc yet.
+ // In that case, just throw away this update. We will get a new one soon
+ // enough.
+ ipcDoc->GetPlatformExtension()->SendSetPivotBoundaries(
+ firstDoc, UNIQUE_ID(first), lastDoc, UNIQUE_ID(last));
+ }
+ } else if (RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(docAcc)) {
+ sessionAcc->UpdateAccessibleFocusBoundaries(
+ first ? static_cast<AccessibleWrap*>(first) : docAcc,
+ last ? static_cast<AccessibleWrap*>(last) : docAcc);
+ }
+
+ docAcc->mCachePivotBoundaries = false;
+ }
+
+ if (docAcc->mCacheRefreshTimer) {
+ docAcc->mCacheRefreshTimer = nullptr;
+ }
+}
+
+void DocAccessibleWrap::CacheViewport(bool aCachePivotBoundaries) {
+ mCachePivotBoundaries |= aCachePivotBoundaries;
+ if (VirtualViewID() == kNoID && !mCacheRefreshTimer) {
+ NS_NewTimerWithFuncCallback(getter_AddRefs(mCacheRefreshTimer),
+ CacheViewportCallback, this,
+ kCacheRefreshInterval, nsITimer::TYPE_ONE_SHOT,
+ "a11y::DocAccessibleWrap::CacheViewport");
+ if (mCacheRefreshTimer) {
+ NS_ADDREF_THIS(); // Kung fu death grip
+ }
+ }
+}
+
+DocAccessibleWrap* DocAccessibleWrap::GetTopLevelContentDoc(
+ AccessibleWrap* aAccessible) {
+ DocAccessibleWrap* doc =
+ static_cast<DocAccessibleWrap*>(aAccessible->Document());
+ while (doc && doc->VirtualViewID() != kNoID) {
+ doc = static_cast<DocAccessibleWrap*>(doc->ParentDocument());
+ }
+
+ return doc;
+}
+
+void DocAccessibleWrap::CacheFocusPath(AccessibleWrap* aAccessible) {
+ mFocusPath.Clear();
+ if (IPCAccessibilityActive()) {
+ DocAccessibleChild* ipcDoc = IPCDoc();
+ nsTArray<BatchData> cacheData;
+ for (AccessibleWrap* acc = aAccessible; acc && acc != this->Parent();
+ acc = static_cast<AccessibleWrap*>(acc->Parent())) {
+ nsAutoString name;
+ acc->Name(name);
+ nsAutoString textValue;
+ acc->Value(textValue);
+ nsAutoString nodeID;
+ acc->WrapperDOMNodeID(nodeID);
+ nsAutoString description;
+ acc->Description(description);
+ nsCOMPtr<nsIPersistentProperties> props = acc->Attributes();
+ nsTArray<Attribute> attributes;
+ nsAccUtils::PersistentPropertiesToArray(props, &attributes);
+ cacheData.AppendElement(
+ BatchData(acc->Document()->IPCDoc(), UNIQUE_ID(acc), acc->State(),
+ acc->Bounds(), acc->ActionCount(), name, textValue, nodeID,
+ description, acc->CurValue(), acc->MinValue(),
+ acc->MaxValue(), acc->Step(), attributes));
+ mFocusPath.Put(acc->UniqueID(), RefPtr{acc});
+ }
+
+ ipcDoc->SendBatch(eBatch_FocusPath, cacheData);
+ } else if (RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(this)) {
+ nsTArray<AccessibleWrap*> accessibles;
+ for (AccessibleWrap* acc = aAccessible; acc && acc != this->Parent();
+ acc = static_cast<AccessibleWrap*>(acc->Parent())) {
+ accessibles.AppendElement(acc);
+ mFocusPath.Put(acc->UniqueID(), RefPtr{acc});
+ }
+
+ sessionAcc->ReplaceFocusPathCache(accessibles);
+ }
+}
+
+void DocAccessibleWrap::UpdateFocusPathBounds() {
+ if (!mFocusPath.Count()) {
+ return;
+ }
+
+ if (IPCAccessibilityActive()) {
+ DocAccessibleChild* ipcDoc = IPCDoc();
+ nsTArray<BatchData> boundsData(mFocusPath.Count());
+ for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) {
+ Accessible* accessible = iter.Data();
+ if (!accessible || accessible->IsDefunct()) {
+ MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone.");
+ continue;
+ }
+
+ boundsData.AppendElement(
+ BatchData(accessible->Document()->IPCDoc(), UNIQUE_ID(accessible), 0,
+ accessible->Bounds(), 0, nsString(), nsString(), nsString(),
+ nsString(), UnspecifiedNaN<double>(),
+ UnspecifiedNaN<double>(), UnspecifiedNaN<double>(),
+ UnspecifiedNaN<double>(), nsTArray<Attribute>()));
+ }
+
+ ipcDoc->SendBatch(eBatch_BoundsUpdate, boundsData);
+ } else if (RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(this)) {
+ nsTArray<AccessibleWrap*> accessibles(mFocusPath.Count());
+ for (auto iter = mFocusPath.Iter(); !iter.Done(); iter.Next()) {
+ Accessible* accessible = iter.Data();
+ if (!accessible || accessible->IsDefunct()) {
+ MOZ_ASSERT_UNREACHABLE("Focus path cached accessible is gone.");
+ continue;
+ }
+
+ accessibles.AppendElement(static_cast<AccessibleWrap*>(accessible));
+ }
+
+ sessionAcc->UpdateCachedBounds(accessibles);
+ }
+}
+
+#undef UNIQUE_ID
diff --git a/accessible/android/DocAccessibleWrap.h b/accessible/android/DocAccessibleWrap.h
new file mode 100644
index 0000000000..5380079fe8
--- /dev/null
+++ b/accessible/android/DocAccessibleWrap.h
@@ -0,0 +1,69 @@
+/* -*- 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/. */
+
+#ifndef mozilla_a11y_DocAccessibleWrap_h__
+#define mozilla_a11y_DocAccessibleWrap_h__
+
+#include "DocAccessible.h"
+#include "nsITimer.h"
+
+namespace mozilla {
+
+class PresShell;
+
+namespace a11y {
+
+class DocAccessibleWrap : public DocAccessible {
+ public:
+ DocAccessibleWrap(Document* aDocument, PresShell* aPresShell);
+ virtual ~DocAccessibleWrap();
+
+ virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
+
+ /**
+ * Manage the mapping from id to Accessible.
+ */
+ void AddID(uint32_t aID, AccessibleWrap* aAcc) {
+ mIDToAccessibleMap.Put(aID, aAcc);
+ }
+ void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
+ AccessibleWrap* GetAccessibleByID(int32_t aID) const;
+
+ DocAccessibleWrap* GetTopLevelContentDoc(AccessibleWrap* aAccessible);
+
+ void CacheFocusPath(AccessibleWrap* aAccessible);
+
+ void CacheViewport(bool aCachePivotBoundaries);
+
+ enum {
+ eBatch_Viewport = 0,
+ eBatch_FocusPath = 1,
+ eBatch_BoundsUpdate = 2,
+ };
+
+ protected:
+ /*
+ * This provides a mapping from 32 bit id to accessible objects.
+ */
+ nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
+
+ virtual void DoInitialUpdate() override;
+
+ private:
+ void UpdateFocusPathBounds();
+
+ static void CacheViewportCallback(nsITimer* aTimer, void* aDocAccParam);
+
+ nsCOMPtr<nsITimer> mCacheRefreshTimer;
+
+ bool mCachePivotBoundaries;
+
+ AccessibleHashtable mFocusPath;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/HTMLTableAccessibleWrap.h b/accessible/android/HTMLTableAccessibleWrap.h
new file mode 100644
index 0000000000..f62e626109
--- /dev/null
+++ b/accessible/android/HTMLTableAccessibleWrap.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#ifndef mozilla_a11y_HTMLTableAccessibleWrap_h__
+#define mozilla_a11y_HTMLTableAccessibleWrap_h__
+
+#include "HTMLTableAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HTMLTableAccessible HTMLTableAccessibleWrap;
+typedef class HTMLTableCellAccessible HTMLTableCellAccessibleWrap;
+typedef class HTMLTableHeaderCellAccessible HTMLTableHeaderCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/HyperTextAccessibleWrap.h b/accessible/android/HyperTextAccessibleWrap.h
new file mode 100644
index 0000000000..da569c8216
--- /dev/null
+++ b/accessible/android/HyperTextAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- 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/. */
+
+#ifndef mozilla_a11y_HyperTextAccessibleWrap_h__
+#define mozilla_a11y_HyperTextAccessibleWrap_h__
+
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class HyperTextAccessible HyperTextAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/ImageAccessibleWrap.h b/accessible/android/ImageAccessibleWrap.h
new file mode 100644
index 0000000000..a5621c7313
--- /dev/null
+++ b/accessible/android/ImageAccessibleWrap.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=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/. */
+
+#ifndef mozilla_a11y_ImageAccessibleWrap_h__
+#define mozilla_a11y_ImageAccessibleWrap_h__
+
+#include "ImageAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class ImageAccessible ImageAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/Platform.cpp b/accessible/android/Platform.cpp
new file mode 100644
index 0000000000..4d17d8be72
--- /dev/null
+++ b/accessible/android/Platform.cpp
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "Platform.h"
+#include "ProxyAccessibleWrap.h"
+#include "DocAccessibleWrap.h"
+#include "SessionAccessibility.h"
+#include "mozilla/a11y/ProxyAccessible.h"
+#include "nsIAccessibleEvent.h"
+#include "nsIAccessiblePivot.h"
+#include "nsIStringBundle.h"
+
+#define ROLE_STRINGS_URL "chrome://global/locale/AccessFu.properties"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+static nsIStringBundle* sStringBundle;
+
+void a11y::PlatformInit() {}
+
+void a11y::PlatformShutdown() { NS_IF_RELEASE(sStringBundle); }
+
+void a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces) {
+ AccessibleWrap* wrapper = nullptr;
+ if (aProxy->IsDoc()) {
+ wrapper = new DocProxyAccessibleWrap(aProxy->AsDoc());
+ } else {
+ wrapper = new ProxyAccessibleWrap(aProxy);
+ }
+
+ wrapper->AddRef();
+ aProxy->SetWrapper(reinterpret_cast<uintptr_t>(wrapper));
+}
+
+void a11y::ProxyDestroyed(ProxyAccessible* aProxy) {
+ AccessibleWrap* wrapper =
+ reinterpret_cast<AccessibleWrap*>(aProxy->GetWrapper());
+
+ // If aProxy is a document that was created, but
+ // RecvPDocAccessibleConstructor failed then aProxy->GetWrapper() will be
+ // null.
+ if (!wrapper) {
+ return;
+ }
+
+ wrapper->Shutdown();
+ aProxy->SetWrapper(0);
+ wrapper->Release();
+}
+
+void a11y::ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType) {
+ RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+ if (!sessionAcc) {
+ return;
+ }
+
+ switch (aEventType) {
+ case nsIAccessibleEvent::EVENT_FOCUS:
+ sessionAcc->SendFocusEvent(WrapperFor(aTarget));
+ break;
+ }
+}
+
+void a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t aState,
+ bool aEnabled) {
+ RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (!sessionAcc) {
+ return;
+ }
+
+ if (aState & states::CHECKED) {
+ sessionAcc->SendClickedEvent(
+ WrapperFor(aTarget),
+ java::SessionAccessibility::FLAG_CHECKABLE |
+ (aEnabled ? java::SessionAccessibility::FLAG_CHECKED : 0));
+ }
+
+ if (aState & states::EXPANDED) {
+ sessionAcc->SendClickedEvent(
+ WrapperFor(aTarget),
+ java::SessionAccessibility::FLAG_EXPANDABLE |
+ (aEnabled ? java::SessionAccessibility::FLAG_EXPANDED : 0));
+ }
+
+ if (aState & states::SELECTED) {
+ sessionAcc->SendSelectedEvent(WrapperFor(aTarget), aEnabled);
+ }
+
+ if (aState & states::BUSY) {
+ sessionAcc->SendWindowStateChangedEvent(WrapperFor(aTarget));
+ }
+}
+
+void a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset,
+ bool aIsSelectionCollapsed) {
+ RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (sessionAcc) {
+ sessionAcc->SendTextSelectionChangedEvent(WrapperFor(aTarget), aOffset);
+ }
+}
+
+void a11y::ProxyTextChangeEvent(ProxyAccessible* aTarget, const nsString& aStr,
+ int32_t aStart, uint32_t aLen, bool aIsInsert,
+ bool aFromUser) {
+ RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (sessionAcc) {
+ sessionAcc->SendTextChangedEvent(WrapperFor(aTarget), aStr, aStart, aLen,
+ aIsInsert, aFromUser);
+ }
+}
+
+void a11y::ProxyShowHideEvent(ProxyAccessible* aTarget,
+ ProxyAccessible* aParent, bool aInsert,
+ bool aFromUser) {
+ // We rely on the window content changed events to be dispatched
+ // after the viewport cache is refreshed.
+}
+
+void a11y::ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t) {}
+
+void a11y::ProxyVirtualCursorChangeEvent(
+ ProxyAccessible* aTarget, ProxyAccessible* aOldPosition,
+ int32_t aOldStartOffset, int32_t aOldEndOffset,
+ ProxyAccessible* aNewPosition, int32_t aNewStartOffset,
+ int32_t aNewEndOffset, int16_t aReason, int16_t aBoundaryType,
+ bool aFromUser) {
+ if (!aNewPosition || !aFromUser) {
+ return;
+ }
+
+ RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (!sessionAcc) {
+ return;
+ }
+
+ if (aReason == nsIAccessiblePivot::REASON_POINT) {
+ sessionAcc->SendHoverEnterEvent(WrapperFor(aNewPosition));
+ } else if (aBoundaryType == nsIAccessiblePivot::NO_BOUNDARY) {
+ RefPtr<AccessibleWrap> wrapperForNewPosition = WrapperFor(aNewPosition);
+ sessionAcc->SendAccessibilityFocusedEvent(wrapperForNewPosition);
+ }
+
+ if (aBoundaryType != nsIAccessiblePivot::NO_BOUNDARY) {
+ sessionAcc->SendTextTraversedEvent(WrapperFor(aNewPosition),
+ aNewStartOffset, aNewEndOffset);
+ }
+}
+
+void a11y::ProxyScrollingEvent(ProxyAccessible* aTarget, uint32_t aEventType,
+ uint32_t aScrollX, uint32_t aScrollY,
+ uint32_t aMaxScrollX, uint32_t aMaxScrollY) {
+ if (aEventType == nsIAccessibleEvent::EVENT_SCROLLING) {
+ RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (sessionAcc) {
+ sessionAcc->SendScrollingEvent(WrapperFor(aTarget), aScrollX, aScrollY,
+ aMaxScrollX, aMaxScrollY);
+ }
+ }
+}
+
+void a11y::ProxyAnnouncementEvent(ProxyAccessible* aTarget,
+ const nsString& aAnnouncement,
+ uint16_t aPriority) {
+ RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(aTarget);
+
+ if (sessionAcc) {
+ sessionAcc->SendAnnouncementEvent(WrapperFor(aTarget), aAnnouncement,
+ aPriority);
+ }
+}
+
+void a11y::ProxyBatch(ProxyAccessible* aDocument, const uint64_t aBatchType,
+ const nsTArray<ProxyAccessible*>& aAccessibles,
+ const nsTArray<BatchData>& aData) {
+ RefPtr<SessionAccessibility> sessionAcc =
+ SessionAccessibility::GetInstanceFor(aDocument);
+ if (!sessionAcc) {
+ return;
+ }
+
+ nsTArray<AccessibleWrap*> accWraps(aAccessibles.Length());
+ for (size_t i = 0; i < aAccessibles.Length(); i++) {
+ accWraps.AppendElement(WrapperFor(aAccessibles.ElementAt(i)));
+ }
+
+ switch (aBatchType) {
+ case DocAccessibleWrap::eBatch_Viewport:
+ sessionAcc->ReplaceViewportCache(accWraps, aData);
+ break;
+ case DocAccessibleWrap::eBatch_FocusPath:
+ sessionAcc->ReplaceFocusPathCache(accWraps, aData);
+ break;
+ case DocAccessibleWrap::eBatch_BoundsUpdate:
+ sessionAcc->UpdateCachedBounds(accWraps, aData);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown batch type.");
+ break;
+ }
+}
+
+bool a11y::LocalizeString(const char* aToken, nsAString& aLocalized,
+ const nsTArray<nsString>& aFormatString) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ nsresult rv = NS_OK;
+ if (!sStringBundle) {
+ nsCOMPtr<nsIStringBundleService> sbs = services::GetStringBundleService();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to get string bundle service");
+ return false;
+ }
+
+ nsCOMPtr<nsIStringBundle> sb;
+ rv = sbs->CreateBundle(ROLE_STRINGS_URL, getter_AddRefs(sb));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to get string bundle");
+ return false;
+ }
+
+ sb.forget(&sStringBundle);
+ }
+
+ MOZ_ASSERT(sStringBundle);
+
+ if (aFormatString.Length()) {
+ rv = sStringBundle->FormatStringFromName(aToken, aFormatString, aLocalized);
+ if (NS_SUCCEEDED(rv)) {
+ return true;
+ }
+ } else {
+ rv = sStringBundle->GetStringFromName(aToken, aLocalized);
+ if (NS_SUCCEEDED(rv)) {
+ return true;
+ }
+ }
+
+ NS_WARNING("Failed to localize string");
+ aLocalized.AssignLiteral("");
+ return false;
+}
diff --git a/accessible/android/ProxyAccessibleWrap.cpp b/accessible/android/ProxyAccessibleWrap.cpp
new file mode 100644
index 0000000000..d8ab49e6d1
--- /dev/null
+++ b/accessible/android/ProxyAccessibleWrap.cpp
@@ -0,0 +1,172 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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 "ProxyAccessibleWrap.h"
+#include "Accessible-inl.h"
+
+#include "nsPersistentProperties.h"
+
+#include "mozilla/a11y/DocAccessiblePlatformExtParent.h"
+
+using namespace mozilla::a11y;
+
+ProxyAccessibleWrap::ProxyAccessibleWrap(ProxyAccessible* aProxy)
+ : AccessibleWrap(nullptr, nullptr) {
+ mType = eProxyType;
+ mBits.proxy = aProxy;
+
+ if (aProxy->mHasValue) {
+ mStateFlags |= eHasNumericValue;
+ }
+
+ if (aProxy->mIsSelection) {
+ mGenericTypes |= eSelect;
+ }
+
+ if (aProxy->mIsHyperText) {
+ mGenericTypes |= eHyperText;
+ }
+
+ auto doc = reinterpret_cast<DocProxyAccessibleWrap*>(
+ Proxy()->Document()->GetWrapper());
+ if (doc) {
+ mID = AcquireID();
+ doc->AddID(mID, this);
+ }
+}
+
+void ProxyAccessibleWrap::Shutdown() {
+ auto doc = reinterpret_cast<DocProxyAccessibleWrap*>(
+ Proxy()->Document()->GetWrapper());
+ if (mID && doc) {
+ doc->RemoveID(mID);
+ ReleaseID(mID);
+ mID = 0;
+ }
+
+ mBits.proxy = nullptr;
+ mStateFlags |= eIsDefunct;
+}
+
+// Accessible
+
+already_AddRefed<nsIPersistentProperties> ProxyAccessibleWrap::Attributes() {
+ AutoTArray<Attribute, 10> attrs;
+ Proxy()->Attributes(&attrs);
+ return AttributeArrayToProperties(attrs);
+}
+
+uint32_t ProxyAccessibleWrap::ChildCount() const {
+ return Proxy()->ChildrenCount();
+}
+
+Accessible* ProxyAccessibleWrap::GetChildAt(uint32_t aIndex) const {
+ ProxyAccessible* child = Proxy()->ChildAt(aIndex);
+ return child ? WrapperFor(child) : nullptr;
+}
+
+ENameValueFlag ProxyAccessibleWrap::Name(nsString& aName) const {
+ Proxy()->Name(aName);
+ return eNameOK;
+}
+
+void ProxyAccessibleWrap::Value(nsString& aValue) const {
+ Proxy()->Value(aValue);
+}
+
+uint64_t ProxyAccessibleWrap::State() { return Proxy()->State(); }
+
+nsIntRect ProxyAccessibleWrap::Bounds() const { return Proxy()->Bounds(); }
+
+void ProxyAccessibleWrap::ScrollTo(uint32_t aHow) const {
+ Proxy()->ScrollTo(aHow);
+}
+
+uint8_t ProxyAccessibleWrap::ActionCount() const {
+ return Proxy()->ActionCount();
+}
+
+bool ProxyAccessibleWrap::DoAction(uint8_t aIndex) const {
+ return Proxy()->DoAction(aIndex);
+}
+
+// Other
+
+void ProxyAccessibleWrap::SetTextContents(const nsAString& aText) {
+ Proxy()->ReplaceText(PromiseFlatString(aText));
+}
+
+void ProxyAccessibleWrap::GetTextContents(nsAString& aText) {
+ nsAutoString text;
+ Proxy()->TextSubstring(0, -1, text);
+ aText.Assign(text);
+}
+
+bool ProxyAccessibleWrap::GetSelectionBounds(int32_t* aStartOffset,
+ int32_t* aEndOffset) {
+ nsAutoString unused;
+ return Proxy()->SelectionBoundsAt(0, unused, aStartOffset, aEndOffset);
+}
+
+void ProxyAccessibleWrap::PivotTo(int32_t aGranularity, bool aForward,
+ bool aInclusive) {
+ Unused << Proxy()->Document()->GetPlatformExtension()->SendPivot(
+ Proxy()->ID(), aGranularity, aForward, aInclusive);
+}
+
+void ProxyAccessibleWrap::ExploreByTouch(float aX, float aY) {
+ Unused << Proxy()->Document()->GetPlatformExtension()->SendExploreByTouch(
+ Proxy()->ID(), aX, aY);
+}
+
+void ProxyAccessibleWrap::NavigateText(int32_t aGranularity,
+ int32_t aStartOffset, int32_t aEndOffset,
+ bool aForward, bool aSelect) {
+ Unused << Proxy()->Document()->GetPlatformExtension()->SendNavigateText(
+ Proxy()->ID(), aGranularity, aStartOffset, aEndOffset, aForward, aSelect);
+}
+
+void ProxyAccessibleWrap::SetSelection(int32_t aStart, int32_t aEnd) {
+ Unused << Proxy()->Document()->GetPlatformExtension()->SendSetSelection(
+ Proxy()->ID(), aStart, aEnd);
+}
+
+void ProxyAccessibleWrap::Cut() {
+ Unused << Proxy()->Document()->GetPlatformExtension()->SendCut(Proxy()->ID());
+}
+
+void ProxyAccessibleWrap::Copy() {
+ Unused << Proxy()->Document()->GetPlatformExtension()->SendCopy(
+ Proxy()->ID());
+}
+
+void ProxyAccessibleWrap::Paste() {
+ Unused << Proxy()->Document()->GetPlatformExtension()->SendPaste(
+ Proxy()->ID());
+}
+
+role ProxyAccessibleWrap::WrapperRole() { return Proxy()->Role(); }
+
+AccessibleWrap* ProxyAccessibleWrap::WrapperParent() {
+ return Proxy()->Parent() ? WrapperFor(Proxy()->Parent()) : nullptr;
+}
+
+bool ProxyAccessibleWrap::WrapperRangeInfo(double* aCurVal, double* aMinVal,
+ double* aMaxVal, double* aStep) {
+ if (HasNumericValue()) {
+ ProxyAccessible* proxy = Proxy();
+ *aCurVal = proxy->CurValue();
+ *aMinVal = proxy->MinValue();
+ *aMaxVal = proxy->MaxValue();
+ *aStep = proxy->Step();
+ return true;
+ }
+
+ return false;
+}
+
+void ProxyAccessibleWrap::WrapperDOMNodeID(nsString& aDOMNodeID) {
+ Proxy()->DOMNodeID(aDOMNodeID);
+}
diff --git a/accessible/android/ProxyAccessibleWrap.h b/accessible/android/ProxyAccessibleWrap.h
new file mode 100644
index 0000000000..40286fcc45
--- /dev/null
+++ b/accessible/android/ProxyAccessibleWrap.h
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. *
+ */
+
+#ifndef MOZILLA_A11Y_ProxyAccessibleWrap_h
+#define MOZILLA_A11Y_ProxyAccessibleWrap_h
+
+#include "AccessibleWrap.h"
+#include "DocAccessibleParent.h"
+
+namespace mozilla {
+namespace a11y {
+
+/**
+ * A wrapper for Accessible proxies. The public methods here should be overriden
+ * from AccessibleWrap or its super classes.
+ * This gives us an abstraction layer so SessionAccessibility doesn't have
+ * to distinguish between a local or remote accessibles.
+ * NOTE: This shouldn't be regarded as a full Accessible implementation.
+ */
+class ProxyAccessibleWrap : public AccessibleWrap {
+ public:
+ explicit ProxyAccessibleWrap(ProxyAccessible* aProxy);
+
+ virtual void Shutdown() override;
+
+ // Accessible
+
+ virtual already_AddRefed<nsIPersistentProperties> Attributes() override;
+
+ virtual uint32_t ChildCount() const override;
+
+ virtual Accessible* GetChildAt(uint32_t aIndex) const override;
+
+ virtual ENameValueFlag Name(nsString& aName) const override;
+
+ virtual void Value(nsString& aValue) const override;
+
+ virtual uint64_t State() override;
+
+ virtual nsIntRect Bounds() const override;
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual void ScrollTo(uint32_t aHow) const override;
+
+ virtual uint8_t ActionCount() const override;
+
+ virtual bool DoAction(uint8_t aIndex) const override;
+
+ // AccessibleWrap
+
+ virtual void SetTextContents(const nsAString& aText) override;
+
+ virtual void GetTextContents(nsAString& aText) override;
+
+ virtual bool GetSelectionBounds(int32_t* aStartOffset,
+ int32_t* aEndOffset) override;
+
+ virtual void PivotTo(int32_t aGranularity, bool aForward,
+ bool aInclusive) override;
+
+ virtual void NavigateText(int32_t aGranularity, int32_t aStartOffset,
+ int32_t aEndOffset, bool aForward,
+ bool aSelect) override;
+
+ virtual void SetSelection(int32_t aStart, int32_t aEnd) override;
+
+ virtual void Cut() override;
+
+ virtual void Copy() override;
+
+ virtual void Paste() override;
+
+ virtual void ExploreByTouch(float aX, float aY) override;
+
+ virtual void WrapperDOMNodeID(nsString& aDOMNodeID) override;
+
+ private:
+ virtual role WrapperRole() override;
+
+ virtual AccessibleWrap* WrapperParent() override;
+
+ virtual bool WrapperRangeInfo(double* aCurVal, double* aMinVal,
+ double* aMaxVal, double* aStep) override;
+};
+
+class DocProxyAccessibleWrap : public ProxyAccessibleWrap {
+ public:
+ explicit DocProxyAccessibleWrap(DocAccessibleParent* aProxy)
+ : ProxyAccessibleWrap(aProxy) {
+ mGenericTypes |= eDocument;
+
+ if (aProxy->IsTopLevel()) {
+ mID = kNoID;
+ } else {
+ mID = AcquireID();
+ }
+ }
+
+ virtual void Shutdown() override {
+ if (mID) {
+ auto doc = static_cast<DocAccessibleParent*>(Proxy());
+ if (!doc->IsTopLevel()) {
+ MOZ_ASSERT(mID != kNoID, "A non root accessible must have an id");
+ ReleaseID(mID);
+ }
+ }
+ mID = 0;
+ mBits.proxy = nullptr;
+ mStateFlags |= eIsDefunct;
+ }
+
+ DocProxyAccessibleWrap* ParentDocument() {
+ DocAccessibleParent* proxy = static_cast<DocAccessibleParent*>(Proxy());
+ MOZ_ASSERT(proxy);
+ if (DocAccessibleParent* parent = proxy->ParentDoc()) {
+ return reinterpret_cast<DocProxyAccessibleWrap*>(parent->GetWrapper());
+ }
+
+ return nullptr;
+ }
+
+ DocProxyAccessibleWrap* GetChildDocumentAt(uint32_t aIndex) {
+ auto doc = Proxy()->AsDoc();
+ if (doc && doc->ChildDocCount() > aIndex) {
+ return reinterpret_cast<DocProxyAccessibleWrap*>(
+ doc->ChildDocAt(aIndex)->GetWrapper());
+ }
+
+ return nullptr;
+ }
+
+ void AddID(uint32_t aID, AccessibleWrap* aAcc) {
+ mIDToAccessibleMap.Put(aID, aAcc);
+ }
+ void RemoveID(uint32_t aID) { mIDToAccessibleMap.Remove(aID); }
+ AccessibleWrap* GetAccessibleByID(uint32_t aID) const {
+ return mIDToAccessibleMap.Get(aID);
+ }
+
+ private:
+ /*
+ * This provides a mapping from 32 bit id to accessible objects.
+ */
+ nsDataHashtable<nsUint32HashKey, AccessibleWrap*> mIDToAccessibleMap;
+};
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/RootAccessibleWrap.cpp b/accessible/android/RootAccessibleWrap.cpp
new file mode 100644
index 0000000000..6a9430da25
--- /dev/null
+++ b/accessible/android/RootAccessibleWrap.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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 "RootAccessibleWrap.h"
+
+#include "Accessible-inl.h"
+#include "AccessibleOrProxy.h"
+#include "DocAccessibleParent.h"
+#include "DocAccessible-inl.h"
+#include "ProxyAccessibleWrap.h"
+#include "SessionAccessibility.h"
+#include "mozilla/PresShell.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+RootAccessibleWrap::RootAccessibleWrap(dom::Document* aDoc,
+ PresShell* aPresShell)
+ : RootAccessible(aDoc, aPresShell) {}
+
+RootAccessibleWrap::~RootAccessibleWrap() {}
+
+AccessibleWrap* RootAccessibleWrap::GetContentAccessible() {
+ if (ProxyAccessible* proxy = GetPrimaryRemoteTopLevelContentDoc()) {
+ return WrapperFor(proxy);
+ }
+
+ // Find first document that is not defunct or hidden.
+ // This is exclusively for Fennec which has a deck of browser elements.
+ // Otherwise, standard GeckoView will only have one browser element.
+ for (size_t i = 0; i < ChildDocumentCount(); i++) {
+ DocAccessible* childDoc = GetChildDocumentAt(i);
+ if (childDoc && !childDoc->IsDefunct() && !childDoc->IsHidden()) {
+ return childDoc;
+ }
+ }
+
+ return nullptr;
+}
+
+AccessibleWrap* RootAccessibleWrap::FindAccessibleById(int32_t aID) {
+ AccessibleWrap* contentAcc = GetContentAccessible();
+
+ if (!contentAcc) {
+ return nullptr;
+ }
+
+ if (aID == AccessibleWrap::kNoID) {
+ return contentAcc;
+ }
+
+ if (contentAcc->IsProxy()) {
+ return FindAccessibleById(static_cast<DocProxyAccessibleWrap*>(contentAcc),
+ aID);
+ }
+
+ return FindAccessibleById(
+ static_cast<DocAccessibleWrap*>(contentAcc->AsDoc()), aID);
+}
+
+AccessibleWrap* RootAccessibleWrap::FindAccessibleById(
+ DocProxyAccessibleWrap* aDoc, int32_t aID) {
+ AccessibleWrap* acc = aDoc->GetAccessibleByID(aID);
+ uint32_t index = 0;
+ while (!acc) {
+ auto child =
+ static_cast<DocProxyAccessibleWrap*>(aDoc->GetChildDocumentAt(index++));
+ if (!child) {
+ break;
+ }
+ // A child document's id is not in its parent document's hash table.
+ if (child->VirtualViewID() == aID) {
+ acc = child;
+ } else {
+ acc = FindAccessibleById(child, aID);
+ }
+ }
+
+ return acc;
+}
+
+AccessibleWrap* RootAccessibleWrap::FindAccessibleById(DocAccessibleWrap* aDoc,
+ int32_t aID) {
+ AccessibleWrap* acc = aDoc->GetAccessibleByID(aID);
+ uint32_t index = 0;
+ while (!acc) {
+ auto child =
+ static_cast<DocAccessibleWrap*>(aDoc->GetChildDocumentAt(index++));
+ if (!child) {
+ break;
+ }
+ acc = FindAccessibleById(child, aID);
+ }
+
+ return acc;
+}
diff --git a/accessible/android/RootAccessibleWrap.h b/accessible/android/RootAccessibleWrap.h
new file mode 100644
index 0000000000..6e782dbda8
--- /dev/null
+++ b/accessible/android/RootAccessibleWrap.h
@@ -0,0 +1,39 @@
+/* -*- 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/. */
+
+#ifndef mozilla_a11y_RootAccessibleWrap_h__
+#define mozilla_a11y_RootAccessibleWrap_h__
+
+#include "RootAccessible.h"
+
+namespace mozilla {
+
+class PresShell;
+
+namespace a11y {
+
+class DocProxyAccessibleWrap;
+
+class RootAccessibleWrap : public RootAccessible {
+ public:
+ RootAccessibleWrap(dom::Document* aDocument, PresShell* aPresShell);
+ virtual ~RootAccessibleWrap();
+
+ AccessibleWrap* GetContentAccessible();
+
+ AccessibleWrap* FindAccessibleById(int32_t aID);
+
+ // Recursively searches for the accessible ID within the document tree.
+ AccessibleWrap* FindAccessibleById(DocAccessibleWrap* aDocument, int32_t aID);
+
+ // Recursively searches for the accessible ID within the proxy document tree.
+ AccessibleWrap* FindAccessibleById(DocProxyAccessibleWrap* aDocument,
+ int32_t aID);
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/SessionAccessibility.cpp b/accessible/android/SessionAccessibility.cpp
new file mode 100644
index 0000000000..2aa90e575e
--- /dev/null
+++ b/accessible/android/SessionAccessibility.cpp
@@ -0,0 +1,467 @@
+/* -*- Mode: c++; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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 "SessionAccessibility.h"
+#include "Accessible-inl.h"
+#include "AndroidUiThread.h"
+#include "DocAccessibleParent.h"
+#include "nsThreadUtils.h"
+#include "AccessibilityEvent.h"
+#include "HyperTextAccessible.h"
+#include "JavaBuiltins.h"
+#include "RootAccessibleWrap.h"
+#include "nsAccessibilityService.h"
+#include "nsViewManager.h"
+#include "nsIPersistentProperties2.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "mozilla/a11y/DocManager.h"
+#include "mozilla/jni/GeckoBundleUtils.h"
+#include "mozilla/widget/GeckoViewSupport.h"
+
+#ifdef DEBUG
+# include <android/log.h>
+# define AALOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "GeckoAccessibilityNative", ##args)
+#else
+# define AALOG(args...) \
+ do { \
+ } while (0)
+#endif
+
+#define FORWARD_ACTION_TO_ACCESSIBLE(funcname, ...) \
+ if (RootAccessibleWrap* rootAcc = GetRoot()) { \
+ AccessibleWrap* acc = rootAcc->FindAccessibleById(aID); \
+ if (!acc) { \
+ return; \
+ } \
+ \
+ acc->funcname(__VA_ARGS__); \
+ }
+
+using namespace mozilla::a11y;
+
+class Settings final
+ : public mozilla::java::SessionAccessibility::Settings::Natives<Settings> {
+ public:
+ static void ToggleNativeAccessibility(bool aEnable) {
+ if (aEnable) {
+ GetOrCreateAccService();
+ } else {
+ MaybeShutdownAccService(nsAccessibilityService::ePlatformAPI);
+ }
+ }
+};
+
+SessionAccessibility::SessionAccessibility(
+ jni::NativeWeakPtr<widget::GeckoViewSupport> aWindow,
+ java::SessionAccessibility::NativeProvider::Param aSessionAccessibility)
+ : mWindow(aWindow), mSessionAccessibility(aSessionAccessibility) {
+ SetAttached(true, nullptr);
+}
+
+void SessionAccessibility::SetAttached(bool aAttached,
+ already_AddRefed<Runnable> aRunnable) {
+ if (RefPtr<nsThread> uiThread = GetAndroidUiThread()) {
+ uiThread->Dispatch(NS_NewRunnableFunction(
+ "SessionAccessibility::Attach",
+ [aAttached,
+ sa = java::SessionAccessibility::NativeProvider::GlobalRef(
+ mSessionAccessibility),
+ runnable = RefPtr<Runnable>(aRunnable)] {
+ sa->SetAttached(aAttached);
+ if (runnable) {
+ runnable->Run();
+ }
+ }));
+ }
+}
+
+void SessionAccessibility::Init() {
+ java::SessionAccessibility::NativeProvider::Natives<
+ SessionAccessibility>::Init();
+ Settings::Init();
+}
+
+mozilla::jni::Object::LocalRef SessionAccessibility::GetNodeInfo(int32_t aID) {
+ java::GeckoBundle::GlobalRef ret = nullptr;
+ RefPtr<SessionAccessibility> self(this);
+ nsAppShell::SyncRunEvent([this, self, aID, &ret] {
+ if (RootAccessibleWrap* rootAcc = GetRoot()) {
+ AccessibleWrap* acc = rootAcc->FindAccessibleById(aID);
+ if (acc) {
+ ret = acc->ToBundle();
+ } else {
+ AALOG("oops, nothing for %d", aID);
+ }
+ }
+ });
+
+ return mozilla::jni::Object::Ref::From(ret);
+}
+
+RootAccessibleWrap* SessionAccessibility::GetRoot() {
+ auto acc(mWindow.Access());
+ if (!acc) {
+ return nullptr;
+ }
+
+ nsWindow* gkWindow = acc->GetNsWindow();
+ if (!gkWindow) {
+ return nullptr;
+ }
+
+ return static_cast<RootAccessibleWrap*>(gkWindow->GetRootAccessible());
+}
+
+void SessionAccessibility::SetText(int32_t aID, jni::String::Param aText) {
+ FORWARD_ACTION_TO_ACCESSIBLE(SetTextContents, aText->ToString());
+}
+
+void SessionAccessibility::Click(int32_t aID) {
+ FORWARD_ACTION_TO_ACCESSIBLE(DoAction, 0);
+}
+
+void SessionAccessibility::Pivot(int32_t aID, int32_t aGranularity,
+ bool aForward, bool aInclusive) {
+ FORWARD_ACTION_TO_ACCESSIBLE(PivotTo, aGranularity, aForward, aInclusive);
+}
+
+void SessionAccessibility::ExploreByTouch(int32_t aID, float aX, float aY) {
+ FORWARD_ACTION_TO_ACCESSIBLE(ExploreByTouch, aX, aY);
+}
+
+void SessionAccessibility::NavigateText(int32_t aID, int32_t aGranularity,
+ int32_t aStartOffset,
+ int32_t aEndOffset, bool aForward,
+ bool aSelect) {
+ FORWARD_ACTION_TO_ACCESSIBLE(NavigateText, aGranularity, aStartOffset,
+ aEndOffset, aForward, aSelect);
+}
+
+void SessionAccessibility::SetSelection(int32_t aID, int32_t aStart,
+ int32_t aEnd) {
+ FORWARD_ACTION_TO_ACCESSIBLE(SetSelection, aStart, aEnd);
+}
+
+void SessionAccessibility::Cut(int32_t aID) {
+ FORWARD_ACTION_TO_ACCESSIBLE(Cut);
+}
+
+void SessionAccessibility::Copy(int32_t aID) {
+ FORWARD_ACTION_TO_ACCESSIBLE(Copy);
+}
+
+void SessionAccessibility::Paste(int32_t aID) {
+ FORWARD_ACTION_TO_ACCESSIBLE(Paste);
+}
+
+RefPtr<SessionAccessibility> SessionAccessibility::GetInstanceFor(
+ ProxyAccessible* aAccessible) {
+ auto tab =
+ static_cast<dom::BrowserParent*>(aAccessible->Document()->Manager());
+ dom::Element* frame = tab->GetOwnerElement();
+ MOZ_ASSERT(frame);
+ if (!frame) {
+ return nullptr;
+ }
+
+ Accessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc());
+ return chromeDoc ? GetInstanceFor(chromeDoc) : nullptr;
+}
+
+RefPtr<SessionAccessibility> SessionAccessibility::GetInstanceFor(
+ Accessible* aAccessible) {
+ RootAccessible* rootAcc = aAccessible->RootAccessible();
+ nsViewManager* vm = rootAcc->PresShellPtr()->GetViewManager();
+ if (!vm) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIWidget> rootWidget;
+ vm->GetRootWidget(getter_AddRefs(rootWidget));
+ // `rootWidget` can be one of several types. Here we make sure it is an
+ // android nsWindow.
+ if (RefPtr<nsWindow> window = nsWindow::From(rootWidget)) {
+ return window->GetSessionAccessibility();
+ }
+
+ return nullptr;
+}
+
+void SessionAccessibility::SendAccessibilityFocusedEvent(
+ AccessibleWrap* aAccessible) {
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr);
+ aAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
+}
+
+void SessionAccessibility::SendHoverEnterEvent(AccessibleWrap* aAccessible) {
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_HOVER_ENTER,
+ aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr);
+}
+
+void SessionAccessibility::SendFocusEvent(AccessibleWrap* aAccessible) {
+ // Suppress focus events from about:blank pages.
+ // This is important for tests.
+ if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
+ return;
+ }
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_FOCUSED,
+ aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr);
+}
+
+void SessionAccessibility::SendScrollingEvent(AccessibleWrap* aAccessible,
+ int32_t aScrollX,
+ int32_t aScrollY,
+ int32_t aMaxScrollX,
+ int32_t aMaxScrollY) {
+ int32_t virtualViewId = aAccessible->VirtualViewID();
+
+ if (virtualViewId != AccessibleWrap::kNoID) {
+ // XXX: Support scrolling in subframes
+ return;
+ }
+
+ GECKOBUNDLE_START(eventInfo);
+ GECKOBUNDLE_PUT(eventInfo, "scrollX", java::sdk::Integer::ValueOf(aScrollX));
+ GECKOBUNDLE_PUT(eventInfo, "scrollY", java::sdk::Integer::ValueOf(aScrollY));
+ GECKOBUNDLE_PUT(eventInfo, "maxScrollX",
+ java::sdk::Integer::ValueOf(aMaxScrollX));
+ GECKOBUNDLE_PUT(eventInfo, "maxScrollY",
+ java::sdk::Integer::ValueOf(aMaxScrollY));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_SCROLLED, virtualViewId,
+ aAccessible->AndroidClass(), eventInfo);
+}
+
+void SessionAccessibility::SendWindowContentChangedEvent() {
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_WINDOW_CONTENT_CHANGED,
+ AccessibleWrap::kNoID, java::SessionAccessibility::CLASSNAME_WEBVIEW,
+ nullptr);
+}
+
+void SessionAccessibility::SendWindowStateChangedEvent(
+ AccessibleWrap* aAccessible) {
+ // Suppress window state changed events from about:blank pages.
+ // This is important for tests.
+ if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
+ return;
+ }
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_WINDOW_STATE_CHANGED,
+ aAccessible->VirtualViewID(), aAccessible->AndroidClass(), nullptr);
+}
+
+void SessionAccessibility::SendTextSelectionChangedEvent(
+ AccessibleWrap* aAccessible, int32_t aCaretOffset) {
+ int32_t fromIndex = aCaretOffset;
+ int32_t startSel = -1;
+ int32_t endSel = -1;
+ if (aAccessible->GetSelectionBounds(&startSel, &endSel)) {
+ fromIndex = startSel == aCaretOffset ? endSel : startSel;
+ }
+
+ GECKOBUNDLE_START(eventInfo);
+ GECKOBUNDLE_PUT(eventInfo, "fromIndex",
+ java::sdk::Integer::ValueOf(fromIndex));
+ GECKOBUNDLE_PUT(eventInfo, "toIndex",
+ java::sdk::Integer::ValueOf(aCaretOffset));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_SELECTION_CHANGED,
+ aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo);
+}
+
+void SessionAccessibility::SendTextChangedEvent(AccessibleWrap* aAccessible,
+ const nsString& aStr,
+ int32_t aStart, uint32_t aLen,
+ bool aIsInsert,
+ bool aFromUser) {
+ if (!aFromUser) {
+ // Only dispatch text change events from users, for now.
+ return;
+ }
+
+ nsAutoString text;
+ aAccessible->GetTextContents(text);
+ nsAutoString beforeText(text);
+ if (aIsInsert) {
+ beforeText.Cut(aStart, aLen);
+ } else {
+ beforeText.Insert(aStr, aStart);
+ }
+
+ GECKOBUNDLE_START(eventInfo);
+ GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
+ GECKOBUNDLE_PUT(eventInfo, "beforeText", jni::StringParam(beforeText));
+ GECKOBUNDLE_PUT(eventInfo, "addedCount",
+ java::sdk::Integer::ValueOf(aIsInsert ? aLen : 0));
+ GECKOBUNDLE_PUT(eventInfo, "removedCount",
+ java::sdk::Integer::ValueOf(aIsInsert ? 0 : aLen));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_CHANGED,
+ aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo);
+}
+
+void SessionAccessibility::SendTextTraversedEvent(AccessibleWrap* aAccessible,
+ int32_t aStartOffset,
+ int32_t aEndOffset) {
+ nsAutoString text;
+ aAccessible->GetTextContents(text);
+
+ GECKOBUNDLE_START(eventInfo);
+ GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
+ GECKOBUNDLE_PUT(eventInfo, "fromIndex",
+ java::sdk::Integer::ValueOf(aStartOffset));
+ GECKOBUNDLE_PUT(eventInfo, "toIndex",
+ java::sdk::Integer::ValueOf(aEndOffset));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::
+ TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+ aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo);
+}
+
+void SessionAccessibility::SendClickedEvent(AccessibleWrap* aAccessible,
+ uint32_t aFlags) {
+ GECKOBUNDLE_START(eventInfo);
+ GECKOBUNDLE_PUT(eventInfo, "flags", java::sdk::Integer::ValueOf(aFlags));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_CLICKED,
+ aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo);
+}
+
+void SessionAccessibility::SendSelectedEvent(AccessibleWrap* aAccessible,
+ bool aSelected) {
+ GECKOBUNDLE_START(eventInfo);
+ // Boolean::FALSE/TRUE gets clobbered by a macro, so ugh.
+ GECKOBUNDLE_PUT(eventInfo, "selected",
+ java::sdk::Integer::ValueOf(aSelected ? 1 : 0));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_VIEW_SELECTED,
+ aAccessible->VirtualViewID(), aAccessible->AndroidClass(), eventInfo);
+}
+
+void SessionAccessibility::SendAnnouncementEvent(AccessibleWrap* aAccessible,
+ const nsString& aAnnouncement,
+ uint16_t aPriority) {
+ GECKOBUNDLE_START(eventInfo);
+ GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(aAnnouncement));
+ GECKOBUNDLE_FINISH(eventInfo);
+
+ // Announcements should have the root as their source, so we ignore the
+ // accessible of the event.
+ mSessionAccessibility->SendEvent(
+ java::sdk::AccessibilityEvent::TYPE_ANNOUNCEMENT, AccessibleWrap::kNoID,
+ java::SessionAccessibility::CLASSNAME_WEBVIEW, eventInfo);
+}
+
+void SessionAccessibility::ReplaceViewportCache(
+ const nsTArray<AccessibleWrap*>& aAccessibles,
+ const nsTArray<BatchData>& aData) {
+ auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length());
+ for (size_t i = 0; i < aAccessibles.Length(); i++) {
+ AccessibleWrap* acc = aAccessibles.ElementAt(i);
+ if (!acc) {
+ MOZ_ASSERT_UNREACHABLE("Updated accessible is gone.");
+ continue;
+ }
+
+ if (aData.Length() == aAccessibles.Length()) {
+ const BatchData& data = aData.ElementAt(i);
+ auto bundle = acc->ToBundle(
+ data.State(), data.Bounds(), data.ActionCount(), data.Name(),
+ data.TextValue(), data.DOMNodeID(), data.Description());
+ infos->SetElement(i, bundle);
+ } else {
+ infos->SetElement(i, acc->ToBundle(true));
+ }
+ }
+
+ mSessionAccessibility->ReplaceViewportCache(infos);
+ SendWindowContentChangedEvent();
+}
+
+void SessionAccessibility::ReplaceFocusPathCache(
+ const nsTArray<AccessibleWrap*>& aAccessibles,
+ const nsTArray<BatchData>& aData) {
+ auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length());
+ for (size_t i = 0; i < aAccessibles.Length(); i++) {
+ AccessibleWrap* acc = aAccessibles.ElementAt(i);
+ if (!acc) {
+ MOZ_ASSERT_UNREACHABLE("Updated accessible is gone.");
+ continue;
+ }
+
+ if (aData.Length() == aAccessibles.Length()) {
+ const BatchData& data = aData.ElementAt(i);
+ nsCOMPtr<nsIPersistentProperties> props =
+ AccessibleWrap::AttributeArrayToProperties(data.Attributes());
+ auto bundle =
+ acc->ToBundle(data.State(), data.Bounds(), data.ActionCount(),
+ data.Name(), data.TextValue(), data.DOMNodeID(),
+ data.Description(), data.CurValue(), data.MinValue(),
+ data.MaxValue(), data.Step(), props);
+ infos->SetElement(i, bundle);
+ } else {
+ infos->SetElement(i, acc->ToBundle());
+ }
+ }
+
+ mSessionAccessibility->ReplaceFocusPathCache(infos);
+}
+
+void SessionAccessibility::UpdateCachedBounds(
+ const nsTArray<AccessibleWrap*>& aAccessibles,
+ const nsTArray<BatchData>& aData) {
+ auto infos = jni::ObjectArray::New<java::GeckoBundle>(aAccessibles.Length());
+ for (size_t i = 0; i < aAccessibles.Length(); i++) {
+ AccessibleWrap* acc = aAccessibles.ElementAt(i);
+ if (!acc) {
+ MOZ_ASSERT_UNREACHABLE("Updated accessible is gone.");
+ continue;
+ }
+
+ if (aData.Length() == aAccessibles.Length()) {
+ const BatchData& data = aData.ElementAt(i);
+ auto bundle = acc->ToBundle(
+ data.State(), data.Bounds(), data.ActionCount(), data.Name(),
+ data.TextValue(), data.DOMNodeID(), data.Description());
+ infos->SetElement(i, bundle);
+ } else {
+ infos->SetElement(i, acc->ToBundle(true));
+ }
+ }
+
+ mSessionAccessibility->UpdateCachedBounds(infos);
+ SendWindowContentChangedEvent();
+}
+
+void SessionAccessibility::UpdateAccessibleFocusBoundaries(
+ AccessibleWrap* aFirst, AccessibleWrap* aLast) {
+ mSessionAccessibility->UpdateAccessibleFocusBoundaries(
+ aFirst ? aFirst->VirtualViewID() : AccessibleWrap::kNoID,
+ aLast ? aLast->VirtualViewID() : AccessibleWrap::kNoID);
+}
+#undef FORWARD_ACTION_TO_ACCESSIBLE
diff --git a/accessible/android/SessionAccessibility.h b/accessible/android/SessionAccessibility.h
new file mode 100644
index 0000000000..79d7b725d5
--- /dev/null
+++ b/accessible/android/SessionAccessibility.h
@@ -0,0 +1,118 @@
+/* -*- 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/. */
+
+#ifndef mozilla_a11y_SessionAccessibility_h_
+#define mozilla_a11y_SessionAccessibility_h_
+
+#include "mozilla/java/SessionAccessibilityNatives.h"
+#include "mozilla/widget/GeckoViewSupport.h"
+#include "nsAppShell.h"
+#include "nsThreadUtils.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace a11y {
+
+class AccessibleWrap;
+class ProxyAccessible;
+class RootAccessibleWrap;
+class BatchData;
+
+class SessionAccessibility final
+ : public java::SessionAccessibility::NativeProvider::Natives<
+ SessionAccessibility> {
+ public:
+ typedef java::SessionAccessibility::NativeProvider::Natives<
+ SessionAccessibility>
+ Base;
+
+ SessionAccessibility(
+ jni::NativeWeakPtr<widget::GeckoViewSupport> aWindow,
+ java::SessionAccessibility::NativeProvider::Param aSessionAccessibility);
+
+ void OnWeakNonIntrusiveDetach(already_AddRefed<Runnable> aDisposer) {
+ SetAttached(false, std::move(aDisposer));
+ }
+
+ const java::SessionAccessibility::NativeProvider::Ref&
+ GetJavaAccessibility() {
+ return mSessionAccessibility;
+ }
+
+ static void Init();
+ static RefPtr<SessionAccessibility> GetInstanceFor(
+ ProxyAccessible* aAccessible);
+ static RefPtr<SessionAccessibility> GetInstanceFor(Accessible* aAccessible);
+
+ // Native implementations
+ using Base::AttachNative;
+ using Base::DisposeNative;
+ jni::Object::LocalRef GetNodeInfo(int32_t aID);
+ void SetText(int32_t aID, jni::String::Param aText);
+ void Click(int32_t aID);
+ void Pivot(int32_t aID, int32_t aGranularity, bool aForward, bool aInclusive);
+ void ExploreByTouch(int32_t aID, float aX, float aY);
+ void NavigateText(int32_t aID, int32_t aGranularity, int32_t aStartOffset,
+ int32_t aEndOffset, bool aForward, bool aSelect);
+ void SetSelection(int32_t aID, int32_t aStart, int32_t aEnd);
+ void Cut(int32_t aID);
+ void Copy(int32_t aID);
+ void Paste(int32_t aID);
+ void StartNativeAccessibility();
+
+ // Event methods
+ void SendFocusEvent(AccessibleWrap* aAccessible);
+ void SendScrollingEvent(AccessibleWrap* aAccessible, int32_t aScrollX,
+ int32_t aScrollY, int32_t aMaxScrollX,
+ int32_t aMaxScrollY);
+ MOZ_CAN_RUN_SCRIPT
+ void SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible);
+ void SendHoverEnterEvent(AccessibleWrap* aAccessible);
+ void SendTextSelectionChangedEvent(AccessibleWrap* aAccessible,
+ int32_t aCaretOffset);
+ void SendTextTraversedEvent(AccessibleWrap* aAccessible, int32_t aStartOffset,
+ int32_t aEndOffset);
+ void SendTextChangedEvent(AccessibleWrap* aAccessible, const nsString& aStr,
+ int32_t aStart, uint32_t aLen, bool aIsInsert,
+ bool aFromUser);
+ void SendSelectedEvent(AccessibleWrap* aAccessible, bool aSelected);
+ void SendClickedEvent(AccessibleWrap* aAccessible, uint32_t aFlags);
+ void SendWindowContentChangedEvent();
+ void SendWindowStateChangedEvent(AccessibleWrap* aAccessible);
+ void SendAnnouncementEvent(AccessibleWrap* aAccessible,
+ const nsString& aAnnouncement, uint16_t aPriority);
+
+ // Cache methods
+ void ReplaceViewportCache(
+ const nsTArray<AccessibleWrap*>& aAccessibles,
+ const nsTArray<BatchData>& aData = nsTArray<BatchData>());
+
+ void ReplaceFocusPathCache(
+ const nsTArray<AccessibleWrap*>& aAccessibles,
+ const nsTArray<BatchData>& aData = nsTArray<BatchData>());
+
+ void UpdateCachedBounds(
+ const nsTArray<AccessibleWrap*>& aAccessibles,
+ const nsTArray<BatchData>& aData = nsTArray<BatchData>());
+
+ void UpdateAccessibleFocusBoundaries(AccessibleWrap* aFirst,
+ AccessibleWrap* aLast);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SessionAccessibility)
+
+ private:
+ ~SessionAccessibility() {}
+
+ void SetAttached(bool aAttached, already_AddRefed<Runnable> aRunnable);
+ RootAccessibleWrap* GetRoot();
+
+ jni::NativeWeakPtr<widget::GeckoViewSupport> mWindow; // Parent only
+ java::SessionAccessibility::NativeProvider::GlobalRef mSessionAccessibility;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/TextLeafAccessibleWrap.h b/accessible/android/TextLeafAccessibleWrap.h
new file mode 100644
index 0000000000..27de110160
--- /dev/null
+++ b/accessible/android/TextLeafAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- 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/. */
+
+#ifndef mozilla_a11y_TextLeafAccessibleWrap_h__
+#define mozilla_a11y_TextLeafAccessibleWrap_h__
+
+#include "TextLeafAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class TextLeafAccessible TextLeafAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/TraversalRule.cpp b/accessible/android/TraversalRule.cpp
new file mode 100644
index 0000000000..6bc0cda40c
--- /dev/null
+++ b/accessible/android/TraversalRule.cpp
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "TraversalRule.h"
+
+#include "mozilla/ArrayUtils.h"
+
+#include "Role.h"
+#include "Accessible.h"
+#include "Accessible-inl.h"
+#include "HTMLListAccessible.h"
+#include "SessionAccessibility.h"
+#include "nsAccUtils.h"
+#include "nsIAccessiblePivot.h"
+
+using namespace mozilla;
+using namespace mozilla::a11y;
+
+TraversalRule::TraversalRule()
+ : TraversalRule(java::SessionAccessibility::HTML_GRANULARITY_DEFAULT) {}
+
+TraversalRule::TraversalRule(int32_t aGranularity)
+ : mGranularity(aGranularity) {}
+
+uint16_t TraversalRule::Match(const AccessibleOrProxy& aAccOrProxy) {
+ MOZ_ASSERT(aAccOrProxy.IsAccessible(),
+ "Should only receive accessibles when processing on android.");
+ Accessible* aAccessible = aAccOrProxy.AsAccessible();
+ uint16_t result = nsIAccessibleTraversalRule::FILTER_IGNORE;
+
+ if (nsAccUtils::MustPrune(aAccessible)) {
+ result |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+
+ uint64_t state = aAccessible->State();
+
+ if ((state & states::INVISIBLE) != 0) {
+ return result;
+ }
+
+ if ((state & states::OPAQUE1) == 0) {
+ nsIFrame* frame = aAccessible->GetFrame();
+ if (frame && frame->StyleEffects()->mOpacity == 0.0f) {
+ return result | nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+ }
+
+ switch (mGranularity) {
+ case java::SessionAccessibility::HTML_GRANULARITY_LINK:
+ result |= LinkMatch(aAccessible);
+ break;
+ case java::SessionAccessibility::HTML_GRANULARITY_CONTROL:
+ result |= ControlMatch(aAccessible);
+ break;
+ case java::SessionAccessibility::HTML_GRANULARITY_SECTION:
+ result |= SectionMatch(aAccessible);
+ break;
+ case java::SessionAccessibility::HTML_GRANULARITY_HEADING:
+ result |= HeadingMatch(aAccessible);
+ break;
+ case java::SessionAccessibility::HTML_GRANULARITY_LANDMARK:
+ result |= LandmarkMatch(aAccessible);
+ break;
+ default:
+ result |= DefaultMatch(aAccessible);
+ break;
+ }
+
+ return result;
+}
+
+bool TraversalRule::IsSingleLineage(Accessible* aAccessible) {
+ Accessible* child = aAccessible;
+ while (child) {
+ switch (child->ChildCount()) {
+ case 0:
+ return true;
+ case 1:
+ child = child->FirstChild();
+ break;
+ case 2:
+ if (Accessible* bullet =
+ child->Parent()->IsHTMLListItem()
+ ? child->Parent()->AsHTMLListItem()->Bullet()
+ : nullptr) {
+ child = bullet->NextSibling();
+ } else {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool TraversalRule::IsListItemBullet(const Accessible* aAccessible) {
+ Accessible* parent = aAccessible->Parent();
+ return parent && parent->IsHTMLListItem() &&
+ parent->AsHTMLListItem()->Bullet() == aAccessible;
+}
+
+bool TraversalRule::IsFlatSubtree(const Accessible* aAccessible) {
+ for (auto child = aAccessible->FirstChild(); child;
+ child = child->NextSibling()) {
+ roles::Role role = child->Role();
+ if (role == roles::TEXT_LEAF || role == roles::STATICTEXT) {
+ continue;
+ }
+
+ if (child->ChildCount() > 0 || child->ActionCount() > 0) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool TraversalRule::HasName(const Accessible* aAccessible) {
+ nsAutoString name;
+ aAccessible->Name(name);
+ name.CompressWhitespace();
+ return !name.IsEmpty();
+}
+
+uint16_t TraversalRule::LinkMatch(Accessible* aAccessible) {
+ if (aAccessible->Role() == roles::LINK &&
+ (aAccessible->State() & states::LINKED) != 0) {
+ return nsIAccessibleTraversalRule::FILTER_MATCH |
+ nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+
+ return nsIAccessibleTraversalRule::FILTER_IGNORE;
+}
+
+uint16_t TraversalRule::HeadingMatch(Accessible* aAccessible) {
+ if (aAccessible->Role() == roles::HEADING && aAccessible->ChildCount()) {
+ return nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+
+ return nsIAccessibleTraversalRule::FILTER_IGNORE;
+}
+
+uint16_t TraversalRule::SectionMatch(Accessible* aAccessible) {
+ roles::Role role = aAccessible->Role();
+ if (role == roles::HEADING || role == roles::LANDMARK ||
+ aAccessible->LandmarkRole()) {
+ return nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+
+ return nsIAccessibleTraversalRule::FILTER_IGNORE;
+}
+
+uint16_t TraversalRule::LandmarkMatch(Accessible* aAccessible) {
+ if (aAccessible->LandmarkRole()) {
+ return nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+
+ return nsIAccessibleTraversalRule::FILTER_IGNORE;
+}
+
+uint16_t TraversalRule::ControlMatch(Accessible* aAccessible) {
+ switch (aAccessible->Role()) {
+ case roles::PUSHBUTTON:
+ case roles::SPINBUTTON:
+ case roles::TOGGLE_BUTTON:
+ case roles::BUTTONDROPDOWN:
+ case roles::BUTTONDROPDOWNGRID:
+ case roles::COMBOBOX:
+ case roles::LISTBOX:
+ case roles::ENTRY:
+ case roles::PASSWORD_TEXT:
+ case roles::PAGETAB:
+ case roles::RADIOBUTTON:
+ case roles::RADIO_MENU_ITEM:
+ case roles::SLIDER:
+ case roles::CHECKBUTTON:
+ case roles::CHECK_MENU_ITEM:
+ case roles::SWITCH:
+ case roles::MENUITEM:
+ return nsIAccessibleTraversalRule::FILTER_MATCH |
+ nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ case roles::LINK:
+ return LinkMatch(aAccessible);
+ case roles::EDITCOMBOBOX:
+ if (aAccessible->NativeState() & states::EDITABLE) {
+ // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is
+ // editable. If it's a 1.1 combobox, the combobox is just a container;
+ // we want to stop on the textbox inside it, not the container.
+ return nsIAccessibleTraversalRule::FILTER_MATCH |
+ nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+ break;
+ default:
+ break;
+ }
+
+ return nsIAccessibleTraversalRule::FILTER_IGNORE;
+}
+
+uint16_t TraversalRule::DefaultMatch(Accessible* aAccessible) {
+ switch (aAccessible->Role()) {
+ case roles::COMBOBOX:
+ // We don't want to ignore the subtree because this is often
+ // where the list box hangs out.
+ return nsIAccessibleTraversalRule::FILTER_MATCH;
+ case roles::EDITCOMBOBOX:
+ if (aAccessible->NativeState() & states::EDITABLE) {
+ // Only match ARIA 1.0 comboboxes; i.e. where the combobox itself is
+ // editable. If it's a 1.1 combobox, the combobox is just a container;
+ // we want to stop on the textbox inside it.
+ return nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ break;
+ case roles::TEXT_LEAF:
+ case roles::GRAPHIC:
+ // Nameless text leaves are boring, skip them.
+ if (HasName(aAccessible)) {
+ return nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ break;
+ case roles::STATICTEXT:
+ // Ignore list bullets
+ if (!IsListItemBullet(aAccessible)) {
+ return nsIAccessibleTraversalRule::FILTER_MATCH;
+ }
+ break;
+ case roles::HEADER:
+ case roles::HEADING:
+ case roles::COLUMNHEADER:
+ case roles::ROWHEADER:
+ case roles::STATUSBAR:
+ if ((aAccessible->ChildCount() > 0 || HasName(aAccessible)) &&
+ (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible))) {
+ return nsIAccessibleTraversalRule::FILTER_MATCH |
+ nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+ break;
+ case roles::GRID_CELL:
+ if (IsSingleLineage(aAccessible) || IsFlatSubtree(aAccessible)) {
+ return nsIAccessibleTraversalRule::FILTER_MATCH |
+ nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+ break;
+ case roles::LISTITEM:
+ if (IsFlatSubtree(aAccessible) || IsSingleLineage(aAccessible)) {
+ return nsIAccessibleTraversalRule::FILTER_MATCH |
+ nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+ break;
+ case roles::LABEL:
+ if (IsFlatSubtree(aAccessible)) {
+ // Match if this is a label with text but no nested controls.
+ return nsIAccessibleTraversalRule::FILTER_MATCH |
+ nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ }
+ break;
+ case roles::MENUITEM:
+ case roles::LINK:
+ case roles::PAGETAB:
+ case roles::PUSHBUTTON:
+ case roles::CHECKBUTTON:
+ case roles::RADIOBUTTON:
+ case roles::PROGRESSBAR:
+ case roles::BUTTONDROPDOWN:
+ case roles::BUTTONMENU:
+ case roles::CHECK_MENU_ITEM:
+ case roles::PASSWORD_TEXT:
+ case roles::RADIO_MENU_ITEM:
+ case roles::TOGGLE_BUTTON:
+ case roles::ENTRY:
+ case roles::KEY:
+ case roles::SLIDER:
+ case roles::SPINBUTTON:
+ case roles::OPTION:
+ case roles::SWITCH:
+ case roles::MATHML_MATH:
+ // Ignore the subtree, if there is one. So that we don't land on
+ // the same content that was already presented by its parent.
+ return nsIAccessibleTraversalRule::FILTER_MATCH |
+ nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+ default:
+ break;
+ }
+
+ return nsIAccessibleTraversalRule::FILTER_IGNORE;
+}
diff --git a/accessible/android/TraversalRule.h b/accessible/android/TraversalRule.h
new file mode 100644
index 0000000000..46d8d264a4
--- /dev/null
+++ b/accessible/android/TraversalRule.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef _TraversalRule_H_
+#define _TraversalRule_H_
+
+#include "Pivot.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Accessible;
+
+/**
+ * Class represents a simple traversal rule.
+ */
+class TraversalRule final : public PivotRule {
+ public:
+ TraversalRule();
+ explicit TraversalRule(int32_t aGranularity);
+
+ ~TraversalRule() = default;
+
+ virtual uint16_t Match(const AccessibleOrProxy& aAccOrProxy) override;
+
+ private:
+ bool IsSingleLineage(Accessible* aAccessible);
+
+ bool IsFlatSubtree(const Accessible* aAccessible);
+
+ bool IsListItemBullet(const Accessible* aAccessible);
+
+ bool HasName(const Accessible* aAccessible);
+
+ uint16_t DefaultMatch(Accessible* aAccessible);
+
+ uint16_t LinkMatch(Accessible* aAccessible);
+
+ uint16_t HeadingMatch(Accessible* aAccessible);
+
+ uint16_t ControlMatch(Accessible* aAccessible);
+
+ uint16_t SectionMatch(Accessible* aAccessible);
+
+ uint16_t LandmarkMatch(Accessible* aAccessible);
+
+ int32_t mGranularity;
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/XULListboxAccessibleWrap.h b/accessible/android/XULListboxAccessibleWrap.h
new file mode 100644
index 0000000000..ad2c54b18d
--- /dev/null
+++ b/accessible/android/XULListboxAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- 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/. */
+
+#ifndef mozilla_a11y_XULListboxAccessibleWrap_h__
+#define mozilla_a11y_XULListboxAccessibleWrap_h__
+
+#include "XULListboxAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULListboxAccessible XULListboxAccessibleWrap;
+typedef class XULListCellAccessible XULListCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/XULMenuAccessibleWrap.h b/accessible/android/XULMenuAccessibleWrap.h
new file mode 100644
index 0000000000..81cdaf94ad
--- /dev/null
+++ b/accessible/android/XULMenuAccessibleWrap.h
@@ -0,0 +1,19 @@
+/* -*- 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/. */
+
+#ifndef mozilla_a11y_XULMenuAccessibleWrap_h__
+#define mozilla_a11y_XULMenuAccessibleWrap_h__
+
+#include "XULMenuAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULMenuitemAccessible XULMenuitemAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/XULTreeGridAccessibleWrap.h b/accessible/android/XULTreeGridAccessibleWrap.h
new file mode 100644
index 0000000000..e991d8f4f4
--- /dev/null
+++ b/accessible/android/XULTreeGridAccessibleWrap.h
@@ -0,0 +1,20 @@
+/* -*- 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/. */
+
+#ifndef mozilla_a11y_XULTreeGridAccessibleWrap_h__
+#define mozilla_a11y_XULTreeGridAccessibleWrap_h__
+
+#include "XULTreeGridAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+typedef class XULTreeGridAccessible XULTreeGridAccessibleWrap;
+typedef class XULTreeGridCellAccessible XULTreeGridCellAccessibleWrap;
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/android/moz.build b/accessible/android/moz.build
new file mode 100644
index 0000000000..456a9d7ec6
--- /dev/null
+++ b/accessible/android/moz.build
@@ -0,0 +1,42 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.a11y += [
+ "AccessibleWrap.h",
+ "HyperTextAccessibleWrap.h",
+ "SessionAccessibility.h",
+ "TraversalRule.h",
+]
+
+SOURCES += [
+ "AccessibleWrap.cpp",
+ "DocAccessibleWrap.cpp",
+ "Platform.cpp",
+ "ProxyAccessibleWrap.cpp",
+ "RootAccessibleWrap.cpp",
+ "SessionAccessibility.cpp",
+ "TraversalRule.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/accessible/base",
+ "/accessible/generic",
+ "/accessible/html",
+ "/accessible/ipc",
+ "/accessible/ipc/other",
+ "/accessible/xpcom",
+ "/accessible/xul",
+ "/dom/base",
+ "/widget",
+ "/widget/android",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ CXXFLAGS += ["-Wno-error=shadow"]