tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

experimental.js (5884B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /* import-globals-from preferences.js */
      6 
      7 ChromeUtils.defineESModuleGetters(this, {
      8  ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
      9  FirefoxLabs: "resource://nimbus/FirefoxLabs.sys.mjs",
     10 });
     11 
     12 const gExperimentalPane = {
     13  inited: false,
     14  _featureGatesContainer: null,
     15  _firefoxLabs: null,
     16 
     17  async init() {
     18    if (this.inited) {
     19      return;
     20    }
     21 
     22    this.inited = true;
     23    this._featureGatesContainer = document.getElementById(
     24      "pane-experimental-featureGates"
     25    );
     26 
     27    this._onCheckboxChanged = this._onCheckboxChanged.bind(this);
     28    this._onNimbusUpdate = this._onNimbusUpdate.bind(this);
     29    this._resetAllFeatures = this._resetAllFeatures.bind(this);
     30 
     31    setEventListener(
     32      "experimentalCategory-reset",
     33      "click",
     34      this._resetAllFeatures
     35    );
     36 
     37    window.addEventListener("unload", () => this._removeObservers());
     38 
     39    await this._maybeRenderLabsRecipes();
     40  },
     41 
     42  async _maybeRenderLabsRecipes() {
     43    this._firefoxLabs = await FirefoxLabs.create();
     44 
     45    const shouldHide = this._firefoxLabs.count === 0;
     46    this._setCategoryVisibility(shouldHide);
     47 
     48    if (shouldHide) {
     49      return;
     50    }
     51 
     52    const frag = document.createDocumentFragment();
     53 
     54    const groups = new Map();
     55    for (const optIn of this._firefoxLabs.all()) {
     56      if (!groups.has(optIn.firefoxLabsGroup)) {
     57        groups.set(optIn.firefoxLabsGroup, []);
     58      }
     59 
     60      groups.get(optIn.firefoxLabsGroup).push(optIn);
     61    }
     62 
     63    for (const [group, optIns] of groups) {
     64      const card = document.createElement("moz-card");
     65      card.classList.add("featureGate");
     66 
     67      const fieldset = document.createElement("moz-fieldset");
     68      document.l10n.setAttributes(fieldset, group);
     69 
     70      card.append(fieldset);
     71 
     72      for (const optIn of optIns) {
     73        const checkbox = document.createElement("moz-checkbox");
     74        checkbox.dataset.nimbusSlug = optIn.slug;
     75        checkbox.dataset.nimbusBranchSlug = optIn.branches[0].slug;
     76        const description = document.createElement("div");
     77        description.slot = "description";
     78        description.id = `${optIn.slug}-description`;
     79        description.classList.add("featureGateDescription");
     80 
     81        for (const [key, value] of Object.entries(
     82          optIn.firefoxLabsDescriptionLinks ?? {}
     83        )) {
     84          const link = document.createElement("a");
     85          link.setAttribute("data-l10n-name", key);
     86          link.setAttribute("href", value);
     87          link.setAttribute("target", "_blank");
     88 
     89          description.append(link);
     90        }
     91 
     92        document.l10n.setAttributes(description, optIn.firefoxLabsDescription);
     93        checkbox.id = optIn.slug;
     94        checkbox.setAttribute("aria-describedby", description.id);
     95        document.l10n.setAttributes(checkbox, optIn.firefoxLabsTitle);
     96 
     97        checkbox.checked =
     98          ExperimentAPI.manager.store.get(optIn.slug)?.active ?? false;
     99        checkbox.addEventListener("change", this._onCheckboxChanged);
    100 
    101        checkbox.append(description);
    102        fieldset.append(checkbox);
    103      }
    104 
    105      frag.append(card);
    106    }
    107 
    108    this._featureGatesContainer.appendChild(frag);
    109 
    110    ExperimentAPI.manager.store.on("update", this._onNimbusUpdate);
    111 
    112    Services.obs.notifyObservers(window, "experimental-pane-loaded");
    113  },
    114 
    115  _removeLabsRecipes() {
    116    ExperimentAPI.manager.store.off("update", this._onNimbusUpdate);
    117 
    118    this._featureGatesContainer
    119      .querySelectorAll(".featureGate")
    120      .forEach(el => el.remove());
    121  },
    122 
    123  async _onCheckboxChanged(event) {
    124    const target = event.target;
    125 
    126    const slug = target.dataset.nimbusSlug;
    127    const branchSlug = target.dataset.nimbusBranchSlug;
    128 
    129    const enrolling = !(ExperimentAPI.manager.store.get(slug)?.active ?? false);
    130 
    131    let shouldRestart = false;
    132    if (this._firefoxLabs.get(slug).requiresRestart) {
    133      const buttonIndex = await confirmRestartPrompt(enrolling, 1, true, false);
    134      shouldRestart = buttonIndex === CONFIRM_RESTART_PROMPT_RESTART_NOW;
    135 
    136      if (!shouldRestart) {
    137        // The user declined to restart, so we will not enroll in the opt-in.
    138        target.checked = false;
    139        return;
    140      }
    141    }
    142 
    143    // Disable the checkbox so that the user cannot interact with it during enrollment.
    144    target.disabled = true;
    145 
    146    if (enrolling) {
    147      await this._firefoxLabs.enroll(slug, branchSlug);
    148    } else {
    149      this._firefoxLabs.unenroll(slug);
    150    }
    151 
    152    target.disabled = false;
    153 
    154    if (shouldRestart) {
    155      Services.startup.quit(
    156        Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart
    157      );
    158    }
    159  },
    160 
    161  _onNimbusUpdate(_event, { slug, active }) {
    162    if (this._firefoxLabs.get(slug)) {
    163      document.getElementById(slug).checked = active;
    164    }
    165  },
    166 
    167  _removeObservers() {
    168    ExperimentAPI.manager.store.off("update", this._onNimbusUpdate);
    169  },
    170 
    171  // Reset the features to their default values
    172  async _resetAllFeatures() {
    173    for (const optIn of this._firefoxLabs.all()) {
    174      const enrolled =
    175        (await ExperimentAPI.manager.store.get(optIn.slug)?.active) ?? false;
    176      if (enrolled) {
    177        this._firefoxLabs.unenroll(optIn.slug);
    178      }
    179    }
    180  },
    181 
    182  _setCategoryVisibility(shouldHide) {
    183    document.getElementById("category-experimental").hidden = shouldHide;
    184 
    185    // Cache the visibility so we can show it quicker in subsequent loads.
    186    Services.prefs.setBoolPref(
    187      "browser.preferences.experimental.hidden",
    188      shouldHide
    189    );
    190 
    191    if (
    192      shouldHide &&
    193      document.getElementById("categories").selectedItem?.id ==
    194        "category-experimental"
    195    ) {
    196      // Leave the 'experimental' category if there are no available features
    197      gotoPref("general");
    198    }
    199  },
    200 };