summaryrefslogtreecommitdiffstats
path: root/js/src/tests/browser.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tests/browser.js')
-rw-r--r--js/src/tests/browser.js601
1 files changed, 601 insertions, 0 deletions
diff --git a/js/src/tests/browser.js b/js/src/tests/browser.js
new file mode 100644
index 000000000..91ee22f3d
--- /dev/null
+++ b/js/src/tests/browser.js
@@ -0,0 +1,601 @@
+/* -*- 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('<title>' + 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();