diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/normandy/content/AboutPages.sys.mjs | 232 |
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; +}); |