summaryrefslogtreecommitdiffstats
path: root/toolkit/components/normandy/content/AboutPages.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/normandy/content/AboutPages.sys.mjs')
-rw-r--r--toolkit/components/normandy/content/AboutPages.sys.mjs232
1 files changed, 232 insertions, 0 deletions
diff --git a/toolkit/components/normandy/content/AboutPages.sys.mjs b/toolkit/components/normandy/content/AboutPages.sys.mjs
new file mode 100644
index 0000000000..fedf85c2e8
--- /dev/null
+++ b/toolkit/components/normandy/content/AboutPages.sys.mjs
@@ -0,0 +1,232 @@
+/* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ AddonStudies: "resource://normandy/lib/AddonStudies.sys.mjs",
+ BranchedAddonStudyAction:
+ "resource://normandy/actions/BranchedAddonStudyAction.sys.mjs",
+ ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
+ PreferenceExperiments:
+ "resource://normandy/lib/PreferenceExperiments.sys.mjs",
+ RecipeRunner: "resource://normandy/lib/RecipeRunner.sys.mjs",
+ RemoteSettingsExperimentLoader:
+ "resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs",
+});
+
+const SHIELD_LEARN_MORE_URL_PREF = "app.normandy.shieldLearnMoreUrl";
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "gOptOutStudiesEnabled",
+ "app.shield.optoutstudies.enabled"
+);
+
+/**
+ * Class for managing an about: page that Normandy provides. Adapted from
+ * browser/components/pocket/content/AboutPocket.sys.mjs.
+ *
+ * @implements nsIFactory
+ * @implements nsIAboutModule
+ */
+class AboutPage {
+ constructor({ chromeUrl, aboutHost, classID, description, uriFlags }) {
+ this.chromeUrl = chromeUrl;
+ this.aboutHost = aboutHost;
+ this.classID = Components.ID(classID);
+ this.description = description;
+ this.uriFlags = uriFlags;
+ }
+
+ getURIFlags() {
+ return this.uriFlags;
+ }
+
+ newChannel(uri, loadInfo) {
+ const newURI = Services.io.newURI(this.chromeUrl);
+ const channel = Services.io.newChannelFromURIWithLoadInfo(newURI, loadInfo);
+ channel.originalURI = uri;
+
+ if (this.uriFlags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) {
+ const principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ channel.owner = principal;
+ }
+ return channel;
+ }
+}
+AboutPage.prototype.QueryInterface = ChromeUtils.generateQI(["nsIAboutModule"]);
+
+/**
+ * The module exported by this file.
+ */
+export let AboutPages = {};
+
+/**
+ * The weak set that keeps track of which browsing contexts
+ * have an about:studies page.
+ */
+let BrowsingContexts = new WeakSet();
+/**
+ * about:studies page for displaying in-progress and past Shield studies.
+ * @type {AboutPage}
+ * @implements {nsIMessageListener}
+ */
+XPCOMUtils.defineLazyGetter(AboutPages, "aboutStudies", () => {
+ const aboutStudies = new AboutPage({
+ chromeUrl: "resource://normandy-content/about-studies/about-studies.html",
+ aboutHost: "studies",
+ classID: "{6ab96943-a163-482c-9622-4faedc0e827f}",
+ description: "Shield Study Listing",
+ uriFlags:
+ Ci.nsIAboutModule.ALLOW_SCRIPT |
+ Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD |
+ Ci.nsIAboutModule.IS_SECURE_CHROME_UI,
+ });
+
+ // Extra methods for about:study-specific behavior.
+ Object.assign(aboutStudies, {
+ getAddonStudyList() {
+ return lazy.AddonStudies.getAll();
+ },
+
+ getPreferenceStudyList() {
+ return lazy.PreferenceExperiments.getAll();
+ },
+
+ getMessagingSystemList() {
+ return lazy.ExperimentManager.store.getAll();
+ },
+
+ async optInToExperiment(data) {
+ try {
+ await lazy.RemoteSettingsExperimentLoader.optInToExperiment(data);
+ return {
+ error: false,
+ message: "Opt-in was successful.",
+ };
+ } catch (error) {
+ return {
+ error: true,
+ message: error.message,
+ };
+ }
+ },
+
+ /** Add a browsing context to the weak set;
+ * this weak set keeps track of all contexts
+ * that are housing an about:studies page.
+ */
+ addToWeakSet(browsingContext) {
+ BrowsingContexts.add(browsingContext);
+ },
+ /** Remove a browsing context to the weak set;
+ * this weak set keeps track of all contexts
+ * that are housing an about:studies page.
+ */
+ removeFromWeakSet(browsingContext) {
+ BrowsingContexts.delete(browsingContext);
+ },
+
+ /**
+ * Sends a message to every about:studies page,
+ * by iterating over the BrowsingContexts weakset.
+ * @param {string} message The message string to send to.
+ * @param {object} data The data object to send.
+ */
+ _sendToAll(message, data) {
+ ChromeUtils.nondeterministicGetWeakSetKeys(BrowsingContexts).forEach(
+ browser =>
+ browser.currentWindowGlobal
+ .getActor("ShieldFrame")
+ .sendAsyncMessage(message, data)
+ );
+ },
+
+ /**
+ * Get if studies are enabled. This has to be in the parent process,
+ * since RecipeRunner is stateful, and can't be interacted with from
+ * content processes safely.
+ */
+ async getStudiesEnabled() {
+ await lazy.RecipeRunner.initializedPromise.promise;
+ return lazy.RecipeRunner.enabled && lazy.gOptOutStudiesEnabled;
+ },
+
+ /**
+ * Disable an active add-on study and remove its add-on.
+ * @param {String} recipeId the id of the addon to remove
+ * @param {String} reason the reason for removal
+ */
+ async removeAddonStudy(recipeId, reason) {
+ try {
+ const action = new lazy.BranchedAddonStudyAction();
+ await action.unenroll(recipeId, reason);
+ } catch (err) {
+ // If the exception was that the study was already removed, that's ok.
+ // If not, rethrow the error.
+ if (!err.toString().includes("already inactive")) {
+ throw err;
+ }
+ } finally {
+ // Update any open tabs with the new study list now that it has changed,
+ // even if the above failed.
+ this.getAddonStudyList().then(list =>
+ this._sendToAll("Shield:UpdateAddonStudyList", list)
+ );
+ }
+ },
+
+ /**
+ * Disable an active preference study.
+ * @param {String} experimentName the name of the experiment to remove
+ * @param {String} reason the reason for removal
+ */
+ async removePreferenceStudy(experimentName, reason) {
+ try {
+ await lazy.PreferenceExperiments.stop(experimentName, {
+ reason,
+ caller: "AboutPages.removePreferenceStudy",
+ });
+ } catch (err) {
+ // If the exception was that the study was already removed, that's ok.
+ // If not, rethrow the error.
+ if (!err.toString().includes("already expired")) {
+ throw err;
+ }
+ } finally {
+ // Update any open tabs with the new study list now that it has changed,
+ // even if the above failed.
+ this.getPreferenceStudyList().then(list =>
+ this._sendToAll("Shield:UpdatePreferenceStudyList", list)
+ );
+ }
+ },
+
+ async removeMessagingSystemExperiment(slug, reason) {
+ lazy.ExperimentManager.unenroll(slug, reason);
+ this._sendToAll(
+ "Shield:UpdateMessagingSystemExperimentList",
+ lazy.ExperimentManager.store.getAll()
+ );
+ },
+
+ openDataPreferences() {
+ const browserWindow =
+ Services.wm.getMostRecentWindow("navigator:browser");
+ browserWindow.openPreferences("privacy-reports");
+ },
+
+ getShieldLearnMoreHref() {
+ return Services.urlFormatter.formatURLPref(SHIELD_LEARN_MORE_URL_PREF);
+ },
+ });
+
+ return aboutStudies;
+});