Source code

Revision control

Copy as Markdown

Other Tools

/* 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";
/* exported attachConsole, attachConsoleToTab, attachConsoleToWorker,
closeDebugger, checkConsoleAPICalls, checkRawHeaders, runTests, nextTest, Ci, Cc,
withActiveServiceWorker, Services, consoleAPICall, createCommandsForTab, FRACTIONAL_NUMBER_REGEX, DevToolsServer */
const { require } = ChromeUtils.importESModule(
);
const {
DevToolsServer,
const {
CommandsFactory,
// timeStamp are the result of a number in microsecond divided by 1000.
// so we can't expect a precise number of decimals, or even if there would
// be decimals at all.
const FRACTIONAL_NUMBER_REGEX = /^\d+(\.\d{1,3})?$/;
function attachConsole(listeners) {
return _attachConsole(listeners);
}
function attachConsoleToTab(listeners) {
return _attachConsole(listeners, true);
}
function attachConsoleToWorker(listeners) {
return _attachConsole(listeners, true, true);
}
var _attachConsole = async function (listeners, attachToTab, attachToWorker) {
try {
function waitForMessage(target) {
return new Promise(resolve => {
target.addEventListener("message", resolve, { once: true });
});
}
// Fetch the console actor out of the expected target
// ParentProcessTarget / WorkerTarget / FrameTarget
let commands, target, worker;
if (!attachToTab) {
commands = await CommandsFactory.forMainProcess();
target = await commands.descriptorFront.getTarget();
} else {
commands = await CommandsFactory.forCurrentTabInChromeMochitest();
// Descriptor's getTarget will only work if the TargetCommand watches for the first top target
await commands.targetCommand.startListening();
target = await commands.descriptorFront.getTarget();
if (attachToWorker) {
const workerName = "console-test-worker.js#" + new Date().getTime();
worker = new Worker(workerName);
await waitForMessage(worker);
const { workers } = await target.listWorkers();
target = workers.filter(w => w.url == workerName)[0];
if (!target) {
console.error(
"listWorkers failed. Unable to find the worker actor\n"
);
return null;
}
// This is still important to attach workers as target is still a descriptor front
// which "becomes" a target when calling this method:
await target.morphWorkerDescriptorIntoWorkerTarget();
}
}
const webConsoleFront = await target.getFront("console");
// By default the console isn't listening for anything,
// request listeners from here
const response = await webConsoleFront.startListeners(listeners);
return {
state: {
dbgClient: commands.client,
webConsoleFront,
actor: webConsoleFront.actor,
// Keep a strong reference to the Worker to avoid it being
// GCd during the test (bug 1237492).
// eslint-disable-next-line camelcase
_worker_ref: worker,
},
response,
};
} catch (error) {
console.error(
`attachConsole failed: ${error.error} ${error.message} - ` + error.stack
);
}
return null;
};
async function createCommandsForTab() {
const commands = await CommandsFactory.forMainProcess();
await commands.targetCommand.startListening();
return commands;
}
function closeDebugger(state, callback) {
const onClose = state.dbgClient.close();
state.dbgClient = null;
state.client = null;
if (typeof callback === "function") {
onClose.then(callback);
}
return onClose;
}
function checkConsoleAPICalls(consoleCalls, expectedConsoleCalls) {
is(
consoleCalls.length,
expectedConsoleCalls.length,
"received correct number of console calls"
);
expectedConsoleCalls.forEach(function (message, index) {
info("checking received console call #" + index);
checkConsoleAPICall(consoleCalls[index], expectedConsoleCalls[index]);
});
}
function checkConsoleAPICall(call, expected) {
is(
call.arguments?.length || 0,
expected.arguments?.length || 0,
"number of arguments"
);
checkObject(call, expected);
}
function checkObject(object, expected) {
if (object && object.getGrip) {
object = object.getGrip();
}
for (const name of Object.keys(expected)) {
const expectedValue = expected[name];
const value = object[name];
checkValue(name, value, expectedValue);
}
}
function checkValue(name, value, expected) {
if (expected === null) {
ok(!value, "'" + name + "' is null");
} else if (value === undefined) {
ok(false, "'" + name + "' is undefined");
} else if (value === null) {
ok(false, "'" + name + "' is null");
} else if (
typeof expected == "string" ||
typeof expected == "number" ||
typeof expected == "boolean"
) {
is(value, expected, "property '" + name + "'");
} else if (expected instanceof RegExp) {
ok(expected.test(value), name + ": " + expected + " matched " + value);
} else if (Array.isArray(expected)) {
info("checking array for property '" + name + "'");
checkObject(value, expected);
} else if (typeof expected == "object") {
info("checking object for property '" + name + "'");
checkObject(value, expected);
}
}
function checkHeadersOrCookies(array, expected) {
const foundHeaders = {};
for (const elem of array) {
if (!(elem.name in expected)) {
continue;
}
foundHeaders[elem.name] = true;
info("checking value of header " + elem.name);
checkValue(elem.name, elem.value, expected[elem.name]);
}
for (const header in expected) {
if (!(header in foundHeaders)) {
ok(false, header + " was not found");
}
}
}
function checkRawHeaders(text, expected) {
const headers = text.split(/\r\n|\n|\r/);
const arr = [];
for (const header of headers) {
const index = header.indexOf(": ");
if (index < 0) {
continue;
}
arr.push({
name: header.substr(0, index),
value: header.substr(index + 2),
});
}
checkHeadersOrCookies(arr, expected);
}
var gTestState = {};
function runTests(tests, endCallback) {
function* driver() {
let lastResult, sendToNext;
for (let i = 0; i < tests.length; i++) {
gTestState.index = i;
const fn = tests[i];
info("will run test #" + i + ": " + fn.name);
lastResult = fn(sendToNext, lastResult);
sendToNext = yield lastResult;
}
yield endCallback(sendToNext, lastResult);
}
gTestState.driver = driver();
return gTestState.driver.next();
}
function nextTest(message) {
return gTestState.driver.next(message);
}
function withActiveServiceWorker(win, url, scope) {
const opts = {};
if (scope) {
opts.scope = scope;
}
return win.navigator.serviceWorker.register(url, opts).then(swr => {
if (swr.active) {
return swr;
}
// Unfortunately we can't just use navigator.serviceWorker.ready promise
// here. If the service worker is for a scope that does not cover the window
// then the ready promise will never resolve. Instead monitor the service
// workers state change events to determine when its activated.
return new Promise(resolve => {
const sw = swr.waiting || swr.installing;
sw.addEventListener("statechange", function stateHandler() {
if (sw.state === "activated") {
sw.removeEventListener("statechange", stateHandler);
resolve(swr);
}
});
});
});
}
/**
*
* @param {Front} consoleFront
* @param {Function} consoleCall: A function which calls the consoleAPI, e.g. :
* `() => top.console.log("test")`.
* @returns {Promise} A promise that will be resolved with the packet sent by the server
* in response to the consoleAPI call.
*/
function consoleAPICall(consoleFront, consoleCall) {
const onConsoleAPICall = consoleFront.once("consoleAPICall");
consoleCall();
return onConsoleAPICall;
}