Bug 1824490 - Use the end page value rather than the start page value of the previous...
[gecko.git] / browser / components / preferences / experimental.js
blob266aeabc4f2c482169babd0dac608357b4de7bca
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/. */
5 /* import-globals-from preferences.js */
7 var gExperimentalPane = {
8   inited: false,
9   _template: null,
10   _featureGatesContainer: null,
11   _boundRestartObserver: null,
12   _observedPrefs: [],
13   _shouldPromptForRestart: true,
15   _featureGatePrefTypeToPrefServiceType(featureGatePrefType) {
16     if (featureGatePrefType != "boolean") {
17       throw new Error("Only boolean FeatureGates are supported");
18     }
19     return "bool";
20   },
22   async _observeRestart(aSubject, aTopic, aData) {
23     if (!this._shouldPromptForRestart) {
24       return;
25     }
26     let prefValue = Services.prefs.getBoolPref(aData);
27     let buttonIndex = await confirmRestartPrompt(prefValue, 1, true, false);
28     if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
29       Services.startup.quit(
30         Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart
31       );
32       return;
33     }
34     this._shouldPromptForRestart = false;
35     Services.prefs.setBoolPref(aData, !prefValue);
36     this._shouldPromptForRestart = true;
37   },
39   addPrefObserver(name, fn) {
40     this._observedPrefs.push({ name, fn });
41     Services.prefs.addObserver(name, fn);
42   },
44   removePrefObservers() {
45     for (let { name, fn } of this._observedPrefs) {
46       Services.prefs.removeObserver(name, fn);
47     }
48     this._observedPrefs = [];
49   },
51   // Reset the features to their default values
52   async resetAllFeatures() {
53     let features = await gExperimentalPane.getFeatures();
54     for (let feature of features) {
55       Services.prefs.setBoolPref(feature.preference, feature.defaultValue);
56     }
57   },
59   async getFeatures() {
60     let searchParams = new URLSearchParams(document.documentURIObject.query);
61     let definitionsUrl = searchParams.get("definitionsUrl");
62     let features = await FeatureGate.all(definitionsUrl);
63     return features.filter(f => f.isPublic);
64   },
66   async _sortFeatures(features) {
67     // Sort the features alphabetically by their title
68     let titles = await document.l10n.formatMessages(
69       features.map(f => {
70         return { id: f.title };
71       })
72     );
73     titles = titles.map((title, index) => [title.attributes[0].value, index]);
74     titles.sort((a, b) => a[0].toLowerCase().localeCompare(b[0].toLowerCase()));
75     // Get the features in order of sorted titles.
76     return titles.map(([, index]) => features[index]);
77   },
79   async init() {
80     if (this.inited) {
81       return;
82     }
83     this.inited = true;
85     let features = await this.getFeatures();
86     let shouldHide = !features.length;
87     document.getElementById("category-experimental").hidden = shouldHide;
88     // Cache the visibility so we can show it quicker in subsequent loads.
89     Services.prefs.setBoolPref(
90       "browser.preferences.experimental.hidden",
91       shouldHide
92     );
93     if (shouldHide) {
94       // Remove the 'experimental' category if there are no available features
95       document.getElementById("firefoxExperimentalCategory").remove();
96       if (
97         document.getElementById("categories").selectedItem?.id ==
98         "category-experimental"
99       ) {
100         // Leave the 'experimental' category if there are no available features
101         gotoPref("general");
102         return;
103       }
104     }
106     features = await this._sortFeatures(features);
108     setEventListener(
109       "experimentalCategory-reset",
110       "command",
111       gExperimentalPane.resetAllFeatures
112     );
114     window.addEventListener("unload", () => this.removePrefObservers());
115     this._template = document.getElementById("template-featureGate");
116     this._featureGatesContainer = document.getElementById(
117       "pane-experimental-featureGates"
118     );
119     this._boundRestartObserver = this._observeRestart.bind(this);
120     let frag = document.createDocumentFragment();
121     for (let feature of features) {
122       if (Preferences.get(feature.preference)) {
123         console.error(
124           "Preference control already exists for experimental feature '" +
125             feature.id +
126             "' with preference '" +
127             feature.preference +
128             "'"
129         );
130         continue;
131       }
132       if (feature.restartRequired) {
133         this.addPrefObserver(feature.preference, this._boundRestartObserver);
134       }
135       let template = this._template.content.cloneNode(true);
136       let description = template.querySelector(".featureGateDescription");
137       description.id = feature.id + "-description";
138       let descriptionLinks = feature.descriptionLinks || {};
139       for (let [key, value] of Object.entries(descriptionLinks)) {
140         let link = document.createElement("a");
141         link.setAttribute("data-l10n-name", key);
142         link.setAttribute("href", value);
143         link.setAttribute("target", "_blank");
144         description.append(link);
145       }
146       document.l10n.setAttributes(description, feature.description);
147       let checkbox = template.querySelector(".featureGateCheckbox");
148       checkbox.setAttribute("preference", feature.preference);
149       checkbox.id = feature.id;
150       checkbox.setAttribute("aria-describedby", description.id);
151       document.l10n.setAttributes(checkbox, feature.title);
152       frag.appendChild(template);
153       let preference = Preferences.add({
154         id: feature.preference,
155         type: gExperimentalPane._featureGatePrefTypeToPrefServiceType(
156           feature.type
157         ),
158       });
159       preference.setElementValue(checkbox);
160     }
161     this._featureGatesContainer.appendChild(frag);
162   },