Bug 1234675 - Ability to disable e10s for users with addons. r=Mossop, a=sylvestre FIREFOX_BETA_45_BASE
authorGabor Krizsanits <gkrizsanits@mozilla.com>
Fri, 22 Jan 2016 10:10:17 +0100
changeset 310996 3bfa5bc61b626761d487b45c170b115259f69d6b
parent 310995 bb50b210ccf3b33ef44b5e2ea66b27f620fae633
child 310997 d82221ba4219e4ac04ecfe2a5301703411e176fa
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMossop, sylvestre
bugs1234675
milestone45.0a2
Bug 1234675 - Ability to disable e10s for users with addons. r=Mossop, a=sylvestre
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/internal/XPIProviderUtils.js
toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js
toolkit/xre/nsAppRunner.cpp
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -4234,16 +4234,43 @@ this.XPIProvider = {
       case PREF_XPI_SIGNATURES_REQUIRED:
         this.updateAddonAppDisabledStates();
         break;
       }
     }
   },
 
   /**
+   * Determine if an add-on should be blocking e10s if enabled.
+   *
+   * @param  aAddon
+   *         The add-on to test
+   * @return true if enabling the add-on should block e10s
+   */
+  isBlockingE10s: function(aAddon) {
+    // Only extensions change behaviour
+    if (aAddon.type != "extension")
+      return false;
+
+    // The hotfix is exempt
+    let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined);
+    if (hotfixID && hotfixID == aAddon.id)
+      return false;
+
+    // System add-ons are exempt
+    let locName = aAddon._installLocation ? aAddon._installLocation.name
+                                          : undefined;
+    if (locName == KEY_APP_SYSTEM_DEFAULTS ||
+        locName == KEY_APP_SYSTEM_ADDONS)
+      return false;
+
+    return true;
+  },
+
+  /**
    * In some cases having add-ons active blocks e10s but turning off e10s
    * requires a restart so some add-ons that are normally restartless will
    * require a restart to install or enable.
    *
    * @param  aAddon
    *         The add-on to test
    * @return true if enabling the add-on requires a restart
    */
@@ -4251,31 +4278,17 @@ this.XPIProvider = {
     // If the preference isn't set then don't block anything
     if (!Preferences.get(PREF_E10S_BLOCK_ENABLE, false))
       return false;
 
     // If e10s isn't active then don't block anything
     if (!Services.appinfo.browserTabsRemoteAutostart)
       return false;
 
-    // Only extensions change behaviour
-    if (aAddon.type != "extension")
-      return false;
-
-    // The hotfix is exempt
-    let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined);
-    if (hotfixID && hotfixID == aAddon.id)
-      return false;
-
-    // System add-ons are exempt
-    if (aAddon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS ||
-        aAddon._installLocation.name == KEY_APP_SYSTEM_ADDONS)
-      return false;
-
-    return true;
+    return this.isBlockingE10s(aAddon);
   },
 
   /**
    * Tests whether enabling an add-on will require a restart.
    *
    * @param  aAddon
    *         The add-on to test
    * @return true if the operation requires a restart
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -43,16 +43,17 @@ const FILE_XPI_ADDONS_LIST            = 
 
 // The last version of DB_SCHEMA implemented in SQLITE
 const LAST_SQLITE_DB_SCHEMA           = 14;
 const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
 const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
 const PREF_EM_ENABLED_ADDONS          = "extensions.enabledAddons";
 const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
 const PREF_EM_AUTO_DISABLED_SCOPES    = "extensions.autoDisableScopes";
+const PREF_E10S_BLOCKED_BY_ADDONS     = "extensions.e10sBlockedByAddons";
 
 const KEY_APP_PROFILE                 = "app-profile";
 const KEY_APP_SYSTEM_ADDONS           = "app-system-addons";
 const KEY_APP_SYSTEM_DEFAULTS         = "app-system-defaults";
 const KEY_APP_GLOBAL                  = "app-global";
 
 // Properties that only exist in the database
 const DB_METADATA        = ["syncGUID",
@@ -455,16 +456,17 @@ this.XPIDatabase = {
     }
 
     if (!this._deferredSave) {
       this._deferredSave = new DeferredSave(this.jsonFile.path,
                                             () => JSON.stringify(this),
                                             ASYNC_SAVE_DELAY_MS);
     }
 
+    this.updateAddonsBlockingE10s();
     let promise = this._deferredSave.saveChanges();
     if (!this._schemaVersionSet) {
       this._schemaVersionSet = true;
       promise = promise.then(
         count => {
           // Update the XPIDB schema version preference the first time we successfully
           // save the database.
           logger.debug("XPI Database saved, setting schema version preference to " + DB_SCHEMA);
@@ -1384,16 +1386,27 @@ this.XPIDatabase = {
    */
   updateAddonActive: function(aAddon, aActive) {
     logger.debug("Updating active state for add-on " + aAddon.id + " to " + aActive);
 
     aAddon.active = aActive;
     this.saveChanges();
   },
 
+  updateAddonsBlockingE10s: function() {
+    let blockE10s = false;
+    for (let [, addon] of this.addonDB) {
+      let active = (addon.visible && !addon.disabled && !addon.pendingUninstall);
+
+      if (active && XPIProvider.isBlockingE10s(addon))
+        blockE10s = true;
+    }
+    Preferences.set(PREF_E10S_BLOCKED_BY_ADDONS, blockE10s);
+  },
+
   /**
    * Synchronously calculates and updates all the active flags in the database.
    */
   updateActiveAddons: function() {
     if (!this.addonDB) {
       logger.warn("updateActiveAddons called when DB isn't loaded");
       // force the DB to load
       AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_updateActive",
--- a/toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js
@@ -1,13 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 const ID = "bootstrap1@tests.mozilla.org";
+const ID2 = "bootstrap2@tests.mozilla.org";
 
 BootstrapMonitor.init();
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
 startupManager();
 
 function* check_normal() {
@@ -110,16 +111,180 @@ add_task(function*() {
   do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL));
   addon.uninstall();
   BootstrapMonitor.checkAddonNotStarted(ID);
   BootstrapMonitor.checkAddonNotInstalled(ID);
 
   restartManager();
 });
 
+add_task(function*() {
+  gAppInfo.browserTabsRemoteAutostart = true;
+  Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
+
+  let install = yield new Promise(resolve => AddonManager.getInstallForFile(do_get_addon("test_bootstrap1_1"), resolve));
+  yield promiseCompleteAllInstalls([install]);
+  do_check_eq(install.state, AddonManager.STATE_INSTALLED);
+  do_check_true(hasFlag(install.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+
+  let addon = yield promiseAddonByID(ID);
+  do_check_eq(addon, null);
+
+  yield promiseRestartManager();
+
+  // After install and restart we should block.
+  let blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_true(blocked);
+
+  BootstrapMonitor.checkAddonInstalled(ID);
+  BootstrapMonitor.checkAddonStarted(ID);
+
+  addon = yield promiseAddonByID(ID);
+  do_check_neq(addon, null);
+
+  do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE));
+  addon.userDisabled = true;
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  do_check_false(addon.isActive);
+  do_check_false(hasFlag(addon.pendingOperations, AddonManager.PENDING_DISABLE));
+  do_check_true(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE));
+
+  yield promiseRestartManager();
+
+  // After disable and restart we should not block.
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_false(blocked);
+
+  addon = yield promiseAddonByID(ID);
+  addon.userDisabled = false;
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  do_check_false(addon.isActive);
+  do_check_true(hasFlag(addon.pendingOperations, AddonManager.PENDING_ENABLE));
+
+  yield promiseRestartManager();
+
+  // After re-enable and restart we should block.
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_true(blocked);
+
+  addon = yield promiseAddonByID(ID);
+  do_check_neq(addon, null);
+
+  do_check_true(addon.isActive);
+  BootstrapMonitor.checkAddonStarted(ID);
+
+  do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL));
+  addon.uninstall();
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  BootstrapMonitor.checkAddonNotInstalled(ID);
+
+  yield promiseRestartManager();
+
+  // After uninstall and restart we should not block.
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_false(blocked);
+
+  restartManager();
+});
+
+add_task(function*() {
+  gAppInfo.browserTabsRemoteAutostart = true;
+  Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
+
+  let install1 = yield new Promise(resolve => AddonManager.getInstallForFile(do_get_addon("test_bootstrap1_1"), resolve));
+  let install2 = yield new Promise(resolve => AddonManager.getInstallForFile(do_get_addon("test_bootstrap2_1"), resolve));
+  yield promiseCompleteAllInstalls([install1, install2]);
+  do_check_eq(install1.state, AddonManager.STATE_INSTALLED);
+  do_check_eq(install2.state, AddonManager.STATE_INSTALLED);
+  do_check_true(hasFlag(install1.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+  do_check_true(hasFlag(install2.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+
+  let addon = yield promiseAddonByID(ID);
+  let addon2 = yield promiseAddonByID(ID2);
+
+  do_check_eq(addon, null);
+  do_check_eq(addon2, null);
+
+  yield promiseRestartManager();
+
+  // After install and restart we should block.
+  let blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_true(blocked);
+
+  BootstrapMonitor.checkAddonInstalled(ID);
+  BootstrapMonitor.checkAddonStarted(ID);
+
+  addon = yield promiseAddonByID(ID);
+  do_check_neq(addon, null);
+  addon2 = yield promiseAddonByID(ID2);
+  do_check_neq(addon2, null);
+
+  do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE));
+  addon.userDisabled = true;
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  do_check_false(addon.isActive);
+  do_check_false(hasFlag(addon.pendingOperations, AddonManager.PENDING_DISABLE));
+  do_check_true(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE));
+
+  yield promiseRestartManager();
+
+  // After disable one addon and restart we should block.
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_true(blocked);
+
+  addon2 = yield promiseAddonByID(ID2);
+
+  do_check_false(hasFlag(addon2.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE));
+  addon2.userDisabled = true;
+  BootstrapMonitor.checkAddonNotStarted(ID2);
+  do_check_false(addon2.isActive);
+  do_check_false(hasFlag(addon2.pendingOperations, AddonManager.PENDING_DISABLE));
+  do_check_true(hasFlag(addon2.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE));
+
+  yield promiseRestartManager();
+
+  // After disable both addons and restart we should not block.
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_false(blocked);
+
+  addon = yield promiseAddonByID(ID);
+  addon.userDisabled = false;
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  do_check_false(addon.isActive);
+  do_check_true(hasFlag(addon.pendingOperations, AddonManager.PENDING_ENABLE));
+
+  yield promiseRestartManager();
+
+  // After re-enable one addon and restart we should block.
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_true(blocked);
+
+  addon = yield promiseAddonByID(ID);
+  do_check_neq(addon, null);
+
+  do_check_true(addon.isActive);
+  BootstrapMonitor.checkAddonStarted(ID);
+
+  do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL));
+  addon.uninstall();
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  BootstrapMonitor.checkAddonNotInstalled(ID);
+
+  yield promiseRestartManager();
+
+  // After uninstall the only enabled addon and restart we should not block.
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_false(blocked);
+
+  addon2 = yield promiseAddonByID(ID2);
+  addon2.uninstall();
+
+  restartManager();
+});
+
 // The hotfix is unaffected
 add_task(function*() {
   gAppInfo.browserTabsRemoteAutostart = true;
   Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
   Services.prefs.setCharPref("extensions.hotfix.id", ID);
 
   yield check_normal();
 });
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -4606,16 +4606,17 @@ LogE10sBlockedReason(const char *reason)
 enum {
   kE10sEnabledByUser = 0,
   kE10sEnabledByDefault = 1,
   kE10sDisabledByUser = 2,
   // kE10sDisabledInSafeMode = 3, was removed in bug 1172491.
   kE10sDisabledForAccessibility = 4,
   kE10sDisabledForMacGfx = 5,
   kE10sDisabledForBidi = 6,
+  kE10sDisabledForAddons = 7,
 };
 
 #ifdef XP_WIN
 const char* kAccessibilityLastRunDatePref = "accessibility.lastLoadDate";
 const char* kAccessibilityLoadedLastSessionPref = "accessibility.loadedInLastSession";
 #endif // XP_WIN
 const char* kForceEnableE10sPref = "browser.tabs.remote.force-enable";
 
@@ -4717,25 +4718,37 @@ mozilla::BrowserTabsRemoteAutostart()
   // We are also allowing e10s to be enabled on Beta (which doesn't have E10S_TESTING_ONLY defined.
   bool e10sAllowed = !Preferences::GetDefaultCString("app.update.channel").EqualsLiteral("release") ||
                      gfxPrefs::GetSingleton().LayersOffMainThreadCompositionTestingEnabled();
 #endif
 
   // Disable for VR
   bool disabledForVR = Preferences::GetBool("dom.vr.enabled", false);
 
+  bool addonsCanDisable = Preferences::GetBool("extensions.e10sBlocksEnabling", false);
+  bool disabledByAddons = Preferences::GetBool("extensions.e10sBlockedByAddons", false);
+
+#ifdef MOZ_CRASHREPORTER
+  CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("AddonsShouldHaveBlockedE10s"),
+                                     disabledByAddons ? NS_LITERAL_CSTRING("1")
+                                                      : NS_LITERAL_CSTRING("0"));
+#endif
+
   if (e10sAllowed && prefEnabled) {
     if (disabledForA11y) {
       status = kE10sDisabledForAccessibility;
       LogE10sBlockedReason("An accessibility tool is or was active. See bug 1198459.");
     } else if (disabledForBidi) {
       status = kE10sDisabledForBidi;
       LogE10sBlockedReason("Disabled for RTL locales due to broken bidi detection.");
     } else if (disabledForVR) {
       LogE10sBlockedReason("Experimental VR interfaces are enabled");
+    } else if (addonsCanDisable && disabledByAddons) {
+      status = kE10sDisabledForAddons;
+      LogE10sBlockedReason("3rd party add-ons are installed and enabled.");
     } else {
       gBrowserTabsRemoteAutostart = true;
     }
   }
 
 #if defined(XP_MACOSX)
   // If for any reason we suspect acceleration will be disabled, disabled
   // e10s auto start on mac.