gecko-dev/accessible/ipc/DocAccessibleParent.cpp

1033 строки
32 KiB
C++

/* -*- 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 "DocAccessibleParent.h"
#include "mozilla/a11y/Platform.h"
#include "mozilla/dom/BrowserBridgeParent.h"
#include "mozilla/dom/BrowserParent.h"
#include "xpcAccessibleDocument.h"
#include "xpcAccEvents.h"
#include "nsAccUtils.h"
#include "TextRange.h"
#if defined(XP_WIN)
# include "AccessibleWrap.h"
# include "Compatibility.h"
# include "mozilla/mscom/PassthruProxy.h"
# include "mozilla/mscom/Ptr.h"
# include "nsWinUtils.h"
# include "RootAccessible.h"
#else
# include "mozilla/a11y/DocAccessiblePlatformExtParent.h"
#endif
namespace mozilla {
#if defined(XP_WIN)
namespace mscom {
namespace detail {
// Needed by mscom::PassthruProxy::Wrap<IAccessible>.
template <>
struct VTableSizer<IAccessible> {
// 3 methods in IUnknown + 4 in IDispatch + 21 in IAccessible = 28 total
enum { Size = 28 };
};
} // namespace detail
} // namespace mscom
#endif // defined (XP_WIN)
namespace a11y {
uint64_t DocAccessibleParent::sMaxDocID = 0;
mozilla::ipc::IPCResult DocAccessibleParent::RecvShowEvent(
const ShowEventData& aData, const bool& aFromUser) {
if (mShutdown) return IPC_OK();
MOZ_ASSERT(CheckDocTree());
if (aData.NewTree().IsEmpty()) {
return IPC_FAIL(this, "No children being added");
}
ProxyAccessible* parent = GetAccessible(aData.ID());
// 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 newChildIdx = aData.Idx();
if (newChildIdx > parent->ChildrenCount()) {
NS_ERROR("invalid index to add child at");
#ifdef DEBUG
return IPC_FAIL(this, "invalid index");
#else
return IPC_OK();
#endif
}
uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx);
MOZ_ASSERT(consumed == aData.NewTree().Length());
// XXX This shouldn't happen, but if we failed to add children then the below
// is pointless and can crash.
if (!consumed) {
return IPC_FAIL(this, "failed to add children");
}
#ifdef DEBUG
for (uint32_t i = 0; i < consumed; i++) {
uint64_t id = aData.NewTree()[i].ID();
MOZ_ASSERT(mAccessibles.GetEntry(id));
}
#endif
MOZ_ASSERT(CheckDocTree());
// Just update, no events.
if (aData.EventSuppressed()) {
return IPC_OK();
}
ProxyAccessible* target = parent->ChildAt(newChildIdx);
ProxyShowHideEvent(target, parent, true, aFromUser);
if (!nsCoreUtils::AccEventObserversExist()) {
return IPC_OK();
}
uint32_t type = nsIAccessibleEvent::EVENT_SHOW;
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
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();
}
uint32_t DocAccessibleParent::AddSubtree(
ProxyAccessible* aParent, const nsTArray<a11y::AccessibleData>& aNewTree,
uint32_t aIdx, uint32_t aIdxInParent) {
if (aNewTree.Length() <= aIdx) {
NS_ERROR("bad index in serialized tree!");
return 0;
}
const AccessibleData& newChild = aNewTree[aIdx];
if (mAccessibles.Contains(newChild.ID())) {
NS_ERROR("ID already in use");
return 0;
}
ProxyAccessible* newProxy = new ProxyAccessible(
newChild.ID(), aParent, this, newChild.Role(), newChild.Interfaces());
aParent->AddChildAt(aIdxInParent, newProxy);
mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy;
ProxyCreated(newProxy, newChild.Interfaces());
#if defined(XP_WIN)
WrapperFor(newProxy)->SetID(newChild.MsaaID());
#endif
for (uint32_t index = 0, len = mPendingChildDocs.Length(); index < len;
++index) {
PendingChildDoc& pending = mPendingChildDocs[index];
if (pending.mParentID == newChild.ID()) {
if (!pending.mChildDoc->IsShutdown()) {
AddChildDoc(pending.mChildDoc, pending.mParentID, false);
}
mPendingChildDocs.RemoveElementAt(index);
break;
}
}
DebugOnly<bool> isOuterDoc = newProxy->ChildrenCount() == 1;
uint32_t accessibles = 1;
uint32_t kids = newChild.ChildrenCount();
for (uint32_t i = 0; i < kids; i++) {
uint32_t consumed = AddSubtree(newProxy, aNewTree, aIdx + accessibles, i);
if (!consumed) return 0;
accessibles += consumed;
}
MOZ_ASSERT((isOuterDoc && kids == 0) || newProxy->ChildrenCount() == kids);
return accessibles;
}
mozilla::ipc::IPCResult DocAccessibleParent::RecvHideEvent(
const uint64_t& aRootID, const bool& aFromUser) {
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();
}
ProxyAccessible* root = rootEntry->mProxy;
if (!root) {
NS_ERROR("invalid root being removed!");
return IPC_OK();
}
ProxyAccessible* parent = root->Parent();
ProxyShowHideEvent(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);
ProxyAccessible* next = root->NextSibling();
xpcAccessibleGeneric* xpcNext = next ? GetXPCAccessible(next) : nullptr;
ProxyAccessible* prev = root->PrevSibling();
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);
root->Shutdown();
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) {
if (mShutdown) {
return IPC_OK();
}
ProxyAccessible* proxy = GetAccessible(aID);
if (!proxy) {
NS_ERROR("no proxy for event!");
return IPC_OK();
}
ProxyEvent(proxy, aEventType);
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(aEventType, xpcAcc, doc, node, fromUser);
nsCoreUtils::DispatchAccEvent(std::move(event));
return IPC_OK();
}
mozilla::ipc::IPCResult DocAccessibleParent::RecvStateChangeEvent(
const uint64_t& aID, const uint64_t& aState, const bool& aEnabled) {
if (mShutdown) {
return IPC_OK();
}
ProxyAccessible* target = GetAccessible(aID);
if (!target) {
NS_ERROR("we don't know about the target of a state change event!");
return IPC_OK();
}
ProxyStateChangeEvent(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,
#if defined(XP_WIN)
const LayoutDeviceIntRect& aCaretRect,
#endif // defined (XP_WIN)
const int32_t& aOffset, const bool& aIsSelectionCollapsed) {
if (mShutdown) {
return IPC_OK();
}
ProxyAccessible* proxy = GetAccessible(aID);
if (!proxy) {
NS_ERROR("unknown caret move event target!");
return IPC_OK();
}
#if defined(XP_WIN)
ProxyCaretMoveEvent(proxy, aCaretRect);
#else
ProxyCaretMoveEvent(proxy, aOffset, aIsSelectionCollapsed);
#endif
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);
nsCoreUtils::DispatchAccEvent(std::move(event));
return IPC_OK();
}
mozilla::ipc::IPCResult DocAccessibleParent::RecvTextChangeEvent(
const uint64_t& aID, const nsString& aStr, const int32_t& aStart,
const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser) {
if (mShutdown) {
return IPC_OK();
}
ProxyAccessible* target = GetAccessible(aID);
if (!target) {
NS_ERROR("text change event target is unknown!");
return IPC_OK();
}
ProxyTextChangeEvent(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();
}
#if defined(XP_WIN)
mozilla::ipc::IPCResult DocAccessibleParent::RecvSyncTextChangeEvent(
const uint64_t& aID, const nsString& aStr, const int32_t& aStart,
const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser) {
return RecvTextChangeEvent(aID, aStr, aStart, aLen, aIsInsert, aFromUser);
}
#endif // defined(XP_WIN)
mozilla::ipc::IPCResult DocAccessibleParent::RecvSelectionEvent(
const uint64_t& aID, const uint64_t& aWidgetID, const uint32_t& aType) {
if (mShutdown) {
return IPC_OK();
}
ProxyAccessible* target = GetAccessible(aID);
ProxyAccessible* widget = GetAccessible(aWidgetID);
if (!target || !widget) {
NS_ERROR("invalid id in selection event");
return IPC_OK();
}
ProxySelectionEvent(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::RecvVirtualCursorChangeEvent(
const uint64_t& aID, const uint64_t& aOldPositionID,
const int32_t& aOldStartOffset, const int32_t& aOldEndOffset,
const uint64_t& aNewPositionID, const int32_t& aNewStartOffset,
const int32_t& aNewEndOffset, const int16_t& aReason,
const int16_t& aBoundaryType, const bool& aFromUser) {
if (mShutdown) {
return IPC_OK();
}
ProxyAccessible* target = GetAccessible(aID);
ProxyAccessible* oldPosition = GetAccessible(aOldPositionID);
ProxyAccessible* newPosition = GetAccessible(aNewPositionID);
if (!target) {
NS_ERROR("no proxy for event!");
return IPC_OK();
}
#if defined(ANDROID)
ProxyVirtualCursorChangeEvent(
target, oldPosition, aOldStartOffset, aOldEndOffset, newPosition,
aNewStartOffset, aNewEndOffset, aReason, aBoundaryType, aFromUser);
#endif
if (!nsCoreUtils::AccEventObserversExist()) {
return IPC_OK();
}
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
RefPtr<xpcAccVirtualCursorChangeEvent> event =
new xpcAccVirtualCursorChangeEvent(
nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED,
GetXPCAccessible(target), doc, nullptr, aFromUser,
GetXPCAccessible(oldPosition), aOldStartOffset, aOldEndOffset,
GetXPCAccessible(newPosition), aNewStartOffset, aNewEndOffset,
aBoundaryType, aReason);
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) {
if (mShutdown) {
return IPC_OK();
}
ProxyAccessible* target = GetAccessible(aID);
if (!target) {
NS_ERROR("no proxy for event!");
return IPC_OK();
}
#if defined(ANDROID)
ProxyScrollingEvent(target, aType, aScrollX, aScrollY, aMaxScrollX,
aMaxScrollY);
#else
ProxyEvent(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();
}
#if !defined(XP_WIN)
mozilla::ipc::IPCResult DocAccessibleParent::RecvAnnouncementEvent(
const uint64_t& aID, const nsString& aAnnouncement,
const uint16_t& aPriority) {
if (mShutdown) {
return IPC_OK();
}
ProxyAccessible* target = GetAccessible(aID);
if (!target) {
NS_ERROR("no proxy for event!");
return IPC_OK();
}
# if defined(ANDROID)
ProxyAnnouncementEvent(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();
}
mozilla::ipc::IPCResult DocAccessibleParent::RecvTextSelectionChangeEvent(
const uint64_t& aID, nsTArray<TextRangeData>&& aSelection) {
# ifdef MOZ_WIDGET_COCOA
if (mShutdown) {
return IPC_OK();
}
ProxyAccessible* target = GetAccessible(aID);
if (!target) {
NS_ERROR("no proxy for event!");
return IPC_OK();
}
ProxyTextSelectionChangeEvent(target, aSelection);
return IPC_OK();
# else
return RecvEvent(aID, nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED);
# endif
}
#endif
mozilla::ipc::IPCResult DocAccessibleParent::RecvRoleChangedEvent(
const a11y::role& aRole) {
if (mShutdown) {
return IPC_OK();
}
mRole = aRole;
#ifdef MOZ_WIDGET_COCOA
ProxyRoleChangedEvent(this, aRole);
#endif
return IPC_OK();
}
mozilla::ipc::IPCResult DocAccessibleParent::RecvBindChildDoc(
PDocAccessibleParent* aChildDoc, const uint64_t& aID) {
// 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);
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) {
if (aChildDoc->IsTopLevelInContentProcess()) {
// aChildDoc is an embedded document in a different content process to
// this document. Sometimes, AddChildDoc gets called before the embedder
// sends us the OuterDocAccessible. We must add the child when the
// OuterDocAccessible proxy gets created later.
#ifdef DEBUG
for (uint32_t index = 0, len = mPendingChildDocs.Length(); index < len;
++index) {
MOZ_ASSERT(mPendingChildDocs[index].mChildDoc != aChildDoc,
"Child doc already pending addition!");
}
#endif
mPendingChildDocs.AppendElement(PendingChildDoc(aChildDoc, aParentID));
if (aCreating) {
ProxyCreated(aChildDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
}
return IPC_OK();
}
return IPC_FAIL(this, "binding to nonexistant proxy!");
}
ProxyAccessible* 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->ChildrenCount() > 1 ||
(outerDoc->ChildrenCount() == 1 && !outerDoc->ChildAt(0)->IsDoc())) {
return IPC_FAIL(this, "binding to proxy that can't be a outerDoc!");
}
if (outerDoc->ChildrenCount() == 1) {
MOZ_ASSERT(outerDoc->ChildAt(0)->AsDoc());
outerDoc->ChildAt(0)->AsDoc()->Unbind();
}
aChildDoc->SetParent(outerDoc);
outerDoc->SetChildDoc(aChildDoc);
mChildDocs.AppendElement(aChildDoc->mActorID);
aChildDoc->mParentDoc = mActorID;
if (aCreating) {
ProxyCreated(aChildDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
}
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)
// Send a COM proxy for the embedded document to the embedder process
// hosting the iframe. This will be returned as the child of the
// embedder OuterDocAccessible.
RefPtr<IDispatch> docAcc;
aChildDoc->GetCOMInterface((void**)getter_AddRefs(docAcc));
RefPtr<IDispatch> docWrapped(
mscom::PassthruProxy::Wrap<IDispatch>(WrapNotNull(docAcc)));
IDispatchHolder::COMPtrType docPtr(
mscom::ToProxyUniquePtr(std::move(docWrapped)));
IDispatchHolder docHolder(std::move(docPtr));
if (bridge->SendSetEmbeddedDocAccessibleCOMProxy(docHolder)) {
# if defined(MOZ_SANDBOX)
aChildDoc->mDocProxyStream = docHolder.GetPreservedStream();
# endif // defined(MOZ_SANDBOX)
}
// Send a COM proxy for the embedder OuterDocAccessible to the embedded
// document process. This will be returned as the parent of the
// embedded document.
aChildDoc->SendParentCOMProxy(WrapperFor(outerDoc));
if (nsWinUtils::IsWindowEmulationStarted()) {
// The embedded document should use the same emulated window handle as
// its embedder. It will return the embedder document (not a window
// accessible) as the parent accessible, so we pass a null accessible
// when sending the window to the embedded document.
aChildDoc->SetEmulatedWindowHandle(mEmulatedWindowHandle);
Unused << aChildDoc->SendEmulatedWindow(
reinterpret_cast<uintptr_t>(mEmulatedWindowHandle), nullptr);
}
// Send a COM proxy for the top level document to the embedded document
// process. This will be returned when the client calls QueryService
// with SID_IAccessibleContentDocument on an accessible in the embedded
// document.
DocAccessibleParent* topDoc = this;
while (DocAccessibleParent* parentDoc = topDoc->ParentDoc()) {
topDoc = parentDoc;
}
MOZ_ASSERT(topDoc && topDoc->IsTopLevel());
RefPtr<IAccessible> topDocAcc;
topDoc->GetCOMInterface((void**)getter_AddRefs(topDocAcc));
RefPtr<IAccessible> topDocWrapped(
mscom::PassthruProxy::Wrap<IAccessible>(WrapNotNull(topDocAcc)));
IAccessibleHolder::COMPtrType topDocPtr(
mscom::ToProxyUniquePtr(std::move(topDocWrapped)));
IAccessibleHolder topDocHolder(std::move(topDocPtr));
if (aChildDoc->SendTopLevelDocCOMProxy(topDocHolder)) {
# if defined(MOZ_SANDBOX)
aChildDoc->mTopLevelDocProxyStream = topDocHolder.GetPreservedStream();
# endif // defined(MOZ_SANDBOX)
}
#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.
// RecvEvent fires both OS and XPCOM events.
Unused << RecvEvent(aParentID, nsIAccessibleEvent::EVENT_REORDER);
}
}
return IPC_OK();
}
mozilla::ipc::IPCResult DocAccessibleParent::RecvShutdown() {
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;
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()) {
MOZ_ASSERT(iter.Get()->mProxy != this);
ProxyDestroyed(iter.Get()->mProxy);
iter.Remove();
}
DocAccessibleParent* thisDoc = LiveDocs().Get(actorID);
MOZ_ASSERT(thisDoc);
if (!thisDoc) {
return;
}
// 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);
}
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(
ProxyAccessible* aProxy) {
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
MOZ_ASSERT(doc);
return doc->GetXPCAccessible(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.
Accessible* outerDoc = OuterDocOfRemoteBrowser();
if (!outerDoc) {
return;
}
RootAccessible* rootDocument = outerDoc->RootAccessible();
MOZ_ASSERT(rootDocument);
bool isActive = true;
nsIntRect rect(CW_USEDEFAULT, CW_USEDEFAULT, 0, 0);
if (Compatibility::IsDolphin()) {
rect = Bounds();
nsIntRect 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 {
IDispatchHolder hWndAccHolder;
::SetPropW(aHwnd, kPropNameDocAccParent,
reinterpret_cast<HANDLE>(thisRef.get()));
thisRef->SetEmulatedWindowHandle(aHwnd);
RefPtr<IAccessible> hwndAcc;
if (SUCCEEDED(::AccessibleObjectFromWindow(
aHwnd, OBJID_WINDOW, IID_IAccessible, getter_AddRefs(hwndAcc)))) {
RefPtr<IDispatch> wrapped(
mscom::PassthruProxy::Wrap<IDispatch>(WrapNotNull(hwndAcc)));
hWndAccHolder.Set(IDispatchHolder::COMPtrType(
mscom::ToProxyUniquePtr(std::move(wrapped))));
}
Unused << thisRef->SendEmulatedWindow(
reinterpret_cast<uintptr_t>(thisRef->mEmulatedWindowHandle),
hWndAccHolder);
});
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::SendParentCOMProxy(Accessible* aOuterDoc) {
// Make sure that we're not racing with a tab shutdown
auto tab = static_cast<dom::BrowserParent*>(Manager());
MOZ_ASSERT(tab);
if (tab->IsDestroyed()) {
return;
}
RefPtr<IAccessible> nativeAcc;
aOuterDoc->GetNativeInterface(getter_AddRefs(nativeAcc));
if (NS_WARN_IF(!nativeAcc)) {
// Couldn't get a COM proxy for the outer doc. That probably means it died,
// but the parent process hasn't received a message to remove it from the
// ProxyAccessible tree yet.
return;
}
RefPtr<IDispatch> wrapped(
mscom::PassthruProxy::Wrap<IDispatch>(WrapNotNull(nativeAcc)));
IDispatchHolder::COMPtrType ptr(mscom::ToProxyUniquePtr(std::move(wrapped)));
IDispatchHolder holder(std::move(ptr));
if (!PDocAccessibleParent::SendParentCOMProxy(holder)) {
return;
}
# if defined(MOZ_SANDBOX)
mParentProxyStream = holder.GetPreservedStream();
# endif // defined(MOZ_SANDBOX)
}
void DocAccessibleParent::SetEmulatedWindowHandle(HWND aWindowHandle) {
if (!aWindowHandle && mEmulatedWindowHandle && IsTopLevel()) {
::DestroyWindow(mEmulatedWindowHandle);
}
mEmulatedWindowHandle = aWindowHandle;
}
mozilla::ipc::IPCResult DocAccessibleParent::RecvGetWindowedPluginIAccessible(
const WindowsHandle& aHwnd, IAccessibleHolder* aPluginCOMProxy) {
# if defined(MOZ_SANDBOX)
// We don't actually want the accessible object for aHwnd, but rather the
// one that belongs to its child (see HTMLWin32ObjectAccessible).
HWND childWnd = ::GetWindow(reinterpret_cast<HWND>(aHwnd), GW_CHILD);
if (!childWnd) {
// We're seeing this in the wild - the plugin is windowed but we no longer
// have a window.
return IPC_OK();
}
IAccessible* rawAccPlugin = nullptr;
HRESULT hr = ::AccessibleObjectFromWindow(
childWnd, OBJID_WINDOW, IID_IAccessible, (void**)&rawAccPlugin);
if (FAILED(hr)) {
// This might happen if the plugin doesn't handle WM_GETOBJECT properly.
// We should not consider that a failure.
return IPC_OK();
}
aPluginCOMProxy->Set(IAccessibleHolder::COMPtrType(rawAccPlugin));
return IPC_OK();
# else
return IPC_FAIL(this, "Message unsupported in this build configuration");
# endif
}
mozilla::ipc::IPCResult DocAccessibleParent::RecvFocusEvent(
const uint64_t& aID, const LayoutDeviceIntRect& aCaretRect) {
if (mShutdown) {
return IPC_OK();
}
ProxyAccessible* proxy = GetAccessible(aID);
if (!proxy) {
NS_ERROR("no proxy for event!");
return IPC_OK();
}
ProxyFocusEvent(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();
}
#endif // defined(XP_WIN)
#if !defined(XP_WIN)
mozilla::ipc::IPCResult DocAccessibleParent::RecvBatch(
const uint64_t& aBatchType, nsTArray<BatchData>&& aData) {
// Only do something in Android. We can't ifdef the entire protocol out in
// the ipdl because it doesn't allow preprocessing.
# if defined(ANDROID)
if (mShutdown) {
return IPC_OK();
}
nsTArray<ProxyAccessible*> proxies(aData.Length());
for (size_t i = 0; i < aData.Length(); i++) {
DocAccessibleParent* doc = static_cast<DocAccessibleParent*>(
aData.ElementAt(i).Document().get_PDocAccessibleParent());
MOZ_ASSERT(doc);
if (doc->IsShutdown()) {
continue;
}
ProxyAccessible* proxy = doc->GetAccessible(aData.ElementAt(i).ID());
if (!proxy) {
MOZ_ASSERT_UNREACHABLE("No proxy found!");
continue;
}
proxies.AppendElement(proxy);
}
ProxyBatch(this, aBatchType, proxies, aData);
# endif // defined(XP_WIN)
return IPC_OK();
}
bool DocAccessibleParent::DeallocPDocAccessiblePlatformExtParent(
PDocAccessiblePlatformExtParent* aActor) {
delete aActor;
return true;
}
PDocAccessiblePlatformExtParent*
DocAccessibleParent::AllocPDocAccessiblePlatformExtParent() {
return new DocAccessiblePlatformExtParent();
}
DocAccessiblePlatformExtParent* DocAccessibleParent::GetPlatformExtension() {
return static_cast<DocAccessiblePlatformExtParent*>(
SingleManagedOrNull(ManagedPDocAccessiblePlatformExtParent()));
}
#endif // !defined(XP_WIN)
Tuple<DocAccessibleParent*, uint64_t> DocAccessibleParent::GetRemoteEmbedder() {
dom::BrowserParent* embeddedBrowser = dom::BrowserParent::GetFrom(Manager());
dom::BrowserBridgeParent* bridge = embeddedBrowser->GetBrowserBridgeParent();
if (!bridge) {
return Tuple<DocAccessibleParent*, uint64_t>(nullptr, 0);
}
DocAccessibleParent* doc;
uint64_t id;
Tie(doc, id) = bridge->GetEmbedderAccessible();
if (doc && doc->IsShutdown()) {
// Sometimes, the embedder document is destroyed before its
// BrowserBridgeParent. Don't return a destroyed document.
doc = nullptr;
id = 0;
}
return Tuple<DocAccessibleParent*, uint64_t>(doc, id);
}
} // namespace a11y
} // namespace mozilla