summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/streams/readable-byte-streams
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/streams/readable-byte-streams
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/streams/readable-byte-streams')
-rw-r--r--testing/web-platform/tests/streams/readable-byte-streams/bad-buffers-and-views.any.js398
-rw-r--r--testing/web-platform/tests/streams/readable-byte-streams/construct-byob-request.any.js53
-rw-r--r--testing/web-platform/tests/streams/readable-byte-streams/enqueue-with-detached-buffer.any.js21
-rw-r--r--testing/web-platform/tests/streams/readable-byte-streams/general.any.js2901
-rw-r--r--testing/web-platform/tests/streams/readable-byte-streams/non-transferable-buffers.any.js70
-rw-r--r--testing/web-platform/tests/streams/readable-byte-streams/read-min.any.js774
-rw-r--r--testing/web-platform/tests/streams/readable-byte-streams/respond-after-enqueue.any.js55
-rw-r--r--testing/web-platform/tests/streams/readable-byte-streams/tee.any.js936
8 files changed, 5208 insertions, 0 deletions
diff --git a/testing/web-platform/tests/streams/readable-byte-streams/bad-buffers-and-views.any.js b/testing/web-platform/tests/streams/readable-byte-streams/bad-buffers-and-views.any.js
new file mode 100644
index 0000000000..afcc61e680
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/bad-buffers-and-views.any.js
@@ -0,0 +1,398 @@
+// META: global=window,worker,shadowrealm
+'use strict';
+
+promise_test(() => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.close();
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const view = new Uint8Array([1, 2, 3]);
+ return reader.read(view).then(({ value, done }) => {
+ // Sanity checks
+ assert_true(value instanceof Uint8Array, 'The value read must be a Uint8Array');
+ assert_not_equals(value, view, 'The value read must not be the *same* Uint8Array');
+ assert_array_equals(value, [], 'The value read must be an empty Uint8Array, since the stream is closed');
+ assert_true(done, 'done must be true, since the stream is closed');
+
+ // The important assertions
+ assert_not_equals(value.buffer, view.buffer, 'a different ArrayBuffer must underlie the value');
+ assert_equals(view.buffer.byteLength, 0, 'the original buffer must be detached');
+ });
+}, 'ReadableStream with byte source: read()ing from a closed stream still transfers the buffer');
+
+promise_test(() => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array([1, 2, 3]));
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const view = new Uint8Array([4, 5, 6]);
+ return reader.read(view).then(({ value, done }) => {
+ // Sanity checks
+ assert_true(value instanceof Uint8Array, 'The value read must be a Uint8Array');
+ assert_not_equals(value, view, 'The value read must not be the *same* Uint8Array');
+ assert_array_equals(value, [1, 2, 3], 'The value read must be the enqueued Uint8Array, not the original values');
+ assert_false(done, 'done must be false, since the stream is not closed');
+
+ // The important assertions
+ assert_not_equals(value.buffer, view.buffer, 'a different ArrayBuffer must underlie the value');
+ assert_equals(view.buffer.byteLength, 0, 'the original buffer must be detached');
+ });
+}, 'ReadableStream with byte source: read()ing from a stream with queued chunks still transfers the buffer');
+
+test(() => {
+ new ReadableStream({
+ start(c) {
+ const view = new Uint8Array([1, 2, 3]);
+ c.enqueue(view);
+ assert_throws_js(TypeError, () => c.enqueue(view));
+ },
+ type: 'bytes'
+ });
+}, 'ReadableStream with byte source: enqueuing an already-detached buffer throws');
+
+test(() => {
+ new ReadableStream({
+ start(c) {
+ const view = new Uint8Array([]);
+ assert_throws_js(TypeError, () => c.enqueue(view));
+ },
+ type: 'bytes'
+ });
+}, 'ReadableStream with byte source: enqueuing a zero-length buffer throws');
+
+test(() => {
+ new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(new ArrayBuffer(10), 0, 0);
+ assert_throws_js(TypeError, () => c.enqueue(view));
+ },
+ type: 'bytes'
+ });
+}, 'ReadableStream with byte source: enqueuing a zero-length view on a non-zero-length buffer throws');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array([1, 2, 3]));
+ },
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const view = new Uint8Array([4, 5, 6]);
+ return reader.read(view).then(() => {
+ // view is now detached
+ return promise_rejects_js(t, TypeError, reader.read(view));
+ });
+}, 'ReadableStream with byte source: reading into an already-detached buffer rejects');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array([1, 2, 3]));
+ },
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const view = new Uint8Array();
+ return promise_rejects_js(t, TypeError, reader.read(view));
+}, 'ReadableStream with byte source: reading into a zero-length buffer rejects');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array([1, 2, 3]));
+ },
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const view = new Uint8Array(new ArrayBuffer(10), 0, 0);
+ return promise_rejects_js(t, TypeError, reader.read(view));
+}, 'ReadableStream with byte source: reading into a zero-length view on a non-zero-length buffer rejects');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ // Detach it by reading into it
+ reader.read(c.byobRequest.view);
+
+ assert_throws_js(TypeError, () => c.byobRequest.respond(1),
+ 'respond() must throw if the corresponding view has become detached');
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respond() throws if the BYOB request\'s buffer has been detached (in the ' +
+ 'readable state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ c.close();
+
+ // Detach it by reading into it
+ reader.read(c.byobRequest.view);
+
+ assert_throws_js(TypeError, () => c.byobRequest.respond(0),
+ 'respond() must throw if the corresponding view has become detached');
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respond() throws if the BYOB request\'s buffer has been detached (in the ' +
+ 'closed state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ // Detach it by reading into it
+ const view = new Uint8Array([1, 2, 3]);
+ reader.read(view);
+
+ assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has been detached ' +
+ '(in the readable state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ const view = new Uint8Array();
+
+ assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer is zero-length ' +
+ '(in the readable state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ const view = new Uint8Array(c.byobRequest.view.buffer, 0, 0);
+
+ assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view is zero-length on a ' +
+ 'non-zero-length buffer (in the readable state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ const view = c.byobRequest.view.subarray(1, 2);
+
+ assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view has a different offset ' +
+ '(in the readable state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ c.close();
+
+ const view = c.byobRequest.view.subarray(1, 1);
+
+ assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view has a different offset ' +
+ '(in the closed state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ const view = new Uint8Array(new ArrayBuffer(10), 0, 3);
+
+ assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has a ' +
+ 'different length (in the readable state)');
+
+async_test(t => {
+ // Tests https://github.com/nodejs/node/issues/41886
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ const view = new Uint8Array(new ArrayBuffer(11), 0, 3);
+
+ assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes',
+ autoAllocateChunkSize: 10
+ });
+ const reader = stream.getReader();
+
+ reader.read();
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has a ' +
+ 'different length (autoAllocateChunkSize)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ const view = new Uint8Array(c.byobRequest.view.buffer, 0, 4);
+ view[0] = 20;
+ view[1] = 21;
+ view[2] = 22;
+ view[3] = 23;
+
+ assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const buffer = new ArrayBuffer(10);
+ const view = new Uint8Array(buffer, 0, 3);
+ view[0] = 10;
+ view[1] = 11;
+ view[2] = 12;
+ reader.read(view);
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view has a larger length ' +
+ '(in the readable state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ c.close();
+
+ // Detach it by reading into it
+ const view = new Uint8Array([1, 2, 3]);
+ reader.read(view);
+
+ assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has been detached ' +
+ '(in the closed state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ const view = new Uint8Array();
+
+ c.close();
+
+ assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer is zero-length ' +
+ '(in the closed state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ const view = new Uint8Array(c.byobRequest.view.buffer, 0, 1);
+
+ c.close();
+
+ assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view is non-zero-length ' +
+ '(in the closed state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ const view = new Uint8Array(new ArrayBuffer(10), 0, 0);
+
+ c.close();
+
+ assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has a ' +
+ 'different length (in the closed state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ // Detach it by reading into it
+ reader.read(c.byobRequest.view);
+
+ assert_throws_js(TypeError, () => c.enqueue(new Uint8Array([1])),
+ 'enqueue() must throw if the BYOB request\'s buffer has become detached');
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: enqueue() throws if the BYOB request\'s buffer has been detached (in the ' +
+ 'readable state)');
+
+async_test(t => {
+ const stream = new ReadableStream({
+ pull: t.step_func_done(c => {
+ c.close();
+
+ // Detach it by reading into it
+ reader.read(c.byobRequest.view);
+
+ assert_throws_js(TypeError, () => c.enqueue(new Uint8Array([1])),
+ 'enqueue() must throw if the BYOB request\'s buffer has become detached');
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ reader.read(new Uint8Array([4, 5, 6]));
+}, 'ReadableStream with byte source: enqueue() throws if the BYOB request\'s buffer has been detached (in the ' +
+ 'closed state)');
diff --git a/testing/web-platform/tests/streams/readable-byte-streams/construct-byob-request.any.js b/testing/web-platform/tests/streams/readable-byte-streams/construct-byob-request.any.js
new file mode 100644
index 0000000000..a26f949ee2
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/construct-byob-request.any.js
@@ -0,0 +1,53 @@
+// META: global=window,worker,shadowrealm
+// META: script=../resources/rs-utils.js
+'use strict';
+
+// Prior to whatwg/stream#870 it was possible to construct a ReadableStreamBYOBRequest directly. This made it possible
+// to construct requests that were out-of-sync with the state of the ReadableStream. They could then be used to call
+// internal operations, resulting in asserts or bad behaviour. This file contains regression tests for the change.
+
+function getRealByteStreamController() {
+ let controller;
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+ return controller;
+}
+
+// Create an object pretending to have prototype |prototype|, of type |type|. |type| is one of "undefined", "null",
+// "fake", or "real". "real" will call the realObjectCreator function to get a real instance of the object.
+function createDummyObject(prototype, type, realObjectCreator) {
+ switch (type) {
+ case 'undefined':
+ return undefined;
+
+ case 'null':
+ return null;
+
+ case 'fake':
+ return Object.create(prototype);
+
+ case 'real':
+ return realObjectCreator();
+ }
+
+ throw new Error('not reached');
+}
+
+const dummyTypes = ['undefined', 'null', 'fake', 'real'];
+
+for (const controllerType of dummyTypes) {
+ const controller = createDummyObject(ReadableByteStreamController.prototype, controllerType,
+ getRealByteStreamController);
+ for (const viewType of dummyTypes) {
+ const view = createDummyObject(Uint8Array.prototype, viewType, () => new Uint8Array(16));
+ test(() => {
+ assert_throws_js(TypeError, () => new ReadableStreamBYOBRequest(controller, view),
+ 'constructor should throw');
+ }, `ReadableStreamBYOBRequest constructor should throw when passed a ${controllerType} ` +
+ `ReadableByteStreamController and a ${viewType} view`);
+ }
+}
diff --git a/testing/web-platform/tests/streams/readable-byte-streams/enqueue-with-detached-buffer.any.js b/testing/web-platform/tests/streams/readable-byte-streams/enqueue-with-detached-buffer.any.js
new file mode 100644
index 0000000000..92bd0a26a0
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/enqueue-with-detached-buffer.any.js
@@ -0,0 +1,21 @@
+// META: global=window,worker,shadowrealm
+
+promise_test(async t => {
+ const error = new Error('cannot proceed');
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.step_func((controller) => {
+ const buffer = controller.byobRequest.view.buffer;
+ // Detach the buffer.
+ structuredClone(buffer, { transfer: [buffer] });
+
+ // Try to enqueue with a new buffer.
+ assert_throws_js(TypeError, () => controller.enqueue(new Uint8Array([42])));
+
+ // If we got here the test passed.
+ controller.error(error);
+ })
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ await promise_rejects_exactly(t, error, reader.read(new Uint8Array(1)));
+}, 'enqueue after detaching byobRequest.view.buffer should throw');
diff --git a/testing/web-platform/tests/streams/readable-byte-streams/general.any.js b/testing/web-platform/tests/streams/readable-byte-streams/general.any.js
new file mode 100644
index 0000000000..cdce2244c3
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/general.any.js
@@ -0,0 +1,2901 @@
+// META: global=window,worker,shadowrealm
+// META: script=../resources/rs-utils.js
+// META: script=../resources/test-utils.js
+'use strict';
+
+const error1 = new Error('error1');
+error1.name = 'error1';
+
+test(() => {
+ assert_throws_js(TypeError, () => new ReadableStream().getReader({ mode: 'byob' }));
+}, 'getReader({mode: "byob"}) throws on non-bytes streams');
+
+
+test(() => {
+ // Constructing ReadableStream with an empty underlying byte source object as parameter shouldn't throw.
+ new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' });
+ // Constructor must perform ToString(type).
+ new ReadableStream({ type: { toString() {return 'bytes';} } })
+ .getReader({ mode: 'byob' });
+ new ReadableStream({ type: { toString: null, valueOf() {return 'bytes';} } })
+ .getReader({ mode: 'byob' });
+}, 'ReadableStream with byte source can be constructed with no errors');
+
+test(() => {
+ const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
+ const rs = new ReadableStream({ type: 'bytes' });
+
+ let reader = rs.getReader({ mode: { toString() { return 'byob'; } } });
+ assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
+ reader.releaseLock();
+
+ reader = rs.getReader({ mode: { toString: null, valueOf() {return 'byob';} } });
+ assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
+ reader.releaseLock();
+
+ reader = rs.getReader({ mode: 'byob', notmode: 'ignored' });
+ assert_true(reader instanceof ReadableStreamBYOBReader, 'must give a BYOB reader');
+}, 'getReader({mode}) must perform ToString()');
+
+promise_test(() => {
+ let startCalled = false;
+ let startCalledBeforePull = false;
+ let desiredSize;
+ let controller;
+
+ let resolveTestPromise;
+ const testPromise = new Promise(resolve => {
+ resolveTestPromise = resolve;
+ });
+
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ startCalled = true;
+ },
+ pull() {
+ startCalledBeforePull = startCalled;
+ desiredSize = controller.desiredSize;
+ resolveTestPromise();
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 256
+ });
+
+ return testPromise.then(() => {
+ assert_true(startCalledBeforePull, 'start should be called before pull');
+ assert_equals(desiredSize, 256, 'desiredSize should equal highWaterMark');
+ });
+
+}, 'ReadableStream with byte source: Construct and expect start and pull being called');
+
+promise_test(() => {
+ let pullCount = 0;
+ let checkedNoPull = false;
+
+ let resolveTestPromise;
+ const testPromise = new Promise(resolve => {
+ resolveTestPromise = resolve;
+ });
+ let resolveStartPromise;
+
+ new ReadableStream({
+ start() {
+ return new Promise(resolve => {
+ resolveStartPromise = resolve;
+ });
+ },
+ pull() {
+ if (checkedNoPull) {
+ resolveTestPromise();
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 256
+ });
+
+ Promise.resolve().then(() => {
+ assert_equals(pullCount, 0);
+ checkedNoPull = true;
+ resolveStartPromise();
+ });
+
+ return testPromise;
+
+}, 'ReadableStream with byte source: No automatic pull call if start doesn\'t finish');
+
+test(() => {
+ assert_throws_js(Error, () => new ReadableStream({ start() { throw new Error(); }, type:'bytes' }),
+ 'start() can throw an exception with type: bytes');
+}, 'ReadableStream with byte source: start() throws an exception');
+
+promise_test(t => {
+ new ReadableStream({
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ }, {
+ highWaterMark: 0
+ });
+
+ return Promise.resolve();
+}, 'ReadableStream with byte source: Construct with highWaterMark of 0');
+
+test(() => {
+ new ReadableStream({
+ start(c) {
+ assert_equals(c.desiredSize, 10, 'desiredSize must start at the highWaterMark');
+ c.close();
+ assert_equals(c.desiredSize, 0, 'after closing, desiredSize must be 0');
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 10
+ });
+}, 'ReadableStream with byte source: desiredSize when closed');
+
+test(() => {
+ new ReadableStream({
+ start(c) {
+ assert_equals(c.desiredSize, 10, 'desiredSize must start at the highWaterMark');
+ c.error();
+ assert_equals(c.desiredSize, null, 'after erroring, desiredSize must be null');
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 10
+ });
+}, 'ReadableStream with byte source: desiredSize when errored');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+ reader.releaseLock();
+
+ return promise_rejects_js(t, TypeError, reader.closed, 'closed must reject');
+}, 'ReadableStream with byte source: getReader(), then releaseLock()');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ reader.releaseLock();
+
+ return promise_rejects_js(t, TypeError, reader.closed, 'closed must reject');
+}, 'ReadableStream with byte source: getReader() with mode set to byob, then releaseLock()');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.close();
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return reader.closed.then(() => {
+ assert_throws_js(TypeError, () => stream.getReader(), 'getReader() must throw');
+ });
+}, 'ReadableStream with byte source: Test that closing a stream does not release a reader automatically');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.close();
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.closed.then(() => {
+ assert_throws_js(TypeError, () => stream.getReader({ mode: 'byob' }), 'getReader() must throw');
+ });
+}, 'ReadableStream with byte source: Test that closing a stream does not release a BYOB reader automatically');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.error(error1);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return promise_rejects_exactly(t, error1, reader.closed, 'closed must reject').then(() => {
+ assert_throws_js(TypeError, () => stream.getReader(), 'getReader() must throw');
+ });
+}, 'ReadableStream with byte source: Test that erroring a stream does not release a reader automatically');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.error(error1);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects_exactly(t, error1, reader.closed, 'closed must reject').then(() => {
+ assert_throws_js(TypeError, () => stream.getReader({ mode: 'byob' }), 'getReader() must throw');
+ });
+}, 'ReadableStream with byte source: Test that erroring a stream does not release a BYOB reader automatically');
+
+promise_test(async t => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+ const read = reader.read();
+ reader.releaseLock();
+ await promise_rejects_js(t, TypeError, read, 'pending read must reject');
+}, 'ReadableStream with byte source: releaseLock() on ReadableStreamDefaultReader must reject pending read()');
+
+promise_test(async t => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const read = reader.read(new Uint8Array(1));
+ reader.releaseLock();
+ await promise_rejects_js(t, TypeError, read, 'pending read must reject');
+}, 'ReadableStream with byte source: releaseLock() on ReadableStreamBYOBReader must reject pending read()');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ const stream = new ReadableStream({
+ pull() {
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 8
+ });
+
+ stream.getReader();
+
+ assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 1, 'pull must be invoked');
+ });
+}, 'ReadableStream with byte source: Automatic pull() after start()');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ const stream = new ReadableStream({
+ pull() {
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 0
+ });
+
+ const reader = stream.getReader();
+ reader.read();
+
+ assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 1, 'pull must be invoked');
+ });
+}, 'ReadableStream with byte source: Automatic pull() after start() and read()');
+
+// View buffers are detached after pull() returns, so record the information at the time that pull() was called.
+function extractViewInfo(view) {
+ return {
+ constructor: view.constructor,
+ bufferByteLength: view.buffer.byteLength,
+ byteOffset: view.byteOffset,
+ byteLength: view.byteLength
+ };
+}
+
+promise_test(() => {
+ let pullCount = 0;
+ let controller;
+ const byobRequests = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ const byobRequest = controller.byobRequest;
+ const view = byobRequest.view;
+ byobRequests[pullCount] = {
+ nonNull: byobRequest !== null,
+ viewNonNull: view !== null,
+ viewInfo: extractViewInfo(view)
+ };
+ if (pullCount === 0) {
+ view[0] = 0x01;
+ byobRequest.respond(1);
+ } else if (pullCount === 1) {
+ view[0] = 0x02;
+ view[1] = 0x03;
+ byobRequest.respond(2);
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes',
+ autoAllocateChunkSize: 16
+ }, {
+ highWaterMark: 0
+ });
+
+ const reader = stream.getReader();
+ const p0 = reader.read();
+ const p1 = reader.read();
+
+ assert_equals(pullCount, 0, 'No pull() as start() just finished and is not yet reflected to the state of the stream');
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 1, 'pull() must have been invoked once');
+ const byobRequest = byobRequests[0];
+ assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16');
+ assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 16, 'first view.byteLength should be 16');
+
+ return p0;
+ }).then(result => {
+ assert_equals(pullCount, 2, 'pull() must have been invoked twice');
+ const value = result.value;
+ assert_not_equals(value, undefined, 'first read should have a value');
+ assert_equals(value.constructor, Uint8Array, 'first value should be a Uint8Array');
+ assert_equals(value.buffer.byteLength, 16, 'first value.buffer.byteLength should be 16');
+ assert_equals(value.byteOffset, 0, 'first value.byteOffset should be 0');
+ assert_equals(value.byteLength, 1, 'first value.byteLength should be 1');
+ assert_equals(value[0], 0x01, 'first value[0] should be 0x01');
+ const byobRequest = byobRequests[1];
+ assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 16, 'second view.buffer.byteLength should be 16');
+ assert_equals(viewInfo.byteOffset, 0, 'second view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 16, 'second view.byteLength should be 16');
+
+ return p1;
+ }).then(result => {
+ assert_equals(pullCount, 2, 'pull() should only be invoked twice');
+ const value = result.value;
+ assert_not_equals(value, undefined, 'second read should have a value');
+ assert_equals(value.constructor, Uint8Array, 'second value should be a Uint8Array');
+ assert_equals(value.buffer.byteLength, 16, 'second value.buffer.byteLength should be 16');
+ assert_equals(value.byteOffset, 0, 'second value.byteOffset should be 0');
+ assert_equals(value.byteLength, 2, 'second value.byteLength should be 2');
+ assert_equals(value[0], 0x02, 'second value[0] should be 0x02');
+ assert_equals(value[1], 0x03, 'second value[1] should be 0x03');
+ });
+}, 'ReadableStream with byte source: autoAllocateChunkSize');
+
+promise_test(() => {
+ let pullCount = 0;
+ let controller;
+ const byobRequests = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ const byobRequest = controller.byobRequest;
+ const view = byobRequest.view;
+ byobRequests[pullCount] = {
+ nonNull: byobRequest !== null,
+ viewNonNull: view !== null,
+ viewInfo: extractViewInfo(view)
+ };
+ if (pullCount === 0) {
+ view[0] = 0x01;
+ byobRequest.respond(1);
+ } else if (pullCount === 1) {
+ view[0] = 0x02;
+ view[1] = 0x03;
+ byobRequest.respond(2);
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes',
+ autoAllocateChunkSize: 16
+ }, {
+ highWaterMark: 0
+ });
+
+ const reader = stream.getReader();
+ return reader.read().then(result => {
+ const value = result.value;
+ assert_not_equals(value, undefined, 'first read should have a value');
+ assert_equals(value.constructor, Uint8Array, 'first value should be a Uint8Array');
+ assert_equals(value.buffer.byteLength, 16, 'first value.buffer.byteLength should be 16');
+ assert_equals(value.byteOffset, 0, 'first value.byteOffset should be 0');
+ assert_equals(value.byteLength, 1, 'first value.byteLength should be 1');
+ assert_equals(value[0], 0x01, 'first value[0] should be 0x01');
+ const byobRequest = byobRequests[0];
+ assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 16, 'first view.buffer.byteLength should be 16');
+ assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 16, 'first view.byteLength should be 16');
+
+ reader.releaseLock();
+ const byobReader = stream.getReader({ mode: 'byob' });
+ return byobReader.read(new Uint8Array(32));
+ }).then(result => {
+ const value = result.value;
+ assert_not_equals(value, undefined, 'second read should have a value');
+ assert_equals(value.constructor, Uint8Array, 'second value should be a Uint8Array');
+ assert_equals(value.buffer.byteLength, 32, 'second value.buffer.byteLength should be 32');
+ assert_equals(value.byteOffset, 0, 'second value.byteOffset should be 0');
+ assert_equals(value.byteLength, 2, 'second value.byteLength should be 2');
+ assert_equals(value[0], 0x02, 'second value[0] should be 0x02');
+ assert_equals(value[1], 0x03, 'second value[1] should be 0x03');
+ const byobRequest = byobRequests[1];
+ assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 32, 'second view.buffer.byteLength should be 32');
+ assert_equals(viewInfo.byteOffset, 0, 'second view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 32, 'second view.byteLength should be 32');
+ assert_equals(pullCount, 2, 'pullCount should be 2');
+ });
+}, 'ReadableStream with byte source: Mix of auto allocate and BYOB');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ const stream = new ReadableStream({
+ pull() {
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 0
+ });
+
+ const reader = stream.getReader();
+ reader.read(new Uint8Array(8));
+
+ assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 1, 'pull must be invoked');
+ });
+}, 'ReadableStream with byte source: Automatic pull() after start() and read(view)');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let desiredSizeInStart;
+ let desiredSizeInPull;
+
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array(16));
+ desiredSizeInStart = c.desiredSize;
+ controller = c;
+ },
+ pull() {
+ ++pullCount;
+
+ if (pullCount === 1) {
+ desiredSizeInPull = controller.desiredSize;
+ }
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 8
+ });
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 0, 'No pull as the queue was filled by start()');
+ assert_equals(desiredSizeInStart, -8, 'desiredSize after enqueue() in start()');
+
+ const reader = stream.getReader();
+
+ const promise = reader.read();
+ assert_equals(pullCount, 1, 'The first pull() should be made on read()');
+ assert_equals(desiredSizeInPull, 8, 'desiredSize in pull()');
+
+ return promise.then(result => {
+ assert_false(result.done, 'result.done');
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array, 'view.constructor');
+ assert_equals(view.buffer.byteLength, 16, 'view.buffer');
+ assert_equals(view.byteOffset, 0, 'view.byteOffset');
+ assert_equals(view.byteLength, 16, 'view.byteLength');
+ });
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then read()');
+
+promise_test(() => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ const promise = reader.read().then(result => {
+ assert_false(result.done);
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array);
+ assert_equals(view.buffer.byteLength, 1);
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 1);
+ });
+
+ controller.enqueue(new Uint8Array(1));
+
+ return promise;
+}, 'ReadableStream with byte source: Push source that doesn\'t understand pull signal');
+
+test(() => {
+ assert_throws_js(TypeError, () => new ReadableStream({
+ pull: 'foo',
+ type: 'bytes'
+ }), 'constructor should throw');
+}, 'ReadableStream with byte source: pull() function is not callable');
+
+promise_test(() => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint16Array(16));
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return reader.read().then(result => {
+ assert_false(result.done);
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array);
+ assert_equals(view.buffer.byteLength, 32);
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 32);
+ });
+}, 'ReadableStream with byte source: enqueue() with Uint16Array, getReader(), then read()');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(16);
+ view[0] = 0x01;
+ view[8] = 0x02;
+ c.enqueue(view);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const byobReader = stream.getReader({ mode: 'byob' });
+
+ return byobReader.read(new Uint8Array(8)).then(result => {
+ assert_false(result.done, 'done');
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array, 'value.constructor');
+ assert_equals(view.buffer.byteLength, 8, 'value.buffer.byteLength');
+ assert_equals(view.byteOffset, 0, 'value.byteOffset');
+ assert_equals(view.byteLength, 8, 'value.byteLength');
+ assert_equals(view[0], 0x01);
+
+ byobReader.releaseLock();
+
+ const reader = stream.getReader();
+
+ return reader.read();
+ }).then(result => {
+ assert_false(result.done, 'done');
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array, 'value.constructor');
+ assert_equals(view.buffer.byteLength, 16, 'value.buffer.byteLength');
+ assert_equals(view.byteOffset, 8, 'value.byteOffset');
+ assert_equals(view.byteLength, 8, 'value.byteLength');
+ assert_equals(view[0], 0x02);
+ });
+}, 'ReadableStream with byte source: enqueue(), read(view) partially, then read()');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ controller.enqueue(new Uint8Array(16));
+ controller.close();
+
+ return reader.read().then(result => {
+ assert_false(result.done, 'done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 16, 'byteLength');
+
+ return reader.read();
+ }).then(result => {
+ assert_true(result.done, 'done');
+ assert_equals(result.value, undefined, 'value');
+ });
+}, 'ReadableStream with byte source: getReader(), enqueue(), close(), then read()');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array(16));
+ c.close();
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return reader.read().then(result => {
+ assert_false(result.done, 'done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 16, 'byteLength');
+
+ return reader.read();
+ }).then(result => {
+ assert_true(result.done, 'done');
+ assert_equals(result.value, undefined, 'value');
+ });
+}, 'ReadableStream with byte source: enqueue(), close(), getReader(), then read()');
+
+promise_test(() => {
+ let controller;
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ controller.enqueue(new Uint8Array(16));
+ byobRequest = controller.byobRequest;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return reader.read().then(result => {
+ assert_false(result.done, 'done');
+ assert_equals(result.value.byteLength, 16, 'byteLength');
+ assert_equals(byobRequest, null, 'byobRequest must be null');
+ });
+}, 'ReadableStream with byte source: Respond to pull() by enqueue()');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let byobRequest;
+ const desiredSizes = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+ desiredSizes.push(controller.desiredSize);
+ controller.enqueue(new Uint8Array(1));
+ desiredSizes.push(controller.desiredSize);
+ controller.enqueue(new Uint8Array(1));
+ desiredSizes.push(controller.desiredSize);
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 0
+ });
+
+ const reader = stream.getReader();
+
+ const p0 = reader.read();
+ const p1 = reader.read();
+ const p2 = reader.read();
+
+ // Respond to the first pull call.
+ controller.enqueue(new Uint8Array(1));
+
+ assert_equals(pullCount, 0, 'pullCount after the enqueue() outside pull');
+
+ return Promise.all([p0, p1, p2]).then(result => {
+ assert_equals(pullCount, 1, 'pullCount after completion of all read()s');
+
+ assert_equals(result[0].done, false, 'result[0].done');
+ assert_equals(result[0].value.byteLength, 1, 'result[0].value.byteLength');
+ assert_equals(result[1].done, false, 'result[1].done');
+ assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength');
+ assert_equals(result[2].done, false, 'result[2].done');
+ assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength');
+ assert_equals(byobRequest, null, 'byobRequest should be null');
+ assert_equals(desiredSizes[0], 0, 'desiredSize on pull should be 0');
+ assert_equals(desiredSizes[1], 0, 'desiredSize after 1st enqueue() should be 0');
+ assert_equals(desiredSizes[2], 0, 'desiredSize after 2nd enqueue() should be 0');
+ assert_equals(pullCount, 1, 'pull() should only be called once');
+ });
+}, 'ReadableStream with byte source: Respond to pull() by enqueue() asynchronously');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let byobRequest;
+ const desiredSizes = [];
+
+ const stream = new ReadableStream({
+ pull(c) {
+ byobRequest = c.byobRequest;
+ desiredSizes.push(c.desiredSize);
+
+ if (pullCount < 3) {
+ c.enqueue(new Uint8Array(1));
+ } else {
+ c.close();
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ }, {
+ highWaterMark: 256
+ });
+
+ const reader = stream.getReader();
+
+ const p0 = reader.read();
+ const p1 = reader.read();
+ const p2 = reader.read();
+
+ assert_equals(pullCount, 0, 'No pull as start() just finished and is not yet reflected to the state of the stream');
+
+ return Promise.all([p0, p1, p2]).then(result => {
+ assert_equals(pullCount, 4, 'pullCount after completion of all read()s');
+
+ assert_equals(result[0].done, false, 'result[0].done');
+ assert_equals(result[0].value.byteLength, 1, 'result[0].value.byteLength');
+ assert_equals(result[1].done, false, 'result[1].done');
+ assert_equals(result[1].value.byteLength, 1, 'result[1].value.byteLength');
+ assert_equals(result[2].done, false, 'result[2].done');
+ assert_equals(result[2].value.byteLength, 1, 'result[2].value.byteLength');
+ assert_equals(byobRequest, null, 'byobRequest should be null');
+ assert_equals(desiredSizes[0], 256, 'desiredSize on pull should be 256');
+ assert_equals(desiredSizes[1], 256, 'desiredSize after 1st enqueue() should be 256');
+ assert_equals(desiredSizes[2], 256, 'desiredSize after 2nd enqueue() should be 256');
+ assert_equals(desiredSizes[3], 256, 'desiredSize after 3rd enqueue() should be 256');
+ });
+}, 'ReadableStream with byte source: Respond to multiple pull() by separate enqueue()');
+
+promise_test(() => {
+ let controller;
+
+ let pullCount = 0;
+ const byobRequestDefined = [];
+ let byobRequestViewDefined;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequestDefined.push(controller.byobRequest !== null);
+ const initialByobRequest = controller.byobRequest;
+
+ const view = controller.byobRequest.view;
+ view[0] = 0x01;
+ controller.byobRequest.respond(1);
+
+ byobRequestDefined.push(controller.byobRequest !== null);
+ byobRequestViewDefined = initialByobRequest.view !== null;
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(1)).then(result => {
+ assert_false(result.done, 'result.done');
+ assert_equals(result.value.byteLength, 1, 'result.value.byteLength');
+ assert_equals(result.value[0], 0x01, 'result.value[0]');
+ assert_equals(pullCount, 1, 'pull() should be called only once');
+ assert_true(byobRequestDefined[0], 'byobRequest must not be null before respond()');
+ assert_false(byobRequestDefined[1], 'byobRequest must be null after respond()');
+ assert_false(byobRequestViewDefined, 'view of initial byobRequest must be null after respond()');
+ });
+}, 'ReadableStream with byte source: read(view), then respond()');
+
+promise_test(() => {
+ let controller;
+
+ let pullCount = 0;
+ const byobRequestDefined = [];
+ let byobRequestViewDefined;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ async pull() {
+ byobRequestDefined.push(controller.byobRequest !== null);
+ const initialByobRequest = controller.byobRequest;
+
+ const transferredView = await transferArrayBufferView(controller.byobRequest.view);
+ transferredView[0] = 0x01;
+ controller.byobRequest.respondWithNewView(transferredView);
+
+ byobRequestDefined.push(controller.byobRequest !== null);
+ byobRequestViewDefined = initialByobRequest.view !== null;
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(1)).then(result => {
+ assert_false(result.done, 'result.done');
+ assert_equals(result.value.byteLength, 1, 'result.value.byteLength');
+ assert_equals(result.value[0], 0x01, 'result.value[0]');
+ assert_equals(pullCount, 1, 'pull() should be called only once');
+ assert_true(byobRequestDefined[0], 'byobRequest must not be null before respondWithNewView()');
+ assert_false(byobRequestDefined[1], 'byobRequest must be null after respondWithNewView()');
+ assert_false(byobRequestViewDefined, 'view of initial byobRequest must be null after respondWithNewView()');
+ });
+}, 'ReadableStream with byte source: read(view), then respondWithNewView() with a transferred ArrayBuffer');
+
+promise_test(() => {
+ let controller;
+ let byobRequestWasDefined;
+ let incorrectRespondException;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequestWasDefined = controller.byobRequest !== null;
+
+ try {
+ controller.byobRequest.respond(2);
+ } catch (e) {
+ incorrectRespondException = e;
+ }
+
+ controller.byobRequest.respond(1);
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(1)).then(() => {
+ assert_true(byobRequestWasDefined, 'byobRequest should be non-null');
+ assert_not_equals(incorrectRespondException, undefined, 'respond() must throw');
+ assert_equals(incorrectRespondException.name, 'RangeError', 'respond() must throw a RangeError');
+ });
+}, 'ReadableStream with byte source: read(view), then respond() with too big value');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let byobRequest;
+ let viewInfo;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ ++pullCount;
+
+ byobRequest = controller.byobRequest;
+ const view = byobRequest.view;
+ viewInfo = extractViewInfo(view);
+
+ view[0] = 0x01;
+ view[1] = 0x02;
+ view[2] = 0x03;
+
+ controller.byobRequest.respond(3);
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint16Array(2)).then(result => {
+ assert_equals(pullCount, 1);
+
+ assert_false(result.done, 'done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 2, 'byteLength');
+
+ const dataView = new DataView(view.buffer, view.byteOffset, view.byteLength);
+ assert_equals(dataView.getUint16(0), 0x0102);
+
+ return reader.read(new Uint8Array(1));
+ }).then(result => {
+ assert_equals(pullCount, 1);
+ assert_not_equals(byobRequest, null, 'byobRequest must not be null');
+ assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 4, 'view.buffer.byteLength should be 4');
+ assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 4, 'view.byteLength should be 4');
+
+ assert_false(result.done, 'done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 1, 'byteLength');
+
+ assert_equals(view[0], 0x03);
+ });
+}, 'ReadableStream with byte source: respond(3) to read(view) with 2 element Uint16Array enqueues the 1 byte ' +
+ 'remainder');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(controller) {
+ const view = new Uint8Array(16);
+ view[15] = 0x01;
+ controller.enqueue(view);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(16)).then(result => {
+ assert_false(result.done);
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 16);
+ assert_equals(view[15], 0x01);
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view)');
+
+promise_test(t => {
+ let cancelCount = 0;
+ let reason;
+
+ const passedReason = new TypeError('foo');
+
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array(16));
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ cancel(r) {
+ if (cancelCount === 0) {
+ reason = r;
+ }
+
+ ++cancelCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return reader.cancel(passedReason).then(result => {
+ assert_equals(result, undefined);
+ assert_equals(cancelCount, 1);
+ assert_equals(reason, passedReason, 'reason should equal the passed reason');
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then cancel() (mode = not BYOB)');
+
+promise_test(t => {
+ let cancelCount = 0;
+ let reason;
+
+ const passedReason = new TypeError('foo');
+
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array(16));
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ cancel(r) {
+ if (cancelCount === 0) {
+ reason = r;
+ }
+
+ ++cancelCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.cancel(passedReason).then(result => {
+ assert_equals(result, undefined);
+ assert_equals(cancelCount, 1);
+ assert_equals(reason, passedReason, 'reason should equal the passed reason');
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then cancel() (mode = BYOB)');
+
+promise_test(t => {
+ let cancelCount = 0;
+ let reason;
+
+ const passedReason = new TypeError('foo');
+
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ cancel(r) {
+ if (cancelCount === 0) {
+ reason = r;
+ }
+
+ ++cancelCount;
+
+ return 'bar';
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const readPromise = reader.read(new Uint8Array(1)).then(result => {
+ assert_true(result.done, 'result.done');
+ assert_equals(result.value, undefined, 'result.value');
+ });
+
+ const cancelPromise = reader.cancel(passedReason).then(result => {
+ assert_equals(result, undefined, 'cancel() return value should be fulfilled with undefined');
+ assert_equals(cancelCount, 1, 'cancel() should be called only once');
+ assert_equals(reason, passedReason, 'reason should equal the passed reason');
+ });
+
+ return Promise.all([readPromise, cancelPromise]);
+}, 'ReadableStream with byte source: getReader(), read(view), then cancel()');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let byobRequest;
+ const viewInfos = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+
+ viewInfos.push(extractViewInfo(controller.byobRequest.view));
+ controller.enqueue(new Uint8Array(1));
+ viewInfos.push(extractViewInfo(controller.byobRequest.view));
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ return Promise.resolve().then(() => {
+ assert_equals(pullCount, 0, 'No pull() as no read(view) yet');
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const promise = reader.read(new Uint16Array(1)).then(result => {
+ assert_true(result.done, 'result.done');
+ assert_equals(result.value, undefined, 'result.value');
+ });
+
+ assert_equals(pullCount, 1, '1 pull() should have been made in response to partial fill by enqueue()');
+ assert_not_equals(byobRequest, null, 'byobRequest should not be null');
+ assert_equals(viewInfos[0].byteLength, 2, 'byteLength before enqueue() should be 2');
+ assert_equals(viewInfos[1].byteLength, 1, 'byteLength after enqueue() should be 1');
+
+ reader.cancel();
+
+ assert_equals(pullCount, 1, 'pull() should only be called once');
+ return promise;
+ });
+}, 'ReadableStream with byte source: cancel() with partially filled pending pull() request');
+
+promise_test(() => {
+ let controller;
+ let pullCalled = false;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(8);
+ view[7] = 0x01;
+ c.enqueue(view);
+
+ controller = c;
+ },
+ pull() {
+ pullCalled = true;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const buffer = new ArrayBuffer(16);
+
+ return reader.read(new Uint8Array(buffer, 8, 8)).then(result => {
+ assert_false(result.done);
+
+ assert_false(pullCalled, 'pull() must not have been called');
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array);
+ assert_equals(view.buffer.byteLength, 16);
+ assert_equals(view.byteOffset, 8);
+ assert_equals(view.byteLength, 8);
+ assert_equals(view[7], 0x01);
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) where view.buffer is not fully ' +
+ 'covered by view');
+
+promise_test(() => {
+ let controller;
+ let pullCalled = false;
+
+ const stream = new ReadableStream({
+ start(c) {
+ let view;
+
+ view = new Uint8Array(16);
+ view[15] = 123;
+ c.enqueue(view);
+
+ view = new Uint8Array(8);
+ view[7] = 111;
+ c.enqueue(view);
+
+ controller = c;
+ },
+ pull() {
+ pullCalled = true;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(24)).then(result => {
+ assert_false(result.done, 'done');
+
+ assert_false(pullCalled, 'pull() must not have been called');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 24, 'byteLength');
+ assert_equals(view[15], 123, 'Contents are set from the first chunk');
+ assert_equals(view[23], 111, 'Contents are set from the second chunk');
+ });
+}, 'ReadableStream with byte source: Multiple enqueue(), getReader(), then read(view)');
+
+promise_test(() => {
+ let pullCalled = false;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(16);
+ view[15] = 0x01;
+ c.enqueue(view);
+ },
+ pull() {
+ pullCalled = true;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(24)).then(result => {
+ assert_false(result.done);
+
+ assert_false(pullCalled, 'pull() must not have been called');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 16);
+ assert_equals(view[15], 0x01);
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with a bigger view');
+
+promise_test(() => {
+ let pullCalled = false;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(16);
+ view[7] = 0x01;
+ view[15] = 0x02;
+ c.enqueue(view);
+ },
+ pull() {
+ pullCalled = true;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(8)).then(result => {
+ assert_false(result.done, 'done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 8);
+ assert_equals(view[7], 0x01);
+
+ return reader.read(new Uint8Array(8));
+ }).then(result => {
+ assert_false(result.done, 'done');
+
+ assert_false(pullCalled, 'pull() must not have been called');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 8);
+ assert_equals(view[7], 0x02);
+ });
+}, 'ReadableStream with byte source: enqueue(), getReader(), then read(view) with smaller views');
+
+promise_test(() => {
+ let controller;
+ let viewInfo;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(1);
+ view[0] = 0xff;
+ c.enqueue(view);
+
+ controller = c;
+ },
+ pull() {
+ if (controller.byobRequest === null) {
+ return;
+ }
+
+ const view = controller.byobRequest.view;
+ viewInfo = extractViewInfo(view);
+
+ view[0] = 0xaa;
+ controller.byobRequest.respond(1);
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint16Array(1)).then(result => {
+ assert_false(result.done);
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 2);
+
+ const dataView = new DataView(view.buffer, view.byteOffset, view.byteLength);
+ assert_equals(dataView.getUint16(0), 0xffaa);
+
+ assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 2, 'view.buffer.byteLength should be 2');
+ assert_equals(viewInfo.byteOffset, 1, 'view.byteOffset should be 1');
+ assert_equals(viewInfo.byteLength, 1, 'view.byteLength should be 1');
+ });
+}, 'ReadableStream with byte source: enqueue() 1 byte, getReader(), then read(view) with Uint16Array');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let byobRequest;
+ let viewInfo;
+ let desiredSize;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(3);
+ view[0] = 0x01;
+ view[2] = 0x02;
+ c.enqueue(view);
+
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+
+ const view = controller.byobRequest.view;
+
+ viewInfo = extractViewInfo(view);
+
+ view[0] = 0x03;
+ controller.byobRequest.respond(1);
+
+ desiredSize = controller.desiredSize;
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ // Wait for completion of the start method to be reflected.
+ return Promise.resolve().then(() => {
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const promise = reader.read(new Uint16Array(2)).then(result => {
+ assert_false(result.done, 'done');
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint16Array, 'constructor');
+ assert_equals(view.buffer.byteLength, 4, 'buffer.byteLength');
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 2, 'byteLength');
+
+ const dataView = new DataView(view.buffer, view.byteOffset, view.byteLength);
+ assert_equals(dataView.getUint16(0), 0x0100, 'contents are set');
+
+ const p = reader.read(new Uint16Array(1));
+
+ assert_equals(pullCount, 1);
+
+ return p;
+ }).then(result => {
+ assert_false(result.done, 'done');
+
+ const view = result.value;
+ assert_equals(view.buffer.byteLength, 2, 'buffer.byteLength');
+ assert_equals(view.byteOffset, 0, 'byteOffset');
+ assert_equals(view.byteLength, 2, 'byteLength');
+
+ const dataView = new DataView(view.buffer, view.byteOffset, view.byteLength);
+ assert_equals(dataView.getUint16(0), 0x0203, 'contents are set');
+
+ assert_not_equals(byobRequest, null, 'byobRequest must not be null');
+ assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 2, 'view.buffer.byteLength should be 2');
+ assert_equals(viewInfo.byteOffset, 1, 'view.byteOffset should be 1');
+ assert_equals(viewInfo.byteLength, 1, 'view.byteLength should be 1');
+ assert_equals(desiredSize, 0, 'desiredSize should be zero');
+ });
+
+ assert_equals(pullCount, 0);
+
+ return promise;
+ });
+}, 'ReadableStream with byte source: enqueue() 3 byte, getReader(), then read(view) with 2-element Uint16Array');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(1);
+ view[0] = 0xff;
+ c.enqueue(view);
+ c.close();
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+
+ return promise_rejects_js(t, TypeError, reader.read(new Uint16Array(1)), 'read(view) must fail')
+ .then(() => promise_rejects_js(t, TypeError, reader.closed, 'reader.closed should reject'));
+}, 'ReadableStream with byte source: read(view) with Uint16Array on close()-d stream with 1 byte enqueue()-d must ' +
+ 'fail');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(1);
+ view[0] = 0xff;
+ c.enqueue(view);
+
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const readPromise = reader.read(new Uint16Array(1));
+
+ assert_throws_js(TypeError, () => controller.close(), 'controller.close() must throw');
+
+ return promise_rejects_js(t, TypeError, readPromise, 'read(view) must fail')
+ .then(() => promise_rejects_js(t, TypeError, reader.closed, 'reader.closed must reject'));
+}, 'ReadableStream with byte source: A stream must be errored if close()-d before fulfilling read(view) with ' +
+ 'Uint16Array');
+
+test(() => {
+ let controller;
+
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ // Enqueue a chunk so that the stream doesn't get closed. This is to check duplicate close() calls are rejected
+ // even if the stream has not yet entered the closed state.
+ const view = new Uint8Array(1);
+ controller.enqueue(view);
+ controller.close();
+
+ assert_throws_js(TypeError, () => controller.close(), 'controller.close() must throw');
+}, 'ReadableStream with byte source: Throw if close()-ed more than once');
+
+test(() => {
+ let controller;
+
+ new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ // Enqueue a chunk so that the stream doesn't get closed. This is to check enqueue() after close() is rejected
+ // even if the stream has not yet entered the closed state.
+ const view = new Uint8Array(1);
+ controller.enqueue(view);
+ controller.close();
+
+ assert_throws_js(TypeError, () => controller.enqueue(view), 'controller.close() must throw');
+}, 'ReadableStream with byte source: Throw on enqueue() after close()');
+
+promise_test(() => {
+ let controller;
+ let byobRequest;
+ let viewInfo;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+ const view = controller.byobRequest.view;
+ viewInfo = extractViewInfo(view);
+
+ view[15] = 0x01;
+ controller.byobRequest.respond(16);
+ controller.close();
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint8Array(16)).then(result => {
+ assert_false(result.done);
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 16);
+ assert_equals(view[15], 0x01);
+
+ return reader.read(new Uint8Array(16));
+ }).then(result => {
+ assert_true(result.done);
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 0);
+
+ assert_not_equals(byobRequest, null, 'byobRequest must not be null');
+ assert_equals(viewInfo.constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 16, 'view.buffer.byteLength should be 16');
+ assert_equals(viewInfo.byteOffset, 0, 'view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 16, 'view.byteLength should be 16');
+ });
+}, 'ReadableStream with byte source: read(view), then respond() and close() in pull()');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ const viewInfos = [];
+ const viewInfosAfterRespond = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ if (controller.byobRequest === null) {
+ return;
+ }
+
+ for (let i = 0; i < 4; ++i) {
+ const view = controller.byobRequest.view;
+ viewInfos.push(extractViewInfo(view));
+
+ view[0] = 0x01;
+ controller.byobRequest.respond(1);
+ viewInfosAfterRespond.push(extractViewInfo(view));
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint32Array(1)).then(result => {
+ assert_false(result.done, 'result.done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'result.value.byteOffset');
+ assert_equals(view.byteLength, 4, 'result.value.byteLength');
+ assert_equals(view[0], 0x01010101, 'result.value[0]');
+
+ assert_equals(pullCount, 1, 'pull() should only be called once');
+
+ for (let i = 0; i < 4; ++i) {
+ assert_equals(viewInfos[i].constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfos[i].bufferByteLength, 4, 'view.buffer.byteLength should be 4');
+
+ assert_equals(viewInfos[i].byteOffset, i, 'view.byteOffset should be i');
+ assert_equals(viewInfos[i].byteLength, 4 - i, 'view.byteLength should be 4 - i');
+
+ assert_equals(viewInfosAfterRespond[i].bufferByteLength, 0, 'view.buffer should be transferred after respond()');
+ }
+ });
+}, 'ReadableStream with byte source: read(view) with Uint32Array, then fill it by multiple respond() calls');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ const viewInfos = [];
+ const viewInfosAfterEnqueue = [];
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ if (controller.byobRequest === null) {
+ return;
+ }
+
+ for (let i = 0; i < 4; ++i) {
+ const view = controller.byobRequest.view;
+ viewInfos.push(extractViewInfo(view));
+
+ controller.enqueue(new Uint8Array([0x01]));
+ viewInfosAfterEnqueue.push(extractViewInfo(view));
+ }
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return reader.read(new Uint32Array(1)).then(result => {
+ assert_false(result.done, 'result.done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'result.value.byteOffset');
+ assert_equals(view.byteLength, 4, 'result.value.byteLength');
+ assert_equals(view[0], 0x01010101, 'result.value[0]');
+
+ assert_equals(pullCount, 1, 'pull() should only be called once');
+
+ for (let i = 0; i < 4; ++i) {
+ assert_equals(viewInfos[i].constructor, Uint8Array, 'view.constructor should be Uint8Array');
+ assert_equals(viewInfos[i].bufferByteLength, 4, 'view.buffer.byteLength should be 4');
+
+ assert_equals(viewInfos[i].byteOffset, i, 'view.byteOffset should be i');
+ assert_equals(viewInfos[i].byteLength, 4 - i, 'view.byteLength should be 4 - i');
+
+ assert_equals(viewInfosAfterEnqueue[i].bufferByteLength, 0, 'view.buffer should be transferred after enqueue()');
+ }
+ });
+}, 'ReadableStream with byte source: read(view) with Uint32Array, then fill it by multiple enqueue() calls');
+
+promise_test(() => {
+ let pullCount = 0;
+
+ let controller;
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ const p0 = reader.read().then(result => {
+ assert_equals(pullCount, 1);
+
+ controller.enqueue(new Uint8Array(2));
+
+ // Since the queue has data no less than HWM, no more pull.
+ assert_equals(pullCount, 1);
+
+ assert_false(result.done);
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array);
+ assert_equals(view.buffer.byteLength, 1);
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 1);
+ });
+
+ assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled');
+
+ const p1 = reader.read().then(result => {
+ assert_equals(pullCount, 1);
+
+ assert_false(result.done);
+
+ const view = result.value;
+ assert_equals(view.constructor, Uint8Array);
+ assert_equals(view.buffer.byteLength, 2);
+ assert_equals(view.byteOffset, 0);
+ assert_equals(view.byteLength, 2);
+
+ assert_equals(byobRequest, null, 'byobRequest must be null');
+ });
+
+ assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled');
+
+ controller.enqueue(new Uint8Array(1));
+
+ assert_equals(pullCount, 0, 'No pull should have been made since the startPromise has not yet been handled');
+
+ return Promise.all([p0, p1]);
+}, 'ReadableStream with byte source: read() twice, then enqueue() twice');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const p0 = reader.read(new Uint8Array(16)).then(result => {
+ assert_true(result.done, '1st read: done');
+
+ const view = result.value;
+ assert_equals(view.buffer.byteLength, 16, '1st read: buffer.byteLength');
+ assert_equals(view.byteOffset, 0, '1st read: byteOffset');
+ assert_equals(view.byteLength, 0, '1st read: byteLength');
+ });
+
+ const p1 = reader.read(new Uint8Array(32)).then(result => {
+ assert_true(result.done, '2nd read: done');
+
+ const view = result.value;
+ assert_equals(view.buffer.byteLength, 32, '2nd read: buffer.byteLength');
+ assert_equals(view.byteOffset, 0, '2nd read: byteOffset');
+ assert_equals(view.byteLength, 0, '2nd read: byteLength');
+ });
+
+ controller.close();
+ controller.byobRequest.respond(0);
+
+ return Promise.all([p0, p1]);
+}, 'ReadableStream with byte source: Multiple read(view), close() and respond()');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const p0 = reader.read(new Uint8Array(16)).then(result => {
+ assert_false(result.done, '1st read: done');
+
+ const view = result.value;
+ assert_equals(view.buffer.byteLength, 16, '1st read: buffer.byteLength');
+ assert_equals(view.byteOffset, 0, '1st read: byteOffset');
+ assert_equals(view.byteLength, 16, '1st read: byteLength');
+ });
+
+ const p1 = reader.read(new Uint8Array(16)).then(result => {
+ assert_false(result.done, '2nd read: done');
+
+ const view = result.value;
+ assert_equals(view.buffer.byteLength, 16, '2nd read: buffer.byteLength');
+ assert_equals(view.byteOffset, 0, '2nd read: byteOffset');
+ assert_equals(view.byteLength, 8, '2nd read: byteLength');
+ });
+
+ controller.enqueue(new Uint8Array(24));
+
+ return Promise.all([p0, p1]);
+}, 'ReadableStream with byte source: Multiple read(view), big enqueue()');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ let bytesRead = 0;
+
+ function pump() {
+ return reader.read(new Uint8Array(7)).then(result => {
+ if (result.done) {
+ assert_equals(bytesRead, 1024);
+ return undefined;
+ }
+
+ bytesRead += result.value.byteLength;
+
+ return pump();
+ });
+ }
+ const promise = pump();
+
+ controller.enqueue(new Uint8Array(512));
+ controller.enqueue(new Uint8Array(512));
+ controller.close();
+
+ return promise;
+}, 'ReadableStream with byte source: Multiple read(view) and multiple enqueue()');
+
+promise_test(t => {
+ let pullCalled = false;
+ const stream = new ReadableStream({
+ pull(controller) {
+ pullCalled = true;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects_js(t, TypeError, reader.read(), 'read() must fail')
+ .then(() => assert_false(pullCalled, 'pull() must not have been called'));
+}, 'ReadableStream with byte source: read(view) with passing undefined as view must fail');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects_js(t, TypeError, reader.read({}), 'read(view) must fail');
+}, 'ReadableStream with byte source: read(view) with passing an empty object as view must fail');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects_js(t, TypeError,
+ reader.read({ buffer: new ArrayBuffer(10), byteOffset: 0, byteLength: 10 }),
+ 'read(view) must fail');
+}, 'ReadableStream with byte source: Even read(view) with passing ArrayBufferView like object as view must fail');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.error(error1);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return promise_rejects_exactly(t, error1, reader.read(), 'read() must fail');
+}, 'ReadableStream with byte source: read() on an errored stream');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ const promise = promise_rejects_exactly(t, error1, reader.read(), 'read() must fail');
+
+ controller.error(error1);
+
+ return promise;
+}, 'ReadableStream with byte source: read(), then error()');
+
+promise_test(t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.error(error1);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects_exactly(t, error1, reader.read(new Uint8Array(1)), 'read() must fail');
+}, 'ReadableStream with byte source: read(view) on an errored stream');
+
+promise_test(t => {
+ let controller;
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const promise = promise_rejects_exactly(t, error1, reader.read(new Uint8Array(1)), 'read() must fail');
+
+ controller.error(error1);
+
+ return promise;
+}, 'ReadableStream with byte source: read(view), then error()');
+
+promise_test(t => {
+ let controller;
+ let byobRequest;
+
+ const testError = new TypeError('foo');
+
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull() {
+ byobRequest = controller.byobRequest;
+ throw testError;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ const promise = promise_rejects_exactly(t, testError, reader.read(), 'read() must fail');
+ return promise_rejects_exactly(t, testError, promise.then(() => reader.closed))
+ .then(() => assert_equals(byobRequest, null, 'byobRequest must be null'));
+}, 'ReadableStream with byte source: Throwing in pull function must error the stream');
+
+promise_test(t => {
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ controller.error(error1);
+ throw new TypeError('foo');
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader();
+
+ return promise_rejects_exactly(t, error1, reader.read(), 'read() must fail')
+ .then(() => promise_rejects_exactly(t, error1, reader.closed, 'closed must fail'))
+ .then(() => assert_equals(byobRequest, null, 'byobRequest must be null'));
+}, 'ReadableStream with byte source: Throwing in pull in response to read() must be ignored if the stream is ' +
+ 'errored in it');
+
+promise_test(t => {
+ let byobRequest;
+
+ const testError = new TypeError('foo');
+
+ const stream = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ throw testError;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects_exactly(t, testError, reader.read(new Uint8Array(1)), 'read(view) must fail')
+ .then(() => promise_rejects_exactly(t, testError, reader.closed, 'reader.closed must reject'))
+ .then(() => assert_not_equals(byobRequest, null, 'byobRequest must not be null'));
+}, 'ReadableStream with byte source: Throwing in pull in response to read(view) function must error the stream');
+
+promise_test(t => {
+ let byobRequest;
+
+ const stream = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ controller.error(error1);
+ throw new TypeError('foo');
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ return promise_rejects_exactly(t, error1, reader.read(new Uint8Array(1)), 'read(view) must fail')
+ .then(() => promise_rejects_exactly(t, error1, reader.closed, 'closed must fail'))
+ .then(() => assert_not_equals(byobRequest, null, 'byobRequest must not be null'));
+}, 'ReadableStream with byte source: Throwing in pull in response to read(view) must be ignored if the stream is ' +
+ 'errored in it');
+
+promise_test(() => {
+ let byobRequest;
+ const rs = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ byobRequest.respond(4);
+ },
+ type: 'bytes'
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ const view = new Uint8Array(16);
+ return reader.read(view).then(() => {
+ assert_throws_js(TypeError, () => byobRequest.respond(4), 'respond() should throw a TypeError');
+ });
+}, 'calling respond() twice on the same byobRequest should throw');
+
+promise_test(() => {
+ let byobRequest;
+ const newView = () => new Uint8Array(16);
+ const rs = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ byobRequest.respondWithNewView(newView());
+ },
+ type: 'bytes'
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ return reader.read(newView()).then(() => {
+ assert_throws_js(TypeError, () => byobRequest.respondWithNewView(newView()),
+ 'respondWithNewView() should throw a TypeError');
+ });
+}, 'calling respondWithNewView() twice on the same byobRequest should throw');
+
+promise_test(() => {
+ let controller;
+ let byobRequest;
+ let resolvePullCalledPromise;
+ const pullCalledPromise = new Promise(resolve => {
+ resolvePullCalledPromise = resolve;
+ });
+ let resolvePull;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull(c) {
+ byobRequest = c.byobRequest;
+ resolvePullCalledPromise();
+ return new Promise(resolve => {
+ resolvePull = resolve;
+ });
+ },
+ type: 'bytes'
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ const readPromise = reader.read(new Uint8Array(16));
+ return pullCalledPromise.then(() => {
+ controller.close();
+ byobRequest.respond(0);
+ resolvePull();
+ return readPromise.then(() => {
+ assert_throws_js(TypeError, () => byobRequest.respond(0), 'respond() should throw');
+ });
+ });
+}, 'calling respond(0) twice on the same byobRequest should throw even when closed');
+
+promise_test(() => {
+ let controller;
+ let byobRequest;
+ let resolvePullCalledPromise;
+ const pullCalledPromise = new Promise(resolve => {
+ resolvePullCalledPromise = resolve;
+ });
+ let resolvePull;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull(c) {
+ byobRequest = c.byobRequest;
+ resolvePullCalledPromise();
+ return new Promise(resolve => {
+ resolvePull = resolve;
+ });
+ },
+ type: 'bytes'
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ const readPromise = reader.read(new Uint8Array(16));
+ return pullCalledPromise.then(() => {
+ const cancelPromise = reader.cancel('meh');
+ assert_throws_js(TypeError, () => byobRequest.respond(0), 'respond() should throw');
+ resolvePull();
+ return Promise.all([readPromise, cancelPromise]);
+ });
+}, 'calling respond() should throw when canceled');
+
+promise_test(async t => {
+ let resolvePullCalledPromise;
+ const pullCalledPromise = new Promise(resolve => {
+ resolvePullCalledPromise = resolve;
+ });
+ let resolvePull;
+ const rs = new ReadableStream({
+ pull() {
+ resolvePullCalledPromise();
+ return new Promise(resolve => {
+ resolvePull = resolve;
+ });
+ },
+ type: 'bytes'
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ const read = reader.read(new Uint8Array(16));
+ await pullCalledPromise;
+ resolvePull();
+ await delay(0);
+ reader.releaseLock();
+ await promise_rejects_js(t, TypeError, read, 'pending read should reject');
+}, 'pull() resolving should not resolve read()');
+
+promise_test(() => {
+ // Tests https://github.com/whatwg/streams/issues/686
+
+ let controller;
+ const rs = new ReadableStream({
+ autoAllocateChunkSize: 128,
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ const readPromise = rs.getReader().read();
+
+ const br = controller.byobRequest;
+ controller.close();
+
+ br.respond(0);
+
+ return readPromise;
+}, 'ReadableStream with byte source: default reader + autoAllocateChunkSize + byobRequest interaction');
+
+test(() => {
+ assert_throws_js(TypeError, () => new ReadableStream({ autoAllocateChunkSize: 0, type: 'bytes' }),
+ 'controller cannot be setup with autoAllocateChunkSize = 0');
+}, 'ReadableStream with byte source: autoAllocateChunkSize cannot be 0');
+
+test(() => {
+ const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
+ const stream = new ReadableStream({ type: 'bytes' });
+ new ReadableStreamBYOBReader(stream);
+}, 'ReadableStreamBYOBReader can be constructed directly');
+
+test(() => {
+ const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
+ assert_throws_js(TypeError, () => new ReadableStreamBYOBReader({}), 'constructor must throw');
+}, 'ReadableStreamBYOBReader constructor requires a ReadableStream argument');
+
+test(() => {
+ const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
+ const stream = new ReadableStream({ type: 'bytes' });
+ stream.getReader();
+ assert_throws_js(TypeError, () => new ReadableStreamBYOBReader(stream), 'constructor must throw');
+}, 'ReadableStreamBYOBReader constructor requires an unlocked ReadableStream');
+
+test(() => {
+ const ReadableStreamBYOBReader = new ReadableStream({ type: 'bytes' }).getReader({ mode: 'byob' }).constructor;
+ const stream = new ReadableStream();
+ assert_throws_js(TypeError, () => new ReadableStreamBYOBReader(stream), 'constructor must throw');
+}, 'ReadableStreamBYOBReader constructor requires a ReadableStream with type "bytes"');
+
+test(() => {
+ assert_throws_js(RangeError, () => new ReadableStream({ type: 'bytes' }, {
+ size() {
+ return 1;
+ }
+ }), 'constructor should throw for size function');
+
+ assert_throws_js(RangeError,
+ () => new ReadableStream({ type: 'bytes' }, new CountQueuingStrategy({ highWaterMark: 1 })),
+ 'constructor should throw when strategy is CountQueuingStrategy');
+
+ assert_throws_js(RangeError,
+ () => new ReadableStream({ type: 'bytes' }, new ByteLengthQueuingStrategy({ highWaterMark: 512 })),
+ 'constructor should throw when strategy is ByteLengthQueuingStrategy');
+
+ class HasSizeMethod {
+ size() {}
+ }
+
+ assert_throws_js(RangeError, () => new ReadableStream({ type: 'bytes' }, new HasSizeMethod()),
+ 'constructor should throw when size on the prototype chain');
+}, 'ReadableStream constructor should not accept a strategy with a size defined if type is "bytes"');
+
+promise_test(async t => {
+ const stream = new ReadableStream({
+ pull: t.step_func(c => {
+ const view = new Uint8Array(c.byobRequest.view.buffer, 0, 1);
+ view[0] = 1;
+
+ c.byobRequest.respondWithNewView(view);
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const result = await reader.read(new Uint8Array([4, 5, 6]));
+ assert_false(result.done, 'result.done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'result.value.byteOffset');
+ assert_equals(view.byteLength, 1, 'result.value.byteLength');
+ assert_equals(view[0], 1, 'result.value[0]');
+ assert_equals(view.buffer.byteLength, 3, 'result.value.buffer.byteLength');
+ assert_array_equals([...new Uint8Array(view.buffer)], [1, 5, 6], 'result.value.buffer');
+}, 'ReadableStream with byte source: respondWithNewView() with a smaller view');
+
+promise_test(async t => {
+ const stream = new ReadableStream({
+ pull: t.step_func(c => {
+ const view = new Uint8Array(c.byobRequest.view.buffer, 0, 0);
+
+ c.close();
+
+ c.byobRequest.respondWithNewView(view);
+ }),
+ type: 'bytes'
+ });
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const result = await reader.read(new Uint8Array([4, 5, 6]));
+ assert_true(result.done, 'result.done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'result.value.byteOffset');
+ assert_equals(view.byteLength, 0, 'result.value.byteLength');
+ assert_equals(view.buffer.byteLength, 3, 'result.value.buffer.byteLength');
+ assert_array_equals([...new Uint8Array(view.buffer)], [4, 5, 6], 'result.value.buffer');
+}, 'ReadableStream with byte source: respondWithNewView() with a zero-length view (in the closed state)');
+
+promise_test(async t => {
+ let controller;
+ let resolvePullCalledPromise;
+ const pullCalledPromise = new Promise(resolve => {
+ resolvePullCalledPromise = resolve;
+ });
+ const stream = new ReadableStream({
+ start: t.step_func((c) => {
+ controller = c;
+ }),
+ pull: t.step_func(() => {
+ resolvePullCalledPromise();
+ }),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const readPromise = reader.read(new Uint8Array([4, 5, 6]));
+ await pullCalledPromise;
+
+ // Transfer the original BYOB request's buffer, and respond with a new view on that buffer
+ const transferredView = await transferArrayBufferView(controller.byobRequest.view);
+ const newView = transferredView.subarray(0, 1);
+ newView[0] = 42;
+
+ controller.byobRequest.respondWithNewView(newView);
+
+ const result = await readPromise;
+ assert_false(result.done, 'result.done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'result.value.byteOffset');
+ assert_equals(view.byteLength, 1, 'result.value.byteLength');
+ assert_equals(view[0], 42, 'result.value[0]');
+ assert_equals(view.buffer.byteLength, 3, 'result.value.buffer.byteLength');
+ assert_array_equals([...new Uint8Array(view.buffer)], [42, 5, 6], 'result.value.buffer');
+
+}, 'ReadableStream with byte source: respondWithNewView() with a transferred non-zero-length view ' +
+ '(in the readable state)');
+
+promise_test(async t => {
+ let controller;
+ let resolvePullCalledPromise;
+ const pullCalledPromise = new Promise(resolve => {
+ resolvePullCalledPromise = resolve;
+ });
+ const stream = new ReadableStream({
+ start: t.step_func((c) => {
+ controller = c;
+ }),
+ pull: t.step_func(() => {
+ resolvePullCalledPromise();
+ }),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const readPromise = reader.read(new Uint8Array([4, 5, 6]));
+ await pullCalledPromise;
+
+ // Transfer the original BYOB request's buffer, and respond with an empty view on that buffer
+ const transferredView = await transferArrayBufferView(controller.byobRequest.view);
+ const newView = transferredView.subarray(0, 0);
+
+ controller.close();
+ controller.byobRequest.respondWithNewView(newView);
+
+ const result = await readPromise;
+ assert_true(result.done, 'result.done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'result.value.byteOffset');
+ assert_equals(view.byteLength, 0, 'result.value.byteLength');
+ assert_equals(view.buffer.byteLength, 3, 'result.value.buffer.byteLength');
+ assert_array_equals([...new Uint8Array(view.buffer)], [4, 5, 6], 'result.value.buffer');
+
+}, 'ReadableStream with byte source: respondWithNewView() with a transferred zero-length view ' +
+ '(in the closed state)');
+
+promise_test(async t => {
+ let controller;
+ let pullCount = 0;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ autoAllocateChunkSize: 10,
+ start: t.step_func((c) => {
+ controller = c;
+ }),
+ pull: t.step_func(() => {
+ ++pullCount;
+ })
+ });
+
+ await flushAsyncEvents();
+ assert_equals(pullCount, 0, 'pull() must not have been invoked yet');
+
+ const reader1 = rs.getReader();
+ const read1 = reader1.read();
+ assert_equals(pullCount, 1, 'pull() must have been invoked once');
+ const byobRequest1 = controller.byobRequest;
+ assert_equals(byobRequest1.view.byteLength, 10, 'first byobRequest.view.byteLength');
+
+ // enqueue() must discard the auto-allocated BYOB request
+ controller.enqueue(new Uint8Array([1, 2, 3]));
+ assert_equals(byobRequest1.view, null, 'first byobRequest must be invalidated after enqueue()');
+
+ const result1 = await read1;
+ assert_false(result1.done, 'first result.done');
+ const view1 = result1.value;
+ assert_equals(view1.byteOffset, 0, 'first result.value.byteOffset');
+ assert_equals(view1.byteLength, 3, 'first result.value.byteLength');
+ assert_array_equals([...new Uint8Array(view1.buffer)], [1, 2, 3], 'first result.value.buffer');
+
+ reader1.releaseLock();
+
+ // read(view) should work after discarding the auto-allocated BYOB request
+ const reader2 = rs.getReader({ mode: 'byob' });
+ const read2 = reader2.read(new Uint8Array([4, 5, 6]));
+ assert_equals(pullCount, 2, 'pull() must have been invoked twice');
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2.view.byteOffset, 0, 'second byobRequest.view.byteOffset');
+ assert_equals(byobRequest2.view.byteLength, 3, 'second byobRequest.view.byteLength');
+ assert_array_equals([...new Uint8Array(byobRequest2.view.buffer)], [4, 5, 6], 'second byobRequest.view.buffer');
+
+ byobRequest2.respond(3);
+ assert_equals(byobRequest2.view, null, 'second byobRequest must be invalidated after respond()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ const view2 = result2.value;
+ assert_equals(view2.byteOffset, 0, 'second result.value.byteOffset');
+ assert_equals(view2.byteLength, 3, 'second result.value.byteLength');
+ assert_array_equals([...new Uint8Array(view2.buffer)], [4, 5, 6], 'second result.value.buffer');
+
+ reader2.releaseLock();
+ assert_equals(pullCount, 2, 'pull() must only have been invoked twice');
+}, 'ReadableStream with byte source: enqueue() discards auto-allocated BYOB request');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader({ mode: 'byob' });
+ const read1 = reader1.read(new Uint8Array([1, 2, 3]));
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader({ mode: 'byob' });
+ const read2 = reader2.read(new Uint8Array([4, 5, 6]));
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ assert_equals(controller.byobRequest, byobRequest1, 'byobRequest should be unchanged');
+ assert_array_equals([...new Uint8Array(byobRequest1.view.buffer)], [1, 2, 3], 'byobRequest.view.buffer should be unchanged');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // respond() should fulfill the *second* read() request
+ byobRequest1.view[0] = 11;
+ byobRequest1.respond(1);
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2, null, 'byobRequest should be null after respond()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ assert_typed_array_equals(result2.value, new Uint8Array([11, 5, 6]).subarray(0, 1), 'second result.value');
+
+}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader, respond()');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader({ mode: 'byob' });
+ const read1 = reader1.read(new Uint8Array([1, 2, 3]));
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader({ mode: 'byob' });
+ const read2 = reader2.read(new Uint16Array(1));
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ assert_equals(controller.byobRequest, byobRequest1, 'byobRequest should be unchanged');
+ assert_array_equals([...new Uint8Array(byobRequest1.view.buffer)], [1, 2, 3], 'byobRequest.view.buffer should be unchanged');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // respond(1) should partially fill the second read(), but not yet fulfill it
+ byobRequest1.view[0] = 0x11;
+ byobRequest1.respond(1);
+
+ // second BYOB request should use remaining buffer from the second read()
+ const byobRequest2 = controller.byobRequest;
+ assert_not_equals(byobRequest2, null, 'second byobRequest should exist');
+ assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11, 0]).subarray(1, 2), 'second byobRequest.view');
+
+ // second respond(1) should fill the read request and fulfill it
+ byobRequest2.view[0] = 0x22;
+ byobRequest2.respond(1);
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ const view2 = result2.value;
+ assert_equals(view2.byteOffset, 0, 'second result.value.byteOffset');
+ assert_equals(view2.byteLength, 2, 'second result.value.byteLength');
+ const dataView2 = new DataView(view2.buffer, view2.byteOffset, view2.byteLength);
+ assert_equals(dataView2.getUint16(0), 0x1122, 'second result.value[0]');
+
+}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader with ' +
+ '1 element Uint16Array, respond(1)');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader({ mode: 'byob' });
+ const read1 = reader1.read(new Uint8Array([1, 2, 3]));
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader({ mode: 'byob' });
+ const read2 = reader2.read(new Uint8Array([4, 5]));
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ assert_equals(controller.byobRequest, byobRequest1, 'byobRequest should be unchanged');
+ assert_array_equals([...new Uint8Array(byobRequest1.view.buffer)], [1, 2, 3], 'byobRequest.view.buffer should be unchanged');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // respond(3) should fulfill the second read(), and put 1 remaining byte in the queue
+ byobRequest1.view[0] = 6;
+ byobRequest1.view[1] = 7;
+ byobRequest1.view[2] = 8;
+ byobRequest1.respond(3);
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2, null, 'byobRequest should be null after respond()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ assert_typed_array_equals(result2.value, new Uint8Array([6, 7]), 'second result.value');
+
+ // third read() should fulfill with the remaining byte
+ const result3 = await reader2.read(new Uint8Array([0, 0, 0]));
+ assert_false(result3.done, 'third result.done');
+ assert_typed_array_equals(result3.value, new Uint8Array([8, 0, 0]).subarray(0, 1), 'third result.value');
+
+}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader with ' +
+ '2 element Uint8Array, respond(3)');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader({ mode: 'byob' });
+ const read1 = reader1.read(new Uint8Array([1, 2, 3]));
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader({ mode: 'byob' });
+ const read2 = reader2.read(new Uint8Array([4, 5, 6]));
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // respondWithNewView() should fulfill the *second* read() request
+ byobRequest1.view[0] = 11;
+ byobRequest1.view[1] = 12;
+ byobRequest1.respondWithNewView(byobRequest1.view.subarray(0, 2));
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2, null, 'byobRequest should be null after respondWithNewView()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ assert_typed_array_equals(result2.value, new Uint8Array([11, 12, 6]).subarray(0, 2), 'second result.value');
+
+}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader, respondWithNewView()');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader({ mode: 'byob' });
+ const read1 = reader1.read(new Uint8Array([1, 2, 3]));
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader({ mode: 'byob' });
+ const read2 = reader2.read(new Uint8Array([4, 5, 6]));
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // enqueue() should fulfill the *second* read() request
+ controller.enqueue(new Uint8Array([11, 12]));
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2, null, 'byobRequest should be null after enqueue()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ assert_typed_array_equals(result2.value, new Uint8Array([11, 12, 6]).subarray(0, 2), 'second result.value');
+
+}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader, enqueue()');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader({ mode: 'byob' });
+ const read1 = reader1.read(new Uint8Array([1, 2, 3]));
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array([1, 2, 3]), 'first byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader({ mode: 'byob' });
+ const read2 = reader2.read(new Uint8Array([4, 5, 6]));
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // close() followed by respond(0) should fulfill the second read()
+ controller.close();
+ byobRequest1.respond(0);
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2, null, 'byobRequest should be null after respond()');
+
+ const result2 = await read2;
+ assert_true(result2.done, 'second result.done');
+ assert_typed_array_equals(result2.value, new Uint8Array([4, 5, 6]).subarray(0, 0), 'second result.value');
+}, 'ReadableStream with byte source: releaseLock() with pending read(view), read(view) on second reader, ' +
+ 'close(), respond(0)');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ autoAllocateChunkSize: 4,
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader();
+ const read1 = reader1.read();
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array(4), 'first byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader();
+ const read2 = reader2.read();
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // respond() should fulfill the *second* read() request
+ byobRequest1.view[0] = 11;
+ byobRequest1.respond(1);
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2, null, 'byobRequest should be null after respond()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ assert_typed_array_equals(result2.value, new Uint8Array([11, 0, 0, 0]).subarray(0, 1), 'second result.value');
+
+}, 'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read() on second reader, respond()');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ autoAllocateChunkSize: 4,
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader();
+ const read1 = reader1.read();
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array(4), 'first byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader();
+ const read2 = reader2.read();
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // enqueue() should fulfill the *second* read() request
+ controller.enqueue(new Uint8Array([11]));
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2, null, 'byobRequest should be null after enqueue()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ assert_typed_array_equals(result2.value, new Uint8Array([11]), 'second result.value');
+
+}, 'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read() on second reader, enqueue()');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ autoAllocateChunkSize: 4,
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader();
+ const read1 = reader1.read();
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array(4), 'first byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader({ mode: 'byob' });
+ const read2 = reader2.read(new Uint8Array([4, 5, 6]));
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // respond() should fulfill the *second* read() request
+ byobRequest1.view[0] = 11;
+ byobRequest1.respond(1);
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2, null, 'byobRequest should be null after respond()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ assert_typed_array_equals(result2.value, new Uint8Array([11, 5, 6]).subarray(0, 1), 'second result.value');
+
+}, 'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read(view) on second reader, respond()');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ autoAllocateChunkSize: 4,
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader();
+ const read1 = reader1.read();
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array(4), 'first byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader({ mode: 'byob' });
+ const read2 = reader2.read(new Uint8Array([4, 5, 6]));
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // enqueue() should fulfill the *second* read() request
+ controller.enqueue(new Uint8Array([11]));
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2, null, 'byobRequest should be null after enqueue()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ assert_typed_array_equals(result2.value, new Uint8Array([11, 5, 6]).subarray(0, 1), 'second result.value');
+
+}, 'ReadableStream with byte source: autoAllocateChunkSize, releaseLock() with pending read(), read(view) on second reader, enqueue()');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader({ mode: 'byob' });
+ const read1 = reader1.read(new Uint16Array(1));
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array([0, 0]), 'first byobRequest.view');
+
+ // respond(1) should partially fill the first read(), but not yet fulfill it
+ byobRequest1.view[0] = 0x11;
+ byobRequest1.respond(1);
+ const byobRequest2 = controller.byobRequest;
+ assert_not_equals(byobRequest2, null, 'second byobRequest should exist');
+ assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11, 0]).subarray(1, 2), 'second byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader({ mode: 'byob' });
+ const read2 = reader2.read(new Uint16Array(1));
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ assert_equals(controller.byobRequest, byobRequest2, 'byobRequest should be unchanged');
+ assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11, 0]).subarray(1, 2), 'byobRequest.view should be unchanged');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // second respond(1) should fill the read request and fulfill it
+ byobRequest2.view[0] = 0x22;
+ byobRequest2.respond(1);
+ assert_equals(controller.byobRequest, null, 'byobRequest should be invalidated after second respond()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ const view2 = result2.value;
+ assert_equals(view2.byteOffset, 0, 'second result.value.byteOffset');
+ assert_equals(view2.byteLength, 2, 'second result.value.byteLength');
+ const dataView2 = new DataView(view2.buffer, view2.byteOffset, view2.byteLength);
+ assert_equals(dataView2.getUint16(0), 0x1122, 'second result.value[0]');
+
+}, 'ReadableStream with byte source: read(view) with 1 element Uint16Array, respond(1), releaseLock(), read(view) on ' +
+ 'second reader with 1 element Uint16Array, respond(1)');
+
+promise_test(async t => {
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start: t.step_func((c) => {
+ controller = c;
+ })
+ });
+ await flushAsyncEvents();
+
+ const reader1 = rs.getReader({ mode: 'byob' });
+ const read1 = reader1.read(new Uint16Array(1));
+ const byobRequest1 = controller.byobRequest;
+ assert_not_equals(byobRequest1, null, 'first byobRequest should exist');
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array([0, 0]), 'first byobRequest.view');
+
+ // respond(1) should partially fill the first read(), but not yet fulfill it
+ byobRequest1.view[0] = 0x11;
+ byobRequest1.respond(1);
+ const byobRequest2 = controller.byobRequest;
+ assert_not_equals(byobRequest2, null, 'second byobRequest should exist');
+ assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11, 0]).subarray(1, 2), 'second byobRequest.view');
+
+ // releaseLock() should reject the pending read, but *not* invalidate the BYOB request
+ reader1.releaseLock();
+ const reader2 = rs.getReader();
+ const read2 = reader2.read();
+ assert_not_equals(controller.byobRequest, null, 'byobRequest should not be invalidated after releaseLock()');
+ assert_equals(controller.byobRequest, byobRequest2, 'byobRequest should be unchanged');
+ assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11, 0]).subarray(1, 2), 'byobRequest.view should be unchanged');
+ await promise_rejects_js(t, TypeError, read1, 'pending read must reject after releaseLock()');
+
+ // enqueue() should fulfill the read request and put remaining byte in the queue
+ controller.enqueue(new Uint8Array([0x22]));
+ assert_equals(controller.byobRequest, null, 'byobRequest should be invalidated after second respond()');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result.done');
+ assert_typed_array_equals(result2.value, new Uint8Array([0x11]), 'second result.value');
+
+ const result3 = await reader2.read();
+ assert_false(result3.done, 'third result.done');
+ assert_typed_array_equals(result3.value, new Uint8Array([0x22]), 'third result.value');
+
+}, 'ReadableStream with byte source: read(view) with 1 element Uint16Array, respond(1), releaseLock(), read() on ' +
+ 'second reader, enqueue()');
+
+promise_test(async t => {
+ // Tests https://github.com/nodejs/node/issues/41886
+ const stream = new ReadableStream({
+ type: 'bytes',
+ autoAllocateChunkSize: 10,
+ pull: t.step_func((c) => {
+ const newView = new Uint8Array(c.byobRequest.view.buffer, 0, 3);
+ newView.set([20, 21, 22]);
+ c.byobRequest.respondWithNewView(newView);
+ })
+ });
+
+ const reader = stream.getReader();
+ const result = await reader.read();
+ assert_false(result.done, 'result.done');
+
+ const view = result.value;
+ assert_equals(view.byteOffset, 0, 'result.value.byteOffset');
+ assert_equals(view.byteLength, 3, 'result.value.byteLength');
+ assert_equals(view.buffer.byteLength, 10, 'result.value.buffer.byteLength');
+ assert_array_equals([...new Uint8Array(view)], [20, 21, 22], 'result.value');
+}, 'ReadableStream with byte source: autoAllocateChunkSize, read(), respondWithNewView()');
diff --git a/testing/web-platform/tests/streams/readable-byte-streams/non-transferable-buffers.any.js b/testing/web-platform/tests/streams/readable-byte-streams/non-transferable-buffers.any.js
new file mode 100644
index 0000000000..4bddaef5d6
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/non-transferable-buffers.any.js
@@ -0,0 +1,70 @@
+// META: global=window,worker,shadowrealm
+'use strict';
+
+promise_test(async t => {
+ const rs = new ReadableStream({
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = rs.getReader({ mode: 'byob' });
+ const memory = new WebAssembly.Memory({ initial: 1 });
+ const view = new Uint8Array(memory.buffer, 0, 1);
+ await promise_rejects_js(t, TypeError, reader.read(view));
+}, 'ReadableStream with byte source: read() with a non-transferable buffer');
+
+promise_test(async t => {
+ const rs = new ReadableStream({
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = rs.getReader({ mode: 'byob' });
+ const memory = new WebAssembly.Memory({ initial: 1 });
+ const view = new Uint8Array(memory.buffer, 0, 1);
+ await promise_rejects_js(t, TypeError, reader.read(view, { min: 1 }));
+}, 'ReadableStream with byte source: fill() with a non-transferable buffer');
+
+test(t => {
+ let controller;
+ const rs = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const memory = new WebAssembly.Memory({ initial: 1 });
+ const view = new Uint8Array(memory.buffer, 0, 1);
+ assert_throws_js(TypeError, () => controller.enqueue(view));
+}, 'ReadableStream with byte source: enqueue() with a non-transferable buffer');
+
+promise_test(async t => {
+ let byobRequest;
+ let resolvePullCalledPromise;
+ const pullCalledPromise = new Promise(resolve => {
+ resolvePullCalledPromise = resolve;
+ });
+ const rs = new ReadableStream({
+ pull(controller) {
+ byobRequest = controller.byobRequest;
+ resolvePullCalledPromise();
+ },
+ type: 'bytes'
+ });
+
+ const memory = new WebAssembly.Memory({ initial: 1 });
+ // Make sure the backing buffers of both views have the same length
+ const byobView = new Uint8Array(new ArrayBuffer(memory.buffer.byteLength), 0, 1);
+ const newView = new Uint8Array(memory.buffer, byobView.byteOffset, byobView.byteLength);
+
+ const reader = rs.getReader({ mode: 'byob' });
+ reader.read(byobView).then(
+ t.unreached_func('read() should not resolve'),
+ t.unreached_func('read() should not reject')
+ );
+ await pullCalledPromise;
+
+ assert_throws_js(TypeError, () => byobRequest.respondWithNewView(newView));
+}, 'ReadableStream with byte source: respondWithNewView() with a non-transferable buffer');
diff --git a/testing/web-platform/tests/streams/readable-byte-streams/read-min.any.js b/testing/web-platform/tests/streams/readable-byte-streams/read-min.any.js
new file mode 100644
index 0000000000..4010e3750c
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/read-min.any.js
@@ -0,0 +1,774 @@
+// META: global=window,worker,shadowrealm
+// META: script=../resources/rs-utils.js
+// META: script=../resources/test-utils.js
+'use strict';
+
+// View buffers are detached after pull() returns, so record the information at the time that pull() was called.
+function extractViewInfo(view) {
+ return {
+ constructor: view.constructor,
+ bufferByteLength: view.buffer.byteLength,
+ byteOffset: view.byteOffset,
+ byteLength: view.byteLength
+ };
+}
+
+promise_test(async t => {
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.unreached_func('pull() should not be called'),
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ await promise_rejects_js(t, TypeError, reader.read(new Uint8Array(1), { min: 0 }));
+}, 'ReadableStream with byte source: read({ min }) rejects if min is 0');
+
+promise_test(async t => {
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.unreached_func('pull() should not be called'),
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ await promise_rejects_js(t, TypeError, reader.read(new Uint8Array(1), { min: -1 }));
+}, 'ReadableStream with byte source: read({ min }) rejects if min is negative');
+
+promise_test(async t => {
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.unreached_func('pull() should not be called'),
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ await promise_rejects_js(t, RangeError, reader.read(new Uint8Array(1), { min: 2 }));
+}, 'ReadableStream with byte source: read({ min }) rejects if min is larger than view\'s length (Uint8Array)');
+
+promise_test(async t => {
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.unreached_func('pull() should not be called'),
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ await promise_rejects_js(t, RangeError, reader.read(new Uint16Array(1), { min: 2 }));
+}, 'ReadableStream with byte source: read({ min }) rejects if min is larger than view\'s length (Uint16Array)');
+
+promise_test(async t => {
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.unreached_func('pull() should not be called'),
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ await promise_rejects_js(t, RangeError, reader.read(new DataView(new ArrayBuffer(1)), { min: 2 }));
+}, 'ReadableStream with byte source: read({ min }) rejects if min is larger than view\'s length (DataView)');
+
+promise_test(async t => {
+ let pullCount = 0;
+ const byobRequests = [];
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.step_func((c) => {
+ const byobRequest = c.byobRequest;
+ const view = byobRequest.view;
+ byobRequests[pullCount] = {
+ nonNull: byobRequest !== null,
+ viewNonNull: view !== null,
+ viewInfo: extractViewInfo(view)
+ };
+ if (pullCount === 0) {
+ view[0] = 0x01;
+ view[1] = 0x02;
+ byobRequest.respond(2);
+ } else if (pullCount === 1) {
+ view[0] = 0x03;
+ byobRequest.respond(1);
+ } else if (pullCount === 2) {
+ view[0] = 0x04;
+ byobRequest.respond(1);
+ }
+ ++pullCount;
+ })
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+ const read1 = reader.read(new Uint8Array(3), { min: 3 });
+ const read2 = reader.read(new Uint8Array(1));
+
+ const result1 = await read1;
+ assert_false(result1.done, 'first result should not be done');
+ assert_typed_array_equals(result1.value, new Uint8Array([0x01, 0x02, 0x03]), 'first result value');
+
+ const result2 = await read2;
+ assert_false(result2.done, 'second result should not be done');
+ assert_typed_array_equals(result2.value, new Uint8Array([0x04]), 'second result value');
+
+ assert_equals(pullCount, 3, 'pull() must have been called 3 times');
+
+ {
+ const byobRequest = byobRequests[0];
+ assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 3, 'first view.buffer.byteLength should be 3');
+ assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 3, 'first view.byteLength should be 3');
+ }
+
+ {
+ const byobRequest = byobRequests[1];
+ assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 3, 'second view.buffer.byteLength should be 3');
+ assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2');
+ assert_equals(viewInfo.byteLength, 1, 'second view.byteLength should be 1');
+ }
+
+ {
+ const byobRequest = byobRequests[2];
+ assert_true(byobRequest.nonNull, 'third byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'third byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'third view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 1, 'third view.buffer.byteLength should be 1');
+ assert_equals(viewInfo.byteOffset, 0, 'third view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 1, 'third view.byteLength should be 1');
+ }
+
+}, 'ReadableStream with byte source: read({ min }), then read()');
+
+promise_test(async t => {
+ let pullCount = 0;
+ const byobRequests = [];
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.step_func((c) => {
+ const byobRequest = c.byobRequest;
+ const view = byobRequest.view;
+ byobRequests[pullCount] = {
+ nonNull: byobRequest !== null,
+ viewNonNull: view !== null,
+ viewInfo: extractViewInfo(view)
+ };
+ if (pullCount === 0) {
+ view[0] = 0x01;
+ view[1] = 0x02;
+ byobRequest.respond(2);
+ } else if (pullCount === 1) {
+ view[0] = 0x03;
+ byobRequest.respond(1);
+ }
+ ++pullCount;
+ })
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+
+ const result = await reader.read(new DataView(new ArrayBuffer(3)), { min: 3 });
+ assert_false(result.done, 'result should not be done');
+ assert_equals(result.value.constructor, DataView, 'result.value must be a DataView');
+ assert_equals(result.value.byteOffset, 0, 'result.value.byteOffset');
+ assert_equals(result.value.byteLength, 3, 'result.value.byteLength');
+ assert_equals(result.value.buffer.byteLength, 3, 'result.value.buffer.byteLength');
+ assert_array_equals([...new Uint8Array(result.value.buffer)], [0x01, 0x02, 0x03], `result.value.buffer contents`);
+
+ assert_equals(pullCount, 2, 'pull() must have been called 2 times');
+
+ {
+ const byobRequest = byobRequests[0];
+ assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 3, 'first view.buffer.byteLength should be 3');
+ assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 3, 'first view.byteLength should be 3');
+ }
+
+ {
+ const byobRequest = byobRequests[1];
+ assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 3, 'second view.buffer.byteLength should be 3');
+ assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2');
+ assert_equals(viewInfo.byteLength, 1, 'second view.byteLength should be 1');
+ }
+
+}, 'ReadableStream with byte source: read({ min }) with a DataView');
+
+promise_test(async t => {
+ let pullCount = 0;
+ const byobRequests = [];
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start: t.step_func((c) => {
+ c.enqueue(new Uint8Array([0x01]));
+ }),
+ pull: t.step_func((c) => {
+ const byobRequest = c.byobRequest;
+ const view = byobRequest.view;
+ byobRequests[pullCount] = {
+ nonNull: byobRequest !== null,
+ viewNonNull: view !== null,
+ viewInfo: extractViewInfo(view)
+ };
+ if (pullCount === 0) {
+ view[0] = 0x02;
+ view[1] = 0x03;
+ byobRequest.respond(2);
+ }
+ ++pullCount;
+ })
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+
+ const result = await reader.read(new Uint8Array(3), { min: 3 });
+ assert_false(result.done, 'first result should not be done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03]), 'first result value');
+
+ assert_equals(pullCount, 1, 'pull() must have only been called once');
+
+ const byobRequest = byobRequests[0];
+ assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 3, 'first view.buffer.byteLength should be 3');
+ assert_equals(viewInfo.byteOffset, 1, 'first view.byteOffset should be 1');
+ assert_equals(viewInfo.byteLength, 2, 'first view.byteLength should be 2');
+
+}, 'ReadableStream with byte source: enqueue(), then read({ min })');
+
+promise_test(async t => {
+ let pullCount = 0;
+ const byobRequests = [];
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.step_func((c) => {
+ const byobRequest = c.byobRequest;
+ const view = byobRequest.view;
+ byobRequests[pullCount] = {
+ nonNull: byobRequest !== null,
+ viewNonNull: view !== null,
+ viewInfo: extractViewInfo(view)
+ };
+ if (pullCount === 0) {
+ c.enqueue(new Uint8Array([0x01, 0x02]));
+ } else if (pullCount === 1) {
+ c.enqueue(new Uint8Array([0x03]));
+ }
+ ++pullCount;
+ })
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+
+ const result = await reader.read(new Uint8Array(3), { min: 3 });
+ assert_false(result.done, 'first result should not be done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03]), 'first result value');
+
+ assert_equals(pullCount, 2, 'pull() must have been called 2 times');
+
+ {
+ const byobRequest = byobRequests[0];
+ assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 3, 'first view.buffer.byteLength should be 3');
+ assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 3, 'first view.byteLength should be 3');
+ }
+
+ {
+ const byobRequest = byobRequests[1];
+ assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 3, 'second view.buffer.byteLength should be 3');
+ assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2');
+ assert_equals(viewInfo.byteLength, 1, 'second view.byteLength should be 1');
+ }
+
+}, 'ReadableStream with byte source: read({ min: 3 }) on a 3-byte Uint8Array, then multiple enqueue() up to 3 bytes');
+
+promise_test(async t => {
+ let pullCount = 0;
+ const byobRequests = [];
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.step_func((c) => {
+ const byobRequest = c.byobRequest;
+ const view = byobRequest.view;
+ byobRequests[pullCount] = {
+ nonNull: byobRequest !== null,
+ viewNonNull: view !== null,
+ viewInfo: extractViewInfo(view)
+ };
+ if (pullCount === 0) {
+ c.enqueue(new Uint8Array([0x01, 0x02]));
+ } else if (pullCount === 1) {
+ c.enqueue(new Uint8Array([0x03]));
+ }
+ ++pullCount;
+ })
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+
+ const result = await reader.read(new Uint8Array(5), { min: 3 });
+ assert_false(result.done, 'first result should not be done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03, 0, 0]).subarray(0, 3), 'first result value');
+
+ assert_equals(pullCount, 2, 'pull() must have been called 2 times');
+
+ {
+ const byobRequest = byobRequests[0];
+ assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 5, 'first view.buffer.byteLength should be 5');
+ assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 5, 'first view.byteLength should be 5');
+ }
+
+ {
+ const byobRequest = byobRequests[1];
+ assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 5, 'second view.buffer.byteLength should be 5');
+ assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2');
+ assert_equals(viewInfo.byteLength, 3, 'second view.byteLength should be 3');
+ }
+
+}, 'ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 3 bytes');
+
+promise_test(async t => {
+ let pullCount = 0;
+ const byobRequests = [];
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.step_func((c) => {
+ const byobRequest = c.byobRequest;
+ const view = byobRequest.view;
+ byobRequests[pullCount] = {
+ nonNull: byobRequest !== null,
+ viewNonNull: view !== null,
+ viewInfo: extractViewInfo(view)
+ };
+ if (pullCount === 0) {
+ c.enqueue(new Uint8Array([0x01, 0x02]));
+ } else if (pullCount === 1) {
+ c.enqueue(new Uint8Array([0x03, 0x04]));
+ }
+ ++pullCount;
+ })
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+
+ const result = await reader.read(new Uint8Array(5), { min: 3 });
+ assert_false(result.done, 'first result should not be done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03, 0x04, 0]).subarray(0, 4), 'first result value');
+
+ assert_equals(pullCount, 2, 'pull() must have been called 2 times');
+
+ {
+ const byobRequest = byobRequests[0];
+ assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 5, 'first view.buffer.byteLength should be 5');
+ assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
+ assert_equals(viewInfo.byteLength, 5, 'first view.byteLength should be 5');
+ }
+
+ {
+ const byobRequest = byobRequests[1];
+ assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
+ assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
+ const viewInfo = byobRequest.viewInfo;
+ assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
+ assert_equals(viewInfo.bufferByteLength, 5, 'second view.buffer.byteLength should be 5');
+ assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2');
+ assert_equals(viewInfo.byteLength, 3, 'second view.byteLength should be 3');
+ }
+
+}, 'ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 4 bytes');
+
+promise_test(async t => {
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(16);
+ view[0] = 0x01;
+ view[8] = 0x02;
+ c.enqueue(view);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const byobReader = stream.getReader({ mode: 'byob' });
+ const result1 = await byobReader.read(new Uint8Array(8), { min: 8 });
+ assert_false(result1.done, 'result1.done');
+
+ const view1 = result1.value;
+ assert_equals(view1.constructor, Uint8Array, 'result1.value.constructor');
+ assert_equals(view1.buffer.byteLength, 8, 'result1.value.buffer.byteLength');
+ assert_equals(view1.byteOffset, 0, 'result1.value.byteOffset');
+ assert_equals(view1.byteLength, 8, 'result1.value.byteLength');
+ assert_equals(view1[0], 0x01, 'result1.value[0]');
+
+ byobReader.releaseLock();
+
+ const reader = stream.getReader();
+ const result2 = await reader.read();
+ assert_false(result2.done, 'result2.done');
+
+ const view2 = result2.value;
+ assert_equals(view2.constructor, Uint8Array, 'result2.value.constructor');
+ assert_equals(view2.buffer.byteLength, 16, 'result2.value.buffer.byteLength');
+ assert_equals(view2.byteOffset, 8, 'result2.value.byteOffset');
+ assert_equals(view2.byteLength, 8, 'result2.value.byteLength');
+ assert_equals(view2[0], 0x02, 'result2.value[0]');
+}, 'ReadableStream with byte source: enqueue(), read({ min }) partially, then read()');
+
+promise_test(async () => {
+ let pullCount = 0;
+ const byobRequestDefined = [];
+ let byobRequestViewDefined;
+
+ const stream = new ReadableStream({
+ async pull(c) {
+ byobRequestDefined.push(c.byobRequest !== null);
+ const initialByobRequest = c.byobRequest;
+
+ const transferredView = await transferArrayBufferView(c.byobRequest.view);
+ transferredView[0] = 0x01;
+ c.byobRequest.respondWithNewView(transferredView);
+
+ byobRequestDefined.push(c.byobRequest !== null);
+ byobRequestViewDefined = initialByobRequest.view !== null;
+
+ ++pullCount;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const result = await reader.read(new Uint8Array(1), { min: 1 });
+ assert_false(result.done, 'result.done');
+ assert_equals(result.value.byteLength, 1, 'result.value.byteLength');
+ assert_equals(result.value[0], 0x01, 'result.value[0]');
+ assert_equals(pullCount, 1, 'pull() should be called only once');
+ assert_true(byobRequestDefined[0], 'byobRequest must not be null before respondWithNewView()');
+ assert_false(byobRequestDefined[1], 'byobRequest must be null after respondWithNewView()');
+ assert_false(byobRequestViewDefined, 'view of initial byobRequest must be null after respondWithNewView()');
+}, 'ReadableStream with byte source: read({ min }), then respondWithNewView() with a transferred ArrayBuffer');
+
+promise_test(async t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.close();
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const result = await reader.read(new Uint8Array([0x01]), { min: 1 });
+ assert_true(result.done, 'result.done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01]).subarray(0, 0), 'result.value');
+
+ await reader.closed;
+}, 'ReadableStream with byte source: read({ min }) on a closed stream');
+
+promise_test(async t => {
+ let pullCount = 0;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.step_func((c) => {
+ if (pullCount === 0) {
+ c.byobRequest.view[0] = 0x01;
+ c.byobRequest.respond(1);
+ } else if (pullCount === 1) {
+ c.close();
+ c.byobRequest.respond(0);
+ }
+ ++pullCount;
+ })
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+
+ const result = await reader.read(new Uint8Array(3), { min: 3 });
+ assert_true(result.done, 'result.done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01, 0, 0]).subarray(0, 1), 'result.value');
+
+ assert_equals(pullCount, 2, 'pull() must have been called 2 times');
+
+ await reader.closed;
+}, 'ReadableStream with byte source: read({ min }) when closed before view is filled');
+
+promise_test(async t => {
+ let pullCount = 0;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.step_func((c) => {
+ if (pullCount === 0) {
+ c.byobRequest.view[0] = 0x01;
+ c.byobRequest.view[1] = 0x02;
+ c.byobRequest.respond(2);
+ } else if (pullCount === 1) {
+ c.byobRequest.view[0] = 0x03;
+ c.byobRequest.respond(1);
+ c.close();
+ }
+ ++pullCount;
+ })
+ });
+ const reader = rs.getReader({ mode: 'byob' });
+
+ const result = await reader.read(new Uint8Array(3), { min: 3 });
+ assert_false(result.done, 'result.done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03]), 'result.value');
+
+ assert_equals(pullCount, 2, 'pull() must have been called 2 times');
+
+ await reader.closed;
+}, 'ReadableStream with byte source: read({ min }) when closed immediately after view is filled');
+
+promise_test(async t => {
+ const error1 = new Error('error1');
+ const stream = new ReadableStream({
+ start(c) {
+ c.error(error1);
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const read = reader.read(new Uint8Array(1), { min: 1 });
+
+ await Promise.all([
+ promise_rejects_exactly(t, error1, read, 'read() must fail'),
+ promise_rejects_exactly(t, error1, reader.closed, 'closed must fail')
+ ]);
+}, 'ReadableStream with byte source: read({ min }) on an errored stream');
+
+promise_test(async t => {
+ const error1 = new Error('error1');
+ let controller;
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const read = reader.read(new Uint8Array(1), { min: 1 });
+
+ controller.error(error1);
+
+ await Promise.all([
+ promise_rejects_exactly(t, error1, read, 'read() must fail'),
+ promise_rejects_exactly(t, error1, reader.closed, 'closed must fail')
+ ]);
+}, 'ReadableStream with byte source: read({ min }), then error()');
+
+promise_test(t => {
+ let cancelCount = 0;
+ let reason;
+
+ const passedReason = new TypeError('foo');
+
+ const stream = new ReadableStream({
+ pull: t.unreached_func('pull() should not be called'),
+ cancel(r) {
+ if (cancelCount === 0) {
+ reason = r;
+ }
+
+ ++cancelCount;
+
+ return 'bar';
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const readPromise = reader.read(new Uint8Array(1), { min: 1 }).then(result => {
+ assert_true(result.done, 'result.done');
+ assert_equals(result.value, undefined, 'result.value');
+ });
+
+ const cancelPromise = reader.cancel(passedReason).then(result => {
+ assert_equals(result, undefined, 'cancel() return value should be fulfilled with undefined');
+ assert_equals(cancelCount, 1, 'cancel() should be called only once');
+ assert_equals(reason, passedReason, 'reason should equal the passed reason');
+ });
+
+ return Promise.all([readPromise, cancelPromise]);
+}, 'ReadableStream with byte source: getReader(), read({ min }), then cancel()');
+
+promise_test(async t => {
+ let pullCount = 0;
+ let byobRequest;
+ const viewInfos = [];
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull: t.step_func((c) => {
+ byobRequest = c.byobRequest;
+
+ viewInfos.push(extractViewInfo(c.byobRequest.view));
+ c.byobRequest.view[0] = 0x01;
+ c.byobRequest.respond(1);
+ viewInfos.push(extractViewInfo(c.byobRequest.view));
+
+ ++pullCount;
+ })
+ });
+
+ await Promise.resolve();
+ assert_equals(pullCount, 0, 'pull() must not have been called yet');
+
+ const reader = rs.getReader({ mode: 'byob' });
+ const read = reader.read(new Uint8Array(3), { min: 3 });
+ assert_equals(pullCount, 1, 'pull() must have been called once');
+ assert_not_equals(byobRequest, null, 'byobRequest should not be null');
+ assert_equals(viewInfos[0].byteLength, 3, 'byteLength before respond() should be 3');
+ assert_equals(viewInfos[1].byteLength, 2, 'byteLength after respond() should be 2');
+
+ reader.cancel().catch(t.unreached_func('cancel() should not reject'));
+
+ const result = await read;
+ assert_true(result.done, 'result.done');
+ assert_equals(result.value, undefined, 'result.value');
+
+ assert_equals(pullCount, 1, 'pull() must only be called once');
+
+ await reader.closed;
+}, 'ReadableStream with byte source: cancel() with partially filled pending read({ min }) request');
+
+promise_test(async () => {
+ let pullCalled = false;
+
+ const stream = new ReadableStream({
+ start(c) {
+ const view = new Uint8Array(16);
+ view[7] = 0x01;
+ view[15] = 0x02;
+ c.enqueue(view);
+ },
+ pull() {
+ pullCalled = true;
+ },
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ const result1 = await reader.read(new Uint8Array(8), { min: 8 });
+ assert_false(result1.done, 'result1.done');
+
+ const view1 = result1.value;
+ assert_equals(view1.byteOffset, 0, 'result1.value.byteOffset');
+ assert_equals(view1.byteLength, 8, 'result1.value.byteLength');
+ assert_equals(view1[7], 0x01, 'result1.value[7]');
+
+ const result2 = await reader.read(new Uint8Array(8), { min: 8 });
+ assert_false(pullCalled, 'pull() must not have been called');
+ assert_false(result2.done, 'result2.done');
+
+ const view2 = result2.value;
+ assert_equals(view2.byteOffset, 0, 'result2.value.byteOffset');
+ assert_equals(view2.byteLength, 8, 'result2.value.byteLength');
+ assert_equals(view2[7], 0x02, 'result2.value[7]');
+}, 'ReadableStream with byte source: enqueue(), then read({ min }) with smaller views');
+
+promise_test(async t => {
+ const stream = new ReadableStream({
+ start(c) {
+ c.enqueue(new Uint8Array([0xaa, 0xbb, 0xcc]));
+ c.close();
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+
+ await promise_rejects_js(t, TypeError, reader.read(new Uint16Array(2), { min: 2 }), 'read() must fail');
+ await promise_rejects_js(t, TypeError, reader.closed, 'reader.closed should reject');
+}, 'ReadableStream with byte source: 3 byte enqueue(), then close(), then read({ min }) with 2-element Uint16Array must fail');
+
+promise_test(async t => {
+ let controller;
+ const stream = new ReadableStream({
+ start(c) {
+ controller = c;
+ },
+ pull: t.unreached_func('pull() should not be called'),
+ type: 'bytes'
+ });
+
+ const reader = stream.getReader({ mode: 'byob' });
+ const readPromise = reader.read(new Uint16Array(2), { min: 2 });
+
+ controller.enqueue(new Uint8Array([0xaa, 0xbb, 0xcc]));
+ assert_throws_js(TypeError, () => controller.close(), 'controller.close() must throw');
+
+ await promise_rejects_js(t, TypeError, readPromise, 'read() must fail');
+ await promise_rejects_js(t, TypeError, reader.closed, 'reader.closed must reject');
+}, 'ReadableStream with byte source: read({ min }) with 2-element Uint16Array, then 3 byte enqueue(), then close() must fail');
+
+promise_test(async t => {
+ let pullCount = 0;
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start: t.step_func((c) => {
+ controller = c;
+ }),
+ pull: t.step_func((c) => {
+ ++pullCount;
+ })
+ });
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ await Promise.resolve();
+ assert_equals(pullCount, 0, 'pull() must not have been called yet');
+
+ const read1 = reader1.read(new Uint8Array(3), { min: 3 });
+ const read2 = reader2.read(new Uint8Array(1));
+
+ assert_equals(pullCount, 1, 'pull() must have been called once');
+ const byobRequest1 = controller.byobRequest;
+ assert_equals(byobRequest1.view.byteLength, 3, 'first byobRequest.view.byteLength should be 3');
+ byobRequest1.view[0] = 0x01;
+ byobRequest1.respond(1);
+
+ const result2 = await read2;
+ assert_false(result2.done, 'branch2 first read() should not be done');
+ assert_typed_array_equals(result2.value, new Uint8Array([0x01]), 'branch2 first read() value');
+
+ assert_equals(pullCount, 2, 'pull() must have been called 2 times');
+ const byobRequest2 = controller.byobRequest;
+ assert_equals(byobRequest2.view.byteLength, 2, 'second byobRequest.view.byteLength should be 2');
+ byobRequest2.view[0] = 0x02;
+ byobRequest2.view[1] = 0x03;
+ byobRequest2.respond(2);
+
+ const result1 = await read1;
+ assert_false(result1.done, 'branch1 read() should not be done');
+ assert_typed_array_equals(result1.value, new Uint8Array([0x01, 0x02, 0x03]), 'branch1 read() value');
+
+ const result3 = await reader2.read(new Uint8Array(2));
+ assert_equals(pullCount, 2, 'pull() must only be called 2 times');
+ assert_false(result3.done, 'branch2 second read() should not be done');
+ assert_typed_array_equals(result3.value, new Uint8Array([0x02, 0x03]), 'branch2 second read() value');
+}, 'ReadableStream with byte source: tee() with read({ min }) from branch1 and read() from branch2');
diff --git a/testing/web-platform/tests/streams/readable-byte-streams/respond-after-enqueue.any.js b/testing/web-platform/tests/streams/readable-byte-streams/respond-after-enqueue.any.js
new file mode 100644
index 0000000000..e51efa061a
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/respond-after-enqueue.any.js
@@ -0,0 +1,55 @@
+// META: global=window,worker,shadowrealm
+
+'use strict';
+
+// Repro for Blink bug https://crbug.com/1255762.
+promise_test(async () => {
+ const rs = new ReadableStream({
+ type: 'bytes',
+ autoAllocateChunkSize: 10,
+ pull(controller) {
+ controller.enqueue(new Uint8Array([1, 2, 3]));
+ controller.byobRequest.respond(10);
+ }
+ });
+
+ const reader = rs.getReader();
+ const {value, done} = await reader.read();
+ assert_false(done, 'done should not be true');
+ assert_array_equals(value, [1, 2, 3], 'value should be 3 bytes');
+}, 'byobRequest.respond() after enqueue() should not crash');
+
+promise_test(async () => {
+ const rs = new ReadableStream({
+ type: 'bytes',
+ autoAllocateChunkSize: 10,
+ pull(controller) {
+ const byobRequest = controller.byobRequest;
+ controller.enqueue(new Uint8Array([1, 2, 3]));
+ byobRequest.respond(10);
+ }
+ });
+
+ const reader = rs.getReader();
+ const {value, done} = await reader.read();
+ assert_false(done, 'done should not be true');
+ assert_array_equals(value, [1, 2, 3], 'value should be 3 bytes');
+}, 'byobRequest.respond() with cached byobRequest after enqueue() should not crash');
+
+promise_test(async () => {
+ const rs = new ReadableStream({
+ type: 'bytes',
+ autoAllocateChunkSize: 10,
+ pull(controller) {
+ controller.enqueue(new Uint8Array([1, 2, 3]));
+ controller.byobRequest.respond(2);
+ }
+ });
+
+ const reader = rs.getReader();
+ const [read1, read2] = await Promise.all([reader.read(), reader.read()]);
+ assert_false(read1.done, 'read1.done should not be true');
+ assert_array_equals(read1.value, [1, 2, 3], 'read1.value should be 3 bytes');
+ assert_false(read2.done, 'read2.done should not be true');
+ assert_array_equals(read2.value, [0, 0], 'read2.value should be 2 bytes');
+}, 'byobRequest.respond() after enqueue() with double read should not crash');
diff --git a/testing/web-platform/tests/streams/readable-byte-streams/tee.any.js b/testing/web-platform/tests/streams/readable-byte-streams/tee.any.js
new file mode 100644
index 0000000000..7dd5ba3f3f
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/tee.any.js
@@ -0,0 +1,936 @@
+// META: global=window,worker,shadowrealm
+// META: script=../resources/rs-utils.js
+// META: script=../resources/test-utils.js
+// META: script=../resources/recording-streams.js
+// META: script=../resources/rs-test-templates.js
+'use strict';
+
+test(() => {
+
+ const rs = new ReadableStream({ type: 'bytes' });
+ const result = rs.tee();
+
+ assert_true(Array.isArray(result), 'return value should be an array');
+ assert_equals(result.length, 2, 'array should have length 2');
+ assert_equals(result[0].constructor, ReadableStream, '0th element should be a ReadableStream');
+ assert_equals(result[1].constructor, ReadableStream, '1st element should be a ReadableStream');
+
+}, 'ReadableStream teeing with byte source: rs.tee() returns an array of two ReadableStreams');
+
+promise_test(async t => {
+
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start(c) {
+ c.enqueue(new Uint8Array([0x01]));
+ c.enqueue(new Uint8Array([0x02]));
+ c.close();
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader({ mode: 'byob' });
+ const reader2 = branch2.getReader({ mode: 'byob' });
+
+ reader2.closed.then(t.unreached_func('branch2 should not be closed'));
+
+ {
+ const result = await reader1.read(new Uint8Array(1));
+ assert_equals(result.done, false, 'done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01]), 'value');
+ }
+
+ {
+ const result = await reader1.read(new Uint8Array(1));
+ assert_equals(result.done, false, 'done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x02]), 'value');
+ }
+
+ {
+ const result = await reader1.read(new Uint8Array(1));
+ assert_equals(result.done, true, 'done');
+ assert_typed_array_equals(result.value, new Uint8Array([0]).subarray(0, 0), 'value');
+ }
+
+ {
+ const result = await reader2.read(new Uint8Array(1));
+ assert_equals(result.done, false, 'done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01]), 'value');
+ }
+
+ await reader1.closed;
+
+}, 'ReadableStream teeing with byte source: should be able to read one branch to the end without affecting the other');
+
+promise_test(async () => {
+
+ let pullCount = 0;
+ const enqueuedChunk = new Uint8Array([0x01]);
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull(c) {
+ ++pullCount;
+ if (pullCount === 1) {
+ c.enqueue(enqueuedChunk);
+ }
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader();
+ const reader2 = branch2.getReader();
+
+ const [result1, result2] = await Promise.all([reader1.read(), reader2.read()]);
+ assert_equals(result1.done, false, 'reader1 done');
+ assert_equals(result2.done, false, 'reader2 done');
+
+ const view1 = result1.value;
+ const view2 = result2.value;
+ assert_typed_array_equals(view1, new Uint8Array([0x01]), 'reader1 value');
+ assert_typed_array_equals(view2, new Uint8Array([0x01]), 'reader2 value');
+
+ assert_not_equals(view1.buffer, view2.buffer, 'chunks should have different buffers');
+ assert_not_equals(enqueuedChunk.buffer, view1.buffer, 'enqueued chunk and branch1\'s chunk should have different buffers');
+ assert_not_equals(enqueuedChunk.buffer, view2.buffer, 'enqueued chunk and branch2\'s chunk should have different buffers');
+
+}, 'ReadableStream teeing with byte source: chunks should be cloned for each branch');
+
+promise_test(async () => {
+
+ let pullCount = 0;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull(c) {
+ ++pullCount;
+ if (pullCount === 1) {
+ c.byobRequest.view[0] = 0x01;
+ c.byobRequest.respond(1);
+ }
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader({ mode: 'byob' });
+ const reader2 = branch2.getReader();
+ const buffer = new Uint8Array([42, 42, 42]).buffer;
+
+ {
+ const result = await reader1.read(new Uint8Array(buffer, 0, 1));
+ assert_equals(result.done, false, 'done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01, 42, 42]).subarray(0, 1), 'value');
+ }
+
+ {
+ const result = await reader2.read();
+ assert_equals(result.done, false, 'done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01]), 'value');
+ }
+
+}, 'ReadableStream teeing with byte source: chunks for BYOB requests from branch 1 should be cloned to branch 2');
+
+promise_test(async t => {
+
+ const theError = { name: 'boo!' };
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start(c) {
+ c.enqueue(new Uint8Array([0x01]));
+ c.enqueue(new Uint8Array([0x02]));
+ },
+ pull() {
+ throw theError;
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader({ mode: 'byob' });
+ const reader2 = branch2.getReader({ mode: 'byob' });
+
+ {
+ const result = await reader1.read(new Uint8Array(1));
+ assert_equals(result.done, false, 'first read from branch1 should not be done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x01]), 'first read from branch1');
+ }
+
+ {
+ const result = await reader1.read(new Uint8Array(1));
+ assert_equals(result.done, false, 'second read from branch1 should not be done');
+ assert_typed_array_equals(result.value, new Uint8Array([0x02]), 'second read from branch1');
+ }
+
+ await promise_rejects_exactly(t, theError, reader1.read(new Uint8Array(1)));
+ await promise_rejects_exactly(t, theError, reader2.read(new Uint8Array(1)));
+
+ await Promise.all([
+ promise_rejects_exactly(t, theError, reader1.closed),
+ promise_rejects_exactly(t, theError, reader2.closed)
+ ]);
+
+}, 'ReadableStream teeing with byte source: errors in the source should propagate to both branches');
+
+promise_test(async () => {
+
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start(c) {
+ c.enqueue(new Uint8Array([0x01]));
+ c.enqueue(new Uint8Array([0x02]));
+ c.close();
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ branch1.cancel();
+
+ const [chunks1, chunks2] = await Promise.all([readableStreamToArray(branch1), readableStreamToArray(branch2)]);
+ assert_array_equals(chunks1, [], 'branch1 should have no chunks');
+ assert_equals(chunks2.length, 2, 'branch2 should have two chunks');
+ assert_typed_array_equals(chunks2[0], new Uint8Array([0x01]), 'first chunk from branch2');
+ assert_typed_array_equals(chunks2[1], new Uint8Array([0x02]), 'second chunk from branch2');
+
+}, 'ReadableStream teeing with byte source: canceling branch1 should not impact branch2');
+
+promise_test(async () => {
+
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start(c) {
+ c.enqueue(new Uint8Array([0x01]));
+ c.enqueue(new Uint8Array([0x02]));
+ c.close();
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ branch2.cancel();
+
+ const [chunks1, chunks2] = await Promise.all([readableStreamToArray(branch1), readableStreamToArray(branch2)]);
+ assert_equals(chunks1.length, 2, 'branch1 should have two chunks');
+ assert_typed_array_equals(chunks1[0], new Uint8Array([0x01]), 'first chunk from branch1');
+ assert_typed_array_equals(chunks1[1], new Uint8Array([0x02]), 'second chunk from branch1');
+ assert_array_equals(chunks2, [], 'branch2 should have no chunks');
+
+}, 'ReadableStream teeing with byte source: canceling branch2 should not impact branch1');
+
+templatedRSTeeCancel('ReadableStream teeing with byte source', (extras) => {
+ return new ReadableStream({ type: 'bytes', ...extras });
+});
+
+promise_test(async () => {
+
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader({ mode: 'byob' });
+ const reader2 = branch2.getReader({ mode: 'byob' });
+
+ const promise = Promise.all([reader1.closed, reader2.closed]);
+
+ controller.close();
+
+ // The branches are created with HWM 0, so we need to read from at least one of them
+ // to observe the stream becoming closed.
+ const read1 = await reader1.read(new Uint8Array(1));
+ assert_equals(read1.done, true, 'first read from branch1 should be done');
+
+ await promise;
+
+}, 'ReadableStream teeing with byte source: closing the original should close the branches');
+
+promise_test(async t => {
+
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader({ mode: 'byob' });
+ const reader2 = branch2.getReader({ mode: 'byob' });
+
+ const theError = { name: 'boo!' };
+ const promise = Promise.all([
+ promise_rejects_exactly(t, theError, reader1.closed),
+ promise_rejects_exactly(t, theError, reader2.closed)
+ ]);
+
+ controller.error(theError);
+ await promise;
+
+}, 'ReadableStream teeing with byte source: erroring the original should immediately error the branches');
+
+promise_test(async t => {
+
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader();
+ const reader2 = branch2.getReader();
+
+ const theError = { name: 'boo!' };
+ const promise = Promise.all([
+ promise_rejects_exactly(t, theError, reader1.read()),
+ promise_rejects_exactly(t, theError, reader2.read())
+ ]);
+
+ controller.error(theError);
+ await promise;
+
+}, 'ReadableStream teeing with byte source: erroring the original should error pending reads from default reader');
+
+promise_test(async t => {
+
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader({ mode: 'byob' });
+ const reader2 = branch2.getReader({ mode: 'byob' });
+
+ const theError = { name: 'boo!' };
+ const promise = Promise.all([
+ promise_rejects_exactly(t, theError, reader1.read(new Uint8Array(1))),
+ promise_rejects_exactly(t, theError, reader2.read(new Uint8Array(1)))
+ ]);
+
+ controller.error(theError);
+ await promise;
+
+}, 'ReadableStream teeing with byte source: erroring the original should error pending reads from BYOB reader');
+
+promise_test(async () => {
+
+ let controller;
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader({ mode: 'byob' });
+ const reader2 = branch2.getReader({ mode: 'byob' });
+ const cancelPromise = reader2.cancel();
+
+ controller.enqueue(new Uint8Array([0x01]));
+
+ const read1 = await reader1.read(new Uint8Array(1));
+ assert_equals(read1.done, false, 'first read() from branch1 should not be done');
+ assert_typed_array_equals(read1.value, new Uint8Array([0x01]), 'first read() from branch1');
+
+ controller.close();
+
+ const read2 = await reader1.read(new Uint8Array(1));
+ assert_equals(read2.done, true, 'second read() from branch1 should be done');
+
+ await Promise.all([
+ reader1.closed,
+ cancelPromise
+ ]);
+
+}, 'ReadableStream teeing with byte source: canceling branch1 should finish when branch2 reads until end of stream');
+
+promise_test(async t => {
+
+ let controller;
+ const theError = { name: 'boo!' };
+ const rs = new ReadableStream({
+ type: 'bytes',
+ start(c) {
+ controller = c;
+ }
+ });
+
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader({ mode: 'byob' });
+ const reader2 = branch2.getReader({ mode: 'byob' });
+ const cancelPromise = reader2.cancel();
+
+ controller.error(theError);
+
+ await Promise.all([
+ promise_rejects_exactly(t, theError, reader1.read(new Uint8Array(1))),
+ cancelPromise
+ ]);
+
+}, 'ReadableStream teeing with byte source: canceling branch1 should finish when original stream errors');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+
+ // Create two branches, each with a HWM of 0. This should result in no chunks being pulled.
+ rs.tee();
+
+ await flushAsyncEvents();
+ assert_array_equals(rs.events, [], 'pull should not be called');
+
+}, 'ReadableStream teeing with byte source: should not pull any chunks if no branches are reading');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({
+ type: 'bytes',
+ pull(controller) {
+ controller.enqueue(new Uint8Array([0x01]));
+ }
+ });
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+ await Promise.all([
+ reader1.read(new Uint8Array(1)),
+ reader2.read(new Uint8Array(1))
+ ]);
+ assert_array_equals(rs.events, ['pull'], 'pull should be called once');
+
+}, 'ReadableStream teeing with byte source: should only pull enough to fill the emptiest queue');
+
+promise_test(async t => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+ const theError = { name: 'boo!' };
+
+ rs.controller.error(theError);
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ await flushAsyncEvents();
+ assert_array_equals(rs.events, [], 'pull should not be called');
+
+ await Promise.all([
+ promise_rejects_exactly(t, theError, reader1.closed),
+ promise_rejects_exactly(t, theError, reader2.closed)
+ ]);
+
+}, 'ReadableStream teeing with byte source: should not pull when original is already errored');
+
+for (const branch of [1, 2]) {
+ promise_test(async t => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+ const theError = { name: 'boo!' };
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ await flushAsyncEvents();
+ assert_array_equals(rs.events, [], 'pull should not be called');
+
+ const reader = (branch === 1) ? reader1 : reader2;
+ const read1 = reader.read(new Uint8Array(1));
+
+ await flushAsyncEvents();
+ assert_array_equals(rs.events, ['pull'], 'pull should be called once');
+
+ rs.controller.error(theError);
+
+ await Promise.all([
+ promise_rejects_exactly(t, theError, read1),
+ promise_rejects_exactly(t, theError, reader1.closed),
+ promise_rejects_exactly(t, theError, reader2.closed)
+ ]);
+
+ await flushAsyncEvents();
+ assert_array_equals(rs.events, ['pull'], 'pull should be called once');
+
+ }, `ReadableStream teeing with byte source: stops pulling when original stream errors while branch ${branch} is reading`);
+}
+
+promise_test(async t => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+ const theError = { name: 'boo!' };
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ await flushAsyncEvents();
+ assert_array_equals(rs.events, [], 'pull should not be called');
+
+ const read1 = reader1.read(new Uint8Array(1));
+ const read2 = reader2.read(new Uint8Array(1));
+
+ await flushAsyncEvents();
+ assert_array_equals(rs.events, ['pull'], 'pull should be called once');
+
+ rs.controller.error(theError);
+
+ await Promise.all([
+ promise_rejects_exactly(t, theError, read1),
+ promise_rejects_exactly(t, theError, read2),
+ promise_rejects_exactly(t, theError, reader1.closed),
+ promise_rejects_exactly(t, theError, reader2.closed)
+ ]);
+
+ await flushAsyncEvents();
+ assert_array_equals(rs.events, ['pull'], 'pull should be called once');
+
+}, 'ReadableStream teeing with byte source: stops pulling when original stream errors while both branches are reading');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ const read1 = reader1.read(new Uint8Array([0x11]));
+ const read2 = reader2.read(new Uint8Array([0x22]));
+
+ const cancel1 = reader1.cancel();
+ await flushAsyncEvents();
+ const cancel2 = reader2.cancel();
+
+ const result1 = await read1;
+ assert_object_equals(result1, { value: undefined, done: true });
+ const result2 = await read2;
+ assert_object_equals(result2, { value: undefined, done: true });
+
+ await Promise.all([cancel1, cancel2]);
+
+}, 'ReadableStream teeing with byte source: canceling both branches in sequence with delay');
+
+promise_test(async t => {
+
+ const theError = { name: 'boo!' };
+ const rs = new ReadableStream({
+ type: 'bytes',
+ cancel() {
+ throw theError;
+ }
+ });
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ const read1 = reader1.read(new Uint8Array([0x11]));
+ const read2 = reader2.read(new Uint8Array([0x22]));
+
+ const cancel1 = reader1.cancel();
+ await flushAsyncEvents();
+ const cancel2 = reader2.cancel();
+
+ const result1 = await read1;
+ assert_object_equals(result1, { value: undefined, done: true });
+ const result2 = await read2;
+ assert_object_equals(result2, { value: undefined, done: true });
+
+ await Promise.all([
+ promise_rejects_exactly(t, theError, cancel1),
+ promise_rejects_exactly(t, theError, cancel2)
+ ]);
+
+}, 'ReadableStream teeing with byte source: failing to cancel when canceling both branches in sequence with delay');
+
+promise_test(async () => {
+
+ let cancelResolve;
+ const cancelCalled = new Promise((resolve) => {
+ cancelResolve = resolve;
+ });
+ const rs = recordingReadableStream({
+ type: 'bytes',
+ cancel() {
+ cancelResolve();
+ }
+ });
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ const read1 = reader1.read(new Uint8Array([0x11]));
+ await flushAsyncEvents();
+ const read2 = reader2.read(new Uint8Array([0x22]));
+ await flushAsyncEvents();
+
+ // We are reading into branch1's buffer.
+ const byobRequest1 = rs.controller.byobRequest;
+ assert_not_equals(byobRequest1, null);
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array([0x11]), 'byobRequest1.view');
+
+ // Cancelling branch1 should not affect the BYOB request.
+ const cancel1 = reader1.cancel();
+ const result1 = await read1;
+ assert_equals(result1.done, true);
+ assert_equals(result1.value, undefined);
+ await flushAsyncEvents();
+ const byobRequest2 = rs.controller.byobRequest;
+ assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11]), 'byobRequest2.view');
+
+ // Cancelling branch1 should invalidate the BYOB request.
+ const cancel2 = reader2.cancel();
+ await cancelCalled;
+ const byobRequest3 = rs.controller.byobRequest;
+ assert_equals(byobRequest3, null);
+ const result2 = await read2;
+ assert_equals(result2.done, true);
+ assert_equals(result2.value, undefined);
+
+ await Promise.all([cancel1, cancel2]);
+
+}, 'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch1, cancel branch2');
+
+promise_test(async () => {
+
+ let cancelResolve;
+ const cancelCalled = new Promise((resolve) => {
+ cancelResolve = resolve;
+ });
+ const rs = recordingReadableStream({
+ type: 'bytes',
+ cancel() {
+ cancelResolve();
+ }
+ });
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ const read1 = reader1.read(new Uint8Array([0x11]));
+ await flushAsyncEvents();
+ const read2 = reader2.read(new Uint8Array([0x22]));
+ await flushAsyncEvents();
+
+ // We are reading into branch1's buffer.
+ const byobRequest1 = rs.controller.byobRequest;
+ assert_not_equals(byobRequest1, null);
+ assert_typed_array_equals(byobRequest1.view, new Uint8Array([0x11]), 'byobRequest1.view');
+
+ // Cancelling branch2 should not affect the BYOB request.
+ const cancel2 = reader2.cancel();
+ const result2 = await read2;
+ assert_equals(result2.done, true);
+ assert_equals(result2.value, undefined);
+ await flushAsyncEvents();
+ const byobRequest2 = rs.controller.byobRequest;
+ assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11]), 'byobRequest2.view');
+
+ // Cancelling branch1 should invalidate the BYOB request.
+ const cancel1 = reader1.cancel();
+ await cancelCalled;
+ const byobRequest3 = rs.controller.byobRequest;
+ assert_equals(byobRequest3, null);
+ const result1 = await read1;
+ assert_equals(result1.done, true);
+ assert_equals(result1.value, undefined);
+
+ await Promise.all([cancel1, cancel2]);
+
+}, 'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch2, cancel branch1');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ const read1 = reader1.read(new Uint8Array([0x11]));
+ await flushAsyncEvents();
+ const read2 = reader2.read(new Uint8Array([0x22]));
+ await flushAsyncEvents();
+
+ // We are reading into branch1's buffer.
+ assert_typed_array_equals(rs.controller.byobRequest.view, new Uint8Array([0x11]), 'first byobRequest.view');
+
+ // Cancelling branch2 should not affect the BYOB request.
+ reader2.cancel();
+ const result2 = await read2;
+ assert_equals(result2.done, true);
+ assert_equals(result2.value, undefined);
+ await flushAsyncEvents();
+ assert_typed_array_equals(rs.controller.byobRequest.view, new Uint8Array([0x11]), 'second byobRequest.view');
+
+ // Respond to the BYOB request.
+ rs.controller.byobRequest.view[0] = 0x33;
+ rs.controller.byobRequest.respond(1);
+
+ // branch1 should receive the read chunk.
+ const result1 = await read1;
+ assert_equals(result1.done, false);
+ assert_typed_array_equals(result1.value, new Uint8Array([0x33]), 'first read() from branch1');
+
+}, 'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch2, enqueue to branch1');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ const read1 = reader1.read(new Uint8Array([0x11]));
+ await flushAsyncEvents();
+ const read2 = reader2.read(new Uint8Array([0x22]));
+ await flushAsyncEvents();
+
+ // We are reading into branch1's buffer.
+ assert_typed_array_equals(rs.controller.byobRequest.view, new Uint8Array([0x11]), 'first byobRequest.view');
+
+ // Cancelling branch1 should not affect the BYOB request.
+ reader1.cancel();
+ const result1 = await read1;
+ assert_equals(result1.done, true);
+ assert_equals(result1.value, undefined);
+ await flushAsyncEvents();
+ assert_typed_array_equals(rs.controller.byobRequest.view, new Uint8Array([0x11]), 'second byobRequest.view');
+
+ // Respond to the BYOB request.
+ rs.controller.byobRequest.view[0] = 0x33;
+ rs.controller.byobRequest.respond(1);
+
+ // branch2 should receive the read chunk.
+ const result2 = await read2;
+ assert_equals(result2.done, false);
+ assert_typed_array_equals(result2.value, new Uint8Array([0x33]), 'first read() from branch2');
+
+}, 'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch1, respond to branch2');
+
+promise_test(async () => {
+
+ let pullCount = 0;
+ const byobRequestDefined = [];
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull(c) {
+ ++pullCount;
+ byobRequestDefined.push(c.byobRequest !== null);
+ c.enqueue(new Uint8Array([pullCount]));
+ }
+ });
+
+ const [branch1, _] = rs.tee();
+ const reader1 = branch1.getReader({ mode: 'byob' });
+
+ const result1 = await reader1.read(new Uint8Array([0x11]));
+ assert_equals(result1.done, false, 'first read should not be done');
+ assert_typed_array_equals(result1.value, new Uint8Array([0x1]), 'first read');
+ assert_equals(pullCount, 1, 'pull() should be called once');
+ assert_equals(byobRequestDefined[0], true, 'should have created a BYOB request for first read');
+
+ reader1.releaseLock();
+ const reader2 = branch1.getReader();
+
+ const result2 = await reader2.read();
+ assert_equals(result2.done, false, 'second read should not be done');
+ assert_typed_array_equals(result2.value, new Uint8Array([0x2]), 'second read');
+ assert_equals(pullCount, 2, 'pull() should be called twice');
+ assert_equals(byobRequestDefined[1], false, 'should not have created a BYOB request for second read');
+
+}, 'ReadableStream teeing with byte source: pull with BYOB reader, then pull with default reader');
+
+promise_test(async () => {
+
+ let pullCount = 0;
+ const byobRequestDefined = [];
+ const rs = new ReadableStream({
+ type: 'bytes',
+ pull(c) {
+ ++pullCount;
+ byobRequestDefined.push(c.byobRequest !== null);
+ c.enqueue(new Uint8Array([pullCount]));
+ }
+ });
+
+ const [branch1, _] = rs.tee();
+ const reader1 = branch1.getReader();
+
+ const result1 = await reader1.read();
+ assert_equals(result1.done, false, 'first read should not be done');
+ assert_typed_array_equals(result1.value, new Uint8Array([0x1]), 'first read');
+ assert_equals(pullCount, 1, 'pull() should be called once');
+ assert_equals(byobRequestDefined[0], false, 'should not have created a BYOB request for first read');
+
+ reader1.releaseLock();
+ const reader2 = branch1.getReader({ mode: 'byob' });
+
+ const result2 = await reader2.read(new Uint8Array([0x22]));
+ assert_equals(result2.done, false, 'second read should not be done');
+ assert_typed_array_equals(result2.value, new Uint8Array([0x2]), 'second read');
+ assert_equals(pullCount, 2, 'pull() should be called twice');
+ assert_equals(byobRequestDefined[1], true, 'should have created a BYOB request for second read');
+
+}, 'ReadableStream teeing with byte source: pull with default reader, then pull with BYOB reader');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({
+ type: 'bytes'
+ });
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+
+ // Wait for each branch's start() promise to resolve.
+ await flushAsyncEvents();
+
+ const read2 = reader2.read(new Uint8Array([0x22]));
+ const read1 = reader1.read(new Uint8Array([0x11]));
+ await flushAsyncEvents();
+
+ // branch2 should provide the BYOB request.
+ const byobRequest = rs.controller.byobRequest;
+ assert_typed_array_equals(byobRequest.view, new Uint8Array([0x22]), 'first BYOB request');
+ byobRequest.view[0] = 0x01;
+ byobRequest.respond(1);
+
+ const result1 = await read1;
+ assert_equals(result1.done, false, 'first read should not be done');
+ assert_typed_array_equals(result1.value, new Uint8Array([0x1]), 'first read');
+
+ const result2 = await read2;
+ assert_equals(result2.done, false, 'second read should not be done');
+ assert_typed_array_equals(result2.value, new Uint8Array([0x1]), 'second read');
+
+}, 'ReadableStream teeing with byte source: read from branch2, then read from branch1');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader();
+ const reader2 = branch2.getReader({ mode: 'byob' });
+ await flushAsyncEvents();
+
+ const read1 = reader1.read();
+ const read2 = reader2.read(new Uint8Array([0x22]));
+ await flushAsyncEvents();
+
+ // There should be no BYOB request.
+ assert_equals(rs.controller.byobRequest, null, 'first BYOB request');
+
+ // Close the stream.
+ rs.controller.close();
+
+ const result1 = await read1;
+ assert_equals(result1.done, true, 'read from branch1 should be done');
+ assert_equals(result1.value, undefined, 'read from branch1');
+
+ // branch2 should get its buffer back.
+ const result2 = await read2;
+ assert_equals(result2.done, true, 'read from branch2 should be done');
+ assert_typed_array_equals(result2.value, new Uint8Array([0x22]).subarray(0, 0), 'read from branch2');
+
+}, 'ReadableStream teeing with byte source: read from branch1 with default reader, then close while branch2 has pending BYOB read');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+ const [branch1, branch2] = rs.tee();
+ const reader1 = branch1.getReader({ mode: 'byob' });
+ const reader2 = branch2.getReader();
+ await flushAsyncEvents();
+
+ const read2 = reader2.read();
+ const read1 = reader1.read(new Uint8Array([0x11]));
+ await flushAsyncEvents();
+
+ // There should be no BYOB request.
+ assert_equals(rs.controller.byobRequest, null, 'first BYOB request');
+
+ // Close the stream.
+ rs.controller.close();
+
+ const result2 = await read2;
+ assert_equals(result2.done, true, 'read from branch2 should be done');
+ assert_equals(result2.value, undefined, 'read from branch2');
+
+ // branch1 should get its buffer back.
+ const result1 = await read1;
+ assert_equals(result1.done, true, 'read from branch1 should be done');
+ assert_typed_array_equals(result1.value, new Uint8Array([0x11]).subarray(0, 0), 'read from branch1');
+
+}, 'ReadableStream teeing with byte source: read from branch2 with default reader, then close while branch1 has pending BYOB read');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+ await flushAsyncEvents();
+
+ const read1 = reader1.read(new Uint8Array([0x11]));
+ const read2 = reader2.read(new Uint8Array([0x22]));
+ await flushAsyncEvents();
+
+ // branch1 should provide the BYOB request.
+ const byobRequest = rs.controller.byobRequest;
+ assert_typed_array_equals(byobRequest.view, new Uint8Array([0x11]), 'first BYOB request');
+
+ // Close the stream.
+ rs.controller.close();
+ byobRequest.respond(0);
+
+ // Both branches should get their buffers back.
+ const result1 = await read1;
+ assert_equals(result1.done, true, 'first read should be done');
+ assert_typed_array_equals(result1.value, new Uint8Array([0x11]).subarray(0, 0), 'first read');
+
+ const result2 = await read2;
+ assert_equals(result2.done, true, 'second read should be done');
+ assert_typed_array_equals(result2.value, new Uint8Array([0x22]).subarray(0, 0), 'second read');
+
+}, 'ReadableStream teeing with byte source: close when both branches have pending BYOB reads');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader());
+ const branch1Reads = [reader1.read(), reader1.read()];
+ const branch2Reads = [reader2.read(), reader2.read()];
+
+ await flushAsyncEvents();
+ rs.controller.enqueue(new Uint8Array([0x11]));
+ rs.controller.close();
+
+ const result1 = await branch1Reads[0];
+ assert_equals(result1.done, false, 'first read() from branch1 should be not done');
+ assert_typed_array_equals(result1.value, new Uint8Array([0x11]), 'first chunk from branch1 should be correct');
+ const result2 = await branch2Reads[0];
+ assert_equals(result2.done, false, 'first read() from branch2 should be not done');
+ assert_typed_array_equals(result2.value, new Uint8Array([0x11]), 'first chunk from branch2 should be correct');
+
+ assert_object_equals(await branch1Reads[1], { value: undefined, done: true }, 'second read() from branch1 should be done');
+ assert_object_equals(await branch2Reads[1], { value: undefined, done: true }, 'second read() from branch2 should be done');
+
+}, 'ReadableStream teeing with byte source: enqueue() and close() while both branches are pulling');
+
+promise_test(async () => {
+
+ const rs = recordingReadableStream({ type: 'bytes' });
+
+ const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
+ const branch1Reads = [reader1.read(new Uint8Array(1)), reader1.read(new Uint8Array(1))];
+ const branch2Reads = [reader2.read(new Uint8Array(1)), reader2.read(new Uint8Array(1))];
+
+ await flushAsyncEvents();
+ rs.controller.byobRequest.view[0] = 0x11;
+ rs.controller.byobRequest.respond(1);
+ rs.controller.close();
+
+ const result1 = await branch1Reads[0];
+ assert_equals(result1.done, false, 'first read() from branch1 should be not done');
+ assert_typed_array_equals(result1.value, new Uint8Array([0x11]), 'first chunk from branch1 should be correct');
+ const result2 = await branch2Reads[0];
+ assert_equals(result2.done, false, 'first read() from branch2 should be not done');
+ assert_typed_array_equals(result2.value, new Uint8Array([0x11]), 'first chunk from branch2 should be correct');
+
+ const result3 = await branch1Reads[1];
+ assert_equals(result3.done, true, 'second read() from branch1 should be done');
+ assert_typed_array_equals(result3.value, new Uint8Array([0]).subarray(0, 0), 'second chunk from branch1 should be correct');
+ const result4 = await branch2Reads[1];
+ assert_equals(result4.done, true, 'second read() from branch2 should be done');
+ assert_typed_array_equals(result4.value, new Uint8Array([0]).subarray(0, 0), 'second chunk from branch2 should be correct');
+
+}, 'ReadableStream teeing with byte source: respond() and close() while both branches are pulling');