Bug 527003 - make sure a11y shutdown and start work with e10s. When accessibility service is requested, created, or shutdown, indicate if it is done by parent process, platform API or XPCOM. r=surkov, tbsaunde draft
authorYura Zenevich <yzenevich@mozilla.com>
Mon, 29 Aug 2016 11:23:11 -0400
changeset 406793 06a22b5282f744e3bb9b984fc194c9cedba4e823
parent 406727 4f72b1d0526767db87007ed8f00f07cf90e49443
child 406794 da26071d494c0e5f668eef21fe11829d5effe4ab
push id27829
push useryura.zenevich@gmail.com
push dateMon, 29 Aug 2016 15:23:40 +0000
reviewerssurkov, tbsaunde
bugs527003
milestone51.0a1
Bug 527003 - make sure a11y shutdown and start work with e10s. When accessibility service is requested, created, or shutdown, indicate if it is done by parent process, platform API or XPCOM. r=surkov, tbsaunde MozReview-Commit-ID: KF5d6xaO83E
accessible/base/nsAccessibilityService.cpp
accessible/base/nsAccessibilityService.h
accessible/xpcom/xpcAccessibilityService.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/PContent.ipdl
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -258,27 +258,26 @@ static const MarkupMapInfo sMarkupMapLis
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessibilityService
 ////////////////////////////////////////////////////////////////////////////////
 
 nsAccessibilityService *nsAccessibilityService::gAccessibilityService = nullptr;
 ApplicationAccessible* nsAccessibilityService::gApplicationAccessible = nullptr;
 xpcAccessibleApplication* nsAccessibilityService::gXPCApplicationAccessible = nullptr;
-bool nsAccessibilityService::gIsShutdown = true;
-bool nsAccessibilityService::gIsPlatformCaller = false;
+uint32_t nsAccessibilityService::gConsumers = 0;
 
 nsAccessibilityService::nsAccessibilityService() :
   DocManager(), FocusManager(), mMarkupMaps(ArrayLength(sMarkupMapList))
 {
 }
 
 nsAccessibilityService::~nsAccessibilityService()
 {
-  NS_ASSERTION(gIsShutdown, "Accessibility wasn't shutdown!");
+  NS_ASSERTION(!gConsumers, "Accessibility wasn't shutdown!");
   gAccessibilityService = nullptr;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIListenerChangeListener
 
 NS_IMETHODIMP
 nsAccessibilityService::ListenersChanged(nsIArray* aEventChanges)
@@ -952,17 +951,17 @@ nsAccessibilityService::GetStringRelatio
 
 Accessible*
 nsAccessibilityService::CreateAccessible(nsINode* aNode,
                                          Accessible* aContext,
                                          bool* aIsSubtreeHidden)
 {
   MOZ_ASSERT(aContext, "No context provided");
   MOZ_ASSERT(aNode, "No node to create an accessible for");
-  MOZ_ASSERT(!gIsShutdown, "No creation after shutdown");
+  MOZ_ASSERT(gConsumers, "No creation after shutdown");
 
   if (aIsSubtreeHidden)
     *aIsSubtreeHidden = false;
 
   DocAccessible* document = aContext->Document();
   MOZ_ASSERT(!document->GetAccessible(aNode),
              "We already have an accessible for this node.");
 
@@ -1279,18 +1278,16 @@ nsAccessibilityService::Init()
                         NS_LITERAL_CSTRING("Active"));
 #endif
 
 #ifdef XP_WIN
   sPendingPlugins = new nsTArray<nsCOMPtr<nsIContent> >;
   sPluginTimers = new nsTArray<nsCOMPtr<nsITimer> >;
 #endif
 
-  gIsShutdown = false;
-
   // Now its safe to start platform accessibility.
   if (XRE_IsParentProcess())
     PlatformInit();
 
   statistics::A11yInitialized();
 
   return true;
 }
@@ -1298,19 +1295,19 @@ nsAccessibilityService::Init()
 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(!gIsShutdown, "Accessibility was shutdown already");
+  MOZ_ASSERT(gConsumers, "Accessibility was shutdown already");
 
-  gIsShutdown = true;
+  gConsumers = 0;
 
   // 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 };
@@ -1339,17 +1336,16 @@ nsAccessibilityService::Shutdown()
   NS_RELEASE(gApplicationAccessible);
   gApplicationAccessible = nullptr;
 
   NS_IF_RELEASE(gXPCApplicationAccessible);
   gXPCApplicationAccessible = nullptr;
 
   NS_RELEASE(gAccessibilityService);
   gAccessibilityService = nullptr;
-  gIsPlatformCaller = false;
 }
 
 already_AddRefed<Accessible>
 nsAccessibilityService::CreateAccessibleByType(nsIContent* aContent,
                                                DocAccessible* aDoc)
 {
   nsAutoString role;
   nsCoreUtils::XBLBindingRole(aContent, role);
@@ -1773,45 +1769,51 @@ nsAccessibilityService::CreateAccessible
   // Table or tree table accessible.
   RefPtr<Accessible> accessible =
     new XULTreeGridAccessibleWrap(aContent, aDoc, treeFrame);
   return accessible.forget();
 }
 #endif
 
 nsAccessibilityService*
-GetOrCreateAccService(bool aIsPlatformCaller)
+GetOrCreateAccService(uint32_t aRequestedBy)
 {
-  if (aIsPlatformCaller) {
-    nsAccessibilityService::gIsPlatformCaller = aIsPlatformCaller;
-  }
-
   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 |= aRequestedBy;
   return nsAccessibilityService::gAccessibilityService;
 }
 
-bool
-CanShutdownAccService()
+void
+MaybeShutdownAccService(uint32_t aRequestedBy)
 {
   nsAccessibilityService* accService = nsAccessibilityService::gAccessibilityService;
-  if (!accService) {
-    return false;
+  if (!accService ||
+      accService->IsShutdown() ||
+      !(nsAccessibilityService::gConsumers & aRequestedBy)) {
+    return;
   }
-  return !xpcAccessibilityService::IsInUse() &&
-         !accService->IsPlatformCaller() && !accService->IsShutdown() &&
-         !nsCoreUtils::AccEventObserversExist();
+
+  if (!nsCoreUtils::AccEventObserversExist() &&
+      !xpcAccessibilityService::IsInUse()) {
+    accService->Shutdown(); // Will unset all nsAccessibilityService::gConsumers
+    return;
+  }
+
+  if (aRequestedBy & nsAccessibilityService::eMainProcess) {
+    nsAccessibilityService::gConsumers &= ~nsAccessibilityService::eMainProcess;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Services
 ////////////////////////////////////////////////////////////////////////////////
 
 namespace mozilla {
 namespace a11y {
--- a/accessible/base/nsAccessibilityService.h
+++ b/accessible/base/nsAccessibilityService.h
@@ -190,22 +190,19 @@ public:
 
   void FireAccessibleEvent(uint32_t aEvent, Accessible* aTarget);
 
   // nsAccessibiltiyService
 
   /**
    * Return true if accessibility service has been shutdown.
    */
-  static bool IsShutdown() { return gIsShutdown; }
-
-  /**
-   * Return true if accessibility service has been initialized by platform.
-   */
-  static bool IsPlatformCaller() { return gIsPlatformCaller; };
+  static bool IsShutdown() {
+    return gConsumers == 0;
+  };
 
   /**
    * Creates an accessible for the given DOM node.
    *
    * @param  aNode             [in] the given node
    * @param  aContext          [in] context the accessible is created in
    * @param  aIsSubtreeHidden  [out, optional] indicates whether the node's
    *                             frame and its subtree is hidden
@@ -221,16 +218,39 @@ public:
   }
 
   /**
    * Set the object attribute defined by markup for the given element.
    */
   void MarkupAttributes(const nsIContent* aContent,
                         nsIPersistentProperties* aAttributes) const;
 
+  /**
+   * A list of possible accessibility service consumers. Accessibility service
+   * can be shut down by a consumer only when it is exclusively used by that
+   * consumer.
+   *
+   * eXPCOM       - accessibility service is used by XPCOM. It can only be shut
+   *                down if it's only used by XPCOM and not by main process
+   *                and/or platform API.
+   *
+   * eMainProcess - accessibility service was started by main process in the
+   *                content process. It can be shut down by main process if it's
+   *                not also used by XPCOM.
+   *
+   * ePlatformAPI - accessibility service is used by the platform api in the
+   *                main process. It can never can never be shut down.
+   */
+  enum ServiceConsumer
+  {
+    eXPCOM       = 1 << 0,
+    eMainProcess = 1 << 1,
+    ePlatformAPI = 1 << 2,
+  };
+
 private:
   // nsAccessibilityService creation is controlled by friend
   // GetOrCreateAccService, keep constructors private.
   nsAccessibilityService();
   nsAccessibilityService(const nsAccessibilityService&);
   nsAccessibilityService& operator =(const nsAccessibilityService&);
 
 private:
@@ -272,30 +292,25 @@ private:
 
   /**
    * Reference for application accessible instance.
    */
   static mozilla::a11y::ApplicationAccessible* gApplicationAccessible;
   static mozilla::a11y::xpcAccessibleApplication* gXPCApplicationAccessible;
 
   /**
-   * Indicates whether accessibility service was shutdown.
+   * Contains a list of accessibility service consumers.
    */
-  static bool gIsShutdown;
-
-  /**
-   * Indicates whether accessibility service was initialized by platform.
-   */
-  static bool gIsPlatformCaller;
+  static uint32_t gConsumers;
 
   nsDataHashtable<nsPtrHashKey<const nsIAtom>, const mozilla::a11y::MarkupMapInfo*> mMarkupMaps;
 
   friend nsAccessibilityService* GetAccService();
-  friend nsAccessibilityService* GetOrCreateAccService(bool);
-  friend bool CanShutdownAccService();
+  friend nsAccessibilityService* GetOrCreateAccService(uint32_t);
+  friend void MaybeShutdownAccService(uint32_t);
   friend mozilla::a11y::FocusManager* mozilla::a11y::FocusMgr();
   friend mozilla::a11y::SelectionManager* mozilla::a11y::SelectionMgr();
   friend mozilla::a11y::ApplicationAccessible* mozilla::a11y::ApplicationAcc();
   friend mozilla::a11y::xpcAccessibleApplication* mozilla::a11y::XPCApplicationAcc();
   friend class xpcAccessibilityService;
 };
 
 /**
@@ -305,22 +320,23 @@ inline nsAccessibilityService*
 GetAccService()
 {
   return nsAccessibilityService::gAccessibilityService;
 }
 
 /**
  * Return accessibility service instance; creating one if necessary.
  */
-nsAccessibilityService* GetOrCreateAccService(bool aIsPlatformCaller = true);
+nsAccessibilityService* GetOrCreateAccService(
+  uint32_t aRequestedBy = nsAccessibilityService::ePlatformAPI);
 
 /**
- * Return a flag indicating if accessibility service can be shutdown.
+ * Shutdown accessibility service if needed.
  */
-bool CanShutdownAccService();
+void MaybeShutdownAccService(uint32_t aRequestedBy);
 
 /**
  * Return true if we're in a content process and not B2G.
  */
 inline bool
 IPCAccessibilityActive()
 {
 #ifdef MOZ_B2G
--- a/accessible/xpcom/xpcAccessibilityService.cpp
+++ b/accessible/xpcom/xpcAccessibilityService.cpp
@@ -18,20 +18,17 @@ using namespace mozilla::dom;
 xpcAccessibilityService *xpcAccessibilityService::gXPCAccessibilityService = nullptr;
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsISupports
 
 void
 xpcAccessibilityService::ShutdownCallback(nsITimer* aTimer, void* aClosure)
 {
-  if (CanShutdownAccService()) {
-    GetAccService()->Shutdown();
-  }
-
+  MaybeShutdownAccService(nsAccessibilityService::eXPCOM);
   xpcAccessibilityService* xpcAccService =
     reinterpret_cast<xpcAccessibilityService*>(aClosure);
 
   if (xpcAccService->mShutdownTimer) {
     xpcAccService->mShutdownTimer->Cancel();
     xpcAccService->mShutdownTimer = nullptr;
   }
 }
@@ -42,17 +39,17 @@ xpcAccessibilityService::AddRef(void)
   MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(xpcAccessibilityService)
   MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
   if (!mRefCnt.isThreadSafe)
     NS_ASSERT_OWNINGTHREAD(xpcAccessibilityService);
   nsrefcnt count = ++mRefCnt;
   NS_LOG_ADDREF(this, count, "xpcAccessibilityService", sizeof(*this));
 
   if (mRefCnt > 1) {
-    GetOrCreateAccService(false);
+    GetOrCreateAccService(nsAccessibilityService::eXPCOM);
   }
 
   return count;
 }
 
 NS_IMETHODIMP_(MozExternalRefCountType)
 xpcAccessibilityService::Release(void)
 {
@@ -233,17 +230,17 @@ xpcAccessibilityService::IsLogged(const 
 ////////////////////////////////////////////////////////////////////////////////
 
 nsresult
 NS_GetAccessibilityService(nsIAccessibilityService** aResult)
 {
   NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER);
   *aResult = nullptr;
 
-  GetOrCreateAccService(false);
+  GetOrCreateAccService(nsAccessibilityService::eXPCOM);
 
   xpcAccessibilityService* service = new xpcAccessibilityService();
   NS_ENSURE_TRUE(service, NS_ERROR_OUT_OF_MEMORY);
   xpcAccessibilityService::gXPCAccessibilityService = service;
   NS_ADDREF(*aResult = service);
 
   return NS_OK;
 }
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2468,17 +2468,28 @@ ContentChild::RecvFlushMemory(const nsSt
 }
 
 bool
 ContentChild::RecvActivateA11y()
 {
 #ifdef ACCESSIBILITY
   // Start accessibility in content process if it's running in chrome
   // process.
-  GetOrCreateAccService();
+  GetOrCreateAccService(nsAccessibilityService::eMainProcess);
+#endif
+  return true;
+}
+
+bool
+ContentChild::RecvShutdownA11y()
+{
+#ifdef ACCESSIBILITY
+  // Try to shutdown accessibility in content process if it's shutting down in
+  // chrome process.
+  MaybeShutdownAccService(nsAccessibilityService::eMainProcess);
 #endif
   return true;
 }
 
 bool
 ContentChild::RecvGarbageCollect()
 {
   // Rebroadcast the "child-gc-request" so that workers will GC.
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -435,16 +435,17 @@ public:
 
   virtual bool RecvUpdateDictionaryList(InfallibleTArray<nsString>&& aDictionaries) override;
 
   virtual bool RecvAddPermission(const IPC::Permission& permission) override;
 
   virtual bool RecvFlushMemory(const nsString& reason) override;
 
   virtual bool RecvActivateA11y() override;
+  virtual bool RecvShutdownA11y() override;
 
   virtual bool RecvGarbageCollect() override;
   virtual bool RecvCycleCollect() override;
 
   virtual bool RecvAppInfo(const nsCString& version, const nsCString& buildID,
                            const nsCString& name, const nsCString& UAName,
                            const nsCString& ID, const nsCString& vendor) override;
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -270,16 +270,20 @@ using namespace mozilla::system;
 #ifdef XP_WIN
 #include "mozilla/widget/AudioSession.h"
 #endif
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsThread.h"
 #endif
 
+#ifdef ACCESSIBILITY
+#include "nsAccessibilityService.h"
+#endif
+
 // For VP9Benchmark::sBenchmarkFpsPref
 #include "Benchmark.h"
 
 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
 
 #if defined(XP_WIN)
 // e10s forced enable pref, defined in nsAppRunner.cpp
 extern const char* kForceEnableE10sPref;
@@ -2838,29 +2842,34 @@ ContentParent::Observe(nsISupports* aSub
     Unused << SendNotifyPhoneStateChange(state);
   }
   else if(!strcmp(aTopic, NS_VOLUME_REMOVED)) {
     nsString volName(aData);
     Unused << SendVolumeRemoved(volName);
   }
 #endif
 #ifdef ACCESSIBILITY
-  // Make sure accessibility is running in content process when accessibility
-  // gets initiated in chrome process.
-  else if (aData && (*aData == '1') &&
-       !strcmp(aTopic, "a11y-init-or-shutdown")) {
+  else if (aData && !strcmp(aTopic, "a11y-init-or-shutdown")) {
+    if (*aData == '1') {
+      // Make sure accessibility is running in content process when
+      // accessibility gets initiated in chrome process.
 #if !defined(XP_WIN)
-    Unused << SendActivateA11y();
+      Unused << SendActivateA11y();
 #else
-    // On Windows we currently only enable a11y in the content process
-    // for testing purposes.
-    if (Preferences::GetBool(kForceEnableE10sPref, false)) {
-      Unused << SendActivateA11y();
+      // On Windows we currently only enable a11y in the content process
+      // for testing purposes.
+      if (Preferences::GetBool(kForceEnableE10sPref, false)) {
+        Unused << SendActivateA11y();
+      }
+#endif
+    } else {
+      // If possible, shut down accessibility in content process when
+      // accessibility gets shutdown in chrome process.
+      Unused << SendShutdownA11y();
     }
-#endif
   }
 #endif
   else if (!strcmp(aTopic, "app-theme-changed")) {
     Unused << SendOnAppThemeChanged();
   }
 #ifdef MOZ_ENABLE_PROFILER_SPS
   else if (!strcmp(aTopic, "profiler-started")) {
     nsCOMPtr<nsIProfilerStartParams> params(do_QueryInterface(aSubject));
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -545,16 +545,21 @@ child:
     async GarbageCollect();
     async CycleCollect();
 
     /**
      * Start accessibility engine in content process.
      */
     async ActivateA11y();
 
+    /**
+     * Shutdown accessibility engine in content process (if not in use).
+     */
+    async ShutdownA11y();
+
     async AppInfo(nsCString version, nsCString buildID, nsCString name, nsCString UAName,
                   nsCString ID, nsCString vendor);
     async AppInit();
 
     /**
      * Send ServiceWorkerRegistrationData to child process.
      */
     async InitServiceWorkers(ServiceWorkerConfiguration aConfig);