mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-09 09:39:39 +00:00
LibJS: Make DataView::byte_offset() return u32
This fixes structured serialization of DataView. It was expected to be uniform with TypedArray, which returns u32 for byte_offset(). This was covered by a number of WPT infrastructure tests, which this commit also imports.
This commit is contained in:
parent
7402ae3a00
commit
969ee0f3e0
Notes:
github-actions[bot]
2024-11-03 23:23:29 +00:00
Author: https://github.com/awesomekling
Commit: 969ee0f3e0
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2148
22 changed files with 2137 additions and 1 deletions
|
@ -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-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-line.html
|
||||||
Text/input/wpt-import/html/syntax/parsing/support/no-doctype-name-space.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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
21
Tests/LibWeb/Text/input/wpt-import/common/sab.js
Normal file
21
Tests/LibWeb/Text/input/wpt-import/common/sab.js
Normal file
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
|
@ -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()
|
||||||
|
});
|
|
@ -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>
|
|
@ -0,0 +1,2 @@
|
||||||
|
Cross-Origin-Embedder-Policy: require-corp
|
||||||
|
Cross-Origin-Resource-Policy: cross-origin
|
|
@ -0,0 +1,5 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
self.onmessage = ({ data }) => {
|
||||||
|
self.postMessage(data);
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
Cross-Origin-Embedder-Policy: require-corp
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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)')
|
||||||
|
}
|
|
@ -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>
|
|
@ -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));
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!doctype html>
|
||||||
|
<meta charset=utf-8>
|
||||||
|
|
||||||
|
<script src="../../../resources/testharness.js"></script>
|
||||||
|
<script src="../../../resources/testharnessreport.js"></script>
|
||||||
|
<script src="../../../common/sab.js"></script>
|
||||||
|
<script src="../../../html/webappapis/structured-clone/structured-clone-battery-of-tests.js"></script>
|
||||||
|
<script src="../../../html/webappapis/structured-clone/structured-clone-battery-of-tests-with-transferables.js"></script>
|
||||||
|
<script src="../../../html/webappapis/structured-clone/structured-clone-battery-of-tests-harness.js"></script>
|
||||||
|
<div id=log></div>
|
||||||
|
<script src="../../../html/infrastructure/safe-passing-of-structured-data/window-postmessage.window.js"></script>
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -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());
|
||||||
|
}
|
|
@ -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])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
|
@ -24,7 +24,7 @@ public:
|
||||||
|
|
||||||
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
|
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
|
||||||
ByteLength const& byte_length() const { return m_byte_length; }
|
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:
|
private:
|
||||||
DataView(ArrayBuffer*, ByteLength byte_length, size_t byte_offset, Object& prototype);
|
DataView(ArrayBuffer*, ByteLength byte_length, size_t byte_offset, Object& prototype);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue