common.js (8320B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /* exported attachConsole, attachConsoleToTab, attachConsoleToWorker, 8 closeDebugger, checkConsoleAPICalls, checkRawHeaders, runTests, nextTest, Ci, Cc, 9 withActiveServiceWorker, Services, consoleAPICall, createCommandsForTab, FRACTIONAL_NUMBER_REGEX, DevToolsServer */ 10 11 const { require } = ChromeUtils.importESModule( 12 "resource://devtools/shared/loader/Loader.sys.mjs" 13 ); 14 const { 15 DevToolsServer, 16 } = require("resource://devtools/server/devtools-server.js"); 17 const { 18 CommandsFactory, 19 } = require("resource://devtools/shared/commands/commands-factory.js"); 20 21 // timeStamp are the result of a number in microsecond divided by 1000. 22 // so we can't expect a precise number of decimals, or even if there would 23 // be decimals at all. 24 const FRACTIONAL_NUMBER_REGEX = /^\d+(\.\d{1,3})?$/; 25 26 function attachConsole(listeners) { 27 return _attachConsole(listeners); 28 } 29 function attachConsoleToTab(listeners) { 30 return _attachConsole(listeners, true); 31 } 32 function attachConsoleToWorker(listeners) { 33 return _attachConsole(listeners, true, true); 34 } 35 36 var _attachConsole = async function (listeners, attachToTab, attachToWorker) { 37 try { 38 function waitForMessage(target) { 39 return new Promise(resolve => { 40 target.addEventListener("message", resolve, { once: true }); 41 }); 42 } 43 44 // Fetch the console actor out of the expected target 45 // ParentProcessTarget / WorkerTarget / FrameTarget 46 let commands, target, worker; 47 if (!attachToTab) { 48 commands = await CommandsFactory.forMainProcess(); 49 target = await commands.descriptorFront.getTarget(); 50 } else { 51 commands = await CommandsFactory.forCurrentTabInChromeMochitest(); 52 // Descriptor's getTarget will only work if the TargetCommand watches for the first top target 53 await commands.targetCommand.startListening(); 54 target = await commands.descriptorFront.getTarget(); 55 if (attachToWorker) { 56 const workerName = "console-test-worker.js#" + new Date().getTime(); 57 worker = new Worker(workerName); 58 await waitForMessage(worker); 59 60 const { workers } = await target.listWorkers(); 61 target = workers.filter(w => w.url == workerName)[0]; 62 if (!target) { 63 console.error( 64 "listWorkers failed. Unable to find the worker actor\n" 65 ); 66 return null; 67 } 68 // This is still important to attach workers as target is still a descriptor front 69 // which "becomes" a target when calling this method: 70 await target.morphWorkerDescriptorIntoWorkerTarget(); 71 } 72 } 73 74 const webConsoleFront = await target.getFront("console"); 75 76 // By default the console isn't listening for anything, 77 // request listeners from here 78 const response = await webConsoleFront.startListeners(listeners); 79 return { 80 state: { 81 dbgClient: commands.client, 82 webConsoleFront, 83 actor: webConsoleFront.actor, 84 // Keep a strong reference to the Worker to avoid it being 85 // GCd during the test (bug 1237492). 86 // eslint-disable-next-line camelcase 87 _worker_ref: worker, 88 }, 89 response, 90 }; 91 } catch (error) { 92 console.error( 93 `attachConsole failed: ${error.error} ${error.message} - ` + error.stack 94 ); 95 } 96 return null; 97 }; 98 99 async function createCommandsForTab() { 100 const commands = await CommandsFactory.forMainProcess(); 101 await commands.targetCommand.startListening(); 102 return commands; 103 } 104 105 function closeDebugger(state, callback) { 106 const onClose = state.dbgClient.close(); 107 108 state.dbgClient = null; 109 state.client = null; 110 111 if (typeof callback === "function") { 112 onClose.then(callback); 113 } 114 return onClose; 115 } 116 117 function checkConsoleAPICalls(consoleCalls, expectedConsoleCalls) { 118 is( 119 consoleCalls.length, 120 expectedConsoleCalls.length, 121 "received correct number of console calls" 122 ); 123 expectedConsoleCalls.forEach(function (message, index) { 124 info("checking received console call #" + index); 125 checkConsoleAPICall(consoleCalls[index], expectedConsoleCalls[index]); 126 }); 127 } 128 129 function checkConsoleAPICall(call, expected) { 130 is( 131 call.arguments?.length || 0, 132 expected.arguments?.length || 0, 133 "number of arguments" 134 ); 135 136 checkObject(call, expected); 137 } 138 139 function checkObject(object, expected) { 140 if (object && object.getGrip) { 141 object = object.getGrip(); 142 } 143 144 for (const name of Object.keys(expected)) { 145 const expectedValue = expected[name]; 146 const value = object[name]; 147 checkValue(name, value, expectedValue); 148 } 149 } 150 151 function checkValue(name, value, expected) { 152 if (expected === null) { 153 ok(!value, "'" + name + "' is null"); 154 } else if (value === undefined) { 155 ok(false, "'" + name + "' is undefined"); 156 } else if (value === null) { 157 ok(false, "'" + name + "' is null"); 158 } else if ( 159 typeof expected == "string" || 160 typeof expected == "number" || 161 typeof expected == "boolean" 162 ) { 163 is(value, expected, "property '" + name + "'"); 164 } else if (expected instanceof RegExp) { 165 ok(expected.test(value), name + ": " + expected + " matched " + value); 166 } else if (Array.isArray(expected)) { 167 info("checking array for property '" + name + "'"); 168 checkObject(value, expected); 169 } else if (typeof expected == "object") { 170 info("checking object for property '" + name + "'"); 171 checkObject(value, expected); 172 } 173 } 174 175 function checkHeadersOrCookies(array, expected) { 176 const foundHeaders = {}; 177 178 for (const elem of array) { 179 if (!(elem.name in expected)) { 180 continue; 181 } 182 foundHeaders[elem.name] = true; 183 info("checking value of header " + elem.name); 184 checkValue(elem.name, elem.value, expected[elem.name]); 185 } 186 187 for (const header in expected) { 188 if (!(header in foundHeaders)) { 189 ok(false, header + " was not found"); 190 } 191 } 192 } 193 194 function checkRawHeaders(text, expected) { 195 const headers = text.split(/\r\n|\n|\r/); 196 const arr = []; 197 for (const header of headers) { 198 const index = header.indexOf(": "); 199 if (index < 0) { 200 continue; 201 } 202 arr.push({ 203 name: header.substr(0, index), 204 value: header.substr(index + 2), 205 }); 206 } 207 208 checkHeadersOrCookies(arr, expected); 209 } 210 211 var gTestState = {}; 212 213 function runTests(tests, endCallback) { 214 function* driver() { 215 let lastResult, sendToNext; 216 for (let i = 0; i < tests.length; i++) { 217 gTestState.index = i; 218 const fn = tests[i]; 219 info("will run test #" + i + ": " + fn.name); 220 lastResult = fn(sendToNext, lastResult); 221 sendToNext = yield lastResult; 222 } 223 yield endCallback(sendToNext, lastResult); 224 } 225 gTestState.driver = driver(); 226 return gTestState.driver.next(); 227 } 228 229 function nextTest(message) { 230 return gTestState.driver.next(message); 231 } 232 233 function withActiveServiceWorker(win, url, scope) { 234 const opts = {}; 235 if (scope) { 236 opts.scope = scope; 237 } 238 return win.navigator.serviceWorker.register(url, opts).then(swr => { 239 if (swr.active) { 240 return swr; 241 } 242 243 // Unfortunately we can't just use navigator.serviceWorker.ready promise 244 // here. If the service worker is for a scope that does not cover the window 245 // then the ready promise will never resolve. Instead monitor the service 246 // workers state change events to determine when its activated. 247 return new Promise(resolve => { 248 const sw = swr.waiting || swr.installing; 249 sw.addEventListener("statechange", function stateHandler() { 250 if (sw.state === "activated") { 251 sw.removeEventListener("statechange", stateHandler); 252 resolve(swr); 253 } 254 }); 255 }); 256 }); 257 } 258 259 /** 260 * 261 * @param {Front} consoleFront 262 * @param {Function} consoleCall: A function which calls the consoleAPI, e.g. : 263 * `() => top.console.log("test")`. 264 * @returns {Promise} A promise that will be resolved with the packet sent by the server 265 * in response to the consoleAPI call. 266 */ 267 function consoleAPICall(consoleFront, consoleCall) { 268 const onConsoleAPICall = consoleFront.once("consoleAPICall"); 269 consoleCall(); 270 return onConsoleAPICall; 271 }