Bug 1401980 - shutdown accessibility service on accessibliity.force_disable pref change. r=surkov
☠☠ backed out by 2d726fc06e2f ☠ ☠
authorYura Zenevich <yura.zenevich@gmail.com>
Mon, 25 Sep 2017 13:02:57 -0400
changeset 696782 72f9e62daa10025ea10b70a31ec2d92d971b8565
parent 696781 52625c644b7cb10f125df9968d7e9dcc16cd86ff
child 696783 8b807149a0db4ee0d1fc5631afaa74a8a42b87a5
push id88784
push usercholler@mozilla.com
push dateSat, 11 Nov 2017 10:21:53 +0000
reviewerssurkov
bugs1401980
milestone58.0a1
Bug 1401980 - shutdown accessibility service on accessibliity.force_disable pref change. r=surkov MozReview-Commit-ID: 8YfCaoB5Stt
accessible/base/nsAccessibilityService.cpp
accessible/base/nsAccessibilityService.h
accessible/tests/browser/browser.ini
accessible/tests/browser/browser_shutdown_pref.js
accessible/xpcom/xpcAccessibilityService.cpp
widget/cocoa/nsChildView.mm
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -21,17 +21,16 @@
 #include "HyperTextAccessibleWrap.h"
 #include "RootAccessible.h"
 #include "nsAccUtils.h"
 #include "nsArrayUtils.h"
 #include "nsAttrName.h"
 #include "nsEventShell.h"
 #include "nsIURI.h"
 #include "OuterDocAccessible.h"
-#include "Platform.h"
 #include "Role.h"
 #ifdef MOZ_ACCESSIBILITY_ATK
 #include "RootAccessibleWrap.h"
 #endif
 #include "States.h"
 #include "Statistics.h"
 #include "TextLeafAccessibleWrap.h"
 #include "TreeWalker.h"
@@ -90,16 +89,25 @@
 #if defined(XP_WIN) || defined(MOZ_ACCESSIBILITY_ATK)
 #include "nsNPAPIPluginInstance.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 using namespace mozilla::dom;
 
+/**
+ * Accessibility service force enable/disable preference.
+ * Supported values:
+ *   Accessibility is force enabled (accessibility should always be enabled): -1
+ *   Accessibility is enabled (will be started upon a request, default value): 0
+ *   Accessibility is force disabled (never enable accessibility):             1
+ */
+#define PREF_ACCESSIBILITY_FORCE_DISABLED "accessibility.force_disabled"
+
 ////////////////////////////////////////////////////////////////////////////////
 // Statics
 ////////////////////////////////////////////////////////////////////////////////
 
 /**
  * Return true if the element must be accessible.
  */
 static bool
@@ -263,16 +271,21 @@ New_MaybeImageOrToolbarButtonAccessible(
   if (!aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::tooltiptext)) {
     return nullptr;
   }
 
   return new ImageAccessibleWrap(aContent, aContext->Document());
 }
 #endif
 
+/**
+ * Cached value of the PREF_ACCESSIBILITY_FORCE_DISABLED preference.
+ */
+static int32_t sPlatformDisabledState = 0;
+
 ////////////////////////////////////////////////////////////////////////////////
 // Markup maps array.
 
 #define Attr(name, value) \
   { &nsGkAtoms::name, &nsGkAtoms::value }
 
 #define AttrFromDOM(name, DOMAttrName) \
   { &nsGkAtoms::name, nullptr, &nsGkAtoms::DOMAttrName }
@@ -1923,23 +1936,45 @@ XPCApplicationAcc()
   }
 
   return nsAccessibilityService::gXPCApplicationAccessible;
 }
 
 EPlatformDisabledState
 PlatformDisabledState()
 {
-  static int disabledState = 0xff;
-
-  if (disabledState == 0xff) {
-    disabledState = Preferences::GetInt("accessibility.force_disabled", 0);
-    if (disabledState < ePlatformIsForceEnabled)
-      disabledState = ePlatformIsForceEnabled;
-    else if (disabledState > ePlatformIsDisabled)
-      disabledState = ePlatformIsDisabled;
+  static bool platformDisabledStateCached = false;
+  if (platformDisabledStateCached) {
+    return static_cast<EPlatformDisabledState>(sPlatformDisabledState);
   }
 
-  return (EPlatformDisabledState)disabledState;
+  platformDisabledStateCached = true;
+  Preferences::RegisterCallback(PrefChanged, PREF_ACCESSIBILITY_FORCE_DISABLED);
+  return ReadPlatformDisabledState();
+}
+
+EPlatformDisabledState
+ReadPlatformDisabledState()
+{
+  sPlatformDisabledState = Preferences::GetInt(PREF_ACCESSIBILITY_FORCE_DISABLED, 0);
+  if (sPlatformDisabledState < ePlatformIsForceEnabled) {
+    sPlatformDisabledState = ePlatformIsForceEnabled;
+  } else if (sPlatformDisabledState > ePlatformIsDisabled){
+    sPlatformDisabledState = ePlatformIsDisabled;
+  }
+
+  return static_cast<EPlatformDisabledState>(sPlatformDisabledState);
+}
+
+void
+PrefChanged(const char* aPref, void* aClosure)
+{
+  if (ReadPlatformDisabledState() == ePlatformIsDisabled) {
+    // Force shut down accessibility.
+    nsAccessibilityService* accService = nsAccessibilityService::gAccessibilityService;
+    if (accService && !accService->IsShutdown()) {
+      accService->Shutdown();
+    }
+  }
 }
 
 }
 }
--- a/accessible/base/nsAccessibilityService.h
+++ b/accessible/base/nsAccessibilityService.h
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __nsAccessibilityService_h__
 #define __nsAccessibilityService_h__
 
 #include "mozilla/a11y/DocManager.h"
 #include "mozilla/a11y/FocusManager.h"
+#include "mozilla/a11y/Platform.h"
 #include "mozilla/a11y/Role.h"
 #include "mozilla/a11y/SelectionManager.h"
 #include "mozilla/Preferences.h"
 
 #include "nsIObserver.h"
 #include "nsIAccessibleEvent.h"
 #include "nsIEventListenerService.h"
 #include "xpcAccessibilityService.h"
@@ -69,16 +70,26 @@ struct MarkupMapInfo {
 
 #ifdef MOZ_XUL
 struct XULMarkupMapInfo {
   nsStaticAtom** tag;
   New_Accessible* new_func;
 };
 #endif
 
+/**
+ * PREF_ACCESSIBILITY_FORCE_DISABLED preference change callback.
+ */
+void PrefChanged(const char* aPref, void* aClosure);
+
+/**
+ * Read and normalize PREF_ACCESSIBILITY_FORCE_DISABLED preference.
+ */
+EPlatformDisabledState ReadPlatformDisabledState();
+
 } // namespace a11y
 } // namespace mozilla
 
 class nsAccessibilityService final : public mozilla::a11y::DocManager,
                                      public mozilla::a11y::FocusManager,
                                      public mozilla::a11y::SelectionManager,
                                      public nsIListenerChangeListener,
                                      public nsIObserver
@@ -320,16 +331,17 @@ private:
   nsDataHashtable<nsPtrHashKey<const nsAtom>, const mozilla::a11y::MarkupMapInfo*> mMarkupMaps;
 #ifdef MOZ_XUL
   nsDataHashtable<nsPtrHashKey<const nsAtom>, const mozilla::a11y::XULMarkupMapInfo*> mXULMarkupMaps;
 #endif
 
   friend nsAccessibilityService* GetAccService();
   friend nsAccessibilityService* GetOrCreateAccService(uint32_t);
   friend void MaybeShutdownAccService(uint32_t);
+  friend void mozilla::a11y::PrefChanged(const char*, void*);
   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;
 };
 
 /**
--- a/accessible/tests/browser/browser.ini
+++ b/accessible/tests/browser/browser.ini
@@ -7,16 +7,17 @@ support-files =
 
 [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 || (os == 'win' && os_version == '5.1') # e10s specific test for a11y start/shutdown between parent and content.
+[browser_shutdown_pref.js]
 [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.
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_shutdown_pref.js
@@ -0,0 +1,66 @@
+/* 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";
+
+const PREF_ACCESSIBILITY_FORCE_DISABLED = "accessibility.force_disabled";
+const NS_ERROR_SERVICE_NOT_AVAILABLE = "NS_ERROR_XPC_GS_RETURNED_FAILURE";
+
+add_task(async function testForceDisable() {
+  ok(!Services.appinfo.accessibilityEnabled, "Accessibility is disabled by default");
+
+  let accService;
+  info("Force disabling a11y service via preference and trying to enable is via XPCOM");
+  Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, 1);
+  try {
+    accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+      Ci.nsIAccessibilityService);
+    ok(false, "Getting accessibility service should've triggered an exception")
+  } catch (e) {
+    is(e.name, NS_ERROR_SERVICE_NOT_AVAILABLE, "Getting accessibility service triggered an exception as expected")
+  }
+  info("Reset force disabled preference");
+  Services.prefs.clearUserPref(PREF_ACCESSIBILITY_FORCE_DISABLED);
+
+  info("Enable accessibility service via XPCOM");
+  let a11yInit = initPromise();
+  accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+    Ci.nsIAccessibilityService);
+  await a11yInit;
+  ok(Services.appinfo.accessibilityEnabled, "Accessibility is enabled");
+
+  info("Force disable a11y service via preference");
+  let a11yShutdown = shutdownPromise();
+  Services.prefs.setIntPref(PREF_ACCESSIBILITY_FORCE_DISABLED, 1);
+  await a11yShutdown;
+  ok(!Services.appinfo.accessibilityEnabled, "Accessibility is disabled");
+
+  info("Attempt to get an instance of a11y service and call its method.");
+  accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+    Ci.nsIAccessibilityService);
+  try {
+    accService.getAccesssibleFor(document);
+    ok(false, "getAccesssibleFor should've triggered an exception.")
+  } catch (e) {
+    ok(true, "getAccesssibleFor triggers an exception as a11y service is shutdown.")
+  }
+  ok(!Services.appinfo.accessibilityEnabled, "Accessibility is disabled");
+
+  info("Reset force disabled preference");
+  Services.prefs.clearUserPref(PREF_ACCESSIBILITY_FORCE_DISABLED);
+
+  info("Create a11y service again");
+  a11yInit = initPromise();
+  accService = Cc["@mozilla.org/accessibilityService;1"].getService(
+    Ci.nsIAccessibilityService);
+  await a11yInit;
+  ok(Services.appinfo.accessibilityEnabled, "Accessibility is enabled");
+
+  info("Remove all references to a11y service");
+  a11yShutdown = shutdownPromise();
+  accService = null;
+  forceGC();
+  await a11yShutdown;
+  ok(!Services.appinfo.accessibilityEnabled, "Accessibility is disabled");
+});
--- a/accessible/xpcom/xpcAccessibilityService.cpp
+++ b/accessible/xpcom/xpcAccessibilityService.cpp
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "xpcAccessibilityService.h"
 
 #include "nsAccessiblePivot.h"
 #include "nsAccessibilityService.h"
+#include "Platform.h"
 
 #ifdef A11Y_LOG
 #include "Logging.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 using namespace mozilla::dom;
@@ -38,17 +39,19 @@ 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) {
+  // We want refcount to be > 1 because one reference is added in the XPCOM
+  // accessibility service getter.
+  if (mRefCnt > 1 && PlatformDisabledState() != ePlatformIsDisabled) {
     GetOrCreateAccService(nsAccessibilityService::eXPCOM);
   }
 
   return count;
 }
 
 NS_IMETHODIMP_(MozExternalRefCountType)
 xpcAccessibilityService::Release(void)
@@ -109,52 +112,77 @@ xpcAccessibilityService::GetAccessibleFo
     return NS_OK;
   }
 
   nsCOMPtr<nsINode> node(do_QueryInterface(aNode));
   if (!node) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  DocAccessible* document = GetAccService()->GetDocAccessible(node->OwnerDoc());
+  nsAccessibilityService* accService = GetAccService();
+  if (!accService) {
+    return NS_ERROR_SERVICE_NOT_AVAILABLE;
+  }
+
+  DocAccessible* document = accService->GetDocAccessible(node->OwnerDoc());
   if (document) {
     NS_IF_ADDREF(*aAccessible = ToXPC(document->GetAccessible(node)));
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessibilityService::GetStringRole(uint32_t aRole, nsAString& aString)
 {
-  GetAccService()->GetStringRole(aRole, aString);
+  nsAccessibilityService* accService = GetAccService();
+  if (!accService) {
+    return NS_ERROR_SERVICE_NOT_AVAILABLE;
+  }
+
+  accService->GetStringRole(aRole, aString);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessibilityService::GetStringStates(uint32_t aState, uint32_t aExtraState,
                                          nsISupports **aStringStates)
 {
-  GetAccService()->GetStringStates(aState, aExtraState, aStringStates);
+  nsAccessibilityService* accService = GetAccService();
+  if (!accService) {
+    return NS_ERROR_SERVICE_NOT_AVAILABLE;
+  }
+
+  accService->GetStringStates(aState, aExtraState, aStringStates);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessibilityService::GetStringEventType(uint32_t aEventType,
                                             nsAString& aString)
 {
-  GetAccService()->GetStringEventType(aEventType, aString);
+  nsAccessibilityService* accService = GetAccService();
+  if (!accService) {
+    return NS_ERROR_SERVICE_NOT_AVAILABLE;
+  }
+
+  accService->GetStringEventType(aEventType, aString);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessibilityService::GetStringRelationType(uint32_t aRelationType,
                                                nsAString& aString)
 {
-  GetAccService()->GetStringRelationType(aRelationType, aString);
+  nsAccessibilityService* accService = GetAccService();
+  if (!accService) {
+    return NS_ERROR_SERVICE_NOT_AVAILABLE;
+  }
+
+  accService->GetStringRelationType(aRelationType, aString);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessibilityService::GetAccessibleFromCache(nsIDOMNode* aNode,
                                                 nsIAccessible** aAccessible)
 {
   NS_ENSURE_ARG_POINTER(aAccessible);
@@ -163,23 +191,28 @@ xpcAccessibilityService::GetAccessibleFr
     return NS_OK;
   }
 
   nsCOMPtr<nsINode> node(do_QueryInterface(aNode));
   if (!node) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  nsAccessibilityService* accService = GetAccService();
+  if (!accService) {
+    return NS_ERROR_SERVICE_NOT_AVAILABLE;
+  }
+
   // Search for an accessible in each of our per document accessible object
   // caches. If we don't find it, and the given node is itself a document, check
   // our cache of document accessibles (document cache). Note usually shutdown
   // document accessibles are not stored in the document cache, however an
   // "unofficially" shutdown document (i.e. not from DocManager) can still
   // exist in the document cache.
-  Accessible* accessible = GetAccService()->FindAccessibleInCache(node);
+  Accessible* accessible = accService->FindAccessibleInCache(node);
   if (!accessible) {
     nsCOMPtr<nsIDocument> document(do_QueryInterface(node));
     if (document) {
       accessible = mozilla::a11y::GetExistingDocAccessible(document);
     }
   }
 
   NS_IF_ADDREF(*aAccessible = ToXPC(accessible));
@@ -230,16 +263,21 @@ xpcAccessibilityService::IsLogged(const 
 ////////////////////////////////////////////////////////////////////////////////
 
 nsresult
 NS_GetAccessibilityService(nsIAccessibilityService** aResult)
 {
   NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER);
   *aResult = nullptr;
 
+  // Do not initialize accessibility if it is force disabled.
+  if (PlatformDisabledState() == ePlatformIsDisabled) {
+    return NS_ERROR_SERVICE_NOT_AVAILABLE;
+  }
+
   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/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -3098,17 +3098,19 @@ nsChildView::LookUpDictionary(
 
 #ifdef ACCESSIBILITY
 already_AddRefed<a11y::Accessible>
 nsChildView::GetDocumentAccessible()
 {
   if (!mozilla::a11y::ShouldA11yBeEnabled())
     return nullptr;
 
-  if (mAccessible) {
+  // mAccessible might be dead if accessibility was previously disabled and is
+  // now being enabled again.
+  if (mAccessible && mAccessible->IsAlive()) {
     RefPtr<a11y::Accessible> ret;
     CallQueryReferent(mAccessible.get(),
                       static_cast<a11y::Accessible**>(getter_AddRefs(ret)));
     return ret.forget();
   }
 
   // need to fetch the accessible anew, because it has gone away.
   // cache the accessible in our weak ptr