From 864c16c23b8cec1844ed1011a2eb7eb6334c60dd Mon Sep 17 00:00:00 2001 From: Youenn Fablet Date: Thu, 13 Apr 2023 12:05:44 +0200 Subject: [PATCH 1/8] Add tests related to ReadableStream of type 'transfer' --- interfaces/streams.idl | 2 +- streams/readable-streams/transfer-type.any.js | 220 ++++++++++++++++++ 2 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 streams/readable-streams/transfer-type.any.js diff --git a/interfaces/streams.idl b/interfaces/streams.idl index f7084dd6061042..58564a1429a377 100644 --- a/interfaces/streams.idl +++ b/interfaces/streams.idl @@ -56,7 +56,7 @@ callback UnderlyingSourceStartCallback = any (ReadableStreamController controlle callback UnderlyingSourcePullCallback = Promise (ReadableStreamController controller); callback UnderlyingSourceCancelCallback = Promise (optional any reason); -enum ReadableStreamType { "bytes" }; +enum ReadableStreamType { "bytes", "transfer" }; interface mixin ReadableStreamGenericReader { readonly attribute Promise closed; diff --git a/streams/readable-streams/transfer-type.any.js b/streams/readable-streams/transfer-type.any.js new file mode 100644 index 00000000000000..21941508e42f3f --- /dev/null +++ b/streams/readable-streams/transfer-type.any.js @@ -0,0 +1,220 @@ +// META: global=window,worker +// META: script=../resources/test-utils.js +// META: script=../resources/rs-utils.js +'use strict'; + +function createArrayBuffer() +{ + return new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); +} + +function createVideoFrame() +{ + let init = { + format: 'I420', + timestamp: 1234, + codedWidth: 4, + codedHeight: 2 + }; + let data = new Uint8Array([ + 1, 2, 3, 4, 5, 6, 7, 8, // y + 1, 2, // u + 1, 2, // v + ]); + + if (!self.VideoFrame) { + self.VideoFrame = class { + constructor(data, init) { + this.format = init.format; + this.isDetached = false; + } + close() { + this.format = null; + this.isDetached = true; + } + }; + } + + return new VideoFrame(data, init); +} + +test(() => { + new ReadableStream({ type: 'transfer' }); // ReadableStream constructed with 'transfer' type +}, 'ReadableStream can be constructed with transfer type'); + +test(() => { + let startCalled = false; + + const source = { + start(controller) { + assert_equals(this, source, 'source is this during start'); + assert_true(controller instanceof ReadableStreamDefaultController, 'default controller'); + startCalled = true; + }, + type: 'transfer' + }; + + new ReadableStream(source); + assert_true(startCalled); +}, 'ReadableStream of type transfer should call start with a ReadableStreamDefaultController'); + +promise_test(async () => { + const videoFrame = createVideoFrame(); + videoFrame.test = 1; + const source = { + start(controller) { + assert_equals(videoFrame.format, 'I420'); + controller.enqueue(videoFrame, [videoFrame]); + assert_equals(videoFrame.format, null); + assert_equals(videoFrame.test, 1); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + // Cancelling the stream should close all video frames, thus no console messages of GCing VideoFrames should happen. + stream.cancel(); +}, 'ReadableStream of type transfer should close serialized chunks'); + +promise_test(async () => { + const buffer = createArrayBuffer(); + buffer.test = 1; + const source = { + start(controller) { + assert_equals(buffer.length, 8); + controller.enqueue(buffer, [buffer]); + assert_equals(buffer.length, 0); + assert_equals(buffer.test, 1); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const reader = stream.getReader(); + + const chunk = await reader.read(); + assert_equals(chunk.value.length, 8); + assert_equals(chunk.value.test, undefined); +}, 'ReadableStream of type transfer should transfer enqueued chunks'); + +promise_test(async () => { + const videoFrame = createVideoFrame(); + videoFrame.test = 1; + const source = { + start(controller) { + assert_equals(videoFrame.format, 'I420'); + controller.enqueue({ videoFrame }, [videoFrame]); + assert_equals(videoFrame.format, null); + assert_equals(videoFrame.test, 1); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const reader = stream.getReader(); + + const chunk = await reader.read(); + assert_equals(chunk.value.videoFrame.format, 'I420'); + assert_equals(chunk.value.videoFrame.test, undefined); + + chunk.value.videoFrame.close(); +}, 'ReadableStream of type transfer should transfer JS chunks with transferred values'); + +promise_test(async () => { + const videoFrame = createVideoFrame(); + videoFrame.close(); + const source = { + start(controller) { + try { + controller.enqueue(videoFrame, [videoFrame]); + assert_unreached('enqueue should throw'); + } catch (e) { + // + } + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const reader = stream.getReader(); + + return reader.read().then(() => { + assert_unreached('enqueue should error the stream'); + }, () => { + }); +}, 'ReadableStream of type transfer should error when trying to enqueue not serializable values'); + +promise_test(async () => { + const videoFrame = createVideoFrame(); + const source = { + start(controller) { + controller.enqueue(videoFrame, [videoFrame]); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const [clone1, clone2] = stream.tee(); + + const chunk1 = await clone1.getReader().read(); + const chunk2 = await clone2.getReader().read(); + + assert_equals(videoFrame.format, null); + assert_equals(chunk1.value.format, 'I420'); + assert_equals(chunk2.value.format, 'I420'); + + chunk1.value.close(); + chunk2.value.close(); +}, 'ReadableStream of type transfer should clone serializable objects when teeing'); + +promise_test(async () => { + const videoFrame = createVideoFrame(); + videoFrame.test = 1; + const source = { + start(controller) { + assert_equals(videoFrame.format, 'I420'); + controller.enqueue({ videoFrame }, [videoFrame]); + assert_equals(videoFrame.format, null); + assert_equals(videoFrame.test, 1); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const [clone1, clone2] = stream.tee(); + + const chunk1 = await clone1.getReader().read(); + const chunk2 = await clone2.getReader().read(); + + assert_equals(videoFrame.format, null); + assert_equals(chunk1.value.videoFrame.format, 'I420'); + assert_equals(chunk2.value.videoFrame.format, 'I420'); + + chunk1.value.videoFrame.close(); + chunk2.value.videoFrame.close(); +}, 'ReadableStream of type transfer should clone JS Objects with serializables when teeing'); + +promise_test(async () => { + const channel = new MessageChannel; + let port1 = channel.port1; + const port2 = channel.port2; + + const source = { + start(controller) { + controller.enqueue({port1}, [port1]); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const [clone1, clone2] = stream.tee(); + + await clone1.getReader().read().then((value) => { + assert_unreached('clone2 should error'); + }, () => { }); + + + await clone2.getReader().read().then((value) => { + assert_unreached('clone2 should error'); + }, () => { }); +}, 'Second branch of transfer-only value ReadableStream tee should end up into errors'); From f8a02fe3296eb3972e3be3350bc62074ae51675e Mon Sep 17 00:00:00 2001 From: Youenn Fablet Date: Thu, 13 Apr 2023 14:18:11 +0200 Subject: [PATCH 2/8] Split tests --- .../transfer-type-message-channel.any.js | 57 ++++++++ .../transfer-type-video-frame.any.js | 136 ++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 streams/readable-streams/transfer-type-message-channel.any.js create mode 100644 streams/readable-streams/transfer-type-video-frame.any.js diff --git a/streams/readable-streams/transfer-type-message-channel.any.js b/streams/readable-streams/transfer-type-message-channel.any.js new file mode 100644 index 00000000000000..7423f40d8dc8fa --- /dev/null +++ b/streams/readable-streams/transfer-type-message-channel.any.js @@ -0,0 +1,57 @@ +// META: global=window,worker +// META: script=../resources/test-utils.js +// META: script=../resources/rs-utils.js +'use strict'; + +promise_test(async () => { + const channel = new MessageChannel; + let port1 = channel.port1; + const port2 = channel.port2; + + const source = { + start(controller) { + controller.enqueue(port1, { transfer : port1 }); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + + const chunk = await clone1.getReader().read().then((value) => { + assert_unreached('clone2 should error'); + }, () => { }); + + assert_not_equals(chunk.value, port1); + + let promise = new Promise(resolve => port2.onmessage = e => resolve(e.data)); + chunk.value.postMessage("toPort2"); + assert_equals(await promise, "toPort2"); + + promise = new Promise(resolve => chunk.value.onmessage = e => resolve(e.data)); + port2.postMessage("toPort1"); + assert_equals(await promise, "toPort1"); +}, 'Transferred MessageChannel works as expected'); + +promise_test(async () => { + const channel = new MessageChannel; + let port1 = channel.port1; + const port2 = channel.port2; + + const source = { + start(controller) { + controller.enqueue({port1}, { transfer : port1 }); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const [clone1, clone2] = stream.tee(); + + await clone1.getReader().read().then((value) => { + assert_unreached('clone1 should error'); + }, () => { }); + + await clone2.getReader().read().then((value) => { + assert_unreached('clone2 should error'); + }, () => { }); +}, 'Second branch of transfer-only value ReadableStream tee should end up into errors'); diff --git a/streams/readable-streams/transfer-type-video-frame.any.js b/streams/readable-streams/transfer-type-video-frame.any.js new file mode 100644 index 00000000000000..a02ddb6846156f --- /dev/null +++ b/streams/readable-streams/transfer-type-video-frame.any.js @@ -0,0 +1,136 @@ +// META: global=window,worker +// META: script=../resources/test-utils.js +// META: script=../resources/rs-utils.js +'use strict'; + +function createVideoFrame() +{ + let init = { + format: 'I420', + timestamp: 1234, + codedWidth: 4, + codedHeight: 2 + }; + let data = new Uint8Array([ + 1, 2, 3, 4, 5, 6, 7, 8, // y + 1, 2, // u + 1, 2, // v + ]); + + return new VideoFrame(data, init); +} + +promise_test(async () => { + const videoFrame = createVideoFrame(); + videoFrame.test = 1; + const source = { + start(controller) { + assert_equals(videoFrame.format, 'I420'); + controller.enqueue(videoFrame, { transfer : videoFrame }); + assert_equals(videoFrame.format, null); + assert_equals(videoFrame.test, 1); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + // Cancelling the stream should close all video frames, thus no console messages of GCing VideoFrames should happen. + stream.cancel(); +}, 'ReadableStream of type transfer should close serialized chunks'); + +promise_test(async () => { + const videoFrame = createVideoFrame(); + videoFrame.test = 1; + const source = { + start(controller) { + assert_equals(videoFrame.format, 'I420'); + controller.enqueue({ videoFrame }, { transfer : videoFrame }); + assert_equals(videoFrame.format, null); + assert_equals(videoFrame.test, 1); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const reader = stream.getReader(); + + const chunk = await reader.read(); + assert_equals(chunk.value.videoFrame.format, 'I420'); + assert_equals(chunk.value.videoFrame.test, undefined); + + chunk.value.videoFrame.close(); +}, 'ReadableStream of type transfer should transfer JS chunks with transferred values'); + +promise_test(async () => { + const videoFrame = createVideoFrame(); + videoFrame.close(); + const source = { + start(controller) { + try { + controller.enqueue(videoFrame, { transfer : videoFrame }); + assert_unreached('enqueue should throw'); + } catch (e) { + // + } + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const reader = stream.getReader(); + + return reader.read().then(() => { + assert_unreached('enqueue should error the stream'); + }, () => { + }); +}, 'ReadableStream of type transfer should error when trying to enqueue not serializable values'); + +promise_test(async () => { + const videoFrame = createVideoFrame(); + const source = { + start(controller) { + controller.enqueue(videoFrame, { transfer : videoFrame }); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const [clone1, clone2] = stream.tee(); + + const chunk1 = await clone1.getReader().read(); + const chunk2 = await clone2.getReader().read(); + + assert_equals(videoFrame.format, null); + assert_equals(chunk1.value.format, 'I420'); + assert_equals(chunk2.value.format, 'I420'); + + chunk1.value.close(); + chunk2.value.close(); +}, 'ReadableStream of type transfer should clone serializable objects when teeing'); + +promise_test(async () => { + const videoFrame = createVideoFrame(); + videoFrame.test = 1; + const source = { + start(controller) { + assert_equals(videoFrame.format, 'I420'); + controller.enqueue({ videoFrame }, { transfer : videoFrame }); + assert_equals(videoFrame.format, null); + assert_equals(videoFrame.test, 1); + }, + type: 'transfer' + }; + + const stream = new ReadableStream(source); + const [clone1, clone2] = stream.tee(); + + const chunk1 = await clone1.getReader().read(); + const chunk2 = await clone2.getReader().read(); + + assert_equals(videoFrame.format, null); + assert_equals(chunk1.value.videoFrame.format, 'I420'); + assert_equals(chunk2.value.videoFrame.format, 'I420'); + + chunk1.value.videoFrame.close(); + chunk2.value.videoFrame.close(); +}, 'ReadableStream of type transfer should clone JS Objects with serializables when teeing'); From ccf9cc2dcf7ea7d954df062908aeba24dc9415b9 Mon Sep 17 00:00:00 2001 From: Youenn Fablet Date: Thu, 13 Apr 2023 14:47:53 +0200 Subject: [PATCH 3/8] switch to owning --- ....js => owning-type-message-channel.any.js} | 14 +- ....any.js => owning-type-video-frame.any.js} | 30 +-- streams/readable-streams/owning-type.any.js | 48 ++++ streams/readable-streams/transfer-type.any.js | 220 ------------------ 4 files changed, 69 insertions(+), 243 deletions(-) rename streams/readable-streams/{transfer-type-message-channel.any.js => owning-type-message-channel.any.js} (77%) rename streams/readable-streams/{transfer-type-video-frame.any.js => owning-type-video-frame.any.js} (78%) create mode 100644 streams/readable-streams/owning-type.any.js delete mode 100644 streams/readable-streams/transfer-type.any.js diff --git a/streams/readable-streams/transfer-type-message-channel.any.js b/streams/readable-streams/owning-type-message-channel.any.js similarity index 77% rename from streams/readable-streams/transfer-type-message-channel.any.js rename to streams/readable-streams/owning-type-message-channel.any.js index 7423f40d8dc8fa..db3f6bdaa1117d 100644 --- a/streams/readable-streams/transfer-type-message-channel.any.js +++ b/streams/readable-streams/owning-type-message-channel.any.js @@ -10,16 +10,14 @@ promise_test(async () => { const source = { start(controller) { - controller.enqueue(port1, { transfer : port1 }); + controller.enqueue(port1, { transfer : [ port1 ] }); }, - type: 'transfer' + type: 'owning' }; const stream = new ReadableStream(source); - const chunk = await clone1.getReader().read().then((value) => { - assert_unreached('clone2 should error'); - }, () => { }); + const chunk = await stream.getReader().read(); assert_not_equals(chunk.value, port1); @@ -39,9 +37,9 @@ promise_test(async () => { const source = { start(controller) { - controller.enqueue({port1}, { transfer : port1 }); + controller.enqueue({ port1 }, { transfer : [ port1 ] }); }, - type: 'transfer' + type: 'owning' }; const stream = new ReadableStream(source); @@ -54,4 +52,4 @@ promise_test(async () => { await clone2.getReader().read().then((value) => { assert_unreached('clone2 should error'); }, () => { }); -}, 'Second branch of transfer-only value ReadableStream tee should end up into errors'); +}, 'Second branch of owning ReadableStream tee should end up into errors with transfer only values'); diff --git a/streams/readable-streams/transfer-type-video-frame.any.js b/streams/readable-streams/owning-type-video-frame.any.js similarity index 78% rename from streams/readable-streams/transfer-type-video-frame.any.js rename to streams/readable-streams/owning-type-video-frame.any.js index a02ddb6846156f..17842358a39f05 100644 --- a/streams/readable-streams/transfer-type-video-frame.any.js +++ b/streams/readable-streams/owning-type-video-frame.any.js @@ -26,17 +26,17 @@ promise_test(async () => { const source = { start(controller) { assert_equals(videoFrame.format, 'I420'); - controller.enqueue(videoFrame, { transfer : videoFrame }); + controller.enqueue(videoFrame, { transfer : [ videoFrame ] }); assert_equals(videoFrame.format, null); assert_equals(videoFrame.test, 1); }, - type: 'transfer' + type: 'owning' }; const stream = new ReadableStream(source); // Cancelling the stream should close all video frames, thus no console messages of GCing VideoFrames should happen. stream.cancel(); -}, 'ReadableStream of type transfer should close serialized chunks'); +}, 'ReadableStream of type owning should close serialized chunks'); promise_test(async () => { const videoFrame = createVideoFrame(); @@ -44,11 +44,11 @@ promise_test(async () => { const source = { start(controller) { assert_equals(videoFrame.format, 'I420'); - controller.enqueue({ videoFrame }, { transfer : videoFrame }); + controller.enqueue({ videoFrame }, { transfer : [ videoFrame ] }); assert_equals(videoFrame.format, null); assert_equals(videoFrame.test, 1); }, - type: 'transfer' + type: 'owning' }; const stream = new ReadableStream(source); @@ -59,7 +59,7 @@ promise_test(async () => { assert_equals(chunk.value.videoFrame.test, undefined); chunk.value.videoFrame.close(); -}, 'ReadableStream of type transfer should transfer JS chunks with transferred values'); +}, 'ReadableStream of type owning should transfer JS chunks with transferred values'); promise_test(async () => { const videoFrame = createVideoFrame(); @@ -67,13 +67,13 @@ promise_test(async () => { const source = { start(controller) { try { - controller.enqueue(videoFrame, { transfer : videoFrame }); + controller.enqueue(videoFrame, { transfer : [ videoFrame ] }); assert_unreached('enqueue should throw'); } catch (e) { // } }, - type: 'transfer' + type: 'owning' }; const stream = new ReadableStream(source); @@ -83,15 +83,15 @@ promise_test(async () => { assert_unreached('enqueue should error the stream'); }, () => { }); -}, 'ReadableStream of type transfer should error when trying to enqueue not serializable values'); +}, 'ReadableStream of type owning should error when trying to enqueue not serializable values'); promise_test(async () => { const videoFrame = createVideoFrame(); const source = { start(controller) { - controller.enqueue(videoFrame, { transfer : videoFrame }); + controller.enqueue(videoFrame, { transfer : [ videoFrame ] }); }, - type: 'transfer' + type: 'owning' }; const stream = new ReadableStream(source); @@ -106,7 +106,7 @@ promise_test(async () => { chunk1.value.close(); chunk2.value.close(); -}, 'ReadableStream of type transfer should clone serializable objects when teeing'); +}, 'ReadableStream of type owning should clone serializable objects when teeing'); promise_test(async () => { const videoFrame = createVideoFrame(); @@ -114,11 +114,11 @@ promise_test(async () => { const source = { start(controller) { assert_equals(videoFrame.format, 'I420'); - controller.enqueue({ videoFrame }, { transfer : videoFrame }); + controller.enqueue({ videoFrame }, { transfer : [ videoFrame ] }); assert_equals(videoFrame.format, null); assert_equals(videoFrame.test, 1); }, - type: 'transfer' + type: 'owning' }; const stream = new ReadableStream(source); @@ -133,4 +133,4 @@ promise_test(async () => { chunk1.value.videoFrame.close(); chunk2.value.videoFrame.close(); -}, 'ReadableStream of type transfer should clone JS Objects with serializables when teeing'); +}, 'ReadableStream of type owning should clone JS Objects with serializables when teeing'); diff --git a/streams/readable-streams/owning-type.any.js b/streams/readable-streams/owning-type.any.js new file mode 100644 index 00000000000000..02af0a78bfaf36 --- /dev/null +++ b/streams/readable-streams/owning-type.any.js @@ -0,0 +1,48 @@ +// META: global=window,worker +// META: script=../resources/test-utils.js +// META: script=../resources/rs-utils.js +'use strict'; + +test(() => { + new ReadableStream({ type: 'owning' }); // ReadableStream constructed with 'owning' type +}, 'ReadableStream can be constructed with owning type'); + +test(() => { + let startCalled = false; + + const source = { + start(controller) { + assert_equals(this, source, 'source is this during start'); + assert_true(controller instanceof ReadableStreamDefaultController, 'default controller'); + startCalled = true; + }, + type: 'owning' + }; + + new ReadableStream(source); + assert_true(startCalled); +}, 'ReadableStream of type owning should call start with a ReadableStreamDefaultController'); + +promise_test(async () => { + const uint8Array = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const buffer = uint8Array.buffer; + buffer.test = 1; + const source = { + start(controller) { + assert_equals(buffer.byteLength, 8); + controller.enqueue(buffer, { transfer : [ buffer ] }); + assert_equals(buffer.byteLength, 0); + assert_equals(buffer.test, 1); + }, + type: 'owning' + }; + + const stream = new ReadableStream(source); + const reader = stream.getReader(); + + const chunk = await reader.read(); + + assert_not_equals(chunk.value, buffer); + assert_equals(chunk.value.byteLength, 8); + assert_equals(chunk.value.test, undefined); +}, 'ReadableStream of type owning should transfer enqueued chunks'); diff --git a/streams/readable-streams/transfer-type.any.js b/streams/readable-streams/transfer-type.any.js deleted file mode 100644 index 21941508e42f3f..00000000000000 --- a/streams/readable-streams/transfer-type.any.js +++ /dev/null @@ -1,220 +0,0 @@ -// META: global=window,worker -// META: script=../resources/test-utils.js -// META: script=../resources/rs-utils.js -'use strict'; - -function createArrayBuffer() -{ - return new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); -} - -function createVideoFrame() -{ - let init = { - format: 'I420', - timestamp: 1234, - codedWidth: 4, - codedHeight: 2 - }; - let data = new Uint8Array([ - 1, 2, 3, 4, 5, 6, 7, 8, // y - 1, 2, // u - 1, 2, // v - ]); - - if (!self.VideoFrame) { - self.VideoFrame = class { - constructor(data, init) { - this.format = init.format; - this.isDetached = false; - } - close() { - this.format = null; - this.isDetached = true; - } - }; - } - - return new VideoFrame(data, init); -} - -test(() => { - new ReadableStream({ type: 'transfer' }); // ReadableStream constructed with 'transfer' type -}, 'ReadableStream can be constructed with transfer type'); - -test(() => { - let startCalled = false; - - const source = { - start(controller) { - assert_equals(this, source, 'source is this during start'); - assert_true(controller instanceof ReadableStreamDefaultController, 'default controller'); - startCalled = true; - }, - type: 'transfer' - }; - - new ReadableStream(source); - assert_true(startCalled); -}, 'ReadableStream of type transfer should call start with a ReadableStreamDefaultController'); - -promise_test(async () => { - const videoFrame = createVideoFrame(); - videoFrame.test = 1; - const source = { - start(controller) { - assert_equals(videoFrame.format, 'I420'); - controller.enqueue(videoFrame, [videoFrame]); - assert_equals(videoFrame.format, null); - assert_equals(videoFrame.test, 1); - }, - type: 'transfer' - }; - - const stream = new ReadableStream(source); - // Cancelling the stream should close all video frames, thus no console messages of GCing VideoFrames should happen. - stream.cancel(); -}, 'ReadableStream of type transfer should close serialized chunks'); - -promise_test(async () => { - const buffer = createArrayBuffer(); - buffer.test = 1; - const source = { - start(controller) { - assert_equals(buffer.length, 8); - controller.enqueue(buffer, [buffer]); - assert_equals(buffer.length, 0); - assert_equals(buffer.test, 1); - }, - type: 'transfer' - }; - - const stream = new ReadableStream(source); - const reader = stream.getReader(); - - const chunk = await reader.read(); - assert_equals(chunk.value.length, 8); - assert_equals(chunk.value.test, undefined); -}, 'ReadableStream of type transfer should transfer enqueued chunks'); - -promise_test(async () => { - const videoFrame = createVideoFrame(); - videoFrame.test = 1; - const source = { - start(controller) { - assert_equals(videoFrame.format, 'I420'); - controller.enqueue({ videoFrame }, [videoFrame]); - assert_equals(videoFrame.format, null); - assert_equals(videoFrame.test, 1); - }, - type: 'transfer' - }; - - const stream = new ReadableStream(source); - const reader = stream.getReader(); - - const chunk = await reader.read(); - assert_equals(chunk.value.videoFrame.format, 'I420'); - assert_equals(chunk.value.videoFrame.test, undefined); - - chunk.value.videoFrame.close(); -}, 'ReadableStream of type transfer should transfer JS chunks with transferred values'); - -promise_test(async () => { - const videoFrame = createVideoFrame(); - videoFrame.close(); - const source = { - start(controller) { - try { - controller.enqueue(videoFrame, [videoFrame]); - assert_unreached('enqueue should throw'); - } catch (e) { - // - } - }, - type: 'transfer' - }; - - const stream = new ReadableStream(source); - const reader = stream.getReader(); - - return reader.read().then(() => { - assert_unreached('enqueue should error the stream'); - }, () => { - }); -}, 'ReadableStream of type transfer should error when trying to enqueue not serializable values'); - -promise_test(async () => { - const videoFrame = createVideoFrame(); - const source = { - start(controller) { - controller.enqueue(videoFrame, [videoFrame]); - }, - type: 'transfer' - }; - - const stream = new ReadableStream(source); - const [clone1, clone2] = stream.tee(); - - const chunk1 = await clone1.getReader().read(); - const chunk2 = await clone2.getReader().read(); - - assert_equals(videoFrame.format, null); - assert_equals(chunk1.value.format, 'I420'); - assert_equals(chunk2.value.format, 'I420'); - - chunk1.value.close(); - chunk2.value.close(); -}, 'ReadableStream of type transfer should clone serializable objects when teeing'); - -promise_test(async () => { - const videoFrame = createVideoFrame(); - videoFrame.test = 1; - const source = { - start(controller) { - assert_equals(videoFrame.format, 'I420'); - controller.enqueue({ videoFrame }, [videoFrame]); - assert_equals(videoFrame.format, null); - assert_equals(videoFrame.test, 1); - }, - type: 'transfer' - }; - - const stream = new ReadableStream(source); - const [clone1, clone2] = stream.tee(); - - const chunk1 = await clone1.getReader().read(); - const chunk2 = await clone2.getReader().read(); - - assert_equals(videoFrame.format, null); - assert_equals(chunk1.value.videoFrame.format, 'I420'); - assert_equals(chunk2.value.videoFrame.format, 'I420'); - - chunk1.value.videoFrame.close(); - chunk2.value.videoFrame.close(); -}, 'ReadableStream of type transfer should clone JS Objects with serializables when teeing'); - -promise_test(async () => { - const channel = new MessageChannel; - let port1 = channel.port1; - const port2 = channel.port2; - - const source = { - start(controller) { - controller.enqueue({port1}, [port1]); - }, - type: 'transfer' - }; - - const stream = new ReadableStream(source); - const [clone1, clone2] = stream.tee(); - - await clone1.getReader().read().then((value) => { - assert_unreached('clone2 should error'); - }, () => { }); - - - await clone2.getReader().read().then((value) => { - assert_unreached('clone2 should error'); - }, () => { }); -}, 'Second branch of transfer-only value ReadableStream tee should end up into errors'); From 35ad0cf07278adce002370d29d94aeb1da83e537 Mon Sep 17 00:00:00 2001 From: Youenn Fablet Date: Thu, 13 Apr 2023 22:37:38 +0200 Subject: [PATCH 4/8] renaming message-channel file to message-port --- ...ype-message-channel.any.js => owning-type-message-port.any.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename streams/readable-streams/{owning-type-message-channel.any.js => owning-type-message-port.any.js} (100%) diff --git a/streams/readable-streams/owning-type-message-channel.any.js b/streams/readable-streams/owning-type-message-port.any.js similarity index 100% rename from streams/readable-streams/owning-type-message-channel.any.js rename to streams/readable-streams/owning-type-message-port.any.js From c844711f65688541d135cba1f5015a899128fcf8 Mon Sep 17 00:00:00 2001 From: Youenn Fablet Date: Thu, 13 Apr 2023 22:59:43 +0200 Subject: [PATCH 5/8] typo and removing clone1 branch test in case we allow it in the future --- streams/readable-streams/owning-type-message-port.any.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/streams/readable-streams/owning-type-message-port.any.js b/streams/readable-streams/owning-type-message-port.any.js index db3f6bdaa1117d..6b8d7dd1faf3bb 100644 --- a/streams/readable-streams/owning-type-message-port.any.js +++ b/streams/readable-streams/owning-type-message-port.any.js @@ -5,7 +5,7 @@ promise_test(async () => { const channel = new MessageChannel; - let port1 = channel.port1; + const port1 = channel.port1; const port2 = channel.port2; const source = { @@ -32,7 +32,7 @@ promise_test(async () => { promise_test(async () => { const channel = new MessageChannel; - let port1 = channel.port1; + const port1 = channel.port1; const port2 = channel.port2; const source = { @@ -45,10 +45,6 @@ promise_test(async () => { const stream = new ReadableStream(source); const [clone1, clone2] = stream.tee(); - await clone1.getReader().read().then((value) => { - assert_unreached('clone1 should error'); - }, () => { }); - await clone2.getReader().read().then((value) => { assert_unreached('clone2 should error'); }, () => { }); From 963bc0e4c006d0daaa38217699ebae4e341c003f Mon Sep 17 00:00:00 2001 From: Youenn Fablet Date: Sat, 22 Apr 2023 10:39:57 +0200 Subject: [PATCH 6/8] Add transfer checks --- streams/readable-streams/owning-type.any.js | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/streams/readable-streams/owning-type.any.js b/streams/readable-streams/owning-type.any.js index 02af0a78bfaf36..27a3dda894e4d6 100644 --- a/streams/readable-streams/owning-type.any.js +++ b/streams/readable-streams/owning-type.any.js @@ -23,6 +23,49 @@ test(() => { assert_true(startCalled); }, 'ReadableStream of type owning should call start with a ReadableStreamDefaultController'); +test(() => { + let startCalled = false; + + const source = { + start(controller) { + controller.enqueue("a", { transfer: [] }); + controller.enqueue("a", { transfer: undefined }); + startCalled = true; + }, + type: 'owning' + }; + + new ReadableStream(source); + assert_true(startCalled); +}, 'ReadableStream should be able to call enqueue with an empty transfer list'); + +test(() => { + let startCalled = false; + + const uint8Array = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); + const buffer = uint8Array.buffer; + let source = { + start(controller) { + startCalled = true; + assert_throws_js(TypeError, () => { controller.enqueue(buffer, { transfer : [ buffer ] }); }, "transfer list is not empty"); + } + }; + + new ReadableStream(source); + assert_true(startCalled); + + startCalled = false; + source = { + start(controller) { + startCalled = true; + assert_throws_js(TypeError, () => { controller.enqueue(buffer, { get transfer() { throw new TypeError(); } }) }, "getter throws"); + } + }; + + new ReadableStream(source); + assert_true(startCalled); +}, 'ReadableStream should check transfer parameter'); + promise_test(async () => { const uint8Array = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]); const buffer = uint8Array.buffer; From 31e7e49f126a37e91460be9dc7eaf301e2df9965 Mon Sep 17 00:00:00 2001 From: youennf Date: Wed, 10 May 2023 09:00:57 +0200 Subject: [PATCH 7/8] Update interfaces/streams.idl Co-authored-by: Mattias Buelens <649348+MattiasBuelens@users.noreply.github.com> --- interfaces/streams.idl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interfaces/streams.idl b/interfaces/streams.idl index 58564a1429a377..d7a0f12e9d955f 100644 --- a/interfaces/streams.idl +++ b/interfaces/streams.idl @@ -56,7 +56,7 @@ callback UnderlyingSourceStartCallback = any (ReadableStreamController controlle callback UnderlyingSourcePullCallback = Promise (ReadableStreamController controller); callback UnderlyingSourceCancelCallback = Promise (optional any reason); -enum ReadableStreamType { "bytes", "transfer" }; +enum ReadableStreamType { "bytes", "owning" }; interface mixin ReadableStreamGenericReader { readonly attribute Promise closed; From 388ab06fbd4330608475925f2834840dcb582466 Mon Sep 17 00:00:00 2001 From: Youenn Fablet Date: Tue, 16 May 2023 15:06:44 +0200 Subject: [PATCH 8/8] Make more use of promise_rejects_dom/assert_throws_dom --- .../owning-type-message-port.any.js | 6 ++---- .../owning-type-video-frame.any.js | 14 +++----------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/streams/readable-streams/owning-type-message-port.any.js b/streams/readable-streams/owning-type-message-port.any.js index 6b8d7dd1faf3bb..e9961ce042256a 100644 --- a/streams/readable-streams/owning-type-message-port.any.js +++ b/streams/readable-streams/owning-type-message-port.any.js @@ -30,7 +30,7 @@ promise_test(async () => { assert_equals(await promise, "toPort1"); }, 'Transferred MessageChannel works as expected'); -promise_test(async () => { +promise_test(async t => { const channel = new MessageChannel; const port1 = channel.port1; const port2 = channel.port2; @@ -45,7 +45,5 @@ promise_test(async () => { const stream = new ReadableStream(source); const [clone1, clone2] = stream.tee(); - await clone2.getReader().read().then((value) => { - assert_unreached('clone2 should error'); - }, () => { }); + await promise_rejects_dom(t, "DataCloneError", clone2.getReader().read()); }, 'Second branch of owning ReadableStream tee should end up into errors with transfer only values'); diff --git a/streams/readable-streams/owning-type-video-frame.any.js b/streams/readable-streams/owning-type-video-frame.any.js index 17842358a39f05..ec01fda0b3c737 100644 --- a/streams/readable-streams/owning-type-video-frame.any.js +++ b/streams/readable-streams/owning-type-video-frame.any.js @@ -61,17 +61,12 @@ promise_test(async () => { chunk.value.videoFrame.close(); }, 'ReadableStream of type owning should transfer JS chunks with transferred values'); -promise_test(async () => { +promise_test(async t => { const videoFrame = createVideoFrame(); videoFrame.close(); const source = { start(controller) { - try { - controller.enqueue(videoFrame, { transfer : [ videoFrame ] }); - assert_unreached('enqueue should throw'); - } catch (e) { - // - } + assert_throws_dom("DataCloneError", () => controller.enqueue(videoFrame, { transfer : [ videoFrame ] })); }, type: 'owning' }; @@ -79,10 +74,7 @@ promise_test(async () => { const stream = new ReadableStream(source); const reader = stream.getReader(); - return reader.read().then(() => { - assert_unreached('enqueue should error the stream'); - }, () => { - }); + await promise_rejects_dom(t, "DataCloneError", reader.read()); }, 'ReadableStream of type owning should error when trying to enqueue not serializable values'); promise_test(async () => {