/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // NOTE: If you're adding new test harness functionality to this file -- first, // should you at all? Most stuff is better in specific tests, or in // nested shell.js/browser.js. Second, can you instead add it to // shell.js? Our goal is to unify these two files for readability, and // the plan is to empty out this file into that one over time. Third, // supposing you must add to this file, please add it to this IIFE for // better modularity/resilience against tests that must do particularly // bizarre things that might break the harness. (function(global) { /********************************************************************** * CACHED PRIMORDIAL FUNCTIONALITY (before a test might overwrite it) * **********************************************************************/ var ReflectApply = global.Reflect.apply; // BEWARE: ObjectGetOwnPropertyDescriptor is only safe to use if its result // is inspected using own-property-examining functionality. Directly // accessing properties on a returned descriptor without first // verifying the property's existence can invoke user-modifiable // behavior. var ObjectGetOwnPropertyDescriptor = global.Object.getOwnPropertyDescriptor; var document = global.document; var documentBody = global.document.body; var documentDocumentElement = global.document.documentElement; var DocumentCreateElement = global.document.createElement; var ElementInnerHTMLSetter = ObjectGetOwnPropertyDescriptor(global.Element.prototype, "innerHTML").set; var HTMLIFramePrototypeContentWindowGetter = ObjectGetOwnPropertyDescriptor(global.HTMLIFrameElement.prototype, "contentWindow").get; var HTMLIFramePrototypeRemove = global.HTMLIFrameElement.prototype.remove; var NodePrototypeAppendChild = global.Node.prototype.appendChild; var NodePrototypeTextContentSetter = ObjectGetOwnPropertyDescriptor(global.Node.prototype, "textContent").set; // Cached DOM nodes used by the test harness itself. (We assume the test // doesn't misbehave in a way that actively interferes with what the test // harness runner observes, e.g. navigating the page to a different location. // Short of running every test in a worker -- which has its own problems -- // there's no way to isolate a test from the page to that extent.) var printOutputContainer = global.document.getElementById("jsreftest-print-output-container"); /**************************** * GENERAL HELPER FUNCTIONS * ****************************/ function AppendChild(elt, kid) { ReflectApply(NodePrototypeAppendChild, elt, [kid]); } function CreateElement(name) { return ReflectApply(DocumentCreateElement, document, [name]); } function HTMLSetAttribute(element, name, value) { ReflectApply(HTMLElementPrototypeSetAttribute, element, [name, value]); } function SetTextContent(element, text) { ReflectApply(NodePrototypeTextContentSetter, element, [text]); } /**************************** * UTILITY FUNCTION EXPORTS * ****************************/ var newGlobal = global.newGlobal; if (typeof newGlobal !== "function") { newGlobal = function newGlobal() { var iframe = CreateElement("iframe"); AppendChild(documentDocumentElement, iframe); var win = ReflectApply(HTMLIFramePrototypeContentWindowGetter, iframe, []); ReflectApply(HTMLIFramePrototypeRemove, iframe, []); // Shim in "evaluate" win.evaluate = win.eval; return win; }; global.newGlobal = newGlobal; } // This function is *only* used by shell.js's for-browsers |print()| function! // It's only defined/exported here because it needs CreateElement and friends, // only defined here, and we're not yet ready to move them to shell.js. function AddPrintOutput(s) { var msgDiv = CreateElement("div"); SetTextContent(msgDiv, s); AppendChild(printOutputContainer, msgDiv); } global.AddPrintOutput = AddPrintOutput; /************************************************************************* * HARNESS-CENTRIC EXPORTS (we should generally work to eliminate these) * *************************************************************************/ // This overwrites shell.js's version that merely prints the given string. function writeHeaderToLog(string) { string = String(string); // First dump to the console. dump(string + "\n"); // Then output to the page. var h2 = CreateElement("h2"); SetTextContent(h2, string); AppendChild(printOutputContainer, h2); } global.writeHeaderToLog = writeHeaderToLog; // XXX This function overwrites one in shell.js. We should define the // separate versions in a single location. Also the dependence on // |global.{PASSED,FAILED}| is very silly. function writeFormattedResult(expect, actual, string, passed) { // XXX remove this? it's unneeded in the shell version string = String(string); dump(string + "\n"); var font = CreateElement("font"); if (passed) { HTMLSetAttribute(font, "color", "#009900"); SetTextContent(font, " \u00A0" + global.PASSED); } else { HTMLSetAttribute(font, "color", "#aa0000"); SetTextContent(font, "\u00A0" + global.FAILED + expect); } var b = CreateElement("b"); AppendChild(b, font); var tt = CreateElement("tt"); SetTextContent(tt, string); AppendChild(tt, b); AppendChild(printOutputContainer, tt); AppendChild(printOutputContainer, CreateElement("br")); } global.writeFormattedResult = writeFormattedResult; })(this); var gPageCompleted; var GLOBAL = this + ''; // Variables local to jstests harness. var jstestsTestPassesUnlessItThrows = false; var jstestsRestoreFunction; var jstestsOptions; /* * Signals to this script that the current test case should be considered to * have passed if it doesn't throw an exception. * * Overrides the same-named function in shell.js. */ function testPassesUnlessItThrows() { jstestsTestPassesUnlessItThrows = true; } /* * Sets a restore function which restores the standard built-in ECMAScript * properties after a destructive test case, and which will be called after * the test case terminates. */ function setRestoreFunction(restore) { jstestsRestoreFunction = restore; } window.onerror = function (msg, page, line) { jstestsTestPassesUnlessItThrows = false; // Restore options in case a test case used this common variable name. options = jstestsOptions; // Restore the ECMAScript environment after potentially destructive tests. if (typeof jstestsRestoreFunction === "function") { jstestsRestoreFunction(); } optionsPush(); if (typeof DESCRIPTION == 'undefined') { DESCRIPTION = 'Unknown'; } if (typeof EXPECTED == 'undefined') { EXPECTED = 'Unknown'; } var testcase = new TestCase("unknown-test-name", DESCRIPTION, EXPECTED, "error"); if (document.location.href.indexOf('-n.js') != -1) { // negative test testcase.passed = true; } testcase.reason = page + ':' + line + ': ' + msg; reportFailure(msg); optionsReset(); }; function gc() { try { SpecialPowers.forceGC(); } catch(ex) { print('gc: ' + ex); } } function options(aOptionName) { // return value of options() is a comma delimited list // of the previously set values var value = ''; for (var optionName in options.currvalues) { value += optionName + ','; } if (value) { value = value.substring(0, value.length-1); } if (aOptionName) { if (!(aOptionName in SpecialPowers.Cu)) { // This test is trying to flip an unsupported option, so it's // likely no longer testing what it was supposed to. Fail it // hard. throw "Unsupported JSContext option '"+ aOptionName +"'"; } if (options.currvalues.hasOwnProperty(aOptionName)) // option is set, toggle it to unset delete options.currvalues[aOptionName]; else // option is not set, toggle it to set options.currvalues[aOptionName] = true; SpecialPowers.Cu[aOptionName] = options.currvalues.hasOwnProperty(aOptionName); } return value; } // Keep a reference to options around so that we can restore it after running // a test case, which may have used this common name for one of its own // variables. jstestsOptions = options; function optionsInit() { // hash containing the set options. options.currvalues = { strict: true, werror: true, strict_mode: true }; // record initial values to support resetting // options to their initial values options.initvalues = {}; // record values in a stack to support pushing // and popping options options.stackvalues = []; for (var optionName in options.currvalues) { var propName = optionName; if (!(propName in SpecialPowers.Cu)) { throw "options.currvalues is out of sync with Components.utils"; } if (!SpecialPowers.Cu[propName]) { delete options.currvalues[optionName]; } else { options.initvalues[optionName] = true; } } } function jsTestDriverBrowserInit() { if (typeof dump != 'function') { dump = print; } optionsInit(); optionsClear(); if (document.location.search.indexOf('?') != 0) { // not called with a query string return; } var properties = {}; var fields = document.location.search.slice(1).split(';'); for (var ifield = 0; ifield < fields.length; ifield++) { var propertycaptures = /^([^=]+)=(.*)$/.exec(fields[ifield]); if (!propertycaptures) { properties[fields[ifield]] = true; } else { properties[propertycaptures[1]] = decodeURIComponent(propertycaptures[2]); if (propertycaptures[1] == 'language') { // language=(type|language);mimetype properties.mimetype = fields[ifield+1]; } } } if (properties.language != 'type') { try { properties.version = /javascript([.0-9]+)/.exec(properties.mimetype)[1]; } catch(ex) { } } if (!properties.version && navigator.userAgent.indexOf('Gecko/') != -1) { // If the version is not specified, and the browser is Gecko, // use the default version corresponding to the shell's version(0). // See https://bugzilla.mozilla.org/show_bug.cgi?id=522760#c11 // Otherwise adjust the version to match the suite version for 1.6, // and later due to the use of for-each, let, yield, etc. // // The logic to upgrade the JS version in the shell lives in the // corresponding shell.js. // // Note that js1_8, js1_8_1, and js1_8_5 are treated identically in // the browser. if (properties.test.match(/^js1_6/)) { properties.version = '1.6'; } else if (properties.test.match(/^js1_7/)) { properties.version = '1.7'; } else if (properties.test.match(/^js1_8/)) { properties.version = '1.8'; } else if (properties.test.match(/^ecma_6\/LexicalEnvironment/)) { properties.version = '1.8'; } else if (properties.test.match(/^ecma_6\/Class/)) { properties.version = '1.8'; } else if (properties.test.match(/^ecma_6\/extensions/)) { properties.version = '1.8'; } } // default to language=type;text/javascript. required for // reftest style manifests. if (!properties.language) { properties.language = 'type'; properties.mimetype = 'text/javascript'; } gTestPath = properties.test; if (properties.gczeal) { gczeal(Number(properties.gczeal)); } var testpathparts = properties.test.split(/\//); if (testpathparts.length < 2) { // must have at least suitepath/testcase.js return; } document.write('' + properties.test + '<\/title>'); // XXX bc - the first document.written script is ignored if the protocol // is file:. insert an empty script tag, to work around it. document.write('<script></script>'); // Output script tags for shell.js, then browser.js, at each level of the // test path hierarchy. var prepath = ""; var i = 0; for (end = testpathparts.length - 1; i < end; i++) { prepath += testpathparts[i] + "/"; outputscripttag(prepath + "shell.js", properties); outputscripttag(prepath + "browser.js", properties); } // Output the test script itself. outputscripttag(prepath + testpathparts[i], properties); // Finally output the driver-end script to advance to the next test. outputscripttag('js-test-driver-end.js', properties); return; } function outputscripttag(src, properties) { if (!src) { return; } var s = '<script src="' + src + '" charset="utf-8" '; if (properties.language != 'type') { s += 'language="javascript'; if (properties.version) { s += properties.version; } } else { s += 'type="' + properties.mimetype; if (properties.version) { s += ';version=' + properties.version; } } s += '"><\/script>'; document.write(s); } function jsTestDriverEnd() { // gDelayTestDriverEnd is used to // delay collection of the test result and // signal to Spider so that tests can continue // to run after page load has fired. They are // responsible for setting gDelayTestDriverEnd = true // then when completed, setting gDelayTestDriverEnd = false // then calling jsTestDriverEnd() if (gDelayTestDriverEnd) { return; } window.onerror = null; // Restore options in case a test case used this common variable name. options = jstestsOptions; // Restore the ECMAScript environment after potentially destructive tests. if (typeof jstestsRestoreFunction === "function") { jstestsRestoreFunction(); } if (jstestsTestPassesUnlessItThrows) { var testcase = new TestCase("unknown-test-name", "", true, true); print(PASSED); jstestsTestPassesUnlessItThrows = false; } try { optionsReset(); } catch(ex) { dump('jsTestDriverEnd ' + ex); } if (window.opener && window.opener.runNextTest) { if (window.opener.reportCallBack) { window.opener.reportCallBack(window.opener.gWindow); } setTimeout('window.opener.runNextTest()', 250); } else { for (var i = 0; i < gTestcases.length; i++) { gTestcases[i].dump(); } // tell reftest the test is complete. document.documentElement.className = ''; // tell Spider page is complete gPageCompleted = true; } } //var dlog = (function (s) { print('debug: ' + s); }); var dlog = (function (s) {}); // dialog closer from http://bclary.com/projects/spider/spider/chrome/content/spider/dialog-closer.js var gDialogCloser; var gDialogCloserObserver; function registerDialogCloser() { gDialogCloser = SpecialPowers. Cc['@mozilla.org/embedcomp/window-watcher;1']. getService(SpecialPowers.Ci.nsIWindowWatcher); gDialogCloserObserver = {observe: dialogCloser_observe}; gDialogCloser.registerNotification(gDialogCloserObserver); } function unregisterDialogCloser() { gczeal(0); if (!gDialogCloserObserver || !gDialogCloser) { return; } gDialogCloser.unregisterNotification(gDialogCloserObserver); gDialogCloserObserver = null; gDialogCloser = null; } // use an array to handle the case where multiple dialogs // appear at one time var gDialogCloserSubjects = []; function dialogCloser_observe(subject, topic, data) { if (subject instanceof ChromeWindow && topic == 'domwindowopened' ) { gDialogCloserSubjects.push(subject); // timeout of 0 needed when running under reftest framework. subject.setTimeout(closeDialog, 0); } } function closeDialog() { var subject; while ( (subject = gDialogCloserSubjects.pop()) != null) { if (subject.document instanceof XULDocument && subject.document.documentURI == 'chrome://global/content/commonDialog.xul') { subject.close(); } else { // alerts inside of reftest framework are not XULDocument dialogs. subject.close(); } } } registerDialogCloser(); window.addEventListener('unload', unregisterDialogCloser, true); jsTestDriverBrowserInit();