tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }