bug 982842 - initial a11y ipc impl r=davidb, bent
authorTrevor Saunders <trev.saunders@gmail.com>
Fri, 07 Mar 2014 16:35:19 -0500
changeset 207815 a11adf1705ec702e75b493060fa7a5e18b15145a
parent 207814 3a2cf29a629f6631628a21780a291b2895a663ef
child 207816 7cb6aaa1fd884ac40b4363aac641c8c7671f292a
push id11043
push usercbook@mozilla.com
push dateTue, 30 Sep 2014 13:42:50 +0000
treeherderb2g-inbound@f4e8988b3881 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdavidb, bent
bugs982842
milestone35.0a1
bug 982842 - initial a11y ipc impl r=davidb, bent
accessible/atk/AccessibleWrap.cpp
accessible/atk/AccessibleWrap.h
accessible/atk/moz.build
accessible/atk/nsMai.h
accessible/base/AccEvent.h
accessible/base/DocManager.cpp
accessible/base/DocManager.h
accessible/base/EventQueue.cpp
accessible/base/NotificationController.cpp
accessible/base/Platform.h
accessible/base/Role.h
accessible/base/moz.build
accessible/generic/DocAccessible.cpp
accessible/generic/DocAccessible.h
accessible/generic/moz.build
accessible/ipc/DocAccessibleChild.cpp
accessible/ipc/DocAccessibleChild.h
accessible/ipc/DocAccessibleParent.cpp
accessible/ipc/DocAccessibleParent.h
accessible/ipc/PDocAccessible.ipdl
accessible/ipc/ProxyAccessible.cpp
accessible/ipc/ProxyAccessible.h
accessible/ipc/moz.build
accessible/mac/Platform.mm
accessible/moz.build
accessible/other/Platform.cpp
accessible/windows/msaa/Platform.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -7,24 +7,26 @@
 #include "AccessibleWrap.h"
 
 #include "Accessible-inl.h"
 #include "ApplicationAccessibleWrap.h"
 #include "InterfaceInitFuncs.h"
 #include "nsAccUtils.h"
 #include "nsIAccessibleRelation.h"
 #include "nsIAccessibleTable.h"
+#include "ProxyAccessible.h"
 #include "RootAccessible.h"
 #include "nsIAccessibleValue.h"
 #include "nsMai.h"
 #include "nsMaiHyperlink.h"
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "prprf.h"
 #include "nsStateMap.h"
+#include "mozilla/a11y/Platform.h"
 #include "Relation.h"
 #include "RootAccessible.h"
 #include "States.h"
 #include "nsISimpleEnumerator.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "nsXPCOMStrings.h"
 #include "nsComponentManagerUtils.h"
@@ -128,19 +130,23 @@ static const GInterfaceInfo atk_if_infos
  */
 struct MaiAtkObject
 {
   AtkObject parent;
   /*
    * 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;
 };
 
 static guint mai_atk_object_signals [LAST_SIGNAL] = { 0, };
 
 static void MaybeFireNameChange(AtkObject* aAtkObj, const nsString& aNewName);
@@ -243,17 +249,17 @@ AccessibleWrap::~AccessibleWrap()
     NS_ASSERTION(!mAtkObject, "ShutdownAtkObject() is not called");
 }
 
 void
 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);
         mAtkObject = nullptr;
     }
 }
 
 void
@@ -577,26 +583,25 @@ initializeCB(AtkObject *aAtkObj, gpointe
     /* AtkObjectClass has not a "initialize" function now,
      * maybe it has later
      */
 
     if (ATK_OBJECT_CLASS(parent_class)->initialize)
         ATK_OBJECT_CLASS(parent_class)->initialize(aAtkObj, aData);
 
   /* initialize object */
-  MAI_ATK_OBJECT(aAtkObj)->accWrap =
-    static_cast<AccessibleWrap*>(aData);
+  MAI_ATK_OBJECT(aAtkObj)->accWrap = reinterpret_cast<uintptr_t>(aData);
 }
 
 void
 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
     if (G_OBJECT_CLASS (parent_class)->finalize)
         G_OBJECT_CLASS (parent_class)->finalize(aObj);
 }
 
 const gchar*
@@ -658,35 +663,43 @@ getDescriptionCB(AtkObject *aAtkObj)
                                    NS_ConvertUTF16toUTF8(uniDesc).get());
 
     return aAtkObj->description;
 }
 
 AtkRole
 getRoleCB(AtkObject *aAtkObj)
 {
-  AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
-  if (!accWrap)
-    return ATK_ROLE_INVALID;
+  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");
+    NS_ASSERTION(nsAccUtils::IsTextInterfaceSupportCorrect(accWrap),
+                 "Does not support nsIAccessibleText when it should");
 #endif
 
-  if (aAtkObj->role != ATK_ROLE_INVALID)
-    return aAtkObj->role;
+    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.");
   };
 
 #undef ROLE
 
   if (aAtkObj->role == ATK_ROLE_LIST_BOX && !IsAtkVersionAtLeast(2, 1))
@@ -941,31 +954,80 @@ refRelationSetCB(AtkObject *aAtkObj)
 }
 
 // Check if aAtkObj is a valid MaiAtkObject, and return the AccessibleWrap
 // for it.
 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<AccessibleWrap*>(MAI_ATK_OBJECT(aAtkObj)->accWrap);
 
   // Check if the accessible was deconstructed.
   if (!accWrap)
     return nullptr;
 
   NS_ENSURE_TRUE(accWrap->GetAtkObject() == aAtkObj, nullptr);
 
   AccessibleWrap* appAccWrap = ApplicationAcc();
   if (appAccWrap != accWrap && !accWrap->IsValidObject())
     return nullptr;
 
   return accWrap;
 }
 
+ProxyAccessible*
+GetProxy(AtkObject* aObj)
+{
+  if (!aObj || !(MAI_ATK_OBJECT(aObj)->accWrap & IS_PROXY))
+    return nullptr;
+
+  return reinterpret_cast<ProxyAccessible*>(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<AtkObject *>
+    (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<uintptr_t>(obj) | IS_PROXY);
+}
+
+void
+a11y::ProxyDestroyed(ProxyAccessible* aProxy)
+{
+  auto obj = reinterpret_cast<MaiAtkObject*>(aProxy->GetWrapper() & ~IS_PROXY);
+  obj->accWrap = 0;
+  g_object_unref(obj);
+  aProxy->SetWrapper(0);
+}
+
 nsresult
 AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
 {
   nsresult rv = Accessible::HandleAccEvent(aEvent);
   NS_ENSURE_SUCCESS(rv, rv);
 
     Accessible* accessible = aEvent->GetAccessible();
     NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
--- a/accessible/atk/AccessibleWrap.h
+++ b/accessible/atk/AccessibleWrap.h
@@ -92,15 +92,15 @@ private:
   enum EAvailableAtkSignals {
     eUnknown,
     eHaveNewAtkTextSignals,
     eNoNewAtkSignals
   };
 
   static EAvailableAtkSignals gAvailableAtkSignals;
 
-  uint16_t CreateMaiInterfaces(void);
+  uint16_t CreateMaiInterfaces();
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif /* __NS_ACCESSIBLE_WRAP_H__ */
--- a/accessible/atk/moz.build
+++ b/accessible/atk/moz.build
@@ -30,16 +30,17 @@ SOURCES += [
     'RootAccessibleWrap.cpp',
     'UtilInterface.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/accessible/base',
     '/accessible/generic',
     '/accessible/html',
+    '/accessible/ipc',
     '/accessible/xpcom',
     '/accessible/xul',
     '/other-licenses/atk-1.0',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['MOZ_ENABLE_GTK']:
--- a/accessible/atk/nsMai.h
+++ b/accessible/atk/nsMai.h
@@ -8,32 +8,39 @@
 #define __NS_MAI_H__
 
 #include <atk/atk.h>
 #include <glib.h>
 #include <glib-object.h>
 
 #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))
 #define MAI_ATK_OBJECT_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), \
                                          MAI_TYPE_ATK_OBJECT, \
                                          MaiAtkObjectClass))
 #define IS_MAI_OBJECT(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
                                          MAI_TYPE_ATK_OBJECT))
 #define IS_MAI_OBJECT_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), \
                                          MAI_TYPE_ATK_OBJECT))
 #define MAI_ATK_OBJECT_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), \
                                          MAI_TYPE_ATK_OBJECT, \
                                          MaiAtkObjectClass))
 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;
 
 /**
  * Return true if the loaded version of libatk-1.0.so is at least
  * aMajor.aMinor.0.
  */
 static inline bool
--- a/accessible/base/AccEvent.h
+++ b/accessible/base/AccEvent.h
@@ -227,16 +227,18 @@ public:
   {
     return AccEvent::GetEventGroups() | (1U << eMutationEvent);
   }
 
   // MutationEvent
   bool IsShow() const { return mEventType == nsIAccessibleEvent::EVENT_SHOW; }
   bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; }
 
+  Accessible* Parent() const { return mParent; }
+
 protected:
   nsCOMPtr<nsINode> mNode;
   nsRefPtr<Accessible> mParent;
   nsRefPtr<AccTextChangeEvent> mTextChangeEvent;
 
   friend class EventQueue;
 };
 
--- a/accessible/base/DocManager.cpp
+++ b/accessible/base/DocManager.cpp
@@ -3,16 +3,17 @@
  * 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 "DocManager.h"
 
 #include "ApplicationAccessible.h"
 #include "ARIAMap.h"
 #include "DocAccessible-inl.h"
+#include "DocAccessibleChild.h"
 #include "nsAccessibilityService.h"
 #include "RootAccessibleWrap.h"
 
 #ifdef A11Y_LOG
 #include "Logging.h"
 #endif
 
 #include "mozilla/EventListenerManager.h"
@@ -22,16 +23,18 @@
 #include "nsIChannel.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMWindow.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIWebNavigation.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIWebProgress.h"
 #include "nsCoreUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/dom/ContentChild.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 using namespace mozilla::dom;
 
 ////////////////////////////////////////////////////////////////////////////////
 // DocManager
 ////////////////////////////////////////////////////////////////////////////////
@@ -413,16 +416,22 @@ DocManager::CreateDocOrRootAccessible(ns
     // the tree. The reorder event is delivered after the document tree is
     // constructed because event processing and tree construction are done by
     // the same document.
     // Note: don't use AccReorderEvent to avoid coalsecense and special reorder
     // events processing.
     docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER,
                              ApplicationAcc());
 
+    if (XRE_GetProcessType() != GeckoProcessType_Default) {
+      DocAccessibleChild* ipcDoc = new DocAccessibleChild(docAcc);
+      docAcc->SetIPCDoc(ipcDoc);
+    auto contentChild = dom::ContentChild::GetSingleton();
+    contentChild->SendPDocAccessibleConstructor(ipcDoc, nullptr, 0);
+    }
   } else {
     parentDocAcc->BindChildDocument(docAcc);
   }
 
 #ifdef A11Y_LOG
   if (logging::IsEnabled(logging::eDocCreate)) {
     logging::DocCreate("document creation finished", aDocument);
     logging::Stack();
--- a/accessible/base/DocManager.h
+++ b/accessible/base/DocManager.h
@@ -12,16 +12,17 @@
 #include "nsWeakReference.h"
 #include "nsIPresShell.h"
 
 namespace mozilla {
 namespace a11y {
 
 class Accessible;
 class DocAccessible;
+class DocAccessibleParent;
 
 /**
  * Manage the document accessible life cycle.
  */
 class DocManager : public nsIWebProgressListener,
                    public nsIDOMEventListener,
                    public nsSupportsWeakReference
 {
@@ -60,16 +61,35 @@ public:
    * Called by document accessible when it gets shutdown.
    */
   inline void NotifyOfDocumentShutdown(nsIDocument* aDocument)
   {
     mDocAccessibleCache.Remove(aDocument);
     RemoveListeners(aDocument);
   }
 
+  /*
+   * Notification that a top level document in a content process has gone away.
+   */
+  void RemoteDocShutdown(DocAccessibleParent* aDoc)
+  {
+    DebugOnly<bool> 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
 
 protected:
   DocManager();
   virtual ~DocManager() { }
 
@@ -139,16 +159,21 @@ private:
 
 #ifdef DEBUG
   static PLDHashOperator
     SearchIfDocIsRefreshing(const nsIDocument* aKey,
                             DocAccessible* aDocAccessible, void* aUserArg);
 #endif
 
   DocAccessibleHashtable mDocAccessibleCache;
+
+  /*
+   * The list of remote top level documents.
+   */
+  nsTArray<DocAccessibleParent*> mRemoteDocuments;
 };
 
 /**
  * Return the existing document accessible for the document if any.
  * Note this returns the doc accessible for the primary pres shell if there is
  * more than one.
  */
 inline DocAccessible*
--- a/accessible/base/EventQueue.cpp
+++ b/accessible/base/EventQueue.cpp
@@ -3,16 +3,17 @@
  * 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 "EventQueue.h"
 
 #include "Accessible-inl.h"
 #include "nsEventShell.h"
 #include "DocAccessible.h"
+#include "DocAccessibleChild.h"
 #include "nsAccessibilityService.h"
 #include "nsTextEquivUtils.h"
 #ifdef A11Y_LOG
 #include "Logging.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::a11y;
@@ -550,10 +551,20 @@ EventQueue::ProcessEventQueue()
       }
     }
 
     if (event->mEventType == nsIAccessibleEvent::EVENT_HIDE)
       mDocument->ShutdownChildrenInSubtree(event->mAccessible);
 
     if (!mDocument)
       return;
+
+    if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    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<uintptr_t>(event->GetAccessible()));
+      else
+        ipcDoc->SendEvent(event->GetEventType());
+    }
   }
 }
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -1,19 +1,21 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "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"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // NotificationCollector
@@ -212,18 +214,29 @@ NotificationController::WillRefresh(mozi
     if (childDoc->IsDefunct())
       continue;
 
     nsIContent* ownerContent = mDocument->DocumentNode()->
       FindContentForSubDocument(childDoc->DocumentNode());
     if (ownerContent) {
       Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
       if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
-        if (mDocument->AppendChildDocument(childDoc))
+        if (mDocument->AppendChildDocument(childDoc)) {
+          if (XRE_GetProcessType() != GeckoProcessType_Default) {
+            DocAccessibleChild* ipcDoc = new DocAccessibleChild(childDoc);
+            childDoc->SetIPCDoc(ipcDoc);
+            auto contentChild = dom::ContentChild::GetSingleton();
+            DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
+            uint64_t id = reinterpret_cast<uintptr_t>(outerDocAcc->UniqueID());
+            contentChild->SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc,
+                                                        id);
+          }
+
           continue;
+        }
 
         outerDocAcc->RemoveChild(childDoc);
       }
 
       // Failed to bind the child document, destroy it.
       childDoc->Shutdown();
     }
   }
--- a/accessible/base/Platform.h
+++ b/accessible/base/Platform.h
@@ -2,16 +2,18 @@
 /* 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/. */
 
 namespace mozilla {
 namespace a11y {
 
+class ProxyAccessible;
+
 enum EPlatformDisabledState {
   ePlatformIsForceEnabled = -1,
   ePlatformIsEnabled = 0,
   ePlatformIsDisabled = 1
 };
 
 /**
  * Return the platform disabled state.
@@ -42,11 +44,22 @@ bool ShouldA11yBeEnabled();
 void PlatformInit();
 
 /**
  * Shutdown platform accessibility.
  * Note this is called before internal accessibility support is shutdown.
  */
 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
 
--- a/accessible/base/Role.h
+++ b/accessible/base/Role.h
@@ -778,17 +778,19 @@ enum Role {
   /**
    * Represent a definition in a definition list (dd in HTML)
    */
   DEFINITION = 128,
 
   /**
    * Represent a keyboard or keypad key (ARIA role "key").
    */
-  KEY = 129
+  KEY = 129,
+
+  LAST_ROLE = KEY
 };
 
 } // namespace role
 
 typedef enum mozilla::a11y::roles::Role role;
 
 } // namespace a11y
 } // namespace mozilla
--- a/accessible/base/moz.build
+++ b/accessible/base/moz.build
@@ -55,16 +55,17 @@ UNIFIED_SOURCES += [
 if CONFIG['A11Y_LOG']:
     UNIFIED_SOURCES += [
         'Logging.cpp',
     ]
 
 LOCAL_INCLUDES += [
     '/accessible/generic',
     '/accessible/html',
+    '/accessible/ipc',
     '/accessible/xpcom',
     '/accessible/xul',
     '/dom/xbl',
     '/ipc/chromium/src',
     '/layout/generic',
     '/layout/style',
     '/layout/svg',
     '/layout/xul',
@@ -88,8 +89,10 @@ else:
     LOCAL_INCLUDES += [
         '/accessible/other',
     ]
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['MOZ_ENABLE_GTK']:
     CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
+
+include('/ipc/chromium/chromium-config.mozbuild')
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "Accessible-inl.h"
 #include "AccIterator.h"
 #include "DocAccessible-inl.h"
+#include "DocAccessibleChild.h"
 #include "HTMLImageMapAccessible.h"
 #include "nsAccCache.h"
 #include "nsAccessiblePivot.h"
 #include "nsAccUtils.h"
 #include "nsEventShell.h"
 #include "nsTextEquivUtils.h"
 #include "Role.h"
 #include "RootAccessible.h"
@@ -78,17 +79,17 @@ DocAccessible::
   HyperTextAccessibleWrap(aRootContent, this), xpcAccessibleDocument(),
   // XXX aaronl should we use an algorithm for the initial cache size?
   mAccessibleCache(kDefaultCacheLength),
   mNodeToAccessibleMap(kDefaultCacheLength),
   mDocumentNode(aDocument),
   mScrollPositionChangedTicks(0),
   mLoadState(eTreeConstructionPending), mDocFlags(0), mLoadEventType(0),
   mVirtualCursor(nullptr),
-  mPresShell(aPresShell)
+  mPresShell(aPresShell), mIPCDoc(nullptr)
 {
   mGenericTypes |= eDocument;
   mStateFlags |= eNotNodeMapEntry;
 
   MOZ_ASSERT(mPresShell, "should have been given a pres shell");
   mPresShell->SetDocAccessible(this);
 
   // If this is a XUL Document, it should not implement nsHyperText
@@ -468,16 +469,22 @@ DocAccessible::Shutdown()
   // Walk the array backwards because child documents remove themselves from the
   // array as they are shutdown.
   int32_t childDocCount = mChildDocuments.Length();
   for (int32_t idx = childDocCount - 1; idx >= 0; idx--)
     mChildDocuments[idx]->Shutdown();
 
   mChildDocuments.Clear();
 
+  // XXX thinking about ordering?
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+    DocAccessibleChild::Send__delete__(mIPCDoc);
+    MOZ_ASSERT(!mIPCDoc);
+  }
+
   if (mVirtualCursor) {
     mVirtualCursor->RemoveObserver(this);
     mVirtualCursor = nullptr;
   }
 
   mPresShell->SetDocAccessible(nullptr);
   mPresShell = nullptr;  // Avoid reentrancy
 
@@ -1431,16 +1438,23 @@ DocAccessible::DoInitialUpdate()
   // Fire reorder event after the document tree is constructed. Note, since
   // this reorder event is processed by parent document then events targeted to
   // this document may be fired prior to this reorder event. If this is
   // a problem then consider to keep event processing per tab document.
   if (!IsRoot()) {
     nsRefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(Parent());
     ParentDocument()->FireDelayedEvent(reorderEvent);
   }
+
+  uint32_t childCount = ChildCount();
+  for (uint32_t i = 0; i < childCount; i++) {
+    Accessible* child = GetChildAt(i);
+    nsRefPtr<AccShowEvent> event = new AccShowEvent(child, child->GetContent());
+  FireDelayedEvent(event);
+  }
 }
 
 void
 DocAccessible::ProcessLoad()
 {
   mLoadState |= eCompletelyLoaded;
 
 #ifdef A11Y_LOG
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -28,16 +28,17 @@ class nsIScrollableView;
 
 const uint32_t kDefaultCacheLength = 128;
 
 namespace mozilla {
 namespace a11y {
 
 class DocManager;
 class NotificationController;
+class DocAccessibleChild;
 class RelatedAccIterator;
 template<class Class, class Arg>
 class TNotification;
 
 class DocAccessible : public HyperTextAccessibleWrap,
                       public xpcAccessibleDocument,
                       public nsIDocumentObserver,
                       public nsIObserver,
@@ -515,16 +516,30 @@ protected:
    * Rules: The root chrome document accessible is never an event target
    * (for example, Firefox UI window). If the sub document is loaded within its
    * parent document then the parent document is a target only (aka events
    * coalescence).
    */
   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.
    *
    * @param aTimer    [in] the timer object
    * @param aClosure  [in] the document accessible where scrolling happens
    */
   static void ScrollTimerCallback(nsITimer* aTimer, void* aClosure);
 
 protected:
@@ -637,16 +652,19 @@ protected:
    */
   nsRefPtr<NotificationController> mNotificationController;
   friend class EventQueue;
   friend class NotificationController;
 
 private:
 
   nsIPresShell* mPresShell;
+
+  // Exclusively owned by IPDL so don't manually delete it!
+  DocAccessibleChild* mIPCDoc;
 };
 
 inline DocAccessible*
 Accessible::AsDoc()
 {
   return IsDoc() ? static_cast<DocAccessible*>(this) : nullptr;
 }
 
--- a/accessible/generic/moz.build
+++ b/accessible/generic/moz.build
@@ -23,16 +23,17 @@ UNIFIED_SOURCES += [
     'RootAccessible.cpp',
     'TableCellAccessible.cpp',
     'TextLeafAccessible.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/accessible/base',
     '/accessible/html',
+    '/accessible/ipc',
     '/accessible/xpcom',
     '/accessible/xul',
     '/content/base/src',
     '/layout/generic',
     '/layout/xul',
 ]
 
 if CONFIG['MOZ_ENABLE_GTK']:
@@ -49,8 +50,10 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'co
         '/accessible/mac',
     ]
 else:
     LOCAL_INCLUDES += [
         '/accessible/other',
     ]
 
 FINAL_LIBRARY = 'xul'
+
+include('/ipc/chromium/chromium-config.mozbuild')
new file mode 100644
--- /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<AccessibleData>& aTree)
+{
+  uint64_t id = reinterpret_cast<uint64_t>(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<uint64_t>(parent->UniqueID());
+  uint32_t idxInParent = aShowEvent->GetAccessible()->IndexInParent();
+  nsTArray<AccessibleData> shownTree;
+  ShowEventData data(parentID, idxInParent, shownTree);
+  SerializeTree(aShowEvent->GetAccessible(), data.NewTree());
+  SendShowEvent(data);
+}
+}
+}
new file mode 100644
--- /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
new file mode 100644
--- /dev/null
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -0,0 +1,112 @@
+/* -*- 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<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;
+  }
+
+  auto role = static_cast<a11y::role>(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;
+  }
+
+  root->Shutdown();
+
+  return true;
+}
+}
+}
new file mode 100644
--- /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<AccessibleData>& aNewTree, uint32_t aIdx,
+                      uint32_t aIdxInParent);
+
+  nsTArray<DocAccessibleParent*> 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<ProxyEntry> mAccessibles;
+};
+
+}
+}
+
+#endif
new file mode 100644
--- /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);
+};
+
+}
+}
new file mode 100644
--- /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;
+  }
+}
+}
+}
new file mode 100644
--- /dev/null
+++ b/accessible/ipc/ProxyAccessible.h
@@ -0,0 +1,77 @@
+/* -*- 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*);
+
+  /**
+   * 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<ProxyAccessible*> mChildren;
+  DocAccessibleParent* mDoc;
+  uintptr_t mWrapper;
+  uint64_t mID;
+  role mRole : 31;
+  bool mOuterDoc : 1;
+  nsString mName;
+};
+
+}
+}
+
+#endif
new file mode 100644
--- /dev/null
+++ b/accessible/ipc/moz.build
@@ -0,0 +1,28 @@
+# -*- 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']
+
+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')
--- a/accessible/mac/Platform.mm
+++ b/accessible/mac/Platform.mm
@@ -28,16 +28,25 @@ PlatformInit()
 {
 }
 
 void
 PlatformShutdown()
 {
 }
 
+void
+ProxyCreated(ProxyAccessible*)
+{
+}
+
+void
+ProxyDestroyed(ProxyAccessible*)
+{
+}
 }
 }
 
 @interface GeckoNSApplication(a11y)
 -(void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
 @end
 
 @implementation GeckoNSApplication(a11y)
--- a/accessible/moz.build
+++ b/accessible/moz.build
@@ -10,14 +10,14 @@ if CONFIG['MOZ_ENABLE_GTK']:
     DIRS += ['atk']
 elif toolkit == 'windows':
     DIRS += ['windows']
 elif toolkit == 'cocoa':
     DIRS += ['mac']
 else:
     DIRS += ['other']
 
-DIRS += ['base', 'generic', 'html', 'interfaces', 'jsat', 'xpcom']
+DIRS += ['base', 'generic', 'html', 'interfaces', 'ipc', 'jsat', 'xpcom']
 
 if CONFIG['MOZ_XUL']:
     DIRS += ['xul']
 
 TEST_DIRS += ['tests/mochitest']
--- a/accessible/other/Platform.cpp
+++ b/accessible/other/Platform.cpp
@@ -13,8 +13,18 @@ void
 a11y::PlatformInit()
 {
 }
 
 void
 a11y::PlatformShutdown()
 {
 }
+
+void
+a11y::ProxyCreated(ProxyAccessible*)
+{
+}
+
+void
+a11y::ProxyDestroyed(ProxyAccessible*)
+{
+}
--- a/accessible/windows/msaa/Platform.cpp
+++ b/accessible/windows/msaa/Platform.cpp
@@ -29,8 +29,17 @@ a11y::PlatformInit()
 void
 a11y::PlatformShutdown()
 {
   ::DestroyCaret();
 
   nsWinUtils::ShutdownWindowEmulation();
 }
 
+void
+a11y::ProxyCreated(ProxyAccessible*)
+{
+}
+
+void
+a11y::ProxyDestroyed(ProxyAccessible*)
+{
+}
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -14,16 +14,17 @@
 
 #include "ContentChild.h"
 
 #include "BlobChild.h"
 #include "CrashReporterChild.h"
 #include "TabChild.h"
 
 #include "mozilla/Attributes.h"
+#include "mozilla/a11y/DocAccessibleChild.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/ContentBridgeChild.h"
 #include "mozilla/dom/ContentBridgeParent.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/PCrashReporterChild.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
@@ -700,16 +701,30 @@ ContentChild::InitXPCOM()
     // This object is held alive by the observer service.
     nsRefPtr<SystemMessageHandledObserver> sysMsgObserver =
         new SystemMessageHandledObserver();
     sysMsgObserver->Init();
 
     InitOnContentProcessCreated();
 }
 
+a11y::PDocAccessibleChild*
+ContentChild::AllocPDocAccessibleChild(PDocAccessibleChild*, const uint64_t&)
+{
+  MOZ_ASSERT(false, "should never call this!");
+  return nullptr;
+}
+
+bool
+ContentChild::DeallocPDocAccessibleChild(a11y::PDocAccessibleChild* aChild)
+{
+  delete static_cast<mozilla::a11y::DocAccessibleChild*>(aChild);
+  return true;
+}
+
 PMemoryReportRequestChild*
 ContentChild::AllocPMemoryReportRequestChild(const uint32_t& aGeneration,
                                              const bool &aAnonymize,
                                              const bool &aMinimizeMemoryUsage,
                                              const MaybeFileDesc& aDMDFile)
 {
     MemoryReportRequestChild *actor =
         new MemoryReportRequestChild(aGeneration, aAnonymize, aDMDFile);
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -371,16 +371,18 @@ public:
                                          const bool& aIsForBrowser) MOZ_OVERRIDE;
 
     virtual bool RecvPBrowserConstructor(PBrowserChild* aCctor,
                                          const IPCTabContext& aContext,
                                          const uint32_t& aChromeFlags,
                                          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;
 
 private:
     virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
     virtual void ProcessingError(Result what) MOZ_OVERRIDE;
 
     /**
      * Exit *now*.  Do not shut down XPCOM, do not pass Go, do not run
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -25,16 +25,18 @@
 #include <set>
 
 #include "AppProcessChecker.h"
 #include "AudioChannelService.h"
 #include "BlobParent.h"
 #include "CrashReporterParent.h"
 #include "IHistory.h"
 #include "mozIApplication.h"
+#include "mozilla/a11y/DocAccessibleParent.h"
+#include "nsAccessibilityService.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/DataStoreService.h"
 #include "mozilla/dom/ExternalHelperAppParent.h"
 #include "mozilla/dom/PContentBridgeParent.h"
 #include "mozilla/dom/PCycleCollectWithLogsParent.h"
 #include "mozilla/dom/PMemoryReportRequestParent.h"
@@ -2690,16 +2692,44 @@ ContentParent::Observe(nsISupports* aSub
     }
 #endif
     else if (!strcmp(aTopic, "app-theme-changed")) {
         unused << SendOnAppThemeChanged();
     }
     return NS_OK;
 }
 
+  a11y::PDocAccessibleParent*
+ContentParent::AllocPDocAccessibleParent(PDocAccessibleParent* aParent, const uint64_t&)
+{
+  return new a11y::DocAccessibleParent();
+}
+
+bool
+ContentParent::DeallocPDocAccessibleParent(PDocAccessibleParent* aParent)
+{
+  delete static_cast<a11y::DocAccessibleParent*>(aParent);
+  return true;
+}
+
+bool
+ContentParent::RecvPDocAccessibleConstructor(PDocAccessibleParent* aDoc, PDocAccessibleParent* aParentDoc, const uint64_t& aParentID)
+{
+  auto doc = static_cast<a11y::DocAccessibleParent*>(aDoc);
+  if (aParentDoc) {
+    MOZ_ASSERT(aParentID);
+    auto parentDoc = static_cast<a11y::DocAccessibleParent*>(aParentDoc);
+    return parentDoc->AddChildDoc(doc, aParentID);
+  } else {
+    MOZ_ASSERT(!aParentID);
+    GetAccService()->RemoteDocAdded(doc);
+  }
+  return true;
+}
+
 PCompositorParent*
 ContentParent::AllocPCompositorParent(mozilla::ipc::Transport* aTransport,
                                       base::ProcessId aOtherProcess)
 {
     return CompositorParent::Create(aTransport, aOtherProcess);
 }
 
 PImageBridgeParent*
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -657,16 +657,21 @@ private:
                           const nsCString& aOrigin,
                           const nsString& aDatabaseName,
                           const int64_t& aFileId,
                           int32_t* aRefCnt,
                           int32_t* aDBRefCnt,
                           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.
 
     GeckoChildProcessHost* mSubprocess;
     ContentParent* mOpener;
 
     uint64_t mChildID;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -8,16 +8,17 @@ include protocol PAsmJSCacheEntry;
 include protocol PBackground;
 include protocol PBlob;
 include protocol PBluetooth;
 include protocol PBrowser;
 include protocol PCompositor;
 include protocol PContentBridge;
 include protocol PCycleCollectWithLogs;
 include protocol PCrashReporter;
+include protocol PDocAccessible;
 include protocol PExternalHelperApp;
 include protocol PDeviceStorageRequest;
 include protocol PFileDescriptorSet;
 include protocol PFMRadio;
 include protocol PFileSystemRequest;
 include protocol PHal;
 include protocol PImageBridge;
 include protocol PMemoryReportRequest;
@@ -320,16 +321,17 @@ intr protocol PContent
     child opens PBackground;
 
     manages PAsmJSCacheEntry;
     manages PBlob;
     manages PBluetooth;
     manages PBrowser;
     manages PCrashReporter;
     manages PCycleCollectWithLogs;
+    manages PDocAccessible;
     manages PDeviceStorageRequest;
     manages PFileSystemRequest;
     manages PExternalHelperApp;
     manages PFileDescriptorSet;
     manages PFMRadio;
     manages PHal;
     manages PMemoryReportRequest;
     manages PMobileConnection;
@@ -477,16 +479,24 @@ child:
 
     /**
      * Notify windows in the child to apply a new app style.
      */
     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
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
      * |id| is a unique ID among all subprocesses.  When |isForApp &&
      * isForBrowser|, we're loading <browser> for an app.  When
      * |isForBrowser|, we're loading <browser>.  When |!isForApp &&