/* -*- 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/Preferences.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/UpdateUtils.jsm"); Cu.import("resource://gre/modules/AppConstants.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. const MULTI_BUCKETS = { "beta": { 1: .5, 4: 1, }, }; 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_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]; Preferences.set(PREF_E10S_ADDON_POLICY, addonPolicy); // This is also the proper place to set the blocklist pref // in case it is necessary. Preferences.set(PREF_E10S_ADDON_BLOCKLIST, // bug 1185672 - Tab Mix Plus "{dc572301-7619-498c-a57d-39143191b318};" // bug 1344345 - Mega + "firefox@mega.co.nz" ); } else { Preferences.reset(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 = Preferences.get(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) { setCohort("optedIn"); eligibleForMulti = true; } 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. setCohort(`temp-disqualified-${temporaryDisqualification}`); Preferences.reset(PREF_TOGGLE_E10S); Preferences.reset(PREF_E10S_PROCESSCOUNT + ".web"); } 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. setCohort(`temp-qualified-${temporaryQualification}`); Preferences.set(PREF_TOGGLE_E10S, true); eligibleForMulti = true; } else if (testGroup) { setCohort(`${cohortPrefix}test`); Preferences.set(PREF_TOGGLE_E10S, true); eligibleForMulti = true; } else { setCohort(`${cohortPrefix}control`); Preferences.reset(PREF_TOGGLE_E10S); Preferences.reset(PREF_E10S_PROCESSCOUNT + ".web"); } // Now determine if this user should be in the e10s-multi experiment. // - We only run the experiment on channels defined in MULTI_BUCKETS. // - 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_BUCKETS) || !eligibleForMulti || userOptedIn.multi || disqualified || getAddonsDisqualifyForMulti()) { Preferences.reset(PREF_E10S_PROCESSCOUNT + ".web"); return; } // If we got here with a cohortPrefix, it must be "addons-set50allmpc-", // and we know because of getAddonsDisqualifyForMulti that the addons that // are installed must be web extensions. if (cohortPrefix) { cohortPrefix = "webextensions-"; } // The user is in the multi experiment! // Decide how many content processes to use for this user. let buckets = MULTI_BUCKETS[updateChannel]; let multiUserSample = getUserSample(true); for (let sampleName of Object.getOwnPropertyNames(buckets)) { if (multiUserSample < buckets[sampleName]) { setCohort(`${cohortPrefix}multiBucket${sampleName}`); Preferences.set(PREF_E10S_PROCESSCOUNT + ".web", sampleName); break; } } } function shutdown(data, reason) { } function uninstall() { } function getUserSample(multi) { let pref = multi ? (PREF_COHORT_SAMPLE + ".multi") : PREF_COHORT_SAMPLE; let prefValue = Preferences.get(pref, undefined); let value = 0.0; if (typeof(prefValue) == "string") { value = parseFloat(prefValue, 10); return value; } if (typeof(prefValue) == "number") { // convert old integer value value = prefValue / 100; } else { value = Math.random(); } Preferences.set(pref, value.toString().substr(0, 8)); return value; } function setCohort(cohortName) { Preferences.set(PREF_COHORT_NAME, cohortName); try { if (Ci.nsICrashReporter) { Services.appinfo.QueryInterface(Ci.nsICrashReporter).annotateCrashReport("E10SCohort", cohortName); } } catch (e) {} } function optedIn() { let e10s = Preferences.get(PREF_E10S_OPTED_IN, false) || Preferences.get(PREF_E10S_FORCE_ENABLED, false); let multi = Preferences.isSet(PREF_E10S_PROCESSCOUNT); 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 = Preferences.get(PREF_E10S_FORCE_DISABLED, false) || (Preferences.isSet(PREF_TOGGLE_E10S) && Preferences.get(PREF_TOGGLE_E10S) == false); let multi = Preferences.get(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 = Preferences.isSet(PREF_OPENED_DEVTOOLS); if (hasOpenedDevTools) { return "devtools"; } return ""; } function getAddonsDisqualifyForMulti() { return Preferences.get("extensions.e10sMultiBlocksEnabling", false) && Preferences.get("extensions.e10sMultiBlockedByAddons", false); }