mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	Writing enrollments to the SQL database is an async process, so the entire unenroll flow needs to become async. This patch lays the groundwork for making that happen by updating our testing helpers to use async functions, as well as adding some new helpers for asserting the state of the enrollments database. For now the unenroll() (_unenroll()) functions are marked async but otherwise have no behavioural changes -- this is just a first step to port all the tests over before landing changes that write to the enrollments store (which have to all be landed together). Most callers of unenroll() have been updated so that they await the result. There are a few callers left that do not await the result, however, mostly because doing so causes race conditions in tests (most notably in the pref observers in ExperimentManager and the PrefFlipsFeature). These issues will be addressed in bug 1956082. Differential Revision: https://phabricator.services.mozilla.com/D250504
		
			
				
	
	
		
			221 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			221 lines
		
	
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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/. */
 | 
						|
 | 
						|
/* import-globals-from preferences.js */
 | 
						|
 | 
						|
ChromeUtils.defineESModuleGetters(this, {
 | 
						|
  ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
 | 
						|
  FirefoxLabs: "resource://nimbus/FirefoxLabs.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
const gExperimentalPane = {
 | 
						|
  inited: false,
 | 
						|
  _featureGatesContainer: null,
 | 
						|
  _firefoxLabs: null,
 | 
						|
 | 
						|
  async init() {
 | 
						|
    if (this.inited) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    this.inited = true;
 | 
						|
    this._featureGatesContainer = document.getElementById(
 | 
						|
      "pane-experimental-featureGates"
 | 
						|
    );
 | 
						|
 | 
						|
    this._onCheckboxChanged = this._onCheckboxChanged.bind(this);
 | 
						|
    this._onNimbusUpdate = this._onNimbusUpdate.bind(this);
 | 
						|
    this._onStudiesEnabledChanged = this._onStudiesEnabledChanged.bind(this);
 | 
						|
    this._resetAllFeatures = this._resetAllFeatures.bind(this);
 | 
						|
 | 
						|
    setEventListener(
 | 
						|
      "experimentalCategory-reset",
 | 
						|
      "click",
 | 
						|
      this._resetAllFeatures
 | 
						|
    );
 | 
						|
 | 
						|
    Services.obs.addObserver(
 | 
						|
      this._onStudiesEnabledChanged,
 | 
						|
      ExperimentAPI.STUDIES_ENABLED_CHANGED
 | 
						|
    );
 | 
						|
    window.addEventListener("unload", () => this._removeObservers());
 | 
						|
 | 
						|
    await this._maybeRenderLabsRecipes();
 | 
						|
  },
 | 
						|
 | 
						|
  async _maybeRenderLabsRecipes() {
 | 
						|
    this._firefoxLabs = await FirefoxLabs.create();
 | 
						|
 | 
						|
    const shouldHide = this._firefoxLabs.count === 0;
 | 
						|
    this._setCategoryVisibility(shouldHide);
 | 
						|
 | 
						|
    if (shouldHide) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    const frag = document.createDocumentFragment();
 | 
						|
 | 
						|
    const groups = new Map();
 | 
						|
    for (const optIn of this._firefoxLabs.all()) {
 | 
						|
      if (!groups.has(optIn.firefoxLabsGroup)) {
 | 
						|
        groups.set(optIn.firefoxLabsGroup, []);
 | 
						|
      }
 | 
						|
 | 
						|
      groups.get(optIn.firefoxLabsGroup).push(optIn);
 | 
						|
    }
 | 
						|
 | 
						|
    for (const [group, optIns] of groups) {
 | 
						|
      const card = document.createElement("moz-card");
 | 
						|
      card.classList.add("featureGate");
 | 
						|
 | 
						|
      const fieldset = document.createElement("moz-fieldset");
 | 
						|
      document.l10n.setAttributes(fieldset, group);
 | 
						|
 | 
						|
      card.append(fieldset);
 | 
						|
 | 
						|
      for (const optIn of optIns) {
 | 
						|
        const checkbox = document.createElement("moz-checkbox");
 | 
						|
        checkbox.dataset.nimbusSlug = optIn.slug;
 | 
						|
        checkbox.dataset.nimbusBranchSlug = optIn.branches[0].slug;
 | 
						|
        const description = document.createElement("div");
 | 
						|
        description.slot = "description";
 | 
						|
        description.id = `${optIn.slug}-description`;
 | 
						|
        description.classList.add("featureGateDescription");
 | 
						|
 | 
						|
        for (const [key, value] of Object.entries(
 | 
						|
          optIn.firefoxLabsDescriptionLinks ?? {}
 | 
						|
        )) {
 | 
						|
          const link = document.createElement("a");
 | 
						|
          link.setAttribute("data-l10n-name", key);
 | 
						|
          link.setAttribute("href", value);
 | 
						|
          link.setAttribute("target", "_blank");
 | 
						|
 | 
						|
          description.append(link);
 | 
						|
        }
 | 
						|
 | 
						|
        document.l10n.setAttributes(description, optIn.firefoxLabsDescription);
 | 
						|
        checkbox.id = optIn.slug;
 | 
						|
        checkbox.setAttribute("aria-describedby", description.id);
 | 
						|
        document.l10n.setAttributes(checkbox, optIn.firefoxLabsTitle);
 | 
						|
 | 
						|
        checkbox.checked =
 | 
						|
          ExperimentAPI.manager.store.get(optIn.slug)?.active ?? false;
 | 
						|
        checkbox.addEventListener("change", this._onCheckboxChanged);
 | 
						|
 | 
						|
        checkbox.append(description);
 | 
						|
        fieldset.append(checkbox);
 | 
						|
      }
 | 
						|
 | 
						|
      frag.append(card);
 | 
						|
    }
 | 
						|
 | 
						|
    this._featureGatesContainer.appendChild(frag);
 | 
						|
 | 
						|
    ExperimentAPI.manager.store.on("update", this._onNimbusUpdate);
 | 
						|
 | 
						|
    Services.obs.notifyObservers(window, "experimental-pane-loaded");
 | 
						|
  },
 | 
						|
 | 
						|
  _removeLabsRecipes() {
 | 
						|
    ExperimentAPI.manager.store.off("update", this._onNimbusUpdate);
 | 
						|
 | 
						|
    this._featureGatesContainer
 | 
						|
      .querySelectorAll(".featureGate")
 | 
						|
      .forEach(el => el.remove());
 | 
						|
  },
 | 
						|
 | 
						|
  async _onCheckboxChanged(event) {
 | 
						|
    const target = event.target;
 | 
						|
 | 
						|
    const slug = target.dataset.nimbusSlug;
 | 
						|
    const branchSlug = target.dataset.nimbusBranchSlug;
 | 
						|
 | 
						|
    const enrolling = !(ExperimentAPI.manager.store.get(slug)?.active ?? false);
 | 
						|
 | 
						|
    let shouldRestart = false;
 | 
						|
    if (this._firefoxLabs.get(slug).requiresRestart) {
 | 
						|
      const buttonIndex = await confirmRestartPrompt(enrolling, 1, true, false);
 | 
						|
      shouldRestart = buttonIndex === CONFIRM_RESTART_PROMPT_RESTART_NOW;
 | 
						|
 | 
						|
      if (!shouldRestart) {
 | 
						|
        // The user declined to restart, so we will not enroll in the opt-in.
 | 
						|
        target.checked = false;
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Disable the checkbox so that the user cannot interact with it during enrollment.
 | 
						|
    target.disabled = true;
 | 
						|
 | 
						|
    if (enrolling) {
 | 
						|
      await this._firefoxLabs.enroll(slug, branchSlug);
 | 
						|
    } else {
 | 
						|
      await this._firefoxLabs.unenroll(slug);
 | 
						|
    }
 | 
						|
 | 
						|
    target.disabled = false;
 | 
						|
 | 
						|
    if (shouldRestart) {
 | 
						|
      Services.startup.quit(
 | 
						|
        Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart
 | 
						|
      );
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _onNimbusUpdate(_event, { slug, active }) {
 | 
						|
    if (this._firefoxLabs.get(slug)) {
 | 
						|
      document.getElementById(slug).checked = active;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  async _onStudiesEnabledChanged() {
 | 
						|
    const studiesEnabled = ExperimentAPI.studiesEnabled;
 | 
						|
 | 
						|
    if (studiesEnabled) {
 | 
						|
      await this._maybeRenderLabsRecipes();
 | 
						|
    } else {
 | 
						|
      this._setCategoryVisibility(true);
 | 
						|
      this._removeLabsRecipes();
 | 
						|
      this._firefoxLabs = null;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _removeObservers() {
 | 
						|
    ExperimentAPI.manager.store.off("update", this._onNimbusUpdate);
 | 
						|
    Services.obs.removeObserver(
 | 
						|
      this._onStudiesEnabledChanged,
 | 
						|
      ExperimentAPI.STUDIES_ENABLED_CHANGED
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  // Reset the features to their default values
 | 
						|
  async _resetAllFeatures() {
 | 
						|
    for (const optIn of this._firefoxLabs.all()) {
 | 
						|
      const enrolled =
 | 
						|
        (await ExperimentAPI.manager.store.get(optIn.slug)?.active) ?? false;
 | 
						|
      if (enrolled) {
 | 
						|
        await this._firefoxLabs.unenroll(optIn.slug);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _setCategoryVisibility(shouldHide) {
 | 
						|
    document.getElementById("category-experimental").hidden = shouldHide;
 | 
						|
 | 
						|
    // Cache the visibility so we can show it quicker in subsequent loads.
 | 
						|
    Services.prefs.setBoolPref(
 | 
						|
      "browser.preferences.experimental.hidden",
 | 
						|
      shouldHide
 | 
						|
    );
 | 
						|
 | 
						|
    if (
 | 
						|
      shouldHide &&
 | 
						|
      document.getElementById("categories").selectedItem?.id ==
 | 
						|
        "category-experimental"
 | 
						|
    ) {
 | 
						|
      // Leave the 'experimental' category if there are no available features
 | 
						|
      gotoPref("general");
 | 
						|
    }
 | 
						|
  },
 | 
						|
};
 |