/* -*- 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 "mozilla/PerfStats.h"
#include "mozilla/ProfilerMarkers.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),
#if defined(XP_WIN)
      mEmulatedWindowHandle(nullptr),
#endif  // defined(XP_WIN)
      mTopLevel(false),
      mTopLevelInContentProcess(false),
      mShutdown(false),
      mIsInitialTreeDone(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::ProcessShowEvent(
    nsTArray<AccessibleData>&& aNewTree, const bool& aEventSuppressed,
    const bool& aComplete, const bool& aFromUser) {
  AUTO_PROFILER_MARKER_TEXT("DocAccessibleParent::ProcessShowEvent", A11Y, {},
                            ""_ns);
  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_ProcessShowEvent>
      autoRecording;
  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.

  ACQUIRE_ANDROID_LOCK

  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) {
    // Avoid repeated hash lookups when there are multiple children of the same
    // parent.
    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
    }

    if (parent->IsOuterDoc()) {
      return IPC_FAIL(this, "Cannot attach non-doc to OuterDoc");
    }

    lastParent = parent;
    lastParentID = accData.ParentID();

    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) {
      if (!AttachChild(parent, childIdx, child)) {
        return IPC_FAIL(this, "failed to attach 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);
    if (!AttachChild(rootParent, mPendingShowIndex, root)) {
      return IPC_FAIL(this, "failed to attach pending show child");
    }
    mPendingShowChild = 0;
    mPendingShowParent = 0;
    mPendingShowIndex = 0;
  }

  // Just update, no events.
  if (aEventSuppressed) {
    return IPC_OK();
  }

  {
    // Scope for PerfStats
    AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns);
    PerfStats::AutoMetricRecording<
        PerfStats::Metric::A11Y_PlatformShowHideEvent>
        autoRecording;
    // WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK:
    // THIS CODE IS MEASURING TIMINGS.
    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) {
  if (aAccData.ID() == 0) {
    MOZ_ASSERT_UNREACHABLE("An ID of 0 is reserved for the document itself");
    return nullptr;
  }

  RemoteAccessible* newProxy;
  if ((newProxy = GetAccessible(aAccData.ID()))) {
    // This is a move. Reuse the Accessible; don't destroy it.
    if (newProxy->RemoteParent()) {
      MOZ_ASSERT_UNREACHABLE(
          "Attempt to move RemoteAccessible which still has a parent!");
      return nullptr;
    }
    return newProxy;
  }

  if (!aria::IsRoleMapIndexValid(aAccData.RoleMapEntryIndex())) {
    MOZ_ASSERT_UNREACHABLE("Invalid role map entry index");
    return nullptr;
  }

  if (aAccData.GenericTypes() & eDocument) {
    MOZ_ASSERT_UNREACHABLE("Invalid acc type");
    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;
}

bool DocAccessibleParent::AttachChild(RemoteAccessible* aParent,
                                      uint32_t aIndex,
                                      RemoteAccessible* aChild) {
  if (aChild->RemoteParent()) {
    MOZ_ASSERT_UNREACHABLE(
        "Attempt to attach child which already has a parent!");
    return false;
  }

  if (aParent == aChild) {
    MOZ_ASSERT_UNREACHABLE("Attempt to make an accessible its own child!");
    return false;
  }

  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->IsTableRow() || 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()) {
        MOZ_DIAGNOSTIC_ASSERT(!childDoc->RemoteParent(),
                              "Pending OOP child doc shouldn't have parent "
                              "once new OuterDoc is attached");
        AddChildDoc(childDoc, aChild->ID(), false);
      }
      return true;
    });
  }

  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) {
      if (child == aAcc) {
        MOZ_ASSERT_UNREACHABLE(
            "Somehow an accessible got added as a child of itself!");
      }
      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->IsTableRow() || 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::ProcessHideEvent(
    const uint64_t& aRootID, const bool& aFromUser) {
  AUTO_PROFILER_MARKER_TEXT("DocAccessibleParent::ProcessHideEvent", A11Y, {},
                            ""_ns);
  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_ProcessHideEvent>
      autoRecording;
  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
  ACQUIRE_ANDROID_LOCK

  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();
  {
    // Scope for PerfStats
    AUTO_PROFILER_MARKER_TEXT("a11y::PlatformShowHideEvent", A11Y, {}, ""_ns);
    PerfStats::AutoMetricRecording<
        PerfStats::Metric::A11Y_PlatformShowHideEvent>
        autoRecording;
    // WITHIN THIS SCOPE, DO NOT ADD CODE ABOVE THIS BLOCK:
    // THIS CODE IS MEASURING TIMINGS.
    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::ProcessTextChangeEvent(
    const uint64_t& aID, const nsAString& aStr, const int32_t& aStart,
    const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser) {
  ACQUIRE_ANDROID_LOCK

  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::RecvMutationEvents(
    nsTArray<MutationEventData>&& aData) {
  // We do not use ACQUIRE_ANDROID_LOCK here since we call functions that do
  // that for us. The lock is not re-entrant.
  mozilla::ipc::IPCResult result = IPC_OK();
  if (mShutdown) {
    return result;
  }
  for (MutationEventData& data : aData) {
    switch (data.type()) {
      case MutationEventData::Type::TCacheEventData: {
        CacheEventData& cacheEventData = data;
        result = RecvCache(cacheEventData.UpdateType(),
                           std::move(cacheEventData.aData()));
        break;
      }
      case MutationEventData::Type::TReorderEventData: {
        ReorderEventData& reorderEventData = data;
        result = RecvEvent(reorderEventData.ID(), reorderEventData.Type());
        break;
      }
      case MutationEventData::Type::THideEventData: {
        HideEventData& hideEventData = data;
        result = ProcessHideEvent(hideEventData.ID(),
                                  hideEventData.IsFromUserInput());
        break;
      }
      case MutationEventData::Type::TShowEventData: {
        ShowEventData& showEventData = data;
        result = ProcessShowEvent(
            std::move(showEventData.NewTree()), showEventData.EventSuppressed(),
            showEventData.Complete(), showEventData.FromUser());
        break;
      }
      case MutationEventData::Type::TTextChangeEventData: {
        TextChangeEventData& textChangeEventData = data;
        result = ProcessTextChangeEvent(
            textChangeEventData.ID(), textChangeEventData.Str(),
            textChangeEventData.Start(), textChangeEventData.Len(),
            textChangeEventData.IsInsert(), textChangeEventData.FromUser());
        break;
      }
      default:
        break;
    }
    if (!result) {
      return result;
    }
  }

  return IPC_OK();
}

mozilla::ipc::IPCResult DocAccessibleParent::RecvRequestAckMutationEvents() {
  if (!mShutdown) {
    if (!mIsInitialTreeDone) {
      // This is the first request for an ACK, which means we now have the
      // initial tree.
      mIsInitialTreeDone = true;
      // If this document is already bound to its embedder, fire a reorder event
      // to notify the client that the embedded document is available. If not,
      // this will be handled when this document is bound in AddChildDoc.
      if (RemoteAccessible* parent = RemoteParent()) {
        parent->Document()->FireEvent(parent,
                                      nsIAccessibleEvent::EVENT_REORDER);
      }
    }
    Unused << SendAckMutationEvents();
  }
  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) {
  AUTO_PROFILER_MARKER_TEXT("DocAccessibleParent::RecvCache", A11Y, {}, ""_ns);
  PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_RecvCache>
      autoRecording;
  // DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.

  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());
  if (childDoc->IsShutdown()) {
    return IPC_FAIL(this, "Attempt to bind a shutdown child doc");
  }

  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) {
  if (aChildDoc->RemoteParent()) {
    return IPC_FAIL(this,
                    "Attempt to add child doc which already has a parent");
  }

  if (aChildDoc->IsShutdown()) {
    return IPC_FAIL(this, "Attempt to add a shutdown child doc");
  }

  // 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_CRASH("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_CRASH("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);

  if (aCreating) {
    ProxyCreated(aChildDoc);
  }

  if (aChildDoc->IsTopLevelInContentProcess()) {
    // aChildDoc is an embedded document in a different content process to
    // this document.
#if defined(XP_WIN)
    if (nsWinUtils::IsWindowEmulationStarted()) {
      aChildDoc->SetEmulatedWindowHandle(mEmulatedWindowHandle);
    }
#endif  // defined(XP_WIN)
  }
  // We need to fire a reorder event on the embedder. We do this here rather
  // than in the content process for two reasons:
  // 1. It isn't possible for the content process to fire a reorder event on the
  // embedder when the embedded document is in a different process to its
  // embedder.
  // 2. Doing it here ensures that the event is fired after the child document
  // is bound. Otherwise, there could be a short period where the content
  // process has fired the reorder event, but the child document isn't bound
  // yet.
  // However, if the initial tree hasn't been received yet, we don't want to
  // fire the reorder event yet. That gets handled in
  // RecvRequestAckMutationEvents.
  if (aChildDoc->mIsInitialTreeDone) {
    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) {
    // Just in case there is a cycle in the document heirarchy.
    mParent = nullptr;
    mIndexInParent = -1;
    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()) {
      // Prevents the invalidation code from trying to walk up the tree.
      acc->SetParent(nullptr);
      CachedTableAccessible::Invalidate(acc);
    }
    ProxyDestroyed(acc);
    // mAccessibles owns acc, so removing it deletes 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 (IsTopLevel()) {
    GetAccService()->RemoteDocShutdown(this);
  } else {
    Unbind();
  }
}

void DocAccessibleParent::ActorDestroy(ActorDestroyReason aWhy) {
  MOZ_ASSERT(CheckDocTree());
  if (!mShutdown) {
    ACQUIRE_ANDROID_LOCK
    Destroy();
  } else if (RemoteParent()) {
    ACQUIRE_ANDROID_LOCK
    Unbind();
  }
}

DocAccessibleParent* DocAccessibleParent::ParentDoc() const {
  if (RemoteAccessible* parent = RemoteParent()) {
    return parent->Document();
  }
  return nullptr;
}

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
