/* 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 */
var gExperimentalPane = {
inited:
false,
_featureGatesContainer:
null,
_boundRestartObserver:
null,
_observedPrefs: [],
_shouldPromptForRestart:
true,
_featureGatePrefTypeToPrefServiceType(featureGatePrefType) {
if (featureGatePrefType !=
"boolean") {
throw new Error(
"Only boolean FeatureGates are supported");
}
return "bool";
},
async _observeRestart(aSubject, aTopic, aData) {
if (!
this._shouldPromptForRestart) {
return;
}
let prefValue = Services.prefs.getBoolPref(aData);
let buttonIndex = await confirmRestartPrompt(prefValue, 1,
true,
false);
if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
Services.startup.quit(
Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart
);
return;
}
this._shouldPromptForRestart =
false;
Services.prefs.setBoolPref(aData, !prefValue);
this._shouldPromptForRestart =
true;
},
addPrefObserver(name, fn) {
this._observedPrefs.push({ name, fn });
Services.prefs.addObserver(name, fn);
},
removePrefObservers() {
for (let { name, fn } of
this._observedPrefs) {
Services.prefs.removeObserver(name, fn);
}
this._observedPrefs = [];
},
// Reset the features to their default values
async resetAllFeatures() {
let features = await gExperimentalPane.getFeatures();
for (let feature of features) {
Services.prefs.setBoolPref(feature.preference, feature.defaultValue);
}
},
async getFeatures() {
let searchParams =
new URLSearchParams(document.documentURIObject.query);
let definitionsUrl = searchParams.get(
"definitionsUrl");
let features = await FeatureGate.all(definitionsUrl);
return features.filter(f => f.isPublic);
},
async init() {
if (
this.inited) {
return;
}
this.inited =
true;
let features = await
this.getFeatures();
let shouldHide = !features.length;
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) {
// Remove the 'experimental' category if there are no available features
document.getElementById(
"firefoxExperimentalCategory").remove();
if (
document.getElementById(
"categories").selectedItem?.id ==
"category-experimental"
) {
// Leave the 'experimental' category if there are no available features
gotoPref(
"general");
return;
}
}
setEventListener(
"experimentalCategory-reset",
"click",
gExperimentalPane.resetAllFeatures
);
window.addEventListener(
"unload", () =>
this.removePrefObservers());
this._featureGatesContainer = document.getElementById(
"pane-experimental-featureGates"
);
this._boundRestartObserver =
this._observeRestart.bind(
this);
let frag = document.createDocumentFragment();
let groups =
new Map();
for (let feature of features) {
if (!groups.has(feature.group)) {
groups.set(feature.group, []);
}
groups.get(feature.group).push(feature);
}
for (let [group, groupFeatures] of groups) {
let card = document.createElement(
"moz-card");
card.classList.add(
"featureGate");
let fieldset = document.createElement(
"moz-fieldset");
document.l10n.setAttributes(fieldset, group);
card.append(fieldset);
for (let feature of groupFeatures) {
if (Preferences.get(feature.preference)) {
console.error(
"Preference control already exists for experimental feature '" +
feature.id +
"' with preference '" +
feature.preference +
"'"
);
continue;
}
if (feature.restartRequired) {
this.addPrefObserver(feature.preference,
this._boundRestartObserver);
}
let checkbox = document.createElement(
"moz-checkbox");
let description = document.createElement(
"div");
description.slot =
"description";
description.id = feature.id +
"-description";
description.classList.add(
"featureGateDescription");
checkbox.append(description);
fieldset.append(checkbox);
let descriptionLinks = feature.descriptionLinks || {};
for (let [key, value] of Object.entries(descriptionLinks)) {
let 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, feature.description);
checkbox.setAttribute(
"preference", feature.preference);
checkbox.id = feature.id;
checkbox.setAttribute(
"aria-describedby", description.id);
document.l10n.setAttributes(checkbox, feature.title);
let extraTemplate = document.getElementById(`template-${feature.id}`);
if (extraTemplate) {
fieldset.appendChild(extraTemplate.content.cloneNode(
true));
}
let preference = Preferences.add({
id: feature.preference,
type: gExperimentalPane._featureGatePrefTypeToPrefServiceType(
feature.type
),
});
preference.setElementValue(checkbox);
}
frag.append(card);
}
this._featureGatesContainer.appendChild(frag);
Services.obs.notifyObservers(window,
"experimental-pane-loaded");
},
};