/* *TestEnvironmentisanabstractionfortheenvironmentinwhichthetest *harnessisused.Eachimplementationofatestenvironmenthastoprovide *thefollowinginterface: * *interfaceTestEnvironment{ *// Invoked after the global 'tests' object has been created and it's *// safe to call add_*_callback() to register event handlers. *voidon_tests_ready(); * *// Invoked after setup() has been called to notify the test environment *// of changes to the test harness properties. *voidon_new_harness_properties(objectproperties); * *// Should return a new unique default test name. *DOMStringnext_default_test_name(); * *// Should return the test harness timeout duration in milliseconds. *floattest_timeout(); * *// Should return the global scope object. *objectglobal_scope(); *};
*/
WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) { this._forEach_windows( function(w, same_origin) { if (same_origin) { try { var has_selector = selector in w;
} catch(e) { // If document.domain was set at some point same_origin can be // wrong and the above will fail.
has_selector = false;
} if (has_selector) { try {
w[selector].apply(undefined, callback_args);
} catch (e) { if (debug) { throw e;
}
}
}
} if (supports_post_message(w) && w !== self) {
w.postMessage(message_arg, "*");
}
});
};
WindowTestEnvironment.prototype._forEach_windows = function(callback) { // Iterate of the the windows [self ... top, opener]. The callback is passed // two objects, the first one is the windows object itself, the second one // is a boolean indicating whether or not its on the same origin as the // current window. var cache = this.window_cache; if (!cache) {
cache = [[self, true]]; var w = self; var i = 0; var so; var origins = location.ancestorOrigins; while (w != w.parent) {
w = w.parent; // In WebKit, calls to parent windows' properties that aren't on the same // origin cause an error message to be displayed in the error console but // don't throw an exception. This is a deviation from the current HTML5 // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504 // The problem with WebKit's behavior is that it pollutes the error console // with error messages that can't be caught. // // This issue can be mitigated by relying on the (for now) proprietary // `location.ancestorOrigins` property which returns an ordered list of // the origins of enclosing windows. See: // http://trac.webkit.org/changeset/113945. if (origins) {
so = (location.origin == origins[i]);
} else {
so = is_same_origin(w);
}
cache.push([w, so]);
i++;
}
w = window.opener; if (w) { // window.opener isn't included in the `location.ancestorOrigins` prop. // We'll just have to deal with a simple check and an error msg on WebKit // browsers in this case.
cache.push([w, is_same_origin(w)]);
} this.window_cache = cache;
}
WindowTestEnvironment.prototype.setup_messages = function(new_events) { var this_obj = this;
forEach(settings.message_events, function(x) { var current_dispatch = this_obj.message_events.includes(x); var new_dispatch = new_events.includes(x); if (!current_dispatch && new_dispatch) {
this_obj.message_functions[x][0](this_obj.message_functions[x][2]);
} elseif (current_dispatch && !new_dispatch) {
this_obj.message_functions[x][1](this_obj.message_functions[x][2]);
}
}); this.message_events = new_events;
}
WindowTestEnvironment.prototype.next_default_test_name = function() { //Don't use document.title to work around an Opera bug in XHTML documents var title = document.getElementsByTagName("title")[0]; var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled"; var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; this.name_counter++;
return prefix + suffix;
};
WorkerTestEnvironment.prototype._dispatch = function(message) { this.message_list.push(message); for (var i = 0; i < this.message_ports.length; ++i)
{ this.message_ports[i].postMessage(message);
}
};
// The only requirement is that port has a postMessage() method. It doesn't // have to be an instance of a MessagePort, and often isn't.
WorkerTestEnvironment.prototype._add_message_port = function(port) { this.message_ports.push(port); for (var i = 0; i < this.message_list.length; ++i)
{
port.postMessage(this.message_list[i]);
}
};
WorkerTestEnvironment.prototype.test_timeout = function() { // Tests running in a worker don't have a default timeout. I.e. all // worker tests behave as if settings.explicit_timeout is true.
return null;
};
/* *Dedicatedwebworkers. *https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope * *Thisclassisusedasthetest_environmentwhentestharnessisrunning *insideadedicatedworker.
*/ function DedicatedWorkerTestEnvironment() {
WorkerTestEnvironment.call(this); // self is an instance of DedicatedWorkerGlobalScope which exposes // a postMessage() method for communicating via the message channel // established when the worker is created. this._add_message_port(self);
}
DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() {
WorkerTestEnvironment.prototype.on_tests_ready.call(this); // In the absence of an onload notification, we a require dedicated // workers to explicitly signal when the tests are done.
tests.wait_for_finish = true;
};
/* *Sharedwebworkers. *https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalscope * *Thisclassisusedasthetest_environmentwhentestharnessisrunning *insideasharedwebworker.
*/ function SharedWorkerTestEnvironment() {
WorkerTestEnvironment.call(this); var this_obj = this; // Shared workers receive message ports via the 'onconnect' event for // each connection.
self.addEventListener("connect", function(message_event) {
this_obj._add_message_port(message_event.source);
});
}
SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
SharedWorkerTestEnvironment.prototype.on_tests_ready = function() {
WorkerTestEnvironment.prototype.on_tests_ready.call(this); // In the absence of an onload notification, we a require shared // workers to explicitly signal when the tests are done.
tests.wait_for_finish = true;
};
/* *Serviceworkers. *http://www.w3.org/TR/service-workers/ * *Thisclassisusedasthetest_environmentwhentestharnessisrunning *insideaserviceworker.
*/ function ServiceWorkerTestEnvironment() {
WorkerTestEnvironment.call(this); this.all_loaded = false; this.on_loaded_callback = null; var this_obj = this;
self.addEventListener("message", function(event) { if (event.data.type && event.data.type === "connect") { if (event.ports && event.ports[0]) { // If a MessageChannel was passed, then use it to // send results back to the main window. This // allows the tests to work even if the browser // does not fully support MessageEvent.source in // ServiceWorkers yet.
this_obj._add_message_port(event.ports[0]);
event.ports[0].start();
} else { // If there is no MessageChannel, then attempt to // use the MessageEvent.source to send results // back to the main window.
this_obj._add_message_port(event.source);
}
}
});
// The oninstall event is received after the service worker script and // all imported scripts have been fetched and executed. It's the // equivalent of an onload event for a document. All tests should have // been added by the time this event is received, thus it's not // necessary to wait until the onactivate event.
on_event(self, "install", function(event) {
this_obj.all_loaded = true; if (this_obj.on_loaded_callback) {
this_obj.on_loaded_callback();
}
});
}
ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
function create_test_environment() { if ('document' in self) {
return new WindowTestEnvironment();
} if ('DedicatedWorkerGlobalScope' in self &&
self instanceof DedicatedWorkerGlobalScope) {
return new DedicatedWorkerTestEnvironment();
} if ('SharedWorkerGlobalScope' in self &&
self instanceof SharedWorkerGlobalScope) {
return new SharedWorkerTestEnvironment();
} if ('ServiceWorkerGlobalScope' in self &&
self instanceof ServiceWorkerGlobalScope) {
return new ServiceWorkerTestEnvironment();
} thrownew Error("Unsupported test environment");
}
var test_environment = create_test_environment();
function is_shared_worker(worker) {
return 'SharedWorker' in self && worker instanceof SharedWorker;
}
function is_service_worker(worker) {
return 'ServiceWorker' in self && worker instanceof ServiceWorker;
}
/* *APIfunctions
*/
function test(func, name, properties)
{ var test_name = name ? name : test_environment.next_default_test_name();
properties = properties ? properties : {}; var test_obj = new Test(test_name, properties);
test_obj.step(func, test_obj, test_obj); if (test_obj.phase === test_obj.phases.STARTED) {
test_obj.done();
}
}
function async_test(func, name, properties)
{ if (typeof func !== "function") {
properties = name;
name = func;
func = null;
} var test_name = name ? name : test_environment.next_default_test_name();
properties = properties ? properties : {}; var test_obj = new Test(test_name, properties); if (func) {
test_obj.step(func, test_obj, test_obj);
}
return test_obj;
}
function promise_test(func, name, properties) { var test = async_test(name, properties); // If there is no promise tests queue make one.
test.step(function() { if (!tests.promise_tests) {
tests.promise_tests = Promise.resolve();
}
});
tests.promise_tests = tests.promise_tests.then(function() {
return Promise.resolve(test.step(func, test, test))
.then( function() {
test.done();
})
.catch(test.step_func( function(value) { if (value instanceof AssertionError) { throw value;
} assert(false, "promise_test", null, "Unhandled rejection with value: ${value}", {value:value});
}));
});
}
function promise_rejects(test, expected, promise) {
return promise.then(test.unreached_func("Should have rejected.")).catch(function(e) {
assert_throws(expected, function() { throw e });
});
}
/** *ThisconstructorhelperallowsDOMeventstobehandledusingPromises, *whichcanmakeitaloteasiertotestaveryspecificseriesofevents, *includingensuringthatunexpectedeventsarenotfiredatanypoint.
*/ function EventWatcher(test, watchedNode, eventTypes)
{ if (typeof eventTypes == 'string') {
eventTypes = [eventTypes];
}
var waitingFor = null;
var eventHandler = test.step_func(function(evt) {
assert_true(!!waitingFor, 'Not expecting event, but got ' + evt.type + ' event');
assert_equals(evt.type, waitingFor.types[0], 'Expected ' + waitingFor.types[0] + ' event, but got ' +
evt.type + ' event instead'); if (waitingFor.types.length > 1) { // Pop first event from array
waitingFor.types.shift();
return;
} // We need to null out waitingFor before calling the resolve function // since the Promise's resolve handlers may call wait_for() which will // need to set waitingFor. var resolveFunc = waitingFor.resolve;
waitingFor = null;
resolveFunc(evt);
});
for (var i = 0; i < eventTypes.length; i++) {
watchedNode.addEventListener(eventTypes[i], eventHandler);
}
/** *ReturnsaPromisethatwillresolveafterthespecifiedeventor *seriesofeventshasoccured.
*/ this.wait_for = function(types) { if (waitingFor) {
return Promise.reject('Already waiting for an event or events');
} if (typeof types == 'string') {
types = [types];
}
return new Promise(function(resolve, reject) {
waitingFor = {
types: types,
resolve: resolve,
reject: reject
};
});
};
function stop_watching() { for (var i = 0; i < eventTypes.length; i++) {
watchedNode.removeEventListener(eventTypes[i], eventHandler);
}
};
/* *Returnastringtruncatedtothegivenlength,with...addedattheend *ifitwaslonger.
*/ function truncate(s, len)
{ if (s.length > len) {
return s.substring(0, len - 3) + "...";
}
return s;
}
/* *ReturntrueifobjectisprobablyaNodeobject.
*/ function is_node(object)
{ // I use duck-typing instead of instanceof, because // instanceof doesn't work if the node is from another window (like an // iframe's contentWindow): // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295 if ("nodeType" in object && "nodeName" in object && "nodeValue" in object && "childNodes" in object) { try {
object.nodeType;
} catch (e) { // The object is probably Node.prototype or another prototype // object that inherits from it, and not a Node instance.
return false;
}
return true;
}
return false;
}
/* *Convertavaluetoanice,human-readablestring
*/ function format_value(val, seen)
{ if (!seen) {
seen = [];
} if (typeof val === "object" && val !== null) { if (seen.includes(val)) {
return "[...]";
}
seen.push(val);
} if (Array.isArray(val)) {
return "[" + val.map(function(x) {return format_value(x, seen);}).join(", ") + "]";
}
function same_value(x, y) { if (y !== y) { //NaN case
return x !== x;
} if (x === 0 && y === 0) { //Distinguish +0 and -0
return 1/x === 1/y;
}
return x === y;
}
function assert_equals(actual, expected, description)
{ /* *Testiftwoprimitivesareequalortwoobjects *arethesameobject
*/ if (typeof actual != typeof expected) { assert(false, "assert_equals", description, "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}",
{expected:expected, actual:actual});
return;
} assert(same_value(actual, expected), "assert_equals", description, "expected ${expected} but got ${actual}",
{expected:expected, actual:actual});
}
expose(assert_equals, "assert_equals");
function assert_in_array(actual, expected, description)
{ assert(expected.includes(actual), "assert_in_array", description, "value ${actual} not in array ${expected}",
{actual:actual, expected:expected});
}
expose(assert_in_array, "assert_in_array");
function assert_object_equals(actual, expected, description)
{ //This needs to be improved a great deal function check_equal(actual, expected, stack)
{
stack.push(actual);
var p; for (p in actual) { assert(expected.hasOwnProperty(p), "assert_object_equals", description, "unexpected property ${p}", {p:p});
for (var i = 0; i < actual.length; i++) { assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), "assert_array_equals", description, "property ${i}, property expected to be ${expected} but was ${actual}",
{i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
actual:actual.hasOwnProperty(i) ? "present" : "missing"}); assert(same_value(expected[i], actual[i]), "assert_array_equals", description, "property ${i}, expected ${expected} but got ${actual}",
{i:i, expected:expected[i], actual:actual[i]});
}
}
expose(assert_array_equals, "assert_array_equals");
function assert_approx_equals(actual, expected, epsilon, description)
{ /* *Testiftwoprimitivenumbersareequalwithing+/-epsilon
*/ assert(typeof actual === "number", "assert_approx_equals", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
function assert_less_than(actual, expected, description)
{ /* *Testifaprimitivenumberislessthananother
*/ assert(typeof actual === "number", "assert_less_than", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(actual < expected, "assert_less_than", description, "expected a number less than ${expected} but got ${actual}",
{expected:expected, actual:actual});
}
expose(assert_less_than, "assert_less_than");
function assert_greater_than(actual, expected, description)
{ /* *Testifaprimitivenumberisgreaterthananother
*/ assert(typeof actual === "number", "assert_greater_than", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(actual > expected, "assert_greater_than", description, "expected a number greater than ${expected} but got ${actual}",
{expected:expected, actual:actual});
}
expose(assert_greater_than, "assert_greater_than");
function assert_between_exclusive(actual, lower, upper, description)
{ /* *Testifaprimitivenumberisbetweentwoothers
*/ assert(typeof actual === "number", "assert_between_exclusive", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(actual > lower && actual < upper, "assert_between_exclusive", description, "expected a number greater than ${lower} " + "and less than ${upper} but got ${actual}",
{lower:lower, upper:upper, actual:actual});
}
expose(assert_between_exclusive, "assert_between_exclusive");
function assert_less_than_equal(actual, expected, description)
{ /* *Testifaprimitivenumberislessthanorequaltoanother
*/ assert(typeof actual === "number", "assert_less_than_equal", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(actual <= expected, "assert_less_than_equal", description, "expected a number less than or equal to ${expected} but got ${actual}",
{expected:expected, actual:actual});
}
expose(assert_less_than_equal, "assert_less_than_equal");
function assert_greater_than_equal(actual, expected, description)
{ /* *Testifaprimitivenumberisgreaterthanorequaltoanother
*/ assert(typeof actual === "number", "assert_greater_than_equal", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(actual >= expected, "assert_greater_than_equal", description, "expected a number greater than or equal to ${expected} but got ${actual}",
{expected:expected, actual:actual});
}
expose(assert_greater_than_equal, "assert_greater_than_equal");
function assert_between_inclusive(actual, lower, upper, description)
{ /* *Testifaprimitivenumberisbetweentotwoothersorequaltoeitherofthem
*/ assert(typeof actual === "number", "assert_between_inclusive", description, "expected a number but got a ${type_actual}",
{type_actual:typeof actual});
assert(actual >= lower && actual <= upper, "assert_between_inclusive", description, "expected a number greater than or equal to ${lower} " + "and less than or equal to ${upper} but got ${actual}",
{lower:lower, upper:upper, actual:actual});
}
expose(assert_between_inclusive, "assert_between_inclusive");
function _assert_inherits(name) {
return function (object, property_name, description)
{ assert(typeof object === "object",
name, description, "provided value is not an object");
assert("hasOwnProperty" in object,
name, description, "provided value is an object but has no hasOwnProperty method");
assert(!object.hasOwnProperty(property_name),
name, description, "property ${p} found on object expected in prototype chain",
{p:property_name});
assert(property_name in object,
name, description, "property ${p} not found in prototype chain",
{p:property_name});
};
}
expose(_assert_inherits("assert_inherits"), "assert_inherits");
expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
function assert_readonly(object, property_name, description)
{ var initial_value = object[property_name]; try { //Note that this can have side effects in the case where //the property has PutForwards
object[property_name] = initial_value + "a"; //XXX use some other value here? assert(same_value(object[property_name], initial_value), "assert_readonly", description, "changing property ${p} succeeded",
{p:property_name});
} finally {
object[property_name] = initial_value;
}
}
expose(assert_readonly, "assert_readonly");
function assert_throws(code, func, description)
{ try {
func.call(this); assert(false, "assert_throws", description, "${func} did not throw", {func:func});
} catch (e) { if (e instanceof AssertionError) { throw e;
} if (code === null) {
return;
} if (typeof code === "object") { assert(typeof e == "object" && "name" in e && e.name == code.name, "assert_throws", description, "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
{func:func, actual:e, actual_name:e.name,
expected:code,
expected_name:code.name});
return;
}
if (!(name in name_code_map)) { thrownew AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
}
var required_props = { code: name_code_map[name] };
if (required_props.code === 0 ||
(typeof e == "object" && "name" in e &&
e.name !== e.name.toUpperCase() &&
e.name !== "DOMException")) { // New style exception: also test the name property.
required_props.name = name;
}
//We'd like to test that e instanceof the appropriate interface, //but we can't, because we don't know what window it was created //in. It might be an instanceof the appropriate interface on some //unknown other window. TODO: Work around this somehow?
assert(typeof e == "object", "assert_throws", description, "${func} threw ${e} with type ${type}, not an object",
{func:func, e:e, type:typeof e});
for (var prop in required_props) { assert(typeof e == "object" && prop in e && e[prop] == required_props[prop], "assert_throws", description, "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
{func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
}
}
}
expose(assert_throws, "assert_throws");
function assert_any(assert_func, actual, expected_array)
{ var args = [].slice.call(arguments, 3); var errors = []; var passed = false;
forEach(expected_array, function(expected)
{ try {
assert_func.apply(this, [actual, expected].concat(args));
passed = true;
} catch (e) {
errors.push(e.message);
}
}); if (!passed) { thrownew AssertionError(errors.join("\n\n"));
}
}
expose(assert_any, "assert_any");
function Test(name, properties)
{ if (tests.file_is_test && tests.tests.length) { thrownew Error("Tried to create a test with file_is_test");
} this.name = name;
Test.prototype.step = function(func, this_obj)
{ if (this.phase > this.phases.STARTED) {
return;
} this.phase = this.phases.STARTED; //If we don't get a result before the harness times out that will be a test timout this.set_status(this.TIMEOUT, "Test timed out");
/*
* A RemoteWorker listens for test events from a worker. These events are
* then used to construct and maintain RemoteTest objects that mirror the
* tests running on the remote worker.
*/
function RemoteWorker(worker) {
this.running = true;
this.tests = new Array();
var this_obj = this;
worker.onerror = function(error) { this_obj.worker_error(error); };
var message_port;
if (is_service_worker(worker)) {
if (window.MessageChannel) {
// The ServiceWorker's implicit MessagePort is currently not
// reliably accessible from the ServiceWorkerGlobalScope due to
// Blink setting MessageEvent.source to null for messages sent
// via ServiceWorker.postMessage(). Until that's resolved,
// create an explicit MessageChannel and pass one end to the
// worker.
var message_channel = new MessageChannel();
message_port = message_channel.port1;
message_port.start();
worker.postMessage({type: "connect"}, [message_channel.port2]);
} else {
// If MessageChannel is not available, then try the
// ServiceWorker.postMessage() approach using MessageEvent.source
// on the other end.
message_port = navigator.serviceWorker;
worker.postMessage({type: "connect"});
}
} else if (is_shared_worker(worker)) {
message_port = worker.port;
} else {
message_port = worker;
}
// Keeping a reference to the worker until worker_done() is seen
// prevents the Worker object and its MessageChannel from going away
// before all the messages are dispatched.
this.worker = worker;
message_port.onmessage =
function(message) {
if (this_obj.running && (message.data.type in this_obj.message_handlers)) {
this_obj.message_handlers[message.data.type].call(this_obj, message.data);
}
};
}
RemoteWorker.prototype.worker_error = function(error) {
var message = error.message || String(error);
var filename = (error.filename ? " " + error.filename: "");
// FIXME: Display worker error states separately from main document
// error state.
this.worker_done({
status: {
status: tests.status.ERROR,
message: "Error in worker" + filename + ": " + message,
stack: error.stack
}
});
error.preventDefault();
};
RemoteWorker.prototype.test_state = function(data) {
var remote_test = this.tests[data.test.index];
if (!remote_test) {
remote_test = new RemoteTest(data.test);
this.tests[data.test.index] = remote_test;
}
remote_test.update_state_from(data.test);
tests.notify_test_state(remote_test);
};
Tests.prototype.set_file_is_test = function() {
if (this.tests.length > 0) {
throw new Error("Tried to set file as test after creating a test");
}
this.wait_for_finish = true;
this.file_is_test = true;
// Create the test, which will add it to the list of tests
async_test();
};
//If output is disabled in testharnessreport.js the test shouldn't be
//able to override that
this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
properties.output : settings.output);
};
var status_number = {};
forEach(tests,
function(test) {
var status = status_text[test.status];
if (status_number.hasOwnProperty(status)) {
status_number[status] += 1;
} else {
status_number[status] = 1;
}
});
function status_class(status)
{
return status.replace(/\s/g, '').toLowerCase();
}
var summary_template = ["section", {"id":"summary"},
["h2", {}, "Summary"],
function()
{
var status = status_text_harness[harness_status.status];
var rv = [["section", {},
["p", {},
"Harness status: ",
["span", {"class":status_class(status)},
status
],
]
]];
if (harness_status.status === harness_status.ERROR) {
rv[0].push(["pre", {}, harness_status.message]);
if (harness_status.stack) {
rv[0].push(["pre", {}, harness_status.stack]);
}
}
return rv;
},
["p", {}, "Found ${num_tests} tests"],
function() {
var rv = [["div", {}]];
var i = 0;
while (status_text.hasOwnProperty(i)) {
if (status_number.hasOwnProperty(status_text[i])) {
var status = status_text[i];
rv[0].push(["div", {"class":status_class(status)},
["label", {},
["input", {type:"checkbox", checked:"checked"}],
status_number[status] + " " + status]]);
}
i++;
}
return rv;
},
];
// This use of innerHTML plus manual escaping is not recommended in
// general, but is necessary here for performance. Using textContent
// on each individual <td> adds tens of seconds of execution time for
// large test suites (tens of thousands of tests).
function escape_html(s)
{
return s.replace(/\&/g, "&")
.replace(/</g, "<")
.replace(/"/g, """)
.replace(/'/g, "39;");
}
function has_assertions()
{
for (var i = 0; i < tests.length; i++) {
if (tests[i].properties.hasOwnProperty("assert")) {
return true;
}
}
return false;
}
function get_assertion(test)
{
if (test.properties.hasOwnProperty("assert")) {
if (Array.isArray(test.properties.assert)) {
return test.properties.assert.join(' ');
}
return test.properties.assert;
}
return '';
}
/*
* Template code
*
* A template is just a javascript structure. An element is represented as:
*
* [tag_name, {attr_name:attr_value}, child1, child2]
*
* the children can either be strings (which act like text nodes), other templates or
* functions (see below)
*
* A text node is represented as
*
* ["{text}", value]
*
* String values have a simple substitution syntax; ${foo} represents a variable foo.
*
* It is possible to embed logic in templates by using a function in a place where a
* node would usually go. The function must either return part of a template or null.
*
* In cases where a set of nodes are required as output rather than a single node
* with children it is possible to just use a list
* [node1, node2, node3]
*
* Usage:
*
* render(template, substitutions) - take a template and an object mapping
* variable names to parameters and return either a DOM node or a list of DOM nodes
*
* substitute(template, substitutions) - take a template and variable mapping object,
* make the variable substitutions and return the substituted template
*
*/
function is_single_node(template)
{
return typeof template[0] === "string";
}
function substitute(template, substitutions)
{
if (typeof template === "function") {
var replacement = template(substitutions);
if (!replacement) {
return null;
}
return substitute(replacement, substitutions);
}
if (is_single_node(template)) {
return substitute_single(template, substitutions);
}
function substitute_single(template, substitutions)
{
var substitution_re = /\$\{([^ }]*)\}/g;
function do_substitution(input) {
var components = input.split(substitution_re);
var rv = [];
for (var i = 0; i < components.length; i += 2) {
rv.push(components[i]);
if (components[i + 1]) {
rv.push(String(substitutions[components[i + 1]]));
}
}
return rv;
}
function substitute_attrs(attrs, rv)
{
rv[1] = {};
for (var name in template[1]) {
if (attrs.hasOwnProperty(name)) {
var new_name = do_substitution(name).join("");
var new_value = do_substitution(attrs[name]).join("");
rv[1][new_name] = new_value;
}
}
}
function substitute_children(children, rv)
{
for (var i = 0; i < children.length; i++) {
if (children[i] instanceof Object) {
var replacement = substitute(children[i], substitutions);
if (replacement !== null) {
if (is_single_node(replacement)) {
rv.push(replacement);
} else {
extend(rv, replacement);
}
}
} else {
extend(rv, do_substitution(String(children[i])));
}
}
return rv;
}
var rv = [];
rv.push(do_substitution(String(template[0])).join(""));
function make_dom_single(template, doc)
{
var output_document = doc || document;
var element;
if (template[0] === "{text}") {
element = output_document.createTextNode("");
for (var i = 1; i < template.length; i++) {
element.data += template[i];
}
} else {
element = output_document.createElementNS(xhtml_ns, template[0]);
for (var name in template[1]) {
if (template[1].hasOwnProperty(name)) {
element.setAttribute(name, template[1][name]);
}
}
for (var i = 2; i < template.length; i++) {
if (template[i] instanceof Object) {
var sub_element = make_dom(template[i]);
element.appendChild(sub_element);
} else {
var text_node = output_document.createTextNode(template[i]);
element.appendChild(text_node);
}
}
}
return element;
}
function make_dom(template, substitutions, output_document)
{
if (is_single_node(template)) {
return make_dom_single(template, output_document);
}
AssertionError.prototype.get_stack = function() {
var stack = new Error().stack;
// IE11 does not initialize 'Error.stack' until the object is thrown.
if (!stack) {
try {
throw new Error();
} catch (e) {
stack = e.stack;
}
}
var lines = stack.split("\n");
// Create a pattern to match stack frames originating within testharness.js. These include the
// script URL, followed by the line/col (e.g., '/resources/testharness.js:120:21').
var re = new RegExp((get_script_url() || "\\btestharness.js") + ":\\d+:\\d+");
// Some browsers include a preamble that specifies the type of the error object. Skip this by
// advancing until we find the first stack frame originating from testharness.js.
var i = 0;
while (!re.test(lines[i]) && i < lines.length) {
i++;
}
// Then skip the top frames originating from testharness.js to begin the stack at the test code.
while (re.test(lines[i]) && i < lines.length) {
i++;
}
// Paranoid check that we didn't skip all frames. If so, return the original stack unmodified.
if (i >= lines.length) {
return stack;
}
return lines.slice(i).join("\n");
}
function make_message(function_name, description, error, substitutions)
{
for (var p in substitutions) {
if (substitutions.hasOwnProperty(p)) {
substitutions[p] = format_value(substitutions[p]);
}
}
var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
merge({function_name:function_name,
description:(description?description + " ":"")},
substitutions));
return node_form.slice(1).join("");
}
function filter(array, callable, thisObj) {
var rv = [];
for (var i = 0; i < array.length; i++) {
if (array.hasOwnProperty(i)) {
var pass = callable.call(thisObj, array[i], i, array);
if (pass) {
rv.push(array[i]);
}
}
}
return rv;
}
function map(array, callable, thisObj)
{
var rv = [];
rv.length = array.length;
for (var i = 0; i < array.length; i++) {
if (array.hasOwnProperty(i)) {
rv[i] = callable.call(thisObj, array[i], i, array);
}
}
return rv;
}
function extend(array, items)
{
Array.prototype.push.apply(array, items);
}
function forEach(array, callback, thisObj)
{
for (var i = 0; i < array.length; i++) {
if (array.hasOwnProperty(i)) {
callback.call(thisObj, array[i], i, array);
}
}
}
function merge(a,b)
{
var rv = {};
var p;
for (p in a) {
rv[p] = a[p];
}
for (p in b) {
rv[p] = b[p];
}
return rv;
}
function expose(object, name)
{
var components = name.split(".");
var target = test_environment.global_scope();
for (var i = 0; i < components.length - 1; i++) {
if (!(components[i] in target)) {
target[components[i]] = {};
}
target = target[components[i]];
}
target[components[components.length - 1]] = object;
}
function is_same_origin(w) {
try {
'random_prop' in w;
return true;
} catch (e) {
return false;
}
}
/** Returns the 'src' URL of the first <script> tag in the page to include the file 'testharness.js'. */
function get_script_url()
{
if (!('document' in self)) {
return undefined;
}
var scripts = document.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
var src;
if (scripts[i].src) {
src = scripts[i].src;
} else if (scripts[i].href) {
//SVG case
src = scripts[i].href.baseVal;
}
var matches = src && src.match(/^(.*\/|)testharness\.js$/);
if (matches) {
return src;
}
}
return undefined;
}
/** Returns the URL path at which the files for testharness.js are assumed to reside (e.g., '/resources/').
The path is derived from inspecting the 'src' of the <script> tag that included 'testharness.js'. */
function get_harness_url()
{
var script_url = get_script_url();
// Exclude the 'testharness.js' file from the returned path, but '+ 1' to include the trailing slash.
return script_url ? script_url.slice(0, script_url.lastIndexOf('/') + 1) : undefined;
}
function supports_post_message(w)
{
var supports;
var type;
// Given IE implements postMessage across nested iframes but not across
// windows or tabs, you can't infer cross-origin communication from the presence
// of postMessage on the current window object only.
//
// Touching the postMessage prop on a window can throw if the window is
// not from the same origin AND post message is not supported in that
// browser. So just doing an existence test here won't do, you also need
// to wrap it in a try..cacth block.
try {
type = typeof w.postMessage;
if (type === "function") {
supports = true;
}
// IE8 supports postMessage, but implements it as a host object which
// returns "object" as its `typeof`.
else if (type === "object") {
supports = true;
}
// This is the case where postMessage isn't supported AND accessing a
// window property across origins does NOT throw (e.g. old Safari browser).
else {
supports = false;
}
} catch (e) {
// This is the case where postMessage isn't supported AND accessing a
// window property across origins throws (e.g. old Firefox browser).
supports = false;
}
return supports;
}
/**
* Setup globals
*/
var tests = new Tests();
addEventListener("error", function(e) {
if (tests.file_is_test) {
var test = tests.tests[0];
if (test.phase >= test.phases.HAS_RESULT) {
return;
}
test.set_status(test.FAIL, e.message, e.stack);
test.phase = test.phases.HAS_RESULT;
test.done();
done();
} else if (!tests.allow_uncaught_exception) {
tests.status.status = tests.status.ERROR;
tests.status.message = e.message;
tests.status.stack = e.stack;
}
});
test_environment.on_tests_ready();
})();
// vim: set expandtab shiftwidth=4 tabstop=4:
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.