diff options
Diffstat (limited to 'toolkit/components/backgroundtasks/BackgroundTask_message.sys.mjs')
-rw-r--r-- | toolkit/components/backgroundtasks/BackgroundTask_message.sys.mjs | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/toolkit/components/backgroundtasks/BackgroundTask_message.sys.mjs b/toolkit/components/backgroundtasks/BackgroundTask_message.sys.mjs new file mode 100644 index 0000000000..6fc1d789b9 --- /dev/null +++ b/toolkit/components/backgroundtasks/BackgroundTask_message.sys.mjs @@ -0,0 +1,300 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * 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/. */ + +// Invoke this task like `firefox.exe --backgroundtask message ...`. +// +// This task is complicated because it's meant for manual testing by QA but also +// for automated testing. We might split these two functions at some point. +// +// First, note that the task takes significant configuration from the command +// line. This is different than the eventual home for this functionality, the +// background update task, which will take this configuration from the default +// browsing profile. +// +// This task accepts the following command line arguments: +// +// --debug: enable very verbose debug logging. Note that the `MOZ_LOG` +// environment variables still apply. +// +// --stage: use stage Remote Settings +// (`https://firefox.settings.services.allizom.org/v1`) rather than production +// (`https://firefox.settings.services.mozilla.com/v1`) +// +// --preview: enable Remote Settings and Experiment previews. +// +// `--url about:studies?...` (as copy-pasted from Experimenter): opt in to +// `optin_branch` of experiment with `optin_slug` from `optin_collection`. +// +// `--url file:///path/to/recipe.json?optin_branch=...` (as downloaded from +// Experimenter): opt in to `optin_branch` of given experiment recipe. +// +// `--experiments file:///path/to/recipe.json` (as downloaded from +// Experimenter): enable given experiment recipe, possibly enrolling into a +// branch via regular bucketing. +// +// `--targeting-snapshot /path/to/targeting.snapshot.json`: take default profile +// targeting information from given JSON file. +// +// `--reset-storage`: clear Activity Stream storage, including lifetime limit +// caps. +// +// The following flags are intended for automated testing. +// +// --sentinel: bracket important output with given sentinel for easy parsing. +// --randomizationId: set Nimbus/Normandy randomization ID for deterministic bucketing. +// --disable-alerts-service: do not show system/OS-level alerts. +// --no-experiments: don't talk to Remote Settings server at all. +// --no-datareporting: set `datareporting.healthreport.uploadEnabled=false` in +// the background task profile. +// --no-optoutstudies: set `app.shield.optoutstudies.enabled=false` in the +// background task profile. + +import { EXIT_CODE } from "resource://gre/modules/BackgroundTasksManager.sys.mjs"; +// eslint-disable-next-line mozilla/no-browser-refs-in-toolkit +import { ASRouter } from "resource:///modules/asrouter/ASRouter.sys.mjs"; +import { BackgroundTasksUtils } from "resource://gre/modules/BackgroundTasksUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + ClientEnvironmentBase: + "resource://gre/modules/components-utils/ClientEnvironment.sys.mjs", + ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs", + IndexedDB: "resource://gre/modules/IndexedDB.sys.mjs", + RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", + RemoteSettingsClient: + "resource://services-settings/RemoteSettingsClient.sys.mjs", + // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit + ToastNotification: "resource:///modules/asrouter/ToastNotification.sys.mjs", + Utils: "resource://services-settings/Utils.sys.mjs", +}); + +const SERVER_STAGE = "https://firefox.settings.services.allizom.org/v1"; + +// Default profile targeting snapshot. +let defaultProfileTargetingSnapshot = {}; + +// Bracket important output with given sentinel for easy parsing. +let outputInfo; +outputInfo = (sentinel, info) => { + dump(`${sentinel}${JSON.stringify(info)}${sentinel}\n`); +}; + +function monkeyPatchRemoteSettingsClient({ + last_modified = new Date().getTime(), + data = [], +}) { + lazy.RemoteSettingsClient.prototype.get = async (options = {}) => { + outputInfo({ "RemoteSettingsClient.get": { options, response: { data } } }); + return data; + }; +} + +async function handleCommandLine(commandLine) { + const CASE_INSENSITIVE = false; + + // Output data over stdout for tests to consume and inspect. + let sentinel = commandLine.handleFlagWithParam("sentinel", CASE_INSENSITIVE); + outputInfo = outputInfo.bind(null, sentinel || ""); + + // We always want `nimbus.debug=true` for `about:studies?...` URLs. + Services.prefs.setBoolPref("nimbus.debug", true); + + // Maybe drive up logging for this testing task. + Services.prefs.clearUserPref("services.settings.preview_enabled"); + Services.prefs.clearUserPref( + "browser.newtabpage.activity-stream.asrouter.debugLogLevel" + ); + Services.prefs.clearUserPref("messaging-system.log"); + Services.prefs.clearUserPref("services.settings.loglevel"); + Services.prefs.clearUserPref("toolkit.backgroundtasks.loglevel"); + if (commandLine.handleFlag("debug", CASE_INSENSITIVE)) { + console.log("Saw --debug, making logging verbose"); + Services.prefs.setBoolPref("services.settings.preview_enabled", true); + Services.prefs.setCharPref( + "browser.newtabpage.activity-stream.asrouter.debugLogLevel", + "debug" + ); + Services.prefs.setCharPref("messaging-system.log", "debug"); + Services.prefs.setCharPref("services.settings.loglevel", "debug"); + Services.prefs.setCharPref("toolkit.backgroundtasks.loglevel", "debug"); + } + + // Always make alert service display message when showing an alert. + // Optionally suppress actually showing OS-level alerts. + let origAlertsService = lazy.ToastNotification.AlertsService; + let disableAlertsService = commandLine.handleFlag( + "disable-alerts-service", + CASE_INSENSITIVE + ); + if (disableAlertsService) { + console.log("Saw --disable-alerts-service, not showing any alerts"); + } + // Remove getter so that we can redefine property. + delete lazy.ToastNotification.AlertsService; + lazy.ToastNotification.AlertsService = { + showAlert(...args) { + outputInfo({ showAlert: { args } }); + if (!disableAlertsService) { + origAlertsService.showAlert(...args); + } + }, + }; + + let targetingSnapshotPath = commandLine.handleFlagWithParam( + "targeting-snapshot", + CASE_INSENSITIVE + ); + if (targetingSnapshotPath) { + defaultProfileTargetingSnapshot = await IOUtils.readJSON( + targetingSnapshotPath + ); + console.log( + `Saw --targeting-snapshot, read snapshot from ${targetingSnapshotPath}` + ); + } + outputInfo({ defaultProfileTargetingSnapshot }); + + lazy.RemoteSettings.enablePreviewMode(false); + Services.prefs.clearUserPref( + "messaging-system.rsexperimentloader.collection_id" + ); + if (commandLine.handleFlag("preview", CASE_INSENSITIVE)) { + console.log( + `Saw --preview, invoking 'RemoteSettings.enablePreviewMode(true)' and ` + + `setting 'messaging-system.rsexperimentloader.collection_id=\"nimbus-preview\"'` + ); + lazy.RemoteSettings.enablePreviewMode(true); + Services.prefs.setCharPref( + "messaging-system.rsexperimentloader.collection_id", + "nimbus-preview" + ); + } + + Services.prefs.clearUserPref("services.settings.server"); + Services.prefs.clearUserPref("services.settings.load_dump"); + if (commandLine.handleFlag("stage", CASE_INSENSITIVE)) { + console.log( + `Saw --stage, setting 'services.settings.server="${SERVER_STAGE}"'` + ); + Services.prefs.setCharPref("services.settings.server", SERVER_STAGE); + Services.prefs.setBoolPref("services.settings.load_dump", false); + + if (lazy.Utils.SERVER_URL !== SERVER_STAGE) { + throw new Error( + "Pref services.settings.server ignored!" + + "remember to set MOZ_REMOTE_SETTINGS_DEVTOOLS=1 in beta and release." + ); + } + } + + // Allow to override Nimbus randomization ID with `--randomizationId ...`. + let randomizationId = commandLine.handleFlagWithParam( + "randomizationId", + CASE_INSENSITIVE + ); + if (randomizationId) { + console.log(`Saw --randomizationId: ${randomizationId}`); + Services.prefs.setCharPref("app.normandy.user_id", randomizationId); + } + outputInfo({ randomizationId: lazy.ClientEnvironmentBase.randomizationId }); + + // Allow to override Nimbus experiments with `--experiments /path/to/data.json`. + let experiments = commandLine.handleFlagWithParam( + "experiments", + CASE_INSENSITIVE + ); + if (experiments) { + let experimentsPath = commandLine.resolveFile(experiments).path; + let data = await IOUtils.readJSON(experimentsPath); + if (!Array.isArray(data)) { + if (data.permissions) { + data = data.data; + } + data = [data]; + } + + monkeyPatchRemoteSettingsClient({ data }); + + console.log(`Saw --experiments, read recipes from ${experimentsPath}`); + } + + // Allow to turn off querying Remote Settings entirely, for tests. + if ( + !experiments && + commandLine.handleFlag("no-experiments", CASE_INSENSITIVE) + ) { + monkeyPatchRemoteSettingsClient({ data: [] }); + + console.log(`Saw --no-experiments, returning [] recipes`); + } + + // Allow to test various red buttons. + Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled"); + if (commandLine.handleFlag("no-datareporting", CASE_INSENSITIVE)) { + Services.prefs.setBoolPref( + "datareporting.healthreport.uploadEnabled", + false + ); + console.log( + `Saw --no-datareporting, set 'datareporting.healthreport.uploadEnabled=false'` + ); + } + + Services.prefs.clearUserPref("app.shield.optoutstudies.enabled"); + if (commandLine.handleFlag("no-optoutstudies", CASE_INSENSITIVE)) { + Services.prefs.setBoolPref("app.shield.optoutstudies.enabled", false); + console.log( + `Saw --no-datareporting, set 'app.shield.optoutstudies.enabled=false'` + ); + } + + outputInfo({ + taskProfilePrefs: { + "app.shield.optoutstudies.enabled": Services.prefs.getBoolPref( + "app.shield.optoutstudies.enabled" + ), + "datareporting.healthreport.uploadEnabled": Services.prefs.getBoolPref( + "datareporting.healthreport.uploadEnabled" + ), + }, + }); + + if (commandLine.handleFlag("reset-storage", CASE_INSENSITIVE)) { + console.log("Saw --reset-storage, deleting database 'ActivityStream'"); + console.log( + `To hard reset, remove the contents of '${PathUtils.profileDir}'` + ); + await lazy.IndexedDB.deleteDatabase("ActivityStream"); + } +} + +export async function runBackgroundTask(commandLine) { + console.error("runBackgroundTask: message"); + + // Most of the task is arranging configuration. + await handleCommandLine(commandLine); + + // Here's where we actually start Nimbus and the Firefox Messaging + // System. + await BackgroundTasksUtils.enableNimbus( + commandLine, + defaultProfileTargetingSnapshot.environment + ); + + await BackgroundTasksUtils.enableFirefoxMessagingSystem( + defaultProfileTargetingSnapshot.environment + ); + + // At the time of writing, toast notifications are torn down as the + // process exits. Give the user a chance to see the notification. + await lazy.ExtensionUtils.promiseTimeout(1000); + + // Everything in `ASRouter` is asynchronous, so we need to give everything a + // chance to complete. + outputInfo({ ASRouterState: ASRouter.state }); + + return EXIT_CODE.SUCCESS; +} |