summaryrefslogtreecommitdiffstats
path: root/accessible/ipc
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/ipc')
-rw-r--r--accessible/ipc/DocAccessibleChild.cpp431
-rw-r--r--accessible/ipc/DocAccessibleChild.h176
-rw-r--r--accessible/ipc/DocAccessibleParent.cpp1266
-rw-r--r--accessible/ipc/DocAccessibleParent.h400
-rw-r--r--accessible/ipc/DocAccessibleTypes.ipdlh19
-rw-r--r--accessible/ipc/IPCTypes.h189
-rw-r--r--accessible/ipc/PDocAccessible.ipdl164
-rw-r--r--accessible/ipc/RemoteAccessible.cpp2092
-rw-r--r--accessible/ipc/RemoteAccessible.h511
-rw-r--r--accessible/ipc/moz.build62
10 files changed, 5310 insertions, 0 deletions
diff --git a/accessible/ipc/DocAccessibleChild.cpp b/accessible/ipc/DocAccessibleChild.cpp
new file mode 100644
index 0000000000..5f9d1a0a63
--- /dev/null
+++ b/accessible/ipc/DocAccessibleChild.cpp
@@ -0,0 +1,431 @@
+/* -*- 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 "chrome/common/ipc_channel.h"
+#include "mozilla/a11y/DocAccessibleChild.h"
+#include "mozilla/a11y/CacheConstants.h"
+#include "mozilla/a11y/FocusManager.h"
+#include "mozilla/ipc/ProcessChild.h"
+#include "nsAccessibilityService.h"
+
+#include "LocalAccessible-inl.h"
+#ifdef A11Y_LOG
+# include "Logging.h"
+#endif
+#include "TextLeafRange.h"
+
+namespace mozilla {
+namespace a11y {
+
+/* static */
+void DocAccessibleChild::FlattenTree(LocalAccessible* aRoot,
+ nsTArray<LocalAccessible*>& aTree) {
+ MOZ_ASSERT(!aRoot->IsDoc(), "documents shouldn't be serialized");
+
+ aTree.AppendElement(aRoot);
+ // OuterDocAccessibles are special because we don't want to serialize the
+ // child doc here, we'll call PDocAccessibleConstructor in
+ // NotificationController.
+ uint32_t childCount = aRoot->IsOuterDoc() ? 0 : aRoot->ChildCount();
+
+ for (uint32_t i = 0; i < childCount; i++) {
+ FlattenTree(aRoot->LocalChildAt(i), aTree);
+ }
+}
+
+/* static */
+AccessibleData DocAccessibleChild::SerializeAcc(LocalAccessible* aAcc) {
+ uint32_t genericTypes = aAcc->mGenericTypes;
+ if (aAcc->ARIAHasNumericValue()) {
+ // XXX: We need to do this because this requires a state check.
+ genericTypes |= eNumericValue;
+ }
+ if (aAcc->IsTextLeaf() || aAcc->IsImage()) {
+ // Ideally, we'd set eActionable for any Accessible with an ancedstor
+ // action. However, that requires an ancestor walk which is too expensive
+ // here. eActionable is only used by ATK. For now, we only expose ancestor
+ // actions on text leaf and image Accessibles. This means that we don't
+ // support "click ancestor" for ATK.
+ if (aAcc->ActionCount()) {
+ genericTypes |= eActionable;
+ }
+ } else if (aAcc->HasPrimaryAction()) {
+ genericTypes |= eActionable;
+ }
+
+ RefPtr<AccAttributes> fields;
+ // Even though we send moves as a hide and a show, we don't want to
+ // push the cache again for moves.
+ if (!aAcc->Document()->IsAccessibleBeingMoved(aAcc)) {
+ fields =
+ aAcc->BundleFieldsForCache(CacheDomain::All, CacheUpdateType::Initial);
+ if (fields->Count() == 0) {
+ fields = nullptr;
+ }
+ }
+
+ return AccessibleData(aAcc->ID(), aAcc->Role(), aAcc->LocalParent()->ID(),
+ static_cast<int32_t>(aAcc->IndexInParent()),
+ static_cast<AccType>(aAcc->mType),
+ static_cast<AccGenericType>(genericTypes),
+ aAcc->mRoleMapEntryIndex, fields);
+}
+
+void DocAccessibleChild::InsertIntoIpcTree(LocalAccessible* aChild,
+ bool aSuppressShowEvent) {
+ nsTArray<LocalAccessible*> shownTree;
+ FlattenTree(aChild, shownTree);
+ uint32_t totalAccs = shownTree.Length();
+ // Exceeding the IPDL maximum message size will cause a crash. Try to avoid
+ // this by only including kMaxAccsPerMessage Accessibels in a single IPDL
+ // call. If there are Accessibles beyond this, they will be split across
+ // multiple calls.
+ constexpr uint32_t kMaxAccsPerMessage =
+ IPC::Channel::kMaximumMessageSize / (2 * 1024);
+ nsTArray<AccessibleData> data(std::min(kMaxAccsPerMessage, totalAccs));
+ for (LocalAccessible* child : shownTree) {
+ if (data.Length() == kMaxAccsPerMessage) {
+ if (ipc::ProcessChild::ExpectingShutdown()) {
+ return;
+ }
+ SendShowEvent(data, aSuppressShowEvent, false, false);
+ data.ClearAndRetainStorage();
+ }
+ data.AppendElement(SerializeAcc(child));
+ }
+ if (ipc::ProcessChild::ExpectingShutdown()) {
+ return;
+ }
+ if (!data.IsEmpty()) {
+ SendShowEvent(data, aSuppressShowEvent, true, false);
+ }
+}
+
+void DocAccessibleChild::ShowEvent(AccShowEvent* aShowEvent) {
+ LocalAccessible* child = aShowEvent->GetAccessible();
+ InsertIntoIpcTree(child, false);
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvTakeFocus(const uint64_t& aID) {
+ LocalAccessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->TakeFocus();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollTo(
+ const uint64_t& aID, const uint32_t& aScrollType) {
+ LocalAccessible* acc = IdToAccessible(aID);
+ if (acc) {
+ RefPtr<PresShell> presShell = acc->Document()->PresShellPtr();
+ nsCOMPtr<nsIContent> content = acc->GetContent();
+ nsCoreUtils::ScrollTo(presShell, content, aScrollType);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvTakeSelection(
+ const uint64_t& aID) {
+ LocalAccessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->TakeSelection();
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvSetSelected(
+ const uint64_t& aID, const bool& aSelect) {
+ LocalAccessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->SetSelected(aSelect);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvVerifyCache(
+ const uint64_t& aID, const uint64_t& aCacheDomain, AccAttributes* aFields) {
+#ifdef A11Y_LOG
+ LocalAccessible* acc = IdToAccessible(aID);
+ if (!acc) {
+ return IPC_OK();
+ }
+
+ RefPtr<AccAttributes> localFields =
+ acc->BundleFieldsForCache(aCacheDomain, CacheUpdateType::Update);
+ bool mismatches = false;
+
+ for (auto prop : *localFields) {
+ if (prop.Value<DeleteEntry>()) {
+ if (aFields->HasAttribute(prop.Name())) {
+ if (!mismatches) {
+ logging::MsgBegin("Mismatch!", "Local and remote values differ");
+ logging::AccessibleInfo("", acc);
+ mismatches = true;
+ }
+ nsAutoCString propName;
+ prop.Name()->ToUTF8String(propName);
+ nsAutoString val;
+ aFields->GetAttribute(prop.Name(), val);
+ logging::MsgEntry(
+ "Remote value for %s should be empty, but instead it is '%s'",
+ propName.get(), NS_ConvertUTF16toUTF8(val).get());
+ }
+ continue;
+ }
+
+ nsAutoString localVal;
+ prop.ValueAsString(localVal);
+ nsAutoString remoteVal;
+ aFields->GetAttribute(prop.Name(), remoteVal);
+ if (!localVal.Equals(remoteVal)) {
+ if (!mismatches) {
+ logging::MsgBegin("Mismatch!", "Local and remote values differ");
+ logging::AccessibleInfo("", acc);
+ mismatches = true;
+ }
+ nsAutoCString propName;
+ prop.Name()->ToUTF8String(propName);
+ logging::MsgEntry("Fields differ: %s '%s' != '%s'", propName.get(),
+ NS_ConvertUTF16toUTF8(remoteVal).get(),
+ NS_ConvertUTF16toUTF8(localVal).get());
+ }
+ }
+ if (mismatches) {
+ logging::MsgEnd();
+ }
+#endif // A11Y_LOG
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvDoActionAsync(
+ const uint64_t& aID, const uint8_t& aIndex) {
+ if (LocalAccessible* acc = IdToAccessible(aID)) {
+ Unused << acc->DoAction(aIndex);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvSetCaretOffset(
+ const uint64_t& aID, const int32_t& aOffset) {
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole() && acc->IsValidOffset(aOffset)) {
+ acc->SetCaretOffset(aOffset);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvSetTextSelection(
+ const uint64_t& aStartID, const int32_t& aStartOffset,
+ const uint64_t& aEndID, const int32_t& aEndOffset,
+ const int32_t& aSelectionNum) {
+ TextLeafRange range(TextLeafPoint(IdToAccessible(aStartID), aStartOffset),
+ TextLeafPoint(IdToAccessible(aEndID), aEndOffset));
+ if (range) {
+ range.SetSelection(aSelectionNum);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollTextLeafRangeIntoView(
+ const uint64_t& aStartID, const int32_t& aStartOffset,
+ const uint64_t& aEndID, const int32_t& aEndOffset,
+ const uint32_t& aScrollType) {
+ TextLeafRange range(TextLeafPoint(IdToAccessible(aStartID), aStartOffset),
+ TextLeafPoint(IdToAccessible(aEndID), aEndOffset));
+ if (range) {
+ range.ScrollIntoView(aScrollType);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvRemoveTextSelection(
+ const uint64_t& aID, const int32_t& aSelectionNum) {
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ acc->RemoveFromSelection(aSelectionNum);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvSetCurValue(
+ const uint64_t& aID, const double& aValue) {
+ LocalAccessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->SetCurValue(aValue);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvReplaceText(
+ const uint64_t& aID, const nsAString& aText) {
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ acc->ReplaceText(aText);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvInsertText(
+ const uint64_t& aID, const nsAString& aText, const int32_t& aPosition) {
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ acc->InsertText(aText, aPosition);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvCopyText(
+ const uint64_t& aID, const int32_t& aStartPos, const int32_t& aEndPos) {
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ acc->CopyText(aStartPos, aEndPos);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvCutText(
+ const uint64_t& aID, const int32_t& aStartPos, const int32_t& aEndPos) {
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ acc->CutText(aStartPos, aEndPos);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvDeleteText(
+ const uint64_t& aID, const int32_t& aStartPos, const int32_t& aEndPos) {
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ acc->DeleteText(aStartPos, aEndPos);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvPasteText(
+ const uint64_t& aID, const int32_t& aPosition) {
+ RefPtr<HyperTextAccessible> acc = IdToHyperTextAccessible(aID);
+ if (acc && acc->IsTextRole()) {
+ acc->PasteText(aPosition);
+ }
+
+ return IPC_OK();
+}
+
+ipc::IPCResult DocAccessibleChild::RecvRestoreFocus() {
+ if (FocusManager* focusMgr = FocusMgr()) {
+ focusMgr->ForceFocusEvent();
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollToPoint(
+ const uint64_t& aID, const uint32_t& aScrollType, const int32_t& aX,
+ const int32_t& aY) {
+ LocalAccessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->ScrollToPoint(aScrollType, aX, aY);
+ }
+
+ return IPC_OK();
+}
+
+LayoutDeviceIntRect DocAccessibleChild::GetCaretRectFor(const uint64_t& aID) {
+#if defined(XP_WIN)
+ LocalAccessible* target;
+
+ if (aID) {
+ target = reinterpret_cast<LocalAccessible*>(aID);
+ } else {
+ target = mDoc;
+ }
+
+ MOZ_ASSERT(target);
+
+ HyperTextAccessible* text = target->AsHyperText();
+ if (!text) {
+ return LayoutDeviceIntRect();
+ }
+
+ nsIWidget* widget = nullptr;
+ return text->GetCaretRect(&widget);
+#else
+ // The caret rect is only used on Windows, so just return an empty rect
+ // on other platforms.
+ return LayoutDeviceIntRect();
+#endif // defined(XP_WIN)
+}
+
+bool DocAccessibleChild::SendFocusEvent(const uint64_t& aID) {
+ return PDocAccessibleChild::SendFocusEvent(aID, GetCaretRectFor(aID));
+}
+
+bool DocAccessibleChild::SendCaretMoveEvent(const uint64_t& aID,
+ const int32_t& aOffset,
+ const bool& aIsSelectionCollapsed,
+ const bool& aIsAtEndOfLine,
+ const int32_t& aGranularity,
+ bool aFromUser) {
+ return PDocAccessibleChild::SendCaretMoveEvent(
+ aID, GetCaretRectFor(aID), aOffset, aIsSelectionCollapsed, aIsAtEndOfLine,
+ aGranularity, aFromUser);
+}
+
+#if !defined(XP_WIN)
+mozilla::ipc::IPCResult DocAccessibleChild::RecvAnnounce(
+ const uint64_t& aID, const nsAString& aAnnouncement,
+ const uint16_t& aPriority) {
+ LocalAccessible* acc = IdToAccessible(aID);
+ if (acc) {
+ acc->Announce(aAnnouncement, aPriority);
+ }
+
+ return IPC_OK();
+}
+#endif // !defined(XP_WIN)
+
+mozilla::ipc::IPCResult DocAccessibleChild::RecvScrollSubstringToPoint(
+ const uint64_t& aID, const int32_t& aStartOffset, const int32_t& aEndOffset,
+ const uint32_t& aCoordinateType, const int32_t& aX, const int32_t& aY) {
+ HyperTextAccessible* acc = IdToHyperTextAccessible(aID);
+ if (acc) {
+ acc->ScrollSubstringToPoint(aStartOffset, aEndOffset, aCoordinateType, aX,
+ aY);
+ }
+
+ return IPC_OK();
+}
+
+LocalAccessible* DocAccessibleChild::IdToAccessible(const uint64_t& aID) const {
+ if (!aID) return mDoc;
+
+ if (!mDoc) return nullptr;
+
+ return mDoc->GetAccessibleByUniqueID(reinterpret_cast<void*>(aID));
+}
+
+HyperTextAccessible* DocAccessibleChild::IdToHyperTextAccessible(
+ const uint64_t& aID) const {
+ LocalAccessible* acc = IdToAccessible(aID);
+ return acc && acc->IsHyperText() ? acc->AsHyperText() : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/ipc/DocAccessibleChild.h b/accessible/ipc/DocAccessibleChild.h
new file mode 100644
index 0000000000..0a6164cce8
--- /dev/null
+++ b/accessible/ipc/DocAccessibleChild.h
@@ -0,0 +1,176 @@
+/* -*- 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_DocAccessibleChild_h
+#define mozilla_a11y_DocAccessibleChild_h
+
+#include "mozilla/a11y/DocAccessible.h"
+#include "mozilla/a11y/PDocAccessibleChild.h"
+#include "mozilla/Unused.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace a11y {
+
+class LocalAccessible;
+class AccShowEvent;
+
+/**
+ * These objects handle content side communication for an accessible document,
+ * and their lifetime is the same as the document they represent.
+ */
+class DocAccessibleChild : public PDocAccessibleChild {
+ public:
+ DocAccessibleChild(DocAccessible* aDoc, IProtocol* aManager) : mDoc(aDoc) {
+ MOZ_COUNT_CTOR(DocAccessibleChild);
+ SetManager(aManager);
+ }
+
+ ~DocAccessibleChild() {
+ // Shutdown() should have been called, but maybe it isn't if the process is
+ // killed?
+ MOZ_ASSERT(!mDoc);
+ if (mDoc) {
+ mDoc->SetIPCDoc(nullptr);
+ }
+
+ MOZ_COUNT_DTOR(DocAccessibleChild);
+ }
+
+ virtual void Shutdown() {
+ DetachDocument();
+ SendShutdown();
+ }
+
+ /**
+ * Serializes a shown tree and sends it to the chrome process.
+ */
+ void InsertIntoIpcTree(LocalAccessible* aChild, bool aSuppressShowEvent);
+ void ShowEvent(AccShowEvent* aShowEvent);
+
+ virtual void ActorDestroy(ActorDestroyReason) override {
+ if (!mDoc) {
+ return;
+ }
+
+ mDoc->SetIPCDoc(nullptr);
+ mDoc = nullptr;
+ }
+
+ virtual mozilla::ipc::IPCResult RecvTakeFocus(const uint64_t& aID) override;
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual mozilla::ipc::IPCResult RecvScrollTo(
+ const uint64_t& aID, const uint32_t& aScrollType) override;
+
+ virtual mozilla::ipc::IPCResult RecvTakeSelection(
+ const uint64_t& aID) override;
+ virtual mozilla::ipc::IPCResult RecvSetSelected(const uint64_t& aID,
+ const bool& aSelect) override;
+
+ virtual mozilla::ipc::IPCResult RecvVerifyCache(
+ const uint64_t& aID, const uint64_t& aCacheDomain,
+ AccAttributes* aFields) override;
+
+ virtual mozilla::ipc::IPCResult RecvDoActionAsync(
+ const uint64_t& aID, const uint8_t& aIndex) override;
+
+ virtual mozilla::ipc::IPCResult RecvSetCaretOffset(
+ const uint64_t& aID, const int32_t& aOffset) override;
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual mozilla::ipc::IPCResult RecvSetTextSelection(
+ const uint64_t& aStartID, const int32_t& aStartOffset,
+ const uint64_t& aEndID, const int32_t& aEndOffset,
+ const int32_t& aSelectionNum) override;
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual mozilla::ipc::IPCResult RecvScrollTextLeafRangeIntoView(
+ const uint64_t& aStartID, const int32_t& aStartOffset,
+ const uint64_t& aEndID, const int32_t& aEndOffset,
+ const uint32_t& aScrollType) override;
+
+ virtual mozilla::ipc::IPCResult RecvRemoveTextSelection(
+ const uint64_t& aID, const int32_t& aSelectionNum) override;
+
+ virtual mozilla::ipc::IPCResult RecvSetCurValue(
+ const uint64_t& aID, const double& aValue) override;
+
+ virtual mozilla::ipc::IPCResult RecvReplaceText(
+ const uint64_t& aID, const nsAString& aText) override;
+
+ virtual mozilla::ipc::IPCResult RecvInsertText(
+ const uint64_t& aID, const nsAString& aText,
+ const int32_t& aPosition) override;
+
+ virtual mozilla::ipc::IPCResult RecvCopyText(const uint64_t& aID,
+ const int32_t& aStartPos,
+ const int32_t& aEndPos) override;
+
+ virtual mozilla::ipc::IPCResult RecvCutText(const uint64_t& aID,
+ const int32_t& aStartPos,
+ const int32_t& aEndPos) override;
+
+ virtual mozilla::ipc::IPCResult RecvDeleteText(
+ const uint64_t& aID, const int32_t& aStartPos,
+ const int32_t& aEndPos) override;
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual mozilla::ipc::IPCResult RecvPasteText(
+ const uint64_t& aID, const int32_t& aPosition) override;
+
+ virtual mozilla::ipc::IPCResult RecvRestoreFocus() override;
+
+ virtual mozilla::ipc::IPCResult RecvScrollToPoint(const uint64_t& aID,
+ const uint32_t& aScrollType,
+ const int32_t& aX,
+ const int32_t& aY) override;
+
+ bool SendCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset,
+ const bool& aIsSelectionCollapsed,
+ const bool& aIsAtEndOfLine,
+ const int32_t& aGranularity, bool aFromUser);
+ bool SendFocusEvent(const uint64_t& aID);
+
+#if !defined(XP_WIN)
+ virtual mozilla::ipc::IPCResult RecvAnnounce(
+ const uint64_t& aID, const nsAString& aAnnouncement,
+ const uint16_t& aPriority) override;
+#endif // !defined(XP_WIN)
+
+ virtual mozilla::ipc::IPCResult RecvScrollSubstringToPoint(
+ const uint64_t& aID, const int32_t& aStartOffset,
+ const int32_t& aEndOffset, const uint32_t& aCoordinateType,
+ const int32_t& aX, const int32_t& aY) override;
+
+ private:
+ LayoutDeviceIntRect GetCaretRectFor(const uint64_t& aID);
+
+ protected:
+ static void FlattenTree(LocalAccessible* aRoot,
+ nsTArray<LocalAccessible*>& aTree);
+
+ static AccessibleData SerializeAcc(LocalAccessible* aAcc);
+
+ void DetachDocument() {
+ if (mDoc) {
+ mDoc->SetIPCDoc(nullptr);
+ mDoc = nullptr;
+ }
+ }
+
+ LocalAccessible* IdToAccessible(const uint64_t& aID) const;
+ HyperTextAccessible* IdToHyperTextAccessible(const uint64_t& aID) const;
+
+ DocAccessible* mDoc;
+
+ friend void DocAccessible::DoInitialUpdate();
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_DocAccessibleChild_h
diff --git a/accessible/ipc/DocAccessibleParent.cpp b/accessible/ipc/DocAccessibleParent.cpp
new file mode 100644
index 0000000000..1c96bd38cd
--- /dev/null
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -0,0 +1,1266 @@
+/* -*- 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 "ARIAMap.h"
+#include "CachedTableAccessible.h"
+#include "DocAccessibleParent.h"
+#include "mozilla/a11y/Platform.h"
+#include "mozilla/Components.h" // for mozilla::components
+#include "mozilla/dom/BrowserBridgeParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "nsAccessibilityService.h"
+#include "xpcAccessibleDocument.h"
+#include "xpcAccEvents.h"
+#include "nsAccUtils.h"
+#include "nsIIOService.h"
+#include "TextRange.h"
+#include "Relation.h"
+#include "RootAccessible.h"
+
+#if defined(XP_WIN)
+# include "Compatibility.h"
+# include "nsWinUtils.h"
+#endif
+
+#if defined(ANDROID)
+# define ACQUIRE_ANDROID_LOCK \
+ MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor());
+#else
+# define ACQUIRE_ANDROID_LOCK \
+ do { \
+ } while (0);
+#endif
+
+namespace mozilla {
+
+namespace a11y {
+uint64_t DocAccessibleParent::sMaxDocID = 0;
+
+DocAccessibleParent::DocAccessibleParent()
+ : RemoteAccessible(this),
+ mParentDoc(kNoParentDoc),
+#if defined(XP_WIN)
+ mEmulatedWindowHandle(nullptr),
+#endif // defined(XP_WIN)
+ mTopLevel(false),
+ mTopLevelInContentProcess(false),
+ mShutdown(false),
+ mFocus(0),
+ mCaretId(0),
+ mCaretOffset(-1),
+ mIsCaretAtEndOfLine(false) {
+ sMaxDocID++;
+ mActorID = sMaxDocID;
+ MOZ_ASSERT(!LiveDocs().Get(mActorID));
+ LiveDocs().InsertOrUpdate(mActorID, this);
+}
+
+DocAccessibleParent::~DocAccessibleParent() {
+ UnregisterWeakMemoryReporter(this);
+ LiveDocs().Remove(mActorID);
+ MOZ_ASSERT(mChildDocs.Length() == 0);
+ MOZ_ASSERT(!ParentDoc());
+}
+
+already_AddRefed<DocAccessibleParent> DocAccessibleParent::New() {
+ RefPtr<DocAccessibleParent> dap(new DocAccessibleParent());
+ // We need to do this with a non-zero reference count. The easiest way is to
+ // do it in this static method and hide the constructor.
+ RegisterWeakMemoryReporter(dap);
+ return dap.forget();
+}
+
+void DocAccessibleParent::SetBrowsingContext(
+ dom::CanonicalBrowsingContext* aBrowsingContext) {
+ mBrowsingContext = aBrowsingContext;
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvShowEvent(
+ nsTArray<AccessibleData>&& aNewTree, const bool& aEventSuppressed,
+ const bool& aComplete, const bool& aFromUser) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) return IPC_OK();
+
+ MOZ_ASSERT(CheckDocTree());
+
+ if (aNewTree.IsEmpty()) {
+ return IPC_FAIL(this, "No children being added");
+ }
+
+ RemoteAccessible* root = nullptr;
+ RemoteAccessible* rootParent = nullptr;
+ RemoteAccessible* lastParent = this;
+ uint64_t lastParentID = 0;
+ for (const auto& accData : aNewTree) {
+ RemoteAccessible* parent = accData.ParentID() == lastParentID
+ ? lastParent
+ : GetAccessible(accData.ParentID());
+ // XXX This should really never happen, but sometimes we fail to fire the
+ // required show events.
+ if (!parent) {
+ NS_ERROR("adding child to unknown accessible");
+#ifdef DEBUG
+ return IPC_FAIL(this, "unknown parent accessible");
+#else
+ return IPC_OK();
+#endif
+ }
+
+ uint32_t childIdx = accData.IndexInParent();
+ if (childIdx > parent->ChildCount()) {
+ NS_ERROR("invalid index to add child at");
+#ifdef DEBUG
+ return IPC_FAIL(this, "invalid index");
+#else
+ return IPC_OK();
+#endif
+ }
+
+ RemoteAccessible* child = CreateAcc(accData);
+ if (!child) {
+ // This shouldn't happen.
+ return IPC_FAIL(this, "failed to add children");
+ }
+ if (!root && !mPendingShowChild) {
+ // This is the first Accessible, which is the root of the shown subtree.
+ root = child;
+ rootParent = parent;
+ }
+ // If this show event has been split across multiple messages and this is
+ // not the last message, don't attach the shown root to the tree yet.
+ // Otherwise, clients might crawl the incomplete subtree and they won't get
+ // mutation events for the remaining pieces.
+ if (aComplete || root != child) {
+ AttachChild(parent, childIdx, child);
+ }
+ }
+
+ MOZ_ASSERT(CheckDocTree());
+
+ if (!aComplete && !mPendingShowChild) {
+ // This is the first message for a show event split across multiple
+ // messages. Save the show target for subsequent messages and return.
+ const auto& accData = aNewTree[0];
+ mPendingShowChild = accData.ID();
+ mPendingShowParent = accData.ParentID();
+ mPendingShowIndex = accData.IndexInParent();
+ return IPC_OK();
+ }
+ if (!aComplete) {
+ // This show event has been split into multiple messages, but this is
+ // neither the first nor the last message. There's nothing more to do here.
+ return IPC_OK();
+ }
+ MOZ_ASSERT(aComplete);
+ if (mPendingShowChild) {
+ // This is the last message for a show event split across multiple
+ // messages. Retrieve the saved show target, attach it to the tree and fire
+ // an event if appropriate.
+ rootParent = GetAccessible(mPendingShowParent);
+ MOZ_ASSERT(rootParent);
+ root = GetAccessible(mPendingShowChild);
+ MOZ_ASSERT(root);
+ AttachChild(rootParent, mPendingShowIndex, root);
+ mPendingShowChild = 0;
+ mPendingShowParent = 0;
+ mPendingShowIndex = 0;
+ }
+
+ // Just update, no events.
+ if (aEventSuppressed) {
+ return IPC_OK();
+ }
+
+ PlatformShowHideEvent(root, rootParent, true, aFromUser);
+
+ if (nsCOMPtr<nsIObserverService> obsService =
+ services::GetObserverService()) {
+ obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr);
+ }
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return IPC_OK();
+ }
+
+ uint32_t type = nsIAccessibleEvent::EVENT_SHOW;
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(root);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsINode* node = nullptr;
+ RefPtr<xpcAccEvent> event =
+ new xpcAccEvent(type, xpcAcc, doc, node, aFromUser);
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+
+ return IPC_OK();
+}
+
+RemoteAccessible* DocAccessibleParent::CreateAcc(
+ const AccessibleData& aAccData) {
+ RemoteAccessible* newProxy;
+ if ((newProxy = GetAccessible(aAccData.ID()))) {
+ // This is a move. Reuse the Accessible; don't destroy it.
+ MOZ_ASSERT(!newProxy->RemoteParent());
+ return newProxy;
+ }
+
+ if (!aria::IsRoleMapIndexValid(aAccData.RoleMapEntryIndex())) {
+ MOZ_ASSERT_UNREACHABLE("Invalid role map entry index");
+ return nullptr;
+ }
+
+ newProxy = new RemoteAccessible(aAccData.ID(), this, aAccData.Role(),
+ aAccData.Type(), aAccData.GenericTypes(),
+ aAccData.RoleMapEntryIndex());
+ mAccessibles.PutEntry(aAccData.ID())->mProxy = newProxy;
+
+ if (RefPtr<AccAttributes> fields = aAccData.CacheFields()) {
+ newProxy->ApplyCache(CacheUpdateType::Initial, fields);
+ }
+
+ return newProxy;
+}
+
+void DocAccessibleParent::AttachChild(RemoteAccessible* aParent,
+ uint32_t aIndex,
+ RemoteAccessible* aChild) {
+ aParent->AddChildAt(aIndex, aChild);
+ aChild->SetParent(aParent);
+ // ProxyCreated might have already been called if aChild is being moved.
+ if (!aChild->GetWrapper()) {
+ ProxyCreated(aChild);
+ }
+ if (aChild->IsTableCell()) {
+ CachedTableAccessible::Invalidate(aChild);
+ }
+ if (aChild->IsOuterDoc()) {
+ // We can only do this after ProxyCreated is called because it will fire an
+ // event on aChild.
+ mPendingOOPChildDocs.RemoveIf([&](dom::BrowserBridgeParent* bridge) {
+ MOZ_ASSERT(bridge->GetBrowserParent(),
+ "Pending BrowserBridgeParent should be alive");
+ if (bridge->GetEmbedderAccessibleId() != aChild->ID()) {
+ return false;
+ }
+ MOZ_ASSERT(bridge->GetEmbedderAccessibleDoc() == this);
+ if (DocAccessibleParent* childDoc = bridge->GetDocAccessibleParent()) {
+ AddChildDoc(childDoc, aChild->ID(), false);
+ }
+ return true;
+ });
+ }
+}
+
+void DocAccessibleParent::ShutdownOrPrepareForMove(RemoteAccessible* aAcc) {
+ // Children might be removed or moved. Handle them the same way. We do this
+ // before checking the moving IDs set in order to ensure that we handle moved
+ // descendants properly. Avoid descending into the children of outer documents
+ // for moves since they are added and removed differently to normal children.
+ if (!aAcc->IsOuterDoc()) {
+ // Even if some children are kept, those will be re-attached when we handle
+ // the show event. For now, clear all of them by moving them to a temporary.
+ auto children{std::move(aAcc->mChildren)};
+ for (RemoteAccessible* child : children) {
+ ShutdownOrPrepareForMove(child);
+ }
+ }
+
+ const uint64_t id = aAcc->ID();
+ if (!mMovingIDs.Contains(id)) {
+ // This Accessible is being removed.
+ aAcc->Shutdown();
+ return;
+ }
+ // This is a move. Moves are sent as a hide and then a show, but for a move,
+ // we want to keep the Accessible alive for reuse later.
+ if (aAcc->IsTable() || aAcc->IsTableCell()) {
+ // For table cells, it's important that we do this before the parent is
+ // cleared because CachedTableAccessible::Invalidate needs the ancestry.
+ CachedTableAccessible::Invalidate(aAcc);
+ }
+ if (aAcc->IsHyperText()) {
+ aAcc->InvalidateCachedHyperTextOffsets();
+ }
+ aAcc->SetParent(nullptr);
+ mMovingIDs.EnsureRemoved(id);
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvHideEvent(
+ const uint64_t& aRootID, const bool& aFromUser) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) return IPC_OK();
+
+ MOZ_ASSERT(CheckDocTree());
+
+ // We shouldn't actually need this because mAccessibles shouldn't have an
+ // entry for the document itself, but it doesn't hurt to be explicit.
+ if (!aRootID) {
+ return IPC_FAIL(this, "Trying to hide entire document?");
+ }
+
+ ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID);
+ if (!rootEntry) {
+ NS_ERROR("invalid root being removed!");
+ return IPC_OK();
+ }
+
+ RemoteAccessible* root = rootEntry->mProxy;
+ if (!root) {
+ NS_ERROR("invalid root being removed!");
+ return IPC_OK();
+ }
+
+ RemoteAccessible* parent = root->RemoteParent();
+ PlatformShowHideEvent(root, parent, false, aFromUser);
+
+ RefPtr<xpcAccHideEvent> event = nullptr;
+ if (nsCoreUtils::AccEventObserversExist()) {
+ uint32_t type = nsIAccessibleEvent::EVENT_HIDE;
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(root);
+ xpcAccessibleGeneric* xpcParent = GetXPCAccessible(parent);
+ RemoteAccessible* next = root->RemoteNextSibling();
+ xpcAccessibleGeneric* xpcNext = next ? GetXPCAccessible(next) : nullptr;
+ RemoteAccessible* prev = root->RemotePrevSibling();
+ xpcAccessibleGeneric* xpcPrev = prev ? GetXPCAccessible(prev) : nullptr;
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsINode* node = nullptr;
+ event = new xpcAccHideEvent(type, xpcAcc, doc, node, aFromUser, xpcParent,
+ xpcNext, xpcPrev);
+ }
+
+ parent->RemoveChild(root);
+ ShutdownOrPrepareForMove(root);
+
+ MOZ_ASSERT(CheckDocTree());
+
+ if (event) {
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvEvent(
+ const uint64_t& aID, const uint32_t& aEventType) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+ if (aEventType == 0 || aEventType >= nsIAccessibleEvent::EVENT_LAST_ENTRY) {
+ MOZ_ASSERT_UNREACHABLE("Invalid event");
+ return IPC_FAIL(this, "Invalid event");
+ }
+
+ RemoteAccessible* remote = GetAccessible(aID);
+ if (!remote) {
+ NS_ERROR("no proxy for event!");
+ return IPC_OK();
+ }
+
+ FireEvent(remote, aEventType);
+ return IPC_OK();
+}
+
+void DocAccessibleParent::FireEvent(RemoteAccessible* aAcc,
+ const uint32_t& aEventType) {
+ if (aEventType == nsIAccessibleEvent::EVENT_REORDER ||
+ aEventType == nsIAccessibleEvent::EVENT_INNER_REORDER) {
+ uint32_t count = aAcc->ChildCount();
+ for (uint32_t c = 0; c < count; ++c) {
+ aAcc->RemoteChildAt(c)->InvalidateGroupInfo();
+ }
+ } else if (aEventType == nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE &&
+ aAcc == this) {
+ // A DocAccessible gets the STALE state while it is still loading, but we
+ // don't fire a state change for that. That state might have been
+ // included in the initial cache push, so clear it here.
+ // We also clear the BUSY state here. Although we do fire a state change
+ // for that, we fire it after doc load complete. It doesn't make sense
+ // for the document to report BUSY after doc load complete and doing so
+ // confuses JAWS.
+ UpdateStateCache(states::STALE | states::BUSY, false);
+ }
+
+ PlatformEvent(aAcc, aEventType);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return;
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(aAcc);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsINode* node = nullptr;
+ bool fromUser = true; // XXX fix me
+ RefPtr<xpcAccEvent> event =
+ new xpcAccEvent(aEventType, xpcAcc, doc, node, fromUser);
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvStateChangeEvent(
+ const uint64_t& aID, const uint64_t& aState, const bool& aEnabled) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ RemoteAccessible* target = GetAccessible(aID);
+ if (!target) {
+ NS_ERROR("we don't know about the target of a state change event!");
+ return IPC_OK();
+ }
+
+ target->UpdateStateCache(aState, aEnabled);
+ if (nsCOMPtr<nsIObserverService> obsService =
+ services::GetObserverService()) {
+ obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr);
+ }
+ PlatformStateChangeEvent(target, aState, aEnabled);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return IPC_OK();
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ uint32_t type = nsIAccessibleEvent::EVENT_STATE_CHANGE;
+ bool extra;
+ uint32_t state = nsAccUtils::To32States(aState, &extra);
+ bool fromUser = true; // XXX fix this
+ nsINode* node = nullptr; // XXX can we do better?
+ RefPtr<xpcAccStateChangeEvent> event = new xpcAccStateChangeEvent(
+ type, xpcAcc, doc, node, fromUser, state, extra, aEnabled);
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvCaretMoveEvent(
+ const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect,
+ const int32_t& aOffset, const bool& aIsSelectionCollapsed,
+ const bool& aIsAtEndOfLine, const int32_t& aGranularity,
+ const bool& aFromUser) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ RemoteAccessible* proxy = GetAccessible(aID);
+ if (!proxy) {
+ NS_ERROR("unknown caret move event target!");
+ return IPC_OK();
+ }
+
+ mCaretId = aID;
+ mCaretOffset = aOffset;
+ mIsCaretAtEndOfLine = aIsAtEndOfLine;
+ if (aIsSelectionCollapsed) {
+ // We don't fire selection events for collapsed selections, but we need to
+ // ensure we don't have a stale cached selection; e.g. when selecting
+ // forward and then unselecting backward.
+ mTextSelections.ClearAndRetainStorage();
+ mTextSelections.AppendElement(TextRangeData(aID, aID, aOffset, aOffset));
+ }
+
+ PlatformCaretMoveEvent(proxy, aOffset, aIsSelectionCollapsed, aGranularity,
+ aCaretRect, aFromUser);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return IPC_OK();
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsINode* node = nullptr;
+ bool fromUser = true; // XXX fix me
+ uint32_t type = nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED;
+ RefPtr<xpcAccCaretMoveEvent> event = new xpcAccCaretMoveEvent(
+ type, xpcAcc, doc, node, fromUser, aOffset, aIsSelectionCollapsed,
+ aIsAtEndOfLine, aGranularity);
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvTextChangeEvent(
+ const uint64_t& aID, const nsAString& aStr, const int32_t& aStart,
+ const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ RemoteAccessible* target = GetAccessible(aID);
+ if (!target) {
+ NS_ERROR("text change event target is unknown!");
+ return IPC_OK();
+ }
+
+ PlatformTextChangeEvent(target, aStr, aStart, aLen, aIsInsert, aFromUser);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return IPC_OK();
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ uint32_t type = aIsInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED
+ : nsIAccessibleEvent::EVENT_TEXT_REMOVED;
+ nsINode* node = nullptr;
+ RefPtr<xpcAccTextChangeEvent> event = new xpcAccTextChangeEvent(
+ type, xpcAcc, doc, node, aFromUser, aStart, aLen, aIsInsert, aStr);
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvSelectionEvent(
+ const uint64_t& aID, const uint64_t& aWidgetID, const uint32_t& aType) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+ if (aType == 0 || aType >= nsIAccessibleEvent::EVENT_LAST_ENTRY) {
+ MOZ_ASSERT_UNREACHABLE("Invalid event");
+ return IPC_FAIL(this, "Invalid event");
+ }
+
+ RemoteAccessible* target = GetAccessible(aID);
+ RemoteAccessible* widget = GetAccessible(aWidgetID);
+ if (!target || !widget) {
+ NS_ERROR("invalid id in selection event");
+ return IPC_OK();
+ }
+
+ PlatformSelectionEvent(target, widget, aType);
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return IPC_OK();
+ }
+ xpcAccessibleGeneric* xpcTarget = GetXPCAccessible(target);
+ xpcAccessibleDocument* xpcDoc = GetAccService()->GetXPCDocument(this);
+ RefPtr<xpcAccEvent> event =
+ new xpcAccEvent(aType, xpcTarget, xpcDoc, nullptr, false);
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvScrollingEvent(
+ const uint64_t& aID, const uint64_t& aType, const uint32_t& aScrollX,
+ const uint32_t& aScrollY, const uint32_t& aMaxScrollX,
+ const uint32_t& aMaxScrollY) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+ if (aType == 0 || aType >= nsIAccessibleEvent::EVENT_LAST_ENTRY) {
+ MOZ_ASSERT_UNREACHABLE("Invalid event");
+ return IPC_FAIL(this, "Invalid event");
+ }
+
+ RemoteAccessible* target = GetAccessible(aID);
+ if (!target) {
+ NS_ERROR("no proxy for event!");
+ return IPC_OK();
+ }
+
+#if defined(ANDROID)
+ PlatformScrollingEvent(target, aType, aScrollX, aScrollY, aMaxScrollX,
+ aMaxScrollY);
+#else
+ PlatformEvent(target, aType);
+#endif
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return IPC_OK();
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsINode* node = nullptr;
+ bool fromUser = true; // XXX: Determine if this was from user input.
+ RefPtr<xpcAccScrollingEvent> event =
+ new xpcAccScrollingEvent(aType, xpcAcc, doc, node, fromUser, aScrollX,
+ aScrollY, aMaxScrollX, aMaxScrollY);
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvCache(
+ const mozilla::a11y::CacheUpdateType& aUpdateType,
+ nsTArray<CacheData>&& aData) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ for (auto& entry : aData) {
+ RemoteAccessible* remote = GetAccessible(entry.ID());
+ if (!remote) {
+ MOZ_ASSERT_UNREACHABLE("No remote found!");
+ continue;
+ }
+
+ remote->ApplyCache(aUpdateType, entry.Fields());
+ }
+
+ if (nsCOMPtr<nsIObserverService> obsService =
+ services::GetObserverService()) {
+ obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvSelectedAccessiblesChanged(
+ nsTArray<uint64_t>&& aSelectedIDs, nsTArray<uint64_t>&& aUnselectedIDs) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ for (auto& id : aSelectedIDs) {
+ RemoteAccessible* remote = GetAccessible(id);
+ if (!remote) {
+ MOZ_ASSERT_UNREACHABLE("No remote found!");
+ continue;
+ }
+
+ remote->UpdateStateCache(states::SELECTED, true);
+ }
+
+ for (auto& id : aUnselectedIDs) {
+ RemoteAccessible* remote = GetAccessible(id);
+ if (!remote) {
+ MOZ_ASSERT_UNREACHABLE("No remote found!");
+ continue;
+ }
+
+ remote->UpdateStateCache(states::SELECTED, false);
+ }
+
+ if (nsCOMPtr<nsIObserverService> obsService =
+ services::GetObserverService()) {
+ obsService->NotifyObservers(nullptr, NS_ACCESSIBLE_CACHE_TOPIC, nullptr);
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvAccessiblesWillMove(
+ nsTArray<uint64_t>&& aIDs) {
+ for (uint64_t id : aIDs) {
+ mMovingIDs.EnsureInserted(id);
+ }
+ return IPC_OK();
+}
+
+#if !defined(XP_WIN)
+mozilla::ipc::IPCResult DocAccessibleParent::RecvAnnouncementEvent(
+ const uint64_t& aID, const nsAString& aAnnouncement,
+ const uint16_t& aPriority) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ RemoteAccessible* target = GetAccessible(aID);
+ if (!target) {
+ NS_ERROR("no proxy for event!");
+ return IPC_OK();
+ }
+
+# if defined(ANDROID)
+ PlatformAnnouncementEvent(target, aAnnouncement, aPriority);
+# endif
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return IPC_OK();
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ RefPtr<xpcAccAnnouncementEvent> event = new xpcAccAnnouncementEvent(
+ nsIAccessibleEvent::EVENT_ANNOUNCEMENT, xpcAcc, doc, nullptr, false,
+ aAnnouncement, aPriority);
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+
+ return IPC_OK();
+}
+#endif // !defined(XP_WIN)
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvTextSelectionChangeEvent(
+ const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ RemoteAccessible* target = GetAccessible(aID);
+ if (!target) {
+ NS_ERROR("no proxy for event!");
+ return IPC_OK();
+ }
+
+ mTextSelections.ClearAndRetainStorage();
+ mTextSelections.AppendElements(aSelection);
+
+#ifdef MOZ_WIDGET_COCOA
+ AutoTArray<TextRange, 1> ranges;
+ SelectionRanges(&ranges);
+ PlatformTextSelectionChangeEvent(target, ranges);
+#else
+ PlatformEvent(target, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED);
+#endif
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return IPC_OK();
+ }
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+ xpcAccessibleDocument* doc = nsAccessibilityService::GetXPCDocument(this);
+ nsINode* node = nullptr;
+ bool fromUser = true; // XXX fix me
+ RefPtr<xpcAccEvent> event =
+ new xpcAccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, xpcAcc,
+ doc, node, fromUser);
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvRoleChangedEvent(
+ const a11y::role& aRole, const uint8_t& aRoleMapEntryIndex) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+ if (!aria::IsRoleMapIndexValid(aRoleMapEntryIndex)) {
+ MOZ_ASSERT_UNREACHABLE("Invalid role map entry index");
+ return IPC_FAIL(this, "Invalid role map entry index");
+ }
+
+ mRole = aRole;
+ mRoleMapEntryIndex = aRoleMapEntryIndex;
+
+#ifdef MOZ_WIDGET_COCOA
+ PlatformRoleChangedEvent(this, aRole, aRoleMapEntryIndex);
+#endif
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvBindChildDoc(
+ NotNull<PDocAccessibleParent*> aChildDoc, const uint64_t& aID) {
+ ACQUIRE_ANDROID_LOCK
+ // One document should never directly be the child of another.
+ // We should always have at least an outer doc accessible in between.
+ MOZ_ASSERT(aID);
+ if (!aID) return IPC_FAIL(this, "ID is 0!");
+
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ MOZ_ASSERT(CheckDocTree());
+
+ auto childDoc = static_cast<DocAccessibleParent*>(aChildDoc.get());
+ childDoc->Unbind();
+ ipc::IPCResult result = AddChildDoc(childDoc, aID, false);
+ MOZ_ASSERT(result);
+ MOZ_ASSERT(CheckDocTree());
+#ifdef DEBUG
+ if (!result) {
+ return result;
+ }
+#else
+ result = IPC_OK();
+#endif
+
+ return result;
+}
+
+ipc::IPCResult DocAccessibleParent::AddChildDoc(DocAccessibleParent* aChildDoc,
+ uint64_t aParentID,
+ bool aCreating) {
+ // We do not use GetAccessible here because we want to be sure to not get the
+ // document it self.
+ ProxyEntry* e = mAccessibles.GetEntry(aParentID);
+ if (!e) {
+#ifndef FUZZING_SNAPSHOT
+ // This diagnostic assert and the one down below expect a well-behaved
+ // child process. In IPC fuzzing, we directly fuzz parameters of each
+ // method over IPDL and the asserts are not valid under these conditions.
+ MOZ_DIAGNOSTIC_ASSERT(false, "Binding to nonexistent proxy!");
+#endif
+ return IPC_FAIL(this, "binding to nonexistant proxy!");
+ }
+
+ RemoteAccessible* outerDoc = e->mProxy;
+ MOZ_ASSERT(outerDoc);
+
+ // OuterDocAccessibles are expected to only have a document as a child.
+ // However for compatibility we tolerate replacing one document with another
+ // here.
+ if (!outerDoc->IsOuterDoc() || outerDoc->ChildCount() > 1 ||
+ (outerDoc->ChildCount() == 1 && !outerDoc->RemoteChildAt(0)->IsDoc())) {
+#ifndef FUZZING_SNAPSHOT
+ MOZ_DIAGNOSTIC_ASSERT(false,
+ "Binding to parent that isn't a valid OuterDoc!");
+#endif
+ return IPC_FAIL(this, "Binding to parent that isn't a valid OuterDoc!");
+ }
+
+ if (outerDoc->ChildCount() == 1) {
+ MOZ_ASSERT(outerDoc->RemoteChildAt(0)->AsDoc());
+ outerDoc->RemoteChildAt(0)->AsDoc()->Unbind();
+ }
+
+ aChildDoc->SetParent(outerDoc);
+ outerDoc->SetChildDoc(aChildDoc);
+ mChildDocs.AppendElement(aChildDoc->mActorID);
+ aChildDoc->mParentDoc = mActorID;
+
+ if (aCreating) {
+ ProxyCreated(aChildDoc);
+ }
+
+ if (aChildDoc->IsTopLevelInContentProcess()) {
+ // aChildDoc is an embedded document in a different content process to
+ // this document.
+ auto embeddedBrowser =
+ static_cast<dom::BrowserParent*>(aChildDoc->Manager());
+ dom::BrowserBridgeParent* bridge =
+ embeddedBrowser->GetBrowserBridgeParent();
+ if (bridge) {
+#if defined(XP_WIN)
+ if (nsWinUtils::IsWindowEmulationStarted()) {
+ aChildDoc->SetEmulatedWindowHandle(mEmulatedWindowHandle);
+ }
+#endif // defined(XP_WIN)
+ // We need to fire a reorder event on the outer doc accessible.
+ // For same-process documents, this is fired by the content process, but
+ // this isn't possible when the document is in a different process to its
+ // embedder.
+ // FireEvent fires both OS and XPCOM events.
+ FireEvent(outerDoc, nsIAccessibleEvent::EVENT_REORDER);
+ }
+ }
+
+ return IPC_OK();
+}
+
+ipc::IPCResult DocAccessibleParent::AddChildDoc(
+ dom::BrowserBridgeParent* aBridge) {
+ MOZ_ASSERT(aBridge->GetEmbedderAccessibleDoc() == this);
+ uint64_t parentId = aBridge->GetEmbedderAccessibleId();
+ MOZ_ASSERT(parentId);
+ if (!mAccessibles.GetEntry(parentId)) {
+ // Sometimes, this gets called before the embedder sends us the
+ // OuterDocAccessible. We must add the child when the OuterDocAccessible
+ // gets created later.
+ mPendingOOPChildDocs.Insert(aBridge);
+ return IPC_OK();
+ }
+ return AddChildDoc(aBridge->GetDocAccessibleParent(), parentId,
+ /* aCreating */ false);
+}
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvShutdown() {
+ ACQUIRE_ANDROID_LOCK
+ Destroy();
+
+ auto mgr = static_cast<dom::BrowserParent*>(Manager());
+ if (!mgr->IsDestroyed()) {
+ if (!PDocAccessibleParent::Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+ }
+
+ return IPC_OK();
+}
+
+void DocAccessibleParent::Destroy() {
+ // If we are already shutdown that is because our containing tab parent is
+ // shutting down in which case we don't need to do anything.
+ if (mShutdown) {
+ return;
+ }
+
+ mShutdown = true;
+ mBrowsingContext = nullptr;
+
+#ifdef ANDROID
+ if (FocusMgr() && FocusMgr()->IsFocusedRemoteDoc(this)) {
+ FocusMgr()->SetFocusedRemoteDoc(nullptr);
+ }
+#endif
+
+ MOZ_DIAGNOSTIC_ASSERT(LiveDocs().Contains(mActorID));
+ uint32_t childDocCount = mChildDocs.Length();
+ for (uint32_t i = 0; i < childDocCount; i++) {
+ for (uint32_t j = i + 1; j < childDocCount; j++) {
+ MOZ_DIAGNOSTIC_ASSERT(mChildDocs[i] != mChildDocs[j]);
+ }
+ }
+
+ // XXX This indirection through the hash map of live documents shouldn't be
+ // needed, but be paranoid for now.
+ int32_t actorID = mActorID;
+ for (uint32_t i = childDocCount - 1; i < childDocCount; i--) {
+ DocAccessibleParent* thisDoc = LiveDocs().Get(actorID);
+ MOZ_ASSERT(thisDoc);
+ if (!thisDoc) {
+ return;
+ }
+
+ thisDoc->ChildDocAt(i)->Destroy();
+ }
+
+ for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) {
+ RemoteAccessible* acc = iter.Get()->mProxy;
+ MOZ_ASSERT(acc != this);
+ if (acc->IsTable()) {
+ CachedTableAccessible::Invalidate(acc);
+ }
+ ProxyDestroyed(acc);
+ iter.Remove();
+ }
+
+ DocAccessibleParent* thisDoc = LiveDocs().Get(actorID);
+ MOZ_ASSERT(thisDoc);
+ if (!thisDoc) {
+ return;
+ }
+
+ mChildren.Clear();
+ // The code above should have already completely cleared these, but to be
+ // extra safe make sure they are cleared here.
+ thisDoc->mAccessibles.Clear();
+ thisDoc->mChildDocs.Clear();
+
+ DocManager::NotifyOfRemoteDocShutdown(thisDoc);
+ thisDoc = LiveDocs().Get(actorID);
+ MOZ_ASSERT(thisDoc);
+ if (!thisDoc) {
+ return;
+ }
+
+ ProxyDestroyed(thisDoc);
+ thisDoc = LiveDocs().Get(actorID);
+ MOZ_ASSERT(thisDoc);
+ if (!thisDoc) {
+ return;
+ }
+
+ if (DocAccessibleParent* parentDoc = thisDoc->ParentDoc()) {
+ parentDoc->RemoveChildDoc(thisDoc);
+ } else if (IsTopLevel()) {
+ GetAccService()->RemoteDocShutdown(this);
+ }
+}
+
+void DocAccessibleParent::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(CheckDocTree());
+ if (!mShutdown) {
+ ACQUIRE_ANDROID_LOCK
+ Destroy();
+ }
+}
+
+DocAccessibleParent* DocAccessibleParent::ParentDoc() const {
+ if (mParentDoc == kNoParentDoc) {
+ return nullptr;
+ }
+
+ return LiveDocs().Get(mParentDoc);
+}
+
+bool DocAccessibleParent::CheckDocTree() const {
+ size_t childDocs = mChildDocs.Length();
+ for (size_t i = 0; i < childDocs; i++) {
+ const DocAccessibleParent* childDoc = ChildDocAt(i);
+ if (!childDoc || childDoc->ParentDoc() != this) return false;
+
+ if (!childDoc->CheckDocTree()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+xpcAccessibleGeneric* DocAccessibleParent::GetXPCAccessible(
+ RemoteAccessible* aProxy) {
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ MOZ_ASSERT(doc);
+
+ return doc->GetAccessible(aProxy);
+}
+
+#if defined(XP_WIN)
+void DocAccessibleParent::MaybeInitWindowEmulation() {
+ if (!nsWinUtils::IsWindowEmulationStarted()) {
+ return;
+ }
+
+ // XXX get the bounds from the browserParent instead of poking at accessibles
+ // which might not exist yet.
+ LocalAccessible* outerDoc = OuterDocOfRemoteBrowser();
+ if (!outerDoc) {
+ return;
+ }
+
+ RootAccessible* rootDocument = outerDoc->RootAccessible();
+ MOZ_ASSERT(rootDocument);
+
+ bool isActive = true;
+ LayoutDeviceIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0);
+ if (Compatibility::IsDolphin()) {
+ rect = Bounds();
+ LayoutDeviceIntRect rootRect = rootDocument->Bounds();
+ rect.MoveToX(rootRect.X() - rect.X());
+ rect.MoveToY(rect.Y() - rootRect.Y());
+
+ auto browserParent = static_cast<dom::BrowserParent*>(Manager());
+ isActive = browserParent->GetDocShellIsActive();
+ }
+
+ // onCreate is guaranteed to be called synchronously by
+ // nsWinUtils::CreateNativeWindow, so this reference isn't really necessary.
+ // However, static analysis complains without it.
+ RefPtr<DocAccessibleParent> thisRef = this;
+ nsWinUtils::NativeWindowCreateProc onCreate([thisRef](HWND aHwnd) -> void {
+ ::SetPropW(aHwnd, kPropNameDocAccParent,
+ reinterpret_cast<HANDLE>(thisRef.get()));
+ thisRef->SetEmulatedWindowHandle(aHwnd);
+ });
+
+ HWND parentWnd = reinterpret_cast<HWND>(rootDocument->GetNativeWindow());
+ DebugOnly<HWND> hWnd = nsWinUtils::CreateNativeWindow(
+ kClassNameTabContent, parentWnd, rect.X(), rect.Y(), rect.Width(),
+ rect.Height(), isActive, &onCreate);
+ MOZ_ASSERT(hWnd);
+}
+
+void DocAccessibleParent::SetEmulatedWindowHandle(HWND aWindowHandle) {
+ if (!aWindowHandle && mEmulatedWindowHandle && IsTopLevel()) {
+ ::DestroyWindow(mEmulatedWindowHandle);
+ }
+ mEmulatedWindowHandle = aWindowHandle;
+}
+#endif // defined(XP_WIN)
+
+mozilla::ipc::IPCResult DocAccessibleParent::RecvFocusEvent(
+ const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect) {
+ ACQUIRE_ANDROID_LOCK
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ RemoteAccessible* proxy = GetAccessible(aID);
+ if (!proxy) {
+ NS_ERROR("no proxy for event!");
+ return IPC_OK();
+ }
+
+#ifdef ANDROID
+ if (FocusMgr()) {
+ FocusMgr()->SetFocusedRemoteDoc(this);
+ }
+#endif
+
+ mFocus = aID;
+ PlatformFocusEvent(proxy, aCaretRect);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return IPC_OK();
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsINode* node = nullptr;
+ bool fromUser = true; // XXX fix me
+ RefPtr<xpcAccEvent> event = new xpcAccEvent(nsIAccessibleEvent::EVENT_FOCUS,
+ xpcAcc, doc, node, fromUser);
+ nsCoreUtils::DispatchAccEvent(std::move(event));
+
+ return IPC_OK();
+}
+
+void DocAccessibleParent::SelectionRanges(nsTArray<TextRange>* aRanges) const {
+ aRanges->SetCapacity(mTextSelections.Length());
+ for (const auto& data : mTextSelections) {
+ // Selection ranges should usually be in sync with the tree. However, tree
+ // and selection updates happen using separate IPDL calls, so it's possible
+ // for a client selection query to arrive between them. Thus, we validate
+ // the Accessibles and offsets here.
+ auto* startAcc =
+ const_cast<RemoteAccessible*>(GetAccessible(data.StartID()));
+ auto* endAcc = const_cast<RemoteAccessible*>(GetAccessible(data.EndID()));
+ if (!startAcc || !endAcc) {
+ continue;
+ }
+ uint32_t startCount = startAcc->CharacterCount();
+ if (startCount == 0 ||
+ data.StartOffset() > static_cast<int32_t>(startCount)) {
+ continue;
+ }
+ uint32_t endCount = endAcc->CharacterCount();
+ if (endCount == 0 || data.EndOffset() > static_cast<int32_t>(endCount)) {
+ continue;
+ }
+ aRanges->AppendElement(TextRange(const_cast<DocAccessibleParent*>(this),
+ startAcc, data.StartOffset(), endAcc,
+ data.EndOffset()));
+ }
+}
+
+Accessible* DocAccessibleParent::FocusedChild() {
+ LocalAccessible* outerDoc = OuterDocOfRemoteBrowser();
+ if (!outerDoc) {
+ return nullptr;
+ }
+
+ RootAccessible* rootDocument = outerDoc->RootAccessible();
+ return rootDocument->FocusedChild();
+}
+
+void DocAccessibleParent::URL(nsACString& aURL) const {
+ if (!mBrowsingContext) {
+ return;
+ }
+ nsCOMPtr<nsIURI> uri = mBrowsingContext->GetCurrentURI();
+ if (!uri) {
+ return;
+ }
+ // Let's avoid treating too long URI in the main process for avoiding
+ // memory fragmentation as far as possible.
+ if (uri->SchemeIs("data") || uri->SchemeIs("blob")) {
+ return;
+ }
+ nsCOMPtr<nsIIOService> io = mozilla::components::IO::Service();
+ if (NS_WARN_IF(!io)) {
+ return;
+ }
+ nsCOMPtr<nsIURI> exposableURI;
+ if (NS_FAILED(io->CreateExposableURI(uri, getter_AddRefs(exposableURI))) ||
+ MOZ_UNLIKELY(!exposableURI)) {
+ return;
+ }
+ exposableURI->GetSpec(aURL);
+}
+
+void DocAccessibleParent::URL(nsAString& aURL) const {
+ nsAutoCString url;
+ URL(url);
+ CopyUTF8toUTF16(url, aURL);
+}
+
+void DocAccessibleParent::MimeType(nsAString& aMime) const {
+ if (mCachedFields) {
+ mCachedFields->GetAttribute(CacheKey::MimeType, aMime);
+ }
+}
+
+Relation DocAccessibleParent::RelationByType(RelationType aType) const {
+ // If the accessible is top-level, provide the NODE_CHILD_OF relation so that
+ // MSAA clients can easily get to true parent instead of getting to oleacc's
+ // ROLE_WINDOW accessible when window emulation is enabled which will prevent
+ // us from going up further (because it is system generated and has no idea
+ // about the hierarchy above it).
+ if (aType == RelationType::NODE_CHILD_OF && IsTopLevel()) {
+ return Relation(Parent());
+ }
+
+ return RemoteAccessible::RelationByType(aType);
+}
+
+DocAccessibleParent* DocAccessibleParent::GetFrom(
+ dom::BrowsingContext* aBrowsingContext) {
+ if (!aBrowsingContext) {
+ return nullptr;
+ }
+
+ dom::BrowserParent* bp = aBrowsingContext->Canonical()->GetBrowserParent();
+ if (!bp) {
+ return nullptr;
+ }
+
+ const ManagedContainer<PDocAccessibleParent>& docs =
+ bp->ManagedPDocAccessibleParent();
+ for (auto* key : docs) {
+ // Iterate over our docs until we find one with a browsing
+ // context that matches the one we passed in. Return that
+ // document.
+ auto* doc = static_cast<a11y::DocAccessibleParent*>(key);
+ if (doc->GetBrowsingContext() == aBrowsingContext) {
+ return doc;
+ }
+ }
+
+ return nullptr;
+}
+
+size_t DocAccessibleParent::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
+ size_t size = 0;
+
+ size += RemoteAccessible::SizeOfExcludingThis(aMallocSizeOf);
+
+ size += mReverseRelations.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto i = mReverseRelations.Iter(); !i.Done(); i.Next()) {
+ size += i.Data().ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto j = i.Data().Iter(); !j.Done(); j.Next()) {
+ size += j.Data().ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ }
+
+ size += mOnScreenAccessibles.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ size += mChildDocs.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ size += mAccessibles.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto i = mAccessibles.Iter(); !i.Done(); i.Next()) {
+ size += i.Get()->mProxy->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ size += mPendingOOPChildDocs.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ // The mTextSelections array contains structs of integers. We can count them
+ // by counting the size of the array - there's no deep structure here.
+ size += mTextSelections.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ return size;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOfAccessibilityCache);
+
+NS_IMETHODIMP
+DocAccessibleParent::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnon) {
+ nsAutoCString path;
+
+ if (aAnon) {
+ path = nsPrintfCString("explicit/a11y/cache(%" PRIu64 ")", mActorID);
+ } else {
+ nsCString url;
+ URL(url);
+ url.ReplaceChar(
+ '/', '\\'); // Tell the memory reporter this is not a path seperator.
+ path = nsPrintfCString("explicit/a11y/cache(%s)", url.get());
+ }
+
+ aHandleReport->Callback(
+ /* process */ ""_ns, path, KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(MallocSizeOfAccessibilityCache),
+ nsLiteralCString("Size of the accessability cache for this document."),
+ aData);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(DocAccessibleParent, nsIMemoryReporter);
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/ipc/DocAccessibleParent.h b/accessible/ipc/DocAccessibleParent.h
new file mode 100644
index 0000000000..bb05fbafad
--- /dev/null
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -0,0 +1,400 @@
+/* -*- 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_DocAccessibleParent_h
+#define mozilla_a11y_DocAccessibleParent_h
+
+#include "nsAccessibilityService.h"
+#include "mozilla/a11y/PDocAccessibleParent.h"
+#include "mozilla/a11y/RemoteAccessible.h"
+#include "mozilla/dom/BrowserBridgeParent.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace dom {
+class CanonicalBrowsingContext;
+}
+
+namespace a11y {
+
+class TextRange;
+class xpcAccessibleGeneric;
+
+/*
+ * These objects live in the main process and comunicate with and represent
+ * an accessible document in a content process.
+ */
+class DocAccessibleParent : public RemoteAccessible,
+ public PDocAccessibleParent,
+ public nsIMemoryReporter {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ private:
+ DocAccessibleParent();
+
+ public:
+ static already_AddRefed<DocAccessibleParent> New();
+
+ /**
+ * Set this as a top level document; i.e. it is not embedded by another remote
+ * document. This also means it is a top level document in its content
+ * process.
+ * Tab documents are top level documents.
+ */
+ void SetTopLevel() {
+ mTopLevel = true;
+ mTopLevelInContentProcess = true;
+ }
+ bool IsTopLevel() const { return mTopLevel; }
+
+ /**
+ * Set this as a top level document in its content process.
+ * Note that this could be an out-of-process iframe embedded by a remote
+ * embedder document. In that case, IsToplevel() will return false, but
+ * IsTopLevelInContentProcess() will return true.
+ */
+ void SetTopLevelInContentProcess() { mTopLevelInContentProcess = true; }
+ bool IsTopLevelInContentProcess() const { return mTopLevelInContentProcess; }
+
+ /**
+ * Determine whether this is an out-of-process iframe document, embedded by a
+ * remote embedder document.
+ */
+ bool IsOOPIframeDoc() const {
+ return !mTopLevel && mTopLevelInContentProcess;
+ }
+
+ bool IsShutdown() const { return mShutdown; }
+
+ /**
+ * Mark this actor as shutdown without doing any cleanup. This should only
+ * be called on actors that have just been initialized, so probably only from
+ * RecvPDocAccessibleConstructor.
+ */
+ void MarkAsShutdown() {
+ MOZ_ASSERT(mChildDocs.IsEmpty());
+ MOZ_ASSERT(mAccessibles.Count() == 0);
+ MOZ_ASSERT(!mBrowsingContext);
+ mShutdown = true;
+ }
+
+ void SetBrowsingContext(dom::CanonicalBrowsingContext* aBrowsingContext);
+
+ dom::CanonicalBrowsingContext* GetBrowsingContext() const {
+ return mBrowsingContext;
+ }
+
+ /*
+ * Called when a message from a document in a child process notifies the main
+ * process it is firing an event.
+ */
+ virtual mozilla::ipc::IPCResult RecvEvent(const uint64_t& aID,
+ const uint32_t& aType) override;
+
+ virtual mozilla::ipc::IPCResult RecvShowEvent(
+ nsTArray<AccessibleData>&& aNewTree, const bool& aEventSuppressed,
+ const bool& aComplete, const bool& aFromUser) override;
+ virtual mozilla::ipc::IPCResult RecvHideEvent(const uint64_t& aRootID,
+ const bool& aFromUser) override;
+ mozilla::ipc::IPCResult RecvStateChangeEvent(const uint64_t& aID,
+ const uint64_t& aState,
+ const bool& aEnabled) final;
+
+ mozilla::ipc::IPCResult RecvCaretMoveEvent(
+ const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect,
+ const int32_t& aOffset, const bool& aIsSelectionCollapsed,
+ const bool& aIsAtEndOfLine, const int32_t& aGranularity,
+ const bool& aFromUser) final;
+
+ virtual mozilla::ipc::IPCResult RecvTextChangeEvent(
+ const uint64_t& aID, const nsAString& aStr, const int32_t& aStart,
+ const uint32_t& aLen, const bool& aIsInsert,
+ const bool& aFromUser) override;
+
+ virtual mozilla::ipc::IPCResult RecvFocusEvent(
+ const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect) override;
+
+ virtual mozilla::ipc::IPCResult RecvSelectionEvent(
+ const uint64_t& aID, const uint64_t& aWidgetID,
+ const uint32_t& aType) override;
+
+ virtual mozilla::ipc::IPCResult RecvScrollingEvent(
+ const uint64_t& aID, const uint64_t& aType, const uint32_t& aScrollX,
+ const uint32_t& aScrollY, const uint32_t& aMaxScrollX,
+ const uint32_t& aMaxScrollY) override;
+
+ virtual mozilla::ipc::IPCResult RecvCache(
+ const mozilla::a11y::CacheUpdateType& aUpdateType,
+ nsTArray<CacheData>&& aData) override;
+
+ virtual mozilla::ipc::IPCResult RecvSelectedAccessiblesChanged(
+ nsTArray<uint64_t>&& aSelectedIDs,
+ nsTArray<uint64_t>&& aUnselectedIDs) override;
+
+ virtual mozilla::ipc::IPCResult RecvAccessiblesWillMove(
+ nsTArray<uint64_t>&& aIDs) override;
+
+#if !defined(XP_WIN)
+ virtual mozilla::ipc::IPCResult RecvAnnouncementEvent(
+ const uint64_t& aID, const nsAString& aAnnouncement,
+ const uint16_t& aPriority) override;
+#endif
+
+ virtual mozilla::ipc::IPCResult RecvTextSelectionChangeEvent(
+ const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) override;
+
+ mozilla::ipc::IPCResult RecvRoleChangedEvent(
+ const a11y::role& aRole, const uint8_t& aRoleMapEntryIndex) final;
+
+ virtual mozilla::ipc::IPCResult RecvBindChildDoc(
+ NotNull<PDocAccessibleParent*> aChildDoc, const uint64_t& aID) override;
+
+ void Unbind() {
+ if (DocAccessibleParent* parent = ParentDoc()) {
+ parent->RemoveChildDoc(this);
+ }
+
+ SetParent(nullptr);
+ }
+
+ virtual mozilla::ipc::IPCResult RecvShutdown() override;
+ void Destroy();
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ /*
+ * Return the main processes representation of the parent document (if any)
+ * of the document this object represents.
+ */
+ DocAccessibleParent* ParentDoc() const;
+ static const uint64_t kNoParentDoc = UINT64_MAX;
+
+ /**
+ * Called when a document in a content process notifies the main process of a
+ * new child document.
+ * Although this is called internally for OOP child documents, these should be
+ * added via the BrowserBridgeParent version of this method, as the parent id
+ * might not exist yet in that case.
+ */
+ ipc::IPCResult AddChildDoc(DocAccessibleParent* aChildDoc, uint64_t aParentID,
+ bool aCreating = true);
+
+ /**
+ * Called when a document in a content process notifies the main process of a
+ * new OOP child document.
+ */
+ ipc::IPCResult AddChildDoc(dom::BrowserBridgeParent* aBridge);
+
+ void RemovePendingOOPChildDoc(dom::BrowserBridgeParent* aBridge) {
+ mPendingOOPChildDocs.Remove(aBridge);
+ }
+
+ /*
+ * Called when the document in the content process this object represents
+ * notifies the main process a child document has been removed.
+ */
+ void RemoveChildDoc(DocAccessibleParent* aChildDoc) {
+ RemoteAccessible* parent = aChildDoc->RemoteParent();
+ MOZ_ASSERT(parent);
+ if (parent) {
+ aChildDoc->RemoteParent()->ClearChildDoc(aChildDoc);
+ }
+ DebugOnly<bool> result = mChildDocs.RemoveElement(aChildDoc->mActorID);
+ aChildDoc->mParentDoc = kNoParentDoc;
+ MOZ_ASSERT(result);
+ }
+
+ void RemoveAccessible(RemoteAccessible* aAccessible) {
+ MOZ_DIAGNOSTIC_ASSERT(mAccessibles.GetEntry(aAccessible->ID()));
+ mAccessibles.RemoveEntry(aAccessible->ID());
+ }
+
+ /**
+ * Return the accessible for given id.
+ */
+ RemoteAccessible* GetAccessible(uintptr_t aID) {
+ if (!aID) return this;
+
+ ProxyEntry* e = mAccessibles.GetEntry(aID);
+ return e ? e->mProxy : nullptr;
+ }
+
+ const RemoteAccessible* GetAccessible(uintptr_t aID) const {
+ return const_cast<DocAccessibleParent*>(this)->GetAccessible(aID);
+ }
+
+ size_t ChildDocCount() const { return mChildDocs.Length(); }
+ const DocAccessibleParent* ChildDocAt(size_t aIdx) const {
+ return const_cast<DocAccessibleParent*>(this)->ChildDocAt(aIdx);
+ }
+ DocAccessibleParent* ChildDocAt(size_t aIdx) {
+ return LiveDocs().Get(mChildDocs[aIdx]);
+ }
+
+#if defined(XP_WIN)
+ void MaybeInitWindowEmulation();
+
+ /**
+ * Set emulated native window handle for a document.
+ * @param aWindowHandle emulated native window handle
+ */
+ void SetEmulatedWindowHandle(HWND aWindowHandle);
+ HWND GetEmulatedWindowHandle() const { return mEmulatedWindowHandle; }
+#endif
+
+ // Accessible
+ virtual Accessible* Parent() const override {
+ if (IsTopLevel()) {
+ return OuterDocOfRemoteBrowser();
+ }
+ return RemoteParent();
+ }
+
+ virtual int32_t IndexInParent() const override {
+ if (IsTopLevel() && OuterDocOfRemoteBrowser()) {
+ // An OuterDoc can only have 1 child.
+ return 0;
+ }
+ return RemoteAccessible::IndexInParent();
+ }
+
+ /**
+ * Get the focused Accessible in this document, if any.
+ */
+ RemoteAccessible* GetFocusedAcc() const {
+ return const_cast<DocAccessibleParent*>(this)->GetAccessible(mFocus);
+ }
+
+ /**
+ * Get the HyperText Accessible containing the caret and the offset of the
+ * caret within. If there is no caret in this document, returns
+ * {nullptr, -1}.
+ */
+ std::pair<RemoteAccessible*, int32_t> GetCaret() const {
+ if (mCaretOffset == -1) {
+ return {nullptr, -1};
+ }
+ RemoteAccessible* acc =
+ const_cast<DocAccessibleParent*>(this)->GetAccessible(mCaretId);
+ if (!acc) {
+ return {nullptr, -1};
+ }
+ return {acc, mCaretOffset};
+ }
+
+ bool IsCaretAtEndOfLine() const { return mIsCaretAtEndOfLine; }
+
+ virtual void SelectionRanges(nsTArray<TextRange>* aRanges) const override;
+
+ virtual Accessible* FocusedChild() override;
+
+ void URL(nsAString& aURL) const;
+ void URL(nsACString& aURL) const;
+
+ void MimeType(nsAString& aURL) const;
+
+ virtual Relation RelationByType(RelationType aType) const override;
+
+ // Tracks cached reverse relations (ie. those not set explicitly by an
+ // attribute like aria-labelledby) for accessibles in this doc. This map is of
+ // the form: {accID, {relationType, [targetAccID, targetAccID, ...]}}
+ nsTHashMap<uint64_t, nsTHashMap<RelationType, nsTArray<uint64_t>>>
+ mReverseRelations;
+
+ // Computed from the viewport cache, the accs referenced by these ids
+ // are currently on screen (making any acc not in this list offscreen).
+ nsTHashSet<uint64_t> mOnScreenAccessibles;
+
+ static DocAccessibleParent* GetFrom(dom::BrowsingContext* aBrowsingContext);
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) override;
+
+ private:
+ ~DocAccessibleParent();
+
+ class ProxyEntry : public PLDHashEntryHdr {
+ public:
+ explicit ProxyEntry(const void*) : mProxy(nullptr) {}
+ ProxyEntry(ProxyEntry&& aOther) : mProxy(aOther.mProxy) {
+ aOther.mProxy = nullptr;
+ }
+ ~ProxyEntry() { delete mProxy; }
+
+ typedef uint64_t KeyType;
+ typedef const void* KeyTypePointer;
+
+ bool KeyEquals(const void* aKey) const {
+ return mProxy->ID() == (uint64_t)aKey;
+ }
+
+ static const void* KeyToPointer(uint64_t aKey) { return (void*)aKey; }
+
+ static PLDHashNumber HashKey(const void* aKey) { return (uint64_t)aKey; }
+
+ enum { ALLOW_MEMMOVE = true };
+
+ RemoteAccessible* mProxy;
+ };
+
+ RemoteAccessible* CreateAcc(const AccessibleData& aAccData);
+ void AttachChild(RemoteAccessible* aParent, uint32_t aIndex,
+ RemoteAccessible* aChild);
+ [[nodiscard]] bool CheckDocTree() const;
+ xpcAccessibleGeneric* GetXPCAccessible(RemoteAccessible* aProxy);
+
+ void FireEvent(RemoteAccessible* aAcc, const uint32_t& aType);
+
+ /**
+ * If this Accessible is being moved, prepare it for reuse. Otherwise, it is
+ * being removed, so shut it down.
+ */
+ void ShutdownOrPrepareForMove(RemoteAccessible* aAcc);
+
+ nsTArray<uint64_t> mChildDocs;
+ uint64_t mParentDoc;
+
+#if defined(XP_WIN)
+ // The handle associated with the emulated window that contains this document
+ HWND mEmulatedWindowHandle;
+#endif // defined(XP_WIN)
+
+ /*
+ * Conceptually this is a map from IDs to proxies, but we store the ID in the
+ * proxy object so we can't use a real map.
+ */
+ nsTHashtable<ProxyEntry> mAccessibles;
+ uint64_t mPendingShowChild = 0;
+ uint64_t mPendingShowParent = 0;
+ uint32_t mPendingShowIndex = 0;
+ nsTHashSet<uint64_t> mMovingIDs;
+ uint64_t mActorID;
+ bool mTopLevel;
+ bool mTopLevelInContentProcess;
+ bool mShutdown;
+ RefPtr<dom::CanonicalBrowsingContext> mBrowsingContext;
+
+ nsTHashSet<RefPtr<dom::BrowserBridgeParent>> mPendingOOPChildDocs;
+
+ uint64_t mFocus;
+ uint64_t mCaretId;
+ int32_t mCaretOffset;
+ bool mIsCaretAtEndOfLine;
+ nsTArray<TextRangeData> mTextSelections;
+
+ static uint64_t sMaxDocID;
+ static nsTHashMap<nsUint64HashKey, DocAccessibleParent*>& LiveDocs() {
+ static nsTHashMap<nsUint64HashKey, DocAccessibleParent*> sLiveDocs;
+ return sLiveDocs;
+ }
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/ipc/DocAccessibleTypes.ipdlh b/accessible/ipc/DocAccessibleTypes.ipdlh
new file mode 100644
index 0000000000..3ef67fc431
--- /dev/null
+++ b/accessible/ipc/DocAccessibleTypes.ipdlh
@@ -0,0 +1,19 @@
+/* -*- 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/. */
+
+[RefCounted] using mozilla::a11y::AccAttributes from "mozilla/a11y/IPCTypes.h";
+
+namespace mozilla {
+namespace a11y {
+
+struct CacheData
+{
+ uint64_t ID;
+ nullable AccAttributes Fields;
+};
+
+}
+}
diff --git a/accessible/ipc/IPCTypes.h b/accessible/ipc/IPCTypes.h
new file mode 100644
index 0000000000..2e911724bb
--- /dev/null
+++ b/accessible/ipc/IPCTypes.h
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=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_IPCTypes_h
+#define mozilla_a11y_IPCTypes_h
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/AccAttributes.h"
+# include "mozilla/a11y/AccTypes.h"
+# include "mozilla/a11y/CacheConstants.h"
+# include "mozilla/a11y/Role.h"
+# include "mozilla/a11y/AccGroupInfo.h"
+# include "mozilla/GfxMessageUtils.h"
+# include "ipc/EnumSerializer.h"
+# include "ipc/IPCMessageUtilsSpecializations.h"
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::a11y::role>
+ : public ContiguousEnumSerializerInclusive<mozilla::a11y::role,
+ mozilla::a11y::role::NOTHING,
+ mozilla::a11y::role::LAST_ROLE> {
+};
+
+template <>
+struct ParamTraits<mozilla::a11y::AccType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::a11y::AccType, mozilla::a11y::AccType::eNoType,
+ mozilla::a11y::AccType::eLastAccType> {};
+
+template <>
+struct ParamTraits<mozilla::a11y::AccGenericType>
+ : public BitFlagsEnumSerializer<
+ mozilla::a11y::AccGenericType,
+ mozilla::a11y::AccGenericType::eAllGenericTypes> {};
+
+template <>
+struct ParamTraits<mozilla::a11y::CacheUpdateType>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::a11y::CacheUpdateType,
+ mozilla::a11y::CacheUpdateType::Initial,
+ mozilla::a11y::CacheUpdateType::Update> {};
+
+template <>
+struct ParamTraits<mozilla::a11y::FontSize> {
+ typedef mozilla::a11y::FontSize paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mValue);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &(aResult->mValue));
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::a11y::DeleteEntry> {
+ typedef mozilla::a11y::DeleteEntry paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mValue);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &(aResult->mValue));
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::a11y::AccGroupInfo> {
+ typedef mozilla::a11y::AccGroupInfo paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ MOZ_ASSERT_UNREACHABLE("Cannot serialize AccGroupInfo");
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ MOZ_ASSERT_UNREACHABLE("Cannot de-serialize AccGroupInfo");
+ return false;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::a11y::Color> {
+ typedef mozilla::a11y::Color paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mValue);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &(aResult->mValue));
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::a11y::AccAttributes*> {
+ typedef mozilla::a11y::AccAttributes paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType* aParam) {
+ if (!aParam) {
+ WriteParam(aWriter, true);
+ return;
+ }
+
+ WriteParam(aWriter, false);
+ uint32_t count = aParam->mData.Count();
+ WriteParam(aWriter, count);
+ for (auto iter = aParam->mData.ConstIter(); !iter.Done(); iter.Next()) {
+ RefPtr<nsAtom> key = iter.Key();
+ WriteParam(aWriter, key);
+ const paramType::AttrValueType& data = iter.Data();
+ WriteParam(aWriter, data);
+ }
+ }
+
+ static bool Read(MessageReader* aReader, RefPtr<paramType>* aResult) {
+ bool isNull = false;
+ if (!ReadParam(aReader, &isNull)) {
+ return false;
+ }
+
+ if (isNull) {
+ *aResult = nullptr;
+ return true;
+ }
+
+ *aResult = mozilla::MakeRefPtr<mozilla::a11y::AccAttributes>();
+ uint32_t count;
+ if (!ReadParam(aReader, &count)) {
+ return false;
+ }
+ for (uint32_t i = 0; i < count; ++i) {
+ RefPtr<nsAtom> key;
+ if (!ReadParam(aReader, &key)) {
+ return false;
+ }
+ paramType::AttrValueType val(0);
+ if (!ReadParam(aReader, &val)) {
+ return false;
+ }
+ (*aResult)->mData.InsertOrUpdate(key, std::move(val));
+ }
+ return true;
+ }
+};
+
+} // namespace IPC
+#else
+namespace mozilla {
+namespace a11y {
+typedef uint32_t role;
+} // namespace a11y
+} // namespace mozilla
+#endif // ACCESSIBILITY
+
+/**
+ * Since IPDL does not support preprocessing, this header file allows us to
+ * define types used by PDocAccessible differently depending on platform.
+ */
+
+#if defined(MOZ_WIDGET_COCOA)
+# if defined(ACCESSIBILITY)
+# include "mozilla/a11y/PlatformExtTypes.h"
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::a11y::EWhichRange>
+ : public ContiguousEnumSerializerInclusive<
+ mozilla::a11y::EWhichRange, mozilla::a11y::EWhichRange::eLeftWord,
+ mozilla::a11y::EWhichRange::eStyle> {};
+
+} // namespace IPC
+
+# else
+namespace mozilla {
+namespace a11y {
+typedef uint32_t EWhichRange;
+} // namespace a11y
+} // namespace mozilla
+# endif // defined(ACCESSIBILITY)
+#endif // defined(MOZ_WIDGET_COCOA)
+
+#endif // mozilla_a11y_IPCTypes_h
diff --git a/accessible/ipc/PDocAccessible.ipdl b/accessible/ipc/PDocAccessible.ipdl
new file mode 100644
index 0000000000..d43fad823e
--- /dev/null
+++ b/accessible/ipc/PDocAccessible.ipdl
@@ -0,0 +1,164 @@
+/* -*- 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 protocol PBrowser;
+
+include DocAccessibleTypes;
+
+include "mozilla/GfxMessageUtils.h";
+
+using mozilla::LayoutDeviceIntRect from "Units.h";
+using mozilla::LayoutDeviceIntPoint from "Units.h";
+using mozilla::LayoutDeviceIntSize from "Units.h";
+using mozilla::a11y::role from "mozilla/a11y/IPCTypes.h";
+using mozilla::a11y::AccType from "mozilla/a11y/IPCTypes.h";
+using mozilla::a11y::AccGenericType from "mozilla/a11y/IPCTypes.h";
+[RefCounted] using mozilla::a11y::AccAttributes from "mozilla/a11y/IPCTypes.h";
+using mozilla::a11y::CacheUpdateType from "mozilla/a11y/IPCTypes.h";
+using mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h";
+
+namespace mozilla {
+namespace a11y {
+
+struct AccessibleData
+{
+ uint64_t ID;
+ role Role;
+ uint64_t ParentID;
+ uint32_t IndexInParent;
+ AccType Type;
+ AccGenericType GenericTypes;
+ uint8_t RoleMapEntryIndex;
+ nullable AccAttributes CacheFields;
+};
+
+struct TextRangeData
+{
+ uint64_t StartID;
+ uint64_t EndID;
+ int32_t StartOffset;
+ int32_t EndOffset;
+};
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+protocol PDocAccessible
+{
+ manager PBrowser;
+
+parent:
+ async Shutdown();
+
+ /*
+ * Notify the parent process the document in the child process is firing an
+ * event.
+ */
+ async Event(uint64_t aID, uint32_t type);
+ async ShowEvent(AccessibleData[] aNewTree, bool aEventSuppressed,
+ bool aComplete, bool aFromuser);
+ async HideEvent(uint64_t aRootID, bool aFromUser);
+ async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled);
+ async CaretMoveEvent(uint64_t aID,
+ LayoutDeviceIntRect aCaretRect,
+ int32_t aOffset,
+ bool aIsSelectionCollapsed, bool aIsAtEndOfLine,
+ int32_t aGranularity, bool aFromUser);
+ async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen,
+ bool aIsInsert, bool aFromUser);
+ async SelectionEvent(uint64_t aID, uint64_t aWidgetID, uint32_t aType);
+ async RoleChangedEvent(role aRole, uint8_t aRoleMapEntryIndex);
+ async FocusEvent(uint64_t aID, LayoutDeviceIntRect aCaretRect);
+ async ScrollingEvent(uint64_t aID, uint64_t aType,
+ uint32_t aScrollX, uint32_t aScrollY,
+ uint32_t aMaxScrollX, uint32_t aMaxScrollY);
+#ifndef XP_WIN
+ async AnnouncementEvent(uint64_t aID,
+ nsString aAnnouncement,
+ uint16_t aPriority);
+#endif
+ async TextSelectionChangeEvent(uint64_t aID, TextRangeData[] aSelection);
+
+ /*
+ * Tell the parent document to bind the existing document as a new child
+ * document.
+ */
+ async BindChildDoc(PDocAccessible aChildDoc, uint64_t aID);
+
+ /*
+ * Cache The World
+ */
+ async Cache(CacheUpdateType aUpdateType, CacheData[] aData);
+
+ /*
+ * Lists of accessibles that either gained or lost a selected state.
+ */
+ async SelectedAccessiblesChanged(uint64_t[] aSelectedIDs, uint64_t[] aUnselectedIDs);
+
+ /*
+ * Tell the parent process that the given Accessibles are about to be moved
+ * via subsequent hide and show events.
+ */
+ async AccessiblesWillMove(uint64_t[] aIDs);
+
+child:
+ async __delete__();
+
+ /*
+ * Called as a result of focus shifting from chrome to content
+ * elements through keyboard navigation.
+ */
+ async RestoreFocus();
+
+ // LocalAccessible
+ async ScrollTo(uint64_t aID, uint32_t aScrollType);
+ async ScrollToPoint(uint64_t aID, uint32_t aScrollType, int32_t aX,
+ int32_t aY);
+#ifndef XP_WIN
+ async Announce(uint64_t aID, nsString aAnnouncement, uint16_t aPriority);
+#endif
+
+ // AccessibleText
+
+ async SetCaretOffset(uint64_t aID, int32_t aOffset);
+
+ async SetTextSelection(uint64_t aStartID, int32_t aStartOffset,
+ uint64_t aEndID, int32_t aEndOffset,
+ int32_t aSelectionNum);
+ async RemoveTextSelection(uint64_t aID, int32_t aSelectionNum);
+
+ async ScrollTextLeafRangeIntoView(uint64_t aStartID, int32_t aStartOffset,
+ uint64_t aEndID, int32_t aEndOffset,
+ uint32_t aScrollType);
+ async ScrollSubstringToPoint(uint64_t aID,
+ int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordinateType,
+ int32_t aX, int32_t aY);
+
+ async ReplaceText(uint64_t aID, nsString aText);
+ async InsertText(uint64_t aID, nsString aText, int32_t aPosition);
+ async CopyText(uint64_t aID, int32_t aStartPos, int32_t aEndPos);
+ async CutText(uint64_t aID, int32_t aStartPos, int32_t aEndPos);
+ async DeleteText(uint64_t aID, int32_t aStartPos, int32_t aEndPos);
+ async PasteText(uint64_t aID, int32_t aPosition);
+
+ async TakeSelection(uint64_t aID);
+ async SetSelected(uint64_t aID, bool aSelected);
+
+ async DoActionAsync(uint64_t aID, uint8_t aIndex);
+
+ async SetCurValue(uint64_t aID, double aValue);
+
+ async TakeFocus(uint64_t aID);
+
+ /*
+ * Verify the cache. Used for testing purposes.
+ */
+ async VerifyCache(uint64_t aID, uint64_t aCacheDomain, nullable AccAttributes aFields);
+
+};
+
+}
+}
diff --git a/accessible/ipc/RemoteAccessible.cpp b/accessible/ipc/RemoteAccessible.cpp
new file mode 100644
index 0000000000..772fc58776
--- /dev/null
+++ b/accessible/ipc/RemoteAccessible.cpp
@@ -0,0 +1,2092 @@
+/* -*- 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 "ARIAMap.h"
+#include "CachedTableAccessible.h"
+#include "RemoteAccessible.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "mozilla/a11y/DocManager.h"
+#include "mozilla/a11y/Platform.h"
+#include "mozilla/a11y/TableAccessible.h"
+#include "mozilla/a11y/TableCellAccessible.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/gfx/Matrix.h"
+#include "nsAccessibilityService.h"
+#include "mozilla/Unused.h"
+#include "nsAccUtils.h"
+#include "nsTextEquivUtils.h"
+#include "Pivot.h"
+#include "Relation.h"
+#include "mozilla/a11y/RelationType.h"
+#include "xpcAccessibleDocument.h"
+
+#ifdef A11Y_LOG
+# include "Logging.h"
+# define VERIFY_CACHE(domain) \
+ if (logging::IsEnabled(logging::eCache)) { \
+ Unused << mDoc->SendVerifyCache(mID, domain, mCachedFields); \
+ }
+#else
+# define VERIFY_CACHE(domain) \
+ do { \
+ } while (0)
+
+#endif
+
+namespace mozilla {
+namespace a11y {
+
+void RemoteAccessible::Shutdown() {
+ MOZ_DIAGNOSTIC_ASSERT(!IsDoc());
+ xpcAccessibleDocument* xpcDoc =
+ GetAccService()->GetCachedXPCDocument(Document());
+ if (xpcDoc) {
+ xpcDoc->NotifyOfShutdown(static_cast<RemoteAccessible*>(this));
+ }
+
+ if (IsTable() || IsTableCell()) {
+ CachedTableAccessible::Invalidate(this);
+ }
+
+ // Remove this acc's relation map from the doc's map of
+ // reverse relations. Prune forward relations associated with this
+ // acc's reverse relations. This also removes the acc's map of reverse
+ // rels from the mDoc's mReverseRelations.
+ PruneRelationsOnShutdown();
+
+ // XXX Ideally this wouldn't be necessary, but it seems OuterDoc
+ // accessibles can be destroyed before the doc they own.
+ uint32_t childCount = mChildren.Length();
+ if (!IsOuterDoc()) {
+ for (uint32_t idx = 0; idx < childCount; idx++) mChildren[idx]->Shutdown();
+ } else {
+ if (childCount > 1) {
+ MOZ_CRASH("outer doc has too many documents!");
+ } else if (childCount == 1) {
+ mChildren[0]->AsDoc()->Unbind();
+ }
+ }
+
+ mChildren.Clear();
+ ProxyDestroyed(static_cast<RemoteAccessible*>(this));
+ mDoc->RemoveAccessible(static_cast<RemoteAccessible*>(this));
+}
+
+void RemoteAccessible::SetChildDoc(DocAccessibleParent* aChildDoc) {
+ MOZ_ASSERT(aChildDoc);
+ MOZ_ASSERT(mChildren.Length() == 0);
+ mChildren.AppendElement(aChildDoc);
+}
+
+void RemoteAccessible::ClearChildDoc(DocAccessibleParent* aChildDoc) {
+ MOZ_ASSERT(aChildDoc);
+ // This is possible if we're replacing one document with another: Doc 1
+ // has not had a chance to remove itself, but was already replaced by Doc 2
+ // in SetChildDoc(). This could result in two subsequent calls to
+ // ClearChildDoc() even though mChildren.Length() == 1.
+ MOZ_ASSERT(mChildren.Length() <= 1);
+ mChildren.RemoveElement(aChildDoc);
+}
+
+uint32_t RemoteAccessible::EmbeddedChildCount() {
+ size_t count = 0, kids = mChildren.Length();
+ for (size_t i = 0; i < kids; i++) {
+ if (mChildren[i]->IsEmbeddedObject()) {
+ count++;
+ }
+ }
+
+ return count;
+}
+
+int32_t RemoteAccessible::IndexOfEmbeddedChild(Accessible* aChild) {
+ size_t index = 0, kids = mChildren.Length();
+ for (size_t i = 0; i < kids; i++) {
+ if (mChildren[i]->IsEmbeddedObject()) {
+ if (mChildren[i] == aChild) {
+ return index;
+ }
+
+ index++;
+ }
+ }
+
+ return -1;
+}
+
+Accessible* RemoteAccessible::EmbeddedChildAt(uint32_t aChildIdx) {
+ size_t index = 0, kids = mChildren.Length();
+ for (size_t i = 0; i < kids; i++) {
+ if (!mChildren[i]->IsEmbeddedObject()) {
+ continue;
+ }
+
+ if (index == aChildIdx) {
+ return mChildren[i];
+ }
+
+ index++;
+ }
+
+ return nullptr;
+}
+
+LocalAccessible* RemoteAccessible::OuterDocOfRemoteBrowser() const {
+ auto tab = static_cast<dom::BrowserParent*>(mDoc->Manager());
+ dom::Element* frame = tab->GetOwnerElement();
+ NS_ASSERTION(frame, "why isn't the tab in a frame!");
+ if (!frame) return nullptr;
+
+ DocAccessible* chromeDoc = GetExistingDocAccessible(frame->OwnerDoc());
+
+ return chromeDoc ? chromeDoc->GetAccessible(frame) : nullptr;
+}
+
+void RemoteAccessible::SetParent(RemoteAccessible* aParent) {
+ if (!aParent) {
+ mParent = kNoParent;
+ } else {
+ MOZ_ASSERT(!IsDoc() || !aParent->IsDoc());
+ mParent = aParent->ID();
+ }
+}
+
+RemoteAccessible* RemoteAccessible::RemoteParent() const {
+ if (mParent == kNoParent) {
+ return nullptr;
+ }
+
+ // if we are not a document then are parent is another proxy in the same
+ // document. That means we can just ask our document for the proxy with our
+ // parent id.
+ if (!IsDoc()) {
+ return Document()->GetAccessible(mParent);
+ }
+
+ // If we are a top level document then our parent is not a proxy.
+ if (AsDoc()->IsTopLevel()) {
+ return nullptr;
+ }
+
+ // Finally if we are a non top level document then our parent id is for a
+ // proxy in our parent document so get the proxy from there.
+ DocAccessibleParent* parentDoc = AsDoc()->ParentDoc();
+ MOZ_ASSERT(parentDoc);
+ MOZ_ASSERT(mParent);
+ return parentDoc->GetAccessible(mParent);
+}
+
+void RemoteAccessible::ApplyCache(CacheUpdateType aUpdateType,
+ AccAttributes* aFields) {
+ if (!aFields) {
+ MOZ_ASSERT_UNREACHABLE("ApplyCache called with aFields == null");
+ return;
+ }
+
+ const nsTArray<bool> relUpdatesNeeded = PreProcessRelations(aFields);
+ if (auto maybeViewportCache =
+ aFields->GetAttribute<nsTArray<uint64_t>>(CacheKey::Viewport)) {
+ // Updating the viewport cache means the offscreen state of this
+ // document's accessibles has changed. Update the HashSet we use for
+ // checking offscreen state here.
+ MOZ_ASSERT(IsDoc(),
+ "Fetched the viewport cache from a non-doc accessible?");
+ AsDoc()->mOnScreenAccessibles.Clear();
+ for (auto id : *maybeViewportCache) {
+ AsDoc()->mOnScreenAccessibles.Insert(id);
+ }
+ }
+
+ if (aUpdateType == CacheUpdateType::Initial) {
+ mCachedFields = aFields;
+ } else {
+ if (!mCachedFields) {
+ // The fields cache can be uninitialized if there were no cache-worthy
+ // fields in the initial cache push.
+ // We don't do a simple assign because we don't want to store the
+ // DeleteEntry entries.
+ mCachedFields = new AccAttributes();
+ }
+ mCachedFields->Update(aFields);
+ }
+
+ if (IsTextLeaf()) {
+ RemoteAccessible* parent = RemoteParent();
+ if (parent && parent->IsHyperText()) {
+ parent->InvalidateCachedHyperTextOffsets();
+ }
+ }
+
+ PostProcessRelations(relUpdatesNeeded);
+}
+
+ENameValueFlag RemoteAccessible::Name(nsString& aName) const {
+ ENameValueFlag nameFlag = eNameOK;
+ if (mCachedFields) {
+ if (IsText()) {
+ mCachedFields->GetAttribute(CacheKey::Text, aName);
+ return eNameOK;
+ }
+ auto cachedNameFlag =
+ mCachedFields->GetAttribute<int32_t>(CacheKey::NameValueFlag);
+ if (cachedNameFlag) {
+ nameFlag = static_cast<ENameValueFlag>(*cachedNameFlag);
+ }
+ if (mCachedFields->GetAttribute(CacheKey::Name, aName)) {
+ VERIFY_CACHE(CacheDomain::NameAndDescription);
+ return nameFlag;
+ }
+ }
+
+ MOZ_ASSERT(aName.IsEmpty());
+ aName.SetIsVoid(true);
+ return nameFlag;
+}
+
+void RemoteAccessible::Description(nsString& aDescription) const {
+ if (mCachedFields) {
+ mCachedFields->GetAttribute(CacheKey::Description, aDescription);
+ VERIFY_CACHE(CacheDomain::NameAndDescription);
+ }
+}
+
+void RemoteAccessible::Value(nsString& aValue) const {
+ if (mCachedFields) {
+ if (mCachedFields->HasAttribute(CacheKey::TextValue)) {
+ mCachedFields->GetAttribute(CacheKey::TextValue, aValue);
+ VERIFY_CACHE(CacheDomain::Value);
+ return;
+ }
+
+ if (HasNumericValue()) {
+ double checkValue = CurValue();
+ if (!std::isnan(checkValue)) {
+ aValue.AppendFloat(checkValue);
+ }
+ return;
+ }
+
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ // Value of textbox is a textified subtree.
+ if (roleMapEntry && roleMapEntry->Is(nsGkAtoms::textbox)) {
+ nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
+ return;
+ }
+
+ if (IsCombobox()) {
+ // For combo boxes, rely on selection state to determine the value.
+ const Accessible* option =
+ const_cast<RemoteAccessible*>(this)->GetSelectedItem(0);
+ if (option) {
+ option->Name(aValue);
+ } else {
+ // If no selected item, determine the value from descendant elements.
+ nsTextEquivUtils::GetTextEquivFromSubtree(this, aValue);
+ }
+ return;
+ }
+
+ if (IsTextLeaf() || IsImage()) {
+ if (const Accessible* actionAcc = ActionAncestor()) {
+ if (const_cast<Accessible*>(actionAcc)->State() & states::LINKED) {
+ // Text and image descendants of links expose the link URL as the
+ // value.
+ return actionAcc->Value(aValue);
+ }
+ }
+ }
+ }
+}
+
+double RemoteAccessible::CurValue() const {
+ if (mCachedFields) {
+ if (auto value =
+ mCachedFields->GetAttribute<double>(CacheKey::NumericValue)) {
+ VERIFY_CACHE(CacheDomain::Value);
+ return *value;
+ }
+ }
+
+ return UnspecifiedNaN<double>();
+}
+
+double RemoteAccessible::MinValue() const {
+ if (mCachedFields) {
+ if (auto min = mCachedFields->GetAttribute<double>(CacheKey::MinValue)) {
+ VERIFY_CACHE(CacheDomain::Value);
+ return *min;
+ }
+ }
+
+ return UnspecifiedNaN<double>();
+}
+
+double RemoteAccessible::MaxValue() const {
+ if (mCachedFields) {
+ if (auto max = mCachedFields->GetAttribute<double>(CacheKey::MaxValue)) {
+ VERIFY_CACHE(CacheDomain::Value);
+ return *max;
+ }
+ }
+
+ return UnspecifiedNaN<double>();
+}
+
+double RemoteAccessible::Step() const {
+ if (mCachedFields) {
+ if (auto step = mCachedFields->GetAttribute<double>(CacheKey::Step)) {
+ VERIFY_CACHE(CacheDomain::Value);
+ return *step;
+ }
+ }
+
+ return UnspecifiedNaN<double>();
+}
+
+bool RemoteAccessible::SetCurValue(double aValue) {
+ if (!HasNumericValue() || IsProgress()) {
+ return false;
+ }
+
+ const uint32_t kValueCannotChange = states::READONLY | states::UNAVAILABLE;
+ if (State() & kValueCannotChange) {
+ return false;
+ }
+
+ double checkValue = MinValue();
+ if (!std::isnan(checkValue) && aValue < checkValue) {
+ return false;
+ }
+
+ checkValue = MaxValue();
+ if (!std::isnan(checkValue) && aValue > checkValue) {
+ return false;
+ }
+
+ Unused << mDoc->SendSetCurValue(mID, aValue);
+ return true;
+}
+
+bool RemoteAccessible::ContainsPoint(int32_t aX, int32_t aY) {
+ if (!BoundsWithOffset(Nothing(), true).Contains(aX, aY)) {
+ return false;
+ }
+ if (!IsTextLeaf()) {
+ if (IsImage() || IsImageMap() || !HasChildren() ||
+ RefPtr{DisplayStyle()} != nsGkAtoms::inlinevalue) {
+ // This isn't an inline element that might contain text, so we don't need
+ // to walk lines. It's enough that our rect contains the point.
+ return true;
+ }
+ // Non-image inline elements with children can wrap across lines just like
+ // text leaves; see below.
+ // Walk the children, which will walk the lines of text in any text leaves.
+ uint32_t count = ChildCount();
+ for (uint32_t c = 0; c < count; ++c) {
+ RemoteAccessible* child = RemoteChildAt(c);
+ if (child->Role() == roles::TEXT_CONTAINER && child->IsClipped()) {
+ // There is a clipped child. This is a candidate for fuzzy hit testing.
+ // See RemoteAccessible::DoFuzzyHittesting.
+ return true;
+ }
+ if (child->ContainsPoint(aX, aY)) {
+ return true;
+ }
+ }
+ // None of our descendants contain the point, so nor do we.
+ return false;
+ }
+ // This is a text leaf. The text might wrap across lines, which means our
+ // rect might cover a wider area than the actual text. For example, if the
+ // text begins in the middle of the first line and wraps on to the second,
+ // the rect will cover the start of the first line and the end of the second.
+ auto lines = GetCachedTextLines();
+ if (!lines) {
+ // This means the text is empty or occupies a single line (but does not
+ // begin the line). In that case, the Bounds check above is sufficient,
+ // since there's only one rect.
+ return true;
+ }
+ uint32_t length = lines->Length();
+ MOZ_ASSERT(length > 0,
+ "Line starts shouldn't be in cache if there aren't any");
+ if (length == 0 || (length == 1 && (*lines)[0] == 0)) {
+ // This means the text begins and occupies a single line. Again, the Bounds
+ // check above is sufficient.
+ return true;
+ }
+ // Walk the lines of the text. Even if this text doesn't start at the
+ // beginning of a line (i.e. lines[0] > 0), we always want to consider its
+ // first line.
+ int32_t lineStart = 0;
+ for (uint32_t index = 0; index <= length; ++index) {
+ int32_t lineEnd;
+ if (index < length) {
+ int32_t nextLineStart = (*lines)[index];
+ if (nextLineStart == 0) {
+ // This Accessible starts at the beginning of a line. Here, we always
+ // treat 0 as the first line start anyway.
+ MOZ_ASSERT(index == 0);
+ continue;
+ }
+ lineEnd = nextLineStart - 1;
+ } else {
+ // This is the last line.
+ lineEnd = static_cast<int32_t>(nsAccUtils::TextLength(this)) - 1;
+ }
+ MOZ_ASSERT(lineEnd >= lineStart);
+ nsRect lineRect = GetCachedCharRect(lineStart);
+ if (lineEnd > lineStart) {
+ lineRect.UnionRect(lineRect, GetCachedCharRect(lineEnd));
+ }
+ if (BoundsWithOffset(Some(lineRect), true).Contains(aX, aY)) {
+ return true;
+ }
+ lineStart = lineEnd + 1;
+ }
+ return false;
+}
+
+RemoteAccessible* RemoteAccessible::DoFuzzyHittesting() {
+ uint32_t childCount = ChildCount();
+ if (!childCount) {
+ return nullptr;
+ }
+ // Check if this match has a clipped child.
+ // This usually indicates invisible text, and we're
+ // interested in returning the inner text content
+ // even if it doesn't contain the point we're hittesting.
+ RemoteAccessible* clippedContainer = nullptr;
+ for (uint32_t i = 0; i < childCount; i++) {
+ RemoteAccessible* child = RemoteChildAt(i);
+ if (child->Role() == roles::TEXT_CONTAINER) {
+ if (child->IsClipped()) {
+ clippedContainer = child;
+ break;
+ }
+ }
+ }
+ // If we found a clipped container, descend it in search of
+ // meaningful text leaves. Ignore non-text-leaf/text-container
+ // siblings.
+ RemoteAccessible* container = clippedContainer;
+ while (container) {
+ RemoteAccessible* textLeaf = nullptr;
+ bool continueSearch = false;
+ childCount = container->ChildCount();
+ for (uint32_t i = 0; i < childCount; i++) {
+ RemoteAccessible* child = container->RemoteChildAt(i);
+ if (child->Role() == roles::TEXT_CONTAINER) {
+ container = child;
+ continueSearch = true;
+ break;
+ }
+ if (child->IsTextLeaf()) {
+ textLeaf = child;
+ // Don't break here -- it's possible a text container
+ // exists as another sibling, and we should descend as
+ // deep as possible.
+ }
+ }
+ if (textLeaf) {
+ return textLeaf;
+ }
+ if (!continueSearch) {
+ // We didn't find anything useful in this set of siblings.
+ // Don't keep searching
+ break;
+ }
+ }
+ return nullptr;
+}
+
+Accessible* RemoteAccessible::ChildAtPoint(
+ int32_t aX, int32_t aY, LocalAccessible::EWhichChildAtPoint aWhichChild) {
+ // Elements that are partially on-screen should have their bounds masked by
+ // their containing scroll area so hittesting yields results that are
+ // consistent with the content's visual representation. Pass this value to
+ // bounds calculation functions to indicate that we're hittesting.
+ const bool hitTesting = true;
+
+ if (IsOuterDoc() && aWhichChild == EWhichChildAtPoint::DirectChild) {
+ // This is an iframe, which is as deep as the viewport cache goes. The
+ // caller wants a direct child, which can only be the embedded document.
+ if (BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
+ return RemoteFirstChild();
+ }
+ return nullptr;
+ }
+
+ RemoteAccessible* lastMatch = nullptr;
+ // If `this` is a document, use its viewport cache instead of
+ // the cache of its parent document.
+ if (DocAccessibleParent* doc = IsDoc() ? AsDoc() : mDoc) {
+ if (!doc->mCachedFields) {
+ // A client call might arrive after we've constructed doc but before we
+ // get a cache push for it.
+ return nullptr;
+ }
+ if (auto maybeViewportCache =
+ doc->mCachedFields->GetAttribute<nsTArray<uint64_t>>(
+ CacheKey::Viewport)) {
+ // The retrieved viewport cache contains acc IDs in hittesting order.
+ // That is, items earlier in the list have z-indexes that are larger than
+ // those later in the list. If you were to build a tree by z-index, where
+ // chilren have larger z indices than their parents, iterating this list
+ // is essentially a postorder tree traversal.
+ const nsTArray<uint64_t>& viewportCache = *maybeViewportCache;
+
+ for (auto id : viewportCache) {
+ RemoteAccessible* acc = doc->GetAccessible(id);
+ if (!acc) {
+ // This can happen if the acc died in between
+ // pushing the viewport cache and doing this hittest
+ continue;
+ }
+
+ if (acc->IsOuterDoc() &&
+ aWhichChild == EWhichChildAtPoint::DeepestChild &&
+ acc->BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
+ // acc is an iframe, which is as deep as the viewport cache goes. This
+ // iframe contains the requested point.
+ RemoteAccessible* innerDoc = acc->RemoteFirstChild();
+ if (innerDoc) {
+ MOZ_ASSERT(innerDoc->IsDoc());
+ // Search the embedded document's viewport cache so we return the
+ // deepest descendant in that embedded document.
+ Accessible* deepestAcc = innerDoc->ChildAtPoint(
+ aX, aY, EWhichChildAtPoint::DeepestChild);
+ MOZ_ASSERT(!deepestAcc || deepestAcc->IsRemote());
+ lastMatch = deepestAcc ? deepestAcc->AsRemote() : nullptr;
+ break;
+ }
+ // If there is no embedded document, the iframe itself is the deepest
+ // descendant.
+ lastMatch = acc;
+ break;
+ }
+
+ if (acc == this) {
+ MOZ_ASSERT(!acc->IsOuterDoc());
+ // Even though we're searching from the doc's cache
+ // this call shouldn't pass the boundary defined by
+ // the acc this call originated on. If we hit `this`,
+ // return our most recent match.
+ if (!lastMatch &&
+ BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
+ // If we haven't found a match, but `this` contains the point we're
+ // looking for, set it as our temp last match so we can
+ // (potentially) do fuzzy hittesting on it below.
+ lastMatch = acc;
+ }
+ break;
+ }
+
+ if (acc->ContainsPoint(aX, aY)) {
+ // Because our rects are in hittesting order, the
+ // first match we encounter is guaranteed to be the
+ // deepest match.
+ lastMatch = acc;
+ break;
+ }
+ }
+ if (lastMatch) {
+ RemoteAccessible* fuzzyMatch = lastMatch->DoFuzzyHittesting();
+ lastMatch = fuzzyMatch ? fuzzyMatch : lastMatch;
+ }
+ }
+ }
+
+ if (aWhichChild == EWhichChildAtPoint::DirectChild && lastMatch) {
+ // lastMatch is the deepest match. Walk up to the direct child of this.
+ RemoteAccessible* parent = lastMatch->RemoteParent();
+ for (;;) {
+ if (parent == this) {
+ break;
+ }
+ if (!parent || parent->IsDoc()) {
+ // `this` is not an ancestor of lastMatch. Ignore lastMatch.
+ lastMatch = nullptr;
+ break;
+ }
+ lastMatch = parent;
+ parent = parent->RemoteParent();
+ }
+ } else if (aWhichChild == EWhichChildAtPoint::DeepestChild && lastMatch &&
+ !IsDoc() && !IsAncestorOf(lastMatch)) {
+ // If we end up with a match that is not in the ancestor chain
+ // of the accessible this call originated on, we should ignore it.
+ // This can happen when the aX, aY given are outside `this`.
+ lastMatch = nullptr;
+ }
+
+ if (!lastMatch && BoundsWithOffset(Nothing(), hitTesting).Contains(aX, aY)) {
+ // Even though the hit target isn't inside `this`, the point is still
+ // within our bounds, so fall back to `this`.
+ return this;
+ }
+
+ return lastMatch;
+}
+
+Maybe<nsRect> RemoteAccessible::RetrieveCachedBounds() const {
+ if (!mCachedFields) {
+ return Nothing();
+ }
+
+ Maybe<const nsTArray<int32_t>&> maybeArray =
+ mCachedFields->GetAttribute<nsTArray<int32_t>>(
+ CacheKey::ParentRelativeBounds);
+ if (maybeArray) {
+ const nsTArray<int32_t>& relativeBoundsArr = *maybeArray;
+ MOZ_ASSERT(relativeBoundsArr.Length() == 4,
+ "Incorrectly sized bounds array");
+ nsRect relativeBoundsRect(relativeBoundsArr[0], relativeBoundsArr[1],
+ relativeBoundsArr[2], relativeBoundsArr[3]);
+ return Some(relativeBoundsRect);
+ }
+
+ return Nothing();
+}
+
+void RemoteAccessible::ApplyCrossDocOffset(nsRect& aBounds) const {
+ if (!IsDoc()) {
+ // We should only apply cross-doc offsets to documents. If we're anything
+ // else, return early here.
+ return;
+ }
+
+ RemoteAccessible* parentAcc = RemoteParent();
+ if (!parentAcc || !parentAcc->IsOuterDoc()) {
+ return;
+ }
+
+ Maybe<const nsTArray<int32_t>&> maybeOffset =
+ parentAcc->mCachedFields->GetAttribute<nsTArray<int32_t>>(
+ CacheKey::CrossDocOffset);
+ if (!maybeOffset) {
+ return;
+ }
+
+ MOZ_ASSERT(maybeOffset->Length() == 2);
+ const nsTArray<int32_t>& offset = *maybeOffset;
+ // Our retrieved value is in app units, so we don't need to do any
+ // unit conversion here.
+ aBounds.MoveBy(offset[0], offset[1]);
+}
+
+bool RemoteAccessible::ApplyTransform(nsRect& aCumulativeBounds) const {
+ // First, attempt to retrieve the transform from the cache.
+ Maybe<const UniquePtr<gfx::Matrix4x4>&> maybeTransform =
+ mCachedFields->GetAttribute<UniquePtr<gfx::Matrix4x4>>(
+ CacheKey::TransformMatrix);
+ if (!maybeTransform) {
+ return false;
+ }
+
+ auto mtxInPixels = gfx::Matrix4x4Typed<CSSPixel, CSSPixel>::FromUnknownMatrix(
+ *(*maybeTransform));
+
+ // Our matrix is in CSS Pixels, so we need our rect to be in CSS
+ // Pixels too. Convert before applying.
+ auto boundsInPixels = CSSRect::FromAppUnits(aCumulativeBounds);
+ boundsInPixels = mtxInPixels.TransformBounds(boundsInPixels);
+ aCumulativeBounds = CSSRect::ToAppUnits(boundsInPixels);
+
+ return true;
+}
+
+bool RemoteAccessible::ApplyScrollOffset(nsRect& aBounds) const {
+ Maybe<const nsTArray<int32_t>&> maybeScrollPosition =
+ mCachedFields->GetAttribute<nsTArray<int32_t>>(CacheKey::ScrollPosition);
+
+ if (!maybeScrollPosition || maybeScrollPosition->Length() != 2) {
+ return false;
+ }
+ // Our retrieved value is in app units, so we don't need to do any
+ // unit conversion here.
+ const nsTArray<int32_t>& scrollPosition = *maybeScrollPosition;
+
+ // Scroll position is an inverse representation of scroll offset (since the
+ // further the scroll bar moves down the page, the further the page content
+ // moves up/closer to the origin).
+ nsPoint scrollOffset(-scrollPosition[0], -scrollPosition[1]);
+
+ aBounds.MoveBy(scrollOffset.x, scrollOffset.y);
+
+ // Return true here even if the scroll offset was 0,0 because the RV is used
+ // as a scroll container indicator. Non-scroll containers won't have cached
+ // scroll position.
+ return true;
+}
+
+nsRect RemoteAccessible::BoundsInAppUnits() const {
+ if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()->Top()) {
+ if (dom::BrowserParent* bp = cbc->GetBrowserParent()) {
+ DocAccessibleParent* topDoc = bp->GetTopLevelDocAccessible();
+ if (topDoc && topDoc->mCachedFields) {
+ auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>(
+ CacheKey::AppUnitsPerDevPixel);
+ MOZ_ASSERT(appUnitsPerDevPixel);
+ return LayoutDeviceIntRect::ToAppUnits(Bounds(), *appUnitsPerDevPixel);
+ }
+ }
+ }
+ return LayoutDeviceIntRect::ToAppUnits(Bounds(), AppUnitsPerCSSPixel());
+}
+
+bool RemoteAccessible::IsFixedPos() const {
+ MOZ_ASSERT(mCachedFields);
+ if (auto maybePosition =
+ mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CssPosition)) {
+ return *maybePosition == nsGkAtoms::fixed;
+ }
+
+ return false;
+}
+
+bool RemoteAccessible::IsOverflowHidden() const {
+ MOZ_ASSERT(mCachedFields);
+ if (auto maybeOverflow =
+ mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSOverflow)) {
+ return *maybeOverflow == nsGkAtoms::hidden;
+ }
+
+ return false;
+}
+
+bool RemoteAccessible::IsClipped() const {
+ MOZ_ASSERT(mCachedFields);
+ if (mCachedFields->GetAttribute<bool>(CacheKey::IsClipped)) {
+ return true;
+ }
+
+ return false;
+}
+
+LayoutDeviceIntRect RemoteAccessible::BoundsWithOffset(
+ Maybe<nsRect> aOffset, bool aBoundsAreForHittesting) const {
+ Maybe<nsRect> maybeBounds = RetrieveCachedBounds();
+ if (maybeBounds) {
+ nsRect bounds = *maybeBounds;
+ // maybeBounds is parent-relative. However, the transform matrix we cache
+ // (if any) is meant to operate on self-relative rects. Therefore, make
+ // bounds self-relative until after we transform.
+ bounds.MoveTo(0, 0);
+ const DocAccessibleParent* topDoc = IsDoc() ? AsDoc() : nullptr;
+
+ if (aOffset.isSome()) {
+ // The rect we've passed in is in app units, so no conversion needed.
+ nsRect internalRect = *aOffset;
+ bounds.SetRectX(bounds.x + internalRect.x, internalRect.width);
+ bounds.SetRectY(bounds.y + internalRect.y, internalRect.height);
+ }
+
+ Unused << ApplyTransform(bounds);
+ // Now apply the parent-relative offset.
+ bounds.MoveBy(maybeBounds->TopLeft());
+
+ ApplyCrossDocOffset(bounds);
+
+ LayoutDeviceIntRect devPxBounds;
+ const Accessible* acc = Parent();
+ bool encounteredFixedContainer = IsFixedPos();
+ while (acc && acc->IsRemote()) {
+ // Return early if we're hit testing and our cumulative bounds are empty,
+ // since walking the ancestor chain won't produce any hits.
+ if (aBoundsAreForHittesting && bounds.IsEmpty()) {
+ return LayoutDeviceIntRect{};
+ }
+
+ RemoteAccessible* remoteAcc = const_cast<Accessible*>(acc)->AsRemote();
+
+ if (Maybe<nsRect> maybeRemoteBounds = remoteAcc->RetrieveCachedBounds()) {
+ nsRect remoteBounds = *maybeRemoteBounds;
+ // We need to take into account a non-1 resolution set on the
+ // presshell. This happens with async pinch zooming, among other
+ // things. We can't reliably query this value in the parent process,
+ // so we retrieve it from the document's cache.
+ if (remoteAcc->IsDoc()) {
+ // Apply the document's resolution to the bounds we've gathered
+ // thus far. We do this before applying the document's offset
+ // because document accs should not have their bounds scaled by
+ // their own resolution. They should be scaled by the resolution
+ // of their containing document (if any).
+ Maybe<float> res =
+ remoteAcc->AsDoc()->mCachedFields->GetAttribute<float>(
+ CacheKey::Resolution);
+ MOZ_ASSERT(res, "No cached document resolution found.");
+ bounds.ScaleRoundOut(res.valueOr(1.0f));
+
+ topDoc = remoteAcc->AsDoc();
+ }
+
+ // We don't account for the document offset of iframes when
+ // computing parent-relative bounds. Instead, we store this value
+ // separately on all iframes and apply it here. See the comments in
+ // LocalAccessible::BundleFieldsForCache where we set the
+ // nsGkAtoms::crossorigin attribute.
+ remoteAcc->ApplyCrossDocOffset(remoteBounds);
+ if (!encounteredFixedContainer) {
+ // Apply scroll offset, if applicable. Only the contents of an
+ // element are affected by its scroll offset, which is why this call
+ // happens in this loop instead of both inside and outside of
+ // the loop (like ApplyTransform).
+ // Never apply scroll offsets past a fixed container.
+ const bool hasScrollArea = remoteAcc->ApplyScrollOffset(bounds);
+
+ // If we are hit testing and the Accessible has a scroll area, ensure
+ // that the bounds we've calculated so far are constrained to the
+ // bounds of the scroll area. Without this, we'll "hit" the off-screen
+ // portions of accs that are are partially (but not fully) within the
+ // scroll area. This is also a problem for accs with overflow:hidden;
+ if (aBoundsAreForHittesting &&
+ (hasScrollArea || remoteAcc->IsOverflowHidden())) {
+ nsRect selfRelativeVisibleBounds(0, 0, remoteBounds.width,
+ remoteBounds.height);
+ bounds = bounds.SafeIntersect(selfRelativeVisibleBounds);
+ }
+ }
+ if (remoteAcc->IsDoc()) {
+ // Fixed elements are document relative, so if we've hit a
+ // document we're now subject to that document's styling
+ // (including scroll offsets that operate on it).
+ // This ordering is important, we don't want to apply scroll
+ // offsets on this doc's content.
+ encounteredFixedContainer = false;
+ }
+ if (!encounteredFixedContainer) {
+ // The transform matrix we cache (if any) is meant to operate on
+ // self-relative rects. Therefore, we must apply the transform before
+ // we make bounds parent-relative.
+ Unused << remoteAcc->ApplyTransform(bounds);
+ // Regardless of whether this is a doc, we should offset `bounds`
+ // by the bounds retrieved here. This is how we build screen
+ // coordinates from relative coordinates.
+ bounds.MoveBy(remoteBounds.X(), remoteBounds.Y());
+ }
+
+ if (remoteAcc->IsFixedPos()) {
+ encounteredFixedContainer = true;
+ }
+ // we can't just break here if we're scroll suppressed because we still
+ // need to find the top doc.
+ }
+ acc = acc->Parent();
+ }
+
+ MOZ_ASSERT(topDoc);
+ if (topDoc) {
+ // We use the top documents app-units-per-dev-pixel even though
+ // theoretically nested docs can have different values. Practically,
+ // that isn't likely since we only offer zoom controls for the top
+ // document and all subdocuments inherit from it.
+ auto appUnitsPerDevPixel = topDoc->mCachedFields->GetAttribute<int32_t>(
+ CacheKey::AppUnitsPerDevPixel);
+ MOZ_ASSERT(appUnitsPerDevPixel);
+ if (appUnitsPerDevPixel) {
+ // Convert our existing `bounds` rect from app units to dev pixels
+ devPxBounds = LayoutDeviceIntRect::FromAppUnitsToNearest(
+ bounds, *appUnitsPerDevPixel);
+ }
+ }
+
+#if !defined(ANDROID)
+ // This block is not thread safe because it queries a LocalAccessible.
+ // It is also not needed in Android since the only local accessible is
+ // the outer doc browser that has an offset of 0.
+ // acc could be null if the OuterDocAccessible died before the top level
+ // DocAccessibleParent.
+ if (LocalAccessible* localAcc =
+ acc ? const_cast<Accessible*>(acc)->AsLocal() : nullptr) {
+ // LocalAccessible::Bounds returns screen-relative bounds in
+ // dev pixels.
+ LayoutDeviceIntRect localBounds = localAcc->Bounds();
+
+ // The root document will always have an APZ resolution of 1,
+ // so we don't factor in its scale here. We also don't scale
+ // by GetFullZoom because LocalAccessible::Bounds already does
+ // that.
+ devPxBounds.MoveBy(localBounds.X(), localBounds.Y());
+ }
+#endif
+
+ return devPxBounds;
+ }
+
+ return LayoutDeviceIntRect();
+}
+
+LayoutDeviceIntRect RemoteAccessible::Bounds() const {
+ return BoundsWithOffset(Nothing());
+}
+
+Relation RemoteAccessible::RelationByType(RelationType aType) const {
+ // We are able to handle some relations completely in the
+ // parent process, without the help of the cache. Those
+ // relations are enumerated here. Other relations, whose
+ // types are stored in kRelationTypeAtoms, are processed
+ // below using the cache.
+ if (aType == RelationType::CONTAINING_TAB_PANE) {
+ if (dom::CanonicalBrowsingContext* cbc = mDoc->GetBrowsingContext()) {
+ if (dom::CanonicalBrowsingContext* topCbc = cbc->Top()) {
+ if (dom::BrowserParent* bp = topCbc->GetBrowserParent()) {
+ return Relation(bp->GetTopLevelDocAccessible());
+ }
+ }
+ }
+ return Relation();
+ }
+
+ if (aType == RelationType::LINKS_TO && Role() == roles::LINK) {
+ Pivot p = Pivot(mDoc);
+ nsString href;
+ Value(href);
+ int32_t i = href.FindChar('#');
+ int32_t len = static_cast<int32_t>(href.Length());
+ if (i != -1 && i < (len - 1)) {
+ nsDependentSubstring anchorName = Substring(href, i + 1, len);
+ MustPruneSameDocRule rule;
+ Accessible* nameMatch = nullptr;
+ for (Accessible* match = p.Next(mDoc, rule); match;
+ match = p.Next(match, rule)) {
+ nsString currID;
+ match->DOMNodeID(currID);
+ MOZ_ASSERT(match->IsRemote());
+ if (anchorName.Equals(currID)) {
+ return Relation(match->AsRemote());
+ }
+ if (!nameMatch) {
+ nsString currName = match->AsRemote()->GetCachedHTMLNameAttribute();
+ if (match->TagName() == nsGkAtoms::a && anchorName.Equals(currName)) {
+ // If we find an element with a matching ID, we should return
+ // that, but if we don't we should return the first anchor with
+ // a matching name. To avoid doing two traversals, store the first
+ // name match here.
+ nameMatch = match;
+ }
+ }
+ }
+ return nameMatch ? Relation(nameMatch->AsRemote()) : Relation();
+ }
+
+ return Relation();
+ }
+
+ // Handle ARIA tree, treegrid parent/child relations. Each of these cases
+ // relies on cached group info. To find the parent of an accessible, use the
+ // unified conceptual parent.
+ if (aType == RelationType::NODE_CHILD_OF) {
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
+ roleMapEntry->role == roles::LISTITEM ||
+ roleMapEntry->role == roles::ROW)) {
+ if (const AccGroupInfo* groupInfo =
+ const_cast<RemoteAccessible*>(this)->GetOrCreateGroupInfo()) {
+ return Relation(groupInfo->ConceptualParent());
+ }
+ }
+ return Relation();
+ }
+
+ // To find the children of a parent, provide an iterator through its items.
+ if (aType == RelationType::NODE_PARENT_OF) {
+ const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
+ if (roleMapEntry && (roleMapEntry->role == roles::OUTLINEITEM ||
+ roleMapEntry->role == roles::LISTITEM ||
+ roleMapEntry->role == roles::ROW ||
+ roleMapEntry->role == roles::OUTLINE ||
+ roleMapEntry->role == roles::LIST ||
+ roleMapEntry->role == roles::TREE_TABLE)) {
+ return Relation(new ItemIterator(this));
+ }
+ return Relation();
+ }
+
+ if (aType == RelationType::MEMBER_OF) {
+ Relation rel = Relation();
+ // HTML radio buttons with cached names should be grouped.
+ if (IsHTMLRadioButton()) {
+ nsString name = GetCachedHTMLNameAttribute();
+ if (name.IsEmpty()) {
+ return rel;
+ }
+
+ RemoteAccessible* ancestor = RemoteParent();
+ while (ancestor && ancestor->Role() != roles::FORM && ancestor != mDoc) {
+ ancestor = ancestor->RemoteParent();
+ }
+ if (ancestor) {
+ // Sometimes we end up with an unparented acc here, potentially
+ // because the acc is being moved. See bug 1807639.
+ // Pivot expects to be created with a non-null mRoot.
+ Pivot p = Pivot(ancestor);
+ PivotRadioNameRule rule(name);
+ Accessible* match = p.Next(ancestor, rule);
+ while (match) {
+ rel.AppendTarget(match->AsRemote());
+ match = p.Next(match, rule);
+ }
+ }
+ return rel;
+ }
+
+ if (IsARIARole(nsGkAtoms::radio)) {
+ // ARIA radio buttons should be grouped by their radio group
+ // parent, if one exists.
+ RemoteAccessible* currParent = RemoteParent();
+ while (currParent && currParent->Role() != roles::RADIO_GROUP) {
+ currParent = currParent->RemoteParent();
+ }
+
+ if (currParent && currParent->Role() == roles::RADIO_GROUP) {
+ // If we found a radiogroup parent, search for all
+ // roles::RADIOBUTTON children and add them to our relation.
+ // This search will include the radio button this method
+ // was called from, which is expected.
+ Pivot p = Pivot(currParent);
+ PivotRoleRule rule(roles::RADIOBUTTON);
+ Accessible* match = p.Next(currParent, rule);
+ while (match) {
+ MOZ_ASSERT(match->IsRemote(),
+ "We should only be traversing the remote tree.");
+ rel.AppendTarget(match->AsRemote());
+ match = p.Next(match, rule);
+ }
+ }
+ }
+ // By webkit's standard, aria radio buttons do not get grouped
+ // if they lack a group parent, so we return an empty
+ // relation here if the above check fails.
+ return rel;
+ }
+
+ Relation rel;
+ if (!mCachedFields) {
+ return rel;
+ }
+
+ for (const auto& data : kRelationTypeAtoms) {
+ if (data.mType != aType ||
+ (data.mValidTag && TagName() != data.mValidTag)) {
+ continue;
+ }
+
+ if (auto maybeIds =
+ mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom)) {
+ rel.AppendIter(new RemoteAccIterator(*maybeIds, Document()));
+ }
+ // Each relation type has only one relevant cached attribute,
+ // so break after we've handled the attr for this type,
+ // even if we didn't find any targets.
+ break;
+ }
+
+ if (auto accRelMapEntry = mDoc->mReverseRelations.Lookup(ID())) {
+ if (auto reverseIdsEntry = accRelMapEntry.Data().Lookup(aType)) {
+ rel.AppendIter(new RemoteAccIterator(reverseIdsEntry.Data(), Document()));
+ }
+ }
+
+ // We handle these relations here rather than before cached relations because
+ // the cached relations need to take precedence. For example, a <figure> with
+ // both aria-labelledby and a <figcaption> must return two LABELLED_BY
+ // targets: the aria-labelledby and then the <figcaption>.
+ if (aType == RelationType::LABELLED_BY && TagName() == nsGkAtoms::figure) {
+ uint32_t count = ChildCount();
+ for (uint32_t c = 0; c < count; ++c) {
+ RemoteAccessible* child = RemoteChildAt(c);
+ MOZ_ASSERT(child);
+ if (child->TagName() == nsGkAtoms::figcaption) {
+ rel.AppendTarget(child);
+ }
+ }
+ } else if (aType == RelationType::LABEL_FOR &&
+ TagName() == nsGkAtoms::figcaption) {
+ if (RemoteAccessible* parent = RemoteParent()) {
+ if (parent->TagName() == nsGkAtoms::figure) {
+ rel.AppendTarget(parent);
+ }
+ }
+ }
+
+ return rel;
+}
+
+void RemoteAccessible::AppendTextTo(nsAString& aText, uint32_t aStartOffset,
+ uint32_t aLength) {
+ if (IsText()) {
+ if (mCachedFields) {
+ if (auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text)) {
+ aText.Append(Substring(*text, aStartOffset, aLength));
+ }
+ VERIFY_CACHE(CacheDomain::Text);
+ }
+ return;
+ }
+
+ if (aStartOffset != 0 || aLength == 0) {
+ return;
+ }
+
+ if (IsHTMLBr()) {
+ aText += kForcedNewLineChar;
+ } else if (RemoteParent() && nsAccUtils::MustPrune(RemoteParent())) {
+ // Expose the embedded object accessible as imaginary embedded object
+ // character if its parent hypertext accessible doesn't expose children to
+ // AT.
+ aText += kImaginaryEmbeddedObjectChar;
+ } else {
+ aText += kEmbeddedObjectChar;
+ }
+}
+
+nsTArray<bool> RemoteAccessible::PreProcessRelations(AccAttributes* aFields) {
+ nsTArray<bool> updateTracker(ArrayLength(kRelationTypeAtoms));
+ for (auto const& data : kRelationTypeAtoms) {
+ if (data.mValidTag) {
+ // The relation we're currently processing only applies to particular
+ // elements. Check to see if we're one of them.
+ nsAtom* tag = TagName();
+ if (!tag) {
+ // TagName() returns null on an initial cache push -- check aFields
+ // for a tag name instead.
+ if (auto maybeTag =
+ aFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
+ tag = *maybeTag;
+ }
+ }
+ MOZ_ASSERT(
+ tag || IsTextLeaf() || IsDoc(),
+ "Could not fetch tag via TagName() or from initial cache push!");
+ if (tag != data.mValidTag) {
+ // If this rel doesn't apply to us, do no pre-processing. Also,
+ // note in our updateTracker that we should do no post-processing.
+ updateTracker.AppendElement(false);
+ continue;
+ }
+ }
+
+ nsStaticAtom* const relAtom = data.mAtom;
+ auto newRelationTargets =
+ aFields->GetAttribute<nsTArray<uint64_t>>(relAtom);
+ bool shouldAddNewImplicitRels =
+ newRelationTargets && newRelationTargets->Length();
+
+ // Remove existing implicit relations if we need to perform an update, or
+ // if we've received a DeleteEntry(). Only do this if mCachedFields is
+ // initialized. If mCachedFields is not initialized, we still need to
+ // construct the update array so we correctly handle reverse rels in
+ // PostProcessRelations.
+ if ((shouldAddNewImplicitRels ||
+ aFields->GetAttribute<DeleteEntry>(relAtom)) &&
+ mCachedFields) {
+ if (auto maybeOldIDs =
+ mCachedFields->GetAttribute<nsTArray<uint64_t>>(relAtom)) {
+ for (uint64_t id : *maybeOldIDs) {
+ // For each target, fetch its reverse relation map
+ // We need to call `Lookup` here instead of `LookupOrInsert` because
+ // it's possible the ID we're querying is from an acc that has since
+ // been Shutdown(), and so has intentionally removed its reverse rels
+ // from the doc's reverse rel cache.
+ if (auto reverseRels = Document()->mReverseRelations.Lookup(id)) {
+ // Then fetch its reverse relation's ID list. This should be safe
+ // to do via LookupOrInsert because by the time we've gotten here,
+ // we know the acc and `this` are still alive in the doc. If we hit
+ // the following assert, we don't have parity on implicit/explicit
+ // rels and something is wrong.
+ nsTArray<uint64_t>& reverseRelIDs =
+ reverseRels->LookupOrInsert(data.mReverseType);
+ // There might be other reverse relations stored for this acc, so
+ // remove our ID instead of deleting the array entirely.
+ DebugOnly<bool> removed = reverseRelIDs.RemoveElement(ID());
+ MOZ_ASSERT(removed, "Can't find old reverse relation");
+ }
+ }
+ }
+ }
+
+ updateTracker.AppendElement(shouldAddNewImplicitRels);
+ }
+
+ return updateTracker;
+}
+
+void RemoteAccessible::PostProcessRelations(const nsTArray<bool>& aToUpdate) {
+ size_t updateCount = aToUpdate.Length();
+ MOZ_ASSERT(updateCount == ArrayLength(kRelationTypeAtoms),
+ "Did not note update status for every relation type!");
+ for (size_t i = 0; i < updateCount; i++) {
+ if (aToUpdate.ElementAt(i)) {
+ // Since kRelationTypeAtoms was used to generate aToUpdate, we
+ // know the ith entry of aToUpdate corresponds to the relation type in
+ // the ith entry of kRelationTypeAtoms. Fetch the related data here.
+ auto const& data = kRelationTypeAtoms[i];
+
+ const nsTArray<uint64_t>& newIDs =
+ *mCachedFields->GetAttribute<nsTArray<uint64_t>>(data.mAtom);
+ for (uint64_t id : newIDs) {
+ nsTHashMap<RelationType, nsTArray<uint64_t>>& relations =
+ Document()->mReverseRelations.LookupOrInsert(id);
+ nsTArray<uint64_t>& ids = relations.LookupOrInsert(data.mReverseType);
+ ids.AppendElement(ID());
+ }
+ }
+ }
+}
+
+void RemoteAccessible::PruneRelationsOnShutdown() {
+ auto reverseRels = mDoc->mReverseRelations.Lookup(ID());
+ if (!reverseRels) {
+ return;
+ }
+ for (auto const& data : kRelationTypeAtoms) {
+ // Fetch the list of targets for this reverse relation
+ auto reverseTargetList = reverseRels->Lookup(data.mReverseType);
+ if (!reverseTargetList) {
+ continue;
+ }
+ for (uint64_t id : *reverseTargetList) {
+ // For each target, retrieve its corresponding forward relation target
+ // list
+ RemoteAccessible* affectedAcc = mDoc->GetAccessible(id);
+ if (!affectedAcc) {
+ // It's possible the affect acc also shut down, in which case
+ // we don't have anything to update.
+ continue;
+ }
+ if (auto forwardTargetList =
+ affectedAcc->mCachedFields
+ ->GetMutableAttribute<nsTArray<uint64_t>>(data.mAtom)) {
+ forwardTargetList->RemoveElement(ID());
+ if (!forwardTargetList->Length()) {
+ // The ID we removed was the only thing in the list, so remove the
+ // entry from the cache entirely -- don't leave an empty array.
+ affectedAcc->mCachedFields->Remove(data.mAtom);
+ }
+ }
+ }
+ }
+ // Remove this ID from the document's map of reverse relations.
+ reverseRels.Remove();
+}
+
+uint32_t RemoteAccessible::GetCachedTextLength() {
+ MOZ_ASSERT(!HasChildren());
+ if (!mCachedFields) {
+ return 0;
+ }
+ VERIFY_CACHE(CacheDomain::Text);
+ auto text = mCachedFields->GetAttribute<nsString>(CacheKey::Text);
+ if (!text) {
+ return 0;
+ }
+ return text->Length();
+}
+
+Maybe<const nsTArray<int32_t>&> RemoteAccessible::GetCachedTextLines() {
+ MOZ_ASSERT(!HasChildren());
+ if (!mCachedFields) {
+ return Nothing();
+ }
+ VERIFY_CACHE(CacheDomain::Text);
+ return mCachedFields->GetAttribute<nsTArray<int32_t>>(
+ CacheKey::TextLineStarts);
+}
+
+nsRect RemoteAccessible::GetCachedCharRect(int32_t aOffset) {
+ MOZ_ASSERT(IsText());
+ if (!mCachedFields) {
+ return nsRect();
+ }
+
+ if (Maybe<const nsTArray<int32_t>&> maybeCharData =
+ mCachedFields->GetAttribute<nsTArray<int32_t>>(
+ CacheKey::TextBounds)) {
+ const nsTArray<int32_t>& charData = *maybeCharData;
+ const int32_t index = aOffset * kNumbersInRect;
+ if (index < static_cast<int32_t>(charData.Length())) {
+ return nsRect(charData[index], charData[index + 1], charData[index + 2],
+ charData[index + 3]);
+ }
+ // It is valid for a client to call this with an offset 1 after the last
+ // character because of the insertion point at the end of text boxes.
+ MOZ_ASSERT(index == static_cast<int32_t>(charData.Length()));
+ }
+
+ return nsRect();
+}
+
+void RemoteAccessible::DOMNodeID(nsString& aID) const {
+ if (mCachedFields) {
+ mCachedFields->GetAttribute(CacheKey::DOMNodeID, aID);
+ VERIFY_CACHE(CacheDomain::DOMNodeIDAndClass);
+ }
+}
+
+void RemoteAccessible::ScrollToPoint(uint32_t aScrollType, int32_t aX,
+ int32_t aY) {
+ Unused << mDoc->SendScrollToPoint(mID, aScrollType, aX, aY);
+}
+
+#if !defined(XP_WIN)
+void RemoteAccessible::Announce(const nsString& aAnnouncement,
+ uint16_t aPriority) {
+ Unused << mDoc->SendAnnounce(mID, aAnnouncement, aPriority);
+}
+#endif // !defined(XP_WIN)
+
+void RemoteAccessible::ScrollSubstringToPoint(int32_t aStartOffset,
+ int32_t aEndOffset,
+ uint32_t aCoordinateType,
+ int32_t aX, int32_t aY) {
+ Unused << mDoc->SendScrollSubstringToPoint(mID, aStartOffset, aEndOffset,
+ aCoordinateType, aX, aY);
+}
+
+RefPtr<const AccAttributes> RemoteAccessible::GetCachedTextAttributes() {
+ MOZ_ASSERT(IsText() || IsHyperText());
+ if (mCachedFields) {
+ auto attrs = mCachedFields->GetAttributeRefPtr<AccAttributes>(
+ CacheKey::TextAttributes);
+ VERIFY_CACHE(CacheDomain::Text);
+ return attrs;
+ }
+ return nullptr;
+}
+
+already_AddRefed<AccAttributes> RemoteAccessible::DefaultTextAttributes() {
+ RefPtr<const AccAttributes> attrs = GetCachedTextAttributes();
+ RefPtr<AccAttributes> result = new AccAttributes();
+ if (attrs) {
+ attrs->CopyTo(result);
+ }
+ return result.forget();
+}
+
+RefPtr<const AccAttributes> RemoteAccessible::GetCachedARIAAttributes() const {
+ if (mCachedFields) {
+ auto attrs = mCachedFields->GetAttributeRefPtr<AccAttributes>(
+ CacheKey::ARIAAttributes);
+ VERIFY_CACHE(CacheDomain::ARIA);
+ return attrs;
+ }
+ return nullptr;
+}
+
+nsString RemoteAccessible::GetCachedHTMLNameAttribute() const {
+ if (mCachedFields) {
+ if (auto maybeName =
+ mCachedFields->GetAttribute<nsString>(CacheKey::DOMName)) {
+ return *maybeName;
+ }
+ }
+ return nsString();
+}
+
+uint64_t RemoteAccessible::State() {
+ uint64_t state = 0;
+ if (mCachedFields) {
+ if (auto rawState =
+ mCachedFields->GetAttribute<uint64_t>(CacheKey::State)) {
+ VERIFY_CACHE(CacheDomain::State);
+ state = *rawState;
+ // Handle states that are derived from other states.
+ if (!(state & states::UNAVAILABLE)) {
+ state |= states::ENABLED | states::SENSITIVE;
+ }
+ if (state & states::EXPANDABLE && !(state & states::EXPANDED)) {
+ state |= states::COLLAPSED;
+ }
+ }
+
+ ApplyImplicitState(state);
+
+ auto* cbc = mDoc->GetBrowsingContext();
+ if (cbc && !cbc->IsActive()) {
+ // If our browsing context is _not_ active, we're in a background tab
+ // and inherently offscreen.
+ state |= states::OFFSCREEN;
+ } else {
+ // If we're in an active browsing context, there are a few scenarios we
+ // need to address:
+ // - We are an iframe document in the visual viewport
+ // - We are an iframe document out of the visual viewport
+ // - We are non-iframe content in the visual viewport
+ // - We are non-iframe content out of the visual viewport
+ // We assume top level tab docs are on screen if their BC is active, so
+ // we don't need additional handling for them here.
+ if (!mDoc->IsTopLevel()) {
+ // Here we handle iframes and iframe content.
+ // We use an iframe's outer doc's position in the embedding document's
+ // viewport to determine if the iframe has been scrolled offscreen.
+ Accessible* docParent = mDoc->Parent();
+ // In rare cases, we might not have an outer doc yet. Return if that's
+ // the case.
+ if (NS_WARN_IF(!docParent || !docParent->IsRemote())) {
+ return state;
+ }
+
+ RemoteAccessible* outerDoc = docParent->AsRemote();
+ DocAccessibleParent* embeddingDocument = outerDoc->Document();
+ if (embeddingDocument &&
+ !embeddingDocument->mOnScreenAccessibles.Contains(outerDoc->ID())) {
+ // Our embedding document's viewport cache doesn't contain the ID of
+ // our outer doc, so this iframe (and any of its content) is
+ // offscreen.
+ state |= states::OFFSCREEN;
+ } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
+ // Our embedding document's viewport cache contains the ID of our
+ // outer doc, but the iframe's viewport cache doesn't contain our ID.
+ // We are offscreen.
+ state |= states::OFFSCREEN;
+ }
+ } else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
+ // We are top level tab content (but not a top level tab doc).
+ // If our tab doc's viewport cache doesn't contain our ID, we're
+ // offscreen.
+ state |= states::OFFSCREEN;
+ }
+ }
+ }
+
+ return state;
+}
+
+already_AddRefed<AccAttributes> RemoteAccessible::Attributes() {
+ RefPtr<AccAttributes> attributes = new AccAttributes();
+ nsAccessibilityService* accService = GetAccService();
+ if (!accService) {
+ // The service can be shut down before RemoteAccessibles. If it is shut
+ // down, we can't calculate some attributes. We're about to die anyway.
+ return attributes.forget();
+ }
+
+ if (mCachedFields) {
+ // We use GetAttribute instead of GetAttributeRefPtr because we need
+ // nsAtom, not const nsAtom.
+ if (auto tag =
+ mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
+ attributes->SetAttribute(nsGkAtoms::tag, *tag);
+ }
+
+ GroupPos groupPos = GroupPosition();
+ nsAccUtils::SetAccGroupAttrs(attributes, groupPos.level, groupPos.setSize,
+ groupPos.posInSet);
+
+ bool hierarchical = false;
+ uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
+ if (itemCount) {
+ attributes->SetAttribute(nsGkAtoms::child_item_count,
+ static_cast<int32_t>(itemCount));
+ }
+
+ if (hierarchical) {
+ attributes->SetAttribute(nsGkAtoms::tree, true);
+ }
+
+ if (auto inputType =
+ mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::InputType)) {
+ attributes->SetAttribute(nsGkAtoms::textInputType, *inputType);
+ }
+
+ if (RefPtr<nsAtom> display = DisplayStyle()) {
+ attributes->SetAttribute(nsGkAtoms::display, display);
+ }
+
+ if (TableCellAccessible* cell = AsTableCell()) {
+ TableAccessible* table = cell->Table();
+ uint32_t row = cell->RowIdx();
+ uint32_t col = cell->ColIdx();
+ int32_t cellIdx = table->CellIndexAt(row, col);
+ if (cellIdx != -1) {
+ attributes->SetAttribute(nsGkAtoms::tableCellIndex, cellIdx);
+ }
+ }
+
+ if (bool layoutGuess = TableIsProbablyForLayout()) {
+ attributes->SetAttribute(nsGkAtoms::layout_guess, layoutGuess);
+ }
+
+ accService->MarkupAttributes(this, attributes);
+
+ const nsRoleMapEntry* roleMap = ARIARoleMap();
+ nsAutoString role;
+ mCachedFields->GetAttribute(CacheKey::ARIARole, role);
+ if (role.IsEmpty()) {
+ if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty) {
+ // Single, known role.
+ attributes->SetAttribute(nsGkAtoms::xmlroles, roleMap->roleAtom);
+ } else if (nsAtom* landmark = LandmarkRole()) {
+ // Landmark role from markup; e.g. HTML <main>.
+ attributes->SetAttribute(nsGkAtoms::xmlroles, landmark);
+ }
+ } else {
+ // Unknown role or multiple roles.
+ attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(role));
+ }
+
+ if (roleMap) {
+ nsAutoString live;
+ if (nsAccUtils::GetLiveAttrValue(roleMap->liveAttRule, live)) {
+ attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live));
+ }
+ }
+
+ if (auto ariaAttrs = GetCachedARIAAttributes()) {
+ ariaAttrs->CopyTo(attributes);
+ }
+
+ nsAccUtils::SetLiveContainerAttributes(attributes, this);
+
+ nsString id;
+ DOMNodeID(id);
+ if (!id.IsEmpty()) {
+ attributes->SetAttribute(nsGkAtoms::id, std::move(id));
+ }
+
+ nsString className;
+ mCachedFields->GetAttribute(CacheKey::DOMNodeClass, className);
+ if (!className.IsEmpty()) {
+ attributes->SetAttribute(nsGkAtoms::_class, std::move(className));
+ }
+
+ if (IsImage()) {
+ nsString src;
+ mCachedFields->GetAttribute(CacheKey::SrcURL, src);
+ if (!src.IsEmpty()) {
+ attributes->SetAttribute(nsGkAtoms::src, std::move(src));
+ }
+ }
+
+ if (IsTextField()) {
+ nsString placeholder;
+ mCachedFields->GetAttribute(CacheKey::HTMLPlaceholder, placeholder);
+ if (!placeholder.IsEmpty()) {
+ attributes->SetAttribute(nsGkAtoms::placeholder,
+ std::move(placeholder));
+ attributes->Remove(nsGkAtoms::aria_placeholder);
+ }
+ }
+
+ nsString popupType;
+ mCachedFields->GetAttribute(CacheKey::PopupType, popupType);
+ if (!popupType.IsEmpty()) {
+ attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popupType));
+ }
+ }
+
+ nsAutoString name;
+ if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
+ attributes->SetAttribute(nsGkAtoms::explicit_name, true);
+ }
+
+ // Expose the string value via the valuetext attribute. We test for the value
+ // interface because we don't want to expose traditional Value() information
+ // such as URLs on links and documents, or text in an input.
+ // XXX This is only needed for ATK, since other APIs have native ways to
+ // retrieve value text. We should probably move this into ATK specific code.
+ // For now, we do this because LocalAccessible does it.
+ if (HasNumericValue()) {
+ nsString valuetext;
+ Value(valuetext);
+ attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext));
+ }
+
+ return attributes.forget();
+}
+
+nsAtom* RemoteAccessible::TagName() const {
+ if (mCachedFields) {
+ if (auto tag =
+ mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::TagName)) {
+ return *tag;
+ }
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsAtom> RemoteAccessible::InputType() const {
+ if (mCachedFields) {
+ if (auto inputType =
+ mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::InputType)) {
+ RefPtr<nsAtom> result = *inputType;
+ return result.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+already_AddRefed<nsAtom> RemoteAccessible::DisplayStyle() const {
+ if (mCachedFields) {
+ if (auto display =
+ mCachedFields->GetAttribute<RefPtr<nsAtom>>(CacheKey::CSSDisplay)) {
+ RefPtr<nsAtom> result = *display;
+ return result.forget();
+ }
+ }
+ return nullptr;
+}
+
+float RemoteAccessible::Opacity() const {
+ if (mCachedFields) {
+ if (auto opacity = mCachedFields->GetAttribute<float>(CacheKey::Opacity)) {
+ return *opacity;
+ }
+ }
+
+ return 1.0f;
+}
+
+void RemoteAccessible::LiveRegionAttributes(nsAString* aLive,
+ nsAString* aRelevant,
+ Maybe<bool>* aAtomic,
+ nsAString* aBusy) const {
+ if (!mCachedFields) {
+ return;
+ }
+ RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes();
+ if (!attrs) {
+ return;
+ }
+ if (aLive) {
+ attrs->GetAttribute(nsGkAtoms::aria_live, *aLive);
+ }
+ if (aRelevant) {
+ attrs->GetAttribute(nsGkAtoms::aria_relevant, *aRelevant);
+ }
+ if (aAtomic) {
+ if (auto value =
+ attrs->GetAttribute<RefPtr<nsAtom>>(nsGkAtoms::aria_atomic)) {
+ *aAtomic = Some(*value == nsGkAtoms::_true);
+ }
+ }
+ if (aBusy) {
+ attrs->GetAttribute(nsGkAtoms::aria_busy, *aBusy);
+ }
+}
+
+Maybe<bool> RemoteAccessible::ARIASelected() const {
+ if (mCachedFields) {
+ return mCachedFields->GetAttribute<bool>(CacheKey::ARIASelected);
+ }
+ return Nothing();
+}
+
+nsAtom* RemoteAccessible::GetPrimaryAction() const {
+ if (mCachedFields) {
+ if (auto action = mCachedFields->GetAttribute<RefPtr<nsAtom>>(
+ CacheKey::PrimaryAction)) {
+ return *action;
+ }
+ }
+
+ return nullptr;
+}
+
+uint8_t RemoteAccessible::ActionCount() const {
+ uint8_t actionCount = 0;
+ if (mCachedFields) {
+ if (HasPrimaryAction() || ActionAncestor()) {
+ actionCount++;
+ }
+
+ if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
+ actionCount++;
+ }
+ VERIFY_CACHE(CacheDomain::Actions);
+ }
+
+ return actionCount;
+}
+
+void RemoteAccessible::ActionNameAt(uint8_t aIndex, nsAString& aName) {
+ if (mCachedFields) {
+ aName.Truncate();
+ nsAtom* action = GetPrimaryAction();
+ bool hasActionAncestor = !action && ActionAncestor();
+
+ switch (aIndex) {
+ case 0:
+ if (action) {
+ action->ToString(aName);
+ } else if (hasActionAncestor) {
+ aName.AssignLiteral("click ancestor");
+ } else if (mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
+ aName.AssignLiteral("showlongdesc");
+ }
+ break;
+ case 1:
+ if ((action || hasActionAncestor) &&
+ mCachedFields->HasAttribute(CacheKey::HasLongdesc)) {
+ aName.AssignLiteral("showlongdesc");
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ VERIFY_CACHE(CacheDomain::Actions);
+}
+
+bool RemoteAccessible::DoAction(uint8_t aIndex) const {
+ if (ActionCount() < aIndex + 1) {
+ return false;
+ }
+
+ Unused << mDoc->SendDoActionAsync(mID, aIndex);
+ return true;
+}
+
+KeyBinding RemoteAccessible::AccessKey() const {
+ if (mCachedFields) {
+ if (auto value =
+ mCachedFields->GetAttribute<uint64_t>(CacheKey::AccessKey)) {
+ return KeyBinding(*value);
+ }
+ }
+ return KeyBinding();
+}
+
+void RemoteAccessible::SelectionRanges(nsTArray<TextRange>* aRanges) const {
+ Document()->SelectionRanges(aRanges);
+}
+
+bool RemoteAccessible::RemoveFromSelection(int32_t aSelectionNum) {
+ MOZ_ASSERT(IsHyperText());
+ if (SelectionCount() <= aSelectionNum) {
+ return false;
+ }
+
+ Unused << mDoc->SendRemoveTextSelection(mID, aSelectionNum);
+
+ return true;
+}
+
+void RemoteAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
+ int32_t* aPosInSet) const {
+ if (!mCachedFields) {
+ return;
+ }
+
+ if (aLevel) {
+ if (auto level =
+ mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_level)) {
+ *aLevel = *level;
+ }
+ }
+ if (aSetSize) {
+ if (auto setsize =
+ mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_setsize)) {
+ *aSetSize = *setsize;
+ }
+ }
+ if (aPosInSet) {
+ if (auto posinset =
+ mCachedFields->GetAttribute<int32_t>(nsGkAtoms::aria_posinset)) {
+ *aPosInSet = *posinset;
+ }
+ }
+}
+
+AccGroupInfo* RemoteAccessible::GetGroupInfo() const {
+ if (!mCachedFields) {
+ return nullptr;
+ }
+
+ if (auto groupInfo = mCachedFields->GetAttribute<UniquePtr<AccGroupInfo>>(
+ CacheKey::GroupInfo)) {
+ return groupInfo->get();
+ }
+
+ return nullptr;
+}
+
+AccGroupInfo* RemoteAccessible::GetOrCreateGroupInfo() {
+ AccGroupInfo* groupInfo = GetGroupInfo();
+ if (groupInfo) {
+ return groupInfo;
+ }
+
+ groupInfo = AccGroupInfo::CreateGroupInfo(this);
+ if (groupInfo) {
+ if (!mCachedFields) {
+ mCachedFields = new AccAttributes();
+ }
+
+ mCachedFields->SetAttribute(CacheKey::GroupInfo, groupInfo);
+ }
+
+ return groupInfo;
+}
+
+void RemoteAccessible::InvalidateGroupInfo() {
+ if (mCachedFields) {
+ mCachedFields->Remove(CacheKey::GroupInfo);
+ }
+}
+
+void RemoteAccessible::GetPositionAndSetSize(int32_t* aPosInSet,
+ int32_t* aSetSize) {
+ if (IsHTMLRadioButton()) {
+ *aSetSize = 0;
+ Relation rel = RelationByType(RelationType::MEMBER_OF);
+ while (Accessible* radio = rel.Next()) {
+ ++*aSetSize;
+ if (radio == this) {
+ *aPosInSet = *aSetSize;
+ }
+ }
+ return;
+ }
+
+ Accessible::GetPositionAndSetSize(aPosInSet, aSetSize);
+}
+
+bool RemoteAccessible::HasPrimaryAction() const {
+ return mCachedFields && mCachedFields->HasAttribute(CacheKey::PrimaryAction);
+}
+
+void RemoteAccessible::TakeFocus() const { Unused << mDoc->SendTakeFocus(mID); }
+
+void RemoteAccessible::ScrollTo(uint32_t aHow) const {
+ Unused << mDoc->SendScrollTo(mID, aHow);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// SelectAccessible
+
+void RemoteAccessible::SelectedItems(nsTArray<Accessible*>* aItems) {
+ Pivot p = Pivot(this);
+ PivotStateRule rule(states::SELECTED);
+ for (Accessible* selected = p.First(rule); selected;
+ selected = p.Next(selected, rule)) {
+ aItems->AppendElement(selected);
+ }
+}
+
+uint32_t RemoteAccessible::SelectedItemCount() {
+ uint32_t count = 0;
+ Pivot p = Pivot(this);
+ PivotStateRule rule(states::SELECTED);
+ for (Accessible* selected = p.First(rule); selected;
+ selected = p.Next(selected, rule)) {
+ count++;
+ }
+
+ return count;
+}
+
+Accessible* RemoteAccessible::GetSelectedItem(uint32_t aIndex) {
+ uint32_t index = 0;
+ Accessible* selected = nullptr;
+ Pivot p = Pivot(this);
+ PivotStateRule rule(states::SELECTED);
+ for (selected = p.First(rule); selected && index < aIndex;
+ selected = p.Next(selected, rule)) {
+ index++;
+ }
+
+ return selected;
+}
+
+bool RemoteAccessible::IsItemSelected(uint32_t aIndex) {
+ uint32_t index = 0;
+ Accessible* selectable = nullptr;
+ Pivot p = Pivot(this);
+ PivotStateRule rule(states::SELECTABLE);
+ for (selectable = p.First(rule); selectable && index < aIndex;
+ selectable = p.Next(selectable, rule)) {
+ index++;
+ }
+
+ return selectable && selectable->State() & states::SELECTED;
+}
+
+bool RemoteAccessible::AddItemToSelection(uint32_t aIndex) {
+ uint32_t index = 0;
+ Accessible* selectable = nullptr;
+ Pivot p = Pivot(this);
+ PivotStateRule rule(states::SELECTABLE);
+ for (selectable = p.First(rule); selectable && index < aIndex;
+ selectable = p.Next(selectable, rule)) {
+ index++;
+ }
+
+ if (selectable) selectable->SetSelected(true);
+
+ return static_cast<bool>(selectable);
+}
+
+bool RemoteAccessible::RemoveItemFromSelection(uint32_t aIndex) {
+ uint32_t index = 0;
+ Accessible* selectable = nullptr;
+ Pivot p = Pivot(this);
+ PivotStateRule rule(states::SELECTABLE);
+ for (selectable = p.First(rule); selectable && index < aIndex;
+ selectable = p.Next(selectable, rule)) {
+ index++;
+ }
+
+ if (selectable) selectable->SetSelected(false);
+
+ return static_cast<bool>(selectable);
+}
+
+bool RemoteAccessible::SelectAll() {
+ if ((State() & states::MULTISELECTABLE) == 0) {
+ return false;
+ }
+
+ bool success = false;
+ Accessible* selectable = nullptr;
+ Pivot p = Pivot(this);
+ PivotStateRule rule(states::SELECTABLE);
+ for (selectable = p.First(rule); selectable;
+ selectable = p.Next(selectable, rule)) {
+ success = true;
+ selectable->SetSelected(true);
+ }
+ return success;
+}
+
+bool RemoteAccessible::UnselectAll() {
+ if ((State() & states::MULTISELECTABLE) == 0) {
+ return false;
+ }
+
+ bool success = false;
+ Accessible* selectable = nullptr;
+ Pivot p = Pivot(this);
+ PivotStateRule rule(states::SELECTABLE);
+ for (selectable = p.First(rule); selectable;
+ selectable = p.Next(selectable, rule)) {
+ success = true;
+ selectable->SetSelected(false);
+ }
+ return success;
+}
+
+void RemoteAccessible::TakeSelection() {
+ Unused << mDoc->SendTakeSelection(mID);
+}
+
+void RemoteAccessible::SetSelected(bool aSelect) {
+ Unused << mDoc->SendSetSelected(mID, aSelect);
+}
+
+TableAccessible* RemoteAccessible::AsTable() {
+ if (IsTable()) {
+ return CachedTableAccessible::GetFrom(this);
+ }
+ return nullptr;
+}
+
+TableCellAccessible* RemoteAccessible::AsTableCell() {
+ if (IsTableCell()) {
+ return CachedTableCellAccessible::GetFrom(this);
+ }
+ return nullptr;
+}
+
+bool RemoteAccessible::TableIsProbablyForLayout() {
+ if (mCachedFields) {
+ if (auto layoutGuess =
+ mCachedFields->GetAttribute<bool>(CacheKey::TableLayoutGuess)) {
+ return *layoutGuess;
+ }
+ }
+ return false;
+}
+
+nsTArray<int32_t>& RemoteAccessible::GetCachedHyperTextOffsets() {
+ if (mCachedFields) {
+ if (auto offsets = mCachedFields->GetMutableAttribute<nsTArray<int32_t>>(
+ CacheKey::HyperTextOffsets)) {
+ return *offsets;
+ }
+ }
+ nsTArray<int32_t> newOffsets;
+ if (!mCachedFields) {
+ mCachedFields = new AccAttributes();
+ }
+ mCachedFields->SetAttribute(CacheKey::HyperTextOffsets,
+ std::move(newOffsets));
+ return *mCachedFields->GetMutableAttribute<nsTArray<int32_t>>(
+ CacheKey::HyperTextOffsets);
+}
+
+void RemoteAccessible::SetCaretOffset(int32_t aOffset) {
+ Unused << mDoc->SendSetCaretOffset(mID, aOffset);
+}
+
+Maybe<int32_t> RemoteAccessible::GetIntARIAAttr(nsAtom* aAttrName) const {
+ if (RefPtr<const AccAttributes> attrs = GetCachedARIAAttributes()) {
+ if (auto val = attrs->GetAttribute<int32_t>(aAttrName)) {
+ return val;
+ }
+ }
+ return Nothing();
+}
+
+void RemoteAccessible::Language(nsAString& aLocale) {
+ if (!IsHyperText()) {
+ return;
+ }
+ if (auto attrs = GetCachedTextAttributes()) {
+ attrs->GetAttribute(nsGkAtoms::language, aLocale);
+ }
+}
+
+void RemoteAccessible::ReplaceText(const nsAString& aText) {
+ Unused << mDoc->SendReplaceText(mID, aText);
+}
+
+void RemoteAccessible::InsertText(const nsAString& aText, int32_t aPosition) {
+ Unused << mDoc->SendInsertText(mID, aText, aPosition);
+}
+
+void RemoteAccessible::CopyText(int32_t aStartPos, int32_t aEndPos) {
+ Unused << mDoc->SendCopyText(mID, aStartPos, aEndPos);
+}
+
+void RemoteAccessible::CutText(int32_t aStartPos, int32_t aEndPos) {
+ Unused << mDoc->SendCutText(mID, aStartPos, aEndPos);
+}
+
+void RemoteAccessible::DeleteText(int32_t aStartPos, int32_t aEndPos) {
+ Unused << mDoc->SendDeleteText(mID, aStartPos, aEndPos);
+}
+
+void RemoteAccessible::PasteText(int32_t aPosition) {
+ Unused << mDoc->SendPasteText(mID, aPosition);
+}
+
+size_t RemoteAccessible::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+size_t RemoteAccessible::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) {
+ size_t size = 0;
+
+ // Count attributes.
+ if (mCachedFields) {
+ size += mCachedFields->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ // We don't recurse into mChildren because they're already counted in their
+ // document's mAccessibles.
+ size += mChildren.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ return size;
+}
+
+} // namespace a11y
+} // namespace mozilla
diff --git a/accessible/ipc/RemoteAccessible.h b/accessible/ipc/RemoteAccessible.h
new file mode 100644
index 0000000000..9215fd7bc5
--- /dev/null
+++ b/accessible/ipc/RemoteAccessible.h
@@ -0,0 +1,511 @@
+/* -*- 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_RemoteAccessible_h
+#define mozilla_a11y_RemoteAccessible_h
+
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/a11y/CacheConstants.h"
+#include "mozilla/a11y/HyperTextAccessibleBase.h"
+#include "mozilla/a11y/Role.h"
+#include "AccAttributes.h"
+#include "nsIAccessibleText.h"
+#include "nsIAccessibleTypes.h"
+#include "nsTArray.h"
+#include "nsRect.h"
+#include "LocalAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+class Attribute;
+class DocAccessibleParent;
+class RemoteAccessible;
+enum class RelationType;
+
+/**
+ * The class for an accessibility tree node that originated in the parent
+ * process.
+ */
+class RemoteAccessible : public Accessible, public HyperTextAccessibleBase {
+ public:
+ virtual ~RemoteAccessible() {
+ MOZ_ASSERT(!mWrapper);
+ MOZ_COUNT_DTOR(RemoteAccessible);
+ }
+
+ virtual bool IsRemote() const override { return true; }
+
+ void AddChildAt(uint32_t aIdx, RemoteAccessible* aChild) {
+ mChildren.InsertElementAt(aIdx, aChild);
+ if (IsHyperText()) {
+ InvalidateCachedHyperTextOffsets();
+ }
+ }
+
+ virtual uint32_t ChildCount() const override { return mChildren.Length(); }
+ RemoteAccessible* RemoteChildAt(uint32_t aIdx) const {
+ return mChildren.SafeElementAt(aIdx);
+ }
+ RemoteAccessible* RemoteFirstChild() const {
+ return mChildren.Length() ? mChildren[0] : nullptr;
+ }
+ RemoteAccessible* RemoteLastChild() const {
+ return mChildren.Length() ? mChildren[mChildren.Length() - 1] : nullptr;
+ }
+ RemoteAccessible* RemotePrevSibling() const {
+ if (IsDoc()) {
+ // The normal code path doesn't work for documents because the parent
+ // might be a local OuterDoc, but IndexInParent() will return 1.
+ // A document is always a single child of an OuterDoc anyway.
+ return nullptr;
+ }
+ int32_t idx = IndexInParent();
+ if (idx == -1) {
+ return nullptr; // No parent.
+ }
+ return idx > 0 ? RemoteParent()->mChildren[idx - 1] : nullptr;
+ }
+ RemoteAccessible* RemoteNextSibling() const {
+ if (IsDoc()) {
+ // The normal code path doesn't work for documents because the parent
+ // might be a local OuterDoc, but IndexInParent() will return 1.
+ // A document is always a single child of an OuterDoc anyway.
+ return nullptr;
+ }
+ int32_t idx = IndexInParent();
+ if (idx == -1) {
+ return nullptr; // No parent.
+ }
+ MOZ_ASSERT(idx >= 0);
+ size_t newIdx = idx + 1;
+ return newIdx < RemoteParent()->mChildren.Length()
+ ? RemoteParent()->mChildren[newIdx]
+ : nullptr;
+ }
+
+ // Accessible hierarchy method overrides
+
+ virtual Accessible* Parent() const override { return RemoteParent(); }
+
+ virtual Accessible* ChildAt(uint32_t aIndex) const override {
+ return RemoteChildAt(aIndex);
+ }
+
+ virtual Accessible* NextSibling() const override {
+ return RemoteNextSibling();
+ }
+
+ virtual Accessible* PrevSibling() const override {
+ return RemotePrevSibling();
+ }
+
+ // XXX evaluate if this is fast enough.
+ virtual int32_t IndexInParent() const override {
+ RemoteAccessible* parent = RemoteParent();
+ if (!parent) {
+ return -1;
+ }
+ return parent->mChildren.IndexOf(
+ static_cast<const RemoteAccessible*>(this));
+ }
+ virtual uint32_t EmbeddedChildCount() override;
+ virtual int32_t IndexOfEmbeddedChild(Accessible* aChild) override;
+ virtual Accessible* EmbeddedChildAt(uint32_t aChildIdx) override;
+
+ void Shutdown();
+
+ void SetChildDoc(DocAccessibleParent* aChildDoc);
+ void ClearChildDoc(DocAccessibleParent* aChildDoc);
+
+ /**
+ * Remove The given child.
+ */
+ void RemoveChild(RemoteAccessible* aChild) {
+ mChildren.RemoveElement(aChild);
+ if (IsHyperText()) {
+ InvalidateCachedHyperTextOffsets();
+ }
+ }
+
+ /**
+ * Return the proxy for the parent of the wrapped accessible.
+ */
+ RemoteAccessible* RemoteParent() const;
+
+ LocalAccessible* OuterDocOfRemoteBrowser() const;
+
+ /**
+ * Get the role of the accessible we're proxying.
+ */
+ virtual role Role() const override { return mRole; }
+
+ /**
+ * Return true if this is an embedded object.
+ */
+ bool IsEmbeddedObject() const { return !IsText(); }
+
+ virtual bool IsLink() const override {
+ if (IsHTMLLink()) {
+ // XXX: HTML links always return true for IsLink.
+ return true;
+ }
+
+ if (IsText()) {
+ return false;
+ }
+
+ if (Accessible* parent = Parent()) {
+ return parent->IsHyperText();
+ }
+
+ return false;
+ }
+
+ virtual bool HasNumericValue() const override {
+ // XXX: We combine the aria and native "has numeric value" field
+ // when we serialize the local accessible into eNumericValue.
+ return HasGenericType(eNumericValue);
+ }
+
+ // Methods that potentially access a cache.
+
+ virtual ENameValueFlag Name(nsString& aName) const override;
+ virtual void Description(nsString& aDescription) const override;
+ virtual void Value(nsString& aValue) const override;
+
+ virtual double CurValue() const override;
+ virtual double MinValue() const override;
+ virtual double MaxValue() const override;
+ virtual double Step() const override;
+ virtual bool SetCurValue(double aValue) override;
+
+ virtual Accessible* ChildAtPoint(
+ int32_t aX, int32_t aY,
+ LocalAccessible::EWhichChildAtPoint aWhichChild) override;
+
+ virtual LayoutDeviceIntRect Bounds() const override;
+
+ virtual nsRect BoundsInAppUnits() const override;
+
+ virtual Relation RelationByType(RelationType aType) const override;
+
+ virtual uint64_t State() override;
+
+ virtual already_AddRefed<AccAttributes> Attributes() override;
+
+ virtual nsAtom* TagName() const override;
+
+ virtual already_AddRefed<nsAtom> InputType() const override;
+
+ virtual already_AddRefed<nsAtom> DisplayStyle() const override;
+
+ virtual float Opacity() const override;
+
+ virtual void LiveRegionAttributes(nsAString* aLive, nsAString* aRelevant,
+ Maybe<bool>* aAtomic,
+ nsAString* aBusy) const override;
+
+ virtual Maybe<bool> ARIASelected() const override;
+
+ virtual uint8_t ActionCount() const override;
+
+ virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;
+
+ virtual bool DoAction(uint8_t aIndex) const override;
+
+ virtual KeyBinding AccessKey() const override;
+
+ virtual void SelectionRanges(nsTArray<TextRange>* aRanges) const override;
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual bool RemoveFromSelection(
+ int32_t aSelectionNum) override;
+
+ virtual Maybe<int32_t> GetIntARIAAttr(nsAtom* aAttrName) const override;
+
+ virtual void Language(nsAString& aLocale) override;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // EditableTextAccessible
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void ReplaceText(
+ const nsAString& aText) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void InsertText(
+ const nsAString& aText, int32_t aPosition) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void CopyText(int32_t aStartPos,
+ int32_t aEndPos) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void CutText(int32_t aStartPos,
+ int32_t aEndPos) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY virtual void DeleteText(int32_t aStartPos,
+ int32_t aEndPos) override;
+ MOZ_CAN_RUN_SCRIPT virtual void PasteText(int32_t aPosition) override;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // SelectAccessible
+
+ virtual void SelectedItems(nsTArray<Accessible*>* aItems) override;
+
+ virtual uint32_t SelectedItemCount() override;
+
+ virtual Accessible* GetSelectedItem(uint32_t aIndex) override;
+
+ virtual bool IsItemSelected(uint32_t aIndex) override;
+
+ virtual bool AddItemToSelection(uint32_t aIndex) override;
+
+ virtual bool RemoveItemFromSelection(uint32_t aIndex) override;
+
+ virtual bool SelectAll() override;
+
+ virtual bool UnselectAll() override;
+
+ virtual void TakeSelection() override;
+
+ virtual void SetSelected(bool aSelect) override;
+
+ // Methods that interact with content.
+
+ virtual void TakeFocus() const override;
+ virtual void ScrollTo(uint32_t aHow) const override;
+ virtual void SetCaretOffset(int32_t aOffset) override;
+
+ /**
+ * Allow the platform to store a pointers worth of data on us.
+ */
+ uintptr_t GetWrapper() const { return mWrapper; }
+ void SetWrapper(uintptr_t aWrapper) { mWrapper = aWrapper; }
+
+ virtual uint64_t ID() const override { return mID; }
+
+ /**
+ * Return the document containing this proxy, or the proxy itself if it is a
+ * document.
+ */
+ DocAccessibleParent* Document() const { return mDoc; }
+
+ DocAccessibleParent* AsDoc() const { return IsDoc() ? mDoc : nullptr; }
+
+ void ApplyCache(CacheUpdateType aUpdateType, AccAttributes* aFields);
+
+ void UpdateStateCache(uint64_t aState, bool aEnabled) {
+ if (aState & kRemoteCalculatedStates) {
+ return;
+ }
+ uint64_t state = 0;
+ if (mCachedFields) {
+ if (auto oldState =
+ mCachedFields->GetAttribute<uint64_t>(CacheKey::State)) {
+ state = *oldState;
+ }
+ } else {
+ mCachedFields = new AccAttributes();
+ }
+ if (aEnabled) {
+ state |= aState;
+ } else {
+ state &= ~aState;
+ }
+ mCachedFields->SetAttribute(CacheKey::State, state);
+ }
+
+ void InvalidateGroupInfo();
+
+ virtual void AppendTextTo(nsAString& aText, uint32_t aStartOffset = 0,
+ uint32_t aLength = UINT32_MAX) override;
+
+ virtual bool TableIsProbablyForLayout();
+
+ /**
+ * Iterates through each atom in kRelationTypeAtoms, checking to see
+ * if it is present in aFields. If it is present (or if aFields contains
+ * a DeleteEntry() for this atom) and mCachedFields is initialized,
+ * fetches the old rel targets and removes their existing reverse relations
+ * stored in mReverseRelations.
+ * Returns an array of bools where the ith array entry corresponds
+ * to whether or not the rel at the ith entry of kRelationTypeAtoms
+ * requires a post-processing update.
+ */
+ nsTArray<bool> PreProcessRelations(AccAttributes* aFields);
+
+ /**
+ * Takes in the array returned from PreProcessRelations.
+ * For each entry requiring an update, fetches the new relation
+ * targets stored in mCachedFields and appropriately
+ * updates their reverse relations in mReverseRelations.
+ */
+ void PostProcessRelations(const nsTArray<bool>& aToUpdate);
+
+ /**
+ * This method is called during shutdown, before we clear our
+ * reverse rel map from the document's mReverseRelations cache.
+ * Here, we traverse our reverse relations, removing our ID from
+ * the corresponding forward relation's target list. This ensures
+ * the stored forward relations do not reference defunct accessibles.
+ */
+ void PruneRelationsOnShutdown();
+
+ uint32_t GetCachedTextLength();
+ Maybe<const nsTArray<int32_t>&> GetCachedTextLines();
+ nsRect GetCachedCharRect(int32_t aOffset);
+ RefPtr<const AccAttributes> GetCachedTextAttributes();
+ RefPtr<const AccAttributes> GetCachedARIAAttributes() const;
+
+ nsString GetCachedHTMLNameAttribute() const;
+
+ virtual HyperTextAccessibleBase* AsHyperTextBase() override {
+ return IsHyperText() ? static_cast<HyperTextAccessibleBase*>(this)
+ : nullptr;
+ }
+
+ virtual TableAccessible* AsTable() override;
+ virtual TableCellAccessible* AsTableCell() override;
+
+ virtual void DOMNodeID(nsString& aID) const override;
+
+ virtual void ScrollToPoint(uint32_t aScrollType, int32_t aX,
+ int32_t aY) override;
+
+#if !defined(XP_WIN)
+ void Announce(const nsString& aAnnouncement, uint16_t aPriority);
+#endif // !defined(XP_WIN)
+
+ // HyperTextAccessibleBase
+ virtual already_AddRefed<AccAttributes> DefaultTextAttributes() override;
+
+ virtual void ScrollSubstringToPoint(int32_t aStartOffset, int32_t aEndOffset,
+ uint32_t aCoordinateType, int32_t aX,
+ int32_t aY) override;
+
+ /**
+ * Invalidate cached HyperText offsets. This should be called whenever a
+ * child is added or removed or the text of a text leaf child is changed.
+ * Although GetChildOffset can either fully or partially invalidate the
+ * offsets cache, calculating which offset to invalidate is not worthwhile
+ * because a client might not even query offsets. This is in contrast to
+ * LocalAccessible, where the offsets are always needed to fire text change
+ * events. For RemoteAccessible, it's cheaper overall to just rebuild the
+ * offsets cache when a client next needs it.
+ */
+ void InvalidateCachedHyperTextOffsets() {
+ if (mCachedFields) {
+ mCachedFields->Remove(CacheKey::HyperTextOffsets);
+ }
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf);
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf);
+
+ protected:
+ RemoteAccessible(uint64_t aID, DocAccessibleParent* aDoc, role aRole,
+ AccType aType, AccGenericType aGenericTypes,
+ uint8_t aRoleMapEntryIndex)
+ : Accessible(aType, aGenericTypes, aRoleMapEntryIndex),
+ mParent(kNoParent),
+ mDoc(aDoc),
+ mWrapper(0),
+ mID(aID),
+ mCachedFields(nullptr),
+ mRole(aRole) {
+ MOZ_COUNT_CTOR(RemoteAccessible);
+ }
+
+ explicit RemoteAccessible(DocAccessibleParent* aThisAsDoc)
+ : mParent(kNoParent),
+ mDoc(aThisAsDoc),
+ mWrapper(0),
+ mID(0),
+ mCachedFields(nullptr),
+ mRole(roles::DOCUMENT) {
+ mGenericTypes = eDocument | eHyperText;
+ MOZ_COUNT_CTOR(RemoteAccessible);
+ }
+
+ protected:
+ void SetParent(RemoteAccessible* aParent);
+ Maybe<nsRect> RetrieveCachedBounds() const;
+ bool ApplyTransform(nsRect& aCumulativeBounds) const;
+ bool ApplyScrollOffset(nsRect& aBounds) const;
+ void ApplyCrossDocOffset(nsRect& aBounds) const;
+ LayoutDeviceIntRect BoundsWithOffset(
+ Maybe<nsRect> aOffset, bool aBoundsAreForHittesting = false) const;
+ bool IsFixedPos() const;
+ bool IsOverflowHidden() const;
+
+ /**
+ * Returns true if an accessible's frame has no scrollable overflow, and
+ * false otherwise.
+ * Does not return true for partially clipped accessibles.
+ */
+ bool IsClipped() const;
+
+ /**
+ * Checks if our hittesting match has any clipped children and, if so
+ * descends it and subsequent TEXT_CONTAINERs in search of a text leaf.
+ * We do this because some sites use clipping to hide text that is only
+ * visible to a11y, while displaying a visual version of the same text on
+ * the web page. We want a hittest of the visible text to resolve to the
+ * hidden, a11y-only text node.
+ */
+ RemoteAccessible* DoFuzzyHittesting();
+
+ // This function is used exclusively for hit testing.
+ bool ContainsPoint(int32_t aX, int32_t aY);
+
+ virtual void ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
+ int32_t* aPosInSet) const override;
+
+ virtual AccGroupInfo* GetGroupInfo() const override;
+
+ virtual AccGroupInfo* GetOrCreateGroupInfo() override;
+
+ virtual void GetPositionAndSetSize(int32_t* aPosInSet,
+ int32_t* aSetSize) override;
+
+ virtual bool HasPrimaryAction() const override;
+
+ nsAtom* GetPrimaryAction() const;
+
+ virtual nsTArray<int32_t>& GetCachedHyperTextOffsets() override;
+
+ private:
+ uintptr_t mParent;
+ static const uintptr_t kNoParent = UINTPTR_MAX;
+
+ friend DocAccessibleParent;
+ friend TextLeafPoint;
+ friend HyperTextAccessibleBase;
+ friend class xpcAccessible;
+ friend class CachedTableCellAccessible;
+#ifdef XP_WIN
+ friend class sdnAccessible;
+#endif
+
+ nsTArray<RemoteAccessible*> mChildren;
+ DocAccessibleParent* mDoc;
+ uintptr_t mWrapper;
+ uint64_t mID;
+
+ protected:
+ virtual const Accessible* Acc() const override { return this; }
+
+ RefPtr<AccAttributes> mCachedFields;
+
+ // XXX DocAccessibleParent gets to change this to change the role of
+ // documents.
+ role mRole : 27;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// RemoteAccessible downcasting method
+
+inline RemoteAccessible* Accessible::AsRemote() {
+ return IsRemote() ? static_cast<RemoteAccessible*>(this) : nullptr;
+}
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
diff --git a/accessible/ipc/moz.build b/accessible/ipc/moz.build
new file mode 100644
index 0000000000..b8ff3b9c4f
--- /dev/null
+++ b/accessible/ipc/moz.build
@@ -0,0 +1,62 @@
+# -*- 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/.
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ LOCAL_INCLUDES += [
+ "/accessible/windows/ia2",
+ "/accessible/windows/msaa",
+ ]
+else:
+ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ LOCAL_INCLUDES += [
+ "/accessible/atk",
+ ]
+ elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ LOCAL_INCLUDES += [
+ "/accessible/mac",
+ ]
+ else:
+ LOCAL_INCLUDES += [
+ "/accessible/other",
+ ]
+
+if CONFIG["ACCESSIBILITY"]:
+ PREPROCESSED_IPDL_SOURCES += [
+ "PDocAccessible.ipdl",
+ ]
+ IPDL_SOURCES += [
+ "DocAccessibleTypes.ipdlh",
+ ]
+
+EXPORTS.mozilla.a11y += [
+ "IPCTypes.h",
+]
+
+if CONFIG["ACCESSIBILITY"]:
+ EXPORTS.mozilla.a11y += [
+ "DocAccessibleChild.h",
+ "DocAccessibleParent.h",
+ "RemoteAccessible.h",
+ ]
+
+ UNIFIED_SOURCES += [
+ "DocAccessibleChild.cpp",
+ "DocAccessibleParent.cpp",
+ "RemoteAccessible.cpp",
+ ]
+
+ LOCAL_INCLUDES += [
+ "/accessible/base",
+ "/accessible/generic",
+ "/accessible/xpcom",
+ ]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")