summaryrefslogtreecommitdiffstats
path: root/accessible/ipc/DocAccessibleParent.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /accessible/ipc/DocAccessibleParent.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'accessible/ipc/DocAccessibleParent.cpp')
-rw-r--r--accessible/ipc/DocAccessibleParent.cpp1266
1 files changed, 1266 insertions, 0 deletions
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