browser_genai_init.js is currently failing due to expecting Firefox Labs to always render in about:preferences. However, now that it is driven by Nimbus, it only render if we know about any opt-in experiments. Since the test doesn't create any, Firefox Labs doesn't render and the test fails. However, the test only waits for an observer notification and stubs out that actual implementation that builds the page. If we emit this notification when we decide not to render Firefox Labs then the test will fail. This will be removed in bug 1951311. Differential Revision: https://phabricator.services.mozilla.com/D240112
230 lines
6.7 KiB
JavaScript
230 lines
6.7 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 STUDIES_ENABLED_CHANGED = "nimbus:studies-enabled-changed";
|
|
|
|
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,
|
|
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) {
|
|
// TODO: Remove this when we remove the GenAI integration with
|
|
// about:preferences, as this just patches over a failing test.
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1951311
|
|
Services.obs.notifyObservers(window, "experimental-pane-loaded");
|
|
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 {
|
|
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._manager.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,
|
|
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) {
|
|
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");
|
|
}
|
|
},
|
|
};
|