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 442201 72f9e62daa10025ea10b70a31ec2d92d971b8565
parent 442200 52625c644b7cb10f125df9968d7e9dcc16cd86ff
child 442202 8b807149a0db4ee0d1fc5631afaa74a8a42b87a5
push id8138
push userryanvm@gmail.com
push dateSat, 11 Nov 2017 14:08:45 +0000
treeherdermozilla-beta@9d19d489729b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssurkov
bugs1401980
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 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