/* -*- 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.5,  // 50%
  "release" : 1.0,  // 100%
  "esr"     : 1.0,  // 100%
};

const ADDON_ROLLOUT_POLICY = {
  "beta"    : "51alladdons", // Any WebExtension or addon except with mpc = false
  "release" : "51set1",
  "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";

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 1332692 - LastPass
                    "support@lastpass.com;");
  } else {
    Preferences.reset(PREF_E10S_ADDON_POLICY);
  }

  let userOptedOut = optedOut();
  let userOptedIn = optedIn();
  let disqualified = (Services.appinfo.multiprocessBlockPolicy != 0);
  let testGroup = (getUserSample() < TEST_THRESHOLD[updateChannel]);
  let hasNonExemptAddon = Preferences.get(PREF_E10S_HAS_NONEXEMPT_ADDON, false);
  let temporaryDisqualification = getTemporaryDisqualification();

  let cohortPrefix = "";
  if (disqualified) {
    cohortPrefix = "disqualified-";
  } else if (hasNonExemptAddon) {
    cohortPrefix = `addons-set${addonPolicy}-`;
  }

  if (userOptedOut) {
    setCohort("optedOut");
  } else if (userOptedIn) {
    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.
    setCohort(`temp-disqualified-${temporaryDisqualification}`);
    Preferences.reset(PREF_TOGGLE_E10S);
  } else if (testGroup) {
    setCohort(`${cohortPrefix}test`);
    Preferences.set(PREF_TOGGLE_E10S, true);
  } else {
    setCohort(`${cohortPrefix}control`);
    Preferences.reset(PREF_TOGGLE_E10S);
  }
}

function shutdown(data, reason) {
}

function uninstall() {
}

function getUserSample() {
  let prefValue = Preferences.get(PREF_COHORT_SAMPLE, 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_COHORT_SAMPLE, 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() {
  return Preferences.get(PREF_E10S_OPTED_IN, false) ||
         Preferences.get(PREF_E10S_FORCE_ENABLED, false);
}

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.
  return Preferences.get(PREF_E10S_FORCE_DISABLED, false) ||
         (Preferences.isSet(PREF_TOGGLE_E10S) &&
          Preferences.get(PREF_TOGGLE_E10S) == false);
}

/* 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 "";
}
