Bug 1297474 - shutdown accessibility service only if there are no references to accessibles in JS. r=surkov
authorYura Zenevich <yzenevich@mozilla.com>
Fri, 18 Nov 2016 23:29:10 -0500
changeset 368805 ddc9b2697068639ae1980c6753f79578301eedf6
parent 368804 a9f041fdef8821a63acbbe4b81542b135b5438e8
child 368806 a4b0052954d201710b37d39fb4e583254382086b
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssurkov
bugs1297474
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1297474 - shutdown accessibility service only if there are no references to accessibles in JS. r=surkov MozReview-Commit-ID: BTOuBQXA1Ly
accessible/base/DocManager.cpp
accessible/base/DocManager.h
accessible/base/nsAccessibilityService.cpp
accessible/tests/browser/browser.ini
accessible/tests/browser/browser_shutdown_acc_reference.js
accessible/tests/browser/browser_shutdown_doc_acc_reference.js
accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js
accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js
accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js
accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js
accessible/tests/browser/browser_shutdown_multi_reference.js
accessible/tests/browser/browser_shutdown_proxy_acc_reference.js
accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js
accessible/tests/browser/head.js
accessible/xpcom/xpcAccessibleDocument.cpp
accessible/xpcom/xpcAccessibleDocument.h
accessible/xpcom/xpcAccessibleGeneric.cpp
accessible/xpcom/xpcAccessibleGeneric.h
--- a/accessible/base/DocManager.cpp
+++ b/accessible/base/DocManager.cpp
@@ -81,46 +81,65 @@ DocManager::FindAccessibleInCache(nsINod
         return accessible;
       }
     }
   }
   return nullptr;
 }
 
 void
+DocManager::RemoveFromXPCDocumentCache(DocAccessible* aDocument)
+{
+  xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
+  if (xpcDoc) {
+    xpcDoc->Shutdown();
+    mXPCDocumentCache.Remove(aDocument);
+  }
+
+  if (!HasXPCDocuments()) {
+    MaybeShutdownAccService(nsAccessibilityService::eXPCOM);
+  }
+}
+
+void
 DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument,
                                      nsIDocument* aDOMDocument)
 {
   // We need to remove listeners in both cases, when document is being shutdown
   // or when accessibility service is being shut down as well.
   RemoveListeners(aDOMDocument);
 
   // Document will already be removed when accessibility service is shutting
   // down so we do not need to remove it twice.
   if (nsAccessibilityService::IsShutdown()) {
     return;
   }
 
-  xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
-  if (xpcDoc) {
-    xpcDoc->Shutdown();
-    mXPCDocumentCache.Remove(aDocument);
-  }
-
+  RemoveFromXPCDocumentCache(aDocument);
   mDocAccessibleCache.Remove(aDOMDocument);
 }
 
 void
-DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc)
+DocManager::RemoveFromRemoteXPCDocumentCache(DocAccessibleParent* aDoc)
 {
   xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
   if (doc) {
     doc->Shutdown();
     sRemoteXPCDocumentCache->Remove(aDoc);
   }
+
+  if (sRemoteXPCDocumentCache && sRemoteXPCDocumentCache->Count() == 0) {
+    MaybeShutdownAccService(nsAccessibilityService::eXPCOM);
+  }
+}
+
+void
+DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc)
+{
+  RemoveFromRemoteXPCDocumentCache(aDoc);
 }
 
 xpcAccessibleDocument*
 DocManager::GetXPCDocument(DocAccessible* aDocument)
 {
   if (!aDocument)
     return nullptr;
 
--- a/accessible/base/DocManager.h
+++ b/accessible/base/DocManager.h
@@ -61,16 +61,18 @@ public:
   Accessible* FindAccessibleInCache(nsINode* aNode) const;
 
   /**
    * Called by document accessible when it gets shutdown.
    */
   void NotifyOfDocumentShutdown(DocAccessible* aDocument,
                                 nsIDocument* aDOMDocument);
 
+  void RemoveFromXPCDocumentCache(DocAccessible* aDocument);
+
   /**
    * Return XPCOM accessible document.
    */
   xpcAccessibleDocument* GetXPCDocument(DocAccessible* aDocument);
   xpcAccessibleDocument* GetCachedXPCDocument(DocAccessible* aDocument) const
     { return mXPCDocumentCache.GetWeak(aDocument); }
 
   /*
@@ -90,16 +92,18 @@ public:
   static const nsTArray<DocAccessibleParent*>* TopLevelRemoteDocs()
     { return sRemoteDocuments; }
 
   /**
    * Remove the xpc document for a remote document if there is one.
    */
   static void NotifyOfRemoteDocShutdown(DocAccessibleParent* adoc);
 
+  static void RemoveFromRemoteXPCDocumentCache(DocAccessibleParent* aDoc);
+
   /**
    * Get a XPC document for a remote document.
    */
   static xpcAccessibleDocument* GetXPCDocument(DocAccessibleParent* aDoc);
   static xpcAccessibleDocument* GetCachedXPCDocument(const DocAccessibleParent* aDoc)
   {
     return sRemoteXPCDocumentCache ? sRemoteXPCDocumentCache->GetWeak(aDoc)
       : nullptr;
@@ -118,16 +122,22 @@ protected:
    */
   bool Init();
 
   /**
    * Shutdown the manager.
    */
   void Shutdown();
 
+  bool HasXPCDocuments()
+  {
+    return mXPCDocumentCache.Count() > 0 ||
+           (sRemoteXPCDocumentCache && sRemoteXPCDocumentCache->Count() > 0);
+  }
+
 private:
   DocManager(const DocManager&);
   DocManager& operator =(const DocManager&);
 
 private:
   /**
    * Create an accessible document if it was't created and fire accessibility
    * events if needed.
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -1810,17 +1810,18 @@ MaybeShutdownAccService(uint32_t aFormer
   nsAccessibilityService* accService =
     nsAccessibilityService::gAccessibilityService;
 
   if (!accService || accService->IsShutdown()) {
     return;
   }
 
   if (nsCoreUtils::AccEventObserversExist() ||
-      xpcAccessibilityService::IsInUse()) {
+      xpcAccessibilityService::IsInUse() ||
+      accService->HasXPCDocuments()) {
     // Still used by XPCOM
     nsAccessibilityService::gConsumers =
       (nsAccessibilityService::gConsumers & ~aFormerConsumer) |
       nsAccessibilityService::eXPCOM;
     return;
   }
 
   if (nsAccessibilityService::gConsumers & ~aFormerConsumer) {
--- a/accessible/tests/browser/browser.ini
+++ b/accessible/tests/browser/browser.ini
@@ -1,17 +1,29 @@
 [DEFAULT]
 
 support-files =
   head.js
   shared-head.js
 
+[browser_shutdown_acc_reference.js]
+[browser_shutdown_doc_acc_reference.js]
+[browser_shutdown_multi_acc_reference_obj.js]
+[browser_shutdown_multi_acc_reference_doc.js]
 [browser_shutdown_multi_reference.js]
 [browser_shutdown_parent_own_reference.js]
 skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_proxy_acc_reference.js]
+skip-if = !e10s || (os == 'win') # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_proxy_doc_acc_reference.js]
+skip-if = !e10s || (os == 'win') # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_multi_proxy_acc_reference_doc.js]
+skip-if = !e10s || (os == 'win') # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_multi_proxy_acc_reference_obj.js]
+skip-if = !e10s || (os == 'win') # e10s specific test for a11y start/shutdown between parent and content.
 [browser_shutdown_remote_no_reference.js]
 skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content.
 [browser_shutdown_remote_only.js]
 skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content.
 [browser_shutdown_remote_own_reference.js]
 skip-if = !e10s # e10s specific test for a11y start/shutdown between parent and content.
 [browser_shutdown_scope_lifecycle.js]
 [browser_shutdown_start_restart.js]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_acc_reference.js
@@ -0,0 +1,55 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+  // Create a11y service.
+  let a11yInit = initPromise();
+  let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+    Ci.nsIAccessibilityService);
+
+  yield a11yInit;
+  ok(accService, 'Service initialized');
+
+  // Accessible object reference will live longer than the scope of this
+  // function.
+  let acc = yield new Promise(resolve => {
+    let intervalId = setInterval(() => {
+      let tabAcc = accService.getAccessibleFor(gBrowser.mCurrentTab);
+      if (tabAcc) {
+        clearInterval(intervalId);
+        resolve(tabAcc);
+      }
+    }, 10);
+  });
+  ok(acc, 'Accessible object is created');
+
+  let canShutdown = false;
+  // This promise will resolve only if canShutdown flag is set to true. If
+  // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
+  // down, the promise will reject.
+  let a11yShutdown = new Promise((resolve, reject) =>
+    shutdownPromise().then(flag => canShutdown ? resolve() :
+      reject('Accessible service was shut down incorrectly')));
+
+  accService = null;
+  ok(!accService, 'Service is removed');
+
+  // Force garbage collection that should not trigger shutdown because there is
+  // a reference to an accessible object.
+  forceGC();
+  // Have some breathing room when removing a11y service references.
+  yield new Promise(resolve => executeSoon(resolve));
+
+  // Now allow a11y service to shutdown.
+  canShutdown = true;
+  // Remove a reference to an accessible object.
+  acc = null;
+  ok(!acc, 'Accessible object is removed');
+
+  // Force garbage collection that should now trigger shutdown.
+  forceGC();
+  yield a11yShutdown;
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_doc_acc_reference.js
@@ -0,0 +1,47 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+  // Create a11y service.
+  let a11yInit = initPromise();
+  let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+    Ci.nsIAccessibilityService);
+
+  yield a11yInit;
+  ok(accService, 'Service initialized');
+
+  // Accessible document reference will live longer than the scope of this
+  // function.
+  let docAcc = accService.getAccessibleFor(document);
+  ok(docAcc, 'Accessible document is created');
+
+  let canShutdown = false;
+  // This promise will resolve only if canShutdown flag is set to true. If
+  // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
+  // down, the promise will reject.
+  let a11yShutdown = new Promise((resolve, reject) =>
+    shutdownPromise().then(flag => canShutdown ? resolve() :
+      reject('Accessible service was shut down incorrectly')));
+
+  accService = null;
+  ok(!accService, 'Service is removed');
+
+  // Force garbage collection that should not trigger shutdown because there is
+  // a reference to an accessible document.
+  forceGC();
+  // Have some breathing room when removing a11y service references.
+  yield new Promise(resolve => executeSoon(resolve));
+
+  // Now allow a11y service to shutdown.
+  canShutdown = true;
+  // Remove a reference to an accessible document.
+  docAcc = null;
+  ok(!docAcc, 'Accessible document is removed');
+
+  // Force garbage collection that should now trigger shutdown.
+  forceGC();
+  yield a11yShutdown;
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_multi_acc_reference_doc.js
@@ -0,0 +1,67 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+  // Create a11y service.
+  let a11yInit = initPromise();
+  let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+    Ci.nsIAccessibilityService);
+
+  yield a11yInit;
+  ok(accService, 'Service initialized');
+
+  let docAcc = accService.getAccessibleFor(document);
+  ok(docAcc, 'Accessible document is created');
+
+  // Accessible object reference will live longer than the scope of this
+  // function.
+  let acc = yield new Promise(resolve => {
+    let intervalId = setInterval(() => {
+      let tabAcc = accService.getAccessibleFor(gBrowser.mCurrentTab);
+      if (tabAcc) {
+        clearInterval(intervalId);
+        resolve(tabAcc);
+      }
+    }, 10);
+  });
+  ok(acc, 'Accessible object is created');
+
+  let canShutdown = false;
+  // This promise will resolve only if canShutdown flag is set to true. If
+  // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
+  // down, the promise will reject.
+  let a11yShutdown = new Promise((resolve, reject) =>
+    shutdownPromise().then(flag => canShutdown ? resolve() :
+      reject('Accessible service was shut down incorrectly')));
+
+  accService = null;
+  ok(!accService, 'Service is removed');
+
+  // Force garbage collection that should not trigger shutdown because there are
+  // references to accessible objects.
+  forceGC();
+  // Have some breathing room when removing a11y service references.
+  yield new Promise(resolve => executeSoon(resolve));
+
+  // Remove a reference to an accessible object.
+  acc = null;
+  ok(!acc, 'Accessible object is removed');
+  // Force garbage collection that should not trigger shutdown because there is
+  // a reference to an accessible document.
+  forceGC();
+  // Have some breathing room when removing a11y service references.
+  yield new Promise(resolve => executeSoon(resolve));
+
+  // Now allow a11y service to shutdown.
+  canShutdown = true;
+  // Remove a reference to an accessible document.
+  docAcc = null;
+  ok(!docAcc, 'Accessible document is removed');
+
+  // Force garbage collection that should now trigger shutdown.
+  forceGC();
+  yield a11yShutdown;
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_multi_acc_reference_obj.js
@@ -0,0 +1,67 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+  // Create a11y service.
+  let a11yInit = initPromise();
+  let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+    Ci.nsIAccessibilityService);
+
+  yield a11yInit;
+  ok(accService, 'Service initialized');
+
+  let docAcc = accService.getAccessibleFor(document);
+  ok(docAcc, 'Accessible document is created');
+
+  // Accessible object reference will live longer than the scope of this
+  // function.
+  let acc = yield new Promise(resolve => {
+    let intervalId = setInterval(() => {
+      let tabAcc = accService.getAccessibleFor(gBrowser.mCurrentTab);
+      if (tabAcc) {
+        clearInterval(intervalId);
+        resolve(tabAcc);
+      }
+    }, 10);
+  });
+  ok(acc, 'Accessible object is created');
+
+  let canShutdown = false;
+  // This promise will resolve only if canShutdown flag is set to true. If
+  // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
+  // down, the promise will reject.
+  let a11yShutdown = new Promise((resolve, reject) =>
+    shutdownPromise().then(flag => canShutdown ? resolve() :
+      reject('Accessible service was shut down incorrectly')));
+
+  accService = null;
+  ok(!accService, 'Service is removed');
+
+  // Force garbage collection that should not trigger shutdown because there are
+  // references to accessible objects.
+  forceGC();
+  // Have some breathing room when removing a11y service references.
+  yield new Promise(resolve => executeSoon(resolve));
+
+  // Remove a reference to an accessible document.
+  docAcc = null;
+  ok(!docAcc, 'Accessible document is removed');
+  // Force garbage collection that should not trigger shutdown because there is
+  // a reference to an accessible object.
+  forceGC();
+  // Have some breathing room when removing a11y service references.
+  yield new Promise(resolve => executeSoon(resolve));
+
+  // Now allow a11y service to shutdown.
+  canShutdown = true;
+  // Remove a reference to an accessible object.
+  acc = null;
+  ok(!acc, 'Accessible object is removed');
+
+  // Force garbage collection that should now trigger shutdown.
+  forceGC();
+  yield a11yShutdown;
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_doc.js
@@ -0,0 +1,76 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+  // Making sure that the e10s is enabled on Windows for testing.
+  yield setE10sPrefs();
+
+  let docLoaded = waitForEvent(
+    Ci.nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE, 'body');
+  let a11yInit = initPromise();
+  let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+    Ci.nsIAccessibilityService);
+  ok(accService, 'Service initialized');
+  yield a11yInit;
+
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: `data:text/html,
+      <html>
+        <head>
+          <meta charset="utf-8"/>
+          <title>Accessibility Test</title>
+        </head>
+        <body id="body"><div id="div"></div></body>
+      </html>`
+  }, function*(browser) {
+    let docLoadedEvent = yield docLoaded;
+    let docAcc = docLoadedEvent.accessibleDocument;
+    ok(docAcc, 'Accessible document proxy is created');
+    // Remove unnecessary dangling references
+    docLoaded = null;
+    docLoadedEvent = null;
+    forceGC();
+
+    let acc = docAcc.getChildAt(0);
+    ok(acc, 'Accessible proxy is created');
+
+    let canShutdown = false;
+    let a11yShutdown = new Promise((resolve, reject) =>
+    shutdownPromise().then(flag => canShutdown ? resolve() :
+      reject('Accessible service was shut down incorrectly')));
+
+    accService = null;
+    ok(!accService, 'Service is removed');
+    // Force garbage collection that should not trigger shutdown because there
+    // is a reference to an accessible proxy.
+    forceGC();
+    // Have some breathing room when removing a11y service references.
+    yield new Promise(resolve => executeSoon(resolve));
+
+    // Remove a reference to an accessible proxy.
+    acc = null;
+    ok(!acc, 'Accessible proxy is removed');
+    // Force garbage collection that should not trigger shutdown because there is
+    // a reference to an accessible document proxy.
+    forceGC();
+    // Have some breathing room when removing a11y service references.
+    yield new Promise(resolve => executeSoon(resolve));
+
+    // Now allow a11y service to shutdown.
+    canShutdown = true;
+    // Remove a last reference to an accessible document proxy.
+    docAcc = null;
+    ok(!docAcc, 'Accessible document proxy is removed');
+
+    // Force garbage collection that should now trigger shutdown.
+    forceGC();
+    yield a11yShutdown;
+  });
+
+  // Unsetting e10s related preferences.
+  yield unsetE10sPrefs();
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_multi_proxy_acc_reference_obj.js
@@ -0,0 +1,76 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+  // Making sure that the e10s is enabled on Windows for testing.
+  yield setE10sPrefs();
+
+  let docLoaded = waitForEvent(
+    Ci.nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE, 'body');
+  let a11yInit = initPromise();
+  let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+    Ci.nsIAccessibilityService);
+  ok(accService, 'Service initialized');
+  yield a11yInit;
+
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: `data:text/html,
+      <html>
+        <head>
+          <meta charset="utf-8"/>
+          <title>Accessibility Test</title>
+        </head>
+        <body id="body"><div id="div"></div></body>
+      </html>`
+  }, function*(browser) {
+    let docLoadedEvent = yield docLoaded;
+    let docAcc = docLoadedEvent.accessibleDocument;
+    ok(docAcc, 'Accessible document proxy is created');
+    // Remove unnecessary dangling references
+    docLoaded = null;
+    docLoadedEvent = null;
+    forceGC();
+
+    let acc = docAcc.getChildAt(0);
+    ok(acc, 'Accessible proxy is created');
+
+    let canShutdown = false;
+    let a11yShutdown = new Promise((resolve, reject) =>
+    shutdownPromise().then(flag => canShutdown ? resolve() :
+      reject('Accessible service was shut down incorrectly')));
+
+    accService = null;
+    ok(!accService, 'Service is removed');
+    // Force garbage collection that should not trigger shutdown because there
+    // is a reference to an accessible proxy.
+    forceGC();
+    // Have some breathing room when removing a11y service references.
+    yield new Promise(resolve => executeSoon(resolve));
+
+    // Remove a reference to an accessible document proxy.
+    docAcc = null;
+    ok(!docAcc, 'Accessible document proxy is removed');
+    // Force garbage collection that should not trigger shutdown because there is
+    // a reference to an accessible proxy.
+    forceGC();
+    // Have some breathing room when removing a11y service references.
+    yield new Promise(resolve => executeSoon(resolve));
+
+    // Now allow a11y service to shutdown.
+    canShutdown = true;
+    // Remove a last reference to an accessible proxy.
+    acc = null;
+    ok(!acc, 'Accessible proxy is removed');
+
+    // Force garbage collection that should now trigger shutdown.
+    forceGC();
+    yield a11yShutdown;
+  });
+
+  // Unsetting e10s related preferences.
+  yield unsetE10sPrefs();
+});
--- a/accessible/tests/browser/browser_shutdown_multi_reference.js
+++ b/accessible/tests/browser/browser_shutdown_multi_reference.js
@@ -16,17 +16,17 @@ add_task(function* () {
   // Add another reference to a11y service. This will not trigger
   // 'a11y-init-or-shutdown' event
   let accService2 = Cc['@mozilla.org/accessibilityService;1'].getService(
     Ci.nsIAccessibilityService);
   ok(accService2, 'Service initialized');
 
   info('Removing all service references');
   let canShutdown = false;
-  // This promise will resolve only if canShutdonw flag is set to true. If
+  // This promise will resolve only if canShutdown flag is set to true. If
   // 'a11y-init-or-shutdown' event with '0' flag comes before it can be shut
   // down, the promise will reject.
   let a11yShutdown = new Promise((resolve, reject) =>
     shutdownPromise().then(flag => canShutdown ?
       resolve() : reject('Accessible service was shut down incorrectly')));
   // Remove first a11y service reference.
   accService1 = null;
   ok(!accService1, 'Service is removed');
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_proxy_acc_reference.js
@@ -0,0 +1,64 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+  // Making sure that the e10s is enabled on Windows for testing.
+  yield setE10sPrefs();
+
+  let a11yInit = initPromise();
+  let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+    Ci.nsIAccessibilityService);
+  ok(accService, 'Service initialized');
+  yield a11yInit;
+
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: `data:text/html,
+      <html>
+        <head>
+          <meta charset="utf-8"/>
+          <title>Accessibility Test</title>
+        </head>
+        <body><div id="div" style="visibility: hidden;"></div></body>
+      </html>`
+  }, function*(browser) {
+    let onShow = waitForEvent(Ci.nsIAccessibleEvent.EVENT_SHOW, 'div');
+    yield invokeSetStyle(browser, 'div', 'visibility', 'visible');
+    let showEvent = yield onShow;
+    let divAcc = showEvent.accessible;
+    ok(divAcc, 'Accessible proxy is created');
+    // Remove unnecessary dangling references
+    onShow = null;
+    showEvent = null;
+    forceGC();
+
+    let canShutdown = false;
+    let a11yShutdown = new Promise((resolve, reject) =>
+    shutdownPromise().then(flag => canShutdown ? resolve() :
+      reject('Accessible service was shut down incorrectly')));
+
+    accService = null;
+    ok(!accService, 'Service is removed');
+    // Force garbage collection that should not trigger shutdown because there
+    // is a reference to an accessible proxy.
+    forceGC();
+    // Have some breathing room when removing a11y service references.
+    yield new Promise(resolve => executeSoon(resolve));
+
+    // Now allow a11y service to shutdown.
+    canShutdown = true;
+    // Remove a last reference to an accessible proxy.
+    divAcc = null;
+    ok(!divAcc, 'Accessible proxy is removed');
+
+    // Force garbage collection that should now trigger shutdown.
+    forceGC();
+    yield a11yShutdown;
+  });
+
+  // Unsetting e10s related preferences.
+  yield unsetE10sPrefs();
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_proxy_doc_acc_reference.js
@@ -0,0 +1,64 @@
+/* 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/. */
+
+'use strict';
+
+add_task(function* () {
+  // Making sure that the e10s is enabled on Windows for testing.
+  yield setE10sPrefs();
+
+  let docLoaded = waitForEvent(
+    Ci.nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE, 'body');
+  let a11yInit = initPromise();
+  let accService = Cc['@mozilla.org/accessibilityService;1'].getService(
+    Ci.nsIAccessibilityService);
+  ok(accService, 'Service initialized');
+  yield a11yInit;
+
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: `data:text/html,
+      <html>
+        <head>
+          <meta charset="utf-8"/>
+          <title>Accessibility Test</title>
+        </head>
+        <body id="body"></body>
+      </html>`
+  }, function*(browser) {
+    let docLoadedEvent = yield docLoaded;
+    let docAcc = docLoadedEvent.accessibleDocument;
+    ok(docAcc, 'Accessible document proxy is created');
+    // Remove unnecessary dangling references
+    docLoaded = null;
+    docLoadedEvent = null;
+    forceGC();
+
+    let canShutdown = false;
+    let a11yShutdown = new Promise((resolve, reject) =>
+    shutdownPromise().then(flag => canShutdown ? resolve() :
+      reject('Accessible service was shut down incorrectly')));
+
+    accService = null;
+    ok(!accService, 'Service is removed');
+    // Force garbage collection that should not trigger shutdown because there
+    // is a reference to an accessible proxy.
+    forceGC();
+    // Have some breathing room when removing a11y service references.
+    yield new Promise(resolve => executeSoon(resolve));
+
+    // Now allow a11y service to shutdown.
+    canShutdown = true;
+    // Remove a last reference to an accessible document proxy.
+    docAcc = null;
+    ok(!docAcc, 'Accessible document proxy is removed');
+
+    // Force garbage collection that should now trigger shutdown.
+    forceGC();
+    yield a11yShutdown;
+  });
+
+  // Unsetting e10s related preferences.
+  yield unsetE10sPrefs();
+});
--- a/accessible/tests/browser/head.js
+++ b/accessible/tests/browser/head.js
@@ -1,16 +1,16 @@
 /* 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/. */
 
 'use strict';
 
-/* exported initPromise, shutdownPromise,
-            setE10sPrefs, unsetE10sPrefs, forceGC */
+/* exported initPromise, shutdownPromise, waitForEvent, setE10sPrefs,
+            unsetE10sPrefs, forceGC */
 
 /**
  * Set e10s related preferences in the test environment.
  * @return {Promise} promise that resolves when preferences are set.
  */
 function setE10sPrefs() {
   return new Promise(resolve =>
     SpecialPowers.pushPrefEnv({
@@ -103,14 +103,38 @@ function shutdownPromise(contentBrowser)
     contentA11yInitOrShutdownPromise(contentBrowser) :
     a11yInitOrShutdownPromise();
   return promiseOK(a11yShutdownPromise, '0').then(
     () => ok(true, 'Service shutdown correctly'),
     () => ok(false, 'Service initialized incorrectly'));
 }
 
 /**
+ * Simpler verions of waitForEvent defined in
+ * accessible/tests/browser/e10s/events.js
+ */
+function waitForEvent(eventType, expectedId) {
+  return new Promise(resolve => {
+    let eventObserver = {
+      observe(subject) {
+        let event = subject.QueryInterface(Ci.nsIAccessibleEvent);
+        if (event.eventType === eventType &&
+            event.accessible.id === expectedId) {
+          Services.obs.removeObserver(this, 'accessible-event');
+          resolve(event);
+        }
+      }
+    };
+    Services.obs.addObserver(eventObserver, 'accessible-event', false);
+  });
+}
+
+/**
  * Force garbage collection.
  */
 function forceGC() {
-  Cu.forceCC();
-  Cu.forceGC();
+  SpecialPowers.gc();
+  SpecialPowers.forceGC();
+  SpecialPowers.forceCC();
+  SpecialPowers.gc();
+  SpecialPowers.forceGC();
+  SpecialPowers.forceCC();
 }
--- a/accessible/xpcom/xpcAccessibleDocument.cpp
+++ b/accessible/xpcom/xpcAccessibleDocument.cpp
@@ -12,36 +12,38 @@
 #include "mozilla/a11y/DocAccessibleParent.h"
 #include "DocAccessible-inl.h"
 #include "nsIDOMDocument.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
-// nsISupports and cycle collection
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(xpcAccessibleDocument)
+// nsISupports
 
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(xpcAccessibleDocument,
-                                                  xpcAccessibleGeneric)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCache)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_QUERY_INTERFACE_INHERITED(xpcAccessibleDocument, xpcAccessibleHyperText,
+                                  nsIAccessibleDocument)
+NS_IMPL_ADDREF_INHERITED(xpcAccessibleDocument, xpcAccessibleHyperText)
+NS_IMETHODIMP_(MozExternalRefCountType) xpcAccessibleDocument::Release(void)
+{
+  nsrefcnt r = xpcAccessibleHyperText::Release();
+  NS_LOG_RELEASE(this, r, "xpcAccessibleDocument");
 
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(xpcAccessibleDocument,
-                                                xpcAccessibleGeneric)
-  tmp->mCache.Clear();
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(xpcAccessibleDocument)
-  NS_INTERFACE_MAP_ENTRY(nsIAccessibleDocument)
-NS_INTERFACE_MAP_END_INHERITING(xpcAccessibleHyperText)
-
-NS_IMPL_ADDREF_INHERITED(xpcAccessibleDocument, xpcAccessibleHyperText)
-NS_IMPL_RELEASE_INHERITED(xpcAccessibleDocument, xpcAccessibleHyperText)
+  // The only reference to the xpcAccessibleDocument is in DocManager's cache.
+  if (r == 1 && !mIntl.IsNull() && mCache.Count() == 0) {
+    if (mIntl.IsAccessible()) {
+      GetAccService()->RemoveFromXPCDocumentCache(
+        mIntl.AsAccessible()->AsDoc());
+    } else {
+      GetAccService()->RemoveFromRemoteXPCDocumentCache(
+        mIntl.AsProxy()->AsDoc());
+    }
+  }
+  return r;
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIAccessibleDocument
 
 NS_IMETHODIMP
 xpcAccessibleDocument::GetURL(nsAString& aURL)
 {
   if (!Intl())
@@ -174,17 +176,17 @@ xpcAccessibleDocument::GetAccessible(Acc
   if (ToXPCDocument(aAccessible->Document()) != this) {
     NS_ERROR("This XPCOM document is not related with given internal accessible!");
     return nullptr;
   }
 
   if (aAccessible->IsDoc())
     return this;
 
-  xpcAccessibleGeneric* xpcAcc = mCache.GetWeak(aAccessible);
+  xpcAccessibleGeneric* xpcAcc = mCache.Get(aAccessible);
   if (xpcAcc)
     return xpcAcc;
 
   if (aAccessible->IsImage())
     xpcAcc = new xpcAccessibleImage(aAccessible);
   else if (aAccessible->IsTable())
     xpcAcc = new xpcAccessibleTable(aAccessible);
   else if (aAccessible->IsTableCell())
@@ -202,27 +204,27 @@ xpcAccessibleGeneric*
 xpcAccessibleDocument::GetXPCAccessible(ProxyAccessible* aProxy)
 {
   MOZ_ASSERT(mRemote);
   MOZ_ASSERT(aProxy->Document() == mIntl.AsProxy());
   if (aProxy->IsDoc()) {
     return this;
   }
 
-  xpcAccessibleGeneric* acc = mCache.GetWeak(aProxy);
+  xpcAccessibleGeneric* acc = mCache.Get(aProxy);
   if (acc) {
     return acc;
   }
 
   // XXX support exposing optional interfaces.
   uint8_t interfaces = 0;
   if (aProxy->mHasValue) {
     interfaces |= eValue;
   }
-  
+
   if (aProxy->mIsHyperLink) {
     interfaces |= eHyperLink;
   }
 
   if (aProxy->mIsHyperText) {
     interfaces |= eText;
     acc = new xpcAccessibleHyperText(aProxy, interfaces);
     mCache.Put(aProxy, acc);
--- a/accessible/xpcom/xpcAccessibleDocument.h
+++ b/accessible/xpcom/xpcAccessibleDocument.h
@@ -27,18 +27,16 @@ public:
   explicit xpcAccessibleDocument(DocAccessible* aIntl) :
     xpcAccessibleHyperText(aIntl), mCache(kDefaultCacheLength), mRemote(false) { }
 
   xpcAccessibleDocument(ProxyAccessible* aProxy, uint32_t aInterfaces) :
     xpcAccessibleHyperText(aProxy, aInterfaces), mCache(kDefaultCacheLength),
     mRemote(true) {}
 
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(xpcAccessibleDocument,
-                                           xpcAccessibleGeneric)
 
   // nsIAccessibleDocument
   NS_IMETHOD GetURL(nsAString& aURL) final override;
   NS_IMETHOD GetTitle(nsAString& aTitle) final override;
   NS_IMETHOD GetMimeType(nsAString& aType) final override;
   NS_IMETHOD GetDocType(nsAString& aType) final override;
   NS_IMETHOD GetDOMDocument(nsIDOMDocument** aDOMDocument) final override;
   NS_IMETHOD GetWindow(mozIDOMWindowProxy** aDOMWindow) final override;
@@ -70,43 +68,53 @@ private:
     }
 
     return nullptr;
   }
 
   void NotifyOfShutdown(Accessible* aAccessible)
   {
     MOZ_ASSERT(!mRemote);
-    xpcAccessibleGeneric* xpcAcc = mCache.GetWeak(aAccessible);
-    if (xpcAcc)
+    xpcAccessibleGeneric* xpcAcc = mCache.Get(aAccessible);
+    if (xpcAcc) {
       xpcAcc->Shutdown();
+    }
 
     mCache.Remove(aAccessible);
+    if (mCache.Count() == 0 && mRefCnt == 1) {
+      GetAccService()->RemoveFromXPCDocumentCache(
+        mIntl.AsAccessible()->AsDoc());
+    }
   }
 
   void NotifyOfShutdown(ProxyAccessible* aProxy)
   {
     MOZ_ASSERT(mRemote);
-    xpcAccessibleGeneric* acc = mCache.GetWeak(aProxy);
-    if (acc) {
-      acc->Shutdown();
+    xpcAccessibleGeneric* xpcAcc = mCache.Get(aProxy);
+    if (xpcAcc) {
+      xpcAcc->Shutdown();
     }
 
     mCache.Remove(aProxy);
+    if (mCache.Count() == 0 && mRefCnt == 1) {
+      GetAccService()->RemoveFromRemoteXPCDocumentCache(
+        mIntl.AsProxy()->AsDoc());
+    }
   }
 
   friend class DocManager;
   friend class DocAccessible;
   friend class ProxyAccessible;
   friend class ProxyAccessibleBase<ProxyAccessible>;
+  friend class xpcAccessibleGeneric;
 
   xpcAccessibleDocument(const xpcAccessibleDocument&) = delete;
   xpcAccessibleDocument& operator =(const xpcAccessibleDocument&) = delete;
 
-  nsRefPtrHashtable<nsPtrHashKey<const void>, xpcAccessibleGeneric> mCache;
+  nsDataHashtable<nsPtrHashKey<const void>, xpcAccessibleGeneric*> mCache;
   bool mRemote;
 };
 
 inline xpcAccessibleGeneric*
 ToXPC(Accessible* aAccessible)
 {
   if (!aAccessible)
     return nullptr;
--- a/accessible/xpcom/xpcAccessibleGeneric.cpp
+++ b/accessible/xpcom/xpcAccessibleGeneric.cpp
@@ -4,33 +4,53 @@
  * 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 "xpcAccessibleGeneric.h"
 
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
-// nsISupports and cycle collection
+// nsISupports
 
-NS_IMPL_CYCLE_COLLECTION_0(xpcAccessibleGeneric)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(xpcAccessibleGeneric)
+NS_INTERFACE_MAP_BEGIN(xpcAccessibleGeneric)
   NS_INTERFACE_MAP_ENTRY(nsIAccessible)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleSelectable,
                                      mSupportedIfaces & eSelectable)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleValue,
                                      mSupportedIfaces & eValue)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAccessibleHyperLink,
                                      mSupportedIfaces & eHyperLink)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessible)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(xpcAccessibleGeneric)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(xpcAccessibleGeneric)
+NS_IMPL_ADDREF(xpcAccessibleGeneric)
+NS_IMPL_RELEASE(xpcAccessibleGeneric)
+
+xpcAccessibleGeneric::~xpcAccessibleGeneric()
+{
+  if (mIntl.IsNull()) {
+    return;
+  }
+
+  xpcAccessibleDocument* xpcDoc = nullptr;
+  if (mIntl.IsAccessible()) {
+    Accessible* acc = mIntl.AsAccessible();
+    if (!acc->IsDoc() && !acc->IsApplication()) {
+      xpcDoc = GetAccService()->GetXPCDocument(acc->Document());
+      xpcDoc->NotifyOfShutdown(acc);
+    }
+  } else {
+    ProxyAccessible* proxy = mIntl.AsProxy();
+    if (!proxy->IsDoc()) {
+      xpcDoc = GetAccService()->GetXPCDocument(proxy->Document());
+      xpcDoc->NotifyOfShutdown(proxy);
+    }
+  }
+}
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIAccessible
 
 Accessible*
 xpcAccessibleGeneric::ToInternalAccessible() const
 {
   return mIntl.AsAccessible();
--- a/accessible/xpcom/xpcAccessibleGeneric.h
+++ b/accessible/xpcom/xpcAccessibleGeneric.h
@@ -36,27 +36,26 @@ public:
       mSupportedIfaces |= eValue;
     if (aInternal->IsLink())
       mSupportedIfaces |= eHyperLink;
   }
 
   xpcAccessibleGeneric(ProxyAccessible* aProxy, uint8_t aInterfaces) :
     mIntl(aProxy), mSupportedIfaces(aInterfaces) {}
 
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(xpcAccessibleGeneric, nsIAccessible)
+  NS_DECL_ISUPPORTS
 
   // nsIAccessible
   virtual Accessible* ToInternalAccessible() const final override;
 
   // xpcAccessibleGeneric
   virtual void Shutdown();
 
 protected:
-  virtual ~xpcAccessibleGeneric() {}
+  virtual ~xpcAccessibleGeneric();
 
   AccessibleOrProxy mIntl;
 
   enum {
     eSelectable = 1 << 0,
     eValue = 1 << 1,
     eHyperLink = 1 << 2,
     eText = 1 << 3