Skip to content

Commit

Permalink
Bug 1667276 - Part 2: Add BackgroundTasksManager to invoke task defin…
Browse files Browse the repository at this point in the history
…ed in JS. r=mossop

Differential Revision: https://phabricator.services.mozilla.com/D97512
  • Loading branch information
ncalexan committed Jan 27, 2021
1 parent eb5ed03 commit 9862045
Show file tree
Hide file tree
Showing 13 changed files with 356 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ if (AppConstants.platform == "macosx") {
gExceptionPaths.push("resource://gre/res/touchbar/");
}

if (AppConstants.MOZ_BACKGROUNDTASKS) {
// `BackgroundTask_id.jsm` is loaded at runtime by `app --backgroundtask id ...`.
gExceptionPaths.push("resource://gre/modules/backgroundtasks/");
}

// Each whitelist entry should have a comment indicating which file is
// referencing the whitelisted file in a way that the test can't detect, or a
// bug number to remove or use the file if it is indeed currently unreferenced.
Expand Down
7 changes: 6 additions & 1 deletion extensions/pref/autoconfig/src/components.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
},
]
17 changes: 14 additions & 3 deletions layout/build/components.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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}',
Expand Down Expand Up @@ -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,
},
},
},
{
Expand Down
12 changes: 12 additions & 0 deletions toolkit/components/backgroundtasks/BackgroundTask_exception.jsm
Original file line number Diff line number Diff line change
@@ -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");
}
12 changes: 12 additions & 0 deletions toolkit/components/backgroundtasks/BackgroundTask_failure.jsm
Original file line number Diff line number Diff line change
@@ -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;
}
12 changes: 12 additions & 0 deletions toolkit/components/backgroundtasks/BackgroundTask_success.jsm
Original file line number Diff line number Diff line change
@@ -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;
}
11 changes: 10 additions & 1 deletion toolkit/components/backgroundtasks/BackgroundTasks.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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;
}
Expand Down
134 changes: 134 additions & 0 deletions toolkit/components/backgroundtasks/BackgroundTasksManager.jsm
Original file line number Diff line number Diff line change
@@ -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;
},
};
11 changes: 11 additions & 0 deletions toolkit/components/backgroundtasks/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
28 changes: 28 additions & 0 deletions toolkit/components/backgroundtasks/nsIBackgroundTasksManager.idl
Original file line number Diff line number Diff line change
@@ -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);
};
86 changes: 86 additions & 0 deletions toolkit/components/backgroundtasks/tests/xpcshell/head.js
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 9862045

Please sign in to comment.