diff --git a/Tests/LibWeb/TestConfig.ini b/Tests/LibWeb/TestConfig.ini
index 051969bd91b..b64f31d714a 100644
--- a/Tests/LibWeb/TestConfig.ini
+++ b/Tests/LibWeb/TestConfig.ini
@@ -34,3 +34,7 @@ Text/input/wpt-import/html/syntax/parsing/named-character-references.html
Text/input/wpt-import/html/syntax/parsing/support/no-doctype-name-eof.html
Text/input/wpt-import/html/syntax/parsing/support/no-doctype-name-line.html
Text/input/wpt-import/html/syntax/parsing/support/no-doctype-name-space.html
+Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html
+Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html
+Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html
+Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/post-parent-type-error.html
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-extra.txt b/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-extra.txt
new file mode 100644
index 00000000000..9b3116bd0f4
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-extra.txt
@@ -0,0 +1,12 @@
+Summary
+
+Harness status: OK
+
+Rerun
+
+Found 2 tests
+
+2 Pass
+Details
+Result Test Name MessagePass Throwing name getter fails serialization
+Pass Errors sent across realms should preserve their type
\ No newline at end of file
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.txt b/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.txt
new file mode 100644
index 00000000000..d57ff0e90af
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.txt
@@ -0,0 +1,52 @@
+Summary
+
+Harness status: OK
+
+Rerun
+
+Found 41 tests
+
+36 Pass
+5 Fail
+Details
+Result Test Name MessagePass Primitive string is cloned
+Pass Primitive integer is cloned
+Pass Primitive floating point is cloned
+Pass Primitive floating point (negative) is cloned
+Pass Primitive number (hex) is cloned
+Pass Primitive number (scientific) is cloned
+Pass Primitive boolean is cloned
+Pass Instance of Boolean is cloned
+Pass Instance of Number is cloned
+Pass Instance of String is cloned
+Pass Instance of Date is cloned
+Pass Instance of RegExp is cloned
+Pass Value 'null' is cloned
+Pass Value 'undefined' is cloned
+Pass Object properties are cloned
+Pass Prototype chains are not walked.
+Pass Property descriptors of Objects are not cloned
+Pass Cycles are preserved in Objects
+Fail Identity of duplicates is preserved
+Pass Property order is preserved
+Pass Enumerable properties of Arrays are cloned
+Pass Property descriptors of Arrays are not cloned
+Pass Cycles are preserved in Arrays
+Fail ImageData object can be cloned Cannot serialize platform objects
+Fail ImageData expandos are not cloned Cannot serialize platform objects
+Pass Window objects cannot be cloned
+Pass Document objects cannot be cloned
+Pass Empty Error objects can be cloned
+Pass Error objects can be cloned
+Pass EvalError objects can be cloned
+Pass RangeError objects can be cloned
+Pass ReferenceError objects can be cloned
+Pass SyntaxError objects can be cloned
+Pass TypeError objects can be cloned
+Pass URIError objects can be cloned
+Pass URIError objects from other realms are treated as URIError
+Pass Cloning a modified Error
+Pass Error.message: getter is ignored when cloning
+Pass Error.message: undefined property is stringified
+Fail DOMException objects can be cloned Cannot serialize platform objects
+Fail DOMException objects created by the UA can be cloned Cannot serialize platform objects
\ No newline at end of file
diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt b/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt
new file mode 100644
index 00000000000..cb88bc26157
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.txt
@@ -0,0 +1,162 @@
+Summary
+
+Harness status: OK
+
+Rerun
+
+Found 150 tests
+
+124 Pass
+25 Fail
+1 Optional Feature Unsupported
+Details
+Result Test Name MessagePass primitive undefined
+Pass primitive null
+Pass primitive true
+Pass primitive false
+Pass primitive string, empty string
+Pass primitive string, lone high surrogate
+Pass primitive string, lone low surrogate
+Pass primitive string, NUL
+Pass primitive string, astral character
+Pass primitive number, 0.2
+Pass primitive number, 0
+Pass primitive number, -0
+Pass primitive number, NaN
+Pass primitive number, Infinity
+Pass primitive number, -Infinity
+Pass primitive number, 9007199254740992
+Pass primitive number, -9007199254740992
+Pass primitive number, 9007199254740994
+Pass primitive number, -9007199254740994
+Pass primitive BigInt, 0n
+Pass primitive BigInt, -0n
+Pass primitive BigInt, -9007199254740994000n
+Pass primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n
+Pass Array primitives
+Pass Object primitives
+Pass Boolean true
+Pass Boolean false
+Pass Array Boolean objects
+Pass Object Boolean objects
+Pass String empty string
+Pass String lone high surrogate
+Pass String lone low surrogate
+Pass String NUL
+Pass String astral character
+Pass Array String objects
+Pass Object String objects
+Pass Number 0.2
+Pass Number 0
+Pass Number -0
+Pass Number NaN
+Pass Number Infinity
+Pass Number -Infinity
+Pass Number 9007199254740992
+Pass Number -9007199254740992
+Pass Number 9007199254740994
+Pass Number -9007199254740994
+Pass BigInt -9007199254740994n
+Pass Array Number objects
+Pass Object Number objects
+Pass Date 0
+Pass Date -0
+Pass Date -8.64e15
+Pass Date 8.64e15
+Pass Array Date objects
+Pass Object Date objects
+Pass RegExp flags and lastIndex
+Pass RegExp sticky flag
+Pass RegExp unicode flag
+Pass RegExp empty
+Pass RegExp slash
+Pass RegExp new line
+Pass Array RegExp object, RegExp flags and lastIndex
+Pass Array RegExp object, RegExp sticky flag
+Pass Array RegExp object, RegExp unicode flag
+Pass Array RegExp object, RegExp empty
+Pass Array RegExp object, RegExp slash
+Pass Array RegExp object, RegExp new line
+Pass Object RegExp object, RegExp flags and lastIndex
+Pass Object RegExp object, RegExp sticky flag
+Pass Object RegExp object, RegExp unicode flag
+Pass Object RegExp object, RegExp empty
+Pass Object RegExp object, RegExp slash
+Pass Object RegExp object, RegExp new line
+Pass Empty Error object
+Pass Error object
+Pass EvalError object
+Pass RangeError object
+Pass ReferenceError object
+Pass SyntaxError object
+Pass TypeError object
+Pass URIError object
+Pass Blob basic
+Pass Blob unpaired high surrogate (invalid utf-8)
+Pass Blob unpaired low surrogate (invalid utf-8)
+Pass Blob paired surrogates (invalid utf-8)
+Pass Blob empty
+Pass Blob NUL
+Pass Array Blob object, Blob basic
+Pass Array Blob object, Blob unpaired high surrogate (invalid utf-8)
+Pass Array Blob object, Blob unpaired low surrogate (invalid utf-8)
+Pass Array Blob object, Blob paired surrogates (invalid utf-8)
+Pass Array Blob object, Blob empty
+Pass Array Blob object, Blob NUL
+Pass Array Blob object, two Blobs
+Pass Object Blob object, Blob basic
+Pass Object Blob object, Blob unpaired high surrogate (invalid utf-8)
+Pass Object Blob object, Blob unpaired low surrogate (invalid utf-8)
+Pass Object Blob object, Blob paired surrogates (invalid utf-8)
+Pass Object Blob object, Blob empty
+Pass Object Blob object, Blob NUL
+Pass File basic
+Pass FileList empty
+Pass Array FileList object, FileList empty
+Pass Object FileList object, FileList empty
+Fail ImageData 1x1 transparent black
+Fail ImageData 1x1 non-transparent non-black
+Fail Array ImageData object, ImageData 1x1 transparent black
+Fail Array ImageData object, ImageData 1x1 non-transparent non-black
+Fail Object ImageData object, ImageData 1x1 transparent black
+Fail Object ImageData object, ImageData 1x1 non-transparent non-black
+Pass Array sparse
+Pass Array with non-index property
+Pass Object with index property and length
+Fail Array with circular reference
+Fail Object with circular reference
+Fail Array with identical property values
+Fail Object with identical property values
+Pass Object with property on prototype
+Pass Object with non-enumerable property
+Pass Object with non-writable property
+Pass Object with non-configurable property
+Pass Object with a getter that throws
+Fail ImageBitmap 1x1 transparent black
+Fail ImageBitmap 1x1 non-transparent non-black
+Fail Array ImageBitmap object, ImageBitmap 1x1 transparent black
+Fail Array ImageBitmap object, ImageBitmap 1x1 transparent non-black
+Fail Object ImageBitmap object, ImageBitmap 1x1 transparent black
+Fail Object ImageBitmap object, ImageBitmap 1x1 transparent non-black
+Pass ObjectPrototype must lose its exotic-ness when cloned
+Pass Serializing a non-serializable platform object fails
+Pass An object whose interface is deleted from the global must still deserialize
+Pass A subclass instance will deserialize as its closest serializable superclass
+Fail Resizable ArrayBuffer
+Fail Growable SharedArrayBuffer
+Pass Length-tracking TypedArray
+Pass Length-tracking DataView
+Pass Serializing OOB TypedArray throws
+Pass Serializing OOB DataView throws
+Fail ArrayBuffer
+Fail MessagePort
+Fail A detached ArrayBuffer cannot be transferred
+Pass A detached platform object cannot be transferred
+Pass Transferring a non-transferable platform object fails
+Fail An object whose interface is deleted from the global object must still be received
+Optional Feature Unsupported A subclass instance will be received as its closest transferable superclass ReadableStream isn't transferable
+Fail Resizable ArrayBuffer is transferable
+Fail Length-tracking TypedArray is transferable
+Fail Length-tracking DataView is transferable
+Pass Transferring OOB TypedArray throws
+Pass Transferring OOB DataView throws
\ No newline at end of file
diff --git a/Tests/LibWeb/Text/input/wpt-import/common/sab.js b/Tests/LibWeb/Text/input/wpt-import/common/sab.js
new file mode 100644
index 00000000000..a3ea610e165
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/common/sab.js
@@ -0,0 +1,21 @@
+const createBuffer = (() => {
+ // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()`
+ let sabConstructor;
+ try {
+ sabConstructor = new WebAssembly.Memory({ shared:true, initial:0, maximum:0 }).buffer.constructor;
+ } catch(e) {
+ sabConstructor = null;
+ }
+ return (type, length, opts) => {
+ if (type === "ArrayBuffer") {
+ return new ArrayBuffer(length, opts);
+ } else if (type === "SharedArrayBuffer") {
+ if (sabConstructor && sabConstructor.name !== "SharedArrayBuffer") {
+ throw new Error("WebAssembly.Memory does not support shared:true");
+ }
+ return new sabConstructor(length, opts);
+ } else {
+ throw new Error("type has to be ArrayBuffer or SharedArrayBuffer");
+ }
+ }
+})();
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/messagechannel.any.js
new file mode 100644
index 00000000000..6ba17f7a89a
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/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/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html
new file mode 100644
index 00000000000..c4fd5824a1c
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html
@@ -0,0 +1,11 @@
+
+
+
A test page that echos back anything postMessaged to it to its parent
+
+
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html.headers b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html.headers
new file mode 100644
index 00000000000..4e798cd9f5d
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/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/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js
new file mode 100644
index 00000000000..cbbde8a73c8
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/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/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js.headers b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js.headers
new file mode 100644
index 00000000000..6604450991a
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-worker.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html
new file mode 100644
index 00000000000..378c953fbe8
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html
@@ -0,0 +1,11 @@
+
+
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/post-parent-type-error.html b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/post-parent-type-error.html
new file mode 100644
index 00000000000..d6713c4192e
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/post-parent-type-error.html
@@ -0,0 +1,9 @@
+
+
+Helper that posts its parent a TypeError
+
+
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-extra.html b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-extra.html
new file mode 100644
index 00000000000..260579a230c
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-extra.html
@@ -0,0 +1,46 @@
+
+
+Structured cloning of Error objects: extra tests
+
+
+
+
+
+
+
+
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structured-cloning-error-stack-optional.sub.window.js
new file mode 100644
index 00000000000..cbc6a73d518
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/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/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html
new file mode 100644
index 00000000000..319c4b5dc79
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html
@@ -0,0 +1,637 @@
+
+
+
+
+ 2.8 Common DOM interfaces - Structured Clone Algorithm
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/transfer-errors.window.js b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/transfer-errors.window.js
new file mode 100644
index 00000000000..b3ecd86b40a
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/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/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.html b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.html
new file mode 100644
index 00000000000..352d6d0b950
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js b/Tests/LibWeb/Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js
new file mode 100644
index 00000000000..2a46d790b87
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/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/Tests/LibWeb/Text/input/wpt-import/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
new file mode 100644
index 00000000000..00a86fa74b9
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js
@@ -0,0 +1,45 @@
+/**
+ * Runs a collection of tests that determine if an API implements structured clone
+ * correctly.
+ *
+ * The `runner` parameter has the following properties:
+ * - `setup()`: An optional function run once before testing starts
+ * - `teardown()`: An option function run once after all tests are done
+ * - `preTest()`: An optional, async function run before a test
+ * - `postTest()`: An optional, async function run after a test is done
+ * - `structuredClone(obj, transferList)`: Required function that somehow
+ * structurally clones an object.
+ * Must return a promise.
+ * - `hasDocument`: When true, disables tests that require a document. True by default.
+ */
+
+function runStructuredCloneBatteryOfTests(runner) {
+ const defaultRunner = {
+ setup() {},
+ preTest() {},
+ postTest() {},
+ teardown() {},
+ hasDocument: true
+ };
+ runner = Object.assign({}, defaultRunner, runner);
+
+ let setupPromise = runner.setup();
+ const allTests = structuredCloneBatteryOfTests.map(test => {
+
+ if (!runner.hasDocument && test.requiresDocument) {
+ return;
+ }
+
+ return new Promise(resolve => {
+ promise_test(async t => {
+ test = await test;
+ await setupPromise;
+ await runner.preTest(test);
+ await test.f(runner, t)
+ await runner.postTest(test);
+ resolve();
+ }, test.description);
+ }).catch(_ => {});
+ });
+ Promise.all(allTests).then(_ => runner.teardown());
+}
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
new file mode 100644
index 00000000000..23cf4f651ac
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js
@@ -0,0 +1,169 @@
+structuredCloneBatteryOfTests.push({
+ description: 'ArrayBuffer',
+ async f(runner) {
+ const buffer = new Uint8Array([1]).buffer;
+ const copy = await runner.structuredClone(buffer, [buffer]);
+ assert_equals(buffer.byteLength, 0);
+ assert_equals(copy.byteLength, 1);
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'MessagePort',
+ async f(runner) {
+ const {port1, port2} = new MessageChannel();
+ const copy = await runner.structuredClone(port2, [port2]);
+ const msg = new Promise(resolve => port1.onmessage = resolve);
+ copy.postMessage('ohai');
+ assert_equals((await msg).data, 'ohai');
+ }
+});
+
+// TODO: ImageBitmap
+
+structuredCloneBatteryOfTests.push({
+ description: 'A detached ArrayBuffer cannot be transferred',
+ async f(runner, t) {
+ const buffer = new ArrayBuffer();
+ await runner.structuredClone(buffer, [buffer]);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(buffer, [buffer])
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'A detached platform object cannot be transferred',
+ async f(runner, t) {
+ const {port1} = new MessageChannel();
+ await runner.structuredClone(port1, [port1]);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(port1, [port1])
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Transferring a non-transferable platform object fails',
+ async f(runner, t) {
+ const blob = new Blob();
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(blob, [blob])
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'An object whose interface is deleted from the global object must still be received',
+ async f(runner) {
+ const {port1} = new MessageChannel();
+ const messagePortInterface = globalThis.MessagePort;
+ delete globalThis.MessagePort;
+ try {
+ const transfer = await runner.structuredClone(port1, [port1]);
+ assert_true(transfer instanceof messagePortInterface);
+ } finally {
+ globalThis.MessagePort = messagePortInterface;
+ }
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'A subclass instance will be received as its closest transferable superclass',
+ async f(runner) {
+ // MessagePort doesn't have a constructor, so we must use something else.
+
+ // Make sure that ReadableStream is transferable before we test its subclasses.
+ try {
+ const stream = new ReadableStream();
+ await runner.structuredClone(stream, [stream]);
+ } catch(err) {
+ if (err instanceof DOMException && err.code === DOMException.DATA_CLONE_ERR) {
+ throw new OptionalFeatureUnsupportedError("ReadableStream isn't transferable");
+ } else {
+ throw err;
+ }
+ }
+
+ class ReadableStreamSubclass extends ReadableStream {}
+ const original = new ReadableStreamSubclass();
+ const transfer = await runner.structuredClone(original, [original]);
+ assert_equals(Object.getPrototypeOf(transfer), ReadableStream.prototype);
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Resizable ArrayBuffer is transferable',
+ async f(runner) {
+ const buffer = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const copy = await runner.structuredClone(buffer, [buffer]);
+ assert_equals(buffer.byteLength, 0);
+ assert_equals(copy.byteLength, 16);
+ assert_equals(copy.maxByteLength, 1024);
+ assert_true(copy.resizable);
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Length-tracking TypedArray is transferable',
+ async f(runner) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const ta = new Uint8Array(ab);
+ const copy = await runner.structuredClone(ta, [ab]);
+ assert_equals(ab.byteLength, 0);
+ assert_equals(copy.buffer.byteLength, 16);
+ assert_equals(copy.buffer.maxByteLength, 1024);
+ assert_true(copy.buffer.resizable);
+ copy.buffer.resize(32);
+ assert_equals(copy.byteLength, 32);
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Length-tracking DataView is transferable',
+ async f(runner) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const dv = new DataView(ab);
+ const copy = await runner.structuredClone(dv, [ab]);
+ assert_equals(ab.byteLength, 0);
+ assert_equals(copy.buffer.byteLength, 16);
+ assert_equals(copy.buffer.maxByteLength, 1024);
+ assert_true(copy.buffer.resizable);
+ copy.buffer.resize(32);
+ assert_equals(copy.byteLength, 32);
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Transferring OOB TypedArray throws',
+ async f(runner, t) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const ta = new Uint8Array(ab, 8);
+ ab.resize(0);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(ta, [ab])
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Transferring OOB DataView throws',
+ async f(runner, t) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const dv = new DataView(ab, 8);
+ ab.resize(0);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(dv, [ab])
+ );
+ }
+});
diff --git a/Tests/LibWeb/Text/input/wpt-import/html/webappapis/structured-clone/structured-clone-battery-of-tests.js b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
new file mode 100644
index 00000000000..923ac9dc164
--- /dev/null
+++ b/Tests/LibWeb/Text/input/wpt-import/html/webappapis/structured-clone/structured-clone-battery-of-tests.js
@@ -0,0 +1,753 @@
+/* This file is mostly a remix of @zcorpan’s web worker test suite */
+
+structuredCloneBatteryOfTests = [];
+
+function check(description, input, callback, requiresDocument = false) {
+ structuredCloneBatteryOfTests.push({
+ description,
+ async f(runner) {
+ let newInput = input;
+ if (typeof input === 'function') {
+ newInput = input();
+ }
+ const copy = await runner.structuredClone(newInput);
+ await callback(copy, newInput);
+ },
+ requiresDocument
+ });
+}
+
+function compare_primitive(actual, input) {
+ assert_equals(actual, input);
+}
+function compare_Array(callback) {
+ return async function(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Array, 'instanceof Array');
+ assert_not_equals(actual, input);
+ assert_equals(actual.length, input.length, 'length');
+ await callback(actual, input);
+ }
+}
+
+function compare_Object(callback) {
+ return async function(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Object, 'instanceof Object');
+ assert_false(actual instanceof Array, 'instanceof Array');
+ assert_not_equals(actual, input);
+ await callback(actual, input);
+ }
+}
+
+function enumerate_props(compare_func) {
+ return async function(actual, input) {
+ for (const x in input) {
+ await compare_func(actual[x], input[x]);
+ }
+ };
+}
+
+check('primitive undefined', undefined, compare_primitive);
+check('primitive null', null, compare_primitive);
+check('primitive true', true, compare_primitive);
+check('primitive false', false, compare_primitive);
+check('primitive string, empty string', '', compare_primitive);
+check('primitive string, lone high surrogate', '\uD800', compare_primitive);
+check('primitive string, lone low surrogate', '\uDC00', compare_primitive);
+check('primitive string, NUL', '\u0000', compare_primitive);
+check('primitive string, astral character', '\uDBFF\uDFFD', compare_primitive);
+check('primitive number, 0.2', 0.2, compare_primitive);
+check('primitive number, 0', 0, compare_primitive);
+check('primitive number, -0', -0, compare_primitive);
+check('primitive number, NaN', NaN, compare_primitive);
+check('primitive number, Infinity', Infinity, compare_primitive);
+check('primitive number, -Infinity', -Infinity, compare_primitive);
+check('primitive number, 9007199254740992', 9007199254740992, compare_primitive);
+check('primitive number, -9007199254740992', -9007199254740992, compare_primitive);
+check('primitive number, 9007199254740994', 9007199254740994, compare_primitive);
+check('primitive number, -9007199254740994', -9007199254740994, compare_primitive);
+check('primitive BigInt, 0n', 0n, compare_primitive);
+check('primitive BigInt, -0n', -0n, compare_primitive);
+check('primitive BigInt, -9007199254740994000n', -9007199254740994000n, compare_primitive);
+check('primitive BigInt, -9007199254740994000900719925474099400090071992547409940009007199254740994000n', -9007199254740994000900719925474099400090071992547409940009007199254740994000n, compare_primitive);
+
+check('Array primitives', [undefined,
+ null,
+ true,
+ false,
+ '',
+ '\uD800',
+ '\uDC00',
+ '\u0000',
+ '\uDBFF\uDFFD',
+ 0.2,
+ 0,
+ -0,
+ NaN,
+ Infinity,
+ -Infinity,
+ 9007199254740992,
+ -9007199254740992,
+ 9007199254740994,
+ -9007199254740994,
+ -12n,
+ -0n,
+ 0n], compare_Array(enumerate_props(compare_primitive)));
+check('Object primitives', {'undefined':undefined,
+ 'null':null,
+ 'true':true,
+ 'false':false,
+ 'empty':'',
+ 'high surrogate':'\uD800',
+ 'low surrogate':'\uDC00',
+ 'nul':'\u0000',
+ 'astral':'\uDBFF\uDFFD',
+ '0.2':0.2,
+ '0':0,
+ '-0':-0,
+ 'NaN':NaN,
+ 'Infinity':Infinity,
+ '-Infinity':-Infinity,
+ '9007199254740992':9007199254740992,
+ '-9007199254740992':-9007199254740992,
+ '9007199254740994':9007199254740994,
+ '-9007199254740994':-9007199254740994}, compare_Object(enumerate_props(compare_primitive)));
+
+function compare_Boolean(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Boolean, 'instanceof Boolean');
+ assert_equals(String(actual), String(input), 'converted to primitive');
+ assert_not_equals(actual, input);
+}
+check('Boolean true', new Boolean(true), compare_Boolean);
+check('Boolean false', new Boolean(false), compare_Boolean);
+check('Array Boolean objects', [new Boolean(true), new Boolean(false)], compare_Array(enumerate_props(compare_Boolean)));
+check('Object Boolean objects', {'true':new Boolean(true), 'false':new Boolean(false)}, compare_Object(enumerate_props(compare_Boolean)));
+
+function compare_obj(what) {
+ const Type = self[what];
+ return function(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Type, 'instanceof '+what);
+ assert_equals(Type(actual), Type(input), 'converted to primitive');
+ assert_not_equals(actual, input);
+ };
+}
+check('String empty string', new String(''), compare_obj('String'));
+check('String lone high surrogate', new String('\uD800'), compare_obj('String'));
+check('String lone low surrogate', new String('\uDC00'), compare_obj('String'));
+check('String NUL', new String('\u0000'), compare_obj('String'));
+check('String astral character', new String('\uDBFF\uDFFD'), compare_obj('String'));
+check('Array String objects', [new String(''),
+ new String('\uD800'),
+ new String('\uDC00'),
+ new String('\u0000'),
+ new String('\uDBFF\uDFFD')], compare_Array(enumerate_props(compare_obj('String'))));
+check('Object String objects', {'empty':new String(''),
+ 'high surrogate':new String('\uD800'),
+ 'low surrogate':new String('\uDC00'),
+ 'nul':new String('\u0000'),
+ 'astral':new String('\uDBFF\uDFFD')}, compare_Object(enumerate_props(compare_obj('String'))));
+
+check('Number 0.2', new Number(0.2), compare_obj('Number'));
+check('Number 0', new Number(0), compare_obj('Number'));
+check('Number -0', new Number(-0), compare_obj('Number'));
+check('Number NaN', new Number(NaN), compare_obj('Number'));
+check('Number Infinity', new Number(Infinity), compare_obj('Number'));
+check('Number -Infinity', new Number(-Infinity), compare_obj('Number'));
+check('Number 9007199254740992', new Number(9007199254740992), compare_obj('Number'));
+check('Number -9007199254740992', new Number(-9007199254740992), compare_obj('Number'));
+check('Number 9007199254740994', new Number(9007199254740994), compare_obj('Number'));
+check('Number -9007199254740994', new Number(-9007199254740994), compare_obj('Number'));
+// BigInt does not have a non-throwing constructor
+check('BigInt -9007199254740994n', Object(-9007199254740994n), compare_obj('BigInt'));
+
+check('Array Number objects', [new Number(0.2),
+ new Number(0),
+ new Number(-0),
+ new Number(NaN),
+ new Number(Infinity),
+ new Number(-Infinity),
+ new Number(9007199254740992),
+ new Number(-9007199254740992),
+ new Number(9007199254740994),
+ new Number(-9007199254740994)], compare_Array(enumerate_props(compare_obj('Number'))));
+check('Object Number objects', {'0.2':new Number(0.2),
+ '0':new Number(0),
+ '-0':new Number(-0),
+ 'NaN':new Number(NaN),
+ 'Infinity':new Number(Infinity),
+ '-Infinity':new Number(-Infinity),
+ '9007199254740992':new Number(9007199254740992),
+ '-9007199254740992':new Number(-9007199254740992),
+ '9007199254740994':new Number(9007199254740994),
+ '-9007199254740994':new Number(-9007199254740994)}, compare_Object(enumerate_props(compare_obj('Number'))));
+
+function compare_Date(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Date, 'instanceof Date');
+ assert_equals(Number(actual), Number(input), 'converted to primitive');
+ assert_not_equals(actual, input);
+}
+check('Date 0', new Date(0), compare_Date);
+check('Date -0', new Date(-0), compare_Date);
+check('Date -8.64e15', new Date(-8.64e15), compare_Date);
+check('Date 8.64e15', new Date(8.64e15), compare_Date);
+check('Array Date objects', [new Date(0),
+ new Date(-0),
+ new Date(-8.64e15),
+ new Date(8.64e15)], compare_Array(enumerate_props(compare_Date)));
+check('Object Date objects', {'0':new Date(0),
+ '-0':new Date(-0),
+ '-8.64e15':new Date(-8.64e15),
+ '8.64e15':new Date(8.64e15)}, compare_Object(enumerate_props(compare_Date)));
+
+function compare_RegExp(expected_source) {
+ // XXX ES6 spec doesn't define exact serialization for `source` (it allows several ways to escape)
+ return function(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof RegExp, 'instanceof RegExp');
+ assert_equals(actual.global, input.global, 'global');
+ assert_equals(actual.ignoreCase, input.ignoreCase, 'ignoreCase');
+ assert_equals(actual.multiline, input.multiline, 'multiline');
+ assert_equals(actual.source, expected_source, 'source');
+ assert_equals(actual.sticky, input.sticky, 'sticky');
+ assert_equals(actual.unicode, input.unicode, 'unicode');
+ assert_equals(actual.lastIndex, 0, 'lastIndex');
+ assert_not_equals(actual, input);
+ }
+}
+function func_RegExp_flags_lastIndex() {
+ const r = /foo/gim;
+ r.lastIndex = 2;
+ return r;
+}
+function func_RegExp_sticky() {
+ return new RegExp('foo', 'y');
+}
+function func_RegExp_unicode() {
+ return new RegExp('foo', 'u');
+}
+check('RegExp flags and lastIndex', func_RegExp_flags_lastIndex, compare_RegExp('foo'));
+check('RegExp sticky flag', func_RegExp_sticky, compare_RegExp('foo'));
+check('RegExp unicode flag', func_RegExp_unicode, compare_RegExp('foo'));
+check('RegExp empty', new RegExp(''), compare_RegExp('(?:)'));
+check('RegExp slash', new RegExp('/'), compare_RegExp('\\/'));
+check('RegExp new line', new RegExp('\n'), compare_RegExp('\\n'));
+check('Array RegExp object, RegExp flags and lastIndex', [func_RegExp_flags_lastIndex()], compare_Array(enumerate_props(compare_RegExp('foo'))));
+check('Array RegExp object, RegExp sticky flag', function() { return [func_RegExp_sticky()]; }, compare_Array(enumerate_props(compare_RegExp('foo'))));
+check('Array RegExp object, RegExp unicode flag', function() { return [func_RegExp_unicode()]; }, compare_Array(enumerate_props(compare_RegExp('foo'))));
+check('Array RegExp object, RegExp empty', [new RegExp('')], compare_Array(enumerate_props(compare_RegExp('(?:)'))));
+check('Array RegExp object, RegExp slash', [new RegExp('/')], compare_Array(enumerate_props(compare_RegExp('\\/'))));
+check('Array RegExp object, RegExp new line', [new RegExp('\n')], compare_Array(enumerate_props(compare_RegExp('\\n'))));
+check('Object RegExp object, RegExp flags and lastIndex', {'x':func_RegExp_flags_lastIndex()}, compare_Object(enumerate_props(compare_RegExp('foo'))));
+check('Object RegExp object, RegExp sticky flag', function() { return {'x':func_RegExp_sticky()}; }, compare_Object(enumerate_props(compare_RegExp('foo'))));
+check('Object RegExp object, RegExp unicode flag', function() { return {'x':func_RegExp_unicode()}; }, compare_Object(enumerate_props(compare_RegExp('foo'))));
+check('Object RegExp object, RegExp empty', {'x':new RegExp('')}, compare_Object(enumerate_props(compare_RegExp('(?:)'))));
+check('Object RegExp object, RegExp slash', {'x':new RegExp('/')}, compare_Object(enumerate_props(compare_RegExp('\\/'))));
+check('Object RegExp object, RegExp new line', {'x':new RegExp('\n')}, compare_Object(enumerate_props(compare_RegExp('\\n'))));
+
+function compare_Error(actual, input) {
+ assert_true(actual instanceof Error, "Checking instanceof");
+ assert_equals(actual.constructor, input.constructor, "Checking constructor");
+ assert_equals(actual.name, input.name, "Checking name");
+ assert_equals(actual.hasOwnProperty("message"), input.hasOwnProperty("message"), "Checking message existence");
+ assert_equals(actual.message, input.message, "Checking message");
+ assert_equals(actual.foo, undefined, "Checking for absence of custom property");
+}
+
+check('Empty Error object', new Error, compare_Error);
+
+const errorConstructors = [Error, EvalError, RangeError, ReferenceError,
+ SyntaxError, TypeError, URIError];
+for (const constructor of errorConstructors) {
+ check(`${constructor.name} object`, () => {
+ let error = new constructor("Error message here");
+ error.foo = "testing";
+ return error;
+ }, compare_Error);
+}
+
+async function compare_Blob(actual, input, expect_File) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Blob, 'instanceof Blob');
+ if (!expect_File)
+ assert_false(actual instanceof File, 'instanceof File');
+ assert_equals(actual.size, input.size, 'size');
+ assert_equals(actual.type, input.type, 'type');
+ assert_not_equals(actual, input);
+ const ab1 = await new Response(actual).arrayBuffer();
+ const ab2 = await new Response(input).arrayBuffer();
+ assert_equals(ab1.byteLength, ab2.byteLength, 'byteLength');
+ const ta1 = new Uint8Array(ab1);
+ const ta2 = new Uint8Array(ab2);
+ for(let i = 0; i < ta1.size; i++) {
+ assert_equals(ta1[i], ta2[i]);
+ }
+}
+function func_Blob_basic() {
+ return new Blob(['foo'], {type:'text/x-bar'});
+}
+check('Blob basic', func_Blob_basic, compare_Blob);
+
+function b(str) {
+ return parseInt(str, 2);
+}
+function encode_cesu8(codeunits) {
+ // http://www.unicode.org/reports/tr26/ section 2.2
+ // only the 3-byte form is supported
+ const rv = [];
+ codeunits.forEach(function(codeunit) {
+ rv.push(b('11100000') + ((codeunit & b('1111000000000000')) >> 12));
+ rv.push(b('10000000') + ((codeunit & b('0000111111000000')) >> 6));
+ rv.push(b('10000000') + (codeunit & b('0000000000111111')));
+ });
+ return rv;
+}
+function func_Blob_bytes(arr) {
+ return function() {
+ const buffer = new ArrayBuffer(arr.length);
+ const view = new DataView(buffer);
+ for (let i = 0; i < arr.length; ++i) {
+ view.setUint8(i, arr[i]);
+ }
+ return new Blob([view]);
+ };
+}
+check('Blob unpaired high surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800])), compare_Blob);
+check('Blob unpaired low surrogate (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xDC00])), compare_Blob);
+check('Blob paired surrogates (invalid utf-8)', func_Blob_bytes(encode_cesu8([0xD800, 0xDC00])), compare_Blob);
+
+function func_Blob_empty() {
+ return new Blob(['']);
+}
+check('Blob empty', func_Blob_empty , compare_Blob);
+function func_Blob_NUL() {
+ return new Blob(['\u0000']);
+}
+check('Blob NUL', func_Blob_NUL, compare_Blob);
+
+check('Array Blob object, Blob basic', [func_Blob_basic()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, Blob unpaired high surrogate (invalid utf-8)', [func_Blob_bytes([0xD800])()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, Blob unpaired low surrogate (invalid utf-8)', [func_Blob_bytes([0xDC00])()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, Blob paired surrogates (invalid utf-8)', [func_Blob_bytes([0xD800, 0xDC00])()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, Blob empty', [func_Blob_empty()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, Blob NUL', [func_Blob_NUL()], compare_Array(enumerate_props(compare_Blob)));
+check('Array Blob object, two Blobs', [func_Blob_basic(), func_Blob_empty()], compare_Array(enumerate_props(compare_Blob)));
+
+check('Object Blob object, Blob basic', {'x':func_Blob_basic()}, compare_Object(enumerate_props(compare_Blob)));
+check('Object Blob object, Blob unpaired high surrogate (invalid utf-8)', {'x':func_Blob_bytes([0xD800])()}, compare_Object(enumerate_props(compare_Blob)));
+check('Object Blob object, Blob unpaired low surrogate (invalid utf-8)', {'x':func_Blob_bytes([0xDC00])()}, compare_Object(enumerate_props(compare_Blob)));
+check('Object Blob object, Blob paired surrogates (invalid utf-8)', {'x':func_Blob_bytes([0xD800, 0xDC00])() }, compare_Object(enumerate_props(compare_Blob)));
+check('Object Blob object, Blob empty', {'x':func_Blob_empty()}, compare_Object(enumerate_props(compare_Blob)));
+check('Object Blob object, Blob NUL', {'x':func_Blob_NUL()}, compare_Object(enumerate_props(compare_Blob)));
+
+async function compare_File(actual, input) {
+ assert_true(actual instanceof File, 'instanceof File');
+ assert_equals(actual.name, input.name, 'name');
+ assert_equals(actual.lastModified, input.lastModified, 'lastModified');
+ await compare_Blob(actual, input, true);
+}
+function func_File_basic() {
+ return new File(['foo'], 'bar', {type:'text/x-bar', lastModified:42});
+}
+check('File basic', func_File_basic, compare_File);
+
+function compare_FileList(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof FileList, 'instanceof FileList');
+ assert_equals(actual.length, input.length, 'length');
+ assert_not_equals(actual, input);
+ // XXX when there's a way to populate or construct a FileList,
+ // check the items in the FileList
+}
+function func_FileList_empty() {
+ const input = document.createElement('input');
+ input.type = 'file';
+ return input.files;
+}
+check('FileList empty', func_FileList_empty, compare_FileList, true);
+check('Array FileList object, FileList empty', () => ([func_FileList_empty()]), compare_Array(enumerate_props(compare_FileList)), true);
+check('Object FileList object, FileList empty', () => ({'x':func_FileList_empty()}), compare_Object(enumerate_props(compare_FileList)), true);
+
+function compare_ArrayBuffer(actual, input) {
+ assert_true(actual instanceof ArrayBuffer, 'instanceof ArrayBuffer');
+ assert_equals(actual.byteLength, input.byteLength, 'byteLength');
+ assert_equals(actual.maxByteLength, input.maxByteLength, 'maxByteLength');
+ assert_equals(actual.resizable, input.resizable, 'resizable');
+ assert_equals(actual.growable, input.growable, 'growable');
+}
+
+function compare_ArrayBufferView(view) {
+ const Type = self[view];
+ return function(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof Type, 'instanceof '+view);
+ assert_equals(actual.length, input.length, 'length');
+ assert_equals(actual.byteLength, input.byteLength, 'byteLength');
+ assert_equals(actual.byteOffset, input.byteOffset, 'byteOffset');
+ assert_not_equals(actual.buffer, input.buffer, 'buffer');
+ for (let i = 0; i < actual.length; ++i) {
+ assert_equals(actual[i], input[i], 'actual['+i+']');
+ }
+ };
+}
+function compare_ImageData(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_equals(actual.width, input.width, 'width');
+ assert_equals(actual.height, input.height, 'height');
+ assert_not_equals(actual.data, input.data, 'data');
+ compare_ArrayBufferView('Uint8ClampedArray')(actual.data, input.data, null);
+}
+function func_ImageData_1x1_transparent_black() {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ return ctx.createImageData(1, 1);
+}
+check('ImageData 1x1 transparent black', func_ImageData_1x1_transparent_black, compare_ImageData, true);
+function func_ImageData_1x1_non_transparent_non_black() {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ const imagedata = ctx.createImageData(1, 1);
+ imagedata.data[0] = 100;
+ imagedata.data[1] = 101;
+ imagedata.data[2] = 102;
+ imagedata.data[3] = 103;
+ return imagedata;
+}
+check('ImageData 1x1 non-transparent non-black', func_ImageData_1x1_non_transparent_non_black, compare_ImageData, true);
+check('Array ImageData object, ImageData 1x1 transparent black', () => ([func_ImageData_1x1_transparent_black()]), compare_Array(enumerate_props(compare_ImageData)), true);
+check('Array ImageData object, ImageData 1x1 non-transparent non-black', () => ([func_ImageData_1x1_non_transparent_non_black()]), compare_Array(enumerate_props(compare_ImageData)), true);
+check('Object ImageData object, ImageData 1x1 transparent black', () => ({'x':func_ImageData_1x1_transparent_black()}), compare_Object(enumerate_props(compare_ImageData)), true);
+check('Object ImageData object, ImageData 1x1 non-transparent non-black', () => ({'x':func_ImageData_1x1_non_transparent_non_black()}), compare_Object(enumerate_props(compare_ImageData)), true);
+
+
+check('Array sparse', new Array(10), compare_Array(enumerate_props(compare_primitive)));
+check('Array with non-index property', function() {
+ const rv = [];
+ rv.foo = 'bar';
+ return rv;
+}, compare_Array(enumerate_props(compare_primitive)));
+check('Object with index property and length', {'0':'foo', 'length':1}, compare_Object(enumerate_props(compare_primitive)));
+function check_circular_property(prop) {
+ return function(actual) {
+ assert_equals(actual[prop], actual);
+ };
+}
+check('Array with circular reference', function() {
+ const rv = [];
+ rv[0] = rv;
+ return rv;
+}, compare_Array(check_circular_property('0')));
+check('Object with circular reference', function() {
+ const rv = {};
+ rv['x'] = rv;
+ return rv;
+}, compare_Object(check_circular_property('x')));
+function check_identical_property_values(prop1, prop2) {
+ return function(actual) {
+ assert_equals(actual[prop1], actual[prop2]);
+ };
+}
+check('Array with identical property values', function() {
+ const obj = {}
+ return [obj, obj];
+}, compare_Array(check_identical_property_values('0', '1')));
+check('Object with identical property values', function() {
+ const obj = {}
+ return {'x':obj, 'y':obj};
+}, compare_Object(check_identical_property_values('x', 'y')));
+
+function check_absent_property(prop) {
+ return function(actual) {
+ assert_false(prop in actual);
+ };
+}
+check('Object with property on prototype', function() {
+ const Foo = function() {};
+ Foo.prototype = {'foo':'bar'};
+ return new Foo();
+}, compare_Object(check_absent_property('foo')));
+
+check('Object with non-enumerable property', function() {
+ const rv = {};
+ Object.defineProperty(rv, 'foo', {value:'bar', enumerable:false, writable:true, configurable:true});
+ return rv;
+}, compare_Object(check_absent_property('foo')));
+
+function check_writable_property(prop) {
+ return function(actual, input) {
+ assert_equals(actual[prop], input[prop]);
+ actual[prop] += ' baz';
+ assert_equals(actual[prop], input[prop] + ' baz');
+ };
+}
+check('Object with non-writable property', function() {
+ const rv = {};
+ Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:false, configurable:true});
+ return rv;
+}, compare_Object(check_writable_property('foo')));
+
+function check_configurable_property(prop) {
+ return function(actual, input) {
+ assert_equals(actual[prop], input[prop]);
+ delete actual[prop];
+ assert_false('prop' in actual);
+ };
+}
+check('Object with non-configurable property', function() {
+ const rv = {};
+ Object.defineProperty(rv, 'foo', {value:'bar', enumerable:true, writable:true, configurable:false});
+ return rv;
+}, compare_Object(check_configurable_property('foo')));
+
+structuredCloneBatteryOfTests.push({
+ description: 'Object with a getter that throws',
+ async f(runner, t) {
+ const exception = new Error();
+ const testObject = {
+ get testProperty() {
+ throw exception;
+ }
+ };
+ await promise_rejects_exactly(
+ t,
+ exception,
+ runner.structuredClone(testObject)
+ );
+ }
+});
+
+/* The tests below are inspired by @zcorpan’s work but got some
+more substantial changed due to their previous async setup */
+
+function get_canvas_1x1_transparent_black() {
+ const canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ return canvas;
+}
+
+function get_canvas_1x1_non_transparent_non_black() {
+ const canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ const ctx = canvas.getContext('2d');
+ const imagedata = ctx.getImageData(0, 0, 1, 1);
+ imagedata.data[0] = 100;
+ imagedata.data[1] = 101;
+ imagedata.data[2] = 102;
+ imagedata.data[3] = 103;
+ return canvas;
+}
+
+function compare_ImageBitmap(actual, input) {
+ if (typeof actual === 'string')
+ assert_unreached(actual);
+ assert_true(actual instanceof ImageBitmap, 'instanceof ImageBitmap');
+ assert_not_equals(actual, input);
+ // XXX paint the ImageBitmap on a canvas and check the data
+}
+
+structuredCloneBatteryOfTests.push({
+ description: 'ImageBitmap 1x1 transparent black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_transparent_black();
+ const bm = await createImageBitmap(canvas);
+ const copy = await runner.structuredClone(bm);
+ compare_ImageBitmap(bm, copy);
+ },
+ requiresDocument: true
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'ImageBitmap 1x1 non-transparent non-black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_non_transparent_non_black();
+ const bm = await createImageBitmap(canvas);
+ const copy = await runner.structuredClone(bm);
+ compare_ImageBitmap(bm, copy);
+ },
+ requiresDocument: true
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Array ImageBitmap object, ImageBitmap 1x1 transparent black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_transparent_black();
+ const bm = [await createImageBitmap(canvas)];
+ const copy = await runner.structuredClone(bm);
+ compare_Array(enumerate_props(compare_ImageBitmap))(bm, copy);
+ },
+ requiresDocument: true
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Array ImageBitmap object, ImageBitmap 1x1 transparent non-black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_non_transparent_non_black();
+ const bm = [await createImageBitmap(canvas)];
+ const copy = await runner.structuredClone(bm);
+ compare_Array(enumerate_props(compare_ImageBitmap))(bm, copy);
+ },
+ requiresDocument: true
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Object ImageBitmap object, ImageBitmap 1x1 transparent black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_transparent_black();
+ const bm = {x: await createImageBitmap(canvas)};
+ const copy = await runner.structuredClone(bm);
+ compare_Object(enumerate_props(compare_ImageBitmap))(bm, copy);
+ },
+ requiresDocument: true
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Object ImageBitmap object, ImageBitmap 1x1 transparent non-black',
+ async f(runner) {
+ const canvas = get_canvas_1x1_non_transparent_non_black();
+ const bm = {x: await createImageBitmap(canvas)};
+ const copy = await runner.structuredClone(bm);
+ compare_Object(enumerate_props(compare_ImageBitmap))(bm, copy);
+ },
+ requiresDocument: true
+});
+
+check('ObjectPrototype must lose its exotic-ness when cloned',
+ () => Object.prototype,
+ (copy, original) => {
+ assert_not_equals(copy, original);
+ assert_true(copy instanceof Object);
+
+ const newProto = { some: 'proto' };
+ // Must not throw:
+ Object.setPrototypeOf(copy, newProto);
+
+ assert_equals(Object.getPrototypeOf(copy), newProto);
+ }
+);
+
+structuredCloneBatteryOfTests.push({
+ description: 'Serializing a non-serializable platform object fails',
+ async f(runner, t) {
+ const request = new Response();
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(request)
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'An object whose interface is deleted from the global must still deserialize',
+ async f(runner) {
+ const blob = new Blob();
+ const blobInterface = globalThis.Blob;
+ delete globalThis.Blob;
+ try {
+ const copy = await runner.structuredClone(blob);
+ assert_true(copy instanceof blobInterface);
+ } finally {
+ globalThis.Blob = blobInterface;
+ }
+ }
+});
+
+check(
+ 'A subclass instance will deserialize as its closest serializable superclass',
+ () => {
+ class FileSubclass extends File {}
+ return new FileSubclass([], "");
+ },
+ (copy) => {
+ assert_equals(Object.getPrototypeOf(copy), File.prototype);
+ }
+);
+
+check(
+ 'Resizable ArrayBuffer',
+ () => {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ assert_true(ab.resizable);
+ return ab;
+ },
+ compare_ArrayBuffer);
+
+structuredCloneBatteryOfTests.push({
+ description: 'Growable SharedArrayBuffer',
+ async f(runner) {
+ const sab = createBuffer('SharedArrayBuffer', 16, { maxByteLength: 1024 });
+ assert_true(sab.growable);
+ try {
+ const copy = await runner.structuredClone(sab);
+ compare_ArrayBuffer(sab, copy);
+ } catch (e) {
+ // If we're cross-origin isolated, cloning SABs should not fail.
+ if (e instanceof DOMException && e.code === DOMException.DATA_CLONE_ERR) {
+ assert_false(self.crossOriginIsolated);
+ } else {
+ throw e;
+ }
+ }
+ }
+});
+
+check(
+ 'Length-tracking TypedArray',
+ () => {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ assert_true(ab.resizable);
+ return new Uint8Array(ab);
+ },
+ compare_ArrayBufferView('Uint8Array'));
+
+check(
+ 'Length-tracking DataView',
+ () => {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ assert_true(ab.resizable);
+ return new DataView(ab);
+ },
+ compare_ArrayBufferView('DataView'));
+
+structuredCloneBatteryOfTests.push({
+ description: 'Serializing OOB TypedArray throws',
+ async f(runner, t) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const ta = new Uint8Array(ab, 8);
+ ab.resize(0);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(ta)
+ );
+ }
+});
+
+structuredCloneBatteryOfTests.push({
+ description: 'Serializing OOB DataView throws',
+ async f(runner, t) {
+ const ab = new ArrayBuffer(16, { maxByteLength: 1024 });
+ const dv = new DataView(ab, 8);
+ ab.resize(0);
+ await promise_rejects_dom(
+ t,
+ "DataCloneError",
+ runner.structuredClone(dv)
+ );
+ }
+});
diff --git a/Userland/Libraries/LibJS/Runtime/DataView.h b/Userland/Libraries/LibJS/Runtime/DataView.h
index 0e2a1ca2fc9..cbf45864256 100644
--- a/Userland/Libraries/LibJS/Runtime/DataView.h
+++ b/Userland/Libraries/LibJS/Runtime/DataView.h
@@ -24,7 +24,7 @@ public:
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
ByteLength const& byte_length() const { return m_byte_length; }
- size_t byte_offset() const { return m_byte_offset; }
+ u32 byte_offset() const { return m_byte_offset; }
private:
DataView(ArrayBuffer*, ByteLength byte_length, size_t byte_offset, Object& prototype);