Bug 1346288 - Setting e10sMultiBlockedByAddons for bootrapped add-on users. r=felipe, a=gchang
authorGabor Krizsanits <gkrizsanits@mozilla.com>
Wed, 19 Apr 2017 15:20:00 -0400
changeset 395943 6a768cc3c85a0fb009f4c7dc252ad837b62f818d
parent 395942 549670514df6371bd80ffe13afb4a187f20a016d
child 395944 3cf4e805acc212faa2879420d33f33bb1a29f398
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe, gchang
bugs1346288
milestone54.0
Bug 1346288 - Setting e10sMultiBlockedByAddons for bootrapped add-on users. r=felipe, a=gchang
browser/app/profile/firefox.js
dom/ipc/ContentParent.cpp
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/internal/XPIProviderUtils.js
toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1448,19 +1448,20 @@ pref("browser.tabs.crashReporting.includ
 pref("browser.tabs.crashReporting.requestEmail", false);
 pref("browser.tabs.crashReporting.emailMe", false);
 pref("browser.tabs.crashReporting.email", "");
 
 // Enable e10s add-on interposition by default.
 pref("extensions.interposition.enabled", true);
 pref("extensions.interposition.prefetching", true);
 
-// Enable blocking of e10s for add-on users on beta/release.
+// Enable blocking of e10s and e10s-multi for add-on users on beta/release.
 #ifdef RELEASE_OR_BETA
 pref("extensions.e10sBlocksEnabling", true);
+pref("extensions.e10sMultiBlocksEnabling", true);
 #endif
 
 // How often to check for CPOW timeouts. CPOWs are only timed out by
 // the hang monitor.
 pref("dom.ipc.cpow.timeout", 500);
 
 // Causes access on unsafe CPOWs from browser code to throw by default.
 pref("dom.ipc.cpows.forbid-unsafe-from-browser", true);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -723,17 +723,19 @@ ContentParent::GetMaxProcessCount(const 
 {
   nsAutoCString processCountPref("dom.ipc.processCount.");
   processCountPref.Append(NS_ConvertUTF16toUTF8(aContentProcessType));
   bool hasUserValue = Preferences::HasUserValue(processCountPref.get()) ||
                       Preferences::HasUserValue("dom.ipc.processCount");
 
   // Let's respect the user's decision to enable multiple content processes
   // despite some add-ons installed that might performing poorly.
-  if (!hasUserValue && Preferences::GetBool("extensions.e10sMultiBlockedByAddons", false)) {
+  if (!hasUserValue &&
+      Preferences::GetBool("extensions.e10sMultiBlocksEnabling", false) &&
+      Preferences::GetBool("extensions.e10sMultiBlockedByAddons", false)) {
     return 1;
   }
 
   int32_t maxContentParents;
   if (NS_FAILED(Preferences::GetInt(processCountPref.get(), &maxContentParents))) {
     maxContentParents = Preferences::GetInt("dom.ipc.processCount", 1);
   }
 
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -4509,16 +4509,42 @@ this.XPIProvider = {
       return false;
     }
 
     logger.debug("Add-on " + aAddon.id + " blocks e10s rollout.");
     return true;
   },
 
   /**
+   * Determine if an add-on should be blocking multiple content processes.
+   *
+   * @param  aAddon
+   *         The add-on to test
+   * @return true if enabling the add-on should block multiple content processes.
+   */
+  isBlockingE10sMulti(aAddon) {
+    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 aAddon.bootstrap;
+  },
+
+  /**
    * 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
    */
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -53,16 +53,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 PREF_E10S_MULTI_BLOCKED_BY_ADDONS = "extensions.e10sMultiBlockedByAddons";
 const PREF_E10S_HAS_NONEXEMPT_ADDON   = "extensions.e10s.rollout.hasAddon";
 
 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
@@ -433,16 +434,17 @@ this.XPIDatabase = {
 
     if (!this._deferredSave) {
       this._deferredSave = new DeferredSave(this.jsonFile.path,
                                             () => JSON.stringify(this),
                                             ASYNC_SAVE_DELAY_MS);
     }
 
     this.updateAddonsBlockingE10s();
+    this.updateAddonsBlockingE10sMulti();
     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);
@@ -1368,16 +1370,30 @@ this.XPIDatabase = {
       if (active && XPIProvider.isBlockingE10s(addon)) {
         blockE10s = true;
         break;
       }
     }
     Preferences.set(PREF_E10S_BLOCKED_BY_ADDONS, blockE10s);
   },
 
+  updateAddonsBlockingE10sMulti() {
+    let blockMulti = false;
+
+    for (let [, addon] of this.addonDB) {
+      let active = (addon.visible && !addon.disabled && !addon.pendingUninstall);
+
+      if (active && XPIProvider.isBlockingE10sMulti(addon)) {
+        blockMulti = true;
+        break;
+      }
+    }
+    Preferences.set(PREF_E10S_MULTI_BLOCKED_BY_ADDONS, blockMulti);
+  },
+
   /**
    * Synchronously calculates and updates all the active flags in the database.
    */
   updateActiveAddons() {
     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
@@ -14,27 +14,39 @@ function getStartupReason(id) {
 }
 
 BootstrapMonitor.init();
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
 startupManager();
 
-function* check_normal() {
+function check_multi_disabled() {
+  try {
+    return Services.prefs.getBoolPref("extensions.e10sMultiBlockedByAddons");
+  } catch (e) {
+    return false;
+  }
+}
+
+function* check_normal(checkMultiDisabled) {
   let install = yield promiseInstallFile(do_get_addon("test_bootstrap1_1"));
   do_check_eq(install.state, AddonManager.STATE_INSTALLED);
   do_check_false(hasFlag(install.addon.pendingOperations, AddonManager.PENDING_INSTALL));
 
   BootstrapMonitor.checkAddonInstalled(ID);
   BootstrapMonitor.checkAddonStarted(ID);
 
   let addon = yield promiseAddonByID(ID);
   do_check_eq(addon, install.addon);
 
+  if (checkMultiDisabled) {
+    do_check_false(check_multi_disabled());
+  }
+
   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_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE));
   addon.userDisabled = false;
@@ -77,73 +89,79 @@ add_task(function*() {
 // Pref and e10s blocks install
 add_task(function*() {
   gAppInfo.browserTabsRemoteAutostart = true;
   Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
 
   let install = yield promiseInstallFile(do_get_addon("test_bootstrap1_1"));
   do_check_eq(install.state, AddonManager.STATE_INSTALLED);
   do_check_true(hasFlag(install.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+  do_check_false(check_multi_disabled());
 
   let addon = yield promiseAddonByID(ID);
   do_check_eq(addon, null);
 
   yield promiseRestartManager();
 
   BootstrapMonitor.checkAddonInstalled(ID);
   BootstrapMonitor.checkAddonStarted(ID);
   do_check_eq(getStartupReason(ID), ADDON_INSTALL);
 
   addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
+  do_check_true(check_multi_disabled());
 
   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_false(check_multi_disabled());
 
   do_check_true(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE));
   addon.userDisabled = false;
   BootstrapMonitor.checkAddonNotStarted(ID);
   do_check_false(addon.isActive);
   do_check_true(hasFlag(addon.pendingOperations, AddonManager.PENDING_ENABLE));
 
   yield promiseRestartManager();
 
   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));
+  do_check_true(check_multi_disabled());
   addon.uninstall();
   BootstrapMonitor.checkAddonNotStarted(ID);
   BootstrapMonitor.checkAddonNotInstalled(ID);
 
   yield promiseRestartManager();
 });
 
 add_task(function*() {
   gAppInfo.browserTabsRemoteAutostart = true;
   Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
 
   let install = yield promiseInstallFile(do_get_addon("test_bootstrap1_1"));
   do_check_eq(install.state, AddonManager.STATE_INSTALLED);
   do_check_true(hasFlag(install.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+  do_check_false(check_multi_disabled());
 
   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);
+  do_check_true(check_multi_disabled());
 
   BootstrapMonitor.checkAddonInstalled(ID);
   BootstrapMonitor.checkAddonStarted(ID);
   do_check_eq(getStartupReason(ID), ADDON_INSTALL);
 
   addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
 
@@ -154,28 +172,30 @@ add_task(function*() {
   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);
+  do_check_false(check_multi_disabled());
 
   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);
+  do_check_true(check_multi_disabled());
 
   addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
 
   do_check_true(addon.isActive);
   BootstrapMonitor.checkAddonStarted(ID);
   // This should probably be ADDON_ENABLE, but its not easy to make
   // that happen.  See bug 1304392 for discussion.
@@ -186,16 +206,17 @@ add_task(function*() {
   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);
+  do_check_false(check_multi_disabled());
 
   restartManager();
 });
 
 add_task(function*() {
   gAppInfo.browserTabsRemoteAutostart = true;
   Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
 
@@ -241,43 +262,46 @@ add_task(function*() {
   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);
+  do_check_true(check_multi_disabled());
 
   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);
+  do_check_false(check_multi_disabled());
 
   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);
+  do_check_true(check_multi_disabled());
 
   addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
 
   do_check_true(addon.isActive);
   BootstrapMonitor.checkAddonStarted(ID);
   // Bug 1304392 again (see comment above)
   do_check_eq(getStartupReason(ID), APP_STARTUP);
@@ -287,16 +311,17 @@ add_task(function*() {
   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);
+  do_check_false(check_multi_disabled());
 
   addon2 = yield promiseAddonByID(ID2);
   addon2.uninstall();
 
   restartManager();
 });
 
 // Check that the rollout policy sets work as expected
@@ -421,10 +446,86 @@ add_task(function*() {
 
 // The hotfix is unaffected
 add_task(function*() {
   gAppInfo.browserTabsRemoteAutostart = true;
   Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
   Services.prefs.setCharPref("extensions.hotfix.id", ID);
   Services.prefs.setBoolPref("extensions.hotfix.cert.checkAttributes", false);
 
-  yield check_normal();
+  yield check_normal(true);
+});
+
+// Test non-restarless add-on's should not block multi
+add_task(function*() {
+  yield promiseInstallAllFiles([do_get_addon("test_install1")], true);
+
+  let non_restartless_ID = "addon1@tests.mozilla.org";
+
+  restartManager();
+
+  let addon = yield promiseAddonByID(non_restartless_ID);
+
+  // non-restartless add-on is installed and started
+  do_check_neq(addon, null);
+
+  do_check_false(check_multi_disabled());
+
+  addon.uninstall();
+
+  BootstrapMonitor.checkAddonNotInstalled(non_restartless_ID);
+  BootstrapMonitor.checkAddonNotStarted(non_restartless_ID);
+
+  yield promiseRestartManager();
 });
+
+// Test experiment add-on should not block multi
+add_task(function*() {
+  yield promiseInstallAllFiles([do_get_addon("test_experiment1")], true);
+
+  let experiment_ID = "experiment1@tests.mozilla.org";
+
+  BootstrapMonitor.checkAddonInstalled(experiment_ID, "1.0");
+  BootstrapMonitor.checkAddonNotStarted(experiment_ID);
+
+  let addon = yield promiseAddonByID(experiment_ID);
+
+  // non-restartless add-on is installed and started
+  do_check_neq(addon, null);
+
+  do_check_false(check_multi_disabled());
+
+  addon.uninstall();
+
+  BootstrapMonitor.checkAddonNotInstalled(experiment_ID);
+  BootstrapMonitor.checkAddonNotStarted(experiment_ID);
+
+  yield promiseRestartManager();
+});
+
+const { GlobalManager } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
+
+// Test web extension add-on's should not block multi
+add_task(function*() {
+
+  yield promiseInstallAllFiles([do_get_addon("webextension_1")], true),
+
+  restartManager();
+
+  yield promiseWebExtensionStartup();
+
+  let we_ID = "webextension1@tests.mozilla.org";
+
+  do_check_eq(GlobalManager.extensionMap.size, 1);
+
+  let addon = yield promiseAddonByID(we_ID);
+
+  do_check_neq(addon, null);
+
+  do_check_false(check_multi_disabled());
+
+  addon.uninstall();
+
+  BootstrapMonitor.checkAddonNotInstalled(we_ID);
+  BootstrapMonitor.checkAddonNotStarted(we_ID);
+
+  yield promiseRestartManager();
+});