/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import { Subprocess } from "resource://gre/modules/Subprocess.sys.mjs"; function getFirefoxExecutableFilename() { if (AppConstants.platform === "win") { return AppConstants.MOZ_APP_NAME + ".exe"; } if (AppConstants.platform == "linux") { return AppConstants.MOZ_APP_NAME + "-bin"; } 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; } export var BackgroundTasksTestUtils = { init(scope) { this.testScope = scope; }, async do_backgroundtask( task, options = { extraArgs: [], extraEnv: {}, onStdoutLine: null } ) { 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"); const { Assert } = this.testScope; 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. console.info(`launching background task`, { command, args, extraEnv: options.extraEnv, }); let { proc, readPromise } = await Subprocess.call({ command, arguments: args, environment: options.extraEnv, environmentAppend: true, stderr: "stdout", }).then(p => { p.stdin.close().catch(() => { // It's possible that the process exists before we close stdin. // In that case, we should ignore the errors. }); const dumpPipe = async pipe => { // We must assemble all of the string fragments from stdout. let leftover = ""; let data = await pipe.readString(); while (data) { data = leftover + data; // When the string is empty and the separator is not empty, // split() returns an array containing one empty string, // rather than an empty array, i.e., we always have // `lines.length > 0`. let lines = data.split(/\r\n|\r|\n/); for (let line of lines.slice(0, -1)) { dump(`${p.pid}> ${line}\n`); if (options.onStdoutLine) { options.onStdoutLine(line, p); } } leftover = lines[lines.length - 1]; data = await pipe.readString(); } if (leftover.length) { dump(`${p.pid}> ${leftover}\n`); if (options.onStdoutLine) { options.onStdoutLine(leftover, p); } } }; let readPromise = dumpPipe(p.stdout); return { proc: p, readPromise }; }); let { exitCode } = await proc.wait(); try { // Read from the output pipe. await readPromise; } catch (e) { if (e.message !== "File closed") { throw e; } } return exitCode; }, // Setup that allows to use the profile service in xpcshell tests, // lifted from `toolkit/profile/xpcshell/head.js`. setupProfileService() { let gProfD = this.testScope.do_get_profile(); let gDataHome = gProfD.clone(); gDataHome.append("data"); gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); let gDataHomeLocal = gProfD.clone(); gDataHomeLocal.append("local"); gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService( Ci.nsIXREDirProvider ); xreDirProvider.setUserDataDirectory(gDataHome, false); xreDirProvider.setUserDataDirectory(gDataHomeLocal, true); }, };