tor-browser

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

head.js (60143B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /*
      8 * This file contains common code that is loaded before each test file(s).
      9 * See https://developer.mozilla.org/en-US/docs/Mozilla/QA/Writing_xpcshell-based_unit_tests
     10 * for more information.
     11 */
     12 
     13 /* defined by the harness */
     14 /* globals _HEAD_FILES, _HEAD_JS_PATH, _JSDEBUGGER_PORT, _JSCOV_DIR,
     15    _MOZINFO_JS_PATH, _TEST_FILE, _TEST_NAME, _TEST_CWD, _TESTING_MODULES_DIR:true,
     16    _PREFS_FILE, _EXPECTED */
     17 
     18 /* defined by XPCShellImpl.cpp */
     19 /* globals load, sendCommand, changeTestShellDir */
     20 
     21 /* must be defined by tests using do_await_remote_message/do_send_remote_message */
     22 /* globals Cc, Ci */
     23 
     24 /* defined by this file but is defined as read-only for tests */
     25 // eslint-disable-next-line no-redeclare
     26 /* globals runningInParent: true */
     27 
     28 /* may be defined in test files */
     29 /* globals run_test */
     30 
     31 var _quit = false;
     32 var _passed = true;
     33 var _tests_pending = 0;
     34 var _cleanupFunctions = [];
     35 var _pendingTimers = [];
     36 var _profileInitialized = false;
     37 
     38 // Assigned in do_load_child_test_harness.
     39 var _XPCSHELL_PROCESS;
     40 
     41 // Register the testing-common resource protocol early, to have access to its
     42 // modules.
     43 let _Services = Services;
     44 _register_modules_protocol_handler();
     45 
     46 let { AppConstants: _AppConstants } = ChromeUtils.importESModule(
     47  "resource://gre/modules/AppConstants.sys.mjs"
     48 );
     49 
     50 let { PromiseTestUtils: _PromiseTestUtils } = ChromeUtils.importESModule(
     51  "resource://testing-common/PromiseTestUtils.sys.mjs"
     52 );
     53 
     54 let { NetUtil: _NetUtil } = ChromeUtils.importESModule(
     55  "resource://gre/modules/NetUtil.sys.mjs"
     56 );
     57 
     58 let { XPCOMUtils: _XPCOMUtils } = ChromeUtils.importESModule(
     59  "resource://gre/modules/XPCOMUtils.sys.mjs"
     60 );
     61 
     62 // Support a common assertion library, Assert.sys.mjs.
     63 var { Assert: AssertCls } = ChromeUtils.importESModule(
     64  "resource://testing-common/Assert.sys.mjs"
     65 );
     66 
     67 // Pass a custom report function for xpcshell-test style reporting.
     68 var Assert = new AssertCls(function (err, message, stack) {
     69  if (err) {
     70    do_report_result(false, err.message, err.stack);
     71  } else {
     72    do_report_result(true, message, stack);
     73  }
     74 }, true);
     75 
     76 // Bug 1506134 for followup.  Some xpcshell tests use ContentTask.sys.mjs, which
     77 // expects browser-test.js to have set a testScope that includes record.
     78 function record(condition, name, diag, stack) {
     79  do_report_result(condition, name, stack);
     80 }
     81 
     82 var _add_params = function (params) {
     83  if (typeof _XPCSHELL_PROCESS != "undefined") {
     84    params.xpcshell_process = _XPCSHELL_PROCESS;
     85  }
     86 };
     87 
     88 var _dumpLog = function (raw_msg) {
     89  dump(JSON.stringify(raw_msg) + "\n");
     90 };
     91 
     92 var { StructuredLogger: _LoggerClass } = ChromeUtils.importESModule(
     93  "resource://testing-common/StructuredLog.sys.mjs"
     94 );
     95 var _testLogger = new _LoggerClass("xpcshell/head.js", _dumpLog, [_add_params]);
     96 
     97 // Disable automatic network detection, so tests work correctly when
     98 // not connected to a network.
     99 _Services.io.manageOfflineStatus = false;
    100 _Services.io.offline = false;
    101 
    102 // Determine if we're running on parent or child
    103 var runningInParent = true;
    104 try {
    105  // Don't use Services.appinfo here as it disables replacing appinfo with stubs
    106  // for test usage.
    107  runningInParent =
    108    // eslint-disable-next-line mozilla/use-services
    109    Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime).processType ==
    110    Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
    111 } catch (e) {}
    112 
    113 // Only if building of places is enabled.
    114 if (runningInParent && "mozIAsyncHistory" in Ci) {
    115  // Ensure places history is enabled for xpcshell-tests as some non-FF
    116  // apps disable it.
    117  _Services.prefs.setBoolPref("places.history.enabled", true);
    118 }
    119 
    120 // Configure crash reporting, if possible
    121 // We rely on the Python harness to set MOZ_CRASHREPORTER,
    122 // MOZ_CRASHREPORTER_NO_REPORT, and handle checking for minidumps.
    123 // Note that if we're in a child process, we don't want to init the
    124 // crashreporter component.
    125 try {
    126  if (runningInParent && "@mozilla.org/toolkit/crash-reporter;1" in Cc) {
    127    // Intentially access the crash reporter service directly for this.
    128    // eslint-disable-next-line mozilla/use-services
    129    let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService(
    130      Ci.nsICrashReporter
    131    );
    132    crashReporter.UpdateCrashEventsDir();
    133    crashReporter.minidumpPath = do_get_minidumpdir();
    134 
    135    // Tell the test harness that the crash reporter is set up, and any crash
    136    // after this point is supposed to be caught by the crash reporter.
    137    //
    138    // Due to the limitation on the remote xpcshell test, the process return
    139    // code does not represent the process crash. Any crash before this point
    140    // can be caught by the absence of this log.
    141    _testLogger.logData("crash_reporter_init");
    142  }
    143 } catch (e) {}
    144 
    145 if (runningInParent) {
    146  _Services.prefs.setBoolPref("dom.push.connection.enabled", false);
    147 }
    148 
    149 // Configure a console listener so messages sent to it are logged as part
    150 // of the test.
    151 try {
    152  let levelNames = {};
    153  for (let level of ["debug", "info", "warn", "error"]) {
    154    levelNames[Ci.nsIConsoleMessage[level]] = level;
    155  }
    156 
    157  let listener = {
    158    QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]),
    159    observe(msg) {
    160      if (typeof info === "function") {
    161        info(
    162          "CONSOLE_MESSAGE: (" +
    163            levelNames[msg.logLevel] +
    164            ") " +
    165            msg.toString()
    166        );
    167      }
    168    },
    169  };
    170  // Don't use _Services.console here as it causes one of the devtools tests
    171  // to fail, probably due to initializing Services.console too early.
    172  // eslint-disable-next-line mozilla/use-services
    173  Cc["@mozilla.org/consoleservice;1"]
    174    .getService(Ci.nsIConsoleService)
    175    .registerListener(listener);
    176 } catch (e) {}
    177 /**
    178 * Date.now() is not necessarily monotonically increasing (insert sob story
    179 * about times not being the right tool to use for measuring intervals of time,
    180 * robarnold can tell all), so be wary of error by erring by at least
    181 * _timerFuzz ms.
    182 */
    183 const _timerFuzz = 15;
    184 
    185 function _Timer(func, delay) {
    186  delay = Number(delay);
    187  if (delay < 0) {
    188    do_throw("do_timeout() delay must be nonnegative");
    189  }
    190 
    191  if (typeof func !== "function") {
    192    do_throw("string callbacks no longer accepted; use a function!");
    193  }
    194 
    195  this._func = func;
    196  this._start = Date.now();
    197  this._delay = delay;
    198 
    199  var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    200  timer.initWithCallback(this, delay + _timerFuzz, timer.TYPE_ONE_SHOT);
    201 
    202  // Keep timer alive until it fires
    203  _pendingTimers.push(timer);
    204 }
    205 _Timer.prototype = {
    206  QueryInterface: ChromeUtils.generateQI(["nsITimerCallback"]),
    207 
    208  notify(timer) {
    209    _pendingTimers.splice(_pendingTimers.indexOf(timer), 1);
    210 
    211    // The current nsITimer implementation can undershoot, but even if it
    212    // couldn't, paranoia is probably a virtue here given the potential for
    213    // random orange on tinderboxen.
    214    var end = Date.now();
    215    var elapsed = end - this._start;
    216    if (elapsed >= this._delay) {
    217      try {
    218        this._func.call(null);
    219      } catch (e) {
    220        do_throw("exception thrown from do_timeout callback: " + e);
    221      }
    222      return;
    223    }
    224 
    225    // Timer undershot, retry with a little overshoot to try to avoid more
    226    // undershoots.
    227    var newDelay = this._delay - elapsed;
    228    do_timeout(newDelay, this._func);
    229  },
    230 };
    231 
    232 function _isGenerator(val) {
    233  return typeof val === "object" && val && typeof val.next === "function";
    234 }
    235 
    236 function _do_main() {
    237  if (_quit) {
    238    return;
    239  }
    240 
    241  _testLogger.info("running event loop");
    242 
    243  var tm = Cc["@mozilla.org/thread-manager;1"].getService();
    244 
    245  tm.spinEventLoopUntil("Test(xpcshell/head.js:_do_main)", () => _quit);
    246 
    247  tm.spinEventLoopUntilEmpty();
    248 }
    249 
    250 function _do_quit() {
    251  _testLogger.info("exiting test");
    252  _quit = true;
    253 }
    254 
    255 // This is useless, except to the extent that it has the side-effect of
    256 // initializing the widget module, which some tests unfortunately
    257 // accidentally rely on.
    258 void Cc["@mozilla.org/widget/transferable;1"].createInstance();
    259 
    260 /**
    261 * Overrides idleService with a mock.  Idle is commonly used for maintenance
    262 * tasks, thus if a test uses a service that requires the idle service, it will
    263 * start handling them.
    264 * This behaviour would cause random failures and slowdown tests execution,
    265 * for example by running database vacuum or cleanups for each test.
    266 *
    267 * Note: Idle service is overridden by default.  If a test requires it, it will
    268 *       have to call do_get_idle() function at least once before use.
    269 */
    270 var _fakeIdleService = {
    271  get registrar() {
    272    delete this.registrar;
    273    return (this.registrar = Components.manager.QueryInterface(
    274      Ci.nsIComponentRegistrar
    275    ));
    276  },
    277  contractID: "@mozilla.org/widget/useridleservice;1",
    278  CID: Components.ID("{9163a4ae-70c2-446c-9ac1-bbe4ab93004e}"),
    279 
    280  activate: function FIS_activate() {
    281    if (!this.originalCID) {
    282      this.originalCID = this.registrar.contractIDToCID(this.contractID);
    283      // Replace with the mock.
    284      this.registrar.registerFactory(
    285        this.CID,
    286        "Fake Idle Service",
    287        this.contractID,
    288        this.factory
    289      );
    290    }
    291  },
    292 
    293  deactivate: function FIS_deactivate() {
    294    if (this.originalCID) {
    295      // Unregister the mock.
    296      this.registrar.unregisterFactory(this.CID, this.factory);
    297      // Restore original factory.
    298      this.registrar.registerFactory(
    299        this.originalCID,
    300        "Idle Service",
    301        this.contractID,
    302        null
    303      );
    304      delete this.originalCID;
    305    }
    306  },
    307 
    308  factory: {
    309    // nsIFactory
    310    createInstance(aIID) {
    311      return _fakeIdleService.QueryInterface(aIID);
    312    },
    313    QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
    314  },
    315 
    316  // nsIUserIdleService
    317  get idleTime() {
    318    return 0;
    319  },
    320  addIdleObserver() {},
    321  removeIdleObserver() {},
    322 
    323  // eslint-disable-next-line mozilla/use-chromeutils-generateqi
    324  QueryInterface(aIID) {
    325    // Useful for testing purposes, see test_get_idle.js.
    326    if (aIID.equals(Ci.nsIFactory)) {
    327      return this.factory;
    328    }
    329    if (aIID.equals(Ci.nsIUserIdleService) || aIID.equals(Ci.nsISupports)) {
    330      return this;
    331    }
    332    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
    333  },
    334 };
    335 
    336 /**
    337 * Restores the idle service factory if needed and returns the service's handle.
    338 *
    339 * @return A handle to the idle service.
    340 */
    341 function do_get_idle() {
    342  _fakeIdleService.deactivate();
    343  return Cc[_fakeIdleService.contractID].getService(Ci.nsIUserIdleService);
    344 }
    345 
    346 // Map resource://test/ to current working directory and
    347 // resource://testing-common/ to the shared test modules directory.
    348 function _register_protocol_handlers() {
    349  let protocolHandler = _Services.io
    350    .getProtocolHandler("resource")
    351    .QueryInterface(Ci.nsIResProtocolHandler);
    352 
    353  let curDirURI = _Services.io.newFileURI(do_get_cwd());
    354  protocolHandler.setSubstitution("test", curDirURI);
    355 
    356  _register_modules_protocol_handler();
    357 }
    358 
    359 function _register_modules_protocol_handler() {
    360  if (!_TESTING_MODULES_DIR) {
    361    throw new Error(
    362      "Please define a path where the testing modules can be " +
    363        "found in a variable called '_TESTING_MODULES_DIR' before " +
    364        "head.js is included."
    365    );
    366  }
    367 
    368  let protocolHandler = _Services.io
    369    .getProtocolHandler("resource")
    370    .QueryInterface(Ci.nsIResProtocolHandler);
    371 
    372  let modulesFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
    373  modulesFile.initWithPath(_TESTING_MODULES_DIR);
    374 
    375  if (!modulesFile.exists()) {
    376    throw new Error(
    377      "Specified modules directory does not exist: " + _TESTING_MODULES_DIR
    378    );
    379  }
    380 
    381  if (!modulesFile.isDirectory()) {
    382    throw new Error(
    383      "Specified modules directory is not a directory: " + _TESTING_MODULES_DIR
    384    );
    385  }
    386 
    387  let modulesURI = _Services.io.newFileURI(modulesFile);
    388 
    389  protocolHandler.setSubstitution("testing-common", modulesURI);
    390 }
    391 
    392 /* Debugging support */
    393 // Used locally and by our self-tests.
    394 function _setupDevToolsServer(breakpointFiles, callback) {
    395  // Always allow remote debugging.
    396  _Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
    397 
    398  // for debugging-the-debugging, let an env var cause log spew.
    399  if (_Services.env.get("DEVTOOLS_DEBUGGER_LOG")) {
    400    _Services.prefs.setBoolPref("devtools.debugger.log", true);
    401  }
    402  if (_Services.env.get("DEVTOOLS_DEBUGGER_LOG_VERBOSE")) {
    403    _Services.prefs.setBoolPref("devtools.debugger.log.verbose", true);
    404  }
    405 
    406  let require;
    407  try {
    408    const {
    409      useDistinctSystemPrincipalLoader,
    410      releaseDistinctSystemPrincipalLoader,
    411    } = ChromeUtils.importESModule(
    412      "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs",
    413      { global: "shared" }
    414    );
    415    const requester = {};
    416    const distinctLoader = useDistinctSystemPrincipalLoader(requester);
    417    registerCleanupFunction(() => {
    418      releaseDistinctSystemPrincipalLoader(requester);
    419    });
    420 
    421    require = distinctLoader.require;
    422  } catch (e) {
    423    throw new Error(
    424      "resource://devtools appears to be inaccessible from the " +
    425        "xpcshell environment.\n" +
    426        "This can usually be resolved by adding:\n" +
    427        "  firefox-appdir = browser\n" +
    428        "to the xpcshell.toml manifest.\n" +
    429        "It is possible for this to alter test behevior by " +
    430        "triggering additional browser code to run, so check " +
    431        "test behavior after making this change.\n" +
    432        "See also https://bugzil.la/1215378."
    433    );
    434  }
    435  let { DevToolsServer } = require("devtools/server/devtools-server");
    436  DevToolsServer.init();
    437  DevToolsServer.registerAllActors();
    438  let { createRootActor } = require("resource://testing-common/dbg-actors.js");
    439  DevToolsServer.setRootActor(createRootActor);
    440  DevToolsServer.allowChromeProcess = true;
    441 
    442  const TOPICS = [
    443    // An observer notification that tells us when the thread actor is ready
    444    // and can accept breakpoints.
    445    "devtools-thread-ready",
    446    // Or when devtools are destroyed and we should stop observing.
    447    "xpcshell-test-devtools-shutdown",
    448  ];
    449  let observe = function (subject, topic) {
    450    if (topic === "devtools-thread-ready") {
    451      const threadActor = subject.wrappedJSObject;
    452      threadActor.setBreakpointOnLoad(breakpointFiles);
    453    }
    454 
    455    for (let topicToRemove of TOPICS) {
    456      _Services.obs.removeObserver(observe, topicToRemove);
    457    }
    458    callback();
    459  };
    460 
    461  for (let topic of TOPICS) {
    462    _Services.obs.addObserver(observe, topic);
    463  }
    464 
    465  const { SocketListener } = require("devtools/shared/security/socket");
    466 
    467  return { DevToolsServer, SocketListener };
    468 }
    469 
    470 function _initDebugging(port) {
    471  let initialized = false;
    472  const { DevToolsServer, SocketListener } = _setupDevToolsServer(
    473    _TEST_FILE,
    474    () => {
    475      initialized = true;
    476    }
    477  );
    478 
    479  info("");
    480  info("*******************************************************************");
    481  info("Waiting for the debugger to connect on port " + port);
    482  info("");
    483  info("To connect the debugger, open a Firefox instance, select 'Connect'");
    484  info("from the Developer menu and specify the port as " + port);
    485  info("*******************************************************************");
    486  info("");
    487 
    488  const AuthenticatorType = DevToolsServer.Authenticators.get("PROMPT");
    489  const authenticator = new AuthenticatorType.Server();
    490  authenticator.allowConnection = () => {
    491    return DevToolsServer.AuthenticationResult.ALLOW;
    492  };
    493  const socketOptions = {
    494    authenticator,
    495    portOrPath: port,
    496  };
    497 
    498  const listener = new SocketListener(DevToolsServer, socketOptions);
    499  listener.open();
    500 
    501  // spin an event loop until the debugger connects.
    502  const tm = Cc["@mozilla.org/thread-manager;1"].getService();
    503  let lastUpdate = Date.now();
    504  tm.spinEventLoopUntil("Test(xpcshell/head.js:_initDebugging)", () => {
    505    if (initialized) {
    506      return true;
    507    }
    508    if (Date.now() - lastUpdate > 5000) {
    509      info("Still waiting for debugger to connect...");
    510      lastUpdate = Date.now();
    511    }
    512    return false;
    513  });
    514  // NOTE: if you want to debug the harness itself, you can now add a 'debugger'
    515  // statement anywhere and it will stop - but we've already added a breakpoint
    516  // for the first line of the test scripts, so we just continue...
    517  info("Debugger connected, starting test execution");
    518 }
    519 
    520 function _do_upload_profile() {
    521  let name = _TEST_NAME.replace(/.*\//, "");
    522  let filename = `profile_${name}.json`;
    523  let path = Services.env.get("MOZ_UPLOAD_DIR");
    524  let profilePath = PathUtils.join(path, filename);
    525  let done = false;
    526  (async function _save_profile() {
    527    const { profile } =
    528      await Services.profiler.getProfileDataAsGzippedArrayBuffer();
    529    await IOUtils.write(profilePath, new Uint8Array(profile));
    530    _testLogger.testStatus(
    531      _TEST_NAME,
    532      "Found unexpected failures during the test; profile uploaded in " +
    533        filename,
    534      "FAIL"
    535    );
    536  })()
    537    .catch(e => {
    538      // If the profile is large, we may encounter out of memory errors.
    539      _testLogger.error(
    540        "Found unexpected failures during the test; failed to upload profile: " +
    541          e
    542      );
    543    })
    544    .then(() => (done = true));
    545  _Services.tm.spinEventLoopUntil(
    546    "Test(xpcshell/head.js:_save_profile)",
    547    () => done
    548  );
    549 }
    550 
    551 // eslint-disable-next-line complexity
    552 function _execute_test() {
    553  if (typeof _TEST_CWD != "undefined") {
    554    try {
    555      changeTestShellDir(_TEST_CWD);
    556    } catch (e) {
    557      _testLogger.error(_exception_message(e));
    558    }
    559  }
    560  if (runningInParent && _AppConstants.platform == "android") {
    561    try {
    562      // GeckoView initialization needs the profile
    563      do_get_profile(true);
    564      // Wake up GeckoViewStartup
    565      let geckoViewStartup = Cc["@mozilla.org/geckoview/startup;1"].getService(
    566        Ci.nsIObserver
    567      );
    568      geckoViewStartup.observe(null, "profile-after-change", null);
    569      geckoViewStartup.observe(null, "app-startup", null);
    570 
    571      // Glean needs to be initialized for metric recording & tests to work.
    572      // Usually this happens through Glean Kotlin,
    573      // but for xpcshell tests we initialize it from here.
    574      _Services.fog.initializeFOG();
    575    } catch (ex) {
    576      do_throw(`Failed to initialize GeckoView: ${ex}`, ex.stack);
    577    }
    578  }
    579 
    580  // _JSDEBUGGER_PORT is dynamically defined by <runxpcshelltests.py>.
    581  if (_JSDEBUGGER_PORT) {
    582    try {
    583      _initDebugging(_JSDEBUGGER_PORT);
    584    } catch (ex) {
    585      // Fail the test run immediately if debugging is requested but fails, so
    586      // that the failure state is more obvious.
    587      do_throw(`Failed to initialize debugging: ${ex}`, ex.stack);
    588    }
    589  }
    590 
    591  _register_protocol_handlers();
    592 
    593  // Override idle service by default.
    594  // Call do_get_idle() to restore the factory and get the service.
    595  _fakeIdleService.activate();
    596 
    597  _PromiseTestUtils.init();
    598 
    599  let coverageCollector = null;
    600  if (typeof _JSCOV_DIR === "string") {
    601    let _CoverageCollector = ChromeUtils.importESModule(
    602      "resource://testing-common/CoverageUtils.sys.mjs"
    603    ).CoverageCollector;
    604    coverageCollector = new _CoverageCollector(_JSCOV_DIR);
    605  }
    606 
    607  let startTime = ChromeUtils.now();
    608 
    609  // _HEAD_FILES is dynamically defined by <runxpcshelltests.py>.
    610  _load_files(_HEAD_FILES);
    611  // _TEST_FILE is dynamically defined by <runxpcshelltests.py>.
    612  _load_files(_TEST_FILE);
    613 
    614  // Tack Assert.sys.mjs methods to the current scope.
    615  this.Assert = Assert;
    616  for (let func in Assert) {
    617    this[func] = Assert[func].bind(Assert);
    618  }
    619 
    620  const { PerTestCoverageUtils } = ChromeUtils.importESModule(
    621    "resource://testing-common/PerTestCoverageUtils.sys.mjs"
    622  );
    623 
    624  if (runningInParent) {
    625    PerTestCoverageUtils.beforeTestSync();
    626  }
    627 
    628  let timer;
    629  if (
    630    // Services.profiler is missing on some tier3 platforms where
    631    // MOZ_GECKO_PROFILER is not set.
    632    Services.profiler?.IsActive() &&
    633    !Services.env.exists("MOZ_PROFILER_SHUTDOWN") &&
    634    Services.env.exists("MOZ_UPLOAD_DIR") &&
    635    Services.env.exists("MOZ_TEST_TIMEOUT_INTERVAL")
    636  ) {
    637    timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    638    timer.initWithCallback(
    639      function upload_test_timeout_profile() {
    640        ChromeUtils.addProfilerMarker(
    641          "xpcshell-test",
    642          { category: "Test", startTime },
    643          _TEST_NAME
    644        );
    645        _do_upload_profile();
    646      },
    647      parseInt(Services.env.get("MOZ_TEST_TIMEOUT_INTERVAL")) * 1000 * 0.9, // Keep 10% of the time to gather the profile.
    648      timer.TYPE_ONE_SHOT
    649    );
    650  }
    651 
    652  try {
    653    do_test_pending("MAIN run_test");
    654    // Check if run_test() is defined. If defined, run it.
    655    // Else, call run_next_test() directly to invoke tests
    656    // added by add_test() and add_task().
    657    if (typeof run_test === "function") {
    658      run_test();
    659    } else {
    660      run_next_test();
    661    }
    662 
    663    do_test_finished("MAIN run_test");
    664    _do_main();
    665    _PromiseTestUtils.assertNoUncaughtRejections();
    666 
    667    if (coverageCollector != null) {
    668      coverageCollector.recordTestCoverage(_TEST_FILE[0]);
    669    }
    670 
    671    if (runningInParent) {
    672      PerTestCoverageUtils.afterTestSync();
    673    }
    674  } catch (e) {
    675    _passed = false;
    676    // do_check failures are already logged and set _quit to true and throw
    677    // NS_ERROR_ABORT. If both of those are true it is likely this exception
    678    // has already been logged so there is no need to log it again. It's
    679    // possible that this will mask an NS_ERROR_ABORT that happens after a
    680    // do_check failure though.
    681 
    682    if (!_quit || e.result != Cr.NS_ERROR_ABORT) {
    683      let extra = {};
    684      if (e.fileName) {
    685        extra.source_file = e.fileName;
    686        if (e.lineNumber) {
    687          extra.line_number = e.lineNumber;
    688        }
    689      } else {
    690        extra.source_file = "xpcshell/head.js";
    691      }
    692      let message = _exception_message(e);
    693      if (e.stack) {
    694        extra.stack = _format_stack(e.stack);
    695      }
    696      _testLogger.error(message, extra);
    697    }
    698  } finally {
    699    if (coverageCollector != null) {
    700      coverageCollector.finalize();
    701    }
    702  }
    703 
    704  // Execute all of our cleanup functions.
    705  let reportCleanupError = function (ex) {
    706    let stack, filename;
    707    if (ex && typeof ex == "object" && "stack" in ex) {
    708      stack = ex.stack;
    709    } else {
    710      stack = Components.stack.caller;
    711    }
    712    if (stack instanceof Ci.nsIStackFrame) {
    713      filename = stack.filename;
    714    } else if (ex.fileName) {
    715      filename = ex.fileName;
    716    }
    717    _testLogger.error(_exception_message(ex), {
    718      stack: _format_stack(stack),
    719      source_file: filename,
    720    });
    721  };
    722 
    723  let complete = !_cleanupFunctions.length;
    724  let cleanupStartTime = complete ? 0 : ChromeUtils.now();
    725  (async () => {
    726    for (let func of _cleanupFunctions.reverse()) {
    727      try {
    728        let result = await func();
    729        if (_isGenerator(result)) {
    730          Assert.ok(false, "Cleanup function returned a generator");
    731        }
    732      } catch (ex) {
    733        reportCleanupError(ex);
    734      }
    735    }
    736    _cleanupFunctions = [];
    737  })()
    738    .catch(reportCleanupError)
    739    .then(() => (complete = true));
    740  _Services.tm.spinEventLoopUntil(
    741    "Test(xpcshell/head.js:_execute_test)",
    742    () => complete
    743  );
    744  if (cleanupStartTime) {
    745    ChromeUtils.addProfilerMarker(
    746      "xpcshell-test",
    747      { category: "Test", startTime: cleanupStartTime },
    748      "Cleanup functions"
    749    );
    750  }
    751 
    752  ChromeUtils.addProfilerMarker(
    753    "xpcshell-test",
    754    { category: "Test", startTime },
    755    _TEST_NAME
    756  );
    757  _Services.obs.notifyObservers(null, "test-complete");
    758 
    759  // Restore idle service to avoid leaks.
    760  _fakeIdleService.deactivate();
    761 
    762  if (
    763    globalThis.hasOwnProperty("storage") &&
    764    StorageManager.isInstance(globalThis.storage)
    765  ) {
    766    globalThis.storage.shutdown();
    767  }
    768 
    769  if (_profileInitialized) {
    770    // Since we have a profile, we will notify profile shutdown topics at
    771    // the end of the current test, to ensure correct cleanup on shutdown.
    772    _Services.startup.advanceShutdownPhase(
    773      _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNNETTEARDOWN
    774    );
    775    _Services.startup.advanceShutdownPhase(
    776      _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNTEARDOWN
    777    );
    778    _Services.startup.advanceShutdownPhase(
    779      _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWN
    780    );
    781    _Services.startup.advanceShutdownPhase(
    782      _Services.startup.SHUTDOWN_PHASE_APPSHUTDOWNQM
    783    );
    784 
    785    _profileInitialized = false;
    786  }
    787 
    788  try {
    789    _PromiseTestUtils.ensureDOMPromiseRejectionsProcessed();
    790    _PromiseTestUtils.assertNoUncaughtRejections();
    791    _PromiseTestUtils.assertNoMoreExpectedRejections();
    792  } finally {
    793    // It's important to terminate the module to avoid crashes on shutdown.
    794    _PromiseTestUtils.uninit();
    795  }
    796 
    797  if (timer) {
    798    timer.cancel();
    799  }
    800 
    801  // If MOZ_PROFILER_SHUTDOWN is set, the profiler got started from --profiler
    802  // and a profile will be shown even if there's no test failure.
    803  if (
    804    !_passed &&
    805    runningInParent &&
    806    Services.env.exists("MOZ_UPLOAD_DIR") &&
    807    !Services.env.exists("MOZ_PROFILER_SHUTDOWN") &&
    808    Services.profiler.IsActive()
    809  ) {
    810    if (_EXPECTED != "pass") {
    811      _testLogger.error(
    812        "Not uploading the profile as the test is expected to fail."
    813      );
    814    } else {
    815      _do_upload_profile();
    816    }
    817  }
    818 
    819  // Skip the normal shutdown path for optimized builds that don't do leak checking.
    820  if (
    821    runningInParent &&
    822    !_AppConstants.RELEASE_OR_BETA &&
    823    !_AppConstants.DEBUG &&
    824    !_AppConstants.MOZ_CODE_COVERAGE &&
    825    !_AppConstants.ASAN &&
    826    !_AppConstants.TSAN
    827  ) {
    828    Cu.exitIfInAutomation();
    829  }
    830 }
    831 
    832 /**
    833 * Loads files.
    834 *
    835 * @param aFiles Array of files to load.
    836 */
    837 function _load_files(aFiles) {
    838  function load_file(element) {
    839    try {
    840      let startTime = ChromeUtils.now();
    841      load(element);
    842      ChromeUtils.addProfilerMarker(
    843        "load_file",
    844        { category: "Test", startTime },
    845        element.replace(/.*\/_?tests\/xpcshell\//, "")
    846      );
    847    } catch (e) {
    848      let extra = {
    849        source_file: element,
    850      };
    851      if (e.stack) {
    852        extra.stack = _format_stack(e.stack);
    853      }
    854      _testLogger.error(_exception_message(e), extra);
    855    }
    856  }
    857 
    858  aFiles.forEach(load_file);
    859 }
    860 
    861 function _wrap_with_quotes_if_necessary(val) {
    862  return typeof val == "string" ? '"' + val + '"' : val;
    863 }
    864 
    865 /* ************* Functions to be used from the tests ************* */
    866 
    867 /**
    868 * Prints a message to the output log.
    869 */
    870 function info(msg, data) {
    871  ChromeUtils.addProfilerMarker("INFO", { category: "Test" }, msg);
    872  msg = _wrap_with_quotes_if_necessary(msg);
    873  data = data ? data : null;
    874  _testLogger.info(msg, data);
    875 }
    876 
    877 /**
    878 * Calls the given function at least the specified number of milliseconds later.
    879 * The callback will not undershoot the given time, but it might overshoot --
    880 * don't expect precision!
    881 *
    882 * @param delay : uint
    883 *   the number of milliseconds to delay
    884 * @param callback : function() : void
    885 *   the function to call
    886 */
    887 function do_timeout(delay, func) {
    888  new _Timer(func, Number(delay));
    889 }
    890 
    891 function executeSoon(callback, aName) {
    892  let funcName = aName ? aName : callback.name;
    893  do_test_pending(funcName);
    894 
    895  _Services.tm.dispatchToMainThread({
    896    run() {
    897      try {
    898        callback();
    899      } catch (e) {
    900        // do_check failures are already logged and set _quit to true and throw
    901        // NS_ERROR_ABORT. If both of those are true it is likely this exception
    902        // has already been logged so there is no need to log it again. It's
    903        // possible that this will mask an NS_ERROR_ABORT that happens after a
    904        // do_check failure though.
    905        if (!_quit || e.result != Cr.NS_ERROR_ABORT) {
    906          let stack = e.stack ? _format_stack(e.stack) : null;
    907          _testLogger.testStatus(
    908            _TEST_NAME,
    909            funcName,
    910            "FAIL",
    911            "PASS",
    912            _exception_message(e),
    913            stack
    914          );
    915          _passed = false;
    916          _do_quit();
    917        }
    918      } finally {
    919        do_test_finished(funcName);
    920      }
    921    },
    922  });
    923 }
    924 
    925 /**
    926 * Shows an error message and the current stack and aborts the test.
    927 *
    928 * @param error  A message string or an Error object.
    929 * @param stack  null or nsIStackFrame object or a string containing
    930 *               \n separated stack lines (as in Error().stack).
    931 */
    932 function do_throw(error, stack) {
    933  let filename = "";
    934  // If we didn't get passed a stack, maybe the error has one
    935  // otherwise get it from our call context
    936  stack = stack || error.stack || Components.stack.caller;
    937 
    938  if (stack instanceof Ci.nsIStackFrame) {
    939    filename = stack.filename;
    940  } else if (error.fileName) {
    941    filename = error.fileName;
    942  }
    943 
    944  _testLogger.error(_exception_message(error), {
    945    source_file: filename,
    946    stack: _format_stack(stack),
    947  });
    948  ChromeUtils.addProfilerMarker(
    949    "ERROR",
    950    { category: "Test", captureStack: true },
    951    _exception_message(error)
    952  );
    953  _abort_failed_test();
    954 }
    955 
    956 function _abort_failed_test() {
    957  // Called to abort the test run after all failures are logged.
    958  _passed = false;
    959  _do_quit();
    960  throw Components.Exception("", Cr.NS_ERROR_ABORT);
    961 }
    962 
    963 function _format_stack(stack) {
    964  let normalized;
    965  if (stack instanceof Ci.nsIStackFrame) {
    966    let frames = [];
    967    for (let frame = stack; frame; frame = frame.caller) {
    968      frames.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
    969    }
    970    normalized = frames.join("\n");
    971  } else {
    972    normalized = "" + stack;
    973  }
    974  return normalized;
    975 }
    976 
    977 // Make a nice display string from an object that behaves
    978 // like Error
    979 function _exception_message(ex) {
    980  if (ex === undefined) {
    981    return "`undefined` exception, maybe from an empty reject()?";
    982  }
    983  let message = "";
    984  if (ex.name) {
    985    message = ex.name + ": ";
    986  }
    987  if (ex.message) {
    988    message += ex.message;
    989  }
    990  if (ex.fileName) {
    991    message += " at " + ex.fileName;
    992    if (ex.lineNumber) {
    993      message += ":" + ex.lineNumber;
    994    }
    995  }
    996  if (message !== "") {
    997    return message;
    998  }
    999  // Force ex to be stringified
   1000  return "" + ex;
   1001 }
   1002 
   1003 function do_report_unexpected_exception(ex, text) {
   1004  let filename = Components.stack.caller.filename;
   1005  text = text ? text + " - " : "";
   1006 
   1007  _passed = false;
   1008  _testLogger.error(text + "Unexpected exception " + _exception_message(ex), {
   1009    source_file: filename,
   1010    stack: _format_stack(ex?.stack),
   1011  });
   1012  _do_quit();
   1013  throw Components.Exception("", Cr.NS_ERROR_ABORT);
   1014 }
   1015 
   1016 function do_note_exception(ex, text) {
   1017  let filename = Components.stack.caller.filename;
   1018  _testLogger.info(text + "Swallowed exception " + _exception_message(ex), {
   1019    source_file: filename,
   1020    stack: _format_stack(ex?.stack),
   1021  });
   1022 }
   1023 
   1024 function do_report_result(passed, text, stack, todo) {
   1025  // Match names like head.js, head_foo.js, and foo_head.js, but not
   1026  // test_headache.js
   1027  while (/(\/head(_.+)?|head)\.js$/.test(stack.filename) && stack.caller) {
   1028    stack = stack.caller;
   1029  }
   1030 
   1031  let name = _gRunningTest ? _gRunningTest.name : stack.name;
   1032  let message;
   1033  if (name) {
   1034    message = "[" + name + " : " + stack.lineNumber + "] " + text;
   1035  } else {
   1036    message = text;
   1037  }
   1038 
   1039  if (passed) {
   1040    if (todo) {
   1041      _testLogger.testStatus(
   1042        _TEST_NAME,
   1043        name,
   1044        "PASS",
   1045        "FAIL",
   1046        message,
   1047        _format_stack(stack)
   1048      );
   1049      ChromeUtils.addProfilerMarker(
   1050        "UNEXPECTED-PASS",
   1051        { category: "Test" },
   1052        message
   1053      );
   1054      _abort_failed_test();
   1055    } else {
   1056      _testLogger.testStatus(_TEST_NAME, name, "PASS", "PASS", message);
   1057      ChromeUtils.addProfilerMarker("PASS", { category: "Test" }, message);
   1058    }
   1059  } else if (todo) {
   1060    _testLogger.testStatus(_TEST_NAME, name, "FAIL", "FAIL", message);
   1061    ChromeUtils.addProfilerMarker("TODO", { category: "Test" }, message);
   1062  } else {
   1063    _testLogger.testStatus(
   1064      _TEST_NAME,
   1065      name,
   1066      "FAIL",
   1067      "PASS",
   1068      message,
   1069      _format_stack(stack)
   1070    );
   1071    ChromeUtils.addProfilerMarker("FAIL", { category: "Test" }, message);
   1072    _abort_failed_test();
   1073  }
   1074 }
   1075 
   1076 function _do_check_eq(left, right, stack, todo) {
   1077  if (!stack) {
   1078    stack = Components.stack.caller;
   1079  }
   1080 
   1081  var text =
   1082    _wrap_with_quotes_if_necessary(left) +
   1083    " == " +
   1084    _wrap_with_quotes_if_necessary(right);
   1085  do_report_result(left == right, text, stack, todo);
   1086 }
   1087 
   1088 function todo_check_eq(left, right, stack) {
   1089  if (!stack) {
   1090    stack = Components.stack.caller;
   1091  }
   1092 
   1093  _do_check_eq(left, right, stack, true);
   1094 }
   1095 
   1096 function todo_check_true(condition, stack) {
   1097  if (!stack) {
   1098    stack = Components.stack.caller;
   1099  }
   1100 
   1101  todo_check_eq(condition, true, stack);
   1102 }
   1103 
   1104 function todo_check_false(condition, stack) {
   1105  if (!stack) {
   1106    stack = Components.stack.caller;
   1107  }
   1108 
   1109  todo_check_eq(condition, false, stack);
   1110 }
   1111 
   1112 function todo_check_null(condition, stack = Components.stack.caller) {
   1113  todo_check_eq(condition, null, stack);
   1114 }
   1115 
   1116 // Check that |func| throws an nsIException that has
   1117 // |Components.results[resultName]| as the value of its 'result' property.
   1118 function do_check_throws_nsIException(
   1119  func,
   1120  resultName,
   1121  stack = Components.stack.caller,
   1122  todo = false
   1123 ) {
   1124  let expected = Cr[resultName];
   1125  if (typeof expected !== "number") {
   1126    do_throw(
   1127      "do_check_throws_nsIException requires a Components.results" +
   1128        " property name, not " +
   1129        uneval(resultName),
   1130      stack
   1131    );
   1132  }
   1133 
   1134  let msg =
   1135    "do_check_throws_nsIException: func should throw" +
   1136    " an nsIException whose 'result' is Components.results." +
   1137    resultName;
   1138 
   1139  try {
   1140    func();
   1141  } catch (ex) {
   1142    if (!(ex instanceof Ci.nsIException) || ex.result !== expected) {
   1143      do_report_result(
   1144        false,
   1145        msg + ", threw " + legible_exception(ex) + " instead",
   1146        stack,
   1147        todo
   1148      );
   1149    }
   1150 
   1151    do_report_result(true, msg, stack, todo);
   1152    return;
   1153  }
   1154 
   1155  // Call this here, not in the 'try' clause, so do_report_result's own
   1156  // throw doesn't get caught by our 'catch' clause.
   1157  do_report_result(false, msg + ", but returned normally", stack, todo);
   1158 }
   1159 
   1160 // Produce a human-readable form of |exception|. This looks up
   1161 // Components.results values, tries toString methods, and so on.
   1162 function legible_exception(exception) {
   1163  switch (typeof exception) {
   1164    case "object":
   1165      if (exception instanceof Ci.nsIException) {
   1166        return "nsIException instance: " + uneval(exception.toString());
   1167      }
   1168      return exception.toString();
   1169 
   1170    case "number":
   1171      for (let name in Cr) {
   1172        if (exception === Cr[name]) {
   1173          return "Components.results." + name;
   1174        }
   1175      }
   1176 
   1177    // Fall through.
   1178    default:
   1179      return uneval(exception);
   1180  }
   1181 }
   1182 
   1183 function do_check_instanceof(
   1184  value,
   1185  constructor,
   1186  stack = Components.stack.caller,
   1187  todo = false
   1188 ) {
   1189  do_report_result(
   1190    value instanceof constructor,
   1191    "value should be an instance of " + constructor.name,
   1192    stack,
   1193    todo
   1194  );
   1195 }
   1196 
   1197 function todo_check_instanceof(
   1198  value,
   1199  constructor,
   1200  stack = Components.stack.caller
   1201 ) {
   1202  do_check_instanceof(value, constructor, stack, true);
   1203 }
   1204 
   1205 function do_test_pending(aName) {
   1206  ++_tests_pending;
   1207 
   1208  _testLogger.info(
   1209    "(xpcshell/head.js) | test" +
   1210      (aName ? " " + aName : "") +
   1211      " pending (" +
   1212      _tests_pending +
   1213      ")"
   1214  );
   1215 }
   1216 
   1217 function do_test_finished(aName) {
   1218  _testLogger.info(
   1219    "(xpcshell/head.js) | test" +
   1220      (aName ? " " + aName : "") +
   1221      " finished (" +
   1222      _tests_pending +
   1223      ")"
   1224  );
   1225  if (--_tests_pending == 0) {
   1226    _do_quit();
   1227  }
   1228 }
   1229 
   1230 function do_get_file(path, allowNonexistent) {
   1231  try {
   1232    let lf = _Services.dirsvc.get("CurWorkD", Ci.nsIFile);
   1233 
   1234    let bits = path.split("/");
   1235    for (let i = 0; i < bits.length; i++) {
   1236      if (bits[i]) {
   1237        if (bits[i] == "..") {
   1238          lf = lf.parent;
   1239        } else {
   1240          lf.append(bits[i]);
   1241        }
   1242      }
   1243    }
   1244 
   1245    if (!allowNonexistent && !lf.exists()) {
   1246      // Not using do_throw(): caller will continue.
   1247      _passed = false;
   1248      var stack = Components.stack.caller;
   1249      _testLogger.error(
   1250        "[" +
   1251          stack.name +
   1252          " : " +
   1253          stack.lineNumber +
   1254          "] " +
   1255          lf.path +
   1256          " does not exist"
   1257      );
   1258    }
   1259 
   1260    return lf;
   1261  } catch (ex) {
   1262    do_throw(ex.toString(), Components.stack.caller);
   1263  }
   1264 
   1265  return null;
   1266 }
   1267 
   1268 // do_get_cwd() isn't exactly self-explanatory, so provide a helper
   1269 function do_get_cwd() {
   1270  return do_get_file("");
   1271 }
   1272 
   1273 function do_load_manifest(path) {
   1274  var lf = do_get_file(path);
   1275  const nsIComponentRegistrar = Ci.nsIComponentRegistrar;
   1276  Assert.ok(Components.manager instanceof nsIComponentRegistrar);
   1277  // Previous do_check_true() is not a test check.
   1278  Components.manager.autoRegister(lf);
   1279 }
   1280 
   1281 /**
   1282 * Parse a DOM document.
   1283 *
   1284 * @param aPath File path to the document.
   1285 * @param aType Content type to use in DOMParser.
   1286 *
   1287 * @return Document from the file.
   1288 */
   1289 function do_parse_document(aPath, aType) {
   1290  switch (aType) {
   1291    case "application/xhtml+xml":
   1292    case "application/xml":
   1293    case "text/xml":
   1294      break;
   1295 
   1296    default:
   1297      do_throw(
   1298        "type: expected application/xhtml+xml, application/xml or text/xml," +
   1299          " got '" +
   1300          aType +
   1301          "'",
   1302        Components.stack.caller
   1303      );
   1304  }
   1305 
   1306  let file = do_get_file(aPath);
   1307  let url = _Services.io.newFileURI(file).spec;
   1308  file = null;
   1309  return new Promise((resolve, reject) => {
   1310    let xhr = new XMLHttpRequest();
   1311    xhr.open("GET", url);
   1312    xhr.responseType = "document";
   1313    xhr.onerror = reject;
   1314    xhr.onload = () => {
   1315      resolve(xhr.response);
   1316    };
   1317    xhr.send();
   1318  });
   1319 }
   1320 
   1321 /**
   1322 * Registers a function that will run when the test harness is done running all
   1323 * tests.
   1324 *
   1325 * @param aFunction
   1326 *        The function to be called when the test harness has finished running.
   1327 */
   1328 function registerCleanupFunction(aFunction) {
   1329  _cleanupFunctions.push(aFunction);
   1330 }
   1331 
   1332 /**
   1333 * Returns the directory for a temp dir, which is created by the
   1334 * test harness. Every test gets its own temp dir.
   1335 *
   1336 * @return nsIFile of the temporary directory
   1337 */
   1338 function do_get_tempdir() {
   1339  // the python harness sets this in the environment for us
   1340  let path = _Services.env.get("XPCSHELL_TEST_TEMP_DIR");
   1341  let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   1342  file.initWithPath(path);
   1343  return file;
   1344 }
   1345 
   1346 /**
   1347 * Returns the directory for crashreporter minidumps.
   1348 *
   1349 * @return nsIFile of the minidump directory
   1350 */
   1351 function do_get_minidumpdir() {
   1352  // the python harness may set this in the environment for us
   1353  let path = _Services.env.get("XPCSHELL_MINIDUMP_DIR");
   1354  if (path) {
   1355    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   1356    file.initWithPath(path);
   1357    return file;
   1358  }
   1359  return do_get_tempdir();
   1360 }
   1361 
   1362 /**
   1363 * Registers a directory with the profile service,
   1364 * and return the directory as an nsIFile.
   1365 *
   1366 * @param notifyProfileAfterChange Whether to notify for "profile-after-change".
   1367 * @return nsIFile of the profile directory.
   1368 */
   1369 function do_get_profile(notifyProfileAfterChange = false) {
   1370  if (!runningInParent) {
   1371    _testLogger.info("Ignoring profile creation from child process.");
   1372    return null;
   1373  }
   1374 
   1375  // the python harness sets this in the environment for us
   1376  let profd = Services.env.get("XPCSHELL_TEST_PROFILE_DIR");
   1377  let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   1378  file.initWithPath(profd);
   1379 
   1380  let provider = {
   1381    getFile(prop, persistent) {
   1382      persistent.value = true;
   1383      if (
   1384        prop == "ProfD" ||
   1385        prop == "ProfLD" ||
   1386        prop == "ProfDS" ||
   1387        prop == "ProfLDS" ||
   1388        prop == "TmpD"
   1389      ) {
   1390        return file.clone();
   1391      }
   1392      return null;
   1393    },
   1394    QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
   1395  };
   1396  _Services.dirsvc
   1397    .QueryInterface(Ci.nsIDirectoryService)
   1398    .registerProvider(provider);
   1399 
   1400  try {
   1401    _Services.dirsvc.undefine("TmpD");
   1402  } catch (e) {
   1403    // This throws if the key is not already registered, but that
   1404    // doesn't matter.
   1405    if (e.result != Cr.NS_ERROR_FAILURE) {
   1406      throw e;
   1407    }
   1408  }
   1409 
   1410  // We need to update the crash events directory when the profile changes.
   1411  if (runningInParent && "@mozilla.org/toolkit/crash-reporter;1" in Cc) {
   1412    // Intentially access the crash reporter service directly for this.
   1413    // eslint-disable-next-line mozilla/use-services
   1414    let crashReporter = Cc["@mozilla.org/toolkit/crash-reporter;1"].getService(
   1415      Ci.nsICrashReporter
   1416    );
   1417    crashReporter.UpdateCrashEventsDir();
   1418  }
   1419 
   1420  if (!_profileInitialized) {
   1421    _Services.obs.notifyObservers(
   1422      null,
   1423      "profile-do-change",
   1424      "xpcshell-do-get-profile"
   1425    );
   1426    _profileInitialized = true;
   1427    if (notifyProfileAfterChange) {
   1428      _Services.obs.notifyObservers(
   1429        null,
   1430        "profile-after-change",
   1431        "xpcshell-do-get-profile"
   1432      );
   1433 
   1434      // The "profile-after-change" dispatch above does not reach services that
   1435      // listen for the message via nsICategoryManager. These services register
   1436      // their observer via `components.conf`. The message for these services
   1437      // gets dispatched separately. So we have to do the same in testing.
   1438 
   1439      // TODO: Bug 1995259: Not all tests / services expect this behavior yet,
   1440      // use an allow-list to determine which services to initialize on
   1441      // synthetic PAC.
   1442      let filterServices = new Set([
   1443        "@mozilla.org/profile-after-change-gate;1",
   1444      ]);
   1445 
   1446      for (let entry of Services.catMan.enumerateCategory(
   1447        "profile-after-change"
   1448      )) {
   1449        if (!filterServices.has(entry.value)) {
   1450          continue;
   1451        }
   1452        try {
   1453          Cc[entry.value]
   1454            ?.getService(Ci.nsIObserver)
   1455            ?.observe(null, "profile-after-change", "");
   1456        } catch (e) {}
   1457      }
   1458    }
   1459  }
   1460 
   1461  // The methods of 'provider' will retain this scope so null out everything
   1462  // to avoid spurious leak reports.
   1463  profd = null;
   1464  provider = null;
   1465  return file.clone();
   1466 }
   1467 
   1468 /**
   1469 * This function loads head.js (this file) in the child process, so that all
   1470 * functions defined in this file (do_throw, etc) are available to subsequent
   1471 * sendCommand calls.  It also sets various constants used by these functions.
   1472 *
   1473 * (Note that you may use sendCommand without calling this function first;  you
   1474 * simply won't have any of the functions in this file available.)
   1475 */
   1476 function do_load_child_test_harness() {
   1477  // Make sure this isn't called from child process
   1478  if (!runningInParent) {
   1479    do_throw("run_test_in_child cannot be called from child!");
   1480  }
   1481 
   1482  // Allow to be called multiple times, but only run once
   1483  if (typeof do_load_child_test_harness.alreadyRun != "undefined") {
   1484    return;
   1485  }
   1486  do_load_child_test_harness.alreadyRun = 1;
   1487 
   1488  _XPCSHELL_PROCESS = "parent";
   1489 
   1490  let command =
   1491    "const _HEAD_JS_PATH=" +
   1492    uneval(_HEAD_JS_PATH) +
   1493    "; " +
   1494    "const _HEAD_FILES=" +
   1495    uneval(_HEAD_FILES) +
   1496    "; " +
   1497    "const _MOZINFO_JS_PATH=" +
   1498    uneval(_MOZINFO_JS_PATH) +
   1499    "; " +
   1500    "const _TEST_NAME=" +
   1501    uneval(_TEST_NAME) +
   1502    "; " +
   1503    // We'll need more magic to get the debugger working in the child
   1504    "const _JSDEBUGGER_PORT=0; " +
   1505    "_XPCSHELL_PROCESS='child';";
   1506 
   1507  if (typeof _JSCOV_DIR === "string") {
   1508    command += " const _JSCOV_DIR=" + uneval(_JSCOV_DIR) + ";";
   1509  }
   1510 
   1511  if (typeof _TEST_CWD != "undefined") {
   1512    command += " const _TEST_CWD=" + uneval(_TEST_CWD) + ";";
   1513  }
   1514 
   1515  if (_TESTING_MODULES_DIR) {
   1516    command +=
   1517      " const _TESTING_MODULES_DIR=" + uneval(_TESTING_MODULES_DIR) + ";";
   1518  }
   1519 
   1520  command += " load(_HEAD_JS_PATH);";
   1521  sendCommand(command);
   1522 }
   1523 
   1524 /**
   1525 * Runs an entire xpcshell unit test in a child process (rather than in chrome,
   1526 * which is the default).
   1527 *
   1528 * This function returns immediately, before the test has completed.
   1529 *
   1530 * @param testFile
   1531 *        The name of the script to run.  Path format same as load().
   1532 * @param optionalCallback.
   1533 *        Optional function to be called (in parent) when test on child is
   1534 *        complete.  If provided, the function must call do_test_finished();
   1535 * @return Promise Resolved when the test in the child is complete.
   1536 */
   1537 function run_test_in_child(testFile, optionalCallback) {
   1538  return new Promise(resolve => {
   1539    var callback = () => {
   1540      resolve();
   1541      if (typeof optionalCallback == "undefined") {
   1542        do_test_finished();
   1543      } else {
   1544        optionalCallback();
   1545      }
   1546    };
   1547 
   1548    do_load_child_test_harness();
   1549 
   1550    var testPath = do_get_file(testFile).path.replace(/\\/g, "/");
   1551    do_test_pending("run in child");
   1552    sendCommand(
   1553      "_testLogger.info('CHILD-TEST-STARTED'); " +
   1554        "const _TEST_FILE=['" +
   1555        testPath +
   1556        "']; " +
   1557        "_execute_test(); " +
   1558        "_testLogger.info('CHILD-TEST-COMPLETED');",
   1559      callback
   1560    );
   1561  });
   1562 }
   1563 
   1564 /**
   1565 * Execute a given function as soon as a particular cross-process message is received.
   1566 * Must be paired with do_send_remote_message or equivalent ProcessMessageManager calls.
   1567 *
   1568 * @param optionalCallback
   1569 *        Optional callback that is invoked when the message is received.  If provided,
   1570 *        the function must call do_test_finished().
   1571 * @return Promise Promise that is resolved when the message is received.
   1572 */
   1573 function do_await_remote_message(name, optionalCallback) {
   1574  return new Promise(resolve => {
   1575    var listener = {
   1576      receiveMessage(message) {
   1577        if (message.name == name) {
   1578          mm.removeMessageListener(name, listener);
   1579          resolve(message.data);
   1580          if (optionalCallback) {
   1581            optionalCallback(message.data);
   1582          } else {
   1583            do_test_finished();
   1584          }
   1585        }
   1586      },
   1587    };
   1588 
   1589    var mm;
   1590    if (runningInParent) {
   1591      mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService();
   1592    } else {
   1593      mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService();
   1594    }
   1595    do_test_pending();
   1596    mm.addMessageListener(name, listener);
   1597  });
   1598 }
   1599 
   1600 /**
   1601 * Asynchronously send a message to all remote processes. Pairs with do_await_remote_message
   1602 * or equivalent ProcessMessageManager listeners.
   1603 */
   1604 function do_send_remote_message(name, data) {
   1605  var mm;
   1606  var sender;
   1607  if (runningInParent) {
   1608    mm = Cc["@mozilla.org/parentprocessmessagemanager;1"].getService();
   1609    sender = "broadcastAsyncMessage";
   1610  } else {
   1611    mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService();
   1612    sender = "sendAsyncMessage";
   1613  }
   1614  mm[sender](name, data);
   1615 }
   1616 
   1617 /**
   1618 * Schedules and awaits a precise GC, and forces CC, `maxCount` number of times.
   1619 *
   1620 * @param maxCount
   1621 *        How many times GC and CC should be scheduled.
   1622 */
   1623 async function schedulePreciseGCAndForceCC(maxCount) {
   1624  for (let count = 0; count < maxCount; count++) {
   1625    await new Promise(resolve => Cu.schedulePreciseGC(resolve));
   1626    Cu.forceCC();
   1627  }
   1628 }
   1629 
   1630 /**
   1631 * Add a test function to the list of tests that are to be run asynchronously.
   1632 *
   1633 * @param funcOrProperties
   1634 *        A function to be run or an object represents test properties.
   1635 *        Supported properties:
   1636 *          skip_if : An arrow function which has an expression to be
   1637 *                    evaluated whether the test is skipped or not.
   1638 *          pref_set: An array of preferences to set for the test, reset at end of test.
   1639 * @param func
   1640 *        A function to be run only if the funcOrProperies is not a function.
   1641 * @param isTask
   1642 *        Optional flag that indicates whether `func` is a task. Defaults to `false`.
   1643 * @param isSetup
   1644 *        Optional flag that indicates whether `func` is a setup task. Defaults to `false`.
   1645 *        Implies isTask.
   1646 *
   1647 * Each test function must call run_next_test() when it's done. Test files
   1648 * should call run_next_test() in their run_test function to execute all
   1649 * async tests.
   1650 *
   1651 * @return the test function that was passed in.
   1652 */
   1653 var _gSupportedProperties = ["skip_if", "pref_set"];
   1654 var _gTests = [];
   1655 var _gRunOnlyThisTest = null;
   1656 function add_test(
   1657  properties,
   1658  func = properties,
   1659  isTask = false,
   1660  isSetup = false
   1661 ) {
   1662  if (isSetup) {
   1663    isTask = true;
   1664  }
   1665  if (typeof properties == "function") {
   1666    properties = { isTask, isSetup };
   1667    _gTests.push([properties, func]);
   1668  } else if (typeof properties == "object") {
   1669    // Ensure only documented properties are in the object.
   1670    for (let prop of Object.keys(properties)) {
   1671      if (!_gSupportedProperties.includes(prop)) {
   1672        do_throw(`Task property is not supported: ${prop}`);
   1673      }
   1674    }
   1675    properties.isTask = isTask;
   1676    properties.isSetup = isSetup;
   1677    _gTests.push([properties, func]);
   1678  } else {
   1679    do_throw("add_test() should take a function or an object and a function");
   1680  }
   1681  func.skip = () => (properties.skip_if = () => true);
   1682  func.only = () => (_gRunOnlyThisTest = func);
   1683  return func;
   1684 }
   1685 
   1686 /**
   1687 * Add a test function which is an asynchronous function.
   1688 *
   1689 * @param funcOrProperties
   1690 *        An async function to be run or an object represents test properties.
   1691 *        Supported properties:
   1692 *          skip_if : An arrow function which has an expression to be
   1693 *                    evaluated whether the test is skipped or not.
   1694 *          pref_set: An array of preferences to set for the test, reset at end of test.
   1695 * @param func
   1696 *        An async function to be run only if the funcOrProperies is not a function.
   1697 *
   1698 * If an exception is thrown, a do_check_* comparison fails, or if a rejected
   1699 * promise is yielded, the test function aborts immediately and the test is
   1700 * reported as a failure.
   1701 *
   1702 * Unlike add_test(), there is no need to call run_next_test(). The next test
   1703 * will run automatically as soon the task function is exhausted. To trigger
   1704 * premature (but successful) termination of the function or simply return.
   1705 *
   1706 * Example usage:
   1707 *
   1708 * add_task(async function test() {
   1709 *   let result = await Promise.resolve(true);
   1710 *
   1711 *   do_check_true(result);
   1712 *
   1713 *   let secondary = await someFunctionThatReturnsAPromise(result);
   1714 *   do_check_eq(secondary, "expected value");
   1715 * });
   1716 *
   1717 * add_task(async function test_early_return() {
   1718 *   let result = await somethingThatReturnsAPromise();
   1719 *
   1720 *   if (!result) {
   1721 *     // Test is ended immediately, with success.
   1722 *     return;
   1723 *   }
   1724 *
   1725 *   do_check_eq(result, "foo");
   1726 * });
   1727 *
   1728 * add_task({
   1729 *   skip_if: () => !("@mozilla.org/telephony/volume-service;1" in Components.classes),
   1730 *   pref_set: [["some.pref", "value"], ["another.pref", true]],
   1731 * }, async function test_volume_service() {
   1732 *   let volumeService = Cc["@mozilla.org/telephony/volume-service;1"]
   1733 *     .getService(Ci.nsIVolumeService);
   1734 *   ...
   1735 * });
   1736 */
   1737 function add_task(properties, func = properties) {
   1738  return add_test(properties, func, true);
   1739 }
   1740 
   1741 /**
   1742 * add_setup is like add_task, but creates setup tasks.
   1743 */
   1744 function add_setup(properties, func = properties) {
   1745  return add_test(properties, func, true, true);
   1746 }
   1747 
   1748 const _setTaskPrefs = prefs => {
   1749  for (let [pref, value] of prefs) {
   1750    if (value === undefined) {
   1751      // Clear any pref that didn't have a user value.
   1752      info(`Clearing pref "${pref}"`);
   1753      _Services.prefs.clearUserPref(pref);
   1754      continue;
   1755    }
   1756 
   1757    info(`Setting pref "${pref}": ${value}`);
   1758    switch (typeof value) {
   1759      case "boolean":
   1760        _Services.prefs.setBoolPref(pref, value);
   1761        break;
   1762      case "number":
   1763        _Services.prefs.setIntPref(pref, value);
   1764        break;
   1765      case "string":
   1766        _Services.prefs.setStringPref(pref, value);
   1767        break;
   1768      default:
   1769        throw new Error("runWithPrefs doesn't support this pref type yet");
   1770    }
   1771  }
   1772 };
   1773 
   1774 const _getTaskPrefs = prefs => {
   1775  return prefs.map(([pref, value]) => {
   1776    info(`Getting initial pref value for "${pref}"`);
   1777    if (!_Services.prefs.prefHasUserValue(pref)) {
   1778      // Check if the pref doesn't have a user value.
   1779      return [pref, undefined];
   1780    }
   1781    switch (typeof value) {
   1782      case "boolean":
   1783        return [pref, _Services.prefs.getBoolPref(pref)];
   1784      case "number":
   1785        return [pref, _Services.prefs.getIntPref(pref)];
   1786      case "string":
   1787        return [pref, _Services.prefs.getStringPref(pref)];
   1788      default:
   1789        throw new Error("runWithPrefs doesn't support this pref type yet");
   1790    }
   1791  });
   1792 };
   1793 
   1794 /**
   1795 * Runs the next test function from the list of async tests.
   1796 */
   1797 var _gRunningTest = null;
   1798 var _gTestIndex = 0; // The index of the currently running test.
   1799 var _gTaskRunning = false;
   1800 var _gSetupRunning = false;
   1801 function run_next_test() {
   1802  if (_gTaskRunning) {
   1803    throw new Error(
   1804      "run_next_test() called from an add_task() test function. " +
   1805        "run_next_test() should not be called from inside add_setup() or add_task() " +
   1806        "under any circumstances!"
   1807    );
   1808  }
   1809 
   1810  if (_gSetupRunning) {
   1811    throw new Error(
   1812      "run_next_test() called from an add_setup() test function. " +
   1813        "run_next_test() should not be called from inside add_setup() or add_task() " +
   1814        "under any circumstances!"
   1815    );
   1816  }
   1817 
   1818  function _run_next_test() {
   1819    if (_gTestIndex < _gTests.length) {
   1820      // Check for uncaught rejections as early and often as possible.
   1821      _PromiseTestUtils.assertNoUncaughtRejections();
   1822      let _properties;
   1823      [_properties, _gRunningTest] = _gTests[_gTestIndex++];
   1824 
   1825      // Must set to pending before we check for skip, so that we keep the
   1826      // running counts correct.
   1827      _testLogger.info(
   1828        `${_TEST_NAME} | Starting ${_properties.isSetup ? "setup " : ""}${
   1829          _gRunningTest.name
   1830        }`
   1831      );
   1832      do_test_pending(_gRunningTest.name);
   1833 
   1834      if (
   1835        (typeof _properties.skip_if == "function" && _properties.skip_if()) ||
   1836        (_gRunOnlyThisTest &&
   1837          _gRunningTest != _gRunOnlyThisTest &&
   1838          !_properties.isSetup)
   1839      ) {
   1840        let _condition = _gRunOnlyThisTest
   1841          ? "only one task may run."
   1842          : _properties.skip_if.toSource().replace(/\(\)\s*=>\s*/, "");
   1843        if (_condition == "true") {
   1844          _condition = "explicitly skipped.";
   1845        }
   1846        let _message =
   1847          _gRunningTest.name +
   1848          " skipped because the following conditions were" +
   1849          " met: (" +
   1850          _condition +
   1851          ")";
   1852        _testLogger.testStatus(
   1853          _TEST_NAME,
   1854          _gRunningTest.name,
   1855          "SKIP",
   1856          "SKIP",
   1857          _message
   1858        );
   1859        executeSoon(run_next_test);
   1860        return;
   1861      }
   1862 
   1863      let initialPrefsValues = [];
   1864      if (_properties.pref_set) {
   1865        initialPrefsValues = _getTaskPrefs(_properties.pref_set);
   1866        _setTaskPrefs(_properties.pref_set);
   1867      }
   1868 
   1869      if (_properties.isTask) {
   1870        if (_properties.isSetup) {
   1871          _gSetupRunning = true;
   1872        } else {
   1873          _gTaskRunning = true;
   1874        }
   1875        let startTime = ChromeUtils.now();
   1876        (async () => _gRunningTest())().then(
   1877          result => {
   1878            _gTaskRunning = _gSetupRunning = false;
   1879            ChromeUtils.addProfilerMarker(
   1880              "task",
   1881              { category: "Test", startTime },
   1882              _gRunningTest.name || undefined
   1883            );
   1884            if (_isGenerator(result)) {
   1885              Assert.ok(false, "Task returned a generator");
   1886            }
   1887            _setTaskPrefs(initialPrefsValues);
   1888            run_next_test();
   1889          },
   1890          ex => {
   1891            _gTaskRunning = _gSetupRunning = false;
   1892            ChromeUtils.addProfilerMarker(
   1893              "task",
   1894              { category: "Test", startTime },
   1895              _gRunningTest.name || undefined
   1896            );
   1897            _setTaskPrefs(initialPrefsValues);
   1898            try {
   1899              // Note `ex` at this point could be undefined, for example as
   1900              // result of a bare call to reject().
   1901              do_report_unexpected_exception(ex);
   1902            } catch (error) {
   1903              // The above throws NS_ERROR_ABORT and we don't want this to show
   1904              // up as an unhandled rejection later. If any other exception
   1905              // happened, something went wrong, so we abort.
   1906              if (error.result != Cr.NS_ERROR_ABORT) {
   1907                let extra = {};
   1908                if (error.fileName) {
   1909                  extra.source_file = error.fileName;
   1910                  if (error.lineNumber) {
   1911                    extra.line_number = error.lineNumber;
   1912                  }
   1913                } else {
   1914                  extra.source_file = "xpcshell/head.js";
   1915                }
   1916                if (error.stack) {
   1917                  extra.stack = _format_stack(error.stack);
   1918                }
   1919                _testLogger.error(_exception_message(error), extra);
   1920                _do_quit();
   1921                throw Components.Exception("", Cr.NS_ERROR_ABORT);
   1922              }
   1923            }
   1924          }
   1925        );
   1926      } else {
   1927        // Exceptions do not kill asynchronous tests, so they'll time out.
   1928        let startTime = ChromeUtils.now();
   1929        try {
   1930          _gRunningTest();
   1931        } catch (e) {
   1932          do_throw(e);
   1933        } finally {
   1934          ChromeUtils.addProfilerMarker(
   1935            "xpcshell-test",
   1936            { category: "Test", startTime },
   1937            _gRunningTest.name || undefined
   1938          );
   1939          _setTaskPrefs(initialPrefsValues);
   1940        }
   1941      }
   1942    }
   1943  }
   1944 
   1945  function frontLoadSetups() {
   1946    _gTests.sort(([propsA], [propsB]) => {
   1947      if (propsB.isSetup === propsA.isSetup) {
   1948        return 0;
   1949      }
   1950      return propsB.isSetup ? 1 : -1;
   1951    });
   1952  }
   1953 
   1954  if (!_gTestIndex) {
   1955    frontLoadSetups();
   1956  }
   1957 
   1958  // For sane stacks during failures, we execute this code soon, but not now.
   1959  // We do this now, before we call do_test_finished(), to ensure the pending
   1960  // counter (_tests_pending) never reaches 0 while we still have tests to run
   1961  // (executeSoon bumps that counter).
   1962  executeSoon(_run_next_test, "run_next_test " + _gTestIndex);
   1963 
   1964  if (_gRunningTest !== null) {
   1965    // Close the previous test do_test_pending call.
   1966    do_test_finished(_gRunningTest.name);
   1967  }
   1968 }
   1969 
   1970 try {
   1971  // Set global preferences
   1972  if (runningInParent) {
   1973    let prefsFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   1974    prefsFile.initWithPath(_PREFS_FILE);
   1975    _Services.prefs.readUserPrefsFromFile(prefsFile);
   1976  }
   1977 } catch (e) {
   1978  do_throw(e);
   1979 }
   1980 
   1981 /**
   1982 * Changing/Adding scalars or events to Telemetry is supported in build-faster/artifacts builds.
   1983 * These need to be loaded explicitly at start.
   1984 * It usually happens once all of Telemetry is initialized and set up.
   1985 * However in xpcshell tests Telemetry is not necessarily fully loaded,
   1986 * so we help out users by loading at least the dynamic-builtin probes.
   1987 */
   1988 try {
   1989  // We only need to run this in the parent process.
   1990  // We only want to run this for local developer builds (which should have a "default" update channel).
   1991  if (runningInParent && _AppConstants.MOZ_UPDATE_CHANNEL == "default") {
   1992    let startTime = ChromeUtils.now();
   1993    let { TelemetryController: _TelemetryController } =
   1994      ChromeUtils.importESModule(
   1995        "resource://gre/modules/TelemetryController.sys.mjs"
   1996      );
   1997 
   1998    let complete = false;
   1999    _TelemetryController.testRegisterJsProbes().finally(() => {
   2000      ChromeUtils.addProfilerMarker(
   2001        "xpcshell-test",
   2002        { category: "Test", startTime },
   2003        "TelemetryController.testRegisterJsProbes"
   2004      );
   2005      complete = true;
   2006    });
   2007    _Services.tm.spinEventLoopUntil(
   2008      "Test(xpcshell/head.js:run_next-Test)",
   2009      () => complete
   2010    );
   2011  }
   2012 } catch (e) {
   2013  do_throw(e);
   2014 }
   2015 
   2016 function _load_mozinfo() {
   2017  let mozinfoFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   2018  mozinfoFile.initWithPath(_MOZINFO_JS_PATH);
   2019  let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
   2020    Ci.nsIFileInputStream
   2021  );
   2022  stream.init(mozinfoFile, -1, 0, 0);
   2023  let bytes = _NetUtil.readInputStream(stream, stream.available());
   2024  let decoded = JSON.parse(new TextDecoder().decode(bytes));
   2025  stream.close();
   2026  return decoded;
   2027 }
   2028 
   2029 Object.defineProperty(this, "mozinfo", {
   2030  configurable: true,
   2031  get() {
   2032    let _mozinfo = _load_mozinfo();
   2033    Object.defineProperty(this, "mozinfo", {
   2034      configurable: false,
   2035      value: _mozinfo,
   2036    });
   2037    return _mozinfo;
   2038  },
   2039 });