summaryrefslogtreecommitdiffstats
path: root/accessible/ipc/DocAccessibleParent.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/ipc/DocAccessibleParent.cpp')
-rw-r--r--accessible/ipc/DocAccessibleParent.cpp526
1 files changed, 526 insertions, 0 deletions
diff --git a/accessible/ipc/DocAccessibleParent.cpp b/accessible/ipc/DocAccessibleParent.cpp
new file mode 100644
index 000000000..0e9dfc080
--- /dev/null
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -0,0 +1,526 @@
+/* -*- 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 {
+
+bool
+DocAccessibleParent::RecvShowEvent(const ShowEventData& aData,
+ const bool& aFromUser)
+{
+ if (mShutdown)
+ return true;
+
+ MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
+
+ if (aData.NewTree().IsEmpty()) {
+ NS_ERROR("no children being added");
+ return false;
+ }
+
+ 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 true;
+ }
+
+ uint32_t newChildIdx = aData.Idx();
+ if (newChildIdx > parent->ChildrenCount()) {
+ NS_ERROR("invalid index to add child at");
+ return true;
+ }
+
+ 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 true;
+ }
+
+#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 true;
+ }
+
+ uint32_t type = nsIAccessibleEvent::EVENT_SHOW;
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsIDOMNode* node = nullptr;
+ RefPtr<xpcAccEvent> event = new xpcAccEvent(type, xpcAcc, doc, node,
+ aFromUser);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+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 (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<a11y::role>(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;
+}
+
+bool
+DocAccessibleParent::RecvHideEvent(const uint64_t& aRootID,
+ const bool& aFromUser)
+{
+ if (mShutdown)
+ return true;
+
+ 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 false;
+ }
+
+ ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID);
+ if (!rootEntry) {
+ NS_ERROR("invalid root being removed!");
+ return true;
+ }
+
+ ProxyAccessible* root = rootEntry->mProxy;
+ if (!root) {
+ NS_ERROR("invalid root being removed!");
+ return true;
+ }
+
+ 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);
+ 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 true;
+}
+
+bool
+DocAccessibleParent::RecvEvent(const uint64_t& aID, const uint32_t& aEventType)
+{
+ ProxyAccessible* proxy = GetAccessible(aID);
+ if (!proxy) {
+ NS_ERROR("no proxy for event!");
+ return true;
+ }
+
+ ProxyEvent(proxy, aEventType);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+
+ xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(proxy);
+ xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
+ nsIDOMNode* node = nullptr;
+ bool fromUser = true; // XXX fix me
+ RefPtr<xpcAccEvent> event = new xpcAccEvent(aEventType, xpcAcc, doc, node,
+ fromUser);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+bool
+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 true;
+ }
+
+ ProxyStateChangeEvent(target, aState, aEnabled);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+
+ 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<xpcAccStateChangeEvent> event =
+ new xpcAccStateChangeEvent(type, xpcAcc, doc, node, fromUser, state, extra,
+ aEnabled);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+bool
+DocAccessibleParent::RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
+{
+ ProxyAccessible* proxy = GetAccessible(aID);
+ if (!proxy) {
+ NS_ERROR("unknown caret move event target!");
+ return true;
+ }
+
+ ProxyCaretMoveEvent(proxy, aOffset);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+
+ 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<xpcAccCaretMoveEvent> event =
+ new xpcAccCaretMoveEvent(type, xpcAcc, doc, node, fromUser, aOffset);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+bool
+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 true;
+ }
+
+ ProxyTextChangeEvent(target, aStr, aStart, aLen, aIsInsert, aFromUser);
+
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+
+ 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<xpcAccTextChangeEvent> event =
+ new xpcAccTextChangeEvent(type, xpcAcc, doc, node, aFromUser, aStart, aLen,
+ aIsInsert, aStr);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+bool
+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 true;
+ }
+
+ ProxySelectionEvent(target, widget, aType);
+ if (!nsCoreUtils::AccEventObserversExist()) {
+ return true;
+ }
+ xpcAccessibleGeneric* xpcTarget = GetXPCAccessible(target);
+ xpcAccessibleDocument* xpcDoc = GetAccService()->GetXPCDocument(this);
+ RefPtr<xpcAccEvent> event = new xpcAccEvent(aType, xpcTarget, xpcDoc,
+ nullptr, false);
+ nsCoreUtils::DispatchAccEvent(Move(event));
+
+ return true;
+}
+
+bool
+DocAccessibleParent::RecvRoleChangedEvent(const uint32_t& aRole)
+{
+ if (aRole >= roles::LAST_ROLE) {
+ NS_ERROR("child sent bad role in RoleChangedEvent");
+ return false;
+ }
+
+ mRole = static_cast<a11y::role>(aRole);
+ return true;
+}
+
+bool
+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 false;
+
+ MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
+
+ auto childDoc = static_cast<DocAccessibleParent*>(aChildDoc);
+ childDoc->Unbind();
+ bool result = AddChildDoc(childDoc, aID, false);
+ MOZ_ASSERT(result);
+ MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
+ return result;
+}
+
+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;
+}
+
+bool
+DocAccessibleParent::RecvShutdown()
+{
+ Destroy();
+
+ if (!static_cast<dom::TabParent*>(Manager())->IsDestroyed()) {
+ return PDocAccessibleParent::Send__delete__(this);
+ }
+
+ return true;
+}
+
+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.
+ */
+void
+DocAccessibleParent::SetCOMProxy(const RefPtr<IAccessible>& aCOMProxy)
+{
+ SetCOMInterface(aCOMProxy);
+
+ // Make sure that we're not racing with a tab shutdown
+ auto tab = static_cast<dom::TabParent*>(Manager());
+ MOZ_ASSERT(tab);
+ if (tab->IsDestroyed()) {
+ return;
+ }
+
+ Accessible* outerDoc = OuterDocOfRemoteBrowser();
+ MOZ_ASSERT(outerDoc);
+
+ IAccessible* rawNative = nullptr;
+ if (outerDoc) {
+ outerDoc->GetNativeInterface((void**) &rawNative);
+ MOZ_ASSERT(rawNative);
+ }
+
+ IAccessibleHolder::COMPtrType ptr(rawNative);
+ IAccessibleHolder holder(Move(ptr));
+ Unused << SendParentCOMProxy(holder);
+}
+
+bool
+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<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 true;
+ }
+
+ 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 true;
+ }
+
+ aPluginCOMProxy->Set(IAccessibleHolder::COMPtrType(rawAccPlugin));
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+#endif // defined(XP_WIN)
+
+} // a11y
+} // mozilla