Bug 1415408 - send 'a11y-consumers-changed' notifcation whenever accessibility services consumers change. r=surkov
authorYura Zenevich <yura.zenevich@gmail.com>
Tue, 07 Nov 2017 23:07:24 -0500
changeset 444657 8b807149a0db4ee0d1fc5631afaa74a8a42b87a5
parent 444656 72f9e62daa10025ea10b70a31ec2d92d971b8565
child 444658 cea72923c4beaa5b5f9b7d5cd52cba08a043720e
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssurkov
bugs1415408
milestone58.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 1415408 - send 'a11y-consumers-changed' notifcation whenever accessibility services consumers change. r=surkov MozReview-Commit-ID: 2V4X4AO3JAT
accessible/base/nsAccessibilityService.cpp
accessible/base/nsAccessibilityService.h
accessible/tests/browser/browser_shutdown_remote_no_reference.js
accessible/tests/browser/browser_shutdown_remote_own_reference.js
accessible/tests/browser/head.js
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -20,16 +20,17 @@
 #include "HTMLTableAccessibleWrap.h"
 #include "HyperTextAccessibleWrap.h"
 #include "RootAccessible.h"
 #include "nsAccUtils.h"
 #include "nsArrayUtils.h"
 #include "nsAttrName.h"
 #include "nsEventShell.h"
 #include "nsIURI.h"
+#include "nsTextFormatter.h"
 #include "OuterDocAccessible.h"
 #include "Role.h"
 #ifdef MOZ_ACCESSIBILITY_ATK
 #include "RootAccessibleWrap.h"
 #endif
 #include "States.h"
 #include "Statistics.h"
 #include "TextLeafAccessibleWrap.h"
@@ -1392,18 +1393,17 @@ void
 nsAccessibilityService::Shutdown()
 {
   // Application is going to be closed, shutdown accessibility and mark
   // accessibility service as shutdown to prevent calls of its methods.
   // Don't null accessibility service static member at this point to be safe
   // if someone will try to operate with it.
 
   MOZ_ASSERT(gConsumers, "Accessibility was shutdown already");
-
-  gConsumers = 0;
+  UnsetConsumers(eXPCOM | eMainProcess | ePlatformAPI);
 
   // Remove observers.
   nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
   if (observerService) {
     observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
 
     static const char16_t kShutdownIndicator[] = { '0', 0 };
@@ -1851,30 +1851,71 @@ nsAccessibilityService::CreateAccessible
 
   // Table or tree table accessible.
   RefPtr<Accessible> accessible =
     new XULTreeGridAccessibleWrap(aContent, aDoc, treeFrame);
   return accessible.forget();
 }
 #endif
 
+void
+nsAccessibilityService::SetConsumers(uint32_t aConsumers) {
+  if (gConsumers & aConsumers) {
+    return;
+  }
+
+  gConsumers |= aConsumers;
+  NotifyOfConsumersChange();
+}
+
+void
+nsAccessibilityService::UnsetConsumers(uint32_t aConsumers) {
+  if (!(gConsumers & aConsumers)) {
+    return;
+  }
+
+  gConsumers &= ~aConsumers;
+  NotifyOfConsumersChange();
+}
+
+void
+nsAccessibilityService::NotifyOfConsumersChange()
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+
+  if (!observerService) {
+    return;
+  }
+
+  const char16_t* kJSONFmt =
+    u"{ \"XPCOM\": %s, \"MainProcess\": %s, \"PlatformAPI\": %s }";
+  nsString json;
+  nsTextFormatter::ssprintf(json, kJSONFmt,
+    gConsumers & eXPCOM ? "true" : "false",
+    gConsumers & eMainProcess ? "true" : "false",
+    gConsumers & ePlatformAPI ? "true" : "false");
+  observerService->NotifyObservers(
+    nullptr, "a11y-consumers-changed", json.get());
+}
+
 nsAccessibilityService*
 GetOrCreateAccService(uint32_t aNewConsumer)
 {
   if (!nsAccessibilityService::gAccessibilityService) {
     RefPtr<nsAccessibilityService> service = new nsAccessibilityService();
     if (!service->Init()) {
       service->Shutdown();
       return nullptr;
     }
   }
 
   MOZ_ASSERT(nsAccessibilityService::gAccessibilityService,
              "Accessible service is not initialized.");
-  nsAccessibilityService::gConsumers |= aNewConsumer;
+  nsAccessibilityService::gAccessibilityService->SetConsumers(aNewConsumer);
   return nsAccessibilityService::gAccessibilityService;
 }
 
 void
 MaybeShutdownAccService(uint32_t aFormerConsumer)
 {
   nsAccessibilityService* accService =
     nsAccessibilityService::gAccessibilityService;
@@ -1882,24 +1923,25 @@ MaybeShutdownAccService(uint32_t aFormer
   if (!accService || accService->IsShutdown()) {
     return;
   }
 
   if (nsCoreUtils::AccEventObserversExist() ||
       xpcAccessibilityService::IsInUse() ||
       accService->HasXPCDocuments()) {
     // Still used by XPCOM
-    nsAccessibilityService::gConsumers =
-      (nsAccessibilityService::gConsumers & ~aFormerConsumer) |
-      nsAccessibilityService::eXPCOM;
+    if (aFormerConsumer != nsAccessibilityService::eXPCOM) {
+      // Only unset non-XPCOM consumers.
+      accService->UnsetConsumers(aFormerConsumer);
+    }
     return;
   }
 
   if (nsAccessibilityService::gConsumers & ~aFormerConsumer) {
-    nsAccessibilityService::gConsumers &= ~aFormerConsumer;
+    accService->UnsetConsumers(aFormerConsumer);
   } else {
     accService->Shutdown(); // Will unset all nsAccessibilityService::gConsumers
   }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Services
 ////////////////////////////////////////////////////////////////////////////////
--- a/accessible/base/nsAccessibilityService.h
+++ b/accessible/base/nsAccessibilityService.h
@@ -299,16 +299,31 @@ private:
 
   /**
    * Create an accessible whose type depends on the given frame.
    */
   already_AddRefed<Accessible>
     CreateAccessibleByFrameType(nsIFrame* aFrame, nsIContent* aContent,
                                 Accessible* aContext);
 
+  /**
+   * Notify observers about change of the accessibility service's consumers.
+   */
+  void NotifyOfConsumersChange();
+
+  /**
+   * Set accessibility service consumers.
+   */
+  void SetConsumers(uint32_t aConsumers);
+
+  /**
+   * Unset accessibility service consumers.
+   */
+  void UnsetConsumers(uint32_t aConsumers);
+
 #ifdef MOZ_XUL
   /**
    * Create accessible for XUL tree element.
    */
   already_AddRefed<Accessible>
     CreateAccessibleForXULTree(nsIContent* aContent, DocAccessible* aDoc);
 #endif
 
--- a/accessible/tests/browser/browser_shutdown_remote_no_reference.js
+++ b/accessible/tests/browser/browser_shutdown_remote_no_reference.js
@@ -20,29 +20,47 @@ add_task(async function() {
       </html>`
   }, async function(browser) {
     info("Creating a service in parent and waiting for service to be created " +
       "in content");
     // Create a11y service in the main process. This will trigger creating of
     // the a11y service in parent as well.
     let parentA11yInit = initPromise();
     let contentA11yInit = initPromise(browser);
+    let parentConsumersChanged = a11yConsumersChangedPromise();
+    let contentConsumersChanged =
+      ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
     let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
       Ci.nsIAccessibilityService);
     ok(accService, "Service initialized in parent");
     await Promise.all([parentA11yInit, contentA11yInit]);
+    await parentConsumersChanged.then(data => Assert.deepEqual(data, {
+      XPCOM: true, MainProcess: false, PlatformAPI: false
+    }, "Accessibility service consumers in parent are correct."));
+    await contentConsumersChanged.then(data => Assert.deepEqual(data, {
+      XPCOM: false, MainProcess: true, PlatformAPI: false
+    }, "Accessibility service consumers in content are correct."));
 
     info("Removing a service in parent and waiting for service to be shut " +
       "down in content");
     // Remove a11y service reference in the main process.
     let parentA11yShutdown = shutdownPromise();
     let contentA11yShutdown = shutdownPromise(browser);
+    parentConsumersChanged = a11yConsumersChangedPromise();
+    contentConsumersChanged =
+      ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
     accService = null;
     ok(!accService, "Service is removed in parent");
     // Force garbage collection that should trigger shutdown in both main and
     // content process.
     forceGC();
     await Promise.all([parentA11yShutdown, contentA11yShutdown]);
+    await parentConsumersChanged.then(data => Assert.deepEqual(data, {
+      XPCOM: false, MainProcess: false, PlatformAPI: false
+    }, "Accessibility service consumers are correct."));
+    await contentConsumersChanged.then(data => Assert.deepEqual(data, {
+      XPCOM: false, MainProcess: false, PlatformAPI: false
+    }, "Accessibility service consumers are correct."));
   });
 
   // Unsetting e10s related preferences.
   await unsetE10sPrefs();
 });
--- a/accessible/tests/browser/browser_shutdown_remote_own_reference.js
+++ b/accessible/tests/browser/browser_shutdown_remote_own_reference.js
@@ -20,56 +20,76 @@ add_task(async function() {
       </html>`
   }, async function(browser) {
     info("Creating a service in parent and waiting for service to be created " +
       "in content");
     // Create a11y service in the main process. This will trigger creating of
     // the a11y service in parent as well.
     let parentA11yInit = initPromise();
     let contentA11yInit = initPromise(browser);
+    let contentConsumersChanged =
+      ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
     let accService = Cc["@mozilla.org/accessibilityService;1"].getService(
       Ci.nsIAccessibilityService);
     ok(accService, "Service initialized in parent");
     await Promise.all([parentA11yInit, contentA11yInit]);
+    await contentConsumersChanged.then(data => Assert.deepEqual(data, {
+      XPCOM: false, MainProcess: true, PlatformAPI: false
+    }, "Accessibility service consumers in content are correct."));
 
     info("Adding additional reference to accessibility service in content " +
       "process");
+    contentConsumersChanged =
+      ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
     // Add a new reference to the a11y service inside the content process.
     loadFrameScripts(browser, `let accService = Components.classes[
       '@mozilla.org/accessibilityService;1'].getService(
         Components.interfaces.nsIAccessibilityService);`);
+    await contentConsumersChanged.then(data => Assert.deepEqual(data, {
+      XPCOM: true, MainProcess: true, PlatformAPI: false
+    }, "Accessibility service consumers in content are correct."));
 
     info("Shutting down a service in parent and making sure the one in " +
       "content stays alive");
     let contentCanShutdown = false;
     let parentA11yShutdown = shutdownPromise();
+    contentConsumersChanged =
+      ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
     // This promise will resolve only if contentCanShutdown flag is set to true.
     // If 'a11y-init-or-shutdown' event with '0' flag (in content) comes before
     // it can be shut down, the promise will reject.
     let contentA11yShutdown = new Promise((resolve, reject) =>
       shutdownPromise(browser).then(flag => contentCanShutdown ?
         resolve() : reject("Accessible service was shut down incorrectly")));
     // Remove a11y service reference in the main process and force garbage
     // collection. This should not trigger shutdown in content since a11y
     // service is used by XPCOM.
     accService = null;
     ok(!accService, "Service is removed in parent");
     // Force garbage collection that should not trigger shutdown because there
     // is a reference in a content process.
     forceGC();
     loadFrameScripts(browser, `Components.utils.forceGC();`);
     await parentA11yShutdown;
+    await contentConsumersChanged.then(data => Assert.deepEqual(data, {
+      XPCOM: true, MainProcess: false, PlatformAPI: false
+    }, "Accessibility service consumers in content are correct."));
 
     // Have some breathing room between a11y service shutdowns.
     await new Promise(resolve => executeSoon(resolve));
 
     info("Removing a service in content");
     // Now allow a11y service to shutdown in content.
     contentCanShutdown = true;
+    contentConsumersChanged =
+      ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
     // Remove last reference to a11y service in content and force garbage
     // collection that should trigger shutdown.
     loadFrameScripts(browser, `accService = null; Components.utils.forceGC();`);
     await contentA11yShutdown;
+    await contentConsumersChanged.then(data => Assert.deepEqual(data, {
+      XPCOM: false, MainProcess: false, PlatformAPI: false
+    }, "Accessibility service consumers in content are correct."));
 
     // Unsetting e10s related preferences.
     await unsetE10sPrefs();
   });
 });
--- a/accessible/tests/browser/head.js
+++ b/accessible/tests/browser/head.js
@@ -33,16 +33,30 @@ function unsetE10sPrefs() {
 
 // Load the shared-head file first.
 /* import-globals-from shared-head.js */
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
   this);
 
 /**
+ * Returns a promise that resolves when 'a11y-consumers-changed' event is fired.
+ * @return {Promise} event promise evaluating to event's data
+ */
+function a11yConsumersChangedPromise() {
+  return new Promise(resolve => {
+    let observe = (subject, topic, data) => {
+      Services.obs.removeObserver(observe, "a11y-consumers-changed");
+      resolve(JSON.parse(data));
+    };
+    Services.obs.addObserver(observe, "a11y-consumers-changed");
+  });
+}
+
+/**
  * Returns a promise that resolves when 'a11y-init-or-shutdown' event is fired.
  * @return {Promise} event promise evaluating to event's data
  */
 function a11yInitOrShutdownPromise() {
   return new Promise(resolve => {
     let observe = (subject, topic, data) => {
       Services.obs.removeObserver(observe, "a11y-init-or-shutdown");
       resolve(data);