1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/imptests/testharness.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,2368 @@ 1.4 +/* 1.5 +Distributed under both the W3C Test Suite License [1] and the W3C 1.6 +3-clause BSD License [2]. To contribute to a W3C Test Suite, see the 1.7 +policies and contribution forms [3]. 1.8 + 1.9 +[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license 1.10 +[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license 1.11 +[3] http://www.w3.org/2004/10/27-testcases 1.12 +*/ 1.13 + 1.14 +/* 1.15 + * == Introduction == 1.16 + * 1.17 + * This file provides a framework for writing testcases. It is intended to 1.18 + * provide a convenient API for making common assertions, and to work both 1.19 + * for testing synchronous and asynchronous DOM features in a way that 1.20 + * promotes clear, robust, tests. 1.21 + * 1.22 + * == Basic Usage == 1.23 + * 1.24 + * To use this file, import the script and the testharnessreport script into 1.25 + * the test document: 1.26 + * <script src="/resources/testharness.js"></script> 1.27 + * <script src="/resources/testharnessreport.js"></script> 1.28 + * 1.29 + * Within each file one may define one or more tests. Each test is atomic 1.30 + * in the sense that a single test has a single result (pass/fail/timeout). 1.31 + * Within each test one may have a number of asserts. The test fails at the 1.32 + * first failing assert, and the remainder of the test is (typically) not run. 1.33 + * 1.34 + * If the file containing the tests is a HTML file with an element of id "log" 1.35 + * this will be populated with a table containing the test results after all 1.36 + * the tests have run. 1.37 + * 1.38 + * NOTE: By default tests must be created before the load event fires. For ways 1.39 + * to create tests after the load event, see "Determining when all tests 1.40 + * are complete", below 1.41 + * 1.42 + * == Synchronous Tests == 1.43 + * 1.44 + * To create a synchronous test use the test() function: 1.45 + * 1.46 + * test(test_function, name, properties) 1.47 + * 1.48 + * test_function is a function that contains the code to test. For example a 1.49 + * trivial passing test would be: 1.50 + * 1.51 + * test(function() {assert_true(true)}, "assert_true with true") 1.52 + * 1.53 + * The function passed in is run in the test() call. 1.54 + * 1.55 + * properties is an object that overrides default test properties. The 1.56 + * recognised properties are: 1.57 + * timeout - the test timeout in ms 1.58 + * 1.59 + * e.g. 1.60 + * test(test_function, "Sample test", {timeout:1000}) 1.61 + * 1.62 + * would run test_function with a timeout of 1s. 1.63 + * 1.64 + * Additionally, test-specific metadata can be passed in the properties. These 1.65 + * are used when the individual test has different metadata from that stored 1.66 + * in the <head>. 1.67 + * The recognized metadata properties are: 1.68 + * 1.69 + * help - The url of the part of the specification being tested 1.70 + * 1.71 + * assert - A human readable description of what the test is attempting 1.72 + * to prove 1.73 + * 1.74 + * author - Name and contact information for the author of the test in the 1.75 + * format: "Name <email_addr>" or "Name http://contact/url" 1.76 + * 1.77 + * == Asynchronous Tests == 1.78 + * 1.79 + * Testing asynchronous features is somewhat more complex since the result of 1.80 + * a test may depend on one or more events or other callbacks. The API provided 1.81 + * for testing these features is indended to be rather low-level but hopefully 1.82 + * applicable to many situations. 1.83 + * 1.84 + * To create a test, one starts by getting a Test object using async_test: 1.85 + * 1.86 + * async_test(name, properties) 1.87 + * 1.88 + * e.g. 1.89 + * var t = async_test("Simple async test") 1.90 + * 1.91 + * Assertions can be added to the test by calling the step method of the test 1.92 + * object with a function containing the test assertions: 1.93 + * 1.94 + * t.step(function() {assert_true(true)}); 1.95 + * 1.96 + * When all the steps are complete, the done() method must be called: 1.97 + * 1.98 + * t.done(); 1.99 + * 1.100 + * As a convenience, async_test can also takes a function as first argument. 1.101 + * This function is called with the test object as both its `this` object and 1.102 + * first argument. The above example can be rewritten as: 1.103 + * 1.104 + * async_test(function(t) { 1.105 + * object.some_event = function() { 1.106 + * t.step(function (){assert_true(true); t.done();}); 1.107 + * }; 1.108 + * }, "Simple async test"); 1.109 + * 1.110 + * which avoids cluttering the global scope with references to async 1.111 + * tests instances. 1.112 + * 1.113 + * The properties argument is identical to that for test(). 1.114 + * 1.115 + * In many cases it is convenient to run a step in response to an event or a 1.116 + * callback. A convenient method of doing this is through the step_func method 1.117 + * which returns a function that, when called runs a test step. For example 1.118 + * 1.119 + * object.some_event = t.step_func(function(e) {assert_true(e.a)}); 1.120 + * 1.121 + * == Making assertions == 1.122 + * 1.123 + * Functions for making assertions start assert_ 1.124 + * The best way to get a list is to look in this file for functions names 1.125 + * matching that pattern. The general signature is 1.126 + * 1.127 + * assert_something(actual, expected, description) 1.128 + * 1.129 + * although not all assertions precisely match this pattern e.g. assert_true 1.130 + * only takes actual and description as arguments. 1.131 + * 1.132 + * The description parameter is used to present more useful error messages when 1.133 + * a test fails 1.134 + * 1.135 + * NOTE: All asserts must be located in a test() or a step of an async_test(). 1.136 + * asserts outside these places won't be detected correctly by the harness 1.137 + * and may cause a file to stop testing. 1.138 + * 1.139 + * == Harness Timeout == 1.140 + * 1.141 + * The overall harness admits two timeout values "normal" (the 1.142 + * default) and "long", used for tests which have an unusually long 1.143 + * runtime. After the timeout is reached, the harness will stop 1.144 + * waiting for further async tests to complete. By default the 1.145 + * timeouts are set to 10s and 60s, respectively, but may be changed 1.146 + * when the test is run on hardware with different performance 1.147 + * characteristics to a common desktop computer. In order to opt-in 1.148 + * to the longer test timeout, the test must specify a meta element: 1.149 + * <meta name="timeout" content="long"> 1.150 + * 1.151 + * == Setup == 1.152 + * 1.153 + * Sometimes tests require non-trivial setup that may fail. For this purpose 1.154 + * there is a setup() function, that may be called with one or two arguments. 1.155 + * The two argument version is: 1.156 + * 1.157 + * setup(func, properties) 1.158 + * 1.159 + * The one argument versions may omit either argument. 1.160 + * func is a function to be run synchronously. setup() becomes a no-op once 1.161 + * any tests have returned results. Properties are global properties of the test 1.162 + * harness. Currently recognised properties are: 1.163 + * 1.164 + * 1.165 + * explicit_done - Wait for an explicit call to done() before declaring all 1.166 + * tests complete (see below) 1.167 + * 1.168 + * output_document - The document to which results should be logged. By default 1.169 + * this is the current document but could be an ancestor 1.170 + * document in some cases e.g. a SVG test loaded in an HTML 1.171 + * wrapper 1.172 + * 1.173 + * explicit_timeout - disable file timeout; only stop waiting for results 1.174 + * when the timeout() function is called (typically for 1.175 + * use when integrating with some existing test framework 1.176 + * that has its own timeout mechanism). 1.177 + * 1.178 + * allow_uncaught_exception - don't treat an uncaught exception as an error; 1.179 + * needed when e.g. testing the window.onerror 1.180 + * handler. 1.181 + * 1.182 + * timeout_multiplier - Multiplier to apply to per-test timeouts. 1.183 + * 1.184 + * == Determining when all tests are complete == 1.185 + * 1.186 + * By default the test harness will assume there are no more results to come 1.187 + * when: 1.188 + * 1) There are no Test objects that have been created but not completed 1.189 + * 2) The load event on the document has fired 1.190 + * 1.191 + * This behaviour can be overridden by setting the explicit_done property to 1.192 + * true in a call to setup(). If explicit_done is true, the test harness will 1.193 + * not assume it is done until the global done() function is called. Once done() 1.194 + * is called, the two conditions above apply like normal. 1.195 + * 1.196 + * == Generating tests == 1.197 + * 1.198 + * NOTE: this functionality may be removed 1.199 + * 1.200 + * There are scenarios in which is is desirable to create a large number of 1.201 + * (synchronous) tests that are internally similar but vary in the parameters 1.202 + * used. To make this easier, the generate_tests function allows a single 1.203 + * function to be called with each set of parameters in a list: 1.204 + * 1.205 + * generate_tests(test_function, parameter_lists, properties) 1.206 + * 1.207 + * For example: 1.208 + * 1.209 + * generate_tests(assert_equals, [ 1.210 + * ["Sum one and one", 1+1, 2], 1.211 + * ["Sum one and zero", 1+0, 1] 1.212 + * ]) 1.213 + * 1.214 + * Is equivalent to: 1.215 + * 1.216 + * test(function() {assert_equals(1+1, 2)}, "Sum one and one") 1.217 + * test(function() {assert_equals(1+0, 1)}, "Sum one and zero") 1.218 + * 1.219 + * Note that the first item in each parameter list corresponds to the name of 1.220 + * the test. 1.221 + * 1.222 + * The properties argument is identical to that for test(). This may be a 1.223 + * single object (used for all generated tests) or an array. 1.224 + * 1.225 + * == Callback API == 1.226 + * 1.227 + * The framework provides callbacks corresponding to 3 events: 1.228 + * 1.229 + * start - happens when the first Test is created 1.230 + * result - happens when a test result is recieved 1.231 + * complete - happens when all results are recieved 1.232 + * 1.233 + * The page defining the tests may add callbacks for these events by calling 1.234 + * the following methods: 1.235 + * 1.236 + * add_start_callback(callback) - callback called with no arguments 1.237 + * add_result_callback(callback) - callback called with a test argument 1.238 + * add_completion_callback(callback) - callback called with an array of tests 1.239 + * and an status object 1.240 + * 1.241 + * tests have the following properties: 1.242 + * status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and 1.243 + * NOTRUN properties on the test object 1.244 + * message: A message indicating the reason for failure. In the future this 1.245 + * will always be a string 1.246 + * 1.247 + * The status object gives the overall status of the harness. It has the 1.248 + * following properties: 1.249 + * status: Can be compared to the OK, ERROR and TIMEOUT properties 1.250 + * message: An error message set when the status is ERROR 1.251 + * 1.252 + * == External API == 1.253 + * 1.254 + * In order to collect the results of multiple pages containing tests, the test 1.255 + * harness will, when loaded in a nested browsing context, attempt to call 1.256 + * certain functions in each ancestor and opener browsing context: 1.257 + * 1.258 + * start - start_callback 1.259 + * result - result_callback 1.260 + * complete - completion_callback 1.261 + * 1.262 + * These are given the same arguments as the corresponding internal callbacks 1.263 + * described above. 1.264 + * 1.265 + * == External API through cross-document messaging == 1.266 + * 1.267 + * Where supported, the test harness will also send messages using 1.268 + * cross-document messaging to each ancestor and opener browsing context. Since 1.269 + * it uses the wildcard keyword (*), cross-origin communication is enabled and 1.270 + * script on different origins can collect the results. 1.271 + * 1.272 + * This API follows similar conventions as those described above only slightly 1.273 + * modified to accommodate message event API. Each message is sent by the harness 1.274 + * is passed a single vanilla object, available as the `data` property of the 1.275 + * event object. These objects are structures as follows: 1.276 + * 1.277 + * start - { type: "start" } 1.278 + * result - { type: "result", test: Test } 1.279 + * complete - { type: "complete", tests: [Test, ...], status: TestsStatus } 1.280 + * 1.281 + * == List of assertions == 1.282 + * 1.283 + * assert_true(actual, description) 1.284 + * asserts that /actual/ is strictly true 1.285 + * 1.286 + * assert_false(actual, description) 1.287 + * asserts that /actual/ is strictly false 1.288 + * 1.289 + * assert_equals(actual, expected, description) 1.290 + * asserts that /actual/ is the same value as /expected/ 1.291 + * 1.292 + * assert_not_equals(actual, expected, description) 1.293 + * asserts that /actual/ is a different value to /expected/. Yes, this means 1.294 + * that "expected" is a misnomer 1.295 + * 1.296 + * assert_in_array(actual, expected, description) 1.297 + * asserts that /expected/ is an Array, and /actual/ is equal to one of the 1.298 + * members -- expected.indexOf(actual) != -1 1.299 + * 1.300 + * assert_array_equals(actual, expected, description) 1.301 + * asserts that /actual/ and /expected/ have the same length and the value of 1.302 + * each indexed property in /actual/ is the strictly equal to the corresponding 1.303 + * property value in /expected/ 1.304 + * 1.305 + * assert_approx_equals(actual, expected, epsilon, description) 1.306 + * asserts that /actual/ is a number within +/- /epsilon/ of /expected/ 1.307 + * 1.308 + * assert_less_than(actual, expected, description) 1.309 + * asserts that /actual/ is a number less than /expected/ 1.310 + * 1.311 + * assert_greater_than(actual, expected, description) 1.312 + * asserts that /actual/ is a number greater than /expected/ 1.313 + * 1.314 + * assert_less_than_equal(actual, expected, description) 1.315 + * asserts that /actual/ is a number less than or equal to /expected/ 1.316 + * 1.317 + * assert_greater_than_equal(actual, expected, description) 1.318 + * asserts that /actual/ is a number greater than or equal to /expected/ 1.319 + * 1.320 + * assert_regexp_match(actual, expected, description) 1.321 + * asserts that /actual/ matches the regexp /expected/ 1.322 + * 1.323 + * assert_class_string(object, class_name, description) 1.324 + * asserts that the class string of /object/ as returned in 1.325 + * Object.prototype.toString is equal to /class_name/. 1.326 + * 1.327 + * assert_own_property(object, property_name, description) 1.328 + * assert that object has own property property_name 1.329 + * 1.330 + * assert_inherits(object, property_name, description) 1.331 + * assert that object does not have an own property named property_name 1.332 + * but that property_name is present in the prototype chain for object 1.333 + * 1.334 + * assert_idl_attribute(object, attribute_name, description) 1.335 + * assert that an object that is an instance of some interface has the 1.336 + * attribute attribute_name following the conditions specified by WebIDL 1.337 + * 1.338 + * assert_readonly(object, property_name, description) 1.339 + * assert that property property_name on object is readonly 1.340 + * 1.341 + * assert_throws(code, func, description) 1.342 + * code - the expected exception: 1.343 + * o string: the thrown exception must be a DOMException with the given 1.344 + * name, e.g., "TimeoutError" (for compatibility with existing 1.345 + * tests, a constant is also supported, e.g., "TIMEOUT_ERR") 1.346 + * o object: the thrown exception must have a property called "name" that 1.347 + * matches code.name 1.348 + * o null: allow any exception (in general, one of the options above 1.349 + * should be used) 1.350 + * func - a function that should throw 1.351 + * 1.352 + * assert_unreached(description) 1.353 + * asserts if called. Used to ensure that some codepath is *not* taken e.g. 1.354 + * an event does not fire. 1.355 + * 1.356 + * assert_any(assert_func, actual, expected_array, extra_arg_1, ... extra_arg_N) 1.357 + * asserts that one assert_func(actual, expected_array_N, extra_arg1, ..., extra_arg_N) 1.358 + * is true for some expected_array_N in expected_array. This only works for assert_func 1.359 + * with signature assert_func(actual, expected, args_1, ..., args_N). Note that tests 1.360 + * with multiple allowed pass conditions are bad practice unless the spec specifically 1.361 + * allows multiple behaviours. Test authors should not use this method simply to hide 1.362 + * UA bugs. 1.363 + * 1.364 + * assert_exists(object, property_name, description) 1.365 + * *** deprecated *** 1.366 + * asserts that object has an own property property_name 1.367 + * 1.368 + * assert_not_exists(object, property_name, description) 1.369 + * *** deprecated *** 1.370 + * assert that object does not have own property property_name 1.371 + */ 1.372 + 1.373 +(function () 1.374 +{ 1.375 + var debug = false; 1.376 + // default timeout is 10 seconds, test can override if needed 1.377 + var settings = { 1.378 + output:true, 1.379 + harness_timeout:{"normal":10000, 1.380 + "long":60000}, 1.381 + test_timeout:null 1.382 + }; 1.383 + 1.384 + var xhtml_ns = "http://www.w3.org/1999/xhtml"; 1.385 + 1.386 + // script_prefix is used by Output.prototype.show_results() to figure out 1.387 + // where to get testharness.css from. It's enclosed in an extra closure to 1.388 + // not pollute the library's namespace with variables like "src". 1.389 + var script_prefix = null; 1.390 + (function () 1.391 + { 1.392 + var scripts = document.getElementsByTagName("script"); 1.393 + for (var i = 0; i < scripts.length; i++) 1.394 + { 1.395 + if (scripts[i].src) 1.396 + { 1.397 + var src = scripts[i].src; 1.398 + } 1.399 + else if (scripts[i].href) 1.400 + { 1.401 + //SVG case 1.402 + var src = scripts[i].href.baseVal; 1.403 + } 1.404 + if (src && src.slice(src.length - "testharness.js".length) === "testharness.js") 1.405 + { 1.406 + script_prefix = src.slice(0, src.length - "testharness.js".length); 1.407 + break; 1.408 + } 1.409 + } 1.410 + })(); 1.411 + 1.412 + /* 1.413 + * API functions 1.414 + */ 1.415 + 1.416 + var name_counter = 0; 1.417 + function next_default_name() 1.418 + { 1.419 + //Don't use document.title to work around an Opera bug in XHTML documents 1.420 + var title = document.getElementsByTagName("title")[0]; 1.421 + var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled"; 1.422 + var suffix = name_counter > 0 ? " " + name_counter : ""; 1.423 + name_counter++; 1.424 + return prefix + suffix; 1.425 + } 1.426 + 1.427 + function test(func, name, properties) 1.428 + { 1.429 + var test_name = name ? name : next_default_name(); 1.430 + properties = properties ? properties : {}; 1.431 + var test_obj = new Test(test_name, properties); 1.432 + test_obj.step(func); 1.433 + if (test_obj.phase === test_obj.phases.STARTED) { 1.434 + test_obj.done(); 1.435 + } 1.436 + } 1.437 + 1.438 + function async_test(func, name, properties) 1.439 + { 1.440 + if (typeof func !== "function") { 1.441 + properties = name; 1.442 + name = func; 1.443 + func = null; 1.444 + } 1.445 + var test_name = name ? name : next_default_name(); 1.446 + properties = properties ? properties : {}; 1.447 + var test_obj = new Test(test_name, properties); 1.448 + if (func) { 1.449 + test_obj.step(func, test_obj, test_obj); 1.450 + } 1.451 + return test_obj; 1.452 + } 1.453 + 1.454 + function setup(func_or_properties, maybe_properties) 1.455 + { 1.456 + var func = null; 1.457 + var properties = {}; 1.458 + if (arguments.length === 2) { 1.459 + func = func_or_properties; 1.460 + properties = maybe_properties; 1.461 + } else if (func_or_properties instanceof Function){ 1.462 + func = func_or_properties; 1.463 + } else { 1.464 + properties = func_or_properties; 1.465 + } 1.466 + tests.setup(func, properties); 1.467 + output.setup(properties); 1.468 + } 1.469 + 1.470 + function done() { 1.471 + tests.end_wait(); 1.472 + } 1.473 + 1.474 + function generate_tests(func, args, properties) { 1.475 + forEach(args, function(x, i) 1.476 + { 1.477 + var name = x[0]; 1.478 + test(function() 1.479 + { 1.480 + func.apply(this, x.slice(1)); 1.481 + }, 1.482 + name, 1.483 + Array.isArray(properties) ? properties[i] : properties); 1.484 + }); 1.485 + } 1.486 + 1.487 + function on_event(object, event, callback) 1.488 + { 1.489 + object.addEventListener(event, callback, false); 1.490 + } 1.491 + 1.492 + expose(test, 'test'); 1.493 + expose(async_test, 'async_test'); 1.494 + expose(generate_tests, 'generate_tests'); 1.495 + expose(setup, 'setup'); 1.496 + expose(done, 'done'); 1.497 + expose(on_event, 'on_event'); 1.498 + 1.499 + /* 1.500 + * Return a string truncated to the given length, with ... added at the end 1.501 + * if it was longer. 1.502 + */ 1.503 + function truncate(s, len) 1.504 + { 1.505 + if (s.length > len) { 1.506 + return s.substring(0, len - 3) + "..."; 1.507 + } 1.508 + return s; 1.509 + } 1.510 + 1.511 + /* 1.512 + * Return true if object is probably a Node object. 1.513 + */ 1.514 + function is_node(object) 1.515 + { 1.516 + // I use duck-typing instead of instanceof, because 1.517 + // instanceof doesn't work if the node is from another window (like an 1.518 + // iframe's contentWindow): 1.519 + // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295 1.520 + if ("nodeType" in object 1.521 + && "nodeName" in object 1.522 + && "nodeValue" in object 1.523 + && "childNodes" in object) 1.524 + { 1.525 + try 1.526 + { 1.527 + object.nodeType; 1.528 + } 1.529 + catch (e) 1.530 + { 1.531 + // The object is probably Node.prototype or another prototype 1.532 + // object that inherits from it, and not a Node instance. 1.533 + return false; 1.534 + } 1.535 + return true; 1.536 + } 1.537 + return false; 1.538 + } 1.539 + 1.540 + /* 1.541 + * Convert a value to a nice, human-readable string 1.542 + */ 1.543 + function format_value(val, seen) 1.544 + { 1.545 + if (!seen) { 1.546 + seen = []; 1.547 + } 1.548 + if (typeof val === "object" && val !== null) 1.549 + { 1.550 + if (seen.indexOf(val) >= 0) 1.551 + { 1.552 + return "[...]"; 1.553 + } 1.554 + seen.push(val); 1.555 + } 1.556 + if (Array.isArray(val)) 1.557 + { 1.558 + return "[" + val.map(function(x) {return format_value(x, seen)}).join(", ") + "]"; 1.559 + } 1.560 + 1.561 + switch (typeof val) 1.562 + { 1.563 + case "string": 1.564 + val = val.replace("\\", "\\\\"); 1.565 + for (var i = 0; i < 32; i++) 1.566 + { 1.567 + var replace = "\\"; 1.568 + switch (i) { 1.569 + case 0: replace += "0"; break; 1.570 + case 1: replace += "x01"; break; 1.571 + case 2: replace += "x02"; break; 1.572 + case 3: replace += "x03"; break; 1.573 + case 4: replace += "x04"; break; 1.574 + case 5: replace += "x05"; break; 1.575 + case 6: replace += "x06"; break; 1.576 + case 7: replace += "x07"; break; 1.577 + case 8: replace += "b"; break; 1.578 + case 9: replace += "t"; break; 1.579 + case 10: replace += "n"; break; 1.580 + case 11: replace += "v"; break; 1.581 + case 12: replace += "f"; break; 1.582 + case 13: replace += "r"; break; 1.583 + case 14: replace += "x0e"; break; 1.584 + case 15: replace += "x0f"; break; 1.585 + case 16: replace += "x10"; break; 1.586 + case 17: replace += "x11"; break; 1.587 + case 18: replace += "x12"; break; 1.588 + case 19: replace += "x13"; break; 1.589 + case 20: replace += "x14"; break; 1.590 + case 21: replace += "x15"; break; 1.591 + case 22: replace += "x16"; break; 1.592 + case 23: replace += "x17"; break; 1.593 + case 24: replace += "x18"; break; 1.594 + case 25: replace += "x19"; break; 1.595 + case 26: replace += "x1a"; break; 1.596 + case 27: replace += "x1b"; break; 1.597 + case 28: replace += "x1c"; break; 1.598 + case 29: replace += "x1d"; break; 1.599 + case 30: replace += "x1e"; break; 1.600 + case 31: replace += "x1f"; break; 1.601 + } 1.602 + val = val.replace(RegExp(String.fromCharCode(i), "g"), replace); 1.603 + } 1.604 + return '"' + val.replace(/"/g, '\\"') + '"'; 1.605 + case "boolean": 1.606 + case "undefined": 1.607 + return String(val); 1.608 + case "number": 1.609 + // In JavaScript, -0 === 0 and String(-0) == "0", so we have to 1.610 + // special-case. 1.611 + if (val === -0 && 1/val === -Infinity) 1.612 + { 1.613 + return "-0"; 1.614 + } 1.615 + return String(val); 1.616 + case "object": 1.617 + if (val === null) 1.618 + { 1.619 + return "null"; 1.620 + } 1.621 + 1.622 + // Special-case Node objects, since those come up a lot in my tests. I 1.623 + // ignore namespaces. 1.624 + if (is_node(val)) 1.625 + { 1.626 + switch (val.nodeType) 1.627 + { 1.628 + case Node.ELEMENT_NODE: 1.629 + var ret = "<" + val.tagName.toLowerCase(); 1.630 + for (var i = 0; i < val.attributes.length; i++) 1.631 + { 1.632 + ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"'; 1.633 + } 1.634 + ret += ">" + val.innerHTML + "</" + val.tagName.toLowerCase() + ">"; 1.635 + return "Element node " + truncate(ret, 60); 1.636 + case Node.TEXT_NODE: 1.637 + return 'Text node "' + truncate(val.data, 60) + '"'; 1.638 + case Node.PROCESSING_INSTRUCTION_NODE: 1.639 + return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60)); 1.640 + case Node.COMMENT_NODE: 1.641 + return "Comment node <!--" + truncate(val.data, 60) + "-->"; 1.642 + case Node.DOCUMENT_NODE: 1.643 + return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children"); 1.644 + case Node.DOCUMENT_TYPE_NODE: 1.645 + return "DocumentType node"; 1.646 + case Node.DOCUMENT_FRAGMENT_NODE: 1.647 + return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children"); 1.648 + default: 1.649 + return "Node object of unknown type"; 1.650 + } 1.651 + } 1.652 + 1.653 + // Fall through to default 1.654 + default: 1.655 + return typeof val + ' "' + truncate(String(val), 60) + '"'; 1.656 + } 1.657 + } 1.658 + expose(format_value, "format_value"); 1.659 + 1.660 + /* 1.661 + * Assertions 1.662 + */ 1.663 + 1.664 + function assert_true(actual, description) 1.665 + { 1.666 + assert(actual === true, "assert_true", description, 1.667 + "expected true got ${actual}", {actual:actual}); 1.668 + }; 1.669 + expose(assert_true, "assert_true"); 1.670 + 1.671 + function assert_false(actual, description) 1.672 + { 1.673 + assert(actual === false, "assert_false", description, 1.674 + "expected false got ${actual}", {actual:actual}); 1.675 + }; 1.676 + expose(assert_false, "assert_false"); 1.677 + 1.678 + function same_value(x, y) { 1.679 + if (y !== y) 1.680 + { 1.681 + //NaN case 1.682 + return x !== x; 1.683 + } 1.684 + else if (x === 0 && y === 0) { 1.685 + //Distinguish +0 and -0 1.686 + return 1/x === 1/y; 1.687 + } 1.688 + else 1.689 + { 1.690 + //typical case 1.691 + return x === y; 1.692 + } 1.693 + } 1.694 + 1.695 + function assert_equals(actual, expected, description) 1.696 + { 1.697 + /* 1.698 + * Test if two primitives are equal or two objects 1.699 + * are the same object 1.700 + */ 1.701 + if (typeof actual != typeof expected) 1.702 + { 1.703 + assert(false, "assert_equals", description, 1.704 + "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}", 1.705 + {expected:expected, actual:actual}); 1.706 + return; 1.707 + } 1.708 + assert(same_value(actual, expected), "assert_equals", description, 1.709 + "expected ${expected} but got ${actual}", 1.710 + {expected:expected, actual:actual}); 1.711 + }; 1.712 + expose(assert_equals, "assert_equals"); 1.713 + 1.714 + function assert_not_equals(actual, expected, description) 1.715 + { 1.716 + /* 1.717 + * Test if two primitives are unequal or two objects 1.718 + * are different objects 1.719 + */ 1.720 + assert(!same_value(actual, expected), "assert_not_equals", description, 1.721 + "got disallowed value ${actual}", 1.722 + {actual:actual}); 1.723 + }; 1.724 + expose(assert_not_equals, "assert_not_equals"); 1.725 + 1.726 + function assert_in_array(actual, expected, description) 1.727 + { 1.728 + assert(expected.indexOf(actual) != -1, "assert_in_array", description, 1.729 + "value ${actual} not in array ${expected}", 1.730 + {actual:actual, expected:expected}); 1.731 + } 1.732 + expose(assert_in_array, "assert_in_array"); 1.733 + 1.734 + function assert_object_equals(actual, expected, description) 1.735 + { 1.736 + //This needs to be improved a great deal 1.737 + function check_equal(actual, expected, stack) 1.738 + { 1.739 + stack.push(actual); 1.740 + 1.741 + var p; 1.742 + for (p in actual) 1.743 + { 1.744 + assert(expected.hasOwnProperty(p), "assert_object_equals", description, 1.745 + "unexpected property ${p}", {p:p}); 1.746 + 1.747 + if (typeof actual[p] === "object" && actual[p] !== null) 1.748 + { 1.749 + if (stack.indexOf(actual[p]) === -1) 1.750 + { 1.751 + check_equal(actual[p], expected[p], stack); 1.752 + } 1.753 + } 1.754 + else 1.755 + { 1.756 + assert(same_value(actual[p], expected[p]), "assert_object_equals", description, 1.757 + "property ${p} expected ${expected} got ${actual}", 1.758 + {p:p, expected:expected, actual:actual}); 1.759 + } 1.760 + } 1.761 + for (p in expected) 1.762 + { 1.763 + assert(actual.hasOwnProperty(p), 1.764 + "assert_object_equals", description, 1.765 + "expected property ${p} missing", {p:p}); 1.766 + } 1.767 + stack.pop(); 1.768 + } 1.769 + check_equal(actual, expected, []); 1.770 + }; 1.771 + expose(assert_object_equals, "assert_object_equals"); 1.772 + 1.773 + function assert_array_equals(actual, expected, description) 1.774 + { 1.775 + assert(actual.length === expected.length, 1.776 + "assert_array_equals", description, 1.777 + "lengths differ, expected ${expected} got ${actual}", 1.778 + {expected:expected.length, actual:actual.length}); 1.779 + 1.780 + for (var i=0; i < actual.length; i++) 1.781 + { 1.782 + assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), 1.783 + "assert_array_equals", description, 1.784 + "property ${i}, property expected to be $expected but was $actual", 1.785 + {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing", 1.786 + actual:actual.hasOwnProperty(i) ? "present" : "missing"}); 1.787 + assert(same_value(expected[i], actual[i]), 1.788 + "assert_array_equals", description, 1.789 + "property ${i}, expected ${expected} but got ${actual}", 1.790 + {i:i, expected:expected[i], actual:actual[i]}); 1.791 + } 1.792 + } 1.793 + expose(assert_array_equals, "assert_array_equals"); 1.794 + 1.795 + function assert_approx_equals(actual, expected, epsilon, description) 1.796 + { 1.797 + /* 1.798 + * Test if two primitive numbers are equal withing +/- epsilon 1.799 + */ 1.800 + assert(typeof actual === "number", 1.801 + "assert_approx_equals", description, 1.802 + "expected a number but got a ${type_actual}", 1.803 + {type_actual:typeof actual}); 1.804 + 1.805 + assert(Math.abs(actual - expected) <= epsilon, 1.806 + "assert_approx_equals", description, 1.807 + "expected ${expected} +/- ${epsilon} but got ${actual}", 1.808 + {expected:expected, actual:actual, epsilon:epsilon}); 1.809 + }; 1.810 + expose(assert_approx_equals, "assert_approx_equals"); 1.811 + 1.812 + function assert_less_than(actual, expected, description) 1.813 + { 1.814 + /* 1.815 + * Test if a primitive number is less than another 1.816 + */ 1.817 + assert(typeof actual === "number", 1.818 + "assert_less_than", description, 1.819 + "expected a number but got a ${type_actual}", 1.820 + {type_actual:typeof actual}); 1.821 + 1.822 + assert(actual < expected, 1.823 + "assert_less_than", description, 1.824 + "expected a number less than ${expected} but got ${actual}", 1.825 + {expected:expected, actual:actual}); 1.826 + }; 1.827 + expose(assert_less_than, "assert_less_than"); 1.828 + 1.829 + function assert_greater_than(actual, expected, description) 1.830 + { 1.831 + /* 1.832 + * Test if a primitive number is greater than another 1.833 + */ 1.834 + assert(typeof actual === "number", 1.835 + "assert_greater_than", description, 1.836 + "expected a number but got a ${type_actual}", 1.837 + {type_actual:typeof actual}); 1.838 + 1.839 + assert(actual > expected, 1.840 + "assert_greater_than", description, 1.841 + "expected a number greater than ${expected} but got ${actual}", 1.842 + {expected:expected, actual:actual}); 1.843 + }; 1.844 + expose(assert_greater_than, "assert_greater_than"); 1.845 + 1.846 + function assert_less_than_equal(actual, expected, description) 1.847 + { 1.848 + /* 1.849 + * Test if a primitive number is less than or equal to another 1.850 + */ 1.851 + assert(typeof actual === "number", 1.852 + "assert_less_than_equal", description, 1.853 + "expected a number but got a ${type_actual}", 1.854 + {type_actual:typeof actual}); 1.855 + 1.856 + assert(actual <= expected, 1.857 + "assert_less_than", description, 1.858 + "expected a number less than or equal to ${expected} but got ${actual}", 1.859 + {expected:expected, actual:actual}); 1.860 + }; 1.861 + expose(assert_less_than_equal, "assert_less_than_equal"); 1.862 + 1.863 + function assert_greater_than_equal(actual, expected, description) 1.864 + { 1.865 + /* 1.866 + * Test if a primitive number is greater than or equal to another 1.867 + */ 1.868 + assert(typeof actual === "number", 1.869 + "assert_greater_than_equal", description, 1.870 + "expected a number but got a ${type_actual}", 1.871 + {type_actual:typeof actual}); 1.872 + 1.873 + assert(actual >= expected, 1.874 + "assert_greater_than_equal", description, 1.875 + "expected a number greater than or equal to ${expected} but got ${actual}", 1.876 + {expected:expected, actual:actual}); 1.877 + }; 1.878 + expose(assert_greater_than_equal, "assert_greater_than_equal"); 1.879 + 1.880 + function assert_regexp_match(actual, expected, description) { 1.881 + /* 1.882 + * Test if a string (actual) matches a regexp (expected) 1.883 + */ 1.884 + assert(expected.test(actual), 1.885 + "assert_regexp_match", description, 1.886 + "expected ${expected} but got ${actual}", 1.887 + {expected:expected, actual:actual}); 1.888 + } 1.889 + expose(assert_regexp_match, "assert_regexp_match"); 1.890 + 1.891 + function assert_class_string(object, class_string, description) { 1.892 + assert_equals({}.toString.call(object), "[object " + class_string + "]", 1.893 + description); 1.894 + } 1.895 + expose(assert_class_string, "assert_class_string"); 1.896 + 1.897 + 1.898 + function _assert_own_property(name) { 1.899 + return function(object, property_name, description) 1.900 + { 1.901 + assert(object.hasOwnProperty(property_name), 1.902 + name, description, 1.903 + "expected property ${p} missing", {p:property_name}); 1.904 + }; 1.905 + } 1.906 + expose(_assert_own_property("assert_exists"), "assert_exists"); 1.907 + expose(_assert_own_property("assert_own_property"), "assert_own_property"); 1.908 + 1.909 + function assert_not_exists(object, property_name, description) 1.910 + { 1.911 + assert(!object.hasOwnProperty(property_name), 1.912 + "assert_not_exists", description, 1.913 + "unexpected property ${p} found", {p:property_name}); 1.914 + }; 1.915 + expose(assert_not_exists, "assert_not_exists"); 1.916 + 1.917 + function _assert_inherits(name) { 1.918 + return function (object, property_name, description) 1.919 + { 1.920 + assert(typeof object === "object", 1.921 + name, description, 1.922 + "provided value is not an object"); 1.923 + 1.924 + assert("hasOwnProperty" in object, 1.925 + name, description, 1.926 + "provided value is an object but has no hasOwnProperty method"); 1.927 + 1.928 + assert(!object.hasOwnProperty(property_name), 1.929 + name, description, 1.930 + "property ${p} found on object expected in prototype chain", 1.931 + {p:property_name}); 1.932 + 1.933 + assert(property_name in object, 1.934 + name, description, 1.935 + "property ${p} not found in prototype chain", 1.936 + {p:property_name}); 1.937 + }; 1.938 + } 1.939 + expose(_assert_inherits("assert_inherits"), "assert_inherits"); 1.940 + expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute"); 1.941 + 1.942 + function assert_readonly(object, property_name, description) 1.943 + { 1.944 + var initial_value = object[property_name]; 1.945 + try { 1.946 + //Note that this can have side effects in the case where 1.947 + //the property has PutForwards 1.948 + object[property_name] = initial_value + "a"; //XXX use some other value here? 1.949 + assert(same_value(object[property_name], initial_value), 1.950 + "assert_readonly", description, 1.951 + "changing property ${p} succeeded", 1.952 + {p:property_name}); 1.953 + } 1.954 + finally 1.955 + { 1.956 + object[property_name] = initial_value; 1.957 + } 1.958 + }; 1.959 + expose(assert_readonly, "assert_readonly"); 1.960 + 1.961 + function assert_throws(code, func, description) 1.962 + { 1.963 + try 1.964 + { 1.965 + func.call(this); 1.966 + assert(false, "assert_throws", description, 1.967 + "${func} did not throw", {func:func}); 1.968 + } 1.969 + catch(e) 1.970 + { 1.971 + if (e instanceof AssertionError) { 1.972 + throw(e); 1.973 + } 1.974 + if (code === null) 1.975 + { 1.976 + return; 1.977 + } 1.978 + if (typeof code === "object") 1.979 + { 1.980 + assert(typeof e == "object" && "name" in e && e.name == code.name, 1.981 + "assert_throws", description, 1.982 + "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})", 1.983 + {func:func, actual:e, actual_name:e.name, 1.984 + expected:code, 1.985 + expected_name:code.name}); 1.986 + return; 1.987 + } 1.988 + 1.989 + var code_name_map = { 1.990 + INDEX_SIZE_ERR: 'IndexSizeError', 1.991 + HIERARCHY_REQUEST_ERR: 'HierarchyRequestError', 1.992 + WRONG_DOCUMENT_ERR: 'WrongDocumentError', 1.993 + INVALID_CHARACTER_ERR: 'InvalidCharacterError', 1.994 + NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError', 1.995 + NOT_FOUND_ERR: 'NotFoundError', 1.996 + NOT_SUPPORTED_ERR: 'NotSupportedError', 1.997 + INVALID_STATE_ERR: 'InvalidStateError', 1.998 + SYNTAX_ERR: 'SyntaxError', 1.999 + INVALID_MODIFICATION_ERR: 'InvalidModificationError', 1.1000 + NAMESPACE_ERR: 'NamespaceError', 1.1001 + INVALID_ACCESS_ERR: 'InvalidAccessError', 1.1002 + TYPE_MISMATCH_ERR: 'TypeMismatchError', 1.1003 + SECURITY_ERR: 'SecurityError', 1.1004 + NETWORK_ERR: 'NetworkError', 1.1005 + ABORT_ERR: 'AbortError', 1.1006 + URL_MISMATCH_ERR: 'URLMismatchError', 1.1007 + QUOTA_EXCEEDED_ERR: 'QuotaExceededError', 1.1008 + TIMEOUT_ERR: 'TimeoutError', 1.1009 + INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError', 1.1010 + DATA_CLONE_ERR: 'DataCloneError' 1.1011 + }; 1.1012 + 1.1013 + var name = code in code_name_map ? code_name_map[code] : code; 1.1014 + 1.1015 + var name_code_map = { 1.1016 + IndexSizeError: 1, 1.1017 + HierarchyRequestError: 3, 1.1018 + WrongDocumentError: 4, 1.1019 + InvalidCharacterError: 5, 1.1020 + NoModificationAllowedError: 7, 1.1021 + NotFoundError: 8, 1.1022 + NotSupportedError: 9, 1.1023 + InvalidStateError: 11, 1.1024 + SyntaxError: 12, 1.1025 + InvalidModificationError: 13, 1.1026 + NamespaceError: 14, 1.1027 + InvalidAccessError: 15, 1.1028 + TypeMismatchError: 17, 1.1029 + SecurityError: 18, 1.1030 + NetworkError: 19, 1.1031 + AbortError: 20, 1.1032 + URLMismatchError: 21, 1.1033 + QuotaExceededError: 22, 1.1034 + TimeoutError: 23, 1.1035 + InvalidNodeTypeError: 24, 1.1036 + DataCloneError: 25, 1.1037 + 1.1038 + UnknownError: 0, 1.1039 + ConstraintError: 0, 1.1040 + DataError: 0, 1.1041 + TransactionInactiveError: 0, 1.1042 + ReadOnlyError: 0, 1.1043 + VersionError: 0 1.1044 + }; 1.1045 + 1.1046 + if (!(name in name_code_map)) 1.1047 + { 1.1048 + throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()'); 1.1049 + } 1.1050 + 1.1051 + var required_props = { code: name_code_map[name] }; 1.1052 + 1.1053 + if (required_props.code === 0 1.1054 + || ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException")) 1.1055 + { 1.1056 + // New style exception: also test the name property. 1.1057 + required_props.name = name; 1.1058 + } 1.1059 + 1.1060 + //We'd like to test that e instanceof the appropriate interface, 1.1061 + //but we can't, because we don't know what window it was created 1.1062 + //in. It might be an instanceof the appropriate interface on some 1.1063 + //unknown other window. TODO: Work around this somehow? 1.1064 + 1.1065 + assert(typeof e == "object", 1.1066 + "assert_throws", description, 1.1067 + "${func} threw ${e} with type ${type}, not an object", 1.1068 + {func:func, e:e, type:typeof e}); 1.1069 + 1.1070 + for (var prop in required_props) 1.1071 + { 1.1072 + assert(typeof e == "object" && prop in e && e[prop] == required_props[prop], 1.1073 + "assert_throws", description, 1.1074 + "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}", 1.1075 + {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]}); 1.1076 + } 1.1077 + } 1.1078 + } 1.1079 + expose(assert_throws, "assert_throws"); 1.1080 + 1.1081 + function assert_unreached(description) { 1.1082 + assert(false, "assert_unreached", description, 1.1083 + "Reached unreachable code"); 1.1084 + } 1.1085 + expose(assert_unreached, "assert_unreached"); 1.1086 + 1.1087 + function assert_any(assert_func, actual, expected_array) 1.1088 + { 1.1089 + var args = [].slice.call(arguments, 3) 1.1090 + var errors = [] 1.1091 + var passed = false; 1.1092 + forEach(expected_array, 1.1093 + function(expected) 1.1094 + { 1.1095 + try { 1.1096 + assert_func.apply(this, [actual, expected].concat(args)) 1.1097 + passed = true; 1.1098 + } catch(e) { 1.1099 + errors.push(e.message); 1.1100 + } 1.1101 + }); 1.1102 + if (!passed) { 1.1103 + throw new AssertionError(errors.join("\n\n")); 1.1104 + } 1.1105 + } 1.1106 + expose(assert_any, "assert_any"); 1.1107 + 1.1108 + function Test(name, properties) 1.1109 + { 1.1110 + this.name = name; 1.1111 + 1.1112 + this.phases = { 1.1113 + INITIAL:0, 1.1114 + STARTED:1, 1.1115 + HAS_RESULT:2, 1.1116 + COMPLETE:3 1.1117 + }; 1.1118 + this.phase = this.phases.INITIAL; 1.1119 + 1.1120 + this.status = this.NOTRUN; 1.1121 + this.timeout_id = null; 1.1122 + 1.1123 + this.properties = properties; 1.1124 + var timeout = properties.timeout ? properties.timeout : settings.test_timeout 1.1125 + if (timeout != null) { 1.1126 + this.timeout_length = timeout * tests.timeout_multiplier; 1.1127 + } else { 1.1128 + this.timeout_length = null; 1.1129 + } 1.1130 + 1.1131 + this.message = null; 1.1132 + 1.1133 + var this_obj = this; 1.1134 + this.steps = []; 1.1135 + 1.1136 + tests.push(this); 1.1137 + } 1.1138 + 1.1139 + Test.statuses = { 1.1140 + PASS:0, 1.1141 + FAIL:1, 1.1142 + TIMEOUT:2, 1.1143 + NOTRUN:3 1.1144 + }; 1.1145 + 1.1146 + Test.prototype = merge({}, Test.statuses); 1.1147 + 1.1148 + Test.prototype.structured_clone = function() 1.1149 + { 1.1150 + if(!this._structured_clone) 1.1151 + { 1.1152 + var msg = this.message; 1.1153 + msg = msg ? String(msg) : msg; 1.1154 + this._structured_clone = merge({ 1.1155 + name:String(this.name), 1.1156 + status:this.status, 1.1157 + message:msg 1.1158 + }, Test.statuses); 1.1159 + } 1.1160 + return this._structured_clone; 1.1161 + }; 1.1162 + 1.1163 + Test.prototype.step = function(func, this_obj) 1.1164 + { 1.1165 + if (this.phase > this.phases.STARTED) 1.1166 + { 1.1167 + return; 1.1168 + } 1.1169 + this.phase = this.phases.STARTED; 1.1170 + //If we don't get a result before the harness times out that will be a test timout 1.1171 + this.set_status(this.TIMEOUT, "Test timed out"); 1.1172 + 1.1173 + tests.started = true; 1.1174 + 1.1175 + if (this.timeout_id === null) 1.1176 + { 1.1177 + this.set_timeout(); 1.1178 + } 1.1179 + 1.1180 + this.steps.push(func); 1.1181 + 1.1182 + if (arguments.length === 1) 1.1183 + { 1.1184 + this_obj = this; 1.1185 + } 1.1186 + 1.1187 + try 1.1188 + { 1.1189 + return func.apply(this_obj, Array.prototype.slice.call(arguments, 2)); 1.1190 + } 1.1191 + catch(e) 1.1192 + { 1.1193 + if (this.phase >= this.phases.HAS_RESULT) 1.1194 + { 1.1195 + return; 1.1196 + } 1.1197 + var message = (typeof e === "object" && e !== null) ? e.message : e; 1.1198 + if (typeof e.stack != "undefined" && typeof e.message == "string") { 1.1199 + //Try to make it more informative for some exceptions, at least 1.1200 + //in Gecko and WebKit. This results in a stack dump instead of 1.1201 + //just errors like "Cannot read property 'parentNode' of null" 1.1202 + //or "root is null". Makes it a lot longer, of course. 1.1203 + message += "(stack: " + e.stack + ")"; 1.1204 + } 1.1205 + this.set_status(this.FAIL, message); 1.1206 + this.phase = this.phases.HAS_RESULT; 1.1207 + this.done(); 1.1208 + } 1.1209 + }; 1.1210 + 1.1211 + Test.prototype.step_func = function(func, this_obj) 1.1212 + { 1.1213 + var test_this = this; 1.1214 + 1.1215 + if (arguments.length === 1) 1.1216 + { 1.1217 + this_obj = test_this; 1.1218 + } 1.1219 + 1.1220 + return function() 1.1221 + { 1.1222 + test_this.step.apply(test_this, [func, this_obj].concat( 1.1223 + Array.prototype.slice.call(arguments))); 1.1224 + }; 1.1225 + }; 1.1226 + 1.1227 + Test.prototype.step_func_done = function(func, this_obj) 1.1228 + { 1.1229 + var test_this = this; 1.1230 + 1.1231 + if (arguments.length === 1) 1.1232 + { 1.1233 + this_obj = test_this; 1.1234 + } 1.1235 + 1.1236 + return function() 1.1237 + { 1.1238 + test_this.step.apply(test_this, [func, this_obj].concat( 1.1239 + Array.prototype.slice.call(arguments))); 1.1240 + test_this.done(); 1.1241 + }; 1.1242 + } 1.1243 + 1.1244 + Test.prototype.set_timeout = function() 1.1245 + { 1.1246 + if (this.timeout_length !== null) 1.1247 + { 1.1248 + var this_obj = this; 1.1249 + this.timeout_id = setTimeout(function() 1.1250 + { 1.1251 + this_obj.timeout(); 1.1252 + }, this.timeout_length); 1.1253 + } 1.1254 + } 1.1255 + 1.1256 + Test.prototype.set_status = function(status, message) 1.1257 + { 1.1258 + this.status = status; 1.1259 + this.message = message; 1.1260 + } 1.1261 + 1.1262 + Test.prototype.timeout = function() 1.1263 + { 1.1264 + this.timeout_id = null; 1.1265 + this.set_status(this.TIMEOUT, "Test timed out") 1.1266 + this.phase = this.phases.HAS_RESULT; 1.1267 + this.done(); 1.1268 + }; 1.1269 + 1.1270 + Test.prototype.done = function() 1.1271 + { 1.1272 + if (this.phase == this.phases.COMPLETE) { 1.1273 + return; 1.1274 + } else if (this.phase <= this.phases.STARTED) 1.1275 + { 1.1276 + this.set_status(this.PASS, null); 1.1277 + } 1.1278 + 1.1279 + if (this.status == this.NOTRUN) 1.1280 + { 1.1281 + alert(this.phase); 1.1282 + } 1.1283 + 1.1284 + this.phase = this.phases.COMPLETE; 1.1285 + 1.1286 + clearTimeout(this.timeout_id); 1.1287 + tests.result(this); 1.1288 + }; 1.1289 + 1.1290 + 1.1291 + /* 1.1292 + * Harness 1.1293 + */ 1.1294 + 1.1295 + function TestsStatus() 1.1296 + { 1.1297 + this.status = null; 1.1298 + this.message = null; 1.1299 + } 1.1300 + 1.1301 + TestsStatus.statuses = { 1.1302 + OK:0, 1.1303 + ERROR:1, 1.1304 + TIMEOUT:2 1.1305 + }; 1.1306 + 1.1307 + TestsStatus.prototype = merge({}, TestsStatus.statuses); 1.1308 + 1.1309 + TestsStatus.prototype.structured_clone = function() 1.1310 + { 1.1311 + if(!this._structured_clone) 1.1312 + { 1.1313 + var msg = this.message; 1.1314 + msg = msg ? String(msg) : msg; 1.1315 + this._structured_clone = merge({ 1.1316 + status:this.status, 1.1317 + message:msg 1.1318 + }, TestsStatus.statuses); 1.1319 + } 1.1320 + return this._structured_clone; 1.1321 + }; 1.1322 + 1.1323 + function Tests() 1.1324 + { 1.1325 + this.tests = []; 1.1326 + this.num_pending = 0; 1.1327 + 1.1328 + this.phases = { 1.1329 + INITIAL:0, 1.1330 + SETUP:1, 1.1331 + HAVE_TESTS:2, 1.1332 + HAVE_RESULTS:3, 1.1333 + COMPLETE:4 1.1334 + }; 1.1335 + this.phase = this.phases.INITIAL; 1.1336 + 1.1337 + this.properties = {}; 1.1338 + 1.1339 + //All tests can't be done until the load event fires 1.1340 + this.all_loaded = false; 1.1341 + this.wait_for_finish = false; 1.1342 + this.processing_callbacks = false; 1.1343 + 1.1344 + this.allow_uncaught_exception = false; 1.1345 + 1.1346 + this.timeout_multiplier = 1; 1.1347 + this.timeout_length = this.get_timeout(); 1.1348 + this.timeout_id = null; 1.1349 + 1.1350 + this.start_callbacks = []; 1.1351 + this.test_done_callbacks = []; 1.1352 + this.all_done_callbacks = []; 1.1353 + 1.1354 + this.status = new TestsStatus(); 1.1355 + 1.1356 + var this_obj = this; 1.1357 + 1.1358 + on_event(window, "load", 1.1359 + function() 1.1360 + { 1.1361 + this_obj.all_loaded = true; 1.1362 + if (this_obj.all_done()) 1.1363 + { 1.1364 + this_obj.complete(); 1.1365 + } 1.1366 + }); 1.1367 + 1.1368 + this.set_timeout(); 1.1369 + } 1.1370 + 1.1371 + Tests.prototype.setup = function(func, properties) 1.1372 + { 1.1373 + if (this.phase >= this.phases.HAVE_RESULTS) 1.1374 + { 1.1375 + return; 1.1376 + } 1.1377 + if (this.phase < this.phases.SETUP) 1.1378 + { 1.1379 + this.phase = this.phases.SETUP; 1.1380 + } 1.1381 + 1.1382 + this.properties = properties; 1.1383 + 1.1384 + for (var p in properties) 1.1385 + { 1.1386 + if (properties.hasOwnProperty(p)) 1.1387 + { 1.1388 + var value = properties[p] 1.1389 + if (p == "allow_uncaught_exception") { 1.1390 + this.allow_uncaught_exception = value; 1.1391 + } 1.1392 + else if (p == "explicit_done" && value) 1.1393 + { 1.1394 + this.wait_for_finish = true; 1.1395 + } 1.1396 + else if (p == "explicit_timeout" && value) { 1.1397 + this.timeout_length = null; 1.1398 + if (this.timeout_id) 1.1399 + { 1.1400 + clearTimeout(this.timeout_id); 1.1401 + } 1.1402 + } 1.1403 + else if (p == "timeout_multiplier") 1.1404 + { 1.1405 + this.timeout_multiplier = value; 1.1406 + } 1.1407 + } 1.1408 + } 1.1409 + 1.1410 + if (func) 1.1411 + { 1.1412 + try 1.1413 + { 1.1414 + func(); 1.1415 + } catch(e) 1.1416 + { 1.1417 + this.status.status = this.status.ERROR; 1.1418 + this.status.message = e; 1.1419 + }; 1.1420 + } 1.1421 + this.set_timeout(); 1.1422 + }; 1.1423 + 1.1424 + Tests.prototype.get_timeout = function() 1.1425 + { 1.1426 + var metas = document.getElementsByTagName("meta"); 1.1427 + for (var i=0; i<metas.length; i++) 1.1428 + { 1.1429 + if (metas[i].name == "timeout") 1.1430 + { 1.1431 + if (metas[i].content == "long") 1.1432 + { 1.1433 + return settings.harness_timeout.long; 1.1434 + } 1.1435 + break; 1.1436 + } 1.1437 + } 1.1438 + return settings.harness_timeout.normal; 1.1439 + } 1.1440 + 1.1441 + Tests.prototype.set_timeout = function() 1.1442 + { 1.1443 + var this_obj = this; 1.1444 + clearTimeout(this.timeout_id); 1.1445 + if (this.timeout_length !== null) 1.1446 + { 1.1447 + this.timeout_id = setTimeout(function() { 1.1448 + this_obj.timeout(); 1.1449 + }, this.timeout_length); 1.1450 + } 1.1451 + }; 1.1452 + 1.1453 + Tests.prototype.timeout = function() { 1.1454 + this.status.status = this.status.TIMEOUT; 1.1455 + this.complete(); 1.1456 + }; 1.1457 + 1.1458 + Tests.prototype.end_wait = function() 1.1459 + { 1.1460 + this.wait_for_finish = false; 1.1461 + if (this.all_done()) { 1.1462 + this.complete(); 1.1463 + } 1.1464 + }; 1.1465 + 1.1466 + Tests.prototype.push = function(test) 1.1467 + { 1.1468 + if (this.phase < this.phases.HAVE_TESTS) { 1.1469 + this.start(); 1.1470 + } 1.1471 + this.num_pending++; 1.1472 + this.tests.push(test); 1.1473 + }; 1.1474 + 1.1475 + Tests.prototype.all_done = function() { 1.1476 + return (this.all_loaded && this.num_pending === 0 && 1.1477 + !this.wait_for_finish && !this.processing_callbacks); 1.1478 + }; 1.1479 + 1.1480 + Tests.prototype.start = function() { 1.1481 + this.phase = this.phases.HAVE_TESTS; 1.1482 + this.notify_start(); 1.1483 + }; 1.1484 + 1.1485 + Tests.prototype.notify_start = function() { 1.1486 + var this_obj = this; 1.1487 + forEach (this.start_callbacks, 1.1488 + function(callback) 1.1489 + { 1.1490 + callback(this_obj.properties); 1.1491 + }); 1.1492 + forEach_windows( 1.1493 + function(w, is_same_origin) 1.1494 + { 1.1495 + if(is_same_origin && w.start_callback) 1.1496 + { 1.1497 + try 1.1498 + { 1.1499 + w.start_callback(this_obj.properties); 1.1500 + } 1.1501 + catch(e) 1.1502 + { 1.1503 + if (debug) 1.1504 + { 1.1505 + throw(e); 1.1506 + } 1.1507 + } 1.1508 + } 1.1509 + if (supports_post_message(w) && w !== self) 1.1510 + { 1.1511 + w.postMessage({ 1.1512 + type: "start", 1.1513 + properties: this_obj.properties 1.1514 + }, "*"); 1.1515 + } 1.1516 + }); 1.1517 + }; 1.1518 + 1.1519 + Tests.prototype.result = function(test) 1.1520 + { 1.1521 + if (this.phase > this.phases.HAVE_RESULTS) 1.1522 + { 1.1523 + return; 1.1524 + } 1.1525 + this.phase = this.phases.HAVE_RESULTS; 1.1526 + this.num_pending--; 1.1527 + this.notify_result(test); 1.1528 + }; 1.1529 + 1.1530 + Tests.prototype.notify_result = function(test) { 1.1531 + var this_obj = this; 1.1532 + this.processing_callbacks = true; 1.1533 + forEach(this.test_done_callbacks, 1.1534 + function(callback) 1.1535 + { 1.1536 + callback(test, this_obj); 1.1537 + }); 1.1538 + 1.1539 + forEach_windows( 1.1540 + function(w, is_same_origin) 1.1541 + { 1.1542 + if(is_same_origin && w.result_callback) 1.1543 + { 1.1544 + try 1.1545 + { 1.1546 + w.result_callback(test); 1.1547 + } 1.1548 + catch(e) 1.1549 + { 1.1550 + if(debug) { 1.1551 + throw e; 1.1552 + } 1.1553 + } 1.1554 + } 1.1555 + if (supports_post_message(w) && w !== self) 1.1556 + { 1.1557 + w.postMessage({ 1.1558 + type: "result", 1.1559 + test: test.structured_clone() 1.1560 + }, "*"); 1.1561 + } 1.1562 + }); 1.1563 + this.processing_callbacks = false; 1.1564 + if (this_obj.all_done()) 1.1565 + { 1.1566 + this_obj.complete(); 1.1567 + } 1.1568 + }; 1.1569 + 1.1570 + Tests.prototype.complete = function() { 1.1571 + if (this.phase === this.phases.COMPLETE) { 1.1572 + return; 1.1573 + } 1.1574 + this.phase = this.phases.COMPLETE; 1.1575 + var this_obj = this; 1.1576 + this.tests.forEach( 1.1577 + function(x) 1.1578 + { 1.1579 + if(x.status === x.NOTRUN) 1.1580 + { 1.1581 + this_obj.notify_result(x); 1.1582 + } 1.1583 + } 1.1584 + ); 1.1585 + this.notify_complete(); 1.1586 + }; 1.1587 + 1.1588 + Tests.prototype.notify_complete = function() 1.1589 + { 1.1590 + clearTimeout(this.timeout_id); 1.1591 + var this_obj = this; 1.1592 + var tests = map(this_obj.tests, 1.1593 + function(test) 1.1594 + { 1.1595 + return test.structured_clone(); 1.1596 + }); 1.1597 + if (this.status.status === null) 1.1598 + { 1.1599 + this.status.status = this.status.OK; 1.1600 + } 1.1601 + 1.1602 + forEach (this.all_done_callbacks, 1.1603 + function(callback) 1.1604 + { 1.1605 + callback(this_obj.tests, this_obj.status); 1.1606 + }); 1.1607 + 1.1608 + forEach_windows( 1.1609 + function(w, is_same_origin) 1.1610 + { 1.1611 + if(is_same_origin && w.completion_callback) 1.1612 + { 1.1613 + try 1.1614 + { 1.1615 + w.completion_callback(this_obj.tests, this_obj.status); 1.1616 + } 1.1617 + catch(e) 1.1618 + { 1.1619 + if (debug) 1.1620 + { 1.1621 + throw e; 1.1622 + } 1.1623 + } 1.1624 + } 1.1625 + if (supports_post_message(w) && w !== self) 1.1626 + { 1.1627 + w.postMessage({ 1.1628 + type: "complete", 1.1629 + tests: tests, 1.1630 + status: this_obj.status.structured_clone() 1.1631 + }, "*"); 1.1632 + } 1.1633 + }); 1.1634 + }; 1.1635 + 1.1636 + var tests = new Tests(); 1.1637 + 1.1638 + window.onerror = function(msg) { 1.1639 + if (!tests.allow_uncaught_exception) 1.1640 + { 1.1641 + tests.status.status = tests.status.ERROR; 1.1642 + tests.status.message = msg; 1.1643 + tests.complete(); 1.1644 + } 1.1645 + } 1.1646 + 1.1647 + function timeout() { 1.1648 + if (tests.timeout_length === null) 1.1649 + { 1.1650 + tests.timeout(); 1.1651 + } 1.1652 + } 1.1653 + expose(timeout, 'timeout'); 1.1654 + 1.1655 + function add_start_callback(callback) { 1.1656 + tests.start_callbacks.push(callback); 1.1657 + } 1.1658 + 1.1659 + function add_result_callback(callback) 1.1660 + { 1.1661 + tests.test_done_callbacks.push(callback); 1.1662 + } 1.1663 + 1.1664 + function add_completion_callback(callback) 1.1665 + { 1.1666 + tests.all_done_callbacks.push(callback); 1.1667 + } 1.1668 + 1.1669 + expose(add_start_callback, 'add_start_callback'); 1.1670 + expose(add_result_callback, 'add_result_callback'); 1.1671 + expose(add_completion_callback, 'add_completion_callback'); 1.1672 + 1.1673 + /* 1.1674 + * Output listener 1.1675 + */ 1.1676 + 1.1677 + function Output() { 1.1678 + this.output_document = document; 1.1679 + this.output_node = null; 1.1680 + this.done_count = 0; 1.1681 + this.enabled = settings.output; 1.1682 + this.phase = this.INITIAL; 1.1683 + } 1.1684 + 1.1685 + Output.prototype.INITIAL = 0; 1.1686 + Output.prototype.STARTED = 1; 1.1687 + Output.prototype.HAVE_RESULTS = 2; 1.1688 + Output.prototype.COMPLETE = 3; 1.1689 + 1.1690 + Output.prototype.setup = function(properties) { 1.1691 + if (this.phase > this.INITIAL) { 1.1692 + return; 1.1693 + } 1.1694 + 1.1695 + //If output is disabled in testharnessreport.js the test shouldn't be 1.1696 + //able to override that 1.1697 + this.enabled = this.enabled && (properties.hasOwnProperty("output") ? 1.1698 + properties.output : settings.output); 1.1699 + }; 1.1700 + 1.1701 + Output.prototype.init = function(properties) 1.1702 + { 1.1703 + if (this.phase >= this.STARTED) { 1.1704 + return; 1.1705 + } 1.1706 + if (properties.output_document) { 1.1707 + this.output_document = properties.output_document; 1.1708 + } else { 1.1709 + this.output_document = document; 1.1710 + } 1.1711 + this.phase = this.STARTED; 1.1712 + }; 1.1713 + 1.1714 + Output.prototype.resolve_log = function() 1.1715 + { 1.1716 + var output_document; 1.1717 + if (typeof this.output_document === "function") 1.1718 + { 1.1719 + output_document = this.output_document.apply(undefined); 1.1720 + } else 1.1721 + { 1.1722 + output_document = this.output_document; 1.1723 + } 1.1724 + if (!output_document) 1.1725 + { 1.1726 + return; 1.1727 + } 1.1728 + var node = output_document.getElementById("log"); 1.1729 + if (node) 1.1730 + { 1.1731 + this.output_document = output_document; 1.1732 + this.output_node = node; 1.1733 + } 1.1734 + }; 1.1735 + 1.1736 + Output.prototype.show_status = function(test) 1.1737 + { 1.1738 + if (this.phase < this.STARTED) 1.1739 + { 1.1740 + this.init(); 1.1741 + } 1.1742 + if (!this.enabled) 1.1743 + { 1.1744 + return; 1.1745 + } 1.1746 + if (this.phase < this.HAVE_RESULTS) 1.1747 + { 1.1748 + this.resolve_log(); 1.1749 + this.phase = this.HAVE_RESULTS; 1.1750 + } 1.1751 + this.done_count++; 1.1752 + if (this.output_node) 1.1753 + { 1.1754 + if (this.done_count < 100 1.1755 + || (this.done_count < 1000 && this.done_count % 100 == 0) 1.1756 + || this.done_count % 1000 == 0) { 1.1757 + this.output_node.textContent = "Running, " 1.1758 + + this.done_count + " complete, " 1.1759 + + tests.num_pending + " remain"; 1.1760 + } 1.1761 + } 1.1762 + }; 1.1763 + 1.1764 + Output.prototype.show_results = function (tests, harness_status) 1.1765 + { 1.1766 + if (this.phase >= this.COMPLETE) { 1.1767 + return; 1.1768 + } 1.1769 + if (!this.enabled) 1.1770 + { 1.1771 + return; 1.1772 + } 1.1773 + if (!this.output_node) { 1.1774 + this.resolve_log(); 1.1775 + } 1.1776 + this.phase = this.COMPLETE; 1.1777 + 1.1778 + var log = this.output_node; 1.1779 + if (!log) 1.1780 + { 1.1781 + return; 1.1782 + } 1.1783 + var output_document = this.output_document; 1.1784 + 1.1785 + while (log.lastChild) 1.1786 + { 1.1787 + log.removeChild(log.lastChild); 1.1788 + } 1.1789 + 1.1790 + if (script_prefix != null) { 1.1791 + var stylesheet = output_document.createElementNS(xhtml_ns, "link"); 1.1792 + stylesheet.setAttribute("rel", "stylesheet"); 1.1793 + stylesheet.setAttribute("href", script_prefix + "testharness.css"); 1.1794 + var heads = output_document.getElementsByTagName("head"); 1.1795 + if (heads.length) { 1.1796 + heads[0].appendChild(stylesheet); 1.1797 + } 1.1798 + } 1.1799 + 1.1800 + var status_text_harness = {}; 1.1801 + status_text_harness[harness_status.OK] = "OK"; 1.1802 + status_text_harness[harness_status.ERROR] = "Error"; 1.1803 + status_text_harness[harness_status.TIMEOUT] = "Timeout"; 1.1804 + 1.1805 + var status_text = {}; 1.1806 + status_text[Test.prototype.PASS] = "Pass"; 1.1807 + status_text[Test.prototype.FAIL] = "Fail"; 1.1808 + status_text[Test.prototype.TIMEOUT] = "Timeout"; 1.1809 + status_text[Test.prototype.NOTRUN] = "Not Run"; 1.1810 + 1.1811 + var status_number = {}; 1.1812 + forEach(tests, function(test) { 1.1813 + var status = status_text[test.status]; 1.1814 + if (status_number.hasOwnProperty(status)) 1.1815 + { 1.1816 + status_number[status] += 1; 1.1817 + } else { 1.1818 + status_number[status] = 1; 1.1819 + } 1.1820 + }); 1.1821 + 1.1822 + function status_class(status) 1.1823 + { 1.1824 + return status.replace(/\s/g, '').toLowerCase(); 1.1825 + } 1.1826 + 1.1827 + var summary_template = ["section", {"id":"summary"}, 1.1828 + ["h2", {}, "Summary"], 1.1829 + function(vars) 1.1830 + { 1.1831 + if (harness_status.status === harness_status.OK) 1.1832 + { 1.1833 + return null; 1.1834 + } 1.1835 + else 1.1836 + { 1.1837 + var status = status_text_harness[harness_status.status]; 1.1838 + var rv = [["p", {"class":status_class(status)}]]; 1.1839 + 1.1840 + if (harness_status.status === harness_status.ERROR) 1.1841 + { 1.1842 + rv[0].push("Harness encountered an error:"); 1.1843 + rv.push(["pre", {}, harness_status.message]); 1.1844 + } 1.1845 + else if (harness_status.status === harness_status.TIMEOUT) 1.1846 + { 1.1847 + rv[0].push("Harness timed out."); 1.1848 + } 1.1849 + else 1.1850 + { 1.1851 + rv[0].push("Harness got an unexpected status."); 1.1852 + } 1.1853 + 1.1854 + return rv; 1.1855 + } 1.1856 + }, 1.1857 + ["p", {}, "Found ${num_tests} tests"], 1.1858 + function(vars) { 1.1859 + var rv = [["div", {}]]; 1.1860 + var i=0; 1.1861 + while (status_text.hasOwnProperty(i)) { 1.1862 + if (status_number.hasOwnProperty(status_text[i])) { 1.1863 + var status = status_text[i]; 1.1864 + rv[0].push(["div", {"class":status_class(status)}, 1.1865 + ["label", {}, 1.1866 + ["input", {type:"checkbox", checked:"checked"}], 1.1867 + status_number[status] + " " + status]]); 1.1868 + } 1.1869 + i++; 1.1870 + } 1.1871 + return rv; 1.1872 + }]; 1.1873 + 1.1874 + log.appendChild(render(summary_template, {num_tests:tests.length}, output_document)); 1.1875 + 1.1876 + forEach(output_document.querySelectorAll("section#summary label"), 1.1877 + function(element) 1.1878 + { 1.1879 + on_event(element, "click", 1.1880 + function(e) 1.1881 + { 1.1882 + if (output_document.getElementById("results") === null) 1.1883 + { 1.1884 + e.preventDefault(); 1.1885 + return; 1.1886 + } 1.1887 + var result_class = element.parentNode.getAttribute("class"); 1.1888 + var style_element = output_document.querySelector("style#hide-" + result_class); 1.1889 + var input_element = element.querySelector("input"); 1.1890 + if (!style_element && !input_element.checked) { 1.1891 + style_element = output_document.createElementNS(xhtml_ns, "style"); 1.1892 + style_element.id = "hide-" + result_class; 1.1893 + style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}"; 1.1894 + output_document.body.appendChild(style_element); 1.1895 + } else if (style_element && input_element.checked) { 1.1896 + style_element.parentNode.removeChild(style_element); 1.1897 + } 1.1898 + }); 1.1899 + }); 1.1900 + 1.1901 + // This use of innerHTML plus manual escaping is not recommended in 1.1902 + // general, but is necessary here for performance. Using textContent 1.1903 + // on each individual <td> adds tens of seconds of execution time for 1.1904 + // large test suites (tens of thousands of tests). 1.1905 + function escape_html(s) 1.1906 + { 1.1907 + return s.replace(/\&/g, "&") 1.1908 + .replace(/</g, "<") 1.1909 + .replace(/"/g, """) 1.1910 + .replace(/'/g, "'"); 1.1911 + } 1.1912 + 1.1913 + function has_assertions() 1.1914 + { 1.1915 + for (var i = 0; i < tests.length; i++) { 1.1916 + if (tests[i].properties.hasOwnProperty("assert")) { 1.1917 + return true; 1.1918 + } 1.1919 + } 1.1920 + return false; 1.1921 + } 1.1922 + 1.1923 + function get_assertion(test) 1.1924 + { 1.1925 + if (test.properties.hasOwnProperty("assert")) { 1.1926 + if (Array.isArray(test.properties.assert)) { 1.1927 + return test.properties.assert.join(' '); 1.1928 + } 1.1929 + return test.properties.assert; 1.1930 + } 1.1931 + return ''; 1.1932 + } 1.1933 + 1.1934 + log.appendChild(document.createElementNS(xhtml_ns, "section")); 1.1935 + var assertions = has_assertions(); 1.1936 + var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">" 1.1937 + + "<thead><tr><th>Result</th><th>Test Name</th>" 1.1938 + + (assertions ? "<th>Assertion</th>" : "") 1.1939 + + "<th>Message</th></tr></thead>" 1.1940 + + "<tbody>"; 1.1941 + for (var i = 0; i < tests.length; i++) { 1.1942 + html += '<tr class="' 1.1943 + + escape_html(status_class(status_text[tests[i].status])) 1.1944 + + '"><td>' 1.1945 + + escape_html(status_text[tests[i].status]) 1.1946 + + "</td><td>" 1.1947 + + escape_html(tests[i].name) 1.1948 + + "</td><td>" 1.1949 + + (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") 1.1950 + + escape_html(tests[i].message ? tests[i].message : " ") 1.1951 + + "</td></tr>"; 1.1952 + } 1.1953 + html += "</tbody></table>"; 1.1954 + try { 1.1955 + log.lastChild.innerHTML = html; 1.1956 + } catch (e) { 1.1957 + log.appendChild(document.createElementNS(xhtml_ns, "p")) 1.1958 + .textContent = "Setting innerHTML for the log threw an exception."; 1.1959 + log.appendChild(document.createElementNS(xhtml_ns, "pre")) 1.1960 + .textContent = html; 1.1961 + } 1.1962 + }; 1.1963 + 1.1964 + var output = new Output(); 1.1965 + add_start_callback(function (properties) {output.init(properties);}); 1.1966 + add_result_callback(function (test) {output.show_status(tests);}); 1.1967 + add_completion_callback(function (tests, harness_status) {output.show_results(tests, harness_status);}); 1.1968 + 1.1969 + /* 1.1970 + * Template code 1.1971 + * 1.1972 + * A template is just a javascript structure. An element is represented as: 1.1973 + * 1.1974 + * [tag_name, {attr_name:attr_value}, child1, child2] 1.1975 + * 1.1976 + * the children can either be strings (which act like text nodes), other templates or 1.1977 + * functions (see below) 1.1978 + * 1.1979 + * A text node is represented as 1.1980 + * 1.1981 + * ["{text}", value] 1.1982 + * 1.1983 + * String values have a simple substitution syntax; ${foo} represents a variable foo. 1.1984 + * 1.1985 + * It is possible to embed logic in templates by using a function in a place where a 1.1986 + * node would usually go. The function must either return part of a template or null. 1.1987 + * 1.1988 + * In cases where a set of nodes are required as output rather than a single node 1.1989 + * with children it is possible to just use a list 1.1990 + * [node1, node2, node3] 1.1991 + * 1.1992 + * Usage: 1.1993 + * 1.1994 + * render(template, substitutions) - take a template and an object mapping 1.1995 + * variable names to parameters and return either a DOM node or a list of DOM nodes 1.1996 + * 1.1997 + * substitute(template, substitutions) - take a template and variable mapping object, 1.1998 + * make the variable substitutions and return the substituted template 1.1999 + * 1.2000 + */ 1.2001 + 1.2002 + function is_single_node(template) 1.2003 + { 1.2004 + return typeof template[0] === "string"; 1.2005 + } 1.2006 + 1.2007 + function substitute(template, substitutions) 1.2008 + { 1.2009 + if (typeof template === "function") { 1.2010 + var replacement = template(substitutions); 1.2011 + if (replacement) 1.2012 + { 1.2013 + var rv = substitute(replacement, substitutions); 1.2014 + return rv; 1.2015 + } 1.2016 + else 1.2017 + { 1.2018 + return null; 1.2019 + } 1.2020 + } 1.2021 + else if (is_single_node(template)) 1.2022 + { 1.2023 + return substitute_single(template, substitutions); 1.2024 + } 1.2025 + else 1.2026 + { 1.2027 + return filter(map(template, function(x) { 1.2028 + return substitute(x, substitutions); 1.2029 + }), function(x) {return x !== null;}); 1.2030 + } 1.2031 + } 1.2032 + 1.2033 + function substitute_single(template, substitutions) 1.2034 + { 1.2035 + var substitution_re = /\${([^ }]*)}/g; 1.2036 + 1.2037 + function do_substitution(input) { 1.2038 + var components = input.split(substitution_re); 1.2039 + var rv = []; 1.2040 + for (var i=0; i<components.length; i+=2) 1.2041 + { 1.2042 + rv.push(components[i]); 1.2043 + if (components[i+1]) 1.2044 + { 1.2045 + rv.push(String(substitutions[components[i+1]])); 1.2046 + } 1.2047 + } 1.2048 + return rv; 1.2049 + } 1.2050 + 1.2051 + var rv = []; 1.2052 + rv.push(do_substitution(String(template[0])).join("")); 1.2053 + 1.2054 + if (template[0] === "{text}") { 1.2055 + substitute_children(template.slice(1), rv); 1.2056 + } else { 1.2057 + substitute_attrs(template[1], rv); 1.2058 + substitute_children(template.slice(2), rv); 1.2059 + } 1.2060 + 1.2061 + function substitute_attrs(attrs, rv) 1.2062 + { 1.2063 + rv[1] = {}; 1.2064 + for (var name in template[1]) 1.2065 + { 1.2066 + if (attrs.hasOwnProperty(name)) 1.2067 + { 1.2068 + var new_name = do_substitution(name).join(""); 1.2069 + var new_value = do_substitution(attrs[name]).join(""); 1.2070 + rv[1][new_name] = new_value; 1.2071 + }; 1.2072 + } 1.2073 + } 1.2074 + 1.2075 + function substitute_children(children, rv) 1.2076 + { 1.2077 + for (var i=0; i<children.length; i++) 1.2078 + { 1.2079 + if (children[i] instanceof Object) { 1.2080 + var replacement = substitute(children[i], substitutions); 1.2081 + if (replacement !== null) 1.2082 + { 1.2083 + if (is_single_node(replacement)) 1.2084 + { 1.2085 + rv.push(replacement); 1.2086 + } 1.2087 + else 1.2088 + { 1.2089 + extend(rv, replacement); 1.2090 + } 1.2091 + } 1.2092 + } 1.2093 + else 1.2094 + { 1.2095 + extend(rv, do_substitution(String(children[i]))); 1.2096 + } 1.2097 + } 1.2098 + return rv; 1.2099 + } 1.2100 + 1.2101 + return rv; 1.2102 + } 1.2103 + 1.2104 + function make_dom_single(template, doc) 1.2105 + { 1.2106 + var output_document = doc || document; 1.2107 + if (template[0] === "{text}") 1.2108 + { 1.2109 + var element = output_document.createTextNode(""); 1.2110 + for (var i=1; i<template.length; i++) 1.2111 + { 1.2112 + element.data += template[i]; 1.2113 + } 1.2114 + } 1.2115 + else 1.2116 + { 1.2117 + var element = output_document.createElementNS(xhtml_ns, template[0]); 1.2118 + for (var name in template[1]) { 1.2119 + if (template[1].hasOwnProperty(name)) 1.2120 + { 1.2121 + element.setAttribute(name, template[1][name]); 1.2122 + } 1.2123 + } 1.2124 + for (var i=2; i<template.length; i++) 1.2125 + { 1.2126 + if (template[i] instanceof Object) 1.2127 + { 1.2128 + var sub_element = make_dom(template[i]); 1.2129 + element.appendChild(sub_element); 1.2130 + } 1.2131 + else 1.2132 + { 1.2133 + var text_node = output_document.createTextNode(template[i]); 1.2134 + element.appendChild(text_node); 1.2135 + } 1.2136 + } 1.2137 + } 1.2138 + 1.2139 + return element; 1.2140 + } 1.2141 + 1.2142 + 1.2143 + 1.2144 + function make_dom(template, substitutions, output_document) 1.2145 + { 1.2146 + if (is_single_node(template)) 1.2147 + { 1.2148 + return make_dom_single(template, output_document); 1.2149 + } 1.2150 + else 1.2151 + { 1.2152 + return map(template, function(x) { 1.2153 + return make_dom_single(x, output_document); 1.2154 + }); 1.2155 + } 1.2156 + } 1.2157 + 1.2158 + function render(template, substitutions, output_document) 1.2159 + { 1.2160 + return make_dom(substitute(template, substitutions), output_document); 1.2161 + } 1.2162 + 1.2163 + /* 1.2164 + * Utility funcions 1.2165 + */ 1.2166 + function assert(expected_true, function_name, description, error, substitutions) 1.2167 + { 1.2168 + if (expected_true !== true) 1.2169 + { 1.2170 + throw new AssertionError(make_message(function_name, description, 1.2171 + error, substitutions)); 1.2172 + } 1.2173 + } 1.2174 + 1.2175 + function AssertionError(message) 1.2176 + { 1.2177 + this.message = message; 1.2178 + } 1.2179 + 1.2180 + function make_message(function_name, description, error, substitutions) 1.2181 + { 1.2182 + for (var p in substitutions) { 1.2183 + if (substitutions.hasOwnProperty(p)) { 1.2184 + substitutions[p] = format_value(substitutions[p]); 1.2185 + } 1.2186 + } 1.2187 + var node_form = substitute(["{text}", "${function_name}: ${description}" + error], 1.2188 + merge({function_name:function_name, 1.2189 + description:(description?description + " ":"")}, 1.2190 + substitutions)); 1.2191 + return node_form.slice(1).join(""); 1.2192 + } 1.2193 + 1.2194 + function filter(array, callable, thisObj) { 1.2195 + var rv = []; 1.2196 + for (var i=0; i<array.length; i++) 1.2197 + { 1.2198 + if (array.hasOwnProperty(i)) 1.2199 + { 1.2200 + var pass = callable.call(thisObj, array[i], i, array); 1.2201 + if (pass) { 1.2202 + rv.push(array[i]); 1.2203 + } 1.2204 + } 1.2205 + } 1.2206 + return rv; 1.2207 + } 1.2208 + 1.2209 + function map(array, callable, thisObj) 1.2210 + { 1.2211 + var rv = []; 1.2212 + rv.length = array.length; 1.2213 + for (var i=0; i<array.length; i++) 1.2214 + { 1.2215 + if (array.hasOwnProperty(i)) 1.2216 + { 1.2217 + rv[i] = callable.call(thisObj, array[i], i, array); 1.2218 + } 1.2219 + } 1.2220 + return rv; 1.2221 + } 1.2222 + 1.2223 + function extend(array, items) 1.2224 + { 1.2225 + Array.prototype.push.apply(array, items); 1.2226 + } 1.2227 + 1.2228 + function forEach (array, callback, thisObj) 1.2229 + { 1.2230 + for (var i=0; i<array.length; i++) 1.2231 + { 1.2232 + if (array.hasOwnProperty(i)) 1.2233 + { 1.2234 + callback.call(thisObj, array[i], i, array); 1.2235 + } 1.2236 + } 1.2237 + } 1.2238 + 1.2239 + function merge(a,b) 1.2240 + { 1.2241 + var rv = {}; 1.2242 + var p; 1.2243 + for (p in a) 1.2244 + { 1.2245 + rv[p] = a[p]; 1.2246 + } 1.2247 + for (p in b) { 1.2248 + rv[p] = b[p]; 1.2249 + } 1.2250 + return rv; 1.2251 + } 1.2252 + 1.2253 + function expose(object, name) 1.2254 + { 1.2255 + var components = name.split("."); 1.2256 + var target = window; 1.2257 + for (var i=0; i<components.length - 1; i++) 1.2258 + { 1.2259 + if (!(components[i] in target)) 1.2260 + { 1.2261 + target[components[i]] = {}; 1.2262 + } 1.2263 + target = target[components[i]]; 1.2264 + } 1.2265 + target[components[components.length - 1]] = object; 1.2266 + } 1.2267 + 1.2268 + function forEach_windows(callback) { 1.2269 + // Iterate of the the windows [self ... top, opener]. The callback is passed 1.2270 + // two objects, the first one is the windows object itself, the second one 1.2271 + // is a boolean indicating whether or not its on the same origin as the 1.2272 + // current window. 1.2273 + var cache = forEach_windows.result_cache; 1.2274 + if (!cache) { 1.2275 + cache = [[self, true]]; 1.2276 + var w = self; 1.2277 + var i = 0; 1.2278 + var so; 1.2279 + var origins = location.ancestorOrigins; 1.2280 + while (w != w.parent) 1.2281 + { 1.2282 + w = w.parent; 1.2283 + // In WebKit, calls to parent windows' properties that aren't on the same 1.2284 + // origin cause an error message to be displayed in the error console but 1.2285 + // don't throw an exception. This is a deviation from the current HTML5 1.2286 + // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504 1.2287 + // The problem with WebKit's behavior is that it pollutes the error console 1.2288 + // with error messages that can't be caught. 1.2289 + // 1.2290 + // This issue can be mitigated by relying on the (for now) proprietary 1.2291 + // `location.ancestorOrigins` property which returns an ordered list of 1.2292 + // the origins of enclosing windows. See: 1.2293 + // http://trac.webkit.org/changeset/113945. 1.2294 + if(origins) { 1.2295 + so = (location.origin == origins[i]); 1.2296 + } 1.2297 + else 1.2298 + { 1.2299 + so = is_same_origin(w); 1.2300 + } 1.2301 + cache.push([w, so]); 1.2302 + i++; 1.2303 + } 1.2304 + w = window.opener; 1.2305 + if(w) 1.2306 + { 1.2307 + // window.opener isn't included in the `location.ancestorOrigins` prop. 1.2308 + // We'll just have to deal with a simple check and an error msg on WebKit 1.2309 + // browsers in this case. 1.2310 + cache.push([w, is_same_origin(w)]); 1.2311 + } 1.2312 + forEach_windows.result_cache = cache; 1.2313 + } 1.2314 + 1.2315 + forEach(cache, 1.2316 + function(a) 1.2317 + { 1.2318 + callback.apply(null, a); 1.2319 + }); 1.2320 + } 1.2321 + 1.2322 + function is_same_origin(w) { 1.2323 + try { 1.2324 + 'random_prop' in w; 1.2325 + return true; 1.2326 + } catch(e) { 1.2327 + return false; 1.2328 + } 1.2329 + } 1.2330 + 1.2331 + function supports_post_message(w) 1.2332 + { 1.2333 + var supports; 1.2334 + var type; 1.2335 + // Given IE implements postMessage across nested iframes but not across 1.2336 + // windows or tabs, you can't infer cross-origin communication from the presence 1.2337 + // of postMessage on the current window object only. 1.2338 + // 1.2339 + // Touching the postMessage prop on a window can throw if the window is 1.2340 + // not from the same origin AND post message is not supported in that 1.2341 + // browser. So just doing an existence test here won't do, you also need 1.2342 + // to wrap it in a try..cacth block. 1.2343 + try 1.2344 + { 1.2345 + type = typeof w.postMessage; 1.2346 + if (type === "function") 1.2347 + { 1.2348 + supports = true; 1.2349 + } 1.2350 + // IE8 supports postMessage, but implements it as a host object which 1.2351 + // returns "object" as its `typeof`. 1.2352 + else if (type === "object") 1.2353 + { 1.2354 + supports = true; 1.2355 + } 1.2356 + // This is the case where postMessage isn't supported AND accessing a 1.2357 + // window property across origins does NOT throw (e.g. old Safari browser). 1.2358 + else 1.2359 + { 1.2360 + supports = false; 1.2361 + } 1.2362 + } 1.2363 + catch(e) { 1.2364 + // This is the case where postMessage isn't supported AND accessing a 1.2365 + // window property across origins throws (e.g. old Firefox browser). 1.2366 + supports = false; 1.2367 + } 1.2368 + return supports; 1.2369 + } 1.2370 +})(); 1.2371 +// vim: set expandtab shiftwidth=4 tabstop=4: