• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*global self*/
2/*jshint latedef: nofunc*/
3
4/* Documentation: https://web-platform-tests.org/writing-tests/testharness-api.html
5 * (../docs/_writing-tests/testharness-api.md) */
6
7(function (global_scope)
8{
9    // default timeout is 10 seconds, test can override if needed
10    var settings = {
11        output:true,
12        harness_timeout:{
13            "normal":10000,
14            "long":60000
15        },
16        test_timeout:null,
17        message_events: ["start", "test_state", "result", "completion"],
18        debug: false,
19    };
20
21    var xhtml_ns = "http://www.w3.org/1999/xhtml";
22
23    /*
24     * TestEnvironment is an abstraction for the environment in which the test
25     * harness is used. Each implementation of a test environment has to provide
26     * the following interface:
27     *
28     * interface TestEnvironment {
29     *   // Invoked after the global 'tests' object has been created and it's
30     *   // safe to call add_*_callback() to register event handlers.
31     *   void on_tests_ready();
32     *
33     *   // Invoked after setup() has been called to notify the test environment
34     *   // of changes to the test harness properties.
35     *   void on_new_harness_properties(object properties);
36     *
37     *   // Should return a new unique default test name.
38     *   DOMString next_default_test_name();
39     *
40     *   // Should return the test harness timeout duration in milliseconds.
41     *   float test_timeout();
42     * };
43     */
44
45    /*
46     * A test environment with a DOM. The global object is 'window'. By default
47     * test results are displayed in a table. Any parent windows receive
48     * callbacks or messages via postMessage() when test events occur. See
49     * apisample11.html and apisample12.html.
50     */
51    function WindowTestEnvironment() {
52        this.name_counter = 0;
53        this.window_cache = null;
54        this.output_handler = null;
55        this.all_loaded = false;
56        var this_obj = this;
57        this.message_events = [];
58        this.dispatched_messages = [];
59
60        this.message_functions = {
61            start: [add_start_callback, remove_start_callback,
62                    function (properties) {
63                        this_obj._dispatch("start_callback", [properties],
64                                           {type: "start", properties: properties});
65                    }],
66
67            test_state: [add_test_state_callback, remove_test_state_callback,
68                         function(test) {
69                             this_obj._dispatch("test_state_callback", [test],
70                                                {type: "test_state",
71                                                 test: test.structured_clone()});
72                         }],
73            result: [add_result_callback, remove_result_callback,
74                     function (test) {
75                         this_obj.output_handler.show_status();
76                         this_obj._dispatch("result_callback", [test],
77                                            {type: "result",
78                                             test: test.structured_clone()});
79                     }],
80            completion: [add_completion_callback, remove_completion_callback,
81                         function (tests, harness_status, asserts) {
82                             var cloned_tests = map(tests, function(test) {
83                                 return test.structured_clone();
84                             });
85                             this_obj._dispatch("completion_callback", [tests, harness_status],
86                                                {type: "complete",
87                                                 tests: cloned_tests,
88                                                 status: harness_status.structured_clone(),
89                                                 asserts: asserts.map(assert => assert.structured_clone())});
90                         }]
91        }
92
93        on_event(window, 'load', function() {
94            this_obj.all_loaded = true;
95        });
96
97        on_event(window, 'message', function(event) {
98            if (event.data && event.data.type === "getmessages" && event.source) {
99                // A window can post "getmessages" to receive a duplicate of every
100                // message posted by this environment so far. This allows subscribers
101                // from fetch_tests_from_window to 'catch up' to the current state of
102                // this environment.
103                for (var i = 0; i < this_obj.dispatched_messages.length; ++i)
104                {
105                    event.source.postMessage(this_obj.dispatched_messages[i], "*");
106                }
107            }
108        });
109    }
110
111    WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) {
112        this.dispatched_messages.push(message_arg);
113        this._forEach_windows(
114                function(w, same_origin) {
115                    if (same_origin) {
116                        try {
117                            var has_selector = selector in w;
118                        } catch(e) {
119                            // If document.domain was set at some point same_origin can be
120                            // wrong and the above will fail.
121                            has_selector = false;
122                        }
123                        if (has_selector) {
124                            try {
125                                w[selector].apply(undefined, callback_args);
126                            } catch (e) {}
127                        }
128                    }
129                    if (w !== self) {
130                        w.postMessage(message_arg, "*");
131                    }
132                });
133    };
134
135    WindowTestEnvironment.prototype._forEach_windows = function(callback) {
136        // Iterate over the windows [self ... top, opener]. The callback is passed
137        // two objects, the first one is the window object itself, the second one
138        // is a boolean indicating whether or not it's on the same origin as the
139        // current window.
140        var cache = this.window_cache;
141        if (!cache) {
142            cache = [[self, true]];
143            var w = self;
144            var i = 0;
145            var so;
146            while (w != w.parent) {
147                w = w.parent;
148                so = is_same_origin(w);
149                cache.push([w, so]);
150                i++;
151            }
152            w = window.opener;
153            if (w) {
154                cache.push([w, is_same_origin(w)]);
155            }
156            this.window_cache = cache;
157        }
158
159        forEach(cache,
160                function(a) {
161                    callback.apply(null, a);
162                });
163    };
164
165    WindowTestEnvironment.prototype.on_tests_ready = function() {
166        var output = new Output();
167        this.output_handler = output;
168
169        var this_obj = this;
170
171        add_start_callback(function (properties) {
172            this_obj.output_handler.init(properties);
173        });
174
175        add_test_state_callback(function(test) {
176            this_obj.output_handler.show_status();
177        });
178
179        add_result_callback(function (test) {
180            this_obj.output_handler.show_status();
181        });
182
183        add_completion_callback(function (tests, harness_status, asserts_run) {
184            this_obj.output_handler.show_results(tests, harness_status, asserts_run);
185        });
186        this.setup_messages(settings.message_events);
187    };
188
189    WindowTestEnvironment.prototype.setup_messages = function(new_events) {
190        var this_obj = this;
191        forEach(settings.message_events, function(x) {
192            var current_dispatch = this_obj.message_events.indexOf(x) !== -1;
193            var new_dispatch = new_events.indexOf(x) !== -1;
194            if (!current_dispatch && new_dispatch) {
195                this_obj.message_functions[x][0](this_obj.message_functions[x][2]);
196            } else if (current_dispatch && !new_dispatch) {
197                this_obj.message_functions[x][1](this_obj.message_functions[x][2]);
198            }
199        });
200        this.message_events = new_events;
201    }
202
203    WindowTestEnvironment.prototype.next_default_test_name = function() {
204        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
205        this.name_counter++;
206        return get_title() + suffix;
207    };
208
209    WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) {
210        this.output_handler.setup(properties);
211        if (properties.hasOwnProperty("message_events")) {
212            this.setup_messages(properties.message_events);
213        }
214    };
215
216    WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
217        on_event(window, 'load', callback);
218    };
219
220    WindowTestEnvironment.prototype.test_timeout = function() {
221        var metas = document.getElementsByTagName("meta");
222        for (var i = 0; i < metas.length; i++) {
223            if (metas[i].name == "timeout") {
224                if (metas[i].content == "long") {
225                    return settings.harness_timeout.long;
226                }
227                break;
228            }
229        }
230        return settings.harness_timeout.normal;
231    };
232
233    /*
234     * Base TestEnvironment implementation for a generic web worker.
235     *
236     * Workers accumulate test results. One or more clients can connect and
237     * retrieve results from a worker at any time.
238     *
239     * WorkerTestEnvironment supports communicating with a client via a
240     * MessagePort.  The mechanism for determining the appropriate MessagePort
241     * for communicating with a client depends on the type of worker and is
242     * implemented by the various specializations of WorkerTestEnvironment
243     * below.
244     *
245     * A client document using testharness can use fetch_tests_from_worker() to
246     * retrieve results from a worker. See apisample16.html.
247     */
248    function WorkerTestEnvironment() {
249        this.name_counter = 0;
250        this.all_loaded = true;
251        this.message_list = [];
252        this.message_ports = [];
253    }
254
255    WorkerTestEnvironment.prototype._dispatch = function(message) {
256        this.message_list.push(message);
257        for (var i = 0; i < this.message_ports.length; ++i)
258        {
259            this.message_ports[i].postMessage(message);
260        }
261    };
262
263    // The only requirement is that port has a postMessage() method. It doesn't
264    // have to be an instance of a MessagePort, and often isn't.
265    WorkerTestEnvironment.prototype._add_message_port = function(port) {
266        this.message_ports.push(port);
267        for (var i = 0; i < this.message_list.length; ++i)
268        {
269            port.postMessage(this.message_list[i]);
270        }
271    };
272
273    WorkerTestEnvironment.prototype.next_default_test_name = function() {
274        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
275        this.name_counter++;
276        return get_title() + suffix;
277    };
278
279    WorkerTestEnvironment.prototype.on_new_harness_properties = function() {};
280
281    WorkerTestEnvironment.prototype.on_tests_ready = function() {
282        var this_obj = this;
283        add_start_callback(
284                function(properties) {
285                    this_obj._dispatch({
286                        type: "start",
287                        properties: properties,
288                    });
289                });
290        add_test_state_callback(
291                function(test) {
292                    this_obj._dispatch({
293                        type: "test_state",
294                        test: test.structured_clone()
295                    });
296                });
297        add_result_callback(
298                function(test) {
299                    this_obj._dispatch({
300                        type: "result",
301                        test: test.structured_clone()
302                    });
303                });
304        add_completion_callback(
305                function(tests, harness_status, asserts) {
306                    this_obj._dispatch({
307                        type: "complete",
308                        tests: map(tests,
309                            function(test) {
310                                return test.structured_clone();
311                            }),
312                        status: harness_status.structured_clone(),
313                        asserts: asserts.map(assert => assert.structured_clone()),
314                    });
315                });
316    };
317
318    WorkerTestEnvironment.prototype.add_on_loaded_callback = function() {};
319
320    WorkerTestEnvironment.prototype.test_timeout = function() {
321        // Tests running in a worker don't have a default timeout. I.e. all
322        // worker tests behave as if settings.explicit_timeout is true.
323        return null;
324    };
325
326    /*
327     * Dedicated web workers.
328     * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope
329     *
330     * This class is used as the test_environment when testharness is running
331     * inside a dedicated worker.
332     */
333    function DedicatedWorkerTestEnvironment() {
334        WorkerTestEnvironment.call(this);
335        // self is an instance of DedicatedWorkerGlobalScope which exposes
336        // a postMessage() method for communicating via the message channel
337        // established when the worker is created.
338        this._add_message_port(self);
339    }
340    DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
341
342    DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() {
343        WorkerTestEnvironment.prototype.on_tests_ready.call(this);
344        // In the absence of an onload notification, we a require dedicated
345        // workers to explicitly signal when the tests are done.
346        tests.wait_for_finish = true;
347    };
348
349    /*
350     * Shared web workers.
351     * https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalscope
352     *
353     * This class is used as the test_environment when testharness is running
354     * inside a shared web worker.
355     */
356    function SharedWorkerTestEnvironment() {
357        WorkerTestEnvironment.call(this);
358        var this_obj = this;
359        // Shared workers receive message ports via the 'onconnect' event for
360        // each connection.
361        self.addEventListener("connect",
362                function(message_event) {
363                    this_obj._add_message_port(message_event.source);
364                }, false);
365    }
366    SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
367
368    SharedWorkerTestEnvironment.prototype.on_tests_ready = function() {
369        WorkerTestEnvironment.prototype.on_tests_ready.call(this);
370        // In the absence of an onload notification, we a require shared
371        // workers to explicitly signal when the tests are done.
372        tests.wait_for_finish = true;
373    };
374
375    /*
376     * Service workers.
377     * http://www.w3.org/TR/service-workers/
378     *
379     * This class is used as the test_environment when testharness is running
380     * inside a service worker.
381     */
382    function ServiceWorkerTestEnvironment() {
383        WorkerTestEnvironment.call(this);
384        this.all_loaded = false;
385        this.on_loaded_callback = null;
386        var this_obj = this;
387        self.addEventListener("message",
388                function(event) {
389                    if (event.data && event.data.type && event.data.type === "connect") {
390                        this_obj._add_message_port(event.source);
391                    }
392                }, false);
393
394        // The oninstall event is received after the service worker script and
395        // all imported scripts have been fetched and executed. It's the
396        // equivalent of an onload event for a document. All tests should have
397        // been added by the time this event is received, thus it's not
398        // necessary to wait until the onactivate event. However, tests for
399        // installed service workers need another event which is equivalent to
400        // the onload event because oninstall is fired only on installation. The
401        // onmessage event is used for that purpose since tests using
402        // testharness.js should ask the result to its service worker by
403        // PostMessage. If the onmessage event is triggered on the service
404        // worker's context, that means the worker's script has been evaluated.
405        on_event(self, "install", on_all_loaded);
406        on_event(self, "message", on_all_loaded);
407        function on_all_loaded() {
408            if (this_obj.all_loaded)
409                return;
410            this_obj.all_loaded = true;
411            if (this_obj.on_loaded_callback) {
412              this_obj.on_loaded_callback();
413            }
414        }
415    }
416
417    ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
418
419    ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
420        if (this.all_loaded) {
421            callback();
422        } else {
423            this.on_loaded_callback = callback;
424        }
425    };
426
427    /*
428     * Shadow realms.
429     * https://github.com/tc39/proposal-shadowrealm
430     *
431     * This class is used as the test_environment when testharness is running
432     * inside a shadow realm.
433     */
434    function ShadowRealmTestEnvironment() {
435        WorkerTestEnvironment.call(this);
436        this.all_loaded = false;
437        this.on_loaded_callback = null;
438    }
439
440    ShadowRealmTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
441
442    /**
443     * Signal to the test environment that the tests are ready and the on-loaded
444     * callback should be run.
445     *
446     * Shadow realms are not *really* a DOM context: they have no `onload` or similar
447     * event for us to use to set up the test environment; so, instead, this method
448     * is manually triggered from the incubating realm
449     *
450     * @param {Function} message_destination - a function that receives JSON-serializable
451     * data to send to the incubating realm, in the same format as used by RemoteContext
452     */
453    ShadowRealmTestEnvironment.prototype.begin = function(message_destination) {
454        if (this.all_loaded) {
455            throw new Error("Tried to start a shadow realm test environment after it has already started");
456        }
457        var fakeMessagePort = {};
458        fakeMessagePort.postMessage = message_destination;
459        this._add_message_port(fakeMessagePort);
460        this.all_loaded = true;
461        if (this.on_loaded_callback) {
462            this.on_loaded_callback();
463        }
464    };
465
466    ShadowRealmTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
467        if (this.all_loaded) {
468            callback();
469        } else {
470            this.on_loaded_callback = callback;
471        }
472    };
473
474    /*
475     * JavaScript shells.
476     *
477     * This class is used as the test_environment when testharness is running
478     * inside a JavaScript shell.
479     */
480    function ShellTestEnvironment() {
481        this.name_counter = 0;
482        this.all_loaded = false;
483        this.on_loaded_callback = null;
484        Promise.resolve().then(function() {
485            this.all_loaded = true
486            if (this.on_loaded_callback) {
487                this.on_loaded_callback();
488            }
489        }.bind(this));
490        this.message_list = [];
491        this.message_ports = [];
492    }
493
494    ShellTestEnvironment.prototype.next_default_test_name = function() {
495        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
496        this.name_counter++;
497        return "Untitled" + suffix;
498    };
499
500    ShellTestEnvironment.prototype.on_new_harness_properties = function() {};
501
502    ShellTestEnvironment.prototype.on_tests_ready = function() {};
503
504    ShellTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
505        if (this.all_loaded) {
506            callback();
507        } else {
508            this.on_loaded_callback = callback;
509        }
510    };
511
512    ShellTestEnvironment.prototype.test_timeout = function() {
513        // Tests running in a shell don't have a default timeout, so behave as
514        // if settings.explicit_timeout is true.
515        return null;
516    };
517
518    function create_test_environment() {
519        if ('document' in global_scope) {
520            return new WindowTestEnvironment();
521        }
522        if ('DedicatedWorkerGlobalScope' in global_scope &&
523            global_scope instanceof DedicatedWorkerGlobalScope) {
524            return new DedicatedWorkerTestEnvironment();
525        }
526        if ('SharedWorkerGlobalScope' in global_scope &&
527            global_scope instanceof SharedWorkerGlobalScope) {
528            return new SharedWorkerTestEnvironment();
529        }
530        if ('ServiceWorkerGlobalScope' in global_scope &&
531            global_scope instanceof ServiceWorkerGlobalScope) {
532            return new ServiceWorkerTestEnvironment();
533        }
534        if ('WorkerGlobalScope' in global_scope &&
535            global_scope instanceof WorkerGlobalScope) {
536            return new DedicatedWorkerTestEnvironment();
537        }
538        /* Shadow realm global objects are _ordinary_ objects (i.e. their prototype is
539         * Object) so we don't have a nice `instanceof` test to use; instead, we
540         * check if the there is a GLOBAL.isShadowRealm() property
541         * on the global object. that was set by the test harness when it
542         * created the ShadowRealm.
543         */
544        if (global_scope.GLOBAL && global_scope.GLOBAL.isShadowRealm()) {
545            return new ShadowRealmTestEnvironment();
546        }
547
548        return new ShellTestEnvironment();
549    }
550
551    var test_environment = create_test_environment();
552
553    function is_shared_worker(worker) {
554        return 'SharedWorker' in global_scope && worker instanceof SharedWorker;
555    }
556
557    function is_service_worker(worker) {
558        // The worker object may be from another execution context,
559        // so do not use instanceof here.
560        return 'ServiceWorker' in global_scope &&
561            Object.prototype.toString.call(worker) == '[object ServiceWorker]';
562    }
563
564    var seen_func_name = Object.create(null);
565
566    function get_test_name(func, name)
567    {
568        if (name) {
569            return name;
570        }
571
572        if (func) {
573            var func_code = func.toString();
574
575            // Try and match with brackets, but fallback to matching without
576            var arrow = func_code.match(/^\(\)\s*=>\s*(?:{(.*)}\s*|(.*))$/);
577
578            // Check for JS line separators
579            if (arrow !== null && !/[\u000A\u000D\u2028\u2029]/.test(func_code)) {
580                var trimmed = (arrow[1] !== undefined ? arrow[1] : arrow[2]).trim();
581                // drop trailing ; if there's no earlier ones
582                trimmed = trimmed.replace(/^([^;]*)(;\s*)+$/, "$1");
583
584                if (trimmed) {
585                    let name = trimmed;
586                    if (seen_func_name[trimmed]) {
587                        // This subtest name already exists, so add a suffix.
588                        name += " " + seen_func_name[trimmed];
589                    } else {
590                        seen_func_name[trimmed] = 0;
591                    }
592                    seen_func_name[trimmed] += 1;
593                    return name;
594                }
595            }
596        }
597
598        return test_environment.next_default_test_name();
599    }
600
601    /**
602     * @callback TestFunction
603     * @param {Test} test - The test currnetly being run.
604     * @param {Any[]} args - Additional args to pass to function.
605     *
606     */
607
608    /**
609     * Create a synchronous test
610     *
611     * @param {TestFunction} func - Test function. This is executed
612     * immediately. If it returns without error, the test status is
613     * set to ``PASS``. If it throws an :js:class:`AssertionError`, or
614     * any other exception, the test status is set to ``FAIL``
615     * (typically from an `assert` function).
616     * @param {String} name - Test name. This must be unique in a
617     * given file and must be invariant between runs.
618     */
619    function test(func, name, properties)
620    {
621        if (tests.promise_setup_called) {
622            tests.status.status = tests.status.ERROR;
623            tests.status.message = '`test` invoked after `promise_setup`';
624            tests.complete();
625        }
626        var test_name = get_test_name(func, name);
627        var test_obj = new Test(test_name, properties);
628        var value = test_obj.step(func, test_obj, test_obj);
629
630        if (value !== undefined) {
631            var msg = 'Test named "' + test_name +
632                '" passed a function to `test` that returned a value.';
633
634            try {
635                if (value && typeof value.then === 'function') {
636                    msg += ' Consider using `promise_test` instead when ' +
637                        'using Promises or async/await.';
638                }
639            } catch (err) {}
640
641            tests.status.status = tests.status.ERROR;
642            tests.status.message = msg;
643        }
644
645        if (test_obj.phase === test_obj.phases.STARTED) {
646            test_obj.done();
647        }
648    }
649
650    /**
651     * Create an asynchronous test
652     *
653     * @param {TestFunction|string} funcOrName - Initial step function
654     * to call immediately with the test name as an argument (if any),
655     * or name of the test.
656     * @param {String} name - Test name (if a test function was
657     * provided). This must be unique in a given file and must be
658     * invariant between runs.
659     * @returns {Test} An object representing the ongoing test.
660     */
661    function async_test(func, name, properties)
662    {
663        if (tests.promise_setup_called) {
664            tests.status.status = tests.status.ERROR;
665            tests.status.message = '`async_test` invoked after `promise_setup`';
666            tests.complete();
667        }
668        if (typeof func !== "function") {
669            properties = name;
670            name = func;
671            func = null;
672        }
673        var test_name = get_test_name(func, name);
674        var test_obj = new Test(test_name, properties);
675        if (func) {
676            var value = test_obj.step(func, test_obj, test_obj);
677
678            // Test authors sometimes return values to async_test, expecting us
679            // to handle the value somehow. Make doing so a harness error to be
680            // clear this is invalid, and point authors to promise_test if it
681            // may be appropriate.
682            //
683            // Note that we only perform this check on the initial function
684            // passed to async_test, not on any later steps - we haven't seen a
685            // consistent problem with those (and it's harder to check).
686            if (value !== undefined) {
687                var msg = 'Test named "' + test_name +
688                    '" passed a function to `async_test` that returned a value.';
689
690                try {
691                    if (value && typeof value.then === 'function') {
692                        msg += ' Consider using `promise_test` instead when ' +
693                            'using Promises or async/await.';
694                    }
695                } catch (err) {}
696
697                tests.set_status(tests.status.ERROR, msg);
698                tests.complete();
699            }
700        }
701        return test_obj;
702    }
703
704    /**
705     * Create a promise test.
706     *
707     * Promise tests are tests which are represented by a promise
708     * object. If the promise is fulfilled the test passes, if it's
709     * rejected the test fails, otherwise the test passes.
710     *
711     * @param {TestFunction} func - Test function. This must return a
712     * promise. The test is automatically marked as complete once the
713     * promise settles.
714     * @param {String} name - Test name. This must be unique in a
715     * given file and must be invariant between runs.
716     */
717    function promise_test(func, name, properties) {
718        if (typeof func !== "function") {
719            properties = name;
720            name = func;
721            func = null;
722        }
723        var test_name = get_test_name(func, name);
724        var test = new Test(test_name, properties);
725        test._is_promise_test = true;
726
727        // If there is no promise tests queue make one.
728        if (!tests.promise_tests) {
729            tests.promise_tests = Promise.resolve();
730        }
731        tests.promise_tests = tests.promise_tests.then(function() {
732            return new Promise(function(resolve) {
733                var promise = test.step(func, test, test);
734
735                test.step(function() {
736                    assert(!!promise, "promise_test", null,
737                           "test body must return a 'thenable' object (received ${value})",
738                           {value:promise});
739                    assert(typeof promise.then === "function", "promise_test", null,
740                           "test body must return a 'thenable' object (received an object with no `then` method)",
741                           null);
742                });
743
744                // Test authors may use the `step` method within a
745                // `promise_test` even though this reflects a mixture of
746                // asynchronous control flow paradigms. The "done" callback
747                // should be registered prior to the resolution of the
748                // user-provided Promise to avoid timeouts in cases where the
749                // Promise does not settle but a `step` function has thrown an
750                // error.
751                add_test_done_callback(test, resolve);
752
753                Promise.resolve(promise)
754                    .catch(test.step_func(
755                        function(value) {
756                            if (value instanceof AssertionError) {
757                                throw value;
758                            }
759                            assert(false, "promise_test", null,
760                                   "Unhandled rejection with value: ${value}", {value:value});
761                        }))
762                    .then(function() {
763                        test.done();
764                    });
765                });
766        });
767    }
768
769    /**
770     * Make a copy of a Promise in the current realm.
771     *
772     * @param {Promise} promise the given promise that may be from a different
773     *                          realm
774     * @returns {Promise}
775     *
776     * An arbitrary promise provided by the caller may have originated
777     * in another frame that have since navigated away, rendering the
778     * frame's document inactive. Such a promise cannot be used with
779     * `await` or Promise.resolve(), as microtasks associated with it
780     * may be prevented from being run. See `issue
781     * 5319<https://github.com/whatwg/html/issues/5319>`_ for a
782     * particular case.
783     *
784     * In functions we define here, there is an expectation from the caller
785     * that the promise is from the current realm, that can always be used with
786     * `await`, etc. We therefore create a new promise in this realm that
787     * inherit the value and status from the given promise.
788     */
789
790    function bring_promise_to_current_realm(promise) {
791        return new Promise(promise.then.bind(promise));
792    }
793
794    /**
795     * Assert that a Promise is rejected with the right ECMAScript exception.
796     *
797     * @param {Test} test - the `Test` to use for the assertion.
798     * @param {Function} constructor - The expected exception constructor.
799     * @param {Promise} promise - The promise that's expected to
800     * reject with the given exception.
801     * @param {string} [description] Error message to add to assert in case of
802     *                               failure.
803     */
804    function promise_rejects_js(test, constructor, promise, description) {
805        return bring_promise_to_current_realm(promise)
806            .then(test.unreached_func("Should have rejected: " + description))
807            .catch(function(e) {
808                assert_throws_js_impl(constructor, function() { throw e },
809                                      description, "promise_rejects_js");
810            });
811    }
812
813    /**
814     * Assert that a Promise is rejected with the right DOMException.
815     *
816     * For the remaining arguments, there are two ways of calling
817     * promise_rejects_dom:
818     *
819     * 1) If the DOMException is expected to come from the current global, the
820     * third argument should be the promise expected to reject, and a fourth,
821     * optional, argument is the assertion description.
822     *
823     * 2) If the DOMException is expected to come from some other global, the
824     * third argument should be the DOMException constructor from that global,
825     * the fourth argument the promise expected to reject, and the fifth,
826     * optional, argument the assertion description.
827     *
828     * @param {Test} test - the `Test` to use for the assertion.
829     * @param {number|string} type - See documentation for
830     * `assert_throws_dom <#assert_throws_dom>`_.
831     * @param {Function} promiseOrConstructor - Either the constructor
832     * for the expected exception (if the exception comes from another
833     * global), or the promise that's expected to reject (if the
834     * exception comes from the current global).
835     * @param {Function|string} descriptionOrPromise - Either the
836     * promise that's expected to reject (if the exception comes from
837     * another global), or the optional description of the condition
838     * being tested (if the exception comes from the current global).
839     * @param {string} [description] - Description of the condition
840     * being tested (if the exception comes from another global).
841     *
842     */
843    function promise_rejects_dom(test, type, promiseOrConstructor, descriptionOrPromise, maybeDescription) {
844        let constructor, promise, description;
845        if (typeof promiseOrConstructor === "function" &&
846            promiseOrConstructor.name === "DOMException") {
847            constructor = promiseOrConstructor;
848            promise = descriptionOrPromise;
849            description = maybeDescription;
850        } else {
851            constructor = self.DOMException;
852            promise = promiseOrConstructor;
853            description = descriptionOrPromise;
854            assert(maybeDescription === undefined,
855                   "Too many args pased to no-constructor version of promise_rejects_dom");
856        }
857        return bring_promise_to_current_realm(promise)
858            .then(test.unreached_func("Should have rejected: " + description))
859            .catch(function(e) {
860                assert_throws_dom_impl(type, function() { throw e }, description,
861                                       "promise_rejects_dom", constructor);
862            });
863    }
864
865    /**
866     * Assert that a Promise is rejected with the provided value.
867     *
868     * @param {Test} test - the `Test` to use for the assertion.
869     * @param {Any} exception - The expected value of the rejected promise.
870     * @param {Promise} promise - The promise that's expected to
871     * reject.
872     * @param {string} [description] Error message to add to assert in case of
873     *                               failure.
874     */
875    function promise_rejects_exactly(test, exception, promise, description) {
876        return bring_promise_to_current_realm(promise)
877            .then(test.unreached_func("Should have rejected: " + description))
878            .catch(function(e) {
879                assert_throws_exactly_impl(exception, function() { throw e },
880                                           description, "promise_rejects_exactly");
881            });
882    }
883
884    /**
885     * Allow DOM events to be handled using Promises.
886     *
887     * This can make it a lot easier to test a very specific series of events,
888     * including ensuring that unexpected events are not fired at any point.
889     *
890     * `EventWatcher` will assert if an event occurs while there is no `wait_for`
891     * created Promise waiting to be fulfilled, or if the event is of a different type
892     * to the type currently expected. This ensures that only the events that are
893     * expected occur, in the correct order, and with the correct timing.
894     *
895     * @constructor
896     * @param {Test} test - The `Test` to use for the assertion.
897     * @param {EventTarget} watchedNode - The target expected to receive the events.
898     * @param {string[]} eventTypes - List of events to watch for.
899     * @param {Promise} timeoutPromise - Promise that will cause the
900     * test to be set to `TIMEOUT` once fulfilled.
901     *
902     */
903    function EventWatcher(test, watchedNode, eventTypes, timeoutPromise)
904    {
905        if (typeof eventTypes == 'string') {
906            eventTypes = [eventTypes];
907        }
908
909        var waitingFor = null;
910
911        // This is null unless we are recording all events, in which case it
912        // will be an Array object.
913        var recordedEvents = null;
914
915        var eventHandler = test.step_func(function(evt) {
916            assert_true(!!waitingFor,
917                        'Not expecting event, but got ' + evt.type + ' event');
918            assert_equals(evt.type, waitingFor.types[0],
919                          'Expected ' + waitingFor.types[0] + ' event, but got ' +
920                          evt.type + ' event instead');
921
922            if (Array.isArray(recordedEvents)) {
923                recordedEvents.push(evt);
924            }
925
926            if (waitingFor.types.length > 1) {
927                // Pop first event from array
928                waitingFor.types.shift();
929                return;
930            }
931            // We need to null out waitingFor before calling the resolve function
932            // since the Promise's resolve handlers may call wait_for() which will
933            // need to set waitingFor.
934            var resolveFunc = waitingFor.resolve;
935            waitingFor = null;
936            // Likewise, we should reset the state of recordedEvents.
937            var result = recordedEvents || evt;
938            recordedEvents = null;
939            resolveFunc(result);
940        });
941
942        for (var i = 0; i < eventTypes.length; i++) {
943            watchedNode.addEventListener(eventTypes[i], eventHandler, false);
944        }
945
946        /**
947         * Returns a Promise that will resolve after the specified event or
948         * series of events has occurred.
949         *
950         * @param {Object} options An optional options object. If the 'record' property
951         *                 on this object has the value 'all', when the Promise
952         *                 returned by this function is resolved,  *all* Event
953         *                 objects that were waited for will be returned as an
954         *                 array.
955         *
956         * @example
957         * const watcher = new EventWatcher(t, div, [ 'animationstart',
958         *                                            'animationiteration',
959         *                                            'animationend' ]);
960         * return watcher.wait_for([ 'animationstart', 'animationend' ],
961         *                         { record: 'all' }).then(evts => {
962         *   assert_equals(evts[0].elapsedTime, 0.0);
963         *   assert_equals(evts[1].elapsedTime, 2.0);
964         * });
965         */
966        this.wait_for = function(types, options) {
967            if (waitingFor) {
968                return Promise.reject('Already waiting for an event or events');
969            }
970            if (typeof types == 'string') {
971                types = [types];
972            }
973            if (options && options.record && options.record === 'all') {
974                recordedEvents = [];
975            }
976            return new Promise(function(resolve, reject) {
977                var timeout = test.step_func(function() {
978                    // If the timeout fires after the events have been received
979                    // or during a subsequent call to wait_for, ignore it.
980                    if (!waitingFor || waitingFor.resolve !== resolve)
981                        return;
982
983                    // This should always fail, otherwise we should have
984                    // resolved the promise.
985                    assert_true(waitingFor.types.length == 0,
986                                'Timed out waiting for ' + waitingFor.types.join(', '));
987                    var result = recordedEvents;
988                    recordedEvents = null;
989                    var resolveFunc = waitingFor.resolve;
990                    waitingFor = null;
991                    resolveFunc(result);
992                });
993
994                if (timeoutPromise) {
995                    timeoutPromise().then(timeout);
996                }
997
998                waitingFor = {
999                    types: types,
1000                    resolve: resolve,
1001                    reject: reject
1002                };
1003            });
1004        };
1005
1006        /**
1007         * Stop listening for events
1008         */
1009        function stop_watching() {
1010            for (var i = 0; i < eventTypes.length; i++) {
1011                watchedNode.removeEventListener(eventTypes[i], eventHandler, false);
1012            }
1013        };
1014
1015        test._add_cleanup(stop_watching);
1016
1017        return this;
1018    }
1019    expose(EventWatcher, 'EventWatcher');
1020
1021    /**
1022     * @typedef {Object} SettingsObject
1023     * @property {bool} single_test - Use the single-page-test
1024     * mode. In this mode the Document represents a single
1025     * `async_test`. Asserts may be used directly without requiring
1026     * `Test.step` or similar wrappers, and any exceptions set the
1027     * status of the test rather than the status of the harness.
1028     * @property {bool} allow_uncaught_exception - don't treat an
1029     * uncaught exception as an error; needed when e.g. testing the
1030     * `window.onerror` handler.
1031     * @property {boolean} explicit_done - Wait for a call to `done()`
1032     * before declaring all tests complete (this is always true for
1033     * single-page tests).
1034     * @property hide_test_state - hide the test state output while
1035     * the test is running; This is helpful when the output of the test state
1036     * may interfere the test results.
1037     * @property {bool} explicit_timeout - disable file timeout; only
1038     * stop waiting for results when the `timeout()` function is
1039     * called This should typically only be set for manual tests, or
1040     * by a test runner that providees its own timeout mechanism.
1041     * @property {number} timeout_multiplier - Multiplier to apply to
1042     * per-test timeouts. This should only be set by a test runner.
1043     * @property {Document} output_document - The document to which
1044     * results should be logged. By default this is the current
1045     * document but could be an ancestor document in some cases e.g. a
1046     * SVG test loaded in an HTML wrapper
1047     *
1048     */
1049
1050    /**
1051     * Configure the harness
1052     *
1053     * @param {Function|SettingsObject} funcOrProperties - Either a
1054     * setup function to run, or a set of properties. If this is a
1055     * function that function is run synchronously. Any exception in
1056     * the function will set the overall harness status to `ERROR`.
1057     * @param {SettingsObject} maybeProperties - An object containing
1058     * the settings to use, if the first argument is a function.
1059     *
1060     */
1061    function setup(func_or_properties, maybe_properties)
1062    {
1063        var func = null;
1064        var properties = {};
1065        if (arguments.length === 2) {
1066            func = func_or_properties;
1067            properties = maybe_properties;
1068        } else if (func_or_properties instanceof Function) {
1069            func = func_or_properties;
1070        } else {
1071            properties = func_or_properties;
1072        }
1073        tests.setup(func, properties);
1074        test_environment.on_new_harness_properties(properties);
1075    }
1076
1077    /**
1078     * Configure the harness, waiting for a promise to resolve
1079     * before running any `promise_test` tests.
1080     *
1081     * @param {Function} func - Function returning a promise that's
1082     * run synchronously. Promise tests are not run until after this
1083     * function has resolved.
1084     * @param {SettingsObject} [properties] - An object containing
1085     * the harness settings to use.
1086     *
1087     */
1088    function promise_setup(func, properties={})
1089    {
1090        if (typeof func !== "function") {
1091            tests.set_status(tests.status.ERROR,
1092                             "promise_test invoked without a function");
1093            tests.complete();
1094            return;
1095        }
1096        tests.promise_setup_called = true;
1097
1098        if (!tests.promise_tests) {
1099            tests.promise_tests = Promise.resolve();
1100        }
1101
1102        tests.promise_tests = tests.promise_tests
1103            .then(function()
1104                  {
1105                      var result;
1106
1107                      tests.setup(null, properties);
1108                      result = func();
1109                      test_environment.on_new_harness_properties(properties);
1110
1111                      if (!result || typeof result.then !== "function") {
1112                          throw "Non-thenable returned by function passed to `promise_setup`";
1113                      }
1114                      return result;
1115                  })
1116            .catch(function(e)
1117                   {
1118                       tests.set_status(tests.status.ERROR,
1119                                        String(e),
1120                                        e && e.stack);
1121                       tests.complete();
1122                   });
1123    }
1124
1125    /**
1126     * Mark test loading as complete.
1127     *
1128     * Typically this function is called implicitly on page load; it's
1129     * only necessary for users to call this when either the
1130     * ``explicit_done`` or ``single_page`` properties have been set
1131     * via the :js:func:`setup` function.
1132     *
1133     * For single page tests this marks the test as complete and sets its status.
1134     * For other tests, this marks test loading as complete, but doesn't affect ongoing tests.
1135     */
1136    function done() {
1137        if (tests.tests.length === 0) {
1138            // `done` is invoked after handling uncaught exceptions, so if the
1139            // harness status is already set, the corresponding message is more
1140            // descriptive than the generic message defined here.
1141            if (tests.status.status === null) {
1142                tests.status.status = tests.status.ERROR;
1143                tests.status.message = "done() was called without first defining any tests";
1144            }
1145
1146            tests.complete();
1147            return;
1148        }
1149        if (tests.file_is_test) {
1150            // file is test files never have asynchronous cleanup logic,
1151            // meaning the fully-synchronous `done` function can be used here.
1152            tests.tests[0].done();
1153        }
1154        tests.end_wait();
1155    }
1156
1157    /**
1158     * @deprecated generate a list of tests from a function and list of arguments
1159     *
1160     * This is deprecated because it runs all the tests outside of the test functions
1161     * and as a result any test throwing an exception will result in no tests being
1162     * run. In almost all cases, you should simply call test within the loop you would
1163     * use to generate the parameter list array.
1164     *
1165     * @param {Function} func - The function that will be called for each generated tests.
1166     * @param {Any[][]} args - An array of arrays. Each nested array
1167     * has the structure `[testName, ...testArgs]`. For each of these nested arrays
1168     * array, a test is generated with name `testName` and test function equivalent to
1169     * `func(..testArgs)`.
1170     */
1171    function generate_tests(func, args, properties) {
1172        forEach(args, function(x, i)
1173                {
1174                    var name = x[0];
1175                    test(function()
1176                         {
1177                             func.apply(this, x.slice(1));
1178                         },
1179                         name,
1180                         Array.isArray(properties) ? properties[i] : properties);
1181                });
1182    }
1183
1184    /**
1185     * @deprecated
1186     *
1187     * Register a function as a DOM event listener to the
1188     * given object for the event bubbling phase.
1189     *
1190     * @param {EventTarget} object - Event target
1191     * @param {string} event - Event name
1192     * @param {Function} callback - Event handler.
1193     */
1194    function on_event(object, event, callback)
1195    {
1196        object.addEventListener(event, callback, false);
1197    }
1198
1199    /**
1200     * Global version of :js:func:`Test.step_timeout` for use in single page tests.
1201     *
1202     * @param {Function} func - Function to run after the timeout
1203     * @param {number} timeout - Time in ms to wait before running the
1204     * test step. The actual wait time is ``timeout`` x
1205     * ``timeout_multiplier``.
1206     */
1207    function step_timeout(func, timeout) {
1208        var outer_this = this;
1209        var args = Array.prototype.slice.call(arguments, 2);
1210        return setTimeout(function() {
1211            func.apply(outer_this, args);
1212        }, timeout * tests.timeout_multiplier);
1213    }
1214
1215    expose(test, 'test');
1216    expose(async_test, 'async_test');
1217    expose(promise_test, 'promise_test');
1218    expose(promise_rejects_js, 'promise_rejects_js');
1219    expose(promise_rejects_dom, 'promise_rejects_dom');
1220    expose(promise_rejects_exactly, 'promise_rejects_exactly');
1221    expose(generate_tests, 'generate_tests');
1222    expose(setup, 'setup');
1223    expose(promise_setup, 'promise_setup');
1224    expose(done, 'done');
1225    expose(on_event, 'on_event');
1226    expose(step_timeout, 'step_timeout');
1227
1228    /*
1229     * Return a string truncated to the given length, with ... added at the end
1230     * if it was longer.
1231     */
1232    function truncate(s, len)
1233    {
1234        if (s.length > len) {
1235            return s.substring(0, len - 3) + "...";
1236        }
1237        return s;
1238    }
1239
1240    /*
1241     * Return true if object is probably a Node object.
1242     */
1243    function is_node(object)
1244    {
1245        // I use duck-typing instead of instanceof, because
1246        // instanceof doesn't work if the node is from another window (like an
1247        // iframe's contentWindow):
1248        // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
1249        try {
1250            var has_node_properties = ("nodeType" in object &&
1251                                       "nodeName" in object &&
1252                                       "nodeValue" in object &&
1253                                       "childNodes" in object);
1254        } catch (e) {
1255            // We're probably cross-origin, which means we aren't a node
1256            return false;
1257        }
1258
1259        if (has_node_properties) {
1260            try {
1261                object.nodeType;
1262            } catch (e) {
1263                // The object is probably Node.prototype or another prototype
1264                // object that inherits from it, and not a Node instance.
1265                return false;
1266            }
1267            return true;
1268        }
1269        return false;
1270    }
1271
1272    var replacements = {
1273        "0": "0",
1274        "1": "x01",
1275        "2": "x02",
1276        "3": "x03",
1277        "4": "x04",
1278        "5": "x05",
1279        "6": "x06",
1280        "7": "x07",
1281        "8": "b",
1282        "9": "t",
1283        "10": "n",
1284        "11": "v",
1285        "12": "f",
1286        "13": "r",
1287        "14": "x0e",
1288        "15": "x0f",
1289        "16": "x10",
1290        "17": "x11",
1291        "18": "x12",
1292        "19": "x13",
1293        "20": "x14",
1294        "21": "x15",
1295        "22": "x16",
1296        "23": "x17",
1297        "24": "x18",
1298        "25": "x19",
1299        "26": "x1a",
1300        "27": "x1b",
1301        "28": "x1c",
1302        "29": "x1d",
1303        "30": "x1e",
1304        "31": "x1f",
1305        "0xfffd": "ufffd",
1306        "0xfffe": "ufffe",
1307        "0xffff": "uffff",
1308    };
1309
1310    /**
1311     * Convert a value to a nice, human-readable string
1312     *
1313     * When many JavaScript Object values are coerced to a String, the
1314     * resulting value will be ``"[object Object]"``. This obscures
1315     * helpful information, making the coerced value unsuitable for
1316     * use in assertion messages, test names, and debugging
1317     * statements. `format_value` produces more distinctive string
1318     * representations of many kinds of objects, including arrays and
1319     * the more important DOM Node types. It also translates String
1320     * values containing control characters to include human-readable
1321     * representations.
1322     *
1323     * @example
1324     * // "Document node with 2 children"
1325     * format_value(document);
1326     * @example
1327     * // "\"foo\\uffffbar\""
1328     * format_value("foo\uffffbar");
1329     * @example
1330     * // "[-0, Infinity]"
1331     * format_value([-0, Infinity]);
1332     * @param {Any} val - The value to convert to a string.
1333     * @returns {string} - A string representation of ``val``, optimised for human readability.
1334     */
1335    function format_value(val, seen)
1336    {
1337        if (!seen) {
1338            seen = [];
1339        }
1340        if (typeof val === "object" && val !== null) {
1341            if (seen.indexOf(val) >= 0) {
1342                return "[...]";
1343            }
1344            seen.push(val);
1345        }
1346        if (Array.isArray(val)) {
1347            let output = "[";
1348            if (val.beginEllipsis !== undefined) {
1349                output += "…, ";
1350            }
1351            output += val.map(function(x) {return format_value(x, seen);}).join(", ");
1352            if (val.endEllipsis !== undefined) {
1353                output += ", …";
1354            }
1355            return output + "]";
1356        }
1357
1358        switch (typeof val) {
1359        case "string":
1360            val = val.replace(/\\/g, "\\\\");
1361            for (var p in replacements) {
1362                var replace = "\\" + replacements[p];
1363                val = val.replace(RegExp(String.fromCharCode(p), "g"), replace);
1364            }
1365            return '"' + val.replace(/"/g, '\\"') + '"';
1366        case "boolean":
1367        case "undefined":
1368            return String(val);
1369        case "number":
1370            // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
1371            // special-case.
1372            if (val === -0 && 1/val === -Infinity) {
1373                return "-0";
1374            }
1375            return String(val);
1376        case "object":
1377            if (val === null) {
1378                return "null";
1379            }
1380
1381            // Special-case Node objects, since those come up a lot in my tests.  I
1382            // ignore namespaces.
1383            if (is_node(val)) {
1384                switch (val.nodeType) {
1385                case Node.ELEMENT_NODE:
1386                    var ret = "<" + val.localName;
1387                    for (var i = 0; i < val.attributes.length; i++) {
1388                        ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';
1389                    }
1390                    ret += ">" + val.innerHTML + "</" + val.localName + ">";
1391                    return "Element node " + truncate(ret, 60);
1392                case Node.TEXT_NODE:
1393                    return 'Text node "' + truncate(val.data, 60) + '"';
1394                case Node.PROCESSING_INSTRUCTION_NODE:
1395                    return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));
1396                case Node.COMMENT_NODE:
1397                    return "Comment node <!--" + truncate(val.data, 60) + "-->";
1398                case Node.DOCUMENT_NODE:
1399                    return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
1400                case Node.DOCUMENT_TYPE_NODE:
1401                    return "DocumentType node";
1402                case Node.DOCUMENT_FRAGMENT_NODE:
1403                    return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
1404                default:
1405                    return "Node object of unknown type";
1406                }
1407            }
1408
1409        /* falls through */
1410        default:
1411            try {
1412                return typeof val + ' "' + truncate(String(val), 1000) + '"';
1413            } catch(e) {
1414                return ("[stringifying object threw " + String(e) +
1415                        " with type " + String(typeof e) + "]");
1416            }
1417        }
1418    }
1419    expose(format_value, "format_value");
1420
1421    /*
1422     * Assertions
1423     */
1424
1425    function expose_assert(f, name) {
1426        function assert_wrapper(...args) {
1427            let status = Test.statuses.TIMEOUT;
1428            let stack = null;
1429            try {
1430                if (settings.debug) {
1431                    console.debug("ASSERT", name, tests.current_test && tests.current_test.name, args);
1432                }
1433                if (tests.output) {
1434                    tests.set_assert(name, args);
1435                }
1436                const rv = f.apply(undefined, args);
1437                status = Test.statuses.PASS;
1438                return rv;
1439            } catch(e) {
1440                status = Test.statuses.FAIL;
1441                stack = e.stack ? e.stack : null;
1442                throw e;
1443            } finally {
1444                if (tests.output && !stack) {
1445                    stack = get_stack();
1446                }
1447                if (tests.output) {
1448                    tests.set_assert_status(status, stack);
1449                }
1450            }
1451        }
1452        expose(assert_wrapper, name);
1453    }
1454
1455    /**
1456     * Assert that ``actual`` is strictly true
1457     *
1458     * @param {Any} actual - Value that is asserted to be true
1459     * @param {string} [description] - Description of the condition being tested
1460     */
1461    function assert_true(actual, description)
1462    {
1463        assert(actual === true, "assert_true", description,
1464                                "expected true got ${actual}", {actual:actual});
1465    }
1466    expose_assert(assert_true, "assert_true");
1467
1468    /**
1469     * Assert that ``actual`` is strictly false
1470     *
1471     * @param {Any} actual - Value that is asserted to be false
1472     * @param {string} [description] - Description of the condition being tested
1473     */
1474    function assert_false(actual, description)
1475    {
1476        assert(actual === false, "assert_false", description,
1477                                 "expected false got ${actual}", {actual:actual});
1478    }
1479    expose_assert(assert_false, "assert_false");
1480
1481    function same_value(x, y) {
1482        if (y !== y) {
1483            //NaN case
1484            return x !== x;
1485        }
1486        if (x === 0 && y === 0) {
1487            //Distinguish +0 and -0
1488            return 1/x === 1/y;
1489        }
1490        return x === y;
1491    }
1492
1493    /**
1494     * Assert that ``actual`` is the same value as ``expected``.
1495     *
1496     * For objects this compares by cobject identity; for primitives
1497     * this distinguishes between 0 and -0, and has correct handling
1498     * of NaN.
1499     *
1500     * @param {Any} actual - Test value.
1501     * @param {Any} expected - Expected value.
1502     * @param {string} [description] - Description of the condition being tested.
1503     */
1504    function assert_equals(actual, expected, description)
1505    {
1506         /*
1507          * Test if two primitives are equal or two objects
1508          * are the same object
1509          */
1510        if (typeof actual != typeof expected) {
1511            assert(false, "assert_equals", description,
1512                          "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}",
1513                          {expected:expected, actual:actual});
1514            return;
1515        }
1516        assert(same_value(actual, expected), "assert_equals", description,
1517                                             "expected ${expected} but got ${actual}",
1518                                             {expected:expected, actual:actual});
1519    }
1520    expose_assert(assert_equals, "assert_equals");
1521
1522    /**
1523     * Assert that ``actual`` is not the same value as ``expected``.
1524     *
1525     * Comparison is as for :js:func:`assert_equals`.
1526     *
1527     * @param {Any} actual - Test value.
1528     * @param {Any} expected - The value ``actual`` is expected to be different to.
1529     * @param {string} [description] - Description of the condition being tested.
1530     */
1531    function assert_not_equals(actual, expected, description)
1532    {
1533        assert(!same_value(actual, expected), "assert_not_equals", description,
1534                                              "got disallowed value ${actual}",
1535                                              {actual:actual});
1536    }
1537    expose_assert(assert_not_equals, "assert_not_equals");
1538
1539    /**
1540     * Assert that ``expected`` is an array and ``actual`` is one of the members.
1541     * This is implemented using ``indexOf``, so doesn't handle NaN or ±0 correctly.
1542     *
1543     * @param {Any} actual - Test value.
1544     * @param {Array} expected - An array that ``actual`` is expected to
1545     * be a member of.
1546     * @param {string} [description] - Description of the condition being tested.
1547     */
1548    function assert_in_array(actual, expected, description)
1549    {
1550        assert(expected.indexOf(actual) != -1, "assert_in_array", description,
1551                                               "value ${actual} not in array ${expected}",
1552                                               {actual:actual, expected:expected});
1553    }
1554    expose_assert(assert_in_array, "assert_in_array");
1555
1556    // This function was deprecated in July of 2015.
1557    // See https://github.com/web-platform-tests/wpt/issues/2033
1558    /**
1559     * @deprecated
1560     * Recursively compare two objects for equality.
1561     *
1562     * See `Issue 2033
1563     * <https://github.com/web-platform-tests/wpt/issues/2033>`_ for
1564     * more information.
1565     *
1566     * @param {Object} actual - Test value.
1567     * @param {Object} expected - Expected value.
1568     * @param {string} [description] - Description of the condition being tested.
1569     */
1570    function assert_object_equals(actual, expected, description)
1571    {
1572         assert(typeof actual === "object" && actual !== null, "assert_object_equals", description,
1573                                                               "value is ${actual}, expected object",
1574                                                               {actual: actual});
1575         //This needs to be improved a great deal
1576         function check_equal(actual, expected, stack)
1577         {
1578             stack.push(actual);
1579
1580             var p;
1581             for (p in actual) {
1582                 assert(expected.hasOwnProperty(p), "assert_object_equals", description,
1583                                                    "unexpected property ${p}", {p:p});
1584
1585                 if (typeof actual[p] === "object" && actual[p] !== null) {
1586                     if (stack.indexOf(actual[p]) === -1) {
1587                         check_equal(actual[p], expected[p], stack);
1588                     }
1589                 } else {
1590                     assert(same_value(actual[p], expected[p]), "assert_object_equals", description,
1591                                                       "property ${p} expected ${expected} got ${actual}",
1592                                                       {p:p, expected:expected[p], actual:actual[p]});
1593                 }
1594             }
1595             for (p in expected) {
1596                 assert(actual.hasOwnProperty(p),
1597                        "assert_object_equals", description,
1598                        "expected property ${p} missing", {p:p});
1599             }
1600             stack.pop();
1601         }
1602         check_equal(actual, expected, []);
1603    }
1604    expose_assert(assert_object_equals, "assert_object_equals");
1605
1606    /**
1607     * Assert that ``actual`` and ``expected`` are both arrays, and that the array properties of
1608     * ``actual`` and ``expected`` are all the same value (as for :js:func:`assert_equals`).
1609     *
1610     * @param {Array} actual - Test array.
1611     * @param {Array} expected - Array that is expected to contain the same values as ``actual``.
1612     * @param {string} [description] - Description of the condition being tested.
1613     */
1614    function assert_array_equals(actual, expected, description)
1615    {
1616        const max_array_length = 20;
1617        function shorten_array(arr, offset = 0) {
1618            // Make ", …" only show up when it would likely reduce the length, not accounting for
1619            // fonts.
1620            if (arr.length < max_array_length + 2) {
1621                return arr;
1622            }
1623            // By default we want half the elements after the offset and half before
1624            // But if that takes us past the end of the array, we have more before, and
1625            // if it takes us before the start we have more after.
1626            const length_after_offset = Math.floor(max_array_length / 2);
1627            let upper_bound = Math.min(length_after_offset + offset, arr.length);
1628            const lower_bound = Math.max(upper_bound - max_array_length, 0);
1629
1630            if (lower_bound === 0) {
1631                upper_bound = max_array_length;
1632            }
1633
1634            const output = arr.slice(lower_bound, upper_bound);
1635            if (lower_bound > 0) {
1636                output.beginEllipsis = true;
1637            }
1638            if (upper_bound < arr.length) {
1639                output.endEllipsis = true;
1640            }
1641            return output;
1642        }
1643
1644        assert(typeof actual === "object" && actual !== null && "length" in actual,
1645               "assert_array_equals", description,
1646               "value is ${actual}, expected array",
1647               {actual:actual});
1648        assert(actual.length === expected.length,
1649               "assert_array_equals", description,
1650               "lengths differ, expected array ${expected} length ${expectedLength}, got ${actual} length ${actualLength}",
1651               {expected:shorten_array(expected, expected.length - 1), expectedLength:expected.length,
1652                actual:shorten_array(actual, actual.length - 1), actualLength:actual.length
1653               });
1654
1655        for (var i = 0; i < actual.length; i++) {
1656            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
1657                   "assert_array_equals", description,
1658                   "expected property ${i} to be ${expected} but was ${actual} (expected array ${arrayExpected} got ${arrayActual})",
1659                   {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
1660                    actual:actual.hasOwnProperty(i) ? "present" : "missing",
1661                    arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)});
1662            assert(same_value(expected[i], actual[i]),
1663                   "assert_array_equals", description,
1664                   "expected property ${i} to be ${expected} but got ${actual} (expected array ${arrayExpected} got ${arrayActual})",
1665                   {i:i, expected:expected[i], actual:actual[i],
1666                    arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)});
1667        }
1668    }
1669    expose_assert(assert_array_equals, "assert_array_equals");
1670
1671    /**
1672     * Assert that each array property in ``actual`` is a number within
1673     * ± `epsilon` of the corresponding property in `expected`.
1674     *
1675     * @param {Array} actual - Array of test values.
1676     * @param {Array} expected - Array of values expected to be close to the values in ``actual``.
1677     * @param {number} epsilon - Magnitude of allowed difference
1678     * between each value in ``actual`` and ``expected``.
1679     * @param {string} [description] - Description of the condition being tested.
1680     */
1681    function assert_array_approx_equals(actual, expected, epsilon, description)
1682    {
1683        /*
1684         * Test if two primitive arrays are equal within +/- epsilon
1685         */
1686        assert(actual.length === expected.length,
1687               "assert_array_approx_equals", description,
1688               "lengths differ, expected ${expected} got ${actual}",
1689               {expected:expected.length, actual:actual.length});
1690
1691        for (var i = 0; i < actual.length; i++) {
1692            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
1693                   "assert_array_approx_equals", description,
1694                   "property ${i}, property expected to be ${expected} but was ${actual}",
1695                   {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
1696                    actual:actual.hasOwnProperty(i) ? "present" : "missing"});
1697            assert(typeof actual[i] === "number",
1698                   "assert_array_approx_equals", description,
1699                   "property ${i}, expected a number but got a ${type_actual}",
1700                   {i:i, type_actual:typeof actual[i]});
1701            assert(Math.abs(actual[i] - expected[i]) <= epsilon,
1702                   "assert_array_approx_equals", description,
1703                   "property ${i}, expected ${expected} +/- ${epsilon}, expected ${expected} but got ${actual}",
1704                   {i:i, expected:expected[i], actual:actual[i], epsilon:epsilon});
1705        }
1706    }
1707    expose_assert(assert_array_approx_equals, "assert_array_approx_equals");
1708
1709    /**
1710     * Assert that ``actual`` is within ± ``epsilon`` of ``expected``.
1711     *
1712     * @param {number} actual - Test value.
1713     * @param {number} expected - Value number is expected to be close to.
1714     * @param {number} epsilon - Magnitude of allowed difference between ``actual`` and ``expected``.
1715     * @param {string} [description] - Description of the condition being tested.
1716     */
1717    function assert_approx_equals(actual, expected, epsilon, description)
1718    {
1719        /*
1720         * Test if two primitive numbers are equal within +/- epsilon
1721         */
1722        assert(typeof actual === "number",
1723               "assert_approx_equals", description,
1724               "expected a number but got a ${type_actual}",
1725               {type_actual:typeof actual});
1726
1727        // The epsilon math below does not place nice with NaN and Infinity
1728        // But in this case Infinity = Infinity and NaN = NaN
1729        if (isFinite(actual) || isFinite(expected)) {
1730            assert(Math.abs(actual - expected) <= epsilon,
1731                   "assert_approx_equals", description,
1732                   "expected ${expected} +/- ${epsilon} but got ${actual}",
1733                   {expected:expected, actual:actual, epsilon:epsilon});
1734        } else {
1735            assert_equals(actual, expected);
1736        }
1737    }
1738    expose_assert(assert_approx_equals, "assert_approx_equals");
1739
1740    /**
1741     * Assert that ``actual`` is a number less than ``expected``.
1742     *
1743     * @param {number} actual - Test value.
1744     * @param {number} expected - Number that ``actual`` must be less than.
1745     * @param {string} [description] - Description of the condition being tested.
1746     */
1747    function assert_less_than(actual, expected, description)
1748    {
1749        /*
1750         * Test if a primitive number is less than another
1751         */
1752        assert(typeof actual === "number",
1753               "assert_less_than", description,
1754               "expected a number but got a ${type_actual}",
1755               {type_actual:typeof actual});
1756
1757        assert(actual < expected,
1758               "assert_less_than", description,
1759               "expected a number less than ${expected} but got ${actual}",
1760               {expected:expected, actual:actual});
1761    }
1762    expose_assert(assert_less_than, "assert_less_than");
1763
1764    /**
1765     * Assert that ``actual`` is a number greater than ``expected``.
1766     *
1767     * @param {number} actual - Test value.
1768     * @param {number} expected - Number that ``actual`` must be greater than.
1769     * @param {string} [description] - Description of the condition being tested.
1770     */
1771    function assert_greater_than(actual, expected, description)
1772    {
1773        /*
1774         * Test if a primitive number is greater than another
1775         */
1776        assert(typeof actual === "number",
1777               "assert_greater_than", description,
1778               "expected a number but got a ${type_actual}",
1779               {type_actual:typeof actual});
1780
1781        assert(actual > expected,
1782               "assert_greater_than", description,
1783               "expected a number greater than ${expected} but got ${actual}",
1784               {expected:expected, actual:actual});
1785    }
1786    expose_assert(assert_greater_than, "assert_greater_than");
1787
1788    /**
1789     * Assert that ``actual`` is a number greater than ``lower`` and less
1790     * than ``upper`` but not equal to either.
1791     *
1792     * @param {number} actual - Test value.
1793     * @param {number} lower - Number that ``actual`` must be greater than.
1794     * @param {number} upper - Number that ``actual`` must be less than.
1795     * @param {string} [description] - Description of the condition being tested.
1796     */
1797    function assert_between_exclusive(actual, lower, upper, description)
1798    {
1799        /*
1800         * Test if a primitive number is between two others
1801         */
1802        assert(typeof actual === "number",
1803               "assert_between_exclusive", description,
1804               "expected a number but got a ${type_actual}",
1805               {type_actual:typeof actual});
1806
1807        assert(actual > lower && actual < upper,
1808               "assert_between_exclusive", description,
1809               "expected a number greater than ${lower} " +
1810               "and less than ${upper} but got ${actual}",
1811               {lower:lower, upper:upper, actual:actual});
1812    }
1813    expose_assert(assert_between_exclusive, "assert_between_exclusive");
1814
1815    /**
1816     * Assert that ``actual`` is a number less than or equal to ``expected``.
1817     *
1818     * @param {number} actual - Test value.
1819     * @param {number} expected - Number that ``actual`` must be less
1820     * than or equal to.
1821     * @param {string} [description] - Description of the condition being tested.
1822     */
1823    function assert_less_than_equal(actual, expected, description)
1824    {
1825        /*
1826         * Test if a primitive number is less than or equal to another
1827         */
1828        assert(typeof actual === "number",
1829               "assert_less_than_equal", description,
1830               "expected a number but got a ${type_actual}",
1831               {type_actual:typeof actual});
1832
1833        assert(actual <= expected,
1834               "assert_less_than_equal", description,
1835               "expected a number less than or equal to ${expected} but got ${actual}",
1836               {expected:expected, actual:actual});
1837    }
1838    expose_assert(assert_less_than_equal, "assert_less_than_equal");
1839
1840    /**
1841     * Assert that ``actual`` is a number greater than or equal to ``expected``.
1842     *
1843     * @param {number} actual - Test value.
1844     * @param {number} expected - Number that ``actual`` must be greater
1845     * than or equal to.
1846     * @param {string} [description] - Description of the condition being tested.
1847     */
1848    function assert_greater_than_equal(actual, expected, description)
1849    {
1850        /*
1851         * Test if a primitive number is greater than or equal to another
1852         */
1853        assert(typeof actual === "number",
1854               "assert_greater_than_equal", description,
1855               "expected a number but got a ${type_actual}",
1856               {type_actual:typeof actual});
1857
1858        assert(actual >= expected,
1859               "assert_greater_than_equal", description,
1860               "expected a number greater than or equal to ${expected} but got ${actual}",
1861               {expected:expected, actual:actual});
1862    }
1863    expose_assert(assert_greater_than_equal, "assert_greater_than_equal");
1864
1865    /**
1866     * Assert that ``actual`` is a number greater than or equal to ``lower`` and less
1867     * than or equal to ``upper``.
1868     *
1869     * @param {number} actual - Test value.
1870     * @param {number} lower - Number that ``actual`` must be greater than or equal to.
1871     * @param {number} upper - Number that ``actual`` must be less than or equal to.
1872     * @param {string} [description] - Description of the condition being tested.
1873     */
1874    function assert_between_inclusive(actual, lower, upper, description)
1875    {
1876        /*
1877         * Test if a primitive number is between to two others or equal to either of them
1878         */
1879        assert(typeof actual === "number",
1880               "assert_between_inclusive", description,
1881               "expected a number but got a ${type_actual}",
1882               {type_actual:typeof actual});
1883
1884        assert(actual >= lower && actual <= upper,
1885               "assert_between_inclusive", description,
1886               "expected a number greater than or equal to ${lower} " +
1887               "and less than or equal to ${upper} but got ${actual}",
1888               {lower:lower, upper:upper, actual:actual});
1889    }
1890    expose_assert(assert_between_inclusive, "assert_between_inclusive");
1891
1892    /**
1893     * Assert that ``actual`` matches the RegExp ``expected``.
1894     *
1895     * @param {String} actual - Test string.
1896     * @param {RegExp} expected - RegExp ``actual`` must match.
1897     * @param {string} [description] - Description of the condition being tested.
1898     */
1899    function assert_regexp_match(actual, expected, description) {
1900        /*
1901         * Test if a string (actual) matches a regexp (expected)
1902         */
1903        assert(expected.test(actual),
1904               "assert_regexp_match", description,
1905               "expected ${expected} but got ${actual}",
1906               {expected:expected, actual:actual});
1907    }
1908    expose_assert(assert_regexp_match, "assert_regexp_match");
1909
1910    /**
1911     * Assert that the class string of ``object`` as returned in
1912     * ``Object.prototype.toString`` is equal to ``class_name``.
1913     *
1914     * @param {Object} object - Object to stringify.
1915     * @param {string} class_string - Expected class string for ``object``.
1916     * @param {string} [description] - Description of the condition being tested.
1917     */
1918    function assert_class_string(object, class_string, description) {
1919        var actual = {}.toString.call(object);
1920        var expected = "[object " + class_string + "]";
1921        assert(same_value(actual, expected), "assert_class_string", description,
1922                                             "expected ${expected} but got ${actual}",
1923                                             {expected:expected, actual:actual});
1924    }
1925    expose_assert(assert_class_string, "assert_class_string");
1926
1927    /**
1928     * Assert that ``object`` has an own property with name ``property_name``.
1929     *
1930     * @param {Object} object - Object that should have the given property.
1931     * @param {string} property_name - Expected property name.
1932     * @param {string} [description] - Description of the condition being tested.
1933     */
1934    function assert_own_property(object, property_name, description) {
1935        assert(object.hasOwnProperty(property_name),
1936               "assert_own_property", description,
1937               "expected property ${p} missing", {p:property_name});
1938    }
1939    expose_assert(assert_own_property, "assert_own_property");
1940
1941    /**
1942     * Assert that ``object`` does not have an own property with name ``property_name``.
1943     *
1944     * @param {Object} object - Object that should not have the given property.
1945     * @param {string} property_name - Property name to test.
1946     * @param {string} [description] - Description of the condition being tested.
1947     */
1948    function assert_not_own_property(object, property_name, description) {
1949        assert(!object.hasOwnProperty(property_name),
1950               "assert_not_own_property", description,
1951               "unexpected property ${p} is found on object", {p:property_name});
1952    }
1953    expose_assert(assert_not_own_property, "assert_not_own_property");
1954
1955    function _assert_inherits(name) {
1956        return function (object, property_name, description)
1957        {
1958            assert((typeof object === "object" && object !== null) ||
1959                   typeof object === "function" ||
1960                   // Or has [[IsHTMLDDA]] slot
1961                   String(object) === "[object HTMLAllCollection]",
1962                   name, description,
1963                   "provided value is not an object");
1964
1965            assert("hasOwnProperty" in object,
1966                   name, description,
1967                   "provided value is an object but has no hasOwnProperty method");
1968
1969            assert(!object.hasOwnProperty(property_name),
1970                   name, description,
1971                   "property ${p} found on object expected in prototype chain",
1972                   {p:property_name});
1973
1974            assert(property_name in object,
1975                   name, description,
1976                   "property ${p} not found in prototype chain",
1977                   {p:property_name});
1978        };
1979    }
1980
1981    /**
1982     * Assert that ``object`` does not have an own property with name
1983     * ``property_name``, but inherits one through the prototype chain.
1984     *
1985     * @param {Object} object - Object that should have the given property in its prototype chain.
1986     * @param {string} property_name - Expected property name.
1987     * @param {string} [description] - Description of the condition being tested.
1988     */
1989    function assert_inherits(object, property_name, description) {
1990        return _assert_inherits("assert_inherits")(object, property_name, description);
1991    }
1992    expose_assert(assert_inherits, "assert_inherits");
1993
1994    /**
1995     * Alias for :js:func:`insert_inherits`.
1996     *
1997     * @param {Object} object - Object that should have the given property in its prototype chain.
1998     * @param {string} property_name - Expected property name.
1999     * @param {string} [description] - Description of the condition being tested.
2000     */
2001    function assert_idl_attribute(object, property_name, description) {
2002        return _assert_inherits("assert_idl_attribute")(object, property_name, description);
2003    }
2004    expose_assert(assert_idl_attribute, "assert_idl_attribute");
2005
2006
2007    /**
2008     * Assert that ``object`` has a property named ``property_name`` and that the property is readonly.
2009     *
2010     * Note: The implementation tries to update the named property, so
2011     * any side effects of updating will be triggered. Users are
2012     * encouraged to instead inspect the property descriptor of ``property_name`` on ``object``.
2013     *
2014     * @param {Object} object - Object that should have the given property in its prototype chain.
2015     * @param {string} property_name - Expected property name.
2016     * @param {string} [description] - Description of the condition being tested.
2017     */
2018    function assert_readonly(object, property_name, description)
2019    {
2020         var initial_value = object[property_name];
2021         try {
2022             //Note that this can have side effects in the case where
2023             //the property has PutForwards
2024             object[property_name] = initial_value + "a"; //XXX use some other value here?
2025             assert(same_value(object[property_name], initial_value),
2026                    "assert_readonly", description,
2027                    "changing property ${p} succeeded",
2028                    {p:property_name});
2029         } finally {
2030             object[property_name] = initial_value;
2031         }
2032    }
2033    expose_assert(assert_readonly, "assert_readonly");
2034
2035    /**
2036     * Assert a JS Error with the expected constructor is thrown.
2037     *
2038     * @param {object} constructor The expected exception constructor.
2039     * @param {Function} func Function which should throw.
2040     * @param {string} [description] Error description for the case that the error is not thrown.
2041     */
2042    function assert_throws_js(constructor, func, description)
2043    {
2044        assert_throws_js_impl(constructor, func, description,
2045                              "assert_throws_js");
2046    }
2047    expose_assert(assert_throws_js, "assert_throws_js");
2048
2049    /**
2050     * Like assert_throws_js but allows specifying the assertion type
2051     * (assert_throws_js or promise_rejects_js, in practice).
2052     */
2053    function assert_throws_js_impl(constructor, func, description,
2054                                   assertion_type)
2055    {
2056        try {
2057            func.call(this);
2058            assert(false, assertion_type, description,
2059                   "${func} did not throw", {func:func});
2060        } catch (e) {
2061            if (e instanceof AssertionError) {
2062                throw e;
2063            }
2064
2065            // Basic sanity-checks on the thrown exception.
2066            assert(typeof e === "object",
2067                   assertion_type, description,
2068                   "${func} threw ${e} with type ${type}, not an object",
2069                   {func:func, e:e, type:typeof e});
2070
2071            assert(e !== null,
2072                   assertion_type, description,
2073                   "${func} threw null, not an object",
2074                   {func:func});
2075
2076            // Basic sanity-check on the passed-in constructor
2077            assert(typeof constructor == "function",
2078                   assertion_type, description,
2079                   "${constructor} is not a constructor",
2080                   {constructor:constructor});
2081            var obj = constructor;
2082            while (obj) {
2083                if (typeof obj === "function" &&
2084                    obj.name === "Error") {
2085                    break;
2086                }
2087                obj = Object.getPrototypeOf(obj);
2088            }
2089            assert(obj != null,
2090                   assertion_type, description,
2091                   "${constructor} is not an Error subtype",
2092                   {constructor:constructor});
2093
2094            // And checking that our exception is reasonable
2095            assert(e.constructor === constructor &&
2096                   e.name === constructor.name,
2097                   assertion_type, description,
2098                   "${func} threw ${actual} (${actual_name}) expected instance of ${expected} (${expected_name})",
2099                   {func:func, actual:e, actual_name:e.name,
2100                    expected:constructor,
2101                    expected_name:constructor.name});
2102        }
2103    }
2104
2105    // TODO: Figure out how to document the overloads better.
2106    // sphinx-js doesn't seem to handle @variation correctly,
2107    // and only expects a single JSDoc entry per function.
2108    /**
2109     * Assert a DOMException with the expected type is thrown.
2110     *
2111     * There are two ways of calling assert_throws_dom:
2112     *
2113     * 1) If the DOMException is expected to come from the current global, the
2114     * second argument should be the function expected to throw and a third,
2115     * optional, argument is the assertion description.
2116     *
2117     * 2) If the DOMException is expected to come from some other global, the
2118     * second argument should be the DOMException constructor from that global,
2119     * the third argument the function expected to throw, and the fourth, optional,
2120     * argument the assertion description.
2121     *
2122     * @param {number|string} type - The expected exception name or
2123     * code.  See the `table of names and codes
2124     * <https://webidl.spec.whatwg.org/#dfn-error-names-table>`_. If a
2125     * number is passed it should be one of the numeric code values in
2126     * that table (e.g. 3, 4, etc).  If a string is passed it can
2127     * either be an exception name (e.g. "HierarchyRequestError",
2128     * "WrongDocumentError") or the name of the corresponding error
2129     * code (e.g. "``HIERARCHY_REQUEST_ERR``", "``WRONG_DOCUMENT_ERR``").
2130     * @param {Function} descriptionOrFunc - The function expected to
2131     * throw (if the exception comes from another global), or the
2132     * optional description of the condition being tested (if the
2133     * exception comes from the current global).
2134     * @param {string} [description] - Description of the condition
2135     * being tested (if the exception comes from another global).
2136     *
2137     */
2138    function assert_throws_dom(type, funcOrConstructor, descriptionOrFunc, maybeDescription)
2139    {
2140        let constructor, func, description;
2141        if (funcOrConstructor.name === "DOMException") {
2142            constructor = funcOrConstructor;
2143            func = descriptionOrFunc;
2144            description = maybeDescription;
2145        } else {
2146            constructor = self.DOMException;
2147            func = funcOrConstructor;
2148            description = descriptionOrFunc;
2149            assert(maybeDescription === undefined,
2150                   "Too many args pased to no-constructor version of assert_throws_dom");
2151        }
2152        assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor)
2153    }
2154    expose_assert(assert_throws_dom, "assert_throws_dom");
2155
2156    /**
2157     * Similar to assert_throws_dom but allows specifying the assertion type
2158     * (assert_throws_dom or promise_rejects_dom, in practice).  The
2159     * "constructor" argument must be the DOMException constructor from the
2160     * global we expect the exception to come from.
2161     */
2162    function assert_throws_dom_impl(type, func, description, assertion_type, constructor)
2163    {
2164        try {
2165            func.call(this);
2166            assert(false, assertion_type, description,
2167                   "${func} did not throw", {func:func});
2168        } catch (e) {
2169            if (e instanceof AssertionError) {
2170                throw e;
2171            }
2172
2173            // Basic sanity-checks on the thrown exception.
2174            assert(typeof e === "object",
2175                   assertion_type, description,
2176                   "${func} threw ${e} with type ${type}, not an object",
2177                   {func:func, e:e, type:typeof e});
2178
2179            assert(e !== null,
2180                   assertion_type, description,
2181                   "${func} threw null, not an object",
2182                   {func:func});
2183
2184            // Sanity-check our type
2185            assert(typeof type == "number" ||
2186                   typeof type == "string",
2187                   assertion_type, description,
2188                   "${type} is not a number or string",
2189                   {type:type});
2190
2191            var codename_name_map = {
2192                INDEX_SIZE_ERR: 'IndexSizeError',
2193                HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
2194                WRONG_DOCUMENT_ERR: 'WrongDocumentError',
2195                INVALID_CHARACTER_ERR: 'InvalidCharacterError',
2196                NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
2197                NOT_FOUND_ERR: 'NotFoundError',
2198                NOT_SUPPORTED_ERR: 'NotSupportedError',
2199                INUSE_ATTRIBUTE_ERR: 'InUseAttributeError',
2200                INVALID_STATE_ERR: 'InvalidStateError',
2201                SYNTAX_ERR: 'SyntaxError',
2202                INVALID_MODIFICATION_ERR: 'InvalidModificationError',
2203                NAMESPACE_ERR: 'NamespaceError',
2204                INVALID_ACCESS_ERR: 'InvalidAccessError',
2205                TYPE_MISMATCH_ERR: 'TypeMismatchError',
2206                SECURITY_ERR: 'SecurityError',
2207                NETWORK_ERR: 'NetworkError',
2208                ABORT_ERR: 'AbortError',
2209                URL_MISMATCH_ERR: 'URLMismatchError',
2210                QUOTA_EXCEEDED_ERR: 'QuotaExceededError',
2211                TIMEOUT_ERR: 'TimeoutError',
2212                INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError',
2213                DATA_CLONE_ERR: 'DataCloneError'
2214            };
2215
2216            var name_code_map = {
2217                IndexSizeError: 1,
2218                HierarchyRequestError: 3,
2219                WrongDocumentError: 4,
2220                InvalidCharacterError: 5,
2221                NoModificationAllowedError: 7,
2222                NotFoundError: 8,
2223                NotSupportedError: 9,
2224                InUseAttributeError: 10,
2225                InvalidStateError: 11,
2226                SyntaxError: 12,
2227                InvalidModificationError: 13,
2228                NamespaceError: 14,
2229                InvalidAccessError: 15,
2230                TypeMismatchError: 17,
2231                SecurityError: 18,
2232                NetworkError: 19,
2233                AbortError: 20,
2234                URLMismatchError: 21,
2235                QuotaExceededError: 22,
2236                TimeoutError: 23,
2237                InvalidNodeTypeError: 24,
2238                DataCloneError: 25,
2239
2240                EncodingError: 0,
2241                NotReadableError: 0,
2242                UnknownError: 0,
2243                ConstraintError: 0,
2244                DataError: 0,
2245                TransactionInactiveError: 0,
2246                ReadOnlyError: 0,
2247                VersionError: 0,
2248                OperationError: 0,
2249                NotAllowedError: 0
2250            };
2251
2252            var code_name_map = {};
2253            for (var key in name_code_map) {
2254                if (name_code_map[key] > 0) {
2255                    code_name_map[name_code_map[key]] = key;
2256                }
2257            }
2258
2259            var required_props = {};
2260            var name;
2261
2262            if (typeof type === "number") {
2263                if (type === 0) {
2264                    throw new AssertionError('Test bug: ambiguous DOMException code 0 passed to assert_throws_dom()');
2265                } else if (!(type in code_name_map)) {
2266                    throw new AssertionError('Test bug: unrecognized DOMException code "' + type + '" passed to assert_throws_dom()');
2267                }
2268                name = code_name_map[type];
2269                required_props.code = type;
2270            } else if (typeof type === "string") {
2271                name = type in codename_name_map ? codename_name_map[type] : type;
2272                if (!(name in name_code_map)) {
2273                    throw new AssertionError('Test bug: unrecognized DOMException code name or name "' + type + '" passed to assert_throws_dom()');
2274                }
2275
2276                required_props.code = name_code_map[name];
2277            }
2278
2279            if (required_props.code === 0 ||
2280               ("name" in e &&
2281                e.name !== e.name.toUpperCase() &&
2282                e.name !== "DOMException")) {
2283                // New style exception: also test the name property.
2284                required_props.name = name;
2285            }
2286
2287            for (var prop in required_props) {
2288                assert(prop in e && e[prop] == required_props[prop],
2289                       assertion_type, description,
2290                       "${func} threw ${e} that is not a DOMException " + type + ": property ${prop} is equal to ${actual}, expected ${expected}",
2291                       {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
2292            }
2293
2294            // Check that the exception is from the right global.  This check is last
2295            // so more specific, and more informative, checks on the properties can
2296            // happen in case a totally incorrect exception is thrown.
2297            assert(e.constructor === constructor,
2298                   assertion_type, description,
2299                   "${func} threw an exception from the wrong global",
2300                   {func});
2301
2302        }
2303    }
2304
2305    /**
2306     * Assert the provided value is thrown.
2307     *
2308     * @param {value} exception The expected exception.
2309     * @param {Function} func Function which should throw.
2310     * @param {string} [description] Error description for the case that the error is not thrown.
2311     */
2312    function assert_throws_exactly(exception, func, description)
2313    {
2314        assert_throws_exactly_impl(exception, func, description,
2315                                   "assert_throws_exactly");
2316    }
2317    expose_assert(assert_throws_exactly, "assert_throws_exactly");
2318
2319    /**
2320     * Like assert_throws_exactly but allows specifying the assertion type
2321     * (assert_throws_exactly or promise_rejects_exactly, in practice).
2322     */
2323    function assert_throws_exactly_impl(exception, func, description,
2324                                        assertion_type)
2325    {
2326        try {
2327            func.call(this);
2328            assert(false, assertion_type, description,
2329                   "${func} did not throw", {func:func});
2330        } catch (e) {
2331            if (e instanceof AssertionError) {
2332                throw e;
2333            }
2334
2335            assert(same_value(e, exception), assertion_type, description,
2336                   "${func} threw ${e} but we expected it to throw ${exception}",
2337                   {func:func, e:e, exception:exception});
2338        }
2339    }
2340
2341    /**
2342     * Asserts if called. Used to ensure that a specific codepath is
2343     * not taken e.g. that an error event isn't fired.
2344     *
2345     * @param {string} [description] - Description of the condition being tested.
2346     */
2347    function assert_unreached(description) {
2348         assert(false, "assert_unreached", description,
2349                "Reached unreachable code");
2350    }
2351    expose_assert(assert_unreached, "assert_unreached");
2352
2353    /**
2354     * @callback AssertFunc
2355     * @param {Any} actual
2356     * @param {Any} expected
2357     * @param {Any[]} args
2358     */
2359
2360    /**
2361     * Asserts that ``actual`` matches at least one value of ``expected``
2362     * according to a comparison defined by ``assert_func``.
2363     *
2364     * Note that tests with multiple allowed pass conditions are bad
2365     * practice unless the spec specifically allows multiple
2366     * behaviours. Test authors should not use this method simply to
2367     * hide UA bugs.
2368     *
2369     * @param {AssertFunc} assert_func - Function to compare actual
2370     * and expected. It must throw when the comparison fails and
2371     * return when the comparison passes.
2372     * @param {Any} actual - Test value.
2373     * @param {Array} expected_array - Array of possible expected values.
2374     * @param {Any[]} args - Additional arguments to pass to ``assert_func``.
2375     */
2376    function assert_any(assert_func, actual, expected_array, ...args)
2377    {
2378        var errors = [];
2379        var passed = false;
2380        forEach(expected_array,
2381                function(expected)
2382                {
2383                    try {
2384                        assert_func.apply(this, [actual, expected].concat(args));
2385                        passed = true;
2386                    } catch (e) {
2387                        errors.push(e.message);
2388                    }
2389                });
2390        if (!passed) {
2391            throw new AssertionError(errors.join("\n\n"));
2392        }
2393    }
2394    // FIXME: assert_any cannot use expose_assert, because assert_wrapper does
2395    // not support nested assert calls (e.g. to assert_func). We need to
2396    // support bypassing assert_wrapper for the inner asserts here.
2397    expose(assert_any, "assert_any");
2398
2399    /**
2400     * Assert that a feature is implemented, based on a 'truthy' condition.
2401     *
2402     * This function should be used to early-exit from tests in which there is
2403     * no point continuing without support for a non-optional spec or spec
2404     * feature. For example:
2405     *
2406     *     assert_implements(window.Foo, 'Foo is not supported');
2407     *
2408     * @param {object} condition The truthy value to test
2409     * @param {string} [description] Error description for the case that the condition is not truthy.
2410     */
2411    function assert_implements(condition, description) {
2412        assert(!!condition, "assert_implements", description);
2413    }
2414    expose_assert(assert_implements, "assert_implements")
2415
2416    /**
2417     * Assert that an optional feature is implemented, based on a 'truthy' condition.
2418     *
2419     * This function should be used to early-exit from tests in which there is
2420     * no point continuing without support for an explicitly optional spec or
2421     * spec feature. For example:
2422     *
2423     *     assert_implements_optional(video.canPlayType("video/webm"),
2424     *                                "webm video playback not supported");
2425     *
2426     * @param {object} condition The truthy value to test
2427     * @param {string} [description] Error description for the case that the condition is not truthy.
2428     */
2429    function assert_implements_optional(condition, description) {
2430        if (!condition) {
2431            throw new OptionalFeatureUnsupportedError(description);
2432        }
2433    }
2434    expose_assert(assert_implements_optional, "assert_implements_optional");
2435
2436    /**
2437     * @class
2438     *
2439     * A single subtest. A Test is not constructed directly but via the
2440     * :js:func:`test`, :js:func:`async_test` or :js:func:`promise_test` functions.
2441     *
2442     * @param {string} name - This must be unique in a given file and must be
2443     * invariant between runs.
2444     *
2445     */
2446    function Test(name, properties)
2447    {
2448        if (tests.file_is_test && tests.tests.length) {
2449            throw new Error("Tried to create a test with file_is_test");
2450        }
2451        /** The test name. */
2452        this.name = name;
2453
2454        this.phase = (tests.is_aborted || tests.phase === tests.phases.COMPLETE) ?
2455            this.phases.COMPLETE : this.phases.INITIAL;
2456
2457        /** The test status code.*/
2458        this.status = this.NOTRUN;
2459        this.timeout_id = null;
2460        this.index = null;
2461
2462        this.properties = properties || {};
2463        this.timeout_length = settings.test_timeout;
2464        if (this.timeout_length !== null) {
2465            this.timeout_length *= tests.timeout_multiplier;
2466        }
2467
2468        /** A message indicating the reason for test failure. */
2469        this.message = null;
2470        /** Stack trace in case of failure. */
2471        this.stack = null;
2472
2473        this.steps = [];
2474        this._is_promise_test = false;
2475
2476        this.cleanup_callbacks = [];
2477        this._user_defined_cleanup_count = 0;
2478        this._done_callbacks = [];
2479
2480        // Tests declared following harness completion are likely an indication
2481        // of a programming error, but they cannot be reported
2482        // deterministically.
2483        if (tests.phase === tests.phases.COMPLETE) {
2484            return;
2485        }
2486
2487        tests.push(this);
2488    }
2489
2490    /**
2491     * Enum of possible test statuses.
2492     *
2493     * :values:
2494     *   - ``PASS``
2495     *   - ``FAIL``
2496     *   - ``TIMEOUT``
2497     *   - ``NOTRUN``
2498     *   - ``PRECONDITION_FAILED``
2499     */
2500    Test.statuses = {
2501        PASS:0,
2502        FAIL:1,
2503        TIMEOUT:2,
2504        NOTRUN:3,
2505        PRECONDITION_FAILED:4
2506    };
2507
2508    Test.prototype = merge({}, Test.statuses);
2509
2510    Test.prototype.phases = {
2511        INITIAL:0,
2512        STARTED:1,
2513        HAS_RESULT:2,
2514        CLEANING:3,
2515        COMPLETE:4
2516    };
2517
2518    Test.prototype.status_formats = {
2519        0: "Pass",
2520        1: "Fail",
2521        2: "Timeout",
2522        3: "Not Run",
2523        4: "Optional Feature Unsupported",
2524    }
2525
2526    Test.prototype.format_status = function() {
2527        return this.status_formats[this.status];
2528    }
2529
2530    Test.prototype.structured_clone = function()
2531    {
2532        if (!this._structured_clone) {
2533            var msg = this.message;
2534            msg = msg ? String(msg) : msg;
2535            this._structured_clone = merge({
2536                name:String(this.name),
2537                properties:merge({}, this.properties),
2538                phases:merge({}, this.phases)
2539            }, Test.statuses);
2540        }
2541        this._structured_clone.status = this.status;
2542        this._structured_clone.message = this.message;
2543        this._structured_clone.stack = this.stack;
2544        this._structured_clone.index = this.index;
2545        this._structured_clone.phase = this.phase;
2546        return this._structured_clone;
2547    };
2548
2549    /**
2550     * Run a single step of an ongoing test.
2551     *
2552     * @param {string} func - Callback function to run as a step. If
2553     * this throws an :js:func:`AssertionError`, or any other
2554     * exception, the :js:class:`Test` status is set to ``FAIL``.
2555     * @param {Object} [this_obj] - The object to use as the this
2556     * value when calling ``func``. Defaults to the  :js:class:`Test` object.
2557     */
2558    Test.prototype.step = function(func, this_obj)
2559    {
2560        if (this.phase > this.phases.STARTED) {
2561            return;
2562        }
2563
2564        if (settings.debug && this.phase !== this.phases.STARTED) {
2565            console.log("TEST START", this.name);
2566        }
2567        this.phase = this.phases.STARTED;
2568        //If we don't get a result before the harness times out that will be a test timeout
2569        this.set_status(this.TIMEOUT, "Test timed out");
2570
2571        tests.started = true;
2572        tests.current_test = this;
2573        tests.notify_test_state(this);
2574
2575        if (this.timeout_id === null) {
2576            this.set_timeout();
2577        }
2578
2579        this.steps.push(func);
2580
2581        if (arguments.length === 1) {
2582            this_obj = this;
2583        }
2584
2585        if (settings.debug) {
2586            console.debug("TEST STEP", this.name);
2587        }
2588
2589        try {
2590            return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
2591        } catch (e) {
2592            if (this.phase >= this.phases.HAS_RESULT) {
2593                return;
2594            }
2595            var status = e instanceof OptionalFeatureUnsupportedError ? this.PRECONDITION_FAILED : this.FAIL;
2596            var message = String((typeof e === "object" && e !== null) ? e.message : e);
2597            var stack = e.stack ? e.stack : null;
2598
2599            this.set_status(status, message, stack);
2600            this.phase = this.phases.HAS_RESULT;
2601            this.done();
2602        } finally {
2603            this.current_test = null;
2604        }
2605    };
2606
2607    /**
2608     * Wrap a function so that it runs as a step of the current test.
2609     *
2610     * This allows creating a callback function that will run as a
2611     * test step.
2612     *
2613     * @example
2614     * let t = async_test("Example");
2615     * onload = t.step_func(e => {
2616     *   assert_equals(e.name, "load");
2617     *   // Mark the test as complete.
2618     *   t.done();
2619     * })
2620     *
2621     * @param {string} func - Function to run as a step. If this
2622     * throws an :js:func:`AssertionError`, or any other exception,
2623     * the :js:class:`Test` status is set to ``FAIL``.
2624     * @param {Object} [this_obj] - The object to use as the this
2625     * value when calling ``func``. Defaults to the :js:class:`Test` object.
2626     */
2627    Test.prototype.step_func = function(func, this_obj)
2628    {
2629        var test_this = this;
2630
2631        if (arguments.length === 1) {
2632            this_obj = test_this;
2633        }
2634
2635        return function()
2636        {
2637            return test_this.step.apply(test_this, [func, this_obj].concat(
2638                Array.prototype.slice.call(arguments)));
2639        };
2640    };
2641
2642    /**
2643     * Wrap a function so that it runs as a step of the current test,
2644     * and automatically marks the test as complete if the function
2645     * returns without error.
2646     *
2647     * @param {string} func - Function to run as a step. If this
2648     * throws an :js:func:`AssertionError`, or any other exception,
2649     * the :js:class:`Test` status is set to ``FAIL``. If it returns
2650     * without error the status is set to ``PASS``.
2651     * @param {Object} [this_obj] - The object to use as the this
2652     * value when calling `func`. Defaults to the :js:class:`Test` object.
2653     */
2654    Test.prototype.step_func_done = function(func, this_obj)
2655    {
2656        var test_this = this;
2657
2658        if (arguments.length === 1) {
2659            this_obj = test_this;
2660        }
2661
2662        return function()
2663        {
2664            if (func) {
2665                test_this.step.apply(test_this, [func, this_obj].concat(
2666                    Array.prototype.slice.call(arguments)));
2667            }
2668            test_this.done();
2669        };
2670    };
2671
2672    /**
2673     * Return a function that automatically sets the current test to
2674     * ``FAIL`` if it's called.
2675     *
2676     * @param {string} [description] - Error message to add to assert
2677     * in case of failure.
2678     *
2679     */
2680    Test.prototype.unreached_func = function(description)
2681    {
2682        return this.step_func(function() {
2683            assert_unreached(description);
2684        });
2685    };
2686
2687    /**
2688     * Run a function as a step of the test after a given timeout.
2689     *
2690     * This multiplies the timeout by the global timeout multiplier to
2691     * account for the expected execution speed of the current test
2692     * environment. For example ``test.step_timeout(f, 2000)`` with a
2693     * timeout multiplier of 2 will wait for 4000ms before calling ``f``.
2694     *
2695     * In general it's encouraged to use :js:func:`Test.step_wait` or
2696     * :js:func:`step_wait_func` in preference to this function where possible,
2697     * as they provide better test performance.
2698     *
2699     * @param {Function} func - Function to run as a test
2700     * step.
2701     * @param {number} timeout - Time in ms to wait before running the
2702     * test step. The actual wait time is ``timeout`` x
2703     * ``timeout_multiplier``.
2704     *
2705     */
2706    Test.prototype.step_timeout = function(func, timeout) {
2707        var test_this = this;
2708        var args = Array.prototype.slice.call(arguments, 2);
2709        return setTimeout(this.step_func(function() {
2710            return func.apply(test_this, args);
2711        }), timeout * tests.timeout_multiplier);
2712    };
2713
2714    /**
2715     * Poll for a function to return true, and call a callback
2716     * function once it does, or assert if a timeout is
2717     * reached. This is preferred over a simple step_timeout
2718     * whenever possible since it allows the timeout to be longer
2719     * to reduce intermittents without compromising test execution
2720     * speed when the condition is quickly met.
2721     *
2722     * @param {Function} cond A function taking no arguments and
2723     *                        returning a boolean. The callback is called
2724     *                        when this function returns true.
2725     * @param {Function} func A function taking no arguments to call once
2726     *                        the condition is met.
2727     * @param {string} [description] Error message to add to assert in case of
2728     *                               failure.
2729     * @param {number} timeout Timeout in ms. This is multiplied by the global
2730     *                         timeout_multiplier
2731     * @param {number} interval Polling interval in ms
2732     *
2733     */
2734    Test.prototype.step_wait_func = function(cond, func, description,
2735                                             timeout=3000, interval=100) {
2736        var timeout_full = timeout * tests.timeout_multiplier;
2737        var remaining = Math.ceil(timeout_full / interval);
2738        var test_this = this;
2739
2740        var wait_for_inner = test_this.step_func(() => {
2741            if (cond()) {
2742                func();
2743            } else {
2744                if(remaining === 0) {
2745                    assert(false, "step_wait_func", description,
2746                           "Timed out waiting on condition");
2747                }
2748                remaining--;
2749                setTimeout(wait_for_inner, interval);
2750            }
2751        });
2752
2753        wait_for_inner();
2754    };
2755
2756    /**
2757     * Poll for a function to return true, and invoke a callback
2758     * followed by this.done() once it does, or assert if a timeout
2759     * is reached. This is preferred over a simple step_timeout
2760     * whenever possible since it allows the timeout to be longer
2761     * to reduce intermittents without compromising test execution speed
2762     * when the condition is quickly met.
2763     *
2764     * @example
2765     * async_test(t => {
2766     *  const popup = window.open("resources/coop-coep.py?coop=same-origin&coep=&navigate=about:blank");
2767     *  t.add_cleanup(() => popup.close());
2768     *  assert_equals(window, popup.opener);
2769     *
2770     *  popup.onload = t.step_func(() => {
2771     *    assert_true(popup.location.href.endsWith("&navigate=about:blank"));
2772     *    // Use step_wait_func_done as about:blank cannot message back.
2773     *    t.step_wait_func_done(() => popup.location.href === "about:blank");
2774     *  });
2775     * }, "Navigating a popup to about:blank");
2776     *
2777     * @param {Function} cond A function taking no arguments and
2778     *                        returning a boolean. The callback is called
2779     *                        when this function returns true.
2780     * @param {Function} func A function taking no arguments to call once
2781     *                        the condition is met.
2782     * @param {string} [description] Error message to add to assert in case of
2783     *                               failure.
2784     * @param {number} timeout Timeout in ms. This is multiplied by the global
2785     *                         timeout_multiplier
2786     * @param {number} interval Polling interval in ms
2787     *
2788     */
2789    Test.prototype.step_wait_func_done = function(cond, func, description,
2790                                                  timeout=3000, interval=100) {
2791         this.step_wait_func(cond, () => {
2792            if (func) {
2793                func();
2794            }
2795            this.done();
2796         }, description, timeout, interval);
2797    };
2798
2799    /**
2800     * Poll for a function to return true, and resolve a promise
2801     * once it does, or assert if a timeout is reached. This is
2802     * preferred over a simple step_timeout whenever possible
2803     * since it allows the timeout to be longer to reduce
2804     * intermittents without compromising test execution speed
2805     * when the condition is quickly met.
2806     *
2807     * @example
2808     * promise_test(async t => {
2809     *  // …
2810     * await t.step_wait(() => frame.contentDocument === null, "Frame navigated to a cross-origin document");
2811     * // …
2812     * }, "");
2813     *
2814     * @param {Function} cond A function taking no arguments and
2815     *                        returning a boolean.
2816     * @param {string} [description] Error message to add to assert in case of
2817     *                              failure.
2818     * @param {number} timeout Timeout in ms. This is multiplied by the global
2819     *                         timeout_multiplier
2820     * @param {number} interval Polling interval in ms
2821     * @returns {Promise} Promise resolved once cond is met.
2822     *
2823     */
2824    Test.prototype.step_wait = function(cond, description, timeout=3000, interval=100) {
2825        return new Promise(resolve => {
2826            this.step_wait_func(cond, resolve, description, timeout, interval);
2827        });
2828    }
2829
2830    /*
2831     * Private method for registering cleanup functions. `testharness.js`
2832     * internals should use this method instead of the public `add_cleanup`
2833     * method in order to hide implementation details from the harness status
2834     * message in the case errors.
2835     */
2836    Test.prototype._add_cleanup = function(callback) {
2837        this.cleanup_callbacks.push(callback);
2838    };
2839
2840    /**
2841     * Schedule a function to be run after the test result is known, regardless
2842     * of passing or failing state.
2843     *
2844     * The behavior of this function will not
2845     * influence the result of the test, but if an exception is thrown, the
2846     * test harness will report an error.
2847     *
2848     * @param {Function} callback - The cleanup function to run. This
2849     * is called with no arguments.
2850     */
2851    Test.prototype.add_cleanup = function(callback) {
2852        this._user_defined_cleanup_count += 1;
2853        this._add_cleanup(callback);
2854    };
2855
2856    Test.prototype.set_timeout = function()
2857    {
2858        if (this.timeout_length !== null) {
2859            var this_obj = this;
2860            this.timeout_id = setTimeout(function()
2861                                         {
2862                                             this_obj.timeout();
2863                                         }, this.timeout_length);
2864        }
2865    };
2866
2867    Test.prototype.set_status = function(status, message, stack)
2868    {
2869        this.status = status;
2870        this.message = message;
2871        this.stack = stack ? stack : null;
2872    };
2873
2874    /**
2875     * Manually set the test status to ``TIMEOUT``.
2876     */
2877    Test.prototype.timeout = function()
2878    {
2879        this.timeout_id = null;
2880        this.set_status(this.TIMEOUT, "Test timed out");
2881        this.phase = this.phases.HAS_RESULT;
2882        this.done();
2883    };
2884
2885    /**
2886     * Manually set the test status to ``TIMEOUT``.
2887     *
2888     * Alias for `Test.timeout <#Test.timeout>`_.
2889     */
2890    Test.prototype.force_timeout = function() {
2891        return this.timeout();
2892    };
2893
2894    /**
2895     * Mark the test as complete.
2896     *
2897     * This sets the test status to ``PASS`` if no other status was
2898     * already recorded. Any subsequent attempts to run additional
2899     * test steps will be ignored.
2900     *
2901     * After setting the test status any test cleanup functions will
2902     * be run.
2903     */
2904    Test.prototype.done = function()
2905    {
2906        if (this.phase >= this.phases.CLEANING) {
2907            return;
2908        }
2909
2910        if (this.phase <= this.phases.STARTED) {
2911            this.set_status(this.PASS, null);
2912        }
2913
2914        if (global_scope.clearTimeout) {
2915            clearTimeout(this.timeout_id);
2916        }
2917
2918        if (settings.debug) {
2919            console.log("TEST DONE",
2920                        this.status,
2921                        this.name);
2922        }
2923
2924        this.cleanup();
2925    };
2926
2927    function add_test_done_callback(test, callback)
2928    {
2929        if (test.phase === test.phases.COMPLETE) {
2930            callback();
2931            return;
2932        }
2933
2934        test._done_callbacks.push(callback);
2935    }
2936
2937    /*
2938     * Invoke all specified cleanup functions. If one or more produce an error,
2939     * the context is in an unpredictable state, so all further testing should
2940     * be cancelled.
2941     */
2942    Test.prototype.cleanup = function() {
2943        var errors = [];
2944        var bad_value_count = 0;
2945        function on_error(e) {
2946            errors.push(e);
2947            // Abort tests immediately so that tests declared within subsequent
2948            // cleanup functions are not run.
2949            tests.abort();
2950        }
2951        var this_obj = this;
2952        var results = [];
2953
2954        this.phase = this.phases.CLEANING;
2955
2956        forEach(this.cleanup_callbacks,
2957                function(cleanup_callback) {
2958                    var result;
2959
2960                    try {
2961                        result = cleanup_callback();
2962                    } catch (e) {
2963                        on_error(e);
2964                        return;
2965                    }
2966
2967                    if (!is_valid_cleanup_result(this_obj, result)) {
2968                        bad_value_count += 1;
2969                        // Abort tests immediately so that tests declared
2970                        // within subsequent cleanup functions are not run.
2971                        tests.abort();
2972                    }
2973
2974                    results.push(result);
2975                });
2976
2977        if (!this._is_promise_test) {
2978            cleanup_done(this_obj, errors, bad_value_count);
2979        } else {
2980            all_async(results,
2981                      function(result, done) {
2982                          if (result && typeof result.then === "function") {
2983                              result
2984                                  .then(null, on_error)
2985                                  .then(done);
2986                          } else {
2987                              done();
2988                          }
2989                      },
2990                      function() {
2991                          cleanup_done(this_obj, errors, bad_value_count);
2992                      });
2993        }
2994    };
2995
2996    /*
2997     * Determine if the return value of a cleanup function is valid for a given
2998     * test. Any test may return the value `undefined`. Tests created with
2999     * `promise_test` may alternatively return "thenable" object values.
3000     */
3001    function is_valid_cleanup_result(test, result) {
3002        if (result === undefined) {
3003            return true;
3004        }
3005
3006        if (test._is_promise_test) {
3007            return result && typeof result.then === "function";
3008        }
3009
3010        return false;
3011    }
3012
3013    function cleanup_done(test, errors, bad_value_count) {
3014        if (errors.length || bad_value_count) {
3015            var total = test._user_defined_cleanup_count;
3016
3017            tests.status.status = tests.status.ERROR;
3018            tests.status.stack = null;
3019            tests.status.message = "Test named '" + test.name +
3020                "' specified " + total +
3021                " 'cleanup' function" + (total > 1 ? "s" : "");
3022
3023            if (errors.length) {
3024                tests.status.message += ", and " + errors.length + " failed";
3025                tests.status.stack = ((typeof errors[0] === "object" &&
3026                                       errors[0].hasOwnProperty("stack")) ?
3027                                      errors[0].stack : null);
3028            }
3029
3030            if (bad_value_count) {
3031                var type = test._is_promise_test ?
3032                   "non-thenable" : "non-undefined";
3033                tests.status.message += ", and " + bad_value_count +
3034                    " returned a " + type + " value";
3035            }
3036
3037            tests.status.message += ".";
3038        }
3039
3040        test.phase = test.phases.COMPLETE;
3041        tests.result(test);
3042        forEach(test._done_callbacks,
3043                function(callback) {
3044                    callback();
3045                });
3046        test._done_callbacks.length = 0;
3047    }
3048
3049    /**
3050     * A RemoteTest object mirrors a Test object on a remote worker. The
3051     * associated RemoteWorker updates the RemoteTest object in response to
3052     * received events. In turn, the RemoteTest object replicates these events
3053     * on the local document. This allows listeners (test result reporting
3054     * etc..) to transparently handle local and remote events.
3055     */
3056    function RemoteTest(clone) {
3057        var this_obj = this;
3058        Object.keys(clone).forEach(
3059                function(key) {
3060                    this_obj[key] = clone[key];
3061                });
3062        this.index = null;
3063        this.phase = this.phases.INITIAL;
3064        this.update_state_from(clone);
3065        this._done_callbacks = [];
3066        tests.push(this);
3067    }
3068
3069    RemoteTest.prototype.structured_clone = function() {
3070        var clone = {};
3071        Object.keys(this).forEach(
3072                (function(key) {
3073                    var value = this[key];
3074                    // `RemoteTest` instances are responsible for managing
3075                    // their own "done" callback functions, so those functions
3076                    // are not relevant in other execution contexts. Because of
3077                    // this (and because Function values cannot be serialized
3078                    // for cross-realm transmittance), the property should not
3079                    // be considered when cloning instances.
3080                    if (key === '_done_callbacks' ) {
3081                        return;
3082                    }
3083
3084                    if (typeof value === "object" && value !== null) {
3085                        clone[key] = merge({}, value);
3086                    } else {
3087                        clone[key] = value;
3088                    }
3089                }).bind(this));
3090        clone.phases = merge({}, this.phases);
3091        return clone;
3092    };
3093
3094    /**
3095     * `RemoteTest` instances are objects which represent tests running in
3096     * another realm. They do not define "cleanup" functions (if necessary,
3097     * such functions are defined on the associated `Test` instance within the
3098     * external realm). However, `RemoteTests` may have "done" callbacks (e.g.
3099     * as attached by the `Tests` instance responsible for tracking the overall
3100     * test status in the parent realm). The `cleanup` method delegates to
3101     * `done` in order to ensure that such callbacks are invoked following the
3102     * completion of the `RemoteTest`.
3103     */
3104    RemoteTest.prototype.cleanup = function() {
3105        this.done();
3106    };
3107    RemoteTest.prototype.phases = Test.prototype.phases;
3108    RemoteTest.prototype.update_state_from = function(clone) {
3109        this.status = clone.status;
3110        this.message = clone.message;
3111        this.stack = clone.stack;
3112        if (this.phase === this.phases.INITIAL) {
3113            this.phase = this.phases.STARTED;
3114        }
3115    };
3116    RemoteTest.prototype.done = function() {
3117        this.phase = this.phases.COMPLETE;
3118
3119        forEach(this._done_callbacks,
3120                function(callback) {
3121                    callback();
3122                });
3123    }
3124
3125    RemoteTest.prototype.format_status = function() {
3126        return Test.prototype.status_formats[this.status];
3127    }
3128
3129    /*
3130     * A RemoteContext listens for test events from a remote test context, such
3131     * as another window or a worker. These events are then used to construct
3132     * and maintain RemoteTest objects that mirror the tests running in the
3133     * remote context.
3134     *
3135     * An optional third parameter can be used as a predicate to filter incoming
3136     * MessageEvents.
3137     */
3138    function RemoteContext(remote, message_target, message_filter) {
3139        this.running = true;
3140        this.started = false;
3141        this.tests = new Array();
3142        this.early_exception = null;
3143
3144        var this_obj = this;
3145        // If remote context is cross origin assigning to onerror is not
3146        // possible, so silently catch those errors.
3147        try {
3148          remote.onerror = function(error) { this_obj.remote_error(error); };
3149        } catch (e) {
3150          // Ignore.
3151        }
3152
3153        // Keeping a reference to the remote object and the message handler until
3154        // remote_done() is seen prevents the remote object and its message channel
3155        // from going away before all the messages are dispatched.
3156        this.remote = remote;
3157        this.message_target = message_target;
3158        this.message_handler = function(message) {
3159            var passesFilter = !message_filter || message_filter(message);
3160            // The reference to the `running` property in the following
3161            // condition is unnecessary because that value is only set to
3162            // `false` after the `message_handler` function has been
3163            // unsubscribed.
3164            // TODO: Simplify the condition by removing the reference.
3165            if (this_obj.running && message.data && passesFilter &&
3166                (message.data.type in this_obj.message_handlers)) {
3167                this_obj.message_handlers[message.data.type].call(this_obj, message.data);
3168            }
3169        };
3170
3171        if (self.Promise) {
3172            this.done = new Promise(function(resolve) {
3173                this_obj.doneResolve = resolve;
3174            });
3175        }
3176
3177        this.message_target.addEventListener("message", this.message_handler);
3178    }
3179
3180    RemoteContext.prototype.remote_error = function(error) {
3181        if (error.preventDefault) {
3182            error.preventDefault();
3183        }
3184
3185        // Defer interpretation of errors until the testing protocol has
3186        // started and the remote test's `allow_uncaught_exception` property
3187        // is available.
3188        if (!this.started) {
3189            this.early_exception = error;
3190        } else if (!this.allow_uncaught_exception) {
3191            this.report_uncaught(error);
3192        }
3193    };
3194
3195    RemoteContext.prototype.report_uncaught = function(error) {
3196        var message = error.message || String(error);
3197        var filename = (error.filename ? " " + error.filename: "");
3198        // FIXME: Display remote error states separately from main document
3199        // error state.
3200        tests.set_status(tests.status.ERROR,
3201                         "Error in remote" + filename + ": " + message,
3202                         error.stack);
3203    };
3204
3205    RemoteContext.prototype.start = function(data) {
3206        this.started = true;
3207        this.allow_uncaught_exception = data.properties.allow_uncaught_exception;
3208
3209        if (this.early_exception && !this.allow_uncaught_exception) {
3210            this.report_uncaught(this.early_exception);
3211        }
3212    };
3213
3214    RemoteContext.prototype.test_state = function(data) {
3215        var remote_test = this.tests[data.test.index];
3216        if (!remote_test) {
3217            remote_test = new RemoteTest(data.test);
3218            this.tests[data.test.index] = remote_test;
3219        }
3220        remote_test.update_state_from(data.test);
3221        tests.notify_test_state(remote_test);
3222    };
3223
3224    RemoteContext.prototype.test_done = function(data) {
3225        var remote_test = this.tests[data.test.index];
3226        remote_test.update_state_from(data.test);
3227        remote_test.done();
3228        tests.result(remote_test);
3229    };
3230
3231    RemoteContext.prototype.remote_done = function(data) {
3232        if (tests.status.status === null &&
3233            data.status.status !== data.status.OK) {
3234            tests.set_status(data.status.status, data.status.message, data.status.stack);
3235        }
3236
3237        for (let assert of data.asserts) {
3238            var record = new AssertRecord();
3239            record.assert_name = assert.assert_name;
3240            record.args = assert.args;
3241            record.test = assert.test != null ? this.tests[assert.test.index] : null;
3242            record.status = assert.status;
3243            record.stack = assert.stack;
3244            tests.asserts_run.push(record);
3245        }
3246
3247        this.message_target.removeEventListener("message", this.message_handler);
3248        this.running = false;
3249
3250        // If remote context is cross origin assigning to onerror is not
3251        // possible, so silently catch those errors.
3252        try {
3253          this.remote.onerror = null;
3254        } catch (e) {
3255          // Ignore.
3256        }
3257
3258        this.remote = null;
3259        this.message_target = null;
3260        if (this.doneResolve) {
3261            this.doneResolve();
3262        }
3263
3264        if (tests.all_done()) {
3265            tests.complete();
3266        }
3267    };
3268
3269    RemoteContext.prototype.message_handlers = {
3270        start: RemoteContext.prototype.start,
3271        test_state: RemoteContext.prototype.test_state,
3272        result: RemoteContext.prototype.test_done,
3273        complete: RemoteContext.prototype.remote_done
3274    };
3275
3276    /**
3277     * @class
3278     * Status of the overall harness
3279     */
3280    function TestsStatus()
3281    {
3282        /** The status code */
3283        this.status = null;
3284        /** Message in case of failure */
3285        this.message = null;
3286        /** Stack trace in case of an exception. */
3287        this.stack = null;
3288    }
3289
3290    /**
3291     * Enum of possible harness statuses.
3292     *
3293     * :values:
3294     *   - ``OK``
3295     *   - ``ERROR``
3296     *   - ``TIMEOUT``
3297     *   - ``PRECONDITION_FAILED``
3298     */
3299    TestsStatus.statuses = {
3300        OK:0,
3301        ERROR:1,
3302        TIMEOUT:2,
3303        PRECONDITION_FAILED:3
3304    };
3305
3306    TestsStatus.prototype = merge({}, TestsStatus.statuses);
3307
3308    TestsStatus.prototype.formats = {
3309        0: "OK",
3310        1: "Error",
3311        2: "Timeout",
3312        3: "Optional Feature Unsupported"
3313    };
3314
3315    TestsStatus.prototype.structured_clone = function()
3316    {
3317        if (!this._structured_clone) {
3318            var msg = this.message;
3319            msg = msg ? String(msg) : msg;
3320            this._structured_clone = merge({
3321                status:this.status,
3322                message:msg,
3323                stack:this.stack
3324            }, TestsStatus.statuses);
3325        }
3326        return this._structured_clone;
3327    };
3328
3329    TestsStatus.prototype.format_status = function() {
3330        return this.formats[this.status];
3331    };
3332
3333    /**
3334     * @class
3335     * Record of an assert that ran.
3336     *
3337     * @param {Test} test - The test which ran the assert.
3338     * @param {string} assert_name - The function name of the assert.
3339     * @param {Any} args - The arguments passed to the assert function.
3340     */
3341    function AssertRecord(test, assert_name, args = []) {
3342        /** Name of the assert that ran */
3343        this.assert_name = assert_name;
3344        /** Test that ran the assert */
3345        this.test = test;
3346        // Avoid keeping complex objects alive
3347        /** Stringification of the arguments that were passed to the assert function */
3348        this.args = args.map(x => format_value(x).replace(/\n/g, " "));
3349        /** Status of the assert */
3350        this.status = null;
3351    }
3352
3353    AssertRecord.prototype.structured_clone = function() {
3354        return {
3355            assert_name: this.assert_name,
3356            test: this.test ? this.test.structured_clone() : null,
3357            args: this.args,
3358            status: this.status,
3359        };
3360    };
3361
3362    function Tests()
3363    {
3364        this.tests = [];
3365        this.num_pending = 0;
3366
3367        this.phases = {
3368            INITIAL:0,
3369            SETUP:1,
3370            HAVE_TESTS:2,
3371            HAVE_RESULTS:3,
3372            COMPLETE:4
3373        };
3374        this.phase = this.phases.INITIAL;
3375
3376        this.properties = {};
3377
3378        this.wait_for_finish = false;
3379        this.processing_callbacks = false;
3380
3381        this.allow_uncaught_exception = false;
3382
3383        this.file_is_test = false;
3384        // This value is lazily initialized in order to avoid introducing a
3385        // dependency on ECMAScript 2015 Promises to all tests.
3386        this.promise_tests = null;
3387        this.promise_setup_called = false;
3388
3389        this.timeout_multiplier = 1;
3390        this.timeout_length = test_environment.test_timeout();
3391        this.timeout_id = null;
3392
3393        this.start_callbacks = [];
3394        this.test_state_callbacks = [];
3395        this.test_done_callbacks = [];
3396        this.all_done_callbacks = [];
3397
3398        this.hide_test_state = false;
3399        this.pending_remotes = [];
3400
3401        this.current_test = null;
3402        this.asserts_run = [];
3403
3404        // Track whether output is enabled, and thus whether or not we should
3405        // track asserts.
3406        //
3407        // On workers we don't get properties set from testharnessreport.js, so
3408        // we don't know whether or not to track asserts. To avoid the
3409        // resulting performance hit, we assume we are not meant to. This means
3410        // that assert tracking does not function on workers.
3411        this.output = settings.output && 'document' in global_scope;
3412
3413        this.status = new TestsStatus();
3414
3415        var this_obj = this;
3416
3417        test_environment.add_on_loaded_callback(function() {
3418            if (this_obj.all_done()) {
3419                this_obj.complete();
3420            }
3421        });
3422
3423        this.set_timeout();
3424    }
3425
3426    Tests.prototype.setup = function(func, properties)
3427    {
3428        if (this.phase >= this.phases.HAVE_RESULTS) {
3429            return;
3430        }
3431
3432        if (this.phase < this.phases.SETUP) {
3433            this.phase = this.phases.SETUP;
3434        }
3435
3436        this.properties = properties;
3437
3438        for (var p in properties) {
3439            if (properties.hasOwnProperty(p)) {
3440                var value = properties[p];
3441                if (p == "allow_uncaught_exception") {
3442                    this.allow_uncaught_exception = value;
3443                } else if (p == "explicit_done" && value) {
3444                    this.wait_for_finish = true;
3445                } else if (p == "explicit_timeout" && value) {
3446                    this.timeout_length = null;
3447                    if (this.timeout_id)
3448                    {
3449                        clearTimeout(this.timeout_id);
3450                    }
3451                } else if (p == "single_test" && value) {
3452                    this.set_file_is_test();
3453                } else if (p == "timeout_multiplier") {
3454                    this.timeout_multiplier = value;
3455                    if (this.timeout_length) {
3456                         this.timeout_length *= this.timeout_multiplier;
3457                    }
3458                } else if (p == "hide_test_state") {
3459                    this.hide_test_state = value;
3460                } else if (p == "output") {
3461                    this.output = value;
3462                } else if (p === "debug") {
3463                    settings.debug = value;
3464                }
3465            }
3466        }
3467
3468        if (func) {
3469            try {
3470                func();
3471            } catch (e) {
3472                this.status.status = e instanceof OptionalFeatureUnsupportedError ? this.status.PRECONDITION_FAILED : this.status.ERROR;
3473                this.status.message = String(e);
3474                this.status.stack = e.stack ? e.stack : null;
3475                this.complete();
3476            }
3477        }
3478        this.set_timeout();
3479    };
3480
3481    Tests.prototype.set_file_is_test = function() {
3482        if (this.tests.length > 0) {
3483            throw new Error("Tried to set file as test after creating a test");
3484        }
3485        this.wait_for_finish = true;
3486        this.file_is_test = true;
3487        // Create the test, which will add it to the list of tests
3488        tests.current_test = async_test();
3489    };
3490
3491    Tests.prototype.set_status = function(status, message, stack)
3492    {
3493        this.status.status = status;
3494        this.status.message = message;
3495        this.status.stack = stack ? stack : null;
3496    };
3497
3498    Tests.prototype.set_timeout = function() {
3499        if (global_scope.clearTimeout) {
3500            var this_obj = this;
3501            clearTimeout(this.timeout_id);
3502            if (this.timeout_length !== null) {
3503                this.timeout_id = setTimeout(function() {
3504                                                 this_obj.timeout();
3505                                             }, this.timeout_length);
3506            }
3507        }
3508    };
3509
3510    Tests.prototype.timeout = function() {
3511        var test_in_cleanup = null;
3512
3513        if (this.status.status === null) {
3514            forEach(this.tests,
3515                    function(test) {
3516                        // No more than one test is expected to be in the
3517                        // "CLEANUP" phase at any time
3518                        if (test.phase === test.phases.CLEANING) {
3519                            test_in_cleanup = test;
3520                        }
3521
3522                        test.phase = test.phases.COMPLETE;
3523                    });
3524
3525            // Timeouts that occur while a test is in the "cleanup" phase
3526            // indicate that some global state was not properly reverted. This
3527            // invalidates the overall test execution, so the timeout should be
3528            // reported as an error and cancel the execution of any remaining
3529            // tests.
3530            if (test_in_cleanup) {
3531                this.status.status = this.status.ERROR;
3532                this.status.message = "Timeout while running cleanup for " +
3533                    "test named \"" + test_in_cleanup.name + "\".";
3534                tests.status.stack = null;
3535            } else {
3536                this.status.status = this.status.TIMEOUT;
3537            }
3538        }
3539
3540        this.complete();
3541    };
3542
3543    Tests.prototype.end_wait = function()
3544    {
3545        this.wait_for_finish = false;
3546        if (this.all_done()) {
3547            this.complete();
3548        }
3549    };
3550
3551    Tests.prototype.push = function(test)
3552    {
3553        if (this.phase < this.phases.HAVE_TESTS) {
3554            this.start();
3555        }
3556        this.num_pending++;
3557        test.index = this.tests.push(test);
3558        this.notify_test_state(test);
3559    };
3560
3561    Tests.prototype.notify_test_state = function(test) {
3562        var this_obj = this;
3563        forEach(this.test_state_callbacks,
3564                function(callback) {
3565                    callback(test, this_obj);
3566                });
3567    };
3568
3569    Tests.prototype.all_done = function() {
3570        return (this.tests.length > 0 || this.pending_remotes.length > 0) &&
3571                test_environment.all_loaded &&
3572                (this.num_pending === 0 || this.is_aborted) && !this.wait_for_finish &&
3573                !this.processing_callbacks &&
3574                !this.pending_remotes.some(function(w) { return w.running; });
3575    };
3576
3577    Tests.prototype.start = function() {
3578        this.phase = this.phases.HAVE_TESTS;
3579        this.notify_start();
3580    };
3581
3582    Tests.prototype.notify_start = function() {
3583        var this_obj = this;
3584        forEach (this.start_callbacks,
3585                 function(callback)
3586                 {
3587                     callback(this_obj.properties);
3588                 });
3589    };
3590
3591    Tests.prototype.result = function(test)
3592    {
3593        // If the harness has already transitioned beyond the `HAVE_RESULTS`
3594        // phase, subsequent tests should not cause it to revert.
3595        if (this.phase <= this.phases.HAVE_RESULTS) {
3596            this.phase = this.phases.HAVE_RESULTS;
3597        }
3598        this.num_pending--;
3599        this.notify_result(test);
3600    };
3601
3602    Tests.prototype.notify_result = function(test) {
3603        var this_obj = this;
3604        this.processing_callbacks = true;
3605        forEach(this.test_done_callbacks,
3606                function(callback)
3607                {
3608                    callback(test, this_obj);
3609                });
3610        this.processing_callbacks = false;
3611        if (this_obj.all_done()) {
3612            this_obj.complete();
3613        }
3614    };
3615
3616    Tests.prototype.complete = function() {
3617        if (this.phase === this.phases.COMPLETE) {
3618            return;
3619        }
3620        var this_obj = this;
3621        var all_complete = function() {
3622            this_obj.phase = this_obj.phases.COMPLETE;
3623            this_obj.notify_complete();
3624        };
3625        var incomplete = filter(this.tests,
3626                                function(test) {
3627                                    return test.phase < test.phases.COMPLETE;
3628                                });
3629
3630        /**
3631         * To preserve legacy behavior, overall test completion must be
3632         * signaled synchronously.
3633         */
3634        if (incomplete.length === 0) {
3635            all_complete();
3636            return;
3637        }
3638
3639        all_async(incomplete,
3640                  function(test, testDone)
3641                  {
3642                      if (test.phase === test.phases.INITIAL) {
3643                          test.phase = test.phases.COMPLETE;
3644                          testDone();
3645                      } else {
3646                          add_test_done_callback(test, testDone);
3647                          test.cleanup();
3648                      }
3649                  },
3650                  all_complete);
3651    };
3652
3653    Tests.prototype.set_assert = function(assert_name, args) {
3654        this.asserts_run.push(new AssertRecord(this.current_test, assert_name, args))
3655    }
3656
3657    Tests.prototype.set_assert_status = function(status, stack) {
3658        let assert_record = this.asserts_run[this.asserts_run.length - 1];
3659        assert_record.status = status;
3660        assert_record.stack = stack;
3661    }
3662
3663    /**
3664     * Update the harness status to reflect an unrecoverable harness error that
3665     * should cancel all further testing. Update all previously-defined tests
3666     * which have not yet started to indicate that they will not be executed.
3667     */
3668    Tests.prototype.abort = function() {
3669        this.status.status = this.status.ERROR;
3670        this.is_aborted = true;
3671
3672        forEach(this.tests,
3673                function(test) {
3674                    if (test.phase === test.phases.INITIAL) {
3675                        test.phase = test.phases.COMPLETE;
3676                    }
3677                });
3678    };
3679
3680    /*
3681     * Determine if any tests share the same `name` property. Return an array
3682     * containing the names of any such duplicates.
3683     */
3684    Tests.prototype.find_duplicates = function() {
3685        var names = Object.create(null);
3686        var duplicates = [];
3687
3688        forEach (this.tests,
3689                 function(test)
3690                 {
3691                     if (test.name in names && duplicates.indexOf(test.name) === -1) {
3692                        duplicates.push(test.name);
3693                     }
3694                     names[test.name] = true;
3695                 });
3696
3697        return duplicates;
3698    };
3699
3700    function code_unit_str(char) {
3701        return 'U+' + char.charCodeAt(0).toString(16);
3702    }
3703
3704    function sanitize_unpaired_surrogates(str) {
3705        return str.replace(
3706            /([\ud800-\udbff]+)(?![\udc00-\udfff])|(^|[^\ud800-\udbff])([\udc00-\udfff]+)/g,
3707            function(_, low, prefix, high) {
3708                var output = prefix || "";  // prefix may be undefined
3709                var string = low || high;  // only one of these alternates can match
3710                for (var i = 0; i < string.length; i++) {
3711                    output += code_unit_str(string[i]);
3712                }
3713                return output;
3714            });
3715    }
3716
3717    function sanitize_all_unpaired_surrogates(tests) {
3718        forEach (tests,
3719                 function (test)
3720                 {
3721                     var sanitized = sanitize_unpaired_surrogates(test.name);
3722
3723                     if (test.name !== sanitized) {
3724                         test.name = sanitized;
3725                         delete test._structured_clone;
3726                     }
3727                 });
3728    }
3729
3730    Tests.prototype.notify_complete = function() {
3731        var this_obj = this;
3732        var duplicates;
3733
3734        if (this.status.status === null) {
3735            duplicates = this.find_duplicates();
3736
3737            // Some transports adhere to UTF-8's restriction on unpaired
3738            // surrogates. Sanitize the titles so that the results can be
3739            // consistently sent via all transports.
3740            sanitize_all_unpaired_surrogates(this.tests);
3741
3742            // Test names are presumed to be unique within test files--this
3743            // allows consumers to use them for identification purposes.
3744            // Duplicated names violate this expectation and should therefore
3745            // be reported as an error.
3746            if (duplicates.length) {
3747                this.status.status = this.status.ERROR;
3748                this.status.message =
3749                   duplicates.length + ' duplicate test name' +
3750                   (duplicates.length > 1 ? 's' : '') + ': "' +
3751                   duplicates.join('", "') + '"';
3752            } else {
3753                this.status.status = this.status.OK;
3754            }
3755        }
3756
3757        forEach (this.all_done_callbacks,
3758                 function(callback)
3759                 {
3760                     callback(this_obj.tests, this_obj.status, this_obj.asserts_run);
3761                 });
3762    };
3763
3764    /*
3765     * Constructs a RemoteContext that tracks tests from a specific worker.
3766     */
3767    Tests.prototype.create_remote_worker = function(worker) {
3768        var message_port;
3769
3770        if (is_service_worker(worker)) {
3771            message_port = navigator.serviceWorker;
3772            worker.postMessage({type: "connect"});
3773        } else if (is_shared_worker(worker)) {
3774            message_port = worker.port;
3775            message_port.start();
3776        } else {
3777            message_port = worker;
3778        }
3779
3780        return new RemoteContext(worker, message_port);
3781    };
3782
3783    /*
3784     * Constructs a RemoteContext that tracks tests from a specific window.
3785     */
3786    Tests.prototype.create_remote_window = function(remote) {
3787        remote.postMessage({type: "getmessages"}, "*");
3788        return new RemoteContext(
3789            remote,
3790            window,
3791            function(msg) {
3792                return msg.source === remote;
3793            }
3794        );
3795    };
3796
3797    Tests.prototype.fetch_tests_from_worker = function(worker) {
3798        if (this.phase >= this.phases.COMPLETE) {
3799            return;
3800        }
3801
3802        var remoteContext = this.create_remote_worker(worker);
3803        this.pending_remotes.push(remoteContext);
3804        return remoteContext.done;
3805    };
3806
3807    /**
3808     * Get test results from a worker and include them in the current test.
3809     *
3810     * @param {Worker|SharedWorker|ServiceWorker|MessagePort} port -
3811     * Either a worker object or a port connected to a worker which is
3812     * running tests..
3813     * @returns {Promise} - A promise that's resolved once all the remote tests are complete.
3814     */
3815    function fetch_tests_from_worker(port) {
3816        return tests.fetch_tests_from_worker(port);
3817    }
3818    expose(fetch_tests_from_worker, 'fetch_tests_from_worker');
3819
3820    Tests.prototype.fetch_tests_from_window = function(remote) {
3821        if (this.phase >= this.phases.COMPLETE) {
3822            return;
3823        }
3824
3825        this.pending_remotes.push(this.create_remote_window(remote));
3826    };
3827
3828    /**
3829     * Aggregate tests from separate windows or iframes
3830     * into the current document as if they were all part of the same test file.
3831     *
3832     * The document of the second window (or iframe) should include
3833     * ``testharness.js``, but not ``testharnessreport.js``, and use
3834     * :js:func:`test`, :js:func:`async_test`, and :js:func:`promise_test` in
3835     * the usual manner.
3836     *
3837     * @param {Window} window - The window to fetch tests from.
3838     */
3839    function fetch_tests_from_window(window) {
3840        tests.fetch_tests_from_window(window);
3841    }
3842    expose(fetch_tests_from_window, 'fetch_tests_from_window');
3843
3844    /**
3845     * Get test results from a shadow realm and include them in the current test.
3846     *
3847     * @param {ShadowRealm} realm - A shadow realm also running the test harness
3848     * @returns {Promise} - A promise that's resolved once all the remote tests are complete.
3849     */
3850    function fetch_tests_from_shadow_realm(realm) {
3851        var chan = new MessageChannel();
3852        function receiveMessage(msg_json) {
3853            chan.port1.postMessage(JSON.parse(msg_json));
3854        }
3855        var done = tests.fetch_tests_from_worker(chan.port2);
3856        realm.evaluate("begin_shadow_realm_tests")(receiveMessage);
3857        chan.port2.start();
3858        return done;
3859    }
3860    expose(fetch_tests_from_shadow_realm, 'fetch_tests_from_shadow_realm');
3861
3862    /**
3863     * Begin running tests in this shadow realm test harness.
3864     *
3865     * To be called after all tests have been loaded; it is an error to call
3866     * this more than once or in a non-Shadow Realm environment
3867     *
3868     * @param {Function} postMessage - A function to send test updates to the
3869     * incubating realm-- accepts JSON-encoded messages in the format used by
3870     * RemoteContext
3871     */
3872    function begin_shadow_realm_tests(postMessage) {
3873        if (!(test_environment instanceof ShadowRealmTestEnvironment)) {
3874            throw new Error("beign_shadow_realm_tests called in non-Shadow Realm environment");
3875        }
3876
3877        test_environment.begin(function (msg) {
3878            postMessage(JSON.stringify(msg));
3879        });
3880    }
3881    expose(begin_shadow_realm_tests, 'begin_shadow_realm_tests');
3882
3883    /**
3884     * Timeout the tests.
3885     *
3886     * This only has an effect when ``explicit_timeout`` has been set
3887     * in :js:func:`setup`. In other cases any call is a no-op.
3888     *
3889     */
3890    function timeout() {
3891        if (tests.timeout_length === null) {
3892            tests.timeout();
3893        }
3894    }
3895    expose(timeout, 'timeout');
3896
3897    /**
3898     * Add a callback that's triggered when the first :js:class:`Test` is created.
3899     *
3900     * @param {Function} callback - Callback function. This is called
3901     * without arguments.
3902     */
3903    function add_start_callback(callback) {
3904        tests.start_callbacks.push(callback);
3905    }
3906
3907    /**
3908     * Add a callback that's triggered when a test state changes.
3909     *
3910     * @param {Function} callback - Callback function, called with the
3911     * :js:class:`Test` as the only argument.
3912     */
3913    function add_test_state_callback(callback) {
3914        tests.test_state_callbacks.push(callback);
3915    }
3916
3917    /**
3918     * Add a callback that's triggered when a test result is received.
3919     *
3920     * @param {Function} callback - Callback function, called with the
3921     * :js:class:`Test` as the only argument.
3922     */
3923    function add_result_callback(callback) {
3924        tests.test_done_callbacks.push(callback);
3925    }
3926
3927    /**
3928     * Add a callback that's triggered when all tests are complete.
3929     *
3930     * @param {Function} callback - Callback function, called with an
3931     * array of :js:class:`Test` objects, a :js:class:`TestsStatus`
3932     * object and an array of :js:class:`AssertRecord` objects. If the
3933     * debug setting is ``false`` the final argument will be an empty
3934     * array.
3935     *
3936     * For performance reasons asserts are only tracked when the debug
3937     * setting is ``true``. In other cases the array of asserts will be
3938     * empty.
3939     */
3940    function add_completion_callback(callback) {
3941        tests.all_done_callbacks.push(callback);
3942    }
3943
3944    expose(add_start_callback, 'add_start_callback');
3945    expose(add_test_state_callback, 'add_test_state_callback');
3946    expose(add_result_callback, 'add_result_callback');
3947    expose(add_completion_callback, 'add_completion_callback');
3948
3949    function remove(array, item) {
3950        var index = array.indexOf(item);
3951        if (index > -1) {
3952            array.splice(index, 1);
3953        }
3954    }
3955
3956    function remove_start_callback(callback) {
3957        remove(tests.start_callbacks, callback);
3958    }
3959
3960    function remove_test_state_callback(callback) {
3961        remove(tests.test_state_callbacks, callback);
3962    }
3963
3964    function remove_result_callback(callback) {
3965        remove(tests.test_done_callbacks, callback);
3966    }
3967
3968    function remove_completion_callback(callback) {
3969       remove(tests.all_done_callbacks, callback);
3970    }
3971
3972    /*
3973     * Output listener
3974    */
3975
3976    function Output() {
3977        this.output_document = document;
3978        this.output_node = null;
3979        this.enabled = settings.output;
3980        this.phase = this.INITIAL;
3981    }
3982
3983    Output.prototype.INITIAL = 0;
3984    Output.prototype.STARTED = 1;
3985    Output.prototype.HAVE_RESULTS = 2;
3986    Output.prototype.COMPLETE = 3;
3987
3988    Output.prototype.setup = function(properties) {
3989        if (this.phase > this.INITIAL) {
3990            return;
3991        }
3992
3993        //If output is disabled in testharnessreport.js the test shouldn't be
3994        //able to override that
3995        this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
3996                                        properties.output : settings.output);
3997    };
3998
3999    Output.prototype.init = function(properties) {
4000        if (this.phase >= this.STARTED) {
4001            return;
4002        }
4003        if (properties.output_document) {
4004            this.output_document = properties.output_document;
4005        } else {
4006            this.output_document = document;
4007        }
4008        this.phase = this.STARTED;
4009    };
4010
4011    Output.prototype.resolve_log = function() {
4012        var output_document;
4013        if (this.output_node) {
4014            return;
4015        }
4016        if (typeof this.output_document === "function") {
4017            output_document = this.output_document.apply(undefined);
4018        } else {
4019            output_document = this.output_document;
4020        }
4021        if (!output_document) {
4022            return;
4023        }
4024        var node = output_document.getElementById("log");
4025        if (!node) {
4026            if (output_document.readyState === "loading") {
4027                return;
4028            }
4029            node = output_document.createElementNS("http://www.w3.org/1999/xhtml", "div");
4030            node.id = "log";
4031            if (output_document.body) {
4032                output_document.body.appendChild(node);
4033            } else {
4034                var root = output_document.documentElement;
4035                var is_html = (root &&
4036                               root.namespaceURI == "http://www.w3.org/1999/xhtml" &&
4037                               root.localName == "html");
4038                var is_svg = (output_document.defaultView &&
4039                              "SVGSVGElement" in output_document.defaultView &&
4040                              root instanceof output_document.defaultView.SVGSVGElement);
4041                if (is_svg) {
4042                    var foreignObject = output_document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
4043                    foreignObject.setAttribute("width", "100%");
4044                    foreignObject.setAttribute("height", "100%");
4045                    root.appendChild(foreignObject);
4046                    foreignObject.appendChild(node);
4047                } else if (is_html) {
4048                    root.appendChild(output_document.createElementNS("http://www.w3.org/1999/xhtml", "body"))
4049                        .appendChild(node);
4050                } else {
4051                    root.appendChild(node);
4052                }
4053            }
4054        }
4055        this.output_document = output_document;
4056        this.output_node = node;
4057    };
4058
4059    Output.prototype.show_status = function() {
4060        if (this.phase < this.STARTED) {
4061            this.init({});
4062        }
4063        if (!this.enabled || this.phase === this.COMPLETE) {
4064            return;
4065        }
4066        this.resolve_log();
4067        if (this.phase < this.HAVE_RESULTS) {
4068            this.phase = this.HAVE_RESULTS;
4069        }
4070        var done_count = tests.tests.length - tests.num_pending;
4071        if (this.output_node && !tests.hide_test_state) {
4072            if (done_count < 100 ||
4073                (done_count < 1000 && done_count % 100 === 0) ||
4074                done_count % 1000 === 0) {
4075                this.output_node.textContent = "Running, " +
4076                    done_count + " complete, " +
4077                    tests.num_pending + " remain";
4078            }
4079        }
4080    };
4081
4082    Output.prototype.show_results = function (tests, harness_status, asserts_run) {
4083        if (this.phase >= this.COMPLETE) {
4084            return;
4085        }
4086        if (!this.enabled) {
4087            return;
4088        }
4089        if (!this.output_node) {
4090            this.resolve_log();
4091        }
4092        this.phase = this.COMPLETE;
4093
4094        var log = this.output_node;
4095        if (!log) {
4096            return;
4097        }
4098        var output_document = this.output_document;
4099
4100        while (log.lastChild) {
4101            log.removeChild(log.lastChild);
4102        }
4103
4104        var stylesheet = output_document.createElementNS(xhtml_ns, "style");
4105        stylesheet.textContent = stylesheetContent;
4106        var heads = output_document.getElementsByTagName("head");
4107        if (heads.length) {
4108            heads[0].appendChild(stylesheet);
4109        }
4110
4111        var status_number = {};
4112        forEach(tests,
4113                function(test) {
4114                    var status = test.format_status();
4115                    if (status_number.hasOwnProperty(status)) {
4116                        status_number[status] += 1;
4117                    } else {
4118                        status_number[status] = 1;
4119                    }
4120                });
4121
4122        function status_class(status)
4123        {
4124            return status.replace(/\s/g, '').toLowerCase();
4125        }
4126
4127        var summary_template = ["section", {"id":"summary"},
4128                                ["h2", {}, "Summary"],
4129                                function()
4130                                {
4131                                    var status = harness_status.format_status();
4132                                    var rv = [["section", {},
4133                                               ["p", {},
4134                                                "Harness status: ",
4135                                                ["span", {"class":status_class(status)},
4136                                                 status
4137                                                ],
4138                                               ],
4139                                               ["button",
4140                                                {"onclick": "let evt = new Event('__test_restart'); " +
4141                                                 "let canceled = !window.dispatchEvent(evt);" +
4142                                                 "if (!canceled) { location.reload() }"},
4143                                                "Rerun"]
4144                                              ]];
4145
4146                                    if (harness_status.status === harness_status.ERROR) {
4147                                        rv[0].push(["pre", {}, harness_status.message]);
4148                                        if (harness_status.stack) {
4149                                            rv[0].push(["pre", {}, harness_status.stack]);
4150                                        }
4151                                    }
4152                                    return rv;
4153                                },
4154                                ["p", {}, "Found ${num_tests} tests"],
4155                                function() {
4156                                    var rv = [["div", {}]];
4157                                    var i = 0;
4158                                    while (Test.prototype.status_formats.hasOwnProperty(i)) {
4159                                        if (status_number.hasOwnProperty(Test.prototype.status_formats[i])) {
4160                                            var status = Test.prototype.status_formats[i];
4161                                            rv[0].push(["div", {},
4162                                                        ["label", {},
4163                                                         ["input", {type:"checkbox", checked:"checked"}],
4164                                                         status_number[status] + " ",
4165                                                         ["span", {"class":status_class(status)}, status]]]);
4166                                        }
4167                                        i++;
4168                                    }
4169                                    return rv;
4170                                },
4171                               ];
4172
4173        log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
4174
4175        forEach(output_document.querySelectorAll("section#summary label"),
4176                function(element)
4177                {
4178                    on_event(element, "click",
4179                             function(e)
4180                             {
4181                                 if (output_document.getElementById("results") === null) {
4182                                     e.preventDefault();
4183                                     return;
4184                                 }
4185                                 var result_class = element.querySelector("span[class]").getAttribute("class");
4186                                 var style_element = output_document.querySelector("style#hide-" + result_class);
4187                                 var input_element = element.querySelector("input");
4188                                 if (!style_element && !input_element.checked) {
4189                                     style_element = output_document.createElementNS(xhtml_ns, "style");
4190                                     style_element.id = "hide-" + result_class;
4191                                     style_element.textContent = "table#results > tbody > tr.overall-"+result_class+"{display:none}";
4192                                     output_document.body.appendChild(style_element);
4193                                 } else if (style_element && input_element.checked) {
4194                                     style_element.parentNode.removeChild(style_element);
4195                                 }
4196                             });
4197                });
4198
4199        // This use of innerHTML plus manual escaping is not recommended in
4200        // general, but is necessary here for performance.  Using textContent
4201        // on each individual <td> adds tens of seconds of execution time for
4202        // large test suites (tens of thousands of tests).
4203        function escape_html(s)
4204        {
4205            return s.replace(/\&/g, "&amp;")
4206                .replace(/</g, "&lt;")
4207                .replace(/"/g, "&quot;")
4208                .replace(/'/g, "&#39;");
4209        }
4210
4211        function has_assertions()
4212        {
4213            for (var i = 0; i < tests.length; i++) {
4214                if (tests[i].properties.hasOwnProperty("assert")) {
4215                    return true;
4216                }
4217            }
4218            return false;
4219        }
4220
4221        function get_assertion(test)
4222        {
4223            if (test.properties.hasOwnProperty("assert")) {
4224                if (Array.isArray(test.properties.assert)) {
4225                    return test.properties.assert.join(' ');
4226                }
4227                return test.properties.assert;
4228            }
4229            return '';
4230        }
4231
4232        var asserts_run_by_test = new Map();
4233        asserts_run.forEach(assert => {
4234            if (!asserts_run_by_test.has(assert.test)) {
4235                asserts_run_by_test.set(assert.test, []);
4236            }
4237            asserts_run_by_test.get(assert.test).push(assert);
4238        });
4239
4240        function get_asserts_output(test) {
4241            var asserts = asserts_run_by_test.get(test);
4242            if (!asserts) {
4243                return "No asserts ran";
4244            }
4245            rv = "<table>";
4246            rv += asserts.map(assert => {
4247                var output_fn = "<strong>" + escape_html(assert.assert_name) + "</strong>(";
4248                var prefix_len = output_fn.length;
4249                var output_args = assert.args;
4250                var output_len = output_args.reduce((prev, current) => prev+current, prefix_len);
4251                if (output_len[output_len.length - 1] > 50) {
4252                    output_args = output_args.map((x, i) =>
4253                    (i > 0 ? "  ".repeat(prefix_len) : "" )+ x + (i < output_args.length - 1 ? ",\n" : ""));
4254                } else {
4255                    output_args = output_args.map((x, i) => x + (i < output_args.length - 1 ? ", " : ""));
4256                }
4257                output_fn += escape_html(output_args.join(""));
4258                output_fn += ')';
4259                var output_location;
4260                if (assert.stack) {
4261                    output_location = assert.stack.split("\n", 1)[0].replace(/@?\w+:\/\/[^ "\/]+(?::\d+)?/g, " ");
4262                }
4263                return "<tr class='overall-" +
4264                    status_class(Test.prototype.status_formats[assert.status]) + "'>" +
4265                    "<td class='" +
4266                    status_class(Test.prototype.status_formats[assert.status]) + "'>" +
4267                    Test.prototype.status_formats[assert.status] + "</td>" +
4268                    "<td><pre>" +
4269                    output_fn +
4270                    (output_location ? "\n" + escape_html(output_location) : "") +
4271                    "</pre></td></tr>";
4272            }
4273            ).join("\n");
4274            rv += "</table>";
4275            return rv;
4276        }
4277
4278        log.appendChild(document.createElementNS(xhtml_ns, "section"));
4279        var assertions = has_assertions();
4280        var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">" +
4281            "<thead><tr><th>Result</th><th>Test Name</th>" +
4282            (assertions ? "<th>Assertion</th>" : "") +
4283            "<th>Message</th></tr></thead>" +
4284            "<tbody>";
4285        for (var i = 0; i < tests.length; i++) {
4286            var test = tests[i];
4287            html += '<tr class="overall-' +
4288                status_class(test.format_status()) +
4289                '">' +
4290                '<td class="' +
4291                status_class(test.format_status()) +
4292                '">' +
4293                test.format_status() +
4294                "</td><td>" +
4295                escape_html(test.name) +
4296                "</td><td>" +
4297                (assertions ? escape_html(get_assertion(test)) + "</td><td>" : "") +
4298                escape_html(test.message ? tests[i].message : " ") +
4299                (tests[i].stack ? "<pre>" +
4300                 escape_html(tests[i].stack) +
4301                 "</pre>": "");
4302            if (!(test instanceof RemoteTest)) {
4303                 html += "<details><summary>Asserts run</summary>" + get_asserts_output(test) + "</details>"
4304            }
4305            html += "</td></tr>";
4306        }
4307        html += "</tbody></table>";
4308        try {
4309            log.lastChild.innerHTML = html;
4310        } catch (e) {
4311            log.appendChild(document.createElementNS(xhtml_ns, "p"))
4312               .textContent = "Setting innerHTML for the log threw an exception.";
4313            log.appendChild(document.createElementNS(xhtml_ns, "pre"))
4314               .textContent = html;
4315        }
4316    };
4317
4318    /*
4319     * Template code
4320     *
4321     * A template is just a JavaScript structure. An element is represented as:
4322     *
4323     * [tag_name, {attr_name:attr_value}, child1, child2]
4324     *
4325     * the children can either be strings (which act like text nodes), other templates or
4326     * functions (see below)
4327     *
4328     * A text node is represented as
4329     *
4330     * ["{text}", value]
4331     *
4332     * String values have a simple substitution syntax; ${foo} represents a variable foo.
4333     *
4334     * It is possible to embed logic in templates by using a function in a place where a
4335     * node would usually go. The function must either return part of a template or null.
4336     *
4337     * In cases where a set of nodes are required as output rather than a single node
4338     * with children it is possible to just use a list
4339     * [node1, node2, node3]
4340     *
4341     * Usage:
4342     *
4343     * render(template, substitutions) - take a template and an object mapping
4344     * variable names to parameters and return either a DOM node or a list of DOM nodes
4345     *
4346     * substitute(template, substitutions) - take a template and variable mapping object,
4347     * make the variable substitutions and return the substituted template
4348     *
4349     */
4350
4351    function is_single_node(template)
4352    {
4353        return typeof template[0] === "string";
4354    }
4355
4356    function substitute(template, substitutions)
4357    {
4358        if (typeof template === "function") {
4359            var replacement = template(substitutions);
4360            if (!replacement) {
4361                return null;
4362            }
4363
4364            return substitute(replacement, substitutions);
4365        }
4366
4367        if (is_single_node(template)) {
4368            return substitute_single(template, substitutions);
4369        }
4370
4371        return filter(map(template, function(x) {
4372                              return substitute(x, substitutions);
4373                          }), function(x) {return x !== null;});
4374    }
4375
4376    function substitute_single(template, substitutions)
4377    {
4378        var substitution_re = /\$\{([^ }]*)\}/g;
4379
4380        function do_substitution(input) {
4381            var components = input.split(substitution_re);
4382            var rv = [];
4383            for (var i = 0; i < components.length; i += 2) {
4384                rv.push(components[i]);
4385                if (components[i + 1]) {
4386                    rv.push(String(substitutions[components[i + 1]]));
4387                }
4388            }
4389            return rv;
4390        }
4391
4392        function substitute_attrs(attrs, rv)
4393        {
4394            rv[1] = {};
4395            for (var name in template[1]) {
4396                if (attrs.hasOwnProperty(name)) {
4397                    var new_name = do_substitution(name).join("");
4398                    var new_value = do_substitution(attrs[name]).join("");
4399                    rv[1][new_name] = new_value;
4400                }
4401            }
4402        }
4403
4404        function substitute_children(children, rv)
4405        {
4406            for (var i = 0; i < children.length; i++) {
4407                if (children[i] instanceof Object) {
4408                    var replacement = substitute(children[i], substitutions);
4409                    if (replacement !== null) {
4410                        if (is_single_node(replacement)) {
4411                            rv.push(replacement);
4412                        } else {
4413                            extend(rv, replacement);
4414                        }
4415                    }
4416                } else {
4417                    extend(rv, do_substitution(String(children[i])));
4418                }
4419            }
4420            return rv;
4421        }
4422
4423        var rv = [];
4424        rv.push(do_substitution(String(template[0])).join(""));
4425
4426        if (template[0] === "{text}") {
4427            substitute_children(template.slice(1), rv);
4428        } else {
4429            substitute_attrs(template[1], rv);
4430            substitute_children(template.slice(2), rv);
4431        }
4432
4433        return rv;
4434    }
4435
4436    function make_dom_single(template, doc)
4437    {
4438        var output_document = doc || document;
4439        var element;
4440        if (template[0] === "{text}") {
4441            element = output_document.createTextNode("");
4442            for (var i = 1; i < template.length; i++) {
4443                element.data += template[i];
4444            }
4445        } else {
4446            element = output_document.createElementNS(xhtml_ns, template[0]);
4447            for (var name in template[1]) {
4448                if (template[1].hasOwnProperty(name)) {
4449                    element.setAttribute(name, template[1][name]);
4450                }
4451            }
4452            for (var i = 2; i < template.length; i++) {
4453                if (template[i] instanceof Object) {
4454                    var sub_element = make_dom(template[i]);
4455                    element.appendChild(sub_element);
4456                } else {
4457                    var text_node = output_document.createTextNode(template[i]);
4458                    element.appendChild(text_node);
4459                }
4460            }
4461        }
4462
4463        return element;
4464    }
4465
4466    function make_dom(template, substitutions, output_document)
4467    {
4468        if (is_single_node(template)) {
4469            return make_dom_single(template, output_document);
4470        }
4471
4472        return map(template, function(x) {
4473                       return make_dom_single(x, output_document);
4474                   });
4475    }
4476
4477    function render(template, substitutions, output_document)
4478    {
4479        return make_dom(substitute(template, substitutions), output_document);
4480    }
4481
4482    /*
4483     * Utility functions
4484     */
4485    function assert(expected_true, function_name, description, error, substitutions)
4486    {
4487        if (expected_true !== true) {
4488            var msg = make_message(function_name, description,
4489                                   error, substitutions);
4490            throw new AssertionError(msg);
4491        }
4492    }
4493
4494    /**
4495     * @class
4496     * Exception type that represents a failing assert.
4497     *
4498     * @param {string} message - Error message.
4499     */
4500    function AssertionError(message)
4501    {
4502        if (typeof message == "string") {
4503            message = sanitize_unpaired_surrogates(message);
4504        }
4505        this.message = message;
4506        this.stack = get_stack();
4507    }
4508    expose(AssertionError, "AssertionError");
4509
4510    AssertionError.prototype = Object.create(Error.prototype);
4511
4512    const get_stack = function() {
4513        var stack = new Error().stack;
4514
4515        // 'Error.stack' is not supported in all browsers/versions
4516        if (!stack) {
4517            return "(Stack trace unavailable)";
4518        }
4519
4520        var lines = stack.split("\n");
4521
4522        // Create a pattern to match stack frames originating within testharness.js.  These include the
4523        // script URL, followed by the line/col (e.g., '/resources/testharness.js:120:21').
4524        // Escape the URL per http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
4525        // in case it contains RegExp characters.
4526        var script_url = get_script_url();
4527        var re_text = script_url ? script_url.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : "\\btestharness.js";
4528        var re = new RegExp(re_text + ":\\d+:\\d+");
4529
4530        // Some browsers include a preamble that specifies the type of the error object.  Skip this by
4531        // advancing until we find the first stack frame originating from testharness.js.
4532        var i = 0;
4533        while (!re.test(lines[i]) && i < lines.length) {
4534            i++;
4535        }
4536
4537        // Then skip the top frames originating from testharness.js to begin the stack at the test code.
4538        while (re.test(lines[i]) && i < lines.length) {
4539            i++;
4540        }
4541
4542        // Paranoid check that we didn't skip all frames.  If so, return the original stack unmodified.
4543        if (i >= lines.length) {
4544            return stack;
4545        }
4546
4547        return lines.slice(i).join("\n");
4548    }
4549
4550    function OptionalFeatureUnsupportedError(message)
4551    {
4552        AssertionError.call(this, message);
4553    }
4554    OptionalFeatureUnsupportedError.prototype = Object.create(AssertionError.prototype);
4555    expose(OptionalFeatureUnsupportedError, "OptionalFeatureUnsupportedError");
4556
4557    function make_message(function_name, description, error, substitutions)
4558    {
4559        for (var p in substitutions) {
4560            if (substitutions.hasOwnProperty(p)) {
4561                substitutions[p] = format_value(substitutions[p]);
4562            }
4563        }
4564        var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
4565                                   merge({function_name:function_name,
4566                                          description:(description?description + " ":"")},
4567                                          substitutions));
4568        return node_form.slice(1).join("");
4569    }
4570
4571    function filter(array, callable, thisObj) {
4572        var rv = [];
4573        for (var i = 0; i < array.length; i++) {
4574            if (array.hasOwnProperty(i)) {
4575                var pass = callable.call(thisObj, array[i], i, array);
4576                if (pass) {
4577                    rv.push(array[i]);
4578                }
4579            }
4580        }
4581        return rv;
4582    }
4583
4584    function map(array, callable, thisObj)
4585    {
4586        var rv = [];
4587        rv.length = array.length;
4588        for (var i = 0; i < array.length; i++) {
4589            if (array.hasOwnProperty(i)) {
4590                rv[i] = callable.call(thisObj, array[i], i, array);
4591            }
4592        }
4593        return rv;
4594    }
4595
4596    function extend(array, items)
4597    {
4598        Array.prototype.push.apply(array, items);
4599    }
4600
4601    function forEach(array, callback, thisObj)
4602    {
4603        for (var i = 0; i < array.length; i++) {
4604            if (array.hasOwnProperty(i)) {
4605                callback.call(thisObj, array[i], i, array);
4606            }
4607        }
4608    }
4609
4610    /**
4611     * Immediately invoke a "iteratee" function with a series of values in
4612     * parallel and invoke a final "done" function when all of the "iteratee"
4613     * invocations have signaled completion.
4614     *
4615     * If all callbacks complete synchronously (or if no callbacks are
4616     * specified), the ``done_callback`` will be invoked synchronously. It is the
4617     * responsibility of the caller to ensure asynchronicity in cases where
4618     * that is desired.
4619     *
4620     * @param {array} value Zero or more values to use in the invocation of
4621     *                      ``iter_callback``
4622     * @param {function} iter_callback A function that will be invoked
4623     *                                 once for each of the values min
4624     *                                 ``value``. Two arguments will
4625     *                                 be available in each
4626     *                                 invocation: the value from
4627     *                                 ``value`` and a function that
4628     *                                 must be invoked to signal
4629     *                                 completion
4630     * @param {function} done_callback A function that will be invoked after
4631     *                                 all operations initiated by the
4632     *                                 ``iter_callback`` function have signaled
4633     *                                 completion
4634     */
4635    function all_async(values, iter_callback, done_callback)
4636    {
4637        var remaining = values.length;
4638
4639        if (remaining === 0) {
4640            done_callback();
4641        }
4642
4643        forEach(values,
4644                function(element) {
4645                    var invoked = false;
4646                    var elDone = function() {
4647                        if (invoked) {
4648                            return;
4649                        }
4650
4651                        invoked = true;
4652                        remaining -= 1;
4653
4654                        if (remaining === 0) {
4655                            done_callback();
4656                        }
4657                    };
4658
4659                    iter_callback(element, elDone);
4660                });
4661    }
4662
4663    function merge(a,b)
4664    {
4665        var rv = {};
4666        var p;
4667        for (p in a) {
4668            rv[p] = a[p];
4669        }
4670        for (p in b) {
4671            rv[p] = b[p];
4672        }
4673        return rv;
4674    }
4675
4676    function expose(object, name)
4677    {
4678        var components = name.split(".");
4679        var target = global_scope;
4680        for (var i = 0; i < components.length - 1; i++) {
4681            if (!(components[i] in target)) {
4682                target[components[i]] = {};
4683            }
4684            target = target[components[i]];
4685        }
4686        target[components[components.length - 1]] = object;
4687    }
4688
4689    function is_same_origin(w) {
4690        try {
4691            'random_prop' in w;
4692            return true;
4693        } catch (e) {
4694            return false;
4695        }
4696    }
4697
4698    /** Returns the 'src' URL of the first <script> tag in the page to include the file 'testharness.js'. */
4699    function get_script_url()
4700    {
4701        if (!('document' in global_scope)) {
4702            return undefined;
4703        }
4704
4705        var scripts = document.getElementsByTagName("script");
4706        for (var i = 0; i < scripts.length; i++) {
4707            var src;
4708            if (scripts[i].src) {
4709                src = scripts[i].src;
4710            } else if (scripts[i].href) {
4711                //SVG case
4712                src = scripts[i].href.baseVal;
4713            }
4714
4715            var matches = src && src.match(/^(.*\/|)testharness\.js$/);
4716            if (matches) {
4717                return src;
4718            }
4719        }
4720        return undefined;
4721    }
4722
4723    /** Returns the <title> or filename or "Untitled" */
4724    function get_title()
4725    {
4726        if ('document' in global_scope) {
4727            //Don't use document.title to work around an Opera/Presto bug in XHTML documents
4728            var title = document.getElementsByTagName("title")[0];
4729            if (title && title.firstChild && title.firstChild.data) {
4730                return title.firstChild.data;
4731            }
4732        }
4733        if ('META_TITLE' in global_scope && META_TITLE) {
4734            return META_TITLE;
4735        }
4736        if ('location' in global_scope) {
4737            return location.pathname.substring(location.pathname.lastIndexOf('/') + 1, location.pathname.indexOf('.'));
4738        }
4739        return "Untitled";
4740    }
4741
4742    /**
4743     * Setup globals
4744     */
4745
4746    var tests = new Tests();
4747
4748    if (global_scope.addEventListener) {
4749        var error_handler = function(error, message, stack) {
4750            var optional_unsupported = error instanceof OptionalFeatureUnsupportedError;
4751            if (tests.file_is_test) {
4752                var test = tests.tests[0];
4753                if (test.phase >= test.phases.HAS_RESULT) {
4754                    return;
4755                }
4756                var status = optional_unsupported ? test.PRECONDITION_FAILED : test.FAIL;
4757                test.set_status(status, message, stack);
4758                test.phase = test.phases.HAS_RESULT;
4759            } else if (!tests.allow_uncaught_exception) {
4760                var status = optional_unsupported ? tests.status.PRECONDITION_FAILED : tests.status.ERROR;
4761                tests.status.status = status;
4762                tests.status.message = message;
4763                tests.status.stack = stack;
4764            }
4765
4766            // Do not transition to the "complete" phase if the test has been
4767            // configured to allow uncaught exceptions. This gives the test an
4768            // opportunity to define subtests based on the exception reporting
4769            // behavior.
4770            if (!tests.allow_uncaught_exception) {
4771                done();
4772            }
4773        };
4774
4775        addEventListener("error", function(e) {
4776            var message = e.message;
4777            var stack;
4778            if (e.error && e.error.stack) {
4779                stack = e.error.stack;
4780            } else {
4781                stack = e.filename + ":" + e.lineno + ":" + e.colno;
4782            }
4783            error_handler(e.error, message, stack);
4784        }, false);
4785
4786        addEventListener("unhandledrejection", function(e) {
4787            var message;
4788            if (e.reason && e.reason.message) {
4789                message = "Unhandled rejection: " + e.reason.message;
4790            } else {
4791                message = "Unhandled rejection";
4792            }
4793            var stack;
4794            if (e.reason && e.reason.stack) {
4795                stack = e.reason.stack;
4796            }
4797            error_handler(e.reason, message, stack);
4798        }, false);
4799    }
4800
4801    test_environment.on_tests_ready();
4802
4803    /**
4804     * Stylesheet
4805     */
4806     var stylesheetContent = "\
4807html {\
4808    font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;\
4809}\
4810\
4811#log .warning,\
4812#log .warning a {\
4813  color: black;\
4814  background: yellow;\
4815}\
4816\
4817#log .error,\
4818#log .error a {\
4819  color: white;\
4820  background: red;\
4821}\
4822\
4823section#summary {\
4824    margin-bottom:1em;\
4825}\
4826\
4827table#results {\
4828    border-collapse:collapse;\
4829    table-layout:fixed;\
4830    width:100%;\
4831}\
4832\
4833table#results > thead > tr > th:first-child,\
4834table#results > tbody > tr > td:first-child {\
4835    width:8em;\
4836}\
4837\
4838table#results > thead > tr > th:last-child,\
4839table#results > thead > tr > td:last-child {\
4840    width:50%;\
4841}\
4842\
4843table#results.assertions > thead > tr > th:last-child,\
4844table#results.assertions > tbody > tr > td:last-child {\
4845    width:35%;\
4846}\
4847\
4848table#results > thead > > tr > th {\
4849    padding:0;\
4850    padding-bottom:0.5em;\
4851    border-bottom:medium solid black;\
4852}\
4853\
4854table#results > tbody > tr> td {\
4855    padding:1em;\
4856    padding-bottom:0.5em;\
4857    border-bottom:thin solid black;\
4858}\
4859\
4860.pass {\
4861    color:green;\
4862}\
4863\
4864.fail {\
4865    color:red;\
4866}\
4867\
4868tr.timeout {\
4869    color:red;\
4870}\
4871\
4872tr.notrun {\
4873    color:blue;\
4874}\
4875\
4876tr.optionalunsupported {\
4877    color:blue;\
4878}\
4879\
4880.ok {\
4881    color:green;\
4882}\
4883\
4884.error {\
4885    color:red;\
4886}\
4887\
4888.pass, .fail, .timeout, .notrun, .optionalunsupported .ok, .timeout, .error {\
4889    font-variant:small-caps;\
4890}\
4891\
4892table#results span {\
4893    display:block;\
4894}\
4895\
4896table#results span.expected {\
4897    font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;\
4898    white-space:pre;\
4899}\
4900\
4901table#results span.actual {\
4902    font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;\
4903    white-space:pre;\
4904}\
4905";
4906
4907})(self);
4908// vim: set expandtab shiftwidth=4 tabstop=4:
4909