summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/infrastructure
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/html/infrastructure')
-rw-r--r--testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/domstringlist.html61
-rw-r--r--testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/historical.html33
-rw-r--r--testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmlallcollection.html334
-rw-r--r--testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmlformcontrolscollection.html121
-rw-r--r--testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmloptionscollection.html216
-rw-r--r--testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/radionodelist.html78
-rw-r--r--testing/web-platform/tests/html/infrastructure/common-microsyntaxes/colours/parsing-legacy-colour-value-ascii-case-insensitive-ref.html6
-rw-r--r--testing/web-platform/tests/html/infrastructure/common-microsyntaxes/colours/parsing-legacy-colour-value-ascii-case-insensitive.html20
-rw-r--r--testing/web-platform/tests/html/infrastructure/conformance-requirements/extensibility/foreign.html45
-rw-r--r--testing/web-platform/tests/html/infrastructure/fetching-resources/crossorigin-enumerated-ascii-case-insensitive.html34
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html28
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js16
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html11
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js5
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js.headers1
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html11
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/post-parent-type-error.html9
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html132
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success-and-failure.https.html35
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success-and-failure.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.https.html55
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/identity-not-preserved.https.html68
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/identity-not-preserved.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-sharedworker-success.https.html8
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-sharedworker-success.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-worker-success.https.html8
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-worker-success.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-coop-coep.https.any.js33
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-transferring.https.html31
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-transferring.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/blank.html2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/blank.html.headers1
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-iframe.html22
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-iframe.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-sharedworker.js7
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-sharedworker.js.headers1
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-worker.js9
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-worker.js.headers1
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-domain-failure.sub.html14
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-domain-failure.sub.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-failure.html3
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-failure.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-complex.html6
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-complex.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-failure.html7
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-failure.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-site-failure.html7
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-site-failure.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe-messagechannel.html10
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe-messagechannel.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe.html10
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-popup.html10
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-popup.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker-with-channel.js7
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker-with-channel.js.headers1
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker.js4
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker.js.headers1
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-1.html5
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-1.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-2.html5
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-2.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-3.html5
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-3.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-4-incrementer.html10
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-4-incrementer.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-worker-success.js14
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-worker-success.js.headers1
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/serviceworker-failure.js33
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/serviceworker-failure.js.headers1
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/sharedworker-failure.js18
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/sharedworker-failure.js.headers1
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/test-incrementer.js80
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/test-sab.js15
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-history.https.html36
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-history.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-idb.any.js44
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-notifications-api.any.js28
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-domain-failure.https.sub.html49
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-domain-failure.https.sub.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-failure.https.html15
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-failure.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-iframe-messagechannel.https.html80
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-iframe-messagechannel.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-messagechannel-success.https.html22
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-messagechannel-success.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-serviceworker-failure.https.html56
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-serviceworker-failure.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-sharedworker-failure.https.html31
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-sharedworker-failure.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-simple-success.https.html72
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-simple-success.https.html.headers2
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-extra.html46
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js106
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html637
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/transfer-errors.window.js47
-rw-r--r--testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js16
-rw-r--r--testing/web-platform/tests/html/infrastructure/terminology/plugins/sample.txt3
-rw-r--r--testing/web-platform/tests/html/infrastructure/terminology/plugins/text-plain.html32
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js34
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-javascript-nav.https.window.js22
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-uri-synthetic-document.html34
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-changes-about-srcdoc-2.https.html67
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-changes-after-nav-about-srcdoc.https.window.js83
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-initiated-grand-parent.https.window.js62
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-window-initiator-is-not-opener.https.window.js40
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-window-open-about-blank.https.window.js24
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/base-url/matches-about-blank-base-url.window.js29
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/base-url/resources/send-back-base-url.html8
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/dynamic-changes-to-base-urls/dynamic-urls.sub.html41
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/dynamic-changes-to-base-urls/historical.sub.xhtml59
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/attributes.sub.html67
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/location.sub.html46
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/lone-surrogates.sub.html30
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/navigation.sub.html162
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/blank.py2
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/css-tmpl.py7
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/resolve-url.js713
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/resource.py103
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/stash.py15
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-16be.htmlbin0 -> 2640 bytes
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-16le.htmlbin0 -> 2646 bytes
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-8.html26
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/windows-1251.html26
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/windows-1252.html26
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url-about-srcdoc.https.window.js27
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url-changes-about-srcdoc.https.window.js47
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url.html93
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/terminology-0/multiple-base.sub.html17
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/terminology-0/nontraditional-about-srcdoc.html106
-rw-r--r--testing/web-platform/tests/html/infrastructure/urls/terminology-0/resources/echo-referrer-text.py4
134 files changed, 5108 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/domstringlist.html b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/domstringlist.html
new file mode 100644
index 0000000000..6e6e4312a0
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/domstringlist.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<title>DOMStringList</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+
+// Returns a promise that resolves to a DOMStringList with
+// the requested entries. Relies on Indexed DB.
+function createDOMStringList(entries) {
+ return new Promise((resolve, reject) => {
+ const dbname = String(self.location + Math.random());
+ const request = indexedDB.open(dbname);
+ request.onerror = () => reject(request.error);
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ entries.forEach(entry => db.createObjectStore(entry));
+ const dsl = db.objectStoreNames;
+ resolve(dsl);
+ request.transaction.abort();
+ }
+ });
+}
+
+function dsl_test(entries, func, description) {
+ promise_test(t => createDOMStringList(entries).then(dsl => func(t, dsl)),
+ description);
+}
+
+dsl_test(['a', 'b', 'c'], (t, dsl) => {
+ assert_equals(dsl.length, 3, 'length attribute');
+}, 'DOMStringList: length attribute');
+
+dsl_test(['a', 'b', 'c'], (t, dsl) => {
+ assert_equals(dsl.item(0), 'a', 'item method');
+ assert_equals(dsl.item(1), 'b', 'item method');
+ assert_equals(dsl.item(2), 'c', 'item method');
+ assert_equals(dsl.item(3), null, 'item method out of range');
+ assert_equals(dsl.item(-1), null, 'item method out of range');
+ assert_throws_js(TypeError, () => dsl.item(),
+ 'item method should throw if called without enough args');
+}, 'DOMStringList: item() method');
+
+dsl_test(['a', 'b', 'c'], (t, dsl) => {
+ assert_equals(dsl[0], 'a', 'indexed getter');
+ assert_equals(dsl[1], 'b', 'indexed getter');
+ assert_equals(dsl[2], 'c', 'indexed getter');
+ assert_equals(dsl[3], undefined, 'indexed getter out of range');
+ assert_equals(dsl[-1], undefined, 'indexed getter out of range');
+}, 'DOMStringList: indexed getter');
+
+dsl_test(['a', 'b', 'c'], (t, dsl) => {
+ assert_true(dsl.contains('a'), 'contains method matched');
+ assert_true(dsl.contains('b'), 'contains method matched');
+ assert_true(dsl.contains('c'), 'contains method matched');
+ assert_false(dsl.contains(''), 'contains method unmatched');
+ assert_false(dsl.contains('d'), 'contains method unmatched');
+ assert_throws_js(TypeError, () => dsl.contains(),
+ 'contains method should throw if called without enough args');
+}, 'DOMStringList: contains() method');
+
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/historical.html b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/historical.html
new file mode 100644
index 0000000000..91142c864e
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/historical.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<title>Historical HTML*Collection features should not be supported</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<form id=form><input name=foo></form>
+<select id=select><option name=bar></select>
+<div id=dupe>
+<div id=dupe>
+<script>
+test(function() {
+ var collection = document.getElementById('form').elements;
+ assert_equals(typeof collection, 'object', 'typeof');
+ assert_throws_js(TypeError, function() {
+ collection('foo');
+ });
+}, 'HTMLFormControlsCollection legacycaller should not be supported');
+
+test(function() {
+ var collection = document.getElementById('select').options;
+ assert_equals(typeof collection, 'object', 'typeof');
+ assert_throws_js(TypeError, function() {
+ collection('bar');
+ });
+}, 'HTMLOptionsCollection legacycaller should not be supported');
+
+test(function() {
+ var collection = document.all('dupe', 0);
+ // If the second argument were used, it would return the first item of the
+ // collection instead of the whole collection.
+ assert_equals(collection.length, 2, 'length');
+}, 'HTMLAllCollection legacycaller with two arguments should not be supported');
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmlallcollection.html b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmlallcollection.html
new file mode 100644
index 0000000000..14faa2128e
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmlallcollection.html
@@ -0,0 +1,334 @@
+<!DOCTYPE HTML>
+<html id="root">
+<head>
+<title>HTMLAllCollection Tests</title>
+<link rel="author" title="Dan Druta" href="mailto:dan.druta@att.com"/>
+<link rel="author" title="Philip Jägenstedt" href="mailto:philip@foolip.org"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/infrastructure.html#the-htmlallcollection-interface"/>
+<meta name="flags" content="TOKENS" />
+<meta name="assert" content="TEST ASSERTION"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body id="tags">
+<img src="../../../../images/green.png" name="picture">
+<a name="foo"></a>
+<a name="foo"></a>
+<span id="42"></span>
+<span id="043"></span>
+<div id="4294967294"></div>
+<div id="4294967295"></div>
+<div id="4294967296"></div>
+<div id="undefined"></div>
+<div id="null"></div>
+<div name="divwithname"></div>
+<div id="-0"></div>
+<div id="log"></div>
+<script>
+var anchors = document.querySelectorAll("a");
+var divs = document.querySelectorAll("div");
+var scripts = document.querySelectorAll("script");
+var spans = document.querySelectorAll("span");
+
+test(function() {
+ assert_true(document.all instanceof HTMLAllCollection);
+}, "document.all is an HTMLAllCollection");
+
+test(function() {
+ assert_equals(document.all.length, 25);
+}, "length attribute");
+
+// indexed property getter
+
+test(function() {
+ assert_equals(document.all[0], document.documentElement);
+ assert_equals(document.all[-0], document.documentElement);
+ assert_equals(document.all[24], scripts[2]);
+}, "indexed property getter");
+
+test(function() {
+ assert_equals(document.all[-1], undefined);
+ assert_equals(document.all[25], undefined);
+ assert_equals(document.all[42], undefined);
+ assert_equals(document.all[43], undefined);
+ assert_equals(document.all[4294967294], undefined);
+ assert_equals(document.all[4294967295], divs[1]);
+ assert_equals(document.all[4294967296], divs[2]);
+}, "indexed property getter out of range");
+
+// named property getter
+
+test(function() {
+ assert_equals(document.all["root"], document.documentElement);
+ assert_equals(document.all["flags"].content, "TOKENS");
+ assert_equals(document.all["picture"].tagName, "IMG");
+}, "named property getter");
+
+test(function() {
+ assert_equals(document.all.root, document.documentElement);
+ assert_equals(document.all.flags.content, "TOKENS");
+ assert_equals(document.all.picture.tagName, "IMG");
+}, "named property getter with dot syntax");
+
+test(function() {
+ assert_equals(document.all[""], undefined);
+ assert_equals(document.all["noname"], undefined);
+ assert_equals(document.all.noname, undefined);
+ assert_equals(document.all["divwithname"], undefined);
+ assert_equals(document.all.divwithname, undefined);
+}, "named property getter with invalid name");
+
+test(function() {
+ var collection = document.all["foo"];
+ assert_equals(collection.length, 2);
+ assert_equals(collection[0], anchors[0]);
+ assert_equals(collection[1], anchors[1]);
+}, "named property getter returning collection");
+
+test(function() {
+ assert_equals(document.all["0"], document.documentElement);
+ assert_equals(document.all["24"], document.scripts[2]);
+ assert_equals(document.all["25"], undefined);
+ assert_equals(document.all["42"], undefined);
+ assert_equals(document.all["43"], undefined);
+}, "named property getter with \"array index property name\"");
+
+test(function() {
+ assert_equals(document.all["00"], undefined);
+ assert_equals(document.all["042"], undefined);
+ assert_equals(document.all["043"], spans[1]);
+ assert_equals(document.all["4294967294"], undefined);
+ assert_equals(document.all["4294967295"], divs[1]);
+ assert_equals(document.all["4294967296"], divs[2]);
+ assert_equals(document.all["-0"], divs[6]);
+}, "named property getter with invalid \"array index property name\"");
+
+test(function() {
+ assert_equals(document.all[undefined], divs[3]);
+}, "named property getter with undefined");
+
+test(function() {
+ assert_equals(document.all[null], divs[4]);
+}, "named property getter with null");
+
+// namedItem method
+
+test(function() {
+ assert_equals(document.all.namedItem("root"), document.documentElement);
+ assert_equals(document.all.namedItem("flags").content, "TOKENS");
+ assert_equals(document.all.namedItem("picture").tagName, "IMG");
+}, "namedItem method");
+
+test(function() {
+ assert_equals(document.all.namedItem(""), null);
+ assert_equals(document.all.namedItem("noname"), null);
+ assert_equals(document.all.namedItem("divwithname"), null);
+}, "namedItem method with invalid name");
+
+test(function() {
+ var collection = document.all.namedItem("foo");
+ assert_equals(collection.length, 2);
+ assert_equals(collection[0], anchors[0]);
+ assert_equals(collection[1], anchors[1]);
+}, "namedItem method returning collection");
+
+test(function() {
+ assert_equals(document.all.namedItem("0"), null);
+ assert_equals(document.all.namedItem("23"), null);
+ assert_equals(document.all.namedItem("24"), null);
+ assert_equals(document.all.namedItem("42"), spans[0]);
+ assert_equals(document.all.namedItem("43"), null);
+}, "namedItem method with \"array index property name\"");
+
+test(function() {
+ assert_equals(document.all.namedItem("00"), null);
+ assert_equals(document.all.namedItem("042"), null);
+ assert_equals(document.all.namedItem("043"), spans[1]);
+ assert_equals(document.all.namedItem("4294967294"), divs[0]);
+ assert_equals(document.all.namedItem("4294967295"), divs[1]);
+ assert_equals(document.all.namedItem("4294967296"), divs[2]);
+ assert_equals(document.all.namedItem("-0"), divs[6]);
+}, "namedItem method with invalid \"array index property name\"");
+
+test(function() {
+ assert_equals(document.all.namedItem(undefined), divs[3]);
+}, "namedItem method with undefined");
+
+test(function() {
+ assert_equals(document.all.namedItem(null), divs[4]);
+}, "namedItem method with null");
+
+test(function() {
+ assert_equals(document.all.namedItem.length, 1);
+ assert_throws_js(TypeError, function() {
+ document.all.namedItem();
+ });
+}, "namedItem method with no argument");
+
+// legacy caller
+
+test(function() {
+ assert_equals(document.all("root"), document.documentElement);
+ assert_equals(document.all("flags").content, "TOKENS");
+ assert_equals(document.all("picture").tagName, "IMG");
+}, "legacy caller");
+
+test(function() {
+ assert_equals(document.all(""), null);
+ assert_equals(document.all("noname"), null);
+ assert_equals(document.all("divwithname"), null);
+}, "legacy caller with invalid name");
+
+test(function() {
+ var collection = document.all("foo");
+ assert_equals(collection.length, 2);
+ assert_equals(collection[0], anchors[0]);
+ assert_equals(collection[1], anchors[1]);
+}, "legacy caller returning collection");
+
+test(function() {
+ assert_equals(document.all("0"), document.documentElement);
+ assert_equals(document.all("24"), document.scripts[2]);
+ assert_equals(document.all("25"), null);
+ assert_equals(document.all("42"), null);
+ assert_equals(document.all("43"), null);
+}, "legacy caller with \"array index property name\"");
+
+test(function() {
+ assert_equals(document.all(0), document.documentElement);
+ assert_equals(document.all(24), document.scripts[2]);
+ assert_equals(document.all(25), null);
+ assert_equals(document.all(42), null);
+ assert_equals(document.all(43), null);
+}, "legacy caller with \"array index property name\" as number");
+
+test(function() {
+ assert_equals(document.all("00"), null);
+ assert_equals(document.all("042"), null);
+ assert_equals(document.all("043"), spans[1]);
+ assert_equals(document.all("4294967294"), null);
+ assert_equals(document.all("4294967295"), divs[1]);
+ assert_equals(document.all("4294967296"), divs[2]);
+ assert_equals(document.all("-0"), divs[6]);
+}, "legacy caller with invalid \"array index property name\"");
+
+test(function() {
+ assert_equals(document.all(undefined), null);
+}, "legacy caller with undefined");
+
+test(function() {
+ assert_equals(document.all(null), divs[4]);
+}, "legacy caller with null");
+
+test(function() {
+ assert_equals(document.all(), null);
+}, "legacy caller with no argument");
+
+test(function() {
+ assert_throws_js(TypeError, function() {
+ new document.all("picture");
+ }, "New should not work on document.all()");
+
+ // https://esdiscuss.org/topic/isconstructor#content-11
+ assert_throws_js(TypeError, function() {
+ new (new Proxy(document.all, {
+ construct: function() {
+ return {};
+ }
+ }));
+ }, "Proxies should treat document.all() as not-a-constructor");
+}, "legacy caller is not a constructor");
+
+test(function() {
+ [undefined, null, {}, document.body].forEach(function(thisValue) {
+ assert_equals(Function.prototype.call.call(document.all, thisValue, "043"), spans[1]);
+ });
+}, "legacy caller with arbitrary this value");
+
+// item method
+
+test(function() {
+ assert_equals(document.all.item("root"), document.documentElement);
+ assert_equals(document.all.item("flags").content, "TOKENS");
+ assert_equals(document.all.item("picture").tagName, "IMG");
+}, "item method");
+
+test(function() {
+ assert_equals(document.all.item(""), null);
+ assert_equals(document.all.item("noname"), null);
+ assert_equals(document.all.item("divwithname"), null);
+}, "item method with invalid name");
+
+test(function() {
+ var collection = document.all.item("foo");
+ assert_equals(collection.length, 2);
+ assert_equals(collection[0], anchors[0]);
+ assert_equals(collection[1], anchors[1]);
+}, "item method returning collection");
+
+test(function() {
+ assert_equals(document.all.item("0"), document.documentElement);
+ assert_equals(document.all.item("24"), document.scripts[2]);
+ assert_equals(document.all.item("25"), null);
+ assert_equals(document.all.item("42"), null);
+ assert_equals(document.all.item("43"), null);
+}, "item method with \"array index property name\"");
+
+test(function() {
+ assert_equals(document.all.item(0), document.documentElement);
+ assert_equals(document.all.item(24), document.scripts[2]);
+ assert_equals(document.all.item(25), null);
+ assert_equals(document.all.item(42), null);
+ assert_equals(document.all.item(43), null);
+}, "item method with \"array index property name\" as number");
+
+test(function() {
+ assert_equals(document.all.item("00"), null);
+ assert_equals(document.all.item("042"), null);
+ assert_equals(document.all.item("043"), spans[1]);
+ assert_equals(document.all.item("4294967294"), null);
+ assert_equals(document.all.item("4294967295"), divs[1]);
+ assert_equals(document.all.item("4294967296"), divs[2]);
+ assert_equals(document.all.item("-0"), divs[6]);
+}, "item method with invalid \"array index property name\"");
+
+test(function() {
+ assert_equals(document.all.item(undefined), null);
+}, "item method with undefined");
+
+test(function() {
+ assert_equals(document.all.item(null), divs[4]);
+}, "item method with null");
+
+test(function() {
+ assert_equals(document.all.item.length, 0);
+ assert_equals(document.all.item(), null);
+}, "item method with no argument");
+
+// live HTMLCollection
+
+test(function() {
+ var collections = [
+ document.all["foo"],
+ document.all.namedItem("foo"),
+ document.all("foo"),
+ document.all.item("foo"),
+ ];
+ // a new HTMLCollection is created for each call
+ for (var i = 0; i < collections.length; i++) {
+ assert_true(collections[i] instanceof HTMLCollection);
+ for (var j = i + 1; j < collections.length; j++) {
+ assert_not_equals(collections[i], collections[j]);
+ }
+ }
+ for (var c of collections) {
+ assert_equals(c.length, 2);
+ }
+ anchors[0].name = "bar";
+ for (var c of collections) {
+ assert_equals(c.length, 1);
+ }
+}, "collections are new live HTMLCollection instances");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmlformcontrolscollection.html b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmlformcontrolscollection.html
new file mode 100644
index 0000000000..5591e190b3
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmlformcontrolscollection.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: the HTMLFormControlsCollection interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/common-dom-interfaces.html#htmlformcontrolscollection">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form id="f1">
+ <input type="radio" id="r1" name="ra">
+ <keygen id="kg" name="key"></keygen> <!-- we test that it does *not* appear in form.elements -->
+</form>
+<form id="f2">
+ <table>
+ <tr>
+ <td>
+ <input type="checkbox" id="cb">
+ <input type="checkbox" name="cb">
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <button id="btn"></button>
+ <button name="btn"></button>
+ </td>
+ </tr>
+ </table>
+</form>
+
+<script>
+
+var coll1, coll2, rdo;
+
+setup(function () {
+ rdo = document.getElementById("r1");
+ coll1 = document.forms[0].elements;
+ coll2 = document.forms[1].elements;
+});
+
+//length
+test(function () {
+ assert_equals(coll1.length, 1, "The length attribute is incorrect.");
+ assert_equals(coll2.length, 4, "The length attribute is incorrect.");
+}, "The length attribute must return the number of elements in the form");
+
+//getter - index
+test(function () {
+ assert_equals(coll1.item(0), rdo, "HTMLFormControlsCollection.item(index) should return the 'input' element in radio status.");
+}, "HTMLFormControlsCollection.item(index) must return the indexed item");
+
+test(function () {
+ assert_equals(coll1[0], rdo, "HTMLFormControlsCollection[index] should return the 'input' element in radio status.");
+}, "HTMLFormControlsCollection[index] must return the indexed item");
+
+//getter - name
+test(function () {
+ assert_throws_js(TypeError, function() { coll1("r1") });
+}, "HTMLFormControlsCollection is not callable");
+
+test(function () {
+ assert_equals(coll1["r1"], rdo, "HTMLFormControlsCollection[name] should return the 'input' element in radio status.");
+}, "HTMLFormControlsCollection[name] must return the named item");
+
+//getter - namedItem
+test(function () {
+ assert_equals(coll1.namedItem("r1"), rdo, "HTMLFormControlsCollection.namedItem(name) should return the 'input' element in radio status.");
+}, "HTMLFormControlsCollection.namedItem(name) must return the named item");
+
+test(function () {
+ assert_true(coll1.namedItem("r1") instanceof Element, "Can not return 'Element' object.");
+}, "The namedItem(name) must return an Element");
+
+test(function () {
+ assert_true(coll2.namedItem("cb") instanceof RadioNodeList, "Can not return 'RadioNodeList' object.");
+}, "The namedItem(name) must return RadioNodeList");
+
+test(function () {
+ assert_equals(coll1.namedItem(""), null, "The return value of namedItem() should be null.");
+}, "The namedItem(name) must return null if the name is empty");
+
+test(function () {
+ assert_equals(coll1.namedItem("test"), null, "The return value of namedItem() should be null.");
+}, "The namedItem(name) must return null if there is no matched element");
+
+test(function () {
+ assert_equals(coll1.namedItem("r1"), document.getElementById("r1"), "Controls can be named by 'id' attribute.");
+ assert_equals(coll1.namedItem("ra"), document.getElementById("r1"), "Controls can be named by 'name' attribute.");
+}, "Controls can be indexed by id or name attribute");
+
+test(function () {
+ assert_equals(coll1.namedItem("kg"), null, "Keygen does not show up when queried by id.");
+ assert_equals(coll1.namedItem("key"), null, "Keygen does not show up when queried by name.");
+}, "Keygen controls do not show up at all");
+
+test(function () {
+ assert_equals(coll2.namedItem("btn").length, 2, "The length attribute should be 2.");
+}, "The namedItem(name) must return the items with id or name attribute");
+
+//various controls in fieldset and form
+var containers = ["form", "fieldset"],
+ controls = ["button", "fieldset", "input", "object", "output", "select", "textarea"];
+for (var m = 0; m < containers.length; m++) {
+ test(function () {
+ var container = document.createElement(containers[m]);
+ var len = controls.length;
+ for (var n = 0; n < len; n++)
+ container.appendChild(document.createElement(controls[n]));
+ document.body.appendChild(container);
+ assert_equals(container.elements.length, len, "The length should be " + len + ".");
+ }, "The HTMLFormControlsCollection interface is used for collections of listed elements in " + containers[m] + " element");
+}
+
+//Check the controls' order
+test(function () {
+ var opt = document.forms[1].insertBefore(document.createElement("output"), document.forms[1].firstChild);
+ assert_array_equals(document.forms[1].elements,
+ [opt, document.getElementsByTagName("input")[1], document.getElementsByTagName("input")[2],
+ document.getElementsByTagName("button")[0], document.getElementsByTagName("button")[1]]);
+}, "The controls in the form element must be sorted in tree order");
+
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmloptionscollection.html b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmloptionscollection.html
new file mode 100644
index 0000000000..130716a9cc
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/htmloptionscollection.html
@@ -0,0 +1,216 @@
+<!doctype html>
+<title>HTMLOptionsCollection</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#htmloptionscollection-0">
+<select id=a>
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+<!--Note whitespace is important-->
+ <option>4</option>
+ <option>5</option>
+</select>
+
+<select id=b>
+ <option id=b1>1</option>
+ <option name=b2>2</option>
+ <option id=b3>3</option>
+ <option id=b3>4</option>
+ <option name=b4>5</option>
+ <option name=b4>6</option>
+ <option id=b5>7</option>
+ <option name=b5>8</option>
+ <option id=b6 name=b7>9</option>
+ <option id=b6 name=b6>10</option>
+ <option id=b8 name=b9>11</option>
+</select>
+
+<script>
+var a;
+var a_opts;
+var a_original_innerHTML;
+var b;
+var b_opts;
+
+setup(function() {
+ a = document.getElementById("a");
+ a_opts = a.options;
+ a_original_innerHTML = a.innerHTML;
+ a.innerHTML = a_original_innerHTML;
+ b = document.getElementById("b");
+ b_opts = b.options;
+ b_original_innerHTML = b.innerHTML;
+ b.innerHTML = b_original_innerHTML;
+})
+
+function assert_values_equals(coll, expected_values, message) {
+ actual = [];
+ for (var i=0; i<coll.length; i++) {
+ actual.push(coll[i].value);
+ }
+ assert_array_equals(actual, expected_values, message);
+}
+
+test(function() {
+ assert_equals(5, a_opts.length);
+}, "Original length");
+
+test(function() {
+ a.innerHTML = a_original_innerHTML;
+ a_opts.value = "3";
+ a_opts.length = 5;
+ assert_equals(a_opts.length, 5);
+ assert_equals(a_opts.value, "3");
+}, "Setting length to original value has no effect");
+
+test(function() {
+ a.innerHTML = a_original_innerHTML;
+ a.value = 3;
+ a_opts.length = 3;
+ assert_equals(3, a_opts.length, "Correct length");
+ assert_values_equals(a_opts, ["1","2","3"], "Correct elements remain")
+ assert_equals(a_opts.value, "3", "Correct value set");
+ assert_equals(a.childNodes.length, 11, "Correct number of child nodes")
+}, "Setting length to shorter value");
+
+test(function() {
+ a.innerHTML = a_original_innerHTML;
+ a.value = 3;
+ a_opts.length = 7;
+ assert_equals(a_opts.length, 7, "Correct length");
+ assert_values_equals(a_opts, ["1","2","3","4","5","",""], "Correct elements inserted")
+ assert_equals(a.value, "3", "Correct value set");
+ assert_equals(a.childNodes.length, 15, "Correct number of child nodes")
+}, "Setting length to longer value");
+
+test(function() {
+ a.innerHTML = a_original_innerHTML;
+ var newChild = document.createElement("p");
+ var newOption = document.createElement("option");
+ newOption.textContent = "6";
+ newChild.appendChild(newOption);
+ a.appendChild(newChild);
+ a.value = 3;
+ assert_equals(a_opts.length, 5, "Correct length");
+ assert_values_equals(a_opts, ["1","2","3","4","5"], "Correct elements inserted")
+ assert_equals(a.value, "3", "Correct value set");
+}, "Insert <p><option>6</option></p> into <select>");
+
+test(function() {
+ a.innerHTML = a_original_innerHTML;
+ var newChild = document.createElement("select");
+ var newOption = document.createElement("option");
+ newOption.textContent = "6";
+ newChild.appendChild(newOption);
+ a.appendChild(newChild);
+ a.value = 3;
+ assert_equals(a_opts.length, 5, "Correct length");
+ assert_values_equals(a_opts, ["1","2","3","4","5"], "Correct elements inserted")
+ assert_equals(a.value, "3", "Correct value set");
+}, "Insert <select><option>6</option></select> into <select>");
+
+test(function() {
+ //This tests the spec but it is probably wrong here; see bug 12665
+ a.innerHTML = a_original_innerHTML;
+ var newChild = document.createElement("optgroup");
+ var newOption = document.createElement("option");
+ newOption.textContent = "6";
+ newChild.appendChild(newOption);
+ a.appendChild(newChild);
+ a.value = 3;
+ assert_equals(a_opts.length, 6, "Correct length");
+ assert_values_equals(a_opts, ["1","2","3","4","5", "6"], "Correct elements inserted")
+ assert_equals(a.value, "3", "Correct value set");
+}, "Insert <optgroup><option>6</option></optgroup> into <select>");
+
+test(function() {
+ a.innerHTML = a_original_innerHTML;
+ var newChild = document.createElement("optgroup");
+ var newChild1 = document.createElement("optgroup");
+ var newOption = document.createElement("option");
+ newOption.textContent = "6";
+ newChild.appendChild(newChild1);
+ newChild1.appendChild(newOption);
+ a.appendChild(newChild);
+ a.value = 3;
+ assert_equals(a_opts.length, 5, "Correct length");
+ assert_values_equals(a_opts, ["1","2","3","4","5"], "Correct elements inserted")
+ assert_equals(a.value, "3", "Correct value set");
+}, "Insert <optgroup><optgroup><option>6</option></optgroup></optgroup> into <select>");
+
+test(function() {
+ assert_equals(b_opts.namedItem("b1").value, "1");
+}, "namedItem id attribute");
+
+test(function() {
+ assert_equals(b_opts.namedItem("b2").value, "2");
+}, "namedItem name attribute");
+
+test(function() {
+ assert_equals(b_opts.namedItem("c"), null);
+}, "namedItem doesn't match anything");
+
+test(function() {
+ assert_equals(b_opts.namedItem("b3").value, "3");
+}, "namedItem multiple IDs");
+
+test(function() {
+ assert_equals(b_opts.namedItem("b4").value, "5");
+}, "namedItem multiple names");
+
+test(function() {
+ assert_equals(b_opts.namedItem("b5").value, "7");
+}, "namedItem multiple name and ID");
+
+test(function() {
+ assert_equals(b_opts.namedItem("b6").value, "9");
+}, "namedItem multiple name and ID with multiple attributes");
+
+test(function() {
+ assert_equals(b_opts.namedItem("b8").value, "11");
+}, "namedItem id attribute multiple attributes one element");
+
+test(function() {
+ assert_equals(b_opts.namedItem("b9").value, "11");
+}, "namedItem name attribute multiple attributes one element");
+
+test(function() {
+ assert_true(b_opts[0] instanceof HTMLOptionElement);
+ assert_equals(b_opts[0].innerHTML, "1");
+}, "HTMLOptionsCollection [index] method return the item with index");
+
+test(function() {
+ assert_true(b_opts["b2"] instanceof HTMLOptionElement);
+ assert_equals(b_opts["b2"].innerHTML, "2");
+}, "HTMLOptionsCollection [name] method return the item with name");
+
+test(function() {
+ assert_true(b_opts.item(0) instanceof HTMLOptionElement);
+ assert_equals(b_opts.item(0).innerHTML, "1");
+}, "HTMLOptionsCollection.item(index) method return the item with index");
+
+test(function() {
+ assert_true(b_opts.item("b2") instanceof HTMLOptionElement);
+ assert_equals(b_opts.item("b2").innerHTML, "1");
+}, "HTMLOptionsCollection.item(name) method return the item with index 0");
+
+test(function() {
+ var b_opts_length = b_opts.length;
+ b_opts.add(new Option("2", "2"));
+ assert_equals(b_opts[b_opts_length].value, "2");
+}, "HTMLOptionsCollection.add method insert HTMLOptionElement Option element");
+
+test(function() {
+ var b_opts_length = b_opts.length;
+ b_opts.remove(0);
+ assert_equals(b_opts.length, b_opts_length - 1);
+}, "HTMLOptionsCollection.remove method remove Option element by index");
+
+test(function() {
+ var add = document.createElement("p");
+ assert_throws_js(TypeError, function() {b_opts.add(add);});
+}, "Add non-option to collection");
+
+</script>
+<div id=log></div>
diff --git a/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/radionodelist.html b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/radionodelist.html
new file mode 100644
index 0000000000..fc70d7172c
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/common-dom-interfaces/collections/radionodelist.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: the RadioNodeList interface</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/multipage/common-dom-interfaces.html#radionodelist">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<form >
+ <input type="checkbox" name="rdo" value="0" id="r0" checked>
+ <input type="radio" name="rdo" id="r1">
+ <input type="radio" name="rdo" id="r2" value="2">
+</form>
+<script>
+
+var rdoList;
+
+setup(function () {
+ rdoList = document.forms[0].elements.namedItem("rdo");
+});
+
+//on getting
+test(function () {
+ assert_equals(rdoList.value, "", "The value attribute should be empty.");
+}, "The value attribute should be empty if no element is checked");
+
+test(function () {
+ document.getElementById("r2").checked = true;
+ assert_equals(rdoList.value, "2", "The value attribute should be 2.");
+}, "The RadioNodeList.value must be the first checked radio button's value");
+
+test(function () {
+ document.getElementById("r1").checked = true;
+ assert_equals(rdoList.value, "on", "The value attribute should be on.");
+
+ document.getElementById("r1").value = 1;
+ assert_equals(rdoList.value, "1", "The value attribute should be 1.");
+}, "Check the RadioNodeList.value on getting");
+
+//on setting
+test(function () {
+ assert_equals(rdoList.value, document.getElementById("r1").value,
+ "The value attribute should be equal to the first checked radio input element's value.");
+ assert_false(document.getElementById("r2").checked,
+ "The second radio input element should not be checked.");
+
+ rdoList.value = "2";
+ assert_equals(rdoList.value, document.getElementById("r2").value,
+ "The value attribute should be equal to the second radio input element's value.");
+ assert_true(document.getElementById("r2").checked,
+ "The second radio input element should be checked.");
+
+ //Do nothing if no element's value is equal to new value.
+ rdoList.value = "3";
+ assert_equals(rdoList.value, document.getElementById("r2").value,
+ "The value attribute should be the second radio input element's value.");
+ assert_true(document.getElementById("r2").checked,
+ "The second radio input element should be checked.");
+}, "Check the RadioNodeList.value on setting");
+
+//setting to on, specific case
+test(function () {
+ rdoList.value = "on";
+ assert_equals(rdoList.value, document.getElementById("r2").value,
+ "The value attribute should be the second radio input element's value.");
+ assert_true(document.getElementById("r2").checked,
+ "The second radio input element should be checked.");
+
+ document.getElementById("r1").removeAttribute("value");
+ rdoList.value = "on";
+ assert_equals(rdoList.value, document.getElementById("r1").value,
+ "The value attribute should be the first radio input element's value.");
+ assert_true(document.getElementById("r1").checked,
+ "The first radio input element should be checked.");
+}, "Check the RadioNodeList.value on setting to 'on'");
+
+
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/common-microsyntaxes/colours/parsing-legacy-colour-value-ascii-case-insensitive-ref.html b/testing/web-platform/tests/html/infrastructure/common-microsyntaxes/colours/parsing-legacy-colour-value-ascii-case-insensitive-ref.html
new file mode 100644
index 0000000000..6565e0e750
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/common-microsyntaxes/colours/parsing-legacy-colour-value-ascii-case-insensitive-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<p>This square should be black: <font face="Ahem">X</font>
+<p>This square should be black: <font face="Ahem">X</font>
+<p>This square should be blue: <font face="Ahem" color="#0000E0">X</font>
diff --git a/testing/web-platform/tests/html/infrastructure/common-microsyntaxes/colours/parsing-legacy-colour-value-ascii-case-insensitive.html b/testing/web-platform/tests/html/infrastructure/common-microsyntaxes/colours/parsing-legacy-colour-value-ascii-case-insensitive.html
new file mode 100644
index 0000000000..eee4a9c7c8
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/common-microsyntaxes/colours/parsing-legacy-colour-value-ascii-case-insensitive.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/#rules-for-parsing-a-legacy-colour-value">
+<link rel="match" href="parsing-legacy-colour-value-ascii-case-insensitive-ref.html">
+<meta name="assert" content="special legacy color value “transparent” is ASCII case-insensitive">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
+<p>This square should be black: <font face="Ahem" color="transparent">X</font>
+<p>This square should be black: <font face="Ahem" color="TrAnSpArEnT">X</font>
+<p>This square should be blue: <font face="Ahem" color="tranſparent">X</font>
+<!--
+ Following the rules for parsing a legacy color value should yield a shade of
+ blue, because only the following steps apply, not step 4:
+
+ 1. tranſparent
+ 10. 00a000a0e00
+ 11. 00a000a0e000
+ 12. 00a0 00a0 e000 (length = 4)
+ 15. 00 00 e0
+ 20. #0000E0
+-->
diff --git a/testing/web-platform/tests/html/infrastructure/conformance-requirements/extensibility/foreign.html b/testing/web-platform/tests/html/infrastructure/conformance-requirements/extensibility/foreign.html
new file mode 100644
index 0000000000..eaa133bade
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/conformance-requirements/extensibility/foreign.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en" foo='bar'>
+ <head foo='bar'>
+ <meta charset="utf-8" foo='bar'>
+ <title id='title' foo='bar'>Foreign content</title>
+ <link rel="author" title="Philippe Le Hegaret" href="mailto:plh@w3.org" foo='bar'>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#extensibility" foo='bar'>
+ <script src="/resources/testharness.js" foo='bar'></script>
+ <script src="/resources/testharnessreport.js" foo='bar'></script>
+ </head>
+
+ <body foo='bar'>
+
+ <p class='assert' foo='bar'>User agents must treat elements and attributes that they do not understand as semantically neutral; leaving them in the DOM (for DOM processors), and styling them according to CSS (for CSS processors), but not inferring any meaning from them.</p>
+
+ <foo foo='bar' echo>Foobar</foo>
+
+ <div id="log">Running test...</div>
+
+ <script>
+ var t = async_test("foreign content");
+
+ on_event(window, "load",
+ t.step_func(function() {
+ var nodes = document.getElementsByTagName("*");
+ var cont = true;
+ var last = null;
+ for(var i=0;i<nodes.length && cont; i++) {
+ var as = nodes.item(i).getAttribute("foo");
+ if (!(as === "bar") && (nodes.item(i).getAttribute("id") === "log")) {
+ cont = false;
+ } else {
+ last = nodes.item(i);
+ assert_equals(as, "bar");
+ }
+ }
+
+ assert_equals(last.nodeName, "FOO");
+ assert_equals(last.getAttribute("echo"), "");
+ assert_equals(last.getAttribute("charly"), null);
+ t.done();
+ }));
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/infrastructure/fetching-resources/crossorigin-enumerated-ascii-case-insensitive.html b/testing/web-platform/tests/html/infrastructure/fetching-resources/crossorigin-enumerated-ascii-case-insensitive.html
new file mode 100644
index 0000000000..7207d43c60
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/fetching-resources/crossorigin-enumerated-ascii-case-insensitive.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<link rel="help" href="https://html.spec.whatwg.org/#cors-settings-attribute">
+<link rel="help" href="https://html.spec.whatwg.org/#enumerated-attribute">
+<meta name="assert" content="@crossorigin values are ASCII case-insensitive">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<img crossorigin="use-credentials">
+<img crossorigin="UsE-cReDenTiAlS">
+<img crossorigin="uſe-credentialſ">
+<img crossorigin="anonymous">
+<img crossorigin="AnOnYmOuS">
+<img crossorigin="anonymouſ">
+<script>
+const img = document.querySelectorAll("img");
+
+test(() => {
+ assert_equals(img[0].crossOrigin, "use-credentials", "lowercase valid");
+ assert_equals(img[1].crossOrigin, "use-credentials", "mixed case valid");
+ assert_equals(img[2].crossOrigin, "anonymous", "non-ASCII invalid");
+}, "keyword use-credentials");
+
+test(() => {
+ assert_equals(img[3].crossOrigin, "anonymous", "lowercase valid");
+
+ // vacuous: the invalid value default is currently anonymous, so even if the
+ // UA treats this as invalid, the observable behaviour would still be correct
+ assert_equals(img[4].crossOrigin, "anonymous", "mixed case valid");
+
+ // vacuous: the invalid value default is currently anonymous, so even if the
+ // UA treats this as valid, the observable behaviour would still be correct
+ assert_equals(img[5].crossOrigin, "anonymous", "non-ASCII invalid");
+}, "keyword anonymous");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html
new file mode 100644
index 0000000000..2b21a1459d
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/cross-origin-transfer-resizable-arraybuffer.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>postMessage transfer ArrayBuffer cross origin iframe</title>
+<script src='/resources/testharness.js'></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src='/common/get-host-info.sub.js'></script>
+
+<script>
+
+async_test(t => {
+ const oopif = document.createElement('iframe');
+
+ window.addEventListener('message', t.step_func((e) => {
+ if (e.data === 'started') {
+ const rab = new ArrayBuffer(32, { maxByteLength: 1024 });
+ oopif.contentWindow.postMessage(rab, '*', [rab]);
+ } else {
+ assert_equals(e.data, 'byteLength=32,maxByteLength=1024,resizable=true');
+ t.done();
+ }
+ }));
+
+ window.addEventListener('load', () => {
+ oopif.src = `${get_host_info().HTTP_REMOTE_ORIGIN}/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html`;
+ document.body.appendChild(oopif);
+ });
+}, 'postMessaging resizable ArrayBuffer to OOPIF');
+
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js
new file mode 100644
index 0000000000..6ba17f7a89
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js
@@ -0,0 +1,16 @@
+// META: global=window,worker
+// META: script=/common/sab.js
+// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
+// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
+// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
+
+runStructuredCloneBatteryOfTests({
+ structuredClone(data, transfer) {
+ return new Promise(resolve => {
+ const channel = new MessageChannel();
+ channel.port2.onmessage = ev => resolve(ev.data.data);
+ channel.port1.postMessage({data, transfer}, transfer);
+ });
+ },
+ hasDocument : self.GLOBAL.isWindow()
+});
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html
new file mode 100644
index 0000000000..c4fd5824a1
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A test page that echos back anything postMessaged to it to its parent</title>
+
+<script>
+"use strict";
+
+window.onmessage = ({ data }) => {
+ parent.postMessage(data, "*");
+};
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js
new file mode 100644
index 0000000000..cbbde8a73c
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js
@@ -0,0 +1,5 @@
+"use strict";
+
+self.onmessage = ({ data }) => {
+ self.postMessage(data);
+};
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html
new file mode 100644
index 0000000000..378c953fbe
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+
+window.addEventListener('message', (e) => {
+ const buffer = e.data;
+ e.source.postMessage(`byteLength=${buffer.byteLength},maxByteLength=${buffer.maxByteLength},resizable=${buffer.resizable}`, '*');
+});
+
+window.parent.postMessage('started', '*');
+
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/post-parent-type-error.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/post-parent-type-error.html
new file mode 100644
index 0000000000..d6713c4192
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/resources/post-parent-type-error.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Helper that posts its parent a TypeError</title>
+
+<script>
+window.doIt = () => {
+ parent.postMessage(new TypeError("!!"), "*");
+};
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html
new file mode 100644
index 0000000000..bfcc8b61ca
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html
@@ -0,0 +1,132 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+const url = new URL("./", self.location);
+
+function httpWorkerIncrementerTest(name) {
+ return `
+promise_test(t => {
+ const worker = new Worker("${url.href}resources/incrementer-worker.js");
+
+ return testSharingViaIncrementerScript(t, worker, "parent worker", worker, "sub-worker");
+}, "${name}: postMessaging to a dedicated HTTP sub-worker allows them to see each others' modifications");
+`;
+}
+
+function blobWorkerIncrementerTest(name, origin = "null") {
+ return `
+promise_test(t => {
+ const worker = new Worker(URL.createObjectURL(new Blob([\`
+const view = new Uint8Array(new SharedArrayBuffer(1));
+self.onmessage = () => {
+ const succeeded = (view[0] === 1);
+ self.postMessage({ succeeded });
+};
+self.postMessage({ origin: self.origin, view });
+\`], { type: "text/javascript" })));
+
+ return new Promise((resolve, reject) => {
+ /* Initially the sub-worker gives us an object containing an origin and a view on a shared
+ // buffer. We then modify the shared buffer through the buffer and tell the sub-worker to
+ // "continue". The sub-worker verifies the modification and relays whether it succeeded.
+ */
+ worker.onmessage = t.step_func(({ data }) => {
+ if ("succeeded" in data) {
+ assert_true(data.succeeded);
+ resolve();
+ } else {
+ assert_equals(data.origin, "${origin}");
+ assert_equals(data.view[0], 0);
+ data.view[0] = 1;
+ worker.postMessage("continue");
+ }
+ });
+ worker.onmessageerror = reject;
+ });
+}, "${name}: postMessaging to a dedicated blob sub-worker allows them to see each others' modifications");
+`;
+}
+
+function propertyTests(name, crossOriginIsolated) {
+ return `
+test(() => {
+ assert_equals(self.origin, self.location.origin);
+}, "${name}: self.origin");
+
+test(() => {
+ assert_equals(self.crossOriginIsolated, ${crossOriginIsolated});
+}, "${name}: self.crossOriginIsolated");
+
+test(() => {
+ assert_true(self.isSecureContext);
+}, "${name}: self.isSecureContext");
+`;
+}
+
+const workerScript = `
+importScripts("${url.origin}/resources/testharness.js");
+importScripts("${url.href}resources/test-incrementer.js");
+
+${httpWorkerIncrementerTest("blob worker")}
+
+${blobWorkerIncrementerTest("blob worker", self.location.origin)}
+
+${propertyTests("blob worker", true)}
+
+done();
+`;
+
+fetch_tests_from_worker(new Worker(URL.createObjectURL(new Blob([workerScript], { type: "text/javascript" }))));
+
+const frameScript = `
+<!doctype html>
+<script src=${url.origin}/resources/testharness.js><\/script>
+<script src=${url.href}resources/test-incrementer.js><\/script>
+<script>
+${httpWorkerIncrementerTest("blob frame")}
+
+${blobWorkerIncrementerTest("blob frame", self.location.origin)}
+
+${propertyTests("blob frame", true)}
+<\/script>
+`;
+
+const frame = document.body.appendChild(document.createElement("iframe"));
+frame.src = URL.createObjectURL(new Blob([frameScript], { type: "text/html" }));
+frame.style = "display:none";
+fetch_tests_from_window(frame.contentWindow);
+
+const dataWorkerScript = `
+importScripts("${url.origin}/resources/testharness.js?pipe=header(Cross-Origin-Resource-Policy,cross-origin)");
+
+/* Cannot use httpWorkerIncrementerTest() here as the HTTP URL is not same origin. */
+
+${blobWorkerIncrementerTest("data worker")}
+
+${propertyTests("data worker", false)}
+
+done();
+`;
+
+fetch_tests_from_worker(new Worker(`data:,${dataWorkerScript}`));
+
+const dataFrameScript = `
+<!doctype html>
+<script src=${url.origin}/resources/testharness.js?pipe=header(Cross-Origin-Resource-Policy,cross-origin)><\/script>
+<script>
+/* Cannot use httpWorkerIncrementerTest() here as the HTTP URL is not same origin. */
+
+${blobWorkerIncrementerTest("data frame")}
+
+${propertyTests("data frame", true)}
+<\/script>
+`;
+
+const dataFrame = document.body.appendChild(document.createElement("iframe"));
+dataFrame.src = `data:text/html,${dataFrameScript}`;
+dataFrame.style = "display:none";
+fetch_tests_from_window(dataFrame.contentWindow);
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/blob-data.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success-and-failure.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success-and-failure.https.html
new file mode 100644
index 0000000000..8902de49cf
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success-and-failure.https.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<title>SharedArrayBuffer cannot cross agent clusters, BroadcastChannel edition</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+async_test(t => {
+ const channel = new BroadcastChannel("anne was here"),
+ dw = new Worker("resources/broadcastchannel-worker.js"),
+ sw = new SharedWorker("resources/broadcastchannel-sharedworker.js");
+ let startCounter = 0,
+ dwStatus = "unknown",
+ swStatus = "unknown";
+
+ channel.onmessage = t.step_func(({ data }) => {
+ if(data === "hi") {
+ startCounter++;
+ if(startCounter === 2) {
+ const sab = new SharedArrayBuffer();
+ channel.postMessage(sab);
+ } else if(startCounter > 2) {
+ assert_unreached();
+ }
+ } else if(data === "dw-success") {
+ dwStatus = "success";
+ } else if(data === "sw-success") {
+ swStatus = "success";
+ } else {
+ assert_unreached();
+ }
+ if(dwStatus === "success" && swStatus === "success") {
+ t.done();
+ }
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success-and-failure.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success-and-failure.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success-and-failure.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.https.html
new file mode 100644
index 0000000000..d3e9956368
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Structured cloning of SharedArrayBuffers: BroadcastChannel within the same agent cluster</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#structuredserialize">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#broadcasting-to-other-browsing-contexts">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script> <!-- Use token() to allow running tests in parallel -->
+
+<div id="log"></div>
+
+<script>
+"use strict";
+
+promise_test(t => {
+ const channelName = token();
+ return Promise.all([
+ createIFrame(`resources/broadcastchannel-iframe.html?channel=${channelName}&index=0`),
+ createIFrame(`resources/broadcastchannel-iframe.html?channel=${channelName}&index=1`),
+ createIFrame(`resources/broadcastchannel-iframe.html?channel=${channelName}&index=2`)
+ ]).then(() => {
+ const sab = new SharedArrayBuffer(3);
+ const view = new Uint8Array(sab);
+ const channel = new BroadcastChannel(channelName);
+
+ return new Promise(resolve => {
+ let soFar = 0;
+ channel.onmessage = t.step_func(({ data: { i } }) => {
+ assert_in_array(i, [0, 1, 2], "Any message events must come from expected sources");
+ ++soFar;
+
+ if (soFar === 3) {
+ assert_equals(view[0], 1, "The first iframe must have set view[0] to 1");
+ assert_equals(view[1], 2, "The second iframe must have set view[1] to 2");
+ assert_equals(view[2], 3, "The third iframe must have set view[2] to 3");
+ resolve();
+ }
+ });
+
+ channel.postMessage({ sab });
+ });
+ });
+});
+
+function createIFrame(src) {
+ return new Promise((resolve, reject) => {
+ const iframe = document.createElement("iframe");
+ iframe.src = src;
+ iframe.onload = () => resolve(iframe);
+ iframe.onerror = () => reject(`iframe with URL ${src} failed to load`);
+ document.body.appendChild(iframe);
+ });
+}
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/identity-not-preserved.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/identity-not-preserved.https.html
new file mode 100644
index 0000000000..869f49043e
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/identity-not-preserved.https.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SharedArrayBuffers, when cloned, do not give back the same object</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#structuredserialize">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/test-sab.js"></script>
+
+<div id="log"></div>
+
+<script>
+"use strict";
+
+async_test(t => {
+ const testId = token();
+ const sab = new SharedArrayBuffer(1);
+ window.addEventListener("message", t.step_func(({ data }) => {
+ if (data.testId !== testId) {
+ return;
+ }
+
+ assertSABsHaveSameBackingBlock(sab, data.sab);
+
+ t.done();
+ }));
+
+ window.postMessage({ testId, sab }, "*");
+}, "postMessaging to this window does not give back the same SharedArrayBuffer (but does use the same backing block)");
+
+async_test(t => {
+ const testId = token();
+ const sab = new SharedArrayBuffer();
+ const worker = new Worker("../resources/echo-worker.js");
+
+ worker.addEventListener("message", t.step_func(({ data }) => {
+ if (data.testId !== testId) {
+ return;
+ }
+
+ assert_not_equals(data.sab, sab);
+ t.done();
+ }));
+
+ worker.postMessage({ testId, sab });
+}, "postMessaging to a worker and back does not give back the same SharedArrayBuffer");
+
+async_test(t => {
+ const testId = token();
+ const sab = new SharedArrayBuffer();
+ window.addEventListener("message", t.step_func(({ data }) => {
+ if (data.testId !== testId) {
+ return;
+ }
+
+ assert_not_equals(data.sab, sab);
+ t.done();
+ }));
+
+ const iframe = document.createElement("iframe");
+ iframe.onload = t.step_func(() => {
+ iframe.contentWindow.postMessage({ testId, sab }, "*");
+ });
+ iframe.src = "../resources/echo-iframe.html";
+ document.body.appendChild(iframe);
+}, "postMessaging to an iframe and back does not give back the same SharedArrayBuffer");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/identity-not-preserved.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/identity-not-preserved.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/identity-not-preserved.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-sharedworker-success.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-sharedworker-success.https.html
new file mode 100644
index 0000000000..dd221502b6
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-sharedworker-success.https.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+fetch_tests_from_worker(new SharedWorker("resources/nested-worker-success.js"));
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-sharedworker-success.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-sharedworker-success.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-sharedworker-success.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-worker-success.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-worker-success.https.html
new file mode 100644
index 0000000000..aeee3705ae
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-worker-success.https.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+fetch_tests_from_worker(new Worker("resources/nested-worker-success.js"));
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-worker-success.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-worker-success.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/nested-worker-success.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-coop-coep.https.any.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-coop-coep.https.any.js
new file mode 100644
index 0000000000..0db16fd6f7
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-coop-coep.https.any.js
@@ -0,0 +1,33 @@
+// META: global=window,worker
+
+test(() => {
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ assert_equals(globalThis.SharedArrayBuffer, undefined);
+ assert_false("SharedArrayBuffer" in globalThis);
+}, "SharedArrayBuffer constructor does not exist without COOP+COEP");
+
+test(() => {
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer;
+ const channel = new MessageChannel();
+ assert_throws_dom("DataCloneError", () => channel.port1.postMessage(sab));
+}, "SharedArrayBuffer over MessageChannel without COOP+COEP");
+
+test(() => {
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer;
+ const channel = new BroadcastChannel("Is mir egal");
+ assert_throws_dom("DataCloneError", () => channel.postMessage(sab));
+}, "SharedArrayBuffer over BroadcastChannel without COOP+COEP");
+
+if (self.GLOBAL.isWindow()) {
+ test(() => {
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer;
+ assert_throws_dom("DataCloneError", () => self.postMessage(sab));
+ }, "SharedArrayBuffer over postMessage() without COOP+COEP");
+}
+
+test(() => {
+ assert_false(self.crossOriginIsolated);
+}, "Bonus: self.crossOriginIsolated");
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-transferring.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-transferring.https.html
new file mode 100644
index 0000000000..dfa57fa200
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-transferring.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SharedArrayBuffers cannot be transferred</title>
+<link rel="help" href="https://html.spec.whatwg.org/#structuredclone">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+test(() => {
+ const sab = new SharedArrayBuffer();
+ assert_throws_dom("DataCloneError", () => window.postMessage(sab, "*", [sab]));
+ assert_throws_dom("DataCloneError", () => window.postMessage("test", "*", [sab]));
+}, "Trying to transfer a SharedArrayBuffer to this window throws");
+
+test(() => {
+ const sab = new SharedArrayBuffer();
+ const worker = new Worker("../resources/echo-worker.js");
+ assert_throws_dom("DataCloneError", () => worker.postMessage(sab, [sab]));
+ assert_throws_dom("DataCloneError", () => worker.postMessage("test", [sab]));
+}, "Trying to transfer a SharedArrayBuffer to a worker throws");
+
+test(() => {
+ const sab = new SharedArrayBuffer();
+ const channel = new MessageChannel();
+ assert_throws_dom("DataCloneError", () => channel.port1.postMessage(sab, [sab]));
+ assert_throws_dom("DataCloneError", () => channel.port1.postMessage("test", [sab]));
+}, "Trying to transfer a SharedArrayBuffer through a MessagePort throws");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-transferring.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-transferring.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/no-transferring.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/blank.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/blank.html
new file mode 100644
index 0000000000..eec1b2cc8e
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/blank.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<title>Used as a service worker-controlled iframe</title>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/blank.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/blank.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/blank.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-iframe.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-iframe.html
new file mode 100644
index 0000000000..02b9bcbbe8
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-iframe.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A test page that messes with a given SharedArrayBuffer sent from a BroadcastChannel</title>
+
+<script>
+"use strict";
+const query = new URLSearchParams(location.search);
+const channel = new BroadcastChannel(query.get("channel"));
+const i = Number(query.get("index"));
+
+channel.onmessage = e => {
+ const sab = e.data.sab;
+ if (sab === undefined) {
+ // We only care about "broadcasts" from the window
+ return;
+ }
+
+ const view = new Uint8Array(sab);
+ view[i] = i + 1;
+ channel.postMessage({ i });
+};
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-iframe.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-iframe.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-iframe.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-sharedworker.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-sharedworker.js
new file mode 100644
index 0000000000..310e0e9358
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-sharedworker.js
@@ -0,0 +1,7 @@
+const channel = new BroadcastChannel("anne was here");
+channel.onmessageerror = ({ data }) => {
+ if(data === null) {
+ channel.postMessage("sw-success");
+ }
+}
+channel.postMessage("hi");
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-sharedworker.js.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-sharedworker.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-sharedworker.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-worker.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-worker.js
new file mode 100644
index 0000000000..36369cde50
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-worker.js
@@ -0,0 +1,9 @@
+const channel = new BroadcastChannel("anne was here");
+channel.onmessage = ({ data }) => {
+ if(data === "hi" || data === "sw-success") {
+ return;
+ } else if(data instanceof SharedArrayBuffer) {
+ channel.postMessage("dw-success");
+ }
+}
+channel.postMessage("hi");
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-worker.js.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-worker.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/broadcastchannel-worker.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-domain-failure.sub.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-domain-failure.sub.html
new file mode 100644
index 0000000000..0cdb8b5f59
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-domain-failure.sub.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A test page that postMessage a SharedArrayBuffer to the parent and also sets document.domain</title>
+
+<script>
+"use strict";
+
+document.domain = "{{host}}";
+parent.postMessage({name: "domain", value: document.domain}, "*");
+parent.postMessage(
+ {name: "crossOriginIsolated", value: self.crossOriginIsolated}, "*");
+parent.postMessage(
+ {name: "hasSharedArrayBuffer", value: Boolean(self.SharedArrayBuffer)}, "*");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-domain-failure.sub.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-domain-failure.sub.html.headers
new file mode 100644
index 0000000000..56d0ac3428
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-domain-failure.sub.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: same-site
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-failure.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-failure.html
new file mode 100644
index 0000000000..2c33dba79a
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-failure.html
@@ -0,0 +1,3 @@
+<script>
+parent.postMessage(new SharedArrayBuffer(10), "*");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-failure.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-failure.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-failure.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-complex.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-complex.html
new file mode 100644
index 0000000000..1666a98458
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-complex.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+ const channel = new MessageChannel();
+ window.parent.postMessage({ state: "port1", data: channel.port1 }, '*', [channel.port1]);
+ window.onmessage = () => window.parent.postMessage({ state: "port2", data: channel.port2 }, '*', [channel.port2]);
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-complex.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-complex.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-complex.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-failure.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-failure.html
new file mode 100644
index 0000000000..c6896762bc
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-failure.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+ const channel = new MessageChannel();
+ window.parent.postMessage(channel.port2, '*', [channel.port2]);
+ channel.port1.onmessage = e => { alert(e.data); channel.port1.postMessage("message event received") };
+ channel.port1.onmessageerror = () => channel.port1.postMessage("messageerror event received");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-failure.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-failure.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-failure.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-site-failure.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-site-failure.html
new file mode 100644
index 0000000000..95a610f928
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-site-failure.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+ const channel = new MessageChannel();
+ window.parent.postMessage(channel.port2, '*', [channel.port2]);
+ channel.port1.onmessage = e => { channel.port1.postMessage("message event received") };
+ channel.port1.onmessageerror = () => channel.port1.postMessage("messageerror event received");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-site-failure.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-site-failure.html.headers
new file mode 100644
index 0000000000..56d0ac3428
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-messagechannel-site-failure.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: same-site
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe-messagechannel.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe-messagechannel.html
new file mode 100644
index 0000000000..6f3f284ae2
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe-messagechannel.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A test page that messes with a given SharedArrayBuffer via MessageChannel</title>
+<script src="test-incrementer.js"></script>
+
+<script>
+ const channel = new MessageChannel();
+ window.parent.postMessage(channel.port2, '*', [channel.port2]);
+ setupDestinationIncrementer(channel.port1, channel.port1);
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe-messagechannel.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe-messagechannel.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe-messagechannel.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe.html
new file mode 100644
index 0000000000..6f27ad7d5b
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A test page that messes with a given SharedArrayBuffer</title>
+<script src="test-incrementer.js"></script>
+
+<script>
+"use strict";
+
+setupDestinationIncrementer(self, parent, "*");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-iframe.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-popup.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-popup.html
new file mode 100644
index 0000000000..e583b5c416
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-popup.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A test page that messes with a given SharedArrayBuffer</title>
+<script src="test-incrementer.js"></script>
+
+<script>
+"use strict";
+
+setupDestinationIncrementer(self, opener, "*");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-popup.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-popup.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-popup.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker-with-channel.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker-with-channel.js
new file mode 100644
index 0000000000..c74fd26d3f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker-with-channel.js
@@ -0,0 +1,7 @@
+"use strict";
+importScripts("./test-incrementer.js");
+
+self.onmessage = ({ data }) => {
+ // data will be a MessagePort
+ setupDestinationIncrementer(data, data);
+};
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker-with-channel.js.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker-with-channel.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker-with-channel.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker.js
new file mode 100644
index 0000000000..5801bd2b97
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker.js
@@ -0,0 +1,4 @@
+"use strict";
+importScripts("./test-incrementer.js");
+
+setupDestinationIncrementer(self, self);
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker.js.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/incrementer-worker.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-1.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-1.html
new file mode 100644
index 0000000000..fe93cc0c4b
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Nesting level 1</title>
+
+<iframe src="nested-iframe-2.html"></iframe>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-1.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-1.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-1.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-2.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-2.html
new file mode 100644
index 0000000000..fad52ce9de
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-2.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Nesting level 2</title>
+
+<iframe src="nested-iframe-3.html"></iframe>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-2.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-2.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-2.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-3.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-3.html
new file mode 100644
index 0000000000..7971022b2c
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-3.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Nesting level 3</title>
+
+<iframe src="nested-iframe-4-incrementer.html"></iframe>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-3.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-3.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-3.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-4-incrementer.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-4-incrementer.html
new file mode 100644
index 0000000000..d374515bdc
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-4-incrementer.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>A test page that messes with a given SharedArrayBuffer, nested 4 levels deep in iframes</title>
+<script src="test-incrementer.js"></script>
+
+<script>
+"use strict";
+
+setupDestinationIncrementer(self, parent.parent.parent.parent.parent, "*");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-4-incrementer.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-4-incrementer.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-iframe-4-incrementer.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-worker-success.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-worker-success.js
new file mode 100644
index 0000000000..ffc3708acb
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-worker-success.js
@@ -0,0 +1,14 @@
+importScripts("/resources/testharness.js");
+importScripts("test-incrementer.js");
+
+promise_test(t => {
+ const worker = new Worker("incrementer-worker.js");
+
+ return testSharingViaIncrementerScript(t, worker, "parent worker", worker, "sub-worker");
+}, "postMessaging to a dedicated sub-worker allows them to see each others' modifications");
+
+test(() => {
+ assert_true(self.crossOriginIsolated);
+}, "Bonus: self.crossOriginIsolated");
+
+done();
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-worker-success.js.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-worker-success.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/nested-worker-success.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/serviceworker-failure.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/serviceworker-failure.js
new file mode 100644
index 0000000000..4e8fba636c
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/serviceworker-failure.js
@@ -0,0 +1,33 @@
+"use strict";
+self.importScripts("/resources/testharness.js");
+
+let state = "start in worker";
+
+self.onmessage = e => {
+ if (e.data === "start in window") {
+ assert_equals(state, "start in worker");
+ e.source.postMessage(state);
+ state = "we are expecting a messageerror due to the window sending us a SAB";
+ } else if (e.data === "we are expecting a messageerror due to the worker sending us a SAB") {
+ assert_equals(state, "onmessageerror was received in worker");
+ e.source.postMessage(new SharedArrayBuffer());
+ state = "done in worker";
+ } else {
+ e.source.postMessage(`worker onmessage was reached when in state "${state}" and data ${e.data}`);
+ }
+};
+
+self.onmessageerror = e => {
+ if (state === "we are expecting a messageerror due to the window sending us a SAB") {
+ assert_equals(e.constructor.name, "ExtendableMessageEvent", "type");
+ assert_equals(e.data, null, "data");
+ assert_equals(e.origin, self.origin, "origin");
+ assert_not_equals(e.source, null, "source");
+ assert_equals(e.ports.length, 0, "ports length");
+
+ state = "onmessageerror was received in worker";
+ e.source.postMessage(state);
+ } else {
+ e.source.postMessage(`worker onmessageerror was reached when in state "${state}" and data ${e.data}`);
+ }
+};
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/serviceworker-failure.js.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/serviceworker-failure.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/serviceworker-failure.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/sharedworker-failure.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/sharedworker-failure.js
new file mode 100644
index 0000000000..8472318abd
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/sharedworker-failure.js
@@ -0,0 +1,18 @@
+let state = "send-sw-failure"
+onconnect = initialE => {
+ let port = initialE.source;
+ port.postMessage(state)
+ port.onmessage = e => {
+ if(state === "" && e.data === "send-window-failure") {
+ port.postMessage(new SharedArrayBuffer())
+ } else {
+ port.postMessage("failure")
+ }
+ }
+ port.onmessageerror = e => {
+ if(state === "send-sw-failure") {
+ port.postMessage("send-sw-failure-success")
+ state = ""
+ }
+ }
+}
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/sharedworker-failure.js.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/sharedworker-failure.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/sharedworker-failure.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/test-incrementer.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/test-incrementer.js
new file mode 100644
index 0000000000..9c3fb813ae
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/test-incrementer.js
@@ -0,0 +1,80 @@
+"use strict";
+
+self.getViewValue = (view, index) => {
+ if(view instanceof DataView) {
+ return view.getInt8(index);
+ }
+ return view[index];
+};
+
+self.setViewValue = (view, index, value) => {
+ if(view instanceof DataView) {
+ view.setInt8(index, value);
+ } else {
+ view[index] = value;
+ }
+};
+
+self.maybeBigInt = (view, value) => {
+ if (view.constructor.name === "BigInt64Array" || view.constructor.name === "BigUint64Array") {
+ return BigInt(value);
+ }
+ return value;
+};
+
+self.testSharingViaIncrementerScript = (t, whereToListen, whereToListenLabel, whereToSend, whereToSendLabel, origin, type = "Int32Array") => {
+ return new Promise(resolve => {
+ const sab = new SharedArrayBuffer(8);
+ const view = new self[type](sab);
+ setViewValue(view, 0, maybeBigInt(view, 1));
+
+ whereToListen.onmessage = t.step_func(({ data }) => {
+ switch (data.message) {
+ case "initial payload received": {
+ assert_equals(data.value, maybeBigInt(view, 1), `The ${whereToSendLabel} must see the same value in the SharedArrayBuffer`);
+ break;
+ }
+
+ case "changed to 2": {
+ assert_equals(getViewValue(view, 0), maybeBigInt(view, 2), `The ${whereToListenLabel} must see changes made in the ${whereToSendLabel}`);
+
+ setViewValue(view, 0, maybeBigInt(view, 3));
+ whereToSend.postMessage({ message: "changed to 3" }, origin);
+
+ break;
+ }
+
+ case "changed to 3 received": {
+ assert_equals(data.value, maybeBigInt(view, 3), `The ${whereToSendLabel} must see changes made in the ${whereToListenLabel}`);
+ resolve();
+ break;
+ }
+ }
+ });
+
+ whereToSend.postMessage({ message: "initial payload", view }, origin);
+ });
+};
+
+self.setupDestinationIncrementer = (whereToListen, whereToSendBackTo, origin) => {
+ let view;
+ whereToListen.onmessage = ({ data }) => {
+ switch (data.message) {
+ case "initial payload": {
+ view = data.view;
+ whereToSendBackTo.postMessage({ message: "initial payload received", value: getViewValue(view, 0) }, origin);
+
+ setViewValue(view, 0, maybeBigInt(view, 2));
+ whereToSendBackTo.postMessage({ message: "changed to 2" }, origin);
+
+ break;
+ }
+
+ case "changed to 3": {
+ whereToSendBackTo.postMessage({ message: "changed to 3 received", value: getViewValue(view, 0) }, origin);
+
+ break;
+ }
+ }
+ };
+};
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/test-sab.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/test-sab.js
new file mode 100644
index 0000000000..6d6efda00d
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/test-sab.js
@@ -0,0 +1,15 @@
+"use strict";
+
+self.assertSABsHaveSameBackingBlock = (originalSAB, clonedSAB) => {
+ const originalView = new Uint8Array(originalSAB);
+ const clonedView = new Uint8Array(clonedSAB);
+
+ assert_not_equals(originalSAB, clonedSAB, "the clone must not be the same object");
+
+ assert_equals(originalView[0], 0, "originalView[0] starts 0");
+ assert_equals(clonedView[0], 0, "clonedView[0] starts 0");
+
+ originalView[0] = 5;
+ assert_equals(originalView[0], 5, "originalView[0] ends up 5");
+ assert_equals(clonedView[0], 5, "clonedView[0] ends up 5");
+};
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-history.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-history.https.html
new file mode 100644
index 0000000000..28859f17cb
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-history.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SharedArrayBuffers cloning via history's methods invoking StructuredSerializeForStorage</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#structuredserializeforstorage">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-history-pushstate">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dom-history-replacestate">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script>
+"use strict";
+
+for (const method of ["pushState", "replaceState"]) {
+ test(() => {
+ assert_throws_dom("DataCloneError", () => {
+ history[method](new SharedArrayBuffer(), "dummy title");
+ });
+ }, `history.${method}(): simple case`);
+
+ test(() => {
+ let getter1Called = false;
+ let getter2Called = false;
+ assert_throws_dom("DataCloneError", () => {
+ history[method]([
+ { get x() { getter1Called = true; return 5; } },
+ new SharedArrayBuffer(),
+ { get x() { getter2Called = true; return 5; } }
+ ], "dummy title");
+ });
+
+ assert_true(getter1Called, "The getter before the SAB must have been called");
+ assert_false(getter2Called, "The getter after the SAB must not have been called");
+ }, `history.${method}(): is interleaved correctly`);
+}
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-history.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-history.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-history.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-idb.any.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-idb.any.js
new file mode 100644
index 0000000000..e317b150cc
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-idb.any.js
@@ -0,0 +1,44 @@
+// META: script=/IndexedDB/resources/support.js
+"use strict";
+
+async_test(t => {
+ const openReq = createdb(t);
+
+ openReq.onupgradeneeded = e => {
+ const db = e.target.result;
+ const store = db.createObjectStore("store", { keyPath: "key" });
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer;
+
+ assert_throws_dom("DataCloneError", () => {
+ store.put({ key: 1, property: sab });
+ });
+ t.done();
+ };
+}, "SharedArrayBuffer cloning via IndexedDB: basic case");
+
+async_test(t => {
+ const openReq = createdb(t);
+
+ openReq.onupgradeneeded = e => {
+ const db = e.target.result;
+ const store = db.createObjectStore("store", { keyPath: "key" });
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer;
+
+ let getter1Called = false;
+ let getter2Called = false;
+
+ assert_throws_dom("DataCloneError", () => {
+ store.put({ key: 1, property: [
+ { get x() { getter1Called = true; return 5; } },
+ sab,
+ { get x() { getter2Called = true; return 5; } }
+ ]});
+ });
+
+ assert_true(getter1Called, "The getter before the SAB must have been called");
+ assert_false(getter2Called, "The getter after the SAB must not have been called");
+ t.done();
+ };
+}, "SharedArrayBuffer cloning via the IndexedDB: is interleaved correctly");
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-notifications-api.any.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-notifications-api.any.js
new file mode 100644
index 0000000000..4c1c1fdabb
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/serialization-via-notifications-api.any.js
@@ -0,0 +1,28 @@
+"use strict";
+
+test(() => {
+ assert_throws_dom("DataCloneError", () => {
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer;
+ new Notification("Bob: Hi", { data: sab });
+ })
+}, "SharedArrayBuffer cloning via the Notifications API's data member: basic case");
+
+test(() => {
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ const sab = new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer;
+
+ let getter1Called = false;
+ let getter2Called = false;
+
+ assert_throws_dom("DataCloneError", () => {
+ new Notification("Bob: Hi", { data: [
+ { get x() { getter1Called = true; return 5; } },
+ sab,
+ { get x() { getter2Called = true; return 5; } }
+ ]});
+ });
+
+ assert_true(getter1Called, "The getter before the SAB must have been called");
+ assert_false(getter2Called, "The getter after the SAB must not have been called");
+}, "SharedArrayBuffer cloning via the Notifications API's data member: is interleaved correctly");
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-domain-failure.https.sub.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-domain-failure.https.sub.html
new file mode 100644
index 0000000000..6fa196e094
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-domain-failure.https.sub.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Structured cloning of SharedArrayBuffers into same-origin-domain windows</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#structuredserialize">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+"use strict";
+document.domain = "{{host}}";
+
+async_test(t => {
+ const iframe = document.createElement("iframe");
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = "//{{domains[www1]}}:{{location[port]}}/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/resources/iframe-domain-failure.sub.html";
+ let domain;
+ let childCrossOriginIsolated;
+ window.onmessage = t.step_func((e) => {
+ if (e.data.name === "domain") {
+ domain = e.data.value;
+ return;
+ }
+ if (e.data.name === "crossOriginIsolated") {
+ childCrossOriginIsolated = e.data.value;
+ return;
+ }
+ if (e.data.name === "hasSharedArrayBuffer") {
+ const hasSharedArrayBuffer = e.data.value;
+
+ // document.domain mutation is no-op because the surrounding agent
+ // cluster's cross-origin isolated is true.
+ assert_equals(domain, "{{domains[www1]}}");
+
+ // crossOriginIsolated is false in the nested frame because the frame is
+ // cross-origin and hence the cross-origin isolated capability is false.
+ // We use assert_equals instead of assert_false here to see if
+ // `childCrossOriginIsolated` is set.
+ assert_equals(childCrossOriginIsolated, false);
+
+ assert_false(hasSharedArrayBuffer);
+ t.done();
+ return;
+ }
+ assert_unreached("Got a message event, expected a messageerror event");
+ });
+ document.body.append(iframe);
+}, "SharedArrayBuffer and a same-origin-domain (but not same-origin) iframe");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-domain-failure.https.sub.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-domain-failure.https.sub.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-domain-failure.https.sub.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-failure.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-failure.https.html
new file mode 100644
index 0000000000..203a9f637f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-failure.https.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<div id=log></div>
+<script>
+async_test(t => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ frame.src = get_host_info().HTTPS_NOTSAMESITE_ORIGIN + new URL("resources/iframe-failure.html", location).pathname;
+ window.onmessage = t.unreached_func("Got a message event, expected a messageerror event");
+ window.onmessageerror = t.step_func_done();
+ document.body.append(frame);
+}, "SharedArrayBuffer and a cross-site <iframe>");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-failure.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-failure.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-failure.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-iframe-messagechannel.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-iframe-messagechannel.https.html
new file mode 100644
index 0000000000..acef65cbdf
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-iframe-messagechannel.https.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Structured cloning of SharedArrayBuffers into windows using MessageChannel</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#structuredserialize">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-incrementer.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<div id="log"></div>
+
+<script>
+promise_test(t => {
+ return new Promise(resolve => {
+ const iframe = document.createElement("iframe");
+ window.onmessage = t.step_func((message) => {
+ // data will be a MessagePort
+ resolve(testSharingViaIncrementerScript(t, message.data, "window", message.data, "iframe"));
+ });
+ iframe.src = "resources/incrementer-iframe-messagechannel.html";
+ document.body.appendChild(iframe);
+ });
+}, `postMessaging to a same-origin iframe via MessageChannel allows them to see each others' modifications`);
+
+promise_test(t => {
+ return new Promise(resolve => {
+ const iframe = document.createElement("iframe");
+ window.onmessage = t.step_func((message) => {
+ // data will be a MessagePort
+ message.data.postMessage(new SharedArrayBuffer(10));
+ message.data.onmessage = t.step_func(message => {
+ assert_equals(message.data, "messageerror event received");
+ resolve();
+ });
+ });
+ iframe.src = get_host_info().HTTPS_REMOTE_ORIGIN + new URL("resources/iframe-messagechannel-site-failure.html", window.location).pathname;
+ document.body.appendChild(iframe);
+ });
+}, "postMessaging to a same-site iframe via MessageChannel should fail");
+
+promise_test(t => {
+ return new Promise(resolve => {
+ const iframe = document.createElement("iframe");
+ window.onmessage = t.step_func(message => {
+ // data will be a MessagePort
+ message.data.postMessage(new SharedArrayBuffer(10));
+ message.data.onmessage = t.step_func(message => {
+ assert_equals(message.data, "messageerror event received");
+ resolve();
+ });
+ });
+ iframe.src = get_host_info().HTTPS_NOTSAMESITE_ORIGIN + new URL("resources/iframe-messagechannel-failure.html", window.location).pathname;
+ document.body.append(iframe);
+ });
+}, "postMessaging to a cross-site iframe via MessageChannel should fail");
+
+promise_test(t => {
+ return new Promise(resolve => {
+ const iframe = document.createElement("iframe");
+ let port = null;
+ window.onmessage = t.step_func(message => {
+ if (message.data.state === "port1") {
+ port = message.data.data;
+ port.postMessage(new SharedArrayBuffer(10));
+ message.source.postMessage("send port2", "*");
+ } else if (message.data.state === "port2") {
+ // Note that onmessage calls start()
+ message.data.data.onmessage = t.step_func(message => {
+ assert_true(message.data instanceof SharedArrayBuffer);
+ assert_equals(message.data.byteLength, 10);
+ resolve();
+ });
+ message.data.data.onmessageerror = t.unreached_func();
+ }
+ });
+ iframe.src = get_host_info().HTTPS_NOTSAMESITE_ORIGIN + new URL("resources/iframe-messagechannel-complex.html", window.location).pathname;
+ document.body.append(iframe);
+ });
+}, "postMessaging with a MessageChannel that's been cross-site should succeed");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-iframe-messagechannel.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-iframe-messagechannel.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-iframe-messagechannel.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-messagechannel-success.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-messagechannel-success.https.html
new file mode 100644
index 0000000000..cd67e5b2c9
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-messagechannel-success.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Structured cloning of SharedArrayBuffers using MessageChannel</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#structuredserialize">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-incrementer.js"></script>
+
+<div id="log"></div>
+
+<script>
+"use strict";
+
+promise_test(t => {
+ const worker = new Worker("resources/incrementer-worker-with-channel.js");
+ const channel = new MessageChannel();
+ worker.postMessage(channel.port2, [channel.port2]);
+
+ return testSharingViaIncrementerScript(t, channel.port1, "window", channel.port1, "worker");
+}, "postMessaging to a dedicated worker via MessageChannel allows them to see each others' modifications");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-messagechannel-success.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-messagechannel-success.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-messagechannel-success.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-serviceworker-failure.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-serviceworker-failure.https.html
new file mode 100644
index 0000000000..373359de85
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-serviceworker-failure.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>SharedArrayBuffer cannot cross agent clusters, service worker edition</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#structuredserialize">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<script>
+"use strict";
+promise_test(t => {
+ const scope = "resources/blank.html";
+ return service_worker_unregister_and_register(t, "resources/serviceworker-failure.js", scope)
+ .then(reg => {
+ t.add_cleanup(() => service_worker_unregister(t, scope));
+ return wait_for_state(t, reg.installing, "activated");
+ })
+ .then(() => with_iframe(scope))
+ .then(iframe => {
+ t.add_cleanup(() => iframe.remove());
+ const sw = iframe.contentWindow.navigator.serviceWorker;
+ let state = "start in window";
+
+ return new Promise(resolve => {
+ sw.onmessage = t.step_func(e => {
+ if (e.data === "start in worker") {
+ assert_equals(state, "start in window");
+ sw.controller.postMessage(new SharedArrayBuffer());
+ state = "we are expecting confirmation of an onmessageerror in the worker";
+ } else if (e.data === "onmessageerror was received in worker") {
+ assert_equals(state, "we are expecting confirmation of an onmessageerror in the worker");
+ state = "we are expecting a messageerror due to the worker sending us a SAB";
+ sw.controller.postMessage(state);
+ } else {
+ assert_unreached("Got an unexpected message from the service worker: " + e.data);
+ }
+ });
+
+ sw.onmessageerror = t.step_func(e => {
+ assert_equals(state, "we are expecting a messageerror due to the worker sending us a SAB");
+
+ assert_equals(e.data, null, "data");
+ assert_equals(e.origin, self.origin, "origin");
+ assert_not_equals(e.source, null, "source");
+ assert_equals(e.ports.length, 0, "ports length");
+
+ state = "done in window";
+ resolve();
+ });
+
+ sw.controller.postMessage(state);
+ });
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-serviceworker-failure.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-serviceworker-failure.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-serviceworker-failure.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-sharedworker-failure.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-sharedworker-failure.https.html
new file mode 100644
index 0000000000..023cb5acde
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-sharedworker-failure.https.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<title>SharedArrayBuffer cannot cross agent clusters, shared worker edition</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+async_test(t => {
+ const sw = new SharedWorker("resources/sharedworker-failure.js")
+ let state = ""
+ sw.port.onmessage = t.step_func(e => {
+ if(e.data === "send-sw-failure") {
+ sw.port.postMessage(new SharedArrayBuffer())
+ } else if(e.data === "send-sw-failure-success") {
+ state = "send-window-failure"
+ sw.port.postMessage(state)
+ } else {
+ assert_unreached()
+ }
+ })
+ sw.port.onmessageerror = t.step_func(e => {
+ if(state === "send-window-failure") {
+ assert_equals(e.data, null, "data")
+ assert_equals(e.origin, "", "origin")
+ assert_equals(e.source, null, "source")
+ assert_equals(e.ports.length, 0, "ports length")
+ t.done()
+ } else {
+ assert_unreached()
+ }
+ })
+})
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-sharedworker-failure.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-sharedworker-failure.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-sharedworker-failure.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-simple-success.https.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-simple-success.https.html
new file mode 100644
index 0000000000..c9b41d0a0d
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-simple-success.https.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Structured cloning of SharedArrayBuffers: simple success cases that don't need dedicated files</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#structuredserialize">
+<link rel="author" title="Domenic Denicola" href="mailto:d@domenic.me">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/test-incrementer.js"></script>
+
+<div id="log"></div>
+
+<script>
+"use strict";
+
+[
+ "DataView",
+ "Int8Array",
+ "Uint8Array",
+ "Uint8ClampedArray",
+ "Int16Array",
+ "Uint16Array",
+ "Int32Array",
+ "Uint32Array",
+ "BigInt64Array",
+ "BigUint64Array",
+ "Float32Array",
+ "Float64Array"
+].forEach(type => {
+ promise_test(t => {
+ const worker = new Worker("resources/incrementer-worker.js");
+
+ return testSharingViaIncrementerScript(t, worker, "window", worker, "worker", undefined, type);
+ }, "postMessaging to a dedicated worker allows them to see each others' modifications with " + type);
+});
+
+promise_test(t => {
+ return new Promise(resolve => {
+ const iframe = document.createElement("iframe");
+ iframe.onload = t.step_func(() => {
+ resolve(testSharingViaIncrementerScript(t, window, "window", iframe.contentWindow, "iframe", "*"));
+ });
+ iframe.src = "resources/incrementer-iframe.html";
+ document.body.appendChild(iframe);
+ });
+}, "postMessaging to a same-origin iframe allows them to see each others' modifications");
+
+promise_test(t => {
+ return new Promise(resolve => {
+ const iframe = document.createElement("iframe");
+ iframe.onload = t.step_func(() => {
+ const level1 = iframe.contentWindow;
+ const level2 = level1.frames[0];
+ const level3 = level2.frames[0];
+ const targetWindow = level3.frames[0];
+ resolve(testSharingViaIncrementerScript(t, window, "window", targetWindow, "nested iframe", "*"));
+ });
+ iframe.src = "resources/nested-iframe-1.html";
+ document.body.appendChild(iframe);
+ });
+}, "postMessaging to a same-origin deeply-nested iframe allows them to see each others' modifications");
+
+promise_test(t => {
+ return new Promise(resolve => {
+ const w = window.open("resources/incrementer-popup.html");
+ w.onload = t.step_func(() => {
+ resolve(testSharingViaIncrementerScript(t, window, "window", w, "popup window", "*").then(() => {
+ w.close();
+ }));
+ });
+ });
+}, "postMessaging to a same-origin opened window allows them to see each others' modifications");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-simple-success.https.html.headers b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-simple-success.https.html.headers
new file mode 100644
index 0000000000..63b60e490f
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-simple-success.https.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-extra.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-extra.html
new file mode 100644
index 0000000000..5ff10cbc10
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-extra.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Structured cloning of Error objects: extra tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- Most tests are in the general framework in structuredclone_0.html.
+ This contains specialty tests that don't fit into that framework. -->
+
+<body>
+
+<script>
+"use strict";
+test(t => {
+ const exceptionToThrow = new Error("throw me!");
+
+ const badError = new Error();
+ Object.defineProperty(badError, "name", { get() { throw exceptionToThrow; } });
+
+ const worker = new Worker("./resources/echo-worker.js");
+ t.add_cleanup(() => worker.terminate());
+
+ assert_throws_exactly(exceptionToThrow, () => {
+ worker.postMessage(badError);
+ });
+}, "Throwing name getter fails serialization");
+
+// https://bugs.chromium.org/p/chromium/issues/detail?id=1030086
+// https://github.com/whatwg/html/pull/5150
+async_test(t => {
+ window.onmessage = t.step_func_done(e => {
+ assert_equals(e.data.name, "TypeError");
+ });
+
+ const iframe = document.createElement("iframe");
+ iframe.onload = () => {
+ if (iframe.contentWindow.location === "about:blank") {
+ return;
+ }
+
+ iframe.contentWindow.doIt();
+ };
+ iframe.src = "resources/post-parent-type-error.html";
+ document.body.append(iframe);
+}, "Errors sent across realms should preserve their type");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js
new file mode 100644
index 0000000000..cbc6a73d51
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js
@@ -0,0 +1,106 @@
+// META: script=/common/utils.js
+
+// .stack properties on errors are unspecified, but are present in most
+// browsers, most of the time. https://github.com/tc39/proposal-error-stacks/ tracks standardizing them.
+// Tests will pass automatically if the .stack property isn't present.
+
+stackTests(() => {
+ return new Error('some message');
+}, 'page-created Error');
+
+stackTests(() => {
+ return new DOMException('InvalidStateError', 'some message');
+}, 'page-created DOMException');
+
+stackTests(() => {
+ try {
+ Object.defineProperty();
+ } catch (e) {
+ return e;
+ }
+}, 'JS-engine-created TypeError');
+
+stackTests(() => {
+ try {
+ HTMLParagraphElement.prototype.align;
+ } catch (e) {
+ return e;
+ }
+}, 'web API-created TypeError');
+
+stackTests(() => {
+ try {
+ document.createElement('');
+ } catch (e) {
+ return e;
+ }
+}, 'web API-created DOMException');
+
+function stackTests(errorFactory, description) {
+ test(t => {
+ const error = errorFactory();
+ const originalStack = error.stack;
+
+ if (!originalStack) {
+ return;
+ }
+
+ const clonedError = structuredClone(error);
+ assert_equals(clonedError.stack, originalStack);
+ }, description + ' (structuredClone())');
+
+ async_test(t => {
+ const error = errorFactory();
+ const originalStack = error.stack;
+
+ if (!originalStack) {
+ t.done();
+ return;
+ }
+
+ const worker = new Worker('resources/echo-worker.js');
+ worker.onmessage = t.step_func_done(e => {
+ assert_equals(e.data.stack, originalStack);
+ });
+
+ worker.postMessage(error);
+ }, description + ' (worker)');
+
+ let iframeTest = (t, url) => {
+ const thisTestId = token();
+
+ const error = errorFactory();
+ const originalStack = error.stack;
+
+ if (!originalStack) {
+ t.done();
+ return;
+ }
+
+ const iframe = document.createElement('iframe');
+ window.addEventListener('message', t.step_func(e => {
+ if (e.data.testId === thisTestId) {
+ assert_equals(e.data.error.stack, originalStack);
+ t.done();
+ }
+ }));
+
+ iframe.onload = t.step_func(() => {
+ iframe.contentWindow.postMessage({ error, testId: thisTestId }, "*");
+ });
+
+ iframe.src = url;
+ document.body.append(iframe);
+ }
+
+ async_test(t => {
+ const crossSiteURL = new URL('resources/echo-iframe.html', location.href);
+ crossSiteURL.hostname = '{{hosts[alt][www1]}}';
+ iframeTest(t, crossSiteURL);
+ }, description + ' (cross-site iframe)');
+
+ async_test(t => {
+ const sameOriginURL = new URL('resources/echo-iframe.html', location.href);
+ iframeTest(t, sameOriginURL);
+ }, description + ' (same-origin iframe)')
+}
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html
new file mode 100644
index 0000000000..c8a6d38393
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html
@@ -0,0 +1,637 @@
+<!doctype html>
+<html>
+ <head>
+ <meta content="text/html; charset=utf-8" http-equiv="content-type" />
+ <title>2.8 Common DOM interfaces - Structured Clone Algorithm </title>
+ <link rel="help" href="http://www.w3.org/TR/html5/common-dom-interfaces.html#safe-passing-of-structured-data" />
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <div id="log"></div>
+ <iframe></iframe> <!-- used for grabbing an URIError from another realm -->
+
+<script type="text/javascript">
+ var worker;
+ var testCollection;
+ setup(function()
+ {
+ //the worker is used for each test in sequence
+ //worker's callback will be set for each test
+ //worker's internal onmessage echoes the data back to this thread through postMessage
+ worker = new Worker("./resources/echo-worker.js");
+ testCollection = [
+ function() {
+ var t = async_test("Primitive string is cloned");
+ t.id = 0;
+ worker.onmessage = t.step_func(function(e) {assert_equals("primitive string", e.data, "\"primitive string\" === event.data"); t.done(); });
+ t.step(function() { worker.postMessage("primitive string");});
+ },
+ function() {
+ var t = async_test("Primitive integer is cloned");
+ t.id = 1;
+ worker.onmessage = t.step_func(function(e) {assert_equals(2000, e.data, "2000 === event.data"); t.done(); });
+ t.step(function() { worker.postMessage(2000);});
+ },
+ function() {
+ var t = async_test("Primitive floating point is cloned");
+ t.id = 2;
+ worker.onmessage = t.step_func(function(e) {assert_equals(111.456, e.data, "111.456 === event.data"); t.done(); });
+ t.step(function() { worker.postMessage(111.456);});
+ },
+ function() {
+ var t = async_test("Primitive floating point (negative) is cloned");
+ t.id = 3;
+ worker.onmessage = t.step_func(function(e) {assert_equals(-111.456, e.data, "-111.456 === event.data"); t.done(); });
+ t.step(function() { worker.postMessage(-111.456);});
+ },
+ function() {
+ var t = async_test("Primitive number (hex) is cloned");
+ t.id = 4;
+ worker.onmessage = t.step_func(function(e) {assert_equals(0xAB25, e.data, "0xAB25 === event.data"); t.done(); });
+ t.step(function() { worker.postMessage(0xAB25);});
+ },
+ function() {
+ var t = async_test("Primitive number (scientific) is cloned");
+ t.id = 5;
+ worker.onmessage = t.step_func(function(e) {assert_equals(15e2, e.data, "15e2 === event.data"); t.done(); });
+ t.step(function() { worker.postMessage(15e2);});
+ },
+ function() {
+ var t = async_test("Primitive boolean is cloned");
+ t.id = 6;
+ worker.onmessage = t.step_func(function(e) {assert_equals(false, e.data, "false === event.data"); t.done(); });
+ t.step(function() { worker.postMessage(false);});
+ },
+ function() {
+ var t = async_test("Instance of Boolean is cloned");
+ t.id = 7;
+ var obj;
+ t.step(function() {obj = new Boolean(false);});
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "Boolean === event.data.constructor");
+ assert_equals(obj.valueOf(), e.data.valueOf(), "(new Boolean(false)).valueof() === event.data.valueOf()");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },function() {
+ var t = async_test("Instance of Number is cloned");
+ t.id = 8;
+ var obj;
+ t.step(function() {obj = new Number(2000);});
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "Number === event.data.constructor");
+ assert_equals(obj.valueOf(), e.data.valueOf(), "(new Number(2000)).valueof() === event.data.valueOf()");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Instance of String is cloned");
+ t.id = 9;
+ var obj;
+ t.step(function() { obj = new String("String Object");});
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "String === event.data.constructor");
+ assert_equals(obj.valueOf(), e.data.valueOf(), "(new String(\"String Object\")).valueof() === event.data.valueOf()");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Instance of Date is cloned");
+ t.id = 10;
+ var obj;
+ t.step(function() { obj= new Date(2011,1,1);});
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "Date === event.data.constructor");
+ assert_equals(obj.valueOf(), e.data.valueOf(), "(new Date(2011,1,1)).valueof() === event.data.valueOf()");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Instance of RegExp is cloned");
+ t.id = 11;
+ var obj;
+ t.step(function() {obj = new RegExp("w3+c","g","i");});
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "RegExp === event.data.constructor");
+ assert_equals(obj.source, e.data.source, "canon.source === event.data.source");
+ assert_equals(obj.multiline, e.data.multiline, "canon.multiline === event.data.multiline");
+ assert_equals(obj.global, e.data.global, "canon.global === event.data.global");
+ assert_equals(obj.ignoreCase, e.data.ignoreCase, "canon.ignoreCase === event.data.ignoreCase");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Value 'null' is cloned");
+ t.id = 12;
+ worker.onmessage = t.step_func(function(e) {assert_equals(null, e.data, "null === event.data"); t.done(); });
+ t.step(function() { worker.postMessage(null);});
+ },
+ function() {
+ var t = async_test("Value 'undefined' is cloned");
+ t.id = 13;
+ worker.onmessage = t.step_func(function(e) {assert_equals(undefined, e.data, "undefined === event.data"); t.done(); });
+ t.step(function() { worker.postMessage(undefined);});
+ },
+ function() {
+ var t = async_test("Object properties are cloned");
+ t.id = 14;
+ var obj;
+ t.step(function() {
+ obj= {};
+ obj.a = "test";
+ obj.b = 2;
+ obj["child"] = 3;
+ });
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor");
+ assert_equals(obj.a, e.data.a, "canon.a === event.data.a");
+ assert_equals(obj.b, e.data.b, "canon.b === event.data.b");
+ assert_equals(obj.child, e.data.child, "canon.child === e.data.child");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Prototype chains are not walked.");
+ t.id = 15;
+ function Custom() {
+ this.a = "hello";
+ }
+
+ var obj;
+ t.step(function() {
+ Object.defineProperty(Custom.prototype, "b", { enumerable: true, value: 100 });
+ obj = new Custom();
+ });
+ worker.onmessage = t.step_func(function(e) {
+ assert_not_equals(obj.constructor, e.data.constructor, "canon.constructor !== event.data.constructor");
+ assert_equals(Object, e.data.constructor, "Object === e.data.constructor");
+ assert_equals(obj.a, e.data.a, "canon.a === e.data.a");
+ assert_equals(undefined, e.data.b, "undefined === e.data.b");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Property descriptors of Objects are not cloned");
+ t.id = 16;
+ var obj;
+ t.step(function() {
+ obj = {};
+ Object.defineProperty(obj, "a", { enumerable: true, writable: false, value: 100 });
+ });
+ worker.onmessage = t.step_func(function(e) {
+ var des = Object.getOwnPropertyDescriptor(e.data, "a");
+ assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor");
+ assert_true(des.writable, "Descriptor is writable");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Cycles are preserved in Objects");
+ t.id = 17;
+ var obj;
+ t.step(function() {
+ obj = {};
+ obj.a = obj;
+ });
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor");
+ assert_equals(e.data, e.data.a, "cycle is preserved");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Identity of duplicates is preserved");
+ t.id = 18;
+ var ref;
+ var obj;
+ t.step(function() {
+ ref = {};
+ ref.called = 0;
+ Object.defineProperty(ref, "child", {get: function(){this.called++;}, enumerable: true});
+
+ obj = {a:ref, b:ref};
+ });
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor");
+ assert_equals(e.data.b.called, 0, "e.data.b.called === 0");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Property order is preserved");
+ t.id = 19;
+ var obj;
+ t.step(function() {
+ obj = { "a": "hello", "b": "w3c", "c": "and world" };
+ obj["a"] = "named1";
+ });
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor");
+ var canonNames = Object.getOwnPropertyNames(obj);
+ var testNames = Object.getOwnPropertyNames(e.data);
+ for (var i in canonNames) {
+ assert_equals(canonNames[i], testNames[i], "canonProperty["+i+"] === dataProperty["+i+"]");
+ }
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Enumerable properties of Arrays are cloned");
+ t.id = 20;
+ var obj;
+ t.step(function() {
+ obj = [0,1];
+ obj["a"] = "named1";
+ });
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor");
+ assert_equals(e.data["a"], "named1", "e.data[\"a\"] === \"named1\"");
+ assert_equals(e.data[0], 0, "e.data[0] === 0");
+ assert_equals(e.data[1], 1, "e.data[1] === 1");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Property descriptors of Arrays are not cloned");
+ t.id = 21;
+ var obj;
+ t.step(function() {
+ obj = [0, 1];
+ Object.defineProperty(obj, "2", { enumerable: true, writable: false, value: 100 });
+ });
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor");
+ assert_equals(e.data[0], 0, "e.data[0] === 0");
+ assert_equals(e.data[1], 1, "e.data[1] === 1");
+ var des = Object.getOwnPropertyDescriptor(e.data, "2");
+ assert_true(des.writable, "Descriptor is writable");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Cycles are preserved in Arrays");
+ t.id = 22;
+ var obj;
+ t.step(function() {
+ obj = [0,1];
+ obj[2] = obj;
+ });
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor");
+ assert_equals(e.data[0], 0, "e.data[0] === 0");
+ assert_equals(e.data[1], 1, "e.data[1] === 1");
+ assert_equals(e.data[2], e.data, "e.data[2] === e.data");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+
+ function() {
+ var t = async_test("ImageData object can be cloned");
+ t.id = 23;
+ var obj;
+ t.step(function() {
+ var canvas = document.createElement("canvas");
+ canvas.width = 40;
+ canvas.height = 40;
+ var context = canvas.getContext('2d');
+ obj = context.createImageData(40, 40);
+ assert_true(window.hasOwnProperty("ImageData"), "ImageData constructor must be present");
+ assert_true(obj instanceof ImageData, "ImageData must be returned by .createImageData");
+ });
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor");
+ assert_not_equals(obj, e.data, "cloned object should be a new instance of ImageData");
+ assert_equals(obj.width, e.data.width, "canon.width === e.data.width");
+ assert_equals(obj.height, e.data.height, "canon.height === e.data.height");
+ assert_array_equals(obj.data, e.data.data, "data arrays are the same");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("ImageData expandos are not cloned");
+ t.id = 24;
+ var obj;
+ t.step(function() {
+ var canvas = document.createElement("canvas");
+ canvas.width = 40;
+ canvas.height = 40;
+ var context = canvas.getContext('2d');
+ obj = context.createImageData(40, 40);
+ assert_true(window.hasOwnProperty("ImageData"), "ImageData constructor must be present");
+ assert_true(obj instanceof ImageData, "ImageData must be returned by .createImageData");
+ obj.foo = "bar";
+ });
+ worker.onmessage = t.step_func(function(e) {
+ assert_equals(obj.constructor, e.data.constructor, "canon.constructor === event.data.constructor");
+ assert_not_equals(obj, e.data, "cloned object should be a new instance of ImageData");
+ assert_equals(obj.width, e.data.width, "canon.width === e.data.width");
+ assert_equals(obj.height, e.data.height, "canon.height === e.data.height");
+ assert_array_equals(obj.data, e.data.data, "data arrays are the same");
+ assert_equals(undefined, e.data.foo, "Expando is lost (undefined === e.data.foo)");
+ t.done();
+ });
+ t.step(function() { worker.postMessage(obj);});
+ },
+ function() {
+ var t = async_test("Window objects cannot be cloned");
+ t.id = 25;
+ worker.onmessage = function() {}; //no op because exception should be thrown.
+ t.step(function() {
+ assert_true(DOMException.hasOwnProperty('DATA_CLONE_ERR'), "DOMException.DATA_CLONE_ERR is present");
+ assert_equals(DOMException.DATA_CLONE_ERR, 25, "DOMException.DATA_CLONE_ERR === 25");
+ assert_throws_dom('DATA_CLONE_ERR', function() {worker.postMessage(window)});
+ });
+ t.done();
+ },
+ function() {
+ var t = async_test("Document objects cannot be cloned");
+ t.id = 26;
+ worker.onmessage = function() {}; //no op because exception should be thrown.
+ t.step(function() {
+ assert_true(DOMException.hasOwnProperty('DATA_CLONE_ERR'), "DOMException.DATA_CLONE_ERR is present");
+ assert_equals(DOMException.DATA_CLONE_ERR, 25, "DOMException.DATA_CLONE_ERR === 25");
+ assert_throws_dom('DATA_CLONE_ERR', function() {worker.postMessage(document)});
+ });
+ t.done();
+ },
+ function() {
+ var t = async_test("Empty Error objects can be cloned");
+ t.id = 27;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), Error.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, Error, "Checking constructor");
+ assert_equals(e.data.name, "Error", "Checking name");
+ assert_false(e.data.hasOwnProperty("message"), "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = Error();
+ assert_false(error.hasOwnProperty("message"), "Checking message on the source realm");
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("Error objects can be cloned");
+ t.id = 28;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), Error.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, Error, "Checking constructor");
+ assert_equals(e.data.name, "Error", "Checking name");
+ assert_equals(e.data.message, "some message", "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = Error("some message");
+ error.foo = "bar";
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("EvalError objects can be cloned");
+ t.id = 29;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), EvalError.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, EvalError, "Checking constructor");
+ assert_equals(e.data.name, "EvalError", "Checking name");
+ assert_equals(e.data.message, "some message", "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = EvalError("some message");
+ error.foo = "bar";
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("RangeError objects can be cloned");
+ t.id = 30;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), RangeError.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, RangeError, "Checking constructor");
+ assert_equals(e.data.name, "RangeError", "Checking name");
+ assert_equals(e.data.message, "some message", "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = RangeError("some message");
+ error.foo = "bar";
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("ReferenceError objects can be cloned");
+ t.id = 31;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), ReferenceError.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, ReferenceError, "Checking constructor");
+ assert_equals(e.data.name, "ReferenceError", "Checking name");
+ assert_equals(e.data.message, "some message", "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = ReferenceError("some message");
+ error.foo = "bar";
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("SyntaxError objects can be cloned");
+ t.id = 32;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), SyntaxError.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, SyntaxError, "Checking constructor");
+ assert_equals(e.data.name, "SyntaxError", "Checking name");
+ assert_equals(e.data.message, "some message", "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = SyntaxError("some message");
+ error.foo = "bar";
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("TypeError objects can be cloned");
+ t.id = 33;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), TypeError.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, TypeError, "Checking constructor");
+ assert_equals(e.data.name, "TypeError", "Checking name");
+ assert_equals(e.data.message, "some message", "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = TypeError("some message");
+ error.foo = "bar";
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("URIError objects can be cloned");
+ t.id = 34;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), URIError.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, URIError, "Checking constructor");
+ assert_equals(e.data.name, "URIError", "Checking name");
+ assert_equals(e.data.message, "some message", "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = URIError("some message");
+ error.foo = "bar";
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("URIError objects from other realms are treated as URIError");
+ t.id = 35;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), URIError.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, URIError, "Checking constructor");
+ assert_equals(e.data.name, "URIError", "Checking name");
+ assert_equals(e.data.message, "some message", "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = frames[0].URIError("some message");
+ assert_equals(Object.getPrototypeOf(error), frames[0].URIError.prototype, "Checking prototype before cloning");
+ assert_equals(error.constructor, frames[0].URIError, "Checking constructor before cloning");
+ assert_equals(error.name, "URIError", "Checking name before cloning");
+ error.foo = "bar";
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("Cloning a modified Error");
+ t.id = 36;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), TypeError.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, TypeError, "Checking constructor");
+ assert_equals(e.data.name, "TypeError", "Checking name");
+ assert_equals(e.data.message, "another message", "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = URIError("some message");
+ Object.setPrototypeOf(error, SyntaxError.prototype);
+ error.message = {toString: () => "another message" }
+ error.constructor = RangeError;
+ error.name = "TypeError";
+ error.foo = "bar";
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("Error.message: getter is ignored when cloning");
+ t.id = 37;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), Error.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, Error, "Checking constructor");
+ assert_equals(e.data.name, "Error", "Checking name");
+ assert_false(e.data.hasOwnProperty("message"), "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = Error();
+ Object.defineProperty(error, "message", { get: () => "hello" });
+ assert_equals(error.message, "hello", "Checking message on the source realm");
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("Error.message: undefined property is stringified");
+ t.id = 38;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), Error.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, Error, "Checking constructor");
+ assert_equals(e.data.name, "Error", "Checking name");
+ assert_equals(e.data.message, "undefined", "Checking message");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = Error();
+ error.message = undefined;
+ assert_equals(error.message, undefined, "Checking message on the source realm");
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("DOMException objects can be cloned");
+ t.id = 39;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), DOMException.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, DOMException, "Checking constructor");
+ assert_equals(e.data.name, "IndexSizeError", "Checking name");
+ assert_equals(e.data.message, "some message", "Checking message");
+ assert_equals(e.data.code, DOMException.INDEX_SIZE_ERR, "Checking code");
+ assert_equals(e.data.foo, undefined, "Checking custom property");
+ });
+ t.step(function() {
+ const error = new DOMException("some message", "IndexSizeError");
+ worker.postMessage(error);
+ });
+ },
+ function() {
+ var t = async_test("DOMException objects created by the UA can be cloned");
+ t.id = 40;
+ worker.onmessage = t.step_func_done(function(e) {
+ assert_equals(Object.getPrototypeOf(e.data), DOMException.prototype, "Checking prototype");
+ assert_equals(e.data.constructor, DOMException, "Checking constructor");
+ assert_equals(e.data.code, DOMException.DATA_CLONE_ERR, "Checking code");
+ assert_equals(e.data.name, "DataCloneError", "Checking name");
+ });
+ t.step(function() {
+ try {
+ worker.postMessage(window);
+ } catch (error) {
+ worker.postMessage(error);
+ return;
+ }
+ assert_unreached("Window must not be clonable");
+ });
+ },
+ ];
+ }, {explicit_done:true});
+
+ //Callback for result_callback
+ //queues the next test in the array testCollection
+ //serves to make test execution sequential from the async worker callbacks
+ //alternatively, we would have to create a worker for each test
+ function testFinished(test) {
+ if(test.id < testCollection.length - 1) {
+ //queue the function so that stack remains shallow
+ queue(testCollection[test.id+1]);
+ } else {
+ //when the last test has run, explicitly end test suite
+ done();
+ }
+ }
+ function queue(func) {
+ step_timeout(func, 10);
+ }
+
+ add_result_callback(testFinished);
+ //start the first test manually
+ queue(testCollection[0]);
+
+
+
+
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/transfer-errors.window.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/transfer-errors.window.js
new file mode 100644
index 0000000000..b3ecd86b40
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/transfer-errors.window.js
@@ -0,0 +1,47 @@
+function assert_transfer_error(transferList) {
+ assert_throws_dom("DataCloneError", () => self.postMessage({ get whatever() { throw new Error("You should not have gotten to this point") } }, "*", transferList));
+}
+
+test(() => {
+ [self, self.document, new Image()].forEach(val => {
+ assert_transfer_error([val]);
+ });
+}, "Cannot transfer all objects");
+
+function transfer_tests(name, create) {
+ promise_test(async () => {
+ const transferable = await create();
+ assert_transfer_error([transferable, transferable]);
+ }, `Cannot transfer the same ${name} twice`);
+
+ promise_test(async () => {
+ const transferable = await create();
+ self.postMessage(null, "*", [transferable]);
+ assert_throws_dom("DataCloneError", () => self.postMessage(null, "*", [transferable]));
+ }, `Serialize should make the ${name} detached, so it cannot be transferred again`);
+
+ promise_test(async () => {
+ const transferable = await create(),
+ customError = new Error("hi");
+ self.postMessage(null, "*", [transferable]);
+ assert_throws_exactly(customError, () => self.postMessage({ get whatever() { throw customError } }, "*", [transferable]));
+ }, `Serialize should throw before a detached ${name} is found`);
+
+ promise_test(async () => {
+ const transferable = await create();
+ let seen = false;
+ const message = {
+ get a() {
+ self.postMessage(null, '*', [transferable]);
+ seen = true;
+ }
+ };
+ assert_throws_dom("DataCloneError", () => self.postMessage(message, "*", [transferable]));
+ assert_true(seen);
+ }, `Cannot transfer ${name} detached while the message was serialized`);
+}
+
+transfer_tests("ArrayBuffer", () => new ArrayBuffer(1));
+transfer_tests("MessagePort", () => new MessageChannel().port1);
+transfer_tests("ImageBitmap", () => self.createImageBitmap(document.createElement("canvas")));
+transfer_tests("OffscreenCanvas", () => new OffscreenCanvas(1, 1));
diff --git a/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js
new file mode 100644
index 0000000000..2a46d790b8
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js
@@ -0,0 +1,16 @@
+// META: script=/common/sab.js
+// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
+// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
+// META: script=/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
+
+runStructuredCloneBatteryOfTests({
+ structuredClone(data, transfer) {
+ return new Promise(resolve => {
+ window.addEventListener('message', function f(ev) {
+ window.removeEventListener('message', f);
+ resolve(ev.data.data);
+ });
+ window.postMessage({data, transfer}, "/", transfer);
+ });
+ }
+});
diff --git a/testing/web-platform/tests/html/infrastructure/terminology/plugins/sample.txt b/testing/web-platform/tests/html/infrastructure/terminology/plugins/sample.txt
new file mode 100644
index 0000000000..cedecd6d88
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/terminology/plugins/sample.txt
@@ -0,0 +1,3 @@
+This is a sample text/plain document.
+
+This is not an HTML document.
diff --git a/testing/web-platform/tests/html/infrastructure/terminology/plugins/text-plain.html b/testing/web-platform/tests/html/infrastructure/terminology/plugins/text-plain.html
new file mode 100644
index 0000000000..0ed0ce23b8
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/terminology/plugins/text-plain.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title id='title'>Media Types</title>
+ <link rel="author" title="Philippe Le Hegaret" href="mailto:plh@w3.org"/>
+ <link rel="help" href="https://html.spec.whatwg.org/multipage/#plugins"/>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+
+ <body>
+ <script>
+ var t = async_test("A user agent must not consider the type text/plain as having a registered plugin.");
+
+ window.onload =
+ t.step_func(function() {
+ assert_equals(document.getElementById("frameContext").contentDocument.body.firstChild.nodeName, "PRE");
+ t.done();
+ });
+ </script>
+
+ <h1>Test of plugin support</h1>
+ <p class='assert'>A user agent must not consider the types text/plain and application/octet-stream as having a registered plugin.</p>
+
+ <iframe id="frameContext" src="sample.txt"></iframe>
+
+
+ <div id="log">Running test...</div>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js
new file mode 100644
index 0000000000..c51f425b7a
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-detached-document.https.window.js
@@ -0,0 +1,34 @@
+// Verify that an about:blank or about:srcdoc document remembers the baseURI
+// it was created with even after it's detached.
+const runTest = (frame_type) => {
+ async_test((t) => {
+ const frame = document.createElement('iframe');
+
+ if (frame_type == "about:blank")
+ frame.src = "about:blank";
+ else
+ frame.srcdoc = "foo";
+
+ frame.onload = () => {
+ const frame_doc = frame.contentDocument;
+ const initial_base_uri = document.baseURI;
+ assert_equals(initial_base_uri, frame_doc.baseURI);
+
+ const base_element = document.createElement('base');
+ base_element.href = "https://example.com";
+ document.head.appendChild(base_element);
+ assert_equals(initial_base_uri, frame_doc.baseURI);
+
+ frame.remove();
+ assert_equals(initial_base_uri, frame_doc.baseURI);
+ t.done();
+ };
+
+ document.body.appendChild(frame);
+ }, frame_type);
+};
+
+onload = () => {
+ runTest("about:blank");
+ runTest("about:srcdoc");
+};
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-javascript-nav.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-javascript-nav.https.window.js
new file mode 100644
index 0000000000..688eeaf384
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/base-url/base-url-javascript-nav.https.window.js
@@ -0,0 +1,22 @@
+// Verify that navigating an about:blank document to a javascript: URL that
+// creates a new document, copies over the base URL from the old document to the
+// new document.
+onload = () => {
+ async_test((t) => {
+ const frame = document.createElement('iframe');
+
+ frame.onload = () => {
+ assert_equals(document.baseURI, frame.contentDocument.baseURI);
+
+ // We'll need to monitor onload again for the javascript: navigation.
+ frame.onload = () => {
+ assert_equals(document.baseURI, frame.contentDocument.baseURI);
+ assert_equals('foo', frame.contentDocument.body.textContent);
+ };
+ frame.src = "javascript:'foo'";
+ t.done();
+ };
+
+ document.body.appendChild(frame);
+ }, "javascript: url nav base url test");
+};
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-uri-synthetic-document.html b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-uri-synthetic-document.html
new file mode 100644
index 0000000000..bed090aac4
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-uri-synthetic-document.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<link rel=author href="mailto:jarhar@chromium.org">
+<link rel=help href="https://bugs.chromium.org/p/chromium/issues/detail?id=1486750">
+<link rel=help href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#document-base-url">
+<link rel=help href="https://html.spec.whatwg.org/multipage/urls-and-fetching.html#fallback-base-url">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+test(() => {
+ const doc1 = document.implementation.createHTMLDocument();
+ assert_equals(doc1.baseURI, 'about:blank', 'document.implementation.createHTMLDocument()');
+
+ const doc2 = document.implementation.createDocument('', '');
+ assert_equals(doc2.baseURI, 'about:blank', 'document.implementation.createDocument("", "")');
+
+ const doc3 = new Document();
+ assert_equals(doc3.baseURI, 'about:blank', 'new Document()');
+}, 'Synthetic documents should return about:blank for document.baseURI.');
+
+test(() => {
+ const doc = document.implementation.createHTMLDocument();
+ assert_equals(doc.baseURI, 'about:blank', 'baseURI should be about:blank without a <base>.');
+
+ const base = doc.createElement('base');
+ base.href = '/foo';
+ doc.head.appendChild(base);
+ assert_equals(doc.baseURI, 'about:blank', '<base> with relative URL should not change the about:blank baseURI.');
+
+ base.href = 'http://example.com/';
+ assert_equals(doc.baseURI, 'http://example.com/', '<base> with complete URL should replace the about:blank baseURI.');
+}, 'Synthetic documents should incorporate <base> href URLs correctly.');
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-changes-about-srcdoc-2.https.html b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-changes-about-srcdoc-2.https.html
new file mode 100644
index 0000000000..dab8d2122b
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-changes-about-srcdoc-2.https.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<iframe src='about:blank'></iframe>
+
+<script>
+// If document.open's behavior is modified to remove the url-rewriting behavior,
+// then this test can be deleted (https://github.com/whatwg/html/issues/3989).
+setup({ explicit_done: true });
+
+// This function is called by directly by the child iframe (above), with the
+// child iframe's document. The child iframe's document contains an about:srcdoc iframe.
+window.start = childDocument => {
+ const grandchildDocument =
+ childDocument.getElementById('foo').contentDocument;
+
+ test(() => {
+ assert_equals(childDocument.URL, 'about:blank',
+ 'Child document starting URL');
+ assert_equals(grandchildDocument.URL, 'about:srcdoc',
+ 'Grandchild document starting URL');
+ const originalChildBaseURL = childDocument.baseURI;
+
+ grandchildDocument.open("", "");
+ // Verify that the document.open() trick worked: the grandchild should now
+ // have the same url as the child, and have inherited the child's base url.
+ assert_equals(grandchildDocument.URL, 'about:blank',
+ 'Grandchild document after document.open() trick');
+ assert_equals(grandchildDocument.baseURI, originalChildBaseURL,
+ 'Grandchild base URL must match child base URL');
+
+ // Give child a new base URL.
+ const baseElement = childDocument.createElement('base');
+ baseElement.href = get_host_info().REMOTE_ORIGIN;
+ childDocument.head.append(baseElement);
+
+ // Verify that changing the child's base url succeeded and did not affect
+ // the grandchild's base url.
+ const newChildBaseURL = childDocument.baseURI;
+ assert_equals(grandchildDocument.URL, 'about:blank',
+ 'Grandchild document after child gets new base URL');
+ assert_not_equals(newChildBaseURL, originalChildBaseURL,
+ 'Child base URL must change');
+ assert_equals(grandchildDocument.baseURI, originalChildBaseURL,
+ 'Grandchild base URL must not change');
+ });
+
+ done();
+};
+
+let subframe_doc = document.querySelector('iframe').contentDocument;
+subframe_doc.body.innerHTML = '<iframe srcdoc="foo" id="foo"></iframe>';
+promise_test(async t => {
+ const grandchildIframe = subframe_doc.querySelector('iframe');
+ await new Promise(resolve => {
+ grandchildIframe.onload = resolve;
+ });
+
+ assert_equals(grandchildIframe.contentDocument.URL, 'about:srcdoc');
+
+ let script = subframe_doc.createElement('script');
+ script.innerHTML = 'parent.start(document);';
+ subframe_doc.body.appendChild(script);
+}, "wrapper promise test for timeout.");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-changes-after-nav-about-srcdoc.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-changes-after-nav-about-srcdoc.https.window.js
new file mode 100644
index 0000000000..897647a45e
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-changes-after-nav-about-srcdoc.https.window.js
@@ -0,0 +1,83 @@
+// META: script=/common/get-host-info.sub.js
+//
+// A test to verify that, when a srcdoc frame is created when it's parent
+// has a non-default base URI, and is later restored from history, it has
+// the same baseURI it started with, even if the parent has changed its own
+// baseURI in the meantime.
+// The parent always communicates with the child via postMessage since some
+// of the test cases include the child being sandboxed.
+
+async function sendMessage(frame, msg) {
+ const result = new Promise(r => onmessage = e => r(e.data));
+ frame.postMessage(msg, "*");
+ return await result;
+}
+
+const runTest = (description, sandbox_flags) => {
+ promise_test(async test => {
+ const original_parent_baseURI = document.baseURI;
+ // Create a URL for the child frame to navigate to.
+ const nav_target =
+ (new URL('./resources/send-back-base-url.html', location.href)).href;
+
+ // Change parent to a non-default baseURI.
+ const base_element = document.createElement("base");
+ base_element.href = get_host_info().REMOTE_ORIGIN;
+ document.head.appendChild(base_element);
+ assert_not_equals(document.baseURI, original_parent_baseURI,
+ "parent baseURI failed to change.");
+ const non_default_parent_baseURI = document.baseURI;
+
+ // Create child and load a srcdoc frame.
+ const iframe = document.createElement("iframe");
+ if (sandbox_flags !== null)
+ iframe.sandbox = sandbox_flags;
+ iframe.srcdoc = `
+ <head>
+ <script>
+ addEventListener('message', (event) => {
+ if (event.data == 'report baseURI')
+ event.source.postMessage(document.baseURI, event.origin);
+ if (event.data == 'click link')
+ document.getElementById('link').click();
+ });
+ parent.postMessage('loaded', '*');
+ </scr`+`ipt>
+ </head>
+ <body>
+ <a id='link' href='` + nav_target+ `'>link</a>
+ </body>
+ `;
+
+ const child_loaded = new Promise(r => onmessage = e => r(e.data));
+ document.body.appendChild(iframe);
+ assert_equals(await child_loaded, "loaded");
+
+ // Verify child's baseURI matches parent.
+ const child_base_uri = await sendMessage(frames[0], "report baseURI");
+ assert_equals(child_base_uri, non_default_parent_baseURI);
+
+ // Navigate child frame to non-srcdoc.
+ const child_loaded2 = await sendMessage(frames[0], "click link");
+ assert_equals(child_loaded2, "loaded");
+ const child_base_uri2 = await sendMessage(frames[0], "report baseURI");
+ assert_not_equals(child_base_uri2, non_default_parent_baseURI);
+ assert_not_equals(child_base_uri2, original_parent_baseURI);
+ assert_equals(child_base_uri2, nav_target);
+
+ // Parent resets its baseURI.
+ base_element.remove();
+ assert_equals(document.baseURI, original_parent_baseURI,
+ "parent baseURI failed to reset.");
+
+ // Navigate child back and verify its baseURI didn't change.
+ const child_loaded3 = new Promise(r => onmessage = e => r(e.data));
+ window.history.back();
+ assert_equals(await child_loaded3, "loaded");
+ const child_base_uri3 = await sendMessage(frames[0], "report baseURI");
+ assert_equals(child_base_uri3, non_default_parent_baseURI);
+ }, description);
+}
+
+runTest("non-sandboxed srcdoc - parent changes baseURI",null);
+runTest("sandboxed srcdoc - parent changes baseURI", "allow-scripts");
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-initiated-grand-parent.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-initiated-grand-parent.https.window.js
new file mode 100644
index 0000000000..1983f02c26
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-initiated-grand-parent.https.window.js
@@ -0,0 +1,62 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+
+const testBaseUriAboutBlankFromGrandParent = (description, child_origin) => {
+ promise_test(async test => {
+ // Create a child in an iframe.
+ const child_token = token();
+ const child_url = child_origin +
+ '/common/dispatcher/executor.html' +
+ `?uuid=${child_token}`;
+ const iframe = document.createElement("iframe");
+ iframe.src = child_url;
+ document.body.appendChild(iframe);
+
+ // The child creates a grand child in an iframe.
+ const reply_token = token();
+ send(child_token, `
+ const iframe = document.createElement("iframe");
+ location.hash = "interesting-fragment";
+ iframe.src = "/common/blank.html";
+ iframe.onload = () => {
+ send("${reply_token}", "grand child loaded");
+ };
+ document.body.appendChild(iframe);
+ `);
+ assert_equals(await receive(reply_token), "grand child loaded");
+
+ const child = iframe.contentWindow;
+ const grandchild = child[0];
+
+ // Navigate the grand-child toward about:blank.
+ // Navigation are always asynchronous. It doesn't exist a ways to know the
+ // about:blank document committed. A timer is used instead:
+ grandchild.location = "about:blank";
+ await new Promise(r => test.step_timeout(r, /*ms=*/500));
+
+ // The grandchild baseURI must correspond to its grand parent.
+ //
+ // Note: `child_token` is removed, to get a stable failure, in case the
+ // about:blank's document.baseURI reports the parent's URL instead of its
+ // grand-parent.
+ assert_equals(
+ grandchild.document.baseURI.replace(child_token, "child_token"),
+ self.document.baseURI);
+ }, description);
+}
+
+onload = () => {
+ testBaseUriAboutBlankFromGrandParent(
+ "Check the baseURL of an about:blank document same-origin with its parent",
+ get_host_info().HTTPS_ORIGIN,
+ );
+ testBaseUriAboutBlankFromGrandParent(
+ "Check the baseURL of an about:blank document cross-origin with its parent",
+ get_host_info().HTTPS_REMOTE_ORIGIN,
+ );
+ testBaseUriAboutBlankFromGrandParent(
+ "Check the baseURL of an about:blank document cross-site with its parent",
+ get_host_info().HTTPS_NOTSAMESITE_ORIGIN,
+ );
+}
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-window-initiator-is-not-opener.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-window-initiator-is-not-opener.https.window.js
new file mode 100644
index 0000000000..a8519b5d1b
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-window-initiator-is-not-opener.https.window.js
@@ -0,0 +1,40 @@
+// Tests that a popup about:blank window inherits its base url from
+// the initiator, and not the opener.
+const runTest = (description) => {
+ const opener_base_uri = document.baseURI;
+
+ promise_test((test) => {
+ return new Promise(async resolve => {
+ window.popup = window.open();
+ test.add_cleanup(() => popup.close());
+ assert_equals(window.popup.location.href, 'about:blank');
+
+ // Create iframe to be the initiator.
+ const iframe = document.createElement('iframe');
+ iframe.srcdoc = `
+ <head>
+ <base href='https://example.com'>
+ <script>
+ window.top.popup.location.href = 'about:blank';
+ </scr` + `ipt>
+ </head>
+ <body></body>
+ `;
+
+ const popup_navigated = new Promise(r => window.popup.onpagehide = e => r());
+ document.body.append(iframe);
+ await popup_navigated; // This makes sure the old child has unloaded, but
+ // with the timeout below it's really not needed.
+
+ // This is necessary, or else the test times out. The about:blank load
+ // does not fire an onload event we can access.
+ test.step_timeout(resolve, 500);
+ }).then(() => {
+ assert_equals('https://example.com/', window.popup.document.baseURI);
+ });
+ }, description);
+};
+
+onload = () => {
+ runTest("window.open() gets base url from initiator not opener.");
+};
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-window-open-about-blank.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-window-open-about-blank.https.window.js
new file mode 100644
index 0000000000..1a60809023
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/base-url/document-base-url-window-open-about-blank.https.window.js
@@ -0,0 +1,24 @@
+// Basic test that a popup about:blank window inherits its base url from
+// the initiator (which in this case is also the opener).
+const runTest = (description) => {
+ // In this test the opener and the initiator will be the same.
+ const initiator_base_uri = document.baseURI;
+ test(() => {
+ const popup = window.open();
+
+ // Window.open synchronously loads the initial empty document.
+ assert_equals("about:blank", popup.location.href);
+ assert_equals(initiator_base_uri, popup.document.baseURI);
+
+ // Verify the popup's base url is properly snapshotted, and doesn't change
+ // if the parent's base url changes.
+ const base_element = document.createElement('base');
+ base_element.href = "https://example.com";
+ document.head.appendChild(base_element);
+ assert_equals(initiator_base_uri, popup.document.baseURI);
+ }, description);
+};
+
+onload = () => {
+ runTest("window.open() gets base url from initiator.");
+};
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/matches-about-blank-base-url.window.js b/testing/web-platform/tests/html/infrastructure/urls/base-url/matches-about-blank-base-url.window.js
new file mode 100644
index 0000000000..b2a0740ddf
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/base-url/matches-about-blank-base-url.window.js
@@ -0,0 +1,29 @@
+// Verify that an iframe whose URL "matches about:blank" [1] uses its about base
+// URL and initiator/creator origin.
+//
+// [1]: https://html.spec.whatwg.org/#matches-about:blank
+onload = () => {
+ async_test(t => {
+ const iframe = document.createElement('iframe');
+ // Navigate the iframe away from the initial about:blank Document [1] to
+ // isolate the effects of a "matches about:blank" URL.
+ //
+ // [1]: https://html.spec.whatwg.org/#is-initial-about:blank
+ iframe.src = '/common/blank.html';
+
+ iframe.addEventListener('load', e => {
+ assert_equals(iframe.contentWindow.location.pathname, '/common/blank.html');
+
+ // Navigate the iframe to a URL that "matches about:blank" but isn't exactly
+ // "about:blank".
+ iframe.onload = t.step_func_done(() => {
+ assert_equals(iframe.contentDocument.URL, 'about:blank?query#fragment');
+ assert_equals(iframe.contentWindow.origin, window.origin);
+ assert_equals(iframe.contentDocument.baseURI, document.baseURI);
+ });
+ iframe.src = 'about:blank?query#fragment';
+ }, {once: true});
+
+ document.body.appendChild(iframe);
+ }, "about:blank and about:blank?foo#bar both 'match about:blank'");
+};
diff --git a/testing/web-platform/tests/html/infrastructure/urls/base-url/resources/send-back-base-url.html b/testing/web-platform/tests/html/infrastructure/urls/base-url/resources/send-back-base-url.html
new file mode 100644
index 0000000000..6fdf81d1db
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/base-url/resources/send-back-base-url.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+ addEventListener('message', (event) => {
+ if (event.data == 'report baseURI')
+ event.source.postMessage(document.baseURI, event.origin);
+ });
+ parent.postMessage('loaded', '*');
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/dynamic-changes-to-base-urls/dynamic-urls.sub.html b/testing/web-platform/tests/html/infrastructure/urls/dynamic-changes-to-base-urls/dynamic-urls.sub.html
new file mode 100644
index 0000000000..ce65f392d6
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/dynamic-changes-to-base-urls/dynamic-urls.sub.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>HTML Test: Dynamic changes to base URLs</title>
+<link rel="author" title="Intel" href="http://www.intel.com/"/>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#dynamic-changes-to-base-urls" />
+<base href="" id="base">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<div id="div" style="display:none"></div>
+<script>
+ var div = document.getElementById("div"),
+ base = document.getElementById("base"),
+ url = document.location.href;
+
+ var testData = [
+ {elements: ["a", "link", "area"], set: "href", get: "href"},
+ {elements: ["q", "blockquote", "ins", "del"], set: "cite", get: "cite"},
+ {elements: ["audio", "input", "img", "embed", "video", "iframe", "script", "source", "track"], set: "src", get: "src"},
+ {elements: ["form"], set: "action", get: "action"},
+ {elements: ["object"], set: "data", get: "data"},
+ {elements: ["button"], set: "formAction", get: "formAction"}
+ ];
+
+ for (var i in testData) {
+ var item = testData[i];
+ for (var j in item.elements) {
+ test(function () {
+ var ele = document.createElement(item.elements[j]);
+
+ ele.setAttribute(item.set, "test.txt");
+ div.appendChild(ele);
+
+ base.setAttribute("href", "");
+ assert_equals(ele[item.get], url.substr(0, url.lastIndexOf("/")) +"/test.txt", "The '" + item.get + "' attribute is incorrect.");
+ base.setAttribute("href", "http://{{domains[www]}}:{{ports[http][0]}}");
+ assert_equals(ele[item.get], "http://{{domains[www]}}:{{ports[http][0]}}/test.txt", "The '" + item.get + "' attribute is incorrect.");
+ }, "The '" + item.set + "' attribute of the '" + item.elements[j] + "' element");
+ }
+ }
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/dynamic-changes-to-base-urls/historical.sub.xhtml b/testing/web-platform/tests/html/infrastructure/urls/dynamic-changes-to-base-urls/historical.sub.xhtml
new file mode 100644
index 0000000000..1be7a7cf2c
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/dynamic-changes-to-base-urls/historical.sub.xhtml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html>
+<html id="h" xmlns="http://www.w3.org/1999/xhtml" xml:base="">
+ <head>
+ <title>Historical xml:base must be removed</title>
+ <link rel="author" title="Intel" href="http://www.intel.com/"/>
+ <link rel="help" href="https://github.com/whatwg/html/pull/84/files" />
+ <script src="/resources/testharness.js" id="s1"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ </head>
+ <body>
+ <div id="log"></div>
+ <div id="div" style="display:none" xml:base=""></div>
+ <script>
+ <![CDATA[
+ var div = document.getElementById("div"),
+ html = document.getElementById("h"),
+ url = document.location.href;
+
+ var testData = [
+ {elements: ["a", "link", "area"], set: "href", get: "href"},
+ {elements: ["q", "blockquote", "ins", "del"], set: "cite", get: "cite"},
+ {elements: ["audio", "input", "img", "embed", "video", "iframe", "script", "source", "track"], set: "src", get: "src"},
+ {elements: ["form"], set: "action", get: "action"},
+ {elements: ["object"], set: "data", get: "data"},
+ {elements: ["button"], set: "formaction", get: "formAction"}
+ ];
+
+ for (var i in testData) {
+ var item = testData[i];
+ for (var j in item.elements) {
+ test(function () {
+ var ele = document.createElement(item.elements[j]);
+
+ ele.setAttribute(item.set, "test.txt");
+ div.appendChild(ele);
+
+ html.setAttribute("xml:base", "");
+ assert_equals(ele[item.get], url.substr(0, url.lastIndexOf("/")) +"/test.txt", "The '" + item.get + "' attribute is incorrect.");
+ html.setAttribute("xml:base", "http://{{domains[www]}}:{{ports[http][0]}}");
+ assert_not_equals(ele[item.get], "http://{{domains[www]}}:{{ports[http][0]}}/test.txt", "The '" + item.get + "' attribute is incorrect.");
+ }, "The '" + item.set + "' attribute of the '" + item.elements[j] + "' element");
+ }
+ }
+
+ test(function () {
+ var s1 = document.getElementById("s1");
+ var val1 = s1.src;
+ var val2 = div.firstChild.href;
+
+ div.setAttribute("xml:base", "http://{{domains[www2]}}:{{ports[http][0]}}");
+ assert_equals(s1.src, val1, "The outer element must not be effected.");
+ assert_equals(div.firstChild.href, val2, "The inner element must be effected.");
+ assert_not_equals(div.firstChild.href, "http://{{domains[www2]}}:{{ports[http][0]}}/test.txt");
+ }, "Change the base URL must not effect the descendant elements");
+ ]]>
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/attributes.sub.html b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/attributes.sub.html
new file mode 100644
index 0000000000..d596f37716
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/attributes.sub.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<meta charset={{GET[encoding]}}> <!-- ends up as <meta charset> by default which is windows-1252 -->
+<meta name=variant content="?encoding=windows-1252">
+<meta name=variant content="?encoding=x-cp1251">
+<meta name=variant content="?encoding=utf8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<link rel=help href=https://html.spec.whatwg.org/multipage/rendering.html#the-page>
+<link rel=help href=https://html.spec.whatwg.org/multipage/rendering.html#tables-2>
+<link rel=help href=https://html.spec.whatwg.org/multipage/#reflecting-content-attributes-in-idl-attributes>
+<div id=log></div>
+<script>
+function expected(encoding) {
+ return "?" + {
+ "UTF-8": "%C3%BF",
+ "windows-1251": "%26%23255%3B",
+ "windows-1252": "%FF"
+ }[encoding];
+}
+
+function assert_ends_with(input, endsWith) {
+ assert_true(input.endsWith(endsWith), input + " did not end with " + endsWith);
+}
+
+"body table thead tbody tfoot tr td th".split(" ").forEach(localName => {
+ test(t => {
+ const elm = document.createElement(localName);
+ document.body.appendChild(elm);
+ t.add_cleanup(() => { document.body.removeChild(elm); });
+ elm.setAttribute("background", "?\u00FF");
+ assert_ends_with(getComputedStyle(elm).backgroundImage, expected(document.characterSet) + "\")");
+ }, "getComputedStyle <" + localName + " background>");
+});
+
+function test_url_reflecting(localName, attr, idlAttr) {
+ idlAttr = idlAttr || attr;
+ test(() => {
+ let input = "?\u00FF";
+ const elm = document.createElement(localName);
+ assert_true(idlAttr in elm, idlAttr + " is not supported");
+ elm.setAttribute(attr, input);
+ assert_ends_with(elm[idlAttr], expected(document.characterSet));
+ }, "Getting <" + localName + ">." + idlAttr);
+}
+
+("iframe src, a href, area href, base href, link href, img src, embed src, object data, " +
+ "track src, video src, audio src, input src, form action, input formaction formAction, " +
+ "button formaction formAction, script src").split(", ").forEach(str => {
+ const arr = str.split(" ");
+ test_url_reflecting(arr[0], arr[1], arr[2]);
+});
+
+function test_string_reflecting(localName, attr) {
+ test(() => {
+ let input = "?\u00FF ?\u00FF";
+ const elm = document.createElement(localName);
+ assert_true(attr in elm, attr + " is not supported");
+ elm.setAttribute(attr, input);
+ assert_equals(elm[attr], input);
+ }, "Getting <" + localName + ">." + attr);
+}
+
+"a ping, area ping".split(", ").forEach(str => {
+ const arr = str.split(" ");
+ test_string_reflecting(arr[0], arr[1]);
+});
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/location.sub.html b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/location.sub.html
new file mode 100644
index 0000000000..1403cf18cd
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/location.sub.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<meta charset={{GET[encoding]}}> <!-- ends up as <meta charset> by default which is windows-1252 -->
+<meta name=variant content="?encoding=windows-1252">
+<meta name=variant content="?encoding=x-cp1251">
+<meta name=variant content="?encoding=utf8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+function expected(encoding) {
+ return "?" + {
+ "UTF-8": "%C3%BF",
+ "windows-1251": "%26%23255%3B",
+ "windows-1252": "%FF"
+ }[encoding];
+}
+
+[
+ [(win, input) => { win.location = input; }, "location [PutForwards]"],
+ [(win, input) => { win.location.assign(input); }, "location.assign()"],
+ [(win, input) => { win.location.replace(input); }, "location.replace()"],
+ [(win, input) => { win.location.href = input; }, "location.href"]
+].forEach(([callback, desc]) => {
+ async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe")),
+ actualEncoding = document.characterSet
+ callback(frame.contentWindow, "/common/blank.html?\u00FF");
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentWindow.location.search, expected(actualEncoding));
+ });
+ }, desc);
+});
+
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe")),
+ actualEncoding = document.characterSet;
+ frame.src = "/common/blank.html";
+ frame.onload = t.step_func(() => {
+ frame.contentWindow.location.search = "\u00FF";
+ frame.onload = t.step_func_done(() => {
+ // location.search always uses UTF-8
+ assert_equals(frame.contentWindow.location.search, expected("UTF-8"));
+ });
+ });
+}, "location.search");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/lone-surrogates.sub.html b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/lone-surrogates.sub.html
new file mode 100644
index 0000000000..e47ab0f310
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/lone-surrogates.sub.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset={{GET[encoding]}}> <!-- ends up as <meta charset> by default which is windows-1252 -->
+<meta name=variant content="?encoding=windows-1252">
+<meta name=variant content="?encoding=utf8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+function expected(encoding) {
+ return "?" + {
+ // Replacement character (not bogus UTF-8)
+ "UTF-8": "%EF%BF%BD",
+ // Charref for the replacement character (not the lone surrogate)
+ "windows-1252": "%26%2365533%3B",
+ }[encoding];
+}
+
+test(t => {
+ const elm = document.createElement("a");
+ document.body.appendChild(elm);
+ t.add_cleanup(() => elm.remove());
+ elm.setAttribute("href", "?\uD800");
+
+ const shouldEndWith = expected(document.characterSet);
+ assert_true(
+ elm.href.endsWith(shouldEndWith),
+ `${elm.href} did not end with ${shouldEndWith}`
+ );
+}, `Query parsing with lone surrogates in ${document.characterSet}`);
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/navigation.sub.html b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/navigation.sub.html
new file mode 100644
index 0000000000..7808434f05
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/navigation.sub.html
@@ -0,0 +1,162 @@
+<!doctype html>
+<meta charset={{GET[encoding]}}> <!-- ends up as <meta charset> by default which is windows-1252 -->
+<meta name=variant content="?encoding=windows-1252">
+<meta name=variant content="?encoding=x-cp1251">
+<meta name=variant content="?encoding=utf8">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/utils.js"></script>
+<link rel=help href=https://html.spec.whatwg.org/multipage/#following-hyperlinks>
+<link rel=help href=https://html.spec.whatwg.org/multipage/#hyperlink-auditing>
+<link rel=help href=https://html.spec.whatwg.org/multipage/#attr-meta-http-equiv-refresh>
+<div id=log></div>
+<script>
+function expected(encoding) {
+ return {
+ "UTF-8": "%C3%BF",
+ "windows-1251": "%26%23255%3B",
+ "windows-1252": "%FF"
+ }[encoding];
+}
+var encoding = document.characterSet;
+var blank = 'resources/blank.py?encoding=' + encoding;
+var stash_put = 'resources/stash.py?q=\u00FF&action=put&id=';
+var stash_take = 'resources/stash.py?action=take&id=';
+var input_url_html = 'resources/resource.py?q=\u00FF&encoding=' + encoding + '&type=html';
+var expected_current = expected(encoding);
+var expected_utf8 = expected('UTF-8');
+
+function assert_ends_with(input, endsWith) {
+ assert_true(input.endsWith(endsWith), input + " did not end with " + endsWith);
+}
+
+
+function poll_for_stash(test_obj, uuid, expected) {
+ var start = new Date();
+ var poll = test_obj.step_func(function () {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', stash_take + uuid);
+ xhr.onload = test_obj.step_func(function(e) {
+ if (xhr.response == "") {
+ if (new Date() - start > 10000) {
+ // If we set the status to TIMEOUT here we avoid a race between the
+ // page and the test timing out
+ test_obj.force_timeout();
+ }
+ test_obj.step_timeout(poll, 200);
+ } else {
+ assert_equals(xhr.response, expected);
+ test_obj.done();
+ }
+ });
+ xhr.send();
+ })
+ test_obj.step_timeout(poll, 200);
+}
+
+function setup_navigation(elm, iframe, id, test_obj) {
+ iframe.name = id;
+ elm.target = id;
+ elm.setAttribute('href', input_url_html);
+ document.body.appendChild(iframe);
+ document.body.appendChild(elm);
+ test_obj.add_cleanup(function() {
+ document.body.removeChild(iframe);
+ document.body.removeChild(elm);
+ });
+}
+
+// follow hyperlink
+function test_follow_link(tag) {
+ async_test(function() {
+ var elm = document.createElement(tag);
+ var iframe = document.createElement('iframe');
+ setup_navigation(elm, iframe, 'test_follow_link_'+tag, this);
+ iframe.onload = this.step_func_done(function() { // when the page navigated to has loaded
+ assert_equals(iframe.contentDocument.body.textContent, expected_current);
+ });
+ // follow the hyperlink
+ elm.click();
+ // check that navigation succeeded by ...??? XXX
+ }, 'follow hyperlink <'+tag+' href>');
+}
+
+'a, area'.split(', ').forEach(function(str) {
+ test_follow_link(str);
+});
+
+async_test(function() {
+ const iframe = document.createElement('iframe');
+ iframe.name = 'test_dont_follow_link';
+ document.body.appendChild(iframe);
+
+ const link = document.createElement('link');
+ link.target = iframe.name;
+ link.setAttribute('href', input_url_html);
+ document.body.appendChild(link);
+
+ const anchor = document.createElement('a');
+ anchor.target = iframe.name;
+ anchor.setAttribute('href', blank);
+ document.body.appendChild(anchor);
+
+ this.add_cleanup(function() {
+ iframe.remove();
+ link.remove();
+ anchor.remove();
+ });
+
+ iframe.onload = this.step_func_done(() => {
+ assert_equals(
+ iframe.contentDocument.location.pathname,
+ '/html/infrastructure/urls/resolving-urls/query-encoding/resources/blank.py',
+ 'The <a> navigation should occur instead of the <link> navigation.');
+ });
+
+ anchor.click();
+ link.click();
+}, `don't follow hyperlink <link href>`);
+
+// follow hyperlink with ping attribute
+function test_follow_link_ping(tag) {
+ async_test(function() {
+ var uuid = token();
+ var elm = document.createElement(tag);
+ // check if ping is supported
+ assert_true('ping' in elm, 'ping not supported');
+ elm.setAttribute('ping', stash_put + uuid);
+ var iframe = document.createElement('iframe');
+ setup_navigation(elm, iframe, 'test_follow_link_ping_'+tag, this);
+ // follow the hyperlink
+ elm.click();
+ // check that navigation succeeded by ...??? XXX
+ // check that the right URL was requested for the ping
+ poll_for_stash(this, uuid, expected_current);
+ }, 'hyperlink auditing <'+tag+' ping>');
+}
+
+'a, area'.split(', ').forEach(function(str) {
+ test_follow_link_ping(str);
+});
+
+// navigating with meta refresh
+async_test(function() {
+ var iframe = document.createElement('iframe');
+ iframe.src = blank;
+ document.body.appendChild(iframe);
+ this.add_cleanup(function() {
+ document.body.removeChild(iframe);
+ });
+ iframe.onload = this.step_func_done(function() {
+ var doc = iframe.contentDocument;
+ var got = doc.body.textContent;
+ if (got == '') {
+ doc.write('<meta http-equiv=refresh content="0; URL='+input_url_html+'">REFRESH');
+ doc.close();
+ return;
+ }
+ assert_equals(got, expected_current);
+ });
+}, 'meta refresh');
+
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/blank.py b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/blank.py
new file mode 100644
index 0000000000..bbd269d170
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/blank.py
@@ -0,0 +1,2 @@
+def main(request, response):
+ return [(b"Content-Type", b"text/html; charset=%s" % (request.GET[b'encoding']))], u""
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/css-tmpl.py b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/css-tmpl.py
new file mode 100644
index 0000000000..673b2fca32
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/css-tmpl.py
@@ -0,0 +1,7 @@
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ encoding = request.GET[b'encoding']
+ tmpl = request.GET[b'tmpl']
+ sheet = isomorphic_decode(tmpl) % u'\\0000E5'
+ return [(b"Content-Type", b"text/css; charset=%s" % encoding)], sheet.encode(isomorphic_decode(encoding))
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/resolve-url.js b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/resolve-url.js
new file mode 100644
index 0000000000..74c4c2e997
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/resolve-url.js
@@ -0,0 +1,713 @@
+// NOTE: this file needs to be split up rather than expanded. See ../location.sub.html for some
+// extracted tests. Tracked by https://github.com/web-platform-tests/wpt/issues/4934.
+
+/*
+* help:
+* https://html.spec.whatwg.org/multipage/#the-link-element
+* https://html.spec.whatwg.org/multipage/#styling
+* https://html.spec.whatwg.org/multipage/#prepare-a-script
+* https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm
+* https://html.spec.whatwg.org/multipage/#track-url
+* https://html.spec.whatwg.org/multipage/#concept-form-submit
+* https://html.spec.whatwg.org/multipage/#set-the-frozen-base-url
+* https://dom.spec.whatwg.org/#dom-node-baseuri
+* https://html.spec.whatwg.org/multipage/#the-a-element
+* https://html.spec.whatwg.org/multipage/#dom-worker
+* https://html.spec.whatwg.org/multipage/#dom-sharedworker
+* https://html.spec.whatwg.org/multipage/#dom-eventsource
+* https://html.spec.whatwg.org/multipage/#dom-xmldocument-load
+* https://html.spec.whatwg.org/multipage/#dom-open
+* http://url.spec.whatwg.org/#dom-url-search
+* https://www.w3.org/Bugs/Public/show_bug.cgi?id=24148
+* https://xhr.spec.whatwg.org/#the-open()-method
+* https://html.spec.whatwg.org/multipage/#set-up-a-worker-script-settings-object
+* https://html.spec.whatwg.org/multipage/#dom-workerglobalscope-importscripts
+* https://html.spec.whatwg.org/multipage/#parse-a-websocket-url's-components
+* https://html.spec.whatwg.org/multipage/#dom-websocket-url
+* https://www.w3.org/Bugs/Public/show_bug.cgi?id=23968
+* http://dev.w3.org/csswg/cssom/#requirements-on-user-agents-implementing-the-xml-stylesheet-processing-instruction
+* http://url.spec.whatwg.org/#dom-url
+*/
+setup({explicit_done:true});
+onload = function() {
+ var encoding = '{{GET[encoding]}}';
+ var input_url = 'resources/resource.py?q=\u00E5&encoding=' + encoding + '&type=';
+ ('html css js worker sharedworker worker_importScripts sharedworker_importScripts worker_worker worker_sharedworker sharedworker_worker '+
+ 'sharedworker_sharedworker eventstream png svg xmlstylesheet_css video webvtt').split(' ').forEach(function(str) {
+ window['input_url_'+str] = input_url + str;
+ });
+ var blank = 'resources/blank.py?encoding=' + encoding;
+ var stash_put = 'resources/stash.py?q=\u00E5&action=put&id=';
+ var stash_take = 'resources/stash.py?action=take&id=';
+ var expected_obj = {
+ 'utf-8':'%C3%A5',
+ 'utf-16be':'%C3%A5',
+ 'utf-16le':'%C3%A5',
+ 'windows-1252':'%E5',
+ 'windows-1251':'%26%23229%3B'
+ };
+ var expected_current = expected_obj[encoding];
+ var expected_utf8 = expected_obj['utf-8'];
+
+ function msg(expected, got) {
+ return 'expected substring '+expected+' got '+got;
+ }
+
+ function poll_for_stash(test_obj, uuid, expected) {
+ var start = new Date();
+ var poll = test_obj.step_func(function () {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', stash_take + uuid);
+ xhr.onload = test_obj.step_func(function(e) {
+ if (xhr.response == "") {
+ if (new Date() - start > 10000) {
+ // If we set the status to TIMEOUT here we avoid a race between the
+ // page and the test timing out
+ test_obj.force_timeout();
+ }
+ test_obj.step_timeout(poll, 200);
+ } else {
+ assert_equals(xhr.response, expected);
+ test_obj.done();
+ }
+ });
+ xhr.send();
+ })
+ test_obj.step_timeout(poll, 200);
+ }
+
+ // loading html (or actually svg to support <embed>)
+ function test_load_nested_browsing_context(tag, attr, spec_url) {
+ subsetTestByKey('nested-browsing', async_test, function() {
+ var id = 'test_load_nested_browsing_context_'+tag;
+ var elm = document.createElement(tag);
+ elm.setAttribute(attr, input_url_svg);
+ elm.name = id;
+ document.body.appendChild(elm);
+ this.add_cleanup(function() {
+ document.body.removeChild(elm);
+ });
+ elm.onload = this.step_func_done(function() {
+ assert_equals(window[id].document.documentElement.textContent, expected_current);
+ });
+
+ }, 'load nested browsing context <'+tag+' '+attr+'>');
+ }
+
+ spec_url_load_nested_browsing_context = {
+ frame:'https://html.spec.whatwg.org/multipage/#process-the-frame-attributes',
+ iframe:'https://html.spec.whatwg.org/multipage/#process-the-iframe-attributes',
+ object:'https://html.spec.whatwg.org/multipage/#the-object-element',
+ embed:'https://html.spec.whatwg.org/multipage/#the-embed-element-setup-steps'
+ };
+
+ 'frame src, iframe src, object data, embed src'.split(', ').forEach(function(str) {
+ var arr = str.split(' ');
+ test_load_nested_browsing_context(arr[0], arr[1], spec_url_load_nested_browsing_context[arr[0]]);
+ });
+
+ // loading css with <link>
+ subsetTestByKey('loading', async_test, function() {
+ var elm = document.createElement('link');
+ elm.href = input_url_css;
+ elm.rel = 'stylesheet';
+ document.head.appendChild(elm);
+ this.add_cleanup(function() {
+ document.head.removeChild(elm);
+ });
+ elm.onload = this.step_func_done(function() {
+ var got = elm.sheet.href;
+ assert_true(elm.sheet.href.indexOf(expected_current) > -1, 'sheet.href ' + msg(expected_current, got));
+ assert_equals(elm.sheet.cssRules[0].style.content, '"'+expected_current+'"', 'sheet.cssRules[0].style.content');
+ });
+ }, 'loading css <link>');
+
+ // loading js
+ subsetTestByKey('loading-css', async_test, function() {
+ var elm = document.createElement('script');
+ elm.src = input_url_js + '&var=test_load_js_got';
+ document.head.appendChild(elm); // no cleanup
+ elm.onload = this.step_func_done(function() {
+ assert_equals(window.test_load_js_got, expected_current);
+ });
+ }, 'loading js <script>');
+
+ // loading image
+ function test_load_image(tag, attr, spec_url) {
+ subsetTestByKey('loading', async_test, function() {
+ var elm = document.createElement(tag);
+ if (tag == 'input') {
+ elm.type = 'image';
+ }
+ elm.setAttribute(attr, input_url_png);
+ document.body.appendChild(elm);
+ this.add_cleanup(function() {
+ document.body.removeChild(elm);
+ });
+ elm.onload = this.step_func_done(function() {
+ var got = elm.offsetWidth;
+ assert_equals(got, query_to_image_width[expected_current], msg(expected_current, image_width_to_query[got]));
+ });
+ // <video poster> doesn't notify when the image is loaded so we need to poll :-(
+ var interval;
+ var check_video_width = function() {
+ var width = elm.offsetWidth;
+ if (width != 300 && width != 0) {
+ clearInterval(interval);
+ elm.onload();
+ }
+ }
+ if (tag == 'video') {
+ interval = setInterval(check_video_width, 10);
+ }
+ }, 'loading image <'+tag+' '+attr+'>');
+ }
+
+ var query_to_image_width = {
+ '%E5':1,
+ '%26%23229%3B':1,
+ '%C3%A5':2,
+ '%3F':16,
+ 'unknown query':256,
+ 'default intrinsic width':300
+ };
+
+ var image_width_to_query = {};
+ (function() {
+ for (var x in query_to_image_width) {
+ image_width_to_query[query_to_image_width[x]] = x;
+ }
+ })();
+
+ var spec_url_load_image = {
+ img:'https://html.spec.whatwg.org/multipage/#update-the-image-data',
+ embed:'https://html.spec.whatwg.org/multipage/#the-embed-element-setup-steps',
+ object:'https://html.spec.whatwg.org/multipage/#the-object-element',
+ input:'https://html.spec.whatwg.org/multipage/#image-button-state-(type=image)',
+ video:'https://html.spec.whatwg.org/multipage/#poster-frame'
+ };
+
+ 'img src, embed src, object data, input src, video poster'.split(', ').forEach(function(str) {
+ var arr = str.split(' ');
+ test_load_image(arr[0], arr[1], spec_url_load_image[arr[0]]);
+ });
+
+ // XXX test <img srcset> or its successor
+
+ // loading video
+ function test_load_video(tag, use_source_element) {
+ subsetTestByKey('loading', async_test, function() {
+ var elm = document.createElement(tag);
+ var video_ext = '';
+ if (elm.canPlayType('video/webm; codecs="vp9,opus"')) {
+ video_ext = 'webm';
+ } else if (elm.canPlayType('video/ogg; codecs="theora,flac"')) {
+ video_ext = 'ogv';
+ } else if (elm.canPlayType('video/mp4; codecs="avc1.42E01E,mp4a.40.2"')) {
+ video_ext = 'mp4';
+ }
+ assert_not_equals(video_ext, '', 'no supported video format');
+ var source;
+ if (use_source_element) {
+ source = document.createElement('source');
+ elm.appendChild(source);
+ } else {
+ source = elm;
+ }
+ source.src = input_url_video + '&ext=' + video_ext;
+ elm.preload = 'auto';
+ elm.load();
+ this.add_cleanup(function() {
+ elm.removeAttribute('src');
+ if (elm.firstChild) {
+ elm.removeChild(elm.firstChild);
+ }
+ elm.load();
+ });
+ elm.onloadedmetadata = this.step_func_done(function() {
+ var got = Math.round(elm.duration);
+ assert_equals(got, query_to_video_duration[expected_current], msg(expected_current, video_duration_to_query[got]));
+ });
+ }, 'loading video <'+tag+'>' + (use_source_element ? '<source>' : ''));
+ }
+
+ var query_to_video_duration = {
+ '%E5':3,
+ '%26%23229%3B':3,
+ '%C3%A5':5,
+ '%3F':30,
+ 'unknown query':300,
+ 'Infinity':Infinity,
+ 'NaN':NaN
+ };
+
+ var video_duration_to_query = {};
+ (function() {
+ for (var x in query_to_video_duration) {
+ video_duration_to_query[query_to_video_duration[x]] = x;
+ }
+ })();
+
+ 'video, audio'.split(', ').forEach(function(str) {
+ test_load_video(str);
+ test_load_video(str, true);
+ });
+
+ // loading webvtt
+ subsetTestByKey('loading', async_test, function() {
+ var video = document.createElement('video');
+ var track = document.createElement('track');
+ video.appendChild(track);
+ track.src = input_url_webvtt;
+ track.track.mode = 'showing';
+ track.onload = this.step_func_done(function() {
+ var got = track.track.cues[0].text;
+ assert_equals(got, expected_current);
+ });
+ }, 'loading webvtt <track>');
+
+ // XXX downloading seems hard to automate
+ // <a href download>
+ // <area href download>
+
+ // submit forms
+ function test_submit_form(tag, attr) {
+ subsetTestByKey('submit', async_test, function(){
+ var elm = document.createElement(tag);
+ elm.setAttribute(attr, input_url_html);
+ var form;
+ var button;
+ if (tag == 'form') {
+ form = elm;
+ button = document.createElement('button');
+ } else {
+ form = document.createElement('form');
+ button = elm;
+ }
+ form.method = 'post';
+ form.appendChild(button);
+ var iframe = document.createElement('iframe');
+ var id = 'test_submit_form_' + tag;
+ iframe.name = id;
+ form.target = id;
+ button.type = 'submit';
+ document.body.appendChild(form);
+ document.body.appendChild(iframe);
+ this.add_cleanup(function() {
+ document.body.removeChild(form);
+ document.body.removeChild(iframe);
+ });
+ button.click();
+ iframe.onload = this.step_func_done(function() {
+ var got = iframe.contentDocument.body.textContent;
+ if (got == '') {
+ return;
+ }
+ assert_equals(got, expected_current);
+ });
+ }, 'submit form <'+tag+' '+attr+'>');
+ }
+
+ 'form action, input formaction, button formaction'.split(', ').forEach(function(str) {
+ var arr = str.split(' ');
+ test_submit_form(arr[0], arr[1]);
+ });
+
+ // <base href>
+ subsetTestByKey('base-href', async_test, function() {
+ var iframe = document.createElement('iframe');
+ iframe.src = blank;
+ document.body.appendChild(iframe);
+ this.add_cleanup(function() {
+ document.body.removeChild(iframe);
+ });
+ iframe.onload = this.step_func_done(function() {
+ var doc = iframe.contentDocument;
+ doc.write('<!doctype html><base href="'+input_url+'"><a href></a>');
+ doc.close();
+ var got_baseURI = doc.baseURI;
+ assert_true(got_baseURI.indexOf(expected_current) > -1, msg(expected_current, got_baseURI), 'doc.baseURI');
+ var got_a_href = doc.links[0].href;
+ assert_true(got_a_href.indexOf(expected_current) > -1, msg(expected_current, got_a_href), 'a.href');
+ });
+ }, '<base href>');
+
+ // XXX drag and drop (<a href> or <img src>) seems hard to automate
+
+ // Worker()
+ subsetTestByKey('workers', async_test, function() {
+ var worker = new Worker(input_url_worker);
+ worker.onmessage = this.step_func_done(function(e) {
+ assert_equals(e.data, expected_current);
+ });
+ }, 'Worker constructor');
+
+ // SharedWorker()
+ subsetTestByKey('workers', async_test, function() {
+ var worker = new SharedWorker(input_url_sharedworker);
+ worker.port.onmessage = this.step_func_done(function(e) {
+ assert_equals(e.data, expected_current);
+ });
+ }, 'SharedWorker constructor');
+
+ // EventSource()
+ subsetTestByKey('eventsource', async_test, function() {
+ var source = new EventSource(input_url_eventstream);
+ this.add_cleanup(function() {
+ source.close();
+ });
+ source.onmessage = this.step_func_done(function(e) {
+ assert_equals(e.data, expected_current);
+ });
+ }, 'EventSource constructor');
+
+ // EventSource#url
+ subsetTestByKey('eventsource', test, function() {
+ var source = new EventSource(input_url_eventstream);
+ source.close();
+ var got = source.url;
+ assert_true(source.url.indexOf(expected_current) > -1, msg(expected_current, got));
+ }, 'EventSource#url');
+
+ // window.open
+ subsetTestByKey('window-open', async_test, function() {
+ var id = 'test_window_open';
+ var iframe = document.createElement('iframe');
+ iframe.name = id;
+ document.body.appendChild(iframe);
+ this.add_cleanup(function() {
+ document.body.removeChild(iframe);
+ });
+ window.open(input_url_html, id);
+ iframe.onload = this.step_func(function() {
+ var got = iframe.contentDocument.body.textContent;
+ if (got != "") {
+ assert_equals(got, expected_current);
+ this.done();
+ }
+ });
+ }, 'window.open()');
+
+ // a.search, area.search
+ function test_hyperlink_search(tag) {
+ subsetTestByKey('hyperlink-search', test, function() {
+ var elm = document.createElement(tag);
+ var input_arr = input_url_html.split('?');
+ elm.href = input_arr[0];
+ elm.search = '?' + input_arr[1];
+ var got_href = elm.getAttribute('href');
+ assert_true(got_href.indexOf(expected_utf8) > -1, 'href content attribute ' + msg(expected_utf8, got_href));
+ var got_search = elm.search;
+ assert_true(got_search.indexOf(expected_utf8) > -1, 'getting .search '+msg(expected_utf8, got_search));
+ }, '<'+tag+'>.search');
+ }
+ 'a, area'.split(', ').forEach(function(str) {
+ test_hyperlink_search(str);
+ });
+
+ // history.pushState
+ // history.replaceState
+ function test_history(prop) {
+ subsetTestByKey('history', async_test, function() {
+ var url = input_url_html.replace('resources/', '');
+ var iframe = document.createElement('iframe');
+ iframe.src = blank;
+ document.body.appendChild(iframe);
+ this.add_cleanup(function() {
+ document.body.removeChild(iframe);
+ });
+ iframe.onload = this.step_func_done(function() {
+ // this should resolve against the iframe's URL
+ // "Parse url, relative to the relevant settings object of history."
+ // https://html.spec.whatwg.org/multipage/nav-history-apis.html#shared-history-push%2Freplace-state-steps
+ iframe.contentWindow.history[prop](null, null, url);
+ var got = iframe.contentWindow.location.href;
+ assert_true(got.indexOf(expected_current) > -1, msg(expected_current, got));
+ assert_not_equals(got.indexOf('/resources/'), -1, 'url was resolved against the test\'s URL');
+ });
+ }, 'history.'+prop);
+ }
+
+ 'pushState, replaceState'.split(', ').forEach(function(str) {
+ test_history(str);
+ });
+
+ // SVG
+ var ns = {svg:'http://www.w3.org/2000/svg', xlink:'http://www.w3.org/1999/xlink'};
+ // a
+ subsetTestByKey('svg', async_test, function() {
+ SVGAElement; // check support
+ var iframe = document.createElement('iframe');
+ var id = 'test_svg_a';
+ iframe.name = id;
+ var svg = document.createElementNS(ns.svg, 'svg');
+ var a = document.createElementNS(ns.svg, 'a');
+ a.setAttributeNS(ns.xlink, 'xlink:href', input_url_html);
+ a.setAttribute('target', id);
+ var span = document.createElement('span');
+ a.appendChild(span);
+ svg.appendChild(a);
+ document.body.appendChild(iframe);
+ document.body.appendChild(svg);
+ this.add_cleanup(function() {
+ document.body.removeChild(iframe);
+ document.body.removeChild(svg);
+ });
+ span.click();
+ iframe.onload = this.step_func_done(function() {
+ var got = iframe.contentDocument.body.textContent;
+ if (got != '') {
+ assert_equals(got, expected_current);
+ }
+ });
+ }, 'SVG <a>');
+
+ // feImage, image, use
+ function test_svg(func, tag) {
+ subsetTestByKey('svg', async_test, function() {
+ var uuid = token();
+ var id = 'test_svg_'+tag;
+ var svg = document.createElementNS(ns.svg, 'svg');
+ var parent = func(svg, id);
+ var elm = document.createElementNS(ns.svg, tag);
+ elm.setAttributeNS(ns.xlink, 'xlink:href', stash_put + uuid + '#foo');
+ parent.appendChild(elm);
+ document.body.appendChild(svg);
+ this.add_cleanup(function() {
+ document.body.removeChild(svg);
+ });
+ poll_for_stash(this, uuid, expected_current);
+ }, 'SVG <' + tag + '>');
+ }
+
+ [[function(svg, id) {
+ SVGFEImageElement; // check support
+ var filter = document.createElementNS(ns.svg, 'filter');
+ filter.setAttribute('id', id);
+ svg.appendChild(filter);
+ var rect = document.createElementNS(ns.svg, 'rect');
+ rect.setAttribute('filter', 'url(#'+id+')');
+ svg.appendChild(rect);
+ return filter;
+ }, 'feImage'],
+ [function(svg, id) { SVGImageElement; return svg; }, 'image'],
+ [function(svg, id) { SVGUseElement; return svg; }, 'use']].forEach(function(arr) {
+ test_svg(arr[0], arr[1]);
+ });
+
+ // UTF-8:
+ // XHR
+ subsetTestByKey('xhr', async_test, function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', input_url_html);
+ xhr.onload = this.step_func_done(function() {
+ assert_equals(xhr.response, expected_current);
+ });
+ xhr.send();
+ }, 'XMLHttpRequest#open()');
+
+ // in a worker
+ subsetTestByKey('workers', async_test, function() {
+ var worker = new Worker(input_url_worker_importScripts);
+ worker.onmessage = this.step_func_done(function(e) {
+ assert_equals(e.data, expected_utf8);
+ });
+ }, 'importScripts() in a dedicated worker');
+
+ subsetTestByKey('workers', async_test, function() {
+ var worker = new Worker(input_url_worker_worker);
+ worker.onmessage = this.step_func_done(function(e) {
+ assert_equals(e.data, expected_utf8);
+ });
+ }, 'Worker() in a dedicated worker');
+
+ subsetTestByKey('workers', async_test, function() {
+ var worker = new SharedWorker(input_url_sharedworker_importScripts);
+ worker.port.onmessage = this.step_func_done(function(e) {
+ assert_equals(e.data, expected_utf8);
+ });
+ }, 'importScripts() in a shared worker');
+
+ subsetTestByKey('workers', async_test, function() {
+ var worker = new SharedWorker(input_url_sharedworker_worker);
+ worker.port.onmessage = this.step_func_done(function(e) {
+ assert_equals(e.data, expected_utf8);
+ });
+ }, 'Worker() in a shared worker');
+
+ // WebSocket()
+ subsetTestByKey('websocket', async_test, function() {
+ var ws = new WebSocket('ws://{{host}}:{{ports[ws][0]}}/echo-query?\u00E5');
+ this.add_cleanup(function() {
+ ws.close();
+ });
+ ws.onmessage = this.step_func_done(function(e) {
+ assert_equals(e.data, expected_utf8);
+ });
+ }, 'WebSocket constructor');
+
+ // WebSocket#url
+ subsetTestByKey('websocket', test, function() {
+ var ws = new WebSocket('ws://{{host}}:{{ports[ws][0]}}/echo-query?\u00E5');
+ ws.close();
+ var got = ws.url;
+ assert_true(ws.url.indexOf(expected_utf8) > -1, msg(expected_utf8, got));
+ }, 'WebSocket#url');
+
+ // CSS
+ function test_css(tmpl, expected_cssom, encoding, use_style_element) {
+ var desc = ['CSS', (use_style_element ? '<style>' : '<link> (' + encoding + ')'), tmpl].join(' ');
+ subsetTestByKey('css', async_test, function(){
+ css_is_supported(tmpl, expected_cssom, this);
+ var uuid = token();
+ var id = 'test_css_' + uuid;
+ var url = 'url(stash.py?q=%s&action=put&id=' + uuid + ')';
+ tmpl = tmpl.replace(/<id>/g, id).replace(/<url>/g, url);
+ var link;
+ if (use_style_element) {
+ link = document.createElement('style');
+ link.textContent = tmpl.replace(/%s/g, '\u00E5').replace(/stash\.py/g, 'resources/stash.py');
+ } else {
+ link = document.createElement('link');
+ link.rel = 'stylesheet';
+ link.href = 'resources/css-tmpl.py?encoding='+encoding+'&tmpl='+encodeURIComponent(tmpl);
+ }
+ var div = document.createElement('div');
+ div.id = id;
+ div.textContent='x';
+ document.head.appendChild(link);
+ document.body.appendChild(div);
+ this.add_cleanup(function() {
+ document.head.removeChild(link);
+ document.body.removeChild(div);
+ });
+ poll_for_stash(this, uuid, expected_utf8);
+ }, desc);
+ }
+
+ // fail fast if the input doesn't parse into the expected cssom
+ function css_is_supported(tmpl, expected_cssom, test_obj) {
+ if (expected_cssom === null) {
+ return;
+ }
+ var style = document.createElement('style');
+ style.textContent = tmpl.replace(/<id>/g, 'x').replace(/<url>/g, 'url(data:,)');
+ document.head.appendChild(style);
+ test_obj.add_cleanup(function() {
+ document.head.removeChild(style);
+ });
+ assert_equals(style.sheet.cssRules.length, expected_cssom.length, 'number of style rules');
+ for (var i = 0; i < expected_cssom.length; ++i) {
+ if (expected_cssom[i] === null) {
+ continue;
+ }
+ assert_equals(style.sheet.cssRules[i].style.length, expected_cssom[i], 'number of declarations in style rule #'+i);
+ }
+ }
+
+ [['#<id> { background-image:<url> }', [1] ],
+ ['#<id> { border-image-source:<url> }', [1] ],
+ ['#<id>::before { content:<url> }', [1] ],
+ ['@font-face { font-family:<id>; src:<url> } #<id> { font-family:<id> }', [null, 1] ],
+ ['#<id> { display:list-item; list-style-image:<url> }', [2] ],
+ ['@import <url>;', null ],
+ // XXX maybe cursor isn't suitable for automation here if browsers delay fetching it
+ ['#<id> { cursor:<url>, pointer }', [1] ]].forEach(function(arr) {
+ var input = arr[0];
+ var expected_cssom = arr[1];
+ var other_encoding = encoding == 'utf-8' ? 'windows-1252' : 'utf-8';
+ test_css(input, expected_cssom, encoding);
+ test_css(input, expected_cssom, other_encoding);
+ test_css(input, expected_cssom, null, true);
+ });
+
+ // XXX maybe test if they become relevant:
+ // binding (obsolete?)
+ // aural: cue-after, cue-before, play-during (not implemented?)
+ // hyphenate-resource (not implemented?)
+ // image() (not implemented?)
+
+ // <?xml-stylesheet?>
+ subsetTestByKey('xml', async_test, function() {
+ var iframe = document.createElement('iframe');
+ iframe.src = input_url_xmlstylesheet_css;
+ document.body.appendChild(iframe);
+ this.add_cleanup(function() {
+ document.body.removeChild(iframe);
+ });
+ iframe.onload = this.step_func_done(function() {
+ assert_equals(iframe.contentDocument.firstChild.sheet.cssRules[0].style.content, '"' + expected_utf8 + '"');
+ });
+ }, '<?xml-stylesheet?> (CSS)');
+
+ // new URL()
+ subsetTestByKey('url', test, function() {
+ var url = new URL('http://example.org/'+input_url);
+ var expected = expected_utf8;
+ assert_true(url.href.indexOf(expected) > -1, 'url.href '+msg(expected, url.href));
+ assert_true(url.search.indexOf(expected) > -1, 'url.search '+msg(expected, url.search));
+ }, 'URL constructor, url');
+
+ subsetTestByKey('url', test, function() {
+ var url = new URL('', 'http://example.org/'+input_url);
+ var expected = expected_utf8;
+ assert_true(url.href.indexOf(expected) > -1, 'url.href '+msg(expected, url.href));
+ assert_true(url.search.indexOf(expected) > -1, 'url.search '+msg(expected, url.search));
+ }, 'URL constructor, base');
+
+ // Test different schemes
+ function test_scheme(url, utf8) {
+ subsetTestByKey('scheme', test, function() {
+ var a = document.createElement('a');
+ a.setAttribute('href', url);
+ var got = a.href;
+ var expected = utf8 ? expected_utf8 : expected_current;
+ assert_true(got.indexOf(expected) != -1, msg(expected, got));
+ }, 'Scheme ' + url.split(':')[0] + ' (getting <a>.href)');
+ }
+
+ var test_scheme_urls = ['ftp://example.invalid/?x=\u00E5',
+ 'file:///?x=\u00E5',
+ 'http://example.invalid/?x=\u00E5',
+ 'https://example.invalid/?x=\u00E5',
+ ];
+
+ var test_scheme_urls_utf8 = ['ws://example.invalid/?x=\u00E5',
+ 'wss://example.invalid/?x=\u00E5',
+ 'gopher://example.invalid/?x=\u00E5',
+ 'mailto:example@invalid?x=\u00E5',
+ 'data:text/plain;charset='+encoding+',?x=\u00E5',
+ 'javascript:"?x=\u00E5"',
+ 'ftps://example.invalid/?x=\u00E5',
+ 'httpbogus://example.invalid/?x=\u00E5',
+ 'bitcoin:foo?x=\u00E5',
+ 'geo:foo?x=\u00E5',
+ 'im:foo?x=\u00E5',
+ 'irc:foo?x=\u00E5',
+ 'ircs:foo?x=\u00E5',
+ 'magnet:foo?x=\u00E5',
+ 'mms:foo?x=\u00E5',
+ 'news:foo?x=\u00E5',
+ 'nntp:foo?x=\u00E5',
+ 'sip:foo?x=\u00E5',
+ 'sms:foo?x=\u00E5',
+ 'smsto:foo?x=\u00E5',
+ 'ssh:foo?x=\u00E5',
+ 'tel:foo?x=\u00E5',
+ 'urn:foo?x=\u00E5',
+ 'webcal:foo?x=\u00E5',
+ 'wtai:foo?x=\u00E5',
+ 'xmpp:foo?x=\u00E5',
+ 'web+http:foo?x=\u00E5',
+ ];
+
+ test_scheme_urls.forEach(function(url) {
+ test_scheme(url);
+ });
+
+ test_scheme_urls_utf8.forEach(function(url) {
+ test_scheme(url, true);
+ });
+
+ done();
+};
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/resource.py b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/resource.py
new file mode 100644
index 0000000000..54b4bc9b5b
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/resource.py
@@ -0,0 +1,103 @@
+import os
+import re
+
+from wptserve.utils import isomorphic_decode, isomorphic_encode
+
+def main(request, response):
+ type = request.GET[b'type']
+ encoding = request.GET[b'encoding']
+ # We want the raw input for 'q'
+ q = re.search(r'q=([^&]+)', request.url_parts.query).groups()[0]
+ if type == b'html':
+ return [(b"Content-Type", b"text/html; charset=utf-8")], isomorphic_encode(q)
+ elif type == b'css':
+ return [(b"Content-Type", b"text/css; charset=utf-8")], b"#test::before { content:'%s' }" % isomorphic_encode(q)
+ elif type == b'js':
+ return [(b"Content-Type", b"text/javascript; charset=utf-8")], b"%s = '%s';" % (request.GET[b'var'], isomorphic_encode(q))
+ elif type == b'worker':
+ return [(b"Content-Type", b"text/javascript")], b"postMessage('%s'); close();" % isomorphic_encode(q)
+ elif type == b'sharedworker':
+ return [(b"Content-Type", b"text/javascript")], b"onconnect = function(e) { e.source.postMessage('%s'); close(); };" % isomorphic_encode(q)
+ elif type == b'worker_importScripts':
+ return ([(b"Content-Type", b"text/javascript; charset=%s" % encoding)], # charset should be ignored for workers
+ b"""try {
+ var x = 'importScripts failed to run';
+ importScripts('?q=\\u00E5&type=js&var=x&encoding=%s');
+ postMessage(x);
+ close();
+ } catch(ex) {
+ postMessage(String(ex));
+ }""" % encoding)
+ elif type == b'worker_worker':
+ return ([(b"Content-Type", b"text/javascript; charset=%s" % encoding)], # charset should be ignored for workers
+ b"""try {
+ var worker = new Worker('?q=\\u00E5&type=worker&encoding=%s');
+ worker.onmessage = function(e) {
+ postMessage(e.data);
+ close();
+ };
+ } catch(ex) {
+ postMessage(String(ex));
+ }""" % encoding)
+ elif type == b'sharedworker_importScripts':
+ return ([(b"Content-Type", b"text/javascript; charset=%s" % encoding)], # charset should be ignored for workers
+ b"""var x = 'importScripts failed to run';
+ onconnect = function(e) {
+ var connect_port = e.source;
+ try {
+ importScripts('?q=\\u00E5&type=js&var=x&encoding=%s');
+ connect_port.postMessage(x);
+ close();
+ } catch(ex) {
+ connect_port.postMessage(String(ex));
+ }
+ };""" % encoding)
+ elif type == b'sharedworker_worker':
+ return ([(b"Content-Type", b"text/javascript; charset=%s" % encoding)], # charset should be ignored for workers
+ b"""onconnect = function(e) {
+ var connect_port = e.source;
+ try {
+ var worker = new Worker('?q=\\u00E5&type=worker&encoding=%s');
+ worker.onmessage = function(e) {
+ connect_port.postMessage(e.data);
+ close();
+ };
+ } catch(ex) {
+ connect_port.postMessage(String(ex));
+ }
+ };""" % encoding)
+ elif type == b'eventstream':
+ return [(b"Content-Type", b"text/event-stream")], b"data: %s\n\n" % isomorphic_encode(q)
+ elif type == b'svg':
+ return [(b"Content-Type", b"image/svg+xml")], b"<svg xmlns='http://www.w3.org/2000/svg'>%s</svg>" % isomorphic_encode(q)
+ elif type == b'xmlstylesheet_css':
+ return ([(b"Content-Type", b"application/xhtml+xml; charset=%s" % encoding)],
+ (u"""<?xml-stylesheet href="?q=&#x00E5;&amp;type=css&amp;encoding=%s"?><html xmlns="http://www.w3.org/1999/xhtml"/>""" % isomorphic_decode(encoding))
+ .encode(isomorphic_decode(encoding)))
+ elif type == b'png':
+ if q == u'%E5' or q == u'%26%23229%3B':
+ image = u'green-1x1.png'
+ elif q == u'%C3%A5':
+ image = u'green-2x2.png'
+ elif q == u'%3F':
+ image = u'green-16x16.png'
+ else:
+ image = u'green-256x256.png'
+ rv = open(os.path.join(request.doc_root, u"images", image), "rb").read()
+ return [(b"Content-Type", b"image/png")], rv
+ elif type == b'video':
+ ext = request.GET[b'ext']
+ if q == u'%E5' or q == u'%26%23229%3B':
+ video = u'A4' # duration: 3
+ elif q == u'%C3%A5':
+ video = u'movie_5' # duration: 5
+ elif q == u'%3F':
+ video = u'green-at-15' # duration: 30
+ else:
+ video = u'movie_300' # duration: 300
+ rv = open(os.path.join(request.doc_root, u"media", u"%s.%s" % (video, isomorphic_decode(ext))), "rb").read()
+ if ext == b'ogv':
+ ext = b'ogg'
+ return [(b"Content-Type", b"video/%s" % ext)], rv
+ elif type == b'webvtt':
+ return [(b"Content-Type", b"text/vtt")], b"WEBVTT\n\n00:00:00.000 --> 00:00:01.000\n%s" % isomorphic_encode(q)
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/stash.py b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/stash.py
new file mode 100644
index 0000000000..a6b589d2ac
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/resources/stash.py
@@ -0,0 +1,15 @@
+import re
+
+def main(request, response):
+ key = request.GET[b'id']
+ action = request.GET[b'action']
+ if action == b'put':
+ # We want the raw input for 'q'
+ q = re.search(r'q=([^&]+)', request.url_parts.query).groups()[0]
+ request.server.stash.put(key, q)
+ return [(b"Content-Type", b"text/html")], u'Put %s' % q
+ else:
+ q = request.server.stash.take(key)
+ if q != None:
+ return [(b"Content-Type", b"text/html")], q
+ return [], u""
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-16be.html b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-16be.html
new file mode 100644
index 0000000000..f0279ab1c6
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-16be.html
Binary files differ
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-16le.html b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-16le.html
new file mode 100644
index 0000000000..e29385b8a7
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-16le.html
Binary files differ
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-8.html b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-8.html
new file mode 100644
index 0000000000..b2b3b9cb28
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/utf-8.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Resolving URLs, URL character encoding, utf-8</title>
+<meta name="timeout" content="long">
+<meta name="variant" content="?include=nested-browsing">
+<meta name="variant" content="?include=loading">
+<meta name="variant" content="?include=submit">
+<meta name="variant" content="?include=base-href">
+<meta name="variant" content="?include=workers">
+<meta name="variant" content="?include=eventsource">
+<meta name="variant" content="?include=window-open">
+<meta name="variant" content="?include=hyperlink-search">
+<meta name="variant" content="?include=history">
+<meta name="variant" content="?include=svg">
+<meta name="variant" content="?include=xhr">
+<meta name="variant" content="?include=websocket">
+<meta name="variant" content="?include=css">
+<meta name="variant" content="?include=xml">
+<meta name="variant" content="?include=url">
+<meta name="variant" content="?include=scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/subset-tests-by-key.js"></script>
+<script src="/common/utils.js"></script>
+<div id=log></div>
+<script src="resources/resolve-url.js?encoding=utf-8&pipe=sub"></script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/windows-1251.html b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/windows-1251.html
new file mode 100644
index 0000000000..dbf136706e
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/windows-1251.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset=windows-1251>
+<title>Resolving URLs, URL character encoding, windows-1251</title>
+<meta name="timeout" content="long">
+<meta name="variant" content="?include=nested-browsing">
+<meta name="variant" content="?include=loading">
+<meta name="variant" content="?include=submit">
+<meta name="variant" content="?include=base-href">
+<meta name="variant" content="?include=workers">
+<meta name="variant" content="?include=eventsource">
+<meta name="variant" content="?include=window-open">
+<meta name="variant" content="?include=hyperlink-search">
+<meta name="variant" content="?include=history">
+<meta name="variant" content="?include=svg">
+<meta name="variant" content="?include=xhr">
+<meta name="variant" content="?include=websocket">
+<meta name="variant" content="?include=css">
+<meta name="variant" content="?include=xml">
+<meta name="variant" content="?include=url">
+<meta name="variant" content="?include=scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/subset-tests-by-key.js"></script>
+<script src="/common/utils.js"></script>
+<div id=log></div>
+<script src="resources/resolve-url.js?encoding=windows-1251&pipe=sub"></script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/windows-1252.html b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/windows-1252.html
new file mode 100644
index 0000000000..43b7dc9ea4
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/resolving-urls/query-encoding/windows-1252.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset=windows-1252>
+<title>Resolving URLs, URL character encoding, windows-1252</title>
+<meta name="timeout" content="long">
+<meta name="variant" content="?include=nested-browsing">
+<meta name="variant" content="?include=loading">
+<meta name="variant" content="?include=submit">
+<meta name="variant" content="?include=base-href">
+<meta name="variant" content="?include=workers">
+<meta name="variant" content="?include=eventsource">
+<meta name="variant" content="?include=window-open">
+<meta name="variant" content="?include=hyperlink-search">
+<meta name="variant" content="?include=history">
+<meta name="variant" content="?include=svg">
+<meta name="variant" content="?include=xhr">
+<meta name="variant" content="?include=websocket">
+<meta name="variant" content="?include=css">
+<meta name="variant" content="?include=xml">
+<meta name="variant" content="?include=url">
+<meta name="variant" content="?include=scheme">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/subset-tests-by-key.js"></script>
+<script src="/common/utils.js"></script>
+<div id=log></div>
+<script src="resources/resolve-url.js?encoding=windows-1252&pipe=sub"></script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url-about-srcdoc.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url-about-srcdoc.https.window.js
new file mode 100644
index 0000000000..b8fad36ccf
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url-about-srcdoc.https.window.js
@@ -0,0 +1,27 @@
+// META: script=/common/get-host-info.sub.js
+
+// Load about:srcdoc in a sandboxed iframe. Check the document.baseURI is
+// correct.
+const runTest = (description, iframe_sandbox) => {
+ promise_test(async test => {
+ const iframe = document.createElement("iframe");
+ iframe.sandbox = iframe_sandbox;
+ iframe.srcdoc = `
+ <script>
+ parent.postMessage(document.baseURI, '*');
+ </scr`+`ipt>
+ `;
+ const child_base_uri = new Promise(r => onmessage = e => r(e.data));
+ document.body.appendChild(iframe);
+ // [spec]: https://html.spec.whatwg.org/C/#fallback-base-url
+ // Step 1: If document is an iframe srcdoc document, then return the
+ // document base URL of document's browsing context's container
+ // document.
+ assert_equals(await child_base_uri, document.baseURI);
+ }, description);
+}
+
+onload = () => {
+ runTest("allow-same-origin", "allow-scripts allow-same-origin");
+ runTest("disallow-same-origin", "allow-scripts");
+}
diff --git a/testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url-changes-about-srcdoc.https.window.js b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url-changes-about-srcdoc.https.window.js
new file mode 100644
index 0000000000..f3eca2e0b5
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url-changes-about-srcdoc.https.window.js
@@ -0,0 +1,47 @@
+// Load about:srcdoc in a iframe. Check the document.baseURI is still
+// correct after the parent changes its base URL..
+const runTest = (description, sandbox_flags) => {
+ promise_test(async test => {
+ // Create child.
+ const iframe = document.createElement("iframe");
+ if (sandbox_flags !== null)
+ iframe.sandbox = sandbox_flags;
+ iframe.srcdoc = `
+ <script>
+ addEventListener('message', (event) => {
+ if (event.data == 'report baseURI')
+ event.source.postMessage(document.baseURI, event.origin);
+ });
+ parent.postMessage('loaded', '*');
+ </scr`+`ipt>
+ `;
+
+ const child_loaded = new Promise(r => onmessage = e => r(e.data));
+ document.body.appendChild(iframe);
+ assert_equals(await child_loaded, "loaded");
+
+ // Verify child's baseURI matches parent.
+ const original_parent_baseURI = document.baseURI;
+ const child_base_uri = new Promise(r => onmessage = e => r(e.data));
+ frames[0].postMessage("report baseURI", "*");
+ assert_equals(await child_base_uri, original_parent_baseURI);
+
+ // Parent changes its baseURI, requests child to report.
+ const base_element = document.createElement("base");
+ base_element.href = "https://foo.com";
+ document.head.appendChild(base_element);
+ assert_not_equals(document.baseURI, original_parent_baseURI,
+ "parent baseURI failed to change.");
+
+ // Verify child's baseURI didn't change.
+ const child_base_uri2 = new Promise(r => onmessage = e => r(e.data));
+ frames[0].postMessage("report baseURI", "*");
+ assert_equals(await child_base_uri2, original_parent_baseURI);
+
+ // Cleanup.
+ base_element.remove();
+ }, description);
+}
+
+runTest("non-sandboxed srcdoc - parent changes baseURI",null);
+runTest("sandboxed srcdoc - parent changes baseURI", "allow-scripts");
diff --git a/testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url.html b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url.html
new file mode 100644
index 0000000000..ee9d726481
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/document-base-url.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: document base URL</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+ iframe { display: none }
+</style>
+<body onload="on_load()">
+ <div id="log"></div>
+ <script>
+ function assert_resolve_url(doc, base) {
+ var img = doc.createElement("img");
+ img.src = "foo";
+ var actual = img.src;
+ var expected = base + "/foo";
+ assert_equals(actual, expected, "img src should resolve correctly");
+ }
+
+ var t1 = async_test("The document base URL of a document containing one or more base elements with href attributes is the frozen base URL of the first base element in the document that has an href attribute, in tree order.");
+
+ function on_load() {
+ t1.step(function () {
+ var base = document.createElement("base");
+ base.setAttribute("href", "/foo/bar");
+ document.head.appendChild(base);
+ t1.add_cleanup(function () {
+ document.head.removeChild(base);
+ });
+
+ assert_resolve_url(document, location.href.replace(location.pathname, "/foo"));
+ assert_equals(document.baseURI, base.href, "The document base URL should be URL of the first base element that has an href attribute.");
+ });
+ t1.done();
+ }
+
+ async_test(function() {
+ var iframe = document.createElement("iframe");
+ iframe.onload = this.step_func_done(function () {
+ assert_resolve_url(iframe.contentDocument, location.href.replace(location.pathname, "/common"));
+ assert_equals(iframe.contentDocument.baseURI, iframe.contentDocument.location.href, "The document base URL should be the document's address.");
+ });
+ iframe.setAttribute("src", "/common/blank.html");
+ document.body.appendChild(iframe);
+ }, "The fallback base URL of a document containing no base element is the document's address.");
+
+ async_test(function () {
+ var iframe = document.createElement("iframe");
+ iframe.onload = this.step_func_done(function () {
+ assert_resolve_url(iframe.contentDocument, location.href.replace("/document-base-url.html", ""));
+ assert_equals(iframe.contentDocument.baseURI, document.baseURI, "The document base URL should be the creator document's base URL.");
+ });
+ iframe.setAttribute("src", "about:blank");
+ document.body.appendChild(iframe);
+ }, "The fallback base URL of a document whose address is about:blank is the document base URL of the creator document.");
+
+ async_test(function () {
+ var iframe = document.createElement("iframe");
+ iframe.onload = this.step_func_done(function () {
+ var doc = iframe.contentDocument;
+ var base = doc.body.appendChild(doc.createElement("base"));
+ base.href = "sub/";
+ assert_resolve_url(doc, location.href.replace("/document-base-url.html", "/sub"));
+ assert_equals(doc.baseURI, document.baseURI.replace("/document-base-url.html", "/sub/"));
+ });
+ iframe.setAttribute("src", "about:blank");
+ document.body.appendChild(iframe);
+ }, "about:blank with a base element.");
+
+ async_test(function () {
+ var iframe = document.createElement("iframe");
+ iframe.onload = this.step_func_done(function () {
+ assert_resolve_url(iframe.contentDocument, location.href.replace("/document-base-url.html", ""));
+ assert_equals(iframe.contentDocument.baseURI, document.baseURI, "The document base URL should be the containing document's base URL.");
+ });
+ iframe.setAttribute("srcdoc", "<p>foobar</p>");
+ document.body.appendChild(iframe);
+ }, "The fallback base URL of an iframe srcdoc document is the document base URL of the document's browsing context's browsing context container's document.");
+
+ async_test(function () {
+ var iframe = document.createElement("iframe");
+ iframe.onload = this.step_func_done(function () {
+ var doc = iframe.contentDocument;
+ assert_resolve_url(doc, location.href.replace("/document-base-url.html", "/sub"));
+ assert_equals(doc.baseURI, document.baseURI.replace("/document-base-url.html", "/sub/"),
+ "The srcdoc document's base URL should be set by the <base> tag.");
+ });
+ iframe.setAttribute("srcdoc", "<base href='sub/'><p>foobar</p>");
+ document.body.appendChild(iframe);
+ }, "The base URL of an iframe srcdoc document with a <base> tag should be set by that tag.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/terminology-0/multiple-base.sub.html b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/multiple-base.sub.html
new file mode 100644
index 0000000000..49c428c5be
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/multiple-base.sub.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>document base URL: multiple base elements</title>
+<base target="_blank" >
+<base href="http://{{domains[www]}}:{{ports[http][0]}}/">
+<base href="http://{{domains[www1]}}:{{ports[http][0]}}/">
+<base href="http://{{domains[www2]}}:{{ports[http][0]}}/">
+<link rel="author" title="Denis Ah-Kang" href="mailto:denis@w3.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+ test(function(){
+ var base = document.querySelectorAll("base");
+ assert_equals(document.baseURI, document.querySelectorAll("base[href]")[0].href);
+ }, "If there are multiple <base> elements, the document base URL is the frozen base URL of the first one that has an href attribute");
+</script>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/terminology-0/nontraditional-about-srcdoc.html b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/nontraditional-about-srcdoc.html
new file mode 100644
index 0000000000..bdb33842a7
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/nontraditional-about-srcdoc.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Non-traditional about:srcdoc documents</title>
+<link rel="help" href="https://github.com/whatwg/html/issues/9514">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+promise_test(async t => {
+ const iframe = document.createElement('iframe');
+
+ const srcdocOpenPromise = new Promise(resolve => {
+ window.srcdocOpenResolve = resolve;
+ });
+
+ iframe.srcdoc = `
+ <body onload="document.open();window.parent.srcdocOpenResolve();"></body>`;
+ document.body.append(iframe);
+
+ await srcdocOpenPromise;
+ assert_equals(iframe.contentDocument.URL, 'about:srcdoc');
+
+ // Calling the `about:srcdoc` Window's `fetch()` like this uses that Window's
+ // environment settings object as the request's client, which is where the
+ // request's referrer comes from, per
+ // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer.
+ //
+ // If this `document.open()`d srcdoc document is considered a proper
+ // `about:srcdoc` document, the referrer will not be `about:srcdoc`, but will
+ // instead come from the parent document.
+ let referrer = await iframe.contentWindow.fetch('resources/echo-referrer-text.py');
+ referrer = await referrer.text();
+ assert_equals(referrer, location.href,
+ 'The request referrer is retrieved from the parent document for ' +
+ 'about:srcdoc documents');
+
+ // Observe that the "about base URL" is retrieved [1] for `document.baseURI`
+ // as well, indicating that the document is treated like a normal srcdoc
+ // document.
+ // [1]: https://html.spec.whatwg.org/#fallback-base-url
+ assert_equals(iframe.contentDocument.baseURI, location.href,
+ 'The about base URL is retrieved as the base URL for srcdoc documents');
+}, 'about:srcdoc with document.open() is treated like a normal about:srcdoc document');
+
+promise_test(async t => {
+ const iframe = document.createElement('iframe');
+
+ const javascriptURLPromise = new Promise(resolve => {
+ window.javascriptURLResolve = resolve;
+ });
+
+ iframe.srcdoc = `
+ <script>
+ location.href = "javascript:'<body onload=window.parent.javascriptURLResolve();>Document contents here</body>'";
+ </scr`+`ipt>`;
+ document.body.append(iframe);
+
+ // This promise will resolve as a result of script running in the *new*
+ // document that gets created by the `javascript:` URL.
+ await javascriptURLPromise;
+ assert_equals(iframe.contentDocument.URL, 'about:srcdoc');
+
+ // See the first assertion in the first test in this file.
+ let referrer = await iframe.contentWindow.fetch('resources/echo-referrer-text.py');
+ referrer = await referrer.text();
+ assert_equals(referrer, location.href,
+ 'The request referrer is retrieved from the parent document for ' +
+ 'about:srcdoc documents');
+
+ // See the second assertion in the first test in this file.
+ assert_equals(iframe.contentDocument.baseURI, location.href,
+ 'The about base URL is retrieved as the base URL for srcdoc documents');
+}, 'about:srcdoc navigated via a `javascript:` URL is treated like a normal about:srcdoc document');
+
+promise_test(async t => {
+ const iframe = document.createElement('iframe');
+
+ const srcdocLoadPromise = new Promise(resolve => {
+ iframe.onload = resolve;
+ });
+ iframe.srcdoc = `Document contents here`;
+ document.body.append(iframe);
+
+ await srcdocLoadPromise;
+ assert_equals(iframe.contentDocument.URL, 'about:srcdoc');
+
+ // Change the document's URL to `about:srcdoc#foo` and ensure that the
+ // observable behavior in that document is consistent with treating it as a
+ // normal `about:srcdoc` document.
+ iframe.contentWindow.history.replaceState(null, '', 'about:srcdoc#foo');
+ assert_equals(iframe.contentDocument.URL, 'about:srcdoc#foo');
+
+ // See the first assertion in the first test in this file.
+ let referrer = await iframe.contentWindow.fetch('resources/echo-referrer-text.py');
+ referrer = await referrer.text();
+ assert_equals(referrer, location.href,
+ 'The request referrer is retrieved from the parent document for ' +
+ 'about:srcdoc documents');
+
+ // See the second assertion in the first test in this file.
+ assert_equals(iframe.contentDocument.baseURI, location.href,
+ 'The about base URL is retrieved as the base URL for srcdoc documents');
+}, 'about:srcdoc with URL changed by history.replaceState() is treated like ' +
+ 'a normal about:srcdoc document');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/infrastructure/urls/terminology-0/resources/echo-referrer-text.py b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/resources/echo-referrer-text.py
new file mode 100644
index 0000000000..8a48d3346a
--- /dev/null
+++ b/testing/web-platform/tests/html/infrastructure/urls/terminology-0/resources/echo-referrer-text.py
@@ -0,0 +1,4 @@
+def main(request, response):
+ response_headers = [(b"Content-Type", b"text/plain")]
+ body = b"%s"% request.headers.get(b"referer", b"")
+ return (200, response_headers, body)