[iot][deprecatedapi][extend timeout value for normal] 42/302842/1
authorchen <chen89.chen@samsung.com>
Thu, 14 Dec 2023 10:06:51 +0000 (18:06 +0800)
committerchen <chen89.chen@samsung.com>
Thu, 14 Dec 2023 10:06:58 +0000 (18:06 +0800)
Change-Id: Ia132cc175ef211f11800526557aca6a0748f679e
Signed-off-by: chen <chen89.chen@samsung.com>
iot/tct-deprecatedapi-tizen-tests/resources/testharness.js
mobile/tct-deprecatedapi-tizen-tests/resources/testharness.js

index ecc050c28ed7c5b7275c511d0a1a0c5979d9540d..6fb5a6518f95a6b50ab26f0b3a45d98c51b0c918 100755 (executable)
@@ -19,7 +19,7 @@ policies and contribution forms [3].
     var settings = {
         output:true,
         harness_timeout:{
-            "normal":10000,
+            "normal":90000,
             "long":60000
         },
         test_timeout:null
index ecc050c28ed7c5b7275c511d0a1a0c5979d9540d..811918b22b9ad7a0e131cf241bb01caf2e231e30 100755 (executable)
-/*global self*/
-/*jshint latedef: nofunc*/
-/*
-Distributed under both the W3C Test Suite License [1] and the W3C
-3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
-policies and contribution forms [3].
-
-[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
-[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
-[3] http://www.w3.org/2004/10/27-testcases
-*/
-
-/* Documentation is in docs/api.md */
-
-(function ()
-{
-    var debug = false;
-    // default timeout is 10 seconds, test can override if needed
-    var settings = {
-        output:true,
-        harness_timeout:{
-            "normal":10000,
-            "long":60000
-        },
-        test_timeout:null
-    };
-
-    var xhtml_ns = "http://www.w3.org/1999/xhtml";
-
-    /*
-     * TestEnvironment is an abstraction for the environment in which the test
-     * harness is used. Each implementation of a test environment has to provide
-     * the following interface:
-     *
-     * interface TestEnvironment {
-     *   // Invoked after the global 'tests' object has been created and it's
-     *   // safe to call add_*_callback() to register event handlers.
-     *   void on_tests_ready();
-     *
-     *   // Invoked after setup() has been called to notify the test environment
-     *   // of changes to the test harness properties.
-     *   void on_new_harness_properties(object properties);
-     *
-     *   // Should return a new unique default test name.
-     *   DOMString next_default_test_name();
-     *
-     *   // Should return the test harness timeout duration in milliseconds.
-     *   float test_timeout();
-     *
-     *   // Should return the global scope object.
-     *   object global_scope();
-     * };
-     */
-
-    /*
-     * A test environment with a DOM. The global object is 'window'. By default
-     * test results are displayed in a table. Any parent windows receive
-     * callbacks or messages via postMessage() when test events occur. See
-     * apisample11.html and apisample12.html.
-     */
-    function WindowTestEnvironment() {
-        this.name_counter = 0;
-        this.window_cache = null;
-        this.output_handler = null;
-        this.all_loaded = false;
-        var this_obj = this;
-        on_event(window, 'load', function() {
-            this_obj.all_loaded = true;
-        });
-    }
-
-    WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) {
-        this._forEach_windows(
-                function(w, is_same_origin) {
-                    if (is_same_origin && selector in w) {
-                        try {
-                            w[selector].apply(undefined, callback_args);
-                        } catch (e) {
-                            if (debug) {
-                                throw e;
-                            }
-                        }
-                    }
-                    if (supports_post_message(w) && w !== self) {
-                        w.postMessage(message_arg, "*");
-                    }
-                });
-    };
-
-    WindowTestEnvironment.prototype._forEach_windows = function(callback) {
-        // Iterate of the the windows [self ... top, opener]. The callback is passed
-        // two objects, the first one is the windows object itself, the second one
-        // is a boolean indicating whether or not its on the same origin as the
-        // current window.
-        var cache = this.window_cache;
-        if (!cache) {
-            cache = [[self, true]];
-            var w = self;
-            var i = 0;
-            var so;
-            var origins = location.ancestorOrigins;
-            while (w != w.parent) {
-                w = w.parent;
-                // In WebKit, calls to parent windows' properties that aren't on the same
-                // origin cause an error message to be displayed in the error console but
-                // don't throw an exception. This is a deviation from the current HTML5
-                // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
-                // The problem with WebKit's behavior is that it pollutes the error console
-                // with error messages that can't be caught.
-                //
-                // This issue can be mitigated by relying on the (for now) proprietary
-                // `location.ancestorOrigins` property which returns an ordered list of
-                // the origins of enclosing windows. See:
-                // http://trac.webkit.org/changeset/113945.
-                if (origins) {
-                    so = (location.origin == origins[i]);
-                } else {
-                    so = is_same_origin(w);
-                }
-                cache.push([w, so]);
-                i++;
-            }
-            w = window.opener;
-            if (w) {
-                // window.opener isn't included in the `location.ancestorOrigins` prop.
-                // We'll just have to deal with a simple check and an error msg on WebKit
-                // browsers in this case.
-                cache.push([w, is_same_origin(w)]);
-            }
-            this.window_cache = cache;
-        }
-
-        forEach(cache,
-                function(a) {
-                    callback.apply(null, a);
-                });
-    };
-
-    WindowTestEnvironment.prototype.on_tests_ready = function() {
-        var output = new Output();
-        this.output_handler = output;
-
-        var this_obj = this;
-        add_start_callback(function (properties) {
-            this_obj.output_handler.init(properties);
-            this_obj._dispatch("start_callback", [properties],
-                           { type: "start", properties: properties });
-        });
-        add_test_state_callback(function(test) {
-            this_obj.output_handler.show_status();
-            this_obj._dispatch("test_state_callback", [test],
-                               { type: "test_state", test: test.structured_clone() });
-        });
-        add_result_callback(function (test) {
-            this_obj.output_handler.show_status();
-            this_obj._dispatch("result_callback", [test],
-                               { type: "result", test: test.structured_clone() });
-        });
-        add_completion_callback(function (tests, harness_status) {
-            this_obj.output_handler.show_results(tests, harness_status);
-            var cloned_tests = map(tests, function(test) { return test.structured_clone(); });
-            this_obj._dispatch("completion_callback", [tests, harness_status],
-                               { type: "complete", tests: cloned_tests,
-                                 status: harness_status.structured_clone() });
-        });
-    };
-
-    WindowTestEnvironment.prototype.next_default_test_name = function() {
-        //Don't use document.title to work around an Opera bug in XHTML documents
-        var title = document.getElementsByTagName("title")[0];
-        var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled";
-        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
-        this.name_counter++;
-        return prefix + suffix;
-    };
-
-    WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) {
-        this.output_handler.setup(properties);
-    };
-
-    WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
-        on_event(window, 'load', callback);
-    };
-
-    WindowTestEnvironment.prototype.test_timeout = function() {
-        var metas = document.getElementsByTagName("meta");
-        for (var i = 0; i < metas.length; i++) {
-            if (metas[i].name == "timeout") {
-                if (metas[i].content == "long") {
-                    return settings.harness_timeout.long;
-                }
-                break;
-            }
-        }
-        return settings.harness_timeout.normal;
-    };
-
-    WindowTestEnvironment.prototype.global_scope = function() {
-        return window;
-    };
-
-    /*
-     * Base TestEnvironment implementation for a generic web worker.
-     *
-     * Workers accumulate test results. One or more clients can connect and
-     * retrieve results from a worker at any time.
-     *
-     * WorkerTestEnvironment supports communicating with a client via a
-     * MessagePort.  The mechanism for determining the appropriate MessagePort
-     * for communicating with a client depends on the type of worker and is
-     * implemented by the various specializations of WorkerTestEnvironment
-     * below.
-     *
-     * A client document using testharness can use fetch_tests_from_worker() to
-     * retrieve results from a worker. See apisample16.html.
-     */
-    function WorkerTestEnvironment() {
-        this.name_counter = 0;
-        this.all_loaded = true;
-        this.message_list = [];
-        this.message_ports = [];
-    }
-
-    WorkerTestEnvironment.prototype._dispatch = function(message) {
-        this.message_list.push(message);
-        for (var i = 0; i < this.message_ports.length; ++i)
-        {
-            this.message_ports[i].postMessage(message);
-        }
-    };
-
-    // The only requirement is that port has a postMessage() method. It doesn't
-    // have to be an instance of a MessagePort, and often isn't.
-    WorkerTestEnvironment.prototype._add_message_port = function(port) {
-        this.message_ports.push(port);
-        for (var i = 0; i < this.message_list.length; ++i)
-        {
-            port.postMessage(this.message_list[i]);
-        }
-    };
-
-    WorkerTestEnvironment.prototype.next_default_test_name = function() {
-        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
-        this.name_counter++;
-        return "Untitled" + suffix;
-    };
-
-    WorkerTestEnvironment.prototype.on_new_harness_properties = function() {};
-
-    WorkerTestEnvironment.prototype.on_tests_ready = function() {
-        var this_obj = this;
-        add_start_callback(
-                function(properties) {
-                    this_obj._dispatch({
-                        type: "start",
-                        properties: properties,
-                    });
-                });
-        add_test_state_callback(
-                function(test) {
-                    this_obj._dispatch({
-                        type: "test_state",
-                        test: test.structured_clone()
-                    });
-                });
-        add_result_callback(
-                function(test) {
-                    this_obj._dispatch({
-                        type: "result",
-                        test: test.structured_clone()
-                    });
-                });
-        add_completion_callback(
-                function(tests, harness_status) {
-                    this_obj._dispatch({
-                        type: "complete",
-                        tests: map(tests,
-                            function(test) {
-                                return test.structured_clone();
-                            }),
-                        status: harness_status.structured_clone()
-                    });
-                });
-    };
-
-    WorkerTestEnvironment.prototype.add_on_loaded_callback = function() {};
-
-    WorkerTestEnvironment.prototype.test_timeout = function() {
-        // Tests running in a worker don't have a default timeout. I.e. all
-        // worker tests behave as if settings.explicit_timeout is true.
-        return null;
-    };
-
-    WorkerTestEnvironment.prototype.global_scope = function() {
-        return self;
-    };
-
-    /*
-     * Dedicated web workers.
-     * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope
-     *
-     * This class is used as the test_environment when testharness is running
-     * inside a dedicated worker.
-     */
-    function DedicatedWorkerTestEnvironment() {
-        WorkerTestEnvironment.call(this);
-        // self is an instance of DedicatedWorkerGlobalScope which exposes
-        // a postMessage() method for communicating via the message channel
-        // established when the worker is created.
-        this._add_message_port(self);
-    }
-    DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
-
-    DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() {
-        WorkerTestEnvironment.prototype.on_tests_ready.call(this);
-        // In the absence of an onload notification, we a require dedicated
-        // workers to explicitly signal when the tests are done.
-        tests.wait_for_finish = true;
-    };
-
-    /*
-     * Shared web workers.
-     * https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalscope
-     *
-     * This class is used as the test_environment when testharness is running
-     * inside a shared web worker.
-     */
-    function SharedWorkerTestEnvironment() {
-        WorkerTestEnvironment.call(this);
-        var this_obj = this;
-        // Shared workers receive message ports via the 'onconnect' event for
-        // each connection.
-        self.addEventListener("connect",
-                function(message_event) {
-                    this_obj._add_message_port(message_event.source);
-                });
-    }
-    SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
-
-    SharedWorkerTestEnvironment.prototype.on_tests_ready = function() {
-        WorkerTestEnvironment.prototype.on_tests_ready.call(this);
-        // In the absence of an onload notification, we a require shared
-        // workers to explicitly signal when the tests are done.
-        tests.wait_for_finish = true;
-    };
-
-    /*
-     * Service workers.
-     * http://www.w3.org/TR/service-workers/
-     *
-     * This class is used as the test_environment when testharness is running
-     * inside a service worker.
-     */
-    function ServiceWorkerTestEnvironment() {
-        WorkerTestEnvironment.call(this);
-        this.all_loaded = false;
-        this.on_loaded_callback = null;
-        var this_obj = this;
-        self.addEventListener("message",
-                function(event) {
-                    if (event.data.type && event.data.type === "connect") {
-                        this_obj._add_message_port(event.ports[0]);
-                        event.ports[0].start();
-                    }
-                });
-
-        // The oninstall event is received after the service worker script and
-        // all imported scripts have been fetched and executed. It's the
-        // equivalent of an onload event for a document. All tests should have
-        // been added by the time this event is received, thus it's not
-        // necessary to wait until the onactivate event.
-        on_event(self, "install",
-                function(event) {
-                    this_obj.all_loaded = true;
-                    if (this_obj.on_loaded_callback) {
-                        this_obj.on_loaded_callback();
-                    }
-                });
-    }
-    ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
-
-    ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
-        if (this.all_loaded) {
-            callback();
-        } else {
-            this.on_loaded_callback = callback;
-        }
-    };
-
-    function create_test_environment() {
-        if ('document' in self) {
-            return new WindowTestEnvironment();
-        }
-        if ('DedicatedWorkerGlobalScope' in self &&
-            self instanceof DedicatedWorkerGlobalScope) {
-            return new DedicatedWorkerTestEnvironment();
-        }
-        if ('SharedWorkerGlobalScope' in self &&
-            self instanceof SharedWorkerGlobalScope) {
-            return new SharedWorkerTestEnvironment();
-        }
-        if ('ServiceWorkerGlobalScope' in self &&
-            self instanceof ServiceWorkerGlobalScope) {
-            return new ServiceWorkerTestEnvironment();
-        }
-        throw new Error("Unsupported test environment");
-    }
-
-    var test_environment = create_test_environment();
-
-    function is_shared_worker(worker) {
-        return 'SharedWorker' in self && worker instanceof SharedWorker;
-    }
-
-    function is_service_worker(worker) {
-        return 'ServiceWorker' in self && worker instanceof ServiceWorker;
-    }
-
-    /*
-     * API functions
-     */
-
-    function test(func, name, properties)
-    {
-        var test_name = name ? name : test_environment.next_default_test_name();
-        properties = properties ? properties : {};
-        var test_obj = new Test(test_name, properties);
-        test_obj.step(func, test_obj, test_obj);
-        if (test_obj.phase === test_obj.phases.STARTED) {
-            test_obj.done();
-        }
-    }
-
-    function async_test(func, name, properties)
-    {
-        if (typeof func !== "function") {
-            properties = name;
-            name = func;
-            func = null;
-        }
-        var test_name = name ? name : test_environment.next_default_test_name();
-        properties = properties ? properties : {};
-        var test_obj = new Test(test_name, properties);
-        if (func) {
-            test_obj.step(func, test_obj, test_obj);
-        }
-        return test_obj;
-    }
-
-    function promise_test(func, name, properties) {
-        var test = async_test(name, properties);
-        Promise.resolve(test.step(func, test, test))
-            .then(
-                function() {
-                    test.done();
-                })
-            .catch(test.step_func(
-                function(value) {
-                    if (value instanceof AssertionError) {
-                        throw value;
-                    }
-                    assert(false, "promise_test", null,
-                           "Unhandled rejection with value: ${value}", {value:value});
-                }));
-    }
-
-    function setup(func_or_properties, maybe_properties)
-    {
-        var func = null;
-        var properties = {};
-        if (arguments.length === 2) {
-            func = func_or_properties;
-            properties = maybe_properties;
-        } else if (func_or_properties instanceof Function) {
-            func = func_or_properties;
-        } else {
-            properties = func_or_properties;
-        }
-        tests.setup(func, properties);
-        test_environment.on_new_harness_properties(properties);
-    }
-
-    function done() {
-        if (tests.tests.length === 0) {
-            tests.set_file_is_test();
-        }
-        if (tests.file_is_test) {
-            tests.tests[0].done();
-        }
-        tests.end_wait();
-    }
-
-    function generate_tests(func, args, properties) {
-        forEach(args, function(x, i)
-                {
-                    var name = x[0];
-                    test(function()
-                         {
-                             func.apply(this, x.slice(1));
-                         },
-                         name,
-                         Array.isArray(properties) ? properties[i] : properties);
-                });
-    }
-
-    function on_event(object, event, callback)
-    {
-        object.addEventListener(event, callback, false);
-    }
-
-    expose(test, 'test');
-    expose(async_test, 'async_test');
-    expose(promise_test, 'promise_test');
-    expose(generate_tests, 'generate_tests');
-    expose(setup, 'setup');
-    expose(done, 'done');
-    expose(on_event, 'on_event');
-
-    /*
-     * Return a string truncated to the given length, with ... added at the end
-     * if it was longer.
-     */
-    function truncate(s, len)
-    {
-        if (s.length > len) {
-            return s.substring(0, len - 3) + "...";
-        }
-        return s;
-    }
-
-    /*
-     * Return true if object is probably a Node object.
-     */
-    function is_node(object)
-    {
-        // I use duck-typing instead of instanceof, because
-        // instanceof doesn't work if the node is from another window (like an
-        // iframe's contentWindow):
-        // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
-        if ("nodeType" in object &&
-            "nodeName" in object &&
-            "nodeValue" in object &&
-            "childNodes" in object) {
-            try {
-                object.nodeType;
-            } catch (e) {
-                // The object is probably Node.prototype or another prototype
-                // object that inherits from it, and not a Node instance.
-                return false;
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /*
-     * Convert a value to a nice, human-readable string
-     */
-    function format_value(val, seen)
-    {
-        if (!seen) {
-            seen = [];
-        }
-        if (typeof val === "object" && val !== null) {
-            if (seen.indexOf(val) >= 0) {
-                return "[...]";
-            }
-            seen.push(val);
-        }
-        if (Array.isArray(val)) {
-            return "[" + val.map(function(x) {return format_value(x, seen);}).join(", ") + "]";
-        }
-
-        switch (typeof val) {
-        case "string":
-            val = val.replace("\\", "\\\\");
-            for (var i = 0; i < 32; i++) {
-                var replace = "\\";
-                switch (i) {
-                case 0: replace += "0"; break;
-                case 1: replace += "x01"; break;
-                case 2: replace += "x02"; break;
-                case 3: replace += "x03"; break;
-                case 4: replace += "x04"; break;
-                case 5: replace += "x05"; break;
-                case 6: replace += "x06"; break;
-                case 7: replace += "x07"; break;
-                case 8: replace += "b"; break;
-                case 9: replace += "t"; break;
-                case 10: replace += "n"; break;
-                case 11: replace += "v"; break;
-                case 12: replace += "f"; break;
-                case 13: replace += "r"; break;
-                case 14: replace += "x0e"; break;
-                case 15: replace += "x0f"; break;
-                case 16: replace += "x10"; break;
-                case 17: replace += "x11"; break;
-                case 18: replace += "x12"; break;
-                case 19: replace += "x13"; break;
-                case 20: replace += "x14"; break;
-                case 21: replace += "x15"; break;
-                case 22: replace += "x16"; break;
-                case 23: replace += "x17"; break;
-                case 24: replace += "x18"; break;
-                case 25: replace += "x19"; break;
-                case 26: replace += "x1a"; break;
-                case 27: replace += "x1b"; break;
-                case 28: replace += "x1c"; break;
-                case 29: replace += "x1d"; break;
-                case 30: replace += "x1e"; break;
-                case 31: replace += "x1f"; break;
-                }
-                val = val.replace(RegExp(String.fromCharCode(i), "g"), replace);
-            }
-            return '"' + val.replace(/"/g, '\\"') + '"';
-        case "boolean":
-        case "undefined":
-            return String(val);
-        case "number":
-            // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
-            // special-case.
-            if (val === -0 && 1/val === -Infinity) {
-                return "-0";
-            }
-            return String(val);
-        case "object":
-            if (val === null) {
-                return "null";
-            }
-
-            // Special-case Node objects, since those come up a lot in my tests.  I
-            // ignore namespaces.
-            if (is_node(val)) {
-                switch (val.nodeType) {
-                case Node.ELEMENT_NODE:
-                    var ret = "<" + val.localName;
-                    for (var i = 0; i < val.attributes.length; i++) {
-                        ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';
-                    }
-                    ret += ">" + val.innerHTML + "</" + val.localName + ">";
-                    return "Element node " + truncate(ret, 60);
-                case Node.TEXT_NODE:
-                    return 'Text node "' + truncate(val.data, 60) + '"';
-                case Node.PROCESSING_INSTRUCTION_NODE:
-                    return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));
-                case Node.COMMENT_NODE:
-                    return "Comment node <!--" + truncate(val.data, 60) + "-->";
-                case Node.DOCUMENT_NODE:
-                    return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
-                case Node.DOCUMENT_TYPE_NODE:
-                    return "DocumentType node";
-                case Node.DOCUMENT_FRAGMENT_NODE:
-                    return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
-                default:
-                    return "Node object of unknown type";
-                }
-            }
-
-        /* falls through */
-        default:
-            return typeof val + ' "' + truncate(String(val), 60) + '"';
-        }
-    }
-    expose(format_value, "format_value");
-
-    /*
-     * Assertions
-     */
-
-    function assert_true(actual, description)
-    {
-        assert(actual === true, "assert_true", description,
-                                "expected true got ${actual}", {actual:actual});
-    }
-    expose(assert_true, "assert_true");
-
-    function assert_false(actual, description)
-    {
-        assert(actual === false, "assert_false", description,
-                                 "expected false got ${actual}", {actual:actual});
-    }
-    expose(assert_false, "assert_false");
-
-    function same_value(x, y) {
-        if (y !== y) {
-            //NaN case
-            return x !== x;
-        }
-        if (x === 0 && y === 0) {
-            //Distinguish +0 and -0
-            return 1/x === 1/y;
-        }
-        return x === y;
-    }
-
-    function assert_equals(actual, expected, description)
-    {
-         /*
-          * Test if two primitives are equal or two objects
-          * are the same object
-          */
-        if (typeof actual != typeof expected) {
-            assert(false, "assert_equals", description,
-                          "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}",
-                          {expected:expected, actual:actual});
-            return;
-        }
-        assert(same_value(actual, expected), "assert_equals", description,
-                                             "expected ${expected} but got ${actual}",
-                                             {expected:expected, actual:actual});
-    }
-    expose(assert_equals, "assert_equals");
-
-    function assert_not_equals(actual, expected, description)
-    {
-         /*
-          * Test if two primitives are unequal or two objects
-          * are different objects
-          */
-        assert(!same_value(actual, expected), "assert_not_equals", description,
-                                              "got disallowed value ${actual}",
-                                              {actual:actual});
-    }
-    expose(assert_not_equals, "assert_not_equals");
-
-    function assert_in_array(actual, expected, description)
-    {
-        assert(expected.indexOf(actual) != -1, "assert_in_array", description,
-                                               "value ${actual} not in array ${expected}",
-                                               {actual:actual, expected:expected});
-    }
-    expose(assert_in_array, "assert_in_array");
-
-    function assert_object_equals(actual, expected, description)
-    {
-         //This needs to be improved a great deal
-         function check_equal(actual, expected, stack)
-         {
-             stack.push(actual);
-
-             var p;
-             for (p in actual) {
-                 assert(expected.hasOwnProperty(p), "assert_object_equals", description,
-                                                    "unexpected property ${p}", {p:p});
-
-                 if (typeof actual[p] === "object" && actual[p] !== null) {
-                     if (stack.indexOf(actual[p]) === -1) {
-                         check_equal(actual[p], expected[p], stack);
-                     }
-                 } else {
-                     assert(same_value(actual[p], expected[p]), "assert_object_equals", description,
-                                                       "property ${p} expected ${expected} got ${actual}",
-                                                       {p:p, expected:expected, actual:actual});
-                 }
-             }
-             for (p in expected) {
-                 assert(actual.hasOwnProperty(p),
-                        "assert_object_equals", description,
-                        "expected property ${p} missing", {p:p});
-             }
-             stack.pop();
-         }
-         check_equal(actual, expected, []);
-    }
-    expose(assert_object_equals, "assert_object_equals");
-
-    function assert_array_equals(actual, expected, description)
-    {
-        assert(actual.length === expected.length,
-               "assert_array_equals", description,
-               "lengths differ, expected ${expected} got ${actual}",
-               {expected:expected.length, actual:actual.length});
-
-        for (var i = 0; i < actual.length; i++) {
-            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
-                   "assert_array_equals", description,
-                   "property ${i}, property expected to be ${expected} but was ${actual}",
-                   {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
-                   actual:actual.hasOwnProperty(i) ? "present" : "missing"});
-            assert(same_value(expected[i], actual[i]),
-                   "assert_array_equals", description,
-                   "property ${i}, expected ${expected} but got ${actual}",
-                   {i:i, expected:expected[i], actual:actual[i]});
-        }
-    }
-    expose(assert_array_equals, "assert_array_equals");
-
-    function assert_approx_equals(actual, expected, epsilon, description)
-    {
-        /*
-         * Test if two primitive numbers are equal withing +/- epsilon
-         */
-        assert(typeof actual === "number",
-               "assert_approx_equals", description,
-               "expected a number but got a ${type_actual}",
-               {type_actual:typeof actual});
-
-        assert(Math.abs(actual - expected) <= epsilon,
-               "assert_approx_equals", description,
-               "expected ${expected} +/- ${epsilon} but got ${actual}",
-               {expected:expected, actual:actual, epsilon:epsilon});
-    }
-    expose(assert_approx_equals, "assert_approx_equals");
-
-    function assert_less_than(actual, expected, description)
-    {
-        /*
-         * Test if a primitive number is less than another
-         */
-        assert(typeof actual === "number",
-               "assert_less_than", description,
-               "expected a number but got a ${type_actual}",
-               {type_actual:typeof actual});
-
-        assert(actual < expected,
-               "assert_less_than", description,
-               "expected a number less than ${expected} but got ${actual}",
-               {expected:expected, actual:actual});
-    }
-    expose(assert_less_than, "assert_less_than");
-
-    function assert_greater_than(actual, expected, description)
-    {
-        /*
-         * Test if a primitive number is greater than another
-         */
-        assert(typeof actual === "number",
-               "assert_greater_than", description,
-               "expected a number but got a ${type_actual}",
-               {type_actual:typeof actual});
-
-        assert(actual > expected,
-               "assert_greater_than", description,
-               "expected a number greater than ${expected} but got ${actual}",
-               {expected:expected, actual:actual});
-    }
-    expose(assert_greater_than, "assert_greater_than");
-
-    function assert_less_than_equal(actual, expected, description)
-    {
-        /*
-         * Test if a primitive number is less than or equal to another
-         */
-        assert(typeof actual === "number",
-               "assert_less_than_equal", description,
-               "expected a number but got a ${type_actual}",
-               {type_actual:typeof actual});
-
-        assert(actual <= expected,
-               "assert_less_than", description,
-               "expected a number less than or equal to ${expected} but got ${actual}",
-               {expected:expected, actual:actual});
-    }
-    expose(assert_less_than_equal, "assert_less_than_equal");
-
-    function assert_greater_than_equal(actual, expected, description)
-    {
-        /*
-         * Test if a primitive number is greater than or equal to another
-         */
-        assert(typeof actual === "number",
-               "assert_greater_than_equal", description,
-               "expected a number but got a ${type_actual}",
-               {type_actual:typeof actual});
-
-        assert(actual >= expected,
-               "assert_greater_than_equal", description,
-               "expected a number greater than or equal to ${expected} but got ${actual}",
-               {expected:expected, actual:actual});
-    }
-    expose(assert_greater_than_equal, "assert_greater_than_equal");
-
-    function assert_regexp_match(actual, expected, description) {
-        /*
-         * Test if a string (actual) matches a regexp (expected)
-         */
-        assert(expected.test(actual),
-               "assert_regexp_match", description,
-               "expected ${expected} but got ${actual}",
-               {expected:expected, actual:actual});
-    }
-    expose(assert_regexp_match, "assert_regexp_match");
-
-    function assert_class_string(object, class_string, description) {
-        assert_equals({}.toString.call(object), "[object " + class_string + "]",
-                      description);
-    }
-    expose(assert_class_string, "assert_class_string");
-
-
-    function _assert_own_property(name) {
-        return function(object, property_name, description)
-        {
-            assert(property_name in object,
-                   name, description,
-                   "expected property ${p} missing", {p:property_name});
-        };
-    }
-    expose(_assert_own_property("assert_exists"), "assert_exists");
-    expose(_assert_own_property("assert_own_property"), "assert_own_property");
-
-    function assert_not_exists(object, property_name, description)
-    {
-        assert(!object.hasOwnProperty(property_name),
-               "assert_not_exists", description,
-               "unexpected property ${p} found", {p:property_name});
-    }
-    expose(assert_not_exists, "assert_not_exists");
-
-    function _assert_inherits(name) {
-        return function (object, property_name, description)
-        {
-            assert(typeof object === "object",
-                   name, description,
-                   "provided value is not an object");
-
-            assert("hasOwnProperty" in object,
-                   name, description,
-                   "provided value is an object but has no hasOwnProperty method");
-
-            assert(!object.hasOwnProperty(property_name),
-                   name, description,
-                   "property ${p} found on object expected in prototype chain",
-                   {p:property_name});
-
-            assert(property_name in object,
-                   name, description,
-                   "property ${p} not found in prototype chain",
-                   {p:property_name});
-        };
-    }
-    expose(_assert_inherits("assert_inherits"), "assert_inherits");
-    expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
-
-    function assert_readonly(object, property_name, description)
-    {
-         var initial_value = object[property_name];
-         try {
-             //Note that this can have side effects in the case where
-             //the property has PutForwards
-             object[property_name] = initial_value + "a"; //XXX use some other value here?
-             assert(same_value(object[property_name], initial_value),
-                    "assert_readonly", description,
-                    "changing property ${p} succeeded",
-                    {p:property_name});
-         } finally {
-             object[property_name] = initial_value;
-         }
-    }
-    expose(assert_readonly, "assert_readonly");
-
-    function assert_throws(code, func, description)
-    {
-        try {
-            func.call(this);
-            assert(false, "assert_throws", description,
-                   "${func} did not throw", {func:func});
-        } catch (e) {
-            if (e instanceof AssertionError) {
-                throw e;
-            }
-            if (code === null) {
-                return;
-            }
-            if (typeof code === "object") {
-                assert(typeof e == "object" && "name" in e && e.name == code.name,
-                       "assert_throws", description,
-                       "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
-                                    {func:func, actual:e, actual_name:e.name,
-                                     expected:code,
-                                     expected_name:code.name});
-                return;
-            }
-
-            var code_name_map = {
-                INDEX_SIZE_ERR: 'IndexSizeError',
-                HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
-                WRONG_DOCUMENT_ERR: 'WrongDocumentError',
-                INVALID_CHARACTER_ERR: 'InvalidCharacterError',
-                NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
-                NOT_FOUND_ERR: 'NotFoundError',
-                NOT_SUPPORTED_ERR: 'NotSupportedError',
-                INVALID_STATE_ERR: 'InvalidStateError',
-                SYNTAX_ERR: 'SyntaxError',
-                INVALID_MODIFICATION_ERR: 'InvalidModificationError',
-                NAMESPACE_ERR: 'NamespaceError',
-                INVALID_ACCESS_ERR: 'InvalidAccessError',
-                TYPE_MISMATCH_ERR: 'TypeMismatchError',
-                SECURITY_ERR: 'SecurityError',
-                NETWORK_ERR: 'NetworkError',
-                ABORT_ERR: 'AbortError',
-                URL_MISMATCH_ERR: 'URLMismatchError',
-                QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
-                TIMEOUT_ERR: 'TimeoutError',
-                INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
-                DATA_CLONE_ERR: 'DataCloneError'
-            };
-
-            var name = code in code_name_map ? code_name_map[code] : code;
-
-            var name_code_map = {
-                IndexSizeError: 1,
-                HierarchyRequestError: 3,
-                WrongDocumentError: 4,
-                InvalidCharacterError: 5,
-                NoModificationAllowedError: 7,
-                NotFoundError: 8,
-                NotSupportedError: 9,
-                InvalidStateError: 11,
-                SyntaxError: 12,
-                InvalidModificationError: 13,
-                NamespaceError: 14,
-                InvalidAccessError: 15,
-                TypeMismatchError: 17,
-                SecurityError: 18,
-                NetworkError: 19,
-                AbortError: 20,
-                URLMismatchError: 21,
-                QuotaExceededError: 22,
-                TimeoutError: 23,
-                InvalidNodeTypeError: 24,
-                DataCloneError: 25,
-
-                UnknownError: 0,
-                ConstraintError: 0,
-                DataError: 0,
-                TransactionInactiveError: 0,
-                ReadOnlyError: 0,
-                VersionError: 0
-            };
-
-            if (!(name in name_code_map)) {
-                throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
-            }
-
-            var required_props = { code: name_code_map[name] };
-
-            if (required_props.code === 0 ||
-               ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException")) {
-                // New style exception: also test the name property.
-                required_props.name = name;
-            }
-
-            //We'd like to test that e instanceof the appropriate interface,
-            //but we can't, because we don't know what window it was created
-            //in.  It might be an instanceof the appropriate interface on some
-            //unknown other window.  TODO: Work around this somehow?
-
-            assert(typeof e == "object",
-                   "assert_throws", description,
-                   "${func} threw ${e} with type ${type}, not an object",
-                   {func:func, e:e, type:typeof e});
-
-            for (var prop in required_props) {
-                assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],
-                       "assert_throws", description,
-                       "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
-                       {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
-            }
-        }
-    }
-    expose(assert_throws, "assert_throws");
-
-    function assert_unreached(description) {
-         assert(false, "assert_unreached", description,
-                "Reached unreachable code");
-    }
-    expose(assert_unreached, "assert_unreached");
-
-    function assert_any(assert_func, actual, expected_array)
-    {
-        var args = [].slice.call(arguments, 3);
-        var errors = [];
-        var passed = false;
-        forEach(expected_array,
-                function(expected)
-                {
-                    try {
-                        assert_func.apply(this, [actual, expected].concat(args));
-                        passed = true;
-                    } catch (e) {
-                        errors.push(e.message);
-                    }
-                });
-        if (!passed) {
-            throw new AssertionError(errors.join("\n\n"));
-        }
-    }
-    expose(assert_any, "assert_any");
-
-    function Test(name, properties)
-    {
-        if (tests.file_is_test && tests.tests.length) {
-            throw new Error("Tried to create a test with file_is_test");
-        }
-        this.name = name;
-
-        this.phase = this.phases.INITIAL;
-
-        this.status = this.NOTRUN;
-        this.timeout_id = null;
-        this.index = null;
-
-        this.properties = properties;
-        var timeout = properties.timeout ? properties.timeout : settings.test_timeout;
-        if (timeout !== null) {
-            this.timeout_length = timeout * tests.timeout_multiplier;
-        } else {
-            this.timeout_length = null;
-        }
-
-        this.message = null;
-
-        this.steps = [];
-
-        this.cleanup_callbacks = [];
-
-        tests.push(this);
-    }
-
-    Test.statuses = {
-        PASS:0,
-        FAIL:1,
-        TIMEOUT:2,
-        NOTRUN:3
-    };
-
-    Test.prototype = merge({}, Test.statuses);
-
-    Test.prototype.phases = {
-        INITIAL:0,
-        STARTED:1,
-        HAS_RESULT:2,
-        COMPLETE:3
-    };
-
-    Test.prototype.structured_clone = function()
-    {
-        if (!this._structured_clone) {
-            var msg = this.message;
-            msg = msg ? String(msg) : msg;
-            this._structured_clone = merge({
-                name:String(this.name),
-                properties:merge({}, this.properties),
-            }, Test.statuses);
-        }
-        this._structured_clone.status = this.status;
-        this._structured_clone.message = this.message;
-        this._structured_clone.index = this.index;
-        return this._structured_clone;
-    };
-
-    Test.prototype.step = function(func, this_obj)
-    {
-        if (this.phase > this.phases.STARTED) {
-            return;
-        }
-        this.phase = this.phases.STARTED;
-        //If we don't get a result before the harness times out that will be a test timout
-        this.set_status(this.TIMEOUT, "Test timed out");
-
-        tests.started = true;
-        tests.notify_test_state(this);
-
-        if (this.timeout_id === null) {
-            this.set_timeout();
-        }
-
-        this.steps.push(func);
-
-        if (arguments.length === 1) {
-            this_obj = this;
-        }
-
-        try {
-            return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
-        } catch (e) {
-            if (this.phase >= this.phases.HAS_RESULT) {
-                return;
-            }
-            var message = (typeof e === "object" && e !== null) ? e.message : e;
-            if (typeof e.stack != "undefined" && typeof e.message == "string") {
-                //Try to make it more informative for some exceptions, at least
-                //in Gecko and WebKit.  This results in a stack dump instead of
-                //just errors like "Cannot read property 'parentNode' of null"
-                //or "root is null".  Makes it a lot longer, of course.
-                message += "(stack: " + e.stack + ")";
-            }
-            this.set_status(this.FAIL, message);
-            this.phase = this.phases.HAS_RESULT;
-            this.done();
-        }
-    };
-
-    Test.prototype.step_func = function(func, this_obj)
-    {
-        var test_this = this;
-
-        if (arguments.length === 1) {
-            this_obj = test_this;
-        }
-
-        return function()
-        {
-            return test_this.step.apply(test_this, [func, this_obj].concat(
-                Array.prototype.slice.call(arguments)));
-        };
-    };
-
-    Test.prototype.step_func_done = function(func, this_obj)
-    {
-        var test_this = this;
-
-        if (arguments.length === 1) {
-            this_obj = test_this;
-        }
-
-        return function()
-        {
-            if (func) {
-                test_this.step.apply(test_this, [func, this_obj].concat(
-                    Array.prototype.slice.call(arguments)));
-            }
-            test_this.done();
-        };
-    };
-
-    Test.prototype.unreached_func = function(description)
-    {
-        return this.step_func(function() {
-            assert_unreached(description);
-        });
-    };
-
-    Test.prototype.add_cleanup = function(callback) {
-        this.cleanup_callbacks.push(callback);
-    };
-
-    Test.prototype.force_timeout = function() {
-        this.set_status(this.TIMEOUT);
-        this.phase = this.phases.HAS_RESULT;
-    };
-
-    Test.prototype.set_timeout = function()
-    {
-        if (this.timeout_length !== null) {
-            var this_obj = this;
-            this.timeout_id = setTimeout(function()
-                                         {
-                                             this_obj.timeout();
-                                         }, this.timeout_length);
-        }
-    };
-
-    Test.prototype.set_status = function(status, message)
-    {
-        this.status = status;
-        this.message = message;
-    };
-
-    Test.prototype.timeout = function()
-    {
-        this.timeout_id = null;
-        this.set_status(this.TIMEOUT, "Test timed out");
-        this.phase = this.phases.HAS_RESULT;
-        this.done();
-    };
-
-    Test.prototype.done = function()
-    {
-        if (this.phase == this.phases.COMPLETE) {
-            return;
-        }
-
-        if (this.phase <= this.phases.STARTED) {
-            this.set_status(this.PASS, null);
-        }
-
-        this.phase = this.phases.COMPLETE;
-
-        clearTimeout(this.timeout_id);
-        tests.result(this);
-        this.cleanup();
-    };
-
-    Test.prototype.cleanup = function() {
-        forEach(this.cleanup_callbacks,
-                function(cleanup_callback) {
-                    cleanup_callback();
-                });
-    };
-
-    /*
-     * A RemoteTest object mirrors a Test object on a remote worker. The
-     * associated RemoteWorker updates the RemoteTest object in response to
-     * received events. In turn, the RemoteTest object replicates these events
-     * on the local document. This allows listeners (test result reporting
-     * etc..) to transparently handle local and remote events.
-     */
-    function RemoteTest(clone) {
-        var this_obj = this;
-        Object.keys(clone).forEach(
-                function(key) {
-                    this_obj[key] = clone[key];
-                });
-        this.index = null;
-        this.phase = this.phases.INITIAL;
-        this.update_state_from(clone);
-        tests.push(this);
-    }
-
-    RemoteTest.prototype.structured_clone = function() {
-        var clone = {};
-        Object.keys(this).forEach(
-                function(key) {
-                    if (typeof(this[key]) === "object") {
-                        clone[key] = merge({}, this[key]);
-                    } else {
-                        clone[key] = this[key];
-                    }
-                });
-        clone.phases = merge({}, this.phases);
-        return clone;
-    };
-
-    RemoteTest.prototype.cleanup = function() {};
-    RemoteTest.prototype.phases = Test.prototype.phases;
-    RemoteTest.prototype.update_state_from = function(clone) {
-        this.status = clone.status;
-        this.message = clone.message;
-        if (this.phase === this.phases.INITIAL) {
-            this.phase = this.phases.STARTED;
-        }
-    };
-    RemoteTest.prototype.done = function() {
-        this.phase = this.phases.COMPLETE;
-    }
-
-    /*
-     * A RemoteWorker listens for test events from a worker. These events are
-     * then used to construct and maintain RemoteTest objects that mirror the
-     * tests running on the remote worker.
-     */
-    function RemoteWorker(worker) {
-        this.running = true;
-        this.tests = new Array();
-
-        var this_obj = this;
-        worker.onerror = function(error) { this_obj.worker_error(error); };
-
-        var message_port;
-
-        if (is_service_worker(worker)) {
-            // The ServiceWorker's implicit MessagePort is currently not
-            // reliably accessible from the ServiceWorkerGlobalScope due to
-            // Blink setting MessageEvent.source to null for messages sent via
-            // ServiceWorker.postMessage(). Until that's resolved, create an
-            // explicit MessageChannel and pass one end to the worker.
-            var message_channel = new MessageChannel();
-            message_port = message_channel.port1;
-            message_port.start();
-            worker.postMessage({type: "connect"}, [message_channel.port2]);
-        } else if (is_shared_worker(worker)) {
-            message_port = worker.port;
-        } else {
-            message_port = worker;
-        }
-
-        // Keeping a reference to the worker until worker_done() is seen
-        // prevents the Worker object and its MessageChannel from going away
-        // before all the messages are dispatched.
-        this.worker = worker;
-
-        message_port.onmessage =
-            function(message) {
-                if (this_obj.running && (message.data.type in this_obj.message_handlers)) {
-                    this_obj.message_handlers[message.data.type].call(this_obj, message.data);
-                }
-            };
-    }
-
-    RemoteWorker.prototype.worker_error = function(error) {
-        var message = error.message || String(error);
-        var filename = (error.filename ? " " + error.filename: "");
-        // FIXME: Display worker error states separately from main document
-        // error state.
-        this.worker_done({
-            status: {
-                status: tests.status.ERROR,
-                message: "Error in worker" + filename + ": " + message
-            }
-        });
-        error.preventDefault();
-    };
-
-    RemoteWorker.prototype.test_state = function(data) {
-        var remote_test = this.tests[data.test.index];
-        if (!remote_test) {
-            remote_test = new RemoteTest(data.test);
-            this.tests[data.test.index] = remote_test;
-        }
-        remote_test.update_state_from(data.test);
-        tests.notify_test_state(remote_test);
-    };
-
-    RemoteWorker.prototype.test_done = function(data) {
-        var remote_test = this.tests[data.test.index];
-        remote_test.update_state_from(data.test);
-        remote_test.done();
-        tests.result(remote_test);
-    };
-
-    RemoteWorker.prototype.worker_done = function(data) {
-        if (tests.status.status === null &&
-            data.status.status !== data.status.OK) {
-            tests.status.status = data.status.status;
-            tests.status.message = data.status.message;
-        }
-        this.running = false;
-        this.worker = null;
-        if (tests.all_done()) {
-            tests.complete();
-        }
-    };
-
-    RemoteWorker.prototype.message_handlers = {
-        test_state: RemoteWorker.prototype.test_state,
-        result: RemoteWorker.prototype.test_done,
-        complete: RemoteWorker.prototype.worker_done
-    };
-
-    /*
-     * Harness
-     */
-
-    function TestsStatus()
-    {
-        this.status = null;
-        this.message = null;
-    }
-
-    TestsStatus.statuses = {
-        OK:0,
-        ERROR:1,
-        TIMEOUT:2
-    };
-
-    TestsStatus.prototype = merge({}, TestsStatus.statuses);
-
-    TestsStatus.prototype.structured_clone = function()
-    {
-        if (!this._structured_clone) {
-            var msg = this.message;
-            msg = msg ? String(msg) : msg;
-            this._structured_clone = merge({
-                status:this.status,
-                message:msg
-            }, TestsStatus.statuses);
-        }
-        return this._structured_clone;
-    };
-
-    function Tests()
-    {
-        this.tests = [];
-        this.num_pending = 0;
-
-        this.phases = {
-            INITIAL:0,
-            SETUP:1,
-            HAVE_TESTS:2,
-            HAVE_RESULTS:3,
-            COMPLETE:4
-        };
-        this.phase = this.phases.INITIAL;
-
-        this.properties = {};
-
-        this.wait_for_finish = false;
-        this.processing_callbacks = false;
-
-        this.allow_uncaught_exception = false;
-
-        this.file_is_test = false;
-
-        this.timeout_multiplier = 1;
-        this.timeout_length = test_environment.test_timeout();
-        this.timeout_id = null;
-
-        this.start_callbacks = [];
-        this.test_state_callbacks = [];
-        this.test_done_callbacks = [];
-        this.all_done_callbacks = [];
-
-        this.pending_workers = [];
-
-        this.status = new TestsStatus();
-
-        var this_obj = this;
-
-        test_environment.add_on_loaded_callback(function() {
-            if (this_obj.all_done()) {
-                this_obj.complete();
-            }
-        });
-
-        this.set_timeout();
-    }
-
-    Tests.prototype.setup = function(func, properties)
-    {
-        if (this.phase >= this.phases.HAVE_RESULTS) {
-            return;
-        }
-
-        if (this.phase < this.phases.SETUP) {
-            this.phase = this.phases.SETUP;
-        }
-
-        this.properties = properties;
-
-        for (var p in properties) {
-            if (properties.hasOwnProperty(p)) {
-                var value = properties[p];
-                if (p == "allow_uncaught_exception") {
-                    this.allow_uncaught_exception = value;
-                } else if (p == "explicit_done" && value) {
-                    this.wait_for_finish = true;
-                } else if (p == "explicit_timeout" && value) {
-                    this.timeout_length = null;
-                    if (this.timeout_id)
-                    {
-                        clearTimeout(this.timeout_id);
-                    }
-                } else if (p == "timeout_multiplier") {
-                    this.timeout_multiplier = value;
-                }
-            }
-        }
-
-        if (func) {
-            try {
-                func();
-            } catch (e) {
-                this.status.status = this.status.ERROR;
-                this.status.message = String(e);
-            }
-        }
-        this.set_timeout();
-    };
-
-    Tests.prototype.set_file_is_test = function() {
-        if (this.tests.length > 0) {
-            throw new Error("Tried to set file as test after creating a test");
-        }
-        this.wait_for_finish = true;
-        this.file_is_test = true;
-        // Create the test, which will add it to the list of tests
-        async_test();
-    };
-
-    Tests.prototype.set_timeout = function() {
-        var this_obj = this;
-        clearTimeout(this.timeout_id);
-        if (this.timeout_length !== null) {
-            this.timeout_id = setTimeout(function() {
-                                             this_obj.timeout();
-                                         }, this.timeout_length);
-        }
-    };
-
-    Tests.prototype.timeout = function() {
-        if (this.status.status === null) {
-            this.status.status = this.status.TIMEOUT;
-        }
-        this.complete();
-    };
-
-    Tests.prototype.end_wait = function()
-    {
-        this.wait_for_finish = false;
-        if (this.all_done()) {
-            this.complete();
-        }
-    };
-
-    Tests.prototype.push = function(test)
-    {
-        if (this.phase < this.phases.HAVE_TESTS) {
-            this.start();
-        }
-        this.num_pending++;
-        test.index = this.tests.push(test);
-        this.notify_test_state(test);
-    };
-
-    Tests.prototype.notify_test_state = function(test) {
-        var this_obj = this;
-        forEach(this.test_state_callbacks,
-                function(callback) {
-                    callback(test, this_obj);
-                });
-    };
-
-    Tests.prototype.all_done = function() {
-        return (this.tests.length > 0 && test_environment.all_loaded &&
-                this.num_pending === 0 && !this.wait_for_finish &&
-                !this.processing_callbacks &&
-                !this.pending_workers.some(function(w) { return w.running; }));
-    };
-
-    Tests.prototype.start = function() {
-        this.phase = this.phases.HAVE_TESTS;
-        this.notify_start();
-    };
-
-    Tests.prototype.notify_start = function() {
-        var this_obj = this;
-        forEach (this.start_callbacks,
-                 function(callback)
-                 {
-                     callback(this_obj.properties);
-                 });
-    };
-
-    Tests.prototype.result = function(test)
-    {
-        if (this.phase > this.phases.HAVE_RESULTS) {
-            return;
-        }
-        this.phase = this.phases.HAVE_RESULTS;
-        this.num_pending--;
-        this.notify_result(test);
-    };
-
-    Tests.prototype.notify_result = function(test) {
-        var this_obj = this;
-        this.processing_callbacks = true;
-        forEach(this.test_done_callbacks,
-                function(callback)
-                {
-                    callback(test, this_obj);
-                });
-        this.processing_callbacks = false;
-        if (this_obj.all_done()) {
-            this_obj.complete();
-        }
-    };
-
-    Tests.prototype.complete = function() {
-        if (this.phase === this.phases.COMPLETE) {
-            return;
-        }
-        this.phase = this.phases.COMPLETE;
-        var this_obj = this;
-        this.tests.forEach(
-            function(x)
-            {
-                if (x.phase < x.phases.COMPLETE) {
-                    this_obj.notify_result(x);
-                    x.cleanup();
-                    x.phase = x.phases.COMPLETE;
-                }
-            }
-        );
-        this.notify_complete();
-    };
-
-    Tests.prototype.notify_complete = function() {
-        var this_obj = this;
-        if (this.status.status === null) {
-            this.status.status = this.status.OK;
-        }
-
-        forEach (this.all_done_callbacks,
-                 function(callback)
-                 {
-                     callback(this_obj.tests, this_obj.status);
-                 });
-    };
-
-    Tests.prototype.fetch_tests_from_worker = function(worker) {
-        if (this.phase >= this.phases.COMPLETE) {
-            return;
-        }
-
-        this.pending_workers.push(new RemoteWorker(worker));
-    };
-
-    function fetch_tests_from_worker(port) {
-        tests.fetch_tests_from_worker(port);
-    }
-    expose(fetch_tests_from_worker, 'fetch_tests_from_worker');
-
-    function timeout() {
-        if (tests.timeout_length === null) {
-            tests.timeout();
-        }
-    }
-    expose(timeout, 'timeout');
-
-    function add_start_callback(callback) {
-        tests.start_callbacks.push(callback);
-    }
-
-    function add_test_state_callback(callback) {
-        tests.test_state_callbacks.push(callback);
-    }
-
-    function add_result_callback(callback)
-    {
-        tests.test_done_callbacks.push(callback);
-    }
-
-    function add_completion_callback(callback)
-    {
-       tests.all_done_callbacks.push(callback);
-    }
-
-    expose(add_start_callback, 'add_start_callback');
-    expose(add_test_state_callback, 'add_test_state_callback');
-    expose(add_result_callback, 'add_result_callback');
-    expose(add_completion_callback, 'add_completion_callback');
-
-    /*
-     * Output listener
-    */
-
-    function Output() {
-        this.output_document = document;
-        this.output_node = null;
-        this.enabled = settings.output;
-        this.phase = this.INITIAL;
-    }
-
-    Output.prototype.INITIAL = 0;
-    Output.prototype.STARTED = 1;
-    Output.prototype.HAVE_RESULTS = 2;
-    Output.prototype.COMPLETE = 3;
-
-    Output.prototype.setup = function(properties) {
-        if (this.phase > this.INITIAL) {
-            return;
-        }
-
-        //If output is disabled in testharnessreport.js the test shouldn't be
-        //able to override that
-        this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
-                                        properties.output : settings.output);
-    };
-
-    Output.prototype.init = function(properties) {
-        if (this.phase >= this.STARTED) {
-            return;
-        }
-        if (properties.output_document) {
-            this.output_document = properties.output_document;
-        } else {
-            this.output_document = document;
-        }
-        this.phase = this.STARTED;
-    };
-
-    Output.prototype.resolve_log = function() {
-        var output_document;
-        if (typeof this.output_document === "function") {
-            output_document = this.output_document.apply(undefined);
-        } else {
-            output_document = this.output_document;
-        }
-        if (!output_document) {
-            return;
-        }
-        var node = output_document.getElementById("log");
-        if (!node) {
-            if (!document.body || document.readyState == "loading") {
-                return;
-            }
-            node = output_document.createElement("div");
-            node.id = "log";
-            output_document.body.appendChild(node);
-        }
-        this.output_document = output_document;
-        this.output_node = node;
-    };
-
-    Output.prototype.show_status = function() {
-        if (this.phase < this.STARTED) {
-            this.init();
-        }
-        if (!this.enabled) {
-            return;
-        }
-        if (this.phase < this.HAVE_RESULTS) {
-            this.resolve_log();
-            this.phase = this.HAVE_RESULTS;
-        }
-        var done_count = tests.tests.length - tests.num_pending;
-        if (this.output_node) {
-            if (done_count < 100 ||
-                (done_count < 1000 && done_count % 100 === 0) ||
-                done_count % 1000 === 0) {
-                this.output_node.textContent = "Running, " +
-                    done_count + " complete, " +
-                    tests.num_pending + " remain";
-            }
-        }
-    };
-
-    Output.prototype.show_results = function (tests, harness_status) {
-        if (this.phase >= this.COMPLETE) {
-            return;
-        }
-        if (!this.enabled) {
-            return;
-        }
-        if (!this.output_node) {
-            this.resolve_log();
-        }
-        this.phase = this.COMPLETE;
-
-        var log = this.output_node;
-        if (!log) {
-            return;
-        }
-        var output_document = this.output_document;
-
-        while (log.lastChild) {
-            log.removeChild(log.lastChild);
-        }
-
-        var script_prefix = null;
-        var scripts = document.getElementsByTagName("script");
-        for (var i = 0; i < scripts.length; i++) {
-            var src;
-            if (scripts[i].src) {
-                src = scripts[i].src;
-            } else if (scripts[i].href) {
-                //SVG case
-                src = scripts[i].href.baseVal;
-            }
-
-            var matches = src && src.match(/^(.*\/|)testharness\.js$/);
-            if (matches) {
-                script_prefix = matches[1];
-                break;
-            }
-        }
-
-        if (script_prefix !== null) {
-            var stylesheet = output_document.createElementNS(xhtml_ns, "link");
-            stylesheet.setAttribute("rel", "stylesheet");
-            stylesheet.setAttribute("href", script_prefix + "testharness.css");
-            var heads = output_document.getElementsByTagName("head");
-            if (heads.length) {
-                heads[0].appendChild(stylesheet);
-            }
-        }
-
-        var status_text_harness = {};
-        status_text_harness[harness_status.OK] = "OK";
-        status_text_harness[harness_status.ERROR] = "Error";
-        status_text_harness[harness_status.TIMEOUT] = "Timeout";
-
-        var status_text = {};
-        status_text[Test.prototype.PASS] = "Pass";
-        status_text[Test.prototype.FAIL] = "Fail";
-        status_text[Test.prototype.TIMEOUT] = "Timeout";
-        status_text[Test.prototype.NOTRUN] = "Not Run";
-
-        var status_number = {};
-        forEach(tests,
-                function(test) {
-                    var status = status_text[test.status];
-                    if (status_number.hasOwnProperty(status)) {
-                        status_number[status] += 1;
-                    } else {
-                        status_number[status] = 1;
-                    }
-                });
-
-        function status_class(status)
-        {
-            return status.replace(/\s/g, '').toLowerCase();
-        }
-
-        var summary_template = ["section", {"id":"summary"},
-                                ["h2", {}, "Summary"],
-                                function()
-                                {
-
-                                    var status = status_text_harness[harness_status.status];
-                                    var rv = [["section", {},
-                                               ["p", {},
-                                                "Harness status: ",
-                                                ["span", {"class":status_class(status)},
-                                                 status
-                                                ],
-                                               ]
-                                              ]];
-
-                                    if (harness_status.status === harness_status.ERROR) {
-                                        rv[0].push(["pre", {}, harness_status.message]);
-                                    }
-                                    return rv;
-                                },
-                                ["p", {}, "Found ${num_tests} tests"],
-                                function() {
-                                    var rv = [["div", {}]];
-                                    var i = 0;
-                                    while (status_text.hasOwnProperty(i)) {
-                                        if (status_number.hasOwnProperty(status_text[i])) {
-                                            var status = status_text[i];
-                                            rv[0].push(["div", {"class":status_class(status)},
-                                                        ["label", {},
-                                                         ["input", {type:"checkbox", checked:"checked"}],
-                                                         status_number[status] + " " + status]]);
-                                        }
-                                        i++;
-                                    }
-                                    return rv;
-                                },
-                               ];
-
-        log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
-
-        forEach(output_document.querySelectorAll("section#summary label"),
-                function(element)
-                {
-                    on_event(element, "click",
-                             function(e)
-                             {
-                                 if (output_document.getElementById("results") === null) {
-                                     e.preventDefault();
-                                     return;
-                                 }
-                                 var result_class = element.parentNode.getAttribute("class");
-                                 var style_element = output_document.querySelector("style#hide-" + result_class);
-                                 var input_element = element.querySelector("input");
-                                 if (!style_element && !input_element.checked) {
-                                     style_element = output_document.createElementNS(xhtml_ns, "style");
-                                     style_element.id = "hide-" + result_class;
-                                     style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";
-                                     output_document.body.appendChild(style_element);
-                                 } else if (style_element && input_element.checked) {
-                                     style_element.parentNode.removeChild(style_element);
-                                 }
-                             });
-                });
-
-        // This use of innerHTML plus manual escaping is not recommended in
-        // general, but is necessary here for performance.  Using textContent
-        // on each individual <td> adds tens of seconds of execution time for
-        // large test suites (tens of thousands of tests).
-        function escape_html(s)
-        {
-            return s.replace(/\&/g, "&amp;")
-                .replace(/</g, "&lt;")
-                .replace(/"/g, "&quot;")
-                .replace(/'/g, "&#39;");
-        }
-
-        function has_assertions()
-        {
-            for (var i = 0; i < tests.length; i++) {
-                if (tests[i].properties.hasOwnProperty("assert")) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        function get_assertion(test)
-        {
-            if (test.properties.hasOwnProperty("assert")) {
-                if (Array.isArray(test.properties.assert)) {
-                    return test.properties.assert.join(' ');
-                }
-                return test.properties.assert;
-            }
-            return '';
-        }
-
-        log.appendChild(document.createElementNS(xhtml_ns, "section"));
-        var assertions = has_assertions();
-        var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">" +
-            "<thead><tr><th>Result</th><th>Test Name</th>" +
-            (assertions ? "<th>Assertion</th>" : "") +
-            "<th>Message</th></tr></thead>" +
-            "<tbody>";
-        for (var i = 0; i < tests.length; i++) {
-            html += '<tr class="' +
-                escape_html(status_class(status_text[tests[i].status])) +
-                '"><td>' +
-                escape_html(status_text[tests[i].status]) +
-                "</td><td>" +
-                escape_html(tests[i].name) +
-                "</td><td>" +
-                (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") +
-                escape_html(tests[i].message ? tests[i].message : " ") +
-                "</td></tr>";
-        }
-        html += "</tbody></table>";
-        try {
-            log.lastChild.innerHTML = html;
-        } catch (e) {
-            log.appendChild(document.createElementNS(xhtml_ns, "p"))
-               .textContent = "Setting innerHTML for the log threw an exception.";
-            log.appendChild(document.createElementNS(xhtml_ns, "pre"))
-               .textContent = html;
-        }
-    };
-
-    /*
-     * Template code
-     *
-     * A template is just a javascript structure. An element is represented as:
-     *
-     * [tag_name, {attr_name:attr_value}, child1, child2]
-     *
-     * the children can either be strings (which act like text nodes), other templates or
-     * functions (see below)
-     *
-     * A text node is represented as
-     *
-     * ["{text}", value]
-     *
-     * String values have a simple substitution syntax; ${foo} represents a variable foo.
-     *
-     * It is possible to embed logic in templates by using a function in a place where a
-     * node would usually go. The function must either return part of a template or null.
-     *
-     * In cases where a set of nodes are required as output rather than a single node
-     * with children it is possible to just use a list
-     * [node1, node2, node3]
-     *
-     * Usage:
-     *
-     * render(template, substitutions) - take a template and an object mapping
-     * variable names to parameters and return either a DOM node or a list of DOM nodes
-     *
-     * substitute(template, substitutions) - take a template and variable mapping object,
-     * make the variable substitutions and return the substituted template
-     *
-     */
-
-    function is_single_node(template)
-    {
-        return typeof template[0] === "string";
-    }
-
-    function substitute(template, substitutions)
-    {
-        if (typeof template === "function") {
-            var replacement = template(substitutions);
-            if (!replacement) {
-                return null;
-            }
-
-            return substitute(replacement, substitutions);
-        }
-
-        if (is_single_node(template)) {
-            return substitute_single(template, substitutions);
-        }
-
-        return filter(map(template, function(x) {
-                              return substitute(x, substitutions);
-                          }), function(x) {return x !== null;});
-    }
-
-    function substitute_single(template, substitutions)
-    {
-        var substitution_re = /\$\{([^ }]*)\}/g;
-
-        function do_substitution(input) {
-            var components = input.split(substitution_re);
-            var rv = [];
-            for (var i = 0; i < components.length; i += 2) {
-                rv.push(components[i]);
-                if (components[i + 1]) {
-                    rv.push(String(substitutions[components[i + 1]]));
-                }
-            }
-            return rv;
-        }
-
-        function substitute_attrs(attrs, rv)
-        {
-            rv[1] = {};
-            for (var name in template[1]) {
-                if (attrs.hasOwnProperty(name)) {
-                    var new_name = do_substitution(name).join("");
-                    var new_value = do_substitution(attrs[name]).join("");
-                    rv[1][new_name] = new_value;
-                }
-            }
-        }
-
-        function substitute_children(children, rv)
-        {
-            for (var i = 0; i < children.length; i++) {
-                if (children[i] instanceof Object) {
-                    var replacement = substitute(children[i], substitutions);
-                    if (replacement !== null) {
-                        if (is_single_node(replacement)) {
-                            rv.push(replacement);
-                        } else {
-                            extend(rv, replacement);
-                        }
-                    }
-                } else {
-                    extend(rv, do_substitution(String(children[i])));
-                }
-            }
-            return rv;
-        }
-
-        var rv = [];
-        rv.push(do_substitution(String(template[0])).join(""));
-
-        if (template[0] === "{text}") {
-            substitute_children(template.slice(1), rv);
-        } else {
-            substitute_attrs(template[1], rv);
-            substitute_children(template.slice(2), rv);
-        }
-
-        return rv;
-    }
-
-    function make_dom_single(template, doc)
-    {
-        var output_document = doc || document;
-        var element;
-        if (template[0] === "{text}") {
-            element = output_document.createTextNode("");
-            for (var i = 1; i < template.length; i++) {
-                element.data += template[i];
-            }
-        } else {
-            element = output_document.createElementNS(xhtml_ns, template[0]);
-            for (var name in template[1]) {
-                if (template[1].hasOwnProperty(name)) {
-                    element.setAttribute(name, template[1][name]);
-                }
-            }
-            for (var i = 2; i < template.length; i++) {
-                if (template[i] instanceof Object) {
-                    var sub_element = make_dom(template[i]);
-                    element.appendChild(sub_element);
-                } else {
-                    var text_node = output_document.createTextNode(template[i]);
-                    element.appendChild(text_node);
-                }
-            }
-        }
-
-        return element;
-    }
-
-    function make_dom(template, substitutions, output_document)
-    {
-        if (is_single_node(template)) {
-            return make_dom_single(template, output_document);
-        }
-
-        return map(template, function(x) {
-                       return make_dom_single(x, output_document);
-                   });
-    }
-
-    function render(template, substitutions, output_document)
-    {
-        return make_dom(substitute(template, substitutions), output_document);
-    }
-
-    /*
-     * Utility funcions
-     */
-    function assert(expected_true, function_name, description, error, substitutions)
-    {
-        if (tests.tests.length === 0) {
-            tests.set_file_is_test();
-        }
-        if (expected_true !== true) {
-            var msg = make_message(function_name, description,
-                                   error, substitutions);
-            throw new AssertionError(msg);
-        }
-    }
-
-    function AssertionError(message)
-    {
-        this.message = message;
-    }
-
-    AssertionError.prototype.toString = function() {
-        return this.message;
-    };
-
-    function make_message(function_name, description, error, substitutions)
-    {
-        for (var p in substitutions) {
-            if (substitutions.hasOwnProperty(p)) {
-                substitutions[p] = format_value(substitutions[p]);
-            }
-        }
-        var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
-                                   merge({function_name:function_name,
-                                          description:(description?description + " ":"")},
-                                          substitutions));
-        return node_form.slice(1).join("");
-    }
-
-    function filter(array, callable, thisObj) {
-        var rv = [];
-        for (var i = 0; i < array.length; i++) {
-            if (array.hasOwnProperty(i)) {
-                var pass = callable.call(thisObj, array[i], i, array);
-                if (pass) {
-                    rv.push(array[i]);
-                }
-            }
-        }
-        return rv;
-    }
-
-    function map(array, callable, thisObj)
-    {
-        var rv = [];
-        rv.length = array.length;
-        for (var i = 0; i < array.length; i++) {
-            if (array.hasOwnProperty(i)) {
-                rv[i] = callable.call(thisObj, array[i], i, array);
-            }
-        }
-        return rv;
-    }
-
-    function extend(array, items)
-    {
-        Array.prototype.push.apply(array, items);
-    }
-
-    function forEach (array, callback, thisObj)
-    {
-        for (var i = 0; i < array.length; i++) {
-            if (array.hasOwnProperty(i)) {
-                callback.call(thisObj, array[i], i, array);
-            }
-        }
-    }
-
-    function merge(a,b)
-    {
-        var rv = {};
-        var p;
-        for (p in a) {
-            rv[p] = a[p];
-        }
-        for (p in b) {
-            rv[p] = b[p];
-        }
-        return rv;
-    }
-
-    function expose(object, name)
-    {
-        var components = name.split(".");
-        var target = test_environment.global_scope();
-        for (var i = 0; i < components.length - 1; i++) {
-            if (!(components[i] in target)) {
-                target[components[i]] = {};
-            }
-            target = target[components[i]];
-        }
-        target[components[components.length - 1]] = object;
-    }
-
-    function is_same_origin(w) {
-        try {
-            'random_prop' in w;
-            return true;
-        } catch (e) {
-            return false;
-        }
-    }
-
-    function supports_post_message(w)
-    {
-        var supports;
-        var type;
-        // Given IE  implements postMessage across nested iframes but not across
-        // windows or tabs, you can't infer cross-origin communication from the presence
-        // of postMessage on the current window object only.
-        //
-        // Touching the postMessage prop on a window can throw if the window is
-        // not from the same origin AND post message is not supported in that
-        // browser. So just doing an existence test here won't do, you also need
-        // to wrap it in a try..cacth block.
-        try {
-            type = typeof w.postMessage;
-            if (type === "function") {
-                supports = true;
-            }
-
-            // IE8 supports postMessage, but implements it as a host object which
-            // returns "object" as its `typeof`.
-            else if (type === "object") {
-                supports = true;
-            }
-
-            // This is the case where postMessage isn't supported AND accessing a
-            // window property across origins does NOT throw (e.g. old Safari browser).
-            else {
-                supports = false;
-            }
-        } catch (e) {
-            // This is the case where postMessage isn't supported AND accessing a
-            // window property across origins throws (e.g. old Firefox browser).
-            supports = false;
-        }
-        return supports;
-    }
-
-    /**
-     * Setup globals
-     */
-
-    var tests = new Tests();
-
-    addEventListener("error", function(e) {
-        if (tests.file_is_test) {
-            var test = tests.tests[0];
-            if (test.phase >= test.phases.HAS_RESULT) {
-                return;
-            }
-            var message = e.message;
-            test.set_status(test.FAIL, message);
-            test.phase = test.phases.HAS_RESULT;
-            test.done();
-            done();
-        } else if (!tests.allow_uncaught_exception) {
-            tests.status.status = tests.status.ERROR;
-            tests.status.message = e.message;
-        }
-    });
-
-    test_environment.on_tests_ready();
-
-})();
-// vim: set expandtab shiftwidth=4 tabstop=4:
+/*global self*/\r
+/*jshint latedef: nofunc*/\r
+/*\r
+Distributed under both the W3C Test Suite License [1] and the W3C\r
+3-clause BSD License [2]. To contribute to a W3C Test Suite, see the\r
+policies and contribution forms [3].\r
+\r
+[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license\r
+[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license\r
+[3] http://www.w3.org/2004/10/27-testcases\r
+*/\r
+\r
+/* Documentation is in docs/api.md */\r
+\r
+(function ()\r
+{\r
+    var debug = false;\r
+    // default timeout is 10 seconds, test can override if needed\r
+    var settings = {\r
+        output:true,\r
+        harness_timeout:{\r
+            "normal":90000,\r
+            "long":60000\r
+        },\r
+        test_timeout:null\r
+    };\r
+\r
+    var xhtml_ns = "http://www.w3.org/1999/xhtml";\r
+\r
+    /*\r
+     * TestEnvironment is an abstraction for the environment in which the test\r
+     * harness is used. Each implementation of a test environment has to provide\r
+     * the following interface:\r
+     *\r
+     * interface TestEnvironment {\r
+     *   // Invoked after the global 'tests' object has been created and it's\r
+     *   // safe to call add_*_callback() to register event handlers.\r
+     *   void on_tests_ready();\r
+     *\r
+     *   // Invoked after setup() has been called to notify the test environment\r
+     *   // of changes to the test harness properties.\r
+     *   void on_new_harness_properties(object properties);\r
+     *\r
+     *   // Should return a new unique default test name.\r
+     *   DOMString next_default_test_name();\r
+     *\r
+     *   // Should return the test harness timeout duration in milliseconds.\r
+     *   float test_timeout();\r
+     *\r
+     *   // Should return the global scope object.\r
+     *   object global_scope();\r
+     * };\r
+     */\r
+\r
+    /*\r
+     * A test environment with a DOM. The global object is 'window'. By default\r
+     * test results are displayed in a table. Any parent windows receive\r
+     * callbacks or messages via postMessage() when test events occur. See\r
+     * apisample11.html and apisample12.html.\r
+     */\r
+    function WindowTestEnvironment() {\r
+        this.name_counter = 0;\r
+        this.window_cache = null;\r
+        this.output_handler = null;\r
+        this.all_loaded = false;\r
+        var this_obj = this;\r
+        on_event(window, 'load', function() {\r
+            this_obj.all_loaded = true;\r
+        });\r
+    }\r
+\r
+    WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) {\r
+        this._forEach_windows(\r
+                function(w, is_same_origin) {\r
+                    if (is_same_origin && selector in w) {\r
+                        try {\r
+                            w[selector].apply(undefined, callback_args);\r
+                        } catch (e) {\r
+                            if (debug) {\r
+                                throw e;\r
+                            }\r
+                        }\r
+                    }\r
+                    if (supports_post_message(w) && w !== self) {\r
+                        w.postMessage(message_arg, "*");\r
+                    }\r
+                });\r
+    };\r
+\r
+    WindowTestEnvironment.prototype._forEach_windows = function(callback) {\r
+        // Iterate of the the windows [self ... top, opener]. The callback is passed\r
+        // two objects, the first one is the windows object itself, the second one\r
+        // is a boolean indicating whether or not its on the same origin as the\r
+        // current window.\r
+        var cache = this.window_cache;\r
+        if (!cache) {\r
+            cache = [[self, true]];\r
+            var w = self;\r
+            var i = 0;\r
+            var so;\r
+            var origins = location.ancestorOrigins;\r
+            while (w != w.parent) {\r
+                w = w.parent;\r
+                // In WebKit, calls to parent windows' properties that aren't on the same\r
+                // origin cause an error message to be displayed in the error console but\r
+                // don't throw an exception. This is a deviation from the current HTML5\r
+                // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504\r
+                // The problem with WebKit's behavior is that it pollutes the error console\r
+                // with error messages that can't be caught.\r
+                //\r
+                // This issue can be mitigated by relying on the (for now) proprietary\r
+                // `location.ancestorOrigins` property which returns an ordered list of\r
+                // the origins of enclosing windows. See:\r
+                // http://trac.webkit.org/changeset/113945.\r
+                if (origins) {\r
+                    so = (location.origin == origins[i]);\r
+                } else {\r
+                    so = is_same_origin(w);\r
+                }\r
+                cache.push([w, so]);\r
+                i++;\r
+            }\r
+            w = window.opener;\r
+            if (w) {\r
+                // window.opener isn't included in the `location.ancestorOrigins` prop.\r
+                // We'll just have to deal with a simple check and an error msg on WebKit\r
+                // browsers in this case.\r
+                cache.push([w, is_same_origin(w)]);\r
+            }\r
+            this.window_cache = cache;\r
+        }\r
+\r
+        forEach(cache,\r
+                function(a) {\r
+                    callback.apply(null, a);\r
+                });\r
+    };\r
+\r
+    WindowTestEnvironment.prototype.on_tests_ready = function() {\r
+        var output = new Output();\r
+        this.output_handler = output;\r
+\r
+        var this_obj = this;\r
+        add_start_callback(function (properties) {\r
+            this_obj.output_handler.init(properties);\r
+            this_obj._dispatch("start_callback", [properties],\r
+                           { type: "start", properties: properties });\r
+        });\r
+        add_test_state_callback(function(test) {\r
+            this_obj.output_handler.show_status();\r
+            this_obj._dispatch("test_state_callback", [test],\r
+                               { type: "test_state", test: test.structured_clone() });\r
+        });\r
+        add_result_callback(function (test) {\r
+            this_obj.output_handler.show_status();\r
+            this_obj._dispatch("result_callback", [test],\r
+                               { type: "result", test: test.structured_clone() });\r
+        });\r
+        add_completion_callback(function (tests, harness_status) {\r
+            this_obj.output_handler.show_results(tests, harness_status);\r
+            var cloned_tests = map(tests, function(test) { return test.structured_clone(); });\r
+            this_obj._dispatch("completion_callback", [tests, harness_status],\r
+                               { type: "complete", tests: cloned_tests,\r
+                                 status: harness_status.structured_clone() });\r
+        });\r
+    };\r
+\r
+    WindowTestEnvironment.prototype.next_default_test_name = function() {\r
+        //Don't use document.title to work around an Opera bug in XHTML documents\r
+        var title = document.getElementsByTagName("title")[0];\r
+        var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled";\r
+        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";\r
+        this.name_counter++;\r
+        return prefix + suffix;\r
+    };\r
+\r
+    WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) {\r
+        this.output_handler.setup(properties);\r
+    };\r
+\r
+    WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) {\r
+        on_event(window, 'load', callback);\r
+    };\r
+\r
+    WindowTestEnvironment.prototype.test_timeout = function() {\r
+        var metas = document.getElementsByTagName("meta");\r
+        for (var i = 0; i < metas.length; i++) {\r
+            if (metas[i].name == "timeout") {\r
+                if (metas[i].content == "long") {\r
+                    return settings.harness_timeout.long;\r
+                }\r
+                break;\r
+            }\r
+        }\r
+        return settings.harness_timeout.normal;\r
+    };\r
+\r
+    WindowTestEnvironment.prototype.global_scope = function() {\r
+        return window;\r
+    };\r
+\r
+    /*\r
+     * Base TestEnvironment implementation for a generic web worker.\r
+     *\r
+     * Workers accumulate test results. One or more clients can connect and\r
+     * retrieve results from a worker at any time.\r
+     *\r
+     * WorkerTestEnvironment supports communicating with a client via a\r
+     * MessagePort.  The mechanism for determining the appropriate MessagePort\r
+     * for communicating with a client depends on the type of worker and is\r
+     * implemented by the various specializations of WorkerTestEnvironment\r
+     * below.\r
+     *\r
+     * A client document using testharness can use fetch_tests_from_worker() to\r
+     * retrieve results from a worker. See apisample16.html.\r
+     */\r
+    function WorkerTestEnvironment() {\r
+        this.name_counter = 0;\r
+        this.all_loaded = true;\r
+        this.message_list = [];\r
+        this.message_ports = [];\r
+    }\r
+\r
+    WorkerTestEnvironment.prototype._dispatch = function(message) {\r
+        this.message_list.push(message);\r
+        for (var i = 0; i < this.message_ports.length; ++i)\r
+        {\r
+            this.message_ports[i].postMessage(message);\r
+        }\r
+    };\r
+\r
+    // The only requirement is that port has a postMessage() method. It doesn't\r
+    // have to be an instance of a MessagePort, and often isn't.\r
+    WorkerTestEnvironment.prototype._add_message_port = function(port) {\r
+        this.message_ports.push(port);\r
+        for (var i = 0; i < this.message_list.length; ++i)\r
+        {\r
+            port.postMessage(this.message_list[i]);\r
+        }\r
+    };\r
+\r
+    WorkerTestEnvironment.prototype.next_default_test_name = function() {\r
+        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";\r
+        this.name_counter++;\r
+        return "Untitled" + suffix;\r
+    };\r
+\r
+    WorkerTestEnvironment.prototype.on_new_harness_properties = function() {};\r
+\r
+    WorkerTestEnvironment.prototype.on_tests_ready = function() {\r
+        var this_obj = this;\r
+        add_start_callback(\r
+                function(properties) {\r
+                    this_obj._dispatch({\r
+                        type: "start",\r
+                        properties: properties,\r
+                    });\r
+                });\r
+        add_test_state_callback(\r
+                function(test) {\r
+                    this_obj._dispatch({\r
+                        type: "test_state",\r
+                        test: test.structured_clone()\r
+                    });\r
+                });\r
+        add_result_callback(\r
+                function(test) {\r
+                    this_obj._dispatch({\r
+                        type: "result",\r
+                        test: test.structured_clone()\r
+                    });\r
+                });\r
+        add_completion_callback(\r
+                function(tests, harness_status) {\r
+                    this_obj._dispatch({\r
+                        type: "complete",\r
+                        tests: map(tests,\r
+                            function(test) {\r
+                                return test.structured_clone();\r
+                            }),\r
+                        status: harness_status.structured_clone()\r
+                    });\r
+                });\r
+    };\r
+\r
+    WorkerTestEnvironment.prototype.add_on_loaded_callback = function() {};\r
+\r
+    WorkerTestEnvironment.prototype.test_timeout = function() {\r
+        // Tests running in a worker don't have a default timeout. I.e. all\r
+        // worker tests behave as if settings.explicit_timeout is true.\r
+        return null;\r
+    };\r
+\r
+    WorkerTestEnvironment.prototype.global_scope = function() {\r
+        return self;\r
+    };\r
+\r
+    /*\r
+     * Dedicated web workers.\r
+     * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope\r
+     *\r
+     * This class is used as the test_environment when testharness is running\r
+     * inside a dedicated worker.\r
+     */\r
+    function DedicatedWorkerTestEnvironment() {\r
+        WorkerTestEnvironment.call(this);\r
+        // self is an instance of DedicatedWorkerGlobalScope which exposes\r
+        // a postMessage() method for communicating via the message channel\r
+        // established when the worker is created.\r
+        this._add_message_port(self);\r
+    }\r
+    DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);\r
+\r
+    DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() {\r
+        WorkerTestEnvironment.prototype.on_tests_ready.call(this);\r
+        // In the absence of an onload notification, we a require dedicated\r
+        // workers to explicitly signal when the tests are done.\r
+        tests.wait_for_finish = true;\r
+    };\r
+\r
+    /*\r
+     * Shared web workers.\r
+     * https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalscope\r
+     *\r
+     * This class is used as the test_environment when testharness is running\r
+     * inside a shared web worker.\r
+     */\r
+    function SharedWorkerTestEnvironment() {\r
+        WorkerTestEnvironment.call(this);\r
+        var this_obj = this;\r
+        // Shared workers receive message ports via the 'onconnect' event for\r
+        // each connection.\r
+        self.addEventListener("connect",\r
+                function(message_event) {\r
+                    this_obj._add_message_port(message_event.source);\r
+                });\r
+    }\r
+    SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);\r
+\r
+    SharedWorkerTestEnvironment.prototype.on_tests_ready = function() {\r
+        WorkerTestEnvironment.prototype.on_tests_ready.call(this);\r
+        // In the absence of an onload notification, we a require shared\r
+        // workers to explicitly signal when the tests are done.\r
+        tests.wait_for_finish = true;\r
+    };\r
+\r
+    /*\r
+     * Service workers.\r
+     * http://www.w3.org/TR/service-workers/\r
+     *\r
+     * This class is used as the test_environment when testharness is running\r
+     * inside a service worker.\r
+     */\r
+    function ServiceWorkerTestEnvironment() {\r
+        WorkerTestEnvironment.call(this);\r
+        this.all_loaded = false;\r
+        this.on_loaded_callback = null;\r
+        var this_obj = this;\r
+        self.addEventListener("message",\r
+                function(event) {\r
+                    if (event.data.type && event.data.type === "connect") {\r
+                        this_obj._add_message_port(event.ports[0]);\r
+                        event.ports[0].start();\r
+                    }\r
+                });\r
+\r
+        // The oninstall event is received after the service worker script and\r
+        // all imported scripts have been fetched and executed. It's the\r
+        // equivalent of an onload event for a document. All tests should have\r
+        // been added by the time this event is received, thus it's not\r
+        // necessary to wait until the onactivate event.\r
+        on_event(self, "install",\r
+                function(event) {\r
+                    this_obj.all_loaded = true;\r
+                    if (this_obj.on_loaded_callback) {\r
+                        this_obj.on_loaded_callback();\r
+                    }\r
+                });\r
+    }\r
+    ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);\r
+\r
+    ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function(callback) {\r
+        if (this.all_loaded) {\r
+            callback();\r
+        } else {\r
+            this.on_loaded_callback = callback;\r
+        }\r
+    };\r
+\r
+    function create_test_environment() {\r
+        if ('document' in self) {\r
+            return new WindowTestEnvironment();\r
+        }\r
+        if ('DedicatedWorkerGlobalScope' in self &&\r
+            self instanceof DedicatedWorkerGlobalScope) {\r
+            return new DedicatedWorkerTestEnvironment();\r
+        }\r
+        if ('SharedWorkerGlobalScope' in self &&\r
+            self instanceof SharedWorkerGlobalScope) {\r
+            return new SharedWorkerTestEnvironment();\r
+        }\r
+        if ('ServiceWorkerGlobalScope' in self &&\r
+            self instanceof ServiceWorkerGlobalScope) {\r
+            return new ServiceWorkerTestEnvironment();\r
+        }\r
+        throw new Error("Unsupported test environment");\r
+    }\r
+\r
+    var test_environment = create_test_environment();\r
+\r
+    function is_shared_worker(worker) {\r
+        return 'SharedWorker' in self && worker instanceof SharedWorker;\r
+    }\r
+\r
+    function is_service_worker(worker) {\r
+        return 'ServiceWorker' in self && worker instanceof ServiceWorker;\r
+    }\r
+\r
+    /*\r
+     * API functions\r
+     */\r
+\r
+    function test(func, name, properties)\r
+    {\r
+        var test_name = name ? name : test_environment.next_default_test_name();\r
+        properties = properties ? properties : {};\r
+        var test_obj = new Test(test_name, properties);\r
+        test_obj.step(func, test_obj, test_obj);\r
+        if (test_obj.phase === test_obj.phases.STARTED) {\r
+            test_obj.done();\r
+        }\r
+    }\r
+\r
+    function async_test(func, name, properties)\r
+    {\r
+        if (typeof func !== "function") {\r
+            properties = name;\r
+            name = func;\r
+            func = null;\r
+        }\r
+        var test_name = name ? name : test_environment.next_default_test_name();\r
+        properties = properties ? properties : {};\r
+        var test_obj = new Test(test_name, properties);\r
+        if (func) {\r
+            test_obj.step(func, test_obj, test_obj);\r
+        }\r
+        return test_obj;\r
+    }\r
+\r
+    function promise_test(func, name, properties) {\r
+        var test = async_test(name, properties);\r
+        Promise.resolve(test.step(func, test, test))\r
+            .then(\r
+                function() {\r
+                    test.done();\r
+                })\r
+            .catch(test.step_func(\r
+                function(value) {\r
+                    if (value instanceof AssertionError) {\r
+                        throw value;\r
+                    }\r
+                    assert(false, "promise_test", null,\r
+                           "Unhandled rejection with value: ${value}", {value:value});\r
+                }));\r
+    }\r
+\r
+    function setup(func_or_properties, maybe_properties)\r
+    {\r
+        var func = null;\r
+        var properties = {};\r
+        if (arguments.length === 2) {\r
+            func = func_or_properties;\r
+            properties = maybe_properties;\r
+        } else if (func_or_properties instanceof Function) {\r
+            func = func_or_properties;\r
+        } else {\r
+            properties = func_or_properties;\r
+        }\r
+        tests.setup(func, properties);\r
+        test_environment.on_new_harness_properties(properties);\r
+    }\r
+\r
+    function done() {\r
+        if (tests.tests.length === 0) {\r
+            tests.set_file_is_test();\r
+        }\r
+        if (tests.file_is_test) {\r
+            tests.tests[0].done();\r
+        }\r
+        tests.end_wait();\r
+    }\r
+\r
+    function generate_tests(func, args, properties) {\r
+        forEach(args, function(x, i)\r
+                {\r
+                    var name = x[0];\r
+                    test(function()\r
+                         {\r
+                             func.apply(this, x.slice(1));\r
+                         },\r
+                         name,\r
+                         Array.isArray(properties) ? properties[i] : properties);\r
+                });\r
+    }\r
+\r
+    function on_event(object, event, callback)\r
+    {\r
+        object.addEventListener(event, callback, false);\r
+    }\r
+\r
+    expose(test, 'test');\r
+    expose(async_test, 'async_test');\r
+    expose(promise_test, 'promise_test');\r
+    expose(generate_tests, 'generate_tests');\r
+    expose(setup, 'setup');\r
+    expose(done, 'done');\r
+    expose(on_event, 'on_event');\r
+\r
+    /*\r
+     * Return a string truncated to the given length, with ... added at the end\r
+     * if it was longer.\r
+     */\r
+    function truncate(s, len)\r
+    {\r
+        if (s.length > len) {\r
+            return s.substring(0, len - 3) + "...";\r
+        }\r
+        return s;\r
+    }\r
+\r
+    /*\r
+     * Return true if object is probably a Node object.\r
+     */\r
+    function is_node(object)\r
+    {\r
+        // I use duck-typing instead of instanceof, because\r
+        // instanceof doesn't work if the node is from another window (like an\r
+        // iframe's contentWindow):\r
+        // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295\r
+        if ("nodeType" in object &&\r
+            "nodeName" in object &&\r
+            "nodeValue" in object &&\r
+            "childNodes" in object) {\r
+            try {\r
+                object.nodeType;\r
+            } catch (e) {\r
+                // The object is probably Node.prototype or another prototype\r
+                // object that inherits from it, and not a Node instance.\r
+                return false;\r
+            }\r
+            return true;\r
+        }\r
+        return false;\r
+    }\r
+\r
+    /*\r
+     * Convert a value to a nice, human-readable string\r
+     */\r
+    function format_value(val, seen)\r
+    {\r
+        if (!seen) {\r
+            seen = [];\r
+        }\r
+        if (typeof val === "object" && val !== null) {\r
+            if (seen.indexOf(val) >= 0) {\r
+                return "[...]";\r
+            }\r
+            seen.push(val);\r
+        }\r
+        if (Array.isArray(val)) {\r
+            return "[" + val.map(function(x) {return format_value(x, seen);}).join(", ") + "]";\r
+        }\r
+\r
+        switch (typeof val) {\r
+        case "string":\r
+            val = val.replace("\\", "\\\\");\r
+            for (var i = 0; i < 32; i++) {\r
+                var replace = "\\";\r
+                switch (i) {\r
+                case 0: replace += "0"; break;\r
+                case 1: replace += "x01"; break;\r
+                case 2: replace += "x02"; break;\r
+                case 3: replace += "x03"; break;\r
+                case 4: replace += "x04"; break;\r
+                case 5: replace += "x05"; break;\r
+                case 6: replace += "x06"; break;\r
+                case 7: replace += "x07"; break;\r
+                case 8: replace += "b"; break;\r
+                case 9: replace += "t"; break;\r
+                case 10: replace += "n"; break;\r
+                case 11: replace += "v"; break;\r
+                case 12: replace += "f"; break;\r
+                case 13: replace += "r"; break;\r
+                case 14: replace += "x0e"; break;\r
+                case 15: replace += "x0f"; break;\r
+                case 16: replace += "x10"; break;\r
+                case 17: replace += "x11"; break;\r
+                case 18: replace += "x12"; break;\r
+                case 19: replace += "x13"; break;\r
+                case 20: replace += "x14"; break;\r
+                case 21: replace += "x15"; break;\r
+                case 22: replace += "x16"; break;\r
+                case 23: replace += "x17"; break;\r
+                case 24: replace += "x18"; break;\r
+                case 25: replace += "x19"; break;\r
+                case 26: replace += "x1a"; break;\r
+                case 27: replace += "x1b"; break;\r
+                case 28: replace += "x1c"; break;\r
+                case 29: replace += "x1d"; break;\r
+                case 30: replace += "x1e"; break;\r
+                case 31: replace += "x1f"; break;\r
+                }\r
+                val = val.replace(RegExp(String.fromCharCode(i), "g"), replace);\r
+            }\r
+            return '"' + val.replace(/"/g, '\\"') + '"';\r
+        case "boolean":\r
+        case "undefined":\r
+            return String(val);\r
+        case "number":\r
+            // In JavaScript, -0 === 0 and String(-0) == "0", so we have to\r
+            // special-case.\r
+            if (val === -0 && 1/val === -Infinity) {\r
+                return "-0";\r
+            }\r
+            return String(val);\r
+        case "object":\r
+            if (val === null) {\r
+                return "null";\r
+            }\r
+\r
+            // Special-case Node objects, since those come up a lot in my tests.  I\r
+            // ignore namespaces.\r
+            if (is_node(val)) {\r
+                switch (val.nodeType) {\r
+                case Node.ELEMENT_NODE:\r
+                    var ret = "<" + val.localName;\r
+                    for (var i = 0; i < val.attributes.length; i++) {\r
+                        ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';\r
+                    }\r
+                    ret += ">" + val.innerHTML + "</" + val.localName + ">";\r
+                    return "Element node " + truncate(ret, 60);\r
+                case Node.TEXT_NODE:\r
+                    return 'Text node "' + truncate(val.data, 60) + '"';\r
+                case Node.PROCESSING_INSTRUCTION_NODE:\r
+                    return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));\r
+                case Node.COMMENT_NODE:\r
+                    return "Comment node <!--" + truncate(val.data, 60) + "-->";\r
+                case Node.DOCUMENT_NODE:\r
+                    return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");\r
+                case Node.DOCUMENT_TYPE_NODE:\r
+                    return "DocumentType node";\r
+                case Node.DOCUMENT_FRAGMENT_NODE:\r
+                    return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");\r
+                default:\r
+                    return "Node object of unknown type";\r
+                }\r
+            }\r
+\r
+        /* falls through */\r
+        default:\r
+            return typeof val + ' "' + truncate(String(val), 60) + '"';\r
+        }\r
+    }\r
+    expose(format_value, "format_value");\r
+\r
+    /*\r
+     * Assertions\r
+     */\r
+\r
+    function assert_true(actual, description)\r
+    {\r
+        assert(actual === true, "assert_true", description,\r
+                                "expected true got ${actual}", {actual:actual});\r
+    }\r
+    expose(assert_true, "assert_true");\r
+\r
+    function assert_false(actual, description)\r
+    {\r
+        assert(actual === false, "assert_false", description,\r
+                                 "expected false got ${actual}", {actual:actual});\r
+    }\r
+    expose(assert_false, "assert_false");\r
+\r
+    function same_value(x, y) {\r
+        if (y !== y) {\r
+            //NaN case\r
+            return x !== x;\r
+        }\r
+        if (x === 0 && y === 0) {\r
+            //Distinguish +0 and -0\r
+            return 1/x === 1/y;\r
+        }\r
+        return x === y;\r
+    }\r
+\r
+    function assert_equals(actual, expected, description)\r
+    {\r
+         /*\r
+          * Test if two primitives are equal or two objects\r
+          * are the same object\r
+          */\r
+        if (typeof actual != typeof expected) {\r
+            assert(false, "assert_equals", description,\r
+                          "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}",\r
+                          {expected:expected, actual:actual});\r
+            return;\r
+        }\r
+        assert(same_value(actual, expected), "assert_equals", description,\r
+                                             "expected ${expected} but got ${actual}",\r
+                                             {expected:expected, actual:actual});\r
+    }\r
+    expose(assert_equals, "assert_equals");\r
+\r
+    function assert_not_equals(actual, expected, description)\r
+    {\r
+         /*\r
+          * Test if two primitives are unequal or two objects\r
+          * are different objects\r
+          */\r
+        assert(!same_value(actual, expected), "assert_not_equals", description,\r
+                                              "got disallowed value ${actual}",\r
+                                              {actual:actual});\r
+    }\r
+    expose(assert_not_equals, "assert_not_equals");\r
+\r
+    function assert_in_array(actual, expected, description)\r
+    {\r
+        assert(expected.indexOf(actual) != -1, "assert_in_array", description,\r
+                                               "value ${actual} not in array ${expected}",\r
+                                               {actual:actual, expected:expected});\r
+    }\r
+    expose(assert_in_array, "assert_in_array");\r
+\r
+    function assert_object_equals(actual, expected, description)\r
+    {\r
+         //This needs to be improved a great deal\r
+         function check_equal(actual, expected, stack)\r
+         {\r
+             stack.push(actual);\r
+\r
+             var p;\r
+             for (p in actual) {\r
+                 assert(expected.hasOwnProperty(p), "assert_object_equals", description,\r
+                                                    "unexpected property ${p}", {p:p});\r
+\r
+                 if (typeof actual[p] === "object" && actual[p] !== null) {\r
+                     if (stack.indexOf(actual[p]) === -1) {\r
+                         check_equal(actual[p], expected[p], stack);\r
+                     }\r
+                 } else {\r
+                     assert(same_value(actual[p], expected[p]), "assert_object_equals", description,\r
+                                                       "property ${p} expected ${expected} got ${actual}",\r
+                                                       {p:p, expected:expected, actual:actual});\r
+                 }\r
+             }\r
+             for (p in expected) {\r
+                 assert(actual.hasOwnProperty(p),\r
+                        "assert_object_equals", description,\r
+                        "expected property ${p} missing", {p:p});\r
+             }\r
+             stack.pop();\r
+         }\r
+         check_equal(actual, expected, []);\r
+    }\r
+    expose(assert_object_equals, "assert_object_equals");\r
+\r
+    function assert_array_equals(actual, expected, description)\r
+    {\r
+        assert(actual.length === expected.length,\r
+               "assert_array_equals", description,\r
+               "lengths differ, expected ${expected} got ${actual}",\r
+               {expected:expected.length, actual:actual.length});\r
+\r
+        for (var i = 0; i < actual.length; i++) {\r
+            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),\r
+                   "assert_array_equals", description,\r
+                   "property ${i}, property expected to be ${expected} but was ${actual}",\r
+                   {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",\r
+                   actual:actual.hasOwnProperty(i) ? "present" : "missing"});\r
+            assert(same_value(expected[i], actual[i]),\r
+                   "assert_array_equals", description,\r
+                   "property ${i}, expected ${expected} but got ${actual}",\r
+                   {i:i, expected:expected[i], actual:actual[i]});\r
+        }\r
+    }\r
+    expose(assert_array_equals, "assert_array_equals");\r
+\r
+    function assert_approx_equals(actual, expected, epsilon, description)\r
+    {\r
+        /*\r
+         * Test if two primitive numbers are equal withing +/- epsilon\r
+         */\r
+        assert(typeof actual === "number",\r
+               "assert_approx_equals", description,\r
+               "expected a number but got a ${type_actual}",\r
+               {type_actual:typeof actual});\r
+\r
+        assert(Math.abs(actual - expected) <= epsilon,\r
+               "assert_approx_equals", description,\r
+               "expected ${expected} +/- ${epsilon} but got ${actual}",\r
+               {expected:expected, actual:actual, epsilon:epsilon});\r
+    }\r
+    expose(assert_approx_equals, "assert_approx_equals");\r
+\r
+    function assert_less_than(actual, expected, description)\r
+    {\r
+        /*\r
+         * Test if a primitive number is less than another\r
+         */\r
+        assert(typeof actual === "number",\r
+               "assert_less_than", description,\r
+               "expected a number but got a ${type_actual}",\r
+               {type_actual:typeof actual});\r
+\r
+        assert(actual < expected,\r
+               "assert_less_than", description,\r
+               "expected a number less than ${expected} but got ${actual}",\r
+               {expected:expected, actual:actual});\r
+    }\r
+    expose(assert_less_than, "assert_less_than");\r
+\r
+    function assert_greater_than(actual, expected, description)\r
+    {\r
+        /*\r
+         * Test if a primitive number is greater than another\r
+         */\r
+        assert(typeof actual === "number",\r
+               "assert_greater_than", description,\r
+               "expected a number but got a ${type_actual}",\r
+               {type_actual:typeof actual});\r
+\r
+        assert(actual > expected,\r
+               "assert_greater_than", description,\r
+               "expected a number greater than ${expected} but got ${actual}",\r
+               {expected:expected, actual:actual});\r
+    }\r
+    expose(assert_greater_than, "assert_greater_than");\r
+\r
+    function assert_less_than_equal(actual, expected, description)\r
+    {\r
+        /*\r
+         * Test if a primitive number is less than or equal to another\r
+         */\r
+        assert(typeof actual === "number",\r
+               "assert_less_than_equal", description,\r
+               "expected a number but got a ${type_actual}",\r
+               {type_actual:typeof actual});\r
+\r
+        assert(actual <= expected,\r
+               "assert_less_than", description,\r
+               "expected a number less than or equal to ${expected} but got ${actual}",\r
+               {expected:expected, actual:actual});\r
+    }\r
+    expose(assert_less_than_equal, "assert_less_than_equal");\r
+\r
+    function assert_greater_than_equal(actual, expected, description)\r
+    {\r
+        /*\r
+         * Test if a primitive number is greater than or equal to another\r
+         */\r
+        assert(typeof actual === "number",\r
+               "assert_greater_than_equal", description,\r
+               "expected a number but got a ${type_actual}",\r
+               {type_actual:typeof actual});\r
+\r
+        assert(actual >= expected,\r
+               "assert_greater_than_equal", description,\r
+               "expected a number greater than or equal to ${expected} but got ${actual}",\r
+               {expected:expected, actual:actual});\r
+    }\r
+    expose(assert_greater_than_equal, "assert_greater_than_equal");\r
+\r
+    function assert_regexp_match(actual, expected, description) {\r
+        /*\r
+         * Test if a string (actual) matches a regexp (expected)\r
+         */\r
+        assert(expected.test(actual),\r
+               "assert_regexp_match", description,\r
+               "expected ${expected} but got ${actual}",\r
+               {expected:expected, actual:actual});\r
+    }\r
+    expose(assert_regexp_match, "assert_regexp_match");\r
+\r
+    function assert_class_string(object, class_string, description) {\r
+        assert_equals({}.toString.call(object), "[object " + class_string + "]",\r
+                      description);\r
+    }\r
+    expose(assert_class_string, "assert_class_string");\r
+\r
+\r
+    function _assert_own_property(name) {\r
+        return function(object, property_name, description)\r
+        {\r
+            assert(property_name in object,\r
+                   name, description,\r
+                   "expected property ${p} missing", {p:property_name});\r
+        };\r
+    }\r
+    expose(_assert_own_property("assert_exists"), "assert_exists");\r
+    expose(_assert_own_property("assert_own_property"), "assert_own_property");\r
+\r
+    function assert_not_exists(object, property_name, description)\r
+    {\r
+        assert(!object.hasOwnProperty(property_name),\r
+               "assert_not_exists", description,\r
+               "unexpected property ${p} found", {p:property_name});\r
+    }\r
+    expose(assert_not_exists, "assert_not_exists");\r
+\r
+    function _assert_inherits(name) {\r
+        return function (object, property_name, description)\r
+        {\r
+            assert(typeof object === "object",\r
+                   name, description,\r
+                   "provided value is not an object");\r
+\r
+            assert("hasOwnProperty" in object,\r
+                   name, description,\r
+                   "provided value is an object but has no hasOwnProperty method");\r
+\r
+            assert(!object.hasOwnProperty(property_name),\r
+                   name, description,\r
+                   "property ${p} found on object expected in prototype chain",\r
+                   {p:property_name});\r
+\r
+            assert(property_name in object,\r
+                   name, description,\r
+                   "property ${p} not found in prototype chain",\r
+                   {p:property_name});\r
+        };\r
+    }\r
+    expose(_assert_inherits("assert_inherits"), "assert_inherits");\r
+    expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");\r
+\r
+    function assert_readonly(object, property_name, description)\r
+    {\r
+         var initial_value = object[property_name];\r
+         try {\r
+             //Note that this can have side effects in the case where\r
+             //the property has PutForwards\r
+             object[property_name] = initial_value + "a"; //XXX use some other value here?\r
+             assert(same_value(object[property_name], initial_value),\r
+                    "assert_readonly", description,\r
+                    "changing property ${p} succeeded",\r
+                    {p:property_name});\r
+         } finally {\r
+             object[property_name] = initial_value;\r
+         }\r
+    }\r
+    expose(assert_readonly, "assert_readonly");\r
+\r
+    function assert_throws(code, func, description)\r
+    {\r
+        try {\r
+            func.call(this);\r
+            assert(false, "assert_throws", description,\r
+                   "${func} did not throw", {func:func});\r
+        } catch (e) {\r
+            if (e instanceof AssertionError) {\r
+                throw e;\r
+            }\r
+            if (code === null) {\r
+                return;\r
+            }\r
+            if (typeof code === "object") {\r
+                assert(typeof e == "object" && "name" in e && e.name == code.name,\r
+                       "assert_throws", description,\r
+                       "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",\r
+                                    {func:func, actual:e, actual_name:e.name,\r
+                                     expected:code,\r
+                                     expected_name:code.name});\r
+                return;\r
+            }\r
+\r
+            var code_name_map = {\r
+                INDEX_SIZE_ERR: 'IndexSizeError',\r
+                HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',\r
+                WRONG_DOCUMENT_ERR: 'WrongDocumentError',\r
+                INVALID_CHARACTER_ERR: 'InvalidCharacterError',\r
+                NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',\r
+                NOT_FOUND_ERR: 'NotFoundError',\r
+                NOT_SUPPORTED_ERR: 'NotSupportedError',\r
+                INVALID_STATE_ERR: 'InvalidStateError',\r
+                SYNTAX_ERR: 'SyntaxError',\r
+                INVALID_MODIFICATION_ERR: 'InvalidModificationError',\r
+                NAMESPACE_ERR: 'NamespaceError',\r
+                INVALID_ACCESS_ERR: 'InvalidAccessError',\r
+                TYPE_MISMATCH_ERR: 'TypeMismatchError',\r
+                SECURITY_ERR: 'SecurityError',\r
+                NETWORK_ERR: 'NetworkError',\r
+                ABORT_ERR: 'AbortError',\r
+                URL_MISMATCH_ERR: 'URLMismatchError',\r
+                QUOTA_EXCEEDED_ERR: 'QuotaExceededError',\r
+                TIMEOUT_ERR: 'TimeoutError',\r
+                INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',\r
+                DATA_CLONE_ERR: 'DataCloneError'\r
+            };\r
+\r
+            var name = code in code_name_map ? code_name_map[code] : code;\r
+\r
+            var name_code_map = {\r
+                IndexSizeError: 1,\r
+                HierarchyRequestError: 3,\r
+                WrongDocumentError: 4,\r
+                InvalidCharacterError: 5,\r
+                NoModificationAllowedError: 7,\r
+                NotFoundError: 8,\r
+                NotSupportedError: 9,\r
+                InvalidStateError: 11,\r
+                SyntaxError: 12,\r
+                InvalidModificationError: 13,\r
+                NamespaceError: 14,\r
+                InvalidAccessError: 15,\r
+                TypeMismatchError: 17,\r
+                SecurityError: 18,\r
+                NetworkError: 19,\r
+                AbortError: 20,\r
+                URLMismatchError: 21,\r
+                QuotaExceededError: 22,\r
+                TimeoutError: 23,\r
+                InvalidNodeTypeError: 24,\r
+                DataCloneError: 25,\r
+\r
+                UnknownError: 0,\r
+                ConstraintError: 0,\r
+                DataError: 0,\r
+                TransactionInactiveError: 0,\r
+                ReadOnlyError: 0,\r
+                VersionError: 0\r
+            };\r
+\r
+            if (!(name in name_code_map)) {\r
+                throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');\r
+            }\r
+\r
+            var required_props = { code: name_code_map[name] };\r
+\r
+            if (required_props.code === 0 ||\r
+               ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException")) {\r
+                // New style exception: also test the name property.\r
+                required_props.name = name;\r
+            }\r
+\r
+            //We'd like to test that e instanceof the appropriate interface,\r
+            //but we can't, because we don't know what window it was created\r
+            //in.  It might be an instanceof the appropriate interface on some\r
+            //unknown other window.  TODO: Work around this somehow?\r
+\r
+            assert(typeof e == "object",\r
+                   "assert_throws", description,\r
+                   "${func} threw ${e} with type ${type}, not an object",\r
+                   {func:func, e:e, type:typeof e});\r
+\r
+            for (var prop in required_props) {\r
+                assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],\r
+                       "assert_throws", description,\r
+                       "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",\r
+                       {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});\r
+            }\r
+        }\r
+    }\r
+    expose(assert_throws, "assert_throws");\r
+\r
+    function assert_unreached(description) {\r
+         assert(false, "assert_unreached", description,\r
+                "Reached unreachable code");\r
+    }\r
+    expose(assert_unreached, "assert_unreached");\r
+\r
+    function assert_any(assert_func, actual, expected_array)\r
+    {\r
+        var args = [].slice.call(arguments, 3);\r
+        var errors = [];\r
+        var passed = false;\r
+        forEach(expected_array,\r
+                function(expected)\r
+                {\r
+                    try {\r
+                        assert_func.apply(this, [actual, expected].concat(args));\r
+                        passed = true;\r
+                    } catch (e) {\r
+                        errors.push(e.message);\r
+                    }\r
+                });\r
+        if (!passed) {\r
+            throw new AssertionError(errors.join("\n\n"));\r
+        }\r
+    }\r
+    expose(assert_any, "assert_any");\r
+\r
+    function Test(name, properties)\r
+    {\r
+        if (tests.file_is_test && tests.tests.length) {\r
+            throw new Error("Tried to create a test with file_is_test");\r
+        }\r
+        this.name = name;\r
+\r
+        this.phase = this.phases.INITIAL;\r
+\r
+        this.status = this.NOTRUN;\r
+        this.timeout_id = null;\r
+        this.index = null;\r
+\r
+        this.properties = properties;\r
+        var timeout = properties.timeout ? properties.timeout : settings.test_timeout;\r
+        if (timeout !== null) {\r
+            this.timeout_length = timeout * tests.timeout_multiplier;\r
+        } else {\r
+            this.timeout_length = null;\r
+        }\r
+\r
+        this.message = null;\r
+\r
+        this.steps = [];\r
+\r
+        this.cleanup_callbacks = [];\r
+\r
+        tests.push(this);\r
+    }\r
+\r
+    Test.statuses = {\r
+        PASS:0,\r
+        FAIL:1,\r
+        TIMEOUT:2,\r
+        NOTRUN:3\r
+    };\r
+\r
+    Test.prototype = merge({}, Test.statuses);\r
+\r
+    Test.prototype.phases = {\r
+        INITIAL:0,\r
+        STARTED:1,\r
+        HAS_RESULT:2,\r
+        COMPLETE:3\r
+    };\r
+\r
+    Test.prototype.structured_clone = function()\r
+    {\r
+        if (!this._structured_clone) {\r
+            var msg = this.message;\r
+            msg = msg ? String(msg) : msg;\r
+            this._structured_clone = merge({\r
+                name:String(this.name),\r
+                properties:merge({}, this.properties),\r
+            }, Test.statuses);\r
+        }\r
+        this._structured_clone.status = this.status;\r
+        this._structured_clone.message = this.message;\r
+        this._structured_clone.index = this.index;\r
+        return this._structured_clone;\r
+    };\r
+\r
+    Test.prototype.step = function(func, this_obj)\r
+    {\r
+        if (this.phase > this.phases.STARTED) {\r
+            return;\r
+        }\r
+        this.phase = this.phases.STARTED;\r
+        //If we don't get a result before the harness times out that will be a test timout\r
+        this.set_status(this.TIMEOUT, "Test timed out");\r
+\r
+        tests.started = true;\r
+        tests.notify_test_state(this);\r
+\r
+        if (this.timeout_id === null) {\r
+            this.set_timeout();\r
+        }\r
+\r
+        this.steps.push(func);\r
+\r
+        if (arguments.length === 1) {\r
+            this_obj = this;\r
+        }\r
+\r
+        try {\r
+            return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));\r
+        } catch (e) {\r
+            if (this.phase >= this.phases.HAS_RESULT) {\r
+                return;\r
+            }\r
+            var message = (typeof e === "object" && e !== null) ? e.message : e;\r
+            if (typeof e.stack != "undefined" && typeof e.message == "string") {\r
+                //Try to make it more informative for some exceptions, at least\r
+                //in Gecko and WebKit.  This results in a stack dump instead of\r
+                //just errors like "Cannot read property 'parentNode' of null"\r
+                //or "root is null".  Makes it a lot longer, of course.\r
+                message += "(stack: " + e.stack + ")";\r
+            }\r
+            this.set_status(this.FAIL, message);\r
+            this.phase = this.phases.HAS_RESULT;\r
+            this.done();\r
+        }\r
+    };\r
+\r
+    Test.prototype.step_func = function(func, this_obj)\r
+    {\r
+        var test_this = this;\r
+\r
+        if (arguments.length === 1) {\r
+            this_obj = test_this;\r
+        }\r
+\r
+        return function()\r
+        {\r
+            return test_this.step.apply(test_this, [func, this_obj].concat(\r
+                Array.prototype.slice.call(arguments)));\r
+        };\r
+    };\r
+\r
+    Test.prototype.step_func_done = function(func, this_obj)\r
+    {\r
+        var test_this = this;\r
+\r
+        if (arguments.length === 1) {\r
+            this_obj = test_this;\r
+        }\r
+\r
+        return function()\r
+        {\r
+            if (func) {\r
+                test_this.step.apply(test_this, [func, this_obj].concat(\r
+                    Array.prototype.slice.call(arguments)));\r
+            }\r
+            test_this.done();\r
+        };\r
+    };\r
+\r
+    Test.prototype.unreached_func = function(description)\r
+    {\r
+        return this.step_func(function() {\r
+            assert_unreached(description);\r
+        });\r
+    };\r
+\r
+    Test.prototype.add_cleanup = function(callback) {\r
+        this.cleanup_callbacks.push(callback);\r
+    };\r
+\r
+    Test.prototype.force_timeout = function() {\r
+        this.set_status(this.TIMEOUT);\r
+        this.phase = this.phases.HAS_RESULT;\r
+    };\r
+\r
+    Test.prototype.set_timeout = function()\r
+    {\r
+        if (this.timeout_length !== null) {\r
+            var this_obj = this;\r
+            this.timeout_id = setTimeout(function()\r
+                                         {\r
+                                             this_obj.timeout();\r
+                                         }, this.timeout_length);\r
+        }\r
+    };\r
+\r
+    Test.prototype.set_status = function(status, message)\r
+    {\r
+        this.status = status;\r
+        this.message = message;\r
+    };\r
+\r
+    Test.prototype.timeout = function()\r
+    {\r
+        this.timeout_id = null;\r
+        this.set_status(this.TIMEOUT, "Test timed out");\r
+        this.phase = this.phases.HAS_RESULT;\r
+        this.done();\r
+    };\r
+\r
+    Test.prototype.done = function()\r
+    {\r
+        if (this.phase == this.phases.COMPLETE) {\r
+            return;\r
+        }\r
+\r
+        if (this.phase <= this.phases.STARTED) {\r
+            this.set_status(this.PASS, null);\r
+        }\r
+\r
+        this.phase = this.phases.COMPLETE;\r
+\r
+        clearTimeout(this.timeout_id);\r
+        tests.result(this);\r
+        this.cleanup();\r
+    };\r
+\r
+    Test.prototype.cleanup = function() {\r
+        forEach(this.cleanup_callbacks,\r
+                function(cleanup_callback) {\r
+                    cleanup_callback();\r
+                });\r
+    };\r
+\r
+    /*\r
+     * A RemoteTest object mirrors a Test object on a remote worker. The\r
+     * associated RemoteWorker updates the RemoteTest object in response to\r
+     * received events. In turn, the RemoteTest object replicates these events\r
+     * on the local document. This allows listeners (test result reporting\r
+     * etc..) to transparently handle local and remote events.\r
+     */\r
+    function RemoteTest(clone) {\r
+        var this_obj = this;\r
+        Object.keys(clone).forEach(\r
+                function(key) {\r
+                    this_obj[key] = clone[key];\r
+                });\r
+        this.index = null;\r
+        this.phase = this.phases.INITIAL;\r
+        this.update_state_from(clone);\r
+        tests.push(this);\r
+    }\r
+\r
+    RemoteTest.prototype.structured_clone = function() {\r
+        var clone = {};\r
+        Object.keys(this).forEach(\r
+                function(key) {\r
+                    if (typeof(this[key]) === "object") {\r
+                        clone[key] = merge({}, this[key]);\r
+                    } else {\r
+                        clone[key] = this[key];\r
+                    }\r
+                });\r
+        clone.phases = merge({}, this.phases);\r
+        return clone;\r
+    };\r
+\r
+    RemoteTest.prototype.cleanup = function() {};\r
+    RemoteTest.prototype.phases = Test.prototype.phases;\r
+    RemoteTest.prototype.update_state_from = function(clone) {\r
+        this.status = clone.status;\r
+        this.message = clone.message;\r
+        if (this.phase === this.phases.INITIAL) {\r
+            this.phase = this.phases.STARTED;\r
+        }\r
+    };\r
+    RemoteTest.prototype.done = function() {\r
+        this.phase = this.phases.COMPLETE;\r
+    }\r
+\r
+    /*\r
+     * A RemoteWorker listens for test events from a worker. These events are\r
+     * then used to construct and maintain RemoteTest objects that mirror the\r
+     * tests running on the remote worker.\r
+     */\r
+    function RemoteWorker(worker) {\r
+        this.running = true;\r
+        this.tests = new Array();\r
+\r
+        var this_obj = this;\r
+        worker.onerror = function(error) { this_obj.worker_error(error); };\r
+\r
+        var message_port;\r
+\r
+        if (is_service_worker(worker)) {\r
+            // The ServiceWorker's implicit MessagePort is currently not\r
+            // reliably accessible from the ServiceWorkerGlobalScope due to\r
+            // Blink setting MessageEvent.source to null for messages sent via\r
+            // ServiceWorker.postMessage(). Until that's resolved, create an\r
+            // explicit MessageChannel and pass one end to the worker.\r
+            var message_channel = new MessageChannel();\r
+            message_port = message_channel.port1;\r
+            message_port.start();\r
+            worker.postMessage({type: "connect"}, [message_channel.port2]);\r
+        } else if (is_shared_worker(worker)) {\r
+            message_port = worker.port;\r
+        } else {\r
+            message_port = worker;\r
+        }\r
+\r
+        // Keeping a reference to the worker until worker_done() is seen\r
+        // prevents the Worker object and its MessageChannel from going away\r
+        // before all the messages are dispatched.\r
+        this.worker = worker;\r
+\r
+        message_port.onmessage =\r
+            function(message) {\r
+                if (this_obj.running && (message.data.type in this_obj.message_handlers)) {\r
+                    this_obj.message_handlers[message.data.type].call(this_obj, message.data);\r
+                }\r
+            };\r
+    }\r
+\r
+    RemoteWorker.prototype.worker_error = function(error) {\r
+        var message = error.message || String(error);\r
+        var filename = (error.filename ? " " + error.filename: "");\r
+        // FIXME: Display worker error states separately from main document\r
+        // error state.\r
+        this.worker_done({\r
+            status: {\r
+                status: tests.status.ERROR,\r
+                message: "Error in worker" + filename + ": " + message\r
+            }\r
+        });\r
+        error.preventDefault();\r
+    };\r
+\r
+    RemoteWorker.prototype.test_state = function(data) {\r
+        var remote_test = this.tests[data.test.index];\r
+        if (!remote_test) {\r
+            remote_test = new RemoteTest(data.test);\r
+            this.tests[data.test.index] = remote_test;\r
+        }\r
+        remote_test.update_state_from(data.test);\r
+        tests.notify_test_state(remote_test);\r
+    };\r
+\r
+    RemoteWorker.prototype.test_done = function(data) {\r
+        var remote_test = this.tests[data.test.index];\r
+        remote_test.update_state_from(data.test);\r
+        remote_test.done();\r
+        tests.result(remote_test);\r
+    };\r
+\r
+    RemoteWorker.prototype.worker_done = function(data) {\r
+        if (tests.status.status === null &&\r
+            data.status.status !== data.status.OK) {\r
+            tests.status.status = data.status.status;\r
+            tests.status.message = data.status.message;\r
+        }\r
+        this.running = false;\r
+        this.worker = null;\r
+        if (tests.all_done()) {\r
+            tests.complete();\r
+        }\r
+    };\r
+\r
+    RemoteWorker.prototype.message_handlers = {\r
+        test_state: RemoteWorker.prototype.test_state,\r
+        result: RemoteWorker.prototype.test_done,\r
+        complete: RemoteWorker.prototype.worker_done\r
+    };\r
+\r
+    /*\r
+     * Harness\r
+     */\r
+\r
+    function TestsStatus()\r
+    {\r
+        this.status = null;\r
+        this.message = null;\r
+    }\r
+\r
+    TestsStatus.statuses = {\r
+        OK:0,\r
+        ERROR:1,\r
+        TIMEOUT:2\r
+    };\r
+\r
+    TestsStatus.prototype = merge({}, TestsStatus.statuses);\r
+\r
+    TestsStatus.prototype.structured_clone = function()\r
+    {\r
+        if (!this._structured_clone) {\r
+            var msg = this.message;\r
+            msg = msg ? String(msg) : msg;\r
+            this._structured_clone = merge({\r
+                status:this.status,\r
+                message:msg\r
+            }, TestsStatus.statuses);\r
+        }\r
+        return this._structured_clone;\r
+    };\r
+\r
+    function Tests()\r
+    {\r
+        this.tests = [];\r
+        this.num_pending = 0;\r
+\r
+        this.phases = {\r
+            INITIAL:0,\r
+            SETUP:1,\r
+            HAVE_TESTS:2,\r
+            HAVE_RESULTS:3,\r
+            COMPLETE:4\r
+        };\r
+        this.phase = this.phases.INITIAL;\r
+\r
+        this.properties = {};\r
+\r
+        this.wait_for_finish = false;\r
+        this.processing_callbacks = false;\r
+\r
+        this.allow_uncaught_exception = false;\r
+\r
+        this.file_is_test = false;\r
+\r
+        this.timeout_multiplier = 1;\r
+        this.timeout_length = test_environment.test_timeout();\r
+        this.timeout_id = null;\r
+\r
+        this.start_callbacks = [];\r
+        this.test_state_callbacks = [];\r
+        this.test_done_callbacks = [];\r
+        this.all_done_callbacks = [];\r
+\r
+        this.pending_workers = [];\r
+\r
+        this.status = new TestsStatus();\r
+\r
+        var this_obj = this;\r
+\r
+        test_environment.add_on_loaded_callback(function() {\r
+            if (this_obj.all_done()) {\r
+                this_obj.complete();\r
+            }\r
+        });\r
+\r
+        this.set_timeout();\r
+    }\r
+\r
+    Tests.prototype.setup = function(func, properties)\r
+    {\r
+        if (this.phase >= this.phases.HAVE_RESULTS) {\r
+            return;\r
+        }\r
+\r
+        if (this.phase < this.phases.SETUP) {\r
+            this.phase = this.phases.SETUP;\r
+        }\r
+\r
+        this.properties = properties;\r
+\r
+        for (var p in properties) {\r
+            if (properties.hasOwnProperty(p)) {\r
+                var value = properties[p];\r
+                if (p == "allow_uncaught_exception") {\r
+                    this.allow_uncaught_exception = value;\r
+                } else if (p == "explicit_done" && value) {\r
+                    this.wait_for_finish = true;\r
+                } else if (p == "explicit_timeout" && value) {\r
+                    this.timeout_length = null;\r
+                    if (this.timeout_id)\r
+                    {\r
+                        clearTimeout(this.timeout_id);\r
+                    }\r
+                } else if (p == "timeout_multiplier") {\r
+                    this.timeout_multiplier = value;\r
+                }\r
+            }\r
+        }\r
+\r
+        if (func) {\r
+            try {\r
+                func();\r
+            } catch (e) {\r
+                this.status.status = this.status.ERROR;\r
+                this.status.message = String(e);\r
+            }\r
+        }\r
+        this.set_timeout();\r
+    };\r
+\r
+    Tests.prototype.set_file_is_test = function() {\r
+        if (this.tests.length > 0) {\r
+            throw new Error("Tried to set file as test after creating a test");\r
+        }\r
+        this.wait_for_finish = true;\r
+        this.file_is_test = true;\r
+        // Create the test, which will add it to the list of tests\r
+        async_test();\r
+    };\r
+\r
+    Tests.prototype.set_timeout = function() {\r
+        var this_obj = this;\r
+        clearTimeout(this.timeout_id);\r
+        if (this.timeout_length !== null) {\r
+            this.timeout_id = setTimeout(function() {\r
+                                             this_obj.timeout();\r
+                                         }, this.timeout_length);\r
+        }\r
+    };\r
+\r
+    Tests.prototype.timeout = function() {\r
+        if (this.status.status === null) {\r
+            this.status.status = this.status.TIMEOUT;\r
+        }\r
+        this.complete();\r
+    };\r
+\r
+    Tests.prototype.end_wait = function()\r
+    {\r
+        this.wait_for_finish = false;\r
+        if (this.all_done()) {\r
+            this.complete();\r
+        }\r
+    };\r
+\r
+    Tests.prototype.push = function(test)\r
+    {\r
+        if (this.phase < this.phases.HAVE_TESTS) {\r
+            this.start();\r
+        }\r
+        this.num_pending++;\r
+        test.index = this.tests.push(test);\r
+        this.notify_test_state(test);\r
+    };\r
+\r
+    Tests.prototype.notify_test_state = function(test) {\r
+        var this_obj = this;\r
+        forEach(this.test_state_callbacks,\r
+                function(callback) {\r
+                    callback(test, this_obj);\r
+                });\r
+    };\r
+\r
+    Tests.prototype.all_done = function() {\r
+        return (this.tests.length > 0 && test_environment.all_loaded &&\r
+                this.num_pending === 0 && !this.wait_for_finish &&\r
+                !this.processing_callbacks &&\r
+                !this.pending_workers.some(function(w) { return w.running; }));\r
+    };\r
+\r
+    Tests.prototype.start = function() {\r
+        this.phase = this.phases.HAVE_TESTS;\r
+        this.notify_start();\r
+    };\r
+\r
+    Tests.prototype.notify_start = function() {\r
+        var this_obj = this;\r
+        forEach (this.start_callbacks,\r
+                 function(callback)\r
+                 {\r
+                     callback(this_obj.properties);\r
+                 });\r
+    };\r
+\r
+    Tests.prototype.result = function(test)\r
+    {\r
+        if (this.phase > this.phases.HAVE_RESULTS) {\r
+            return;\r
+        }\r
+        this.phase = this.phases.HAVE_RESULTS;\r
+        this.num_pending--;\r
+        this.notify_result(test);\r
+    };\r
+\r
+    Tests.prototype.notify_result = function(test) {\r
+        var this_obj = this;\r
+        this.processing_callbacks = true;\r
+        forEach(this.test_done_callbacks,\r
+                function(callback)\r
+                {\r
+                    callback(test, this_obj);\r
+                });\r
+        this.processing_callbacks = false;\r
+        if (this_obj.all_done()) {\r
+            this_obj.complete();\r
+        }\r
+    };\r
+\r
+    Tests.prototype.complete = function() {\r
+        if (this.phase === this.phases.COMPLETE) {\r
+            return;\r
+        }\r
+        this.phase = this.phases.COMPLETE;\r
+        var this_obj = this;\r
+        this.tests.forEach(\r
+            function(x)\r
+            {\r
+                if (x.phase < x.phases.COMPLETE) {\r
+                    this_obj.notify_result(x);\r
+                    x.cleanup();\r
+                    x.phase = x.phases.COMPLETE;\r
+                }\r
+            }\r
+        );\r
+        this.notify_complete();\r
+    };\r
+\r
+    Tests.prototype.notify_complete = function() {\r
+        var this_obj = this;\r
+        if (this.status.status === null) {\r
+            this.status.status = this.status.OK;\r
+        }\r
+\r
+        forEach (this.all_done_callbacks,\r
+                 function(callback)\r
+                 {\r
+                     callback(this_obj.tests, this_obj.status);\r
+                 });\r
+    };\r
+\r
+    Tests.prototype.fetch_tests_from_worker = function(worker) {\r
+        if (this.phase >= this.phases.COMPLETE) {\r
+            return;\r
+        }\r
+\r
+        this.pending_workers.push(new RemoteWorker(worker));\r
+    };\r
+\r
+    function fetch_tests_from_worker(port) {\r
+        tests.fetch_tests_from_worker(port);\r
+    }\r
+    expose(fetch_tests_from_worker, 'fetch_tests_from_worker');\r
+\r
+    function timeout() {\r
+        if (tests.timeout_length === null) {\r
+            tests.timeout();\r
+        }\r
+    }\r
+    expose(timeout, 'timeout');\r
+\r
+    function add_start_callback(callback) {\r
+        tests.start_callbacks.push(callback);\r
+    }\r
+\r
+    function add_test_state_callback(callback) {\r
+        tests.test_state_callbacks.push(callback);\r
+    }\r
+\r
+    function add_result_callback(callback)\r
+    {\r
+        tests.test_done_callbacks.push(callback);\r
+    }\r
+\r
+    function add_completion_callback(callback)\r
+    {\r
+       tests.all_done_callbacks.push(callback);\r
+    }\r
+\r
+    expose(add_start_callback, 'add_start_callback');\r
+    expose(add_test_state_callback, 'add_test_state_callback');\r
+    expose(add_result_callback, 'add_result_callback');\r
+    expose(add_completion_callback, 'add_completion_callback');\r
+\r
+    /*\r
+     * Output listener\r
+    */\r
+\r
+    function Output() {\r
+        this.output_document = document;\r
+        this.output_node = null;\r
+        this.enabled = settings.output;\r
+        this.phase = this.INITIAL;\r
+    }\r
+\r
+    Output.prototype.INITIAL = 0;\r
+    Output.prototype.STARTED = 1;\r
+    Output.prototype.HAVE_RESULTS = 2;\r
+    Output.prototype.COMPLETE = 3;\r
+\r
+    Output.prototype.setup = function(properties) {\r
+        if (this.phase > this.INITIAL) {\r
+            return;\r
+        }\r
+\r
+        //If output is disabled in testharnessreport.js the test shouldn't be\r
+        //able to override that\r
+        this.enabled = this.enabled && (properties.hasOwnProperty("output") ?\r
+                                        properties.output : settings.output);\r
+    };\r
+\r
+    Output.prototype.init = function(properties) {\r
+        if (this.phase >= this.STARTED) {\r
+            return;\r
+        }\r
+        if (properties.output_document) {\r
+            this.output_document = properties.output_document;\r
+        } else {\r
+            this.output_document = document;\r
+        }\r
+        this.phase = this.STARTED;\r
+    };\r
+\r
+    Output.prototype.resolve_log = function() {\r
+        var output_document;\r
+        if (typeof this.output_document === "function") {\r
+            output_document = this.output_document.apply(undefined);\r
+        } else {\r
+            output_document = this.output_document;\r
+        }\r
+        if (!output_document) {\r
+            return;\r
+        }\r
+        var node = output_document.getElementById("log");\r
+        if (!node) {\r
+            if (!document.body || document.readyState == "loading") {\r
+                return;\r
+            }\r
+            node = output_document.createElement("div");\r
+            node.id = "log";\r
+            output_document.body.appendChild(node);\r
+        }\r
+        this.output_document = output_document;\r
+        this.output_node = node;\r
+    };\r
+\r
+    Output.prototype.show_status = function() {\r
+        if (this.phase < this.STARTED) {\r
+            this.init();\r
+        }\r
+        if (!this.enabled) {\r
+            return;\r
+        }\r
+        if (this.phase < this.HAVE_RESULTS) {\r
+            this.resolve_log();\r
+            this.phase = this.HAVE_RESULTS;\r
+        }\r
+        var done_count = tests.tests.length - tests.num_pending;\r
+        if (this.output_node) {\r
+            if (done_count < 100 ||\r
+                (done_count < 1000 && done_count % 100 === 0) ||\r
+                done_count % 1000 === 0) {\r
+                this.output_node.textContent = "Running, " +\r
+                    done_count + " complete, " +\r
+                    tests.num_pending + " remain";\r
+            }\r
+        }\r
+    };\r
+\r
+    Output.prototype.show_results = function (tests, harness_status) {\r
+        if (this.phase >= this.COMPLETE) {\r
+            return;\r
+        }\r
+        if (!this.enabled) {\r
+            return;\r
+        }\r
+        if (!this.output_node) {\r
+            this.resolve_log();\r
+        }\r
+        this.phase = this.COMPLETE;\r
+\r
+        var log = this.output_node;\r
+        if (!log) {\r
+            return;\r
+        }\r
+        var output_document = this.output_document;\r
+\r
+        while (log.lastChild) {\r
+            log.removeChild(log.lastChild);\r
+        }\r
+\r
+        var script_prefix = null;\r
+        var scripts = document.getElementsByTagName("script");\r
+        for (var i = 0; i < scripts.length; i++) {\r
+            var src;\r
+            if (scripts[i].src) {\r
+                src = scripts[i].src;\r
+            } else if (scripts[i].href) {\r
+                //SVG case\r
+                src = scripts[i].href.baseVal;\r
+            }\r
+\r
+            var matches = src && src.match(/^(.*\/|)testharness\.js$/);\r
+            if (matches) {\r
+                script_prefix = matches[1];\r
+                break;\r
+            }\r
+        }\r
+\r
+        if (script_prefix !== null) {\r
+            var stylesheet = output_document.createElementNS(xhtml_ns, "link");\r
+            stylesheet.setAttribute("rel", "stylesheet");\r
+            stylesheet.setAttribute("href", script_prefix + "testharness.css");\r
+            var heads = output_document.getElementsByTagName("head");\r
+            if (heads.length) {\r
+                heads[0].appendChild(stylesheet);\r
+            }\r
+        }\r
+\r
+        var status_text_harness = {};\r
+        status_text_harness[harness_status.OK] = "OK";\r
+        status_text_harness[harness_status.ERROR] = "Error";\r
+        status_text_harness[harness_status.TIMEOUT] = "Timeout";\r
+\r
+        var status_text = {};\r
+        status_text[Test.prototype.PASS] = "Pass";\r
+        status_text[Test.prototype.FAIL] = "Fail";\r
+        status_text[Test.prototype.TIMEOUT] = "Timeout";\r
+        status_text[Test.prototype.NOTRUN] = "Not Run";\r
+\r
+        var status_number = {};\r
+        forEach(tests,\r
+                function(test) {\r
+                    var status = status_text[test.status];\r
+                    if (status_number.hasOwnProperty(status)) {\r
+                        status_number[status] += 1;\r
+                    } else {\r
+                        status_number[status] = 1;\r
+                    }\r
+                });\r
+\r
+        function status_class(status)\r
+        {\r
+            return status.replace(/\s/g, '').toLowerCase();\r
+        }\r
+\r
+        var summary_template = ["section", {"id":"summary"},\r
+                                ["h2", {}, "Summary"],\r
+                                function()\r
+                                {\r
+\r
+                                    var status = status_text_harness[harness_status.status];\r
+                                    var rv = [["section", {},\r
+                                               ["p", {},\r
+                                                "Harness status: ",\r
+                                                ["span", {"class":status_class(status)},\r
+                                                 status\r
+                                                ],\r
+                                               ]\r
+                                              ]];\r
+\r
+                                    if (harness_status.status === harness_status.ERROR) {\r
+                                        rv[0].push(["pre", {}, harness_status.message]);\r
+                                    }\r
+                                    return rv;\r
+                                },\r
+                                ["p", {}, "Found ${num_tests} tests"],\r
+                                function() {\r
+                                    var rv = [["div", {}]];\r
+                                    var i = 0;\r
+                                    while (status_text.hasOwnProperty(i)) {\r
+                                        if (status_number.hasOwnProperty(status_text[i])) {\r
+                                            var status = status_text[i];\r
+                                            rv[0].push(["div", {"class":status_class(status)},\r
+                                                        ["label", {},\r
+                                                         ["input", {type:"checkbox", checked:"checked"}],\r
+                                                         status_number[status] + " " + status]]);\r
+                                        }\r
+                                        i++;\r
+                                    }\r
+                                    return rv;\r
+                                },\r
+                               ];\r
+\r
+        log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));\r
+\r
+        forEach(output_document.querySelectorAll("section#summary label"),\r
+                function(element)\r
+                {\r
+                    on_event(element, "click",\r
+                             function(e)\r
+                             {\r
+                                 if (output_document.getElementById("results") === null) {\r
+                                     e.preventDefault();\r
+                                     return;\r
+                                 }\r
+                                 var result_class = element.parentNode.getAttribute("class");\r
+                                 var style_element = output_document.querySelector("style#hide-" + result_class);\r
+                                 var input_element = element.querySelector("input");\r
+                                 if (!style_element && !input_element.checked) {\r
+                                     style_element = output_document.createElementNS(xhtml_ns, "style");\r
+                                     style_element.id = "hide-" + result_class;\r
+                                     style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";\r
+                                     output_document.body.appendChild(style_element);\r
+                                 } else if (style_element && input_element.checked) {\r
+                                     style_element.parentNode.removeChild(style_element);\r
+                                 }\r
+                             });\r
+                });\r
+\r
+        // This use of innerHTML plus manual escaping is not recommended in\r
+        // general, but is necessary here for performance.  Using textContent\r
+        // on each individual <td> adds tens of seconds of execution time for\r
+        // large test suites (tens of thousands of tests).\r
+        function escape_html(s)\r
+        {\r
+            return s.replace(/\&/g, "&amp;")\r
+                .replace(/</g, "&lt;")\r
+                .replace(/"/g, "&quot;")\r
+                .replace(/'/g, "&#39;");\r
+        }\r
+\r
+        function has_assertions()\r
+        {\r
+            for (var i = 0; i < tests.length; i++) {\r
+                if (tests[i].properties.hasOwnProperty("assert")) {\r
+                    return true;\r
+                }\r
+            }\r
+            return false;\r
+        }\r
+\r
+        function get_assertion(test)\r
+        {\r
+            if (test.properties.hasOwnProperty("assert")) {\r
+                if (Array.isArray(test.properties.assert)) {\r
+                    return test.properties.assert.join(' ');\r
+                }\r
+                return test.properties.assert;\r
+            }\r
+            return '';\r
+        }\r
+\r
+        log.appendChild(document.createElementNS(xhtml_ns, "section"));\r
+        var assertions = has_assertions();\r
+        var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">" +\r
+            "<thead><tr><th>Result</th><th>Test Name</th>" +\r
+            (assertions ? "<th>Assertion</th>" : "") +\r
+            "<th>Message</th></tr></thead>" +\r
+            "<tbody>";\r
+        for (var i = 0; i < tests.length; i++) {\r
+            html += '<tr class="' +\r
+                escape_html(status_class(status_text[tests[i].status])) +\r
+                '"><td>' +\r
+                escape_html(status_text[tests[i].status]) +\r
+                "</td><td>" +\r
+                escape_html(tests[i].name) +\r
+                "</td><td>" +\r
+                (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") +\r
+                escape_html(tests[i].message ? tests[i].message : " ") +\r
+                "</td></tr>";\r
+        }\r
+        html += "</tbody></table>";\r
+        try {\r
+            log.lastChild.innerHTML = html;\r
+        } catch (e) {\r
+            log.appendChild(document.createElementNS(xhtml_ns, "p"))\r
+               .textContent = "Setting innerHTML for the log threw an exception.";\r
+            log.appendChild(document.createElementNS(xhtml_ns, "pre"))\r
+               .textContent = html;\r
+        }\r
+    };\r
+\r
+    /*\r
+     * Template code\r
+     *\r
+     * A template is just a javascript structure. An element is represented as:\r
+     *\r
+     * [tag_name, {attr_name:attr_value}, child1, child2]\r
+     *\r
+     * the children can either be strings (which act like text nodes), other templates or\r
+     * functions (see below)\r
+     *\r
+     * A text node is represented as\r
+     *\r
+     * ["{text}", value]\r
+     *\r
+     * String values have a simple substitution syntax; ${foo} represents a variable foo.\r
+     *\r
+     * It is possible to embed logic in templates by using a function in a place where a\r
+     * node would usually go. The function must either return part of a template or null.\r
+     *\r
+     * In cases where a set of nodes are required as output rather than a single node\r
+     * with children it is possible to just use a list\r
+     * [node1, node2, node3]\r
+     *\r
+     * Usage:\r
+     *\r
+     * render(template, substitutions) - take a template and an object mapping\r
+     * variable names to parameters and return either a DOM node or a list of DOM nodes\r
+     *\r
+     * substitute(template, substitutions) - take a template and variable mapping object,\r
+     * make the variable substitutions and return the substituted template\r
+     *\r
+     */\r
+\r
+    function is_single_node(template)\r
+    {\r
+        return typeof template[0] === "string";\r
+    }\r
+\r
+    function substitute(template, substitutions)\r
+    {\r
+        if (typeof template === "function") {\r
+            var replacement = template(substitutions);\r
+            if (!replacement) {\r
+                return null;\r
+            }\r
+\r
+            return substitute(replacement, substitutions);\r
+        }\r
+\r
+        if (is_single_node(template)) {\r
+            return substitute_single(template, substitutions);\r
+        }\r
+\r
+        return filter(map(template, function(x) {\r
+                              return substitute(x, substitutions);\r
+                          }), function(x) {return x !== null;});\r
+    }\r
+\r
+    function substitute_single(template, substitutions)\r
+    {\r
+        var substitution_re = /\$\{([^ }]*)\}/g;\r
+\r
+        function do_substitution(input) {\r
+            var components = input.split(substitution_re);\r
+            var rv = [];\r
+            for (var i = 0; i < components.length; i += 2) {\r
+                rv.push(components[i]);\r
+                if (components[i + 1]) {\r
+                    rv.push(String(substitutions[components[i + 1]]));\r
+                }\r
+            }\r
+            return rv;\r
+        }\r
+\r
+        function substitute_attrs(attrs, rv)\r
+        {\r
+            rv[1] = {};\r
+            for (var name in template[1]) {\r
+                if (attrs.hasOwnProperty(name)) {\r
+                    var new_name = do_substitution(name).join("");\r
+                    var new_value = do_substitution(attrs[name]).join("");\r
+                    rv[1][new_name] = new_value;\r
+                }\r
+            }\r
+        }\r
+\r
+        function substitute_children(children, rv)\r
+        {\r
+            for (var i = 0; i < children.length; i++) {\r
+                if (children[i] instanceof Object) {\r
+                    var replacement = substitute(children[i], substitutions);\r
+                    if (replacement !== null) {\r
+                        if (is_single_node(replacement)) {\r
+                            rv.push(replacement);\r
+                        } else {\r
+                            extend(rv, replacement);\r
+                        }\r
+                    }\r
+                } else {\r
+                    extend(rv, do_substitution(String(children[i])));\r
+                }\r
+            }\r
+            return rv;\r
+        }\r
+\r
+        var rv = [];\r
+        rv.push(do_substitution(String(template[0])).join(""));\r
+\r
+        if (template[0] === "{text}") {\r
+            substitute_children(template.slice(1), rv);\r
+        } else {\r
+            substitute_attrs(template[1], rv);\r
+            substitute_children(template.slice(2), rv);\r
+        }\r
+\r
+        return rv;\r
+    }\r
+\r
+    function make_dom_single(template, doc)\r
+    {\r
+        var output_document = doc || document;\r
+        var element;\r
+        if (template[0] === "{text}") {\r
+            element = output_document.createTextNode("");\r
+            for (var i = 1; i < template.length; i++) {\r
+                element.data += template[i];\r
+            }\r
+        } else {\r
+            element = output_document.createElementNS(xhtml_ns, template[0]);\r
+            for (var name in template[1]) {\r
+                if (template[1].hasOwnProperty(name)) {\r
+                    element.setAttribute(name, template[1][name]);\r
+                }\r
+            }\r
+            for (var i = 2; i < template.length; i++) {\r
+                if (template[i] instanceof Object) {\r
+                    var sub_element = make_dom(template[i]);\r
+                    element.appendChild(sub_element);\r
+                } else {\r
+                    var text_node = output_document.createTextNode(template[i]);\r
+                    element.appendChild(text_node);\r
+                }\r
+            }\r
+        }\r
+\r
+        return element;\r
+    }\r
+\r
+    function make_dom(template, substitutions, output_document)\r
+    {\r
+        if (is_single_node(template)) {\r
+            return make_dom_single(template, output_document);\r
+        }\r
+\r
+        return map(template, function(x) {\r
+                       return make_dom_single(x, output_document);\r
+                   });\r
+    }\r
+\r
+    function render(template, substitutions, output_document)\r
+    {\r
+        return make_dom(substitute(template, substitutions), output_document);\r
+    }\r
+\r
+    /*\r
+     * Utility funcions\r
+     */\r
+    function assert(expected_true, function_name, description, error, substitutions)\r
+    {\r
+        if (tests.tests.length === 0) {\r
+            tests.set_file_is_test();\r
+        }\r
+        if (expected_true !== true) {\r
+            var msg = make_message(function_name, description,\r
+                                   error, substitutions);\r
+            throw new AssertionError(msg);\r
+        }\r
+    }\r
+\r
+    function AssertionError(message)\r
+    {\r
+        this.message = message;\r
+    }\r
+\r
+    AssertionError.prototype.toString = function() {\r
+        return this.message;\r
+    };\r
+\r
+    function make_message(function_name, description, error, substitutions)\r
+    {\r
+        for (var p in substitutions) {\r
+            if (substitutions.hasOwnProperty(p)) {\r
+                substitutions[p] = format_value(substitutions[p]);\r
+            }\r
+        }\r
+        var node_form = substitute(["{text}", "${function_name}: ${description}" + error],\r
+                                   merge({function_name:function_name,\r
+                                          description:(description?description + " ":"")},\r
+                                          substitutions));\r
+        return node_form.slice(1).join("");\r
+    }\r
+\r
+    function filter(array, callable, thisObj) {\r
+        var rv = [];\r
+        for (var i = 0; i < array.length; i++) {\r
+            if (array.hasOwnProperty(i)) {\r
+                var pass = callable.call(thisObj, array[i], i, array);\r
+                if (pass) {\r
+                    rv.push(array[i]);\r
+                }\r
+            }\r
+        }\r
+        return rv;\r
+    }\r
+\r
+    function map(array, callable, thisObj)\r
+    {\r
+        var rv = [];\r
+        rv.length = array.length;\r
+        for (var i = 0; i < array.length; i++) {\r
+            if (array.hasOwnProperty(i)) {\r
+                rv[i] = callable.call(thisObj, array[i], i, array);\r
+            }\r
+        }\r
+        return rv;\r
+    }\r
+\r
+    function extend(array, items)\r
+    {\r
+        Array.prototype.push.apply(array, items);\r
+    }\r
+\r
+    function forEach (array, callback, thisObj)\r
+    {\r
+        for (var i = 0; i < array.length; i++) {\r
+            if (array.hasOwnProperty(i)) {\r
+                callback.call(thisObj, array[i], i, array);\r
+            }\r
+        }\r
+    }\r
+\r
+    function merge(a,b)\r
+    {\r
+        var rv = {};\r
+        var p;\r
+        for (p in a) {\r
+            rv[p] = a[p];\r
+        }\r
+        for (p in b) {\r
+            rv[p] = b[p];\r
+        }\r
+        return rv;\r
+    }\r
+\r
+    function expose(object, name)\r
+    {\r
+        var components = name.split(".");\r
+        var target = test_environment.global_scope();\r
+        for (var i = 0; i < components.length - 1; i++) {\r
+            if (!(components[i] in target)) {\r
+                target[components[i]] = {};\r
+            }\r
+            target = target[components[i]];\r
+        }\r
+        target[components[components.length - 1]] = object;\r
+    }\r
+\r
+    function is_same_origin(w) {\r
+        try {\r
+            'random_prop' in w;\r
+            return true;\r
+        } catch (e) {\r
+            return false;\r
+        }\r
+    }\r
+\r
+    function supports_post_message(w)\r
+    {\r
+        var supports;\r
+        var type;\r
+        // Given IE  implements postMessage across nested iframes but not across\r
+        // windows or tabs, you can't infer cross-origin communication from the presence\r
+        // of postMessage on the current window object only.\r
+        //\r
+        // Touching the postMessage prop on a window can throw if the window is\r
+        // not from the same origin AND post message is not supported in that\r
+        // browser. So just doing an existence test here won't do, you also need\r
+        // to wrap it in a try..cacth block.\r
+        try {\r
+            type = typeof w.postMessage;\r
+            if (type === "function") {\r
+                supports = true;\r
+            }\r
+\r
+            // IE8 supports postMessage, but implements it as a host object which\r
+            // returns "object" as its `typeof`.\r
+            else if (type === "object") {\r
+                supports = true;\r
+            }\r
+\r
+            // This is the case where postMessage isn't supported AND accessing a\r
+            // window property across origins does NOT throw (e.g. old Safari browser).\r
+            else {\r
+                supports = false;\r
+            }\r
+        } catch (e) {\r
+            // This is the case where postMessage isn't supported AND accessing a\r
+            // window property across origins throws (e.g. old Firefox browser).\r
+            supports = false;\r
+        }\r
+        return supports;\r
+    }\r
+\r
+    /**\r
+     * Setup globals\r
+     */\r
+\r
+    var tests = new Tests();\r
+\r
+    addEventListener("error", function(e) {\r
+        if (tests.file_is_test) {\r
+            var test = tests.tests[0];\r
+            if (test.phase >= test.phases.HAS_RESULT) {\r
+                return;\r
+            }\r
+            var message = e.message;\r
+            test.set_status(test.FAIL, message);\r
+            test.phase = test.phases.HAS_RESULT;\r
+            test.done();\r
+            done();\r
+        } else if (!tests.allow_uncaught_exception) {\r
+            tests.status.status = tests.status.ERROR;\r
+            tests.status.message = e.message;\r
+        }\r
+    });\r
+\r
+    test_environment.on_tests_ready();\r
+\r
+})();\r
+// vim: set expandtab shiftwidth=4 tabstop=4:\r