Bug 1892041 - Part 3: Update test exclusions. r=spidermonkey-reviewers,dminor
[gecko.git] / testing / mochitest / browser-test.js
blob1f9798bb368af908667cb8cb321160a48cb2bb66
1 /* -*- js-indent-level: 2; tab-width: 2; indent-tabs-mode: nil -*- */
3 /* eslint-env mozilla/browser-window */
4 /* import-globals-from chrome-harness.js */
5 /* import-globals-from mochitest-e10s-utils.js */
7 // Test timeout (seconds)
8 var gTimeoutSeconds = 45;
9 var gConfig;
11 var { AppConstants } = ChromeUtils.importESModule(
12   "resource://gre/modules/AppConstants.sys.mjs"
15 ChromeUtils.defineESModuleGetters(this, {
16   AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
17 });
19 const SIMPLETEST_OVERRIDES = [
20   "ok",
21   "record",
22   "is",
23   "isnot",
24   "todo",
25   "todo_is",
26   "todo_isnot",
27   "info",
28   "expectAssertions",
29   "requestCompleteLog",
32 setTimeout(testInit, 0);
34 var TabDestroyObserver = {
35   outstanding: new Set(),
36   promiseResolver: null,
38   init() {
39     Services.obs.addObserver(this, "message-manager-close");
40     Services.obs.addObserver(this, "message-manager-disconnect");
41   },
43   destroy() {
44     Services.obs.removeObserver(this, "message-manager-close");
45     Services.obs.removeObserver(this, "message-manager-disconnect");
46   },
48   observe(subject, topic) {
49     if (topic == "message-manager-close") {
50       this.outstanding.add(subject);
51     } else if (topic == "message-manager-disconnect") {
52       this.outstanding.delete(subject);
53       if (!this.outstanding.size && this.promiseResolver) {
54         this.promiseResolver();
55       }
56     }
57   },
59   wait() {
60     if (!this.outstanding.size) {
61       return Promise.resolve();
62     }
64     return new Promise(resolve => {
65       this.promiseResolver = resolve;
66     });
67   },
70 function testInit() {
71   gConfig = readConfig();
73   if (gConfig.testRoot == "browser") {
74     // Make sure to launch the test harness for the first opened window only
75     var prefs = Services.prefs;
76     if (prefs.prefHasUserValue("testing.browserTestHarness.running")) {
77       return;
78     }
80     prefs.setBoolPref("testing.browserTestHarness.running", true);
82     if (prefs.prefHasUserValue("testing.browserTestHarness.timeout")) {
83       gTimeoutSeconds = prefs.getIntPref("testing.browserTestHarness.timeout");
84     }
86     var sstring = Cc["@mozilla.org/supports-string;1"].createInstance(
87       Ci.nsISupportsString
88     );
89     sstring.data = location.search;
91     Services.ww.openWindow(
92       window,
93       "chrome://mochikit/content/browser-harness.xhtml",
94       "browserTest",
95       "chrome,centerscreen,dialog=no,resizable,titlebar,toolbar=no,width=800,height=600",
96       sstring
97     );
98   } else {
99     // This code allows us to redirect without requiring specialpowers for chrome and a11y tests.
100     let messageHandler = function (m) {
101       // eslint-disable-next-line no-undef
102       messageManager.removeMessageListener("chromeEvent", messageHandler);
103       var url = m.json.data;
105       // Window is the [ChromeWindow] for messageManager, so we need content.window
106       // Currently chrome tests are run in a content window instead of a ChromeWindow
107       // eslint-disable-next-line no-undef
108       var webNav = content.window.docShell.QueryInterface(Ci.nsIWebNavigation);
109       let loadURIOptions = {
110         triggeringPrincipal:
111           Services.scriptSecurityManager.getSystemPrincipal(),
112       };
113       webNav.fixupAndLoadURIString(url, loadURIOptions);
114     };
116     var listener =
117       'data:,function doLoad(e) { var data=e.detail&&e.detail.data;removeEventListener("contentEvent", function (e) { doLoad(e); }, false, true);sendAsyncMessage("chromeEvent", {"data":data}); };addEventListener("contentEvent", function (e) { doLoad(e); }, false, true);';
118     // eslint-disable-next-line no-undef
119     messageManager.addMessageListener("chromeEvent", messageHandler);
120     // eslint-disable-next-line no-undef
121     messageManager.loadFrameScript(listener, true);
122   }
123   if (gConfig.e10s) {
124     e10s_init();
126     let processCount = prefs.getIntPref("dom.ipc.processCount", 1);
127     if (processCount > 1) {
128       // Currently starting a content process is slow, to aviod timeouts, let's
129       // keep alive content processes.
130       prefs.setIntPref("dom.ipc.keepProcessesAlive.web", processCount);
131     }
133     Services.mm.loadFrameScript(
134       "chrome://mochikit/content/shutdown-leaks-collector.js",
135       true
136     );
137   } else {
138     // In non-e10s, only run the ShutdownLeaksCollector in the parent process.
139     ChromeUtils.importESModule(
140       "chrome://mochikit/content/ShutdownLeaksCollector.sys.mjs"
141     );
142   }
145 function isGenerator(value) {
146   return value && typeof value === "object" && typeof value.next === "function";
149 function Tester(aTests, structuredLogger, aCallback) {
150   this.structuredLogger = structuredLogger;
151   this.tests = aTests;
152   this.callback = aCallback;
154   this._scriptLoader = Services.scriptloader;
155   this.EventUtils = {};
156   this._scriptLoader.loadSubScript(
157     "chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
158     this.EventUtils
159   );
161   this._scriptLoader.loadSubScript(
162     "chrome://mochikit/content/tests/SimpleTest/AccessibilityUtils.js",
163     // AccessibilityUtils are integrated with EventUtils to perform additional
164     // accessibility checks for certain user interactions (clicks, etc). Load
165     // them into the EventUtils scope here.
166     this.EventUtils
167   );
168   this.AccessibilityUtils = this.EventUtils.AccessibilityUtils;
170   this.AccessibilityUtils.init();
172   // Make sure our SpecialPowers actor is instantiated, in case it was
173   // registered after our DOMWindowCreated event was fired (which it
174   // most likely was).
175   void window.windowGlobalChild.getActor("SpecialPowers");
177   var simpleTestScope = {};
178   this._scriptLoader.loadSubScript(
179     "chrome://mochikit/content/tests/SimpleTest/SimpleTest.js",
180     simpleTestScope
181   );
182   this._scriptLoader.loadSubScript(
183     "chrome://mochikit/content/tests/SimpleTest/MemoryStats.js",
184     simpleTestScope
185   );
186   this._scriptLoader.loadSubScript(
187     "chrome://mochikit/content/chrome-harness.js",
188     simpleTestScope
189   );
190   this.SimpleTest = simpleTestScope.SimpleTest;
192   window.SpecialPowers.SimpleTest = this.SimpleTest;
193   window.SpecialPowers.setAsDefaultAssertHandler();
195   var extensionUtilsScope = {
196     registerCleanupFunction: fn => {
197       this.currentTest.scope.registerCleanupFunction(fn);
198     },
199   };
200   extensionUtilsScope.SimpleTest = this.SimpleTest;
201   this._scriptLoader.loadSubScript(
202     "chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js",
203     extensionUtilsScope
204   );
205   this.ExtensionTestUtils = extensionUtilsScope.ExtensionTestUtils;
207   this.SimpleTest.harnessParameters = gConfig;
209   this.MemoryStats = simpleTestScope.MemoryStats;
210   this.ContentTask = ChromeUtils.importESModule(
211     "resource://testing-common/ContentTask.sys.mjs"
212   ).ContentTask;
213   this.BrowserTestUtils = ChromeUtils.importESModule(
214     "resource://testing-common/BrowserTestUtils.sys.mjs"
215   ).BrowserTestUtils;
216   this.TestUtils = ChromeUtils.importESModule(
217     "resource://testing-common/TestUtils.sys.mjs"
218   ).TestUtils;
219   this.PromiseTestUtils = ChromeUtils.importESModule(
220     "resource://testing-common/PromiseTestUtils.sys.mjs"
221   ).PromiseTestUtils;
222   this.Assert = ChromeUtils.importESModule(
223     "resource://testing-common/Assert.sys.mjs"
224   ).Assert;
225   this.PerTestCoverageUtils = ChromeUtils.importESModule(
226     "resource://testing-common/PerTestCoverageUtils.sys.mjs"
227   ).PerTestCoverageUtils;
229   this.PromiseTestUtils.init();
231   this.SimpleTestOriginal = {};
232   SIMPLETEST_OVERRIDES.forEach(m => {
233     this.SimpleTestOriginal[m] = this.SimpleTest[m];
234   });
236   this._coverageCollector = null;
238   const { XPCOMUtils } = ChromeUtils.importESModule(
239     "resource://gre/modules/XPCOMUtils.sys.mjs"
240   );
242   // Avoid failing tests when XPCOMUtils.defineLazyScriptGetter is used.
243   XPCOMUtils.overrideScriptLoaderForTests({
244     loadSubScript: (url, obj) => {
245       let before = Object.keys(window);
246       try {
247         return this._scriptLoader.loadSubScript(url, obj);
248       } finally {
249         for (let property of Object.keys(window)) {
250           if (
251             !before.includes(property) &&
252             !this._globalProperties.includes(property)
253           ) {
254             this._globalProperties.push(property);
255             this.SimpleTest.info(
256               `Global property added while loading ${url}: ${property}`
257             );
258           }
259         }
260       }
261     },
262     loadSubScriptWithOptions: this._scriptLoader.loadSubScriptWithOptions.bind(
263       this._scriptLoader
264     ),
265   });
267   // ensure the mouse is reset before each test run
268   if (Services.env.exists("MOZ_AUTOMATION")) {
269     this.EventUtils.synthesizeNativeMouseEvent({
270       type: "mousemove",
271       screenX: 1000,
272       screenY: 10,
273     });
274   }
276 Tester.prototype = {
277   EventUtils: {},
278   AccessibilityUtils: {},
279   SimpleTest: {},
280   ContentTask: null,
281   ExtensionTestUtils: null,
282   Assert: null,
284   repeat: 0,
285   a11y_checks: false,
286   runUntilFailure: false,
287   checker: null,
288   currentTestIndex: -1,
289   lastStartTime: null,
290   lastStartTimestamp: null,
291   lastAssertionCount: 0,
292   failuresFromInitialWindowState: 0,
294   get currentTest() {
295     return this.tests[this.currentTestIndex];
296   },
297   get done() {
298     return this.currentTestIndex == this.tests.length - 1 && this.repeat <= 0;
299   },
301   start: function Tester_start() {
302     TabDestroyObserver.init();
304     // if testOnLoad was not called, then gConfig is not defined
305     if (!gConfig) {
306       gConfig = readConfig();
307     }
309     if (gConfig.runUntilFailure) {
310       this.runUntilFailure = true;
311     }
313     if (gConfig.a11y_checks != undefined) {
314       this.a11y_checks = gConfig.a11y_checks;
315     }
317     if (gConfig.repeat) {
318       this.repeat = gConfig.repeat;
319     }
321     if (gConfig.jscovDirPrefix) {
322       let coveragePath = gConfig.jscovDirPrefix;
323       let { CoverageCollector } = ChromeUtils.importESModule(
324         "resource://testing-common/CoverageUtils.sys.mjs"
325       );
326       this._coverageCollector = new CoverageCollector(coveragePath);
327     }
329     this.structuredLogger.info("*** Start BrowserChrome Test Results ***");
330     Services.console.registerListener(this);
331     this._globalProperties = Object.keys(window);
332     this._globalPropertyWhitelist = [
333       "navigator",
334       "constructor",
335       "top",
336       "Application",
337       "__SS_tabsToRestore",
338       "__SSi",
339       "webConsoleCommandController",
340       // Thunderbird
341       "MailMigrator",
342       "SearchIntegration",
343       // lit
344       "reactiveElementVersions",
345       "litHtmlVersions",
346       "litElementVersions",
347     ];
349     this._repeatingTimers = this._getRepeatingTimers();
351     this.PerTestCoverageUtils.beforeTestSync();
353     if (this.tests.length) {
354       this.waitForWindowsReady().then(() => {
355         this.nextTest();
356       });
357     } else {
358       this.finish();
359     }
360   },
362   _getRepeatingTimers() {
363     const kNonRepeatingTimerTypes = [
364       Ci.nsITimer.TYPE_ONE_SHOT,
365       Ci.nsITimer.TYPE_ONE_SHOT_LOW_PRIORITY,
366     ];
367     return Cc["@mozilla.org/timer-manager;1"]
368       .getService(Ci.nsITimerManager)
369       .getTimers()
370       .filter(t => !kNonRepeatingTimerTypes.includes(t.type));
371   },
373   async waitForWindowsReady() {
374     await this.setupDefaultTheme();
375     await new Promise(resolve =>
376       this.waitForGraphicsTestWindowToBeGone(resolve)
377     );
378     await this.promiseMainWindowReady();
379   },
381   async promiseMainWindowReady() {
382     if (window.gBrowserInit) {
383       await window.gBrowserInit.idleTasksFinishedPromise;
384     }
385   },
387   async setupDefaultTheme() {
388     // Developer Edition enables the wrong theme by default. Make sure
389     // the ordinary default theme is enabled.
390     let theme = await AddonManager.getAddonByID("default-theme@mozilla.org");
391     await theme.enable();
392   },
394   waitForGraphicsTestWindowToBeGone(aCallback) {
395     for (let win of Services.wm.getEnumerator(null)) {
396       if (
397         win != window &&
398         !win.closed &&
399         win.document.documentURI ==
400           "chrome://gfxsanity/content/sanityparent.html"
401       ) {
402         this.BrowserTestUtils.domWindowClosed(win).then(aCallback);
403         return;
404       }
405     }
406     // graphics test window is already gone, just call callback immediately
407     aCallback();
408   },
410   waitForWindowsState: function Tester_waitForWindowsState(aCallback) {
411     let timedOut = this.currentTest && this.currentTest.timedOut;
412     // eslint-disable-next-line no-nested-ternary
413     let baseMsg = timedOut
414       ? "Found a {elt} after previous test timed out"
415       : this.currentTest
416       ? "Found an unexpected {elt} at the end of test run"
417       : "Found an unexpected {elt}";
419     // Remove stale tabs
420     if (
421       this.currentTest &&
422       window.gBrowser &&
423       AppConstants.MOZ_APP_NAME != "thunderbird" &&
424       gBrowser.tabs.length > 1
425     ) {
426       let lastURI = "";
427       let lastURIcount = 0;
428       while (gBrowser.tabs.length > 1) {
429         let lastTab = gBrowser.tabs[gBrowser.tabs.length - 1];
430         if (!lastTab.closing) {
431           // Report the stale tab as an error only when they're not closing.
432           // Tests can finish without waiting for the closing tabs.
433           if (lastURI != lastTab.linkedBrowser.currentURI.spec) {
434             lastURI = lastTab.linkedBrowser.currentURI.spec;
435           } else {
436             lastURIcount++;
437             if (lastURIcount >= 3) {
438               this.currentTest.addResult(
439                 new testResult({
440                   name: "terminating browser early - unable to close tabs; skipping remaining tests in folder",
441                   allowFailure: this.currentTest.allowFailure,
442                 })
443               );
444               this.finish();
445             }
446           }
447           this.currentTest.addResult(
448             new testResult({
449               name:
450                 baseMsg.replace("{elt}", "tab") +
451                 ": " +
452                 lastTab.linkedBrowser.currentURI.spec,
453               allowFailure: this.currentTest.allowFailure,
454             })
455           );
456         }
457         gBrowser.removeTab(lastTab);
458       }
459     }
461     // Replace the last tab with a fresh one
462     if (window.gBrowser && AppConstants.MOZ_APP_NAME != "thunderbird") {
463       gBrowser.addTab("about:blank", {
464         skipAnimation: true,
465         triggeringPrincipal:
466           Services.scriptSecurityManager.getSystemPrincipal(),
467       });
468       gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
469       gBrowser.stop();
470     }
472     // Remove stale windows
473     this.structuredLogger.info("checking window state");
474     for (let win of Services.wm.getEnumerator(null)) {
475       let type = win.document.documentElement.getAttribute("windowtype");
476       if (
477         win != window &&
478         !win.closed &&
479         win.document.documentElement.getAttribute("id") !=
480           "browserTestHarness" &&
481         type != "devtools:webconsole"
482       ) {
483         switch (type) {
484           case "navigator:browser":
485             type = "browser window";
486             break;
487           case "mail:3pane":
488             type = "mail window";
489             break;
490           case null:
491             type =
492               "unknown window with document URI: " +
493               win.document.documentURI +
494               " and title: " +
495               win.document.title;
496             break;
497         }
498         let msg = baseMsg.replace("{elt}", type);
499         if (this.currentTest) {
500           this.currentTest.addResult(
501             new testResult({
502               name: msg,
503               allowFailure: this.currentTest.allowFailure,
504             })
505           );
506         } else {
507           this.failuresFromInitialWindowState++;
508           this.structuredLogger.error("browser-test.js | " + msg);
509         }
511         win.close();
512       }
513     }
515     // Make sure the window is raised before each test.
516     this.SimpleTest.waitForFocus(aCallback);
517   },
519   finish: function Tester_finish() {
520     var passCount = this.tests.reduce((a, f) => a + f.passCount, 0);
521     var failCount = this.tests.reduce((a, f) => a + f.failCount, 0);
522     var todoCount = this.tests.reduce((a, f) => a + f.todoCount, 0);
524     // Include failures from window state checking prior to running the first test
525     failCount += this.failuresFromInitialWindowState;
527     TabDestroyObserver.destroy();
528     Services.console.unregisterListener(this);
530     this.AccessibilityUtils.uninit();
532     // It's important to terminate the module to avoid crashes on shutdown.
533     this.PromiseTestUtils.uninit();
535     // In the main process, we print the ShutdownLeaksCollector message here.
536     let pid = Services.appinfo.processID;
537     dump("Completed ShutdownLeaks collections in process " + pid + "\n");
539     this.structuredLogger.info("TEST-START | Shutdown");
541     if (this.tests.length) {
542       let e10sMode = window.gMultiProcessBrowser ? "e10s" : "non-e10s";
543       this.structuredLogger.info("Browser Chrome Test Summary");
544       this.structuredLogger.info("Passed:  " + passCount);
545       this.structuredLogger.info("Failed:  " + failCount);
546       this.structuredLogger.info("Todo:    " + todoCount);
547       this.structuredLogger.info("Mode:    " + e10sMode);
548     } else {
549       this.structuredLogger.error(
550         "browser-test.js | No tests to run. Did you pass invalid test_paths?"
551       );
552     }
553     this.structuredLogger.info("*** End BrowserChrome Test Results ***");
555     // Tests complete, notify the callback and return
556     this.callback(this.tests);
557     this.callback = null;
558     this.tests = null;
559   },
561   haltTests: function Tester_haltTests() {
562     // Do not run any further tests
563     this.currentTestIndex = this.tests.length - 1;
564     this.repeat = 0;
565   },
567   observe: function Tester_observe(aSubject, aTopic) {
568     if (!aTopic) {
569       this.onConsoleMessage(aSubject);
570     }
571   },
573   onConsoleMessage: function Tester_onConsoleMessage(aConsoleMessage) {
574     // Ignore empty messages.
575     if (!aConsoleMessage.message) {
576       return;
577     }
579     try {
580       var msg = "Console message: " + aConsoleMessage.message;
581       if (this.currentTest) {
582         this.currentTest.addResult(new testMessage(msg));
583       } else {
584         this.structuredLogger.info(
585           "TEST-INFO | (browser-test.js) | " + msg.replace(/\n$/, "") + "\n"
586         );
587       }
588     } catch (ex) {
589       // Swallow exception so we don't lead to another error being reported,
590       // throwing us into an infinite loop
591     }
592   },
594   async ensureVsyncDisabled() {
595     // The WebExtension process keeps vsync enabled forever in headless mode.
596     // See bug 1782541.
597     if (Services.env.get("MOZ_HEADLESS")) {
598       return;
599     }
601     try {
602       await this.TestUtils.waitForCondition(
603         () => !ChromeUtils.vsyncEnabled(),
604         "waiting for vsync to be disabled"
605       );
606     } catch (e) {
607       this.Assert.ok(false, e);
608       this.Assert.ok(
609         false,
610         "vsync remained enabled at the end of the test. " +
611           "Is there an animation still running? " +
612           "Consider talking to the performance team for tips to solve this."
613       );
614     }
615   },
617   getNewRepeatingTimers() {
618     let repeatingTimers = this._getRepeatingTimers();
619     let results = [];
620     for (let timer of repeatingTimers) {
621       let { name, delay } = timer;
622       // For now ignore long repeating timers (typically from nsExpirationTracker).
623       if (delay >= 10000) {
624         continue;
625       }
627       // Also ignore the nsAvailableMemoryWatcher timer that is started when the
628       // user-interaction-active notification is fired, and stopped when the
629       // user-interaction-inactive notification occurs.
630       // On Linux it's a 5s timer, on other platforms it's 10s, which is already
631       // ignored by the previous case.
632       if (
633         AppConstants.platform == "linux" &&
634         name == "nsAvailableMemoryWatcher"
635       ) {
636         continue;
637       }
639       // Ignore nsHttpConnectionMgr timers which show up on browser mochitests
640       // running with http3. See Bug 1829841.
641       if (name == "nsHttpConnectionMgr") {
642         continue;
643       }
645       if (
646         !this._repeatingTimers.find(t => t.delay == delay && t.name == name)
647       ) {
648         results.push(timer);
649       }
650     }
651     if (results.length) {
652       ChromeUtils.addProfilerMarker(
653         "NewRepeatingTimers",
654         { category: "Test" },
655         results.map(t => `${t.name}: ${t.delay}ms`).join(", ")
656       );
657     }
658     return results;
659   },
661   async ensureNoNewRepeatingTimers() {
662     let newTimers;
663     try {
664       await this.TestUtils.waitForCondition(
665         async function () {
666           // The array returned by nsITimerManager.getTimers doesn't include
667           // timers that are queued in the event loop of their target thread.
668           // By waiting for a tick, we ensure the timers that might fire about
669           // at the same time as our waitForCondition timer will be included.
670           await this.TestUtils.waitForTick();
672           newTimers = this.getNewRepeatingTimers();
673           return !newTimers.length;
674         }.bind(this),
675         "waiting for new repeating timers to be cancelled"
676       );
677     } catch (e) {
678       this.Assert.ok(false, e);
679       for (let { name, delay } of newTimers) {
680         this.Assert.ok(
681           false,
682           `test left unexpected repeating timer ${name} (duration: ${delay}ms)`
683         );
684       }
685       // Once the new repeating timers have been reported, add them to
686       // this._repeatingTimers to avoid reporting them again for the next
687       // tests of the manifest.
688       this._repeatingTimers.push(...newTimers);
689     }
690   },
692   async nextTest() {
693     if (this.currentTest) {
694       if (this._coverageCollector) {
695         this._coverageCollector.recordTestCoverage(this.currentTest.path);
696       }
698       this.PerTestCoverageUtils.afterTestSync();
700       // Run cleanup functions for the current test before moving on to the
701       // next one.
702       let testScope = this.currentTest.scope;
703       while (testScope.__cleanupFunctions.length) {
704         let func = testScope.__cleanupFunctions.shift();
705         try {
706           let result = await func.apply(testScope);
707           if (isGenerator(result)) {
708             this.SimpleTest.ok(false, "Cleanup function returned a generator");
709           }
710         } catch (ex) {
711           this.currentTest.addResult(
712             new testResult({
713               name: "Cleanup function threw an exception",
714               ex,
715               allowFailure: this.currentTest.allowFailure,
716             })
717           );
718         }
719       }
721       // Spare tests cleanup work.
722       // Reset gReduceMotionOverride in case the test set it.
723       if (typeof gReduceMotionOverride == "boolean") {
724         gReduceMotionOverride = null;
725       }
727       Services.obs.notifyObservers(null, "test-complete");
729       // Ensure to reset the clipboard in case the test has modified it,
730       // so it won't affect the next tests.
731       window.SpecialPowers.clipboardCopyString("");
733       if (
734         this.currentTest.passCount === 0 &&
735         this.currentTest.failCount === 0 &&
736         this.currentTest.todoCount === 0
737       ) {
738         this.currentTest.addResult(
739           new testResult({
740             name:
741               "This test contains no passes, no fails and no todos. Maybe" +
742               " it threw a silent exception? Make sure you use" +
743               " waitForExplicitFinish() if you need it.",
744           })
745         );
746       }
748       let winUtils = window.windowUtils;
749       if (winUtils.isTestControllingRefreshes) {
750         this.currentTest.addResult(
751           new testResult({
752             name: "test left refresh driver under test control",
753           })
754         );
755         winUtils.restoreNormalRefresh();
756       }
758       if (this.SimpleTest.isExpectingUncaughtException()) {
759         this.currentTest.addResult(
760           new testResult({
761             name:
762               "expectUncaughtException was called but no uncaught" +
763               " exception was detected!",
764             allowFailure: this.currentTest.allowFailure,
765           })
766         );
767       }
769       this.resolveFinishTestPromise();
770       this.resolveFinishTestPromise = null;
771       this.TestUtils.promiseTestFinished = null;
773       this.PromiseTestUtils.ensureDOMPromiseRejectionsProcessed();
774       this.PromiseTestUtils.assertNoUncaughtRejections();
775       this.PromiseTestUtils.assertNoMoreExpectedRejections();
776       await this.ensureVsyncDisabled();
778       Object.keys(window).forEach(function (prop) {
779         if (parseInt(prop) == prop) {
780           // This is a string which when parsed as an integer and then
781           // stringified gives the original string.  As in, this is in fact a
782           // string representation of an integer, so an index into
783           // window.frames.  Skip those.
784           return;
785         }
786         if (!this._globalProperties.includes(prop)) {
787           this._globalProperties.push(prop);
788           if (!this._globalPropertyWhitelist.includes(prop)) {
789             this.currentTest.addResult(
790               new testResult({
791                 name: "test left unexpected property on window: " + prop,
792                 allowFailure: this.currentTest.allowFailure,
793               })
794             );
795           }
796         }
797       }, this);
799       await this.ensureNoNewRepeatingTimers();
801       // eslint-disable-next-line no-undef
802       await new Promise(resolve => SpecialPowers.flushPrefEnv(resolve));
804       if (gConfig.cleanupCrashes) {
805         let gdir = Services.dirsvc.get("UAppData", Ci.nsIFile);
806         gdir.append("Crash Reports");
807         gdir.append("pending");
808         if (gdir.exists()) {
809           let entries = gdir.directoryEntries;
810           while (entries.hasMoreElements()) {
811             let entry = entries.nextFile;
812             if (entry.isFile()) {
813               let msg = "this test left a pending crash report; ";
814               try {
815                 entry.remove(false);
816                 msg += "deleted " + entry.path;
817               } catch (e) {
818                 msg += "could not delete " + entry.path;
819               }
820               this.structuredLogger.info(msg);
821             }
822           }
823         }
824       }
826       // Notify a long running test problem if it didn't end up in a timeout.
827       if (this.currentTest.unexpectedTimeouts && !this.currentTest.timedOut) {
828         this.currentTest.addResult(
829           new testResult({
830             name:
831               "This test exceeded the timeout threshold. It should be" +
832               " rewritten or split up. If that's not possible, use" +
833               " requestLongerTimeout(N), but only as a last resort.",
834           })
835         );
836       }
838       // If we're in a debug build, check assertion counts.  This code
839       // is similar to the code in TestRunner.testUnloaded in
840       // TestRunner.js used for all other types of mochitests.
841       let debugsvc = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
842       if (debugsvc.isDebugBuild) {
843         let newAssertionCount = debugsvc.assertionCount;
844         let numAsserts = newAssertionCount - this.lastAssertionCount;
845         this.lastAssertionCount = newAssertionCount;
847         let max = testScope.__expectedMaxAsserts;
848         let min = testScope.__expectedMinAsserts;
849         if (numAsserts > max) {
850           // TEST-UNEXPECTED-FAIL
851           this.currentTest.addResult(
852             new testResult({
853               name:
854                 "Assertion count " +
855                 numAsserts +
856                 " is greater than expected range " +
857                 min +
858                 "-" +
859                 max +
860                 " assertions.",
861               pass: true, // TEMPORARILY TEST-KNOWN-FAIL
862               todo: true,
863               allowFailure: this.currentTest.allowFailure,
864             })
865           );
866         } else if (numAsserts < min) {
867           // TEST-UNEXPECTED-PASS
868           this.currentTest.addResult(
869             new testResult({
870               name:
871                 "Assertion count " +
872                 numAsserts +
873                 " is less than expected range " +
874                 min +
875                 "-" +
876                 max +
877                 " assertions.",
878               todo: true,
879               allowFailure: this.currentTest.allowFailure,
880             })
881           );
882         } else if (numAsserts > 0) {
883           // TEST-KNOWN-FAIL
884           this.currentTest.addResult(
885             new testResult({
886               name:
887                 "Assertion count " +
888                 numAsserts +
889                 " is within expected range " +
890                 min +
891                 "-" +
892                 max +
893                 " assertions.",
894               pass: true,
895               todo: true,
896               allowFailure: this.currentTest.allowFailure,
897             })
898           );
899         }
900       }
902       if (this.currentTest.allowFailure) {
903         if (this.currentTest.expectedAllowedFailureCount) {
904           this.currentTest.addResult(
905             new testResult({
906               name:
907                 "Expected " +
908                 this.currentTest.expectedAllowedFailureCount +
909                 " failures in this file, got " +
910                 this.currentTest.allowedFailureCount +
911                 ".",
912               pass:
913                 this.currentTest.expectedAllowedFailureCount ==
914                 this.currentTest.allowedFailureCount,
915             })
916           );
917         } else if (this.currentTest.allowedFailureCount == 0) {
918           this.currentTest.addResult(
919             new testResult({
920               name:
921                 "We expect at least one assertion to fail because this" +
922                 " test file is marked as fail-if in the manifest.",
923               todo: true,
924               knownFailure: this.currentTest.allowFailure,
925             })
926           );
927         }
928       }
930       // Dump memory stats for main thread.
931       if (
932         Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
933       ) {
934         this.MemoryStats.dump(
935           this.currentTestIndex,
936           this.currentTest.path,
937           gConfig.dumpOutputDirectory,
938           gConfig.dumpAboutMemoryAfterTest,
939           gConfig.dumpDMDAfterTest
940         );
941       }
943       // Note the test run time
944       let name = this.currentTest.path;
945       name = name.slice(name.lastIndexOf("/") + 1);
946       ChromeUtils.addProfilerMarker(
947         "browser-test",
948         { category: "Test", startTime: this.lastStartTimestamp },
949         name
950       );
952       // See if we should upload a profile of a failing test.
953       if (this.currentTest.failCount) {
954         // If MOZ_PROFILER_SHUTDOWN is set, the profiler got started from --profiler
955         // and a profile will be shown even if there's no test failure.
956         if (
957           Services.env.exists("MOZ_UPLOAD_DIR") &&
958           !Services.env.exists("MOZ_PROFILER_SHUTDOWN") &&
959           Services.profiler.IsActive()
960         ) {
961           let filename = `profile_${name}.json`;
962           let path = Services.env.get("MOZ_UPLOAD_DIR");
963           let profilePath = PathUtils.join(path, filename);
964           try {
965             const { profile } =
966               await Services.profiler.getProfileDataAsGzippedArrayBuffer();
967             await IOUtils.write(profilePath, new Uint8Array(profile));
968             this.currentTest.addResult(
969               new testResult({
970                 name:
971                   "Found unexpected failures during the test; profile uploaded in " +
972                   filename,
973               })
974             );
975           } catch (e) {
976             // If the profile is large, we may encounter out of memory errors.
977             this.currentTest.addResult(
978               new testResult({
979                 name:
980                   "Found unexpected failures during the test; failed to upload profile: " +
981                   e,
982               })
983             );
984           }
985         }
986       }
988       let time = Date.now() - this.lastStartTime;
990       this.structuredLogger.testEnd(
991         this.currentTest.path,
992         "OK",
993         undefined,
994         "finished in " + time + "ms"
995       );
996       this.currentTest.setDuration(time);
998       if (this.runUntilFailure && this.currentTest.failCount > 0) {
999         this.haltTests();
1000       }
1002       // Restore original SimpleTest methods to avoid leaks.
1003       SIMPLETEST_OVERRIDES.forEach(m => {
1004         this.SimpleTest[m] = this.SimpleTestOriginal[m];
1005       });
1007       this.ContentTask.setTestScope(null);
1008       testScope.destroy();
1009       this.currentTest.scope = null;
1010     }
1012     // Check the window state for the current test before moving to the next one.
1013     // This also causes us to check before starting any tests, since nextTest()
1014     // is invoked to start the tests.
1015     this.waitForWindowsState(() => {
1016       if (this.done) {
1017         if (this._coverageCollector) {
1018           this._coverageCollector.finalize();
1019         } else if (
1020           !AppConstants.RELEASE_OR_BETA &&
1021           !AppConstants.DEBUG &&
1022           !AppConstants.MOZ_CODE_COVERAGE &&
1023           !AppConstants.ASAN &&
1024           !AppConstants.TSAN
1025         ) {
1026           this.finish();
1027           return;
1028         }
1030         // Uninitialize a few things explicitly so that they can clean up
1031         // frames and browser intentionally kept alive until shutdown to
1032         // eliminate false positives.
1033         if (gConfig.testRoot == "browser") {
1034           // Skip if SeaMonkey
1035           if (AppConstants.MOZ_APP_NAME != "seamonkey") {
1036             // Replace the document currently loaded in the browser's sidebar.
1037             // This will prevent false positives for tests that were the last
1038             // to touch the sidebar. They will thus not be blamed for leaking
1039             // a document.
1040             let sidebar = document.getElementById("sidebar");
1041             if (sidebar) {
1042               sidebar.setAttribute("src", "data:text/html;charset=utf-8,");
1043               sidebar.docShell.createAboutBlankDocumentViewer(null, null);
1044               sidebar.setAttribute("src", "about:blank");
1045             }
1046           }
1048           // Destroy BackgroundPageThumbs resources.
1049           let { BackgroundPageThumbs } = ChromeUtils.importESModule(
1050             "resource://gre/modules/BackgroundPageThumbs.sys.mjs"
1051           );
1052           BackgroundPageThumbs._destroy();
1054           if (window.gBrowser) {
1055             NewTabPagePreloading.removePreloadedBrowser(window);
1056           }
1057         }
1059         // Schedule GC and CC runs before finishing in order to detect
1060         // DOM windows leaked by our tests or the tested code. Note that we
1061         // use a shrinking GC so that the JS engine will discard JIT code and
1062         // JIT caches more aggressively.
1064         let shutdownCleanup = aCallback => {
1065           Cu.schedulePreciseShrinkingGC(() => {
1066             // Run the GC and CC a few times to make sure that as much
1067             // as possible is freed.
1068             let numCycles = 3;
1069             for (let i = 0; i < numCycles; i++) {
1070               Cu.forceGC();
1071               Cu.forceCC();
1072             }
1073             aCallback();
1074           });
1075         };
1077         let { AsyncShutdown } = ChromeUtils.importESModule(
1078           "resource://gre/modules/AsyncShutdown.sys.mjs"
1079         );
1081         let barrier = new AsyncShutdown.Barrier(
1082           "ShutdownLeaks: Wait for cleanup to be finished before checking for leaks"
1083         );
1084         Services.obs.notifyObservers(
1085           { wrappedJSObject: barrier },
1086           "shutdown-leaks-before-check"
1087         );
1089         barrier.client.addBlocker(
1090           "ShutdownLeaks: Wait for tabs to finish closing",
1091           TabDestroyObserver.wait()
1092         );
1094         barrier.wait().then(() => {
1095           // Simulate memory pressure so that we're forced to free more resources
1096           // and thus get rid of more false leaks like already terminated workers.
1097           Services.obs.notifyObservers(
1098             null,
1099             "memory-pressure",
1100             "heap-minimize"
1101           );
1103           Services.ppmm.broadcastAsyncMessage("browser-test:collect-request");
1105           shutdownCleanup(() => {
1106             setTimeout(() => {
1107               shutdownCleanup(() => {
1108                 this.finish();
1109               });
1110             }, 1000);
1111           });
1112         });
1114         return;
1115       }
1117       if (this.repeat > 0) {
1118         --this.repeat;
1119         if (this.currentTestIndex < 0) {
1120           this.currentTestIndex = 0;
1121         }
1122         this.execTest();
1123       } else {
1124         this.currentTestIndex++;
1125         if (gConfig.repeat) {
1126           this.repeat = gConfig.repeat;
1127         }
1128         this.execTest();
1129       }
1130     });
1131   },
1133   async handleTask(task, currentTest, PromiseTestUtils, isSetup = false) {
1134     let currentScope = currentTest.scope;
1135     let desc = isSetup ? "setup" : "test";
1136     currentScope.SimpleTest.info(`Entering ${desc} ${task.name}`);
1137     let startTimestamp = performance.now();
1138     try {
1139       let result = await task();
1140       if (isGenerator(result)) {
1141         currentScope.SimpleTest.ok(false, "Task returned a generator");
1142       }
1143     } catch (ex) {
1144       if (currentTest.timedOut) {
1145         currentTest.addResult(
1146           new testResult({
1147             name: `Uncaught exception received from previously timed out ${desc} ${task.name}`,
1148             pass: false,
1149             ex,
1150             stack: typeof ex == "object" && "stack" in ex ? ex.stack : null,
1151             allowFailure: currentTest.allowFailure,
1152           })
1153         );
1154         // We timed out, so we've already cleaned up for this test, just get outta here.
1155         return;
1156       }
1157       currentTest.addResult(
1158         new testResult({
1159           name: `Uncaught exception in ${desc} ${task.name}`,
1160           pass: currentScope.SimpleTest.isExpectingUncaughtException(),
1161           ex,
1162           stack: typeof ex == "object" && "stack" in ex ? ex.stack : null,
1163           allowFailure: currentTest.allowFailure,
1164         })
1165       );
1166     }
1167     PromiseTestUtils.assertNoUncaughtRejections();
1168     ChromeUtils.addProfilerMarker(
1169       isSetup ? "setup-task" : "task",
1170       { category: "Test", startTime: startTimestamp },
1171       task.name.replace(/^bound /, "") || undefined
1172     );
1173     currentScope.SimpleTest.info(`Leaving ${desc} ${task.name}`);
1174   },
1176   async _runTaskBasedTest(currentTest) {
1177     let currentScope = currentTest.scope;
1179     // First run all the setups:
1180     let setupFn;
1181     while ((setupFn = currentScope.__setups.shift())) {
1182       await this.handleTask(
1183         setupFn,
1184         currentTest,
1185         this.PromiseTestUtils,
1186         true /* is setup task */
1187       );
1188     }
1190     // Allow for a task to be skipped; we need only use the structured logger
1191     // for this, whilst deactivating log buffering to ensure that messages
1192     // are always printed to stdout.
1193     let skipTask = task => {
1194       let logger = this.structuredLogger;
1195       logger.deactivateBuffering();
1196       logger.testStatus(this.currentTest.path, task.name, "SKIP");
1197       logger.warning("Skipping test " + task.name);
1198       logger.activateBuffering();
1199     };
1201     let task;
1202     while ((task = currentScope.__tasks.shift())) {
1203       if (
1204         task.__skipMe ||
1205         (currentScope.__runOnlyThisTask &&
1206           task != currentScope.__runOnlyThisTask)
1207       ) {
1208         skipTask(task);
1209         continue;
1210       }
1211       await this.handleTask(task, currentTest, this.PromiseTestUtils);
1212     }
1213     currentScope.finish();
1214   },
1216   execTest: function Tester_execTest() {
1217     this.structuredLogger.testStart(this.currentTest.path);
1219     this.SimpleTest.reset();
1220     // Reset accessibility environment.
1221     this.AccessibilityUtils.reset(this.a11y_checks, this.currentTest.path);
1223     // Load the tests into a testscope
1224     let currentScope = (this.currentTest.scope = new testScope(
1225       this,
1226       this.currentTest,
1227       this.currentTest.expected
1228     ));
1229     let currentTest = this.currentTest;
1231     // HTTPS-First (Bug 1704453) TODO: in case a test is annoated
1232     // with https_first_disabled then we explicitly flip the pref
1233     // dom.security.https_first to false for the duration of the test.
1234     if (currentTest.https_first_disabled) {
1235       window.SpecialPowers.pushPrefEnv({
1236         set: [["dom.security.https_first", false]],
1237       });
1238     }
1240     if (currentTest.allow_xul_xbl) {
1241       window.SpecialPowers.pushPermissions([
1242         { type: "allowXULXBL", allow: true, context: "http://mochi.test:8888" },
1243         { type: "allowXULXBL", allow: true, context: "http://example.org" },
1244       ]);
1245     }
1247     // Import utils in the test scope.
1248     let { scope } = this.currentTest;
1249     scope.EventUtils = this.EventUtils;
1250     scope.AccessibilityUtils = this.AccessibilityUtils;
1251     scope.SimpleTest = this.SimpleTest;
1252     scope.gTestPath = this.currentTest.path;
1253     scope.ContentTask = this.ContentTask;
1254     scope.BrowserTestUtils = this.BrowserTestUtils;
1255     scope.TestUtils = this.TestUtils;
1256     scope.ExtensionTestUtils = this.ExtensionTestUtils;
1257     // Pass a custom report function for mochitest style reporting.
1258     scope.Assert = new this.Assert(function (err, message, stack) {
1259       currentTest.addResult(
1260         new testResult(
1261           err
1262             ? {
1263                 name: err.message,
1264                 ex: err.stack,
1265                 stack: err.stack,
1266                 allowFailure: currentTest.allowFailure,
1267               }
1268             : {
1269                 name: message,
1270                 pass: true,
1271                 stack,
1272                 allowFailure: currentTest.allowFailure,
1273               }
1274         )
1275       );
1276     }, true);
1278     this.ContentTask.setTestScope(currentScope);
1280     // Allow Assert.sys.mjs methods to be tacked to the current scope.
1281     scope.export_assertions = function () {
1282       for (let func in this.Assert) {
1283         this[func] = this.Assert[func].bind(this.Assert);
1284       }
1285     };
1287     // Override SimpleTest methods with ours.
1288     SIMPLETEST_OVERRIDES.forEach(function (m) {
1289       this.SimpleTest[m] = this[m];
1290     }, scope);
1292     // load the tools to work with chrome .jar and remote
1293     try {
1294       this._scriptLoader.loadSubScript(
1295         "chrome://mochikit/content/chrome-harness.js",
1296         scope
1297       );
1298     } catch (ex) {
1299       /* no chrome-harness tools */
1300     }
1302     // Ensure we are not idle at the beginning of the test. If we don't do this,
1303     // the browser may behave differently if the previous tests ran long.
1304     // eg. the session store behavior changes 3 minutes after the last user event.
1305     Cc["@mozilla.org/widget/useridleservice;1"]
1306       .getService(Ci.nsIUserIdleServiceInternal)
1307       .resetIdleTimeOut(0);
1309     // Import head.js script if it exists.
1310     var currentTestDirPath = this.currentTest.path.substr(
1311       0,
1312       this.currentTest.path.lastIndexOf("/")
1313     );
1314     var headPath = currentTestDirPath + "/head.js";
1315     try {
1316       this._scriptLoader.loadSubScript(headPath, scope);
1317     } catch (ex) {
1318       // Bug 755558 - Ignore loadSubScript errors due to a missing head.js.
1319       const isImportError = /^Error opening input stream/.test(ex.toString());
1321       // Bug 1503169 - head.js may call loadSubScript, and generate similar errors.
1322       // Only swallow errors that are strictly related to loading head.js.
1323       const containsHeadPath = ex.toString().includes(headPath);
1325       if (!isImportError || !containsHeadPath) {
1326         this.currentTest.addResult(
1327           new testResult({
1328             name: "head.js import threw an exception",
1329             ex,
1330           })
1331         );
1332       }
1333     }
1335     // Import the test script.
1336     try {
1337       this.lastStartTimestamp = performance.now();
1338       this.TestUtils.promiseTestFinished = new Promise(resolve => {
1339         this.resolveFinishTestPromise = resolve;
1340       });
1341       this._scriptLoader.loadSubScript(this.currentTest.path, scope);
1342       // Run the test
1343       this.lastStartTime = Date.now();
1344       if (this.currentTest.scope.__tasks) {
1345         // This test consists of tasks, added via the `add_task()` API.
1346         if ("test" in this.currentTest.scope) {
1347           throw new Error(
1348             "Cannot run both a add_task test and a normal test at the same time."
1349           );
1350         }
1351         // Spin off the async work without waiting for it to complete.
1352         // It'll call finish() when it's done.
1353         this._runTaskBasedTest(this.currentTest);
1354       } else if (typeof scope.test == "function") {
1355         scope.test();
1356       } else {
1357         throw new Error(
1358           "This test didn't call add_task, nor did it define a generatorTest() function, nor did it define a test() function, so we don't know how to run it."
1359         );
1360       }
1361     } catch (ex) {
1362       if (!this.SimpleTest.isIgnoringAllUncaughtExceptions()) {
1363         this.currentTest.addResult(
1364           new testResult({
1365             name: "Exception thrown",
1366             pass: this.SimpleTest.isExpectingUncaughtException(),
1367             ex,
1368             allowFailure: this.currentTest.allowFailure,
1369           })
1370         );
1371         this.SimpleTest.expectUncaughtException(false);
1372       } else {
1373         this.currentTest.addResult(new testMessage("Exception thrown: " + ex));
1374       }
1375       this.currentTest.scope.finish();
1376     }
1378     // If the test ran synchronously, move to the next test, otherwise the test
1379     // will trigger the next test when it is done.
1380     if (this.currentTest.scope.__done) {
1381       this.nextTest();
1382     } else {
1383       var self = this;
1384       var timeoutExpires = Date.now() + gTimeoutSeconds * 1000;
1385       var waitUntilAtLeast = timeoutExpires - 1000;
1386       this.currentTest.scope.__waitTimer =
1387         this.SimpleTest._originalSetTimeout.apply(window, [
1388           function timeoutFn() {
1389             // We sometimes get woken up long before the gTimeoutSeconds
1390             // have elapsed (when running in chaos mode for example). This
1391             // code ensures that we don't wrongly time out in that case.
1392             if (Date.now() < waitUntilAtLeast) {
1393               self.currentTest.scope.__waitTimer = setTimeout(
1394                 timeoutFn,
1395                 timeoutExpires - Date.now()
1396               );
1397               return;
1398             }
1400             if (--self.currentTest.scope.__timeoutFactor > 0) {
1401               // We were asked to wait a bit longer.
1402               self.currentTest.scope.info(
1403                 "Longer timeout required, waiting longer...  Remaining timeouts: " +
1404                   self.currentTest.scope.__timeoutFactor
1405               );
1406               self.currentTest.scope.__waitTimer = setTimeout(
1407                 timeoutFn,
1408                 gTimeoutSeconds * 1000
1409               );
1410               return;
1411             }
1413             // If the test is taking longer than expected, but it's not hanging,
1414             // mark the fact, but let the test continue.  At the end of the test,
1415             // if it didn't timeout, we will notify the problem through an error.
1416             // To figure whether it's an actual hang, compare the time of the last
1417             // result or message to half of the timeout time.
1418             // Though, to protect against infinite loops, limit the number of times
1419             // we allow the test to proceed.
1420             const MAX_UNEXPECTED_TIMEOUTS = 10;
1421             if (
1422               Date.now() - self.currentTest.lastOutputTime <
1423                 (gTimeoutSeconds / 2) * 1000 &&
1424               ++self.currentTest.unexpectedTimeouts <= MAX_UNEXPECTED_TIMEOUTS
1425             ) {
1426               self.currentTest.scope.__waitTimer = setTimeout(
1427                 timeoutFn,
1428                 gTimeoutSeconds * 1000
1429               );
1430               return;
1431             }
1433             let knownFailure = false;
1434             if (gConfig.timeoutAsPass) {
1435               knownFailure = true;
1436             }
1437             self.currentTest.addResult(
1438               new testResult({
1439                 name: "Test timed out",
1440                 allowFailure: knownFailure,
1441               })
1442             );
1443             self.currentTest.timedOut = true;
1444             self.currentTest.scope.__waitTimer = null;
1445             if (gConfig.timeoutAsPass) {
1446               self.nextTest();
1447             } else {
1448               self.finish();
1449             }
1450           },
1451           gTimeoutSeconds * 1000,
1452         ]);
1453     }
1454   },
1456   QueryInterface: ChromeUtils.generateQI(["nsIConsoleListener"]),
1459 // Note: duplicated in SimpleTest.js . See also bug 1820150.
1460 function isErrorOrException(err) {
1461   // It'd be nice if we had either `Error.isError(err)` or `Error.isInstance(err)`
1462   // but we don't, so do it ourselves:
1463   if (!err) {
1464     return false;
1465   }
1466   if (err instanceof Ci.nsIException) {
1467     return true;
1468   }
1469   try {
1470     let glob = Cu.getGlobalForObject(err);
1471     return err instanceof glob.Error;
1472   } catch {
1473     // getGlobalForObject can be upset if it doesn't get passed an object.
1474     // Just do a standard instanceof check using this global and cross fingers:
1475   }
1476   return err instanceof Error;
1480  * Represents the result of one test assertion. This is described with a string
1481  * in traditional logging, and has a "status" and "expected" property used in
1482  * structured logging. Normally, results are mapped as follows:
1484  *   pass:    todo:    Added to:    Described as:           Status:  Expected:
1485  *     true     false    passCount    TEST-PASS               PASS     PASS
1486  *     true     true     todoCount    TEST-KNOWN-FAIL         FAIL     FAIL
1487  *     false    false    failCount    TEST-UNEXPECTED-FAIL    FAIL     PASS
1488  *     false    true     failCount    TEST-UNEXPECTED-PASS    PASS     FAIL
1490  * The "allowFailure" argument indicates that this is one of the assertions that
1491  * should be allowed to fail, for example because "fail-if" is true for the
1492  * current test file in the manifest. In this case, results are mapped this way:
1494  *   pass:    todo:    Added to:    Described as:           Status:  Expected:
1495  *     true     false    passCount    TEST-PASS               PASS     PASS
1496  *     true     true     todoCount    TEST-KNOWN-FAIL         FAIL     FAIL
1497  *     false    false    todoCount    TEST-KNOWN-FAIL         FAIL     FAIL
1498  *     false    true     todoCount    TEST-KNOWN-FAIL         FAIL     FAIL
1499  */
1500 function testResult({ name, pass, todo, ex, stack, allowFailure }) {
1501   this.info = false;
1502   this.name = name;
1503   this.msg = "";
1505   if (allowFailure && !pass) {
1506     this.allowedFailure = true;
1507     this.pass = true;
1508     this.todo = false;
1509   } else if (allowFailure && pass) {
1510     this.pass = true;
1511     this.todo = false;
1512   } else {
1513     this.pass = !!pass;
1514     this.todo = todo;
1515   }
1517   this.expected = this.todo ? "FAIL" : "PASS";
1519   if (this.pass) {
1520     this.status = this.expected;
1521     return;
1522   }
1524   this.status = this.todo ? "PASS" : "FAIL";
1526   if (ex) {
1527     if (typeof ex == "object" && "fileName" in ex) {
1528       // we have an exception - print filename and linenumber information
1529       this.msg += "at " + ex.fileName + ":" + ex.lineNumber + " - ";
1530     }
1532     if (
1533       typeof ex == "string" ||
1534       (typeof ex == "object" && isErrorOrException(ex))
1535     ) {
1536       this.msg += String(ex);
1537     } else {
1538       try {
1539         this.msg += JSON.stringify(ex);
1540       } catch {
1541         this.msg += String(ex);
1542       }
1543     }
1544   }
1546   if (stack) {
1547     this.msg += "\nStack trace:\n";
1548     let normalized;
1549     if (stack instanceof Ci.nsIStackFrame) {
1550       let frames = [];
1551       for (
1552         let frame = stack;
1553         frame;
1554         frame = frame.asyncCaller || frame.caller
1555       ) {
1556         let msg = `${frame.filename}:${frame.name}:${frame.lineNumber}`;
1557         frames.push(frame.asyncCause ? `${frame.asyncCause}*${msg}` : msg);
1558       }
1559       normalized = frames.join("\n");
1560     } else {
1561       normalized = "" + stack;
1562     }
1563     this.msg += normalized;
1564   }
1566   if (gConfig.debugOnFailure) {
1567     // You've hit this line because you requested to break into the
1568     // debugger upon a testcase failure on your test run.
1569     // eslint-disable-next-line no-debugger
1570     debugger;
1571   }
1574 function testMessage(msg) {
1575   this.msg = msg || "";
1576   this.info = true;
1579 // Need to be careful adding properties to this object, since its properties
1580 // cannot conflict with global variables used in tests.
1581 function testScope(aTester, aTest, expected) {
1582   this.__tester = aTester;
1584   aTest.allowFailure = expected == "fail";
1586   var self = this;
1587   this.ok = function test_ok(condition, name) {
1588     if (arguments.length > 2) {
1589       const ex = "Too many arguments passed to ok(condition, name)`.";
1590       self.record(false, name, ex);
1591     } else {
1592       self.record(condition, name);
1593     }
1594   };
1595   this.record = function test_record(condition, name, ex, stack, expected) {
1596     if (expected == "fail") {
1597       aTest.addResult(
1598         new testResult({
1599           name,
1600           pass: !condition,
1601           todo: true,
1602           ex,
1603           stack: stack || Components.stack.caller,
1604           allowFailure: aTest.allowFailure,
1605         })
1606       );
1607     } else {
1608       aTest.addResult(
1609         new testResult({
1610           name,
1611           pass: condition,
1612           ex,
1613           stack: stack || Components.stack.caller,
1614           allowFailure: aTest.allowFailure,
1615         })
1616       );
1617     }
1618   };
1619   this.is = function test_is(a, b, name) {
1620     self.record(
1621       Object.is(a, b),
1622       name,
1623       `Got ${self.repr(a)}, expected ${self.repr(b)}`,
1624       false,
1625       Components.stack.caller
1626     );
1627   };
1628   this.isfuzzy = function test_isfuzzy(a, b, epsilon, name) {
1629     self.record(
1630       a >= b - epsilon && a <= b + epsilon,
1631       name,
1632       `Got ${self.repr(a)}, expected ${self.repr(b)} epsilon: +/- ${self.repr(
1633         epsilon
1634       )}`,
1635       false,
1636       Components.stack.caller
1637     );
1638   };
1639   this.isnot = function test_isnot(a, b, name) {
1640     self.record(
1641       !Object.is(a, b),
1642       name,
1643       `Didn't expect ${self.repr(a)}, but got it`,
1644       false,
1645       Components.stack.caller
1646     );
1647   };
1648   this.todo = function test_todo(condition, name, ex, stack) {
1649     aTest.addResult(
1650       new testResult({
1651         name,
1652         pass: !condition,
1653         todo: true,
1654         ex,
1655         stack: stack || Components.stack.caller,
1656         allowFailure: aTest.allowFailure,
1657       })
1658     );
1659   };
1660   this.todo_is = function test_todo_is(a, b, name) {
1661     self.todo(
1662       Object.is(a, b),
1663       name,
1664       `Got ${self.repr(a)}, expected ${self.repr(b)}`,
1665       Components.stack.caller
1666     );
1667   };
1668   this.todo_isnot = function test_todo_isnot(a, b, name) {
1669     self.todo(
1670       !Object.is(a, b),
1671       name,
1672       `Didn't expect ${self.repr(a)}, but got it`,
1673       Components.stack.caller
1674     );
1675   };
1676   this.info = function test_info(name) {
1677     aTest.addResult(new testMessage(name));
1678   };
1679   this.repr = function repr(o) {
1680     if (typeof o == "undefined") {
1681       return "undefined";
1682     } else if (o === null) {
1683       return "null";
1684     }
1685     try {
1686       if (typeof o.__repr__ == "function") {
1687         return o.__repr__();
1688       } else if (typeof o.repr == "function" && o.repr != repr) {
1689         return o.repr();
1690       }
1691     } catch (e) {}
1692     try {
1693       if (
1694         typeof o.NAME == "string" &&
1695         (o.toString == Function.prototype.toString ||
1696           o.toString == Object.prototype.toString)
1697       ) {
1698         return o.NAME;
1699       }
1700     } catch (e) {}
1701     var ostring;
1702     try {
1703       if (Object.is(o, +0)) {
1704         ostring = "+0";
1705       } else if (Object.is(o, -0)) {
1706         ostring = "-0";
1707       } else if (typeof o === "string") {
1708         ostring = JSON.stringify(o);
1709       } else if (Array.isArray(o)) {
1710         ostring = "[" + o.map(val => repr(val)).join(", ") + "]";
1711       } else {
1712         ostring = String(o);
1713       }
1714     } catch (e) {
1715       return `[${Object.prototype.toString.call(o)}]`;
1716     }
1717     if (typeof o == "function") {
1718       ostring = ostring.replace(/\) \{[^]*/, ") { ... }");
1719     }
1720     return ostring;
1721   };
1723   this.executeSoon = function test_executeSoon(func) {
1724     Services.tm.dispatchToMainThread({
1725       run() {
1726         func();
1727       },
1728     });
1729   };
1731   this.waitForExplicitFinish = function test_waitForExplicitFinish() {
1732     self.__done = false;
1733   };
1735   this.waitForFocus = function test_waitForFocus(
1736     callback,
1737     targetWindow,
1738     expectBlankPage
1739   ) {
1740     self.SimpleTest.waitForFocus(callback, targetWindow, expectBlankPage);
1741   };
1743   this.waitForClipboard = function test_waitForClipboard(
1744     expected,
1745     setup,
1746     success,
1747     failure,
1748     flavor
1749   ) {
1750     self.SimpleTest.waitForClipboard(expected, setup, success, failure, flavor);
1751   };
1753   this.registerCleanupFunction = function test_registerCleanupFunction(
1754     aFunction
1755   ) {
1756     self.__cleanupFunctions.push(aFunction);
1757   };
1759   this.requestLongerTimeout = function test_requestLongerTimeout(aFactor) {
1760     self.__timeoutFactor = aFactor;
1761   };
1763   this.expectUncaughtException = function test_expectUncaughtException(
1764     aExpecting
1765   ) {
1766     self.SimpleTest.expectUncaughtException(aExpecting);
1767   };
1769   this.ignoreAllUncaughtExceptions = function test_ignoreAllUncaughtExceptions(
1770     aIgnoring
1771   ) {
1772     self.SimpleTest.ignoreAllUncaughtExceptions(aIgnoring);
1773   };
1775   this.expectAssertions = function test_expectAssertions(aMin, aMax) {
1776     let min = aMin;
1777     let max = aMax;
1778     if (typeof max == "undefined") {
1779       max = min;
1780     }
1781     if (
1782       typeof min != "number" ||
1783       typeof max != "number" ||
1784       min < 0 ||
1785       max < min
1786     ) {
1787       throw new Error("bad parameter to expectAssertions");
1788     }
1789     self.__expectedMinAsserts = min;
1790     self.__expectedMaxAsserts = max;
1791   };
1793   this.setExpectedFailuresForSelfTest =
1794     function test_setExpectedFailuresForSelfTest(expectedAllowedFailureCount) {
1795       aTest.allowFailure = true;
1796       aTest.expectedAllowedFailureCount = expectedAllowedFailureCount;
1797     };
1799   this.finish = function test_finish() {
1800     self.__done = true;
1801     if (self.__waitTimer) {
1802       self.executeSoon(function () {
1803         if (self.__done && self.__waitTimer) {
1804           clearTimeout(self.__waitTimer);
1805           self.__waitTimer = null;
1806           self.__tester.nextTest();
1807         }
1808       });
1809     }
1810   };
1812   this.requestCompleteLog = function test_requestCompleteLog() {
1813     self.__tester.structuredLogger.deactivateBuffering();
1814     self.registerCleanupFunction(function () {
1815       self.__tester.structuredLogger.activateBuffering();
1816     });
1817   };
1819   return this;
1822 function decorateTaskFn(fn) {
1823   fn = fn.bind(this);
1824   fn.skip = (val = true) => (fn.__skipMe = val);
1825   fn.only = () => (this.__runOnlyThisTask = fn);
1826   return fn;
1829 testScope.prototype = {
1830   __done: true,
1831   __tasks: null,
1832   __setups: [],
1833   __runOnlyThisTask: null,
1834   __waitTimer: null,
1835   __cleanupFunctions: [],
1836   __timeoutFactor: 1,
1837   __expectedMinAsserts: 0,
1838   __expectedMaxAsserts: 0,
1840   EventUtils: {},
1841   AccessibilityUtils: {},
1842   SimpleTest: {},
1843   ContentTask: null,
1844   BrowserTestUtils: null,
1845   TestUtils: null,
1846   ExtensionTestUtils: null,
1847   Assert: null,
1849   /**
1850    * Add a function which returns a promise (usually an async function)
1851    * as a test task.
1852    *
1853    * The task ends when the promise returned by the function resolves or
1854    * rejects. If the test function throws, or the promise it returns
1855    * rejects, the test is reported as a failure. Execution continues
1856    * with the next test function.
1857    *
1858    * Example usage:
1859    *
1860    * add_task(async function test() {
1861    *   let result = await Promise.resolve(true);
1862    *
1863    *   ok(result);
1864    *
1865    *   let secondary = await someFunctionThatReturnsAPromise(result);
1866    *   is(secondary, "expected value");
1867    * });
1868    *
1869    * add_task(async function test_early_return() {
1870    *   let result = await somethingThatReturnsAPromise();
1871    *
1872    *   if (!result) {
1873    *     // Test is ended immediately, with success.
1874    *     return;
1875    *   }
1876    *
1877    *   is(result, "foo");
1878    * });
1879    */
1880   add_task(aFunction) {
1881     if (!this.__tasks) {
1882       this.waitForExplicitFinish();
1883       this.__tasks = [];
1884     }
1885     let bound = decorateTaskFn.call(this, aFunction);
1886     this.__tasks.push(bound);
1887     return bound;
1888   },
1890   add_setup(aFunction) {
1891     if (!this.__setups.length) {
1892       this.waitForExplicitFinish();
1893     }
1894     let bound = aFunction.bind(this);
1895     this.__setups.push(bound);
1896     return bound;
1897   },
1899   destroy: function test_destroy() {
1900     for (let prop in this) {
1901       delete this[prop];
1902     }
1903   },