gecko-dev/toolkit/components/backgroundtasks/BackgroundTasksManager.jsm

203 строки
6.5 KiB
JavaScript

/* -*- 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/. */
var EXPORTED_SYMBOLS = ["BackgroundTasksManager"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
setTimeout: "resource://gre/modules/Timer.jsm",
});
XPCOMUtils.defineLazyGetter(this, "log", () => {
let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {})
.ConsoleAPI;
let consoleOptions = {
// tip: set maxLogLevel to "debug" and use log.debug() to create detailed
// messages during development. See LOG_LEVELS in Console.jsm for details.
maxLogLevel: "error",
maxLogLevelPref: "toolkit.backgroundtasks.loglevel",
prefix: "BackgroundTasksManager",
};
return new ConsoleAPI(consoleOptions);
});
// Map resource://testing-common/ to the shared test modules directory. This is
// a transliteration of `register_modules_protocol_handler` from
// https://searchfox.org/mozilla-central/rev/f081504642a115cb8236bea4d8250e5cb0f39b02/testing/xpcshell/head.js#358-389.
function registerModulesProtocolHandler() {
let env = Cc["@mozilla.org/process/environment;1"].getService(
Ci.nsIEnvironment
);
let _TESTING_MODULES_URI = env.get("XPCSHELL_TESTING_MODULES_URI", "");
if (!_TESTING_MODULES_URI) {
return false;
}
let protocolHandler = Services.io
.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
protocolHandler.setSubstitution(
"testing-common",
Services.io.newURI(_TESTING_MODULES_URI)
);
// Log loudly so that when testing, we always actually use the
// console logging mechanism and therefore deterministically load that code.
log.error(
`Substitution set: resource://testing-common aliases ${_TESTING_MODULES_URI}`
);
return true;
}
/**
* Find a JSM named like `backgroundtasks/BackgroundTask_${name}.jsm`,
* import it, and return the whole module.
*
* When testing, allow to load from `XPCSHELL_TESTING_MODULES_URI`,
* which is registered at `resource://testing-common`, the standard
* location for test-only modules.
*
* @return {Object} The imported module.
* @throws NS_ERROR_NOT_AVAILABLE if a background task with the given `name` is
* not found.
*/
function findBackgroundTaskModule(name) {
const subModules = [
"resource:///modules", // App-specific first.
"resource://gre/modules", // Toolkit/general second.
];
if (registerModulesProtocolHandler()) {
subModules.push("resource://testing-common"); // Test-only third.
}
for (const subModule of subModules) {
let URI = `${subModule}/backgroundtasks/BackgroundTask_${name}.jsm`;
log.debug(`Looking for background task at URI: ${URI}`);
try {
const taskModule = ChromeUtils.import(URI);
log.info(`Found background task at URI: ${URI}`);
return taskModule;
} catch (ex) {
if (ex.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
throw ex;
}
}
}
log.warn(`No backgroundtask named '${name}' registered`);
throw new Components.Exception(
`No backgroundtask named '${name}' registered`,
Cr.NS_ERROR_NOT_AVAILABLE
);
}
var BackgroundTasksManager = {
async runBackgroundTaskNamed(name, commandLine) {
function addMarker(markerName) {
return ChromeUtils.addProfilerMarker(markerName, undefined, name);
}
addMarker("BackgroundTasksManager:AfterRunBackgroundTaskNamed");
log.info(
`Running background task named '${name}' (with ${commandLine.length} arguments)`
);
let exitCode = BackgroundTasksManager.EXIT_CODE.NOT_FOUND;
try {
let taskModule = findBackgroundTaskModule(name);
addMarker("BackgroundTasksManager:AfterFindRunBackgroundTask");
let timeoutSec = Services.prefs.getIntPref(
"toolkit.backgroundtasks.defaultTimeoutSec",
10 * 60
);
if (taskModule.backgroundTaskTimeoutSec) {
timeoutSec = taskModule.backgroundTaskTimeoutSec;
}
try {
exitCode = await Promise.race([
new Promise(resolve =>
setTimeout(() => {
log.error(`Background task named '${name}' timed out`);
resolve(BackgroundTasksManager.EXIT_CODE.TIMEOUT);
}, timeoutSec * 1000)
),
taskModule.runBackgroundTask(commandLine),
]);
log.info(
`Backgroundtask named '${name}' completed with exit code ${exitCode}`
);
} catch (e) {
log.error(`Backgroundtask named '${name}' threw exception`, e);
exitCode = BackgroundTasksManager.EXIT_CODE.EXCEPTION;
}
} finally {
addMarker("BackgroundTasksManager:AfterAwaitRunBackgroundTask");
log.info(`Invoking Services.startup.quit(..., ${exitCode})`);
Services.startup.quit(Ci.nsIAppStartup.eForceQuit, exitCode);
}
return exitCode;
},
};
/**
* Background tasks should standard exit code conventions where 0 denotes
* success and non-zero denotes failure and/or an error. In addition, since
* background tasks have limited channels to communicate with consumers, the
* special values `NOT_FOUND` (integer 2) and `THREW_EXCEPTION` (integer 3) are
* distinguished.
*
* If you extend this to add background task-specific exit codes, use exit codes
* greater than 10 to allow for additional shared exit codes to be added here.
* Exit codes should be between 0 and 127 to be safe across platforms.
*/
BackgroundTasksManager.EXIT_CODE = {
/**
* The task succeeded.
*
* The `runBackgroundTask(...)` promise resolved to 0.
*/
SUCCESS: 0,
/**
* The task with the specified name could not be found or imported.
*
* The corresponding `runBackgroundTask` method could not be found.
*/
NOT_FOUND: 2,
/**
* The task failed with an uncaught exception.
*
* The `runBackgroundTask(...)` promise rejected with an exception.
*/
EXCEPTION: 3,
/**
* The task took too long and timed out.
*
* The default timeout is controlled by the pref:
* "toolkit.backgroundtasks.defaultTimeoutSec", but tasks can override this
* by exporting a non-zero `backgroundTaskTimeoutSec` value.
*/
TIMEOUT: 4,
/**
* The last exit code reserved by this structure. Use codes larger than this
* code for background task-specific exit codes.
*/
LAST_RESERVED: 10,
};