Upload Kmake

This commit is contained in:
Gorochu
2026-05-26 23:36:42 -07:00
parent ba051b2f74
commit 555ec72358
41615 changed files with 13344630 additions and 1 deletions

View File

@ -0,0 +1,39 @@
// META: script=/common/get-host-info.sub.js
// META: script=resources/create-wasm-module.js
// META: timeout=long
const { HTTPS_NOTSAMESITE_ORIGIN } = get_host_info();
const iframe = document.createElement('iframe');
iframe.src = `${HTTPS_NOTSAMESITE_ORIGIN}/streams/transferable/resources/deserialize-error-frame.html`;
window.addEventListener('message', async evt => {
// Tests are serialized to make the results deterministic.
switch (evt.data) {
case 'init done': {
const ws = new WritableStream();
iframe.contentWindow.postMessage(ws, '*', [ws]);
return;
}
case 'ws done': {
const module = await createWasmModule();
const rs = new ReadableStream({
start(controller) {
controller.enqueue(module);
}
});
iframe.contentWindow.postMessage(rs, '*', [rs]);
return;
}
case 'rs done': {
iframe.remove();
}
}
});
// Need to do this after adding the listener to ensure we catch the first
// message.
document.body.appendChild(iframe);
fetch_tests_from_window(iframe.contentWindow);

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html class="test-wait">
<script src="/common/gc.js"></script>
<script type="module">
const b = new ReadableStream({
start(c) {
c.enqueue({}) // the value we will transfer
},
})
const transferred = structuredClone(b, { transfer: [b] })
// Here we request a read, triggering a message transfer
transferred.getReader().read()
// And immediately trigger GC without waiting for the read,
// causing the actual transfer to be done after GC
await garbageCollect()
document.documentElement.classList.remove("test-wait")
</script>

View File

@ -0,0 +1,260 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/helpers.js"></script>
<script src="../resources/recording-streams.js"></script>
<script src="../resources/test-utils.js"></script>
<script>
'use strict';
promise_test(async () => {
const rs = await createTransferredReadableStream({
start(controller) {
controller.enqueue('a');
controller.close();
}
});
const reader = rs.getReader();
{
const {value, done} = await reader.read();
assert_false(done, 'should not be done yet');
assert_equals(value, 'a', 'first chunk should be a');
}
{
const {done} = await reader.read();
assert_true(done, 'should be done now');
}
}, 'sending one chunk through a transferred stream should work');
promise_test(async () => {
let controller;
const rs = await createTransferredReadableStream({
start(c) {
controller = c;
}
});
for (let i = 0; i < 10; ++i) {
controller.enqueue(i);
}
controller.close();
const reader = rs.getReader();
for (let i = 0; i < 10; ++i) {
const {value, done} = await reader.read();
assert_false(done, 'should not be done yet');
assert_equals(value, i, 'chunk content should match index');
}
const {done} = await reader.read();
assert_true(done, 'should be done now');
}, 'sending ten chunks through a transferred stream should work');
promise_test(async () => {
let controller;
const rs = await createTransferredReadableStream({
start(c) {
controller = c;
}
});
const reader = rs.getReader();
for (let i = 0; i < 10; ++i) {
controller.enqueue(i);
const {value, done} = await reader.read();
assert_false(done, 'should not be done yet');
assert_equals(value, i, 'chunk content should match index');
}
controller.close();
const {done} = await reader.read();
assert_true(done, 'should be done now');
}, 'sending ten chunks one at a time should work');
promise_test(async () => {
let controller;
const rs = await createTransferredReadableStream({
start() {
this.counter = 0;
},
pull(controller) {
controller.enqueue(this.counter);
++this.counter;
if (this.counter === 10)
controller.close();
}
});
const reader = rs.getReader();
for (let i = 0; i < 10; ++i) {
const {value, done} = await reader.read();
assert_false(done, 'should not be done yet');
assert_equals(value, i, 'chunk content should match index');
}
const {done} = await reader.read();
assert_true(done, 'should be done now');
}, 'sending ten chunks on demand should work');
promise_test(async () => {
const rs = recordingReadableStream({}, { highWaterMark: 0 });
await delay(0);
assert_array_equals(rs.events, [], 'pull() should not have been called');
// Eat the message so it can't interfere with other tests.
addEventListener('message', () => {}, {once: true});
// The transfer is done manually to verify that it is posting the stream that
// relieves backpressure, not receiving it.
postMessage(rs, '*', [rs]);
await delay(0);
assert_array_equals(rs.events, ['pull'], 'pull() should have been called');
}, 'transferring a stream should relieve backpressure');
promise_test(async () => {
const rs = await recordingTransferredReadableStream({
pull(controller) {
controller.enqueue('a');
}
}, { highWaterMark: 2 });
await delay(0);
assert_array_equals(rs.events, ['pull', 'pull', 'pull'],
'pull() should have been called three times');
}, 'transferring a stream should add one chunk to the queue size');
promise_test(async () => {
const rs = await recordingTransferredReadableStream({
start(controller) {
controller.enqueue(new Uint8Array(1024));
controller.enqueue(new Uint8Array(1024));
}
}, new ByteLengthQueuingStrategy({highWaterMark: 512}));
await delay(0);
// At this point the queue contains 1024/512 bytes and 1/1 chunk, so it's full
// and pull() is not called.
assert_array_equals(rs.events, [], 'pull() should not have been called');
const reader = rs.getReader();
const {value, done} = await reader.read();
assert_false(done, 'we should not be done');
assert_equals(value.byteLength, 1024, 'expected chunk should be returned');
// Now the queue contains 0/512 bytes and 1/1 chunk, so pull() is called. If
// the implementation erroneously counted the extra queue space in bytes, then
// the queue would contain 1024/513 bytes and pull() wouldn't be called.
assert_array_equals(rs.events, ['pull'], 'pull() should have been called');
}, 'the extra queue from transferring is counted in chunks');
async function transferredReadableStreamWithCancelPromise() {
let resolveCancelCalled;
const cancelCalled = new Promise(resolve => {
resolveCancelCalled = resolve;
});
const rs = await recordingTransferredReadableStream({
cancel() {
resolveCancelCalled();
}
});
return { rs, cancelCalled };
}
promise_test(async () => {
const { rs, cancelCalled } = await transferredReadableStreamWithCancelPromise();
rs.cancel('message');
await cancelCalled;
assert_array_equals(rs.events, ['pull', 'cancel', 'message'],
'cancel() should have been called');
const reader = rs.getReader();
// Check the stream really got closed.
await reader.closed;
}, 'cancel should be propagated to the original');
promise_test(async () => {
const { rs, cancelCalled } = await transferredReadableStreamWithCancelPromise();
const reader = rs.getReader();
const readPromise = reader.read();
reader.cancel('done');
const { done } = await readPromise;
assert_true(done, 'should be done');
await cancelCalled;
assert_array_equals(rs.events, ['pull', 'cancel', 'done'],
'events should match');
}, 'cancel should abort a pending read()');
promise_test(async () => {
let cancelComplete = false;
const rs = await createTransferredReadableStream({
async cancel() {
await flushAsyncEvents();
cancelComplete = true;
}
});
await rs.cancel();
assert_false(cancelComplete,
'cancel() on the underlying sink should not have completed');
}, 'stream cancel should not wait for underlying source cancel');
promise_test(async t => {
const rs = await recordingTransferredReadableStream();
const reader = rs.getReader();
let serializationHappened = false;
rs.controller.enqueue({
get getter() {
serializationHappened = true;
return 'a';
}
});
await flushAsyncEvents();
assert_false(serializationHappened,
'serialization should not have happened yet');
const {value, done} = await reader.read();
assert_false(done, 'should not be done');
assert_equals(value.getter, 'a', 'getter should be a');
assert_true(serializationHappened,
'serialization should have happened');
}, 'serialization should not happen until the value is read');
promise_test(async t => {
const rs = await recordingTransferredReadableStream();
const reader = rs.getReader();
rs.controller.enqueue(new ReadableStream());
await promise_rejects_dom(t, 'DataCloneError', reader.read(),
'closed promise should reject');
assert_throws_js(TypeError, () => rs.controller.enqueue(),
'original stream should be errored');
}, 'transferring a non-serializable chunk should error both sides');
promise_test(async t => {
const rs = await createTransferredReadableStream({
start(controller) {
controller.error('foo');
}
});
const reader = rs.getReader();
return promise_rejects_exactly(t, 'foo', reader.read(),
'error should be passed through');
}, 'errors should be passed through');
promise_test(async () => {
const rs = await recordingTransferredReadableStream();
await delay(0);
const reader = rs.getReader();
reader.cancel();
rs.controller.error();
const {done} = await reader.read();
assert_true(done, 'should be done');
assert_throws_js(TypeError, () => rs.controller.enqueue(),
'enqueue should throw');
}, 'race between cancel() and error() should leave sides in different states');
promise_test(async () => {
const rs = await recordingTransferredReadableStream();
await delay(0);
const reader = rs.getReader();
reader.cancel();
rs.controller.close();
const {done} = await reader.read();
assert_true(done, 'should be done');
}, 'race between cancel() and close() should be benign');
promise_test(async () => {
const rs = await recordingTransferredReadableStream();
await delay(0);
const reader = rs.getReader();
reader.cancel();
rs.controller.enqueue('a');
const {done} = await reader.read();
assert_true(done, 'should be done');
}, 'race between cancel() and enqueue() should be benign');
</script>

View File

@ -0,0 +1,132 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/helpers.js"></script>
<script>
'use strict';
// Chrome used to special-case the reason for cancel() and abort() in order to
// handle exceptions correctly. This is no longer necessary. These tests are
// retained to avoid regressions.
async function getTransferredReason(originalReason) {
let resolvePromise;
const rv = new Promise(resolve => {
resolvePromise = resolve;
});
const rs = await createTransferredReadableStream({
cancel(reason) {
resolvePromise(reason);
}
});
await rs.cancel(originalReason);
return rv;
}
for (const value of ['hi', '\t\r\n', 7, 3.0, undefined, null, true, false,
NaN, Infinity]) {
promise_test(async () => {
const reason = await getTransferredReason(value);
assert_equals(reason, value, 'reason should match');
}, `reason with a simple value of '${value}' should be preserved`);
}
for (const badType of [Symbol('hi'), _ => 'hi']) {
promise_test(async t => {
return promise_rejects_dom(t, 'DataCloneError',
getTransferredReason(badType),
'cancel() should reject');
}, `reason with a type of '${typeof badType}' should be rejected and ` +
`error the stream`);
}
promise_test(async () => {
const reasonAsJson =
`{"foo":[1,"col"],"bar":{"hoge":0.2,"baz":{},"shan":null}}`;
const reason = await getTransferredReason(JSON.parse(reasonAsJson));
assert_equals(JSON.stringify(reason), reasonAsJson,
'object should be preserved');
}, 'objects that can be completely expressed in JSON should be preserved');
promise_test(async () => {
const circularObject = {};
circularObject.self = circularObject;
const reason = await getTransferredReason(circularObject);
assert_true(reason instanceof Object, 'an Object should be output');
assert_equals(reason.self, reason,
'the object should have a circular reference');
}, 'objects that cannot be expressed in JSON should also be preserved');
promise_test(async () => {
const originalReason = new TypeError('hi');
const reason = await getTransferredReason(originalReason);
assert_true(reason instanceof TypeError,
'type should be preserved');
assert_equals(reason.message, originalReason.message,
'message should be preserved');
}, 'the type and message of a TypeError should be preserved');
promise_test(async () => {
const originalReason = new TypeError('hi');
originalReason.foo = 'bar';
const reason = await getTransferredReason(originalReason);
assert_false('foo' in reason,
'foo should not be preserved');
}, 'other attributes of a TypeError should not be preserved');
promise_test(async () => {
const originalReason = new TypeError();
originalReason.message = [1, 2, 3];
const reason = await getTransferredReason(originalReason);
assert_equals(reason.message, '1,2,3', 'message should be stringified');
}, 'a TypeError message should be converted to a string');
promise_test(async () => {
const originalReason = new TypeError();
Object.defineProperty(originalReason, 'message', {
get() { return 'words'; }
});
const reason = await getTransferredReason(originalReason);
assert_equals(reason.message, '', 'message should not be preserved');
}, 'a TypeError message should not be preserved if it is a getter');
promise_test(async () => {
const originalReason = new TypeError();
delete originalReason.message;
TypeError.prototype.message = 'inherited message';
const reason = await getTransferredReason(originalReason);
delete TypeError.prototype.message;
assert_equals(reason.message, '', 'message should not be preserved');
}, 'a TypeError message should not be preserved if it is inherited');
promise_test(async () => {
const originalReason = new DOMException('yes', 'AbortError');
const reason = await getTransferredReason(originalReason);
assert_true(reason instanceof DOMException,
'reason should be a DOMException');
assert_equals(reason.message, originalReason.message,
'the messages should match');
assert_equals(reason.name, originalReason.name,
'the names should match');
}, 'DOMException errors should be preserved');
for (const errorConstructor of [EvalError, RangeError,
ReferenceError, SyntaxError, TypeError,
URIError]) {
promise_test(async () => {
const originalReason = new errorConstructor('nope');
const reason = await getTransferredReason(originalReason);
assert_equals(typeof reason, 'object', 'reason should have type object');
assert_true(reason instanceof errorConstructor,
`reason should inherit ${errorConstructor.name}`);
assert_true(reason instanceof Error, 'reason should inherit Error');
assert_equals(reason.constructor, errorConstructor,
'reason should have the right constructor');
assert_equals(reason.name, errorConstructor.name,
`name should match constructor name`);
assert_equals(reason.message, 'nope', 'message should match');
}, `${errorConstructor.name} should be preserved`);
}
</script>

View File

@ -0,0 +1,11 @@
// There aren't many cloneable types that will cause an error on
// deserialization. WASM modules have the property that it's an error to
// deserialize them cross-site, which works for our purposes.
async function createWasmModule() {
// It doesn't matter what the module is, so we use one from another
// test.
const response =
await fetch("/wasm/serialization/module/resources/incrementer.wasm");
const ab = await response.arrayBuffer();
return WebAssembly.compile(ab);
}

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="create-wasm-module.js"></script>
<script>
async_test(t => {
parent.postMessage('init done', '*');
window.addEventListener('message', async evt => {
if (evt.data.constructor.name !== 'WritableStream') {
return;
}
const ws = evt.data;
const writer = ws.getWriter();
const module = await createWasmModule();
writer.write(module);
await promise_rejects_dom(t, 'DataCloneError', writer.closed,
'should reject with a DataCloneError');
t.done();
// Signal that this test is done. When both tests are done the iframe will
// be removed.
parent.postMessage('ws done', '*');
});
}, 'a WritableStream deserialization failure should result in a DataCloneError');
async_test(t => {
window.addEventListener('message', async evt => {
if (evt.data.constructor.name !== 'ReadableStream') {
return;
}
const rs = evt.data;
const reader = rs.getReader();
await promise_rejects_dom(t, 'DataCloneError', reader.read(),
'should reject with a DataCloneError');
t.done();
// Signal that this test is done. When both tests are done the iframe will
// be removed.
parent.postMessage('rs done', '*');
});
}, 'a ReadableStream deserialization failure should result in a DataCloneError');
</script>

View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script>
addEventListener('message', evt => {
evt.source.postMessage(evt.data, '*', [evt.data]);
});
</script>

View File

@ -0,0 +1,2 @@
// A worker that just transfers back any message that is sent to it.
onmessage = evt => postMessage(evt.data, [evt.data]);

View File

@ -0,0 +1,132 @@
'use strict';
(() => {
// Create a ReadableStream that will pass the tests in
// testTransferredReadableStream(), below.
function createOriginalReadableStream() {
return new ReadableStream({
start(controller) {
controller.enqueue('a');
controller.close();
}
});
}
// Common tests to roughly determine that |rs| is a correctly transferred
// version of a stream created by createOriginalReadableStream().
function testTransferredReadableStream(rs) {
assert_equals(rs.constructor, ReadableStream,
'rs should be a ReadableStream in this realm');
assert_true(rs instanceof ReadableStream,
'instanceof check should pass');
// Perform a brand-check on |rs| in the process of calling getReader().
const reader = ReadableStream.prototype.getReader.call(rs);
return reader.read().then(({value, done}) => {
assert_false(done, 'done should be false');
assert_equals(value, 'a', 'value should be "a"');
return reader.read();
}).then(({done}) => {
assert_true(done, 'done should be true');
});
}
function testMessage(msg) {
assert_array_equals(msg.ports, [], 'there should be no ports in the event');
return testTransferredReadableStream(msg.data);
}
function testMessageEvent(target) {
return new Promise((resolve, reject) => {
target.addEventListener('message', ev => {
try {
resolve(testMessage(ev));
} catch (e) {
reject(e);
}
}, {once: true});
});
}
function testMessageEventOrErrorMessage(target) {
return new Promise((resolve, reject) => {
target.addEventListener('message', ev => {
if (typeof ev.data === 'string') {
// Assume it's an error message and reject with it.
reject(ev.data);
return;
}
try {
resolve(testMessage(ev));
} catch (e) {
reject(e);
}
}, {once: true});
});
}
function checkTestResults(target) {
return new Promise((resolve, reject) => {
target.onmessage = msg => {
// testharness.js sends us objects which we need to ignore.
if (typeof msg.data !== 'string')
return;
if (msg.data === 'OK') {
resolve();
} else {
reject(msg.data);
}
};
});
}
// These tests assume that a transferred ReadableStream will behave the same
// regardless of how it was transferred. This enables us to simply transfer the
// stream to ourselves.
function createTransferredReadableStream(underlyingSource) {
const original = new ReadableStream(underlyingSource);
const promise = new Promise((resolve, reject) => {
addEventListener('message', msg => {
const rs = msg.data;
if (rs instanceof ReadableStream) {
resolve(rs);
} else {
reject(new Error(`what is this thing: "${rs}"?`));
}
}, {once: true});
});
postMessage(original, '*', [original]);
return promise;
}
function recordingTransferredReadableStream(underlyingSource, strategy) {
const original = recordingReadableStream(underlyingSource, strategy);
const promise = new Promise((resolve, reject) => {
addEventListener('message', msg => {
const rs = msg.data;
if (rs instanceof ReadableStream) {
rs.events = original.events;
rs.eventsWithoutPulls = original.eventsWithoutPulls;
rs.controller = original.controller;
resolve(rs);
} else {
reject(new Error(`what is this thing: "${rs}"?`));
}
}, {once: true});
});
postMessage(original, '*', [original]);
return promise;
}
self.createOriginalReadableStream = createOriginalReadableStream;
self.testMessage = testMessage;
self.testMessageEvent = testMessageEvent;
self.testMessageEventOrErrorMessage = testMessageEventOrErrorMessage;
self.checkTestResults = checkTestResults;
self.createTransferredReadableStream = createTransferredReadableStream;
self.recordingTransferredReadableStream = recordingTransferredReadableStream;
})();

View File

@ -0,0 +1,11 @@
'use strict';
importScripts('/resources/testharness.js', 'helpers.js');
onconnect = evt => {
const port = evt.source;
const promise = testMessageEvent(port);
port.start();
promise
.then(() => port.postMessage('OK'))
.catch(err => port.postMessage(`BAD: ${err}`));
};

View File

@ -0,0 +1,7 @@
'use strict';
importScripts('/resources/testharness.js', 'helpers.js');
const promise = testMessageEvent(self);
promise
.then(() => postMessage('OK'))
.catch(err => postMessage(`BAD: ${err}`));

View File

@ -0,0 +1,12 @@
'use strict';
importScripts('helpers.js');
onconnect = msg => {
const port = msg.source;
const orig = createOriginalReadableStream();
try {
port.postMessage(orig, [orig]);
} catch (e) {
port.postMessage(e.message);
}
};

View File

@ -0,0 +1,5 @@
'use strict';
importScripts('helpers.js');
const orig = createOriginalReadableStream();
postMessage(orig, [orig]);

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="helpers.js"></script>
<script>
'use strict';
setup({
explicit_done: true
});
function startTests() {
promise_test(() => {
const orig = createOriginalReadableStream();
const promise = checkTestResults(navigator.serviceWorker);
navigator.serviceWorker.controller.postMessage(orig, [orig]);
assert_true(orig.locked, 'the original stream should be locked');
return promise;
}, 'serviceWorker.controller.postMessage should be able to transfer a ' +
'ReadableStream');
promise_test(() => {
const promise = testMessageEventOrErrorMessage(navigator.serviceWorker);
navigator.serviceWorker.controller.postMessage('SEND');
return promise;
}, 'postMessage in a service worker should be able to transfer ReadableStream');
done();
}
// Delay running the tests until we get a message from the page telling us to.
// This is to work around an issue where testharness.js doesn't detect
// completion of the tests if they fail too early.
onmessage = msg => {
if (msg.data === 'explicit trigger')
startTests();
};
</script>

View File

@ -0,0 +1,30 @@
'use strict';
importScripts('/resources/testharness.js', 'helpers.js');
onmessage = msg => {
const client = msg.source;
if (msg.data === 'SEND') {
sendingTest(client);
} else {
receivingTest(msg, client);
}
};
function sendingTest(client) {
const orig = createOriginalReadableStream();
try {
client.postMessage(orig, [orig]);
} catch (e) {
client.postMessage(e.message);
}
}
function receivingTest(msg, client) {
try {
msg.waitUntil(testMessage(msg)
.then(() => client.postMessage('OK'))
.catch(e => client.postMessage(`BAD: ${e}`)));
} catch (e) {
client.postMessage(`BAD: ${e}`);
}
}

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
<script>
'use strict';
const kServiceWorkerUrl = 'resources/service-worker.js';
const kIframeUrl = 'resources/service-worker-iframe.html';
// A dummy test so that we can use the test-helpers.sub.js functions
const test = async_test('service-worker');
async function registerAndStart() {
const reg = await service_worker_unregister_and_register(
test, kServiceWorkerUrl, kIframeUrl);
await wait_for_state(test, reg.installing, 'activated');
const iframe = await with_iframe(kIframeUrl);
fetch_tests_from_window(iframe.contentWindow);
add_completion_callback(() => iframe.remove());
iframe.contentWindow.postMessage('explicit trigger', '*');
return service_worker_unregister_and_done(test, kIframeUrl);
}
onload = registerAndStart;
</script>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/helpers.js"></script>
<script>
'use strict';
promise_test(t => {
const orig = createOriginalReadableStream();
const w = new SharedWorker('resources/receiving-shared-worker.js');
const promise = checkTestResults(w.port);
w.port.postMessage(orig, [orig]);
assert_true(orig.locked, 'the original stream should be locked');
return promise;
}, 'worker.postMessage should be able to transfer a ReadableStream');
promise_test(t => {
const w = new SharedWorker('resources/sending-shared-worker.js');
const promise = testMessageEventOrErrorMessage(w.port);
w.port.start();
return promise;
}, 'postMessage in a worker should be able to transfer a ReadableStream');
</script>

View File

@ -0,0 +1,219 @@
"use strict";
function receiveEventOnce(target, name) {
return new Promise(resolve => {
target.addEventListener(
name,
ev => {
resolve(ev);
},
{ once: true }
);
});
}
async function postAndTestMessageEvent(data, transfer, title) {
postMessage(data, "*", transfer);
const messagePortCount = transfer.filter(i => i instanceof MessagePort)
.length;
const ev = await receiveEventOnce(window, "message");
assert_equals(
ev.ports.length,
messagePortCount,
`Correct number of ports ${title}`
);
for (const [i, port] of ev.ports.entries()) {
assert_true(
port instanceof MessagePort,
`ports[${i}] include MessagePort ${title}`
);
}
for (const [key, value] of Object.entries(data)) {
assert_true(
ev.data[key] instanceof value.constructor,
`data.${key} has correct interface ${value.constructor.name} ${title}`
);
}
}
async function transferMessagePortWithOrder1(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ stream, port2: channel.port2 },
[stream, channel.port2],
`when transferring [${stream.constructor.name}, MessagePort]`
);
}
async function transferMessagePortWithOrder2(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ stream, port2: channel.port2 },
[channel.port2, stream],
`when transferring [MessagePort, ${stream.constructor.name}]`
);
}
async function transferMessagePortWithOrder3(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ port1: channel.port1, stream, port2: channel.port2 },
[channel.port1, stream, channel.port2],
`when transferring [MessagePort, ${stream.constructor.name}, MessagePort]`
);
}
async function transferMessagePortWithOrder4(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{},
[channel.port1, stream, channel.port2],
`when transferring [MessagePort, ${stream.constructor.name}, MessagePort] but with empty data`
);
}
async function transferMessagePortWithOrder5(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ port2: channel.port2, port1: channel.port1, stream },
[channel.port1, stream, channel.port2],
`when transferring [MessagePort, ${stream.constructor.name}, MessagePort] but with data having different order`
);
}
async function transferMessagePortWithOrder6(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ port2: channel.port2, port1: channel.port1 },
[channel.port1, stream, channel.port2],
`when transferring [MessagePort, ${stream.constructor.name}, MessagePort] but with stream not being in the data`
);
}
async function transferMessagePortWithOrder7(stream) {
const channel = new MessageChannel();
await postAndTestMessageEvent(
{ stream },
[channel.port1, stream, channel.port2],
`when transferring [MessagePort, ${stream.constructor.name}, MessagePort] but with ports not being in the data`
);
}
async function transferMessagePortWith(constructor) {
await transferMessagePortWithOrder1(new constructor());
await transferMessagePortWithOrder2(new constructor());
await transferMessagePortWithOrder3(new constructor());
}
async function advancedTransferMessagePortWith(constructor) {
await transferMessagePortWithOrder4(new constructor());
await transferMessagePortWithOrder5(new constructor());
await transferMessagePortWithOrder6(new constructor());
await transferMessagePortWithOrder7(new constructor());
}
async function mixedTransferMessagePortWithOrder1() {
const channel = new MessageChannel();
const readable = new ReadableStream();
const writable = new WritableStream();
const transform = new TransformStream();
await postAndTestMessageEvent(
{
readable,
writable,
transform,
port1: channel.port1,
port2: channel.port2,
},
[readable, writable, transform, channel.port1, channel.port2],
`when transferring [ReadableStream, WritableStream, TransformStream, MessagePort, MessagePort]`
);
}
async function mixedTransferMessagePortWithOrder2() {
const channel = new MessageChannel();
const readable = new ReadableStream();
const writable = new WritableStream();
const transform = new TransformStream();
await postAndTestMessageEvent(
{ readable, writable, transform },
[transform, channel.port1, readable, channel.port2, writable],
`when transferring [TransformStream, MessagePort, ReadableStream, MessagePort, WritableStream]`
);
}
async function mixedTransferMessagePortWithOrder3() {
const channel = new MessageChannel();
const readable1 = new ReadableStream();
const readable2 = new ReadableStream();
const writable1 = new WritableStream();
const writable2 = new WritableStream();
const transform1 = new TransformStream();
const transform2 = new TransformStream();
await postAndTestMessageEvent(
{ readable1, writable1, transform1, readable2, writable2, transform2 },
[
transform2,
channel.port1,
readable1,
channel.port2,
writable2,
readable2,
writable1,
transform1,
],
`when transferring [TransformStream, MessagePort, ReadableStream, MessagePort, WritableStream, ReadableStream, WritableStream, TransformStream] but with the data having different order`
);
}
async function mixedTransferMessagePortWith() {
await mixedTransferMessagePortWithOrder1();
await mixedTransferMessagePortWithOrder2();
await mixedTransferMessagePortWithOrder3();
}
promise_test(async t => {
await transferMessagePortWith(ReadableStream);
}, "Transferring a MessagePort with a ReadableStream should set `.ports`");
promise_test(async t => {
await transferMessagePortWith(WritableStream);
}, "Transferring a MessagePort with a WritableStream should set `.ports`");
promise_test(async t => {
await transferMessagePortWith(TransformStream);
}, "Transferring a MessagePort with a TransformStream should set `.ports`");
promise_test(async t => {
await advancedTransferMessagePortWith(ReadableStream);
}, "Transferring a MessagePort with a ReadableStream should set `.ports`, advanced");
promise_test(async t => {
await advancedTransferMessagePortWith(WritableStream);
}, "Transferring a MessagePort with a WritableStream should set `.ports`, advanced");
promise_test(async t => {
await advancedTransferMessagePortWith(TransformStream);
}, "Transferring a MessagePort with a TransformStream should set `.ports`, advanced");
promise_test(async t => {
await mixedTransferMessagePortWith();
}, "Transferring a MessagePort with multiple streams should set `.ports`");
test(() => {
assert_throws_dom("DataCloneError", () =>
postMessage({ stream: new ReadableStream() }, "*")
);
}, "ReadableStream must not be serializable");
test(() => {
assert_throws_dom("DataCloneError", () =>
postMessage({ stream: new WritableStream() }, "*")
);
}, "WritableStream must not be serializable");
test(() => {
assert_throws_dom("DataCloneError", () =>
postMessage({ stream: new TransformStream() }, "*")
);
}, "TransformStream must not be serializable");

View File

@ -0,0 +1,18 @@
// META: global=window,dedicatedworker,shadowrealm
const combinations = [
(t => [t, t.readable])(new TransformStream()),
(t => [t.readable, t])(new TransformStream()),
(t => [t, t.writable])(new TransformStream()),
(t => [t.writable, t])(new TransformStream()),
];
for (const combination of combinations) {
test(() => {
assert_throws_dom(
"DataCloneError",
() => structuredClone(combination, { transfer: combination }),
"structuredClone should throw"
);
}, `Transferring ${combination} should fail`);
}

View File

@ -0,0 +1,108 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/test-utils.js"></script>
<script>
'use strict';
promise_test(t => {
const orig = new TransformStream();
const promise = new Promise(resolve => {
addEventListener('message', t.step_func(evt => {
const transferred = evt.data;
assert_equals(transferred.constructor, TransformStream,
'transferred should be a TransformStream in this realm');
assert_true(transferred instanceof TransformStream,
'instanceof check should pass');
// Perform a brand-check on |transferred|.
const readableGetter = Object.getOwnPropertyDescriptor(
TransformStream.prototype, 'readable').get;
assert_true(readableGetter.call(transferred) instanceof ReadableStream,
'brand check should pass and readable stream should result');
const writableGetter = Object.getOwnPropertyDescriptor(
TransformStream.prototype, 'writable').get;
assert_true(writableGetter.call(transferred) instanceof WritableStream,
'brand check should pass and writable stream should result');
resolve();
}), {once: true});
});
postMessage(orig, '*', [orig]);
assert_true(orig.readable.locked, 'the readable side should be locked');
assert_true(orig.writable.locked, 'the writable side should be locked');
return promise;
}, 'window.postMessage should be able to transfer a TransformStream');
test(() => {
const ts = new TransformStream();
const writer = ts.writable.getWriter();
assert_throws_dom('DataCloneError', () => postMessage(ts, '*', [ts]),
'postMessage should throw');
assert_false(ts.readable.locked, 'readable side should not get locked');
}, 'a TransformStream with a locked writable should not be transferable');
test(() => {
const ts = new TransformStream();
const reader = ts.readable.getReader();
assert_throws_dom('DataCloneError', () => postMessage(ts, '*', [ts]),
'postMessage should throw');
assert_false(ts.writable.locked, 'writable side should not get locked');
}, 'a TransformStream with a locked readable should not be transferable');
test(() => {
const ts = new TransformStream();
const reader = ts.readable.getReader();
const writer = ts.writable.getWriter();
assert_throws_dom('DataCloneError', () => postMessage(ts, '*', [ts]),
'postMessage should throw');
}, 'a TransformStream with both sides locked should not be transferable');
promise_test(t => {
const source = new ReadableStream({
start(controller) {
controller.enqueue('hello ');
controller.enqueue('there ');
controller.close();
}
});
let resolve;
const ready = new Promise(r => resolve = r);
let result = '';
const sink = new WritableStream({
write(chunk) {
if (result) {
resolve();
}
result += chunk;
}
});
const transform1 = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toUpperCase());
}
});
const transform2 = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk + chunk);
}
});
const promise = new Promise(resolve => {
addEventListener('message', t.step_func(evt => {
const data = evt.data;
resolve(data.source
.pipeThrough(data.transform1)
.pipeThrough(data.transform2)
.pipeTo(data.sink));
}));
});
postMessage({source, sink, transform1, transform2}, '*',
[source, transform1, sink, transform2]);
return ready
.then(() => {
assert_equals(result, 'HELLO HELLO THERE THERE ',
'transforms should have been applied');
});
}, 'piping through transferred transforms should work');
</script>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/helpers.js"></script>
<script>
'use strict';
promise_test(t => {
const orig = createOriginalReadableStream();
const promise = testMessageEvent(window);
postMessage(orig, '*', [orig]);
assert_true(orig.locked, 'the original stream should be locked');
return promise;
}, 'window.postMessage should be able to transfer a ReadableStream');
promise_test(t => {
const orig = createOriginalReadableStream();
const mc = new MessageChannel();
const promise = testMessageEvent(mc.port1);
mc.port1.start();
mc.port2.postMessage(orig, [orig]);
mc.port2.close();
assert_true(orig.locked, 'the original stream should be locked');
return promise;
}, 'port.postMessage should be able to transfer a ReadableStream');
promise_test(t => {
const orig = createOriginalReadableStream();
const promise = new Promise(resolve => {
addEventListener('message', t.step_func(evt => {
const [rs1, rs2] = evt.data;
assert_equals(rs1, rs2, 'both ReadableStreams should be the same object');
resolve();
}), {once: true});
});
postMessage([orig, orig], '*', [orig]);
return promise;
}, 'the same ReadableStream posted multiple times should arrive together');
const onloadPromise = new Promise(resolve => onload = resolve);
promise_test(() => {
const orig = createOriginalReadableStream();
const promise = testMessageEvent(window);
return onloadPromise.then(() => {
const echoIframe = document.querySelector('#echo');
echoIframe.contentWindow.postMessage(orig, '*', [orig]);
return promise;
});
}, 'transfer to and from an iframe should work');
</script>
<iframe id=echo src="resources/echo-iframe.html" style="display:none"></iframe>

View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/helpers.js"></script>
<script src="../resources/test-utils.js"></script>
<script>
'use strict';
promise_test(t => {
const orig = createOriginalReadableStream();
const w = new Worker('resources/receiving-worker.js');
t.add_cleanup(() => {
w.terminate();
});
const promise = new Promise((resolve, reject) => {
checkTestResults(w).then(resolve, reject);
w.onerror = () => reject('error in worker');
});
w.postMessage(orig, [orig]);
assert_true(orig.locked, 'the original stream should be locked');
return promise;
}, 'worker.postMessage should be able to transfer a ReadableStream');
promise_test(t => {
const w = new Worker('resources/sending-worker.js');
t.add_cleanup(() => {
w.terminate();
});
return new Promise((resolve, reject) => {
testMessageEvent(w).then(resolve, reject);
w.onerror = () => reject('error in worker');
});
}, 'postMessage in a worker should be able to transfer a ReadableStream');
promise_test(async t => {
const w = new Worker('resources/echo-worker.js');
let controller;
const orig = new ReadableStream({
start(c) {
controller = c;
}
});
const targetStream = await new Promise((resolve, reject) => {
w.onmessage = evt => resolve(evt.data);
w.onerror = () => reject('error in worker');
w.postMessage(orig, [orig]);
});
const reader = targetStream.getReader();
const reads = [];
// Place a lot of chunks "in transit". This should increase the likelihood
// that they is a chunk at each relevant step when the worker is terminated.
for (let i = 0; i < 50; ++i) {
await delay(0);
controller.enqueue(i);
const expected = i;
reads.push(reader.read().then(({value, done}) => {
assert_false(done, 'we should not be done');
assert_equals(value, expected, 'value should match expectation');
}));
}
w.terminate();
for (let i = 50; i < 60; ++i) {
controller.enqueue(i);
reads.push(
reader.read().then(t.unreached_func('read() should not resolve')));
await delay(0);
}
// We don't expect every read() to complete, but we want to give them a chance
// to reject if they're going to.
return Promise.race([
Promise.all(reads),
flushAsyncEvents()
]);
}, 'terminating a worker should not error the stream');
</script>

View File

@ -0,0 +1,146 @@
<!DOCTYPE html>
<meta charset="utf-8">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="resources/helpers.js"></script>
<script src="../resources/test-utils.js"></script>
<script src="../resources/recording-streams.js"></script>
<script>
'use strict';
promise_test(t => {
const orig = new WritableStream();
const promise = new Promise(resolve => {
addEventListener('message', t.step_func(evt => {
const transferred = evt.data;
assert_equals(transferred.constructor, WritableStream,
'transferred should be a WritableStream in this realm');
assert_true(transferred instanceof WritableStream,
'instanceof check should pass');
// Perform a brand-check on |transferred|.
const writer = WritableStream.prototype.getWriter.call(transferred);
resolve();
}), {once: true});
});
postMessage(orig, '*', [orig]);
assert_true(orig.locked, 'the original stream should be locked');
return promise;
}, 'window.postMessage should be able to transfer a WritableStream');
test(() => {
const ws = new WritableStream();
const writer = ws.getWriter();
assert_throws_dom('DataCloneError', () => postMessage(ws, '*', [ws]),
'postMessage should throw');
}, 'a locked WritableStream should not be transferable');
promise_test(t => {
const {writable, readable} = new TransformStream();
const promise = new Promise(resolve => {
addEventListener('message', t.step_func(async evt => {
const {writable, readable} = evt.data;
const reader = readable.getReader();
const writer = writable.getWriter();
const writerPromises = Promise.all([
writer.write('hi'),
writer.close(),
]);
const {value, done} = await reader.read();
assert_false(done, 'we should not be done');
assert_equals(value, 'hi', 'chunk should have been delivered');
const readResult = await reader.read();
assert_true(readResult.done, 'readable should be closed');
await writerPromises;
resolve();
}), {once: true});
});
postMessage({writable, readable}, '*', [writable, readable]);
return promise;
}, 'window.postMessage should be able to transfer a {readable, writable} pair');
function transfer(stream) {
return new Promise(resolve => {
addEventListener('message', evt => resolve(evt.data), { once: true });
postMessage(stream, '*', [stream]);
});
}
promise_test(async () => {
const orig = new WritableStream(
{}, new ByteLengthQueuingStrategy({ highWaterMark: 65536 }));
const transferred = await transfer(orig);
const writer = transferred.getWriter();
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
}, 'desiredSize for a newly-transferred stream should be 1');
promise_test(async () => {
const orig = new WritableStream({
write() {
return new Promise(() => {});
}
});
const transferred = await transfer(orig);
const writer = transferred.getWriter();
await writer.write('a');
assert_equals(writer.desiredSize, 1, 'desiredSize should be 1');
}, 'effective queue size of a transferred writable should be 2');
promise_test(async () => {
const [writeCalled, resolveWriteCalled] = makePromiseAndResolveFunc();
let resolveWrite;
const orig = new WritableStream({
write() {
resolveWriteCalled();
return new Promise(resolve => {
resolveWrite = resolve;
});
}
});
const transferred = await transfer(orig);
const writer = transferred.getWriter();
await writer.write('a');
let writeDone = false;
const writePromise = writer.write('b').then(() => {
writeDone = true;
});
await writeCalled;
assert_false(writeDone, 'second write should not have resolved yet');
resolveWrite();
await writePromise; // (makes sure this resolves)
}, 'second write should wait for first underlying write to complete');
async function transferredWritableStreamWithAbortPromise() {
const [abortCalled, resolveAbortCalled] = makePromiseAndResolveFunc();
const orig = recordingWritableStream({
abort() {
resolveAbortCalled();
}
});
const transferred = await transfer(orig);
return { orig, transferred, abortCalled };
}
promise_test(async t => {
const { orig, transferred, abortCalled } = await transferredWritableStreamWithAbortPromise();
transferred.abort('p');
await abortCalled;
assert_array_equals(orig.events, ['abort', 'p'],
'abort() should have been called');
}, 'abort() should work');
promise_test(async t => {
const { orig, transferred, abortCalled } = await transferredWritableStreamWithAbortPromise();
const writer = transferred.getWriter();
// A WritableStream object cannot be cloned.
await promise_rejects_dom(t, 'DataCloneError', writer.write(new WritableStream()),
'the write should reject');
await promise_rejects_dom(t, 'DataCloneError', writer.closed,
'the stream should be errored');
await abortCalled;
assert_equals(orig.events.length, 2, 'abort should have been called');
assert_equals(orig.events[0], 'abort', 'first event should be abort');
assert_equals(orig.events[1].name, 'DataCloneError',
'reason should be a DataCloneError');
}, 'writing a unclonable object should error the stream');
</script>