Bug 1667276 - Part 2: Add BackgroundTasksManager to invoke task defined in JS. r=mossop

Differential Revision: https://phabricator.services.mozilla.com/D97512
This commit is contained in:
Nick Alexander 2021-01-25 23:45:17 +00:00
parent 93f94ef1a1
commit 02c231aa60
12 changed files with 351 additions and 5 deletions

View file

@ -11,6 +11,11 @@ Classes = [
'type': 'nsReadConfig',
'headers': ['/extensions/pref/autoconfig/src/nsReadConfig.h'],
'init_method': 'Init',
'categories': {'pref-config-startup': 'ReadConfig Module'},
'categories': {
'pref-config-startup': {
'name': 'ReadConfig Module',
'backgroundtasks': BackgroundTasksSelector.ALL_TASKS,
},
},
},
]

View file

@ -96,7 +96,12 @@ Classes = [
'cid': '{fc886801-e768-11d4-9885-00c04fa0cf4b}',
'contract_ids': ['@mozilla.org/content/document-loader-factory;1'],
'type': 'nsIDocumentLoaderFactory',
'categories': {'Gecko-Content-Viewers': content_types},
'categories': {
'Gecko-Content-Viewers': {
'name': content_types,
'backgroundtasks': BackgroundTasksSelector.ALL_TASKS,
}
},
},
{
'cid': '{0ddf4df8-4dbb-4133-8b79-9afb966514f5}',
@ -289,8 +294,14 @@ Classes = [
'type': 'nsMixedContentBlocker',
'headers': ['mozilla/dom/nsMixedContentBlocker.h'],
'categories': {
'content-policy': '@mozilla.org/mixedcontentblocker;1',
'net-channel-event-sinks': '@mozilla.org/mixedcontentblocker;1',
'content-policy': {
'name': '@mozilla.org/mixedcontentblocker;1',
'backgroundtasks': BackgroundTasksSelector.ALL_TASKS,
},
'net-channel-event-sinks': {
'name': '@mozilla.org/mixedcontentblocker;1',
'backgroundtasks': BackgroundTasksSelector.ALL_TASKS,
},
},
},
{

View file

@ -0,0 +1,12 @@
/* -*- 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 = ["runBackgroundTask"];
async function runBackgroundTask() {
console.error("runBackgroundTask: exception");
throw new Error("test");
}

View file

@ -0,0 +1,12 @@
/* -*- 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 = ["runBackgroundTask"];
async function runBackgroundTask() {
console.error("runBackgroundTask: failure");
return 1;
}

View file

@ -0,0 +1,12 @@
/* -*- 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 = ["runBackgroundTask"];
async function runBackgroundTask() {
console.error("runBackgroundTask: success");
return 0;
}

View file

@ -8,9 +8,11 @@
#include "nsCOMPtr.h"
#include "nsIBackgroundTasks.h"
#include "nsIBackgroundTasksManager.h"
#include "nsICommandLine.h"
#include "nsIFile.h"
#include "nsISupports.h"
#include "nsImportModule.h"
#include "nsString.h"
#include "nsXULAppAPI.h"
@ -111,7 +113,14 @@ class BackgroundTasks final : public nsIBackgroundTasks {
return NS_ERROR_NOT_AVAILABLE;
}
// For now, do nothing.
nsCOMPtr<nsIBackgroundTasksManager> manager =
do_ImportModule("resource://gre/modules/BackgroundTasksManager.jsm",
"BackgroundTasksManager");
NS_ENSURE_TRUE(manager, NS_ERROR_FAILURE);
NS_ConvertASCIItoUTF16 name(task.ref().get());
Unused << manager->RunBackgroundTaskNamed(name, aCmdLine);
return NS_OK;
}

View file

@ -0,0 +1,134 @@
/* -*- 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.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`
* and return its `runBackgroundTask` function.
*
* 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 {function} `runBackgroundTask` function.
* @throws NS_ERROR_NOT_AVAILABLE if a background task with the given `name` is
* not found.
*/
function findRunBackgroundTask(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 { runBackgroundTask } = ChromeUtils.import(URI);
log.info(`Found background task at URI: ${URI}`);
return runBackgroundTask;
} 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 = 2;
try {
let runBackgroundTask = findRunBackgroundTask(name);
addMarker("BackgroundTasksManager:AfterFindRunBackgroundTask");
try {
// TODO: timeout tasks that run too long.
exitCode = await runBackgroundTask(commandLine);
log.info(
`Backgroundtask named '${name}' completed with exit code ${exitCode}`
);
} catch (e) {
log.error(`Backgroundtask named '${name}' threw exception`, e);
exitCode = 3;
}
} finally {
addMarker("BackgroundTasksManager:AfterAwaitRunBackgroundTask");
log.info(`Invoking Services.startup.quit(..., ${exitCode})`);
Services.startup.quit(Ci.nsIAppStartup.eForceQuit, exitCode);
}
return exitCode;
},
};

View file

@ -22,8 +22,19 @@ XPCOM_MANIFESTS += [
XPIDL_SOURCES += [
"nsIBackgroundTasks.idl",
"nsIBackgroundTasksManager.idl",
]
XPIDL_MODULE = "toolkit_backgroundtasks"
EXTRA_JS_MODULES += [
"BackgroundTasksManager.jsm",
]
EXTRA_JS_MODULES.backgroundtasks += [
"BackgroundTask_exception.jsm",
"BackgroundTask_failure.jsm",
"BackgroundTask_success.jsm",
]
XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.ini"]

View file

@ -0,0 +1,28 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
#include "nsISupports.idl"
interface nsICommandLine;
/**
* Import and run named backgroundtask implementations.
*/
[scriptable, function, uuid(4d48c536-e16f-4699-8f9c-add4f28f92f0)]
interface nsIBackgroundTasksManager : nsISupports
{
/**
* Run the named background task.
*
* @param aTaskName the name of the task to be run.
* @param aCommandLine the command line of this invocation.
*
* This returns a promise which resolves to an integer exit code, 0 when the
* task succeeded, >0 otherwise. The task manager will quit after this
* promise resolves.
*/
void runBackgroundTaskNamed(in AString aTaskName,
in nsICommandLine aCommandLine);
};

View file

@ -0,0 +1,86 @@
/* -*- 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 { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { Subprocess } = ChromeUtils.import(
"resource://gre/modules/Subprocess.jsm"
);
function getFirefoxExecutableFilename() {
if (AppConstants.platform === "win") {
return AppConstants.MOZ_APP_NAME + ".exe";
}
return AppConstants.MOZ_APP_NAME;
}
// Returns a nsIFile to the firefox.exe (really, application) executable file.
function getFirefoxExecutableFile() {
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
file = Services.dirsvc.get("GreBinD", Ci.nsIFile);
file.append(getFirefoxExecutableFilename());
return file;
}
async function do_backgroundtask(
task,
options = { extraArgs: [], extraEnv: {} }
) {
options = Object.assign({}, options);
options.extraArgs = options.extraArgs || [];
options.extraEnv = options.extraEnv || {};
let command = getFirefoxExecutableFile().path;
let args = ["--backgroundtask", task];
args.push(...options.extraArgs);
// Ensure `resource://testing-common` gets mapped.
let protocolHandler = Services.io
.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
let uri = protocolHandler.getSubstitution("testing-common");
Assert.ok(uri, "resource://testing-common is not substituted");
// The equivalent of _TESTING_MODULES_DIR in xpcshell.
options.extraEnv.XPCSHELL_TESTING_MODULES_URI = uri.spec;
// Now we can actually invoke the process.
info(
`launching child process ${command} with args: ${args} and extra environment: ${JSON.stringify(
options.extraEnv
)}`
);
let proc = await Subprocess.call({
command,
arguments: args,
environment: options.extraEnv,
environmentAppend: true,
stderr: "stdout",
}).then(p => {
p.stdin.close();
const dumpPipe = async pipe => {
let data = await pipe.readString();
while (data) {
for (let line of data.split(/\r\n|\r|\n/).slice(0, -1)) {
dump("> " + line + "\n");
}
data = await pipe.readString();
}
};
dumpPipe(p.stdout);
return p;
});
let { exitCode } = await proc.wait();
return exitCode;
}

View file

@ -0,0 +1,25 @@
/* -*- 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/. */
add_task(async function test_success() {
let exitCode = await do_backgroundtask("success");
Assert.equal(0, exitCode);
});
add_task(async function test_failure() {
let exitCode = await do_backgroundtask("failure");
Assert.equal(1, exitCode);
});
add_task(async function test_exception() {
let exitCode = await do_backgroundtask("exception");
Assert.equal(3, exitCode);
});
add_task(async function test_not_found() {
let exitCode = await do_backgroundtask("not_found");
Assert.equal(2, exitCode);
});

View file

@ -7,5 +7,6 @@ skip-if = toolkit == 'android'
support-files =
CatBackgroundTaskRegistrationComponents.manifest
[test_backgroundtask_exitcodes.js]
[test_manifest_with_backgroundtask.js]
[test_manifest_without_backgroundtask.js]