Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / js / src / tests / browser.js
bloba67da99e4634056e1fb08f634691c094acf17527
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 // NOTE: If you're adding new test harness functionality to this file -- first,
7 //       should you at all?  Most stuff is better in specific tests, or in
8 //       nested shell.js/browser.js.  Second, can you instead add it to
9 //       shell.js?  Our goal is to unify these two files for readability, and
10 //       the plan is to empty out this file into that one over time.  Third,
11 //       supposing you must add to this file, please add it to this IIFE for
12 //       better modularity/resilience against tests that must do particularly
13 //       bizarre things that might break the harness.
15 (function initializeUtilityExports(global, parent) {
16   "use strict";
18   /**********************************************************************
19    * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
20    **********************************************************************/
22   var Error = global.Error;
23   var String = global.String;
24   var GlobalEval = global.eval;
25   var ReflectApply = global.Reflect.apply;
26   var FunctionToString = global.Function.prototype.toString;
27   var ObjectDefineProperty = global.Object.defineProperty;
29   // BEWARE: ObjectGetOwnPropertyDescriptor is only safe to use if its result
30   //         is inspected using own-property-examining functionality.  Directly
31   //         accessing properties on a returned descriptor without first
32   //         verifying the property's existence can invoke user-modifiable
33   //         behavior.
34   var ObjectGetOwnPropertyDescriptor = global.Object.getOwnPropertyDescriptor;
36   var {get: ArrayBufferByteLength} =
37     ObjectGetOwnPropertyDescriptor(global.ArrayBuffer.prototype, "byteLength");
39   var Worker = global.Worker;
40   var Blob = global.Blob;
41   var URL = global.URL;
43   var document = global.document;
44   var documentDocumentElement = global.document.documentElement;
45   var DocumentCreateElement = global.document.createElement;
47   var DocumentPrototypeAllGetter = ObjectGetOwnPropertyDescriptor(global.Document.prototype, "all").get;
48   var EventTargetPrototypeAddEventListener = global.EventTarget.prototype.addEventListener;
49   var HTMLElementPrototypeStyleSetter =
50     ObjectGetOwnPropertyDescriptor(global.HTMLElement.prototype, "style").set;
51   var HTMLIFramePrototypeContentWindowGetter =
52     ObjectGetOwnPropertyDescriptor(global.HTMLIFrameElement.prototype, "contentWindow").get;
53   var HTMLScriptElementTextSetter =
54     ObjectGetOwnPropertyDescriptor(global.HTMLScriptElement.prototype, "text").set;
55   var NodePrototypeAppendChild = global.Node.prototype.appendChild;
56   var NodePrototypeRemoveChild = global.Node.prototype.removeChild;
57   var {get: WindowOnErrorGetter, set: WindowOnErrorSetter} =
58     ObjectGetOwnPropertyDescriptor(global, "onerror");
59   var WorkerPrototypePostMessage = Worker.prototype.postMessage;
60   var URLCreateObjectURL = URL.createObjectURL;
62   // List of saved window.onerror handlers.
63   var savedGlobalOnError = [];
65   // Set |newOnError| as the current window.onerror handler.
66   function setGlobalOnError(newOnError) {
67     var currentOnError = ReflectApply(WindowOnErrorGetter, global, []);
68     ArrayPush(savedGlobalOnError, currentOnError);
69     ReflectApply(WindowOnErrorSetter, global, [newOnError]);
70   }
72   // Restore the previous window.onerror handler.
73   function restoreGlobalOnError() {
74     var previousOnError = ArrayPop(savedGlobalOnError);
75     ReflectApply(WindowOnErrorSetter, global, [previousOnError]);
76   }
78   /****************************
79    * GENERAL HELPER FUNCTIONS *
80    ****************************/
82   function ArrayPush(array, value) {
83     ReflectApply(ObjectDefineProperty, null, [
84       array, array.length,
85       {__proto__: null, value, writable: true, enumerable: true, configurable: true}
86     ]);
87   }
89   function ArrayPop(array) {
90     if (array.length) {
91       var item = array[array.length - 1];
92       array.length -= 1;
93       return item;
94     }
95   }
97   function AppendChild(elt, kid) {
98     ReflectApply(NodePrototypeAppendChild, elt, [kid]);
99   }
101   function CreateElement(name) {
102     return ReflectApply(DocumentCreateElement, document, [name]);
103   }
105   function RemoveChild(elt, kid) {
106     ReflectApply(NodePrototypeRemoveChild, elt, [kid]);
107   }
109   function CreateWorker(script) {
110     var blob = new Blob([script], {__proto__: null, type: "text/javascript"});
111     return new Worker(URLCreateObjectURL(blob));
112   }
114   /****************************
115    * UTILITY FUNCTION EXPORTS *
116    ****************************/
118   var evaluate = global.evaluate;
119   if (typeof evaluate !== "function") {
120     // Shim in "evaluate".
121     evaluate = function evaluate(code) {
122       if (typeof code !== "string")
123         throw Error("Expected string argument for evaluate()");
125       return GlobalEval(code);
126     };
128     global.evaluate = evaluate;
129   }
131   var evaluateScript = global.evaluateScript;
132   if (typeof evaluateScript !== "function") {
133     evaluateScript = function evaluateScript(code) {
134       code = String(code);
135       var script = CreateElement("script");
137       // Temporarily install a new onerror handler to catch script errors.
138       var hasUncaughtError = false;
139       var uncaughtError;
140       var eventOptions = {__proto__: null, once: true};
141       ReflectApply(EventTargetPrototypeAddEventListener, script, [
142         "beforescriptexecute", function() {
143           setGlobalOnError(function(messageOrEvent, source, lineno, colno, error) {
144             hasUncaughtError = true;
145             uncaughtError = error;
146             return true;
147           });
148         }, eventOptions
149       ]);
150       ReflectApply(EventTargetPrototypeAddEventListener, script, [
151         "afterscriptexecute", function() {
152           restoreGlobalOnError();
153         }, eventOptions
154       ]);
156       ReflectApply(HTMLScriptElementTextSetter, script, [code]);
157       AppendChild(documentDocumentElement, script);
158       RemoveChild(documentDocumentElement, script);
160       if (hasUncaughtError)
161         throw uncaughtError;
162     };
164     global.evaluateScript = evaluateScript;
165   }
167   var newGlobal = global.newGlobal;
168   if (typeof newGlobal !== "function") {
169     // Reuse the parent's newGlobal to ensure iframes can be added to the DOM.
170     newGlobal = parent ? parent.newGlobal : function newGlobal() {
171       var iframe = CreateElement("iframe");
172       AppendChild(documentDocumentElement, iframe);
173       var win =
174         ReflectApply(HTMLIFramePrototypeContentWindowGetter, iframe, []);
176       // Removing the iframe breaks evaluateScript() and detachArrayBuffer().
177       ReflectApply(HTMLElementPrototypeStyleSetter, iframe, ["display:none"]);
179       // Create utility functions in the new global object.
180       var initFunction = ReflectApply(FunctionToString, initializeUtilityExports, []);
181       win.Function("parent", initFunction + "; initializeUtilityExports(this, parent);")(global);
183       return win;
184     };
186     global.newGlobal = newGlobal;
187   }
189   var detachArrayBuffer = global.detachArrayBuffer;
190   if (typeof detachArrayBuffer !== "function") {
191     var worker = null;
192     detachArrayBuffer = function detachArrayBuffer(arrayBuffer) {
193       if (worker === null) {
194         worker = CreateWorker("/* black hole */");
195       }
196       try {
197         ReflectApply(WorkerPrototypePostMessage, worker, ["detach", [arrayBuffer]]);
198       } catch (e) {
199         // postMessage throws an error if the array buffer was already detached.
200         // Test for this condition by checking if the byte length is zero.
201         if (ReflectApply(ArrayBufferByteLength, arrayBuffer, []) !== 0) {
202           throw e;
203         }
204       }
205     };
207     global.detachArrayBuffer = detachArrayBuffer;
208   }
210   var createIsHTMLDDA = global.createIsHTMLDDA;
211   if (typeof createIsHTMLDDA !== "function") {
212     createIsHTMLDDA = function() {
213       return ReflectApply(DocumentPrototypeAllGetter, document, []);
214     };
216     global.createIsHTMLDDA = createIsHTMLDDA;
217   }
218 })(this);
220 (function(global) {
221   "use strict";
223   /**********************************************************************
224    * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) *
225    **********************************************************************/
227   var undefined; // sigh
229   var Error = global.Error;
230   var Number = global.Number;
231   var Object = global.Object;
232   var String = global.String;
234   var decodeURIComponent = global.decodeURIComponent;
235   var ReflectApply = global.Reflect.apply;
236   var ObjectDefineProperty = Object.defineProperty;
237   var ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty;
238   var ObjectPrototypeIsPrototypeOf = Object.prototype.isPrototypeOf;
240   // BEWARE: ObjectGetOwnPropertyDescriptor is only safe to use if its result
241   //         is inspected using own-property-examining functionality.  Directly
242   //         accessing properties on a returned descriptor without first
243   //         verifying the property's existence can invoke user-modifiable
244   //         behavior.
245   var ObjectGetOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
247   var window = global.window;
248   var document = global.document;
249   var documentDocumentElement = document.documentElement;
250   var DocumentCreateElement = document.createElement;
251   var ElementSetClassName =
252     ObjectGetOwnPropertyDescriptor(global.Element.prototype, "className").set;
253   var NodePrototypeAppendChild = global.Node.prototype.appendChild;
254   var NodePrototypeTextContentSetter =
255     ObjectGetOwnPropertyDescriptor(global.Node.prototype, "textContent").set;
256   var setTimeout = global.setTimeout;
258   // Saved harness functions.
259   var dump = global.dump;
260   var gczeal = global.gczeal;
261   var print = global.print;
262   var reportFailure = global.reportFailure;
263   var TestCase = global.TestCase;
265   var SpecialPowers = global.SpecialPowers;
266   var SpecialPowersCu = SpecialPowers.Cu;
267   var SpecialPowersForceGC = SpecialPowers.forceGC;
268   var TestingFunctions = SpecialPowers.Cu.getJSTestingFunctions();
269   var ClearKeptObjects = TestingFunctions.clearKeptObjects;
271   // Cached DOM nodes used by the test harness itself.  (We assume the test
272   // doesn't misbehave in a way that actively interferes with what the test
273   // harness runner observes, e.g. navigating the page to a different location.
274   // Short of running every test in a worker -- which has its own problems --
275   // there's no way to isolate a test from the page to that extent.)
276   var printOutputContainer =
277     global.document.getElementById("jsreftest-print-output-container");
279   /****************************
280    * GENERAL HELPER FUNCTIONS *
281    ****************************/
283   function ArrayPush(array, value) {
284     ReflectApply(ObjectDefineProperty, null, [
285       array, array.length,
286       {__proto__: null, value, writable: true, enumerable: true, configurable: true}
287     ]);
288   }
290   function ArrayPop(array) {
291     if (array.length) {
292       var item = array[array.length - 1];
293       array.length -= 1;
294       return item;
295     }
296   }
298   function HasOwnProperty(object, property) {
299     return ReflectApply(ObjectPrototypeHasOwnProperty, object, [property]);
300   }
302   function AppendChild(elt, kid) {
303     ReflectApply(NodePrototypeAppendChild, elt, [kid]);
304   }
306   function CreateElement(name) {
307     return ReflectApply(DocumentCreateElement, document, [name]);
308   }
310   function SetTextContent(element, text) {
311     ReflectApply(NodePrototypeTextContentSetter, element, [text]);
312   }
314   // Object containing the set options.
315   var currentOptions;
317   // browser.js version of shell.js' |shellOptionsClear| function.
318   function browserOptionsClear() {
319     for (var optionName in currentOptions) {
320       delete currentOptions[optionName];
321       SpecialPowersCu[optionName] = false;
322     }
323   }
325   // This function is *only* used by shell.js's for-browsers |print()| function!
326   // It's only defined/exported here because it needs CreateElement and friends,
327   // only defined here, and we're not yet ready to move them to shell.js.
328   function AddPrintOutput(s) {
329     var msgDiv = CreateElement("div");
330     SetTextContent(msgDiv, s);
331     AppendChild(printOutputContainer, msgDiv);
332   }
333   global.AddPrintOutput = AddPrintOutput;
335   /*************************************************************************
336    * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) *
337    *************************************************************************/
339   // This overwrites shell.js's version that merely prints the given string.
340   function writeHeaderToLog(string) {
341     string = String(string);
343     // First dump to the console.
344     dump(string + "\n");
346     // Then output to the page.
347     var h2 = CreateElement("h2");
348     SetTextContent(h2, string);
349     AppendChild(printOutputContainer, h2);
350   }
351   global.writeHeaderToLog = writeHeaderToLog;
353   /*************************
354    * GLOBAL ERROR HANDLING *
355    *************************/
357   // Possible values:
358   // - "Unknown" if no error is expected,
359   // - "error" if no specific error type is expected,
360   // - otherwise the error name, e.g. "TypeError" or "RangeError".
361   var expectedError;
363   window.onerror = function (msg, page, line, column, error) {
364     // Unset all options even when the test finished with an error.
365     browserOptionsClear();
367     if (DESCRIPTION === undefined) {
368       DESCRIPTION = "Unknown";
369     }
371     var actual = "error";
372     var expected = expectedError;
373     if (expected !== "error" && expected !== "Unknown") {
374       // Check the error type when an actual Error object is available.
375       // NB: The |error| parameter of the onerror handler is not required to
376       // be an Error instance.
377       if (ReflectApply(ObjectPrototypeIsPrototypeOf, Error.prototype, [error])) {
378         actual = error.constructor.name;
379       } else {
380         expected = "error";
381       }
382     }
384     var reason = `${page}:${line}: ${msg}`;
385     new TestCase(DESCRIPTION, expected, actual, reason);
387     reportFailure(msg);
388   };
390   /**********************************************
391    * BROWSER IMPLEMENTATION FOR SHELL FUNCTIONS *
392    **********************************************/
394   function gc() {
395     try {
396       SpecialPowersForceGC();
397     } catch (ex) {
398       print("gc: " + ex);
399     }
400   }
401   global.gc = gc;
403   global.clearKeptObjects = ClearKeptObjects;
405   function options(aOptionName) {
406     // return value of options() is a comma delimited list
407     // of the previously set values
409     var value = "";
410     for (var optionName in currentOptions) {
411       if (value)
412         value += ",";
413       value += optionName;
414     }
416     if (aOptionName) {
417       if (!HasOwnProperty(SpecialPowersCu, aOptionName)) {
418         // This test is trying to flip an unsupported option, so it's
419         // likely no longer testing what it was supposed to.  Fail it
420         // hard.
421         throw "Unsupported JSContext option '" + aOptionName + "'";
422       }
424       if (aOptionName in currentOptions) {
425         // option is set, toggle it to unset
426         delete currentOptions[aOptionName];
427         SpecialPowersCu[aOptionName] = false;
428       } else {
429         // option is not set, toggle it to set
430         currentOptions[aOptionName] = true;
431         SpecialPowersCu[aOptionName] = true;
432       }
433     }
435     return value;
436   }
437   global.options = options;
439   /****************************************
440    * HARNESS SETUP AND TEARDOWN FUNCTIONS *
441    ****************************************/
443   function jsTestDriverBrowserInit() {
444     // Initialize with an empty set, because we just turned off all options.
445     currentOptions = Object.create(null);
447     if (document.location.search.indexOf("?") !== 0) {
448       // not called with a query string
449       return;
450     }
452     var properties = Object.create(null);
453     var fields = document.location.search.slice(1).split(";");
454     for (var i = 0; i < fields.length; i++) {
455       var propertycaptures = /^([^=]+)=(.*)$/.exec(fields[i]);
456       if (propertycaptures === null) {
457         properties[fields[i]] = true;
458       } else {
459         properties[propertycaptures[1]] = decodeURIComponent(propertycaptures[2]);
460       }
461     }
463     // The test path may contain \ separators for the path.
464     // Bug 1877606: use / consistently
465     properties.test = properties.test.replace(/\\/g, "/");
467     global.gTestPath = properties.test;
469     var testpathparts = properties.test.split("/");
470     if (testpathparts.length < 2) {
471       // must have at least suitepath/testcase.js
472       return;
473     }
475     var testFileName = testpathparts[testpathparts.length - 1];
477     if (testFileName.endsWith("-n.js")) {
478       // Negative test without a specific error type.
479       expectedError = "error";
480     } else if (properties.error) {
481       // Negative test which expects a specific error type.
482       expectedError = properties.error;
483     } else {
484       // No error is expected.
485       expectedError = "Unknown";
486     }
488     if (properties.gczeal) {
489       gczeal(Number(properties.gczeal));
490     }
492     // Display the test path in the title.
493     document.title = properties.test;
495     // Output script tags for shell.js, then browser.js, at each level of the
496     // test path hierarchy.
497     var prepath = "";
498     var scripts = [];
499     for (var i = 0; i < testpathparts.length - 1; i++) {
500       prepath += testpathparts[i] + "/";
502       if (properties["test262-raw"]) {
503         // Skip running test harness files (shell.js and browser.js) if the
504         // test has the raw flag.
505         continue;
506       }
508       scripts.push({src: prepath + "shell.js", module: false});
509       scripts.push({src: prepath + "browser.js", module: false});
510     }
512     // Output the test script itself.
513     var moduleTest = !!properties.module;
514     scripts.push({src: prepath + testFileName, module: moduleTest});
516     // Finally output the driver-end script to advance to the next test.
517     scripts.push({src: "js-test-driver-end.js", module: false});
519     if (properties.async) {
520       gDelayTestDriverEnd = true;
521     }
523     if (!moduleTest) {
524       for (var i = 0; i < scripts.length; i++) {
525         var src = scripts[i].src;
526         document.write(`<script src="${src}" charset="utf-8"><\/script>`);
527       }
528     } else {
529       // Modules are loaded asynchronously by default, but for the test harness
530       // we need to execute all scripts and modules one after the other.
532       // Appends the next script element to the DOM.
533       function appendScript(index) {
534         var script = scriptElements[index];
535         scriptElements[index] = null;
536         if (script !== null) {
537           ReflectApply(NodePrototypeAppendChild, documentDocumentElement, [script]);
538         }
539       }
541       // Create all script elements upfront, so we don't need to worry about
542       // modified built-ins.
543       var scriptElements = [];
544       for (var i = 0; i < scripts.length; i++) {
545         var spec = scripts[i];
547         var script = document.createElement("script");
548         script.charset = "utf-8";
549         if (spec.module) {
550           script.type = "module";
551         }
552         script.src = spec.src;
554         let nextScriptIndex = i + 1;
555         if (nextScriptIndex < scripts.length) {
556           var callNextAppend = () => appendScript(nextScriptIndex);
557           script.addEventListener("afterscriptexecute", callNextAppend, {once: true});
559           // Module scripts don't fire the "afterscriptexecute" event when there
560           // was an error, instead the "error" event is emitted. So listen for
561           // both events when creating module scripts.
562           if (spec.module) {
563             script.addEventListener("error", callNextAppend, {once: true});
564           }
565         }
567         scriptElements[i] = script;
568       }
570       // Append the first script.
571       appendScript(0);
572     }
573   }
575   global.gDelayTestDriverEnd = false;
577   function jsTestDriverEnd() {
578     // gDelayTestDriverEnd is used to delay collection of the test result and
579     // signal to Spider so that tests can continue to run after page load has
580     // fired. They are responsible for setting gDelayTestDriverEnd = true then
581     // when completed, setting gDelayTestDriverEnd = false then calling
582     // jsTestDriverEnd()
584     if (gDelayTestDriverEnd) {
585       return;
586     }
588     window.onerror = null;
590     // Unset all options when the test has finished.
591     browserOptionsClear();
593     if (window.opener && window.opener.runNextTest) {
594       if (window.opener.reportCallBack) {
595         window.opener.reportCallBack(window.opener.gWindow);
596       }
598       setTimeout("window.opener.runNextTest()", 250);
599     } else {
600       // tell reftest the test is complete.
601       ReflectApply(ElementSetClassName, documentDocumentElement, [""]);
602       // tell Spider page is complete
603       gPageCompleted = true;
604     }
605   }
606   global.jsTestDriverEnd = jsTestDriverEnd;
608   /***************************************************************************
609    * DIALOG CLOSER, PRESUMABLY TO CLOSE SLOW SCRIPT DIALOGS AND OTHER POPUPS *
610    ***************************************************************************/
612   // dialog closer from http://bclary.com/projects/spider/spider/chrome/content/spider/dialog-closer.js
614   // Use an array to handle the case where multiple dialogs appear at one time.
615   var dialogCloserSubjects = [];
616   var dialogCloser = SpecialPowers
617                      .Cc["@mozilla.org/embedcomp/window-watcher;1"]
618                      .getService(SpecialPowers.Ci.nsIWindowWatcher);
619   var dialogCloserObserver = {
620     observe(subject, topic, data) {
621       if (topic === "domwindowopened" && subject.isChromeWindow) {
622         ArrayPush(dialogCloserSubjects, subject);
624         // Timeout of 0 needed when running under reftest framework.
625         subject.setTimeout(closeDialog, 0);
626       }
627     }
628   };
630   function closeDialog() {
631     while (dialogCloserSubjects.length > 0) {
632       var subject = ArrayPop(dialogCloserSubjects);
633       subject.close();
634     }
635   }
637   function unregisterDialogCloser() {
638     gczeal(0);
640     if (!dialogCloserObserver || !dialogCloser) {
641       return;
642     }
644     dialogCloser.unregisterNotification(dialogCloserObserver);
646     dialogCloserObserver = null;
647     dialogCloser = null;
648   }
650   dialogCloser.registerNotification(dialogCloserObserver);
651   window.addEventListener("unload", unregisterDialogCloser, true);
653   /*******************************************
654    * RUN ONCE CODE TO SETUP ADDITIONAL STATE *
655    *******************************************/
657   jsTestDriverBrowserInit();
659 })(this);
661 var gPageCompleted;