/* -*- 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/TabParent.h" #include "xpcAccessibleDocument.h" #include "xpcAccEvents.h" #include "nsAccUtils.h" #include "nsCoreUtils.h" namespace mozilla { namespace a11y { mozilla::ipc::IPCResult DocAccessibleParent::RecvShowEvent(const ShowEventData& aData, const bool& aFromUser) { if (mShutdown) return IPC_OK(); MOZ_DIAGNOSTIC_ASSERT(CheckDocTree()); if (aData.NewTree().IsEmpty()) { NS_ERROR("no children being added"); return IPC_FAIL_NO_REASON(this); } 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"); return IPC_OK(); } uint32_t newChildIdx = aData.Idx(); if (newChildIdx > parent->ChildrenCount()) { NS_ERROR("invalid index to add child at"); return IPC_OK(); } 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_OK(); } #ifdef DEBUG for (uint32_t i = 0; i < consumed; i++) { uint64_t id = aData.NewTree()[i].ID(); MOZ_ASSERT(mAccessibles.GetEntry(id)); } #endif MOZ_DIAGNOSTIC_ASSERT(CheckDocTree()); 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); nsIDOMNode* node = nullptr; RefPtr event = new xpcAccEvent(type, xpcAcc, doc, node, aFromUser); nsCoreUtils::DispatchAccEvent(Move(event)); return IPC_OK(); } uint32_t DocAccessibleParent::AddSubtree(ProxyAccessible* aParent, const nsTArray& 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 (newChild.Role() > roles::LAST_ROLE) { NS_ERROR("invalid role"); return 0; } if (mAccessibles.Contains(newChild.ID())) { NS_ERROR("ID already in use"); return 0; } auto role = static_cast(newChild.Role()); ProxyAccessible* newProxy = new ProxyAccessible(newChild.ID(), aParent, this, 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 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(newProxy->ChildrenCount() == kids); return accessibles; } mozilla::ipc::IPCResult DocAccessibleParent::RecvHideEvent(const uint64_t& aRootID, const bool& aFromUser) { if (mShutdown) return IPC_OK(); MOZ_DIAGNOSTIC_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) { NS_ERROR("trying to hide entire document?"); return IPC_FAIL_NO_REASON(this); } 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 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); nsIDOMNode* node = nullptr; event = new xpcAccHideEvent(type, xpcAcc, doc, node, aFromUser, xpcParent, xpcNext, xpcPrev); } parent->RemoveChild(root); root->Shutdown(); MOZ_DIAGNOSTIC_ASSERT(CheckDocTree()); if (event) { nsCoreUtils::DispatchAccEvent(Move(event)); } return IPC_OK(); } mozilla::ipc::IPCResult DocAccessibleParent::RecvEvent(const uint64_t& aID, const uint32_t& aEventType) { 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); nsIDOMNode* node = nullptr; bool fromUser = true; // XXX fix me RefPtr event = new xpcAccEvent(aEventType, xpcAcc, doc, node, fromUser); nsCoreUtils::DispatchAccEvent(Move(event)); return IPC_OK(); } mozilla::ipc::IPCResult DocAccessibleParent::RecvStateChangeEvent(const uint64_t& aID, const uint64_t& aState, const bool& aEnabled) { 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 nsIDOMNode* node = nullptr; // XXX can we do better? RefPtr event = new xpcAccStateChangeEvent(type, xpcAcc, doc, node, fromUser, state, extra, aEnabled); nsCoreUtils::DispatchAccEvent(Move(event)); return IPC_OK(); } mozilla::ipc::IPCResult DocAccessibleParent::RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset) { ProxyAccessible* proxy = GetAccessible(aID); if (!proxy) { NS_ERROR("unknown caret move event target!"); return IPC_OK(); } ProxyCaretMoveEvent(proxy, aOffset); if (!nsCoreUtils::AccEventObserversExist()) { return IPC_OK(); } xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy); xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this); nsIDOMNode* node = nullptr; bool fromUser = true; // XXX fix me uint32_t type = nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED; RefPtr event = new xpcAccCaretMoveEvent(type, xpcAcc, doc, node, fromUser, aOffset); nsCoreUtils::DispatchAccEvent(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) { 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; nsIDOMNode* node = nullptr; RefPtr event = new xpcAccTextChangeEvent(type, xpcAcc, doc, node, aFromUser, aStart, aLen, aIsInsert, aStr); nsCoreUtils::DispatchAccEvent(Move(event)); return IPC_OK(); } mozilla::ipc::IPCResult DocAccessibleParent::RecvSelectionEvent(const uint64_t& aID, const uint64_t& aWidgetID, const uint32_t& aType) { 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 event = new xpcAccEvent(aType, xpcTarget, xpcDoc, nullptr, false); nsCoreUtils::DispatchAccEvent(Move(event)); return IPC_OK(); } mozilla::ipc::IPCResult DocAccessibleParent::RecvRoleChangedEvent(const uint32_t& aRole) { if (aRole >= roles::LAST_ROLE) { NS_ERROR("child sent bad role in RoleChangedEvent"); return IPC_FAIL_NO_REASON(this); } mRole = static_cast(aRole); 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_NO_REASON(this); MOZ_DIAGNOSTIC_ASSERT(CheckDocTree()); auto childDoc = static_cast(aChildDoc); childDoc->Unbind(); bool result = AddChildDoc(childDoc, aID, false); MOZ_ASSERT(result); MOZ_DIAGNOSTIC_ASSERT(CheckDocTree()); if (!result) { return IPC_FAIL_NO_REASON(this); } return IPC_OK(); } bool 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) return false; 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 false; } aChildDoc->mParent = outerDoc; outerDoc->SetChildDoc(aChildDoc); mChildDocs.AppendElement(aChildDoc); aChildDoc->mParentDoc = this; if (aCreating) { ProxyCreated(aChildDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT); } return true; } mozilla::ipc::IPCResult DocAccessibleParent::RecvShutdown() { Destroy(); if (!static_cast(Manager())->IsDestroyed()) { if (!PDocAccessibleParent::Send__delete__(this)) { return IPC_FAIL_NO_REASON(this); } } return IPC_OK(); } void DocAccessibleParent::Destroy() { NS_ASSERTION(mChildDocs.IsEmpty(), "why weren't the child docs destroyed already?"); MOZ_ASSERT(!mShutdown); mShutdown = true; uint32_t childDocCount = mChildDocs.Length(); for (uint32_t i = childDocCount - 1; i < childDocCount; i--) mChildDocs[i]->Destroy(); for (auto iter = mAccessibles.Iter(); !iter.Done(); iter.Next()) { MOZ_ASSERT(iter.Get()->mProxy != this); ProxyDestroyed(iter.Get()->mProxy); iter.Remove(); } DocManager::NotifyOfRemoteDocShutdown(this); ProxyDestroyed(this); if (mParentDoc) mParentDoc->RemoveChildDoc(this); else if (IsTopLevel()) GetAccService()->RemoteDocShutdown(this); } bool DocAccessibleParent::CheckDocTree() const { size_t childDocs = mChildDocs.Length(); for (size_t i = 0; i < childDocs; i++) { if (!mChildDocs[i] || mChildDocs[i]->mParentDoc != this) return false; if (!mChildDocs[i]->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) /** * @param aCOMProxy COM Proxy to the document in the content process. * @param aParentCOMProxy COM Proxy to the OuterDocAccessible that is * the parent of the document. The content process will use this * proxy when traversing up across the content/chrome boundary. */ mozilla::ipc::IPCResult DocAccessibleParent::RecvCOMProxy(const IAccessibleHolder& aCOMProxy, IAccessibleHolder* aParentCOMProxy) { RefPtr ptr(aCOMProxy.Get()); SetCOMInterface(ptr); Accessible* outerDoc = OuterDocOfRemoteBrowser(); IAccessible* rawNative = nullptr; if (outerDoc) { outerDoc->GetNativeInterface((void**) &rawNative); } aParentCOMProxy->Set(IAccessibleHolder::COMPtrType(rawNative)); return IPC_OK(); } mozilla::ipc::IPCResult DocAccessibleParent::RecvGetWindowedPluginIAccessible( const WindowsHandle& aHwnd, IAccessibleHolder* aPluginCOMProxy) { #if defined(MOZ_CONTENT_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(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 } #endif // defined(XP_WIN) } // a11y } // mozilla