Backed out 6 changesets (bug 1406212) for mass reftest failures
authorPhil Ringnalda <philringnalda@gmail.com>
Thu, 12 Oct 2017 19:45:23 -0700
changeset 385982 c42cf76778166be6c6f63de92149645863517169
parent 385981 eda76603d637de47018e7244fc65b816a82077fe
child 385983 7ec4ec57761e35268664383f94c1bb21d91d0384
push id32672
push userarchaeopteryx@coole-files.de
push dateFri, 13 Oct 2017 09:00:05 +0000
treeherdermozilla-central@3efcb26e5f37 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1406212
milestone58.0a1
backs out1c9fe35de9016a150b8322416ada8b8b9050a358
1acc4c270bf91002e01e84ca03d10db8e036e05e
d9ea9cff849f246c9fafb412d4f8831ba0f5e0d9
5bf2f08f01f98f76a7c0135696a77e8f848d34e1
1a050da96e9e0af6801f44a764424e656d12153a
1b5e78113f06a05ffa7a3345e59cac35cd0835cc
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
Backed out 6 changesets (bug 1406212) for mass reftest failures CLOSED TREE Backed out changeset 1c9fe35de901 (bug 1406212) Backed out changeset 1acc4c270bf9 (bug 1406212) Backed out changeset d9ea9cff849f (bug 1406212) Backed out changeset 5bf2f08f01f9 (bug 1406212) Backed out changeset 1a050da96e9e (bug 1406212) Backed out changeset 1b5e78113f06 (bug 1406212) MozReview-Commit-ID: LizV8CD4IY4
browser/app/profile/firefox.js
browser/components/preferences/in-content/main.js
browser/components/preferences/in-content/main.xul
browser/components/preferences/in-content/tests/browser_performance_e10srollout.js
browser/extensions/e10srollout/bootstrap.js
browser/extensions/e10srollout/install.rdf.in
browser/extensions/e10srollout/moz.build
browser/extensions/moz.build
layout/tools/reftest/reftest-preferences.js
modules/libpref/init/all.js
testing/marionette/client/marionette_driver/geckoinstance.py
testing/profiles/prefs_general.js
testing/talos/talos/config.py
testing/talos/talos/xtalos/xperf_whitelist.json
toolkit/mozapps/extensions/internal/E10SAddonsRollout.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/internal/XPIProviderUtils.js
toolkit/mozapps/extensions/internal/moz.build
toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsAppRunner.h
tools/lint/eslint/modules.json
xpcom/system/nsIXULRuntime.idl
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1536,19 +1536,27 @@ pref("privacy.userContext.ui.enabled", f
 pref("privacy.usercontext.about_newtab_segregation.enabled", false);
 
 // 0 disables long press, 1 when clicked, the menu is shown, 2 the menu is shown after X milliseconds.
 pref("privacy.userContext.longPressBehavior", 0);
 #endif
 pref("privacy.userContext.extension", "");
 
 // Start the browser in e10s mode
-pref("browser.tabs.remote.autostart", true);
+pref("browser.tabs.remote.autostart", false);
 pref("browser.tabs.remote.desktopbehavior", true);
 
+#if !defined(RELEASE_OR_BETA) || defined(MOZ_DEV_EDITION)
+// At the moment, autostart.2 is used, while autostart.1 is unused.
+// We leave it here set to false to reset users' defaults and allow
+// us to change everybody to true in the future, when desired.
+pref("browser.tabs.remote.autostart.1", false);
+pref("browser.tabs.remote.autostart.2", true);
+#endif
+
 // For speculatively warming up tabs to improve perceived
 // performance while using the async tab switcher.
 // Disabled until bug 1397426 is fixed.
 pref("browser.tabs.remote.warmup.enabled", false);
 pref("browser.tabs.remote.warmup.maxTabs", 3);
 pref("browser.tabs.remote.warmup.unloadDelayMs", 2000);
 
 // For the about:tabcrashed page
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -320,19 +320,20 @@ var gMainPane = {
     }
 
     if (AppConstants.E10S_TESTING_ONLY) {
       setEventListener("e10sAutoStart", "command",
         gMainPane.enableE10SChange);
       let e10sCheckbox = document.getElementById("e10sAutoStart");
 
       let e10sPref = document.getElementById("browser.tabs.remote.autostart");
+      let e10sTempPref = document.getElementById("e10sTempPref");
       let e10sForceEnable = document.getElementById("e10sForceEnable");
 
-      let preffedOn = e10sPref.value || e10sForceEnable.value;
+      let preffedOn = e10sPref.value || e10sTempPref.value || e10sForceEnable.value;
 
       if (preffedOn) {
         // The checkbox is checked if e10s is preffed on and enabled.
         e10sCheckbox.checked = Services.appinfo.browserTabsRemoteAutostart;
 
         // but if it's force disabled, then the checkbox is disabled.
         e10sCheckbox.disabled = !Services.appinfo.browserTabsRemoteAutostart;
       }
@@ -559,38 +560,42 @@ var gMainPane = {
 
     return e10sEnabled;
   },
 
   enableE10SChange() {
     if (AppConstants.E10S_TESTING_ONLY) {
       let e10sCheckbox = document.getElementById("e10sAutoStart");
       let e10sPref = document.getElementById("browser.tabs.remote.autostart");
+      let e10sTempPref = document.getElementById("e10sTempPref");
 
       let prefsToChange;
       if (e10sCheckbox.checked) {
         // Enabling e10s autostart
         prefsToChange = [e10sPref];
       } else {
         // Disabling e10s autostart
         prefsToChange = [e10sPref];
+        if (e10sTempPref.value) {
+          prefsToChange.push(e10sTempPref);
+        }
       }
 
       let buttonIndex = confirmRestartPrompt(e10sCheckbox.checked, 0,
         true, false);
       if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
         for (let prefToChange of prefsToChange) {
           prefToChange.value = e10sCheckbox.checked;
         }
 
         Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
       }
 
       // Revert the checkbox in case we didn't quit
-      e10sCheckbox.checked = e10sPref.value;
+      e10sCheckbox.checked = e10sPref.value || e10sTempPref.value;
     }
   },
 
   separateProfileModeChange() {
     if (AppConstants.MOZ_DEV_EDITION) {
       function quitApp() {
         Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestartNotSameProfile);
       }
@@ -1247,29 +1252,41 @@ var gMainPane = {
 
   updatePerformanceSettingsBox({ duringChangeEvent }) {
     let defaultPerformancePref =
       document.getElementById("browser.preferences.defaultPerformanceSettings.enabled");
     let performanceSettings = document.getElementById("performanceSettings");
     let processCountPref = document.getElementById("dom.ipc.processCount");
     if (defaultPerformancePref.value) {
       let accelerationPref = document.getElementById("layers.acceleration.disabled");
-      // Unset the value so process count will be decided by the platform.
+      // Unset the value so process count will be decided by e10s rollout.
       processCountPref.value = processCountPref.defaultValue;
       accelerationPref.value = accelerationPref.defaultValue;
       performanceSettings.hidden = true;
     } else {
+      let e10sRolloutProcessCountPref =
+        document.getElementById("dom.ipc.processCount.web");
+      // Take the e10s rollout value as the default value (if it exists),
+      // but don't overwrite the user set value.
+      if (duringChangeEvent &&
+        e10sRolloutProcessCountPref.value &&
+        processCountPref.value == processCountPref.defaultValue) {
+        processCountPref.value = e10sRolloutProcessCountPref.value;
+      }
       performanceSettings.hidden = false;
     }
   },
 
   buildContentProcessCountMenuList() {
     if (gMainPane.isE10SEnabled()) {
       let processCountPref = document.getElementById("dom.ipc.processCount");
-      let defaultProcessCount = processCountPref.defaultValue;
+      let e10sRolloutProcessCountPref =
+        document.getElementById("dom.ipc.processCount.web");
+      let defaultProcessCount =
+        e10sRolloutProcessCountPref.value || processCountPref.defaultValue;
       let bundlePreferences = document.getElementById("bundlePreferences");
       let label = bundlePreferences.getFormattedString("defaultContentProcessCount",
         [defaultProcessCount]);
       let contentProcessCount =
         document.querySelector(`#contentProcessCount > menupopup >
                                 menuitem[value="${defaultProcessCount}"]`);
       contentProcessCount.label = label;
 
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -17,16 +17,19 @@
 <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences.properties"/>
 
 <preferences id="mainPreferences" hidden="true" data-category="paneGeneral">
 
 #ifdef E10S_TESTING_ONLY
     <preference id="browser.tabs.remote.autostart"
                 name="browser.tabs.remote.autostart"
                 type="bool"/>
+    <preference id="e10sTempPref"
+                name="browser.tabs.remote.autostart.2"
+                type="bool"/>
     <preference id="e10sForceEnable"
                 name="browser.tabs.remote.force-enable"
                 type="bool"/>
 #endif
 
     <!-- Startup -->
     <preference id="browser.startup.page"
                 name="browser.startup.page"
--- a/browser/components/preferences/in-content/tests/browser_performance_e10srollout.js
+++ b/browser/components/preferences/in-content/tests/browser_performance_e10srollout.js
@@ -4,52 +4,79 @@ const DEFAULT_PROCESS_COUNT = Services.p
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({set: [
     ["layers.acceleration.disabled", DEFAULT_HW_ACCEL_PREF],
     ["dom.ipc.processCount", DEFAULT_PROCESS_COUNT],
     ["browser.preferences.defaultPerformanceSettings.enabled", true],
   ]});
 });
 
-add_task(async function testPrefsAreDefault() {
-  Services.prefs.setIntPref("dom.ipc.processCount", DEFAULT_PROCESS_COUNT);
+add_task(async function() {
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
 
   let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   is(prefs.selectedPane, "paneGeneral", "General pane was selected");
 
   let doc = gBrowser.contentDocument;
   let useRecommendedPerformanceSettings = doc.querySelector("#useRecommendedPerformanceSettings");
 
   is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
-     "pref value should be true before clicking on checkbox");
+    "pref value should be true before clicking on checkbox");
   ok(useRecommendedPerformanceSettings.checked, "checkbox should be checked before clicking on checkbox");
 
   useRecommendedPerformanceSettings.click();
 
   let performanceSettings = doc.querySelector("#performanceSettings");
   is(performanceSettings.hidden, false, "performance settings section is shown");
 
   is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), false,
      "pref value should be false after clicking on checkbox");
   ok(!useRecommendedPerformanceSettings.checked, "checkbox should not be checked after clicking on checkbox");
 
   let contentProcessCount = doc.querySelector("#contentProcessCount");
   is(contentProcessCount.disabled, false, "process count control should be enabled");
-  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default pref should be default value");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT + 1, "e10s rollout value should be default value");
+  is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT + 1, "selected item should be the default one");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.clearUserPref("dom.ipc.processCount.web");
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
+});
+
+add_task(async function() {
+  Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
+  Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
+
+  let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
+  is(prefs.selectedPane, "paneGeneral", "General pane was selected");
+
+  let doc = gBrowser.contentDocument;
+  let performanceSettings = doc.querySelector("#performanceSettings");
+  is(performanceSettings.hidden, false, "performance settings section is shown");
+
+  let contentProcessCount = doc.querySelector("#contentProcessCount");
+  is(contentProcessCount.disabled, false, "process count control should be enabled");
+  is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT, "default value should be the current value");
   is(contentProcessCount.selectedItem.value, DEFAULT_PROCESS_COUNT, "selected item should be the default one");
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.clearUserPref("dom.ipc.processCount.web");
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
 });
 
-add_task(async function testPrefsSetByUser() {
+add_task(async function() {
   Services.prefs.setIntPref("dom.ipc.processCount", DEFAULT_PROCESS_COUNT + 2);
+  Services.prefs.setIntPref("dom.ipc.processCount.web", DEFAULT_PROCESS_COUNT + 1);
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", false);
 
   let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   is(prefs.selectedPane, "paneGeneral", "General pane was selected");
 
   let doc = gBrowser.contentDocument;
   let performanceSettings = doc.querySelector("#performanceSettings");
   is(performanceSettings.hidden, false, "performance settings section is shown");
@@ -65,10 +92,11 @@ add_task(async function testPrefsSetByUs
   is(Services.prefs.getBoolPref("browser.preferences.defaultPerformanceSettings.enabled"), true,
     "pref value should be true after clicking on checkbox");
   is(Services.prefs.getIntPref("dom.ipc.processCount"), DEFAULT_PROCESS_COUNT,
     "process count should be default value");
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   Services.prefs.clearUserPref("dom.ipc.processCount");
+  Services.prefs.clearUserPref("dom.ipc.processCount.web");
   Services.prefs.setBoolPref("browser.preferences.defaultPerformanceSettings.enabled", true);
 });
new file mode 100644
--- /dev/null
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -0,0 +1,306 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/UpdateUtils.jsm");
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/TelemetryEnvironment.jsm");
+
+// The amount of people to be part of e10s
+const TEST_THRESHOLD = {
+  "beta": 0.9,  // 90%
+  "release": 1.0,  // 100%
+  "esr": 1.0,  // 100%
+};
+
+// If a user qualifies for the e10s-multi experiement, this is how many
+// content processes to use and whether to allow addons for the experiment.
+const MULTI_EXPERIMENT = {
+  "beta": { buckets: { 4: 1 }, // 4 processes: 100%
+
+            // The extensions code only allows webextensions and legacy-style
+            // extensions that have been verified to work with multi.
+            // Therefore, we can allow all extensions.
+            addonsDisableExperiment(prefix) { return false; } },
+
+  "release": { buckets: { 4: 1 }, // 4 processes: 100%
+
+               // See the comment above the "beta" addonsDisableExperiment.
+               addonsDisableExperiment(prefix) { return false; } }
+};
+
+const ADDON_ROLLOUT_POLICY = {
+  "beta": "50allmpc",
+  "release": "50allmpc",
+  "esr": "esrA", // WebExtensions and Addons with mpc=true
+};
+
+if (AppConstants.RELEASE_OR_BETA) {
+  // Bug 1348576 - e10s is never enabled for non-official release builds
+  // This is hacky, but the problem it solves is the following:
+  // the e10s rollout is controlled by the channel name, which
+  // is the only way to distinguish between Beta and Release.
+  // However, non-official release builds (like the ones done by distros
+  // to ship Firefox on their package managers) do not set a value
+  // for the release channel, which gets them to the default value
+  // of.. (drumroll) "default".
+  // But we can't just always configure the same settings for the
+  // "default" channel because that's also the name that a locally
+  // built Firefox gets, and e10s is managed in a different way
+  // there (directly by prefs, on Nightly and Aurora).
+  TEST_THRESHOLD.default = TEST_THRESHOLD.release;
+  ADDON_ROLLOUT_POLICY.default = ADDON_ROLLOUT_POLICY.release;
+}
+
+
+const PREF_COHORT_SAMPLE       = "e10s.rollout.cohortSample";
+const PREF_COHORT_NAME         = "e10s.rollout.cohort";
+const PREF_E10S_OPTED_IN       = "browser.tabs.remote.autostart";
+const PREF_E10S_FORCE_ENABLED  = "browser.tabs.remote.force-enable";
+const PREF_E10S_FORCE_DISABLED = "browser.tabs.remote.force-disable";
+const PREF_TOGGLE_E10S         = "browser.tabs.remote.autostart.2";
+const PREF_E10S_ADDON_POLICY   = "extensions.e10s.rollout.policy";
+const PREF_E10S_ADDON_BLOCKLIST = "extensions.e10s.rollout.blocklist";
+const PREF_E10S_HAS_NONEXEMPT_ADDON = "extensions.e10s.rollout.hasAddon";
+const PREF_E10S_MULTI_OPTOUT   = "dom.ipc.multiOptOut";
+const PREF_E10S_PROCESSCOUNT   = "dom.ipc.processCount";
+const PREF_USE_DEFAULT_PERF_SETTINGS = "browser.preferences.defaultPerformanceSettings.enabled";
+const PREF_E10S_MULTI_ADDON_BLOCKS = "extensions.e10sMultiBlocksEnabling";
+const PREF_E10S_MULTI_BLOCKED_BY_ADDONS = "extensions.e10sMultiBlockedByAddons";
+
+function startup() {
+  // In theory we only need to run this once (on install()), but
+  // it's better to also run it on every startup. If the user has
+  // made manual changes to the prefs, this will keep the data
+  // reported more accurate.
+  // It's also fine (and preferred) to just do it here on startup
+  // (instead of observing prefs), because e10s takes a restart
+  // to take effect, so we keep the data based on how it was when
+  // the session started.
+  defineCohort();
+}
+
+function install() {
+  defineCohort();
+}
+
+let cohortDefinedOnThisSession = false;
+
+function defineCohort() {
+  // Avoid running twice when it was called by install() first
+  if (cohortDefinedOnThisSession) {
+    return;
+  }
+  cohortDefinedOnThisSession = true;
+
+  let updateChannel = UpdateUtils.getUpdateChannel(false);
+  if (!(updateChannel in TEST_THRESHOLD)) {
+    setCohort("unsupportedChannel");
+    return;
+  }
+
+  let addonPolicy = "unknown";
+  if (updateChannel in ADDON_ROLLOUT_POLICY) {
+    addonPolicy = ADDON_ROLLOUT_POLICY[updateChannel];
+    Services.prefs.setStringPref(PREF_E10S_ADDON_POLICY, addonPolicy);
+
+    // This is also the proper place to set the blocklist pref
+    // in case it is necessary.
+    Services.prefs.setStringPref(PREF_E10S_ADDON_BLOCKLIST, "");
+  } else {
+    Services.prefs.clearUserPref(PREF_E10S_ADDON_POLICY);
+  }
+
+  let userOptedOut = optedOut();
+  let userOptedIn = optedIn();
+  let disqualified = (Services.appinfo.multiprocessBlockPolicy != 0);
+  let testThreshold = TEST_THRESHOLD[updateChannel];
+  let testGroup = (getUserSample(false) < testThreshold);
+  let hasNonExemptAddon = Services.prefs.getBoolPref(PREF_E10S_HAS_NONEXEMPT_ADDON, false);
+  let temporaryDisqualification = getTemporaryDisqualification();
+  let temporaryQualification = getTemporaryQualification();
+
+  let cohortPrefix = "";
+  if (disqualified) {
+    cohortPrefix = "disqualified-";
+  } else if (hasNonExemptAddon) {
+    cohortPrefix = `addons-set${addonPolicy}-`;
+  }
+
+  let eligibleForMulti = false;
+  if (userOptedOut.e10s || userOptedOut.multi) {
+    // If we detected that the user opted out either for multi or e10s, then
+    // the proper prefs must already be set.
+    setCohort("optedOut");
+  } else if (userOptedIn.e10s) {
+    eligibleForMulti = true;
+    setCohort("optedIn");
+  } else if (temporaryDisqualification != "") {
+    // Users who are disqualified by the backend (from multiprocessBlockPolicy)
+    // can be put into either the test or control groups, because e10s will
+    // still be denied by the backend, which is useful so that the E10S_STATUS
+    // telemetry probe can be correctly set.
+
+    // For these volatile disqualification reasons, however, we must not try
+    // to activate e10s because the backend doesn't know about it. E10S_STATUS
+    // here will be accumulated as "2 - Disabled", which is fine too.
+    Services.prefs.clearUserPref(PREF_TOGGLE_E10S);
+    Services.prefs.clearUserPref(PREF_E10S_PROCESSCOUNT + ".web");
+    setCohort(`temp-disqualified-${temporaryDisqualification}`);
+  } else if (!disqualified && testThreshold < 1.0 &&
+             temporaryQualification != "") {
+    // Users who are qualified for e10s and on channels where some population
+    // would not receive e10s can be pushed into e10s anyway via a temporary
+    // qualification which overrides the user sample value when non-empty.
+    Services.prefs.setBoolPref(PREF_TOGGLE_E10S, true);
+    eligibleForMulti = true;
+    setCohort(`temp-qualified-${temporaryQualification}`);
+  } else if (testGroup) {
+    Services.prefs.setBoolPref(PREF_TOGGLE_E10S, true);
+    eligibleForMulti = true;
+    setCohort(`${cohortPrefix}test`);
+  } else {
+    Services.prefs.clearUserPref(PREF_TOGGLE_E10S);
+    Services.prefs.clearUserPref(PREF_E10S_PROCESSCOUNT + ".web");
+    setCohort(`${cohortPrefix}control`);
+  }
+
+  // Now determine if this user should be in the e10s-multi experiment.
+  // - We only run the experiment on channels defined in MULTI_EXPERIMENT.
+  // - If this experiment doesn't allow addons and we have a cohort prefix
+  //   (i.e. there's at least one addon installed) we stop here.
+  // - We decided above whether this user qualifies for the experiment.
+  // - If the user already opted into multi, then their prefs are already set
+  //   correctly, we're done.
+  // - If the user has addons that disqualify them for multi, leave them with
+  //   the default number of content processes (1 on beta) but still in the
+  //   test cohort.
+  if (!(updateChannel in MULTI_EXPERIMENT) ||
+      MULTI_EXPERIMENT[updateChannel].addonsDisableExperiment(cohortPrefix) ||
+      !eligibleForMulti ||
+      userOptedIn.multi ||
+      disqualified) {
+    Services.prefs.clearUserPref(PREF_E10S_PROCESSCOUNT + ".web");
+    return;
+  }
+
+  // If we got here with a cohortPrefix, it must be "addons-set50allmpc-",
+  // which means that there's at least one add-on installed. If
+  // getAddonsDisqualifyForMulti returns false, that means that all installed
+  // addons are webextension based, so note that in the cohort name.
+  if (cohortPrefix && !getAddonsDisqualifyForMulti()) {
+    cohortPrefix = "webextensions-";
+  }
+
+  // The user is in the multi experiment!
+  // Decide how many content processes to use for this user.
+  let buckets = MULTI_EXPERIMENT[updateChannel].buckets;
+
+  let multiUserSample = getUserSample(true);
+  for (let sampleName of Object.getOwnPropertyNames(buckets)) {
+    if (multiUserSample < buckets[sampleName]) {
+      // NB: Coerce sampleName to an integer because this is an integer pref.
+      Services.prefs.setIntPref(PREF_E10S_PROCESSCOUNT + ".web", +sampleName);
+      setCohort(`${cohortPrefix}multiBucket${sampleName}`);
+      break;
+    }
+  }
+}
+
+function shutdown(data, reason) {
+}
+
+function uninstall() {
+}
+
+function getUserSample(multi) {
+  let pref = multi ? (PREF_COHORT_SAMPLE + ".multi") : PREF_COHORT_SAMPLE;
+  let prefType = Services.prefs.getPrefType(pref);
+
+  if (prefType == Ci.nsIPrefBranch.PREF_STRING) {
+    return parseFloat(Services.prefs.getStringPref(pref), 10);
+  }
+
+  let value = 0.0;
+  if (prefType == Ci.nsIPrefBranch.PREF_INT) {
+    // convert old integer value
+    value = Services.prefs.getIntPref(pref) / 100;
+  } else {
+    value = Math.random();
+  }
+
+  Services.prefs.setStringPref(pref, value.toString().substr(0, 8));
+  return value;
+}
+
+function setCohort(cohortName) {
+  Services.prefs.setStringPref(PREF_COHORT_NAME, cohortName);
+  if (cohortName != "unsupportedChannel") {
+    TelemetryEnvironment.setExperimentActive("e10sCohort", cohortName);
+  }
+  try {
+    if (Ci.nsICrashReporter) {
+      Services.appinfo.QueryInterface(Ci.nsICrashReporter).annotateCrashReport("E10SCohort", cohortName);
+    }
+  } catch (e) {}
+}
+
+function optedIn() {
+  let e10s = Services.prefs.getBoolPref(PREF_E10S_OPTED_IN, false) ||
+             Services.prefs.getBoolPref(PREF_E10S_FORCE_ENABLED, false);
+  let multi = Services.prefs.prefHasUserValue(PREF_E10S_PROCESSCOUNT) ||
+             !Services.prefs.getBoolPref(PREF_USE_DEFAULT_PERF_SETTINGS, true);
+  return { e10s, multi };
+}
+
+function optedOut() {
+  // Users can also opt-out by toggling back the pref to false.
+  // If they reset the pref instead they might be re-enabled if
+  // they are still part of the threshold.
+  let e10s = Services.prefs.getBoolPref(PREF_E10S_FORCE_DISABLED, false) ||
+               (Services.prefs.prefHasUserValue(PREF_TOGGLE_E10S) &&
+                Services.prefs.getBoolPref(PREF_TOGGLE_E10S) == false);
+  let multi = Services.prefs.getIntPref(PREF_E10S_MULTI_OPTOUT, 0) >=
+              Services.appinfo.E10S_MULTI_EXPERIMENT;
+  return { e10s, multi };
+}
+
+/* If this function returns a non-empty string, it
+ * means that this particular user should be temporarily
+ * disqualified due to some particular reason.
+ * If a user shouldn't be disqualified, then an empty
+ * string must be returned.
+ */
+function getTemporaryDisqualification() {
+  return "";
+}
+
+/* If this function returns a non-empty string, it
+ * means that this particular user should be temporarily
+ * qualified due to some particular reason.
+ * If a user shouldn't be qualified, then an empty
+ * string must be returned.
+ */
+function getTemporaryQualification() {
+  // Whenever the DevTools toolbox is opened for the first time in a release, it
+  // records this fact in the following pref as part of the DevTools telemetry
+  // system.  If this pref is set, then it means the user has opened DevTools at
+  // some point in time.
+  const PREF_OPENED_DEVTOOLS = "devtools.telemetry.tools.opened.version";
+  let hasOpenedDevTools = Services.prefs.prefHasUserValue(PREF_OPENED_DEVTOOLS);
+  if (hasOpenedDevTools) {
+    return "devtools";
+  }
+
+  return "";
+}
+
+function getAddonsDisqualifyForMulti() {
+  return Services.prefs.getBoolPref("extensions.e10sMultiBlocksEnabling", false) &&
+         Services.prefs.getBoolPref("extensions.e10sMultiBlockedByAddons", false);
+}
new file mode 100644
--- /dev/null
+++ b/browser/extensions/e10srollout/install.rdf.in
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>e10srollout@mozilla.org</em:id>
+    <em:version>3.05</em:version>
+    <em:type>2</em:type>
+    <em:bootstrap>true</em:bootstrap>
+    <em:multiprocessCompatible>true</em:multiprocessCompatible>
+
+    <!-- Target Application this theme can install into,
+        with minimum and maximum supported versions. -->
+    <em:targetApplication>
+      <Description>
+        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
+        <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+        <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+    <!-- Front End MetaData -->
+    <em:name>Multi-process staged rollout</em:name>
+    <em:description>Staged rollout of Firefox multi-process feature.</em:description>
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/browser/extensions/e10srollout/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM: Content Processes")
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
+
+FINAL_TARGET_FILES.features['e10srollout@mozilla.org'] += [
+  'bootstrap.js'
+]
+
+FINAL_TARGET_PP_FILES.features['e10srollout@mozilla.org'] += [
+  'install.rdf.in'
+]
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -2,16 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 DIRS += [
     'activity-stream',
     'aushelper',
+    'e10srollout',
     'followonsearch',
     'formautofill',
     'onboarding',
     'pdfjs',
     'pocket',
     'screenshots',
     'shield-recipe-client',
     'webcompat',
--- a/layout/tools/reftest/reftest-preferences.js
+++ b/layout/tools/reftest/reftest-preferences.js
@@ -117,17 +117,18 @@ user_pref("browser.newtabpage.directory.
 // Only allow add-ons from the profile and app and allow foreign
 // injection
 user_pref("extensions.enabledScopes", 5);
 user_pref("extensions.autoDisableScopes", 0);
 // Allow unsigned add-ons
 user_pref("xpinstall.signatures.required", false);
 
 // Don't use auto-enabled e10s
-user_pref("browser.tabs.remote.autostart", false);
+user_pref("browser.tabs.remote.autostart.1", false);
+user_pref("browser.tabs.remote.autostart.2", false);
 
 user_pref("startup.homepage_welcome_url", "");
 user_pref("startup.homepage_welcome_url.additional", "");
 user_pref("startup.homepage_override_url", "");
 
 user_pref("media.gmp-manager.url.override", "http://localhost/dummy-gmp-manager.xml");
 user_pref("media.gmp-manager.updateEnabled", false);
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3314,18 +3314,23 @@ pref("dom.ipc.plugins.reportCrashURL", t
 // Defaults to 30 seconds.
 pref("dom.ipc.plugins.unloadTimeoutSecs", 30);
 
 // Allow Flash async drawing mode in 64-bit release builds
 pref("dom.ipc.plugins.asyncdrawing.enabled", true);
 // Force the accelerated direct path for a subset of Flash wmode values
 pref("dom.ipc.plugins.forcedirect.enabled", true);
 
-// Enable multi by default.
+// Enable multi by default for Nightly and DevEdition only.
+// For Beta and Release builds, multi is controlled by the e10srollout addon.
+#if defined(RELEASE_OR_BETA) && !defined(MOZ_DEV_EDITION)
+pref("dom.ipc.processCount", 1);
+#else
 pref("dom.ipc.processCount", 4);
+#endif
 
 // Default to allow only one file:// URL content process.
 pref("dom.ipc.processCount.file", 1);
 
 // WebExtensions only support a single extension process.
 pref("dom.ipc.processCount.extension", 1);
 
 // Don't use a native event loop in the content process.
--- a/testing/marionette/client/marionette_driver/geckoinstance.py
+++ b/testing/marionette/client/marionette_driver/geckoinstance.py
@@ -303,16 +303,18 @@ class FennecInstance(GeckoInstance):
         "browser.safebrowsing.passwords.enabled": False,
         "browser.safebrowsing.malware.enabled": False,
         "browser.safebrowsing.phishing.enabled": False,
 
         # Do not restore the last open set of tabs if the browser has crashed
         "browser.sessionstore.resume_from_crash": False,
 
         # Disable e10s by default
+        "browser.tabs.remote.autostart.1": False,
+        "browser.tabs.remote.autostart.2": False,
         "browser.tabs.remote.autostart": False,
 
         # Do not allow background tabs to be zombified, otherwise for tests that
         # open additional tabs, the test harness tab itself might get unloaded
         "browser.tabs.disableBackgroundZombification": True,
     }
 
     def __init__(self, emulator_binary=None, avd_home=None, avd=None,
@@ -446,16 +448,18 @@ class DesktopInstance(GeckoInstance):
 
         # Do not restore the last open set of tabs if the browser has crashed
         "browser.sessionstore.resume_from_crash": False,
 
         # Don't check for the default web browser during startup
         "browser.shell.checkDefaultBrowser": False,
 
         # Disable e10s by default
+        "browser.tabs.remote.autostart.1": False,
+        "browser.tabs.remote.autostart.2": False,
         "browser.tabs.remote.autostart": False,
 
         # Needed for branded builds to prevent opening a second tab on startup
         "browser.startup.homepage_override.mstone": "ignore",
         # Start with a blank page by default
         "browser.startup.page": 0,
 
         # Disable browser animations
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -336,17 +336,18 @@ user_pref("media.eme.enabled", true);
 // Set the number of shmems the PChromiumCDM protocol pre-allocates to 0,
 // so that we test the case where we under-estimate how many shmems we need
 // to send decoded video frames from the CDM to Gecko.
 user_pref("media.eme.chromium-api.video-shmems", 0);
 
 user_pref("media.autoplay.enabled", true);
 
 // Don't use auto-enabled e10s
-user_pref("browser.tabs.remote.autostart", false);
+user_pref("browser.tabs.remote.autostart.1", false);
+user_pref("browser.tabs.remote.autostart.2", false);
 // Don't show a delay when hiding the audio indicator during tests
 user_pref("browser.tabs.delayHidingAudioPlayingIconMS", 0);
 // Don't forceably kill content processes after a timeout
 user_pref("dom.ipc.tabs.shutdownTimeoutSecs", 0);
 
 // Don't block add-ons for e10s
 user_pref("extensions.e10sBlocksEnabling", false);
 
--- a/testing/talos/talos/config.py
+++ b/testing/talos/talos/config.py
@@ -291,16 +291,18 @@ def set_webserver(config):
 @validator
 def update_prefs(config):
     # if e10s is enabled, set prefs accordingly
     if config['e10s']:
         config['preferences']['browser.tabs.remote.autostart'] = True
         config['preferences']['extensions.e10sBlocksEnabling'] = False
     else:
         config['preferences']['browser.tabs.remote.autostart'] = False
+        config['preferences']['browser.tabs.remote.autostart.1'] = False
+        config['preferences']['browser.tabs.remote.autostart.2'] = False
 
     # update prefs from command line
     prefs = config.pop('extraPrefs')
     if prefs:
         for arg in prefs:
             k, v = arg.split('=', 1)
             config['preferences'][k] = utils.parse_pref(v)
 
--- a/testing/talos/talos/xtalos/xperf_whitelist.json
+++ b/testing/talos/talos/xtalos/xperf_whitelist.json
@@ -2,16 +2,17 @@
  "C:\\$Mft": {"ignore": true},
  "C:\\$Extend\\$UsnJrnl:$J": {"ignore": true},
  "C:\\Windows\\Prefetch\\{prefetch}.pf": {"ignore": true},
  "C:\\$Secure": {"ignore": true},
  "C:\\$logfile": {"ignore": true},
  "{firefox}\\omni.ja": {"mincount": 0, "maxcount": 46, "minbytes": 0, "maxbytes": 3014656},
  "{firefox}\\browser\\omni.ja": {"mincount": 0, "maxcount": 28, "minbytes": 0, "maxbytes": 1835008},
  "{firefox}\\browser\\features\\aushelper@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
+ "{firefox}\\browser\\features\\e10srollout@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\flyweb@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\formautofill@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\loop@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\firefox@getpocket.com.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\presentation@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\webcompat@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\webcompat-reporter@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
  "{firefox}\\browser\\features\\shield-recipe-client@mozilla.org.xpi": {"mincount": 0, "maxcount": 100, "minbytes": 0, "maxbytes": 10000000},
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/internal/E10SAddonsRollout.jsm
@@ -0,0 +1,65 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = [ "isAddonPartOfE10SRollout" ];
+
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PREF_E10S_ADDON_BLOCKLIST = "extensions.e10s.rollout.blocklist";
+const PREF_E10S_ADDON_POLICY    = "extensions.e10s.rollout.policy";
+
+// NOTE: Do not modify policies after they have already been
+// published to users. They must remain unchanged to provide valid data.
+
+// We use these named policies to correlate the telemetry
+// data with them, in order to understand how each set
+// is behaving in the wild.
+const RolloutPolicy = {
+  // Beta testing on 50
+  "50allmpc": { webextensions: true, mpc: true },
+
+  // ESR
+  "esrA": { mpc: true, webextensions: true },
+  "esrB": { mpc: true, webextensions: false },
+  "esrC": { mpc: false, webextensions: true },
+
+  "xpcshell-test": { mpc: true, webextensions: false },
+};
+
+Object.defineProperty(this, "isAddonPartOfE10SRollout", {
+  configurable: false,
+  enumerable: false,
+  writable: false,
+  value: function isAddonPartOfE10SRollout(aAddon) {
+    let blocklist = Services.prefs.getStringPref(PREF_E10S_ADDON_BLOCKLIST, "");
+    let policyId = Services.prefs.getStringPref(PREF_E10S_ADDON_POLICY, "");
+
+    if (!policyId || !RolloutPolicy.hasOwnProperty(policyId)) {
+      return false;
+    }
+
+    if (blocklist && blocklist.indexOf(aAddon.id) > -1) {
+      return false;
+    }
+
+    let policy = RolloutPolicy[policyId];
+
+    if (aAddon.mpcOptedOut == true) {
+      return false;
+    }
+
+    if (policy.webextensions && (aAddon.type == "webextension" || aAddon.type == "webextension-theme")) {
+      return true;
+    }
+
+    if (policy.mpc && aAddon.multiprocessCompatible) {
+      return true;
+    }
+
+    return false;
+  },
+});
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -28,16 +28,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   FileUtils: "resource://gre/modules/FileUtils.jsm",
   ZipUtils: "resource://gre/modules/ZipUtils.jsm",
   NetUtil: "resource://gre/modules/NetUtil.jsm",
   PermissionsUtils: "resource://gre/modules/PermissionsUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   ConsoleAPI: "resource://gre/modules/Console.jsm",
   ProductAddonChecker: "resource://gre/modules/addons/ProductAddonChecker.jsm",
   UpdateUtils: "resource://gre/modules/UpdateUtils.jsm",
+  isAddonPartOfE10SRollout: "resource://gre/modules/addons/E10SAddonsRollout.jsm",
   JSONFile: "resource://gre/modules/JSONFile.jsm",
   LegacyExtensionsUtils: "resource://gre/modules/LegacyExtensionsUtils.jsm",
   setTimeout: "resource://gre/modules/Timer.jsm",
   clearTimeout: "resource://gre/modules/Timer.jsm",
 
   DownloadAddonInstall: "resource://gre/modules/addons/XPIInstall.jsm",
   LocalAddonInstall: "resource://gre/modules/addons/XPIInstall.jsm",
   StagedAddonInstall: "resource://gre/modules/addons/XPIInstall.jsm",
@@ -83,16 +84,20 @@ const PREF_XPI_SIGNATURES_REQUIRED    = 
 const PREF_XPI_SIGNATURES_DEV_ROOT    = "xpinstall.signatures.dev-root";
 const PREF_XPI_PERMISSIONS_BRANCH     = "xpinstall.";
 const PREF_INSTALL_REQUIRESECUREORIGIN = "extensions.install.requireSecureOrigin";
 const PREF_INSTALL_DISTRO_ADDONS      = "extensions.installDistroAddons";
 const PREF_BRANCH_INSTALLED_ADDON     = "extensions.installedDistroAddon.";
 const PREF_INTERPOSITION_ENABLED      = "extensions.interposition.enabled";
 const PREF_SYSTEM_ADDON_SET           = "extensions.systemAddonSet";
 const PREF_SYSTEM_ADDON_UPDATE_URL    = "extensions.systemAddon.update.url";
+const PREF_E10S_BLOCK_ENABLE          = "extensions.e10sBlocksEnabling";
+const PREF_E10S_ADDON_BLOCKLIST       = "extensions.e10s.rollout.blocklist";
+const PREF_E10S_ADDON_POLICY          = "extensions.e10s.rollout.policy";
+const PREF_E10S_HAS_NONEXEMPT_ADDON   = "extensions.e10s.rollout.hasAddon";
 const PREF_ALLOW_LEGACY               = "extensions.legacy.enabled";
 const PREF_ALLOW_NON_MPC              = "extensions.allow-non-mpc-extensions";
 
 const PREF_EM_MIN_COMPAT_APP_VERSION      = "extensions.minCompatibleAppVersion";
 const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
 
 const PREF_EM_HOTFIX_ID               = "extensions.hotfix.id";
 const PREF_EM_LAST_APP_BUILD_ID       = "extensions.lastAppBuildId";
@@ -2165,16 +2170,18 @@ this.XPIProvider = {
 
       this.minCompatibleAppVersion = Services.prefs.getStringPref(PREF_EM_MIN_COMPAT_APP_VERSION,
                                                                   null);
       this.minCompatiblePlatformVersion = Services.prefs.getStringPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
                                                                        null);
 
       Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this);
       Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this);
+      Services.prefs.addObserver(PREF_E10S_ADDON_BLOCKLIST, this);
+      Services.prefs.addObserver(PREF_E10S_ADDON_POLICY, this);
       if (!AppConstants.MOZ_REQUIRE_SIGNING || Cu.isInAutomation)
         Services.prefs.addObserver(PREF_XPI_SIGNATURES_REQUIRED, this);
       Services.prefs.addObserver(PREF_ALLOW_LEGACY, this);
       Services.prefs.addObserver(PREF_ALLOW_NON_MPC, this);
       Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS);
       Services.obs.addObserver(this, NOTIFICATION_TOOLBOX_CONNECTION_CHANGE);
 
 
@@ -4011,18 +4018,108 @@ this.XPIProvider = {
                                                                          null);
         this.updateAddonAppDisabledStates();
         break;
       case PREF_XPI_SIGNATURES_REQUIRED:
       case PREF_ALLOW_LEGACY:
       case PREF_ALLOW_NON_MPC:
         this.updateAddonAppDisabledStates();
         break;
-      }
-    }
+
+      case PREF_E10S_ADDON_BLOCKLIST:
+      case PREF_E10S_ADDON_POLICY:
+        XPIDatabase.updateAddonsBlockingE10s();
+        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(aAddon) {
+    if (aAddon.type != "extension" &&
+        aAddon.type != "theme" &&
+        aAddon.type != "webextension" &&
+        aAddon.type != "webextension-theme")
+      return false;
+
+    // The hotfix is exempt
+    let hotfixID = Services.prefs.getStringPref(PREF_EM_HOTFIX_ID, undefined);
+    if (hotfixID && hotfixID == aAddon.id)
+      return false;
+
+    // The default theme is exempt
+    if (aAddon.type == "theme" &&
+        aAddon.internalName == XPIProvider.defaultSkin)
+      return false;
+
+    // System add-ons are exempt
+    let loc = aAddon._installLocation;
+    if (loc && loc.isSystem)
+      return false;
+
+    if (isAddonPartOfE10SRollout(aAddon)) {
+      Services.prefs.setBoolPref(PREF_E10S_HAS_NONEXEMPT_ADDON, true);
+      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) {
+    // WebExtensions have type = "webextension" or type="webextension-theme",
+    // so they won't block multi.
+    if (aAddon.type != "extension")
+      return false;
+
+    // The hotfix is exempt
+    let hotfixID = Services.prefs.getStringPref(PREF_EM_HOTFIX_ID, undefined);
+    if (hotfixID && hotfixID == aAddon.id)
+      return false;
+
+    // System add-ons are exempt
+    let loc = aAddon._installLocation;
+    if (loc && loc.isSystem)
+      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
+   */
+  e10sBlocksEnabling(aAddon) {
+    // If the preference isn't set then don't block anything
+    if (!Services.prefs.getBoolPref(PREF_E10S_BLOCK_ENABLE, false))
+      return false;
+
+    // If e10s isn't active then don't block anything
+    if (!Services.appinfo.browserTabsRemoteAutostart)
+      return false;
+
+    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
@@ -4051,16 +4148,19 @@ this.XPIProvider = {
       }
 
       // If the theme is already the theme in use then no restart is necessary.
       // This covers the case where the default theme is in use but a
       // lightweight theme is considered active.
       return aAddon.internalName != this.currentSkin;
     }
 
+    if (this.e10sBlocksEnabling(aAddon))
+      return true;
+
     return !aAddon.bootstrap;
   },
 
   /**
    * Tests whether disabling an add-on will require a restart.
    *
    * @param  aAddon
    *         The add-on to test
@@ -4136,16 +4236,19 @@ this.XPIProvider = {
         return true;
     }
 
     // If the add-on is not going to be active after installation then it
     // doesn't require a restart to install.
     if (aAddon.disabled)
       return false;
 
+    if (this.e10sBlocksEnabling(aAddon))
+      return true;
+
     // Themes will require a restart (even if dynamic switching is enabled due
     // to some caching issues) and non-bootstrapped add-ons will require a
     // restart
     return aAddon.type == "theme" || !aAddon.bootstrap;
   },
 
   /**
    * Tests whether uninstalling an add-on will require a restart.
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -45,16 +45,19 @@ var logger = Log.repository.getLogger(LO
 const KEY_PROFILEDIR                  = "ProfD";
 const FILE_JSON_DB                    = "extensions.json";
 
 // 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_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_SYSTEM_ADDONS           = "app-system-addons";
 const KEY_APP_SYSTEM_DEFAULTS         = "app-system-defaults";
 const KEY_APP_GLOBAL                  = "app-global";
 const KEY_APP_TEMPORARY               = "app-temporary";
 
 // Properties to save in JSON file
 const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type",
@@ -308,16 +311,18 @@ 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);
@@ -1067,16 +1072,54 @@ this.XPIDatabase = {
    */
   updateAddonActive(aAddon, aActive) {
     logger.debug("Updating active state for add-on " + aAddon.id + " to " + aActive);
 
     aAddon.active = aActive;
     this.saveChanges();
   },
 
+  updateAddonsBlockingE10s() {
+    if (!this.addonDB) {
+      // jank-tastic! Must synchronously load DB if the theme switches from
+      // an XPI theme to a lightweight theme before the DB has loaded,
+      // because we're called from sync XPIProvider.addonChanged
+      logger.warn("Synchronous load of XPI database due to updateAddonsBlockingE10s()");
+      AddonManagerPrivate.recordSimpleMeasure("XPIDB_lateOpen_byType", XPIProvider.runPhase);
+      this.syncLoadDB(true);
+    }
+
+    let blockE10s = false;
+
+    Services.prefs.setBoolPref(PREF_E10S_HAS_NONEXEMPT_ADDON, false);
+    for (let [, addon] of this.addonDB) {
+      let active = (addon.visible && !addon.disabled && !addon.pendingUninstall);
+
+      if (active && XPIProvider.isBlockingE10s(addon)) {
+        blockE10s = true;
+        break;
+      }
+    }
+    Services.prefs.setBoolPref(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;
+      }
+    }
+    Services.prefs.setBoolPref(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/internal/moz.build
+++ b/toolkit/mozapps/extensions/internal/moz.build
@@ -6,16 +6,17 @@
 
 EXTRA_JS_MODULES.addons += [
     'AddonRepository.jsm',
     'AddonRepository_SQLiteMigrator.jsm',
     'AddonSettings.jsm',
     'AddonUpdateChecker.jsm',
     'APIExtensionBootstrap.js',
     'Content.js',
+    'E10SAddonsRollout.jsm',
     'GMPProvider.jsm',
     'LightweightThemeImageOptimizer.jsm',
     'ProductAddonChecker.jsm',
     'SpellCheckDictionaryBootstrap.js',
     'XPIInstall.jsm',
     'XPIProvider.jsm',
     'XPIProviderUtils.js',
 ]
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js
@@ -0,0 +1,552 @@
+/* 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";
+const ID3 = "bootstrap3@tests.mozilla.org";
+
+const APP_STARTUP   = 1;
+const ADDON_INSTALL = 5;
+
+function getStartupReason(id) {
+  let info = BootstrapMonitor.started.get(id);
+  return info ? info.reason : undefined;
+}
+
+BootstrapMonitor.init();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+
+startupManager();
+
+function check_multi_disabled() {
+  return Services.prefs.getBoolPref("extensions.e10sMultiBlockedByAddons", false);
+}
+
+async function check_normal(checkMultiDisabled) {
+  let install = await 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 = await 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;
+  BootstrapMonitor.checkAddonStarted(ID);
+  do_check_true(addon.isActive);
+  do_check_false(hasFlag(addon.pendingOperations, AddonManager.PENDING_ENABLE));
+
+  do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL));
+  addon.uninstall();
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  BootstrapMonitor.checkAddonNotInstalled(ID);
+
+  await promiseRestartManager();
+}
+
+// Installing the add-on normally doesn't require a restart
+add_task(async function() {
+  gAppInfo.browserTabsRemoteAutostart = false;
+  Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", false);
+
+  await check_normal();
+});
+
+// Enabling the pref doesn't change anything
+add_task(async function() {
+  gAppInfo.browserTabsRemoteAutostart = false;
+  Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
+
+  await check_normal();
+});
+
+// Default e10s doesn't change anything
+add_task(async function() {
+  gAppInfo.browserTabsRemoteAutostart = true;
+  Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", false);
+
+  await check_normal();
+});
+
+// Pref and e10s blocks install
+add_task(async function() {
+  gAppInfo.browserTabsRemoteAutostart = true;
+  Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
+
+  let install = await 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 = await promiseAddonByID(ID);
+  do_check_eq(addon, null);
+
+  await promiseRestartManager();
+
+  BootstrapMonitor.checkAddonInstalled(ID);
+  BootstrapMonitor.checkAddonStarted(ID);
+  do_check_eq(getStartupReason(ID), ADDON_INSTALL);
+
+  addon = await 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));
+
+  await promiseRestartManager();
+
+  addon = await 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);
+
+  await promiseRestartManager();
+});
+
+add_task(async function() {
+  gAppInfo.browserTabsRemoteAutostart = true;
+  Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
+
+  let install = await 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 = await promiseAddonByID(ID);
+  do_check_eq(addon, null);
+
+  await 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 = await 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));
+
+  await 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 = await promiseAddonByID(ID);
+  addon.userDisabled = false;
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  do_check_false(addon.isActive);
+  do_check_true(hasFlag(addon.pendingOperations, AddonManager.PENDING_ENABLE));
+
+  await 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 = await 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.
+  do_check_eq(getStartupReason(ID), APP_STARTUP);
+
+  do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL));
+  addon.uninstall();
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  BootstrapMonitor.checkAddonNotInstalled(ID);
+
+  await 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(async function() {
+  gAppInfo.browserTabsRemoteAutostart = true;
+  Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
+
+  let [install1, install2] = await Promise.all([
+    promiseInstallFile(do_get_addon("test_bootstrap1_1")),
+    promiseInstallFile(do_get_addon("test_bootstrap2_1")),
+  ]);
+
+  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 = await promiseAddonByID(ID);
+  let addon2 = await promiseAddonByID(ID2);
+
+  do_check_eq(addon, null);
+  do_check_eq(addon2, null);
+
+  await 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);
+  do_check_eq(getStartupReason(ID), ADDON_INSTALL);
+
+  BootstrapMonitor.checkAddonInstalled(ID2);
+  BootstrapMonitor.checkAddonStarted(ID2);
+  do_check_eq(getStartupReason(ID2), ADDON_INSTALL);
+
+  addon = await promiseAddonByID(ID);
+  do_check_neq(addon, null);
+  addon2 = await 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));
+
+  await 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 = await 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));
+
+  await 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 = await promiseAddonByID(ID);
+  addon.userDisabled = false;
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  do_check_false(addon.isActive);
+  do_check_true(hasFlag(addon.pendingOperations, AddonManager.PENDING_ENABLE));
+
+  await 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 = await 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);
+
+  do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL));
+  addon.uninstall();
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  BootstrapMonitor.checkAddonNotInstalled(ID);
+
+  await 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 = await promiseAddonByID(ID2);
+  addon2.uninstall();
+
+  restartManager();
+});
+
+// Check that the rollout policy sets work as expected
+add_task(async function() {
+  gAppInfo.browserTabsRemoteAutostart = true;
+  Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true);
+  Services.prefs.setCharPref("extensions.e10s.rollout.policy", "xpcshell-test");
+
+  // Both 'bootstrap1' and 'bootstrap2' addons are part of the allowed policy
+  // set, because they are marked as mpc=true.
+  await check_normal();
+
+  // Check that the two add-ons can be installed together correctly as
+  // check_normal() only perform checks on bootstrap1.
+  let [install1, install2] = await Promise.all([
+    promiseInstallFile(do_get_addon("test_bootstrap1_1")),
+    promiseInstallFile(do_get_addon("test_bootstrap2_1")),
+  ]);
+
+  do_check_eq(install1.state, AddonManager.STATE_INSTALLED);
+  do_check_eq(install2.state, AddonManager.STATE_INSTALLED);
+  do_check_false(hasFlag(install1.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+  do_check_false(hasFlag(install2.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+
+  let addon = await promiseAddonByID(ID);
+  let addon2 = await promiseAddonByID(ID2);
+
+  do_check_neq(addon, null);
+  do_check_neq(addon2, null);
+
+  BootstrapMonitor.checkAddonInstalled(ID);
+  BootstrapMonitor.checkAddonStarted(ID);
+
+  BootstrapMonitor.checkAddonInstalled(ID2);
+  BootstrapMonitor.checkAddonStarted(ID2);
+
+  await promiseRestartManager();
+
+  // After install and restart e10s should not be blocked.
+  let blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_false(blocked);
+
+  // Check that adding bootstrap2 to the blocklist will trigger a disable of e10s.
+  Services.prefs.setCharPref("extensions.e10s.rollout.blocklist", ID2);
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_true(blocked);
+
+  await promiseRestartManager();
+
+  // Check that after restarting, e10s continues to be blocked.
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_true(blocked);
+
+  // Check that uninstalling bootstrap2 (which is in the blocklist) will
+  // cause e10s to be re-enabled.
+  addon2 = await promiseAddonByID(ID2);
+  do_check_false(hasFlag(addon2.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL));
+  addon2.uninstall();
+  BootstrapMonitor.checkAddonNotStarted(ID2);
+  BootstrapMonitor.checkAddonNotInstalled(ID2);
+
+  await promiseRestartManager();
+
+  // After uninstall the blocklisted addon and restart we should not block.
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_false(blocked);
+
+
+  // Let's perform similar checks again, now that bootstrap2 is in the blocklist.
+  // The bootstrap1 add-on should install and start correctly, but bootstrap2 should not.
+  addon = await promiseAddonByID(ID);
+  addon.uninstall();
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  BootstrapMonitor.checkAddonNotInstalled(ID);
+
+  await promiseRestartManager();
+
+  [install1, install2] = await Promise.all([
+    promiseInstallFile(do_get_addon("test_bootstrap1_1")),
+    promiseInstallFile(do_get_addon("test_bootstrap2_1")),
+  ]);
+
+  do_check_eq(install1.state, AddonManager.STATE_INSTALLED);
+  do_check_eq(install2.state, AddonManager.STATE_INSTALLED);
+  do_check_false(hasFlag(install1.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+  do_check_true(hasFlag(install2.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+
+  addon = await promiseAddonByID(ID);
+  addon2 = await promiseAddonByID(ID2);
+
+  do_check_neq(addon, null);
+  do_check_eq(addon2, null);
+
+  BootstrapMonitor.checkAddonInstalled(ID);
+  BootstrapMonitor.checkAddonStarted(ID);
+
+  BootstrapMonitor.checkAddonNotInstalled(ID2);
+  BootstrapMonitor.checkAddonNotStarted(ID2);
+
+  await promiseRestartManager();
+
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_true(blocked);
+
+  // Now let's test that an add-on that is not mpc=true doesn't get installed,
+  // since the rollout policy is restricted to only mpc=true addons.
+  let install3 = await promiseInstallFile(do_get_addon("test_bootstrap3_1"));
+
+  do_check_eq(install3.state, AddonManager.STATE_INSTALLED);
+  do_check_true(hasFlag(install3.addon.pendingOperations, AddonManager.PENDING_INSTALL));
+
+  let addon3 = await promiseAddonByID(ID3);
+
+  do_check_eq(addon3, null);
+
+  BootstrapMonitor.checkAddonNotInstalled(ID3);
+  BootstrapMonitor.checkAddonNotStarted(ID3);
+
+  await promiseRestartManager();
+
+  blocked = Services.prefs.getBoolPref("extensions.e10sBlockedByAddons");
+  do_check_true(blocked);
+
+  // Clean-up
+  addon = await promiseAddonByID(ID);
+  addon2 = await promiseAddonByID(ID2);
+  addon3 = await promiseAddonByID(ID3);
+
+  addon.uninstall();
+  BootstrapMonitor.checkAddonNotStarted(ID);
+  BootstrapMonitor.checkAddonNotInstalled(ID);
+
+  addon2.uninstall();
+  BootstrapMonitor.checkAddonNotStarted(ID2);
+  BootstrapMonitor.checkAddonNotInstalled(ID2);
+
+  addon3.uninstall();
+  BootstrapMonitor.checkAddonNotStarted(ID3);
+  BootstrapMonitor.checkAddonNotInstalled(ID3);
+
+  Services.prefs.clearUserPref("extensions.e10s.rollout.policy");
+  Services.prefs.clearUserPref("extensions.e10s.rollout.blocklist");
+
+  await promiseRestartManager();
+});
+
+// The hotfix is unaffected
+add_task(async 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);
+
+  await check_normal(true);
+});
+
+// Test non-restarless add-on should block multi
+add_task(async function() {
+  await promiseInstallAllFiles([do_get_addon("test_install1")], true);
+
+  let non_restartless_ID = "addon1@tests.mozilla.org";
+  let addon = await promiseAddonByID(non_restartless_ID);
+
+  // non-restartless add-on is installed and started
+  do_check_eq(addon, null);
+
+  await promiseRestartManager();
+
+  do_check_true(check_multi_disabled());
+
+  addon = await promiseAddonByID(non_restartless_ID);
+  addon.uninstall();
+
+  BootstrapMonitor.checkAddonNotInstalled(non_restartless_ID);
+  BootstrapMonitor.checkAddonNotStarted(non_restartless_ID);
+
+  await promiseRestartManager();
+});
+
+// Test experiment add-on should not block multi
+add_task(async function() {
+  await 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 = await 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);
+
+  await promiseRestartManager();
+});
+
+const { GlobalManager } = Components.utils.import("resource://gre/modules/Extension.jsm", {});
+
+// Test web extension add-on's should not block multi
+add_task(async function() {
+
+  await promiseInstallAllFiles([do_get_addon("webextension_1")], true),
+
+  restartManager();
+
+  await promiseWebExtensionStartup();
+
+  let we_ID = "webextension1@tests.mozilla.org";
+
+  do_check_eq(GlobalManager.extensionMap.size, 1);
+
+  let addon = await promiseAddonByID(we_ID);
+
+  do_check_neq(addon, null);
+
+  do_check_false(check_multi_disabled());
+
+  addon.uninstall();
+
+  BootstrapMonitor.checkAddonNotInstalled(we_ID);
+  BootstrapMonitor.checkAddonNotStarted(we_ID);
+
+  await promiseRestartManager();
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -334,16 +334,17 @@ tags = webextensions
 [test_webextension_install_syntax_error.js]
 skip-if = appname == "thunderbird"
 tags = webextensions
 [test_bootstrap_globals.js]
 [test_bug1180901_2.js]
 skip-if = os != "win"
 [test_bug1180901.js]
 skip-if = os != "win"
+[test_e10s_restartless.js]
 [test_switch_os.js]
 # Bug 1246231
 skip-if = os == "mac" && debug
 [test_softblocked.js]
 tags = blocklist
 [test_ext_management.js]
 skip-if = appname == "thunderbird"
 tags = webextensions
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -950,16 +950,17 @@ nsXULAppInfo::GetRemoteType(nsAString& a
   }
 
   return NS_OK;
 }
 
 static bool gBrowserTabsRemoteAutostart = false;
 static uint64_t gBrowserTabsRemoteStatus = 0;
 static bool gBrowserTabsRemoteAutostartInitialized = false;
+static bool gListeningForCohortChange = false;
 
 NS_IMETHODIMP
 nsXULAppInfo::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) {
   if (!nsCRT::strcmp(aTopic, "getE10SBlocked")) {
     nsCOMPtr<nsISupportsPRUint64> ret = do_QueryInterface(aSubject);
     if (!ret)
       return NS_ERROR_FAILURE;
 
@@ -980,16 +981,23 @@ nsXULAppInfo::GetBrowserTabsRemoteAutost
 NS_IMETHODIMP
 nsXULAppInfo::GetMaxWebProcessCount(uint32_t* aResult)
 {
   *aResult = mozilla::GetMaxWebProcessCount();
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsXULAppInfo::GetMultiprocessBlockPolicy(uint32_t* aResult)
+{
+  *aResult = MultiprocessBlockPolicy();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsXULAppInfo::GetAccessibilityEnabled(bool* aResult)
 {
 #ifdef ACCESSIBILITY
   *aResult = GetAccService() != nullptr;
 #else
   *aResult = false;
 #endif
   return NS_OK;
@@ -5095,74 +5103,163 @@ enum {
   kE10sForceDisabled = 8,
   // kE10sDisabledForXPAcceleration = 9, removed in bug 1296353
   // kE10sDisabledForOperatingSystem = 10, removed due to xp-eol
 };
 
 const char* kForceEnableE10sPref = "browser.tabs.remote.force-enable";
 const char* kForceDisableE10sPref = "browser.tabs.remote.force-disable";
 
+uint32_t
+MultiprocessBlockPolicy()
+{
+  if (XRE_IsContentProcess()) {
+    // If we're in a content process, we're not blocked.
+    return 0;
+  }
+
+  /**
+   * Avoids enabling e10s if there are add-ons installed.
+   */
+  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 (addonsCanDisable && disabledByAddons) {
+    return kE10sDisabledForAddons;
+  }
+
+  /*
+   * None of the blocking policies matched, so e10s is allowed to run. Return
+   * 0, indicating success.
+   */
+  return 0;
+}
+
 namespace mozilla {
 
+static void
+CohortChanged(const char* aPref, void* aClosure)
+{
+  // Reset to the default state and recompute on the next call.
+  gBrowserTabsRemoteAutostartInitialized = false;
+  gBrowserTabsRemoteAutostart = false;
+  Preferences::UnregisterCallback(CohortChanged, "e10s.rollout.cohort");
+}
+
 bool
 BrowserTabsRemoteAutostart()
 {
   if (gBrowserTabsRemoteAutostartInitialized) {
     return gBrowserTabsRemoteAutostart;
   }
   gBrowserTabsRemoteAutostartInitialized = true;
 
   // If we're in the content process, we are running E10S.
   if (XRE_IsContentProcess()) {
     gBrowserTabsRemoteAutostart = true;
     return gBrowserTabsRemoteAutostart;
   }
 
-  bool optInPref = Preferences::GetBool("browser.tabs.remote.autostart", true);
-  int status = kE10sEnabledByDefault;
-
+  // This is a pretty heinous hack. On the first launch, we end up retrieving
+  // whether e10s is enabled setting up a document very early in startup. This
+  // caches that e10s is off before the e10srollout extension can run. See
+  // bug 1372824 comment 3 for a more thorough explanation.
+  if (!gListeningForCohortChange) {
+    gListeningForCohortChange = true;
+    Preferences::RegisterCallback(CohortChanged, "e10s.rollout.cohort");
+  }
+
+  bool optInPref = Preferences::GetBool("browser.tabs.remote.autostart", false);
+  bool trialPref = Preferences::GetBool("browser.tabs.remote.autostart.2", false);
+  bool prefEnabled = optInPref || trialPref;
+  int status;
   if (optInPref) {
-    gBrowserTabsRemoteAutostart = true;
+    status = kE10sEnabledByUser;
+  } else if (trialPref) {
+    status = kE10sEnabledByDefault;
   } else {
     status = kE10sDisabledByUser;
   }
 
+  if (prefEnabled) {
+    uint32_t blockPolicy = MultiprocessBlockPolicy();
+    if (blockPolicy != 0) {
+      status = blockPolicy;
+    } else {
+      gBrowserTabsRemoteAutostart = true;
+    }
+  }
+
   // Uber override pref for manual testing purposes
   if (Preferences::GetBool(kForceEnableE10sPref, false)) {
     gBrowserTabsRemoteAutostart = true;
+    prefEnabled = true;
     status = kE10sEnabledByUser;
   }
 
   // Uber override pref for emergency blocking
   if (gBrowserTabsRemoteAutostart &&
       (Preferences::GetBool(kForceDisableE10sPref, false) ||
        EnvHasValue("MOZ_FORCE_DISABLE_E10S"))) {
     gBrowserTabsRemoteAutostart = false;
     status = kE10sForceDisabled;
   }
 
   gBrowserTabsRemoteStatus = status;
 
   mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_STATUS, status);
+  if (prefEnabled) {
+    mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_BLOCKED_FROM_RUNNING,
+                                    !gBrowserTabsRemoteAutostart);
+  }
   return gBrowserTabsRemoteAutostart;
 }
 
 uint32_t
 GetMaxWebProcessCount()
 {
   // multiOptOut is in int to allow us to run multiple experiments without
   // introducing multiple prefs a la the autostart.N prefs.
   if (Preferences::GetInt("dom.ipc.multiOptOut", 0) >=
           nsIXULRuntime::E10S_MULTI_EXPERIMENT) {
     return 1;
   }
 
   const char* optInPref = "dom.ipc.processCount";
   uint32_t optInPrefValue = Preferences::GetInt(optInPref, 1);
-  return std::max(1u, optInPrefValue);
+  const char* useDefaultPerformanceSettings =
+    "browser.preferences.defaultPerformanceSettings.enabled";
+  bool useDefaultPerformanceSettingsValue =
+    Preferences::GetBool(useDefaultPerformanceSettings, true);
+
+  // If the user has set dom.ipc.processCount, or if they have opt out of
+  // default performances settings from about:preferences, respect their
+  // decision regardless of add-ons that might affect their experience or
+  // experiment cohort.
+  if (Preferences::HasUserValue(optInPref) || !useDefaultPerformanceSettingsValue) {
+    return std::max(1u, optInPrefValue);
+  }
+
+#ifdef RELEASE_OR_BETA
+  // For our rollout on Release and Beta, we set this pref from the
+  // e10srollout extension. On Nightly, we don't touch the pref at all,
+  // allowing stale values to disable e10s-multi for certain users.
+  if (Preferences::HasUserValue("dom.ipc.processCount.web")) {
+    // The user didn't opt in or out so read the .web version of the pref.
+    return std::max(1, Preferences::GetInt("dom.ipc.processCount.web", 1));
+  }
+#endif
+
+  return optInPrefValue;
 }
 
 const char*
 PlatformBuildID()
 {
   return gToolkitBuildID;
 }
 
--- a/toolkit/xre/nsAppRunner.h
+++ b/toolkit/xre/nsAppRunner.h
@@ -124,13 +124,20 @@ const char* PlatformBuildID();
 } // namespace mozilla
 
 /**
  * Set up platform specific error handling such as suppressing DLL load dialog
  * and the JIT debugger on Windows, and install unix signal handlers.
  */
 void SetupErrorHandling(const char* progname);
 
+/**
+ * A numeric value indicating whether multiprocess might be blocked.
+ * Possible values can be found at nsAppRunner.cpp. A value of 0
+ * represents not blocking.
+ */
+uint32_t MultiprocessBlockPolicy();
+
 #ifdef MOZ_WIDGET_GTK
 const char* DetectDisplay();
 #endif
 
 #endif // nsAppRunner_h__
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -50,16 +50,17 @@
   "dispatcher.js": ["dispatcher"],
   "distribution.js": ["DistributionCustomizer"],
   "DNSTypes.jsm": ["DNS_QUERY_RESPONSE_CODES", "DNS_AUTHORITATIVE_ANSWER_CODES", "DNS_CLASS_CODES", "DNS_RECORD_TYPES"],
   "doctor.js": ["Doctor"],
   "dom.js": ["getAttributes"],
   "DOMRequestHelper.jsm": ["DOMRequestIpcHelper"],
   "DownloadCore.jsm": ["Download", "DownloadSource", "DownloadTarget", "DownloadError", "DownloadSaver", "DownloadCopySaver", "DownloadLegacySaver", "DownloadPDFSaver"],
   "DownloadList.jsm": ["DownloadList", "DownloadCombinedList", "DownloadSummary"],
+  "E10SAddonsRollout.jsm": ["isAddonPartOfE10SRollout"],
   "elementslib.js": ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath", "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib"],
   "engines.js": ["EngineManager", "Engine", "SyncEngine", "Tracker", "Store", "Changeset"],
   "enginesync.js": ["EngineSynchronizer"],
   "errors.js": ["BaseError", "ApplicationQuitError", "AssertionError", "TimeoutError"],
   "evaluate.js": ["evaluate", "sandbox", "Sandboxes"],
   "event-emitter.js": ["EventEmitter"],
   "EventUtils.js": ["disableNonTestMouseEvents", "sendMouseEvent", "sendChar", "sendString", "sendKey", "synthesizeMouse", "synthesizeTouch", "synthesizeMouseAtPoint", "synthesizeTouchAtPoint", "synthesizeMouseAtCenter", "synthesizeTouchAtCenter", "synthesizeWheel", "synthesizeKey", "synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent", "synthesizeText", "synthesizeComposition", "synthesizeQuerySelectedText"],
   "Extension.jsm": ["Extension", "ExtensionData"],
--- a/xpcom/system/nsIXULRuntime.idl
+++ b/xpcom/system/nsIXULRuntime.idl
@@ -115,16 +115,23 @@ interface nsIXULRuntime : nsISupports
 
   /**
    * The current e10s-multi experiment number. Set dom.ipc.multiOptOut to (at
    * least) this to disable it until the next experiment.
    */
   const uint32_t E10S_MULTI_EXPERIMENT = 1;
 
   /**
+   * A numeric value indicating whether multiprocess might be blocked.
+   * Possible values can be found at nsAppRunner.cpp. A value of 0
+   * represents not blocking.
+   */
+   readonly attribute unsigned long multiprocessBlockPolicy;
+
+  /**
    * If true, the accessibility service is running.
    */
   readonly attribute boolean accessibilityEnabled;
 
   /**
    * If true, the AccessibleHandler dll is used.
    */
   readonly attribute boolean accessibleHandlerUsed;