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:
Andreas Kling 2024-11-03 23:30:44 +01:00 committed by Andreas Kling
parent 7402ae3a00
commit 969ee0f3e0
Notes: github-actions[bot] 2024-11-03 23:23:29 +00:00
22 changed files with 2137 additions and 1 deletions

View file

@ -34,3 +34,7 @@ Text/input/wpt-import/html/syntax/parsing/named-character-references.html
Text/input/wpt-import/html/syntax/parsing/support/no-doctype-name-eof.html
Text/input/wpt-import/html/syntax/parsing/support/no-doctype-name-line.html
Text/input/wpt-import/html/syntax/parsing/support/no-doctype-name-space.html
Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/structuredclone_0.html
Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/echo-iframe.html
Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/iframe-resizable-arraybuffer-helper.html
Text/input/wpt-import/html/infrastructure/safe-passing-of-structured-data/resources/post-parent-type-error.html

View file

@ -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

View file

@ -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

View file

@ -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

View 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");
}
}
})();

View file

@ -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()
});

View file

@ -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>

View file

@ -0,0 +1,2 @@
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: cross-origin

View file

@ -0,0 +1,5 @@
"use strict";
self.onmessage = ({ data }) => {
self.postMessage(data);
};

View file

@ -0,0 +1 @@
Cross-Origin-Embedder-Policy: require-corp

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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)')
}

View file

@ -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>

View file

@ -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));

View file

@ -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>

View file

@ -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);
});
}
});

View file

@ -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());
}

View file

@ -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])
);
}
});

View file

@ -0,0 +1,753 @@
/* This file is mostly a remix of @zcorpans 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 @zcorpans 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)
);
}
});

View file

@ -24,7 +24,7 @@ public:
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
ByteLength const& byte_length() const { return m_byte_length; }
size_t byte_offset() const { return m_byte_offset; }
u32 byte_offset() const { return m_byte_offset; }
private:
DataView(ArrayBuffer*, ByteLength byte_length, size_t byte_offset, Object& prototype);