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 });