diff --git a/accessible/atk/AccessibleWrap.cpp b/accessible/atk/AccessibleWrap.cpp index 5f98faac2cc5..cca059e9b34c 100644 --- a/accessible/atk/AccessibleWrap.cpp +++ b/accessible/atk/AccessibleWrap.cpp @@ -12,6 +12,7 @@ #include "nsAccUtils.h" #include "nsIAccessibleRelation.h" #include "nsIAccessibleTable.h" +#include "ProxyAccessible.h" #include "RootAccessible.h" #include "nsIAccessibleValue.h" #include "nsMai.h" @@ -20,6 +21,7 @@ #include "nsAutoPtr.h" #include "prprf.h" #include "nsStateMap.h" +#include "mozilla/a11y/Platform.h" #include "Relation.h" #include "RootAccessible.h" #include "States.h" @@ -133,9 +135,13 @@ struct MaiAtkObject * The AccessibleWrap whose properties and features are exported * via this object instance. */ - AccessibleWrap* accWrap; + uintptr_t accWrap; }; +// This is or'd with the pointer in MaiAtkObject::accWrap if the wrap-ee is a +// proxy. +static const uintptr_t IS_PROXY = 1; + struct MaiAtkObjectClass { AtkObjectClass parent_class; @@ -248,7 +254,7 @@ AccessibleWrap::ShutdownAtkObject() { if (mAtkObject) { if (IS_MAI_OBJECT(mAtkObject)) { - MAI_ATK_OBJECT(mAtkObject)->accWrap = nullptr; + MAI_ATK_OBJECT(mAtkObject)->accWrap = 0; } SetMaiHyperlink(nullptr); g_object_unref(mAtkObject); @@ -582,8 +588,7 @@ initializeCB(AtkObject *aAtkObj, gpointer aData) ATK_OBJECT_CLASS(parent_class)->initialize(aAtkObj, aData); /* initialize object */ - MAI_ATK_OBJECT(aAtkObj)->accWrap = - static_cast(aData); + MAI_ATK_OBJECT(aAtkObj)->accWrap = reinterpret_cast(aData); } void @@ -591,7 +596,7 @@ finalizeCB(GObject *aObj) { if (!IS_MAI_OBJECT(aObj)) return; - NS_ASSERTION(MAI_ATK_OBJECT(aObj)->accWrap == nullptr, "AccWrap NOT null"); + NS_ASSERTION(MAI_ATK_OBJECT(aObj)->accWrap == 0, "AccWrap NOT null"); // call parent finalize function // finalize of GObjectClass will unref the accessible parent if has @@ -663,25 +668,33 @@ getDescriptionCB(AtkObject *aAtkObj) AtkRole getRoleCB(AtkObject *aAtkObj) { - AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj); - if (!accWrap) - return ATK_ROLE_INVALID; - -#ifdef DEBUG - NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap), - "Does not support nsIAccessibleText when it should"); -#endif - if (aAtkObj->role != ATK_ROLE_INVALID) return aAtkObj->role; + AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj); + a11y::role role; + if (!accWrap) { + ProxyAccessible* proxy = GetProxy(aAtkObj); + if (!proxy) + return ATK_ROLE_INVALID; + + role = proxy->Role(); + } else { +#ifdef DEBUG + NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap), + "Does not support nsIAccessibleText when it should"); +#endif + + role = accWrap->Role(); + } + #define ROLE(geckoRole, stringRole, atkRole, macRole, \ msaaRole, ia2Role, nameRule) \ case roles::geckoRole: \ aAtkObj->role = atkRole; \ break; - switch (accWrap->Role()) { + switch (role) { #include "RoleMap.h" default: MOZ_CRASH("Unknown role."); @@ -946,7 +959,13 @@ AccessibleWrap* GetAccessibleWrap(AtkObject* aAtkObj) { NS_ENSURE_TRUE(IS_MAI_OBJECT(aAtkObj), nullptr); - AccessibleWrap* accWrap = MAI_ATK_OBJECT(aAtkObj)->accWrap; + + // Make sure its native is an AccessibleWrap not a proxy. + if (MAI_ATK_OBJECT(aAtkObj)->accWrap & IS_PROXY) + return nullptr; + + AccessibleWrap* accWrap = + reinterpret_cast(MAI_ATK_OBJECT(aAtkObj)->accWrap); // Check if the accessible was deconstructed. if (!accWrap) @@ -961,6 +980,49 @@ GetAccessibleWrap(AtkObject* aAtkObj) return accWrap; } +ProxyAccessible* +GetProxy(AtkObject* aObj) +{ + if (!aObj || !(MAI_ATK_OBJECT(aObj)->accWrap & IS_PROXY)) + return nullptr; + + return reinterpret_cast(MAI_ATK_OBJECT(aObj)->accWrap + & ~IS_PROXY); +} + +static uint16_t +GetInterfacesForProxy(ProxyAccessible* aProxy) +{ + return MAI_INTERFACE_COMPONENT; +} + +void +a11y::ProxyCreated(ProxyAccessible* aProxy) +{ + GType type = GetMaiAtkType(GetInterfacesForProxy(aProxy)); + NS_ASSERTION(type, "why don't we have a type!"); + + AtkObject* obj = + reinterpret_cast + (g_object_new(type, nullptr)); + if (!obj) + return; + + atk_object_initialize(obj, aProxy); + obj->role = ATK_ROLE_INVALID; + obj->layer = ATK_LAYER_INVALID; + aProxy->SetWrapper(reinterpret_cast(obj) | IS_PROXY); +} + +void +a11y::ProxyDestroyed(ProxyAccessible* aProxy) +{ + auto obj = reinterpret_cast(aProxy->GetWrapper() & ~IS_PROXY); + obj->accWrap = 0; + g_object_unref(obj); + aProxy->SetWrapper(0); +} + nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) { diff --git a/accessible/atk/AccessibleWrap.h b/accessible/atk/AccessibleWrap.h index 5518a80bd5d8..95d03c5fc16c 100644 --- a/accessible/atk/AccessibleWrap.h +++ b/accessible/atk/AccessibleWrap.h @@ -97,7 +97,7 @@ private: static EAvailableAtkSignals gAvailableAtkSignals; - uint16_t CreateMaiInterfaces(void); + uint16_t CreateMaiInterfaces(); }; } // namespace a11y diff --git a/accessible/atk/moz.build b/accessible/atk/moz.build index 37ec9e8fded5..96fc8cee15d7 100644 --- a/accessible/atk/moz.build +++ b/accessible/atk/moz.build @@ -35,6 +35,7 @@ LOCAL_INCLUDES += [ '/accessible/base', '/accessible/generic', '/accessible/html', + '/accessible/ipc', '/accessible/xpcom', '/accessible/xul', '/other-licenses/atk-1.0', diff --git a/accessible/atk/nsMai.h b/accessible/atk/nsMai.h index 5de552ac67a5..6338a3492bae 100644 --- a/accessible/atk/nsMai.h +++ b/accessible/atk/nsMai.h @@ -13,6 +13,12 @@ #include "AccessibleWrap.h" +namespace mozilla { +namespace a11y { +class ProxyAccessible; +} +} + #define MAI_TYPE_ATK_OBJECT (mai_atk_object_get_type ()) #define MAI_ATK_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ MAI_TYPE_ATK_OBJECT, MaiAtkObject)) @@ -29,6 +35,7 @@ GType mai_atk_object_get_type(void); GType mai_util_get_type(); mozilla::a11y::AccessibleWrap* GetAccessibleWrap(AtkObject* aAtkObj); +mozilla::a11y::ProxyAccessible* GetProxy(AtkObject* aAtkObj); extern int atkMajorVersion, atkMinorVersion; diff --git a/accessible/base/AccEvent.h b/accessible/base/AccEvent.h index f97b1fc4e01b..ee28efa511b6 100644 --- a/accessible/base/AccEvent.h +++ b/accessible/base/AccEvent.h @@ -232,6 +232,8 @@ public: bool IsShow() const { return mEventType == nsIAccessibleEvent::EVENT_SHOW; } bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; } + Accessible* Parent() const { return mParent; } + protected: nsCOMPtr mNode; nsRefPtr mParent; diff --git a/accessible/base/DocManager.cpp b/accessible/base/DocManager.cpp index eed51d5ca01a..5ff0f3350459 100644 --- a/accessible/base/DocManager.cpp +++ b/accessible/base/DocManager.cpp @@ -8,6 +8,7 @@ #include "ApplicationAccessible.h" #include "ARIAMap.h" #include "DocAccessible-inl.h" +#include "DocAccessibleChild.h" #include "nsAccessibilityService.h" #include "RootAccessibleWrap.h" @@ -27,6 +28,8 @@ #include "nsServiceManagerUtils.h" #include "nsIWebProgress.h" #include "nsCoreUtils.h" +#include "nsXULAppAPI.h" +#include "mozilla/dom/ContentChild.h" using namespace mozilla; using namespace mozilla::a11y; @@ -418,6 +421,12 @@ DocManager::CreateDocOrRootAccessible(nsIDocument* aDocument) docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER, ApplicationAcc()); + if (IPCAccessibilityActive()) { + DocAccessibleChild* ipcDoc = new DocAccessibleChild(docAcc); + docAcc->SetIPCDoc(ipcDoc); + auto contentChild = dom::ContentChild::GetSingleton(); + contentChild->SendPDocAccessibleConstructor(ipcDoc, nullptr, 0); + } } else { parentDocAcc->BindChildDocument(docAcc); } diff --git a/accessible/base/DocManager.h b/accessible/base/DocManager.h index 13e80a602372..69193820eb67 100644 --- a/accessible/base/DocManager.h +++ b/accessible/base/DocManager.h @@ -17,6 +17,7 @@ namespace a11y { class Accessible; class DocAccessible; +class DocAccessibleParent; /** * Manage the document accessible life cycle. @@ -65,6 +66,25 @@ public: RemoveListeners(aDocument); } + /* + * Notification that a top level document in a content process has gone away. + */ + void RemoteDocShutdown(DocAccessibleParent* aDoc) + { + DebugOnly result = mRemoteDocuments.RemoveElement(aDoc); + MOZ_ASSERT(result, "Why didn't we find the document!"); + } + + /* + * Notify of a new top level document in a content process. + */ + void RemoteDocAdded(DocAccessibleParent* aDoc) + { + MOZ_ASSERT(!mRemoteDocuments.Contains(aDoc), + "How did we already have the doc!"); + mRemoteDocuments.AppendElement(aDoc); + } + #ifdef DEBUG bool IsProcessingRefreshDriverNotification() const; #endif @@ -144,6 +164,11 @@ private: #endif DocAccessibleHashtable mDocAccessibleCache; + + /* + * The list of remote top level documents. + */ + nsTArray mRemoteDocuments; }; /** diff --git a/accessible/base/EventQueue.cpp b/accessible/base/EventQueue.cpp index 6338785e49c9..31c4687ffdfb 100644 --- a/accessible/base/EventQueue.cpp +++ b/accessible/base/EventQueue.cpp @@ -8,6 +8,7 @@ #include "Accessible-inl.h" #include "nsEventShell.h" #include "DocAccessible.h" +#include "DocAccessibleChild.h" #include "nsAccessibilityService.h" #include "nsTextEquivUtils.h" #ifdef A11Y_LOG @@ -555,5 +556,15 @@ EventQueue::ProcessEventQueue() if (!mDocument) return; + + if (IPCAccessibilityActive()) { + DocAccessibleChild* ipcDoc = mDocument->IPCDoc(); + if (event->mEventType == nsIAccessibleEvent::EVENT_SHOW) + ipcDoc->ShowEvent(downcast_accEvent(event)); + else if (event->mEventType == nsIAccessibleEvent::EVENT_HIDE) + ipcDoc->SendHideEvent(reinterpret_cast(event->GetAccessible())); + else + ipcDoc->SendEvent(event->GetEventType()); + } } } diff --git a/accessible/base/NotificationController.cpp b/accessible/base/NotificationController.cpp index 82a591ab72c1..8dee12548eae 100644 --- a/accessible/base/NotificationController.cpp +++ b/accessible/base/NotificationController.cpp @@ -6,9 +6,11 @@ #include "NotificationController.h" #include "DocAccessible-inl.h" +#include "DocAccessibleChild.h" #include "TextLeafAccessible.h" #include "TextUpdater.h" +#include "mozilla/dom/ContentChild.h" #include "mozilla/dom/Element.h" #include "mozilla/Telemetry.h" @@ -217,8 +219,19 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime) if (ownerContent) { Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent); if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) { - if (mDocument->AppendChildDocument(childDoc)) + if (mDocument->AppendChildDocument(childDoc)) { + if (IPCAccessibilityActive()) { + DocAccessibleChild* ipcDoc = new DocAccessibleChild(childDoc); + childDoc->SetIPCDoc(ipcDoc); + auto contentChild = dom::ContentChild::GetSingleton(); + DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc(); + uint64_t id = reinterpret_cast(outerDocAcc->UniqueID()); + contentChild->SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, + id); + } + continue; + } outerDocAcc->RemoveChild(childDoc); } diff --git a/accessible/base/Platform.h b/accessible/base/Platform.h index fa56963fdbed..41954be440d8 100644 --- a/accessible/base/Platform.h +++ b/accessible/base/Platform.h @@ -7,6 +7,8 @@ namespace mozilla { namespace a11y { +class ProxyAccessible; + enum EPlatformDisabledState { ePlatformIsForceEnabled = -1, ePlatformIsEnabled = 0, @@ -47,6 +49,17 @@ void PlatformInit(); */ void PlatformShutdown(); +/** + * called when a new ProxyAccessible is created, so the platform may setup a + * wrapper for it, or take other action. + */ +void ProxyCreated(ProxyAccessible*); + +/** + * Called just before a ProxyAccessible is destroyed so its wrapper can be + * disposed of and other action taken. + */ +void ProxyDestroyed(ProxyAccessible*); } // namespace a11y } // namespace mozilla diff --git a/accessible/base/Role.h b/accessible/base/Role.h index 2cd378d64482..0d5746f3dc13 100644 --- a/accessible/base/Role.h +++ b/accessible/base/Role.h @@ -783,7 +783,9 @@ enum Role { /** * Represent a keyboard or keypad key (ARIA role "key"). */ - KEY = 129 + KEY = 129, + + LAST_ROLE = KEY }; } // namespace role diff --git a/accessible/base/moz.build b/accessible/base/moz.build index e31cf3e5c4c3..0dad363cc445 100644 --- a/accessible/base/moz.build +++ b/accessible/base/moz.build @@ -60,6 +60,7 @@ if CONFIG['A11Y_LOG']: LOCAL_INCLUDES += [ '/accessible/generic', '/accessible/html', + '/accessible/ipc', '/accessible/xpcom', '/accessible/xul', '/dom/xbl', @@ -93,3 +94,5 @@ FINAL_LIBRARY = 'xul' if CONFIG['MOZ_ENABLE_GTK']: CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] + +include('/ipc/chromium/chromium-config.mozbuild') diff --git a/accessible/base/nsAccessibilityService.h b/accessible/base/nsAccessibilityService.h index d466e6f457b0..fc616748d50c 100644 --- a/accessible/base/nsAccessibilityService.h +++ b/accessible/base/nsAccessibilityService.h @@ -238,6 +238,19 @@ GetAccService() return nsAccessibilityService::gAccessibilityService; } +/** + * Return true if we're in a content process and not B2G. + */ +inline bool +IPCAccessibilityActive() +{ +#ifdef MOZ_B2G + return false; +#else + return XRE_GetProcessType() != GeckoProcessType_Default; +#endif +} + /** * Map nsIAccessibleEvents constants to strings. Used by * nsIAccessibleRetrieval::getStringEventType() method. diff --git a/accessible/generic/DocAccessible.cpp b/accessible/generic/DocAccessible.cpp index bd63bf17ec95..c950f45b76f5 100644 --- a/accessible/generic/DocAccessible.cpp +++ b/accessible/generic/DocAccessible.cpp @@ -6,6 +6,7 @@ #include "Accessible-inl.h" #include "AccIterator.h" #include "DocAccessible-inl.h" +#include "DocAccessibleChild.h" #include "HTMLImageMapAccessible.h" #include "nsAccCache.h" #include "nsAccessiblePivot.h" @@ -83,7 +84,7 @@ DocAccessible:: mScrollPositionChangedTicks(0), mLoadState(eTreeConstructionPending), mDocFlags(0), mLoadEventType(0), mVirtualCursor(nullptr), - mPresShell(aPresShell) + mPresShell(aPresShell), mIPCDoc(nullptr) { mGenericTypes |= eDocument; mStateFlags |= eNotNodeMapEntry; @@ -473,6 +474,12 @@ DocAccessible::Shutdown() mChildDocuments.Clear(); + // XXX thinking about ordering? + if (IPCAccessibilityActive()) { + DocAccessibleChild::Send__delete__(mIPCDoc); + MOZ_ASSERT(!mIPCDoc); + } + if (mVirtualCursor) { mVirtualCursor->RemoveObserver(this); mVirtualCursor = nullptr; @@ -1446,6 +1453,13 @@ DocAccessible::DoInitialUpdate() nsRefPtr reorderEvent = new AccReorderEvent(Parent()); ParentDocument()->FireDelayedEvent(reorderEvent); } + + uint32_t childCount = ChildCount(); + for (uint32_t i = 0; i < childCount; i++) { + Accessible* child = GetChildAt(i); + nsRefPtr event = new AccShowEvent(child, child->GetContent()); + FireDelayedEvent(event); + } } void diff --git a/accessible/generic/DocAccessible.h b/accessible/generic/DocAccessible.h index 4d7858c1cfa0..42b655e69a55 100644 --- a/accessible/generic/DocAccessible.h +++ b/accessible/generic/DocAccessible.h @@ -33,6 +33,7 @@ namespace a11y { class DocManager; class NotificationController; +class DocAccessibleChild; class RelatedAccIterator; template class TNotification; @@ -519,6 +520,20 @@ protected: */ bool IsLoadEventTarget() const; + /** + * If this document is in a content process return the object responsible for + * communicating with the main process for it. + */ + DocAccessibleChild* IPCDoc() const { return mIPCDoc; } + + /* + * Set the object responsible for communicating with the main process on + * behalf of this document. + */ + void SetIPCDoc(DocAccessibleChild* aIPCDoc) { mIPCDoc = aIPCDoc; } + + friend class DocAccessibleChild; + /** * Used to fire scrolling end event after page scroll. * @@ -642,6 +657,9 @@ protected: private: nsIPresShell* mPresShell; + + // Exclusively owned by IPDL so don't manually delete it! + DocAccessibleChild* mIPCDoc; }; inline DocAccessible* diff --git a/accessible/generic/moz.build b/accessible/generic/moz.build index 236715bda01d..3b393c650fb6 100644 --- a/accessible/generic/moz.build +++ b/accessible/generic/moz.build @@ -28,6 +28,7 @@ UNIFIED_SOURCES += [ LOCAL_INCLUDES += [ '/accessible/base', '/accessible/html', + '/accessible/ipc', '/accessible/xpcom', '/accessible/xul', '/content/base/src', @@ -54,3 +55,5 @@ else: ] FINAL_LIBRARY = 'xul' + +include('/ipc/chromium/chromium-config.mozbuild') diff --git a/accessible/ipc/DocAccessibleChild.cpp b/accessible/ipc/DocAccessibleChild.cpp new file mode 100644 index 000000000000..5070b1fe9739 --- /dev/null +++ b/accessible/ipc/DocAccessibleChild.cpp @@ -0,0 +1,40 @@ +/* -*- 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 "DocAccessibleChild.h" + +#include "Accessible-inl.h" + +namespace mozilla { +namespace a11y { + +void +SerializeTree(Accessible* aRoot, nsTArray& aTree) +{ + uint64_t id = reinterpret_cast(aRoot->UniqueID()); + uint32_t role = aRoot->Role(); + uint32_t childCount = aRoot->ChildCount(); + + nsString name; + aRoot->Name(name); + aTree.AppendElement(AccessibleData(id, role, childCount, name)); + for (uint32_t i = 0; i < childCount; i++) + SerializeTree(aRoot->GetChildAt(i), aTree); +} + +void +DocAccessibleChild::ShowEvent(AccShowEvent* aShowEvent) +{ + Accessible* parent = aShowEvent->Parent(); + uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast(parent->UniqueID()); + uint32_t idxInParent = aShowEvent->GetAccessible()->IndexInParent(); + nsTArray shownTree; + ShowEventData data(parentID, idxInParent, shownTree); + SerializeTree(aShowEvent->GetAccessible(), data.NewTree()); + SendShowEvent(data); +} +} +} diff --git a/accessible/ipc/DocAccessibleChild.h b/accessible/ipc/DocAccessibleChild.h new file mode 100644 index 000000000000..50fdcc7cfd1f --- /dev/null +++ b/accessible/ipc/DocAccessibleChild.h @@ -0,0 +1,43 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_DocAccessibleChild_h +#define mozilla_a11y_DocAccessibleChild_h + +#include "mozilla/a11y/DocAccessible.h" +#include "mozilla/a11y/PDocAccessibleChild.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace a11y { +class AccShowEvent; + + /* + * These objects handle content side communication for an accessible document, + * and their lifetime is the same as the document they represent. + */ +class DocAccessibleChild : public PDocAccessibleChild +{ +public: + DocAccessibleChild(DocAccessible* aDoc) : + mDoc(aDoc) + { MOZ_COUNT_CTOR(DocAccessibleChild); } + ~DocAccessibleChild() + { + mDoc->SetIPCDoc(nullptr); + MOZ_COUNT_DTOR(DocAccessibleChild); + } + + void ShowEvent(AccShowEvent* aShowEvent); + +private: + DocAccessible* mDoc; +}; + +} +} + +#endif diff --git a/accessible/ipc/DocAccessibleParent.cpp b/accessible/ipc/DocAccessibleParent.cpp new file mode 100644 index 000000000000..3bbec6e54117 --- /dev/null +++ b/accessible/ipc/DocAccessibleParent.cpp @@ -0,0 +1,114 @@ +/* -*- 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 "nsAutoPtr.h" +#include "mozilla/a11y/Platform.h" + +namespace mozilla { +namespace a11y { + +bool +DocAccessibleParent::RecvShowEvent(const ShowEventData& aData) +{ + if (aData.NewTree().IsEmpty()) { + NS_ERROR("no children being added"); + return false; + } + + ProxyAccessible* parent = nullptr; + if (aData.ID()) { + ProxyEntry* e = mAccessibles.GetEntry(aData.ID()); + if (e) + parent = e->mProxy; + } else { + parent = this; + } + + // 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 false; + } + + uint32_t newChildIdx = aData.Idx(); + if (newChildIdx > parent->ChildrenCount()) { + NS_ERROR("invalid index to add child at"); + return false; + } + + uint32_t consumed = AddSubtree(parent, aData.NewTree(), 0, newChildIdx); + MOZ_ASSERT(consumed == aData.NewTree().Length()); + for (uint32_t i = 0; i < consumed; i++) { + uint64_t id = aData.NewTree()[i].ID(); + MOZ_ASSERT(mAccessibles.GetEntry(id)); + } + + return consumed; +} + +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; + } + + auto role = static_cast(newChild.Role()); + ProxyAccessible* newProxy = + new ProxyAccessible(newChild.ID(), aParent, this, role, newChild.Name()); + aParent->AddChildAt(aIdxInParent, newProxy); + mAccessibles.PutEntry(newChild.ID())->mProxy = newProxy; + ProxyCreated(newProxy); + + 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) +{ + 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(); + parent->RemoveChild(root); + root->Shutdown(); + + return true; +} +} +} diff --git a/accessible/ipc/DocAccessibleParent.h b/accessible/ipc/DocAccessibleParent.h new file mode 100644 index 000000000000..b92a5e0ac127 --- /dev/null +++ b/accessible/ipc/DocAccessibleParent.h @@ -0,0 +1,141 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_DocAccessibleParent_h +#define mozilla_a11y_DocAccessibleParent_h + +#include "nsAccessibilityService.h" +#include "ProxyAccessible.h" +#include "mozilla/a11y/PDocAccessibleParent.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsISupportsImpl.h" + +namespace mozilla { +namespace a11y { + +/* + * These objects live in the main process and comunicate with and represent + * an accessible document in a content process. + */ +class DocAccessibleParent : public ProxyAccessible, + public PDocAccessibleParent +{ +public: + DocAccessibleParent() : + mParentDoc(nullptr) + { MOZ_COUNT_CTOR_INHERITED(DocAccessibleParent, ProxyAccessible); } + ~DocAccessibleParent() + { + MOZ_COUNT_DTOR_INHERITED(DocAccessibleParent, ProxyAccessible); + MOZ_ASSERT(mChildDocs.Length() == 0); + MOZ_ASSERT(!mParentDoc); + } + + /* + * Called when a message from a document in a child process notifies the main + * process it is firing an event. + */ + virtual bool RecvEvent(const uint32_t& aType) MOZ_OVERRIDE + { + return true; + } + + virtual bool RecvShowEvent(const ShowEventData& aData) MOZ_OVERRIDE; + virtual bool RecvHideEvent(const uint64_t& aRootID) MOZ_OVERRIDE; + + virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE + { + MOZ_ASSERT(mChildDocs.IsEmpty(), + "why wheren't the child docs destroyed already?"); + mParentDoc ? mParentDoc->RemoveChildDoc(this) + : GetAccService()->RemoteDocShutdown(this); + } + + /* + * Return the main processes representation of the parent document (if any) + * of the document this object represents. + */ + DocAccessibleParent* Parent() const { return mParentDoc; } + + /* + * Called when a document in a content process notifies the main process of a + * new child document. + */ + bool AddChildDoc(DocAccessibleParent* aChildDoc, uint64_t aParentID) + { + ProxyAccessible* outerDoc = mAccessibles.GetEntry(aParentID)->mProxy; + if (!outerDoc) + return false; + + aChildDoc->mParent = outerDoc; + outerDoc->SetChildDoc(aChildDoc); + mChildDocs.AppendElement(aChildDoc); + aChildDoc->mParentDoc = this; + return true; + } + + /* + * Called when the document in the content process this object represents + * notifies the main process a child document has been removed. + */ + void RemoveChildDoc(DocAccessibleParent* aChildDoc) + { + aChildDoc->mParent->SetChildDoc(nullptr); + mChildDocs.RemoveElement(aChildDoc); + aChildDoc->mParentDoc = nullptr; + MOZ_ASSERT(aChildDoc->mChildDocs.Length() == 0); + } + + void RemoveAccessible(ProxyAccessible* aAccessible) + { + MOZ_ASSERT(mAccessibles.GetEntry(aAccessible->ID())); + mAccessibles.RemoveEntry(aAccessible->ID()); + } + +private: + + class ProxyEntry : public PLDHashEntryHdr + { + public: + ProxyEntry(const void*) : mProxy(nullptr) {} + ProxyEntry(ProxyEntry&& aOther) : + mProxy(aOther.mProxy) { aOther.mProxy = nullptr; } + ~ProxyEntry() { delete mProxy; } + + typedef uint64_t KeyType; + typedef const void* KeyTypePointer; + + bool KeyEquals(const void* aKey) const + { return mProxy->ID() == (uint64_t)aKey; } + + static const void* KeyToPointer(uint64_t aKey) { return (void*)aKey; } + + static PLDHashNumber HashKey(const void* aKey) { return (uint64_t)aKey; } + + enum { ALLOW_MEMMOVE = true }; + + ProxyAccessible* mProxy; + }; + + uint32_t AddSubtree(ProxyAccessible* aParent, + const nsTArray& aNewTree, uint32_t aIdx, + uint32_t aIdxInParent); + + nsTArray mChildDocs; + DocAccessibleParent* mParentDoc; + + /* + * Conceptually this is a map from IDs to proxies, but we store the ID in the + * proxy object so we can't use a real map. + */ + nsTHashtable mAccessibles; +}; + +} +} + +#endif diff --git a/accessible/ipc/PDocAccessible.ipdl b/accessible/ipc/PDocAccessible.ipdl new file mode 100644 index 000000000000..b3328b31b680 --- /dev/null +++ b/accessible/ipc/PDocAccessible.ipdl @@ -0,0 +1,44 @@ +/* -*- 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 protocol PContent; + +namespace mozilla { +namespace a11y { + +struct AccessibleData +{ + uint64_t ID; + uint32_t Role; + uint32_t ChildrenCount; + nsString Name; +}; + +struct ShowEventData +{ + uint64_t ID; + uint32_t Idx; + AccessibleData[] NewTree; +}; + +protocol PDocAccessible +{ + manager PContent; + +parent: + __delete__(); + + /* + * Notify the parent process the document in the child process is firing an + * event. + */ + Event(uint32_t type); + ShowEvent(ShowEventData data); + HideEvent(uint64_t aRootID); +}; + +} +} diff --git a/accessible/ipc/ProxyAccessible.cpp b/accessible/ipc/ProxyAccessible.cpp new file mode 100644 index 000000000000..5af1aa41d8fb --- /dev/null +++ b/accessible/ipc/ProxyAccessible.cpp @@ -0,0 +1,42 @@ +/* -*- 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 "ProxyAccessible.h" +#include "DocAccessibleParent.h" +#include "mozilla/a11y/Platform.h" + +namespace mozilla { +namespace a11y { + +void +ProxyAccessible::Shutdown() +{ + MOZ_ASSERT(!mOuterDoc); + + uint32_t childCount = mChildren.Length(); + for (uint32_t idx = 0; idx < childCount; idx++) + mChildren[idx]->Shutdown(); + + mChildren.Clear(); + ProxyDestroyed(this); + mDoc->RemoveAccessible(this); +} + +void +ProxyAccessible::SetChildDoc(DocAccessibleParent* aParent) +{ + if (aParent) { + MOZ_ASSERT(mChildren.IsEmpty()); + mChildren.AppendElement(aParent); + mOuterDoc = true; + } else { + MOZ_ASSERT(mChildren.Length() == 1); + mChildren.Clear(); + mOuterDoc = false; + } +} +} +} diff --git a/accessible/ipc/ProxyAccessible.h b/accessible/ipc/ProxyAccessible.h new file mode 100644 index 000000000000..126e9bfc1cc5 --- /dev/null +++ b/accessible/ipc/ProxyAccessible.h @@ -0,0 +1,88 @@ +/* -*- 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/. */ + +#ifndef mozilla_a11y_ProxyAccessible_h +#define mozilla_a11y_ProxyAccessible_h + +#include "mozilla/a11y/Role.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace a11y { + +class DocAccessibleParent; + +class ProxyAccessible +{ +public: + + ProxyAccessible(uint64_t aID, ProxyAccessible* aParent, + DocAccessibleParent* aDoc, role aRole, + const nsString& aName) : + mParent(aParent), mDoc(aDoc), mID(aID), mRole(aRole), mOuterDoc(false), mName(aName) + { + MOZ_COUNT_CTOR(ProxyAccessible); + } + ~ProxyAccessible() { MOZ_COUNT_DTOR(ProxyAccessible); } + + void AddChildAt(uint32_t aIdx, ProxyAccessible* aChild) + { mChildren.InsertElementAt(aIdx, aChild); } + + uint32_t ChildrenCount() const { return mChildren.Length(); } + + void Shutdown(); + + void SetChildDoc(DocAccessibleParent*); + + /** + * Remove The given child. + */ + void RemoveChild(ProxyAccessible* aChild) + { mChildren.RemoveElement(aChild); } + + /** + * Return the proxy for the parent of the wrapped accessible. + */ + ProxyAccessible* Parent() const { return mParent; } + + /** + * Get the role of the accessible we're proxying. + */ + role Role() const { return mRole; } + + /** + * Allow the platform to store a pointers worth of data on us. + */ + uintptr_t GetWrapper() const { return mWrapper; } + void SetWrapper(uintptr_t aWrapper) { mWrapper = aWrapper; } + + /* + * Return the ID of the accessible being proxied. + */ + uint64_t ID() const { return mID; } + +protected: + ProxyAccessible() : + mParent(nullptr), mDoc(nullptr) { MOZ_COUNT_CTOR(ProxyAccessible); } + +protected: + ProxyAccessible* mParent; + +private: + nsTArray mChildren; + DocAccessibleParent* mDoc; + uintptr_t mWrapper; + uint64_t mID; + role mRole : 31; + bool mOuterDoc : 1; + nsString mName; +}; + +} +} + +#endif diff --git a/accessible/ipc/moz.build b/accessible/ipc/moz.build new file mode 100644 index 000000000000..340e79bf2984 --- /dev/null +++ b/accessible/ipc/moz.build @@ -0,0 +1,31 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +IPDL_SOURCES += ['PDocAccessible.ipdl'] + +# with --disable-accessibility we need to compile PDocAccessible.ipdl, but not +# the C++. +if CONFIG['ACCESSIBILITY']: + EXPORTS.mozilla.a11y += [ + 'DocAccessibleChild.h', + 'DocAccessibleParent.h', + 'ProxyAccessible.h' + ] + + SOURCES += [ + 'DocAccessibleChild.cpp', + 'DocAccessibleParent.cpp', + 'ProxyAccessible.cpp' + ] + + LOCAL_INCLUDES += [ + '../base', + '../generic', + ] + + FINAL_LIBRARY = 'xul' + +include('/ipc/chromium/chromium-config.mozbuild') diff --git a/accessible/mac/Platform.mm b/accessible/mac/Platform.mm index 98bbac02a9b9..4d82f2c5e836 100644 --- a/accessible/mac/Platform.mm +++ b/accessible/mac/Platform.mm @@ -33,6 +33,15 @@ PlatformShutdown() { } +void +ProxyCreated(ProxyAccessible*) +{ +} + +void +ProxyDestroyed(ProxyAccessible*) +{ +} } } diff --git a/accessible/moz.build b/accessible/moz.build index 44f345780018..993d2202937f 100644 --- a/accessible/moz.build +++ b/accessible/moz.build @@ -15,7 +15,7 @@ elif toolkit == 'cocoa': else: DIRS += ['other'] -DIRS += ['base', 'generic', 'html', 'interfaces', 'jsat', 'xpcom'] +DIRS += ['base', 'generic', 'html', 'interfaces', 'ipc', 'jsat', 'xpcom'] if CONFIG['MOZ_XUL']: DIRS += ['xul'] diff --git a/accessible/other/Platform.cpp b/accessible/other/Platform.cpp index 87c9fc6071f8..768f687c738c 100644 --- a/accessible/other/Platform.cpp +++ b/accessible/other/Platform.cpp @@ -18,3 +18,13 @@ void a11y::PlatformShutdown() { } + +void +a11y::ProxyCreated(ProxyAccessible*) +{ +} + +void +a11y::ProxyDestroyed(ProxyAccessible*) +{ +} diff --git a/accessible/windows/msaa/Platform.cpp b/accessible/windows/msaa/Platform.cpp index 263eb1a4d130..0ed1f5ffc073 100644 --- a/accessible/windows/msaa/Platform.cpp +++ b/accessible/windows/msaa/Platform.cpp @@ -34,3 +34,12 @@ a11y::PlatformShutdown() nsWinUtils::ShutdownWindowEmulation(); } +void +a11y::ProxyCreated(ProxyAccessible*) +{ +} + +void +a11y::ProxyDestroyed(ProxyAccessible*) +{ +} diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index bfd13920ad45..d5132dcb5aec 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -19,6 +19,9 @@ #include "TabChild.h" #include "mozilla/Attributes.h" +#ifdef ACCESSIBILITY +#include "mozilla/a11y/DocAccessibleChild.h" +#endif #include "mozilla/Preferences.h" #include "mozilla/dom/ContentBridgeChild.h" #include "mozilla/dom/ContentBridgeParent.h" @@ -731,6 +734,22 @@ ContentChild::InitXPCOM() InitOnContentProcessCreated(); } +a11y::PDocAccessibleChild* +ContentChild::AllocPDocAccessibleChild(PDocAccessibleChild*, const uint64_t&) +{ + MOZ_ASSERT(false, "should never call this!"); + return nullptr; +} + +bool +ContentChild::DeallocPDocAccessibleChild(a11y::PDocAccessibleChild* aChild) +{ +#ifdef ACCESSIBILITY + delete static_cast(aChild); +#endif + return true; +} + PMemoryReportRequestChild* ContentChild::AllocPMemoryReportRequestChild(const uint32_t& aGeneration, const bool &aAnonymize, diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index dcf3dc638fed..a8bdf7a1303b 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -382,6 +382,8 @@ public: const uint64_t& aID, const bool& aIsForApp, const bool& aIsForBrowser) MOZ_OVERRIDE; + virtual PDocAccessibleChild* AllocPDocAccessibleChild(PDocAccessibleChild*, const uint64_t&) MOZ_OVERRIDE; + virtual bool DeallocPDocAccessibleChild(PDocAccessibleChild*) MOZ_OVERRIDE; void GetAvailableDictionaries(InfallibleTArray& aDictionaries); diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index d9bdcf1713c3..4f87332b34bf 100755 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -30,6 +30,10 @@ #include "CrashReporterParent.h" #include "IHistory.h" #include "mozIApplication.h" +#ifdef ACCESSIBILITY +#include "mozilla/a11y/DocAccessibleParent.h" +#include "nsAccessibilityService.h" +#endif #include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/DataStoreService.h" #include "mozilla/dom/DOMStorageIPC.h" @@ -2706,6 +2710,42 @@ ContentParent::Observe(nsISupports* aSubject, return NS_OK; } + a11y::PDocAccessibleParent* +ContentParent::AllocPDocAccessibleParent(PDocAccessibleParent* aParent, const uint64_t&) +{ +#ifdef ACCESSIBILITY + return new a11y::DocAccessibleParent(); +#else + return nullptr; +#endif +} + +bool +ContentParent::DeallocPDocAccessibleParent(PDocAccessibleParent* aParent) +{ +#ifdef ACCESSIBILITY + delete static_cast(aParent); +#endif + return true; +} + +bool +ContentParent::RecvPDocAccessibleConstructor(PDocAccessibleParent* aDoc, PDocAccessibleParent* aParentDoc, const uint64_t& aParentID) +{ +#ifdef ACCESSIBILITY + auto doc = static_cast(aDoc); + if (aParentDoc) { + MOZ_ASSERT(aParentID); + auto parentDoc = static_cast(aParentDoc); + return parentDoc->AddChildDoc(doc, aParentID); + } else { + MOZ_ASSERT(!aParentID); + GetAccService()->RemoteDocAdded(doc); + } +#endif + return true; +} + PCompositorParent* ContentParent::AllocPCompositorParent(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index c38705e98149..b6bfefdc2de5 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -679,6 +679,11 @@ private: int32_t* aSliceRefCnt, bool* aResult) MOZ_OVERRIDE; + virtual PDocAccessibleParent* AllocPDocAccessibleParent(PDocAccessibleParent*, const uint64_t&) MOZ_OVERRIDE; + virtual bool DeallocPDocAccessibleParent(PDocAccessibleParent*) MOZ_OVERRIDE; + virtual bool RecvPDocAccessibleConstructor(PDocAccessibleParent* aDoc, + PDocAccessibleParent* aParentDoc, const uint64_t& aParentID) MOZ_OVERRIDE; + // If you add strong pointers to cycle collected objects here, be sure to // release these objects in ShutDownProcess. See the comment there for more // details. diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index c8d3defa0767..cfb59d0a134b 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -14,6 +14,7 @@ include protocol PCompositor; include protocol PContentBridge; include protocol PCycleCollectWithLogs; include protocol PCrashReporter; +include protocol PDocAccessible; include protocol PExternalHelperApp; include protocol PDeviceStorageRequest; include protocol PFileDescriptorSet; @@ -333,6 +334,7 @@ prio(normal upto high) intr protocol PContent manages PCellBroadcast; manages PCrashReporter; manages PCycleCollectWithLogs; + manages PDocAccessible; manages PDeviceStorageRequest; manages PFileSystemRequest; manages PExternalHelperApp; @@ -491,6 +493,14 @@ child: OnAppThemeChanged(); parent: + /** + * Tell the parent process a new accessible document has been created. + * aParentDoc is the accessible document it was created in if any, and + * aParentAcc is the id of the accessible in that document the new document + * is a child of. + */ + PDocAccessible(nullable PDocAccessible aParentDoc, uint64_t aParentAcc); + /** * Tell the content process some attributes of itself. This is * among the first information queried by content processes after diff --git a/toolkit/toolkit.mozbuild b/toolkit/toolkit.mozbuild index 79a078b42da7..2dcea0d5122c 100644 --- a/toolkit/toolkit.mozbuild +++ b/toolkit/toolkit.mozbuild @@ -118,6 +118,8 @@ if CONFIG['MOZ_UNIVERSALCHARDET']: if CONFIG['ACCESSIBILITY']: DIRS += ['/accessible'] +else: + DIRS += ['/accessible/ipc'] # toolkit