dom/imptests/testharness.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /*
michael@0 2 Distributed under both the W3C Test Suite License [1] and the W3C
michael@0 3 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
michael@0 4 policies and contribution forms [3].
michael@0 5
michael@0 6 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
michael@0 7 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
michael@0 8 [3] http://www.w3.org/2004/10/27-testcases
michael@0 9 */
michael@0 10
michael@0 11 /*
michael@0 12 * == Introduction ==
michael@0 13 *
michael@0 14 * This file provides a framework for writing testcases. It is intended to
michael@0 15 * provide a convenient API for making common assertions, and to work both
michael@0 16 * for testing synchronous and asynchronous DOM features in a way that
michael@0 17 * promotes clear, robust, tests.
michael@0 18 *
michael@0 19 * == Basic Usage ==
michael@0 20 *
michael@0 21 * To use this file, import the script and the testharnessreport script into
michael@0 22 * the test document:
michael@0 23 * <script src="/resources/testharness.js"></script>
michael@0 24 * <script src="/resources/testharnessreport.js"></script>
michael@0 25 *
michael@0 26 * Within each file one may define one or more tests. Each test is atomic
michael@0 27 * in the sense that a single test has a single result (pass/fail/timeout).
michael@0 28 * Within each test one may have a number of asserts. The test fails at the
michael@0 29 * first failing assert, and the remainder of the test is (typically) not run.
michael@0 30 *
michael@0 31 * If the file containing the tests is a HTML file with an element of id "log"
michael@0 32 * this will be populated with a table containing the test results after all
michael@0 33 * the tests have run.
michael@0 34 *
michael@0 35 * NOTE: By default tests must be created before the load event fires. For ways
michael@0 36 * to create tests after the load event, see "Determining when all tests
michael@0 37 * are complete", below
michael@0 38 *
michael@0 39 * == Synchronous Tests ==
michael@0 40 *
michael@0 41 * To create a synchronous test use the test() function:
michael@0 42 *
michael@0 43 * test(test_function, name, properties)
michael@0 44 *
michael@0 45 * test_function is a function that contains the code to test. For example a
michael@0 46 * trivial passing test would be:
michael@0 47 *
michael@0 48 * test(function() {assert_true(true)}, "assert_true with true")
michael@0 49 *
michael@0 50 * The function passed in is run in the test() call.
michael@0 51 *
michael@0 52 * properties is an object that overrides default test properties. The
michael@0 53 * recognised properties are:
michael@0 54 * timeout - the test timeout in ms
michael@0 55 *
michael@0 56 * e.g.
michael@0 57 * test(test_function, "Sample test", {timeout:1000})
michael@0 58 *
michael@0 59 * would run test_function with a timeout of 1s.
michael@0 60 *
michael@0 61 * Additionally, test-specific metadata can be passed in the properties. These
michael@0 62 * are used when the individual test has different metadata from that stored
michael@0 63 * in the <head>.
michael@0 64 * The recognized metadata properties are:
michael@0 65 *
michael@0 66 * help - The url of the part of the specification being tested
michael@0 67 *
michael@0 68 * assert - A human readable description of what the test is attempting
michael@0 69 * to prove
michael@0 70 *
michael@0 71 * author - Name and contact information for the author of the test in the
michael@0 72 * format: "Name <email_addr>" or "Name http://contact/url"
michael@0 73 *
michael@0 74 * == Asynchronous Tests ==
michael@0 75 *
michael@0 76 * Testing asynchronous features is somewhat more complex since the result of
michael@0 77 * a test may depend on one or more events or other callbacks. The API provided
michael@0 78 * for testing these features is indended to be rather low-level but hopefully
michael@0 79 * applicable to many situations.
michael@0 80 *
michael@0 81 * To create a test, one starts by getting a Test object using async_test:
michael@0 82 *
michael@0 83 * async_test(name, properties)
michael@0 84 *
michael@0 85 * e.g.
michael@0 86 * var t = async_test("Simple async test")
michael@0 87 *
michael@0 88 * Assertions can be added to the test by calling the step method of the test
michael@0 89 * object with a function containing the test assertions:
michael@0 90 *
michael@0 91 * t.step(function() {assert_true(true)});
michael@0 92 *
michael@0 93 * When all the steps are complete, the done() method must be called:
michael@0 94 *
michael@0 95 * t.done();
michael@0 96 *
michael@0 97 * As a convenience, async_test can also takes a function as first argument.
michael@0 98 * This function is called with the test object as both its `this` object and
michael@0 99 * first argument. The above example can be rewritten as:
michael@0 100 *
michael@0 101 * async_test(function(t) {
michael@0 102 * object.some_event = function() {
michael@0 103 * t.step(function (){assert_true(true); t.done();});
michael@0 104 * };
michael@0 105 * }, "Simple async test");
michael@0 106 *
michael@0 107 * which avoids cluttering the global scope with references to async
michael@0 108 * tests instances.
michael@0 109 *
michael@0 110 * The properties argument is identical to that for test().
michael@0 111 *
michael@0 112 * In many cases it is convenient to run a step in response to an event or a
michael@0 113 * callback. A convenient method of doing this is through the step_func method
michael@0 114 * which returns a function that, when called runs a test step. For example
michael@0 115 *
michael@0 116 * object.some_event = t.step_func(function(e) {assert_true(e.a)});
michael@0 117 *
michael@0 118 * == Making assertions ==
michael@0 119 *
michael@0 120 * Functions for making assertions start assert_
michael@0 121 * The best way to get a list is to look in this file for functions names
michael@0 122 * matching that pattern. The general signature is
michael@0 123 *
michael@0 124 * assert_something(actual, expected, description)
michael@0 125 *
michael@0 126 * although not all assertions precisely match this pattern e.g. assert_true
michael@0 127 * only takes actual and description as arguments.
michael@0 128 *
michael@0 129 * The description parameter is used to present more useful error messages when
michael@0 130 * a test fails
michael@0 131 *
michael@0 132 * NOTE: All asserts must be located in a test() or a step of an async_test().
michael@0 133 * asserts outside these places won't be detected correctly by the harness
michael@0 134 * and may cause a file to stop testing.
michael@0 135 *
michael@0 136 * == Harness Timeout ==
michael@0 137 *
michael@0 138 * The overall harness admits two timeout values "normal" (the
michael@0 139 * default) and "long", used for tests which have an unusually long
michael@0 140 * runtime. After the timeout is reached, the harness will stop
michael@0 141 * waiting for further async tests to complete. By default the
michael@0 142 * timeouts are set to 10s and 60s, respectively, but may be changed
michael@0 143 * when the test is run on hardware with different performance
michael@0 144 * characteristics to a common desktop computer. In order to opt-in
michael@0 145 * to the longer test timeout, the test must specify a meta element:
michael@0 146 * <meta name="timeout" content="long">
michael@0 147 *
michael@0 148 * == Setup ==
michael@0 149 *
michael@0 150 * Sometimes tests require non-trivial setup that may fail. For this purpose
michael@0 151 * there is a setup() function, that may be called with one or two arguments.
michael@0 152 * The two argument version is:
michael@0 153 *
michael@0 154 * setup(func, properties)
michael@0 155 *
michael@0 156 * The one argument versions may omit either argument.
michael@0 157 * func is a function to be run synchronously. setup() becomes a no-op once
michael@0 158 * any tests have returned results. Properties are global properties of the test
michael@0 159 * harness. Currently recognised properties are:
michael@0 160 *
michael@0 161 *
michael@0 162 * explicit_done - Wait for an explicit call to done() before declaring all
michael@0 163 * tests complete (see below)
michael@0 164 *
michael@0 165 * output_document - The document to which results should be logged. By default
michael@0 166 * this is the current document but could be an ancestor
michael@0 167 * document in some cases e.g. a SVG test loaded in an HTML
michael@0 168 * wrapper
michael@0 169 *
michael@0 170 * explicit_timeout - disable file timeout; only stop waiting for results
michael@0 171 * when the timeout() function is called (typically for
michael@0 172 * use when integrating with some existing test framework
michael@0 173 * that has its own timeout mechanism).
michael@0 174 *
michael@0 175 * allow_uncaught_exception - don't treat an uncaught exception as an error;
michael@0 176 * needed when e.g. testing the window.onerror
michael@0 177 * handler.
michael@0 178 *
michael@0 179 * timeout_multiplier - Multiplier to apply to per-test timeouts.
michael@0 180 *
michael@0 181 * == Determining when all tests are complete ==
michael@0 182 *
michael@0 183 * By default the test harness will assume there are no more results to come
michael@0 184 * when:
michael@0 185 * 1) There are no Test objects that have been created but not completed
michael@0 186 * 2) The load event on the document has fired
michael@0 187 *
michael@0 188 * This behaviour can be overridden by setting the explicit_done property to
michael@0 189 * true in a call to setup(). If explicit_done is true, the test harness will
michael@0 190 * not assume it is done until the global done() function is called. Once done()
michael@0 191 * is called, the two conditions above apply like normal.
michael@0 192 *
michael@0 193 * == Generating tests ==
michael@0 194 *
michael@0 195 * NOTE: this functionality may be removed
michael@0 196 *
michael@0 197 * There are scenarios in which is is desirable to create a large number of
michael@0 198 * (synchronous) tests that are internally similar but vary in the parameters
michael@0 199 * used. To make this easier, the generate_tests function allows a single
michael@0 200 * function to be called with each set of parameters in a list:
michael@0 201 *
michael@0 202 * generate_tests(test_function, parameter_lists, properties)
michael@0 203 *
michael@0 204 * For example:
michael@0 205 *
michael@0 206 * generate_tests(assert_equals, [
michael@0 207 * ["Sum one and one", 1+1, 2],
michael@0 208 * ["Sum one and zero", 1+0, 1]
michael@0 209 * ])
michael@0 210 *
michael@0 211 * Is equivalent to:
michael@0 212 *
michael@0 213 * test(function() {assert_equals(1+1, 2)}, "Sum one and one")
michael@0 214 * test(function() {assert_equals(1+0, 1)}, "Sum one and zero")
michael@0 215 *
michael@0 216 * Note that the first item in each parameter list corresponds to the name of
michael@0 217 * the test.
michael@0 218 *
michael@0 219 * The properties argument is identical to that for test(). This may be a
michael@0 220 * single object (used for all generated tests) or an array.
michael@0 221 *
michael@0 222 * == Callback API ==
michael@0 223 *
michael@0 224 * The framework provides callbacks corresponding to 3 events:
michael@0 225 *
michael@0 226 * start - happens when the first Test is created
michael@0 227 * result - happens when a test result is recieved
michael@0 228 * complete - happens when all results are recieved
michael@0 229 *
michael@0 230 * The page defining the tests may add callbacks for these events by calling
michael@0 231 * the following methods:
michael@0 232 *
michael@0 233 * add_start_callback(callback) - callback called with no arguments
michael@0 234 * add_result_callback(callback) - callback called with a test argument
michael@0 235 * add_completion_callback(callback) - callback called with an array of tests
michael@0 236 * and an status object
michael@0 237 *
michael@0 238 * tests have the following properties:
michael@0 239 * status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and
michael@0 240 * NOTRUN properties on the test object
michael@0 241 * message: A message indicating the reason for failure. In the future this
michael@0 242 * will always be a string
michael@0 243 *
michael@0 244 * The status object gives the overall status of the harness. It has the
michael@0 245 * following properties:
michael@0 246 * status: Can be compared to the OK, ERROR and TIMEOUT properties
michael@0 247 * message: An error message set when the status is ERROR
michael@0 248 *
michael@0 249 * == External API ==
michael@0 250 *
michael@0 251 * In order to collect the results of multiple pages containing tests, the test
michael@0 252 * harness will, when loaded in a nested browsing context, attempt to call
michael@0 253 * certain functions in each ancestor and opener browsing context:
michael@0 254 *
michael@0 255 * start - start_callback
michael@0 256 * result - result_callback
michael@0 257 * complete - completion_callback
michael@0 258 *
michael@0 259 * These are given the same arguments as the corresponding internal callbacks
michael@0 260 * described above.
michael@0 261 *
michael@0 262 * == External API through cross-document messaging ==
michael@0 263 *
michael@0 264 * Where supported, the test harness will also send messages using
michael@0 265 * cross-document messaging to each ancestor and opener browsing context. Since
michael@0 266 * it uses the wildcard keyword (*), cross-origin communication is enabled and
michael@0 267 * script on different origins can collect the results.
michael@0 268 *
michael@0 269 * This API follows similar conventions as those described above only slightly
michael@0 270 * modified to accommodate message event API. Each message is sent by the harness
michael@0 271 * is passed a single vanilla object, available as the `data` property of the
michael@0 272 * event object. These objects are structures as follows:
michael@0 273 *
michael@0 274 * start - { type: "start" }
michael@0 275 * result - { type: "result", test: Test }
michael@0 276 * complete - { type: "complete", tests: [Test, ...], status: TestsStatus }
michael@0 277 *
michael@0 278 * == List of assertions ==
michael@0 279 *
michael@0 280 * assert_true(actual, description)
michael@0 281 * asserts that /actual/ is strictly true
michael@0 282 *
michael@0 283 * assert_false(actual, description)
michael@0 284 * asserts that /actual/ is strictly false
michael@0 285 *
michael@0 286 * assert_equals(actual, expected, description)
michael@0 287 * asserts that /actual/ is the same value as /expected/
michael@0 288 *
michael@0 289 * assert_not_equals(actual, expected, description)
michael@0 290 * asserts that /actual/ is a different value to /expected/. Yes, this means
michael@0 291 * that "expected" is a misnomer
michael@0 292 *
michael@0 293 * assert_in_array(actual, expected, description)
michael@0 294 * asserts that /expected/ is an Array, and /actual/ is equal to one of the
michael@0 295 * members -- expected.indexOf(actual) != -1
michael@0 296 *
michael@0 297 * assert_array_equals(actual, expected, description)
michael@0 298 * asserts that /actual/ and /expected/ have the same length and the value of
michael@0 299 * each indexed property in /actual/ is the strictly equal to the corresponding
michael@0 300 * property value in /expected/
michael@0 301 *
michael@0 302 * assert_approx_equals(actual, expected, epsilon, description)
michael@0 303 * asserts that /actual/ is a number within +/- /epsilon/ of /expected/
michael@0 304 *
michael@0 305 * assert_less_than(actual, expected, description)
michael@0 306 * asserts that /actual/ is a number less than /expected/
michael@0 307 *
michael@0 308 * assert_greater_than(actual, expected, description)
michael@0 309 * asserts that /actual/ is a number greater than /expected/
michael@0 310 *
michael@0 311 * assert_less_than_equal(actual, expected, description)
michael@0 312 * asserts that /actual/ is a number less than or equal to /expected/
michael@0 313 *
michael@0 314 * assert_greater_than_equal(actual, expected, description)
michael@0 315 * asserts that /actual/ is a number greater than or equal to /expected/
michael@0 316 *
michael@0 317 * assert_regexp_match(actual, expected, description)
michael@0 318 * asserts that /actual/ matches the regexp /expected/
michael@0 319 *
michael@0 320 * assert_class_string(object, class_name, description)
michael@0 321 * asserts that the class string of /object/ as returned in
michael@0 322 * Object.prototype.toString is equal to /class_name/.
michael@0 323 *
michael@0 324 * assert_own_property(object, property_name, description)
michael@0 325 * assert that object has own property property_name
michael@0 326 *
michael@0 327 * assert_inherits(object, property_name, description)
michael@0 328 * assert that object does not have an own property named property_name
michael@0 329 * but that property_name is present in the prototype chain for object
michael@0 330 *
michael@0 331 * assert_idl_attribute(object, attribute_name, description)
michael@0 332 * assert that an object that is an instance of some interface has the
michael@0 333 * attribute attribute_name following the conditions specified by WebIDL
michael@0 334 *
michael@0 335 * assert_readonly(object, property_name, description)
michael@0 336 * assert that property property_name on object is readonly
michael@0 337 *
michael@0 338 * assert_throws(code, func, description)
michael@0 339 * code - the expected exception:
michael@0 340 * o string: the thrown exception must be a DOMException with the given
michael@0 341 * name, e.g., "TimeoutError" (for compatibility with existing
michael@0 342 * tests, a constant is also supported, e.g., "TIMEOUT_ERR")
michael@0 343 * o object: the thrown exception must have a property called "name" that
michael@0 344 * matches code.name
michael@0 345 * o null: allow any exception (in general, one of the options above
michael@0 346 * should be used)
michael@0 347 * func - a function that should throw
michael@0 348 *
michael@0 349 * assert_unreached(description)
michael@0 350 * asserts if called. Used to ensure that some codepath is *not* taken e.g.
michael@0 351 * an event does not fire.
michael@0 352 *
michael@0 353 * assert_any(assert_func, actual, expected_array, extra_arg_1, ... extra_arg_N)
michael@0 354 * asserts that one assert_func(actual, expected_array_N, extra_arg1, ..., extra_arg_N)
michael@0 355 * is true for some expected_array_N in expected_array. This only works for assert_func
michael@0 356 * with signature assert_func(actual, expected, args_1, ..., args_N). Note that tests
michael@0 357 * with multiple allowed pass conditions are bad practice unless the spec specifically
michael@0 358 * allows multiple behaviours. Test authors should not use this method simply to hide
michael@0 359 * UA bugs.
michael@0 360 *
michael@0 361 * assert_exists(object, property_name, description)
michael@0 362 * *** deprecated ***
michael@0 363 * asserts that object has an own property property_name
michael@0 364 *
michael@0 365 * assert_not_exists(object, property_name, description)
michael@0 366 * *** deprecated ***
michael@0 367 * assert that object does not have own property property_name
michael@0 368 */
michael@0 369
michael@0 370 (function ()
michael@0 371 {
michael@0 372 var debug = false;
michael@0 373 // default timeout is 10 seconds, test can override if needed
michael@0 374 var settings = {
michael@0 375 output:true,
michael@0 376 harness_timeout:{"normal":10000,
michael@0 377 "long":60000},
michael@0 378 test_timeout:null
michael@0 379 };
michael@0 380
michael@0 381 var xhtml_ns = "http://www.w3.org/1999/xhtml";
michael@0 382
michael@0 383 // script_prefix is used by Output.prototype.show_results() to figure out
michael@0 384 // where to get testharness.css from. It's enclosed in an extra closure to
michael@0 385 // not pollute the library's namespace with variables like "src".
michael@0 386 var script_prefix = null;
michael@0 387 (function ()
michael@0 388 {
michael@0 389 var scripts = document.getElementsByTagName("script");
michael@0 390 for (var i = 0; i < scripts.length; i++)
michael@0 391 {
michael@0 392 if (scripts[i].src)
michael@0 393 {
michael@0 394 var src = scripts[i].src;
michael@0 395 }
michael@0 396 else if (scripts[i].href)
michael@0 397 {
michael@0 398 //SVG case
michael@0 399 var src = scripts[i].href.baseVal;
michael@0 400 }
michael@0 401 if (src && src.slice(src.length - "testharness.js".length) === "testharness.js")
michael@0 402 {
michael@0 403 script_prefix = src.slice(0, src.length - "testharness.js".length);
michael@0 404 break;
michael@0 405 }
michael@0 406 }
michael@0 407 })();
michael@0 408
michael@0 409 /*
michael@0 410 * API functions
michael@0 411 */
michael@0 412
michael@0 413 var name_counter = 0;
michael@0 414 function next_default_name()
michael@0 415 {
michael@0 416 //Don't use document.title to work around an Opera bug in XHTML documents
michael@0 417 var title = document.getElementsByTagName("title")[0];
michael@0 418 var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled";
michael@0 419 var suffix = name_counter > 0 ? " " + name_counter : "";
michael@0 420 name_counter++;
michael@0 421 return prefix + suffix;
michael@0 422 }
michael@0 423
michael@0 424 function test(func, name, properties)
michael@0 425 {
michael@0 426 var test_name = name ? name : next_default_name();
michael@0 427 properties = properties ? properties : {};
michael@0 428 var test_obj = new Test(test_name, properties);
michael@0 429 test_obj.step(func);
michael@0 430 if (test_obj.phase === test_obj.phases.STARTED) {
michael@0 431 test_obj.done();
michael@0 432 }
michael@0 433 }
michael@0 434
michael@0 435 function async_test(func, name, properties)
michael@0 436 {
michael@0 437 if (typeof func !== "function") {
michael@0 438 properties = name;
michael@0 439 name = func;
michael@0 440 func = null;
michael@0 441 }
michael@0 442 var test_name = name ? name : next_default_name();
michael@0 443 properties = properties ? properties : {};
michael@0 444 var test_obj = new Test(test_name, properties);
michael@0 445 if (func) {
michael@0 446 test_obj.step(func, test_obj, test_obj);
michael@0 447 }
michael@0 448 return test_obj;
michael@0 449 }
michael@0 450
michael@0 451 function setup(func_or_properties, maybe_properties)
michael@0 452 {
michael@0 453 var func = null;
michael@0 454 var properties = {};
michael@0 455 if (arguments.length === 2) {
michael@0 456 func = func_or_properties;
michael@0 457 properties = maybe_properties;
michael@0 458 } else if (func_or_properties instanceof Function){
michael@0 459 func = func_or_properties;
michael@0 460 } else {
michael@0 461 properties = func_or_properties;
michael@0 462 }
michael@0 463 tests.setup(func, properties);
michael@0 464 output.setup(properties);
michael@0 465 }
michael@0 466
michael@0 467 function done() {
michael@0 468 tests.end_wait();
michael@0 469 }
michael@0 470
michael@0 471 function generate_tests(func, args, properties) {
michael@0 472 forEach(args, function(x, i)
michael@0 473 {
michael@0 474 var name = x[0];
michael@0 475 test(function()
michael@0 476 {
michael@0 477 func.apply(this, x.slice(1));
michael@0 478 },
michael@0 479 name,
michael@0 480 Array.isArray(properties) ? properties[i] : properties);
michael@0 481 });
michael@0 482 }
michael@0 483
michael@0 484 function on_event(object, event, callback)
michael@0 485 {
michael@0 486 object.addEventListener(event, callback, false);
michael@0 487 }
michael@0 488
michael@0 489 expose(test, 'test');
michael@0 490 expose(async_test, 'async_test');
michael@0 491 expose(generate_tests, 'generate_tests');
michael@0 492 expose(setup, 'setup');
michael@0 493 expose(done, 'done');
michael@0 494 expose(on_event, 'on_event');
michael@0 495
michael@0 496 /*
michael@0 497 * Return a string truncated to the given length, with ... added at the end
michael@0 498 * if it was longer.
michael@0 499 */
michael@0 500 function truncate(s, len)
michael@0 501 {
michael@0 502 if (s.length > len) {
michael@0 503 return s.substring(0, len - 3) + "...";
michael@0 504 }
michael@0 505 return s;
michael@0 506 }
michael@0 507
michael@0 508 /*
michael@0 509 * Return true if object is probably a Node object.
michael@0 510 */
michael@0 511 function is_node(object)
michael@0 512 {
michael@0 513 // I use duck-typing instead of instanceof, because
michael@0 514 // instanceof doesn't work if the node is from another window (like an
michael@0 515 // iframe's contentWindow):
michael@0 516 // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
michael@0 517 if ("nodeType" in object
michael@0 518 && "nodeName" in object
michael@0 519 && "nodeValue" in object
michael@0 520 && "childNodes" in object)
michael@0 521 {
michael@0 522 try
michael@0 523 {
michael@0 524 object.nodeType;
michael@0 525 }
michael@0 526 catch (e)
michael@0 527 {
michael@0 528 // The object is probably Node.prototype or another prototype
michael@0 529 // object that inherits from it, and not a Node instance.
michael@0 530 return false;
michael@0 531 }
michael@0 532 return true;
michael@0 533 }
michael@0 534 return false;
michael@0 535 }
michael@0 536
michael@0 537 /*
michael@0 538 * Convert a value to a nice, human-readable string
michael@0 539 */
michael@0 540 function format_value(val, seen)
michael@0 541 {
michael@0 542 if (!seen) {
michael@0 543 seen = [];
michael@0 544 }
michael@0 545 if (typeof val === "object" && val !== null)
michael@0 546 {
michael@0 547 if (seen.indexOf(val) >= 0)
michael@0 548 {
michael@0 549 return "[...]";
michael@0 550 }
michael@0 551 seen.push(val);
michael@0 552 }
michael@0 553 if (Array.isArray(val))
michael@0 554 {
michael@0 555 return "[" + val.map(function(x) {return format_value(x, seen)}).join(", ") + "]";
michael@0 556 }
michael@0 557
michael@0 558 switch (typeof val)
michael@0 559 {
michael@0 560 case "string":
michael@0 561 val = val.replace("\\", "\\\\");
michael@0 562 for (var i = 0; i < 32; i++)
michael@0 563 {
michael@0 564 var replace = "\\";
michael@0 565 switch (i) {
michael@0 566 case 0: replace += "0"; break;
michael@0 567 case 1: replace += "x01"; break;
michael@0 568 case 2: replace += "x02"; break;
michael@0 569 case 3: replace += "x03"; break;
michael@0 570 case 4: replace += "x04"; break;
michael@0 571 case 5: replace += "x05"; break;
michael@0 572 case 6: replace += "x06"; break;
michael@0 573 case 7: replace += "x07"; break;
michael@0 574 case 8: replace += "b"; break;
michael@0 575 case 9: replace += "t"; break;
michael@0 576 case 10: replace += "n"; break;
michael@0 577 case 11: replace += "v"; break;
michael@0 578 case 12: replace += "f"; break;
michael@0 579 case 13: replace += "r"; break;
michael@0 580 case 14: replace += "x0e"; break;
michael@0 581 case 15: replace += "x0f"; break;
michael@0 582 case 16: replace += "x10"; break;
michael@0 583 case 17: replace += "x11"; break;
michael@0 584 case 18: replace += "x12"; break;
michael@0 585 case 19: replace += "x13"; break;
michael@0 586 case 20: replace += "x14"; break;
michael@0 587 case 21: replace += "x15"; break;
michael@0 588 case 22: replace += "x16"; break;
michael@0 589 case 23: replace += "x17"; break;
michael@0 590 case 24: replace += "x18"; break;
michael@0 591 case 25: replace += "x19"; break;
michael@0 592 case 26: replace += "x1a"; break;
michael@0 593 case 27: replace += "x1b"; break;
michael@0 594 case 28: replace += "x1c"; break;
michael@0 595 case 29: replace += "x1d"; break;
michael@0 596 case 30: replace += "x1e"; break;
michael@0 597 case 31: replace += "x1f"; break;
michael@0 598 }
michael@0 599 val = val.replace(RegExp(String.fromCharCode(i), "g"), replace);
michael@0 600 }
michael@0 601 return '"' + val.replace(/"/g, '\\"') + '"';
michael@0 602 case "boolean":
michael@0 603 case "undefined":
michael@0 604 return String(val);
michael@0 605 case "number":
michael@0 606 // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
michael@0 607 // special-case.
michael@0 608 if (val === -0 && 1/val === -Infinity)
michael@0 609 {
michael@0 610 return "-0";
michael@0 611 }
michael@0 612 return String(val);
michael@0 613 case "object":
michael@0 614 if (val === null)
michael@0 615 {
michael@0 616 return "null";
michael@0 617 }
michael@0 618
michael@0 619 // Special-case Node objects, since those come up a lot in my tests. I
michael@0 620 // ignore namespaces.
michael@0 621 if (is_node(val))
michael@0 622 {
michael@0 623 switch (val.nodeType)
michael@0 624 {
michael@0 625 case Node.ELEMENT_NODE:
michael@0 626 var ret = "<" + val.tagName.toLowerCase();
michael@0 627 for (var i = 0; i < val.attributes.length; i++)
michael@0 628 {
michael@0 629 ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';
michael@0 630 }
michael@0 631 ret += ">" + val.innerHTML + "</" + val.tagName.toLowerCase() + ">";
michael@0 632 return "Element node " + truncate(ret, 60);
michael@0 633 case Node.TEXT_NODE:
michael@0 634 return 'Text node "' + truncate(val.data, 60) + '"';
michael@0 635 case Node.PROCESSING_INSTRUCTION_NODE:
michael@0 636 return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));
michael@0 637 case Node.COMMENT_NODE:
michael@0 638 return "Comment node <!--" + truncate(val.data, 60) + "-->";
michael@0 639 case Node.DOCUMENT_NODE:
michael@0 640 return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
michael@0 641 case Node.DOCUMENT_TYPE_NODE:
michael@0 642 return "DocumentType node";
michael@0 643 case Node.DOCUMENT_FRAGMENT_NODE:
michael@0 644 return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
michael@0 645 default:
michael@0 646 return "Node object of unknown type";
michael@0 647 }
michael@0 648 }
michael@0 649
michael@0 650 // Fall through to default
michael@0 651 default:
michael@0 652 return typeof val + ' "' + truncate(String(val), 60) + '"';
michael@0 653 }
michael@0 654 }
michael@0 655 expose(format_value, "format_value");
michael@0 656
michael@0 657 /*
michael@0 658 * Assertions
michael@0 659 */
michael@0 660
michael@0 661 function assert_true(actual, description)
michael@0 662 {
michael@0 663 assert(actual === true, "assert_true", description,
michael@0 664 "expected true got ${actual}", {actual:actual});
michael@0 665 };
michael@0 666 expose(assert_true, "assert_true");
michael@0 667
michael@0 668 function assert_false(actual, description)
michael@0 669 {
michael@0 670 assert(actual === false, "assert_false", description,
michael@0 671 "expected false got ${actual}", {actual:actual});
michael@0 672 };
michael@0 673 expose(assert_false, "assert_false");
michael@0 674
michael@0 675 function same_value(x, y) {
michael@0 676 if (y !== y)
michael@0 677 {
michael@0 678 //NaN case
michael@0 679 return x !== x;
michael@0 680 }
michael@0 681 else if (x === 0 && y === 0) {
michael@0 682 //Distinguish +0 and -0
michael@0 683 return 1/x === 1/y;
michael@0 684 }
michael@0 685 else
michael@0 686 {
michael@0 687 //typical case
michael@0 688 return x === y;
michael@0 689 }
michael@0 690 }
michael@0 691
michael@0 692 function assert_equals(actual, expected, description)
michael@0 693 {
michael@0 694 /*
michael@0 695 * Test if two primitives are equal or two objects
michael@0 696 * are the same object
michael@0 697 */
michael@0 698 if (typeof actual != typeof expected)
michael@0 699 {
michael@0 700 assert(false, "assert_equals", description,
michael@0 701 "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}",
michael@0 702 {expected:expected, actual:actual});
michael@0 703 return;
michael@0 704 }
michael@0 705 assert(same_value(actual, expected), "assert_equals", description,
michael@0 706 "expected ${expected} but got ${actual}",
michael@0 707 {expected:expected, actual:actual});
michael@0 708 };
michael@0 709 expose(assert_equals, "assert_equals");
michael@0 710
michael@0 711 function assert_not_equals(actual, expected, description)
michael@0 712 {
michael@0 713 /*
michael@0 714 * Test if two primitives are unequal or two objects
michael@0 715 * are different objects
michael@0 716 */
michael@0 717 assert(!same_value(actual, expected), "assert_not_equals", description,
michael@0 718 "got disallowed value ${actual}",
michael@0 719 {actual:actual});
michael@0 720 };
michael@0 721 expose(assert_not_equals, "assert_not_equals");
michael@0 722
michael@0 723 function assert_in_array(actual, expected, description)
michael@0 724 {
michael@0 725 assert(expected.indexOf(actual) != -1, "assert_in_array", description,
michael@0 726 "value ${actual} not in array ${expected}",
michael@0 727 {actual:actual, expected:expected});
michael@0 728 }
michael@0 729 expose(assert_in_array, "assert_in_array");
michael@0 730
michael@0 731 function assert_object_equals(actual, expected, description)
michael@0 732 {
michael@0 733 //This needs to be improved a great deal
michael@0 734 function check_equal(actual, expected, stack)
michael@0 735 {
michael@0 736 stack.push(actual);
michael@0 737
michael@0 738 var p;
michael@0 739 for (p in actual)
michael@0 740 {
michael@0 741 assert(expected.hasOwnProperty(p), "assert_object_equals", description,
michael@0 742 "unexpected property ${p}", {p:p});
michael@0 743
michael@0 744 if (typeof actual[p] === "object" && actual[p] !== null)
michael@0 745 {
michael@0 746 if (stack.indexOf(actual[p]) === -1)
michael@0 747 {
michael@0 748 check_equal(actual[p], expected[p], stack);
michael@0 749 }
michael@0 750 }
michael@0 751 else
michael@0 752 {
michael@0 753 assert(same_value(actual[p], expected[p]), "assert_object_equals", description,
michael@0 754 "property ${p} expected ${expected} got ${actual}",
michael@0 755 {p:p, expected:expected, actual:actual});
michael@0 756 }
michael@0 757 }
michael@0 758 for (p in expected)
michael@0 759 {
michael@0 760 assert(actual.hasOwnProperty(p),
michael@0 761 "assert_object_equals", description,
michael@0 762 "expected property ${p} missing", {p:p});
michael@0 763 }
michael@0 764 stack.pop();
michael@0 765 }
michael@0 766 check_equal(actual, expected, []);
michael@0 767 };
michael@0 768 expose(assert_object_equals, "assert_object_equals");
michael@0 769
michael@0 770 function assert_array_equals(actual, expected, description)
michael@0 771 {
michael@0 772 assert(actual.length === expected.length,
michael@0 773 "assert_array_equals", description,
michael@0 774 "lengths differ, expected ${expected} got ${actual}",
michael@0 775 {expected:expected.length, actual:actual.length});
michael@0 776
michael@0 777 for (var i=0; i < actual.length; i++)
michael@0 778 {
michael@0 779 assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
michael@0 780 "assert_array_equals", description,
michael@0 781 "property ${i}, property expected to be $expected but was $actual",
michael@0 782 {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
michael@0 783 actual:actual.hasOwnProperty(i) ? "present" : "missing"});
michael@0 784 assert(same_value(expected[i], actual[i]),
michael@0 785 "assert_array_equals", description,
michael@0 786 "property ${i}, expected ${expected} but got ${actual}",
michael@0 787 {i:i, expected:expected[i], actual:actual[i]});
michael@0 788 }
michael@0 789 }
michael@0 790 expose(assert_array_equals, "assert_array_equals");
michael@0 791
michael@0 792 function assert_approx_equals(actual, expected, epsilon, description)
michael@0 793 {
michael@0 794 /*
michael@0 795 * Test if two primitive numbers are equal withing +/- epsilon
michael@0 796 */
michael@0 797 assert(typeof actual === "number",
michael@0 798 "assert_approx_equals", description,
michael@0 799 "expected a number but got a ${type_actual}",
michael@0 800 {type_actual:typeof actual});
michael@0 801
michael@0 802 assert(Math.abs(actual - expected) <= epsilon,
michael@0 803 "assert_approx_equals", description,
michael@0 804 "expected ${expected} +/- ${epsilon} but got ${actual}",
michael@0 805 {expected:expected, actual:actual, epsilon:epsilon});
michael@0 806 };
michael@0 807 expose(assert_approx_equals, "assert_approx_equals");
michael@0 808
michael@0 809 function assert_less_than(actual, expected, description)
michael@0 810 {
michael@0 811 /*
michael@0 812 * Test if a primitive number is less than another
michael@0 813 */
michael@0 814 assert(typeof actual === "number",
michael@0 815 "assert_less_than", description,
michael@0 816 "expected a number but got a ${type_actual}",
michael@0 817 {type_actual:typeof actual});
michael@0 818
michael@0 819 assert(actual < expected,
michael@0 820 "assert_less_than", description,
michael@0 821 "expected a number less than ${expected} but got ${actual}",
michael@0 822 {expected:expected, actual:actual});
michael@0 823 };
michael@0 824 expose(assert_less_than, "assert_less_than");
michael@0 825
michael@0 826 function assert_greater_than(actual, expected, description)
michael@0 827 {
michael@0 828 /*
michael@0 829 * Test if a primitive number is greater than another
michael@0 830 */
michael@0 831 assert(typeof actual === "number",
michael@0 832 "assert_greater_than", description,
michael@0 833 "expected a number but got a ${type_actual}",
michael@0 834 {type_actual:typeof actual});
michael@0 835
michael@0 836 assert(actual > expected,
michael@0 837 "assert_greater_than", description,
michael@0 838 "expected a number greater than ${expected} but got ${actual}",
michael@0 839 {expected:expected, actual:actual});
michael@0 840 };
michael@0 841 expose(assert_greater_than, "assert_greater_than");
michael@0 842
michael@0 843 function assert_less_than_equal(actual, expected, description)
michael@0 844 {
michael@0 845 /*
michael@0 846 * Test if a primitive number is less than or equal to another
michael@0 847 */
michael@0 848 assert(typeof actual === "number",
michael@0 849 "assert_less_than_equal", description,
michael@0 850 "expected a number but got a ${type_actual}",
michael@0 851 {type_actual:typeof actual});
michael@0 852
michael@0 853 assert(actual <= expected,
michael@0 854 "assert_less_than", description,
michael@0 855 "expected a number less than or equal to ${expected} but got ${actual}",
michael@0 856 {expected:expected, actual:actual});
michael@0 857 };
michael@0 858 expose(assert_less_than_equal, "assert_less_than_equal");
michael@0 859
michael@0 860 function assert_greater_than_equal(actual, expected, description)
michael@0 861 {
michael@0 862 /*
michael@0 863 * Test if a primitive number is greater than or equal to another
michael@0 864 */
michael@0 865 assert(typeof actual === "number",
michael@0 866 "assert_greater_than_equal", description,
michael@0 867 "expected a number but got a ${type_actual}",
michael@0 868 {type_actual:typeof actual});
michael@0 869
michael@0 870 assert(actual >= expected,
michael@0 871 "assert_greater_than_equal", description,
michael@0 872 "expected a number greater than or equal to ${expected} but got ${actual}",
michael@0 873 {expected:expected, actual:actual});
michael@0 874 };
michael@0 875 expose(assert_greater_than_equal, "assert_greater_than_equal");
michael@0 876
michael@0 877 function assert_regexp_match(actual, expected, description) {
michael@0 878 /*
michael@0 879 * Test if a string (actual) matches a regexp (expected)
michael@0 880 */
michael@0 881 assert(expected.test(actual),
michael@0 882 "assert_regexp_match", description,
michael@0 883 "expected ${expected} but got ${actual}",
michael@0 884 {expected:expected, actual:actual});
michael@0 885 }
michael@0 886 expose(assert_regexp_match, "assert_regexp_match");
michael@0 887
michael@0 888 function assert_class_string(object, class_string, description) {
michael@0 889 assert_equals({}.toString.call(object), "[object " + class_string + "]",
michael@0 890 description);
michael@0 891 }
michael@0 892 expose(assert_class_string, "assert_class_string");
michael@0 893
michael@0 894
michael@0 895 function _assert_own_property(name) {
michael@0 896 return function(object, property_name, description)
michael@0 897 {
michael@0 898 assert(object.hasOwnProperty(property_name),
michael@0 899 name, description,
michael@0 900 "expected property ${p} missing", {p:property_name});
michael@0 901 };
michael@0 902 }
michael@0 903 expose(_assert_own_property("assert_exists"), "assert_exists");
michael@0 904 expose(_assert_own_property("assert_own_property"), "assert_own_property");
michael@0 905
michael@0 906 function assert_not_exists(object, property_name, description)
michael@0 907 {
michael@0 908 assert(!object.hasOwnProperty(property_name),
michael@0 909 "assert_not_exists", description,
michael@0 910 "unexpected property ${p} found", {p:property_name});
michael@0 911 };
michael@0 912 expose(assert_not_exists, "assert_not_exists");
michael@0 913
michael@0 914 function _assert_inherits(name) {
michael@0 915 return function (object, property_name, description)
michael@0 916 {
michael@0 917 assert(typeof object === "object",
michael@0 918 name, description,
michael@0 919 "provided value is not an object");
michael@0 920
michael@0 921 assert("hasOwnProperty" in object,
michael@0 922 name, description,
michael@0 923 "provided value is an object but has no hasOwnProperty method");
michael@0 924
michael@0 925 assert(!object.hasOwnProperty(property_name),
michael@0 926 name, description,
michael@0 927 "property ${p} found on object expected in prototype chain",
michael@0 928 {p:property_name});
michael@0 929
michael@0 930 assert(property_name in object,
michael@0 931 name, description,
michael@0 932 "property ${p} not found in prototype chain",
michael@0 933 {p:property_name});
michael@0 934 };
michael@0 935 }
michael@0 936 expose(_assert_inherits("assert_inherits"), "assert_inherits");
michael@0 937 expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
michael@0 938
michael@0 939 function assert_readonly(object, property_name, description)
michael@0 940 {
michael@0 941 var initial_value = object[property_name];
michael@0 942 try {
michael@0 943 //Note that this can have side effects in the case where
michael@0 944 //the property has PutForwards
michael@0 945 object[property_name] = initial_value + "a"; //XXX use some other value here?
michael@0 946 assert(same_value(object[property_name], initial_value),
michael@0 947 "assert_readonly", description,
michael@0 948 "changing property ${p} succeeded",
michael@0 949 {p:property_name});
michael@0 950 }
michael@0 951 finally
michael@0 952 {
michael@0 953 object[property_name] = initial_value;
michael@0 954 }
michael@0 955 };
michael@0 956 expose(assert_readonly, "assert_readonly");
michael@0 957
michael@0 958 function assert_throws(code, func, description)
michael@0 959 {
michael@0 960 try
michael@0 961 {
michael@0 962 func.call(this);
michael@0 963 assert(false, "assert_throws", description,
michael@0 964 "${func} did not throw", {func:func});
michael@0 965 }
michael@0 966 catch(e)
michael@0 967 {
michael@0 968 if (e instanceof AssertionError) {
michael@0 969 throw(e);
michael@0 970 }
michael@0 971 if (code === null)
michael@0 972 {
michael@0 973 return;
michael@0 974 }
michael@0 975 if (typeof code === "object")
michael@0 976 {
michael@0 977 assert(typeof e == "object" && "name" in e && e.name == code.name,
michael@0 978 "assert_throws", description,
michael@0 979 "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
michael@0 980 {func:func, actual:e, actual_name:e.name,
michael@0 981 expected:code,
michael@0 982 expected_name:code.name});
michael@0 983 return;
michael@0 984 }
michael@0 985
michael@0 986 var code_name_map = {
michael@0 987 INDEX_SIZE_ERR: 'IndexSizeError',
michael@0 988 HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
michael@0 989 WRONG_DOCUMENT_ERR: 'WrongDocumentError',
michael@0 990 INVALID_CHARACTER_ERR: 'InvalidCharacterError',
michael@0 991 NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
michael@0 992 NOT_FOUND_ERR: 'NotFoundError',
michael@0 993 NOT_SUPPORTED_ERR: 'NotSupportedError',
michael@0 994 INVALID_STATE_ERR: 'InvalidStateError',
michael@0 995 SYNTAX_ERR: 'SyntaxError',
michael@0 996 INVALID_MODIFICATION_ERR: 'InvalidModificationError',
michael@0 997 NAMESPACE_ERR: 'NamespaceError',
michael@0 998 INVALID_ACCESS_ERR: 'InvalidAccessError',
michael@0 999 TYPE_MISMATCH_ERR: 'TypeMismatchError',
michael@0 1000 SECURITY_ERR: 'SecurityError',
michael@0 1001 NETWORK_ERR: 'NetworkError',
michael@0 1002 ABORT_ERR: 'AbortError',
michael@0 1003 URL_MISMATCH_ERR: 'URLMismatchError',
michael@0 1004 QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
michael@0 1005 TIMEOUT_ERR: 'TimeoutError',
michael@0 1006 INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
michael@0 1007 DATA_CLONE_ERR: 'DataCloneError'
michael@0 1008 };
michael@0 1009
michael@0 1010 var name = code in code_name_map ? code_name_map[code] : code;
michael@0 1011
michael@0 1012 var name_code_map = {
michael@0 1013 IndexSizeError: 1,
michael@0 1014 HierarchyRequestError: 3,
michael@0 1015 WrongDocumentError: 4,
michael@0 1016 InvalidCharacterError: 5,
michael@0 1017 NoModificationAllowedError: 7,
michael@0 1018 NotFoundError: 8,
michael@0 1019 NotSupportedError: 9,
michael@0 1020 InvalidStateError: 11,
michael@0 1021 SyntaxError: 12,
michael@0 1022 InvalidModificationError: 13,
michael@0 1023 NamespaceError: 14,
michael@0 1024 InvalidAccessError: 15,
michael@0 1025 TypeMismatchError: 17,
michael@0 1026 SecurityError: 18,
michael@0 1027 NetworkError: 19,
michael@0 1028 AbortError: 20,
michael@0 1029 URLMismatchError: 21,
michael@0 1030 QuotaExceededError: 22,
michael@0 1031 TimeoutError: 23,
michael@0 1032 InvalidNodeTypeError: 24,
michael@0 1033 DataCloneError: 25,
michael@0 1034
michael@0 1035 UnknownError: 0,
michael@0 1036 ConstraintError: 0,
michael@0 1037 DataError: 0,
michael@0 1038 TransactionInactiveError: 0,
michael@0 1039 ReadOnlyError: 0,
michael@0 1040 VersionError: 0
michael@0 1041 };
michael@0 1042
michael@0 1043 if (!(name in name_code_map))
michael@0 1044 {
michael@0 1045 throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
michael@0 1046 }
michael@0 1047
michael@0 1048 var required_props = { code: name_code_map[name] };
michael@0 1049
michael@0 1050 if (required_props.code === 0
michael@0 1051 || ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException"))
michael@0 1052 {
michael@0 1053 // New style exception: also test the name property.
michael@0 1054 required_props.name = name;
michael@0 1055 }
michael@0 1056
michael@0 1057 //We'd like to test that e instanceof the appropriate interface,
michael@0 1058 //but we can't, because we don't know what window it was created
michael@0 1059 //in. It might be an instanceof the appropriate interface on some
michael@0 1060 //unknown other window. TODO: Work around this somehow?
michael@0 1061
michael@0 1062 assert(typeof e == "object",
michael@0 1063 "assert_throws", description,
michael@0 1064 "${func} threw ${e} with type ${type}, not an object",
michael@0 1065 {func:func, e:e, type:typeof e});
michael@0 1066
michael@0 1067 for (var prop in required_props)
michael@0 1068 {
michael@0 1069 assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],
michael@0 1070 "assert_throws", description,
michael@0 1071 "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
michael@0 1072 {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
michael@0 1073 }
michael@0 1074 }
michael@0 1075 }
michael@0 1076 expose(assert_throws, "assert_throws");
michael@0 1077
michael@0 1078 function assert_unreached(description) {
michael@0 1079 assert(false, "assert_unreached", description,
michael@0 1080 "Reached unreachable code");
michael@0 1081 }
michael@0 1082 expose(assert_unreached, "assert_unreached");
michael@0 1083
michael@0 1084 function assert_any(assert_func, actual, expected_array)
michael@0 1085 {
michael@0 1086 var args = [].slice.call(arguments, 3)
michael@0 1087 var errors = []
michael@0 1088 var passed = false;
michael@0 1089 forEach(expected_array,
michael@0 1090 function(expected)
michael@0 1091 {
michael@0 1092 try {
michael@0 1093 assert_func.apply(this, [actual, expected].concat(args))
michael@0 1094 passed = true;
michael@0 1095 } catch(e) {
michael@0 1096 errors.push(e.message);
michael@0 1097 }
michael@0 1098 });
michael@0 1099 if (!passed) {
michael@0 1100 throw new AssertionError(errors.join("\n\n"));
michael@0 1101 }
michael@0 1102 }
michael@0 1103 expose(assert_any, "assert_any");
michael@0 1104
michael@0 1105 function Test(name, properties)
michael@0 1106 {
michael@0 1107 this.name = name;
michael@0 1108
michael@0 1109 this.phases = {
michael@0 1110 INITIAL:0,
michael@0 1111 STARTED:1,
michael@0 1112 HAS_RESULT:2,
michael@0 1113 COMPLETE:3
michael@0 1114 };
michael@0 1115 this.phase = this.phases.INITIAL;
michael@0 1116
michael@0 1117 this.status = this.NOTRUN;
michael@0 1118 this.timeout_id = null;
michael@0 1119
michael@0 1120 this.properties = properties;
michael@0 1121 var timeout = properties.timeout ? properties.timeout : settings.test_timeout
michael@0 1122 if (timeout != null) {
michael@0 1123 this.timeout_length = timeout * tests.timeout_multiplier;
michael@0 1124 } else {
michael@0 1125 this.timeout_length = null;
michael@0 1126 }
michael@0 1127
michael@0 1128 this.message = null;
michael@0 1129
michael@0 1130 var this_obj = this;
michael@0 1131 this.steps = [];
michael@0 1132
michael@0 1133 tests.push(this);
michael@0 1134 }
michael@0 1135
michael@0 1136 Test.statuses = {
michael@0 1137 PASS:0,
michael@0 1138 FAIL:1,
michael@0 1139 TIMEOUT:2,
michael@0 1140 NOTRUN:3
michael@0 1141 };
michael@0 1142
michael@0 1143 Test.prototype = merge({}, Test.statuses);
michael@0 1144
michael@0 1145 Test.prototype.structured_clone = function()
michael@0 1146 {
michael@0 1147 if(!this._structured_clone)
michael@0 1148 {
michael@0 1149 var msg = this.message;
michael@0 1150 msg = msg ? String(msg) : msg;
michael@0 1151 this._structured_clone = merge({
michael@0 1152 name:String(this.name),
michael@0 1153 status:this.status,
michael@0 1154 message:msg
michael@0 1155 }, Test.statuses);
michael@0 1156 }
michael@0 1157 return this._structured_clone;
michael@0 1158 };
michael@0 1159
michael@0 1160 Test.prototype.step = function(func, this_obj)
michael@0 1161 {
michael@0 1162 if (this.phase > this.phases.STARTED)
michael@0 1163 {
michael@0 1164 return;
michael@0 1165 }
michael@0 1166 this.phase = this.phases.STARTED;
michael@0 1167 //If we don't get a result before the harness times out that will be a test timout
michael@0 1168 this.set_status(this.TIMEOUT, "Test timed out");
michael@0 1169
michael@0 1170 tests.started = true;
michael@0 1171
michael@0 1172 if (this.timeout_id === null)
michael@0 1173 {
michael@0 1174 this.set_timeout();
michael@0 1175 }
michael@0 1176
michael@0 1177 this.steps.push(func);
michael@0 1178
michael@0 1179 if (arguments.length === 1)
michael@0 1180 {
michael@0 1181 this_obj = this;
michael@0 1182 }
michael@0 1183
michael@0 1184 try
michael@0 1185 {
michael@0 1186 return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
michael@0 1187 }
michael@0 1188 catch(e)
michael@0 1189 {
michael@0 1190 if (this.phase >= this.phases.HAS_RESULT)
michael@0 1191 {
michael@0 1192 return;
michael@0 1193 }
michael@0 1194 var message = (typeof e === "object" && e !== null) ? e.message : e;
michael@0 1195 if (typeof e.stack != "undefined" && typeof e.message == "string") {
michael@0 1196 //Try to make it more informative for some exceptions, at least
michael@0 1197 //in Gecko and WebKit. This results in a stack dump instead of
michael@0 1198 //just errors like "Cannot read property 'parentNode' of null"
michael@0 1199 //or "root is null". Makes it a lot longer, of course.
michael@0 1200 message += "(stack: " + e.stack + ")";
michael@0 1201 }
michael@0 1202 this.set_status(this.FAIL, message);
michael@0 1203 this.phase = this.phases.HAS_RESULT;
michael@0 1204 this.done();
michael@0 1205 }
michael@0 1206 };
michael@0 1207
michael@0 1208 Test.prototype.step_func = function(func, this_obj)
michael@0 1209 {
michael@0 1210 var test_this = this;
michael@0 1211
michael@0 1212 if (arguments.length === 1)
michael@0 1213 {
michael@0 1214 this_obj = test_this;
michael@0 1215 }
michael@0 1216
michael@0 1217 return function()
michael@0 1218 {
michael@0 1219 test_this.step.apply(test_this, [func, this_obj].concat(
michael@0 1220 Array.prototype.slice.call(arguments)));
michael@0 1221 };
michael@0 1222 };
michael@0 1223
michael@0 1224 Test.prototype.step_func_done = function(func, this_obj)
michael@0 1225 {
michael@0 1226 var test_this = this;
michael@0 1227
michael@0 1228 if (arguments.length === 1)
michael@0 1229 {
michael@0 1230 this_obj = test_this;
michael@0 1231 }
michael@0 1232
michael@0 1233 return function()
michael@0 1234 {
michael@0 1235 test_this.step.apply(test_this, [func, this_obj].concat(
michael@0 1236 Array.prototype.slice.call(arguments)));
michael@0 1237 test_this.done();
michael@0 1238 };
michael@0 1239 }
michael@0 1240
michael@0 1241 Test.prototype.set_timeout = function()
michael@0 1242 {
michael@0 1243 if (this.timeout_length !== null)
michael@0 1244 {
michael@0 1245 var this_obj = this;
michael@0 1246 this.timeout_id = setTimeout(function()
michael@0 1247 {
michael@0 1248 this_obj.timeout();
michael@0 1249 }, this.timeout_length);
michael@0 1250 }
michael@0 1251 }
michael@0 1252
michael@0 1253 Test.prototype.set_status = function(status, message)
michael@0 1254 {
michael@0 1255 this.status = status;
michael@0 1256 this.message = message;
michael@0 1257 }
michael@0 1258
michael@0 1259 Test.prototype.timeout = function()
michael@0 1260 {
michael@0 1261 this.timeout_id = null;
michael@0 1262 this.set_status(this.TIMEOUT, "Test timed out")
michael@0 1263 this.phase = this.phases.HAS_RESULT;
michael@0 1264 this.done();
michael@0 1265 };
michael@0 1266
michael@0 1267 Test.prototype.done = function()
michael@0 1268 {
michael@0 1269 if (this.phase == this.phases.COMPLETE) {
michael@0 1270 return;
michael@0 1271 } else if (this.phase <= this.phases.STARTED)
michael@0 1272 {
michael@0 1273 this.set_status(this.PASS, null);
michael@0 1274 }
michael@0 1275
michael@0 1276 if (this.status == this.NOTRUN)
michael@0 1277 {
michael@0 1278 alert(this.phase);
michael@0 1279 }
michael@0 1280
michael@0 1281 this.phase = this.phases.COMPLETE;
michael@0 1282
michael@0 1283 clearTimeout(this.timeout_id);
michael@0 1284 tests.result(this);
michael@0 1285 };
michael@0 1286
michael@0 1287
michael@0 1288 /*
michael@0 1289 * Harness
michael@0 1290 */
michael@0 1291
michael@0 1292 function TestsStatus()
michael@0 1293 {
michael@0 1294 this.status = null;
michael@0 1295 this.message = null;
michael@0 1296 }
michael@0 1297
michael@0 1298 TestsStatus.statuses = {
michael@0 1299 OK:0,
michael@0 1300 ERROR:1,
michael@0 1301 TIMEOUT:2
michael@0 1302 };
michael@0 1303
michael@0 1304 TestsStatus.prototype = merge({}, TestsStatus.statuses);
michael@0 1305
michael@0 1306 TestsStatus.prototype.structured_clone = function()
michael@0 1307 {
michael@0 1308 if(!this._structured_clone)
michael@0 1309 {
michael@0 1310 var msg = this.message;
michael@0 1311 msg = msg ? String(msg) : msg;
michael@0 1312 this._structured_clone = merge({
michael@0 1313 status:this.status,
michael@0 1314 message:msg
michael@0 1315 }, TestsStatus.statuses);
michael@0 1316 }
michael@0 1317 return this._structured_clone;
michael@0 1318 };
michael@0 1319
michael@0 1320 function Tests()
michael@0 1321 {
michael@0 1322 this.tests = [];
michael@0 1323 this.num_pending = 0;
michael@0 1324
michael@0 1325 this.phases = {
michael@0 1326 INITIAL:0,
michael@0 1327 SETUP:1,
michael@0 1328 HAVE_TESTS:2,
michael@0 1329 HAVE_RESULTS:3,
michael@0 1330 COMPLETE:4
michael@0 1331 };
michael@0 1332 this.phase = this.phases.INITIAL;
michael@0 1333
michael@0 1334 this.properties = {};
michael@0 1335
michael@0 1336 //All tests can't be done until the load event fires
michael@0 1337 this.all_loaded = false;
michael@0 1338 this.wait_for_finish = false;
michael@0 1339 this.processing_callbacks = false;
michael@0 1340
michael@0 1341 this.allow_uncaught_exception = false;
michael@0 1342
michael@0 1343 this.timeout_multiplier = 1;
michael@0 1344 this.timeout_length = this.get_timeout();
michael@0 1345 this.timeout_id = null;
michael@0 1346
michael@0 1347 this.start_callbacks = [];
michael@0 1348 this.test_done_callbacks = [];
michael@0 1349 this.all_done_callbacks = [];
michael@0 1350
michael@0 1351 this.status = new TestsStatus();
michael@0 1352
michael@0 1353 var this_obj = this;
michael@0 1354
michael@0 1355 on_event(window, "load",
michael@0 1356 function()
michael@0 1357 {
michael@0 1358 this_obj.all_loaded = true;
michael@0 1359 if (this_obj.all_done())
michael@0 1360 {
michael@0 1361 this_obj.complete();
michael@0 1362 }
michael@0 1363 });
michael@0 1364
michael@0 1365 this.set_timeout();
michael@0 1366 }
michael@0 1367
michael@0 1368 Tests.prototype.setup = function(func, properties)
michael@0 1369 {
michael@0 1370 if (this.phase >= this.phases.HAVE_RESULTS)
michael@0 1371 {
michael@0 1372 return;
michael@0 1373 }
michael@0 1374 if (this.phase < this.phases.SETUP)
michael@0 1375 {
michael@0 1376 this.phase = this.phases.SETUP;
michael@0 1377 }
michael@0 1378
michael@0 1379 this.properties = properties;
michael@0 1380
michael@0 1381 for (var p in properties)
michael@0 1382 {
michael@0 1383 if (properties.hasOwnProperty(p))
michael@0 1384 {
michael@0 1385 var value = properties[p]
michael@0 1386 if (p == "allow_uncaught_exception") {
michael@0 1387 this.allow_uncaught_exception = value;
michael@0 1388 }
michael@0 1389 else if (p == "explicit_done" && value)
michael@0 1390 {
michael@0 1391 this.wait_for_finish = true;
michael@0 1392 }
michael@0 1393 else if (p == "explicit_timeout" && value) {
michael@0 1394 this.timeout_length = null;
michael@0 1395 if (this.timeout_id)
michael@0 1396 {
michael@0 1397 clearTimeout(this.timeout_id);
michael@0 1398 }
michael@0 1399 }
michael@0 1400 else if (p == "timeout_multiplier")
michael@0 1401 {
michael@0 1402 this.timeout_multiplier = value;
michael@0 1403 }
michael@0 1404 }
michael@0 1405 }
michael@0 1406
michael@0 1407 if (func)
michael@0 1408 {
michael@0 1409 try
michael@0 1410 {
michael@0 1411 func();
michael@0 1412 } catch(e)
michael@0 1413 {
michael@0 1414 this.status.status = this.status.ERROR;
michael@0 1415 this.status.message = e;
michael@0 1416 };
michael@0 1417 }
michael@0 1418 this.set_timeout();
michael@0 1419 };
michael@0 1420
michael@0 1421 Tests.prototype.get_timeout = function()
michael@0 1422 {
michael@0 1423 var metas = document.getElementsByTagName("meta");
michael@0 1424 for (var i=0; i<metas.length; i++)
michael@0 1425 {
michael@0 1426 if (metas[i].name == "timeout")
michael@0 1427 {
michael@0 1428 if (metas[i].content == "long")
michael@0 1429 {
michael@0 1430 return settings.harness_timeout.long;
michael@0 1431 }
michael@0 1432 break;
michael@0 1433 }
michael@0 1434 }
michael@0 1435 return settings.harness_timeout.normal;
michael@0 1436 }
michael@0 1437
michael@0 1438 Tests.prototype.set_timeout = function()
michael@0 1439 {
michael@0 1440 var this_obj = this;
michael@0 1441 clearTimeout(this.timeout_id);
michael@0 1442 if (this.timeout_length !== null)
michael@0 1443 {
michael@0 1444 this.timeout_id = setTimeout(function() {
michael@0 1445 this_obj.timeout();
michael@0 1446 }, this.timeout_length);
michael@0 1447 }
michael@0 1448 };
michael@0 1449
michael@0 1450 Tests.prototype.timeout = function() {
michael@0 1451 this.status.status = this.status.TIMEOUT;
michael@0 1452 this.complete();
michael@0 1453 };
michael@0 1454
michael@0 1455 Tests.prototype.end_wait = function()
michael@0 1456 {
michael@0 1457 this.wait_for_finish = false;
michael@0 1458 if (this.all_done()) {
michael@0 1459 this.complete();
michael@0 1460 }
michael@0 1461 };
michael@0 1462
michael@0 1463 Tests.prototype.push = function(test)
michael@0 1464 {
michael@0 1465 if (this.phase < this.phases.HAVE_TESTS) {
michael@0 1466 this.start();
michael@0 1467 }
michael@0 1468 this.num_pending++;
michael@0 1469 this.tests.push(test);
michael@0 1470 };
michael@0 1471
michael@0 1472 Tests.prototype.all_done = function() {
michael@0 1473 return (this.all_loaded && this.num_pending === 0 &&
michael@0 1474 !this.wait_for_finish && !this.processing_callbacks);
michael@0 1475 };
michael@0 1476
michael@0 1477 Tests.prototype.start = function() {
michael@0 1478 this.phase = this.phases.HAVE_TESTS;
michael@0 1479 this.notify_start();
michael@0 1480 };
michael@0 1481
michael@0 1482 Tests.prototype.notify_start = function() {
michael@0 1483 var this_obj = this;
michael@0 1484 forEach (this.start_callbacks,
michael@0 1485 function(callback)
michael@0 1486 {
michael@0 1487 callback(this_obj.properties);
michael@0 1488 });
michael@0 1489 forEach_windows(
michael@0 1490 function(w, is_same_origin)
michael@0 1491 {
michael@0 1492 if(is_same_origin && w.start_callback)
michael@0 1493 {
michael@0 1494 try
michael@0 1495 {
michael@0 1496 w.start_callback(this_obj.properties);
michael@0 1497 }
michael@0 1498 catch(e)
michael@0 1499 {
michael@0 1500 if (debug)
michael@0 1501 {
michael@0 1502 throw(e);
michael@0 1503 }
michael@0 1504 }
michael@0 1505 }
michael@0 1506 if (supports_post_message(w) && w !== self)
michael@0 1507 {
michael@0 1508 w.postMessage({
michael@0 1509 type: "start",
michael@0 1510 properties: this_obj.properties
michael@0 1511 }, "*");
michael@0 1512 }
michael@0 1513 });
michael@0 1514 };
michael@0 1515
michael@0 1516 Tests.prototype.result = function(test)
michael@0 1517 {
michael@0 1518 if (this.phase > this.phases.HAVE_RESULTS)
michael@0 1519 {
michael@0 1520 return;
michael@0 1521 }
michael@0 1522 this.phase = this.phases.HAVE_RESULTS;
michael@0 1523 this.num_pending--;
michael@0 1524 this.notify_result(test);
michael@0 1525 };
michael@0 1526
michael@0 1527 Tests.prototype.notify_result = function(test) {
michael@0 1528 var this_obj = this;
michael@0 1529 this.processing_callbacks = true;
michael@0 1530 forEach(this.test_done_callbacks,
michael@0 1531 function(callback)
michael@0 1532 {
michael@0 1533 callback(test, this_obj);
michael@0 1534 });
michael@0 1535
michael@0 1536 forEach_windows(
michael@0 1537 function(w, is_same_origin)
michael@0 1538 {
michael@0 1539 if(is_same_origin && w.result_callback)
michael@0 1540 {
michael@0 1541 try
michael@0 1542 {
michael@0 1543 w.result_callback(test);
michael@0 1544 }
michael@0 1545 catch(e)
michael@0 1546 {
michael@0 1547 if(debug) {
michael@0 1548 throw e;
michael@0 1549 }
michael@0 1550 }
michael@0 1551 }
michael@0 1552 if (supports_post_message(w) && w !== self)
michael@0 1553 {
michael@0 1554 w.postMessage({
michael@0 1555 type: "result",
michael@0 1556 test: test.structured_clone()
michael@0 1557 }, "*");
michael@0 1558 }
michael@0 1559 });
michael@0 1560 this.processing_callbacks = false;
michael@0 1561 if (this_obj.all_done())
michael@0 1562 {
michael@0 1563 this_obj.complete();
michael@0 1564 }
michael@0 1565 };
michael@0 1566
michael@0 1567 Tests.prototype.complete = function() {
michael@0 1568 if (this.phase === this.phases.COMPLETE) {
michael@0 1569 return;
michael@0 1570 }
michael@0 1571 this.phase = this.phases.COMPLETE;
michael@0 1572 var this_obj = this;
michael@0 1573 this.tests.forEach(
michael@0 1574 function(x)
michael@0 1575 {
michael@0 1576 if(x.status === x.NOTRUN)
michael@0 1577 {
michael@0 1578 this_obj.notify_result(x);
michael@0 1579 }
michael@0 1580 }
michael@0 1581 );
michael@0 1582 this.notify_complete();
michael@0 1583 };
michael@0 1584
michael@0 1585 Tests.prototype.notify_complete = function()
michael@0 1586 {
michael@0 1587 clearTimeout(this.timeout_id);
michael@0 1588 var this_obj = this;
michael@0 1589 var tests = map(this_obj.tests,
michael@0 1590 function(test)
michael@0 1591 {
michael@0 1592 return test.structured_clone();
michael@0 1593 });
michael@0 1594 if (this.status.status === null)
michael@0 1595 {
michael@0 1596 this.status.status = this.status.OK;
michael@0 1597 }
michael@0 1598
michael@0 1599 forEach (this.all_done_callbacks,
michael@0 1600 function(callback)
michael@0 1601 {
michael@0 1602 callback(this_obj.tests, this_obj.status);
michael@0 1603 });
michael@0 1604
michael@0 1605 forEach_windows(
michael@0 1606 function(w, is_same_origin)
michael@0 1607 {
michael@0 1608 if(is_same_origin && w.completion_callback)
michael@0 1609 {
michael@0 1610 try
michael@0 1611 {
michael@0 1612 w.completion_callback(this_obj.tests, this_obj.status);
michael@0 1613 }
michael@0 1614 catch(e)
michael@0 1615 {
michael@0 1616 if (debug)
michael@0 1617 {
michael@0 1618 throw e;
michael@0 1619 }
michael@0 1620 }
michael@0 1621 }
michael@0 1622 if (supports_post_message(w) && w !== self)
michael@0 1623 {
michael@0 1624 w.postMessage({
michael@0 1625 type: "complete",
michael@0 1626 tests: tests,
michael@0 1627 status: this_obj.status.structured_clone()
michael@0 1628 }, "*");
michael@0 1629 }
michael@0 1630 });
michael@0 1631 };
michael@0 1632
michael@0 1633 var tests = new Tests();
michael@0 1634
michael@0 1635 window.onerror = function(msg) {
michael@0 1636 if (!tests.allow_uncaught_exception)
michael@0 1637 {
michael@0 1638 tests.status.status = tests.status.ERROR;
michael@0 1639 tests.status.message = msg;
michael@0 1640 tests.complete();
michael@0 1641 }
michael@0 1642 }
michael@0 1643
michael@0 1644 function timeout() {
michael@0 1645 if (tests.timeout_length === null)
michael@0 1646 {
michael@0 1647 tests.timeout();
michael@0 1648 }
michael@0 1649 }
michael@0 1650 expose(timeout, 'timeout');
michael@0 1651
michael@0 1652 function add_start_callback(callback) {
michael@0 1653 tests.start_callbacks.push(callback);
michael@0 1654 }
michael@0 1655
michael@0 1656 function add_result_callback(callback)
michael@0 1657 {
michael@0 1658 tests.test_done_callbacks.push(callback);
michael@0 1659 }
michael@0 1660
michael@0 1661 function add_completion_callback(callback)
michael@0 1662 {
michael@0 1663 tests.all_done_callbacks.push(callback);
michael@0 1664 }
michael@0 1665
michael@0 1666 expose(add_start_callback, 'add_start_callback');
michael@0 1667 expose(add_result_callback, 'add_result_callback');
michael@0 1668 expose(add_completion_callback, 'add_completion_callback');
michael@0 1669
michael@0 1670 /*
michael@0 1671 * Output listener
michael@0 1672 */
michael@0 1673
michael@0 1674 function Output() {
michael@0 1675 this.output_document = document;
michael@0 1676 this.output_node = null;
michael@0 1677 this.done_count = 0;
michael@0 1678 this.enabled = settings.output;
michael@0 1679 this.phase = this.INITIAL;
michael@0 1680 }
michael@0 1681
michael@0 1682 Output.prototype.INITIAL = 0;
michael@0 1683 Output.prototype.STARTED = 1;
michael@0 1684 Output.prototype.HAVE_RESULTS = 2;
michael@0 1685 Output.prototype.COMPLETE = 3;
michael@0 1686
michael@0 1687 Output.prototype.setup = function(properties) {
michael@0 1688 if (this.phase > this.INITIAL) {
michael@0 1689 return;
michael@0 1690 }
michael@0 1691
michael@0 1692 //If output is disabled in testharnessreport.js the test shouldn't be
michael@0 1693 //able to override that
michael@0 1694 this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
michael@0 1695 properties.output : settings.output);
michael@0 1696 };
michael@0 1697
michael@0 1698 Output.prototype.init = function(properties)
michael@0 1699 {
michael@0 1700 if (this.phase >= this.STARTED) {
michael@0 1701 return;
michael@0 1702 }
michael@0 1703 if (properties.output_document) {
michael@0 1704 this.output_document = properties.output_document;
michael@0 1705 } else {
michael@0 1706 this.output_document = document;
michael@0 1707 }
michael@0 1708 this.phase = this.STARTED;
michael@0 1709 };
michael@0 1710
michael@0 1711 Output.prototype.resolve_log = function()
michael@0 1712 {
michael@0 1713 var output_document;
michael@0 1714 if (typeof this.output_document === "function")
michael@0 1715 {
michael@0 1716 output_document = this.output_document.apply(undefined);
michael@0 1717 } else
michael@0 1718 {
michael@0 1719 output_document = this.output_document;
michael@0 1720 }
michael@0 1721 if (!output_document)
michael@0 1722 {
michael@0 1723 return;
michael@0 1724 }
michael@0 1725 var node = output_document.getElementById("log");
michael@0 1726 if (node)
michael@0 1727 {
michael@0 1728 this.output_document = output_document;
michael@0 1729 this.output_node = node;
michael@0 1730 }
michael@0 1731 };
michael@0 1732
michael@0 1733 Output.prototype.show_status = function(test)
michael@0 1734 {
michael@0 1735 if (this.phase < this.STARTED)
michael@0 1736 {
michael@0 1737 this.init();
michael@0 1738 }
michael@0 1739 if (!this.enabled)
michael@0 1740 {
michael@0 1741 return;
michael@0 1742 }
michael@0 1743 if (this.phase < this.HAVE_RESULTS)
michael@0 1744 {
michael@0 1745 this.resolve_log();
michael@0 1746 this.phase = this.HAVE_RESULTS;
michael@0 1747 }
michael@0 1748 this.done_count++;
michael@0 1749 if (this.output_node)
michael@0 1750 {
michael@0 1751 if (this.done_count < 100
michael@0 1752 || (this.done_count < 1000 && this.done_count % 100 == 0)
michael@0 1753 || this.done_count % 1000 == 0) {
michael@0 1754 this.output_node.textContent = "Running, "
michael@0 1755 + this.done_count + " complete, "
michael@0 1756 + tests.num_pending + " remain";
michael@0 1757 }
michael@0 1758 }
michael@0 1759 };
michael@0 1760
michael@0 1761 Output.prototype.show_results = function (tests, harness_status)
michael@0 1762 {
michael@0 1763 if (this.phase >= this.COMPLETE) {
michael@0 1764 return;
michael@0 1765 }
michael@0 1766 if (!this.enabled)
michael@0 1767 {
michael@0 1768 return;
michael@0 1769 }
michael@0 1770 if (!this.output_node) {
michael@0 1771 this.resolve_log();
michael@0 1772 }
michael@0 1773 this.phase = this.COMPLETE;
michael@0 1774
michael@0 1775 var log = this.output_node;
michael@0 1776 if (!log)
michael@0 1777 {
michael@0 1778 return;
michael@0 1779 }
michael@0 1780 var output_document = this.output_document;
michael@0 1781
michael@0 1782 while (log.lastChild)
michael@0 1783 {
michael@0 1784 log.removeChild(log.lastChild);
michael@0 1785 }
michael@0 1786
michael@0 1787 if (script_prefix != null) {
michael@0 1788 var stylesheet = output_document.createElementNS(xhtml_ns, "link");
michael@0 1789 stylesheet.setAttribute("rel", "stylesheet");
michael@0 1790 stylesheet.setAttribute("href", script_prefix + "testharness.css");
michael@0 1791 var heads = output_document.getElementsByTagName("head");
michael@0 1792 if (heads.length) {
michael@0 1793 heads[0].appendChild(stylesheet);
michael@0 1794 }
michael@0 1795 }
michael@0 1796
michael@0 1797 var status_text_harness = {};
michael@0 1798 status_text_harness[harness_status.OK] = "OK";
michael@0 1799 status_text_harness[harness_status.ERROR] = "Error";
michael@0 1800 status_text_harness[harness_status.TIMEOUT] = "Timeout";
michael@0 1801
michael@0 1802 var status_text = {};
michael@0 1803 status_text[Test.prototype.PASS] = "Pass";
michael@0 1804 status_text[Test.prototype.FAIL] = "Fail";
michael@0 1805 status_text[Test.prototype.TIMEOUT] = "Timeout";
michael@0 1806 status_text[Test.prototype.NOTRUN] = "Not Run";
michael@0 1807
michael@0 1808 var status_number = {};
michael@0 1809 forEach(tests, function(test) {
michael@0 1810 var status = status_text[test.status];
michael@0 1811 if (status_number.hasOwnProperty(status))
michael@0 1812 {
michael@0 1813 status_number[status] += 1;
michael@0 1814 } else {
michael@0 1815 status_number[status] = 1;
michael@0 1816 }
michael@0 1817 });
michael@0 1818
michael@0 1819 function status_class(status)
michael@0 1820 {
michael@0 1821 return status.replace(/\s/g, '').toLowerCase();
michael@0 1822 }
michael@0 1823
michael@0 1824 var summary_template = ["section", {"id":"summary"},
michael@0 1825 ["h2", {}, "Summary"],
michael@0 1826 function(vars)
michael@0 1827 {
michael@0 1828 if (harness_status.status === harness_status.OK)
michael@0 1829 {
michael@0 1830 return null;
michael@0 1831 }
michael@0 1832 else
michael@0 1833 {
michael@0 1834 var status = status_text_harness[harness_status.status];
michael@0 1835 var rv = [["p", {"class":status_class(status)}]];
michael@0 1836
michael@0 1837 if (harness_status.status === harness_status.ERROR)
michael@0 1838 {
michael@0 1839 rv[0].push("Harness encountered an error:");
michael@0 1840 rv.push(["pre", {}, harness_status.message]);
michael@0 1841 }
michael@0 1842 else if (harness_status.status === harness_status.TIMEOUT)
michael@0 1843 {
michael@0 1844 rv[0].push("Harness timed out.");
michael@0 1845 }
michael@0 1846 else
michael@0 1847 {
michael@0 1848 rv[0].push("Harness got an unexpected status.");
michael@0 1849 }
michael@0 1850
michael@0 1851 return rv;
michael@0 1852 }
michael@0 1853 },
michael@0 1854 ["p", {}, "Found ${num_tests} tests"],
michael@0 1855 function(vars) {
michael@0 1856 var rv = [["div", {}]];
michael@0 1857 var i=0;
michael@0 1858 while (status_text.hasOwnProperty(i)) {
michael@0 1859 if (status_number.hasOwnProperty(status_text[i])) {
michael@0 1860 var status = status_text[i];
michael@0 1861 rv[0].push(["div", {"class":status_class(status)},
michael@0 1862 ["label", {},
michael@0 1863 ["input", {type:"checkbox", checked:"checked"}],
michael@0 1864 status_number[status] + " " + status]]);
michael@0 1865 }
michael@0 1866 i++;
michael@0 1867 }
michael@0 1868 return rv;
michael@0 1869 }];
michael@0 1870
michael@0 1871 log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
michael@0 1872
michael@0 1873 forEach(output_document.querySelectorAll("section#summary label"),
michael@0 1874 function(element)
michael@0 1875 {
michael@0 1876 on_event(element, "click",
michael@0 1877 function(e)
michael@0 1878 {
michael@0 1879 if (output_document.getElementById("results") === null)
michael@0 1880 {
michael@0 1881 e.preventDefault();
michael@0 1882 return;
michael@0 1883 }
michael@0 1884 var result_class = element.parentNode.getAttribute("class");
michael@0 1885 var style_element = output_document.querySelector("style#hide-" + result_class);
michael@0 1886 var input_element = element.querySelector("input");
michael@0 1887 if (!style_element && !input_element.checked) {
michael@0 1888 style_element = output_document.createElementNS(xhtml_ns, "style");
michael@0 1889 style_element.id = "hide-" + result_class;
michael@0 1890 style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";
michael@0 1891 output_document.body.appendChild(style_element);
michael@0 1892 } else if (style_element && input_element.checked) {
michael@0 1893 style_element.parentNode.removeChild(style_element);
michael@0 1894 }
michael@0 1895 });
michael@0 1896 });
michael@0 1897
michael@0 1898 // This use of innerHTML plus manual escaping is not recommended in
michael@0 1899 // general, but is necessary here for performance. Using textContent
michael@0 1900 // on each individual <td> adds tens of seconds of execution time for
michael@0 1901 // large test suites (tens of thousands of tests).
michael@0 1902 function escape_html(s)
michael@0 1903 {
michael@0 1904 return s.replace(/\&/g, "&amp;")
michael@0 1905 .replace(/</g, "&lt;")
michael@0 1906 .replace(/"/g, "&quot;")
michael@0 1907 .replace(/'/g, "&#39;");
michael@0 1908 }
michael@0 1909
michael@0 1910 function has_assertions()
michael@0 1911 {
michael@0 1912 for (var i = 0; i < tests.length; i++) {
michael@0 1913 if (tests[i].properties.hasOwnProperty("assert")) {
michael@0 1914 return true;
michael@0 1915 }
michael@0 1916 }
michael@0 1917 return false;
michael@0 1918 }
michael@0 1919
michael@0 1920 function get_assertion(test)
michael@0 1921 {
michael@0 1922 if (test.properties.hasOwnProperty("assert")) {
michael@0 1923 if (Array.isArray(test.properties.assert)) {
michael@0 1924 return test.properties.assert.join(' ');
michael@0 1925 }
michael@0 1926 return test.properties.assert;
michael@0 1927 }
michael@0 1928 return '';
michael@0 1929 }
michael@0 1930
michael@0 1931 log.appendChild(document.createElementNS(xhtml_ns, "section"));
michael@0 1932 var assertions = has_assertions();
michael@0 1933 var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">"
michael@0 1934 + "<thead><tr><th>Result</th><th>Test Name</th>"
michael@0 1935 + (assertions ? "<th>Assertion</th>" : "")
michael@0 1936 + "<th>Message</th></tr></thead>"
michael@0 1937 + "<tbody>";
michael@0 1938 for (var i = 0; i < tests.length; i++) {
michael@0 1939 html += '<tr class="'
michael@0 1940 + escape_html(status_class(status_text[tests[i].status]))
michael@0 1941 + '"><td>'
michael@0 1942 + escape_html(status_text[tests[i].status])
michael@0 1943 + "</td><td>"
michael@0 1944 + escape_html(tests[i].name)
michael@0 1945 + "</td><td>"
michael@0 1946 + (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "")
michael@0 1947 + escape_html(tests[i].message ? tests[i].message : " ")
michael@0 1948 + "</td></tr>";
michael@0 1949 }
michael@0 1950 html += "</tbody></table>";
michael@0 1951 try {
michael@0 1952 log.lastChild.innerHTML = html;
michael@0 1953 } catch (e) {
michael@0 1954 log.appendChild(document.createElementNS(xhtml_ns, "p"))
michael@0 1955 .textContent = "Setting innerHTML for the log threw an exception.";
michael@0 1956 log.appendChild(document.createElementNS(xhtml_ns, "pre"))
michael@0 1957 .textContent = html;
michael@0 1958 }
michael@0 1959 };
michael@0 1960
michael@0 1961 var output = new Output();
michael@0 1962 add_start_callback(function (properties) {output.init(properties);});
michael@0 1963 add_result_callback(function (test) {output.show_status(tests);});
michael@0 1964 add_completion_callback(function (tests, harness_status) {output.show_results(tests, harness_status);});
michael@0 1965
michael@0 1966 /*
michael@0 1967 * Template code
michael@0 1968 *
michael@0 1969 * A template is just a javascript structure. An element is represented as:
michael@0 1970 *
michael@0 1971 * [tag_name, {attr_name:attr_value}, child1, child2]
michael@0 1972 *
michael@0 1973 * the children can either be strings (which act like text nodes), other templates or
michael@0 1974 * functions (see below)
michael@0 1975 *
michael@0 1976 * A text node is represented as
michael@0 1977 *
michael@0 1978 * ["{text}", value]
michael@0 1979 *
michael@0 1980 * String values have a simple substitution syntax; ${foo} represents a variable foo.
michael@0 1981 *
michael@0 1982 * It is possible to embed logic in templates by using a function in a place where a
michael@0 1983 * node would usually go. The function must either return part of a template or null.
michael@0 1984 *
michael@0 1985 * In cases where a set of nodes are required as output rather than a single node
michael@0 1986 * with children it is possible to just use a list
michael@0 1987 * [node1, node2, node3]
michael@0 1988 *
michael@0 1989 * Usage:
michael@0 1990 *
michael@0 1991 * render(template, substitutions) - take a template and an object mapping
michael@0 1992 * variable names to parameters and return either a DOM node or a list of DOM nodes
michael@0 1993 *
michael@0 1994 * substitute(template, substitutions) - take a template and variable mapping object,
michael@0 1995 * make the variable substitutions and return the substituted template
michael@0 1996 *
michael@0 1997 */
michael@0 1998
michael@0 1999 function is_single_node(template)
michael@0 2000 {
michael@0 2001 return typeof template[0] === "string";
michael@0 2002 }
michael@0 2003
michael@0 2004 function substitute(template, substitutions)
michael@0 2005 {
michael@0 2006 if (typeof template === "function") {
michael@0 2007 var replacement = template(substitutions);
michael@0 2008 if (replacement)
michael@0 2009 {
michael@0 2010 var rv = substitute(replacement, substitutions);
michael@0 2011 return rv;
michael@0 2012 }
michael@0 2013 else
michael@0 2014 {
michael@0 2015 return null;
michael@0 2016 }
michael@0 2017 }
michael@0 2018 else if (is_single_node(template))
michael@0 2019 {
michael@0 2020 return substitute_single(template, substitutions);
michael@0 2021 }
michael@0 2022 else
michael@0 2023 {
michael@0 2024 return filter(map(template, function(x) {
michael@0 2025 return substitute(x, substitutions);
michael@0 2026 }), function(x) {return x !== null;});
michael@0 2027 }
michael@0 2028 }
michael@0 2029
michael@0 2030 function substitute_single(template, substitutions)
michael@0 2031 {
michael@0 2032 var substitution_re = /\${([^ }]*)}/g;
michael@0 2033
michael@0 2034 function do_substitution(input) {
michael@0 2035 var components = input.split(substitution_re);
michael@0 2036 var rv = [];
michael@0 2037 for (var i=0; i<components.length; i+=2)
michael@0 2038 {
michael@0 2039 rv.push(components[i]);
michael@0 2040 if (components[i+1])
michael@0 2041 {
michael@0 2042 rv.push(String(substitutions[components[i+1]]));
michael@0 2043 }
michael@0 2044 }
michael@0 2045 return rv;
michael@0 2046 }
michael@0 2047
michael@0 2048 var rv = [];
michael@0 2049 rv.push(do_substitution(String(template[0])).join(""));
michael@0 2050
michael@0 2051 if (template[0] === "{text}") {
michael@0 2052 substitute_children(template.slice(1), rv);
michael@0 2053 } else {
michael@0 2054 substitute_attrs(template[1], rv);
michael@0 2055 substitute_children(template.slice(2), rv);
michael@0 2056 }
michael@0 2057
michael@0 2058 function substitute_attrs(attrs, rv)
michael@0 2059 {
michael@0 2060 rv[1] = {};
michael@0 2061 for (var name in template[1])
michael@0 2062 {
michael@0 2063 if (attrs.hasOwnProperty(name))
michael@0 2064 {
michael@0 2065 var new_name = do_substitution(name).join("");
michael@0 2066 var new_value = do_substitution(attrs[name]).join("");
michael@0 2067 rv[1][new_name] = new_value;
michael@0 2068 };
michael@0 2069 }
michael@0 2070 }
michael@0 2071
michael@0 2072 function substitute_children(children, rv)
michael@0 2073 {
michael@0 2074 for (var i=0; i<children.length; i++)
michael@0 2075 {
michael@0 2076 if (children[i] instanceof Object) {
michael@0 2077 var replacement = substitute(children[i], substitutions);
michael@0 2078 if (replacement !== null)
michael@0 2079 {
michael@0 2080 if (is_single_node(replacement))
michael@0 2081 {
michael@0 2082 rv.push(replacement);
michael@0 2083 }
michael@0 2084 else
michael@0 2085 {
michael@0 2086 extend(rv, replacement);
michael@0 2087 }
michael@0 2088 }
michael@0 2089 }
michael@0 2090 else
michael@0 2091 {
michael@0 2092 extend(rv, do_substitution(String(children[i])));
michael@0 2093 }
michael@0 2094 }
michael@0 2095 return rv;
michael@0 2096 }
michael@0 2097
michael@0 2098 return rv;
michael@0 2099 }
michael@0 2100
michael@0 2101 function make_dom_single(template, doc)
michael@0 2102 {
michael@0 2103 var output_document = doc || document;
michael@0 2104 if (template[0] === "{text}")
michael@0 2105 {
michael@0 2106 var element = output_document.createTextNode("");
michael@0 2107 for (var i=1; i<template.length; i++)
michael@0 2108 {
michael@0 2109 element.data += template[i];
michael@0 2110 }
michael@0 2111 }
michael@0 2112 else
michael@0 2113 {
michael@0 2114 var element = output_document.createElementNS(xhtml_ns, template[0]);
michael@0 2115 for (var name in template[1]) {
michael@0 2116 if (template[1].hasOwnProperty(name))
michael@0 2117 {
michael@0 2118 element.setAttribute(name, template[1][name]);
michael@0 2119 }
michael@0 2120 }
michael@0 2121 for (var i=2; i<template.length; i++)
michael@0 2122 {
michael@0 2123 if (template[i] instanceof Object)
michael@0 2124 {
michael@0 2125 var sub_element = make_dom(template[i]);
michael@0 2126 element.appendChild(sub_element);
michael@0 2127 }
michael@0 2128 else
michael@0 2129 {
michael@0 2130 var text_node = output_document.createTextNode(template[i]);
michael@0 2131 element.appendChild(text_node);
michael@0 2132 }
michael@0 2133 }
michael@0 2134 }
michael@0 2135
michael@0 2136 return element;
michael@0 2137 }
michael@0 2138
michael@0 2139
michael@0 2140
michael@0 2141 function make_dom(template, substitutions, output_document)
michael@0 2142 {
michael@0 2143 if (is_single_node(template))
michael@0 2144 {
michael@0 2145 return make_dom_single(template, output_document);
michael@0 2146 }
michael@0 2147 else
michael@0 2148 {
michael@0 2149 return map(template, function(x) {
michael@0 2150 return make_dom_single(x, output_document);
michael@0 2151 });
michael@0 2152 }
michael@0 2153 }
michael@0 2154
michael@0 2155 function render(template, substitutions, output_document)
michael@0 2156 {
michael@0 2157 return make_dom(substitute(template, substitutions), output_document);
michael@0 2158 }
michael@0 2159
michael@0 2160 /*
michael@0 2161 * Utility funcions
michael@0 2162 */
michael@0 2163 function assert(expected_true, function_name, description, error, substitutions)
michael@0 2164 {
michael@0 2165 if (expected_true !== true)
michael@0 2166 {
michael@0 2167 throw new AssertionError(make_message(function_name, description,
michael@0 2168 error, substitutions));
michael@0 2169 }
michael@0 2170 }
michael@0 2171
michael@0 2172 function AssertionError(message)
michael@0 2173 {
michael@0 2174 this.message = message;
michael@0 2175 }
michael@0 2176
michael@0 2177 function make_message(function_name, description, error, substitutions)
michael@0 2178 {
michael@0 2179 for (var p in substitutions) {
michael@0 2180 if (substitutions.hasOwnProperty(p)) {
michael@0 2181 substitutions[p] = format_value(substitutions[p]);
michael@0 2182 }
michael@0 2183 }
michael@0 2184 var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
michael@0 2185 merge({function_name:function_name,
michael@0 2186 description:(description?description + " ":"")},
michael@0 2187 substitutions));
michael@0 2188 return node_form.slice(1).join("");
michael@0 2189 }
michael@0 2190
michael@0 2191 function filter(array, callable, thisObj) {
michael@0 2192 var rv = [];
michael@0 2193 for (var i=0; i<array.length; i++)
michael@0 2194 {
michael@0 2195 if (array.hasOwnProperty(i))
michael@0 2196 {
michael@0 2197 var pass = callable.call(thisObj, array[i], i, array);
michael@0 2198 if (pass) {
michael@0 2199 rv.push(array[i]);
michael@0 2200 }
michael@0 2201 }
michael@0 2202 }
michael@0 2203 return rv;
michael@0 2204 }
michael@0 2205
michael@0 2206 function map(array, callable, thisObj)
michael@0 2207 {
michael@0 2208 var rv = [];
michael@0 2209 rv.length = array.length;
michael@0 2210 for (var i=0; i<array.length; i++)
michael@0 2211 {
michael@0 2212 if (array.hasOwnProperty(i))
michael@0 2213 {
michael@0 2214 rv[i] = callable.call(thisObj, array[i], i, array);
michael@0 2215 }
michael@0 2216 }
michael@0 2217 return rv;
michael@0 2218 }
michael@0 2219
michael@0 2220 function extend(array, items)
michael@0 2221 {
michael@0 2222 Array.prototype.push.apply(array, items);
michael@0 2223 }
michael@0 2224
michael@0 2225 function forEach (array, callback, thisObj)
michael@0 2226 {
michael@0 2227 for (var i=0; i<array.length; i++)
michael@0 2228 {
michael@0 2229 if (array.hasOwnProperty(i))
michael@0 2230 {
michael@0 2231 callback.call(thisObj, array[i], i, array);
michael@0 2232 }
michael@0 2233 }
michael@0 2234 }
michael@0 2235
michael@0 2236 function merge(a,b)
michael@0 2237 {
michael@0 2238 var rv = {};
michael@0 2239 var p;
michael@0 2240 for (p in a)
michael@0 2241 {
michael@0 2242 rv[p] = a[p];
michael@0 2243 }
michael@0 2244 for (p in b) {
michael@0 2245 rv[p] = b[p];
michael@0 2246 }
michael@0 2247 return rv;
michael@0 2248 }
michael@0 2249
michael@0 2250 function expose(object, name)
michael@0 2251 {
michael@0 2252 var components = name.split(".");
michael@0 2253 var target = window;
michael@0 2254 for (var i=0; i<components.length - 1; i++)
michael@0 2255 {
michael@0 2256 if (!(components[i] in target))
michael@0 2257 {
michael@0 2258 target[components[i]] = {};
michael@0 2259 }
michael@0 2260 target = target[components[i]];
michael@0 2261 }
michael@0 2262 target[components[components.length - 1]] = object;
michael@0 2263 }
michael@0 2264
michael@0 2265 function forEach_windows(callback) {
michael@0 2266 // Iterate of the the windows [self ... top, opener]. The callback is passed
michael@0 2267 // two objects, the first one is the windows object itself, the second one
michael@0 2268 // is a boolean indicating whether or not its on the same origin as the
michael@0 2269 // current window.
michael@0 2270 var cache = forEach_windows.result_cache;
michael@0 2271 if (!cache) {
michael@0 2272 cache = [[self, true]];
michael@0 2273 var w = self;
michael@0 2274 var i = 0;
michael@0 2275 var so;
michael@0 2276 var origins = location.ancestorOrigins;
michael@0 2277 while (w != w.parent)
michael@0 2278 {
michael@0 2279 w = w.parent;
michael@0 2280 // In WebKit, calls to parent windows' properties that aren't on the same
michael@0 2281 // origin cause an error message to be displayed in the error console but
michael@0 2282 // don't throw an exception. This is a deviation from the current HTML5
michael@0 2283 // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
michael@0 2284 // The problem with WebKit's behavior is that it pollutes the error console
michael@0 2285 // with error messages that can't be caught.
michael@0 2286 //
michael@0 2287 // This issue can be mitigated by relying on the (for now) proprietary
michael@0 2288 // `location.ancestorOrigins` property which returns an ordered list of
michael@0 2289 // the origins of enclosing windows. See:
michael@0 2290 // http://trac.webkit.org/changeset/113945.
michael@0 2291 if(origins) {
michael@0 2292 so = (location.origin == origins[i]);
michael@0 2293 }
michael@0 2294 else
michael@0 2295 {
michael@0 2296 so = is_same_origin(w);
michael@0 2297 }
michael@0 2298 cache.push([w, so]);
michael@0 2299 i++;
michael@0 2300 }
michael@0 2301 w = window.opener;
michael@0 2302 if(w)
michael@0 2303 {
michael@0 2304 // window.opener isn't included in the `location.ancestorOrigins` prop.
michael@0 2305 // We'll just have to deal with a simple check and an error msg on WebKit
michael@0 2306 // browsers in this case.
michael@0 2307 cache.push([w, is_same_origin(w)]);
michael@0 2308 }
michael@0 2309 forEach_windows.result_cache = cache;
michael@0 2310 }
michael@0 2311
michael@0 2312 forEach(cache,
michael@0 2313 function(a)
michael@0 2314 {
michael@0 2315 callback.apply(null, a);
michael@0 2316 });
michael@0 2317 }
michael@0 2318
michael@0 2319 function is_same_origin(w) {
michael@0 2320 try {
michael@0 2321 'random_prop' in w;
michael@0 2322 return true;
michael@0 2323 } catch(e) {
michael@0 2324 return false;
michael@0 2325 }
michael@0 2326 }
michael@0 2327
michael@0 2328 function supports_post_message(w)
michael@0 2329 {
michael@0 2330 var supports;
michael@0 2331 var type;
michael@0 2332 // Given IE implements postMessage across nested iframes but not across
michael@0 2333 // windows or tabs, you can't infer cross-origin communication from the presence
michael@0 2334 // of postMessage on the current window object only.
michael@0 2335 //
michael@0 2336 // Touching the postMessage prop on a window can throw if the window is
michael@0 2337 // not from the same origin AND post message is not supported in that
michael@0 2338 // browser. So just doing an existence test here won't do, you also need
michael@0 2339 // to wrap it in a try..cacth block.
michael@0 2340 try
michael@0 2341 {
michael@0 2342 type = typeof w.postMessage;
michael@0 2343 if (type === "function")
michael@0 2344 {
michael@0 2345 supports = true;
michael@0 2346 }
michael@0 2347 // IE8 supports postMessage, but implements it as a host object which
michael@0 2348 // returns "object" as its `typeof`.
michael@0 2349 else if (type === "object")
michael@0 2350 {
michael@0 2351 supports = true;
michael@0 2352 }
michael@0 2353 // This is the case where postMessage isn't supported AND accessing a
michael@0 2354 // window property across origins does NOT throw (e.g. old Safari browser).
michael@0 2355 else
michael@0 2356 {
michael@0 2357 supports = false;
michael@0 2358 }
michael@0 2359 }
michael@0 2360 catch(e) {
michael@0 2361 // This is the case where postMessage isn't supported AND accessing a
michael@0 2362 // window property across origins throws (e.g. old Firefox browser).
michael@0 2363 supports = false;
michael@0 2364 }
michael@0 2365 return supports;
michael@0 2366 }
michael@0 2367 })();
michael@0 2368 // vim: set expandtab shiftwidth=4 tabstop=4:

mercurial