dom/imptests/testharness.js

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

mercurial