summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/resources/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/resources/test
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/resources/test')
-rw-r--r--testing/web-platform/tests/resources/test/README.md83
-rw-r--r--testing/web-platform/tests/resources/test/conftest.py269
-rw-r--r--testing/web-platform/tests/resources/test/harness.html26
-rw-r--r--testing/web-platform/tests/resources/test/idl-helper.js24
-rw-r--r--testing/web-platform/tests/resources/test/nested-testharness.js80
-rw-r--r--testing/web-platform/tests/resources/test/requirements.txt1
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/abortsignal.html49
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup.html91
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async.html85
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_bad_return.html50
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_rejection.html94
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_rejection_after_load.html52
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_timeout.html57
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup_bad_return.html61
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup_count.html39
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup_err.html45
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup_err_multi.html52
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/add_cleanup_sync_queue.html55
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/api-tests-1.html991
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/api-tests-2.html62
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/api-tests-3.html34
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/assert-array-equals.html162
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/assert-throws-dom.html55
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/force_timeout.html60
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/generate-callback.html153
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html89
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_exposed_wildcard.html233
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_immutable_prototype.html298
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_interface_mixin.html131
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html187
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_primary_interface_of.html116
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_to_json_operation.html177
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_attribute.html100
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_operation.html242
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_partial_namespace.html125
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/iframe-callback.html116
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/iframe-consolidate-errors.html50
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/iframe-consolidate-tests.html85
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/iframe-msg.html84
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/log-insertion.html46
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/no-title.html146
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/order.html36
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/promise-async.html172
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/promise-with-sync.html79
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/promise.html219
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/queue.html130
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/setup-function-worker.js14
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/setup-worker-service.html86
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/single-page-test-fail.html28
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/single-page-test-no-assertions.html25
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/single-page-test-no-body.html26
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/single-page-test-pass.html28
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/step_wait.html79
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/step_wait_func.html49
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/task-scheduling-promise-test.html241
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/task-scheduling-test.html141
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/uncaught-exception-handle.html33
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/uncaught-exception-ignore.html35
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/worker-dedicated-uncaught-allow.html45
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/worker-dedicated-uncaught-single.html56
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/worker-dedicated.sub.html88
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/worker-error.js8
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/worker-service.html115
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/worker-shared.html73
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/worker-uncaught-allow.js19
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/worker-uncaught-single.js8
-rw-r--r--testing/web-platform/tests/resources/test/tests/functional/worker.js34
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlArray/is_json_type.html192
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlDictionary/get_reverse_inheritance_stack.html47
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlDictionary/test_partial_dictionary.html39
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/constructors.html26
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/default_to_json_operation.html114
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/do_member_unscopable_asserts.html56
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_interface_object.html22
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_interface_object_owner.html21
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_legacy_namespace.html20
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_qualified_name.html20
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_reverse_inheritance_stack.html49
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/has_default_to_json_regular_operation.html47
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/has_to_json_regular_operation.html31
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/should_have_interface_object.html30
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterface/test_primary_interface_of_undefined.html29
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterfaceMember/is_to_json_regular_operation.html42
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/IdlInterfaceMember/toString.html36
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/assert_implements.html43
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/assert_implements_optional.html43
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/assert_object_equals.html152
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/async-test-return-restrictions.html135
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/basic.html48
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/exceptional-cases-timeouts.html120
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/exceptional-cases.html392
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/format-value.html123
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/helpers.js21
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/late-test.html56
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/promise_setup-timeout.html28
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/promise_setup.html333
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/single_test.html94
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/test-return-restrictions.html156
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/throwing-assertions.html268
-rw-r--r--testing/web-platform/tests/resources/test/tests/unit/unpaired-surrogates.html143
-rw-r--r--testing/web-platform/tests/resources/test/tox.ini13
-rw-r--r--testing/web-platform/tests/resources/test/wptserver.py58
102 files changed, 9869 insertions, 0 deletions
diff --git a/testing/web-platform/tests/resources/test/README.md b/testing/web-platform/tests/resources/test/README.md
new file mode 100644
index 0000000000..edc03ef214
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/README.md
@@ -0,0 +1,83 @@
+# `testharness.js` test suite
+
+The test suite for the `testharness.js` testing framework.
+
+## Executing Tests
+
+Install the following dependencies:
+
+- [Python 2.7.9+](https://www.python.org/)
+- [the tox Python package](https://tox.readthedocs.io/en/latest/)
+- [the Mozilla Firefox web browser](https://mozilla.org/firefox)
+- [the GeckoDriver server](https://github.com/mozilla/geckodriver)
+
+Make sure `geckodriver` can be found in your `PATH`.
+
+Currently, the tests should be run with the latest *Firefox Nightly*. In order to
+specify the path to Firefox Nightly, use the following command-line option:
+
+ tox -- --binary=/path/to/FirefoxNightly
+
+### Automated Script
+
+Alternatively, you may run `tools/ci/ci_resources_unittest.sh`, which only depends on
+Python 2. The script will install other dependencies automatically and start `tox` with
+the correct arguments.
+
+## Authoring Tests
+
+Test cases are expressed as `.html` files located within the `tests/unit/` and
+`tests/functional/` sub-directories. Each test should include the
+`testharness.js` library with the following markup:
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+
+This should be followed by one or more `<script>` tags that interface with the
+`testharness.js` API in some way. For example:
+
+ <script>
+ test(function() {
+ 1 = 1;
+ }, 'This test is expected to fail.');
+ </script>
+
+### Unit tests
+
+The "unit test" type allows for concisely testing the expected behavior of
+assertion methods. These tests may define any number of sub-tests; the
+acceptance criteria is simply that all tests executed pass.
+
+### Functional tests
+
+Thoroughly testing the behavior of the harness itself requires ensuring a
+number of considerations which cannot be verified with the "unit testing"
+strategy. These include:
+
+- Ensuring that some tests are not run
+- Ensuring conditions that cause test failures
+- Ensuring conditions that cause harness errors
+
+Functional tests allow for these details to be verified. Every functional test
+must include a summary of the expected results as a JSON string within a
+`<script>` tag with an `id` of `"expected"`, e.g.:
+
+ <script type="text/json" id="expected">
+ {
+ "summarized_status": {
+ "message": null,
+ "stack": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "message": "ReferenceError: invalid assignment left-hand side",
+ "name": "Sample HTML5 API Tests",
+ "properties": {},
+ "stack": "(implementation-defined)",
+ "status_string": "FAIL"
+ }
+ ],
+ "type": "complete"
+ }
+ </script>
diff --git a/testing/web-platform/tests/resources/test/conftest.py b/testing/web-platform/tests/resources/test/conftest.py
new file mode 100644
index 0000000000..7253cac9ac
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/conftest.py
@@ -0,0 +1,269 @@
+import copy
+import json
+import os
+import ssl
+import sys
+import subprocess
+import urllib
+
+import html5lib
+import py
+import pytest
+
+from wptserver import WPTServer
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+WPT_ROOT = os.path.normpath(os.path.join(HERE, '..', '..'))
+HARNESS = os.path.join(HERE, 'harness.html')
+TEST_TYPES = ('functional', 'unit')
+
+sys.path.insert(0, os.path.normpath(os.path.join(WPT_ROOT, "tools")))
+import localpaths
+
+sys.path.insert(0, os.path.normpath(os.path.join(WPT_ROOT, "tools", "webdriver")))
+import webdriver
+
+
+def pytest_addoption(parser):
+ parser.addoption("--binary", action="store", default=None, help="path to browser binary")
+ parser.addoption("--headless", action="store_true", default=False, help="run browser in headless mode")
+
+
+def pytest_collect_file(file_path, path, parent):
+ if file_path.suffix.lower() != '.html':
+ return
+
+ # Tests are organized in directories by type
+ test_type = os.path.relpath(str(file_path), HERE)
+ if os.path.sep not in test_type or ".." in test_type:
+ # HTML files in this directory are not tests
+ return
+ test_type = test_type.split(os.path.sep)[1]
+
+ # Handle the deprecation of Node construction in pytest6
+ # https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent
+ if hasattr(HTMLItem, "from_parent"):
+ return HTMLItem.from_parent(parent, filename=str(file_path), test_type=test_type)
+ return HTMLItem(parent, str(file_path), test_type)
+
+
+def pytest_configure(config):
+ config.proc = subprocess.Popen(["geckodriver"])
+ config.add_cleanup(config.proc.kill)
+
+ capabilities = {"alwaysMatch": {"acceptInsecureCerts": True, "moz:firefoxOptions": {}}}
+ if config.getoption("--binary"):
+ capabilities["alwaysMatch"]["moz:firefoxOptions"]["binary"] = config.getoption("--binary")
+ if config.getoption("--headless"):
+ capabilities["alwaysMatch"]["moz:firefoxOptions"]["args"] = ["--headless"]
+
+ config.driver = webdriver.Session("localhost", 4444,
+ capabilities=capabilities)
+ config.add_cleanup(config.driver.end)
+
+ # Although the name of the `_create_unverified_context` method suggests
+ # that it is not intended for external consumption, the standard library's
+ # documentation explicitly endorses its use:
+ #
+ # > To revert to the previous, unverified, behavior
+ # > ssl._create_unverified_context() can be passed to the context
+ # > parameter.
+ #
+ # https://docs.python.org/2/library/httplib.html#httplib.HTTPSConnection
+ config.ssl_context = ssl._create_unverified_context()
+
+ config.server = WPTServer(WPT_ROOT)
+ config.server.start(config.ssl_context)
+ config.add_cleanup(config.server.stop)
+
+
+def resolve_uri(context, uri):
+ if uri.startswith('/'):
+ base = WPT_ROOT
+ path = uri[1:]
+ else:
+ base = os.path.dirname(context)
+ path = uri
+
+ return os.path.exists(os.path.join(base, path))
+
+
+def _summarize(actual):
+ def _scrub_stack(test_obj):
+ copy = dict(test_obj)
+ del copy['stack']
+ return copy
+
+ def _expand_status(status_obj):
+ for key, value in [item for item in status_obj.items()]:
+ # In "status" and "test" objects, the "status" value enum
+ # definitions are interspersed with properties for unrelated
+ # metadata. The following condition is a best-effort attempt to
+ # ignore non-enum properties.
+ if key != key.upper() or not isinstance(value, int):
+ continue
+
+ del status_obj[key]
+
+ if status_obj['status'] == value:
+ status_obj[u'status_string'] = key
+
+ del status_obj['status']
+
+ return status_obj
+
+ def _summarize_test(test_obj):
+ del test_obj['index']
+
+ assert 'phase' in test_obj
+ assert 'phases' in test_obj
+ assert 'COMPLETE' in test_obj['phases']
+ assert test_obj['phase'] == test_obj['phases']['COMPLETE']
+ del test_obj['phases']
+ del test_obj['phase']
+
+ return _expand_status(_scrub_stack(test_obj))
+
+ def _summarize_status(status_obj):
+ return _expand_status(_scrub_stack(status_obj))
+
+
+ summarized = {}
+
+ summarized[u'summarized_status'] = _summarize_status(actual['status'])
+ summarized[u'summarized_tests'] = [
+ _summarize_test(test) for test in actual['tests']]
+ summarized[u'summarized_tests'].sort(key=lambda test_obj: test_obj.get('name'))
+ summarized[u'summarized_asserts'] = [
+ {"assert_name": assert_item["assert_name"],
+ "test": assert_item["test"]["name"] if assert_item["test"] else None,
+ "args": assert_item["args"],
+ "status": assert_item["status"]} for assert_item in actual["asserts"]]
+ summarized[u'type'] = actual['type']
+
+ return summarized
+
+
+class HTMLItem(pytest.Item, pytest.Collector):
+ def __init__(self, parent, filename, test_type):
+ self.url = parent.session.config.server.url(filename)
+ self.type = test_type
+ # Some tests are reliant on the WPT servers substitution functionality,
+ # so tests must be retrieved from the server rather than read from the
+ # file system directly.
+ handle = urllib.request.urlopen(self.url,
+ context=parent.session.config.ssl_context)
+ try:
+ markup = handle.read()
+ finally:
+ handle.close()
+
+ if test_type not in TEST_TYPES:
+ raise ValueError('Unrecognized test type: "%s"' % test_type)
+
+ parsed = html5lib.parse(markup, namespaceHTMLElements=False)
+ name = None
+ self.expected = None
+
+ for element in parsed.iter():
+ if not name and element.tag == 'title':
+ name = element.text
+ continue
+ if element.tag == 'script':
+ if element.attrib.get('id') == 'expected':
+ try:
+ self.expected = json.loads(element.text)
+ except ValueError:
+ print("Failed parsing JSON in %s" % filename)
+ raise
+
+ if not name:
+ raise ValueError('No name found in %s add a <title> element' % filename)
+ elif self.type == 'functional':
+ if not self.expected:
+ raise ValueError('Functional tests must specify expected report data')
+ elif self.type == 'unit' and self.expected:
+ raise ValueError('Unit tests must not specify expected report data')
+
+ # Ensure that distinct items have distinct fspath attributes.
+ # This is necessary because pytest has an internal cache keyed on it,
+ # and only the first test with any given fspath will be run.
+ #
+ # This cannot use super(HTMLItem, self).__init__(..) because only the
+ # Collector constructor takes the fspath argument.
+ pytest.Item.__init__(self, name, parent)
+ pytest.Collector.__init__(self, name, parent, fspath=py.path.local(filename))
+
+
+ def reportinfo(self):
+ return self.fspath, None, self.url
+
+ def repr_failure(self, excinfo):
+ return pytest.Collector.repr_failure(self, excinfo)
+
+ def runtest(self):
+ if self.type == 'unit':
+ self._run_unit_test()
+ elif self.type == 'functional':
+ self._run_functional_test()
+ else:
+ raise NotImplementedError
+
+ def _run_unit_test(self):
+ driver = self.session.config.driver
+ server = self.session.config.server
+
+ driver.url = server.url(HARNESS)
+
+ actual = driver.execute_async_script(
+ 'runTest("%s", "foo", arguments[0])' % self.url
+ )
+
+ summarized = _summarize(copy.deepcopy(actual))
+
+ print(json.dumps(summarized, indent=2))
+
+ assert summarized[u'summarized_status'][u'status_string'] == u'OK', summarized[u'summarized_status'][u'message']
+ for test in summarized[u'summarized_tests']:
+ msg = "%s\n%s" % (test[u'name'], test[u'message'])
+ assert test[u'status_string'] == u'PASS', msg
+
+ def _run_functional_test(self):
+ driver = self.session.config.driver
+ server = self.session.config.server
+
+ driver.url = server.url(HARNESS)
+
+ test_url = self.url
+ actual = driver.execute_async_script('runTest("%s", "foo", arguments[0])' % test_url)
+
+ print(json.dumps(actual, indent=2))
+
+ summarized = _summarize(copy.deepcopy(actual))
+
+ print(json.dumps(summarized, indent=2))
+
+ # Test object ordering is not guaranteed. This weak assertion verifies
+ # that the indices are unique and sequential
+ indices = [test_obj.get('index') for test_obj in actual['tests']]
+ self._assert_sequence(indices)
+
+ self.expected[u'summarized_tests'].sort(key=lambda test_obj: test_obj.get('name'))
+
+ # Make asserts opt-in for now
+ if "summarized_asserts" not in self.expected:
+ del summarized["summarized_asserts"]
+ else:
+ # We can't be sure of the order of asserts even within the same test
+ # although we could also check for the failing assert being the final
+ # one
+ for obj in [summarized, self.expected]:
+ obj["summarized_asserts"].sort(
+ key=lambda x: (x["test"] or "", x["status"], x["assert_name"], tuple(x["args"])))
+
+ assert summarized == self.expected
+
+ @staticmethod
+ def _assert_sequence(nums):
+ if nums and len(nums) > 0:
+ assert nums == list(range(1, nums[-1] + 1))
diff --git a/testing/web-platform/tests/resources/test/harness.html b/testing/web-platform/tests/resources/test/harness.html
new file mode 100644
index 0000000000..5ee0f285e8
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/harness.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ </head>
+ <body>
+ <script>
+function runTest(url, id, done) {
+ var child;
+
+ function onMessage(event) {
+ if (!event.data || event.data.type !== 'complete') {
+ return;
+ }
+
+ window.removeEventListener('message', onMessage);
+ child.close();
+ done(event.data);
+ }
+ window.addEventListener('message', onMessage);
+
+ window.child = child = window.open(url, id);
+}
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/idl-helper.js b/testing/web-platform/tests/resources/test/idl-helper.js
new file mode 100644
index 0000000000..2b73527ff2
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/idl-helper.js
@@ -0,0 +1,24 @@
+"use strict";
+
+var typedefFrom = interfaceFrom;
+var dictionaryFrom = interfaceFrom;
+function interfaceFrom(i) {
+ var idl = new IdlArray();
+ idl.add_idls(i);
+ for (var prop in idl.members) {
+ return idl.members[prop];
+ }
+}
+
+function memberFrom(m) {
+ var idl = new IdlArray();
+ idl.add_idls('interface A { ' + m + '; };');
+ return idl.members["A"].members[0];
+}
+
+function typeFrom(type) {
+ var ast = WebIDL2.parse('interface Foo { ' + type + ' a(); };');
+ ast = ast[0]; // get the first fragment
+ ast = ast.members[0]; // get the first member
+ return ast.idlType; // get the type of the first field
+}
diff --git a/testing/web-platform/tests/resources/test/nested-testharness.js b/testing/web-platform/tests/resources/test/nested-testharness.js
new file mode 100644
index 0000000000..d97c1568c7
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/nested-testharness.js
@@ -0,0 +1,80 @@
+'use strict';
+
+/**
+ * Execute testharness.js and one or more scripts in an iframe. Report the
+ * results of the execution.
+ *
+ * @param {...function|...string} bodies - a function body. If specified as a
+ * function object, it will be
+ * serialized to a string using the
+ * built-in
+ * `Function.prototype.toString` prior
+ * to inclusion in the generated
+ * iframe.
+ *
+ * @returns {Promise} eventual value describing the result of the test
+ * execution; the summary object has two properties:
+ * `harness` (a string describing the harness status) and
+ * `tests` (an object whose "own" property names are the
+ * titles of the defined sub-tests and whose associated
+ * values are the subtest statuses).
+ */
+function makeTest(...bodies) {
+ const closeScript = '<' + '/script>';
+ let src = `
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Document title</title>
+<script src="/resources/testharness.js?${Math.random()}">${closeScript}
+</head>
+
+<body>
+<div id="log"></div>`;
+ bodies.forEach((body) => {
+ src += '<script>(' + body + ')();' + closeScript;
+ });
+
+ const iframe = document.createElement('iframe');
+
+ document.body.appendChild(iframe);
+ iframe.contentDocument.write(src);
+
+ return new Promise((resolve) => {
+ window.addEventListener('message', function onMessage(e) {
+ if (e.source !== iframe.contentWindow) {
+ return;
+ }
+ if (!e.data || e.data.type !=='complete') {
+ return;
+ }
+ window.removeEventListener('message', onMessage);
+ resolve(e.data);
+ });
+
+ iframe.contentDocument.close();
+ }).then(({ tests, status }) => {
+ const summary = {
+ harness: getEnumProp(status, status.status),
+ tests: {}
+ };
+
+ tests.forEach((test) => {
+ summary.tests[test.name] = getEnumProp(test, test.status);
+ });
+
+ return summary;
+ });
+}
+
+function getEnumProp(object, value) {
+ for (let property in object) {
+ if (!/^[A-Z]+$/.test(property)) {
+ continue;
+ }
+
+ if (object[property] === value) {
+ return property;
+ }
+ }
+}
diff --git a/testing/web-platform/tests/resources/test/requirements.txt b/testing/web-platform/tests/resources/test/requirements.txt
new file mode 100644
index 0000000000..95d87c3875
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/requirements.txt
@@ -0,0 +1 @@
+html5lib==1.1
diff --git a/testing/web-platform/tests/resources/test/tests/functional/abortsignal.html b/testing/web-platform/tests/resources/test/tests/functional/abortsignal.html
new file mode 100644
index 0000000000..e6080e96ec
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/abortsignal.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>Test#get_signal</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ "use strict";
+
+ setup(() => {
+ assert_implements_optional(window.AbortController, "No AbortController");
+ });
+
+ let signal;
+ let observed = false;
+
+ test(t => {
+ signal = t.get_signal();
+ assert_true(signal instanceof AbortSignal, "Returns an abort signal");
+ assert_false(signal.aborted, "Signal should not be aborted before test end");
+ signal.onabort = () => observed = true;
+ }, "t.signal existence");
+
+ test(t => {
+ assert_true(signal.aborted, "Signal should be aborted after test end");
+ assert_true(observed, "onabort should have been called");
+ }, "t.signal.aborted");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "message": null,
+ "name": "t.signal existence",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "t.signal.aborted",
+ "properties": {},
+ "status_string": "PASS"
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup.html
new file mode 100644
index 0000000000..468319fdbe
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+var log_sync;
+test(function(t) {
+ log_sync = "";
+ t.add_cleanup(function() { log_sync += "1"; });
+ t.add_cleanup(function() { log_sync += "2"; });
+ t.add_cleanup(function() { log_sync += "3"; });
+ t.add_cleanup(function() { log_sync += "4"; });
+ t.add_cleanup(function() { log_sync += "5"; });
+ log_sync += "0";
+}, "probe synchronous");
+
+test(function() {
+ if (log_sync !== "012345") {
+ throw new Error("Expected: '012345'. Actual: '" + log_sync + "'.");
+ }
+}, "Cleanup methods are invoked exactly once and in the expected sequence.");
+
+var complete, log_async;
+async_test(function(t) {
+ complete = t.step_func(function() {
+ if (log_async !== "012") {
+ throw new Error("Expected: '012'. Actual: '" + log_async + "'.");
+ }
+
+ t.done();
+ });
+}, "Cleanup methods are invoked following the completion of asynchronous tests");
+
+async_test(function(t) {
+ log_async = "";
+ t.add_cleanup(function() { log_async += "1"; });
+
+ setTimeout(t.step_func(function() {
+ t.add_cleanup(function() {
+ log_async += "2";
+ complete();
+ });
+ log_async += "0";
+ t.done();
+ }), 0);
+}, "probe asynchronous");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Cleanup methods are invoked exactly once and in the expected sequence.",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Cleanup methods are invoked following the completion of asynchronous tests",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "probe asynchronous",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "probe synchronous",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async.html
new file mode 100644
index 0000000000..07ade4b93b
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup with Promise-returning functions</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+
+<script>
+"use strict";
+var completeCount = 0;
+var counts = {
+ afterTick: null,
+ afterFirst: null
+};
+
+add_result_callback(function(result_t) {
+ completeCount += 1;
+});
+
+promise_test(function(t) {
+ t.add_cleanup(function() {
+ return new Promise(function(resolve) {
+ setTimeout(function() {
+ counts.afterTick = completeCount;
+ resolve();
+ }, 0);
+ });
+ });
+ t.add_cleanup(function() {
+ return new Promise(function(resolve) {
+
+ setTimeout(function() {
+ counts.afterFirst = completeCount;
+ resolve();
+ }, 0);
+ });
+ });
+
+ return Promise.resolve();
+}, 'promise_test with asynchronous cleanup');
+
+promise_test(function() {
+ assert_equals(
+ counts.afterTick,
+ 0,
+ "test is not asynchronously considered 'complete'"
+ );
+ assert_equals(
+ counts.afterFirst,
+ 0,
+ "test is not considered 'complete' following fulfillment of first promise"
+ );
+ assert_equals(completeCount, 1);
+
+ return Promise.resolve();
+}, "synchronously-defined promise_test");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "promise_test with asynchronous cleanup",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "synchronously-defined promise_test",
+ "message": null,
+ "properties": {}
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_bad_return.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_bad_return.html
new file mode 100644
index 0000000000..867bde2c39
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_bad_return.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup with non-thenable-returning function</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+
+<script>
+"use strict";
+
+promise_test(function(t) {
+ t.add_cleanup(function() {});
+ t.add_cleanup(function() {
+ return { then: 9 };
+ });
+ t.add_cleanup(function() { return Promise.resolve(); });
+
+ return Promise.resolve();
+}, "promise_test that returns a non-thenable object in one \"cleanup\" callback");
+
+promise_test(function() {}, "The test runner is in an unpredictable state ('NOT RUN')");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Test named 'promise_test that returns a non-thenable object in one \"cleanup\" callback' specified 3 'cleanup' functions, and 1 returned a non-thenable value."
+ },
+ "summarized_tests": [
+ {
+ "status_string": "NOTRUN",
+ "name": "The test runner is in an unpredictable state ('NOT RUN')",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "promise_test that returns a non-thenable object in one \"cleanup\" callback",
+ "message": null,
+ "properties": {}
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_rejection.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_rejection.html
new file mode 100644
index 0000000000..e51465e7eb
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_rejection.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup with Promise-returning functions (rejection handling)</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+
+<script>
+"use strict";
+var resolve, reject;
+var completeCount = 0;
+add_result_callback(function(result_t) {
+ completeCount += 1;
+});
+promise_test(function(t) {
+ t.add_cleanup(function() {
+ return new Promise(function(_, _reject) { reject = _reject; });
+ });
+ t.add_cleanup(function() {
+ return new Promise(function(_resolve) { resolve = _resolve; });
+ });
+
+ // The following cleanup function defines empty tests so that the reported
+ // data demonstrates the intended run-time behavior without relying on the
+ // test harness's handling of errors during test cleanup (which is tested
+ // elsewhere).
+ t.add_cleanup(function() {
+ if (completeCount === 0) {
+ promise_test(
+ function() {},
+ "test is not asynchronously considered 'complete' ('NOT RUN')"
+ );
+ }
+
+ reject();
+
+ setTimeout(function() {
+ if (completeCount === 0) {
+ promise_test(
+ function() {},
+ "test is not considered 'complete' following rejection of first " +
+ "promise ('NOT RUN')"
+ );
+ }
+
+ resolve();
+ }, 0);
+ });
+
+ return Promise.resolve();
+}, "promise_test with asynchronous cleanup including rejection");
+
+promise_test(function() {}, "synchronously-defined test ('NOT RUN')");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Test named 'promise_test with asynchronous cleanup including rejection' specified 3 'cleanup' functions, and 1 failed."
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "promise_test with asynchronous cleanup including rejection",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "synchronously-defined test ('NOT RUN')",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "test is not asynchronously considered 'complete' ('NOT RUN')",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "test is not considered 'complete' following rejection of first promise ('NOT RUN')",
+ "message": null,
+ "properties": {}
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_rejection_after_load.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_rejection_after_load.html
new file mode 100644
index 0000000000..f9b2846100
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_rejection_after_load.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup with Promise-returning functions (rejection handling following "load" event)</title>
+</head>
+<body>
+<h1>Promise Tests</h1>
+<p>This test demonstrates the use of <tt>promise_test</tt>. Assumes ECMAScript 6
+Promise support. Some failures are expected.</p>
+<div id="log"></div>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+<script>
+promise_test(function(t) {
+ t.add_cleanup(function() {
+ return Promise.reject(new Error("foo"));
+ });
+
+ return new Promise((resolve) => {
+ document.addEventListener("DOMContentLoaded", function() {
+ setTimeout(resolve, 0)
+ });
+ });
+}, "Test with failing cleanup that completes after DOMContentLoaded event");
+
+promise_test(function(t) {
+ return Promise.resolve();
+}, "Test that should not be run due to invalid harness state ('NOT RUN')");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Test named 'Test with failing cleanup that completes after DOMContentLoaded event' specified 1 'cleanup' function, and 1 failed."
+ },
+ "summarized_tests": [
+ {
+ "status_string": "NOTRUN",
+ "name": "Test that should not be run due to invalid harness state ('NOT RUN')",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test with failing cleanup that completes after DOMContentLoaded event",
+ "message": null,
+ "properties": {}
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_timeout.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_timeout.html
new file mode 100644
index 0000000000..429536ce6e
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_async_timeout.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup with Promise-returning functions (timeout handling)</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+promise_test(function(t) {
+ t.add_cleanup(function() {
+ return Promise.resolve();
+ });
+
+ t.add_cleanup(function() {
+ return new Promise(function() {});
+ });
+
+ t.add_cleanup(function() {});
+
+ t.add_cleanup(function() {
+ return new Promise(function() {});
+ });
+
+ return Promise.resolve();
+}, "promise_test with asynchronous cleanup");
+
+promise_test(function() {}, "promise_test following timed out cleanup ('NOT RUN')");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Timeout while running cleanup for test named \"promise_test with asynchronous cleanup\"."
+ },
+ "summarized_tests": [
+ {
+ "status_string": "NOTRUN",
+ "name": "promise_test following timed out cleanup ('NOT RUN')",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "promise_test with asynchronous cleanup",
+ "message": null,
+ "properties": {}
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_bad_return.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_bad_return.html
new file mode 100644
index 0000000000..3cfb28adf2
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_bad_return.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup with value-returning function</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+test(function(t) {
+ t.add_cleanup(function() {});
+ t.add_cleanup(function() { return null; });
+ t.add_cleanup(function() {
+ test(
+ function() {},
+ "The test runner is in an unpredictable state #1 ('NOT RUN')"
+ );
+
+ throw new Error();
+ });
+ t.add_cleanup(function() { return 4; });
+ t.add_cleanup(function() { return { then: function() {} }; });
+ t.add_cleanup(function() {});
+}, "Test that returns a value in three \"cleanup\" functions");
+
+test(function() {}, "The test runner is in an unpredictable state #2 ('NOT RUN')");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Test named 'Test that returns a value in three \"cleanup\" functions' specified 6 'cleanup' functions, and 1 failed, and 3 returned a non-undefined value."
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Test that returns a value in three \"cleanup\" functions",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "The test runner is in an unpredictable state #1 ('NOT RUN')",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "The test runner is in an unpredictable state #2 ('NOT RUN')",
+ "message": null,
+ "properties": {}
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_count.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_count.html
new file mode 100644
index 0000000000..2c9b51c6d0
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_count.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup reported count</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+
+<script>
+promise_test(function(t) {
+ t.add_cleanup(function() {});
+ t.add_cleanup(function() {});
+ t.add_cleanup(function() { throw new Error(); });
+ new EventWatcher(t, document.body, []);
+
+ return Promise.resolve();
+}, 'test with 3 user-defined cleanup functions');
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Test named 'test with 3 user-defined cleanup functions' specified 3 'cleanup' functions, and 1 failed."
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "test with 3 user-defined cleanup functions",
+ "message": null,
+ "properties": {}
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_err.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_err.html
new file mode 100644
index 0000000000..60357c66ee
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_err.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup: error</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+<script>
+"use strict";
+
+test(function(t) {
+ t.add_cleanup(function() {
+ throw new Error('exception in cleanup function');
+ });
+}, "Exception in cleanup function causes harness failure.");
+
+test(function() {}, "This test should not be run.");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Test named 'Exception in cleanup function causes harness failure.' specified 1 'cleanup' function, and 1 failed."
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Exception in cleanup function causes harness failure.",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "This test should not be run.",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_err_multi.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_err_multi.html
new file mode 100644
index 0000000000..80ba1b4959
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_err_multi.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup: multiple functions with one in error</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+
+<script>
+"use strict";
+
+test(function(t) {
+ t.add_cleanup(function() {
+ throw new Error("exception in cleanup function");
+ });
+
+ // The following cleanup function defines a test so that the reported
+ // data demonstrates the intended run-time behavior, i.e. that
+ // `testharness.js` invokes all cleanup functions even when one or more
+ // throw errors.
+ t.add_cleanup(function() {
+ test(function() {}, "Verification test");
+ });
+ }, "Test with multiple cleanup functions");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Test named 'Test with multiple cleanup functions' specified 2 'cleanup' functions, and 1 failed."
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Test with multiple cleanup functions",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "Verification test",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_sync_queue.html b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_sync_queue.html
new file mode 100644
index 0000000000..0a61503cc8
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/add_cleanup_sync_queue.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#add_cleanup: queuing tests</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+
+<script>
+"use strict";
+var firstCleaned = false;
+
+promise_test(function(t) {
+ promise_test(function() {
+ assert_true(
+ firstCleaned, "should not execute until first test is complete"
+ );
+
+ return Promise.resolve();
+ }, "test defined when no tests are queued, but one test is executing");
+
+ t.add_cleanup(function() {
+ firstCleaned = true;
+ });
+
+ return Promise.resolve();
+}, "Test with a 'cleanup' function");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "message": null,
+ "name": "Test with a 'cleanup' function",
+ "status_string": "PASS",
+ "properties": {}
+ },
+ {
+ "message": null,
+ "name": "test defined when no tests are queued, but one test is executing",
+ "status_string": "PASS",
+ "properties": {}
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/api-tests-1.html b/testing/web-platform/tests/resources/test/tests/functional/api-tests-1.html
new file mode 100644
index 0000000000..9de875b0f1
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/api-tests-1.html
@@ -0,0 +1,991 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Sample HTML5 API Tests</title>
+<meta name="timeout" content="6000">
+</head>
+<body onload="load_test_attr.done()">
+<h1>Sample HTML5 API Tests</h1>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ setup_run = false;
+ setup(function() {
+ setup_run = true;
+ });
+ test(function() {assert_true(setup_run)}, "Setup function ran");
+
+ // Two examples for testing events from handler and attributes
+ var load_test_event = async_test("window onload event fires when set from the handler");
+
+ function windowLoad()
+ {
+ load_test_event.done();
+ }
+ on_event(window, "load", windowLoad);
+
+ test(function() {
+ var sequence = [];
+ var outer = document.createElement("div");
+ var inner = document.createElement("div");
+ outer.appendChild(inner);
+ document.body.appendChild(outer);
+ inner.addEventListener("click", function() {
+ sequence.push("inner");
+ }, false);
+
+ on_event(outer, "click", function() {
+ sequence.push("outer");
+ });
+ inner.click();
+
+ assert_array_equals(sequence, ["inner", "outer"]);
+ }, "on_event does not use event capture");
+
+ // see the body onload below
+ var load_test_attr = async_test("body element fires the onload event set from the attribute");
+</script>
+<script>
+ function bodyElement()
+ {
+ assert_equals(document.body, document.getElementsByTagName("body")[0]);
+ }
+ test(bodyElement, "document.body should be the first body element in the document");
+
+ test(function() {
+ assert_equals(1,1);
+ assert_equals(NaN, NaN, "NaN case");
+ assert_equals(0, 0, "Zero case");
+ }, "assert_equals tests")
+
+ test(function() {
+ assert_equals(-0, 0, "Zero case");
+ }, "assert_equals tests expected to fail")
+
+ test(function() {
+ assert_not_equals({}, {}, "object case");
+ assert_not_equals(-0, 0, "Zero case");
+ }, "assert_not_equals tests")
+
+ function testAssertPass()
+ {
+ assert_true(true);
+ }
+ test(testAssertPass, "assert_true expected to pass");
+
+ function testAssertFalse()
+ {
+ assert_true(false, "false should not be true");
+ }
+ test(testAssertFalse, "assert_true expected to fail");
+
+ function basicAssertArrayEquals()
+ {
+ assert_array_equals([1, NaN], [1, NaN], "[1, NaN] is equal to [1, NaN]");
+ }
+ test(basicAssertArrayEquals, "basic assert_array_equals test");
+
+ function assertArrayEqualsUndefined()
+ {
+ assert_array_equals(undefined, [1], "undefined equals [1]?");
+ }
+ test(assertArrayEqualsUndefined, "assert_array_equals with first param undefined");
+
+ function assertArrayEqualsTrue()
+ {
+ assert_array_equals(true, [1], "true equals [1]?");
+ }
+ test(assertArrayEqualsTrue, "assert_array_equals with first param true");
+
+ function assertArrayEqualsFalse()
+ {
+ assert_array_equals(false, [1], "false equals [1]?");
+ }
+ test(assertArrayEqualsFalse, "assert_array_equals with first param false");
+
+ function assertArrayEqualsNull()
+ {
+ assert_array_equals(null, [1], "null equals [1]?");
+ }
+ test(assertArrayEqualsNull, "assert_array_equals with first param null");
+
+ function assertArrayEqualsNumeric()
+ {
+ assert_array_equals(1, [1], "1 equals [1]?");
+ }
+ test(assertArrayEqualsNumeric, "assert_array_equals with first param 1");
+
+ function basicAssertArrayApproxEquals()
+ {
+ assert_array_approx_equals([10, 11], [11, 10], 1, "[10, 11] is approximately (+/- 1) [11, 10]")
+ }
+ test(basicAssertArrayApproxEquals, "basic assert_array_approx_equals test");
+
+ function basicAssertApproxEquals()
+ {
+ assert_approx_equals(10, 11, 1, "10 is approximately (+/- 1) 11")
+ }
+ test(basicAssertApproxEquals, "basic assert_approx_equals test");
+
+ function basicAssertLessThan()
+ {
+ assert_less_than(10, 11, "10 is less than 11")
+ }
+ test(basicAssertApproxEquals, "basic assert_less_than test");
+
+ function basicAssertGreaterThan()
+ {
+ assert_greater_than(10, 11, "10 is not greater than 11");
+ }
+ test(basicAssertGreaterThan, "assert_greater_than expected to fail");
+
+ function basicAssertGreaterThanEqual()
+ {
+ assert_greater_than_equal(10, 10, "10 is greater than or equal to 10")
+ }
+ test(basicAssertGreaterThanEqual, "basic assert_greater_than_equal test");
+
+ function basicAssertLessThanEqual()
+ {
+ assert_greater_than_equal('10', 10, "'10' is not a number")
+ }
+ test(basicAssertLessThanEqual, "assert_less_than_equal expected to fail");
+
+ function testAssertInherits() {
+ var A = function(){this.a = "a"}
+ A.prototype = {b:"b"}
+ var a = new A();
+ assert_own_property(a, "a");
+ assert_not_own_property(a, "b", "unexpected property found: \"b\"");
+ assert_inherits(a, "b");
+ }
+ test(testAssertInherits, "test for assert[_not]_own_property and insert_inherits")
+
+ test(function()
+ {
+ var a = document.createElement("a")
+ var b = document.createElement("b")
+ assert_throws_dom("NOT_FOUND_ERR", function () {a.removeChild(b)});
+ }, "Test throw DOM exception")
+
+ test(function()
+ {
+ var a = document.createElement("a")
+ var b = document.createElement("b")
+ assert_throws_js(DOMException, function () {a.removeChild(b)});
+ }, "Test throw DOMException as JS exception expected to fail")
+
+ test(function()
+ {
+ assert_throws_js(SyntaxError, function () {document.querySelector("")});
+ }, "Test throw SyntaxError DOMException where JS SyntaxError expected; expected to fail")
+
+ test(function()
+ {
+ assert_throws_js(SyntaxError, function () {JSON.parse("{")});
+ }, "Test throw JS SyntaxError")
+
+ test(function()
+ {
+ assert_throws_dom("SyntaxError", function () {document.querySelector("")});
+ }, "Test throw DOM SyntaxError")
+
+ test(function()
+ {
+ var ifr = document.createElement("iframe");
+ document.body.appendChild(ifr);
+ this.add_cleanup(() => ifr.remove());
+ assert_throws_dom("SyntaxError", ifr.contentWindow.DOMException,
+ function () {ifr.contentDocument.querySelector("")});
+ }, "Test throw DOM SyntaxError from subframe");
+
+ test(function()
+ {
+ var ifr = document.createElement("iframe");
+ document.body.appendChild(ifr);
+ this.add_cleanup(() => ifr.remove());
+ assert_throws_dom("SyntaxError",
+ function () {ifr.contentDocument.querySelector("")});
+ }, "Test throw DOM SyntaxError from subframe with incorrect global expectation; expected to fail");
+
+ test(function()
+ {
+ var ifr = document.createElement("iframe");
+ document.body.appendChild(ifr);
+ this.add_cleanup(() => ifr.remove());
+ assert_throws_dom("SyntaxError", ifr.contentWindow.DOMException,
+ function () {document.querySelector("")});
+ }, "Test throw DOM SyntaxError with incorrect expectation; expected to fail");
+
+ test(function()
+ {
+ assert_throws_dom("SyntaxError", function () {JSON.parse("{")});
+ }, "Test throw JS SyntaxError where SyntaxError DOMException expected; expected to fail")
+
+ test(function()
+ {
+ var a = document.createTextNode("a")
+ var b = document.createElement("b")
+ assert_throws_dom("NOT_FOUND_ERR", function () {a.appendChild(b)});
+ }, "Test throw DOM exception expected to fail")
+
+ test(function()
+ {
+ var e = new DOMException("I am not known", "TEST_ERROR_NO_SUCH_THING");
+ assert_throws_dom(0, function() {throw e});
+ }, "Test assert_throws_dom with ambiguous DOM-exception expected to Fail");
+
+ test(function()
+ {
+ var e = {code:0, name:"TEST_ERR", TEST_ERR:0};
+ e.constructor = DOMException;
+ assert_throws_dom("TEST_ERR", function() {throw e});
+ }, "Test assert_throws_dom with non-DOM-exception expected to Fail");
+
+ test(function()
+ {
+ var e = {code: DOMException.SYNTAX_ERR, name:"SyntaxError"};
+ e.constructor = DOMException;
+ assert_throws_dom(DOMException.SYNTAX_ERR, function() {throw e});
+ }, "Test assert_throws_dom with number code value expected to Pass");
+
+ test(function()
+ {
+ var e = new DOMException("Some message", "SyntaxError");
+ assert_throws_dom(DOMException.SYNTAX_ERR, function() {throw e});
+ }, "Test assert_throws_dom with number code value and real DOMException expected to Pass");
+
+ var t = async_test("Test step_func")
+ setTimeout(
+ t.step_func(
+ function () {
+ assert_true(true); t.done();
+ }), 0);
+
+ async_test(function(t) {
+ setTimeout(t.step_func(function (){assert_true(true); t.done();}), 0);
+ }, "Test async test with callback");
+
+ async_test(function() {
+ setTimeout(this.step_func(function (){assert_true(true); this.done();}), 0);
+ }, "Test async test with callback and `this` obj.");
+
+ async_test("test should timeout (fail) with the default of 2 seconds").step(function(){});
+
+ async_test("async test that is never started, should have status Not Run");
+
+
+ test(function(t) {
+ window.global = 1;
+ t.add_cleanup(function() {delete window.global});
+ assert_equals(window.global, 1);
+ },
+ "Test that defines a global and cleans it up");
+
+ test(function() {assert_equals(window.global, undefined)},
+ "Test that cleanup handlers from previous test ran");
+
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "TIMEOUT",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Setup function ran",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Test assert_throws_dom with ambiguous DOM-exception expected to Fail",
+ "message": "Test bug: ambiguous DOMException code 0 passed to assert_throws_dom()",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Test assert_throws_dom with non-DOM-exception expected to Fail",
+ "message": "Test bug: unrecognized DOMException code name or name \"TEST_ERR\" passed to assert_throws_dom()",
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test assert_throws_dom with number code value expected to Pass",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test assert_throws_dom with number code value and real DOMException expected to Pass",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test async test with callback",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test async test with callback and `this` obj.",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test step_func",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test that cleanup handlers from previous test ran",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test that defines a global and cleans it up",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test throw DOM exception",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Test throw DOMException as JS exception expected to fail",
+ "message": "assert_throws_js: function \"function DOMException() {\n [native code]\n}\" is not an Error subtype",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Test throw SyntaxError DOMException where JS SyntaxError expected; expected to fail",
+ "message": "assert_throws_js: function \"function () {document.querySelector(\"\")}\" threw object \"SyntaxError: Document.querySelector: '' is not a valid selector\" (\"SyntaxError\") expected instance of function \"function SyntaxError() {\n [native code]\n}\" (\"SyntaxError\")",
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test throw JS SyntaxError",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test throw DOM SyntaxError",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test throw DOM SyntaxError from subframe",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Test throw DOM SyntaxError from subframe with incorrect global expectation; expected to fail",
+ "message": "assert_throws_dom: function \"function () {ifr.contentDocument.querySelector(\"\")}\" threw an exception from the wrong global",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Test throw DOM SyntaxError with incorrect expectation; expected to fail",
+ "message": "assert_throws_dom: function \"function () {document.querySelector(\"\")}\" threw an exception from the wrong global",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Test throw JS SyntaxError where SyntaxError DOMException expected; expected to fail",
+ "message": "assert_throws_dom: function \"function () {JSON.parse(\"{\")}\" threw object \"SyntaxError: JSON.parse: end of data while reading object contents at line 1 column 2 of the JSON data\" that is not a DOMException SyntaxError: property \"code\" is equal to undefined, expected 12",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Test throw DOM exception expected to fail",
+ "message": "assert_throws_dom: function \"function () {a.appendChild(b)}\" threw object \"HierarchyRequestError: Node.appendChild: Cannot add children to a Text\" that is not a DOMException NOT_FOUND_ERR: property \"code\" is equal to 3, expected 8",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "assert_array_equals with first param 1",
+ "message": "assert_array_equals: 1 equals [1]? value is 1, expected array",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "assert_array_equals with first param false",
+ "message": "assert_array_equals: false equals [1]? value is false, expected array",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "assert_array_equals with first param null",
+ "message": "assert_array_equals: null equals [1]? value is null, expected array",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "assert_array_equals with first param true",
+ "message": "assert_array_equals: true equals [1]? value is true, expected array",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "assert_array_equals with first param undefined",
+ "message": "assert_array_equals: undefined equals [1]? value is undefined, expected array",
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "assert_equals tests",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "assert_equals tests expected to fail",
+ "message": "assert_equals: Zero case expected 0 but got -0",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "assert_greater_than expected to fail",
+ "message": "assert_greater_than: 10 is not greater than 11 expected a number greater than 11 but got 10",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "assert_less_than_equal expected to fail",
+ "message": "assert_greater_than_equal: '10' is not a number expected a number but got a \"string\"",
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "assert_not_equals tests",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "assert_true expected to fail",
+ "message": "assert_true: false should not be true expected true got false",
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "assert_true expected to pass",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "async test that is never started, should have status Not Run",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "basic assert_approx_equals test",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "basic assert_array_approx_equals test",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "basic assert_array_equals test",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "basic assert_greater_than_equal test",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "basic assert_less_than test",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "body element fires the onload event set from the attribute",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "document.body should be the first body element in the document",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "test for assert[_not]_own_property and insert_inherits",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "TIMEOUT",
+ "name": "test should timeout (fail) with the default of 2 seconds",
+ "message": "Test timed out",
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "window onload event fires when set from the handler",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "on_event does not use event capture",
+ "message": null,
+ "properties": {}
+ }
+ ],
+ "summarized_asserts": [
+ {
+ "assert_name": "assert_true",
+ "test": "Setup function ran",
+ "args": [
+ "true"
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_array_equals",
+ "test": "on_event does not use event capture",
+ "args": [
+ "[\"inner\", \"outer\"]",
+ "[\"inner\", \"outer\"]"
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_equals",
+ "test": "document.body should be the first body element in the document",
+ "args": [
+ "Element node <body onload=\"load_test_attr.done()\"> <h1>Sample HTML5 AP...",
+ "Element node <body onload=\"load_test_attr.done()\"> <h1>Sample HTML5 AP..."
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_equals",
+ "test": "assert_equals tests",
+ "args": [
+ "1",
+ "1"
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_equals",
+ "test": "assert_equals tests",
+ "args": [
+ "NaN",
+ "NaN",
+ "\"NaN case\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_equals",
+ "test": "assert_equals tests",
+ "args": [
+ "0",
+ "0",
+ "\"Zero case\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_equals",
+ "test": "assert_equals tests expected to fail",
+ "args": [
+ "-0",
+ "0",
+ "\"Zero case\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_not_equals",
+ "test": "assert_not_equals tests",
+ "args": [
+ "object \"[object Object]\"",
+ "object \"[object Object]\"",
+ "\"object case\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_not_equals",
+ "test": "assert_not_equals tests",
+ "args": [
+ "-0",
+ "0",
+ "\"Zero case\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_true",
+ "test": "assert_true expected to pass",
+ "args": [
+ "true"
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_true",
+ "test": "assert_true expected to fail",
+ "args": [
+ "false",
+ "\"false should not be true\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_array_equals",
+ "test": "basic assert_array_equals test",
+ "args": [
+ "[1, NaN]",
+ "[1, NaN]",
+ "\"[1, NaN] is equal to [1, NaN]\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_array_equals",
+ "test": "assert_array_equals with first param undefined",
+ "args": [
+ "undefined",
+ "[1]",
+ "\"undefined equals [1]?\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_array_equals",
+ "test": "assert_array_equals with first param true",
+ "args": [
+ "true",
+ "[1]",
+ "\"true equals [1]?\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_array_equals",
+ "test": "assert_array_equals with first param false",
+ "args": [
+ "false",
+ "[1]",
+ "\"false equals [1]?\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_array_equals",
+ "test": "assert_array_equals with first param null",
+ "args": [
+ "null",
+ "[1]",
+ "\"null equals [1]?\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_array_equals",
+ "test": "assert_array_equals with first param 1",
+ "args": [
+ "1",
+ "[1]",
+ "\"1 equals [1]?\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_array_approx_equals",
+ "test": "basic assert_array_approx_equals test",
+ "args": [
+ "[10, 11]",
+ "[11, 10]",
+ "1",
+ "\"[10, 11] is approximately (+/- 1) [11, 10]\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_approx_equals",
+ "test": "basic assert_approx_equals test",
+ "args": [
+ "10",
+ "11",
+ "1",
+ "\"10 is approximately (+/- 1) 11\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_approx_equals",
+ "test": "basic assert_less_than test",
+ "args": [
+ "10",
+ "11",
+ "1",
+ "\"10 is approximately (+/- 1) 11\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_greater_than",
+ "test": "assert_greater_than expected to fail",
+ "args": [
+ "10",
+ "11",
+ "\"10 is not greater than 11\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_greater_than_equal",
+ "test": "basic assert_greater_than_equal test",
+ "args": [
+ "10",
+ "10",
+ "\"10 is greater than or equal to 10\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_greater_than_equal",
+ "test": "assert_less_than_equal expected to fail",
+ "args": [
+ "\"10\"",
+ "10",
+ "\"'10' is not a number\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_own_property",
+ "test": "test for assert[_not]_own_property and insert_inherits",
+ "args": [
+ "object \"[object Object]\"",
+ "\"a\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_not_own_property",
+ "test": "test for assert[_not]_own_property and insert_inherits",
+ "args": [
+ "object \"[object Object]\"",
+ "\"b\"",
+ "\"unexpected property found: \\\"b\\\"\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_inherits",
+ "test": "test for assert[_not]_own_property and insert_inherits",
+ "args": [
+ "object \"[object Object]\"",
+ "\"b\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test throw DOM exception",
+ "args": [
+ "\"NOT_FOUND_ERR\"",
+ "function \"function () {a.removeChild(b)}\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_throws_js",
+ "test": "Test throw DOMException as JS exception expected to fail",
+ "args": [
+ "function \"function DOMException() { [native code] }\"",
+ "function \"function () {a.removeChild(b)}\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_throws_js",
+ "test": "Test throw SyntaxError DOMException where JS SyntaxError expected; expected to fail",
+ "args": [
+ "function \"function SyntaxError() { [native code] }\"",
+ "function \"function () {document.querySelector(\"\")}\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_throws_js",
+ "test": "Test throw JS SyntaxError",
+ "args": [
+ "function \"function SyntaxError() { [native code] }\"",
+ "function \"function () {JSON.parse(\"{\")}\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test throw DOM SyntaxError",
+ "args": [
+ "\"SyntaxError\"",
+ "function \"function () {document.querySelector(\"\")}\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test throw DOM SyntaxError from subframe",
+ "args": [
+ "\"SyntaxError\"",
+ "function \"function DOMException() { [native code] }\"",
+ "function \"function () {ifr.contentDocument.querySelector(\"\")}\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test throw DOM SyntaxError from subframe with incorrect global expectation; expected to fail",
+ "args": [
+ "\"SyntaxError\"",
+ "function \"function () {ifr.contentDocument.querySelector(\"\")}\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test throw DOM SyntaxError with incorrect expectation; expected to fail",
+ "args": [
+ "\"SyntaxError\"",
+ "function \"function DOMException() { [native code] }\"",
+ "function \"function () {document.querySelector(\"\")}\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test throw JS SyntaxError where SyntaxError DOMException expected; expected to fail",
+ "args": [
+ "\"SyntaxError\"",
+ "function \"function () {JSON.parse(\"{\")}\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test throw DOM exception expected to fail",
+ "args": [
+ "\"NOT_FOUND_ERR\"",
+ "function \"function () {a.appendChild(b)}\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test assert_throws_dom with ambiguous DOM-exception expected to Fail",
+ "args": [
+ "0",
+ "function \"function() {throw e}\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test assert_throws_dom with non-DOM-exception expected to Fail",
+ "args": [
+ "\"TEST_ERR\"",
+ "function \"function() {throw e}\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test assert_throws_dom with number code value expected to Pass",
+ "args": [
+ "12",
+ "function \"function() {throw e}\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Test assert_throws_dom with number code value and real DOMException expected to Pass",
+ "args": [
+ "12",
+ "function \"function() {throw e}\""
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_equals",
+ "test": "Test that defines a global and cleans it up",
+ "args": [
+ "1",
+ "1"
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_equals",
+ "test": "Test that cleanup handlers from previous test ran",
+ "args": [
+ "undefined",
+ "undefined"
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_true",
+ "test": "Test step_func",
+ "args": [
+ "true"
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_true",
+ "test": "Test async test with callback",
+ "args": [
+ "true"
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_true",
+ "test": "Test async test with callback and `this` obj.",
+ "args": [
+ "true"
+ ],
+ "status": 0
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/api-tests-2.html b/testing/web-platform/tests/resources/test/tests/functional/api-tests-2.html
new file mode 100644
index 0000000000..9af94f61ac
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/api-tests-2.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Sample HTML5 API Tests</title>
+</head>
+<body>
+<h1>Sample HTML5 API Tests</h1>
+<p>There should be two results</p>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({explicit_done:true})
+test(function() {assert_true(true)}, "Test defined before onload");
+
+onload = function() {test(function (){assert_true(true)}, "Test defined after onload");
+done();
+}
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Test defined after onload",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test defined before onload",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "summarized_asserts": [
+ {
+ "assert_name": "assert_true",
+ "test": "Test defined before onload",
+ "args": [
+ "true"
+ ],
+ "status": 0
+ },
+ {
+ "assert_name": "assert_true",
+ "test": "Test defined after onload",
+ "args": [
+ "true"
+ ],
+ "status": 0
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/api-tests-3.html b/testing/web-platform/tests/resources/test/tests/functional/api-tests-3.html
new file mode 100644
index 0000000000..991fc6da67
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/api-tests-3.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Sample HTML5 API Tests</title>
+</head>
+<script src="/resources/testharness.js"></script>
+
+<body>
+<h1>Sample HTML5 API Tests</h1>
+<div id="log"></div>
+<script>
+setup({explicit_timeout:true});
+var t = async_test("This test should give a status of 'Not Run' without a delay");
+timeout();
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "TIMEOUT",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "NOTRUN",
+ "name": "This test should give a status of 'Not Run' without a delay",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/assert-array-equals.html b/testing/web-platform/tests/resources/test/tests/functional/assert-array-equals.html
new file mode 100644
index 0000000000..b6460a4868
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/assert-array-equals.html
@@ -0,0 +1,162 @@
+<!DOCTYPE HTML>
+<title>assert_array_equals</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(() => {
+ assert_array_equals([], []);
+}, "empty and equal");
+test(() => {
+ assert_array_equals([1], [1]);
+}, "non-empty and equal");
+test(() => {
+ assert_array_equals([], [1]);
+}, "length differs");
+test(() => {
+ assert_array_equals([1], [,]);
+}, "property is present");
+test(() => {
+ assert_array_equals([,], [1]);
+}, "property is missing");
+test(() => {
+ assert_array_equals([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20], ["x",1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]);
+}, "property 0 differs");
+test(() => {
+ assert_array_equals([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20], [0,1,2,3,4,"x",6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]);
+}, "property 5 differs");
+test(() => {
+ assert_array_equals([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21], [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]);
+}, "lengths differ and input array beyond display limit");
+test(() => {
+ assert_array_equals([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21], [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]);
+}, "lengths differ and expected array beyond display limit");
+test(() => {
+ assert_array_equals([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21], [,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]);
+}, "property 0 is present and arrays are beyond display limit");
+test(() => {
+ assert_array_equals([,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21], [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]);
+}, "property 0 is missing and arrays are beyond display limit");
+test(() => {
+ assert_array_equals([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21], [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,,19,20,21]);
+}, "property 18 is present and arrays are beyond display limit");
+test(() => {
+ assert_array_equals([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,,19,20,21], [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]);
+}, "property 18 is missing and arrays are beyond display limit");
+test(() => {
+ assert_array_equals([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21], ["x",1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]);
+}, "property 0 differs and arrays are beyond display limit");
+test(() => {
+ assert_array_equals([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21], [0,1,2,3,4,"x",6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21]);
+}, "property 5 differs and arrays are beyond display limit");
+test(() => {
+ assert_array_equals([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26], [0,1,2,3,4,5,6,7,8,9,10,11,"x",13,14,15,16,17,18,19,20,21,22,23,24,25,26]);
+}, "property 5 differs and arrays are beyond display limit on both sides");
+</script>
+<script type="text/json" id="expected">
+{
+ "type": "complete",
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "name": "empty and equal",
+ "message": null,
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "name": "length differs",
+ "message": "assert_array_equals: lengths differ, expected array [1] length 1, got [] length 0",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "lengths differ and expected array beyond display limit",
+ "message": "assert_array_equals: lengths differ, expected array [\u2026, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] length 22, got [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] length 21",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "lengths differ and input array beyond display limit",
+ "message": "assert_array_equals: lengths differ, expected array [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] length 21, got [\u2026, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] length 22",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "non-empty and equal",
+ "message": null,
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "name": "property 0 differs",
+ "message": "assert_array_equals: expected property 0 to be \"x\" but got 0 (expected array [\"x\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] got [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "property 0 differs and arrays are beyond display limit",
+ "message": "assert_array_equals: expected property 0 to be \"x\" but got 0 (expected array [\"x\", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, \u2026] got [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, \u2026])",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "property 0 is missing and arrays are beyond display limit",
+ "message": "assert_array_equals: expected property 0 to be \"present\" but was \"missing\" (expected array [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, \u2026] got [, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, \u2026])",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "property 0 is present and arrays are beyond display limit",
+ "message": "assert_array_equals: expected property 0 to be \"missing\" but was \"present\" (expected array [, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, \u2026] got [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, \u2026])",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "property 18 is missing and arrays are beyond display limit",
+ "message": "assert_array_equals: expected property 18 to be \"present\" but was \"missing\" (expected array [\u2026, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] got [\u2026, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, , 19, 20, 21])",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "property 18 is present and arrays are beyond display limit",
+ "message": "assert_array_equals: expected property 18 to be \"missing\" but was \"present\" (expected array [\u2026, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, , 19, 20, 21] got [\u2026, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21])",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "property 5 differs",
+ "message": "assert_array_equals: expected property 5 to be \"x\" but got 5 (expected array [0, 1, 2, 3, 4, \"x\", 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] got [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "property 5 differs and arrays are beyond display limit",
+ "message": "assert_array_equals: expected property 5 to be \"x\" but got 5 (expected array [0, 1, 2, 3, 4, \"x\", 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, \u2026] got [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, \u2026])",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "property 5 differs and arrays are beyond display limit on both sides",
+ "message": "assert_array_equals: expected property 12 to be \"x\" but got 12 (expected array [\u2026, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, \"x\", 13, 14, 15, 16, 17, 18, 19, 20, 21, \u2026] got [\u2026, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, \u2026])",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "property is missing",
+ "message": "assert_array_equals: expected property 0 to be \"present\" but was \"missing\" (expected array [1] got [])",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "property is present",
+ "message": "assert_array_equals: expected property 0 to be \"missing\" but was \"present\" (expected array [] got [1])",
+ "properties": {},
+ "status_string": "FAIL"
+ }
+ ]
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/assert-throws-dom.html b/testing/web-platform/tests/resources/test/tests/functional/assert-throws-dom.html
new file mode 100644
index 0000000000..4dd66b2372
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/assert-throws-dom.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<title>assert_throws_dom</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(() => {
+ function f() {
+ assert_true(false, "Trivial assertion.");
+
+ // Would lead to throwing a SyntaxError.
+ document.createElement("div").contentEditable = "invalid";
+ }
+
+ assert_throws_dom("SyntaxError", () => { f(); });
+}, "Violated assertion nested in `assert_throws_dom`.");
+</script>
+<script type="text/json" id="expected">
+{
+ "type": "complete",
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "message": "assert_true: Trivial assertion. expected true got false",
+ "name": "Violated assertion nested in `assert_throws_dom`.",
+ "properties": {},
+ "status_string": "FAIL"
+ }
+ ],
+ "summarized_asserts": [
+ {
+ "assert_name": "assert_throws_dom",
+ "test": "Violated assertion nested in `assert_throws_dom`.",
+ "args": [
+ "\"SyntaxError\"",
+ "function \"() => { f(); }\""
+ ],
+ "status": 1
+ },
+ {
+ "assert_name": "assert_true",
+ "test": "Violated assertion nested in `assert_throws_dom`.",
+ "args": [
+ "false",
+ "\"Trivial assertion.\""
+ ],
+ "status": 1
+ }
+ ]
+}
+</script>
+
diff --git a/testing/web-platform/tests/resources/test/tests/functional/force_timeout.html b/testing/web-platform/tests/resources/test/tests/functional/force_timeout.html
new file mode 100644
index 0000000000..2058fdb862
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/force_timeout.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test#force_timeout</title>
+</head>
+<body>
+<h1>Test#force_timeout</h1>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ explicit_timeout: true });
+
+test(function(t) {
+ t.force_timeout();
+ }, 'test (synchronous)');
+
+async_test(function(t) {
+ t.step_timeout(function() {
+ t.force_timeout();
+ }, 0);
+ }, 'async_test');
+
+promise_test(function(t) {
+ t.force_timeout();
+
+ return new Promise(function() {});
+ }, 'promise_test');
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "TIMEOUT",
+ "name": "async_test",
+ "message": "Test timed out",
+ "properties": {}
+ },
+ {
+ "status_string": "TIMEOUT",
+ "name": "promise_test",
+ "message": "Test timed out",
+ "properties": {}
+ },
+ {
+ "status_string": "TIMEOUT",
+ "name": "test (synchronous)",
+ "message": "Test timed out",
+ "properties": {}
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/generate-callback.html b/testing/web-platform/tests/resources/test/tests/functional/generate-callback.html
new file mode 100644
index 0000000000..11d41743b3
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/generate-callback.html
@@ -0,0 +1,153 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Sample for using generate_tests to create a series of tests that share the same callback.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+// generate_tests takes an array of arrays that define tests
+// but lets pass it an empty array and verify it does nothing.
+function null_callback() {
+ throw "null_callback should not be called.";
+}
+generate_tests(null_callback, []);
+
+// Generate 3 tests specifying the name and one parameter
+function validate_arguments(arg1) {
+ assert_equals(arg1, 1, "Ensure that we get our expected argument");
+}
+generate_tests(validate_arguments, [
+ ["first test", 1],
+ ["second test", 1],
+ ["third test", 1],
+]);
+
+// Generate a test passing in a properties object that is shared across tests.
+function validate_properties() {
+ assert_true(this.properties.sentinel, "Ensure that we got the right properties object.");
+}
+generate_tests(validate_properties, [["sentinel check 1"], ["sentinel check 2"]], {sentinel: true});
+
+// Generate a test passing in a properties object that is shared across tests.
+function validate_separate_properties() {
+ if (this.name === "sentinel check 1 unique properties") {
+ assert_true(this.properties.sentinel, "Ensure that we got the right properties object. Expect sentinel: true.");
+ }
+ else {
+ assert_false(this.properties.sentinel, "Ensure that we got the right properties object. Expect sentinel: false.");
+ }
+}
+generate_tests(validate_separate_properties, [["sentinel check 1 unique properties"], ["sentinel check 2 unique properties"]], [{sentinel: true}, {sentinel: false}]);
+
+// Finally generate a complicated set of tests from another data source
+var letters = ["a", "b", "c", "d", "e", "f"];
+var numbers = [0, 1, 2, 3, 4, 5];
+function validate_related_arguments(arg1, arg2) {
+ assert_equals(arg1.charCodeAt(0) - "a".charCodeAt(0), arg2, "Ensure that we can map letters to numbers.");
+}
+function format_as_test(letter, index, letters) {
+ return ["Test to map " + letter + " to " + numbers[index], letter, numbers[index]];
+}
+generate_tests(validate_related_arguments, letters.map(format_as_test));
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Test to map a to 0",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test to map b to 1",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test to map c to 2",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test to map d to 3",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test to map e to 4",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test to map f to 5",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "first test",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "second test",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "sentinel check 1",
+ "properties": {
+ "sentinel": true
+ },
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "sentinel check 1 unique properties",
+ "properties": {
+ "sentinel": true
+ },
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "sentinel check 2",
+ "properties": {
+ "sentinel": true
+ },
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "sentinel check 2 unique properties",
+ "properties": {
+ "sentinel": false
+ },
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "third test",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html
new file mode 100644
index 0000000000..f635768c69
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlDictionary/test_partial_interface_of.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>idlharness: Partial dictionary</title>
+ <script src="/resources/test/variants.js"></script>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+</head>
+
+<body>
+ <p>Verify the series of sub-tests that are executed for "partial" dictionary objects.</p>
+ <script>
+ "use strict";
+
+ // No original existence
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls('partial dictionary A {};');
+ idlArray.test();
+ })();
+
+ // Multiple partials existence
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_untested_idls('partial dictionary B {};');
+ idlArray.add_idls('partial dictionary B {};');
+ idlArray.add_idls('partial dictionary B {};');
+ idlArray.add_idls('partial dictionary B {};');
+ idlArray.add_idls('dictionary B {};');
+ idlArray.test();
+ })();
+
+ // Original is a namespace, not a dictionary.
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls(`
+ partial dictionary C {};
+ namespace C {};`);
+ idlArray.merge_partials();
+ })();
+ </script>
+ <script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "name": "Partial dictionary A: original dictionary defined",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_true: Original dictionary should be defined expected true got false"
+ },
+ {
+ "name": "Partial dictionary B[2]: original dictionary defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial dictionary B[3]: original dictionary defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial dictionary B[4]: original dictionary defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial dictionary C: original dictionary defined",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_true: Original C definition should have type dictionary expected true got false"
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_exposed_wildcard.html b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_exposed_wildcard.html
new file mode 100644
index 0000000000..addc0eb4fc
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_exposed_wildcard.html
@@ -0,0 +1,233 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>idlharness: Exposed=*</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+</head>
+<body>
+<script>
+"use strict";
+
+Object.defineProperty(window, "A", {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: function A() {}
+ });
+Object.defineProperty(window.A, "prototype", {
+ writable: false,
+ value: window.A.prototype
+ });
+A.prototype[Symbol.toStringTag] = "A";
+
+Object.defineProperty(window, "C", {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: function C() {}
+ });
+Object.defineProperty(window.C, "prototype", {
+ writable: false,
+ value: window.C.prototype
+ });
+C.prototype[Symbol.toStringTag] = "C";
+
+Object.defineProperty(window, "D", {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: function D() {}
+ });
+Object.defineProperty(window.D, "prototype", {
+ writable: false,
+ value: window.D.prototype
+ });
+C.prototype[Symbol.toStringTag] = "D";
+Object.defineProperty(window, "B", {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: window.A
+ });
+
+var idlArray = new IdlArray();
+idlArray.add_idls(`
+[Exposed=*, LegacyWindowAlias=B] interface A {};
+[Exposed=*] partial interface A {};
+[Exposed=Window] interface C {};
+[Exposed=*] partial interface C {};
+[Exposed=*] interface D {};
+[Exposed=Window] partial interface D {};
+`);
+idlArray.add_objects({
+ Window: ["window"]
+});
+idlArray.test();
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "name": "A interface object length",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "A interface object name",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "A interface: existence and properties of interface object",
+ "properties": {},
+ "message": "assert_throws_js: interface object didn't throw TypeError when called as a function function \"function() {\n interface_object();\n }\" did not throw",
+ "status_string": "FAIL"
+ },
+ {
+ "name": "A interface: existence and properties of interface prototype object",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "A interface: existence and properties of interface prototype object's \"constructor\" property",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "A interface: existence and properties of interface prototype object's @@unscopables property",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "A interface: legacy window alias",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "C interface object length",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "C interface object name",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "C interface: existence and properties of interface object",
+ "properties": {},
+ "message": "assert_throws_js: interface object didn't throw TypeError when called as a function function \"function() {\n interface_object();\n }\" did not throw",
+ "status_string": "FAIL"
+ },
+ {
+ "name": "C interface: existence and properties of interface prototype object",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "C interface: existence and properties of interface prototype object's \"constructor\" property",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "C interface: existence and properties of interface prototype object's @@unscopables property",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "D interface object length",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "D interface object name",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "D interface: existence and properties of interface object",
+ "properties": {},
+ "message": "assert_throws_js: interface object didn't throw TypeError when called as a function function \"function() {\n interface_object();\n }\" did not throw",
+ "status_string": "FAIL"
+ },
+ {
+ "name": "D interface: existence and properties of interface prototype object",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "D interface: existence and properties of interface prototype object's \"constructor\" property",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "D interface: existence and properties of interface prototype object's @@unscopables property",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "Partial interface A: original interface defined",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "Partial interface A: valid exposure set",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "Partial interface C: original interface defined",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "Partial interface C: valid exposure set",
+ "properties": {},
+ "message": "Partial C interface is exposed everywhere, the original interface is not.",
+ "status_string": "FAIL"
+ },
+ {
+ "name": "Partial interface D: original interface defined",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ },
+ {
+ "name": "Partial interface D: valid exposure set",
+ "properties": {},
+ "message": null,
+ "status_string": "PASS"
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_immutable_prototype.html b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_immutable_prototype.html
new file mode 100644
index 0000000000..5fe05915b0
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_immutable_prototype.html
@@ -0,0 +1,298 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>idlharness: Immutable prototypes</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+</head>
+<body>
+<script>
+"use strict";
+
+Object.defineProperty(window, "Foo", {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: function Foo() {
+ if (!new.target) {
+ throw new TypeError('Foo() must be called with new');
+ }
+ }
+ });
+Object.defineProperty(window.Foo, "prototype", {
+ writable: false,
+ value: window.Foo.prototype
+ });
+Foo.prototype[Symbol.toStringTag] = "Foo";
+
+var idlArray = new IdlArray();
+idlArray.add_untested_idls("interface EventTarget {};");
+idlArray.add_idls(
+ "[Global=Window, Exposed=Window]\n" +
+ "interface Window : EventTarget {};\n" +
+
+ "[Global=Window, Exposed=Window]\n" +
+ "interface Foo { constructor(); };"
+ );
+idlArray.add_objects({
+ Foo: ["new Foo()"],
+ Window: ["window"]
+});
+idlArray.test();
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "name": "Foo interface object length",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface object name",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: existence and properties of interface object",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: existence and properties of interface prototype object",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: existence and properties of interface prototype object's \"constructor\" property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: existence and properties of interface prototype object's @@unscopables property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via Object.setPrototypeOf should throw a TypeError",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_throws_js: function \"function() {\n Object.setPrototypeOf(obj, newValue);\n }\" did not throw"
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via Reflect.setPrototypeOf should return false",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_false: expected false got true"
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via __proto__ should throw a TypeError",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_throws_js: function \"function() {\n obj.__proto__ = newValue;\n }\" did not throw"
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via Object.setPrototypeOf should not throw",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via Reflect.setPrototypeOf should return true",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via __proto__ should not throw",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via Object.setPrototypeOf should throw a TypeError",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_throws_js: function \"function() {\n Object.setPrototypeOf(obj, newValue);\n }\" did not throw"
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via Reflect.setPrototypeOf should return false",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_false: expected false got true"
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via __proto__ should throw a TypeError",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_throws_js: function \"function() {\n obj.__proto__ = newValue;\n }\" did not throw"
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via Object.setPrototypeOf should not throw",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via Reflect.setPrototypeOf should return true",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via __proto__ should not throw",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo must be primary interface of new Foo()",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Stringification of new Foo()",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Stringification of window",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface object length",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface object name",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: existence and properties of interface object",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: existence and properties of interface prototype object",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: existence and properties of interface prototype object's \"constructor\" property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: existence and properties of interface prototype object's @@unscopables property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via Object.setPrototypeOf should throw a TypeError",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via Reflect.setPrototypeOf should return false",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to a new value via __proto__ should throw a TypeError",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via Object.setPrototypeOf should not throw",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via Reflect.setPrototypeOf should return true",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of global platform object - setting to its original value via __proto__ should not throw",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via Object.setPrototypeOf should throw a TypeError",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via Reflect.setPrototypeOf should return false",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to a new value via __proto__ should throw a TypeError",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via Object.setPrototypeOf should not throw",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via Reflect.setPrototypeOf should return true",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window interface: internal [[SetPrototypeOf]] method of interface prototype object - setting to its original value via __proto__ should not throw",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Window must be primary interface of window",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_interface_mixin.html b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_interface_mixin.html
new file mode 100644
index 0000000000..be2844e698
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_interface_mixin.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>idlharness: interface mixins</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+</head>
+
+<body>
+ <p>Verify the series of sub-tests that are executed for "interface mixin" objects.</p>
+ <script>
+ "use strict";
+
+ // Simple includes statement (valid)
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls(`
+ [Exposed=Window] interface I1 {};
+ interface mixin M1 { attribute any a1; };
+ I1 includes M1;`);
+ idlArray.merge_partials();
+ idlArray.merge_mixins();
+ })();
+
+ // Partial interface mixin (valid)
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls(`
+ [Exposed=Window] interface I2 {};
+ interface mixin M2 {};
+ partial interface mixin M2 { attribute any a2; };
+ I2 includes M2;`);
+ idlArray.merge_partials();
+ idlArray.merge_mixins();
+ })();
+
+ // Partial interface mixin without original mixin
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls('partial interface mixin M3 {};');
+ idlArray.merge_partials();
+ idlArray.merge_mixins();
+ })();
+
+ // Name clash between mixin and partial mixin
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls(`
+ interface mixin M4 { attribute any a4; };
+ partial interface mixin M4 { attribute any a4; };`);
+ idlArray.merge_partials();
+ idlArray.merge_mixins();
+ })();
+
+ // Name clash between interface and mixin
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_untested_idls(`
+ interface mixin M5 { attribute any a5; };
+ interface I5 { attribute any a5; };
+ I5 includes M5;`);
+ idlArray.merge_partials();
+ idlArray.merge_mixins();
+ })();
+ </script>
+ <script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "name": "I1 includes M1: member names are unique",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "I2 includes M2: member names are unique",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "I5 includes M5: member names are unique",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_true: member a5 is unique expected true got false"
+ },
+ {
+ "name": "Partial interface mixin M2: member names are unique",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial interface mixin M2: original interface mixin defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial interface mixin M3: original interface mixin defined",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_true: Original interface mixin should be defined expected true got false"
+ },
+ {
+ "name": "Partial interface mixin M4: member names are unique",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_true: member a4 is unique expected true got false"
+ },
+ {
+ "name": "Partial interface mixin M4: original interface mixin defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html
new file mode 100644
index 0000000000..7dd9e676af
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_partial_interface_of.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>idlharness: Partial interface</title>
+ <script src="/resources/test/variants.js"></script>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+</head>
+
+<body>
+ <p>Verify the series of sub-tests that are executed for "partial" interface objects.</p>
+ <script>
+ "use strict";
+
+ // No original existence
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls('partial interface A {};');
+ idlArray.test();
+ })();
+
+ // Valid exposure (Note: Worker -> {Shared,Dedicated,Service}Worker)
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_untested_idls(`
+ [Exposed=(Worker)]
+ interface B {};
+
+ [Exposed=(DedicatedWorker, ServiceWorker, SharedWorker)]
+ interface C {};`);
+ idlArray.add_idls(`
+ [Exposed=(DedicatedWorker, ServiceWorker, SharedWorker)]
+ partial interface B {};
+
+ [Exposed=(Worker)]
+ partial interface C {};`);
+ idlArray.merge_partials();
+ })();
+
+ // Invalid exposure
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_untested_idls(`
+ [Exposed=(Window, ServiceWorker)]
+ interface D {};`);
+ idlArray.add_idls(`
+ [Exposed=(DedicatedWorker)]
+ partial interface D {};`);
+ idlArray.merge_partials();
+ })();
+
+ // Original is a namespace, not an interface.
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls(`
+ partial interface E {};
+ namespace E {};`);
+ idlArray.merge_partials();
+ })();
+
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls(`
+ partial interface F {};
+ partial interface mixin G {};
+ `);
+ idlArray.add_dependency_idls(`
+ interface F {};
+ interface mixin G {};
+ interface mixin H {};
+ F includes H;
+ I includes H;
+ J includes G;
+ interface K : J {};
+ interface L : F {};
+ `);
+ test(() => {
+ // Convert idlArray.includes into a Map from name of target interface to
+ // name of included mixin. (This assumes each interface includes at most
+ // one mixin, otherwise later includes would clobber earlier ones.)
+ const includes = new Map(idlArray.includes.map(i => [i.target, i.includes]));
+ // F is tested, so H is a dep.
+ assert_equals(includes.get('F'), 'H', 'F should be picked up');
+ // H is not tested, so I is not a dep.
+ assert_false(includes.has('I'), 'I should be ignored');
+ // G is a dep, so J is a dep.
+ assert_equals(includes.get('J'), 'G', 'J should be picked up');
+ // K isn't a dep because J isn't defined.
+ assert_false('K' in idlArray.members, 'K should be ignored');
+ // L isn't a dep because F is untested.
+ assert_false('L' in idlArray.members, 'L should be ignored');
+ }, 'partial mixin dep implications');
+ })();
+
+ // Name clash (partials)
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls(`
+ interface M { attribute any A; };
+ partial interface M { attribute any A; };`);
+ idlArray.merge_partials();
+ })();
+ </script>
+ <script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "name": "Partial interface A: original interface defined",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_true: Original interface should be defined expected true got false"
+ },
+ {
+ "name": "Partial interface B: original interface defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial interface B: valid exposure set",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial interface C: original interface defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial interface C: valid exposure set",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial interface D: original interface defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial interface D: valid exposure set",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "Partial D interface is exposed to 'DedicatedWorker', the original interface is not."
+ },
+ {
+ "name": "Partial interface E: original interface defined",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_true: Original E definition should have type interface expected true got false"
+ },
+ {
+ "name": "partial mixin dep implications",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial interface M: original interface defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial interface M: member names are unique",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_true: member A is unique expected true got false"
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_primary_interface_of.html b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_primary_interface_of.html
new file mode 100644
index 0000000000..309de60bb7
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_primary_interface_of.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>idlharness: Primary interface</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+</head>
+<body>
+<p>Verify the series of sub-tests that are executed for "tested" interface
+objects but skipped for "untested" interface objects.</p>
+<script>
+"use strict";
+
+function FooParent() {
+ if (!new.target) {
+ throw new TypeError('FooParent() must be called with new');
+ }
+}
+Object.defineProperty(window, "Foo", {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: function Foo() {
+ if (!new.target) {
+ throw new TypeError('Foo() must be called with new');
+ }
+ }
+ });
+Object.defineProperty(window.Foo, "prototype", {
+ writable: false,
+ value: new FooParent()
+ });
+Object.defineProperty(window.Foo.prototype, "constructor", {
+ enumerable: false,
+ writable: true,
+ configurable: true,
+ value: window.Foo
+ });
+Object.setPrototypeOf(Foo, FooParent);
+Foo.prototype[Symbol.toStringTag] = "Foo";
+
+var idlArray = new IdlArray();
+idlArray.add_untested_idls("interface FooParent {};");
+idlArray.add_idls(
+ "interface Foo : FooParent { constructor(); };"
+ );
+idlArray.add_objects({
+ Foo: ["new Foo()"],
+ FooParent: ["new FooParent()"]
+});
+idlArray.test();
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "name": "Foo interface object length",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface object name",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: existence and properties of interface object",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: existence and properties of interface prototype object",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: existence and properties of interface prototype object's \"constructor\" property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo interface: existence and properties of interface prototype object's @@unscopables property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Foo must be primary interface of new Foo()",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Stringification of new Foo()",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_to_json_operation.html b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_to_json_operation.html
new file mode 100644
index 0000000000..bbc502a313
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlInterface/test_to_json_operation.html
@@ -0,0 +1,177 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>IdlInterface.prototype.test_to_json_operation()</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+ <script src="../../../../idl-helper.js"></script>
+</head>
+<body>
+<script>
+ "use strict";
+ function wrap(member, obj) {
+ function F(obj) {
+ this._obj = obj;
+ }
+
+ F.prototype.toJSON = function() {
+ return this._obj;
+ }
+ Object.defineProperty(F, 'name', { value: member.name });
+ return new F(obj);
+ }
+
+ var i, obj;
+ i = interfaceFrom("interface A { [Default] object toJSON(); attribute long foo; };");
+ i.test_to_json_operation("object", wrap(i, { foo: 123 }), i.members[0]);
+
+ // should fail (wrong type)
+ i = interfaceFrom("interface B { [Default] object toJSON(); attribute long foo; };");
+ i.test_to_json_operation("object", wrap(i, { foo: "a value" }), i.members[0]);
+
+ // should handle extraneous attributes (e.g. from an extension specification)
+ i = interfaceFrom("interface C { [Default] object toJSON(); attribute long foo; };");
+ i.test_to_json_operation("object", wrap(i, { foo: 123, bar: 456 }), i.members[0]);
+
+ // should fail (missing property)
+ i = interfaceFrom("interface D { [Default] object toJSON(); attribute long foo; };");
+ i.test_to_json_operation("object", wrap(i, { }), i.members[0]);
+
+ // should fail (should be writable)
+ obj = Object.defineProperties({}, { foo: {
+ writable: false,
+ enumerable: true,
+ configurable: true,
+ value: 123
+ }});
+ i = interfaceFrom("interface F { [Default] object toJSON(); attribute long foo; };");
+ i.test_to_json_operation("object", wrap(i, obj), i.members[0]);
+
+ // should fail (should be enumerable)
+ obj = Object.defineProperties({}, { foo: {
+ writable: true,
+ enumerable: false,
+ configurable: true,
+ value: 123
+ }});
+ i = interfaceFrom("interface G { [Default] object toJSON(); attribute long foo; };");
+ i.test_to_json_operation("object", wrap(i, obj), i.members[0]);
+
+ // should fail (should be configurable)
+ obj = Object.defineProperties({}, { foo: {
+ writable: true,
+ enumerable: true,
+ configurable: false,
+ value: 123
+ }});
+ i = interfaceFrom("interface H { [Default] object toJSON(); attribute long foo; };");
+ i.test_to_json_operation("object", wrap(i, obj), i.members[0]);
+
+ var idl = new IdlArray();
+ idl.add_idls("interface I : J { [Default] object toJSON(); attribute long foo; };");
+ idl.add_idls("interface J { [Default] object toJSON(); attribute DOMString foo;};");
+ var i = idl.members.I;
+ i.test_to_json_operation("object", wrap(i, { foo: 123 }), i.members[0]);
+
+ i = interfaceFrom("interface K { [Default] object toJSON(); };");
+ i.test_to_json_operation("object", wrap(i, {}), i.members[0]);
+
+ i = interfaceFrom("interface L { DOMString toJSON(); };");
+ i.test_to_json_operation("object", wrap(i, "a string"), i.members[0]);
+
+ // should fail (wrong output type)
+ i = interfaceFrom("interface M { DOMString toJSON(); };");
+ i.test_to_json_operation("object", wrap(i, {}), i.members[0]);
+
+ // should fail (not an IDL type)
+ i = interfaceFrom("interface N { DOMException toJSON(); };");
+ i.test_to_json_operation("object", wrap(i, {}), i.members[0]);
+</script>
+<script type="text/json" id="expected">
+ {
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "message": null,
+ "name": "A interface: default toJSON operation on object",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": "assert_equals: expected \"number\" but got \"string\"",
+ "name": "B interface: default toJSON operation on object",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": null,
+ "name": "C interface: default toJSON operation on object",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": "assert_true: property \"foo\" should be present in the output of D.prototype.toJSON() expected true got false",
+ "name": "D interface: default toJSON operation on object",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": "assert_true: property foo should be writable expected true got false",
+ "name": "F interface: default toJSON operation on object",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": "assert_true: property foo should be enumerable expected true got false",
+ "name": "G interface: default toJSON operation on object",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": "assert_true: property foo should be configurable expected true got false",
+ "name": "H interface: default toJSON operation on object",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": null,
+ "name": "I interface: default toJSON operation on object",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "K interface: default toJSON operation on object",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "L interface: toJSON operation on object",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": "assert_equals: expected \"string\" but got \"object\"",
+ "name": "M interface: toJSON operation on object",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": "assert_true: {\"type\":\"return-type\",\"extAttrs\":[],\"generic\":\"\",\"nullable\":false,\"union\":false,\"idlType\":\"DOMException\"} is not an appropriate return value for the toJSON operation of N expected true got false",
+ "name": "N interface: toJSON operation on object",
+ "properties": {},
+ "status_string": "FAIL"
+ }
+ ],
+ "type": "complete"
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_attribute.html b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_attribute.html
new file mode 100644
index 0000000000..2c94061fc1
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_attribute.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>idlharness: namespace attribute</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+</head>
+<body>
+<p>Verify the series of sub-tests that are executed for namespace attributes.</p>
+<script>
+"use strict";
+
+Object.defineProperty(self, "foo", {
+ value: {
+ truth: true,
+ },
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+var idlArray = new IdlArray();
+idlArray.add_idls(`
+[Exposed=Window]
+namespace foo {
+ readonly attribute bool truth;
+ readonly attribute bool lies;
+};`);
+idlArray.test();
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "name": "foo namespace: extended attributes",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: property descriptor",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: [[Extensible]] is true",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: [[Prototype]] is Object.prototype",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: typeof is \"object\"",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: has no length property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: has no name property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: attribute truth",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: attribute lies",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_own_property: foo does not have property \"lies\" expected property \"lies\" missing"
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_operation.html b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_operation.html
new file mode 100644
index 0000000000..da70c8fa31
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_operation.html
@@ -0,0 +1,242 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>idlharness: namespace operation</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+</head>
+<body>
+<p>Verify the series of sub-tests that are executed for namespace operations.</p>
+<script>
+"use strict";
+
+Object.defineProperty(self, "foo", {
+ value: Object.defineProperty({}, "Truth", {
+ value: function() {},
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ }),
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+Object.defineProperty(self, "bar", {
+ value: Object.defineProperty({}, "Truth", {
+ value: function() {},
+ writable: false,
+ enumerable: true,
+ configurable: false,
+ }),
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+Object.defineProperty(self, "baz", {
+ value: {
+ LongStory: function(hero, ...details) {
+ return `${hero} went and ${details.join(', then')}`
+ },
+ ShortStory: function(...details) {
+ return `${details.join('. ')}`;
+ },
+ },
+ writable: true,
+ enumerable: false,
+ configurable: true,
+});
+
+var idlArray = new IdlArray();
+idlArray.add_idls(`
+[Exposed=Window]
+namespace foo {
+ undefined Truth();
+ undefined Lies();
+};
+[Exposed=Window]
+namespace bar {
+ [LegacyUnforgeable]
+ undefined Truth();
+};
+[Exposed=Window]
+namespace baz {
+ DOMString LongStory(any hero, DOMString... details);
+ DOMString ShortStory(DOMString... details);
+};`);
+idlArray.test();
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "name": "foo namespace: extended attributes",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: property descriptor",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: [[Extensible]] is true",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: [[Prototype]] is Object.prototype",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: typeof is \"object\"",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: has no length property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: has no name property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: operation Truth()",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "foo namespace: operation Lies()",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_own_property: namespace object missing operation \"Lies\" expected property \"Lies\" missing"
+ },
+ {
+ "name": "bar namespace: extended attributes",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "bar namespace: property descriptor",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "bar namespace: [[Extensible]] is true",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "bar namespace: [[Prototype]] is Object.prototype",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "bar namespace: typeof is \"object\"",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "bar namespace: has no length property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "bar namespace: has no name property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "bar namespace: operation Truth()",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+
+ {
+ "name": "baz namespace: extended attributes",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "baz namespace: property descriptor",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "baz namespace: [[Extensible]] is true",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "baz namespace: [[Prototype]] is Object.prototype",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "baz namespace: typeof is \"object\"",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "baz namespace: has no length property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "baz namespace: has no name property",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "baz namespace: operation LongStory(any, DOMString...)",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "baz namespace: operation ShortStory(DOMString...)",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_partial_namespace.html b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_partial_namespace.html
new file mode 100644
index 0000000000..eabdcd10a9
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/idlharness/IdlNamespace/test_partial_namespace.html
@@ -0,0 +1,125 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>idlharness: Partial namespace</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+</head>
+
+<body>
+ <p>Verify the series of sub-tests that are executed for "partial" namespace objects.</p>
+ <script>
+ "use strict";
+
+ // No original existence
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls('partial namespace A {};');
+ idlArray.test();
+ })();
+
+ // Valid exposure (Note: Worker -> {Shared,Dedicated,Service}Worker)
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_untested_idls(`
+ [Exposed=(Worker)]
+ namespace B {};
+
+ [Exposed=(DedicatedWorker, ServiceWorker, SharedWorker)]
+ namespace C {};`);
+ idlArray.add_idls(`
+ [Exposed=(DedicatedWorker, ServiceWorker, SharedWorker)]
+ partial namespace B {};
+
+ [Exposed=(Worker)]
+ partial namespace C {};`);
+ idlArray.merge_partials();
+ })();
+
+ // Invalid exposure
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_untested_idls(`
+ [Exposed=(Window, ServiceWorker)]
+ namespace D {};`);
+ idlArray.add_idls(`
+ [Exposed=(DedicatedWorker)]
+ partial namespace D {};`);
+ idlArray.merge_partials();
+ })();
+
+ // Original is an interface, not a namespace.
+ (() => {
+ const idlArray = new IdlArray();
+ idlArray.add_idls(`
+ partial namespace E {};
+ interface E {};`);
+ idlArray.merge_partials();
+ })();
+ </script>
+ <script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "name": "Partial namespace A: original namespace defined",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_true: Original namespace should be defined expected true got false"
+ },
+ {
+ "name": "Partial namespace B: original namespace defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial namespace B: valid exposure set",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial namespace C: original namespace defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial namespace C: valid exposure set",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial namespace D: original namespace defined",
+ "status_string": "PASS",
+ "properties": {},
+ "message": null
+ },
+ {
+ "name": "Partial namespace D: valid exposure set",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "Partial D namespace is exposed to 'DedicatedWorker', the original namespace is not."
+ },
+ {
+ "name": "Partial namespace E: original namespace defined",
+ "status_string": "FAIL",
+ "properties": {},
+ "message": "assert_true: Original E definition should have type namespace expected true got false"
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/iframe-callback.html b/testing/web-platform/tests/resources/test/tests/functional/iframe-callback.html
new file mode 100644
index 0000000000..f49d0aa6b8
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/iframe-callback.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Example with iframe that notifies containing document via callbacks</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body onload="start_test_in_iframe()">
+<h1>Callbacks From Tests Running In An IFRAME</h1>
+<p>A test is run inside an <tt>iframe</tt> with a same origin document. The
+containing document should receive callbacks as the tests progress inside the
+<tt>iframe</tt>. A single passing test is expected in the summary below.
+<div id="log"></div>
+
+<script>
+var callbacks = [];
+var START = 1
+var TEST_STATE = 2
+var RESULT = 3
+var COMPLETION = 4
+var test_complete = false;
+
+setup({explicit_done: true});
+
+// The following callbacks are called for tests in this document as well as the
+// tests in the IFRAME. Currently, callbacks invoked from this document and any
+// child document are indistinguishable from each other.
+
+function start_callback(properties) {
+ callbacks.push(START);
+}
+
+function test_state_callback(test) {
+ callbacks.push(TEST_STATE);
+}
+
+function result_callback(test) {
+ callbacks.push(RESULT);
+}
+
+function completion_callback(tests, status) {
+ if (test_complete) {
+ return;
+ }
+ test_complete = true;
+ callbacks.push(COMPLETION);
+ verify_received_callbacks();
+ done();
+}
+
+function verify_received_callbacks() {
+ var copy_of_callbacks = callbacks.slice(0);
+
+ // Note that you can't run test assertions directly in a callback even if
+ // this is a file test. When the callback is invoked from a same-origin child
+ // page, the callstack reaches into the calling child document. Any
+ // exception thrown in a callback will be handled by the child rather than
+ // this document.
+ test(
+ function() {
+ // callbacks list should look like:
+ // START 1*(TEST_STATE) RESULT COMPLETION
+ assert_equals(copy_of_callbacks.shift(), START,
+ "The first received callback should be 'start_callback'.");
+ assert_equals(copy_of_callbacks.shift(), TEST_STATE,
+ "'test_state_callback' should be received before any " +
+ "result or completion callbacks.");
+ while(copy_of_callbacks.length > 0) {
+ var callback = copy_of_callbacks.shift();
+ if (callback != TEST_STATE) {
+ copy_of_callbacks.unshift(callback);
+ break;
+ }
+ }
+ assert_equals(copy_of_callbacks.shift(), RESULT,
+ "'test_state_callback' should be followed by 'result_callback'.");
+ assert_equals(copy_of_callbacks.shift(), COMPLETION,
+ "Final 'result_callback' should be followed by 'completion_callback'.");
+ assert_equals(copy_of_callbacks.length, 0,
+ "'completion_callback' should be the last callback.");
+ });
+}
+
+function start_test_in_iframe() {
+ // This document is going to clear any received callbacks and maintain
+ // radio silence until the test in the iframe runs to completion. The
+ // completion_callback() will then complete the testing on this document.
+ callbacks.length = 0;
+ var iframe = document.createElement("iframe");
+ // single-page-test-pass.html has a single test.
+ iframe.src = "single-page-test-pass.html";
+ iframe.style.setProperty("display", "none");
+ document.getElementById("target").appendChild(iframe);
+}
+</script>
+
+<div id="target">
+</div>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Example with iframe that notifies containing document via callbacks",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/iframe-consolidate-errors.html b/testing/web-platform/tests/resources/test/tests/functional/iframe-consolidate-errors.html
new file mode 100644
index 0000000000..ef9b8702ec
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/iframe-consolidate-errors.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Example with iframe that consolidates errors via fetch_tests_from_window</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var parent_test = async_test("Test executing in parent context");
+</script>
+</head>
+<body onload="parent_test.done()">
+<h1>Fetching Tests From a Child Context</h1>
+<p>This test demonstrates the use of <tt>fetch_tests_from_window</tt> to pull
+tests from an <tt>iframe</tt> into the primary document.</p>
+<p>The test suite is expected to fail due to an unhandled exception in the
+child context.</p>
+<div id="log"></div>
+
+<iframe id="childContext" src="uncaught-exception-handle.html" style="display:none"></iframe>
+<!-- apisample4.html is a failing suite due to an unhandled Error. -->
+
+<script>
+ var childContext = document.getElementById("childContext");
+ fetch_tests_from_window(childContext.contentWindow);
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Error in remote: Error: Example Error"
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Test executing in parent context",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "This should show a harness status of 'Error' and a test status of 'Not Run'",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/iframe-consolidate-tests.html b/testing/web-platform/tests/resources/test/tests/functional/iframe-consolidate-tests.html
new file mode 100644
index 0000000000..246dddee11
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/iframe-consolidate-tests.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Example with iframe that consolidates tests via fetch_tests_from_window</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+var parent_test = async_test("Test executing in parent context");
+</script>
+</head>
+<body onload="parent_test.done()">
+<h1>Fetching Tests From a Child Context</h1>
+<p>This test demonstrates the use of <tt>fetch_tests_from_window</tt> to pull
+tests from an <tt>iframe</tt> into the primary document.</p>
+<p>The test suite will not complete until tests in the child context have finished
+executing</p>
+<div id="log"></div>
+
+<iframe id="childContext" src="promise-async.html" style="display:none"></iframe>
+
+<script>
+ var childContext = document.getElementById("childContext");
+ fetch_tests_from_window(childContext.contentWindow);
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Promise rejection",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Promise resolution",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Promises and test assertion failures (should fail)",
+ "properties": {},
+ "message": "assert_true: This failure is expected expected true got false"
+ },
+ {
+ "status_string": "PASS",
+ "name": "Promises are supported in your browser",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Promises resolution chaining",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test executing in parent context",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Use of step_func with Promises",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Use of unreached_func with Promises (should fail)",
+ "properties": {},
+ "message": "assert_unreached: This failure is expected Reached unreachable code"
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/iframe-msg.html b/testing/web-platform/tests/resources/test/tests/functional/iframe-msg.html
new file mode 100644
index 0000000000..283a5d98cc
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/iframe-msg.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Example with iframe that notifies containing document via cross document messaging</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Notifications From Tests Running In An IFRAME</h1>
+<p>A test is run inside an <tt>iframe</tt> with a same origin document. The
+containing document should receive messages via <tt>postMessage</tt>/
+<tt>onmessage</tt> as the tests progress inside the <tt>iframe</tt>. A single
+passing test is expected in the summary below.
+</p>
+<div id="log"></div>
+
+<script>
+var t = async_test("Containing document receives messages");
+var start_received = false;
+var result_received = false;
+var completion_received = false;
+
+// These are the messages that are expected to be seen while running the tests
+// in the IFRAME.
+var expected_messages = [
+ t.step_func(
+ function(message) {
+ assert_equals(message.data.type, "start");
+ assert_own_property(message.data, "properties");
+ }),
+
+ t.step_func(
+ function(message) {
+ assert_equals(message.data.type, "test_state");
+ assert_equals(message.data.test.status, message.data.test.NOTRUN);
+ }),
+
+ t.step_func(
+ function(message) {
+ assert_equals(message.data.type, "result");
+ assert_equals(message.data.test.status, message.data.test.PASS);
+ }),
+
+ t.step_func(
+ function(message) {
+ assert_equals(message.data.type, "complete");
+ assert_equals(message.data.tests.length, 1);
+ assert_equals(message.data.tests[0].status,
+ message.data.tests[0].PASS);
+ assert_equals(message.data.status.status, message.data.status.OK);
+ t.done();
+ }),
+
+ t.unreached_func("Too many messages received")
+];
+
+on_event(window,
+ "message",
+ function(message) {
+ var handler = expected_messages.shift();
+ handler(message);
+ });
+</script>
+<iframe src="single-page-test-pass.html" style="display:none">
+ <!-- single-page-test-pass.html implements a file_is_test test. -->
+</iframe>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Containing document receives messages",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/log-insertion.html b/testing/web-platform/tests/resources/test/tests/functional/log-insertion.html
new file mode 100644
index 0000000000..9a63c3dbde
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/log-insertion.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<title>Log insertion</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function(t) {
+ assert_equals(document.body, null);
+}, "Log insertion before load");
+test(function(t) {
+ assert_equals(document.body, null);
+}, "Log insertion before load (again)");
+async_test(function(t) {
+ window.onload = t.step_func_done(function() {
+ var body = document.body;
+ assert_not_equals(body, null);
+
+ var log = document.getElementById("log");
+ assert_equals(log.parentNode, body);
+ });
+}, "Log insertion after load");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [{
+ "status_string": "PASS",
+ "name": "Log insertion before load",
+ "message": null,
+ "properties": {}
+ }, {
+ "status_string": "PASS",
+ "name": "Log insertion before load (again)",
+ "message": null,
+ "properties": {}
+ }, {
+ "status_string": "PASS",
+ "name": "Log insertion after load",
+ "message": null,
+ "properties": {}
+ }],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/no-title.html b/testing/web-platform/tests/resources/test/tests/functional/no-title.html
new file mode 100644
index 0000000000..a337e4e5f5
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/no-title.html
@@ -0,0 +1,146 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Tests with no title</title>
+</head>
+<script src="/resources/testharness.js"></script>
+
+<body>
+<h1>Tests with no title</h1>
+<div id="log"></div>
+<script>
+ test(function(){assert_true(true, '1')});
+ test(()=>assert_true(true, '2'));
+ test(() => assert_true(true, '3'));
+ test(() => assert_true(true, '3')); // test duplicate behaviour
+ test(() => assert_true(true, '3')); // test duplicate behaviour
+ test(() => {
+ assert_true(true, '4');
+ });
+ test(() => { assert_true(true, '5') });
+ test(() => { assert_true(true, '6') } );
+ test(() => { assert_true(true, '7'); });
+ test(() => {});
+ test(() => { });
+ test(() => {;;;;});
+ test(() => { ; ; ; ; });
+ test(()=>{});
+ test(()=>{ });
+ test(()=>{;;;;});
+ test(()=>{ ; ; ; ; });
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Tests with no title",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "assert_true(true, '2')",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "assert_true(true, '3')",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "assert_true(true, '3') 1",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "assert_true(true, '3') 2",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Tests with no title 1",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "assert_true(true, '5')",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "assert_true(true, '6')",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "assert_true(true, '7')",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Tests with no title 2",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Tests with no title 3",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Tests with no title 4",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Tests with no title 5",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Tests with no title 6",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Tests with no title 7",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Tests with no title 8",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Tests with no title 9",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/order.html b/testing/web-platform/tests/resources/test/tests/functional/order.html
new file mode 100644
index 0000000000..686383861a
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/order.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Ordering</title>
+<meta name="timeout" content="6000">
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(function() {}, 'second');
+test(function() {}, 'first');
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [{
+ "status_string": "PASS",
+ "name": "first",
+ "message": null,
+ "properties": {}
+ }, {
+ "status_string": "PASS",
+ "name": "second",
+ "message": null,
+ "properties": {}
+ }],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/promise-async.html b/testing/web-platform/tests/resources/test/tests/functional/promise-async.html
new file mode 100644
index 0000000000..fa82665cf0
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/promise-async.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Async Tests and Promises</title>
+</head>
+<body>
+<h1>Async Tests and Promises</h1>
+<p>This test assumes ECMAScript 6 Promise support. Some failures are expected.</p>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+test(function() {
+ var p = new Promise(function(resolve, reject) {});
+ assert_true('then' in p);
+ assert_equals(typeof Promise.resolve, 'function');
+ assert_equals(typeof Promise.reject, 'function');
+}, "Promises are supported in your browser");
+
+(function() {
+ var t = async_test("Promise resolution");
+ t.step(function() {
+ Promise.resolve('x').then(
+ t.step_func(function(value) {
+ assert_equals(value, 'x');
+ t.done();
+ }),
+ t.unreached_func('Promise should not reject')
+ );
+ });
+}());
+
+(function() {
+ var t = async_test("Promise rejection");
+ t.step(function() {
+ Promise.reject(Error('fail')).then(
+ t.unreached_func('Promise should reject'),
+ t.step_func(function(reason) {
+ assert_true(reason instanceof Error);
+ assert_equals(reason.message, 'fail');
+ t.done();
+ })
+ );
+ });
+}());
+
+(function() {
+ var t = async_test("Promises resolution chaining");
+ t.step(function() {
+ var resolutions = [];
+ Promise.resolve('a').then(
+ t.step_func(function(value) {
+ resolutions.push(value);
+ return 'b';
+ })
+ ).then(
+ t.step_func(function(value) {
+ resolutions.push(value);
+ return 'c';
+ })
+ ).then(
+ t.step_func(function(value) {
+ resolutions.push(value);
+
+ assert_array_equals(resolutions, ['a', 'b', 'c']);
+ t.done();
+ })
+ ).catch(
+ t.unreached_func('promise should not have rejected')
+ );
+ });
+}());
+
+(function() {
+ var t = async_test("Use of step_func with Promises");
+ t.step(function() {
+ var resolutions = [];
+ Promise.resolve('x').then(
+ t.step_func_done(),
+ t.unreached_func('Promise should not have rejected')
+ );
+ });
+}());
+
+(function() {
+ var t = async_test("Promises and test assertion failures (should fail)");
+ t.step(function() {
+ var resolutions = [];
+ Promise.resolve('x').then(
+ t.step_func(function(value) {
+ assert_true(false, 'This failure is expected');
+ })
+ ).then(
+ t.unreached_func('Promise should not have resolved')
+ ).catch(
+ t.unreached_func('Promise should not have rejected')
+ );
+ });
+}());
+
+(function() {
+ var t = async_test("Use of unreached_func with Promises (should fail)");
+ t.step(function() {
+ var resolutions = [];
+ var r;
+ var p = new Promise(function(resolve, reject) {
+ // Reject instead of resolve, to demonstrate failure.
+ reject(123);
+ });
+ p.then(
+ function(value) {
+ assert_equals(value, 123, 'This should not actually happen');
+ },
+ t.unreached_func('This failure is expected')
+ );
+ });
+}());
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Promise rejection",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Promise resolution",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Promises and test assertion failures (should fail)",
+ "properties": {},
+ "message": "assert_true: This failure is expected expected true got false"
+ },
+ {
+ "status_string": "PASS",
+ "name": "Promises are supported in your browser",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Promises resolution chaining",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Use of step_func with Promises",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Use of unreached_func with Promises (should fail)",
+ "properties": {},
+ "message": "assert_unreached: This failure is expected Reached unreachable code"
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/promise-with-sync.html b/testing/web-platform/tests/resources/test/tests/functional/promise-with-sync.html
new file mode 100644
index 0000000000..e8e680a9c7
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/promise-with-sync.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Promise Tests and Synchronous Tests</title>
+</head>
+<body>
+<h1>Promise Tests</h1>
+<p>This test demonstrates the use of <tt>promise_test</tt> alongside synchronous tests.</p>
+<div id="log"></div>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+<script>
+"use strict";
+var sequence = [];
+
+test(function(t) {
+ assert_array_equals(sequence, []);
+ sequence.push(1);
+}, "first synchronous test");
+
+promise_test(function() {
+ assert_array_equals(sequence, [1, 2]);
+
+ return Promise.resolve()
+ .then(function() {
+ assert_array_equals(sequence, [1, 2]);
+ sequence.push(3);
+ });
+}, "first promise_test");;
+
+test(function(t) {
+ assert_array_equals(sequence, [1]);
+ sequence.push(2);
+}, "second synchronous test");
+
+promise_test(function() {
+ assert_array_equals(sequence, [1, 2, 3]);
+
+ return Promise.resolve()
+ .then(function() {
+ assert_array_equals(sequence, [1, 2, 3]);
+ });
+}, "second promise_test");;
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "message": null,
+ "properties": {},
+ "name": "first promise_test",
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "properties": {},
+ "name": "first synchronous test",
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "properties": {},
+ "name": "second promise_test",
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "properties": {},
+ "name": "second synchronous test",
+ "status_string": "PASS"
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/promise.html b/testing/web-platform/tests/resources/test/tests/functional/promise.html
new file mode 100644
index 0000000000..f35feb0e21
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/promise.html
@@ -0,0 +1,219 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Promise Tests</title>
+</head>
+<body>
+<h1>Promise Tests</h1>
+<p>This test demonstrates the use of <tt>promise_test</tt>. Assumes ECMAScript 6
+Promise support. Some failures are expected.</p>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(
+ function() {
+ var p = new Promise(function(resolve, reject){});
+ assert_true("then" in p);
+ assert_equals(typeof Promise.resolve, "function");
+ assert_equals(typeof Promise.reject, "function");
+ },
+ "Promises are supported in your browser");
+
+promise_test(
+ function() {
+ return Promise.resolve("x")
+ .then(
+ function(value) {
+ assert_equals(value,
+ "x",
+ "Fulfilled promise should pass result to " +
+ "fulfill reaction.");
+ });
+ },
+ "Promise fulfillment with result");
+
+promise_test(
+ function(t) {
+ return Promise.reject(new Error("fail"))
+ .then(t.unreached_func("Promise should reject"),
+ function(reason) {
+ assert_true(
+ reason instanceof Error,
+ "Rejected promise should pass reason to fulfill reaction.");
+ assert_equals(
+ reason.message,
+ "fail",
+ "Rejected promise should pass reason to reject reaction.");
+ });
+ },
+ "Promise rejection with result");
+
+promise_test(
+ function() {
+ var resolutions = [];
+ return Promise.resolve("a")
+ .then(
+ function(value) {
+ resolutions.push(value);
+ return "b";
+ })
+ .then(
+ function(value) {
+ resolutions.push(value);
+ return "c";
+ })
+ .then(
+ function(value) {
+ resolutions.push(value);
+ assert_array_equals(resolutions, ["a", "b", "c"]);
+ });
+ },
+ "Chain of promise resolutions");
+
+promise_test(
+ function(t) {
+ var resolutions = [];
+ return Promise.resolve("x")
+ .then(
+ function(value) {
+ assert_true(false, "Expected failure.");
+ })
+ .then(t.unreached_func("UNEXPECTED FAILURE: Promise should not have resolved."));
+ },
+ "Assertion failure in a fulfill reaction (should FAIL with an expected failure)");
+
+promise_test(
+ function(t) {
+ return new Promise(
+ function(resolve, reject) {
+ reject(123);
+ })
+ .then(t.unreached_func("UNEXPECTED FAILURE: Fulfill reaction reached after rejection."),
+ t.unreached_func("Expected failure."));
+ },
+ "unreached_func as reactor (should FAIL with an expected failure)");
+
+promise_test(
+ function() {
+ return true;
+ },
+ "promise_test with function that doesn't return a Promise (should FAIL)");
+
+promise_test(function(){},
+ "promise_test with function that doesn't return anything");
+
+promise_test(
+ function() { return { then: 23 }; },
+ "promise_test that returns a non-thenable (should FAIL)");
+
+promise_test(
+ function() {
+ return Promise.reject("Expected rejection");
+ },
+ "promise_test with unhandled rejection (should FAIL)");
+
+promise_test(
+ function() {
+ return Promise.resolve(10)
+ .then(
+ function(value) {
+ throw Error("Expected exception.");
+ });
+ },
+ "promise_test with unhandled exception in fulfill reaction (should FAIL)");
+
+promise_test(
+ function(t) {
+ return Promise.reject(10)
+ .then(
+ t.unreached_func("UNEXPECTED FAILURE: Fulfill reaction reached after rejection."),
+ function(value) {
+ throw Error("Expected exception.");
+ });
+ },
+ "promise_test with unhandled exception in reject reaction (should FAIL)");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "FAIL",
+ "name": "Assertion failure in a fulfill reaction (should FAIL with an expected failure)",
+ "message": "assert_true: Expected failure. expected true got false",
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Chain of promise resolutions",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Promise fulfillment with result",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Promise rejection with result",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "PASS",
+ "name": "Promises are supported in your browser",
+ "message": null,
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "promise_test with function that doesn't return a Promise (should FAIL)",
+ "message": "promise_test: test body must return a 'thenable' object (received an object with no `then` method)",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "promise_test with function that doesn't return anything",
+ "message": "promise_test: test body must return a 'thenable' object (received undefined)",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "promise_test that returns a non-thenable (should FAIL)",
+ "message": "promise_test: test body must return a 'thenable' object (received an object with no `then` method)",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "promise_test with unhandled exception in fulfill reaction (should FAIL)",
+ "message": "promise_test: Unhandled rejection with value: object \"Error: Expected exception.\"",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "promise_test with unhandled exception in reject reaction (should FAIL)",
+ "message": "promise_test: Unhandled rejection with value: object \"Error: Expected exception.\"",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "promise_test with unhandled rejection (should FAIL)",
+ "message": "promise_test: Unhandled rejection with value: \"Expected rejection\"",
+ "properties": {}
+ },
+ {
+ "status_string": "FAIL",
+ "name": "unreached_func as reactor (should FAIL with an expected failure)",
+ "message": "assert_unreached: Expected failure. Reached unreachable code",
+ "properties": {}
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/queue.html b/testing/web-platform/tests/resources/test/tests/functional/queue.html
new file mode 100644
index 0000000000..0c721286ec
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/queue.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test queuing synchronous tests</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<div id="log"></div>
+
+<script>
+"use strict";
+var inInitialTurn = true;
+
+test(function(t) {
+ assert_true(
+ inInitialTurn, "should execute in the initial turn of the event loop"
+ );
+}, "First synchronous test");
+
+test(function(t) {
+ assert_true(
+ inInitialTurn, "should execute in the initial turn of the event loop"
+ );
+}, "Second synchronous test");
+
+async_test(function(t) {
+ assert_true(
+ inInitialTurn, "should execute in the initial turn of the event loop"
+ );
+ t.done();
+}, "First async_test (run in parallel)");
+
+async_test(function(t) {
+ assert_true(
+ inInitialTurn, "should execute in the initial turn of the event loop"
+ );
+ t.done();
+}, "Second async_test (run in parallel)");
+
+test(function(t) {
+ assert_true(
+ inInitialTurn, "should execute in the initial turn of the event loop"
+ );
+}, "Third synchronous test");
+
+promise_test(function(t) {
+ assert_false(
+ inInitialTurn, "should not execute in the initial turn of the event loop"
+ );
+
+ return Promise.resolve();
+}, "promise_test");
+
+async_test(function(t) {
+ assert_true(
+ inInitialTurn, "should execute in the initial turn of the event loop"
+ );
+ t.done();
+}, "Third async_test (run in parallel)");
+
+test(function(t) {
+ assert_true(
+ inInitialTurn, "should execute in the initial turn of the event loop"
+ );
+}, "Fourth synchronous test");
+
+inInitialTurn = false;
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "properties": {},
+ "name": "First async_test (run in parallel)",
+ "status_string": "PASS",
+ "message": null
+ },
+ {
+ "properties": {},
+ "name": "First synchronous test",
+ "status_string": "PASS",
+ "message": null
+ },
+ {
+ "properties": {},
+ "name": "Fourth synchronous test",
+ "status_string": "PASS",
+ "message": null
+ },
+ {
+ "properties": {},
+ "name": "Second async_test (run in parallel)",
+ "status_string": "PASS",
+ "message": null
+ },
+ {
+ "properties": {},
+ "name": "Second synchronous test",
+ "status_string": "PASS",
+ "message": null
+ },
+ {
+ "properties": {},
+ "name": "Third async_test (run in parallel)",
+ "status_string": "PASS",
+ "message": null
+ },
+ {
+ "properties": {},
+ "name": "Third synchronous test",
+ "status_string": "PASS",
+ "message": null
+ },
+ {
+ "properties": {},
+ "name": "promise_test",
+ "status_string": "PASS",
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/setup-function-worker.js b/testing/web-platform/tests/resources/test/tests/functional/setup-function-worker.js
new file mode 100644
index 0000000000..82c1456aa6
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/setup-function-worker.js
@@ -0,0 +1,14 @@
+importScripts("/resources/testharness.js");
+
+// Regression test for https://github.com/web-platform-tests/wpt/issues/27299,
+// where we broke the ability for a setup function in a worker to contain an
+// assertion (even a passing one).
+setup(function() {
+ assert_true(true, "True is true");
+});
+
+// We must define at least one test for the harness, though it is not what we
+// are testing here.
+test(function() {
+ assert_false(false, "False is false");
+}, 'Worker test');
diff --git a/testing/web-platform/tests/resources/test/tests/functional/setup-worker-service.html b/testing/web-platform/tests/resources/test/tests/functional/setup-worker-service.html
new file mode 100644
index 0000000000..9f24adac2d
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/setup-worker-service.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Setup function in a service worker</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Setup function in a service worker</h1>
+<p>This test assumes that the browser supports <a href="http://www.w3.org/TR/service-workers/">ServiceWorkers</a>.
+<div id="log"></div>
+
+<script>
+test(function(t) {
+ assert_true("serviceWorker" in navigator,
+ "navigator.serviceWorker exists");
+}, "Browser supports ServiceWorker");
+
+promise_test(function() {
+ // Since the service worker registration could be in an indeterminate
+ // state (due to, for example, a previous test run failing), we start by
+ // unregstering our service worker and then registering it again.
+ var scope = "service-worker-scope";
+ var worker_url = "setup-function-worker.js";
+
+ return navigator.serviceWorker.register(worker_url, {scope: scope})
+ .then(function(registration) {
+ return registration.unregister();
+ }).then(function() {
+ return navigator.serviceWorker.register(worker_url, {scope: scope});
+ }).then(function(registration) {
+ add_completion_callback(function() {
+ registration.unregister();
+ });
+
+ return new Promise(function(resolve) {
+ registration.addEventListener("updatefound", function() {
+ resolve(registration.installing);
+ });
+ });
+ }).then(function(worker) {
+ fetch_tests_from_worker(worker);
+ });
+}, "Register ServiceWorker");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Browser supports ServiceWorker",
+ "properties": {},
+ "message": null
+ },
+ {
+ "message": null,
+ "name": "Register ServiceWorker",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "Worker test",
+ "properties": {},
+ "status_string": "PASS"
+ }
+ ],
+ "summarized_asserts": [
+ {
+ "assert_name": "assert_true",
+ "test": "Browser supports ServiceWorker",
+ "args": [
+ "true",
+ "\"navigator.serviceWorker exists\""
+ ],
+ "status": 0
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/single-page-test-fail.html b/testing/web-platform/tests/resources/test/tests/functional/single-page-test-fail.html
new file mode 100644
index 0000000000..8bbd530c48
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/single-page-test-fail.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<title>Example with file_is_test (should fail)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ single_test: true });
+onload = function() {
+ assert_true(false);
+ done();
+}
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "FAIL",
+ "name": "Example with file_is_test (should fail)",
+ "properties": {},
+ "message": "uncaught exception: Error: assert_true: expected true got false"
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/single-page-test-no-assertions.html b/testing/web-platform/tests/resources/test/tests/functional/single-page-test-no-assertions.html
new file mode 100644
index 0000000000..9b39d2a02c
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/single-page-test-no-assertions.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<title>Example single page test with no asserts</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ single_test: true });
+done();
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Example single page test with no asserts",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/single-page-test-no-body.html b/testing/web-platform/tests/resources/test/tests/functional/single-page-test-no-body.html
new file mode 100644
index 0000000000..cb018f4dae
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/single-page-test-no-body.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<title>Example single page test with no body</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ single_test: true });
+assert_true(true);
+done();
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Example single page test with no body",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/single-page-test-pass.html b/testing/web-platform/tests/resources/test/tests/functional/single-page-test-pass.html
new file mode 100644
index 0000000000..e143e22f3c
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/single-page-test-pass.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<title>Example with file_is_test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+setup({ single_test: true });
+onload = function() {
+ assert_true(true);
+ done();
+}
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Example with file_is_test",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/step_wait.html b/testing/web-platform/tests/resources/test/tests/functional/step_wait.html
new file mode 100644
index 0000000000..8235d9d48a
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/step_wait.html
@@ -0,0 +1,79 @@
+<!doctype html>
+<title>Tests for step_wait</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+promise_test(async t => {
+ let x = 1;
+ Promise.resolve().then(() => ++x);
+ await t.step_wait(() => x === 1);
+ assert_equals(x, 2);
+}, "Basic step_wait() test");
+
+promise_test(async t => {
+ let cond = false;
+ let x = 0;
+ setTimeout(() => cond = true, 100);
+ await t.step_wait(() => {
+ ++x;
+ return cond;
+ });
+ assert_equals(x, 2);
+}, "step_wait() isn't invoked too often");
+
+promise_test(async t => {
+ await t.step_wait(); // Throws
+}, "step_wait() takes an argument");
+
+promise_test(async t => {
+ function wait(ms) {
+ return new Promise(resolve => step_timeout(resolve, ms));
+ }
+ let x = 1;
+ step_timeout(() => {
+ ++x;
+ }, 100);
+
+ await t.step_wait(async () => {
+ await wait(1);
+ return x === 2;
+ });
+ assert_equals(x, 2);
+}, "async step_wait()");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_tests": [
+ {
+ "name": "Basic step_wait() test",
+ "message": null,
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "name": "step_wait() isn't invoked too often",
+ "message": null,
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "name": "step_wait() takes an argument",
+ "message": "cond is not a function",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "name": "async step_wait()",
+ "message": null,
+ "properties": {},
+ "status_string": "PASS"
+ }
+ ],
+ "type": "complete",
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ }
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/step_wait_func.html b/testing/web-platform/tests/resources/test/tests/functional/step_wait_func.html
new file mode 100644
index 0000000000..9fed18a3e2
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/step_wait_func.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<title>Tests for step_wait_func and step_wait_func_done</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+async_test(t => {
+ let x = 0;
+ let step_x = 0;
+ setTimeout(() => ++x, 100);
+ t.step_wait_func(() => {
+ ++step_x;
+ return x === 1;
+ }, () => {
+ assert_equals(step_x, 2);
+ t.done();
+ });
+}, "Basic step_wait_func() test");
+
+async_test(t => {
+ let x = 0;
+ setTimeout(() => ++x, 100);
+ t.step_wait_func_done(() => true, () => assert_equals(x, 0));
+}, "Basic step_wait_func_done() test");
+
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "properties": {},
+ "message": null,
+ "name": "Basic step_wait_func() test",
+ "status_string": "PASS"
+ },
+ {
+ "properties": {},
+ "message": null,
+ "name": "Basic step_wait_func_done() test",
+ "status_string": "PASS"
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/task-scheduling-promise-test.html b/testing/web-platform/tests/resources/test/tests/functional/task-scheduling-promise-test.html
new file mode 100644
index 0000000000..9d8e5c11cc
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/task-scheduling-promise-test.html
@@ -0,0 +1,241 @@
+<!doctype html>
+<title>testharness.js - task scheduling</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+<script>
+var sameTask = null;
+var sameMicrotask = null;
+var expectedError = new Error('This error is expected');
+
+promise_test(function() {
+ return Promise.resolve()
+ .then(function() {
+ sameMirotask = true;
+ Promise.resolve().then(() => sameMicrotask = false);
+ });
+}, 'promise test without cleanup #1');
+
+promise_test(function() {
+ assert_false(sameMicrotask);
+
+ return Promise.resolve();
+}, 'sub-test with 0 cleanup functions executes in distinct microtask from a passing sub-test');
+
+promise_test(function() {
+ return Promise.resolve()
+ .then(function() {
+ sameMirotask = true;
+ Promise.resolve().then(() => sameMicrotask = false);
+ throw expectedError;
+ });
+}, 'failing promise test without cleanup #1');
+
+promise_test(function() {
+ assert_false(sameMicrotask);
+
+ return Promise.resolve();
+}, 'sub-test with 0 cleanup functions executes in distinct microtask from a failing sub-test');
+
+promise_test(function(t) {
+ t.add_cleanup(function() {});
+
+ return Promise.resolve()
+ .then(function() {
+ sameMirotask = true;
+ Promise.resolve().then(() => sameMicrotask = false);
+ });
+}, 'promise test with cleanup #1');
+
+promise_test(function() {
+ assert_false(sameMicrotask);
+
+ return Promise.resolve();
+}, 'sub-test with some cleanup functions executes in distinct microtask from a passing sub-test');
+
+promise_test(function(t) {
+ t.add_cleanup(function() {});
+
+ return Promise.resolve()
+ .then(function() {
+ sameMirotask = true;
+ Promise.resolve().then(() => sameMicrotask = false);
+ throw expectedError;
+ });
+}, 'failing promise test with cleanup #1');
+
+promise_test(function() {
+ assert_false(sameMicrotask);
+
+ return Promise.resolve();
+}, 'sub-test with some cleanup functions executes in distinct microtask from a failing sub-test');
+
+promise_test(function(t) {
+ return Promise.resolve()
+ .then(function() {
+ sameTask = true;
+ t.step_timeout(() => sameTask = false, 0);
+ });
+}, 'promise test without cleanup #2');
+
+promise_test(function() {
+ assert_true(sameTask);
+
+ return Promise.resolve();
+}, 'sub-test with 0 cleanup functions executes in the same task as a passing sub-test');
+
+promise_test(function(t) {
+ return Promise.resolve()
+ .then(function() {
+ sameTask = true;
+ t.step_timeout(() => sameTask = false, 0);
+ throw expectedError;
+ });
+}, 'failing promise test without cleanup #2');
+
+promise_test(function() {
+ assert_true(sameTask);
+
+ return Promise.resolve();
+}, 'sub-test with 0 cleanup functions executes in the same task as a failing sub-test');
+
+promise_test(function(t) {
+ t.add_cleanup(function() {});
+
+ return Promise.resolve()
+ .then(function() {
+ sameTask = true;
+ t.step_timeout(() => sameTask = false, 0);
+ });
+}, 'promise test with cleanup #2');
+
+promise_test(function() {
+ assert_true(sameTask);
+
+ return Promise.resolve();
+}, 'sub-test with some cleanup functions executes in the same task as a passing sub-test');
+
+promise_test(function(t) {
+ t.add_cleanup(function() {});
+
+ return Promise.resolve()
+ .then(function() {
+ sameTask = true;
+ t.step_timeout(() => sameTask = false, 0);
+ throw expectedError;
+ });
+}, 'failing promise test with cleanup #2');
+
+promise_test(function() {
+ assert_true(sameTask);
+
+ return Promise.resolve();
+}, 'sub-test with some cleanup functions executes in the same task as a failing sub-test');
+</script>
+
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "message": "promise_test: Unhandled rejection with value: object \"Error: This error is expected\"",
+ "name": "failing promise test with cleanup #1",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": "promise_test: Unhandled rejection with value: object \"Error: This error is expected\"",
+ "name": "failing promise test with cleanup #2",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": "promise_test: Unhandled rejection with value: object \"Error: This error is expected\"",
+ "name": "failing promise test without cleanup #1",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": "promise_test: Unhandled rejection with value: object \"Error: This error is expected\"",
+ "name": "failing promise test without cleanup #2",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": null,
+ "name": "promise test with cleanup #1",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "promise test with cleanup #2",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "promise test without cleanup #1",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "promise test without cleanup #2",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with 0 cleanup functions executes in distinct microtask from a failing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with 0 cleanup functions executes in distinct microtask from a passing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with 0 cleanup functions executes in the same task as a failing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with 0 cleanup functions executes in the same task as a passing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with some cleanup functions executes in distinct microtask from a failing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with some cleanup functions executes in distinct microtask from a passing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with some cleanup functions executes in the same task as a failing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with some cleanup functions executes in the same task as a passing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/task-scheduling-test.html b/testing/web-platform/tests/resources/test/tests/functional/task-scheduling-test.html
new file mode 100644
index 0000000000..035844448d
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/task-scheduling-test.html
@@ -0,0 +1,141 @@
+<!doctype html>
+<title>testharness.js - task scheduling</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+<script>
+var sameMicrotask = null;
+var expectedError = new Error('This error is expected');
+
+// Derived from `immediate`
+// https://github.com/calvinmetcalf/immediate/blob/c353bd2106648cee1d525bfda22cfc4456e69c0e/lib/mutation.js
+function microTask(callback) {
+ var observer = new MutationObserver(callback);
+ var element = document.createTextNode('');
+ observer.observe(element, {
+ characterData: true
+ });
+
+ element.data = true;
+};
+
+async_test(function(t) {
+ var microtask_ran = false;
+
+ t.step_timeout(t.step_func(function() {
+ assert_true(microtask_ran, 'function registered as a microtask was executed before task');
+ t.done();
+ }), 0);
+
+ microTask(function() {
+ microtask_ran = true;
+ });
+}, 'precondition: microtask creation logic functions as expected');
+
+test(function() {
+ sameMicrotask = true;
+ microTask(function() { sameMicrotask = false; });
+}, 'synchronous test without cleanup');
+
+test(function() {
+ assert_true(sameMicrotask);
+}, 'sub-test with 0 cleanup functions executes in the same microtask as a passing sub-test');
+
+test(function() {
+ sameMicrotask = true;
+ microTask(function() { sameMicrotask = false; });
+ throw expectedError;
+}, 'failing synchronous test without cleanup');
+
+test(function() {
+ assert_true(sameMicrotask);
+}, 'sub-test with 0 cleanup functions executes in the same microtask as a failing sub-test');
+
+test(function(t) {
+ t.add_cleanup(function() {});
+
+ sameMicrotask = true;
+ microTask(function() { sameMicrotask = false; });
+}, 'synchronous test with cleanup');
+
+test(function() {
+ assert_true(sameMicrotask);
+}, 'sub-test with some cleanup functions executes in the same microtask as a passing sub-test');
+
+test(function(t) {
+ t.add_cleanup(function() {});
+
+ sameMicrotask = true;
+ microTask(function() { sameMicrotask = false; });
+ throw expectedError;
+}, 'failing synchronous test with cleanup');
+
+test(function() {
+ assert_true(sameMicrotask);
+}, 'sub-test with some cleanup functions executes in the same microtask as a failing sub-test');
+</script>
+
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "message": null,
+ "status_string": "OK"
+ },
+ "summarized_tests": [
+ {
+ "message": "This error is expected",
+ "name": "failing synchronous test with cleanup",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": "This error is expected",
+ "name": "failing synchronous test without cleanup",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": null,
+ "name": "precondition: microtask creation logic functions as expected",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with 0 cleanup functions executes in the same microtask as a failing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with 0 cleanup functions executes in the same microtask as a passing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with some cleanup functions executes in the same microtask as a failing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "sub-test with some cleanup functions executes in the same microtask as a passing sub-test",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "synchronous test with cleanup",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "synchronous test without cleanup",
+ "properties": {},
+ "status_string": "PASS"
+ }
+ ],
+ "type": "complete"
+}
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/uncaught-exception-handle.html b/testing/web-platform/tests/resources/test/tests/functional/uncaught-exception-handle.html
new file mode 100644
index 0000000000..764b0c4055
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/uncaught-exception-handle.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Harness Handling Uncaught Exception</title>
+</head>
+<script src="/resources/testharness.js"></script>
+
+<body>
+<h1>Harness Handling Uncaught Exception</h1>
+<div id="log"></div>
+<script>
+var t = async_test("This should show a harness status of 'Error' and a test status of 'Not Run'");
+throw new Error("Example Error");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Error: Example Error"
+ },
+ "summarized_tests": [
+ {
+ "status_string": "NOTRUN",
+ "name": "This should show a harness status of 'Error' and a test status of 'Not Run'",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/uncaught-exception-ignore.html b/testing/web-platform/tests/resources/test/tests/functional/uncaught-exception-ignore.html
new file mode 100644
index 0000000000..6bd0ddbb0d
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/uncaught-exception-ignore.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Harness Ignoring Uncaught Exception</title>
+</head>
+<script src="/resources/testharness.js"></script>
+
+<body>
+<h1>Harness Ignoring Uncaught Exception</h1>
+<div id="log"></div>
+<script>
+setup({allow_uncaught_exception:true});
+var t = async_test("setup({allow_uncaught_exception:true}) should allow tests to pass even if there is an exception");
+onerror = t.step_func(function() {t.done()});
+throw new Error("Example Error");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "setup({allow_uncaught_exception:true}) should allow tests to pass even if there is an exception",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/worker-dedicated-uncaught-allow.html b/testing/web-platform/tests/resources/test/tests/functional/worker-dedicated-uncaught-allow.html
new file mode 100644
index 0000000000..ba28d4914f
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/worker-dedicated-uncaught-allow.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Dedicated Worker Tests - Allowed Uncaught Exception</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Dedicated Web Worker Tests - Allowed Uncaught Exception</h1>
+<p>Demonstrates running <tt>testharness</tt> based tests inside a dedicated web worker.
+<p>The test harness is expected to pass despite an uncaught exception in a worker because that worker is configured to allow uncaught exceptions.</p>
+<div id="log"></div>
+
+<script>
+test(function(t) {
+ assert_true("Worker" in self, "Browser should support Workers");
+ },
+ "Browser supports Workers");
+
+fetch_tests_from_worker(new Worker("worker-uncaught-allow.js"));
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Browser supports Workers",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "onerror event is triggered",
+ "properties": {},
+ "message": null
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/worker-dedicated-uncaught-single.html b/testing/web-platform/tests/resources/test/tests/functional/worker-dedicated-uncaught-single.html
new file mode 100644
index 0000000000..486e067114
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/worker-dedicated-uncaught-single.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Dedicated Worker Tests - Uncaught Exception in Single-Page Test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Dedicated Web Worker Tests - Uncaught Exception in Single-Page Test</h1>
+<p>Demonstrates running <tt>testharness</tt> based tests inside a dedicated web worker.
+<p>The test harness is expected to pass despite an uncaught exception in a worker because that worker is a single-page test.</p>
+<div id="log"></div>
+
+<script>
+test(function(t) {
+ assert_true("Worker" in self, "Browser should support Workers");
+ },
+ "Browser supports Workers");
+
+fetch_tests_from_worker(new Worker("worker-uncaught-single.js"));
+
+test(function(t) {
+ assert_false(false, "False is false");
+ },
+ "Test running on main document.");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "OK",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Browser supports Workers",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test running on main document.",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "FAIL",
+ "name": "worker-uncaught-single",
+ "properties": {},
+ "message": "Error: This failure is expected."
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/worker-dedicated.sub.html b/testing/web-platform/tests/resources/test/tests/functional/worker-dedicated.sub.html
new file mode 100644
index 0000000000..efd703c760
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/worker-dedicated.sub.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Dedicated Worker Tests</title>
+<script src="../../../testharness.js"></script>
+<script src="../../../testharnessreport.js"></script>
+</head>
+<body>
+<h1>Dedicated Web Worker Tests</h1>
+<p>Demonstrates running <tt>testharness</tt> based tests inside a dedicated web worker.
+<p>The test harness is expected to fail due to an uncaught exception in one worker.</p>
+<div id="log"></div>
+
+<script>
+test(function(t) {
+ assert_true("Worker" in self, "Browser should support Workers");
+ },
+ "Browser supports Workers");
+
+fetch_tests_from_worker(new Worker("worker.js"));
+
+fetch_tests_from_worker(new Worker("worker-error.js"));
+
+test(function(t) {
+ assert_false(false, "False is false");
+ },
+ "Test running on main document.");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "ERROR",
+ "message": "Error: This failure is expected."
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Browser supports Workers",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Test running on main document.",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Worker async_test that completes successfully",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Worker test that completes successfully",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "worker test that completes successfully before exception",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "Worker test that doesn't run ('NOT RUN')",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Worker test that fails ('FAIL')",
+ "properties": {},
+ "message": "assert_true: Failing test expected true got false"
+ },
+ {
+ "status_string": "TIMEOUT",
+ "name": "Worker test that times out ('TIMEOUT')",
+ "properties": {},
+ "message": "Test timed out"
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/worker-error.js b/testing/web-platform/tests/resources/test/tests/functional/worker-error.js
new file mode 100644
index 0000000000..7b89602f04
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/worker-error.js
@@ -0,0 +1,8 @@
+importScripts("/resources/testharness.js");
+
+// The following sub-test ensures that the worker is not interpreted as a
+// single-page test. The subsequent uncaught exception should therefore be
+// interpreted as a harness error rather than a single-page test failure.
+test(function() {}, "worker test that completes successfully before exception");
+
+throw new Error("This failure is expected.");
diff --git a/testing/web-platform/tests/resources/test/tests/functional/worker-service.html b/testing/web-platform/tests/resources/test/tests/functional/worker-service.html
new file mode 100644
index 0000000000..2e07746e62
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/worker-service.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Example with a service worker</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Service Worker Tests</h1>
+<p>Demonstrates running <tt>testharness</tt> based tests inside a service worker.
+<p>The test harness should time out due to one of the tests inside the worker timing out.
+<p>This test assumes that the browser supports <a href="http://www.w3.org/TR/service-workers/">ServiceWorkers</a>.
+<div id="log"></div>
+
+<script>
+test(
+ function(t) {
+ assert_true("serviceWorker" in navigator,
+ "navigator.serviceWorker exists");
+ },
+ "Browser supports ServiceWorker");
+
+promise_test(
+ function() {
+ // Since the service worker registration could be in an indeterminate
+ // state (due to, for example, a previous test run failing), we start by
+ // unregstering our service worker and then registering it again.
+ var scope = "service-worker-scope";
+ var worker_url = "worker.js";
+
+ return navigator.serviceWorker.register(worker_url, {scope: scope})
+ .then(
+ function(registration) {
+ return registration.unregister();
+ })
+ .then(
+ function() {
+ return navigator.serviceWorker.register(worker_url, {scope: scope});
+ })
+ .then(
+ function(registration) {
+ add_completion_callback(
+ function() {
+ registration.unregister();
+ });
+
+ return new Promise(
+ function(resolve) {
+ registration.addEventListener("updatefound",
+ function() {
+ resolve(registration.installing);
+ });
+ });
+ })
+ .then(
+ function(worker) {
+ fetch_tests_from_worker(worker);
+ });
+ },
+ "Register ServiceWorker");
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "TIMEOUT",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Browser supports ServiceWorker",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Register ServiceWorker",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Worker async_test that completes successfully",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "PASS",
+ "name": "Worker test that completes successfully",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "NOTRUN",
+ "name": "Worker test that doesn't run ('NOT RUN')",
+ "properties": {},
+ "message": null
+ },
+ {
+ "status_string": "FAIL",
+ "name": "Worker test that fails ('FAIL')",
+ "properties": {},
+ "message": "assert_true: Failing test expected true got false"
+ },
+ {
+ "status_string": "TIMEOUT",
+ "name": "Worker test that times out ('TIMEOUT')",
+ "properties": {},
+ "message": "Test timed out"
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/worker-shared.html b/testing/web-platform/tests/resources/test/tests/functional/worker-shared.html
new file mode 100644
index 0000000000..e26f17dec2
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/worker-shared.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Example with a shared worker</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<h1>Shared Web Worker Tests</h1>
+<p>Demonstrates running <tt>testharness</tt> based tests inside a shared worker.
+<p>The test harness should time out due to one of the tests in the worker timing out.
+<p>This test assumes that the browser supports <a href="http://www.w3.org/TR/workers/#shared-workers-and-the-sharedworker-interface">shared web workers</a>.
+<div id="log"></div>
+
+<script>
+test(
+ function(t) {
+ assert_true("SharedWorker" in self,
+ "Browser should support SharedWorkers");
+ },
+ "Browser supports SharedWorkers");
+
+fetch_tests_from_worker(new SharedWorker("worker.js",
+ "My shared worker"));
+</script>
+<script type="text/json" id="expected">
+{
+ "summarized_status": {
+ "status_string": "TIMEOUT",
+ "message": null
+ },
+ "summarized_tests": [
+ {
+ "status_string": "PASS",
+ "name": "Browser supports SharedWorkers",
+ "properties": {},
+ "message": null
+ },
+ {
+ "message": null,
+ "name": "Worker async_test that completes successfully",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "Worker test that completes successfully",
+ "properties": {},
+ "status_string": "PASS"
+ },
+ {
+ "message": null,
+ "name": "Worker test that doesn't run ('NOT RUN')",
+ "properties": {},
+ "status_string": "NOTRUN"
+ },
+ {
+ "message": "assert_true: Failing test expected true got false",
+ "name": "Worker test that fails ('FAIL')",
+ "properties": {},
+ "status_string": "FAIL"
+ },
+ {
+ "message": "Test timed out",
+ "name": "Worker test that times out ('TIMEOUT')",
+ "properties": {},
+ "status_string": "TIMEOUT"
+ }
+ ],
+ "type": "complete"
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/resources/test/tests/functional/worker-uncaught-allow.js b/testing/web-platform/tests/resources/test/tests/functional/worker-uncaught-allow.js
new file mode 100644
index 0000000000..6925d59349
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/worker-uncaught-allow.js
@@ -0,0 +1,19 @@
+importScripts("/resources/testharness.js");
+
+setup({allow_uncaught_exception:true});
+
+async_test(function(t) {
+ onerror = function() {
+ // Further delay the test's completion to ensure that the worker's
+ // `onerror` handler does not influence results in the parent context.
+ setTimeout(function() {
+ t.done();
+ }, 0);
+ };
+
+ setTimeout(function() {
+ throw new Error("This error is expected.");
+ }, 0);
+}, 'onerror event is triggered');
+
+done();
diff --git a/testing/web-platform/tests/resources/test/tests/functional/worker-uncaught-single.js b/testing/web-platform/tests/resources/test/tests/functional/worker-uncaught-single.js
new file mode 100644
index 0000000000..c04542b2f5
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/worker-uncaught-single.js
@@ -0,0 +1,8 @@
+importScripts("/resources/testharness.js");
+
+setup({ single_test: true });
+
+// Because this script enables the `single_test` configuration option, it
+// should be interpreted as a single-page test, and the uncaught exception
+// should be reported as a test failure (harness status: OK).
+throw new Error("This failure is expected.");
diff --git a/testing/web-platform/tests/resources/test/tests/functional/worker.js b/testing/web-platform/tests/resources/test/tests/functional/worker.js
new file mode 100644
index 0000000000..a923bc2d89
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/functional/worker.js
@@ -0,0 +1,34 @@
+importScripts("/resources/testharness.js");
+
+test(
+ function(test) {
+ assert_true(true, "True is true");
+ },
+ "Worker test that completes successfully");
+
+test(
+ function(test) {
+ assert_true(false, "Failing test");
+ },
+ "Worker test that fails ('FAIL')");
+
+async_test(
+ function(test) {
+ assert_true(true, "True is true");
+ },
+ "Worker test that times out ('TIMEOUT')");
+
+async_test("Worker test that doesn't run ('NOT RUN')");
+
+async_test(
+ function(test) {
+ self.setTimeout(
+ function() {
+ test.done();
+ },
+ 0);
+ },
+ "Worker async_test that completes successfully");
+
+// An explicit done() is required for dedicated and shared web workers.
+done();
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlArray/is_json_type.html b/testing/web-platform/tests/resources/test/tests/unit/IdlArray/is_json_type.html
new file mode 100644
index 0000000000..18e83a8e89
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlArray/is_json_type.html
@@ -0,0 +1,192 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>IdlArray.prototype.is_json_type()</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+ "use strict";
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_true(idl.is_json_type(typeFrom("DOMString")));
+ assert_true(idl.is_json_type(typeFrom("ByteString")));
+ assert_true(idl.is_json_type(typeFrom("USVString")));
+ idl.add_untested_idls('enum BarEnum { "a", "b", "c" };');
+ assert_true(idl.is_json_type(typeFrom("BarEnum")));
+ }, 'should return true for all string types');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_false(idl.is_json_type(typeFrom("Error")));
+ assert_false(idl.is_json_type(typeFrom("DOMException")));
+ }, 'should return false for all exception types');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_false(idl.is_json_type(typeFrom("Int8Array")));
+ assert_false(idl.is_json_type(typeFrom("Int16Array")));
+ assert_false(idl.is_json_type(typeFrom("Int32Array")));
+ assert_false(idl.is_json_type(typeFrom("Uint8Array")));
+ assert_false(idl.is_json_type(typeFrom("Uint16Array")));
+ assert_false(idl.is_json_type(typeFrom("Uint32Array")));
+ assert_false(idl.is_json_type(typeFrom("Uint8ClampedArray")));
+ assert_false(idl.is_json_type(typeFrom("BigInt64Array")));
+ assert_false(idl.is_json_type(typeFrom("BigUint64Array")));
+ assert_false(idl.is_json_type(typeFrom("Float32Array")));
+ assert_false(idl.is_json_type(typeFrom("Float64Array")));
+ assert_false(idl.is_json_type(typeFrom("ArrayBuffer")));
+ assert_false(idl.is_json_type(typeFrom("DataView")));
+ }, 'should return false for all buffer source types');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_true(idl.is_json_type(typeFrom("boolean")));
+ }, 'should return true for boolean');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_true(idl.is_json_type(typeFrom("byte")));
+ assert_true(idl.is_json_type(typeFrom("octet")));
+ assert_true(idl.is_json_type(typeFrom("short")));
+ assert_true(idl.is_json_type(typeFrom("unsigned short")));
+ assert_true(idl.is_json_type(typeFrom("long")));
+ assert_true(idl.is_json_type(typeFrom("unsigned long")));
+ assert_true(idl.is_json_type(typeFrom("long long")));
+ assert_true(idl.is_json_type(typeFrom("unsigned long long")));
+ assert_true(idl.is_json_type(typeFrom("float")));
+ assert_true(idl.is_json_type(typeFrom("unrestricted float")));
+ assert_true(idl.is_json_type(typeFrom("double")));
+ assert_true(idl.is_json_type(typeFrom("unrestricted double")));
+ }, 'should return true for all numeric types');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_false(idl.is_json_type(typeFrom("Promise<DOMString>")));
+ }, 'should return false for promises');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_false(idl.is_json_type(typeFrom("sequence<DOMException>")));
+ assert_true(idl.is_json_type(typeFrom("sequence<DOMString>")));
+ }, 'should handle sequences according to their inner types');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_false(idl.is_json_type(typeFrom("FrozenArray<DOMException>")));
+ assert_true(idl.is_json_type(typeFrom("FrozenArray<DOMString>")));
+ }, 'should handle frozen arrays according to their inner types');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_true(idl.is_json_type(typeFrom("record<DOMString, DOMString>")));
+ assert_false(idl.is_json_type(typeFrom("record<DOMString, Error>")));
+ }, 'should handle records according to their inner types');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_true(idl.is_json_type(typeFrom("object")));
+ }, 'should return true for object type');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_false(idl.is_json_type(typeFrom("any")));
+ }, 'should return false for any type');
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_untested_idls('dictionary Foo { DOMString foo; }; dictionary Bar : Foo { DOMString bar; };');
+ assert_true(idl.is_json_type(typeFrom("Foo")));
+ assert_true(idl.is_json_type(typeFrom("Bar")));
+ }, 'should return true for dictionaries whose members are all JSON types');
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_untested_idls('dictionary Foo { };');
+ assert_true(idl.is_json_type(typeFrom("Foo")));
+ }, 'should return true for dictionaries which have no members');
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_untested_idls('dictionary FooBar { DOMString a; Error b; }; dictionary Baz : FooBar {};');
+ assert_false(idl.is_json_type(typeFrom("FooBar")));
+ assert_false(idl.is_json_type(typeFrom("Baz")));
+ }, 'should return false for dictionaries whose members are not all JSON types');
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_untested_idls('interface Foo { DOMString toJSON(); };');
+ assert_true(idl.is_json_type(typeFrom("Foo")));
+ }, 'should return true for interfaces which declare a toJSON operation');
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_untested_idls('interface Foo { DOMString toJSON(); }; interface Bar : Foo { };');
+ assert_true(idl.is_json_type(typeFrom("Bar")));
+ }, 'should return true for interfaces which inherit from an interface which declares a toJSON operation');
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_untested_idls('interface Foo { }; interface mixin Bar { DOMString toJSON(); }; Foo includes Bar;');
+ idl.merge_mixins();
+ assert_true(idl.is_json_type(typeFrom("Foo")));
+ }, 'should return true for interfaces which mixin an interface which declare a toJSON operation');
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_untested_idls('interface Foo { };');
+ assert_false(idl.is_json_type(typeFrom("Foo")));
+ }, 'should return false for interfaces which do not declare a toJSON operation');
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_untested_idls('interface Foo { object toJSON(); };');
+ assert_true(idl.is_json_type(typeFrom("(Foo or DOMString)")));
+ }, 'should return true for union types whose member types are JSON types');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_false(idl.is_json_type(typeFrom("(DataView or DOMString)")));
+ }, 'should return false for union types whose member types are not all JSON types');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_true(idl.is_json_type(typeFrom("DOMString?")));
+ assert_false(idl.is_json_type(typeFrom("DataView?")));
+ }, 'should consider the inner types of nullable types');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_true(idl.is_json_type(typeFrom("[XAttr] long")));
+ assert_false(idl.is_json_type(typeFrom("[XAttr] DataView")));
+ }, 'should consider the inner types of annotated types.');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_throws_js(Error, _ => idl.is_json_type(typeFrom("Foo")));
+ }, "should throw if it references a dictionary, enum or interface which wasn't added to the IdlArray");
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_untested_idls('interface Foo : Bar { };');
+ assert_throws_js(Error, _ => idl.is_json_type(typeFrom("Foo")));
+ }, "should throw for interfaces which inherit from another interface which wasn't added to the IdlArray");
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_true(idl.is_json_type(typedefFrom("typedef double DOMHighResTimeStamp;").idlType));
+ }, 'should return true for typedefs whose source type is a JSON type');
+
+ test(function() {
+ var idl = new IdlArray();
+ assert_false(idl.is_json_type(typedefFrom("typedef DataView DOMHighResTimeStamp;").idlType));
+ }, 'should return false for typedefs whose source type is not a JSON type');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlDictionary/get_reverse_inheritance_stack.html b/testing/web-platform/tests/resources/test/tests/unit/IdlDictionary/get_reverse_inheritance_stack.html
new file mode 100644
index 0000000000..418bcdec92
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlDictionary/get_reverse_inheritance_stack.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>IdlDictionary.prototype.get_reverse_inheritance_stack()</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+ "use strict";
+ test(function() {
+ var stack = dictionaryFrom('dictionary A { };').get_reverse_inheritance_stack();
+ assert_array_equals(stack.map(d => d.name), ["A"]);
+ }, 'should return an array that includes itself.');
+
+ test(function() {
+ var d = dictionaryFrom('dictionary A : B { };');
+ assert_throws_js(Error, _ => d.get_reverse_inheritance_stack());
+ }, "should throw for dictionaries which inherit from another dictionary which wasn't added to the IdlArray");
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_idls('dictionary A : B { };');
+ idl.add_untested_idls('dictionary B : C { }; dictionary C { };');
+ var A = idl.members["A"];
+ assert_array_equals(A.get_reverse_inheritance_stack().map(d => d.name), ["C", "B", "A"]);
+ }, 'should return an array of dictionaries in order of inheritance, starting with the base dictionary');
+
+ test(function () {
+ let i = new IdlArray();
+ i.add_untested_idls('dictionary A : B {};');
+ i.assert_throws(new IdlHarnessError('A inherits B, but B is undefined.'), i => i.test());
+ }, 'A : B with B undeclared should throw IdlHarnessError');
+
+ test(function () {
+ let i = new IdlArray();
+ i.add_untested_idls('dictionary A : B {};');
+ i.add_untested_idls('interface B {};');
+ i.assert_throws(new IdlHarnessError('A inherits B, but A is not an interface.'), i => i.test());
+ }, 'dictionary A : B with B interface should throw IdlHarnessError');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlDictionary/test_partial_dictionary.html b/testing/web-platform/tests/resources/test/tests/unit/IdlDictionary/test_partial_dictionary.html
new file mode 100644
index 0000000000..d6137f6895
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlDictionary/test_partial_dictionary.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>idlharness: partial dictionaries</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+ <script src="../../../idl-helper.js"></script>
+</head>
+
+<body>
+<pre id='idl'>
+dictionary A {};
+partial dictionary A {
+ boolean B;
+};
+partial dictionary A {
+ boolean C;
+};
+</pre>
+
+<script>
+'use strict';
+
+test(() => {
+ let idlArray = new IdlArray();
+ idlArray.add_idls(document.getElementById('idl').textContent);
+ idlArray.test();
+
+ let members = idlArray.members["A"].members.map(m => m.name);
+ assert_array_equals(members, ["B", "C"], 'A should contain B, C');
+}, 'Partial dictionaries');
+</script>
+
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/constructors.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/constructors.html
new file mode 100644
index 0000000000..e9ee3f8680
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/constructors.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<title>IdlInterface.prototype.constructors()</title>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+"use strict";
+// [Constructor] extended attribute should not be supported:
+test(function() {
+ var i = interfaceFrom('[Constructor] interface A { };');
+ assert_equals(i.constructors().length, 0);
+}, 'Interface with Constructor extended attribute.');
+
+test(function() {
+ var i = interfaceFrom('interface A { constructor(); };');
+ assert_equals(i.constructors().length, 1);
+}, 'Interface with constructor method');
+
+test(function() {
+ var i = interfaceFrom('interface A { constructor(); constructor(any value); };');
+ assert_equals(i.constructors().length, 2);
+}, 'Interface with constructor overloads');
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/default_to_json_operation.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/default_to_json_operation.html
new file mode 100644
index 0000000000..5ade7d0d28
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/default_to_json_operation.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>IdlDictionary.prototype.default_to_json_operation()</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+ "use strict";
+ test(function() {
+ var map = interfaceFrom('interface A { [Default] object toJSON(); };').default_to_json_operation();
+ assert_equals(map.size, 0);
+ }, 'should return an empty map when there are no attributes');
+
+ test(function() {
+ var r = interfaceFrom('interface A { };').default_to_json_operation();
+ assert_equals(r, null);
+ }, 'should return null when there is no toJSON method');
+
+ test(function() {
+ var r = interfaceFrom('interface A { DOMString toJSON(); };').default_to_json_operation();
+ assert_equals(r, null);
+ }, 'should return null when there is a toJSON method but it does not have the [Default] extended attribute');
+
+ test(function() {
+ var context = new IdlArray();
+ context.add_idls("interface A : B { DOMString toJSON(); };");
+ context.add_idls("interface B { [Default] object toJSON(); };");
+ var r = context.members.A.default_to_json_operation();
+ assert_equals(r, null);
+ }, 'should return null when there is a toJSON method but it does not have the [Default] extended attribute even if this extended attribute exists on inherited interfaces');
+
+ test(function() {
+ var map = interfaceFrom('interface A { [Default] object toJSON(); static attribute DOMString foo; };').default_to_json_operation();
+ assert_equals(map.size, 0);
+ }, 'should not include static attributes');
+
+ test(function() {
+ var map = interfaceFrom('interface A { [Default] object toJSON(); attribute Promise<DOMString> bar; };').default_to_json_operation();
+ assert_equals(map.size, 0);
+ }, 'should not include attributes which are not JSON types');
+
+ test(function() {
+ var map = interfaceFrom('interface A { [Default] object toJSON(); DOMString bar(); };').default_to_json_operation();
+ assert_equals(map.size, 0);
+ }, 'should not include operations');
+
+ test(function() {
+ var map = interfaceFrom('interface A { [Default] object toJSON(); attribute DOMString bar; };').default_to_json_operation();
+ assert_equals(map.size, 1);
+ assert_true(map.has("bar"));
+ assert_equals(map.get("bar").idlType, "DOMString");
+ }, 'should return a map whose key/value pair represent the identifier and IDL type of valid attributes');
+
+ test(function() {
+ var context = new IdlArray();
+ context.add_idls("interface A : B { [Default] object toJSON(); attribute DOMString a; };");
+ context.add_idls("interface B { [Default] object toJSON(); attribute long b; };");
+ var map = context.members.A.default_to_json_operation();
+ assert_array_equals([...map.keys()], ["b", "a"]);
+ assert_array_equals([...map.values()].map(v => v.idlType), ["long", "DOMString"]);
+ }, 'should return a properly ordered map that contains IDL types of valid attributes for inherited interfaces');
+
+ test(function() {
+ var context = new IdlArray();
+ context.add_idls("interface A : B { attribute DOMString a; };");
+ context.add_idls("interface B { [Default] object toJSON(); attribute long b; };");
+ var map = context.members.A.default_to_json_operation();
+ assert_equals(map.size, 1);
+ assert_true(map.has("b"));
+ assert_equals(map.get("b").idlType, "long");
+ assert_array_equals([...map.keys()], ["b"]);
+ }, 'should not include attributes of the current interface when the [Default] toJSON method in inherited');
+
+ test(function() {
+ var context = new IdlArray();
+ context.add_idls("interface A : B { [Default] object toJSON(); };");
+ context.add_idls("interface B : C { [Default] object toJSON(); attribute DOMString foo; };");
+ context.add_idls("interface C { [Default] object toJSON(); attribute long foo; };");
+ var map = context.members.A.default_to_json_operation();
+ assert_equals(map.size, 1);
+ assert_true(map.has("foo"));
+ assert_equals(map.get("foo").idlType, "DOMString");
+ }, 'attributes declared further away in the inheritance hierarchy should be masked by attributes declared closer');
+
+ test(function() {
+ var context = new IdlArray();
+ context.add_idls("interface A { [Default] object toJSON(); attribute DOMString a; };");
+ context.add_idls("interface B : A { attribute any b; };");
+ context.add_idls("interface C : B { [Default] object toJSON(); attribute long c; };");
+ var map = context.members.C.default_to_json_operation();
+ assert_array_equals([...map.keys()], ["a", "c"]);
+ assert_array_equals([...map.values()].map(v => v.idlType), ["DOMString", "long"]);
+ }, 'should return an ordered map that ignores attributes of inherited interfaces which do not declare a [Default] toJSON operation.');
+
+ test(function() {
+ var context = new IdlArray();
+ context.add_idls("interface D { attribute DOMString d; };");
+ context.add_idls("interface mixin M { [Default] object toJSON(); attribute long m; };");
+ context.add_idls("D includes M;");
+ context.merge_mixins();
+ var map = context.members.D.default_to_json_operation();
+ assert_array_equals([...map.keys()], ["d", "m"]);
+ assert_array_equals([...map.values()].map(v => v.idlType), ["DOMString", "long"]);
+ }, 'should return a properly ordered map that accounts for mixed-in interfaces which declare a [Default] toJSON operation.');
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/do_member_unscopable_asserts.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/do_member_unscopable_asserts.html
new file mode 100644
index 0000000000..90142efe6b
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/do_member_unscopable_asserts.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>IdlDictionary.prototype.do_member_unscopable_asserts()</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+ 'use strict';
+ function mock_interface_A(unscopables) {
+ self.A = function A() {};
+ A.prototype[Symbol.unscopables] = unscopables;
+ }
+
+ test(function() {
+ const i = interfaceFrom('interface A { [Unscopable] attribute any x; };');
+ const member = i.members[0];
+ assert_true(member.isUnscopable);
+ mock_interface_A({ x: true });
+ i.do_member_unscopable_asserts(member);
+ }, 'should not throw for [Unscopable] with property in @@unscopables');
+
+ test(function() {
+ const i = interfaceFrom('interface A { [Unscopable] attribute any x; };');
+ const member = i.members[0];
+ assert_true(member.isUnscopable);
+ mock_interface_A({});
+ // assert_throws_* can't be used because they rethrow AssertionErrors.
+ try {
+ i.do_member_unscopable_asserts(member);
+ } catch(e) {
+ assert_true(e.message.includes('Symbol.unscopables'));
+ return;
+ }
+ assert_unreached('did not throw');
+ }, 'should throw for [Unscopable] with property missing from @@unscopables');
+
+ // This test checks that for attributes/methods which aren't [Unscopable]
+ // in the IDL, we don't assert that @@unscopables is missing the property.
+ // This could miss implementation bugs, but [Unscopable] is so rarely used
+ // that it's fairly unlikely to ever happen.
+ test(function() {
+ const i = interfaceFrom('interface A { attribute any x; };');
+ const member = i.members[0];
+ assert_false(member.isUnscopable);
+ mock_interface_A({ x: true });
+ i.do_member_unscopable_asserts(member);
+ }, 'should not throw if [Unscopable] is used but property is in @@unscopables');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_interface_object.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_interface_object.html
new file mode 100644
index 0000000000..a3d901a752
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_interface_object.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<title>IdlInterface.prototype.get_interface_object()</title>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+"use strict";
+test(function() {
+ window.A = {};
+ var i = interfaceFrom('interface A { };');
+ assert_equals(i.get_interface_object(), window.A);
+}, 'Interface does not have LegacyNamespace.');
+
+test(function() {
+ window.Foo = { A: {} };
+ var i = interfaceFrom('[LegacyNamespace=Foo] interface A { }; namespace Foo { };');
+ assert_equals(i.get_interface_object(), window.Foo.A);
+}, 'Interface has LegacyNamespace');
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_interface_object_owner.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_interface_object_owner.html
new file mode 100644
index 0000000000..51ab2067bc
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_interface_object_owner.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<title>IdlInterface.prototype.get_interface_object_owner()</title>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+"use strict";
+test(function() {
+ var i = interfaceFrom('interface A { };');
+ assert_equals(i.get_interface_object_owner(), window);
+}, 'Interface does not have LegacyNamespace.');
+
+test(function() {
+ window.Foo = {};
+ var i = interfaceFrom('[LegacyNamespace=Foo] interface A { }; namespace Foo { };');
+ assert_equals(i.get_interface_object_owner(), window.Foo);
+}, 'Interface has LegacyNamespace');
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_legacy_namespace.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_legacy_namespace.html
new file mode 100644
index 0000000000..e2d42bb09e
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_legacy_namespace.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<title>IdlInterface.prototype.get_legacy_namespace()</title>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+"use strict";
+test(function() {
+ var i = interfaceFrom('interface A { };');
+ assert_equals(i.get_legacy_namespace(), undefined);
+}, 'Interface does not have LegacyNamespace.');
+
+test(function() {
+ var i = interfaceFrom('[LegacyNamespace=Foo] interface A { }; namespace Foo { };');
+ assert_equals(i.get_legacy_namespace(), "Foo");
+}, 'Interface has LegacyNamespace');
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_qualified_name.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_qualified_name.html
new file mode 100644
index 0000000000..677a31b5e7
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_qualified_name.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<title>IdlInterface.prototype.get_qualified_name()</title>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+"use strict";
+test(function() {
+ var i = interfaceFrom('interface A { };');
+ assert_equals(i.get_qualified_name(), "A");
+}, 'Interface does not have LegacyNamespace.');
+
+test(function() {
+ var i = interfaceFrom('[LegacyNamespace=Foo] interface A { }; namespace Foo { };');
+ assert_equals(i.get_qualified_name(), "Foo.A");
+}, 'Interface has LegacyNamespace');
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_reverse_inheritance_stack.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_reverse_inheritance_stack.html
new file mode 100644
index 0000000000..0c066baabb
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/get_reverse_inheritance_stack.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>IdlInterface.prototype.get_reverse_inheritance_stack()</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+ "use strict";
+ test(function() {
+ var stack = interfaceFrom('interface A { };').get_reverse_inheritance_stack();
+ assert_array_equals(stack.map(i => i.name), ["A"]);
+ }, 'should return an array that includes itself.');
+
+ test(function() {
+ var i = interfaceFrom('interface A : B { };');
+ assert_throws_js(Error, _ => i.get_reverse_inheritance_stack());
+ }, "should throw for interfaces which inherit from another interface which wasn't added to the IdlArray");
+
+ test(function() {
+ var idl = new IdlArray();
+ idl.add_idls('interface A : B { };');
+ idl.add_untested_idls('interface B : C { }; interface C { };');
+ var A = idl.members["A"];
+ assert_array_equals(A.get_reverse_inheritance_stack().map(i => i.name), ["C", "B", "A"]);
+ }, 'should return an array of interfaces in order of inheritance, starting with the base interface');
+
+ test(function () {
+ var idl = new IdlArray();
+ idl.add_untested_idls('interface A : B { };');
+ idl.add_untested_idls('interface B : A { };');
+ idl.assert_throws('A has a circular dependency: A,B,A', i => i.test());
+ }, 'should throw when inheritance is circular');
+
+ test(function () {
+ var idl = new IdlArray();
+ idl.add_untested_idls('interface A : B { };');
+ idl.assert_throws(
+ 'Duplicate identifier A',
+ i => i.add_untested_idls('interface A : C { };'));
+ }, 'should throw when multiple inheritances defined');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/has_default_to_json_regular_operation.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/has_default_to_json_regular_operation.html
new file mode 100644
index 0000000000..b47262b72b
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/has_default_to_json_regular_operation.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>IdlInterface.prototype.has_default_to_json_regular_operation()</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+ "use strict";
+ test(function() {
+ var i = interfaceFrom('interface A { };');
+ assert_false(i.has_default_to_json_regular_operation());
+ }, 'should return false when the interface declares no toJSON operation.');
+
+ test(function() {
+ var i = interfaceFrom('interface A { static object toJSON(); };');
+ assert_false(i.has_default_to_json_regular_operation());
+ }, 'should return false when the interface declares a static toJSON operation.');
+
+ test(function() {
+ var i = interfaceFrom('interface A { object toJSON(); };');
+ assert_false(i.has_default_to_json_regular_operation());
+ }, 'should return false when the interface declares a regular toJSON operation with no extended attribute.');
+
+ test(function() {
+ var i = interfaceFrom('interface A { [x] object toJSON(); };');
+ assert_false(i.has_default_to_json_regular_operation());
+ }, 'should return false when the interface declares a regular toJSON operation with another extented attribute.');
+
+ test(function() {
+ var i = interfaceFrom('interface A { [Default] object toJSON(); };');
+ assert_true(i.has_default_to_json_regular_operation());
+ }, 'should return true when the interface declares a regular toJSON operation with the [Default] extented attribute.');
+
+ test(function() {
+ var i = interfaceFrom('interface A { [Attr, AnotherAttr, Default] object toJSON(); };');
+ assert_true(i.has_default_to_json_regular_operation());
+ }, 'should return true when the interface declares a regular toJSON operation with multiple extended attributes, including [Default].');
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/has_to_json_regular_operation.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/has_to_json_regular_operation.html
new file mode 100644
index 0000000000..a1a641bd97
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/has_to_json_regular_operation.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>IdlInterface.prototype.has_to_json_regular_operation()</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+ "use strict";
+ test(function() {
+ var i = interfaceFrom('interface A { };');
+ assert_false(i.has_to_json_regular_operation());
+ }, 'should return false when the interface declares no toJSON operation.');
+
+ test(function() {
+ var i = interfaceFrom('interface A { static object toJSON(); };');
+ assert_false(i.has_to_json_regular_operation());
+ }, 'should return false when the interface declares a static toJSON operation.');
+
+ test(function() {
+ var i = interfaceFrom('interface A { object toJSON(); };');
+ assert_true(i.has_to_json_regular_operation());
+ }, 'should return true when the interface declares a regular toJSON operation.');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/should_have_interface_object.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/should_have_interface_object.html
new file mode 100644
index 0000000000..3ce945751d
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/should_have_interface_object.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<title>IdlInterface.prototype.should_have_interface_object()</title>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+"use strict";
+test(function() {
+ var i = interfaceFrom("callback interface A { const unsigned short B = 0; };");
+ assert_true(i.should_have_interface_object());
+}, "callback interface with a constant");
+
+test(function() {
+ var i = interfaceFrom("callback interface A { undefined b(); sequence<any> c(); };");
+ assert_false(i.should_have_interface_object());
+}, "callback interface without a constant");
+
+test(function() {
+ var i = interfaceFrom("[LegacyNoInterfaceObject] interface A { };");
+ assert_false(i.should_have_interface_object());
+}, "non-callback interface with [LegacyNoInterfaceObject]");
+
+test(function() {
+ var i = interfaceFrom("interface A { };");
+ assert_true(i.should_have_interface_object());
+}, "non-callback interface without [LegacyNoInterfaceObject]");
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/test_primary_interface_of_undefined.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/test_primary_interface_of_undefined.html
new file mode 100644
index 0000000000..0031558ad4
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterface/test_primary_interface_of_undefined.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>idlharness test_primary_interface_of_undefined</title>
+</head>
+
+<body>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/WebIDLParser.js"></script>
+ <script src="/resources/idlharness.js"></script>
+ <script>
+ 'use strict';
+ test(function () {
+ let i = new IdlArray();
+ i.add_untested_idls('interface A : B {};');
+ i.assert_throws(new IdlHarnessError('A inherits B, but B is undefined.'), i => i.test());
+ }, 'A : B with B undeclared should throw IdlHarnessError');
+
+ test(function () {
+ let i = new IdlArray();
+ i.add_untested_idls('interface A : B {};');
+ i.add_untested_idls('dictionary B {};');
+ i.assert_throws(new IdlHarnessError('A inherits B, but B is not an interface.'), i => i.test());
+ }, 'interface A : B with B dictionary should throw IdlHarnessError');
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterfaceMember/is_to_json_regular_operation.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterfaceMember/is_to_json_regular_operation.html
new file mode 100644
index 0000000000..b3f402dd08
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterfaceMember/is_to_json_regular_operation.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>IdlInterfaceMember.prototype.is_to_json_regular_operation()</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+ "use strict";
+ test(function() {
+ var m = memberFrom("readonly attribute DOMString foo");
+ assert_false(m.is_to_json_regular_operation());
+ }, 'should return false when member is an attribute.');
+
+ test(function() {
+ var m = memberFrom("static undefined foo()");
+ assert_false(m.is_to_json_regular_operation());
+ }, 'should return false when member is a static operation.');
+
+ test(function() {
+ var m = memberFrom("static object toJSON()");
+ assert_false(m.is_to_json_regular_operation());
+ }, 'should return false when member is a static toJSON operation.');
+
+ test(function() {
+ var m = memberFrom("object toJSON()");
+ assert_true(m.is_to_json_regular_operation());
+ }, 'should return true when member is a regular toJSON operation.');
+
+ test(function() {
+ var m = memberFrom("[Foo] object toJSON()");
+ assert_true(m.is_to_json_regular_operation());
+ }, 'should return true when member is a regular toJSON operation with extensible attributes.');
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/resources/test/tests/unit/IdlInterfaceMember/toString.html b/testing/web-platform/tests/resources/test/tests/unit/IdlInterfaceMember/toString.html
new file mode 100644
index 0000000000..054dbb1ccb
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/IdlInterfaceMember/toString.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>IdlInterfaceMember.prototype.toString()</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script src="../../../idl-helper.js"></script>
+<script>
+"use strict";
+const tests = [
+ ["long x", "long"],
+ ["long? x", "long?"],
+ ["Promise<long> x", "Promise<long>"],
+ ["Promise<long?> x", "Promise<long?>"],
+ ["sequence<long> x", "sequence<long>"],
+ ["(long or DOMString) x", "(long or DOMString)"],
+ ["long x, boolean y", "long, boolean"],
+ ["long x, optional boolean y", "long, optional boolean"],
+ ["long... args", "long..."],
+ ["sequence<long>... args", "sequence<long>..."],
+ ["(long or DOMString)... args", "(long or DOMString)..."],
+];
+for (const [input, output] of tests) {
+ test(function() {
+ var m = memberFrom(`undefined foo(${input})`);
+ assert_equals(m.toString(), `foo(${output})`);
+ }, `toString for ${input}`);
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/assert_implements.html b/testing/web-platform/tests/resources/test/tests/unit/assert_implements.html
new file mode 100644
index 0000000000..6e35f38502
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/assert_implements.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test/tests/unit/helpers.js"></script>
+<title>assert_implements unittests</title>
+<script>
+'use strict';
+
+test(() => {
+ // All values in JS that are not falsy are truthy, so we just check some
+ // common cases here.
+ assert_implements(true, 'true is a truthy value');
+ assert_implements(5, 'positive integeter is a truthy value');
+ assert_implements(-5, 'negative integeter is a truthy value');
+ assert_implements('foo', 'non-empty string is a truthy value');
+}, 'truthy values');
+
+test_failure(() => {
+ assert_implements(false);
+}, 'false is a falsy value');
+
+test_failure(() => {
+ assert_implements(0);
+}, '0 is a falsy value');
+
+test_failure(() => {
+ assert_implements('');
+}, 'empty string is a falsy value');
+
+test_failure(() => {
+ assert_implements(null);
+}, 'null is a falsy value');
+
+test_failure(() => {
+ assert_implements(undefined);
+}, 'undefined is a falsy value');
+
+test_failure(() => {
+ assert_implements(NaN);
+}, 'NaN is a falsy value');
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/assert_implements_optional.html b/testing/web-platform/tests/resources/test/tests/unit/assert_implements_optional.html
new file mode 100644
index 0000000000..4f23e203c5
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/assert_implements_optional.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test/tests/unit/helpers.js"></script>
+<title>assert_implements_optional unittests</title>
+<script>
+'use strict';
+
+test(() => {
+ // All values in JS that are not falsy are truthy, so we just check some
+ // common cases here.
+ assert_implements_optional(true, 'true is a truthy value');
+ assert_implements_optional(5, 'positive integeter is a truthy value');
+ assert_implements_optional(-5, 'negative integeter is a truthy value');
+ assert_implements_optional('foo', 'non-empty string is a truthy value');
+}, 'truthy values');
+
+test_failure(() => {
+ assert_implements_optional(false);
+}, 'false is a falsy value');
+
+test_failure(() => {
+ assert_implements_optional(0);
+}, '0 is a falsy value');
+
+test_failure(() => {
+ assert_implements_optional('');
+}, 'empty string is a falsy value');
+
+test_failure(() => {
+ assert_implements_optional(null);
+}, 'null is a falsy value');
+
+test_failure(() => {
+ assert_implements_optional(undefined);
+}, 'undefined is a falsy value');
+
+test_failure(() => {
+ assert_implements_optional(NaN);
+}, 'NaN is a falsy value');
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/assert_object_equals.html b/testing/web-platform/tests/resources/test/tests/unit/assert_object_equals.html
new file mode 100644
index 0000000000..313d77b977
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/assert_object_equals.html
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/test/tests/unit/helpers.js"></script>
+<title>Assertion functions</title>
+<script>
+'use strict';
+
+test(function() {
+ assert_object_equals({}, {});
+}, 'empty objects');
+
+test(function() {
+ var actual = {};
+ var expected = {};
+ actual.a = actual;
+ expected.a = actual;
+
+ assert_object_equals(actual, expected);
+}, 'tolerates cycles in actual value');
+
+test(function() {
+ var actual = {};
+ var expected = {};
+ actual.a = expected;
+ expected.a = expected;
+
+ assert_object_equals(actual, expected);
+}, 'tolerates cycles in expected value');
+
+test(function() {
+ assert_object_equals({2: 99, 0: 23, 1: 45}, [23, 45, 99]);
+}, 'recognizes equivalence of actual object and expected array');
+
+test(function() {
+ assert_object_equals([23, 45, 99], {2: 99, 0: 23, 1: 45});
+}, 'recognizes equivalence of actual array and expected object');
+
+test(function() {
+ var actual = {};
+ var expected = {};
+ Object.defineProperty(actual, 'a', { value: 1, enumerable: false });
+
+ assert_not_equals(actual.a, expected.a);
+ assert_object_equals(actual, expected);
+}, 'non-enumerable properties in actual value ignored');
+
+test(function() {
+ var actual = {};
+ var expected = {};
+ Object.defineProperty(expected, 'a', { value: 1, enumerable: false });
+
+ assert_not_equals(actual.a, expected.a);
+ assert_object_equals(actual, expected);
+}, 'non-enumerable properties in expected value ignored');
+
+test(function() {
+ assert_object_equals({c: 3, a: 1, b: 2}, {a: 1, b: 2, c: 3});
+}, 'equivalent objects - "flat" object');
+
+test(function() {
+ assert_object_equals(
+ {c: {e: 5, d: 4}, b: 2, a: 1},
+ {a: 1, b: 2, c: {d: 4, e: 5}}
+ );
+}, 'equivalent objects - nested object');
+
+test(function() {
+ assert_object_equals(
+ {c: [4, 5], b: 2, a: 1},
+ {a: 1, b: 2, c: [4, 5]}
+ );
+}, 'equivalent objects - nested array');
+
+test(function() {
+ assert_object_equals({a: NaN}, {a: NaN});
+}, 'equivalent objects - NaN value');
+
+test(function() {
+ assert_object_equals({a: -0}, {a: -0});
+}, 'equivalent objects - negative zero value');
+
+test_failure(function() {
+ assert_object_equals(undefined, {});
+}, 'invalid actual value: undefined');
+
+test_failure(function() {
+ assert_object_equals(null, {});
+}, 'invalid actual value: null');
+
+test_failure(function() {
+ assert_object_equals(34, {});
+}, 'invalid actual value: number');
+
+test_failure(function() {
+ assert_object_equals({c: 3, a: 1, b: 2}, {a: 1, b: 1, c: 3});
+}, 'unequal property value - "flat" object');
+
+test_failure(function() {
+ var actual = Object.create({a: undefined});
+ var expected = {};
+
+ assert_object_equals(actual, expected);
+}, 'non-own properties in actual value verified');
+
+test_failure(function() {
+ var actual = {};
+ var expected = Object.create({a: undefined});
+
+ assert_object_equals(actual, expected);
+}, 'non-own properties in expected value verified');
+
+test_failure(function() {
+ assert_object_equals(
+ {a: 1, b: 2, c: {d: 5, e: 5, f: 6}},
+ {a: 1, b: 2, c: {d: 5, e: 7, f: 6}}
+ );
+}, 'unequal property value - nested object');
+
+test_failure(function() {
+ assert_object_equals(
+ {a: 1, b: 2, c: [4, 5, 6]},
+ {a: 1, b: 2, c: [4, 7, 6]}
+ );
+}, 'unequal property value - nested array');
+
+test_failure(function() {
+ assert_object_equals({a: NaN}, {a: 0});
+}, 'equivalent objects - NaN actual value');
+
+test_failure(function() {
+ assert_object_equals({a: 0}, {a: NaN});
+}, 'equivalent objects - NaN expected value');
+
+test_failure(function() {
+ assert_object_equals({a: -0}, {a: 0});
+}, 'equivalent objects - negative zero actual value');
+
+test_failure(function() {
+ assert_object_equals({a: 0}, {a: -0});
+}, 'equivalent objects - negative zero expected value');
+
+test_failure(function() {
+ assert_object_equals({a: 1}, {});
+}, 'actual contains additional property');
+
+test_failure(function() {
+ assert_object_equals({}, {a: 1});
+}, 'expected contains additional property');
+</script>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/async-test-return-restrictions.html b/testing/web-platform/tests/resources/test/tests/unit/async-test-return-restrictions.html
new file mode 100644
index 0000000000..0fde2e2422
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/async-test-return-restrictions.html
@@ -0,0 +1,135 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <title>Restrictions on return value from `async_test`</title>
+</head>
+<body>
+<script>
+function makeTest(...bodies) {
+ const closeScript = '<' + '/script>';
+ let src = `
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Document title</title>
+<script src="/resources/testharness.js?${Math.random()}">${closeScript}
+</head>
+
+<body>
+<div id="log"></div>`;
+ bodies.forEach((body) => {
+ src += '<script>(' + body + ')();' + closeScript;
+ });
+
+ const iframe = document.createElement('iframe');
+
+ document.body.appendChild(iframe);
+ iframe.contentDocument.write(src);
+
+ return new Promise((resolve) => {
+ window.addEventListener('message', function onMessage(e) {
+ if (e.source !== iframe.contentWindow) {
+ return;
+ }
+ if (!e.data || e.data.type !=='complete') {
+ return;
+ }
+ window.removeEventListener('message', onMessage);
+ resolve(e.data);
+ });
+
+ iframe.contentDocument.close();
+ }).then(({ tests, status }) => {
+ const summary = {
+ harness: {
+ status: getEnumProp(status, status.status),
+ message: status.message
+ },
+ tests: {}
+ };
+
+ tests.forEach((test) => {
+ summary.tests[test.name] = getEnumProp(test, test.status);
+ });
+
+ return summary;
+ });
+}
+
+function getEnumProp(object, value) {
+ for (let property in object) {
+ if (!/^[A-Z]+$/.test(property)) {
+ continue;
+ }
+
+ if (object[property] === value) {
+ return property;
+ }
+ }
+}
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ async_test((t) => {t.done(); return undefined;}, 'before');
+ async_test((t) => {t.done(); return null;}, 'null');
+ async_test((t) => {t.done(); return undefined;}, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness.status, 'ERROR');
+ assert_equals(
+ harness.message,
+ 'Test named "null" passed a function to `async_test` that returned a value.'
+ );
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.null, 'PASS');
+ // This test did not get the chance to start.
+ assert_equals(tests.after, undefined);
+ });
+}, 'test returning `null`');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ async_test((t) => {t.done(); return undefined;}, 'before');
+ async_test((t) => {t.done(); return {};}, 'object');
+ async_test((t) => {t.done(); return undefined;}, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness.status, 'ERROR');
+ assert_equals(
+ harness.message,
+ 'Test named "object" passed a function to `async_test` that returned a value.'
+ );
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.object, 'PASS');
+ // This test did not get the chance to start.
+ assert_equals(tests.after, undefined);
+ });
+}, 'test returning an ordinary object');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ async_test((t) => {t.done(); return undefined;}, 'before');
+ async_test((t) => {t.done(); return Promise.resolve(5);}, 'thenable');
+ async_test((t) => {t.done(); return undefined;}, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness.status, 'ERROR');
+ assert_equals(
+ harness.message,
+ 'Test named "thenable" passed a function to `async_test` that returned a value. ' +
+ 'Consider using `promise_test` instead when using Promises or async/await.'
+ );
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.thenable, 'PASS');
+ // This test did not get a chance to start.
+ assert_equals(tests.after, undefined);
+ });
+}, 'test returning a thenable object');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/basic.html b/testing/web-platform/tests/resources/test/tests/unit/basic.html
new file mode 100644
index 0000000000..d52082f2e0
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/basic.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>idlharness basic</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/WebIDLParser.js"></script>
+<script src="/resources/idlharness.js"></script>
+<script>
+ "use strict";
+ test(function() {
+ assert_true("IdlArray" in window);
+ }, 'IdlArray constructor should be a global object');
+ test(function() {
+ assert_true(new IdlArray() instanceof IdlArray);
+ }, 'IdlArray constructor should be constructible');
+ test(function() {
+ assert_true("WebIDL2" in window);
+ }, 'WebIDL2 namespace should be a global object');
+ test(function() {
+ assert_equals(typeof WebIDL2.parse, "function");
+ }, 'WebIDL2 namespace should have a parse method');
+ test(function() {
+ try {
+ WebIDL2.parse("I'm a syntax error");
+ throw new Error("Web IDL didn't throw");
+ } catch (e) {
+ assert_equals(e.name, "WebIDLParseError");
+ }
+ }, 'WebIDL2 parse method should bail on incorrect WebIDL');
+ test(function() {
+ assert_equals(typeof WebIDL2.parse("interface Foo {};"), "object");
+ }, 'WebIDL2 parse method should produce an AST for correct WebIDL');
+ test(function () {
+ try {
+ let i = new IdlArray();
+ i.add_untested_idls(`interface C {};`);
+ i.assert_throws('Anything', i => i.test());
+ } catch (e) {
+ assert_true(e instanceof IdlHarnessError);
+ }
+ }, `assert_throws should throw if no IdlHarnessError thrown`);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/exceptional-cases-timeouts.html b/testing/web-platform/tests/resources/test/tests/unit/exceptional-cases-timeouts.html
new file mode 100644
index 0000000000..760ac7154f
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/exceptional-cases-timeouts.html
@@ -0,0 +1,120 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <title>Exceptional cases - timeouts</title>
+</head>
+<body>
+<p>
+ The tests in this file are executed in parallel to avoid exceeding the "long"
+ timeout duration.
+</p>
+<script>
+function makeTest(...bodies) {
+ const closeScript = '<' + '/script>';
+ let src = `
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Document title</title>
+<script src="/resources/testharness.js?${Math.random()}">${closeScript}
+</head>
+
+<body>
+<div id="log"></div>`;
+ bodies.forEach((body) => {
+ src += '<script>(' + body + ')();' + closeScript;
+ });
+
+ const iframe = document.createElement('iframe');
+
+ document.body.appendChild(iframe);
+ iframe.contentDocument.write(src);
+
+ return new Promise((resolve) => {
+ window.addEventListener('message', function onMessage(e) {
+ if (e.source !== iframe.contentWindow) {
+ return;
+ }
+ if (!e.data || e.data.type !=='complete') {
+ return;
+ }
+ window.removeEventListener('message', onMessage);
+ resolve(e.data);
+ });
+
+ iframe.contentDocument.close();
+ }).then(({ tests, status }) => {
+ const summary = {
+ harness: getEnumProp(status, status.status),
+ tests: {}
+ };
+
+ tests.forEach((test) => {
+ summary.tests[test.name] = getEnumProp(test, test.status);
+ });
+
+ return summary;
+ });
+}
+
+function getEnumProp(object, value) {
+ for (let property in object) {
+ if (!/^[A-Z]+$/.test(property)) {
+ continue;
+ }
+
+ if (object[property] === value) {
+ return property;
+ }
+ }
+}
+
+(() => {
+ window.asyncTestCleanupCount1 = 0;
+ const nestedTest = makeTest(
+ () => {
+ async_test((t) => {
+ t.add_cleanup(() => window.parent.asyncTestCleanupCount1 += 1);
+ setTimeout(() => {
+ throw new Error('this error is expected');
+ });
+ }, 'test');
+ }
+ );
+ promise_test(() => {
+ return nestedTest.then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.test, 'TIMEOUT');
+ assert_equals(window.asyncTestCleanupCount1, 1);
+ });
+ }, 'uncaught exception during async_test which times out');
+})();
+
+(() => {
+ window.promiseTestCleanupCount2 = 0;
+ const nestedTest = makeTest(
+ () => {
+ promise_test((t) => {
+ t.add_cleanup(() => window.parent.promiseTestCleanupCount2 += 1);
+ setTimeout(() => {
+ throw new Error('this error is expected');
+ });
+
+ return new Promise(() => {});
+ }, 'test');
+ }
+ );
+ promise_test(() => {
+ return nestedTest.then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.test, 'TIMEOUT');
+ assert_equals(window.promiseTestCleanupCount2, 1);
+ });
+ }, 'uncaught exception during promise_test which times out');
+})();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/exceptional-cases.html b/testing/web-platform/tests/resources/test/tests/unit/exceptional-cases.html
new file mode 100644
index 0000000000..4054d0311d
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/exceptional-cases.html
@@ -0,0 +1,392 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <title>Exceptional cases</title>
+</head>
+<body>
+<script>
+function makeTest(...bodies) {
+ const closeScript = '<' + '/script>';
+ let src = `
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Document title</title>
+<script src="/resources/testharness.js?${Math.random()}">${closeScript}
+</head>
+
+<body>
+<div id="log"></div>`;
+ bodies.forEach((body) => {
+ src += '<script>(' + body + ')();' + closeScript;
+ });
+
+ const iframe = document.createElement('iframe');
+
+ document.body.appendChild(iframe);
+ iframe.contentDocument.write(src);
+
+ return new Promise((resolve) => {
+ window.addEventListener('message', function onMessage(e) {
+ if (e.source !== iframe.contentWindow) {
+ return;
+ }
+ if (!e.data || e.data.type !=='complete') {
+ return;
+ }
+ window.removeEventListener('message', onMessage);
+ resolve(e.data);
+ });
+
+ iframe.contentDocument.close();
+ }).then(({ tests, status }) => {
+ const summary = {
+ harness: getEnumProp(status, status.status),
+ tests: {}
+ };
+
+ tests.forEach((test) => {
+ summary.tests[test.name] = getEnumProp(test, test.status);
+ });
+
+ return summary;
+ });
+}
+
+function getEnumProp(object, value) {
+ for (let property in object) {
+ if (!/^[A-Z]+$/.test(property)) {
+ continue;
+ }
+
+ if (object[property] === value) {
+ return property;
+ }
+ }
+}
+
+promise_test(() => {
+ return makeTest(
+ () => { done(); }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_array_equals(Object.keys(tests), []);
+ });
+}, 'completion signaled before testing begins');
+
+promise_test(() => {
+ return makeTest(
+ () => { assert_true(true); done(); }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_array_equals(Object.keys(tests), []);
+ });
+}, 'passing assertion before testing begins');
+
+promise_test(() => {
+ return makeTest(
+ () => { assert_false(true); }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_array_equals(Object.keys(tests), []);
+ });
+}, 'failing assertion before testing begins');
+
+promise_test(() => {
+ return makeTest(
+ () => { throw new Error('this error is expected'); }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_array_equals(Object.keys(tests), []);
+ });
+}, 'uncaught exception before testing begins');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ setup({ allow_uncaught_exception: true });
+ throw new Error('this error is expected');
+ },
+ () => {
+ test(function() {}, 'a');
+ test(function() {}, 'b');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests.a, 'PASS');
+ assert_equals(tests.b, 'PASS');
+ });
+}, 'uncaught exception with subsequent subtest');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ async_test((t) => {
+ setTimeout(() => {
+ setTimeout(() => t.done(), 0);
+ async_test((t) => { setTimeout(t.done.bind(t), 0); }, 'after');
+ throw new Error('this error is expected');
+ }, 0);
+ }, 'during');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.during, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'uncaught exception during async_test');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ promise_test(() => {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve();
+ promise_test(() => Promise.resolve(), 'after');
+ throw new Error('this error is expected');
+ }, 0);
+ });
+ }, 'during');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.during, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'uncaught exception during promise_test');
+
+promise_test(() => {
+ return makeTest(
+ () => { test(() => {}, 'before'); },
+ () => { throw new Error('this error is expected'); },
+ () => { test(() => {}, 'after'); }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'uncaught exception between tests');
+
+promise_test(() => {
+ return makeTest(
+ () => { promise_test(() => Promise.resolve(), 'before'); },
+ () => { throw new Error('this error is expected'); },
+ () => { promise_test(() => Promise.resolve(), 'after'); }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'uncaught exception between promise_tests');
+
+
+// This feature of testharness.js is only observable in browsers which
+// implement the `unhandledrejection` event.
+if ('onunhandledrejection' in window) {
+
+ promise_test(() => {
+ return makeTest(
+ () => { Promise.reject(new Error('this error is expected')); }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_array_equals(Object.keys(tests), []);
+ });
+ }, 'unhandled rejection before testing begins');
+
+ promise_test(() => {
+ return makeTest(
+ () => {
+ async_test((t) => {
+ Promise.reject(new Error('this error is expected'));
+
+ window.addEventListener('unhandledrejection', () => {
+ setTimeout(() => t.done(), 0);
+ async_test((t) => { setTimeout(t.done.bind(t), 0); }, 'after');
+ t.done();
+ });
+ }, 'during');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.during, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+ }, 'unhandled rejection during async_test');
+
+ promise_test(() => {
+ return makeTest(
+ () => {
+ promise_test(() => {
+ return new Promise((resolve) => {
+ Promise.reject(new Error('this error is expected'));
+
+ window.addEventListener('unhandledrejection', () => {
+ resolve();
+ promise_test(() => Promise.resolve(), 'after');
+ throw new Error('this error is expected');
+ }, 0);
+ });
+ }, 'during');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.during, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+ }, 'unhandled rejection during promise_test');
+
+ promise_test(() => {
+ return makeTest(
+ () => {
+ setup({ explicit_done: true });
+ test(() => {}, 'before');
+ Promise.reject(new Error('this error is expected'));
+ window.addEventListener('unhandledrejection', () => {
+ test(() => {}, 'after');
+ done();
+ });
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_true('after' in tests);
+ });
+ }, 'unhandled rejection between tests');
+
+ promise_test(() => {
+ return makeTest(
+ () => {
+ setup({ explicit_done: true });
+ async_test((t) => { setTimeout(t.done.bind(t), 0); }, 'before');
+ Promise.reject(new Error('this error is expected'));
+ window.addEventListener('unhandledrejection', () => {
+ async_test((t) => { setTimeout(t.done.bind(t), 0); }, 'after');
+ done();
+ });
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+ }, 'unhandled rejection between async_tests');
+
+ promise_test(() => {
+ return makeTest(
+ () => {
+ setup({ explicit_done: true });
+ promise_test(() => Promise.resolve(), 'before');
+ Promise.reject(new Error('this error is expected'));
+ window.addEventListener('unhandledrejection', () => {
+ promise_test(() => Promise.resolve(), 'after');
+ done();
+ });
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_true('after' in tests);
+ });
+ }, 'unhandled rejection between promise_tests');
+
+ promise_test(() => {
+ return makeTest(
+ () => {
+ test((t) => {
+ t.add_cleanup(() => { throw new Error('this error is expected'); });
+ }, 'during');
+ test((t) => {}, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.during, 'PASS');
+ assert_equals(tests.after, 'NOTRUN');
+ });
+ }, 'exception in `add_cleanup` of a test');
+
+}
+
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ setup({explicit_done: true});
+ window.addEventListener('DOMContentLoaded', () => {
+ async_test((t) => {
+ t.add_cleanup(() => {
+ setTimeout(() => {
+ async_test((t) => t.done(), 'after');
+ done();
+ }, 0);
+ throw new Error('this error is expected');
+ });
+ setTimeout(t.done.bind(t), 0);
+ }, 'during');
+ });
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.during, 'PASS');
+ assert_equals(tests.after, 'NOTRUN');
+ });
+}, 'exception in `add_cleanup` of an async_test');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ promise_test((t) => {
+ t.add_cleanup(() => { throw new Error('this error is expected'); });
+ return Promise.resolve();
+ }, 'test');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.test, 'PASS');
+ });
+}, 'exception in `add_cleanup` of a promise_test');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ promise_test((t) => {
+ t.step(() => {
+ throw new Error('this error is expected');
+ });
+ }, 'test');
+ async_test((t) => t.done(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests.test, 'FAIL');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'exception in `step` of an async_test');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ promise_test((t) => {
+ t.step(() => {
+ throw new Error('this error is expected');
+ });
+
+ return new Promise(() => {});
+ }, 'test');
+
+ // This following test should be run to completion despite the fact
+ // that the promise returned by the previous test never resolves.
+ promise_test((t) => Promise.resolve(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests.test, 'FAIL');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'exception in `step` of a promise_test');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/format-value.html b/testing/web-platform/tests/resources/test/tests/unit/format-value.html
new file mode 100644
index 0000000000..13d01b81f3
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/format-value.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>format_value utility function</title>
+ <meta charset="utf-8">
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+"use strict";
+
+test(function() {
+ assert_equals(format_value(null), "null");
+}, "null");
+
+test(function() {
+ assert_equals(format_value(undefined), "undefined");
+}, "undefined");
+
+test(function() {
+ assert_equals(format_value(true), "true");
+ assert_equals(format_value(false), "false");
+}, "boolean values");
+
+test(function() {
+ assert_equals(format_value(0.4), "0.4");
+ assert_equals(format_value(0), "0");
+ assert_equals(format_value(-0), "-0");
+}, "number values");
+
+test(function() {
+ assert_equals(format_value("a string"), "\"a string\"");
+ assert_equals(format_value("new\nline"), "\"new\\nline\"");
+}, "string values");
+
+test(function() {
+ var node = document.createElement("span");
+ node.setAttribute("data-foo", "bar");
+ assert_true(
+ /<span\b/i.test(format_value(node)), "element includes tag name"
+ );
+ assert_true(
+ /data-foo=["']?bar["']?/i.test(format_value(node)),
+ "element includes attributes"
+ );
+}, "node value: element node");
+
+test(function() {
+ var text = document.createTextNode("wpt");
+ assert_equals(format_value(text), "Text node \"wpt\"");
+}, "node value: text node");
+
+test(function() {
+ var node = document.createProcessingInstruction("wpt1", "wpt2");
+ assert_equals(
+ format_value(node),
+ "ProcessingInstruction node with target \"wpt1\" and data \"wpt2\""
+ );
+}, "node value: ProcessingInstruction node");
+
+test(function() {
+ var node = document.createComment("wpt");
+ assert_equals(format_value(node), "Comment node <!--wpt-->");
+}, "node value: comment node");
+
+test(function() {
+ var node = document.implementation.createDocument(
+ "application/xhtml+xml", "", null
+ );
+
+ assert_equals(format_value(node), "Document node with 0 children");
+
+ node.appendChild(document.createElement('html'));
+
+ assert_equals(format_value(node), "Document node with 1 child");
+}, "node value: document node");
+
+test(function() {
+ var node = document.implementation.createDocumentType("foo", "baz", "baz");
+
+ assert_equals(format_value(node), "DocumentType node");
+}, "node value: DocumentType node");
+
+test(function() {
+ var node = document.createDocumentFragment();
+
+ assert_equals(format_value(node), "DocumentFragment node with 0 children");
+
+ node.appendChild(document.createElement("span"));
+
+ assert_equals(format_value(node), "DocumentFragment node with 1 child");
+
+ node.appendChild(document.createElement("span"));
+
+ assert_equals(format_value(node), "DocumentFragment node with 2 children");
+}, "node value: DocumentFragment node");
+
+test(function() {
+ assert_equals(format_value(Symbol("wpt")), "symbol \"Symbol(wpt)\"");
+}, "symbol value");
+
+test(function() {
+ assert_equals(format_value([]), "[]");
+ assert_equals(format_value(["one"]), "[\"one\"]");
+ assert_equals(format_value(["one", "two"]), "[\"one\", \"two\"]");
+}, "array values");
+
+test(function() {
+ var obj = {
+ toString: function() {
+ throw "wpt";
+ }
+ };
+
+ assert_equals(
+ format_value(obj), "[stringifying object threw wpt with type string]"
+ );
+}, "object value with faulty `toString`");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/helpers.js b/testing/web-platform/tests/resources/test/tests/unit/helpers.js
new file mode 100644
index 0000000000..ca378a27c9
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/helpers.js
@@ -0,0 +1,21 @@
+// Helper for testing assertion failure cases for a testharness.js API
+//
+// The `assert_throws_*` functions cannot be used for this purpose because they
+// always fail in response to AssertionError exceptions, even when this is
+// expressed as the expected error.
+function test_failure(fn, name) {
+ test(function() {
+ try {
+ fn();
+ } catch (err) {
+ if (err instanceof AssertionError) {
+ return;
+ }
+ throw new AssertionError('Expected an AssertionError, but' + err);
+ }
+ throw new AssertionError(
+ 'Expected an AssertionError, but no error was thrown'
+ );
+ }, name);
+}
+
diff --git a/testing/web-platform/tests/resources/test/tests/unit/late-test.html b/testing/web-platform/tests/resources/test/tests/unit/late-test.html
new file mode 100644
index 0000000000..c9f8ec61fe
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/late-test.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test declared after harness completion</title>
+</head>
+<body>
+<div id="log"></div>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<p>This test simulates an automated test running scenario, where the test
+results emitted by testharness.js may be interpreted after some delay. It is
+intended to demonstrate that in such cases, any additional tests which are
+executed during that delay are included in the dataset.</p>
+
+<p>Although these "late tests" are likely an indication of a mistake in test
+design, they are also recorded. Previously, "late tests" were ignored.
+This test changed to assert "late tests" were no longer ignored after
+https://github.com/web-platform-tests/wpt/pull/38806 was introduced.</p>
+<script>
+async_test(function(t) {
+ var source = [
+ "<div id='log'></div>",
+ "<script src='/resources/testharness.js'></" + "script>",
+ "<script src='/resources/testharnessreport.js'></" + "script>",
+ "<script>",
+ "parent.childReady(window);",
+ "setup({ explicit_done: true });",
+ "test(function() {}, 'acceptable test');",
+ "onload = function() {",
+ " done();",
+ " test(function() {}, 'test registered in onload handler');",
+ "};",
+ "</" + "script>"
+ ].join("\n");
+ var iframe = document.createElement("iframe");
+
+ document.body.appendChild(iframe);
+ window.childReady = t.step_func(function(childWindow) {
+ childWindow.add_completion_callback(t.step_func(function(tests, status) {
+ t.step_timeout(t.step_func(function() {
+ assert_equals(tests.length, 2);
+ assert_equals(tests[0].name, "acceptable test");
+ assert_equals(tests[1].name, "test registered in onload handler");
+ assert_equals(status.status, status.OK);
+ t.done();
+ }), 0);
+ }));
+ });
+
+ iframe.contentDocument.open();
+ iframe.contentDocument.write(source);
+ iframe.contentDocument.close();
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/promise_setup-timeout.html b/testing/web-platform/tests/resources/test/tests/unit/promise_setup-timeout.html
new file mode 100644
index 0000000000..c4947feef4
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/promise_setup-timeout.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="../../nested-testharness.js"></script>
+ <title>promise_setup - timeout</title>
+</head>
+<body>
+<script>
+'use strict';
+promise_test(() => {
+ return makeTest(
+ () => {
+ test(() => {}, 'before');
+ promise_setup(() => new Promise(() => {}));
+ promise_test(() => Promise.resolve(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'TIMEOUT');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'NOTRUN');
+ });
+}, 'timeout when returned promise does not settle');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/promise_setup.html b/testing/web-platform/tests/resources/test/tests/unit/promise_setup.html
new file mode 100644
index 0000000000..2abb10a476
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/promise_setup.html
@@ -0,0 +1,333 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <script src="../../nested-testharness.js"></script>
+ <title>promise_setup</title>
+</head>
+<body>
+<script>
+'use strict';
+promise_test(() => {
+ return makeTest(
+ () => {
+ // Ensure that the harness error is the result of explicit error
+ // handling
+ setup({ allow_uncaught_exception: true });
+
+ test(() => {}, 'before');
+ promise_setup({});
+ promise_test(() => Promise.resolve(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, undefined);
+ });
+}, 'Error when no function provided');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ test(() => {}, 'before');
+ promise_setup(() => Promise.resolve(), {});
+ promise_test(() => Promise.resolve(), 'after');
+ throw new Error('this error is expected');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'Does not apply unspecified configuration properties');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ var properties = {
+ allow_uncaught_exception: true
+ };
+ test(() => {}, 'before');
+ promise_setup(() => Promise.resolve(), properties);
+ promise_test(() => Promise.resolve(), 'after');
+ throw new Error('this error is expected');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'Ignores configuration properties when some tests have already run');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ var properties = {
+ allow_uncaught_exception: true
+ };
+ promise_setup(() => Promise.resolve(), properties);
+ promise_test(() => {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve();
+ throw new Error('this error is expected');
+ });
+ });
+ }, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'Honors configuration properties');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ // Ensure that the harness error is the result of explicit error
+ // handling
+ setup({ allow_uncaught_exception: true });
+
+ test(() => {}, 'before');
+ promise_setup(() => { throw new Error('this error is expected'); });
+ promise_test(() => Promise.resolve(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'NOTRUN');
+ });
+}, 'Error for synchronous exceptions');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ // Ensure that the harness error is the result of explicit error
+ // handling
+ setup({ allow_uncaught_exception: true });
+
+ test(() => {}, 'before');
+ promise_setup(() => undefined);
+ promise_test(() => Promise.resolve(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'NOTRUN');
+ });
+}, 'Error for missing return value');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ // Ensure that the harness error is the result of explicit error
+ // handling
+ setup({ allow_uncaught_exception: true });
+
+ test(() => {}, 'before');
+ var noThen = Promise.resolve();
+ noThen.then = undefined;
+ promise_setup(() => noThen);
+ promise_test(() => Promise.resolve(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'NOTRUN');
+ });
+}, 'Error for non-thenable return value');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ // Ensure that the harness error is the result of explicit error
+ // handling
+ setup({ allow_uncaught_exception: true });
+
+ test(() => {}, 'before');
+ var poisonedThen = {
+ get then() {
+ throw new Error('this error is expected');
+ }
+ };
+ promise_setup(() => poisonedThen);
+ promise_test(() => Promise.resolve(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'NOTRUN');
+ });
+}, 'Error for "poisoned" `then` property');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ // Ensure that the harness error is the result of explicit error
+ // handling
+ setup({ allow_uncaught_exception: true });
+
+ test(() => {}, 'before');
+ var badThen = {
+ then() {
+ throw new Error('this error is expected');
+ }
+ };
+ promise_setup(() => badThen);
+ promise_test(() => Promise.resolve(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'NOTRUN');
+ });
+}, 'Error for synchronous error from `then` method');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ // Ensure that the harness error is the result of explicit error
+ // handling
+ setup({ allow_uncaught_exception: true });
+
+ test(() => {}, 'before');
+ promise_setup(() => Promise.resolve());
+ test(() => {}, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, undefined);
+ });
+}, 'Error for subsequent invocation of `test`');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ // Ensure that the harness error is the result of explicit error
+ // handling
+ setup({ allow_uncaught_exception: true });
+
+ test(() => {}, 'before');
+ promise_setup(() => Promise.resolve());
+ async_test((t) => t.done(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, undefined);
+ });
+}, 'Error for subsequent invocation of `async_test`');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ // Ensure that the harness error is the result of explicit error
+ // handling
+ setup({ allow_uncaught_exception: true });
+
+ test(() => {}, 'before');
+ promise_setup(() => Promise.reject());
+ promise_test(() => Promise.resolve(), 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'NOTRUN');
+ });
+}, 'Error for rejected promise');
+
+promise_test(() => {
+ var expected_sequence = [
+ 'test body',
+ 'promise_setup begin',
+ 'promise_setup end',
+ 'promise_test body'
+ ];
+ var actual_sequence = window.actual_sequence = [];
+
+ return makeTest(
+ () => {
+ test(() => { parent.actual_sequence.push('test body'); }, 'before');
+ promise_setup(() => {
+ parent.actual_sequence.push('promise_setup begin');
+
+ return Promise.resolve()
+ .then(() => new Promise((resolve) => setTimeout(resolve, 300)))
+ .then(() => parent.actual_sequence.push('promise_setup end'));
+ });
+ promise_test(() => {
+ parent.actual_sequence.push('promise_test body');
+ return Promise.resolve();
+ }, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ assert_array_equals(actual_sequence, expected_sequence);
+ });
+}, 'Waits for promise to settle');
+
+promise_test(() => {
+ var expected_sequence = [
+ 'promise_test 1 begin',
+ 'promise_test 1 end',
+ 'promise_setup begin',
+ 'promise_setup end',
+ 'promise_test 2 body'
+ ];
+ var actual_sequence = window.actual_sequence = [];
+
+ return makeTest(
+ () => {
+ promise_test((t) => {
+ parent.actual_sequence.push('promise_test 1 begin');
+
+ return Promise.resolve()
+ .then(() => new Promise((resolve) => t.step_timeout(resolve, 300)))
+ .then(() => parent.actual_sequence.push('promise_test 1 end'));
+ }, 'before');
+ promise_setup(() => {
+ parent.actual_sequence.push('promise_setup begin');
+
+ return Promise.resolve()
+ .then(() => new Promise((resolve) => setTimeout(resolve, 300)))
+ .then(() => parent.actual_sequence.push('promise_setup end'));
+ });
+ promise_test(() => {
+ parent.actual_sequence.push('promise_test 2 body');
+ return Promise.resolve();
+ }, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ assert_array_equals(actual_sequence, expected_sequence);
+ });
+}, 'Waits for existing promise_test to complete');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ var properties = { allow_uncaught_exception: true };
+ promise_test(() => {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve();
+ throw new Error('this error is expected');
+ });
+ });
+ }, 'before');
+ promise_setup(() => Promise.resolve(), properties);
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'ERROR');
+ assert_equals(tests.before, 'PASS');
+ });
+}, 'Defers application of setup properties');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/single_test.html b/testing/web-platform/tests/resources/test/tests/unit/single_test.html
new file mode 100644
index 0000000000..ff766e66ce
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/single_test.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="../../nested-testharness.js"></script>
+ <title>single_test</title>
+</head>
+<body>
+<script>
+promise_test(() => {
+ return makeTest(
+ () => {
+ setup({ single_test: true });
+ done();
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'PASS');
+ });
+}, 'Expected usage');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ setup({ single_test: true });
+ throw new Error('this error is expected');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ });
+}, 'Uncaught exception');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ setup({ single_test: true });
+ Promise.reject(new Error('this error is expected'));
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ });
+}, 'Unhandled rejection');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ setup({ single_test: true });
+ test(function() {}, 'sync test');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ assert_equals(
+ Object.keys(tests).length, 1, 'no additional subtests created'
+ );
+ });
+}, 'Erroneous usage: subtest declaration (synchronous test)');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ setup({ single_test: true });
+ async_test(function(t) { t.done(); }, 'async test');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ assert_equals(
+ Object.keys(tests).length, 1, 'no additional subtests created'
+ );
+ });
+}, 'Erroneous usage: subtest declaration (asynchronous test)');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ setup({ single_test: true });
+ promise_test(function() { return Promise.resolve(); }, 'promise test');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ assert_equals(
+ Object.keys(tests).length, 1, 'no additional subtests created'
+ );
+ });
+}, 'Erroneous usage: subtest declaration (promise test)');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/test-return-restrictions.html b/testing/web-platform/tests/resources/test/tests/unit/test-return-restrictions.html
new file mode 100644
index 0000000000..0295c5214d
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/test-return-restrictions.html
@@ -0,0 +1,156 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <title>Restrictions on return value from `test`</title>
+</head>
+<body>
+<script>
+function makeTest(...bodies) {
+ const closeScript = '<' + '/script>';
+ let src = `
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Document title</title>
+<script src="/resources/testharness.js?${Math.random()}">${closeScript}
+</head>
+
+<body>
+<div id="log"></div>`;
+ bodies.forEach((body) => {
+ src += '<script>(' + body + ')();' + closeScript;
+ });
+
+ const iframe = document.createElement('iframe');
+
+ document.body.appendChild(iframe);
+ iframe.contentDocument.write(src);
+
+ return new Promise((resolve) => {
+ window.addEventListener('message', function onMessage(e) {
+ if (e.source !== iframe.contentWindow) {
+ return;
+ }
+ if (!e.data || e.data.type !=='complete') {
+ return;
+ }
+ window.removeEventListener('message', onMessage);
+ resolve(e.data);
+ });
+
+ iframe.contentDocument.close();
+ }).then(({ tests, status }) => {
+ const summary = {
+ harness: {
+ status: getEnumProp(status, status.status),
+ message: status.message
+ },
+ tests: {}
+ };
+
+ tests.forEach((test) => {
+ summary.tests[test.name] = getEnumProp(test, test.status);
+ });
+
+ return summary;
+ });
+}
+
+function getEnumProp(object, value) {
+ for (let property in object) {
+ if (!/^[A-Z]+$/.test(property)) {
+ continue;
+ }
+
+ if (object[property] === value) {
+ return property;
+ }
+ }
+}
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ test(() => undefined, 'before');
+ test(() => null, 'null');
+ test(() => undefined, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness.status, 'ERROR');
+ assert_equals(
+ harness.message,
+ 'Test named "null" passed a function to `test` that returned a value.'
+ );
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.null, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'test returning `null`');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ test(() => undefined, 'before');
+ test(() => ({}), 'object');
+ test(() => undefined, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness.status, 'ERROR');
+ assert_equals(
+ harness.message,
+ 'Test named "object" passed a function to `test` that returned a value.'
+ );
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.object, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'test returning an ordinary object');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ test(() => undefined, 'before');
+ test(() => Promise.resolve(5), 'thenable');
+ test(() => undefined, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness.status, 'ERROR');
+ assert_equals(
+ harness.message,
+ 'Test named "thenable" passed a function to `test` that returned a value. ' +
+ 'Consider using `promise_test` instead when using Promises or async/await.'
+ );
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.thenable, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'test returning a thenable object');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ test(() => undefined, 'before');
+ test(() => {
+ const iframe = document.createElement('iframe');
+ iframe.setAttribute('sandbox', '');
+ document.body.appendChild(iframe);
+ return iframe.contentWindow;
+ }, 'restricted');
+ test(() => undefined, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness.status, 'ERROR');
+ assert_equals(
+ harness.message,
+ 'Test named "restricted" passed a function to `test` that returned a value.'
+ );
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests.restricted, 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'test returning a restricted object');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/throwing-assertions.html b/testing/web-platform/tests/resources/test/tests/unit/throwing-assertions.html
new file mode 100644
index 0000000000..a36a56043c
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/throwing-assertions.html
@@ -0,0 +1,268 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <title>Test the methods that make assertions about exceptions</title>
+</head>
+<body>
+<script>
+function makeTest(...bodies) {
+ const closeScript = '<' + '/script>';
+ let src = `
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Document title</title>
+<script src="/resources/testharness.js?${Math.random()}">${closeScript}
+</head>
+
+<body>
+<div id="log"></div>`;
+ bodies.forEach((body) => {
+ src += '<script>(' + body + ')();' + closeScript;
+ });
+
+ const iframe = document.createElement('iframe');
+
+ document.body.appendChild(iframe);
+ iframe.contentDocument.write(src);
+
+ return new Promise((resolve) => {
+ window.addEventListener('message', function onMessage(e) {
+ if (e.source !== iframe.contentWindow) {
+ return;
+ }
+ if (!e.data || e.data.type !=='complete') {
+ return;
+ }
+ window.removeEventListener('message', onMessage);
+ resolve(e.data);
+ });
+
+ iframe.contentDocument.close();
+ }).then(({ tests, status }) => {
+ const summary = {
+ harness: getEnumProp(status, status.status),
+ tests: {}
+ };
+
+ tests.forEach((test) => {
+ summary.tests[test.name] = getEnumProp(test, test.status);
+ });
+
+ return summary;
+ });
+}
+
+function getEnumProp(object, value) {
+ for (let property in object) {
+ if (!/^[A-Z]+$/.test(property)) {
+ continue;
+ }
+
+ if (object[property] === value) {
+ return property;
+ }
+ }
+}
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_js(TypeError, () => { throw new TypeError(); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'PASS');
+ });
+}, 'assert_throws_js on a TypeError');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_js(RangeError, () => { throw new RangeError(); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'PASS');
+ });
+}, 'assert_throws_js on a RangeError');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_js(TypeError, () => { throw new RangeError(); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ });
+}, 'assert_throws_js on a TypeError when RangeError is thrown');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_js(Error, () => { throw new TypeError(); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ });
+}, 'assert_throws_js on an Error when TypeError is thrown');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_js(Error,
+ () => { throw new DOMException("hello", "Error"); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ });
+}, 'assert_throws_js on an Error when a DOMException is thrown');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_js(SyntaxError,
+ () => { throw new DOMException("hey", "SyntaxError"); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ });
+}, 'assert_throws_js on a SyntaxError when a DOMException is thrown');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_dom("SyntaxError",
+ () => { throw new DOMException("x", "SyntaxError"); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'PASS');
+ });
+}, 'assert_throws_dom basic sanity');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_dom(12,
+ () => { throw new DOMException("x", "SyntaxError"); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'PASS');
+ });
+}, 'assert_throws_dom with numeric code');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_dom("SYNTAX_ERR",
+ () => { throw new DOMException("x", "SyntaxError"); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'PASS');
+ });
+}, 'assert_throws_dom with string name for code');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_dom("DataError",
+ () => { throw new DOMException("x", "DataError"); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'PASS');
+ });
+}, 'assert_throws_dom for a code-less DOMException type');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_dom("NoSuchError",
+ () => { throw new DOMException("x", "NoSuchError"); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ });
+}, 'assert_throws_dom for a nonexistent DOMException type');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_dom("SyntaxError", () => { throw new SyntaxError(); });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ });
+}, 'assert_throws_dom when a non-DOM exception is thrown');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_exactly(5, () => { throw 5; });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'PASS');
+ });
+}, 'assert_throws_exactly with number');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_exactly("foo", () => { throw "foo"; });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'PASS');
+ });
+}, 'assert_throws_exactly with string');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_exactly({}, () => { throw {}; });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ });
+}, 'assert_throws_exactly with different objects');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ var obj = {};
+ assert_throws_exactly(obj, () => { throw obj; });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'PASS');
+ });
+}, 'assert_throws_exactly with same object');
+
+promise_test(() => {
+ return makeTest(() => {
+ test(() => {
+ assert_throws_exactly(TypeError, () => { throw new TypeError; });
+ });
+ }).then(({harness, tests}) => {
+ assert_equals(harness, 'OK');
+ assert_equals(tests['Document title'], 'FAIL');
+ });
+}, 'assert_throws_exactly with bogus TypeError bits ');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tests/unit/unpaired-surrogates.html b/testing/web-platform/tests/resources/test/tests/unit/unpaired-surrogates.html
new file mode 100644
index 0000000000..b232111326
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tests/unit/unpaired-surrogates.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="/resources/testharness.js"></script>
+ <title>Restrictions on return value from `test`</title>
+</head>
+<body>
+<script>
+function makeTest(...bodies) {
+ const closeScript = '<' + '/script>';
+ let src = `
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Document title</title>
+<script src="/resources/testharness.js?${Math.random()}">${closeScript}
+</head>
+
+<body>
+<div id="log"></div>`;
+ bodies.forEach((body) => {
+ src += '<script>(' + body + ')();' + closeScript;
+ });
+
+ const iframe = document.createElement('iframe');
+
+ document.body.appendChild(iframe);
+ iframe.contentDocument.write(src);
+
+ return new Promise((resolve) => {
+ window.addEventListener('message', function onMessage(e) {
+ if (e.source !== iframe.contentWindow) {
+ return;
+ }
+ if (!e.data || e.data.type !=='complete') {
+ return;
+ }
+ window.removeEventListener('message', onMessage);
+ resolve(e.data);
+ });
+
+ iframe.contentDocument.close();
+ }).then(({ tests, status }) => {
+ const summary = {
+ harness: {
+ status: getEnumProp(status, status.status),
+ message: status.message
+ },
+ tests: {}
+ };
+
+ tests.forEach((test) => {
+ summary.tests[test.name] = getEnumProp(test, test.status);
+ });
+
+ return summary;
+ });
+}
+
+function getEnumProp(object, value) {
+ for (let property in object) {
+ if (!/^[A-Z]+$/.test(property)) {
+ continue;
+ }
+
+ if (object[property] === value) {
+ return property;
+ }
+ }
+}
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ test(() => {}, 'before');
+ test(() => {}, 'U+d7ff is not modified: \ud7ff');
+ test(() => {}, 'U+e000 is not modified: \ue000');
+ test(() => {}, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness.status, 'OK');
+ assert_equals(harness.message, null);
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests['U+d7ff is not modified: \ud7ff'], 'PASS');
+ assert_equals(tests['U+e000 is not modified: \ue000'], 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'sub-test names which include valid code units');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ test(() => {}, 'before');
+ test(() => {}, 'U+d800U+dfff is not modified: \ud800\udfff');
+ test(() => {}, 'U+dbffU+dc00 is not modified: \udbff\udc00');
+ test(() => {}, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness.status, 'OK');
+ assert_equals(harness.message, null);
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests['U+d800U+dfff is not modified: \ud800\udfff'], 'PASS');
+ assert_equals(tests['U+dbffU+dc00 is not modified: \udbff\udc00'], 'PASS');
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'sub-test names which include paired surrogates');
+
+promise_test(() => {
+ return makeTest(
+ () => {
+ test(() => {}, 'before');
+ test(() => {}, 'U+d800 must be sanitized: \ud800');
+ test(() => {}, 'U+d800U+d801 must be sanitized: \ud800\ud801');
+ test(() => {}, 'U+dfff must be sanitized: \udfff');
+ test(() => {}, 'U+dc00U+d800U+dc00U+d800 must be sanitized: \udc00\ud800\udc00\ud800');
+ test(() => {}, 'U+dbffU+dfffU+dfff must be sanitized: \udbff\udfff\udfff');
+ test(() => {}, 'after');
+ }
+ ).then(({harness, tests}) => {
+ assert_equals(harness.status, 'OK');
+ assert_equals(harness.message, null);
+ assert_equals(tests.before, 'PASS');
+ assert_equals(tests['U+d800 must be sanitized: U+d800'], 'PASS');
+ assert_equals(tests['U+dfff must be sanitized: U+dfff'], 'PASS');
+ assert_equals(
+ tests['U+d800U+d801 must be sanitized: U+d800U+d801'],
+ 'PASS'
+ );
+ assert_equals(
+ tests['U+dc00U+d800U+dc00U+d800 must be sanitized: U+dc00\ud800\udc00U+d800'],
+ 'PASS'
+ );
+ assert_equals(
+ tests['U+dbffU+dfffU+dfff must be sanitized: \udbff\udfffU+dfff'],
+ 'PASS'
+ );
+ assert_equals(tests.after, 'PASS');
+ });
+}, 'sub-test names which include unpaired surrogates');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/resources/test/tox.ini b/testing/web-platform/tests/resources/test/tox.ini
new file mode 100644
index 0000000000..12013a1a70
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/tox.ini
@@ -0,0 +1,13 @@
+[tox]
+envlist = py37,py38,py39,py310,py311
+skipsdist=True
+
+[testenv]
+passenv=DISPLAY # Necessary for the spawned GeckoDriver process to connect to
+ # the appropriate display.
+
+deps =
+ -r{toxinidir}/../../tools/requirements_pytest.txt
+ -r{toxinidir}/requirements.txt
+
+commands = pytest -vv {posargs}
diff --git a/testing/web-platform/tests/resources/test/wptserver.py b/testing/web-platform/tests/resources/test/wptserver.py
new file mode 100644
index 0000000000..1f913dd96d
--- /dev/null
+++ b/testing/web-platform/tests/resources/test/wptserver.py
@@ -0,0 +1,58 @@
+import logging
+import os
+import subprocess
+import time
+import sys
+import urllib
+
+
+class WPTServer(object):
+ def __init__(self, wpt_root):
+ self.logger = logging.getLogger()
+ self.wpt_root = wpt_root
+
+ # This is a terrible hack to get the default config of wptserve.
+ sys.path.insert(0, os.path.join(wpt_root, "tools"))
+ from serve.serve import build_config
+ with build_config(self.logger) as config:
+ self.host = config["browser_host"]
+ self.http_port = config["ports"]["http"][0]
+ self.https_port = config["ports"]["https"][0]
+
+ self.base_url = 'http://%s:%s' % (self.host, self.http_port)
+ self.https_base_url = 'https://%s:%s' % (self.host, self.https_port)
+
+ def start(self, ssl_context):
+ self.devnull = open(os.devnull, 'w')
+ wptserve_cmd = [os.path.join(self.wpt_root, 'wpt'), 'serve']
+ if sys.executable:
+ wptserve_cmd[0:0] = [sys.executable]
+ self.logger.info('Executing %s' % ' '.join(wptserve_cmd))
+ self.proc = subprocess.Popen(
+ wptserve_cmd,
+ stderr=self.devnull,
+ cwd=self.wpt_root)
+
+ for retry in range(5):
+ # Exponential backoff.
+ time.sleep(2 ** retry)
+ exit_code = self.proc.poll()
+ if exit_code != None:
+ logging.warning('Command "%s" exited with %s', ' '.join(wptserve_cmd), exit_code)
+ break
+ try:
+ urllib.request.urlopen(self.base_url, timeout=1)
+ urllib.request.urlopen(self.https_base_url, timeout=1, context=ssl_context)
+ return
+ except urllib.error.URLError:
+ pass
+
+ raise Exception('Could not start wptserve on %s' % self.base_url)
+
+ def stop(self):
+ self.proc.terminate()
+ self.proc.wait()
+ self.devnull.close()
+
+ def url(self, abs_path):
+ return self.https_base_url + '/' + os.path.relpath(abs_path, self.wpt_root)