summaryrefslogtreecommitdiffstats
path: root/devtools/shared/webconsole/test/chrome/common.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/webconsole/test/chrome/common.js')
-rw-r--r--devtools/shared/webconsole/test/chrome/common.js274
1 files changed, 274 insertions, 0 deletions
diff --git a/devtools/shared/webconsole/test/chrome/common.js b/devtools/shared/webconsole/test/chrome/common.js
new file mode 100644
index 0000000000..03bf988af2
--- /dev/null
+++ b/devtools/shared/webconsole/test/chrome/common.js
@@ -0,0 +1,274 @@
+/* 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(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
+const {
+ DevToolsServer,
+} = require("resource://devtools/server/devtools-server.js");
+const {
+ CommandsFactory,
+} = require("resource://devtools/shared/commands/commands-factory.js");
+
+// 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();
+ }
+ }
+
+ // Attach the Target and the target thread in order to instantiate the console client.
+ await target.attachThread();
+
+ 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(evt) {
+ 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;
+}