summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/streams/readable-byte-streams/tee.any.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/web-platform/tests/streams/readable-byte-streams/tee.any.js
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/streams/readable-byte-streams/tee.any.js')
-rw-r--r--testing/web-platform/tests/streams/readable-byte-streams/tee.any.js936
1 files changed, 936 insertions, 0 deletions
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..85844669cd
--- /dev/null
+++ b/testing/web-platform/tests/streams/readable-byte-streams/tee.any.js
@@ -0,0 +1,936 @@
+// META: global=window,worker
+// 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');