tor-browser

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

head_dbg.js (30359B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 /* eslint-disable no-shadow */
      4 
      5 "use strict";
      6 var CC = Components.Constructor;
      7 
      8 // Populate AppInfo before anything (like the shared loader) accesses
      9 // System.appinfo, which is a lazy getter.
     10 const appInfo = ChromeUtils.importESModule(
     11  "resource://testing-common/AppInfo.sys.mjs"
     12 );
     13 appInfo.updateAppInfo({
     14  ID: "devtools@tests.mozilla.org",
     15  name: "devtools-tests",
     16  version: "1",
     17  platformVersion: "42",
     18  crashReporter: true,
     19 });
     20 
     21 const { require, loader } = ChromeUtils.importESModule(
     22  "resource://devtools/shared/loader/Loader.sys.mjs"
     23 );
     24 const { worker } = ChromeUtils.importESModule(
     25  "resource://devtools/shared/loader/worker-loader.sys.mjs"
     26 );
     27 
     28 const { NetUtil } = ChromeUtils.importESModule(
     29  "resource://gre/modules/NetUtil.sys.mjs"
     30 );
     31 
     32 // Always log packets when running tests. runxpcshelltests.py will throw
     33 // the output away anyway, unless you give it the --verbose flag.
     34 Services.prefs.setBoolPref("devtools.debugger.log", false);
     35 // Enable remote debugging for the relevant tests.
     36 Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
     37 
     38 const makeDebugger = require("resource://devtools/server/actors/utils/make-debugger.js");
     39 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
     40 const {
     41  ActorRegistry,
     42 } = require("resource://devtools/server/actors/utils/actor-registry.js");
     43 const {
     44  DevToolsServer,
     45 } = require("resource://devtools/server/devtools-server.js");
     46 const { DevToolsServer: WorkerDevToolsServer } = worker.require(
     47  "resource://devtools/server/devtools-server.js"
     48 );
     49 const {
     50  DevToolsClient,
     51 } = require("resource://devtools/client/devtools-client.js");
     52 const { ObjectFront } = require("resource://devtools/client/fronts/object.js");
     53 const {
     54  LongStringFront,
     55 } = require("resource://devtools/client/fronts/string.js");
     56 const {
     57  createCommandsDictionary,
     58 } = require("resource://devtools/shared/commands/index.js");
     59 const {
     60  CommandsFactory,
     61 } = require("resource://devtools/shared/commands/commands-factory.js");
     62 
     63 const { addDebuggerToGlobal } = ChromeUtils.importESModule(
     64  "resource://gre/modules/jsdebugger.sys.mjs"
     65 );
     66 
     67 const { AddonTestUtils } = ChromeUtils.importESModule(
     68  "resource://testing-common/AddonTestUtils.sys.mjs"
     69 );
     70 const { getAppInfo } = ChromeUtils.importESModule(
     71  "resource://testing-common/AppInfo.sys.mjs"
     72 );
     73 
     74 const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(
     75  Ci.nsIPrincipal
     76 );
     77 
     78 var { loadSubScript, loadSubScriptWithOptions } = Services.scriptloader;
     79 
     80 /**
     81 * The logic here must resemble the logic of --start-debugger-server as closely
     82 * as possible. DevToolsStartup.sys.mjs uses a distinct loader that results in
     83 * the existence of two isolated module namespaces. In practice, this can cause
     84 * bugs such as bug 1837185.
     85 */
     86 function getDistinctDevToolsServer() {
     87  const {
     88    useDistinctSystemPrincipalLoader,
     89    releaseDistinctSystemPrincipalLoader,
     90  } = ChromeUtils.importESModule(
     91    "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs",
     92    { global: "shared" }
     93  );
     94  const requester = {};
     95  const distinctLoader = useDistinctSystemPrincipalLoader(requester);
     96  registerCleanupFunction(() => {
     97    releaseDistinctSystemPrincipalLoader(requester);
     98  });
     99 
    100  const { DevToolsServer: DistinctDevToolsServer } = distinctLoader.require(
    101    "resource://devtools/server/devtools-server.js"
    102  );
    103  return DistinctDevToolsServer;
    104 }
    105 
    106 /**
    107 * Initializes any test that needs to work with add-ons.
    108 *
    109 * Should be called once per test script that needs to use AddonTestUtils (and
    110 * not once per test task!).
    111 */
    112 async function startupAddonsManager() {
    113  // Create a directory for extensions.
    114  const profileDir = do_get_profile().clone();
    115  profileDir.append("extensions");
    116 
    117  AddonTestUtils.init(globalThis);
    118  AddonTestUtils.overrideCertDB();
    119  AddonTestUtils.appInfo = getAppInfo();
    120 
    121  await AddonTestUtils.promiseStartupManager();
    122 }
    123 
    124 async function createTargetForFakeTab(title) {
    125  const client = await startTestDevToolsServer(title);
    126 
    127  const tabs = await listTabs(client);
    128  const tabDescriptor = findTab(tabs, title);
    129 
    130  // These xpcshell tests use mocked actors (xpcshell-test/testactors)
    131  // which still don't support watcher actor.
    132  // Because of that we still can't enable server side targets and target swiching.
    133  tabDescriptor.disableTargetSwitching();
    134 
    135  return tabDescriptor.getTarget();
    136 }
    137 
    138 async function createTargetForMainProcess() {
    139  const commands = await CommandsFactory.forMainProcess();
    140  return commands.descriptorFront.getTarget();
    141 }
    142 
    143 /**
    144 * Create a MemoryFront for a fake test tab.
    145 */
    146 async function createTabMemoryFront() {
    147  const target = await createTargetForFakeTab("test_memory");
    148 
    149  // MemoryFront requires the HeadSnapshotActor actor to be available
    150  // as a global actor. This isn't registered by startTestDevToolsServer which
    151  // only register the target actors and not the browser ones.
    152  DevToolsServer.registerActors({ browser: true });
    153 
    154  const memoryFront = await target.getFront("memory");
    155  await memoryFront.attach();
    156 
    157  registerCleanupFunction(async () => {
    158    await memoryFront.detach();
    159 
    160    // On XPCShell, the target isn't for a local tab and so target.destroy
    161    // won't close the client. So do it so here. It will automatically destroy the target.
    162    await target.client.close();
    163  });
    164 
    165  return { target, memoryFront };
    166 }
    167 
    168 /**
    169 * Same as createTabMemoryFront but attaches the MemoryFront to the MemoryActor
    170 * scoped to the full runtime rather than to a tab.
    171 */
    172 async function createMainProcessMemoryFront() {
    173  const target = await createTargetForMainProcess();
    174 
    175  const memoryFront = await target.getFront("memory");
    176  await memoryFront.attach();
    177 
    178  registerCleanupFunction(async () => {
    179    await memoryFront.detach();
    180    // For XPCShell, the main process target actor is ContentProcessTargetActor
    181    // which doesn't expose any `detach` method. So that the target actor isn't
    182    // destroyed when calling target.destroy.
    183    // Close the client to cleanup everything.
    184    await target.client.close();
    185  });
    186 
    187  return { client: target.client, memoryFront };
    188 }
    189 
    190 function createLongStringFront(conn, form) {
    191  // CAUTION -- do not replicate in the codebase. Instead, use marshalling
    192  // This code is simulating how the LongStringFront would be created by protocol.js
    193  // We should not use it like this in the codebase, this is done only for testing
    194  // purposes until we can return a proper LongStringFront from the server.
    195  const front = new LongStringFront(conn, form);
    196  front.actorID = form.actor;
    197  front.manage(front);
    198  return front;
    199 }
    200 
    201 function createTestGlobal(name, options) {
    202  // By default, use a content principal to better simulate running as a web page
    203  const origin = "http://example.com/" + name;
    204  const principal = options?.chrome
    205    ? Services.scriptSecurityManager.getSystemPrincipal()
    206    : Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);
    207 
    208  const chromeWebNav = Services.appShell.createWindowlessBrowser(false);
    209  const { docShell } = chromeWebNav;
    210  docShell.createAboutBlankDocumentViewer(principal, principal);
    211  const window = docShell.docViewer.DOMDocument.defaultView;
    212  window.document.title = name;
    213 
    214  // When using a chrome document, the window object won't be an xray wrapper.
    215  return window.wrappedJSObject || window;
    216 }
    217 
    218 function connect(client) {
    219  dump("Connecting client.\n");
    220  return client.connect();
    221 }
    222 
    223 function close(client) {
    224  dump("Closing client.\n");
    225  return client.close();
    226 }
    227 
    228 function listTabs(client) {
    229  dump("Listing tabs.\n");
    230  return client.mainRoot.listTabs();
    231 }
    232 
    233 function findTab(tabs, title) {
    234  dump("Finding tab with title '" + title + "'.\n");
    235  for (const tab of tabs) {
    236    if (tab.title === title) {
    237      return tab;
    238    }
    239  }
    240  return null;
    241 }
    242 
    243 function waitForNewSource(threadFront, url) {
    244  dump("Waiting for new source with url '" + url + "'.\n");
    245  return waitForEvent(threadFront, "newSource", function (packet) {
    246    return packet.source.url === url;
    247  });
    248 }
    249 
    250 function resume(threadFront) {
    251  dump("Resuming thread.\n");
    252  return threadFront.resume();
    253 }
    254 
    255 async function addWatchpoint(threadFront, frame, variable, property, type) {
    256  const path = `${variable}.${property}`;
    257  info(`Add an ${path} ${type} watchpoint`);
    258  const environment = await frame.getEnvironment();
    259  const obj = environment.bindings.variables[variable];
    260  const objFront = threadFront.pauseGrip(obj.value);
    261  return objFront.addWatchpoint(property, path, type);
    262 }
    263 
    264 function getSources(threadFront) {
    265  dump("Getting sources.\n");
    266  return threadFront.getSources();
    267 }
    268 
    269 function findSource(sources, url) {
    270  dump("Finding source with url '" + url + "'.\n");
    271  for (const source of sources) {
    272    if (source.url === url) {
    273      return source;
    274    }
    275  }
    276  return null;
    277 }
    278 
    279 function waitForPause(threadFront) {
    280  dump("Waiting for pause.\n");
    281  return waitForEvent(threadFront, "paused");
    282 }
    283 
    284 function waitForProperty(dbg, property) {
    285  return new Promise(resolve => {
    286    Object.defineProperty(dbg, property, {
    287      set(newValue) {
    288        resolve(newValue);
    289      },
    290    });
    291  });
    292 }
    293 
    294 function setBreakpoint(threadFront, location) {
    295  dump("Setting breakpoint.\n");
    296  return threadFront.setBreakpoint(location, {});
    297 }
    298 
    299 function getPrototypeAndProperties(objClient) {
    300  dump("getting prototype and properties.\n");
    301 
    302  return objClient.getPrototypeAndProperties();
    303 }
    304 
    305 function dumpn(msg) {
    306  dump("DBG-TEST: " + msg + "\n");
    307 }
    308 
    309 function testExceptionHook(ex) {
    310  try {
    311    do_report_unexpected_exception(ex);
    312  } catch (e) {
    313    return { throw: e };
    314  }
    315  return undefined;
    316 }
    317 
    318 // Convert an nsIScriptError 'logLevel' value into an appropriate string.
    319 function scriptErrorLogLevel(message) {
    320  switch (message.logLevel) {
    321    case Ci.nsIConsoleMessage.info:
    322      return "info";
    323    case Ci.nsIConsoleMessage.warn:
    324      return "warning";
    325    default:
    326      Assert.equal(message.logLevel, Ci.nsIConsoleMessage.error);
    327      return "error";
    328  }
    329 }
    330 
    331 // Register a console listener, so console messages don't just disappear
    332 // into the ether.
    333 var errorCount = 0;
    334 var listener = {
    335  observe(message) {
    336    try {
    337      let string;
    338      errorCount++;
    339      try {
    340        // If we've been given an nsIScriptError, then we can print out
    341        // something nicely formatted, for tools like Emacs to pick up.
    342        message.QueryInterface(Ci.nsIScriptError);
    343        dumpn(
    344          message.sourceName +
    345            ":" +
    346            message.lineNumber +
    347            ": " +
    348            scriptErrorLogLevel(message) +
    349            ": " +
    350            message.errorMessage
    351        );
    352        string = message.errorMessage;
    353      } catch (e1) {
    354        // Be a little paranoid with message, as the whole goal here is to lose
    355        // no information.
    356        try {
    357          string = "" + message.message;
    358        } catch (e2) {
    359          string = "<error converting error message to string>";
    360        }
    361      }
    362 
    363      // Make sure we exit all nested event loops so that the test can finish.
    364      while (
    365        DevToolsServer &&
    366        DevToolsServer.xpcInspector &&
    367        DevToolsServer.xpcInspector.eventLoopNestLevel > 0
    368      ) {
    369        DevToolsServer.xpcInspector.exitNestedEventLoop();
    370      }
    371 
    372      // In the world before bug 997440, exceptions were getting lost because of
    373      // the arbitrary JSContext being used in nsXPCWrappedJS::CallMethod.
    374      // In the new world, the wanderers have returned. However, because of the,
    375      // currently very-broken, exception reporting machinery in
    376      // nsXPCWrappedJS these get reported as errors to the console, even if
    377      // there's actually JS on the stack above that will catch them.  If we
    378      // throw an error here because of them our tests start failing.  So, we'll
    379      // just dump the message to the logs instead, to make sure the information
    380      // isn't lost.
    381      dumpn("head_dbg.js observed a console message: " + string);
    382    } catch (_) {
    383      // Swallow everything to avoid console reentrancy errors. We did our best
    384      // to log above, but apparently that didn't cut it.
    385    }
    386  },
    387 };
    388 
    389 Services.console.registerListener(listener);
    390 
    391 function addTestGlobal(name, server = DevToolsServer) {
    392  const global = createTestGlobal(name);
    393  server.addTestGlobal(global);
    394  return global;
    395 }
    396 
    397 // List the DevToolsClient |client|'s tabs, look for one whose title is
    398 // |title|.
    399 async function getTestTab(client, title) {
    400  const tabs = await client.mainRoot.listTabs();
    401  for (const tab of tabs) {
    402    if (tab.title === title) {
    403      return tab;
    404    }
    405  }
    406  return null;
    407 }
    408 /**
    409 *  Attach to the client's tab whose title is specified
    410 *
    411 * @param {object} client
    412 * @param {object} title
    413 * @returns commands
    414 */
    415 async function attachTestTab(client, title) {
    416  const descriptorFront = await getTestTab(client, title);
    417 
    418  // These xpcshell tests use mocked actors (xpcshell-test/testactors)
    419  // which still don't support watcher actor.
    420  // Because of that we still can't enable server side targets and target swiching.
    421  descriptorFront.disableTargetSwitching();
    422 
    423  const commands = await createCommandsDictionary(descriptorFront);
    424  await commands.targetCommand.startListening();
    425  return commands;
    426 }
    427 
    428 /**
    429 * Attach to the client's tab whose title is specified, and then attach to
    430 * that tab's thread.
    431 *
    432 * @param {object} client
    433 * @param {object} title
    434 * @returns {object}
    435 *         targetFront
    436 *         threadFront
    437 *         commands
    438 */
    439 async function attachTestThread(client, title) {
    440  const commands = await attachTestTab(client, title);
    441  const targetFront = commands.targetCommand.targetFront;
    442 
    443  // Pass any configuration, in order to ensure starting all the thread actors
    444  // and have them to handle debugger statements.
    445  await commands.threadConfigurationCommand.updateConfiguration({
    446    skipBreakpoints: false,
    447    // Disable pause overlay as we don't have a true tab and it would throw trying to display them
    448    pauseOverlay: false,
    449  });
    450 
    451  const threadFront = await targetFront.getFront("thread");
    452  Assert.equal(threadFront.state, "attached", "Thread front is attached");
    453  return { targetFront, threadFront, commands };
    454 }
    455 
    456 /**
    457 * Initialize the testing devtools server.
    458 */
    459 function initTestDevToolsServer(server = DevToolsServer) {
    460  if (server === WorkerDevToolsServer) {
    461    const { createRootActor } = worker.require("xpcshell-test/testactors");
    462    server.setRootActor(createRootActor);
    463  } else {
    464    const { createRootActor } = require("xpcshell-test/testactors");
    465    server.setRootActor(createRootActor);
    466  }
    467 
    468  // Allow incoming connections.
    469  server.init(function () {
    470    return true;
    471  });
    472 }
    473 
    474 /**
    475 * Initialize the testing devtools server with a tab whose title is |title|.
    476 */
    477 async function startTestDevToolsServer(title, server = DevToolsServer) {
    478  initTestDevToolsServer(server);
    479  addTestGlobal(title);
    480  DevToolsServer.registerActors({ target: true });
    481 
    482  const transport = DevToolsServer.connectPipe();
    483  const client = new DevToolsClient(transport);
    484 
    485  await connect(client);
    486  return client;
    487 }
    488 
    489 async function finishClient(client) {
    490  await client.close();
    491  DevToolsServer.destroy();
    492  do_test_finished();
    493 }
    494 
    495 /**
    496 * Takes a relative file path and returns the absolute file url for it.
    497 */
    498 function getFileUrl(name, allowMissing = false) {
    499  const file = do_get_file(name, allowMissing);
    500  return Services.io.newFileURI(file).spec;
    501 }
    502 
    503 /**
    504 * Returns the full path of the file with the specified name in a
    505 * platform-independent and URL-like form.
    506 */
    507 function getFilePath(
    508  name,
    509  allowMissing = false,
    510  usePlatformPathSeparator = false
    511 ) {
    512  const file = do_get_file(name, allowMissing);
    513  let path = Services.io.newFileURI(file).spec;
    514  let filePrePath = "file://";
    515  if ("nsILocalFileWin" in Ci && file instanceof Ci.nsILocalFileWin) {
    516    filePrePath += "/";
    517  }
    518 
    519  path = path.slice(filePrePath.length);
    520 
    521  if (usePlatformPathSeparator && path.match(/^\w:/)) {
    522    path = path.replace(/\//g, "\\");
    523  }
    524 
    525  return path;
    526 }
    527 
    528 /**
    529 * Returns the full text contents of the given file.
    530 */
    531 function readFile(fileName) {
    532  const f = do_get_file(fileName);
    533  const s = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
    534    Ci.nsIFileInputStream
    535  );
    536  s.init(f, -1, -1, false);
    537  try {
    538    return NetUtil.readInputStreamToString(s, s.available());
    539  } finally {
    540    s.close();
    541  }
    542 }
    543 
    544 function writeFile(fileName, content) {
    545  const file = do_get_file(fileName, true);
    546  const stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
    547    Ci.nsIFileOutputStream
    548  );
    549  stream.init(file, -1, -1, 0);
    550  try {
    551    do {
    552      const numWritten = stream.write(content, content.length);
    553      content = content.slice(numWritten);
    554    } while (content.length);
    555  } finally {
    556    stream.close();
    557  }
    558 }
    559 
    560 function StubTransport() {}
    561 StubTransport.prototype.ready = function () {};
    562 StubTransport.prototype.send = function () {};
    563 StubTransport.prototype.close = function () {};
    564 
    565 // Create async version of the object where calling each method
    566 // is equivalent of calling it with asyncall. Mainly useful for
    567 // destructuring objects with methods that take callbacks.
    568 const Async = target => new Proxy(target, Async);
    569 Async.get = (target, name) =>
    570  typeof target[name] === "function"
    571    ? asyncall.bind(null, target[name], target)
    572    : target[name];
    573 
    574 // Calls async function that takes callback and errorback and returns
    575 // returns promise representing result.
    576 const asyncall = (fn, self, ...args) =>
    577  new Promise((...etc) => fn.call(self, ...args, ...etc));
    578 
    579 const Test = task => () => {
    580  add_task(task);
    581  run_next_test();
    582 };
    583 
    584 const assert = Assert.ok.bind(Assert);
    585 
    586 /**
    587 * Create a promise that is resolved on the next occurence of the given event.
    588 *
    589 * @param ThreadFront threadFront
    590 * @param String event
    591 * @param Function predicate
    592 * @returns Promise
    593 */
    594 function waitForEvent(front, type, predicate) {
    595  if (!predicate) {
    596    return front.once(type);
    597  }
    598 
    599  return new Promise(function (resolve) {
    600    function listener(packet) {
    601      if (!predicate(packet)) {
    602        return;
    603      }
    604      front.off(type, listener);
    605      resolve(packet);
    606    }
    607    front.on(type, listener);
    608  });
    609 }
    610 
    611 /**
    612 * Execute the action on the next tick and return a promise that is resolved on
    613 * the next pause.
    614 *
    615 * When using promises and Task.jsm, we often want to do an action that causes a
    616 * pause and continue the task once the pause has ocurred. Unfortunately, if we
    617 * do the action that causes the pause within the task's current tick we will
    618 * pause before we have a chance to yield the promise that waits for the pause
    619 * and we enter a dead lock. The solution is to create the promise that waits
    620 * for the pause, schedule the action to run on the next tick of the event loop,
    621 * and finally yield the promise.
    622 *
    623 * @param Function action
    624 * @param ThreadFront threadFront
    625 * @returns Promise
    626 */
    627 function executeOnNextTickAndWaitForPause(action, threadFront) {
    628  const paused = waitForPause(threadFront);
    629  executeSoon(action);
    630  return paused;
    631 }
    632 
    633 function evalCallback(debuggeeGlobal, func) {
    634  Cu.evalInSandbox("(" + func + ")()", debuggeeGlobal, "1.8", "test.js", 1);
    635 }
    636 
    637 /**
    638 * Interrupt JS execution for the specified thread.
    639 *
    640 * @param ThreadFront threadFront
    641 * @returns Promise
    642 */
    643 function interrupt(threadFront) {
    644  dumpn("Interrupting.");
    645  return threadFront.interrupt();
    646 }
    647 
    648 /**
    649 * Resume JS execution for the specified thread and then wait for the next pause
    650 * event.
    651 *
    652 * @param DevToolsClient client
    653 * @param ThreadFront threadFront
    654 * @returns Promise
    655 */
    656 async function resumeAndWaitForPause(threadFront) {
    657  const paused = waitForPause(threadFront);
    658  await resume(threadFront);
    659  return paused;
    660 }
    661 
    662 /**
    663 * Resume JS execution for a single step and wait for the pause after the step
    664 * has been taken.
    665 *
    666 * @param ThreadFront threadFront
    667 * @returns Promise
    668 */
    669 function stepIn(threadFront) {
    670  dumpn("Stepping in.");
    671  const paused = waitForPause(threadFront);
    672  return threadFront.stepIn().then(() => paused);
    673 }
    674 
    675 /**
    676 * Resume JS execution for a step over and wait for the pause after the step
    677 * has been taken.
    678 *
    679 * @param ThreadFront threadFront
    680 * @returns Promise
    681 */
    682 async function stepOver(threadFront, frameActor) {
    683  dumpn("Stepping over.");
    684  await threadFront.stepOver(frameActor);
    685  return waitForPause(threadFront);
    686 }
    687 
    688 /**
    689 * Resume JS execution for a step out and wait for the pause after the step
    690 * has been taken.
    691 *
    692 * @param DevToolsClient client
    693 * @param ThreadFront threadFront
    694 * @returns Promise
    695 */
    696 async function stepOut(threadFront, frameActor) {
    697  dumpn("Stepping out.");
    698  await threadFront.stepOut(frameActor);
    699  return waitForPause(threadFront);
    700 }
    701 
    702 /**
    703 * Restart specific frame and wait for the pause after the restart
    704 * has been taken.
    705 *
    706 * @param DevToolsClient client
    707 * @param ThreadFront threadFront
    708 * @returns Promise
    709 */
    710 async function restartFrame(threadFront, frameActor) {
    711  dumpn("Restarting frame.");
    712  await threadFront.restart(frameActor);
    713  return waitForPause(threadFront);
    714 }
    715 
    716 /**
    717 * Get the list of `count` frames currently on stack, starting at the index
    718 * `first` for the specified thread.
    719 *
    720 * @param ThreadFront threadFront
    721 * @param Number first
    722 * @param Number count
    723 * @returns Promise
    724 */
    725 function getFrames(threadFront, first, count) {
    726  dumpn("Getting frames.");
    727  return threadFront.getFrames(first, count);
    728 }
    729 
    730 /**
    731 * Black box the specified source.
    732 *
    733 * @param SourceFront sourceFront
    734 * @returns Promise
    735 */
    736 async function blackBox(sourceFront, range = null) {
    737  dumpn("Black boxing source: " + sourceFront.actor);
    738  const pausedInSource = await sourceFront.blackBox(range);
    739  ok(true, "blackBox didn't throw");
    740  return pausedInSource;
    741 }
    742 
    743 /**
    744 * Stop black boxing the specified source.
    745 *
    746 * @param SourceFront sourceFront
    747 * @returns Promise
    748 */
    749 async function unBlackBox(sourceFront, range = null) {
    750  dumpn("Un-black boxing source: " + sourceFront.actor);
    751  await sourceFront.unblackBox(range);
    752  ok(true, "unblackBox didn't throw");
    753 }
    754 
    755 /**
    756 * Get a source at the specified url.
    757 *
    758 * @param ThreadFront threadFront
    759 * @param string url
    760 * @returns Promise<SourceFront>
    761 */
    762 async function getSource(threadFront, url) {
    763  const source = await getSourceForm(threadFront, url);
    764  if (source) {
    765    return threadFront.source(source);
    766  }
    767 
    768  throw new Error("source not found");
    769 }
    770 
    771 async function getSourceById(threadFront, id) {
    772  const form = await getSourceFormById(threadFront, id);
    773  return threadFront.source(form);
    774 }
    775 
    776 async function getSourceForm(threadFront, url) {
    777  const { sources } = await threadFront.getSources();
    778  return sources.find(s => s.url === url);
    779 }
    780 
    781 async function getSourceFormById(threadFront, id) {
    782  const { sources } = await threadFront.getSources();
    783  return sources.find(source => source.actor == id);
    784 }
    785 
    786 async function checkFramesLength(threadFront, expectedFrames) {
    787  const frameResponse = await threadFront.getFrames(0, null);
    788  Assert.equal(
    789    frameResponse.frames.length,
    790    expectedFrames,
    791    "Thread front has the expected number of frames"
    792  );
    793 }
    794 
    795 /**
    796 * Do a reload which clears the thread debugger
    797 *
    798 * @param TabFront tabFront
    799 * @returns Promise<response>
    800 */
    801 function reload(tabFront) {
    802  return tabFront.reload({});
    803 }
    804 
    805 /**
    806 * Returns an array of stack location strings given a thread and a sample.
    807 *
    808 * @param object thread
    809 * @param object sample
    810 * @returns object
    811 */
    812 function getInflatedStackLocations(thread, sample) {
    813  const stackTable = thread.stackTable;
    814  const frameTable = thread.frameTable;
    815  const stringTable = thread.stringTable;
    816  const SAMPLE_STACK_SLOT = thread.samples.schema.stack;
    817  const STACK_PREFIX_SLOT = stackTable.schema.prefix;
    818  const STACK_FRAME_SLOT = stackTable.schema.frame;
    819  const FRAME_LOCATION_SLOT = frameTable.schema.location;
    820 
    821  // Build the stack from the raw data and accumulate the locations in
    822  // an array.
    823  let stackIndex = sample[SAMPLE_STACK_SLOT];
    824  const locations = [];
    825  while (stackIndex !== null) {
    826    const stackEntry = stackTable.data[stackIndex];
    827    const frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
    828    locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
    829    stackIndex = stackEntry[STACK_PREFIX_SLOT];
    830  }
    831 
    832  // The profiler tree is inverted, so reverse the array.
    833  return locations.reverse();
    834 }
    835 
    836 async function setupTestFromUrl(url) {
    837  do_test_pending();
    838 
    839  const { createRootActor } = require("xpcshell-test/testactors");
    840  DevToolsServer.setRootActor(createRootActor);
    841  DevToolsServer.init(() => true);
    842 
    843  const global = createTestGlobal("test");
    844  DevToolsServer.addTestGlobal(global);
    845 
    846  const devToolsClient = new DevToolsClient(DevToolsServer.connectPipe());
    847  await connect(devToolsClient);
    848 
    849  const tabs = await listTabs(devToolsClient);
    850  const descriptorFront = findTab(tabs, "test");
    851 
    852  // These xpcshell tests use mocked actors (xpcshell-test/testactors)
    853  // which still don't support watcher actor.
    854  // Because of that we still can't enable server side targets and target swiching.
    855  descriptorFront.disableTargetSwitching();
    856 
    857  const targetFront = await descriptorFront.getTarget();
    858 
    859  const commands = await createCommandsDictionary(descriptorFront);
    860 
    861  // Pass any configuration, in order to ensure starting all the thread actor
    862  // and have it to notify about all sources
    863  await commands.threadConfigurationCommand.updateConfiguration({
    864    skipBreakpoints: false,
    865    // Disable pause overlay as we don't have a true tab and it would throw trying to display them
    866    pauseOverlay: false,
    867  });
    868 
    869  const threadFront = await targetFront.getFront("thread");
    870 
    871  const sourceUrl = getFileUrl(url);
    872  const promise = waitForNewSource(threadFront, sourceUrl);
    873  loadSubScript(sourceUrl, global);
    874  const { source } = await promise;
    875 
    876  const sourceFront = threadFront.source(source);
    877  return { global, devToolsClient, threadFront, sourceFront };
    878 }
    879 
    880 /**
    881 * Run the given test function twice, one with a regular DevToolsServer,
    882 * testing against a fake tab. And another one against a WorkerDevToolsServer,
    883 * testing the worker codepath.
    884 *
    885 * @param Function test
    886 *        Test function to run twice.
    887 *        This test function is called with a dictionary:
    888 *        - Sandbox debuggee
    889 *          The custom JS debuggee created for this test. This is a Sandbox using system
    890 *           principals by default.
    891 *        - ThreadFront threadFront
    892 *          A reference to a ThreadFront instance that is attached to the debuggee.
    893 *        - DevToolsClient client
    894 *          A reference to the DevToolsClient used to communicated with the RDP server.
    895 * @param Object options
    896 *        Optional arguments to tweak test environment
    897 *        - JSPrincipal principal
    898 *          Principal to use for the debuggee. Defaults to systemPrincipal.
    899 *        - boolean doNotRunWorker
    900 *          If true, do not run this tests in worker debugger context. Defaults to false.
    901 *        - bool wantXrays
    902 *          Whether the debuggee wants Xray vision with respect to same-origin objects
    903 *          outside the sandbox. Defaults to true.
    904 *        - bool waitForFinish
    905 *          Whether to wait for a call to threadFrontTestFinished after the test
    906 *          function finishes.
    907 */
    908 function threadFrontTest(test, options = {}) {
    909  const {
    910    principal = "http://example.com",
    911    doNotRunWorker = false,
    912    wantXrays = true,
    913    waitForFinish = false,
    914  } = options;
    915 
    916  async function runThreadFrontTestWithServer(server, test) {
    917    // Setup a server and connect a client to it.
    918    initTestDevToolsServer(server);
    919 
    920    // Create a custom debuggee and register it to the server.
    921    // We are using a custom Sandbox as debuggee. Create a new zone because
    922    // debugger and debuggee must be in different compartments.
    923    const debuggee = Cu.Sandbox(principal, { freshZone: true, wantXrays });
    924    const scriptName = "debuggee.js";
    925    debuggee.document = { title: scriptName }; // Reproduce a window object, as createTestGlobal returns a real window object
    926    server.addTestGlobal(debuggee);
    927 
    928    const client = new DevToolsClient(server.connectPipe());
    929    await client.connect();
    930 
    931    // Attach to the fake tab target and retrieve the ThreadFront instance.
    932    // Automatically resume as the thread is paused by default after attach.
    933    const { targetFront, threadFront, commands } = await attachTestThread(
    934      client,
    935      scriptName
    936    );
    937 
    938    // Cross the client/server boundary to retrieve the target actor & thread
    939    // actor instances, used by some tests.
    940    const rootActor = client.transport._serverConnection.rootActor;
    941    const targetActor =
    942      rootActor._parameters.tabList.getTargetActorForTab(scriptName);
    943    const { threadActor } = targetActor;
    944 
    945    // Run the test function
    946    const args = {
    947      threadActor,
    948      threadFront,
    949      debuggee,
    950      client,
    951      server,
    952      targetFront,
    953      commands,
    954      isWorkerServer: server === WorkerDevToolsServer,
    955    };
    956    if (waitForFinish) {
    957      // Use dispatchToMainThread so that the test function does not have to
    958      // finish executing before the test itself finishes.
    959      const promise = new Promise(
    960        resolve => (threadFrontTestFinished = resolve)
    961      );
    962      Services.tm.dispatchToMainThread(() => test(args));
    963      await promise;
    964    } else {
    965      await test(args);
    966    }
    967 
    968    // Cleanup the client after the test ran
    969    await client.close();
    970 
    971    server.removeTestGlobal(debuggee);
    972 
    973    // Also cleanup the created server
    974    server.destroy();
    975  }
    976 
    977  return async () => {
    978    dump(">>> Run thread front test against a regular DevToolsServer\n");
    979    await runThreadFrontTestWithServer(DevToolsServer, test);
    980 
    981    // Skip tests that fail in the worker context
    982    if (!doNotRunWorker) {
    983      dump(">>> Run thread front test against a worker DevToolsServer\n");
    984      await runThreadFrontTestWithServer(WorkerDevToolsServer, test);
    985    }
    986  };
    987 }
    988 
    989 // This callback is used in tandem with the waitForFinish option of
    990 // threadFrontTest to support thread front tests that use promises to
    991 // asynchronously finish the tests, instead of using async/await.
    992 // Newly written tests should avoid using this. See bug 1596114 for migrating
    993 // existing tests to async/await and removing this functionality.
    994 let threadFrontTestFinished;