Backed out changeset f594e6f00208 (bug 1940883) for causing crashes in bug 1941164.
[gecko.git] / toolkit / components / backgroundtasks / BackgroundTask_message.sys.mjs
blob48170946a1d3bd7d17e742731ce2a128a4e592c1
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 // Invoke this task like `firefox.exe --backgroundtask message ...`.
7 //
8 // This task is complicated because it's meant for manual testing by QA but also
9 // for automated testing.  We might split these two functions at some point.
11 // First, note that the task takes significant configuration from the command
12 // line.  This is different than the eventual home for this functionality, the
13 // background update task, which will take this configuration from the default
14 // browsing profile.
16 // This task accepts the following command line arguments:
18 // --debug: enable very verbose debug logging.  Note that the `MOZ_LOG`
19 // environment variables still apply.
21 // --stage: use stage Remote Settings
22 //   (`https://firefox.settings.services.allizom.org/v1`) rather than production
23 //   (`https://firefox.settings.services.mozilla.com/v1`)
25 // --preview: enable Remote Settings and Experiment previews.
27 // `--url about:studies?...` (as copy-pasted from Experimenter): opt in to
28 //   `optin_branch` of experiment with `optin_slug` from `optin_collection`.
30 // `--url file:///path/to/recipe.json?optin_branch=...` (as downloaded from
31 //   Experimenter): opt in to `optin_branch` of given experiment recipe.
33 // `--experiments file:///path/to/recipe.json` (as downloaded from
34 //   Experimenter): enable given experiment recipe, possibly enrolling into a
35 //   branch via regular bucketing.
37 // `--targeting-snapshot /path/to/targeting.snapshot.json`: take default profile
38 //   targeting information from given JSON file.
40 // `--reset-storage`: clear Activity Stream storage, including lifetime limit
41 //   caps.
43 // The following flags are intended for automated testing.
45 // --sentinel: bracket important output with given sentinel for easy parsing.
46 // --randomizationId: set Nimbus/Normandy randomization ID for deterministic bucketing.
47 // --disable-alerts-service: do not show system/OS-level alerts.
48 // --no-experiments: don't talk to Remote Settings server at all.
49 // --no-datareporting: set `datareporting.healthreport.uploadEnabled=false` in
50 //   the background task profile.
51 // --no-optoutstudies: set `app.shield.optoutstudies.enabled=false` in the
52 //   background task profile.
54 import { EXIT_CODE } from "resource://gre/modules/BackgroundTasksManager.sys.mjs";
55 // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
56 import { ASRouter } from "resource:///modules/asrouter/ASRouter.sys.mjs";
57 import { BackgroundTasksUtils } from "resource://gre/modules/BackgroundTasksUtils.sys.mjs";
59 const lazy = {};
61 ChromeUtils.defineESModuleGetters(lazy, {
62   ClientEnvironmentBase:
63     "resource://gre/modules/components-utils/ClientEnvironment.sys.mjs",
64   ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs",
65   IndexedDB: "resource://gre/modules/IndexedDB.sys.mjs",
66   RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
67   RemoteSettingsClient:
68     "resource://services-settings/RemoteSettingsClient.sys.mjs",
69   // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
70   ToastNotification: "resource:///modules/asrouter/ToastNotification.sys.mjs",
71   Utils: "resource://services-settings/Utils.sys.mjs",
72 });
74 const SERVER_STAGE = "https://firefox.settings.services.allizom.org/v1";
76 // Default profile targeting snapshot.
77 let defaultProfileTargetingSnapshot = {};
79 // Bracket important output with given sentinel for easy parsing.
80 let outputInfo;
81 outputInfo = (sentinel, info) => {
82   dump(`${sentinel}${JSON.stringify(info)}${sentinel}\n`);
85 function monkeyPatchRemoteSettingsClient({ data = [] }) {
86   lazy.RemoteSettingsClient.prototype.get = async (options = {}) => {
87     outputInfo({ "RemoteSettingsClient.get": { options, response: { data } } });
88     return data;
89   };
92 async function handleCommandLine(commandLine) {
93   const CASE_INSENSITIVE = false;
95   // Output data over stdout for tests to consume and inspect.
96   let sentinel = commandLine.handleFlagWithParam("sentinel", CASE_INSENSITIVE);
97   outputInfo = outputInfo.bind(null, sentinel || "");
99   // We always want `nimbus.debug=true` for `about:studies?...` URLs.
100   Services.prefs.setBoolPref("nimbus.debug", true);
102   // Maybe drive up logging for this testing task.
103   Services.prefs.clearUserPref("services.settings.preview_enabled");
104   Services.prefs.clearUserPref(
105     "browser.newtabpage.activity-stream.asrouter.debugLogLevel"
106   );
107   Services.prefs.clearUserPref("messaging-system.log");
108   Services.prefs.clearUserPref("services.settings.loglevel");
109   Services.prefs.clearUserPref("toolkit.backgroundtasks.loglevel");
110   if (commandLine.handleFlag("debug", CASE_INSENSITIVE)) {
111     console.log("Saw --debug, making logging verbose");
112     Services.prefs.setBoolPref("services.settings.preview_enabled", true);
113     Services.prefs.setCharPref(
114       "browser.newtabpage.activity-stream.asrouter.debugLogLevel",
115       "debug"
116     );
117     Services.prefs.setCharPref("messaging-system.log", "debug");
118     Services.prefs.setCharPref("services.settings.loglevel", "debug");
119     Services.prefs.setCharPref("toolkit.backgroundtasks.loglevel", "debug");
120   }
122   Services.prefs.setCharPref(
123     "browser.region.network.url",
124     `data:application/json,{"country_code": "US"}`
125   );
127   // Always make alert service display message when showing an alert.
128   // Optionally suppress actually showing OS-level alerts.
129   let origAlertsService = lazy.ToastNotification.AlertsService;
130   let disableAlertsService = commandLine.handleFlag(
131     "disable-alerts-service",
132     CASE_INSENSITIVE
133   );
134   if (disableAlertsService) {
135     console.log("Saw --disable-alerts-service, not showing any alerts");
136   }
137   // Remove getter so that we can redefine property.
138   delete lazy.ToastNotification.AlertsService;
139   lazy.ToastNotification.AlertsService = {
140     showAlert(...args) {
141       outputInfo({ showAlert: { args } });
142       if (!disableAlertsService) {
143         origAlertsService.showAlert(...args);
144       }
145     },
146   };
148   let targetingSnapshotPath = commandLine.handleFlagWithParam(
149     "targeting-snapshot",
150     CASE_INSENSITIVE
151   );
152   if (targetingSnapshotPath) {
153     defaultProfileTargetingSnapshot = await IOUtils.readJSON(
154       targetingSnapshotPath
155     );
156     console.log(
157       `Saw --targeting-snapshot, read snapshot from ${targetingSnapshotPath}`
158     );
159   }
160   outputInfo({ defaultProfileTargetingSnapshot });
162   lazy.RemoteSettings.enablePreviewMode(false);
163   Services.prefs.clearUserPref(
164     "messaging-system.rsexperimentloader.collection_id"
165   );
166   if (commandLine.handleFlag("preview", CASE_INSENSITIVE)) {
167     console.log(
168       `Saw --preview, invoking 'RemoteSettings.enablePreviewMode(true)' and ` +
169         `setting 'messaging-system.rsexperimentloader.collection_id=\"nimbus-preview\"'`
170     );
171     lazy.RemoteSettings.enablePreviewMode(true);
172     Services.prefs.setCharPref(
173       "messaging-system.rsexperimentloader.collection_id",
174       "nimbus-preview"
175     );
176   }
178   Services.prefs.clearUserPref("services.settings.server");
179   Services.prefs.clearUserPref("services.settings.load_dump");
180   if (commandLine.handleFlag("stage", CASE_INSENSITIVE)) {
181     console.log(
182       `Saw --stage, setting 'services.settings.server="${SERVER_STAGE}"'`
183     );
184     Services.prefs.setCharPref("services.settings.server", SERVER_STAGE);
185     Services.prefs.setBoolPref("services.settings.load_dump", false);
187     if (lazy.Utils.SERVER_URL !== SERVER_STAGE) {
188       throw new Error(
189         "Pref services.settings.server ignored!" +
190           "remember to set MOZ_REMOTE_SETTINGS_DEVTOOLS=1 in beta and release."
191       );
192     }
193   }
195   // Allow to override Nimbus randomization ID with `--randomizationId ...`.
196   let randomizationId = commandLine.handleFlagWithParam(
197     "randomizationId",
198     CASE_INSENSITIVE
199   );
200   if (randomizationId) {
201     console.log(`Saw --randomizationId: ${randomizationId}`);
202     Services.prefs.setCharPref("app.normandy.user_id", randomizationId);
203   }
204   outputInfo({ randomizationId: lazy.ClientEnvironmentBase.randomizationId });
206   // Allow to override Nimbus experiments with `--experiments /path/to/data.json`.
207   let experiments = commandLine.handleFlagWithParam(
208     "experiments",
209     CASE_INSENSITIVE
210   );
211   if (experiments) {
212     let experimentsPath = commandLine.resolveFile(experiments).path;
213     let data = await IOUtils.readJSON(experimentsPath);
214     if (!Array.isArray(data)) {
215       if (data.permissions) {
216         data = data.data;
217       }
218       data = [data];
219     }
221     monkeyPatchRemoteSettingsClient({ data });
223     console.log(`Saw --experiments, read recipes from ${experimentsPath}`);
224   }
226   // Allow to turn off querying Remote Settings entirely, for tests.
227   if (
228     !experiments &&
229     commandLine.handleFlag("no-experiments", CASE_INSENSITIVE)
230   ) {
231     monkeyPatchRemoteSettingsClient({ data: [] });
233     console.log(`Saw --no-experiments, returning [] recipes`);
234   }
236   // Allow to test various red buttons.
237   Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
238   if (commandLine.handleFlag("no-datareporting", CASE_INSENSITIVE)) {
239     Services.prefs.setBoolPref(
240       "datareporting.healthreport.uploadEnabled",
241       false
242     );
243     console.log(
244       `Saw --no-datareporting, set 'datareporting.healthreport.uploadEnabled=false'`
245     );
246   }
248   Services.prefs.clearUserPref("app.shield.optoutstudies.enabled");
249   if (commandLine.handleFlag("no-optoutstudies", CASE_INSENSITIVE)) {
250     Services.prefs.setBoolPref("app.shield.optoutstudies.enabled", false);
251     console.log(
252       `Saw --no-datareporting, set 'app.shield.optoutstudies.enabled=false'`
253     );
254   }
256   outputInfo({
257     taskProfilePrefs: {
258       "app.shield.optoutstudies.enabled": Services.prefs.getBoolPref(
259         "app.shield.optoutstudies.enabled"
260       ),
261       "datareporting.healthreport.uploadEnabled": Services.prefs.getBoolPref(
262         "datareporting.healthreport.uploadEnabled"
263       ),
264     },
265   });
267   if (commandLine.handleFlag("reset-storage", CASE_INSENSITIVE)) {
268     console.log("Saw --reset-storage, deleting database 'ActivityStream'");
269     console.log(
270       `To hard reset, remove the contents of '${PathUtils.profileDir}'`
271     );
272     await lazy.IndexedDB.deleteDatabase("ActivityStream");
273   }
276 export async function runBackgroundTask(commandLine) {
277   console.error("runBackgroundTask: message");
279   // Most of the task is arranging configuration.
280   await handleCommandLine(commandLine);
282   // Here's where we actually start Nimbus and the Firefox Messaging
283   // System.
284   await BackgroundTasksUtils.enableNimbus(
285     commandLine,
286     defaultProfileTargetingSnapshot.environment
287   );
289   await BackgroundTasksUtils.enableFirefoxMessagingSystem(
290     defaultProfileTargetingSnapshot.environment
291   );
293   // At the time of writing, toast notifications are torn down as the
294   // process exits.  Give the user a chance to see the notification.
295   await lazy.ExtensionUtils.promiseTimeout(1000);
297   // Everything in `ASRouter` is asynchronous, so we need to give everything a
298   // chance to complete.
299   outputInfo({ ASRouterState: ASRouter.state });
301   return EXIT_CODE.SUCCESS;