summaryrefslogtreecommitdiffstats
path: root/toolkit/components/backgroundtasks/tests/browser
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/backgroundtasks/tests/browser')
-rw-r--r--toolkit/components/backgroundtasks/tests/browser/browser.toml8
-rw-r--r--toolkit/components/backgroundtasks/tests/browser/browser_backgroundtask_specific_pref.js23
-rw-r--r--toolkit/components/backgroundtasks/tests/browser/browser_xpcom_graph_wait.js406
-rw-r--r--toolkit/components/backgroundtasks/tests/browser/head.js15
4 files changed, 452 insertions, 0 deletions
diff --git a/toolkit/components/backgroundtasks/tests/browser/browser.toml b/toolkit/components/backgroundtasks/tests/browser/browser.toml
new file mode 100644
index 0000000000..80bc728294
--- /dev/null
+++ b/toolkit/components/backgroundtasks/tests/browser/browser.toml
@@ -0,0 +1,8 @@
+[DEFAULT]
+skip-if = ["os == 'android'"]
+head = "head.js"
+
+["browser_backgroundtask_specific_pref.js"]
+
+["browser_xpcom_graph_wait.js"]
+skip-if = ["tsan"] # TSan times out on pretty much all profiler-consuming tests.
diff --git a/toolkit/components/backgroundtasks/tests/browser/browser_backgroundtask_specific_pref.js b/toolkit/components/backgroundtasks/tests/browser/browser_backgroundtask_specific_pref.js
new file mode 100644
index 0000000000..b80ee2f593
--- /dev/null
+++ b/toolkit/components/backgroundtasks/tests/browser/browser_backgroundtask_specific_pref.js
@@ -0,0 +1,23 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * 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/. */
+
+"use strict";
+
+add_task(async function test_backgroundtask_specific_pref() {
+ // First, verify this pref isn't set in Gecko itself.
+ Assert.equal(
+ -1,
+ Services.prefs.getIntPref("test.backgroundtask_specific_pref.exitCode", -1)
+ );
+
+ // Second, verify that this pref is set in background tasks.
+ // mochitest-chrome tests locally test both unpackaged and packaged
+ // builds (with `--appname=dist`).
+ let exitCode = await do_backgroundtask("backgroundtask_specific_pref", {
+ extraArgs: ["test.backgroundtask_specific_pref.exitCode"],
+ });
+ Assert.equal(79, exitCode);
+});
diff --git a/toolkit/components/backgroundtasks/tests/browser/browser_xpcom_graph_wait.js b/toolkit/components/backgroundtasks/tests/browser/browser_xpcom_graph_wait.js
new file mode 100644
index 0000000000..501b50fa7a
--- /dev/null
+++ b/toolkit/components/backgroundtasks/tests/browser/browser_xpcom_graph_wait.js
@@ -0,0 +1,406 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * 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/. */
+
+/* This test records code loaded during a dummy background task.
+ *
+ * To run this test similar to try server, you need to run:
+ * ./mach package
+ * ./mach test --appname=dist <path to test>
+ *
+ * If you made changes that cause this test to fail, it's likely
+ * because you are changing the application startup process. In
+ * general, you should prefer to defer loading code as long as you
+ * can, especially if it's not going to be used in background tasks.
+ */
+
+"use strict";
+
+const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+// Shortcuts for conditions.
+const LINUX = AppConstants.platform == "linux";
+const WIN = AppConstants.platform == "win";
+const MAC = AppConstants.platform == "macosx";
+
+const backgroundtaskPhases = {
+ AfterRunBackgroundTaskNamed: {
+ allowlist: {
+ modules: [
+ "resource://gre/modules/AppConstants.sys.mjs",
+ "resource://gre/modules/AsyncShutdown.sys.mjs",
+ "resource://gre/modules/BackgroundTasksManager.sys.mjs",
+ "resource://gre/modules/Console.sys.mjs",
+ "resource://gre/modules/EnterprisePolicies.sys.mjs",
+ "resource://gre/modules/EnterprisePoliciesParent.sys.mjs",
+ "resource://gre/modules/XPCOMUtils.sys.mjs",
+ "resource://gre/modules/nsAsyncShutdown.sys.mjs",
+ ],
+ // Human-readable contract IDs are many-to-one mapped to CIDs, so this
+ // list is a little misleading. For example, all of
+ // "@mozilla.org/xre/app-info;1", "@mozilla.org/xre/runtime;1", and
+ // "@mozilla.org/toolkit/crash-reporter;1", map to the CID
+ // {95d89e3e-a169-41a3-8e56-719978e15b12}, but only one is listed here.
+ // We could be more precise by listing CIDs, but that's a good deal harder
+ // to read and modify.
+ services: [
+ "@mozilla.org/async-shutdown-service;1",
+ "@mozilla.org/backgroundtasks;1",
+ "@mozilla.org/backgroundtasksmanager;1",
+ "@mozilla.org/base/telemetry;1",
+ "@mozilla.org/categorymanager;1",
+ "@mozilla.org/chrome/chrome-registry;1",
+ "@mozilla.org/cookieService;1",
+ "@mozilla.org/docloaderservice;1",
+ "@mozilla.org/embedcomp/window-watcher;1",
+ "@mozilla.org/enterprisepolicies;1",
+ "@mozilla.org/file/directory_service;1",
+ "@mozilla.org/intl/stringbundle;1",
+ "@mozilla.org/layout/content-policy;1",
+ "@mozilla.org/memory-reporter-manager;1",
+ "@mozilla.org/network/captive-portal-service;1",
+ "@mozilla.org/network/effective-tld-service;1",
+ "@mozilla.org/network/idn-service;1",
+ "@mozilla.org/network/io-service;1",
+ "@mozilla.org/network/network-link-service;1",
+ "@mozilla.org/network/protocol;1?name=file",
+ "@mozilla.org/network/protocol;1?name=jar",
+ "@mozilla.org/network/protocol;1?name=resource",
+ "@mozilla.org/network/socket-transport-service;1",
+ "@mozilla.org/network/stream-transport-service;1",
+ "@mozilla.org/network/url-parser;1?auth=maybe",
+ "@mozilla.org/network/url-parser;1?auth=no",
+ "@mozilla.org/network/url-parser;1?auth=yes",
+ "@mozilla.org/observer-service;1",
+ "@mozilla.org/power/powermanagerservice;1",
+ "@mozilla.org/preferences-service;1",
+ "@mozilla.org/process/environment;1",
+ "@mozilla.org/storage/service;1",
+ "@mozilla.org/thirdpartyutil;1",
+ "@mozilla.org/toolkit/app-startup;1",
+ {
+ name: "@mozilla.org/widget/appshell/mac;1",
+ condition: MAC,
+ },
+ {
+ name: "@mozilla.org/widget/appshell/gtk;1",
+ condition: LINUX,
+ },
+ {
+ name: "@mozilla.org/widget/appshell/win;1",
+ condition: WIN,
+ },
+ "@mozilla.org/xpcom/debug;1",
+ "@mozilla.org/xre/app-info;1",
+ "@mozilla.org/mime;1",
+ ],
+ },
+ },
+ AfterFindRunBackgroundTask: {
+ allowlist: {
+ modules: [
+ // We have a profile marker for this, even though it failed to load!
+ "resource:///modules/backgroundtasks/BackgroundTask_wait.sys.mjs",
+
+ "resource://gre/modules/ConsoleAPIStorage.sys.mjs",
+ "resource://gre/modules/Timer.sys.mjs",
+
+ // We have a profile marker for this, even though it failed to load!
+ "resource://gre/modules/backgroundtasks/BackgroundTask_wait.sys.mjs",
+
+ "resource://testing-common/backgroundtasks/BackgroundTask_wait.sys.mjs",
+ ],
+ services: ["@mozilla.org/consoleAPI-storage;1"],
+ },
+ },
+ AfterAwaitRunBackgroundTask: {
+ allowlist: {
+ modules: [],
+ services: [],
+ },
+ },
+};
+
+function getStackFromProfile(profile, stack, libs) {
+ const stackPrefixCol = profile.stackTable.schema.prefix;
+ const stackFrameCol = profile.stackTable.schema.frame;
+ const frameLocationCol = profile.frameTable.schema.location;
+
+ let index = 1;
+ let result = [];
+ while (stack) {
+ let sp = profile.stackTable.data[stack];
+ let frame = profile.frameTable.data[sp[stackFrameCol]];
+ stack = sp[stackPrefixCol];
+ frame = profile.stringTable[frame[frameLocationCol]];
+
+ if (frame.startsWith("0x")) {
+ try {
+ let addr = frame.slice("0x".length);
+ addr = Number.parseInt(addr, 16);
+ for (let lib of libs) {
+ if (lib.start <= addr && addr <= lib.end) {
+ // Only handle two digits for now.
+ let indexString = index.toString(10);
+ if (indexString.length == 1) {
+ indexString = "0" + indexString;
+ }
+ let offset = addr - lib.start;
+ frame = `#${indexString}: ???[${lib.debugPath} ${
+ "+0x" + offset.toString(16)
+ }]`;
+ break;
+ }
+ }
+ } catch (e) {
+ // Fall through.
+ }
+ }
+
+ if (frame != "js::RunScript" && !frame.startsWith("next (self-hosted:")) {
+ result.push(frame);
+ index += 1;
+ }
+ }
+ return result;
+}
+
+add_task(async function test_xpcom_graph_wait() {
+ TestUtils.assertPackagedBuild();
+
+ let profilePath = Services.env.get("MOZ_UPLOAD_DIR");
+ profilePath =
+ profilePath ||
+ (await IOUtils.createUniqueDirectory(
+ PathUtils.profileDir,
+ "testBackgroundTask",
+ 0o700
+ ));
+
+ profilePath = PathUtils.join(profilePath, "profile_backgroundtask_wait.json");
+ await IOUtils.remove(profilePath, { ignoreAbsent: true });
+
+ let extraEnv = {
+ MOZ_PROFILER_STARTUP: "1",
+ MOZ_PROFILER_SHUTDOWN: profilePath,
+ };
+
+ let exitCode = await do_backgroundtask("wait", { extraEnv });
+ Assert.equal(0, exitCode);
+
+ let rootProfile = await IOUtils.readJSON(profilePath);
+ let profile = rootProfile.threads[0];
+
+ const nameCol = profile.markers.schema.name;
+ const dataCol = profile.markers.schema.data;
+
+ function newMarkers() {
+ return {
+ // The equivalent of `Cu.loadedJSModules` + `Cu.loadedESModules`.
+ modules: [],
+ services: [],
+ };
+ }
+
+ let phases = {};
+ let markersForCurrentPhase = newMarkers();
+
+ // If a subsequent phase loads an already loaded resource, that's
+ // fine. Track all loaded resources to ignore such repeated loads.
+ let markersForAllPhases = newMarkers();
+
+ for (let m of profile.markers.data) {
+ let markerName = profile.stringTable[m[nameCol]];
+ if (markerName.startsWith("BackgroundTasksManager:")) {
+ phases[markerName.split("BackgroundTasksManager:")[1]] =
+ markersForCurrentPhase;
+ markersForCurrentPhase = newMarkers();
+ continue;
+ }
+
+ if (
+ ![
+ "ChromeUtils.import", // JSMs.
+ "ChromeUtils.importESModule", // System ESMs.
+ "ChromeUtils.importESModule static import",
+ "GetService", // XPCOM services.
+ ].includes(markerName)
+ ) {
+ continue;
+ }
+
+ let markerData = m[dataCol];
+ if (
+ markerName == "ChromeUtils.import" ||
+ markerName == "ChromeUtils.importESModule" ||
+ markerName == "ChromeUtils.importESModule static import"
+ ) {
+ let module = markerData.name;
+ if (!markersForAllPhases.modules.includes(module)) {
+ markersForAllPhases.modules.push(module);
+ markersForCurrentPhase.modules.push(module);
+ }
+ }
+
+ if (markerName == "GetService") {
+ // We get a CID from the marker itself, but not a human-readable contract
+ // ID. Now, most of the time, the stack will contain a label like
+ // `GetServiceByContractID @...;1`, and we could extract the contract ID
+ // from that. But there are multiple ways to instantiate services, and
+ // not all of them are annotated in this manner. Therefore we "go the
+ // other way" and use the component manager's mapping from contract IDs to
+ // CIDs. This opens up the possibility for that mapping to be different
+ // between `--backgroundtask` and `xpcshell`, but that's not an issue
+ // right at this moment. It's worth noting that one CID can (and
+ // sometimes does) correspond to more than one contract ID.
+ let cid = markerData.name;
+
+ if (!markersForAllPhases.services.includes(cid)) {
+ markersForAllPhases.services.push(cid);
+ markersForCurrentPhase.services.push(cid);
+ }
+ }
+ }
+
+ // Turn `["1", {name: "2", condition: false}, {name: "3", condition: true}]`
+ // into `new Set(["1", "3"])`.
+ function filterConditions(l) {
+ let set = new Set([]);
+ for (let entry of l) {
+ if (typeof entry == "object") {
+ if ("condition" in entry && !entry.condition) {
+ continue;
+ }
+ entry = entry.name;
+ }
+ set.add(entry);
+ }
+ return set;
+ }
+
+ for (let phaseName in backgroundtaskPhases) {
+ for (let listName in backgroundtaskPhases[phaseName]) {
+ for (let scriptType in backgroundtaskPhases[phaseName][listName]) {
+ backgroundtaskPhases[phaseName][listName][scriptType] =
+ filterConditions(
+ backgroundtaskPhases[phaseName][listName][scriptType]
+ );
+ }
+
+ // Turn human-readable contract IDs into CIDs. It's worth noting that one
+ // CID can (and sometimes does) correspond to more than one contract ID.
+ let services = Array.from(
+ backgroundtaskPhases[phaseName][listName].services || new Set([])
+ );
+ services = services
+ .map(contractID => {
+ try {
+ return Cm.contractIDToCID(contractID).toString();
+ } catch (e) {
+ return null;
+ }
+ })
+ .filter(cid => cid);
+ services.sort();
+ backgroundtaskPhases[phaseName][listName].services = new Set(services);
+ info(
+ `backgroundtaskPhases[${phaseName}][${listName}].services = ${JSON.stringify(
+ services.map(c => c.toString())
+ )}`
+ );
+ }
+ }
+
+ // Turn `{CID}` into `{CID} (@contractID)` or `{CID} (one of
+ // @contractID1, ..., @contractIDn)` as appropriate.
+ function renderResource(resource) {
+ const UUID_PATTERN =
+ /^\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}$/i;
+ if (UUID_PATTERN.test(resource)) {
+ let foundContractIDs = [];
+ for (let contractID of Cm.getContractIDs()) {
+ try {
+ if (resource == Cm.contractIDToCID(contractID).toString()) {
+ foundContractIDs.push(contractID);
+ }
+ } catch (e) {
+ // This can throw for contract IDs that are filtered. The common
+ // reason is that they're limited to a particular process.
+ }
+ }
+ if (!foundContractIDs.length) {
+ return `${resource} (CID with no human-readable contract IDs)`;
+ } else if (foundContractIDs.length == 1) {
+ return `${resource} (CID with human-readable contract ID ${foundContractIDs[0]})`;
+ }
+ foundContractIDs.sort();
+ return `${resource} (CID with human-readable contract IDs ${JSON.stringify(
+ foundContractIDs
+ )})`;
+ }
+
+ return resource;
+ }
+
+ for (let phase in backgroundtaskPhases) {
+ let loadedList = phases[phase];
+ let allowlist = backgroundtaskPhases[phase].allowlist || null;
+ if (allowlist) {
+ for (let scriptType in allowlist) {
+ loadedList[scriptType] = loadedList[scriptType].filter(c => {
+ if (!allowlist[scriptType].has(c)) {
+ return true;
+ }
+ allowlist[scriptType].delete(c);
+ return false;
+ });
+ Assert.deepEqual(
+ loadedList[scriptType],
+ [],
+ `${phase}: should have no unexpected ${scriptType} loaded`
+ );
+
+ // Present errors in deterministic order.
+ let unexpected = Array.from(loadedList[scriptType]);
+ unexpected.sort();
+ for (let script of unexpected) {
+ // It would be nice to show stacks here, but that can be follow-up.
+ ok(
+ false,
+ `${phase}: unexpected ${scriptType}: ${renderResource(script)}`
+ );
+ }
+ Assert.deepEqual(
+ allowlist[scriptType].size,
+ 0,
+ `${phase}: all ${scriptType} allowlist entries should have been used`
+ );
+ let unused = Array.from(allowlist[scriptType]);
+ unused.sort();
+ for (let script of unused) {
+ ok(
+ false,
+ `${phase}: unused ${scriptType} allowlist entry: ${renderResource(
+ script
+ )}`
+ );
+ }
+ }
+ }
+ let denylist = backgroundtaskPhases[phase].denylist || null;
+ if (denylist) {
+ for (let scriptType in denylist) {
+ let resources = denylist[scriptType];
+ resources.sort();
+ for (let resource of resources) {
+ let loaded = loadedList[scriptType].includes(resource);
+ let message = `${phase}: ${renderResource(resource)} is not allowed`;
+ // It would be nice to show stacks here, but that can be follow-up.
+ ok(!loaded, message);
+ }
+ }
+ }
+ }
+});
diff --git a/toolkit/components/backgroundtasks/tests/browser/head.js b/toolkit/components/backgroundtasks/tests/browser/head.js
new file mode 100644
index 0000000000..703a3d64c9
--- /dev/null
+++ b/toolkit/components/backgroundtasks/tests/browser/head.js
@@ -0,0 +1,15 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * 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/. */
+
+"use strict";
+
+const { BackgroundTasksTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/BackgroundTasksTestUtils.sys.mjs"
+);
+BackgroundTasksTestUtils.init(this);
+const do_backgroundtask = BackgroundTasksTestUtils.do_backgroundtask.bind(
+ BackgroundTasksTestUtils
+);