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) {
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
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;
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]);
72 // Restore the previous window.onerror handler.
73 function restoreGlobalOnError() {
74 var previousOnError = ArrayPop(savedGlobalOnError);
75 ReflectApply(WindowOnErrorSetter, global, [previousOnError]);
78 /****************************
79 * GENERAL HELPER FUNCTIONS *
80 ****************************/
82 function ArrayPush(array, value) {
83 ReflectApply(ObjectDefineProperty, null, [
85 {__proto__: null, value, writable: true, enumerable: true, configurable: true}
89 function ArrayPop(array) {
91 var item = array[array.length - 1];
97 function AppendChild(elt, kid) {
98 ReflectApply(NodePrototypeAppendChild, elt, [kid]);
101 function CreateElement(name) {
102 return ReflectApply(DocumentCreateElement, document, [name]);
105 function RemoveChild(elt, kid) {
106 ReflectApply(NodePrototypeRemoveChild, elt, [kid]);
109 function CreateWorker(script) {
110 var blob = new Blob([script], {__proto__: null, type: "text/javascript"});
111 return new Worker(URLCreateObjectURL(blob));
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);
128 global.evaluate = evaluate;
131 var evaluateScript = global.evaluateScript;
132 if (typeof evaluateScript !== "function") {
133 evaluateScript = function evaluateScript(code) {
135 var script = CreateElement("script");
137 // Temporarily install a new onerror handler to catch script errors.
138 var hasUncaughtError = false;
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;
150 ReflectApply(EventTargetPrototypeAddEventListener, script, [
151 "afterscriptexecute", function() {
152 restoreGlobalOnError();
156 ReflectApply(HTMLScriptElementTextSetter, script, [code]);
157 AppendChild(documentDocumentElement, script);
158 RemoveChild(documentDocumentElement, script);
160 if (hasUncaughtError)
164 global.evaluateScript = evaluateScript;
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);
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);
186 global.newGlobal = newGlobal;
189 var detachArrayBuffer = global.detachArrayBuffer;
190 if (typeof detachArrayBuffer !== "function") {
192 detachArrayBuffer = function detachArrayBuffer(arrayBuffer) {
193 if (worker === null) {
194 worker = CreateWorker("/* black hole */");
197 ReflectApply(WorkerPrototypePostMessage, worker, ["detach", [arrayBuffer]]);
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) {
207 global.detachArrayBuffer = detachArrayBuffer;
210 var createIsHTMLDDA = global.createIsHTMLDDA;
211 if (typeof createIsHTMLDDA !== "function") {
212 createIsHTMLDDA = function() {
213 return ReflectApply(DocumentPrototypeAllGetter, document, []);
216 global.createIsHTMLDDA = createIsHTMLDDA;
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
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, [
286 {__proto__: null, value, writable: true, enumerable: true, configurable: true}
290 function ArrayPop(array) {
292 var item = array[array.length - 1];
298 function HasOwnProperty(object, property) {
299 return ReflectApply(ObjectPrototypeHasOwnProperty, object, [property]);
302 function AppendChild(elt, kid) {
303 ReflectApply(NodePrototypeAppendChild, elt, [kid]);
306 function CreateElement(name) {
307 return ReflectApply(DocumentCreateElement, document, [name]);
310 function SetTextContent(element, text) {
311 ReflectApply(NodePrototypeTextContentSetter, element, [text]);
314 // Object containing the set options.
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;
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);
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.
346 // Then output to the page.
347 var h2 = CreateElement("h2");
348 SetTextContent(h2, string);
349 AppendChild(printOutputContainer, h2);
351 global.writeHeaderToLog = writeHeaderToLog;
353 /*************************
354 * GLOBAL ERROR HANDLING *
355 *************************/
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".
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";
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;
384 var reason = `${page}:${line}: ${msg}`;
385 new TestCase(DESCRIPTION, expected, actual, reason);
390 /**********************************************
391 * BROWSER IMPLEMENTATION FOR SHELL FUNCTIONS *
392 **********************************************/
396 SpecialPowersForceGC();
403 global.clearKeptObjects = ClearKeptObjects;
405 function options(aOptionName) {
406 // return value of options() is a comma delimited list
407 // of the previously set values
410 for (var optionName in currentOptions) {
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
421 throw "Unsupported JSContext option '" + aOptionName + "'";
424 if (aOptionName in currentOptions) {
425 // option is set, toggle it to unset
426 delete currentOptions[aOptionName];
427 SpecialPowersCu[aOptionName] = false;
429 // option is not set, toggle it to set
430 currentOptions[aOptionName] = true;
431 SpecialPowersCu[aOptionName] = true;
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
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;
459 properties[propertycaptures[1]] = decodeURIComponent(propertycaptures[2]);
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
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;
484 // No error is expected.
485 expectedError = "Unknown";
488 if (properties.gczeal) {
489 gczeal(Number(properties.gczeal));
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.
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.
508 scripts.push({src: prepath + "shell.js", module: false});
509 scripts.push({src: prepath + "browser.js", module: false});
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;
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>`);
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]);
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";
550 script.type = "module";
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.
563 script.addEventListener("error", callNextAppend, {once: true});
567 scriptElements[i] = script;
570 // Append the first script.
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
584 if (gDelayTestDriverEnd) {
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);
598 setTimeout("window.opener.runNextTest()", 250);
600 // tell reftest the test is complete.
601 ReflectApply(ElementSetClassName, documentDocumentElement, [""]);
602 // tell Spider page is complete
603 gPageCompleted = true;
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);
630 function closeDialog() {
631 while (dialogCloserSubjects.length > 0) {
632 var subject = ArrayPop(dialogCloserSubjects);
637 function unregisterDialogCloser() {
640 if (!dialogCloserObserver || !dialogCloser) {
644 dialogCloser.unregisterNotification(dialogCloserObserver);
646 dialogCloserObserver = null;
650 dialogCloser.registerNotification(dialogCloserObserver);
651 window.addEventListener("unload", unregisterDialogCloser, true);
653 /*******************************************
654 * RUN ONCE CODE TO SETUP ADDITIONAL STATE *
655 *******************************************/
657 jsTestDriverBrowserInit();