tor-browser

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

SimpleTest.js (69080B)


      1 /* -*- js-indent-level: 2; tab-width: 2; indent-tabs-mode: nil -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et: */
      3 
      4 // Generally gTestPath should be set by the harness.
      5 /* global gTestPath */
      6 
      7 /**
      8 * SimpleTest framework object.
      9 *
     10 * @class
     11 */
     12 var SimpleTest = {};
     13 var parentRunner = null;
     14 
     15 // Using a try/catch rather than SpecialPowers.Cu.isRemoteProxy() because
     16 // it doesn't cover the case where an iframe is xorigin but fission is
     17 // not enabled.
     18 let isSameOrigin = function (w) {
     19  try {
     20    w.top.TestRunner;
     21  } catch (e) {
     22    if (e instanceof DOMException) {
     23      return false;
     24    }
     25  }
     26  return true;
     27 };
     28 let isXOrigin = !isSameOrigin(window);
     29 
     30 // Note: duplicated in browser-test.js . See also bug 1820150.
     31 function isErrorOrException(err) {
     32  // It'd be nice if we had either `Error.isError(err)` or `Error.isInstance(err)`
     33  // but we don't, so do it ourselves:
     34  if (!err) {
     35    return false;
     36  }
     37  if (err instanceof SpecialPowers.Ci.nsIException) {
     38    return true;
     39  }
     40  try {
     41    let glob = SpecialPowers.Cu.getGlobalForObject(err);
     42    return err instanceof glob.Error;
     43  } catch {
     44    // getGlobalForObject can be upset if it doesn't get passed an object.
     45    // Just do a standard instanceof check using this global and cross fingers:
     46  }
     47  return err instanceof Error;
     48 }
     49 
     50 // In normal test runs, the window that has a TestRunner in its parent is
     51 // the primary window.  In single test runs, if there is no parent and there
     52 // is no opener then it is the primary window.
     53 var isSingleTestRun =
     54  parent == window &&
     55  !(opener || (window.arguments && window.arguments[0].SimpleTest));
     56 try {
     57  var isPrimaryTestWindow =
     58    (isXOrigin && parent != window && parent == top) ||
     59    (!isXOrigin && (!!parent.TestRunner || isSingleTestRun));
     60 } catch (e) {
     61  dump(
     62    "TEST-UNEXPECTED-FAIL, Exception caught: " +
     63      e.message +
     64      ", at: " +
     65      e.fileName +
     66      " (" +
     67      e.lineNumber +
     68      "), location: " +
     69      window.location.href +
     70      "\n"
     71  );
     72 }
     73 
     74 let xOriginRunner = {
     75  init(harnessWindow) {
     76    this.harnessWindow = harnessWindow;
     77    let url = new URL(document.URL);
     78    this.testFile = url.pathname;
     79    this.showTestReport = url.searchParams.get("showTestReport") == "true";
     80    this.expected = url.searchParams.get("expected");
     81  },
     82  callHarnessMethod(applyOn, command, ...params) {
     83    // Message handled by xOriginTestRunnerHandler in TestRunner.js
     84    this.harnessWindow.postMessage(
     85      {
     86        harnessType: "SimpleTest",
     87        applyOn,
     88        command,
     89        params,
     90      },
     91      "*"
     92    );
     93  },
     94  getParameterInfo() {
     95    let url = new URL(document.URL);
     96    return {
     97      currentTestURL: url.searchParams.get("currentTestURL"),
     98      testRoot: url.searchParams.get("testRoot"),
     99    };
    100  },
    101  addFailedTest(test) {
    102    this.callHarnessMethod("runner", "addFailedTest", test);
    103  },
    104  expectAssertions(min, max) {
    105    this.callHarnessMethod("runner", "expectAssertions", min, max);
    106  },
    107  expectChildProcessCrash() {
    108    this.callHarnessMethod("runner", "expectChildProcessCrash");
    109  },
    110  requestLongerTimeout(factor) {
    111    this.callHarnessMethod("runner", "requestLongerTimeout", factor);
    112  },
    113  _lastAssertionCount: 0,
    114  testFinished(tests) {
    115    var newAssertionCount = SpecialPowers.assertionCount();
    116    var numAsserts = newAssertionCount - this._lastAssertionCount;
    117    this._lastAssertionCount = newAssertionCount;
    118    this.callHarnessMethod("runner", "addAssertionCount", numAsserts);
    119    this.callHarnessMethod("runner", "testFinished", tests);
    120  },
    121  structuredLogger: {
    122    info(msg) {
    123      xOriginRunner.callHarnessMethod("logger", "structuredLogger.info", msg);
    124    },
    125    warning(msg) {
    126      xOriginRunner.callHarnessMethod(
    127        "logger",
    128        "structuredLogger.warning",
    129        msg
    130      );
    131    },
    132    error(msg) {
    133      xOriginRunner.callHarnessMethod("logger", "structuredLogger.error", msg);
    134    },
    135    activateBuffering() {
    136      xOriginRunner.callHarnessMethod(
    137        "logger",
    138        "structuredLogger.activateBuffering"
    139      );
    140    },
    141    deactivateBuffering() {
    142      xOriginRunner.callHarnessMethod(
    143        "logger",
    144        "structuredLogger.deactivateBuffering"
    145      );
    146    },
    147    testStatus(url, subtest, status, expected, diagnostic, stack) {
    148      xOriginRunner.callHarnessMethod(
    149        "logger",
    150        "structuredLogger.testStatus",
    151        url,
    152        subtest,
    153        status,
    154        expected,
    155        diagnostic,
    156        stack
    157      );
    158    },
    159  },
    160 };
    161 
    162 // Finds the TestRunner for this test run and the SpecialPowers object (in
    163 // case it is not defined) from a parent/opener window.
    164 //
    165 // Finding the SpecialPowers object is needed when we have ChromePowers in
    166 // harness.xhtml and we need SpecialPowers in the iframe, and also for tests
    167 // like test_focus.xhtml where we open a window which opens another window which
    168 // includes SimpleTest.js.
    169 (function () {
    170  function ancestor(w) {
    171    return w.parent != w
    172      ? w.parent
    173      : w.opener ||
    174          (!isXOrigin &&
    175            w.arguments &&
    176            SpecialPowers.wrap(Window).isInstance(w.arguments[0]) &&
    177            w.arguments[0]);
    178  }
    179 
    180  var w = ancestor(window);
    181  while (w && !parentRunner) {
    182    isXOrigin = !isSameOrigin(w);
    183 
    184    if (isXOrigin) {
    185      if (w.parent != w) {
    186        w = w.top;
    187      }
    188      xOriginRunner.init(w);
    189      parentRunner = xOriginRunner;
    190    }
    191 
    192    if (!parentRunner) {
    193      parentRunner = w.TestRunner;
    194      if (!parentRunner && w.wrappedJSObject) {
    195        parentRunner = w.wrappedJSObject.TestRunner;
    196      }
    197    }
    198    w = ancestor(w);
    199  }
    200 
    201  if (parentRunner) {
    202    SimpleTest.harnessParameters = parentRunner.getParameterInfo();
    203  }
    204 })();
    205 
    206 /* Helper functions pulled out of various MochiKit modules */
    207 if (typeof repr == "undefined") {
    208  this.repr = function repr(o) {
    209    if (typeof o == "undefined") {
    210      return "undefined";
    211    } else if (o === null) {
    212      return "null";
    213    }
    214    try {
    215      if (typeof o.__repr__ == "function") {
    216        return o.__repr__();
    217      } else if (typeof o.repr == "function" && o.repr != repr) {
    218        return o.repr();
    219      }
    220    } catch (e) {}
    221    try {
    222      if (
    223        typeof o.NAME == "string" &&
    224        (o.toString == Function.prototype.toString ||
    225          o.toString == Object.prototype.toString)
    226      ) {
    227        return o.NAME;
    228      }
    229    } catch (e) {}
    230    var ostring;
    231    try {
    232      if (o === 0) {
    233        ostring = 1 / o > 0 ? "+0" : "-0";
    234      } else if (typeof o === "string") {
    235        ostring = JSON.stringify(o);
    236      } else if (Array.isArray(o)) {
    237        ostring = "[" + o.map(val => repr(val)).join(", ") + "]";
    238      } else {
    239        ostring = o + "";
    240      }
    241    } catch (e) {
    242      return "[" + typeof o + "]";
    243    }
    244    if (typeof o == "function") {
    245      o = ostring.replace(/^\s+/, "");
    246      var idx = o.indexOf("{");
    247      if (idx != -1) {
    248        o = o.substr(0, idx) + "{...}";
    249      }
    250    }
    251    return ostring;
    252  };
    253 }
    254 
    255 /* This returns a function that applies the previously given parameters.
    256 * This is used by SimpleTest.showReport
    257 */
    258 if (typeof partial == "undefined") {
    259  this.partial = function (func) {
    260    var args = [];
    261    for (let i = 1; i < arguments.length; i++) {
    262      args.push(arguments[i]);
    263    }
    264    return function () {
    265      if (arguments.length) {
    266        for (let i = 1; i < arguments.length; i++) {
    267          args.push(arguments[i]);
    268        }
    269      }
    270      func(args);
    271    };
    272  };
    273 }
    274 
    275 if (typeof getElement == "undefined") {
    276  this.getElement = function (id) {
    277    return typeof id == "string" ? document.getElementById(id) : id;
    278  };
    279  this.$ = this.getElement;
    280 }
    281 
    282 SimpleTest._newCallStack = function (path) {
    283  var rval = function callStackHandler() {
    284    var callStack = callStackHandler.callStack;
    285    for (var i = 0; i < callStack.length; i++) {
    286      if (callStack[i].apply(this, arguments) === false) {
    287        break;
    288      }
    289    }
    290    try {
    291      this[path] = null;
    292    } catch (e) {
    293      // pass
    294    }
    295  };
    296  rval.callStack = [];
    297  return rval;
    298 };
    299 
    300 if (typeof addLoadEvent == "undefined") {
    301  this.addLoadEvent = function (func) {
    302    var existing = window.onload;
    303    var regfunc = existing;
    304    if (
    305      !(
    306        typeof existing == "function" &&
    307        typeof existing.callStack == "object" &&
    308        existing.callStack !== null
    309      )
    310    ) {
    311      regfunc = SimpleTest._newCallStack("onload");
    312      if (typeof existing == "function") {
    313        regfunc.callStack.push(existing);
    314      }
    315      window.onload = regfunc;
    316    }
    317    regfunc.callStack.push(func);
    318  };
    319 }
    320 
    321 function createEl(type, attrs, html) {
    322  //use createElementNS so the xul/xhtml tests have no issues
    323  var el;
    324  if (!document.body) {
    325    el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
    326  } else {
    327    el = document.createElement(type);
    328  }
    329  if (attrs !== null && attrs !== undefined) {
    330    for (var k in attrs) {
    331      el.setAttribute(k, attrs[k]);
    332    }
    333  }
    334  if (html !== null && html !== undefined) {
    335    el.appendChild(document.createTextNode(html));
    336  }
    337  return el;
    338 }
    339 
    340 /* lots of tests use this as a helper to get css properties */
    341 if (typeof computedStyle == "undefined") {
    342  this.computedStyle = function (elem, cssProperty) {
    343    elem = getElement(elem);
    344    if (elem.currentStyle) {
    345      return elem.currentStyle[cssProperty];
    346    }
    347    if (typeof document.defaultView == "undefined" || document === null) {
    348      return undefined;
    349    }
    350    var style = document.defaultView.getComputedStyle(elem);
    351    if (typeof style == "undefined" || style === null) {
    352      return undefined;
    353    }
    354 
    355    var selectorCase = cssProperty.replace(/([A-Z])/g, "-$1").toLowerCase();
    356 
    357    return style.getPropertyValue(selectorCase);
    358  };
    359 }
    360 
    361 SimpleTest._tests = [];
    362 SimpleTest._stopOnLoad = true;
    363 SimpleTest._cleanupFunctions = [];
    364 SimpleTest._taskCleanupFunctions = [];
    365 SimpleTest._currentTask = null;
    366 SimpleTest._timeoutFunctions = [];
    367 SimpleTest._inChaosMode = false;
    368 // When using failure pattern file to filter unexpected issues,
    369 // SimpleTest.expected would be an array of [pattern, expected count],
    370 // and SimpleTest.num_failed would be an array of actual counts which
    371 // has the same length as SimpleTest.expected.
    372 SimpleTest.expected = "pass";
    373 SimpleTest.num_failed = 0;
    374 
    375 SpecialPowers.setAsDefaultAssertHandler();
    376 
    377 function usesFailurePatterns() {
    378  return Array.isArray(SimpleTest.expected);
    379 }
    380 
    381 /**
    382 * Checks whether there is any failure pattern matches the given error
    383 * message, and if found, bumps the counter of the failure pattern.
    384 *
    385 * @return {boolean} Whether a matched failure pattern is found.
    386 */
    387 function recordIfMatchesFailurePattern(name, diag) {
    388  let index = SimpleTest.expected.findIndex(([pat]) => {
    389    return (
    390      pat == null ||
    391      (typeof name == "string" && name.includes(pat)) ||
    392      (typeof diag == "string" && diag.includes(pat))
    393    );
    394  });
    395  if (index >= 0) {
    396    SimpleTest.num_failed[index]++;
    397    return true;
    398  }
    399  return false;
    400 }
    401 
    402 SimpleTest.setExpected = function () {
    403  if (!parentRunner) {
    404    return;
    405  }
    406  if (!Array.isArray(parentRunner.expected)) {
    407    SimpleTest.expected = parentRunner.expected;
    408  } else {
    409    // Assertions are checked by the runner.
    410    SimpleTest.expected = parentRunner.expected.filter(
    411      ([pat]) => pat != "ASSERTION"
    412    );
    413    SimpleTest.num_failed = new Array(SimpleTest.expected.length);
    414    SimpleTest.num_failed.fill(0);
    415  }
    416 };
    417 SimpleTest.setExpected();
    418 
    419 /**
    420 * Something like assert.
    421 */
    422 SimpleTest.ok = function (condition, name) {
    423  if (arguments.length > 2) {
    424    const diag = "Too many arguments passed to `ok(condition, name)`";
    425    SimpleTest.record(false, name, diag);
    426  } else {
    427    SimpleTest.record(condition, name);
    428  }
    429 };
    430 
    431 SimpleTest.record = function (condition, name, diag, stack, expected) {
    432  var test = { result: !!condition, name, diag };
    433  let successInfo;
    434  let failureInfo;
    435  if (SimpleTest.expected == "fail") {
    436    if (!test.result) {
    437      SimpleTest.num_failed++;
    438      test.result = true;
    439    }
    440    successInfo = {
    441      status: "PASS",
    442      expected: "PASS",
    443      message: "TEST-PASS",
    444    };
    445    failureInfo = {
    446      status: "FAIL",
    447      expected: "FAIL",
    448      message: "TEST-KNOWN-FAIL",
    449    };
    450  } else if (!test.result && usesFailurePatterns()) {
    451    if (recordIfMatchesFailurePattern(name, diag)) {
    452      test.result = true;
    453      // Add a mark for unexpected failures suppressed by failure pattern.
    454      name = "[suppressed] " + name;
    455    }
    456    successInfo = {
    457      status: "FAIL",
    458      expected: "FAIL",
    459      message: "TEST-KNOWN-FAIL",
    460    };
    461    failureInfo = {
    462      status: "FAIL",
    463      expected: "PASS",
    464      message: "TEST-UNEXPECTED-FAIL",
    465    };
    466  } else if (expected == "fail") {
    467    successInfo = {
    468      status: "PASS",
    469      expected: "FAIL",
    470      message: "TEST-UNEXPECTED-PASS",
    471    };
    472    failureInfo = {
    473      status: "FAIL",
    474      expected: "FAIL",
    475      message: "TEST-KNOWN-FAIL",
    476    };
    477  } else {
    478    successInfo = {
    479      status: "PASS",
    480      expected: "PASS",
    481      message: "TEST-PASS",
    482    };
    483    failureInfo = {
    484      status: "FAIL",
    485      expected: "PASS",
    486      message: "TEST-UNEXPECTED-FAIL",
    487    };
    488  }
    489 
    490  if (condition) {
    491    stack = null;
    492  } else if (!stack) {
    493    stack = new Error().stack
    494      .replace(/^(.*@)http:\/\/mochi.test:8888\/tests\//gm, "    $1")
    495      .split("\n");
    496    stack.splice(0, 1);
    497    stack = stack.join("\n");
    498  }
    499  SimpleTest._logResult(test, successInfo, failureInfo, stack);
    500  SimpleTest._tests.push(test);
    501 };
    502 
    503 /**
    504 * Roughly equivalent to ok(Object.is(a, b), name)
    505 */
    506 SimpleTest.is = function (a, b, name) {
    507  // Be lazy and use Object.is til we want to test a browser without it.
    508  var pass = Object.is(a, b);
    509  var diag = pass ? "" : "got " + repr(a) + ", expected " + repr(b);
    510  SimpleTest.record(pass, name, diag);
    511 };
    512 
    513 SimpleTest.isfuzzy = function (a, b, epsilon, name) {
    514  var pass = a >= b - epsilon && a <= b + epsilon;
    515  var diag = pass
    516    ? ""
    517    : "got " +
    518      repr(a) +
    519      ", expected " +
    520      repr(b) +
    521      " epsilon: +/- " +
    522      repr(epsilon);
    523  SimpleTest.record(pass, name, diag);
    524 };
    525 
    526 SimpleTest.isnot = function (a, b, name) {
    527  var pass = !Object.is(a, b);
    528  var diag = pass ? "" : "didn't expect " + repr(a) + ", but got it";
    529  SimpleTest.record(pass, name, diag);
    530 };
    531 
    532 /**
    533 * Check that the function call throws an exception.
    534 */
    535 SimpleTest.doesThrow = function (fn, name) {
    536  var gotException = false;
    537  try {
    538    fn();
    539  } catch (ex) {
    540    gotException = true;
    541  }
    542  ok(gotException, name);
    543 };
    544 
    545 //  --------------- Test.Builder/Test.More todo() -----------------
    546 
    547 SimpleTest.todo = function (condition, name, diag) {
    548  var test = { result: !!condition, name, diag, todo: true };
    549  if (
    550    test.result &&
    551    usesFailurePatterns() &&
    552    recordIfMatchesFailurePattern(name, diag)
    553  ) {
    554    // Flipping the result to false so we don't get unexpected result. There
    555    // is no perfect way here. A known failure can trigger unexpected pass,
    556    // in which case, tagging it as KNOWN-FAIL probably makes more sense than
    557    // marking it PASS.
    558    test.result = false;
    559    // Add a mark for unexpected failures suppressed by failure pattern.
    560    name = "[suppressed] " + name;
    561  }
    562  var successInfo = {
    563    status: "PASS",
    564    expected: "FAIL",
    565    message: "TEST-UNEXPECTED-PASS",
    566  };
    567  var failureInfo = {
    568    status: "FAIL",
    569    expected: "FAIL",
    570    message: "TEST-KNOWN-FAIL",
    571  };
    572  SimpleTest._logResult(test, successInfo, failureInfo);
    573  SimpleTest._tests.push(test);
    574 };
    575 
    576 /*
    577 * Returns the absolute URL to a test data file from where tests
    578 * are served. i.e. the file doesn't necessarely exists where tests
    579 * are executed.
    580 *
    581 * (For android, mochitest are executed on the device, while
    582 * all mochitest html (and others) files are served from the test runner
    583 * slave)
    584 */
    585 SimpleTest.getTestFileURL = function (path) {
    586  var location = window.location;
    587  // Remove mochitest html file name from the path
    588  var remotePath = location.pathname.replace(/\/[^\/]+?$/, "");
    589  var url = location.origin + remotePath + "/" + path;
    590  return url;
    591 };
    592 
    593 SimpleTest._getCurrentTestURL = function () {
    594  return (
    595    (SimpleTest.harnessParameters &&
    596      SimpleTest.harnessParameters.currentTestURL) ||
    597    (parentRunner && parentRunner.currentTestURL) ||
    598    (typeof gTestPath == "string" && gTestPath) ||
    599    "unknown test url"
    600  );
    601 };
    602 
    603 SimpleTest._forceLogMessageOutput = false;
    604 
    605 /**
    606 * Force all test messages to be displayed.  Only applies for the current test.
    607 */
    608 SimpleTest.requestCompleteLog = function () {
    609  if (!parentRunner || SimpleTest._forceLogMessageOutput) {
    610    return;
    611  }
    612 
    613  parentRunner.structuredLogger.deactivateBuffering();
    614  SimpleTest._forceLogMessageOutput = true;
    615 
    616  SimpleTest.registerCleanupFunction(function () {
    617    parentRunner.structuredLogger.activateBuffering();
    618    SimpleTest._forceLogMessageOutput = false;
    619  });
    620 };
    621 
    622 SimpleTest._logResult = function (test, passInfo, failInfo, stack) {
    623  var url = SimpleTest._getCurrentTestURL();
    624  var result = test.result ? passInfo : failInfo;
    625  var diagnostic = test.diag || null;
    626  // BUGFIX : coercing test.name to a string, because some a11y tests pass an xpconnect object
    627  var subtest = test.name ? String(test.name) : null;
    628  var isError = !test.result == !test.todo;
    629 
    630  if (parentRunner) {
    631    if (!result.status || !result.expected) {
    632      if (diagnostic) {
    633        parentRunner.structuredLogger.info(diagnostic);
    634      }
    635      return;
    636    }
    637 
    638    if (isError) {
    639      parentRunner.addFailedTest(url);
    640    }
    641 
    642    parentRunner.structuredLogger.testStatus(
    643      url,
    644      subtest,
    645      result.status,
    646      result.expected,
    647      diagnostic,
    648      stack
    649    );
    650  } else if (typeof dump === "function") {
    651    var diagMessage = test.name + (test.diag ? " - " + test.diag : "");
    652    var debugMsg = [result.message, url, diagMessage].join(" | ");
    653    dump(debugMsg + "\n");
    654  } else {
    655    // Non-Mozilla browser?  Just do nothing.
    656  }
    657 };
    658 
    659 SimpleTest.info = function (name, message) {
    660  var log = message ? name + " | " + message : name;
    661  if (parentRunner) {
    662    parentRunner.structuredLogger.info(log);
    663  } else {
    664    dump(log + "\n");
    665  }
    666 };
    667 
    668 /**
    669 * Copies of is and isnot with the call to ok replaced by a call to todo.
    670 */
    671 
    672 SimpleTest.todo_is = function (a, b, name) {
    673  var pass = Object.is(a, b);
    674  var diag = pass
    675    ? repr(a) + " should equal " + repr(b)
    676    : "got " + repr(a) + ", expected " + repr(b);
    677  SimpleTest.todo(pass, name, diag);
    678 };
    679 
    680 SimpleTest.todo_isnot = function (a, b, name) {
    681  var pass = !Object.is(a, b);
    682  var diag = pass
    683    ? repr(a) + " should not equal " + repr(b)
    684    : "didn't expect " + repr(a) + ", but got it";
    685  SimpleTest.todo(pass, name, diag);
    686 };
    687 
    688 /**
    689 * Makes a test report, returns it as a DIV element.
    690 */
    691 SimpleTest.report = function () {
    692  var passed = 0;
    693  var failed = 0;
    694  var todo = 0;
    695 
    696  var tallyAndCreateDiv = function (test) {
    697    var cls, msg, div;
    698    var diag = test.diag ? " - " + test.diag : "";
    699    if (test.todo && !test.result) {
    700      todo++;
    701      cls = "test_todo";
    702      msg = "todo | " + test.name + diag;
    703    } else if (test.result && !test.todo) {
    704      passed++;
    705      cls = "test_ok";
    706      msg = "passed | " + test.name + diag;
    707    } else {
    708      failed++;
    709      cls = "test_not_ok";
    710      msg = "failed | " + test.name + diag;
    711    }
    712    div = createEl("div", { class: cls }, msg);
    713    return div;
    714  };
    715  var results = [];
    716  for (var d = 0; d < SimpleTest._tests.length; d++) {
    717    results.push(tallyAndCreateDiv(SimpleTest._tests[d]));
    718  }
    719 
    720  var summary_class =
    721    // eslint-disable-next-line no-nested-ternary
    722    failed != 0 ? "some_fail" : passed == 0 ? "todo_only" : "all_pass";
    723 
    724  var div1 = createEl("div", { class: "tests_report" });
    725  var div2 = createEl("div", { class: "tests_summary " + summary_class });
    726  var div3 = createEl("div", { class: "tests_passed" }, "Passed: " + passed);
    727  var div4 = createEl("div", { class: "tests_failed" }, "Failed: " + failed);
    728  var div5 = createEl("div", { class: "tests_todo" }, "Todo: " + todo);
    729  div2.appendChild(div3);
    730  div2.appendChild(div4);
    731  div2.appendChild(div5);
    732  div1.appendChild(div2);
    733  for (var t = 0; t < results.length; t++) {
    734    //iterate in order
    735    div1.appendChild(results[t]);
    736  }
    737  return div1;
    738 };
    739 
    740 /**
    741 * Toggle element visibility
    742 */
    743 SimpleTest.toggle = function (el) {
    744  if (computedStyle(el, "display") == "block") {
    745    el.style.display = "none";
    746  } else {
    747    el.style.display = "block";
    748  }
    749 };
    750 
    751 /**
    752 * Toggle visibility for divs with a specific class.
    753 */
    754 SimpleTest.toggleByClass = function (cls, evt) {
    755  var children = document.getElementsByTagName("div");
    756  var elements = [];
    757  for (var i = 0; i < children.length; i++) {
    758    var child = children[i];
    759    var clsName = child.className;
    760    if (!clsName) {
    761      continue;
    762    }
    763    var classNames = clsName.split(" ");
    764    for (var j = 0; j < classNames.length; j++) {
    765      if (classNames[j] == cls) {
    766        elements.push(child);
    767        break;
    768      }
    769    }
    770  }
    771  for (var t = 0; t < elements.length; t++) {
    772    //TODO: again, for-in loop over elems seems to break this
    773    SimpleTest.toggle(elements[t]);
    774  }
    775  if (evt) {
    776    evt.preventDefault();
    777  }
    778 };
    779 
    780 /**
    781 * Shows the report in the browser
    782 */
    783 SimpleTest.showReport = function () {
    784  var togglePassed = createEl("a", { href: "#" }, "Toggle passed checks");
    785  var toggleFailed = createEl("a", { href: "#" }, "Toggle failed checks");
    786  var toggleTodo = createEl("a", { href: "#" }, "Toggle todo checks");
    787  togglePassed.onclick = partial(SimpleTest.toggleByClass, "test_ok");
    788  toggleFailed.onclick = partial(SimpleTest.toggleByClass, "test_not_ok");
    789  toggleTodo.onclick = partial(SimpleTest.toggleByClass, "test_todo");
    790  var body = document.body; // Handles HTML documents
    791  if (!body) {
    792    // Do the XML thing.
    793    body = document.getElementsByTagNameNS(
    794      "http://www.w3.org/1999/xhtml",
    795      "body"
    796    )[0];
    797  }
    798  var firstChild = body.childNodes[0];
    799  var addNode;
    800  if (firstChild) {
    801    addNode = function (el) {
    802      body.insertBefore(el, firstChild);
    803    };
    804  } else {
    805    addNode = function (el) {
    806      body.appendChild(el);
    807    };
    808  }
    809  addNode(togglePassed);
    810  addNode(createEl("span", null, " "));
    811  addNode(toggleFailed);
    812  addNode(createEl("span", null, " "));
    813  addNode(toggleTodo);
    814  addNode(SimpleTest.report());
    815  // Add a separator from the test content.
    816  addNode(createEl("hr"));
    817 };
    818 
    819 /**
    820 * Tells SimpleTest to don't finish the test when the document is loaded,
    821 * useful for asynchronous tests.
    822 *
    823 * When SimpleTest.waitForExplicitFinish is called,
    824 * explicit SimpleTest.finish() is required.
    825 */
    826 SimpleTest.waitForExplicitFinish = function () {
    827  SimpleTest._stopOnLoad = false;
    828 };
    829 
    830 /**
    831 * Multiply the timeout the parent runner uses for this test by the
    832 * given factor.
    833 *
    834 * For example, in a test that may take a long time to complete, using
    835 * "SimpleTest.requestLongerTimeout(5)" will give it 5 times as long to
    836 * finish.
    837 *
    838 * @param {number} factor
    839 *        The multiplication factor to use on the timeout for this test.
    840 */
    841 SimpleTest.requestLongerTimeout = function (factor) {
    842  if (parentRunner) {
    843    parentRunner.requestLongerTimeout(factor);
    844  } else {
    845    dump(
    846      "[SimpleTest.requestLongerTimeout()] ignoring request, maybe you meant to call the global `requestLongerTimeout` instead?\n"
    847    );
    848  }
    849 };
    850 
    851 /**
    852 * Note that the given range of assertions is to be expected.  When
    853 * this function is not called, 0 assertions are expected.  When only
    854 * one argument is given, that number of assertions are expected.
    855 *
    856 * A test where we expect to have assertions (which should largely be a
    857 * transitional mechanism to get assertion counts down from our current
    858 * situation) can call the SimpleTest.expectAssertions() function, with
    859 * either one or two arguments:  one argument gives an exact number
    860 * expected, and two arguments give a range.  For example, a test might do
    861 * one of the following:
    862 *
    863 * @example
    864 *
    865 *   // Currently triggers two assertions (bug NNNNNN).
    866 *   SimpleTest.expectAssertions(2);
    867 *
    868 *   // Currently triggers one assertion on Mac (bug NNNNNN).
    869 *   if (navigator.platform.indexOf("Mac") == 0) {
    870 *     SimpleTest.expectAssertions(1);
    871 *   }
    872 *
    873 *   // Currently triggers two assertions on all platforms (bug NNNNNN),
    874 *   // but intermittently triggers two additional assertions (bug NNNNNN)
    875 *   // on Windows.
    876 *   if (navigator.platform.indexOf("Win") == 0) {
    877 *     SimpleTest.expectAssertions(2, 4);
    878 *   } else {
    879 *     SimpleTest.expectAssertions(2);
    880 *   }
    881 *
    882 *   // Intermittently triggers up to three assertions (bug NNNNNN).
    883 *   SimpleTest.expectAssertions(0, 3);
    884 */
    885 SimpleTest.expectAssertions = function (min, max) {
    886  if (parentRunner) {
    887    parentRunner.expectAssertions(min, max);
    888  }
    889 };
    890 
    891 SimpleTest._flakyTimeoutIsOK = false;
    892 SimpleTest._originalSetTimeout = window.setTimeout;
    893 window.setTimeout = function SimpleTest_setTimeoutShim() {
    894  // Don't break tests that are loaded without a parent runner.
    895  if (parentRunner) {
    896    // Right now, we only enable these checks for mochitest-plain.
    897    switch (SimpleTest.harnessParameters.testRoot) {
    898      case "browser":
    899      case "chrome":
    900      case "a11y":
    901        break;
    902      default:
    903        if (
    904          !SimpleTest._alreadyFinished &&
    905          arguments.length > 1 &&
    906          arguments[1] > 0
    907        ) {
    908          if (SimpleTest._flakyTimeoutIsOK) {
    909            SimpleTest.todo(
    910              false,
    911              "The author of the test has indicated that flaky timeouts are expected.  Reason: " +
    912                SimpleTest._flakyTimeoutReason
    913            );
    914          } else {
    915            SimpleTest.ok(
    916              false,
    917              "Test attempted to use a flaky timeout value " + arguments[1]
    918            );
    919          }
    920        }
    921    }
    922  }
    923  return SimpleTest._originalSetTimeout.apply(window, arguments);
    924 };
    925 
    926 /**
    927 * Request the framework to allow usage of setTimeout(func, timeout)
    928 * where ``timeout > 0``.  This is required to note that the author of
    929 * the test is aware of the inherent flakiness in the test caused by
    930 * that, and asserts that there is no way around using the magic timeout
    931 * value number for some reason.
    932 *
    933 * Use of this function is **STRONGLY** discouraged.  Think twice before
    934 * using it.  Such magic timeout values could result in intermittent
    935 * failures in your test, and are almost never necessary!
    936 *
    937 * @param {string} reason
    938 *        A string representation of the reason why the test needs timeouts.
    939 */
    940 SimpleTest.requestFlakyTimeout = function (reason) {
    941  SimpleTest.is(typeof reason, "string", "A valid string reason is expected");
    942  SimpleTest.isnot(reason, "", "Reason cannot be empty");
    943  SimpleTest._flakyTimeoutIsOK = true;
    944  SimpleTest._flakyTimeoutReason = reason;
    945 };
    946 
    947 /**
    948 * If the page is not yet loaded, waits for the load event. If the page is
    949 * not yet focused, focuses and waits for the window to be focused.
    950 * If the current page is 'about:blank', then the page is assumed to not
    951 * yet be loaded. Pass true for expectBlankPage to not make this assumption
    952 * if you expect a blank page to be present.
    953 *
    954 * The target object should be specified if it is different than 'window'. The
    955 * actual focused window may be a descendant window of aObject.
    956 *
    957 * @param {Window|browser|BrowsingContext} [aObject]
    958 *        Optional object to be focused, and may be any of:
    959 *          window - a window object to focus
    960 *          browser - a <browser>/<iframe> element. The top-level window
    961 *                    within the frame will be focused.
    962 *          browsing context - a browsing context containing a window to focus
    963 *        If not specified, defaults to the global 'window'.
    964 * @param {boolean} [expectBlankPage=false]
    965 *        True if targetWindow.location is 'about:blank'.
    966 * @param {boolean} [aBlurSubframe=false]
    967 *        If true, and a subframe within the window to focus is focused, blur
    968 *        it so that the specified window or browsing context will receive
    969 *        focus events.
    970 *
    971 * @returns The browsing context that was focused.
    972 */
    973 SimpleTest.promiseFocus = async function (
    974  aObject,
    975  aExpectBlankPage = false,
    976  aBlurSubframe = false
    977 ) {
    978  let browser;
    979  let browsingContext;
    980  let windowToFocus;
    981 
    982  if (!aObject) {
    983    aObject = window;
    984  }
    985 
    986  async function waitForEvent(aTarget, aEventName) {
    987    return new Promise(resolve => {
    988      aTarget.addEventListener(aEventName, resolve, {
    989        capture: true,
    990        once: true,
    991      });
    992    });
    993  }
    994 
    995  if (SpecialPowers.wrap(Window).isInstance(aObject)) {
    996    windowToFocus = aObject;
    997 
    998    let isBlank = windowToFocus.location.href == "about:blank";
    999    if (
   1000      aExpectBlankPage != isBlank ||
   1001      windowToFocus.document.readyState != "complete"
   1002    ) {
   1003      info("must wait for load");
   1004      await waitForEvent(windowToFocus, "load");
   1005    }
   1006  } else {
   1007    if (SpecialPowers.wrap(Element).isInstance(aObject)) {
   1008      // assume this is a browser/iframe element
   1009      browsingContext = aObject.browsingContext;
   1010    } else {
   1011      browsingContext = aObject;
   1012    }
   1013 
   1014    browser =
   1015      browsingContext == aObject ? aObject.top.embedderElement : aObject;
   1016    windowToFocus = browser.ownerGlobal;
   1017  }
   1018 
   1019  if (!windowToFocus.document.hasFocus()) {
   1020    info("must wait for focus");
   1021    let focusPromise = waitForEvent(windowToFocus.document, "focus");
   1022    SpecialPowers.focus(windowToFocus);
   1023    await focusPromise;
   1024  }
   1025 
   1026  if (browser) {
   1027    if (windowToFocus.document.activeElement != browser) {
   1028      browser.focus();
   1029    }
   1030 
   1031    info("must wait for focus in content");
   1032 
   1033    // Make sure that the child process thinks it is focused as well.
   1034    await SpecialPowers.ensureFocus(browsingContext, aBlurSubframe);
   1035  } else {
   1036    if (aBlurSubframe) {
   1037      SpecialPowers.clearFocus(windowToFocus);
   1038    }
   1039 
   1040    browsingContext = windowToFocus.browsingContext;
   1041  }
   1042 
   1043  // Some tests rely on this delay, likely expecting layout or paint to occur.
   1044  await new Promise(resolve => {
   1045    SimpleTest.executeSoon(resolve);
   1046  });
   1047 
   1048  return browsingContext;
   1049 };
   1050 
   1051 /**
   1052 * Version of promiseFocus that uses a callback. For compatibility,
   1053 * the callback is passed one argument, the window that was focused.
   1054 * If the focused window is not in the same process, null is supplied.
   1055 */
   1056 SimpleTest.waitForFocus = function (callback, aObject, expectBlankPage) {
   1057  SimpleTest.promiseFocus(aObject, expectBlankPage).then(focusedBC => {
   1058    callback(focusedBC?.window);
   1059  });
   1060 };
   1061 /* eslint-enable mozilla/use-services */
   1062 
   1063 SimpleTest.stripLinebreaksAndWhitespaceAfterTags = function (aString) {
   1064  return aString.replace(/(>\s*(\r\n|\n|\r)*\s*)/gm, ">");
   1065 };
   1066 
   1067 /*
   1068 * `navigator.platform` should include this, when the platform is Windows.
   1069 */
   1070 const kPlatformWindows = "Win";
   1071 
   1072 /*
   1073 * See `SimpleTest.waitForClipboard`.
   1074 */
   1075 const kTextHtmlPrefixClipboardDataWindows =
   1076  "<html><body>\n<!--StartFragment-->";
   1077 
   1078 /*
   1079 * See `SimpleTest.waitForClipboard`.
   1080 */
   1081 const kTextHtmlSuffixClipboardDataWindows =
   1082  "<!--EndFragment-->\n</body>\n</html>";
   1083 
   1084 /**
   1085 * Polls the clipboard waiting for the expected value. A known value different than
   1086 * the expected value is put on the clipboard first (and also polled for) so we
   1087 * can be sure the value we get isn't just the expected value because it was already
   1088 * on the clipboard. This only uses the global clipboard and only for text/plain
   1089 * values.
   1090 *
   1091 * @param {string | Function} aExpectedStringOrValidatorFn
   1092 *        The string value that is expected to be on the clipboard, or a
   1093 *        validator function getting expected clipboard data and returning a bool.
   1094 *        If you specify string value, line breakers in clipboard are treated
   1095 *        as LineFeed.  Therefore, you cannot include CarriageReturn to the
   1096 *        string.
   1097 *        If you specify string value and expect "text/html" data, this wraps
   1098 *        the expected value with `kTextHtmlPrefixClipboardDataWindows` and
   1099 *        `kTextHtmlSuffixClipboardDataWindows` only when it runs on Windows
   1100 *        because they are appended only by nsDataObj.cpp for Windows.
   1101 *        https://searchfox.org/mozilla-central/rev/8f7b017a31326515cb467e69eef1f6c965b4f00e/widget/windows/nsDataObj.cpp#1798-1805,1839-1840,1842
   1102 *        Therefore, you can specify selected (copied) HTML data simply on any
   1103 *        platforms.
   1104 * @param {Function} aSetupFn
   1105 *        A function responsible for setting the clipboard to the expected value,
   1106 *        called after the known value setting succeeds.
   1107 * @param {Function} aSuccessFn
   1108 *        A function called when the expected value is found on the clipboard.
   1109 * @param {Function} aFailureFn
   1110 *        A function called if the expected value isn't found on the clipboard
   1111 *        within 5s. It can also be called if the known value can't be found.
   1112 * @param {string} [aFlavor="text/plain"]
   1113 *        The flavor to look for.
   1114 * @param {number} [aTimeout=5000]
   1115 *        The timeout (in milliseconds) to wait for a clipboard change.
   1116 * @param {boolean} [aExpectFailure=false]
   1117 *        If true, fail if the clipboard contents are modified within the timeout
   1118 *        interval defined by aTimeout.  When aExpectFailure is true, the argument
   1119 *        aExpectedStringOrValidatorFn must be null, as it won't be used.
   1120 * @param {boolean} [aDontInitializeClipboardIfExpectFailure=false]
   1121 *        If aExpectFailure and this is set to true, this does NOT initialize
   1122 *        clipboard with random data before running aSetupFn.
   1123 */
   1124 SimpleTest.waitForClipboard = function (
   1125  aExpectedStringOrValidatorFn,
   1126  aSetupFn,
   1127  aSuccessFn,
   1128  aFailureFn,
   1129  aFlavor,
   1130  aTimeout,
   1131  aExpectFailure,
   1132  aDontInitializeClipboardIfExpectFailure
   1133 ) {
   1134  let promise = SimpleTest.promiseClipboardChange(
   1135    aExpectedStringOrValidatorFn,
   1136    aSetupFn,
   1137    aFlavor,
   1138    aTimeout,
   1139    aExpectFailure,
   1140    aDontInitializeClipboardIfExpectFailure
   1141  );
   1142  promise.then(aSuccessFn).catch(aFailureFn);
   1143 };
   1144 
   1145 /**
   1146 * Promise-oriented version of waitForClipboard.
   1147 */
   1148 SimpleTest.promiseClipboardChange = async function (
   1149  aExpectedStringOrValidatorFn,
   1150  aSetupFn,
   1151  aFlavor,
   1152  aTimeout,
   1153  aExpectFailure,
   1154  aDontInitializeClipboardIfExpectFailure
   1155 ) {
   1156  let requestedFlavor = aFlavor || "text/plain";
   1157 
   1158  // The known value we put on the clipboard before running aSetupFn
   1159  let initialVal = "waitForClipboard-known-value-" + Math.random();
   1160  let preExpectedVal = initialVal;
   1161 
   1162  let inputValidatorFn;
   1163  if (aExpectFailure) {
   1164    // If we expect failure, the aExpectedStringOrValidatorFn should be null
   1165    if (aExpectedStringOrValidatorFn !== null) {
   1166      SimpleTest.ok(
   1167        false,
   1168        "When expecting failure, aExpectedStringOrValidatorFn must be null"
   1169      );
   1170    }
   1171 
   1172    inputValidatorFn = function (aData) {
   1173      return aData != initialVal;
   1174    };
   1175    // Build a default validator function for common string input.
   1176  } else if (typeof aExpectedStringOrValidatorFn == "string") {
   1177    if (aExpectedStringOrValidatorFn.includes("\r")) {
   1178      throw new Error(
   1179        "Use function instead of string to compare raw line breakers in clipboard"
   1180      );
   1181    }
   1182    if (requestedFlavor === "text/html" && navigator.platform.includes("Win")) {
   1183      inputValidatorFn = function (aData) {
   1184        return (
   1185          aData.replace(/\r\n?/g, "\n") ===
   1186          kTextHtmlPrefixClipboardDataWindows +
   1187            aExpectedStringOrValidatorFn +
   1188            kTextHtmlSuffixClipboardDataWindows
   1189        );
   1190      };
   1191    } else {
   1192      inputValidatorFn = function (aData) {
   1193        return aData.replace(/\r\n?/g, "\n") === aExpectedStringOrValidatorFn;
   1194      };
   1195    }
   1196  } else {
   1197    inputValidatorFn = aExpectedStringOrValidatorFn;
   1198  }
   1199 
   1200  let maxPolls = aTimeout ? aTimeout / 100 : 50;
   1201 
   1202  async function putAndVerify(operationFn, validatorFn, flavor, expectFailure) {
   1203    await operationFn();
   1204 
   1205    let data;
   1206    for (let i = 0; i < maxPolls; i++) {
   1207      data = SpecialPowers.getClipboardData(flavor);
   1208      if (validatorFn(data)) {
   1209        // Don't show the success message when waiting for preExpectedVal
   1210        if (preExpectedVal) {
   1211          preExpectedVal = null;
   1212        } else {
   1213          SimpleTest.ok(
   1214            !expectFailure,
   1215            "Clipboard has the given value: '" + data + "'"
   1216          );
   1217        }
   1218 
   1219        return data;
   1220      }
   1221 
   1222      // Wait 100ms and check again.
   1223      await new Promise(resolve => {
   1224        SimpleTest._originalSetTimeout.apply(window, [resolve, 100]);
   1225      });
   1226    }
   1227 
   1228    let errorMsg = `Timed out while polling clipboard for ${
   1229      preExpectedVal ? "initialized" : "requested"
   1230    } data, got: ${
   1231      flavor == "text/plain"
   1232        ? data
   1233            .replaceAll("\\", "\\\\")
   1234            .replaceAll("\t", "\\t")
   1235            .replaceAll("\n", "\\n")
   1236        : data
   1237    }`;
   1238    SimpleTest.ok(expectFailure, errorMsg);
   1239    if (!expectFailure) {
   1240      throw new Error(errorMsg);
   1241    }
   1242    return data;
   1243  }
   1244 
   1245  if (!aExpectFailure || !aDontInitializeClipboardIfExpectFailure) {
   1246    // First we wait for a known value different from the expected one.
   1247    SimpleTest.info(`Initializing clipboard with "${preExpectedVal}"...`);
   1248    await putAndVerify(
   1249      function () {
   1250        SpecialPowers.clipboardCopyString(preExpectedVal);
   1251      },
   1252      function (aData) {
   1253        return aData == preExpectedVal;
   1254      },
   1255      "text/plain",
   1256      false
   1257    );
   1258 
   1259    SimpleTest.info(
   1260      "Succeeded initializing clipboard, start requested things..."
   1261    );
   1262  } else {
   1263    preExpectedVal = null;
   1264  }
   1265 
   1266  return putAndVerify(
   1267    aSetupFn,
   1268    inputValidatorFn,
   1269    requestedFlavor,
   1270    aExpectFailure
   1271  );
   1272 };
   1273 
   1274 /**
   1275 * Wait for a condition for a while (actually up to 3s here).
   1276 *
   1277 * @param {Function} aCond
   1278 *        A function returns the result of the condition
   1279 * @param {Function} aCallback
   1280 *        A function called after the condition is passed or timeout.
   1281 * @param {string} aErrorMsg
   1282 *        The message displayed when the condition failed to pass
   1283 *        before timeout.
   1284 * @param interval
   1285 *        The time interval to poll the condition function. Defaults
   1286 *        to 100ms.
   1287 * @param maxTries
   1288 *        The number of times to poll before giving up and rejecting
   1289 *        if the condition has not yet returned true. Defaults to 30
   1290 *        (~3 seconds for 100ms intervals)
   1291 */
   1292 SimpleTest.waitForCondition = function (
   1293  aCond,
   1294  aCallback,
   1295  aErrorMsg,
   1296  interval = 100,
   1297  maxTries = 30
   1298 ) {
   1299  this.promiseWaitForCondition(aCond, aErrorMsg, interval, maxTries).then(() =>
   1300    aCallback()
   1301  );
   1302 };
   1303 SimpleTest.promiseWaitForCondition = async function (
   1304  aCond,
   1305  aErrorMsg,
   1306  interval = 100,
   1307  maxTries = 30
   1308 ) {
   1309  for (let tries = 0; tries < maxTries; ++tries) {
   1310    // Wait 100ms between checks.
   1311    await new Promise(resolve => {
   1312      SimpleTest._originalSetTimeout.apply(window, [resolve, interval]);
   1313    });
   1314 
   1315    let conditionPassed;
   1316    try {
   1317      conditionPassed = await aCond();
   1318    } catch (e) {
   1319      ok(false, `${e}\n${e.stack}`);
   1320      conditionPassed = false;
   1321    }
   1322    if (conditionPassed) {
   1323      return;
   1324    }
   1325  }
   1326  ok(false, aErrorMsg);
   1327 };
   1328 
   1329 /**
   1330 * Executes a function shortly after the call, but lets the caller continue
   1331 * working (or finish).
   1332 *
   1333 * @param {Function} aFunc
   1334 *        Function to execute soon.
   1335 */
   1336 SimpleTest.executeSoon = function (aFunc) {
   1337  if ("SpecialPowers" in window) {
   1338    return SpecialPowers.executeSoon(aFunc, window);
   1339  }
   1340  setTimeout(aFunc, 0);
   1341  return null; // Avoid warning.
   1342 };
   1343 
   1344 /**
   1345 * Register a cleanup/teardown function (which may be async) to run after all
   1346 * tasks have finished, before running the next test. If async (or the function
   1347 * returns a promise), the framework will wait for the promise/async function
   1348 * to resolve.
   1349 *
   1350 * @param {Function} aFunc
   1351 *        The cleanup/teardown function to run.
   1352 */
   1353 SimpleTest.registerCleanupFunction = function (aFunc) {
   1354  SimpleTest._cleanupFunctions.push(aFunc);
   1355 };
   1356 
   1357 /**
   1358 * Register a cleanup/teardown function (which may be async) to run after the
   1359 * current task has finished, before running the next task. If async (or the
   1360 * function returns a promise), the framework will wait for the promise/async
   1361 * function to resolve.
   1362 *
   1363 * @param {Function} aFunc
   1364 *        The cleanup/teardown function to run.
   1365 */
   1366 SimpleTest.registerCurrentTaskCleanupFunction = function (aFunc) {
   1367  if (!SimpleTest._currentTask) {
   1368    return;
   1369  }
   1370  SimpleTest._currentTask._cleanupFunctions ||= [];
   1371  SimpleTest._currentTask._cleanupFunctions.push(aFunc);
   1372 };
   1373 
   1374 /**
   1375 * Register a cleanup/teardown function (which may be async) to run after each
   1376 * task has finished, before running the next task. If async (or the
   1377 * function returns a promise), the framework will wait for the promise/async
   1378 * function to resolve.
   1379 *
   1380 * @param {Function} aFunc
   1381 *        The cleanup/teardown function to run.
   1382 */
   1383 SimpleTest.registerTaskCleanupFunction = function (aFunc) {
   1384  SimpleTest._taskCleanupFunctions.push(aFunc);
   1385 };
   1386 
   1387 SimpleTest.registerTimeoutFunction = function (aFunc) {
   1388  SimpleTest._timeoutFunctions.push(aFunc);
   1389 };
   1390 
   1391 SimpleTest.testInChaosMode = function () {
   1392  if (SimpleTest._inChaosMode) {
   1393    // It's already enabled for this test, don't enter twice
   1394    return;
   1395  }
   1396  SpecialPowers.DOMWindowUtils.enterChaosMode();
   1397  SimpleTest._inChaosMode = true;
   1398  // increase timeout here as chaosmode is very slow (i.e. 10x)
   1399  // doing 20x as this overwrites anything the tests set
   1400  SimpleTest.requestLongerTimeout(20);
   1401 };
   1402 
   1403 SimpleTest.timeout = async function () {
   1404  for (const func of SimpleTest._timeoutFunctions) {
   1405    await func();
   1406  }
   1407  SimpleTest._timeoutFunctions = [];
   1408 };
   1409 
   1410 SimpleTest.finishWithFailure = function (msg) {
   1411  SimpleTest.ok(false, msg);
   1412  SimpleTest.finish();
   1413 };
   1414 
   1415 /**
   1416 * Finishes the tests. This is automatically called, except when
   1417 * SimpleTest.waitForExplicitFinish() has been invoked.
   1418 */
   1419 SimpleTest.finish = function () {
   1420  if (SimpleTest._alreadyFinished) {
   1421    var err =
   1422      "TEST-UNEXPECTED-FAIL | SimpleTest | this test already called finish!";
   1423    if (parentRunner) {
   1424      parentRunner.structuredLogger.error(err);
   1425    } else {
   1426      dump(err + "\n");
   1427    }
   1428  }
   1429 
   1430  if (SimpleTest.expected == "fail" && SimpleTest.num_failed <= 0) {
   1431    let msg = "We expected at least one failure";
   1432    let test = {
   1433      result: false,
   1434      name: "fail-if condition in manifest",
   1435      diag: msg,
   1436    };
   1437    let successInfo = {
   1438      status: "FAIL",
   1439      expected: "FAIL",
   1440      message: "TEST-KNOWN-FAIL",
   1441    };
   1442    let failureInfo = {
   1443      status: "PASS",
   1444      expected: "FAIL",
   1445      message: "TEST-UNEXPECTED-PASS",
   1446    };
   1447    SimpleTest._logResult(test, successInfo, failureInfo);
   1448    SimpleTest._tests.push(test);
   1449  } else if (usesFailurePatterns()) {
   1450    SimpleTest.expected.forEach(([pat, expected_count], i) => {
   1451      let count = SimpleTest.num_failed[i];
   1452      let diag;
   1453      if (expected_count === null && count == 0) {
   1454        diag = "expected some failures but got none";
   1455      } else if (expected_count !== null && expected_count != count) {
   1456        diag = `expected ${expected_count} failures but got ${count}`;
   1457      } else {
   1458        return;
   1459      }
   1460      let name = pat
   1461        ? `failure pattern \`${pat}\` in this test`
   1462        : "failures in this test";
   1463      let test = { result: false, name, diag };
   1464      let successInfo = {
   1465        status: "PASS",
   1466        expected: "PASS",
   1467        message: "TEST-PASS",
   1468      };
   1469      let failureInfo = {
   1470        status: "FAIL",
   1471        expected: "PASS",
   1472        message: "TEST-UNEXPECTED-FAIL",
   1473      };
   1474      SimpleTest._logResult(test, successInfo, failureInfo);
   1475      SimpleTest._tests.push(test);
   1476    });
   1477  }
   1478 
   1479  SimpleTest._timeoutFunctions = [];
   1480 
   1481  SimpleTest.testsLength = SimpleTest._tests.length;
   1482 
   1483  SimpleTest._alreadyFinished = true;
   1484 
   1485  if (SimpleTest._inChaosMode) {
   1486    SpecialPowers.DOMWindowUtils.leaveChaosMode();
   1487    SimpleTest._inChaosMode = false;
   1488  }
   1489 
   1490  var afterCleanup = async function () {
   1491    SpecialPowers.removeFiles();
   1492 
   1493    if (SpecialPowers.DOMWindowUtils.isTestControllingRefreshes) {
   1494      SimpleTest.ok(false, "test left refresh driver under test control");
   1495      SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
   1496    }
   1497    if (SimpleTest._expectingUncaughtException) {
   1498      SimpleTest.ok(
   1499        false,
   1500        "expectUncaughtException was called but no uncaught exception was detected!"
   1501      );
   1502    }
   1503    if (!SimpleTest._tests.length) {
   1504      SimpleTest.ok(
   1505        false,
   1506        "[SimpleTest.finish()] No checks actually run. " +
   1507          "(You need to call ok(), is(), or similar " +
   1508          "functions at least once.  Make sure you use " +
   1509          "SimpleTest.waitForExplicitFinish() if you need " +
   1510          "it.)"
   1511      );
   1512    }
   1513 
   1514    let workers = await SpecialPowers.registeredServiceWorkers();
   1515    let promise = null;
   1516    if (SimpleTest._expectingRegisteredServiceWorker) {
   1517      if (workers.length === 0) {
   1518        SimpleTest.ok(
   1519          false,
   1520          "This test is expected to leave a service worker registered"
   1521        );
   1522      }
   1523    } else if (workers.length) {
   1524      let FULL_PROFILE_WORKERS_TO_IGNORE = new Set();
   1525      if (parentRunner.conditionedProfile) {
   1526        // Full profile has service workers in the profile, without clearing the
   1527        // profile service workers will be leftover.
   1528        for (const knownWorker of parentRunner.conditionedProfile
   1529          .knownServiceWorkers) {
   1530          FULL_PROFILE_WORKERS_TO_IGNORE.add(knownWorker.scriptSpec);
   1531        }
   1532      } else {
   1533        SimpleTest.ok(
   1534          false,
   1535          "This test left a service worker registered without cleaning it up"
   1536        );
   1537      }
   1538 
   1539      for (let worker of workers) {
   1540        if (FULL_PROFILE_WORKERS_TO_IGNORE.has(worker.scriptSpec)) {
   1541          continue;
   1542        }
   1543        SimpleTest.ok(
   1544          false,
   1545          `Left over worker: ${worker.scriptSpec} (scope: ${worker.scope})`
   1546        );
   1547      }
   1548      promise = SpecialPowers.removeAllServiceWorkerData();
   1549    }
   1550 
   1551    // If we want to wait for removeAllServiceWorkerData to finish, above,
   1552    // there's a small chance that spinning the event loop could cause
   1553    // SpecialPowers and SimpleTest to go away (e.g. if the test did
   1554    // document.open). promise being non-null should be rare (a test would
   1555    // have had to already fail by leaving a service worker around), so
   1556    // limit the chances of the async wait happening to that case.
   1557    function finish() {
   1558      if (parentRunner) {
   1559        /* We're running in an iframe, and the parent has a TestRunner */
   1560        parentRunner.testFinished(SimpleTest._tests);
   1561      }
   1562 
   1563      if (!parentRunner || parentRunner.showTestReport) {
   1564        SpecialPowers.cleanupAllClipboard(window);
   1565        SpecialPowers.flushPermissions(function () {
   1566          SpecialPowers.flushPrefEnv(function () {
   1567            SimpleTest.showReport();
   1568          });
   1569        });
   1570      }
   1571    }
   1572 
   1573    if (promise) {
   1574      promise.then(finish);
   1575    } else {
   1576      finish();
   1577    }
   1578  };
   1579 
   1580  var executeCleanupFunction = function () {
   1581    var func = SimpleTest._cleanupFunctions.pop();
   1582 
   1583    if (!func) {
   1584      afterCleanup();
   1585      return;
   1586    }
   1587 
   1588    var ret;
   1589    try {
   1590      ret = func();
   1591    } catch (ex) {
   1592      SimpleTest.ok(false, "Cleanup function threw exception: " + ex);
   1593    }
   1594 
   1595    if (ret && ret.constructor.name == "Promise") {
   1596      ret.then(executeCleanupFunction, ex =>
   1597        SimpleTest.ok(false, "Cleanup promise rejected: " + ex)
   1598      );
   1599    } else {
   1600      executeCleanupFunction();
   1601    }
   1602  };
   1603 
   1604  executeCleanupFunction();
   1605 
   1606  SpecialPowers.notifyObservers(null, "test-complete");
   1607 };
   1608 
   1609 /**
   1610 * Monitor console output from now until endMonitorConsole is called.
   1611 *
   1612 * Expect to receive all console messages described by the elements of
   1613 * ``msgs``, an array, in the order listed in ``msgs``; each element is an
   1614 * object which may have any number of the following properties:
   1615 *
   1616 *   message, errorMessage, sourceName, sourceLine, category: string or regexp
   1617 *   lineNumber, columnNumber: number
   1618 *   isScriptError, isWarning: boolean
   1619 *
   1620 * Strings, numbers, and booleans must compare equal to the named
   1621 * property of the Nth console message.  Regexps must match.  Any
   1622 * fields present in the message but not in the pattern object are ignored.
   1623 *
   1624 * In addition to the above properties, elements in ``msgs`` may have a ``forbid``
   1625 * boolean property.  When ``forbid`` is true, a failure is logged each time a
   1626 * matching message is received.
   1627 *
   1628 * If ``forbidUnexpectedMsgs`` is true, then the messages received in the console
   1629 * must exactly match the non-forbidden messages in ``msgs``; for each received
   1630 * message not described by the next element in ``msgs``, a failure is logged.  If
   1631 * false, then other non-forbidden messages are ignored, but all expected
   1632 * messages must still be received.
   1633 *
   1634 * After endMonitorConsole is called, ``continuation`` will be called
   1635 * asynchronously.  (Normally, you will want to pass ``SimpleTest.finish`` here.)
   1636 *
   1637 * It is incorrect to use this function in a test which has not called
   1638 * SimpleTest.waitForExplicitFinish.
   1639 */
   1640 SimpleTest.monitorConsole = function (
   1641  continuation,
   1642  msgs,
   1643  forbidUnexpectedMsgs
   1644 ) {
   1645  if (SimpleTest._stopOnLoad) {
   1646    ok(false, "Console monitoring requires use of waitForExplicitFinish.");
   1647  }
   1648 
   1649  function msgMatches(msg, pat) {
   1650    for (var k in pat) {
   1651      if (!(k in msg)) {
   1652        return false;
   1653      }
   1654      if (pat[k] instanceof RegExp && typeof msg[k] === "string") {
   1655        if (!pat[k].test(msg[k])) {
   1656          return false;
   1657        }
   1658      } else if (msg[k] !== pat[k]) {
   1659        return false;
   1660      }
   1661    }
   1662    return true;
   1663  }
   1664 
   1665  var forbiddenMsgs = [];
   1666  var i = 0;
   1667  while (i < msgs.length) {
   1668    let pat = msgs[i];
   1669    if ("forbid" in pat) {
   1670      var forbid = pat.forbid;
   1671      delete pat.forbid;
   1672      if (forbid) {
   1673        forbiddenMsgs.push(pat);
   1674        msgs.splice(i, 1);
   1675        continue;
   1676      }
   1677    }
   1678    i++;
   1679  }
   1680 
   1681  var counter = 0;
   1682  var assertionLabel = JSON.stringify(msgs);
   1683  function listener(msg) {
   1684    if (msg.message === "SENTINEL" && !msg.isScriptError) {
   1685      is(
   1686        counter,
   1687        msgs.length,
   1688        "monitorConsole | number of messages " + assertionLabel
   1689      );
   1690      SimpleTest.executeSoon(continuation);
   1691      return;
   1692    }
   1693    for (let pat of forbiddenMsgs) {
   1694      if (msgMatches(msg, pat)) {
   1695        ok(
   1696          false,
   1697          "monitorConsole | observed forbidden message " + JSON.stringify(msg)
   1698        );
   1699        return;
   1700      }
   1701    }
   1702    if (counter >= msgs.length) {
   1703      var str = "monitorConsole | extra message | " + JSON.stringify(msg);
   1704      if (forbidUnexpectedMsgs) {
   1705        ok(false, str);
   1706      } else {
   1707        info(str);
   1708      }
   1709      return;
   1710    }
   1711    var matches = msgMatches(msg, msgs[counter]);
   1712    if (forbidUnexpectedMsgs) {
   1713      ok(
   1714        matches,
   1715        "monitorConsole | [" + counter + "] must match " + JSON.stringify(msg)
   1716      );
   1717    } else {
   1718      info(
   1719        "monitorConsole | [" +
   1720          counter +
   1721          "] " +
   1722          (matches ? "matched " : "did not match ") +
   1723          JSON.stringify(msg)
   1724      );
   1725    }
   1726    if (matches) {
   1727      counter++;
   1728    }
   1729  }
   1730  SpecialPowers.registerConsoleListener(listener);
   1731 };
   1732 
   1733 /**
   1734 * Stop monitoring console output.
   1735 */
   1736 SimpleTest.endMonitorConsole = function () {
   1737  SpecialPowers.postConsoleSentinel();
   1738 };
   1739 
   1740 /**
   1741 * Run ``testfn`` synchronously, and monitor its console output.
   1742 *
   1743 * ``msgs`` is handled as described above for monitorConsole.
   1744 *
   1745 * After ``testfn`` returns, console monitoring will stop, and ``continuation``
   1746 * will be called asynchronously.
   1747 *
   1748 */
   1749 SimpleTest.expectConsoleMessages = function (testfn, msgs, continuation) {
   1750  SimpleTest.monitorConsole(continuation, msgs);
   1751  testfn();
   1752  SimpleTest.executeSoon(SimpleTest.endMonitorConsole);
   1753 };
   1754 
   1755 /**
   1756 * Wrapper around ``expectConsoleMessages`` for the case where the test has
   1757 * only one ``testfn`` to run.
   1758 */
   1759 SimpleTest.runTestExpectingConsoleMessages = function (testfn, msgs) {
   1760  SimpleTest.waitForExplicitFinish();
   1761  SimpleTest.expectConsoleMessages(testfn, msgs, SimpleTest.finish);
   1762 };
   1763 
   1764 /**
   1765 * Indicates to the test framework that the current test expects one or
   1766 * more crashes (from plugins or IPC documents), and that the minidumps from
   1767 * those crashes should be removed.
   1768 */
   1769 SimpleTest.expectChildProcessCrash = function () {
   1770  if (parentRunner) {
   1771    parentRunner.expectChildProcessCrash();
   1772  }
   1773 };
   1774 
   1775 /**
   1776 * Indicates to the test framework that the next uncaught exception during
   1777 * the test is expected, and should not cause a test failure.
   1778 */
   1779 SimpleTest.expectUncaughtException = function (aExpecting) {
   1780  SimpleTest._expectingUncaughtException =
   1781    aExpecting === void 0 || !!aExpecting;
   1782 };
   1783 
   1784 /**
   1785 * Returns whether the test has indicated that it expects an uncaught exception
   1786 * to occur.
   1787 */
   1788 SimpleTest.isExpectingUncaughtException = function () {
   1789  return SimpleTest._expectingUncaughtException;
   1790 };
   1791 
   1792 /**
   1793 * Indicates to the test framework that all of the uncaught exceptions
   1794 * during the test are known problems that should be fixed in the future,
   1795 * but which should not cause the test to fail currently.
   1796 */
   1797 SimpleTest.ignoreAllUncaughtExceptions = function (aIgnoring) {
   1798  SimpleTest._ignoringAllUncaughtExceptions =
   1799    aIgnoring === void 0 || !!aIgnoring;
   1800 };
   1801 
   1802 /**
   1803 * Returns whether the test has indicated that all uncaught exceptions should be
   1804 * ignored.
   1805 */
   1806 SimpleTest.isIgnoringAllUncaughtExceptions = function () {
   1807  return SimpleTest._ignoringAllUncaughtExceptions;
   1808 };
   1809 
   1810 /**
   1811 * Indicates to the test framework that this test is expected to leave a
   1812 * service worker registered when it finishes.
   1813 */
   1814 SimpleTest.expectRegisteredServiceWorker = function () {
   1815  SimpleTest._expectingRegisteredServiceWorker = true;
   1816 };
   1817 
   1818 /**
   1819 * Resets any state this SimpleTest object has.  This is important for
   1820 * browser chrome mochitests, which reuse the same SimpleTest object
   1821 * across a run.
   1822 */
   1823 SimpleTest.reset = function () {
   1824  SimpleTest._ignoringAllUncaughtExceptions = false;
   1825  SimpleTest._expectingUncaughtException = false;
   1826  SimpleTest._expectingRegisteredServiceWorker = false;
   1827  SimpleTest._bufferedMessages = [];
   1828 };
   1829 
   1830 if (isPrimaryTestWindow) {
   1831  addLoadEvent(function () {
   1832    if (SimpleTest._stopOnLoad) {
   1833      SimpleTest.finish();
   1834    }
   1835  });
   1836 }
   1837 
   1838 //  --------------- Test.Builder/Test.More isDeeply() -----------------
   1839 
   1840 SimpleTest.DNE = { dne: "Does not exist" };
   1841 SimpleTest.LF = "\r\n";
   1842 
   1843 SimpleTest._deepCheck = function (e1, e2, stack, seen) {
   1844  var ok = false;
   1845  if (Object.is(e1, e2)) {
   1846    // Handles identical primitives and references.
   1847    ok = true;
   1848  } else if (
   1849    typeof e1 != "object" ||
   1850    typeof e2 != "object" ||
   1851    e1 === null ||
   1852    e2 === null
   1853  ) {
   1854    // If either argument is a primitive or function, don't consider the arguments the same.
   1855    ok = false;
   1856  } else if (e1 == SimpleTest.DNE || e2 == SimpleTest.DNE) {
   1857    ok = false;
   1858  } else if (SimpleTest.isa(e1, "Array") && SimpleTest.isa(e2, "Array")) {
   1859    ok = SimpleTest._eqArray(e1, e2, stack, seen);
   1860  } else {
   1861    ok = SimpleTest._eqAssoc(e1, e2, stack, seen);
   1862  }
   1863  return ok;
   1864 };
   1865 
   1866 SimpleTest._eqArray = function (a1, a2, stack, seen) {
   1867  // Return if they're the same object.
   1868  if (a1 == a2) {
   1869    return true;
   1870  }
   1871 
   1872  // JavaScript objects have no unique identifiers, so we have to store
   1873  // references to them all in an array, and then compare the references
   1874  // directly. It's slow, but probably won't be much of an issue in
   1875  // practice. Start by making a local copy of the array to as to avoid
   1876  // confusing a reference seen more than once (such as [a, a]) for a
   1877  // circular reference.
   1878  for (var j = 0; j < seen.length; j++) {
   1879    if (seen[j][0] == a1) {
   1880      return seen[j][1] == a2;
   1881    }
   1882  }
   1883 
   1884  // If we get here, we haven't seen a1 before, so store it with reference
   1885  // to a2.
   1886  seen.push([a1, a2]);
   1887 
   1888  var ok = true;
   1889  // Only examines enumerable attributes. Only works for numeric arrays!
   1890  // Associative arrays return 0. So call _eqAssoc() for them, instead.
   1891  var max = Math.max(a1.length, a2.length);
   1892  if (max == 0) {
   1893    return SimpleTest._eqAssoc(a1, a2, stack, seen);
   1894  }
   1895  for (var i = 0; i < max; i++) {
   1896    var e1 = i < a1.length ? a1[i] : SimpleTest.DNE;
   1897    var e2 = i < a2.length ? a2[i] : SimpleTest.DNE;
   1898    stack.push({ type: "Array", idx: i, vals: [e1, e2] });
   1899    ok = SimpleTest._deepCheck(e1, e2, stack, seen);
   1900    if (ok) {
   1901      stack.pop();
   1902    } else {
   1903      break;
   1904    }
   1905  }
   1906  return ok;
   1907 };
   1908 
   1909 SimpleTest._eqAssoc = function (o1, o2, stack, seen) {
   1910  // Return if they're the same object.
   1911  if (o1 == o2) {
   1912    return true;
   1913  }
   1914 
   1915  // JavaScript objects have no unique identifiers, so we have to store
   1916  // references to them all in an array, and then compare the references
   1917  // directly. It's slow, but probably won't be much of an issue in
   1918  // practice. Start by making a local copy of the array to as to avoid
   1919  // confusing a reference seen more than once (such as [a, a]) for a
   1920  // circular reference.
   1921  seen = seen.slice(0);
   1922  for (let j = 0; j < seen.length; j++) {
   1923    if (seen[j][0] == o1) {
   1924      return seen[j][1] == o2;
   1925    }
   1926  }
   1927 
   1928  // If we get here, we haven't seen o1 before, so store it with reference
   1929  // to o2.
   1930  seen.push([o1, o2]);
   1931 
   1932  // They should be of the same class.
   1933 
   1934  var ok = true;
   1935  // Only examines enumerable attributes.
   1936  var o1Size = 0;
   1937  // eslint-disable-next-line no-unused-vars
   1938  for (let i in o1) {
   1939    o1Size++;
   1940  }
   1941  var o2Size = 0;
   1942  // eslint-disable-next-line no-unused-vars
   1943  for (let i in o2) {
   1944    o2Size++;
   1945  }
   1946  var bigger = o1Size > o2Size ? o1 : o2;
   1947  for (let i in bigger) {
   1948    var e1 = i in o1 ? o1[i] : SimpleTest.DNE;
   1949    var e2 = i in o2 ? o2[i] : SimpleTest.DNE;
   1950    stack.push({ type: "Object", idx: i, vals: [e1, e2] });
   1951    ok = SimpleTest._deepCheck(e1, e2, stack, seen);
   1952    if (ok) {
   1953      stack.pop();
   1954    } else {
   1955      break;
   1956    }
   1957  }
   1958  return ok;
   1959 };
   1960 
   1961 SimpleTest._formatStack = function (stack) {
   1962  var variable = "$Foo";
   1963  for (let i = 0; i < stack.length; i++) {
   1964    var entry = stack[i];
   1965    var type = entry.type;
   1966    var idx = entry.idx;
   1967    if (idx != null) {
   1968      if (type == "Array") {
   1969        // Numeric array index.
   1970        variable += "[" + idx + "]";
   1971      } else {
   1972        // Associative array index.
   1973        idx = idx.replace("'", "\\'");
   1974        variable += "['" + idx + "']";
   1975      }
   1976    }
   1977  }
   1978 
   1979  var vals = stack[stack.length - 1].vals.slice(0, 2);
   1980  var vars = [
   1981    variable.replace("$Foo", "got"),
   1982    variable.replace("$Foo", "expected"),
   1983  ];
   1984 
   1985  var out = "Structures begin differing at:" + SimpleTest.LF;
   1986  for (let i = 0; i < vals.length; i++) {
   1987    var val = vals[i];
   1988    if (val === SimpleTest.DNE) {
   1989      val = "Does not exist";
   1990    } else {
   1991      val = repr(val);
   1992    }
   1993    out += vars[i] + " = " + val + SimpleTest.LF;
   1994  }
   1995 
   1996  return "    " + out;
   1997 };
   1998 
   1999 SimpleTest.isDeeply = function (it, as, name) {
   2000  var stack = [{ vals: [it, as] }];
   2001  var seen = [];
   2002  if (SimpleTest._deepCheck(it, as, stack, seen)) {
   2003    SimpleTest.record(true, name);
   2004  } else {
   2005    SimpleTest.record(false, name, SimpleTest._formatStack(stack));
   2006  }
   2007 };
   2008 
   2009 SimpleTest.typeOf = function (object) {
   2010  var c = Object.prototype.toString.apply(object);
   2011  var name = c.substring(8, c.length - 1);
   2012  if (name != "Object") {
   2013    return name;
   2014  }
   2015  // It may be a non-core class. Try to extract the class name from
   2016  // the constructor function. This may not work in all implementations.
   2017  if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) {
   2018    return RegExp.$1;
   2019  }
   2020  // No idea. :-(
   2021  return name;
   2022 };
   2023 
   2024 SimpleTest.isa = function (object, clas) {
   2025  return SimpleTest.typeOf(object) == clas;
   2026 };
   2027 
   2028 // Global symbols:
   2029 var ok = SimpleTest.ok;
   2030 var record = SimpleTest.record;
   2031 var is = SimpleTest.is;
   2032 var isfuzzy = SimpleTest.isfuzzy;
   2033 var isnot = SimpleTest.isnot;
   2034 var todo = SimpleTest.todo;
   2035 var todo_is = SimpleTest.todo_is;
   2036 var todo_isnot = SimpleTest.todo_isnot;
   2037 var isDeeply = SimpleTest.isDeeply;
   2038 var info = SimpleTest.info;
   2039 
   2040 var gOldOnError = window.onerror;
   2041 window.onerror = function simpletestOnerror(
   2042  errorMsg,
   2043  url,
   2044  lineNumber,
   2045  columnNumber,
   2046  originalException
   2047 ) {
   2048  // Log the message.
   2049  // XXX Chrome mochitests sometimes trigger this window.onerror handler,
   2050  // but there are a number of uncaught JS exceptions from those tests.
   2051  // For now, for tests that self identify as having unintentional uncaught
   2052  // exceptions, just dump it so that the error is visible but doesn't cause
   2053  // a test failure.  See bug 652494.
   2054  var isExpected = !!SimpleTest._expectingUncaughtException;
   2055  var message = (isExpected ? "expected " : "") + "uncaught exception";
   2056  var error = errorMsg + " at ";
   2057  try {
   2058    error += originalException.stack;
   2059  } catch (e) {
   2060    // At least use the url+line+column we were given
   2061    error += url + ":" + lineNumber + ":" + columnNumber;
   2062  }
   2063  if (!SimpleTest._ignoringAllUncaughtExceptions) {
   2064    // Don't log if SimpleTest.finish() is already called, it would cause failures
   2065    if (!SimpleTest._alreadyFinished) {
   2066      SimpleTest.record(isExpected, message, error);
   2067    }
   2068    SimpleTest._expectingUncaughtException = false;
   2069  } else {
   2070    SimpleTest.todo(false, message + ": " + error);
   2071  }
   2072  // There is no Components.stack.caller to log. (See bug 511888.)
   2073 
   2074  // Call previous handler.
   2075  if (gOldOnError) {
   2076    try {
   2077      // Ignore return value: always run default handler.
   2078      gOldOnError(errorMsg, url, lineNumber);
   2079    } catch (e) {
   2080      // Log the error.
   2081      SimpleTest.info("Exception thrown by gOldOnError(): " + e);
   2082      // Log its stack.
   2083      if (e.stack) {
   2084        SimpleTest.info("JavaScript error stack:\n" + e.stack);
   2085      }
   2086    }
   2087  }
   2088 
   2089  if (!SimpleTest._stopOnLoad && !isExpected && !SimpleTest._alreadyFinished) {
   2090    // Need to finish() manually here, yet let the test actually end first.
   2091    SimpleTest.executeSoon(SimpleTest.finish);
   2092  }
   2093 };
   2094 
   2095 // Lifted from dom/media/test/manifest.js
   2096 // Make sure to not touch navigator in here, since we want to push prefs that
   2097 // will affect the APIs it exposes, but the set of exposed APIs is determined
   2098 // when Navigator.prototype is created.  So if we touch navigator before pushing
   2099 // the prefs, the APIs it exposes will not take those prefs into account.  We
   2100 // work around this by using a navigator object from a different global for our
   2101 // UA string testing.
   2102 var gAndroidSdk = null;
   2103 function getAndroidSdk() {
   2104  if (gAndroidSdk === null) {
   2105    var iframe = document.documentElement.appendChild(
   2106      document.createElement("iframe")
   2107    );
   2108    iframe.style.display = "none";
   2109    var nav = iframe.contentWindow.navigator;
   2110    if (
   2111      !nav.userAgent.includes("Mobile") &&
   2112      !nav.userAgent.includes("Tablet")
   2113    ) {
   2114      gAndroidSdk = -1;
   2115    } else {
   2116      // See nsSystemInfo.cpp, the getProperty('version') returns different value
   2117      // on each platforms, so we need to distinguish the android platform.
   2118      var versionString = nav.userAgent.includes("Android")
   2119        ? "version"
   2120        : "sdk_version";
   2121      gAndroidSdk = SpecialPowers.Services.sysinfo.getProperty(versionString);
   2122    }
   2123    document.documentElement.removeChild(iframe);
   2124  }
   2125  return gAndroidSdk;
   2126 }
   2127 
   2128 // add_task(generatorFunction):
   2129 // Call `add_task(generatorFunction)` for each separate
   2130 // asynchronous task in a mochitest. Tasks are run consecutively.
   2131 // Before the first task, `SimpleTest.waitForExplicitFinish()`
   2132 // will be called automatically, and after the last task,
   2133 // `SimpleTest.finish()` will be called.
   2134 var add_task = (function () {
   2135  // The list of tasks to run.
   2136  var task_list = [];
   2137  var run_only_this_task = null;
   2138 
   2139  function isGenerator(value) {
   2140    return (
   2141      value && typeof value === "object" && typeof value.next === "function"
   2142    );
   2143  }
   2144 
   2145  // The "add_task" function
   2146  return function (generatorFunction, options = { isSetup: false }) {
   2147    if (task_list.length === 0) {
   2148      // This is the first time add_task has been called.
   2149      // First, confirm that SimpleTest is available.
   2150      if (!SimpleTest) {
   2151        throw new Error("SimpleTest not available.");
   2152      }
   2153      // Don't stop tests until asynchronous tasks are finished.
   2154      SimpleTest.waitForExplicitFinish();
   2155      // Because the client is using add_task for this set of tests,
   2156      // we need to spawn a "master task" that calls each task in succesion.
   2157      // Use setTimeout to ensure the master task runs after the client
   2158      // script finishes.
   2159      setTimeout(function nextTick() {
   2160        // If we are in a HTML document, we should wait for the document
   2161        // to be fully loaded.
   2162        // These checks ensure that we are in an HTML document without
   2163        // throwing TypeError; also I am told that readyState in XUL documents
   2164        // are totally bogus so we don't try to do this there.
   2165        // The readyState of the initial about:blank is stuck at "complete",
   2166        // so check for "about:blank" separately.
   2167        if (
   2168          (typeof window !== "undefined" &&
   2169            typeof HTMLDocument !== "undefined" &&
   2170            window.document instanceof HTMLDocument &&
   2171            window.document.readyState !== "complete") ||
   2172          (typeof window !== "undefined" &&
   2173            window.document.location.href === "about:blank")
   2174        ) {
   2175          setTimeout(nextTick);
   2176          return;
   2177        }
   2178 
   2179        (async () => {
   2180          // Allow for a task to be skipped; we need only use the structured logger
   2181          // for this, whilst deactivating log buffering to ensure that messages
   2182          // are always printed to stdout.
   2183          function skipTask(name) {
   2184            let logger = parentRunner && parentRunner.structuredLogger;
   2185            if (!logger) {
   2186              info("add_task | Skipping test " + name);
   2187              return;
   2188            }
   2189            logger.deactivateBuffering();
   2190            logger.testStatus(SimpleTest._getCurrentTestURL(), name, "SKIP");
   2191            logger.warning("add_task | Skipping test " + name);
   2192            logger.activateBuffering();
   2193          }
   2194 
   2195          // We stop the entire test file at the first exception because this
   2196          // may mean that the state of subsequent tests may be corrupt.
   2197          try {
   2198            for (var task of task_list) {
   2199              SimpleTest._currentTask = task;
   2200              var name = task.name || "";
   2201              if (
   2202                task.__skipMe ||
   2203                (run_only_this_task &&
   2204                  task != run_only_this_task &&
   2205                  !task.isSetup)
   2206              ) {
   2207                skipTask(name);
   2208                continue;
   2209              }
   2210              const taskInfo = action =>
   2211                info(
   2212                  `${
   2213                    task.isSetup ? "add_setup" : "add_task"
   2214                  } | ${action} ${name}`
   2215                );
   2216              taskInfo("Entering");
   2217              let result = await task();
   2218              if (isGenerator(result)) {
   2219                ok(false, "Task returned a generator");
   2220              }
   2221              if (task._cleanupFunctions) {
   2222                for (const fn of task._cleanupFunctions) {
   2223                  await fn();
   2224                }
   2225              }
   2226              for (const fn of SimpleTest._taskCleanupFunctions) {
   2227                await fn();
   2228              }
   2229              taskInfo("Leaving");
   2230              delete SimpleTest._currentTask;
   2231            }
   2232          } catch (ex) {
   2233            try {
   2234              let serializedEx;
   2235              if (
   2236                typeof ex == "string" ||
   2237                (typeof ex == "object" && isErrorOrException(ex))
   2238              ) {
   2239                serializedEx = `${ex}`;
   2240              } else {
   2241                serializedEx = JSON.stringify(ex);
   2242              }
   2243 
   2244              SimpleTest.record(
   2245                false,
   2246                serializedEx,
   2247                "Should not throw any errors",
   2248                ex.stack
   2249              );
   2250            } catch (ex2) {
   2251              SimpleTest.record(
   2252                false,
   2253                "(The exception cannot be converted to string.)",
   2254                "Should not throw any errors",
   2255                ex.stack
   2256              );
   2257            }
   2258          }
   2259          // All tasks are finished.
   2260          SimpleTest.finish();
   2261        })();
   2262      });
   2263    }
   2264    generatorFunction.skip = () => (generatorFunction.__skipMe = true);
   2265    generatorFunction.only = () => (run_only_this_task = generatorFunction);
   2266    // Add the task to the list of tasks to run after
   2267    // the main thread is finished.
   2268    if (options.isSetup) {
   2269      generatorFunction.isSetup = true;
   2270      let lastSetupIndex = task_list.findLastIndex(t => t.isSetup) + 1;
   2271      task_list.splice(lastSetupIndex, 0, generatorFunction);
   2272    } else {
   2273      task_list.push(generatorFunction);
   2274    }
   2275    return generatorFunction;
   2276  };
   2277 })();
   2278 
   2279 // Like add_task, but setup tasks are executed first.
   2280 function add_setup(generatorFunction) {
   2281  return add_task(generatorFunction, { isSetup: true });
   2282 }
   2283 
   2284 // Import Mochia methods in the test scope
   2285 SpecialPowers.Services.scriptloader.loadSubScript(
   2286  "resource://testing-common/Mochia.js",
   2287  this
   2288 );
   2289 
   2290 // Request complete log when using failure patterns so that failure info
   2291 // from infra can be useful.
   2292 if (usesFailurePatterns()) {
   2293  SimpleTest.requestCompleteLog();
   2294 }
   2295 
   2296 addEventListener("message", async event => {
   2297  if (event.data == "SimpleTest:timeout") {
   2298    await SimpleTest.timeout();
   2299    SimpleTest.finish();
   2300  }
   2301 });