tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

testharness.js (198291B)


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