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 323842 ddc9b2697068639ae1980c6753f79578301eedf6
parent 323841 a9f041fdef8821a63acbbe4b81542b135b5438e8
child 323843 a4b0052954d201710b37d39fb4e583254382086b
push id21
push usermaklebus@msu.edu
push dateThu, 01 Dec 2016 06:22:08 +0000
reviewerssurkov
bugs1297474
milestone53.0a1
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