Mypal/js/src/tests/browser.js

602 lines
16 KiB
JavaScript

/* -*- 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();