diff options
Diffstat (limited to 'testing/web-platform/tests/streams/readable-byte-streams')
7 files changed, 4422 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..3322116b19 --- /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 +'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..8d460a1c81 --- /dev/null +++ b/testing/web-platform/tests/streams/readable-byte-streams/construct-byob-request.any.js @@ -0,0 +1,53 @@ +// META: global=window,worker +// 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..d2b37f00a9 --- /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 + +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..dd4fdc8557 --- /dev/null +++ b/testing/web-platform/tests/streams/readable-byte-streams/general.any.js @@ -0,0 +1,2901 @@ +// META: global=window,worker +// 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..e8ea3c4f96 --- /dev/null +++ b/testing/web-platform/tests/streams/readable-byte-streams/non-transferable-buffers.any.js @@ -0,0 +1,58 @@ +// META: global=window,worker +'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'); + +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/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..b93cec9739 --- /dev/null +++ b/testing/web-platform/tests/streams/readable-byte-streams/respond-after-enqueue.any.js @@ -0,0 +1,55 @@ +// META: global=window,worker + +'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..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'); |