Upload Kmake

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

View File

@ -0,0 +1,398 @@
// META: global=window,worker,shadowrealm
'use strict';
promise_test(() => {
const stream = new ReadableStream({
start(c) {
c.close();
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const view = new Uint8Array([1, 2, 3]);
return reader.read(view).then(({ value, done }) => {
// Sanity checks
assert_true(value instanceof Uint8Array, 'The value read must be a Uint8Array');
assert_not_equals(value, view, 'The value read must not be the *same* Uint8Array');
assert_array_equals(value, [], 'The value read must be an empty Uint8Array, since the stream is closed');
assert_true(done, 'done must be true, since the stream is closed');
// The important assertions
assert_not_equals(value.buffer, view.buffer, 'a different ArrayBuffer must underlie the value');
assert_equals(view.buffer.byteLength, 0, 'the original buffer must be detached');
});
}, 'ReadableStream with byte source: read()ing from a closed stream still transfers the buffer');
promise_test(() => {
const stream = new ReadableStream({
start(c) {
c.enqueue(new Uint8Array([1, 2, 3]));
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const view = new Uint8Array([4, 5, 6]);
return reader.read(view).then(({ value, done }) => {
// Sanity checks
assert_true(value instanceof Uint8Array, 'The value read must be a Uint8Array');
assert_not_equals(value, view, 'The value read must not be the *same* Uint8Array');
assert_array_equals(value, [1, 2, 3], 'The value read must be the enqueued Uint8Array, not the original values');
assert_false(done, 'done must be false, since the stream is not closed');
// The important assertions
assert_not_equals(value.buffer, view.buffer, 'a different ArrayBuffer must underlie the value');
assert_equals(view.buffer.byteLength, 0, 'the original buffer must be detached');
});
}, 'ReadableStream with byte source: read()ing from a stream with queued chunks still transfers the buffer');
test(() => {
new ReadableStream({
start(c) {
const view = new Uint8Array([1, 2, 3]);
c.enqueue(view);
assert_throws_js(TypeError, () => c.enqueue(view));
},
type: 'bytes'
});
}, 'ReadableStream with byte source: enqueuing an already-detached buffer throws');
test(() => {
new ReadableStream({
start(c) {
const view = new Uint8Array([]);
assert_throws_js(TypeError, () => c.enqueue(view));
},
type: 'bytes'
});
}, 'ReadableStream with byte source: enqueuing a zero-length buffer throws');
test(() => {
new ReadableStream({
start(c) {
const view = new Uint8Array(new ArrayBuffer(10), 0, 0);
assert_throws_js(TypeError, () => c.enqueue(view));
},
type: 'bytes'
});
}, 'ReadableStream with byte source: enqueuing a zero-length view on a non-zero-length buffer throws');
promise_test(t => {
const stream = new ReadableStream({
start(c) {
c.enqueue(new Uint8Array([1, 2, 3]));
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const view = new Uint8Array([4, 5, 6]);
return reader.read(view).then(() => {
// view is now detached
return promise_rejects_js(t, TypeError, reader.read(view));
});
}, 'ReadableStream with byte source: reading into an already-detached buffer rejects');
promise_test(t => {
const stream = new ReadableStream({
start(c) {
c.enqueue(new Uint8Array([1, 2, 3]));
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const view = new Uint8Array();
return promise_rejects_js(t, TypeError, reader.read(view));
}, 'ReadableStream with byte source: reading into a zero-length buffer rejects');
promise_test(t => {
const stream = new ReadableStream({
start(c) {
c.enqueue(new Uint8Array([1, 2, 3]));
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const view = new Uint8Array(new ArrayBuffer(10), 0, 0);
return promise_rejects_js(t, TypeError, reader.read(view));
}, 'ReadableStream with byte source: reading into a zero-length view on a non-zero-length buffer rejects');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
// Detach it by reading into it
reader.read(c.byobRequest.view);
assert_throws_js(TypeError, () => c.byobRequest.respond(1),
'respond() must throw if the corresponding view has become detached');
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respond() throws if the BYOB request\'s buffer has been detached (in the ' +
'readable state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
c.close();
// Detach it by reading into it
reader.read(c.byobRequest.view);
assert_throws_js(TypeError, () => c.byobRequest.respond(0),
'respond() must throw if the corresponding view has become detached');
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respond() throws if the BYOB request\'s buffer has been detached (in the ' +
'closed state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
// Detach it by reading into it
const view = new Uint8Array([1, 2, 3]);
reader.read(view);
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has been detached ' +
'(in the readable state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array();
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer is zero-length ' +
'(in the readable state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array(c.byobRequest.view.buffer, 0, 0);
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view is zero-length on a ' +
'non-zero-length buffer (in the readable state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = c.byobRequest.view.subarray(1, 2);
assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view has a different offset ' +
'(in the readable state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
c.close();
const view = c.byobRequest.view.subarray(1, 1);
assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view has a different offset ' +
'(in the closed state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array(new ArrayBuffer(10), 0, 3);
assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has a ' +
'different length (in the readable state)');
async_test(t => {
// Tests https://github.com/nodejs/node/issues/41886
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array(new ArrayBuffer(11), 0, 3);
assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes',
autoAllocateChunkSize: 10
});
const reader = stream.getReader();
reader.read();
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has a ' +
'different length (autoAllocateChunkSize)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array(c.byobRequest.view.buffer, 0, 4);
view[0] = 20;
view[1] = 21;
view[2] = 22;
view[3] = 23;
assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const buffer = new ArrayBuffer(10);
const view = new Uint8Array(buffer, 0, 3);
view[0] = 10;
view[1] = 11;
view[2] = 12;
reader.read(view);
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view has a larger length ' +
'(in the readable state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
c.close();
// Detach it by reading into it
const view = new Uint8Array([1, 2, 3]);
reader.read(view);
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has been detached ' +
'(in the closed state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array();
c.close();
assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer is zero-length ' +
'(in the closed state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array(c.byobRequest.view.buffer, 0, 1);
c.close();
assert_throws_js(TypeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view is non-zero-length ' +
'(in the closed state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
const view = new Uint8Array(new ArrayBuffer(10), 0, 0);
c.close();
assert_throws_js(RangeError, () => c.byobRequest.respondWithNewView(view));
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: respondWithNewView() throws if the supplied view\'s buffer has a ' +
'different length (in the closed state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
// Detach it by reading into it
reader.read(c.byobRequest.view);
assert_throws_js(TypeError, () => c.enqueue(new Uint8Array([1])),
'enqueue() must throw if the BYOB request\'s buffer has become detached');
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: enqueue() throws if the BYOB request\'s buffer has been detached (in the ' +
'readable state)');
async_test(t => {
const stream = new ReadableStream({
pull: t.step_func_done(c => {
c.close();
// Detach it by reading into it
reader.read(c.byobRequest.view);
assert_throws_js(TypeError, () => c.enqueue(new Uint8Array([1])),
'enqueue() must throw if the BYOB request\'s buffer has become detached');
}),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
reader.read(new Uint8Array([4, 5, 6]));
}, 'ReadableStream with byte source: enqueue() throws if the BYOB request\'s buffer has been detached (in the ' +
'closed state)');

View File

@ -0,0 +1,53 @@
// META: global=window,worker,shadowrealm
// META: script=../resources/rs-utils.js
'use strict';
// Prior to whatwg/stream#870 it was possible to construct a ReadableStreamBYOBRequest directly. This made it possible
// to construct requests that were out-of-sync with the state of the ReadableStream. They could then be used to call
// internal operations, resulting in asserts or bad behaviour. This file contains regression tests for the change.
function getRealByteStreamController() {
let controller;
new ReadableStream({
start(c) {
controller = c;
},
type: 'bytes'
});
return controller;
}
// Create an object pretending to have prototype |prototype|, of type |type|. |type| is one of "undefined", "null",
// "fake", or "real". "real" will call the realObjectCreator function to get a real instance of the object.
function createDummyObject(prototype, type, realObjectCreator) {
switch (type) {
case 'undefined':
return undefined;
case 'null':
return null;
case 'fake':
return Object.create(prototype);
case 'real':
return realObjectCreator();
}
throw new Error('not reached');
}
const dummyTypes = ['undefined', 'null', 'fake', 'real'];
for (const controllerType of dummyTypes) {
const controller = createDummyObject(ReadableByteStreamController.prototype, controllerType,
getRealByteStreamController);
for (const viewType of dummyTypes) {
const view = createDummyObject(Uint8Array.prototype, viewType, () => new Uint8Array(16));
test(() => {
assert_throws_js(TypeError, () => new ReadableStreamBYOBRequest(controller, view),
'constructor should throw');
}, `ReadableStreamBYOBRequest constructor should throw when passed a ${controllerType} ` +
`ReadableByteStreamController and a ${viewType} view`);
}
}

View File

@ -0,0 +1,21 @@
// META: global=window,worker,shadowrealm
promise_test(async t => {
const error = new Error('cannot proceed');
const rs = new ReadableStream({
type: 'bytes',
pull: t.step_func((controller) => {
const buffer = controller.byobRequest.view.buffer;
// Detach the buffer.
structuredClone(buffer, { transfer: [buffer] });
// Try to enqueue with a new buffer.
assert_throws_js(TypeError, () => controller.enqueue(new Uint8Array([42])));
// If we got here the test passed.
controller.error(error);
})
});
const reader = rs.getReader({ mode: 'byob' });
await promise_rejects_exactly(t, error, reader.read(new Uint8Array(1)));
}, 'enqueue after detaching byobRequest.view.buffer should throw');

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
// META: global=window,worker,shadowrealm
'use strict';
promise_test(async t => {
const rs = new ReadableStream({
pull: t.unreached_func('pull() should not be called'),
type: 'bytes'
});
const reader = rs.getReader({ mode: 'byob' });
const memory = new WebAssembly.Memory({ initial: 1 });
const view = new Uint8Array(memory.buffer, 0, 1);
await promise_rejects_js(t, TypeError, reader.read(view));
}, 'ReadableStream with byte source: read() with a non-transferable buffer');
promise_test(async t => {
const rs = new ReadableStream({
pull: t.unreached_func('pull() should not be called'),
type: 'bytes'
});
const reader = rs.getReader({ mode: 'byob' });
const memory = new WebAssembly.Memory({ initial: 1 });
const view = new Uint8Array(memory.buffer, 0, 1);
await promise_rejects_js(t, TypeError, reader.read(view, { min: 1 }));
}, 'ReadableStream with byte source: fill() with a non-transferable buffer');
test(t => {
let controller;
const rs = new ReadableStream({
start(c) {
controller = c;
},
pull: t.unreached_func('pull() should not be called'),
type: 'bytes'
});
const memory = new WebAssembly.Memory({ initial: 1 });
const view = new Uint8Array(memory.buffer, 0, 1);
assert_throws_js(TypeError, () => controller.enqueue(view));
}, 'ReadableStream with byte source: enqueue() with a non-transferable buffer');
promise_test(async t => {
let byobRequest;
let resolvePullCalledPromise;
const pullCalledPromise = new Promise(resolve => {
resolvePullCalledPromise = resolve;
});
const rs = new ReadableStream({
pull(controller) {
byobRequest = controller.byobRequest;
resolvePullCalledPromise();
},
type: 'bytes'
});
const memory = new WebAssembly.Memory({ initial: 1 });
// Make sure the backing buffers of both views have the same length
const byobView = new Uint8Array(new ArrayBuffer(memory.buffer.byteLength), 0, 1);
const newView = new Uint8Array(memory.buffer, byobView.byteOffset, byobView.byteLength);
const reader = rs.getReader({ mode: 'byob' });
reader.read(byobView).then(
t.unreached_func('read() should not resolve'),
t.unreached_func('read() should not reject')
);
await pullCalledPromise;
assert_throws_js(TypeError, () => byobRequest.respondWithNewView(newView));
}, 'ReadableStream with byte source: respondWithNewView() with a non-transferable buffer');

View File

@ -0,0 +1,54 @@
// META: global=window,worker,shadowrealm
// META: script=../resources/test-utils.js
'use strict';
// Tests which patch the global environment are kept separate to avoid
// interfering with other tests.
promise_test(async (t) => {
let controller;
const rs = new ReadableStream({
type: 'bytes',
start(c) {
controller = c;
}
});
const reader = rs.getReader({mode: 'byob'});
const length = 0x4000;
const buffer = new ArrayBuffer(length);
const bigArray = new BigUint64Array(buffer, length - 8, 1);
const read1 = reader.read(new Uint8Array(new ArrayBuffer(0x100)));
const read2 = reader.read(bigArray);
let flag = false;
Object.defineProperty(Object.prototype, 'then', {
get: t.step_func(() => {
if (!flag) {
flag = true;
assert_equals(controller.byobRequest, null, 'byobRequest should be null after filling both views');
}
}),
configurable: true
});
t.add_cleanup(() => {
delete Object.prototype.then;
});
controller.enqueue(new Uint8Array(0x110).fill(0x42));
assert_true(flag, 'patched then() should be called');
// The first read() is filled entirely with 0x100 bytes
const result1 = await read1;
assert_false(result1.done, 'result1.done');
assert_typed_array_equals(result1.value, new Uint8Array(0x100).fill(0x42), 'result1.value');
// The second read() is filled with the remaining 0x10 bytes
const result2 = await read2;
assert_false(result2.done, 'result2.done');
assert_equals(result2.value.constructor, BigUint64Array, 'result2.value constructor');
assert_equals(result2.value.byteOffset, length - 8, 'result2.value byteOffset');
assert_equals(result2.value.length, 1, 'result2.value length');
assert_array_equals([...result2.value], [0x42424242_42424242n], 'result2.value contents');
}, 'Patched then() sees byobRequest after filling all pending pull-into descriptors');

View File

@ -0,0 +1,774 @@
// META: global=window,worker,shadowrealm
// META: script=../resources/rs-utils.js
// META: script=../resources/test-utils.js
'use strict';
// View buffers are detached after pull() returns, so record the information at the time that pull() was called.
function extractViewInfo(view) {
return {
constructor: view.constructor,
bufferByteLength: view.buffer.byteLength,
byteOffset: view.byteOffset,
byteLength: view.byteLength
};
}
promise_test(async t => {
const rs = new ReadableStream({
type: 'bytes',
pull: t.unreached_func('pull() should not be called'),
});
const reader = rs.getReader({ mode: 'byob' });
await promise_rejects_js(t, TypeError, reader.read(new Uint8Array(1), { min: 0 }));
}, 'ReadableStream with byte source: read({ min }) rejects if min is 0');
promise_test(async t => {
const rs = new ReadableStream({
type: 'bytes',
pull: t.unreached_func('pull() should not be called'),
});
const reader = rs.getReader({ mode: 'byob' });
await promise_rejects_js(t, TypeError, reader.read(new Uint8Array(1), { min: -1 }));
}, 'ReadableStream with byte source: read({ min }) rejects if min is negative');
promise_test(async t => {
const rs = new ReadableStream({
type: 'bytes',
pull: t.unreached_func('pull() should not be called'),
});
const reader = rs.getReader({ mode: 'byob' });
await promise_rejects_js(t, RangeError, reader.read(new Uint8Array(1), { min: 2 }));
}, 'ReadableStream with byte source: read({ min }) rejects if min is larger than view\'s length (Uint8Array)');
promise_test(async t => {
const rs = new ReadableStream({
type: 'bytes',
pull: t.unreached_func('pull() should not be called'),
});
const reader = rs.getReader({ mode: 'byob' });
await promise_rejects_js(t, RangeError, reader.read(new Uint16Array(1), { min: 2 }));
}, 'ReadableStream with byte source: read({ min }) rejects if min is larger than view\'s length (Uint16Array)');
promise_test(async t => {
const rs = new ReadableStream({
type: 'bytes',
pull: t.unreached_func('pull() should not be called'),
});
const reader = rs.getReader({ mode: 'byob' });
await promise_rejects_js(t, RangeError, reader.read(new DataView(new ArrayBuffer(1)), { min: 2 }));
}, 'ReadableStream with byte source: read({ min }) rejects if min is larger than view\'s length (DataView)');
promise_test(async t => {
let pullCount = 0;
const byobRequests = [];
const rs = new ReadableStream({
type: 'bytes',
pull: t.step_func((c) => {
const byobRequest = c.byobRequest;
const view = byobRequest.view;
byobRequests[pullCount] = {
nonNull: byobRequest !== null,
viewNonNull: view !== null,
viewInfo: extractViewInfo(view)
};
if (pullCount === 0) {
view[0] = 0x01;
view[1] = 0x02;
byobRequest.respond(2);
} else if (pullCount === 1) {
view[0] = 0x03;
byobRequest.respond(1);
} else if (pullCount === 2) {
view[0] = 0x04;
byobRequest.respond(1);
}
++pullCount;
})
});
const reader = rs.getReader({ mode: 'byob' });
const read1 = reader.read(new Uint8Array(3), { min: 3 });
const read2 = reader.read(new Uint8Array(1));
const result1 = await read1;
assert_false(result1.done, 'first result should not be done');
assert_typed_array_equals(result1.value, new Uint8Array([0x01, 0x02, 0x03]), 'first result value');
const result2 = await read2;
assert_false(result2.done, 'second result should not be done');
assert_typed_array_equals(result2.value, new Uint8Array([0x04]), 'second result value');
assert_equals(pullCount, 3, 'pull() must have been called 3 times');
{
const byobRequest = byobRequests[0];
assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 3, 'first view.buffer.byteLength should be 3');
assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
assert_equals(viewInfo.byteLength, 3, 'first view.byteLength should be 3');
}
{
const byobRequest = byobRequests[1];
assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 3, 'second view.buffer.byteLength should be 3');
assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2');
assert_equals(viewInfo.byteLength, 1, 'second view.byteLength should be 1');
}
{
const byobRequest = byobRequests[2];
assert_true(byobRequest.nonNull, 'third byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'third byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'third view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 1, 'third view.buffer.byteLength should be 1');
assert_equals(viewInfo.byteOffset, 0, 'third view.byteOffset should be 0');
assert_equals(viewInfo.byteLength, 1, 'third view.byteLength should be 1');
}
}, 'ReadableStream with byte source: read({ min }), then read()');
promise_test(async t => {
let pullCount = 0;
const byobRequests = [];
const rs = new ReadableStream({
type: 'bytes',
pull: t.step_func((c) => {
const byobRequest = c.byobRequest;
const view = byobRequest.view;
byobRequests[pullCount] = {
nonNull: byobRequest !== null,
viewNonNull: view !== null,
viewInfo: extractViewInfo(view)
};
if (pullCount === 0) {
view[0] = 0x01;
view[1] = 0x02;
byobRequest.respond(2);
} else if (pullCount === 1) {
view[0] = 0x03;
byobRequest.respond(1);
}
++pullCount;
})
});
const reader = rs.getReader({ mode: 'byob' });
const result = await reader.read(new DataView(new ArrayBuffer(3)), { min: 3 });
assert_false(result.done, 'result should not be done');
assert_equals(result.value.constructor, DataView, 'result.value must be a DataView');
assert_equals(result.value.byteOffset, 0, 'result.value.byteOffset');
assert_equals(result.value.byteLength, 3, 'result.value.byteLength');
assert_equals(result.value.buffer.byteLength, 3, 'result.value.buffer.byteLength');
assert_array_equals([...new Uint8Array(result.value.buffer)], [0x01, 0x02, 0x03], `result.value.buffer contents`);
assert_equals(pullCount, 2, 'pull() must have been called 2 times');
{
const byobRequest = byobRequests[0];
assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 3, 'first view.buffer.byteLength should be 3');
assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
assert_equals(viewInfo.byteLength, 3, 'first view.byteLength should be 3');
}
{
const byobRequest = byobRequests[1];
assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 3, 'second view.buffer.byteLength should be 3');
assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2');
assert_equals(viewInfo.byteLength, 1, 'second view.byteLength should be 1');
}
}, 'ReadableStream with byte source: read({ min }) with a DataView');
promise_test(async t => {
let pullCount = 0;
const byobRequests = [];
const rs = new ReadableStream({
type: 'bytes',
start: t.step_func((c) => {
c.enqueue(new Uint8Array([0x01]));
}),
pull: t.step_func((c) => {
const byobRequest = c.byobRequest;
const view = byobRequest.view;
byobRequests[pullCount] = {
nonNull: byobRequest !== null,
viewNonNull: view !== null,
viewInfo: extractViewInfo(view)
};
if (pullCount === 0) {
view[0] = 0x02;
view[1] = 0x03;
byobRequest.respond(2);
}
++pullCount;
})
});
const reader = rs.getReader({ mode: 'byob' });
const result = await reader.read(new Uint8Array(3), { min: 3 });
assert_false(result.done, 'first result should not be done');
assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03]), 'first result value');
assert_equals(pullCount, 1, 'pull() must have only been called once');
const byobRequest = byobRequests[0];
assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 3, 'first view.buffer.byteLength should be 3');
assert_equals(viewInfo.byteOffset, 1, 'first view.byteOffset should be 1');
assert_equals(viewInfo.byteLength, 2, 'first view.byteLength should be 2');
}, 'ReadableStream with byte source: enqueue(), then read({ min })');
promise_test(async t => {
let pullCount = 0;
const byobRequests = [];
const rs = new ReadableStream({
type: 'bytes',
pull: t.step_func((c) => {
const byobRequest = c.byobRequest;
const view = byobRequest.view;
byobRequests[pullCount] = {
nonNull: byobRequest !== null,
viewNonNull: view !== null,
viewInfo: extractViewInfo(view)
};
if (pullCount === 0) {
c.enqueue(new Uint8Array([0x01, 0x02]));
} else if (pullCount === 1) {
c.enqueue(new Uint8Array([0x03]));
}
++pullCount;
})
});
const reader = rs.getReader({ mode: 'byob' });
const result = await reader.read(new Uint8Array(3), { min: 3 });
assert_false(result.done, 'first result should not be done');
assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03]), 'first result value');
assert_equals(pullCount, 2, 'pull() must have been called 2 times');
{
const byobRequest = byobRequests[0];
assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 3, 'first view.buffer.byteLength should be 3');
assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
assert_equals(viewInfo.byteLength, 3, 'first view.byteLength should be 3');
}
{
const byobRequest = byobRequests[1];
assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 3, 'second view.buffer.byteLength should be 3');
assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2');
assert_equals(viewInfo.byteLength, 1, 'second view.byteLength should be 1');
}
}, 'ReadableStream with byte source: read({ min: 3 }) on a 3-byte Uint8Array, then multiple enqueue() up to 3 bytes');
promise_test(async t => {
let pullCount = 0;
const byobRequests = [];
const rs = new ReadableStream({
type: 'bytes',
pull: t.step_func((c) => {
const byobRequest = c.byobRequest;
const view = byobRequest.view;
byobRequests[pullCount] = {
nonNull: byobRequest !== null,
viewNonNull: view !== null,
viewInfo: extractViewInfo(view)
};
if (pullCount === 0) {
c.enqueue(new Uint8Array([0x01, 0x02]));
} else if (pullCount === 1) {
c.enqueue(new Uint8Array([0x03]));
}
++pullCount;
})
});
const reader = rs.getReader({ mode: 'byob' });
const result = await reader.read(new Uint8Array(5), { min: 3 });
assert_false(result.done, 'first result should not be done');
assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03, 0, 0]).subarray(0, 3), 'first result value');
assert_equals(pullCount, 2, 'pull() must have been called 2 times');
{
const byobRequest = byobRequests[0];
assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 5, 'first view.buffer.byteLength should be 5');
assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
assert_equals(viewInfo.byteLength, 5, 'first view.byteLength should be 5');
}
{
const byobRequest = byobRequests[1];
assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 5, 'second view.buffer.byteLength should be 5');
assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2');
assert_equals(viewInfo.byteLength, 3, 'second view.byteLength should be 3');
}
}, 'ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 3 bytes');
promise_test(async t => {
let pullCount = 0;
const byobRequests = [];
const rs = new ReadableStream({
type: 'bytes',
pull: t.step_func((c) => {
const byobRequest = c.byobRequest;
const view = byobRequest.view;
byobRequests[pullCount] = {
nonNull: byobRequest !== null,
viewNonNull: view !== null,
viewInfo: extractViewInfo(view)
};
if (pullCount === 0) {
c.enqueue(new Uint8Array([0x01, 0x02]));
} else if (pullCount === 1) {
c.enqueue(new Uint8Array([0x03, 0x04]));
}
++pullCount;
})
});
const reader = rs.getReader({ mode: 'byob' });
const result = await reader.read(new Uint8Array(5), { min: 3 });
assert_false(result.done, 'first result should not be done');
assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03, 0x04, 0]).subarray(0, 4), 'first result value');
assert_equals(pullCount, 2, 'pull() must have been called 2 times');
{
const byobRequest = byobRequests[0];
assert_true(byobRequest.nonNull, 'first byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'first byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'first view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 5, 'first view.buffer.byteLength should be 5');
assert_equals(viewInfo.byteOffset, 0, 'first view.byteOffset should be 0');
assert_equals(viewInfo.byteLength, 5, 'first view.byteLength should be 5');
}
{
const byobRequest = byobRequests[1];
assert_true(byobRequest.nonNull, 'second byobRequest must not be null');
assert_true(byobRequest.viewNonNull, 'second byobRequest.view must not be null');
const viewInfo = byobRequest.viewInfo;
assert_equals(viewInfo.constructor, Uint8Array, 'second view.constructor should be Uint8Array');
assert_equals(viewInfo.bufferByteLength, 5, 'second view.buffer.byteLength should be 5');
assert_equals(viewInfo.byteOffset, 2, 'second view.byteOffset should be 2');
assert_equals(viewInfo.byteLength, 3, 'second view.byteLength should be 3');
}
}, 'ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 4 bytes');
promise_test(async t => {
const stream = new ReadableStream({
start(c) {
const view = new Uint8Array(16);
view[0] = 0x01;
view[8] = 0x02;
c.enqueue(view);
},
pull: t.unreached_func('pull() should not be called'),
type: 'bytes'
});
const byobReader = stream.getReader({ mode: 'byob' });
const result1 = await byobReader.read(new Uint8Array(8), { min: 8 });
assert_false(result1.done, 'result1.done');
const view1 = result1.value;
assert_equals(view1.constructor, Uint8Array, 'result1.value.constructor');
assert_equals(view1.buffer.byteLength, 8, 'result1.value.buffer.byteLength');
assert_equals(view1.byteOffset, 0, 'result1.value.byteOffset');
assert_equals(view1.byteLength, 8, 'result1.value.byteLength');
assert_equals(view1[0], 0x01, 'result1.value[0]');
byobReader.releaseLock();
const reader = stream.getReader();
const result2 = await reader.read();
assert_false(result2.done, 'result2.done');
const view2 = result2.value;
assert_equals(view2.constructor, Uint8Array, 'result2.value.constructor');
assert_equals(view2.buffer.byteLength, 16, 'result2.value.buffer.byteLength');
assert_equals(view2.byteOffset, 8, 'result2.value.byteOffset');
assert_equals(view2.byteLength, 8, 'result2.value.byteLength');
assert_equals(view2[0], 0x02, 'result2.value[0]');
}, 'ReadableStream with byte source: enqueue(), read({ min }) partially, then read()');
promise_test(async () => {
let pullCount = 0;
const byobRequestDefined = [];
let byobRequestViewDefined;
const stream = new ReadableStream({
async pull(c) {
byobRequestDefined.push(c.byobRequest !== null);
const initialByobRequest = c.byobRequest;
const transferredView = await transferArrayBufferView(c.byobRequest.view);
transferredView[0] = 0x01;
c.byobRequest.respondWithNewView(transferredView);
byobRequestDefined.push(c.byobRequest !== null);
byobRequestViewDefined = initialByobRequest.view !== null;
++pullCount;
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const result = await reader.read(new Uint8Array(1), { min: 1 });
assert_false(result.done, 'result.done');
assert_equals(result.value.byteLength, 1, 'result.value.byteLength');
assert_equals(result.value[0], 0x01, 'result.value[0]');
assert_equals(pullCount, 1, 'pull() should be called only once');
assert_true(byobRequestDefined[0], 'byobRequest must not be null before respondWithNewView()');
assert_false(byobRequestDefined[1], 'byobRequest must be null after respondWithNewView()');
assert_false(byobRequestViewDefined, 'view of initial byobRequest must be null after respondWithNewView()');
}, 'ReadableStream with byte source: read({ min }), then respondWithNewView() with a transferred ArrayBuffer');
promise_test(async t => {
const stream = new ReadableStream({
start(c) {
c.close();
},
pull: t.unreached_func('pull() should not be called'),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const result = await reader.read(new Uint8Array([0x01]), { min: 1 });
assert_true(result.done, 'result.done');
assert_typed_array_equals(result.value, new Uint8Array([0x01]).subarray(0, 0), 'result.value');
await reader.closed;
}, 'ReadableStream with byte source: read({ min }) on a closed stream');
promise_test(async t => {
let pullCount = 0;
const rs = new ReadableStream({
type: 'bytes',
pull: t.step_func((c) => {
if (pullCount === 0) {
c.byobRequest.view[0] = 0x01;
c.byobRequest.respond(1);
} else if (pullCount === 1) {
c.close();
c.byobRequest.respond(0);
}
++pullCount;
})
});
const reader = rs.getReader({ mode: 'byob' });
const result = await reader.read(new Uint8Array(3), { min: 3 });
assert_true(result.done, 'result.done');
assert_typed_array_equals(result.value, new Uint8Array([0x01, 0, 0]).subarray(0, 1), 'result.value');
assert_equals(pullCount, 2, 'pull() must have been called 2 times');
await reader.closed;
}, 'ReadableStream with byte source: read({ min }) when closed before view is filled');
promise_test(async t => {
let pullCount = 0;
const rs = new ReadableStream({
type: 'bytes',
pull: t.step_func((c) => {
if (pullCount === 0) {
c.byobRequest.view[0] = 0x01;
c.byobRequest.view[1] = 0x02;
c.byobRequest.respond(2);
} else if (pullCount === 1) {
c.byobRequest.view[0] = 0x03;
c.byobRequest.respond(1);
c.close();
}
++pullCount;
})
});
const reader = rs.getReader({ mode: 'byob' });
const result = await reader.read(new Uint8Array(3), { min: 3 });
assert_false(result.done, 'result.done');
assert_typed_array_equals(result.value, new Uint8Array([0x01, 0x02, 0x03]), 'result.value');
assert_equals(pullCount, 2, 'pull() must have been called 2 times');
await reader.closed;
}, 'ReadableStream with byte source: read({ min }) when closed immediately after view is filled');
promise_test(async t => {
const error1 = new Error('error1');
const stream = new ReadableStream({
start(c) {
c.error(error1);
},
pull: t.unreached_func('pull() should not be called'),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const read = reader.read(new Uint8Array(1), { min: 1 });
await Promise.all([
promise_rejects_exactly(t, error1, read, 'read() must fail'),
promise_rejects_exactly(t, error1, reader.closed, 'closed must fail')
]);
}, 'ReadableStream with byte source: read({ min }) on an errored stream');
promise_test(async t => {
const error1 = new Error('error1');
let controller;
const stream = new ReadableStream({
start(c) {
controller = c;
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const read = reader.read(new Uint8Array(1), { min: 1 });
controller.error(error1);
await Promise.all([
promise_rejects_exactly(t, error1, read, 'read() must fail'),
promise_rejects_exactly(t, error1, reader.closed, 'closed must fail')
]);
}, 'ReadableStream with byte source: read({ min }), then error()');
promise_test(t => {
let cancelCount = 0;
let reason;
const passedReason = new TypeError('foo');
const stream = new ReadableStream({
pull: t.unreached_func('pull() should not be called'),
cancel(r) {
if (cancelCount === 0) {
reason = r;
}
++cancelCount;
return 'bar';
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const readPromise = reader.read(new Uint8Array(1), { min: 1 }).then(result => {
assert_true(result.done, 'result.done');
assert_equals(result.value, undefined, 'result.value');
});
const cancelPromise = reader.cancel(passedReason).then(result => {
assert_equals(result, undefined, 'cancel() return value should be fulfilled with undefined');
assert_equals(cancelCount, 1, 'cancel() should be called only once');
assert_equals(reason, passedReason, 'reason should equal the passed reason');
});
return Promise.all([readPromise, cancelPromise]);
}, 'ReadableStream with byte source: getReader(), read({ min }), then cancel()');
promise_test(async t => {
let pullCount = 0;
let byobRequest;
const viewInfos = [];
const rs = new ReadableStream({
type: 'bytes',
pull: t.step_func((c) => {
byobRequest = c.byobRequest;
viewInfos.push(extractViewInfo(c.byobRequest.view));
c.byobRequest.view[0] = 0x01;
c.byobRequest.respond(1);
viewInfos.push(extractViewInfo(c.byobRequest.view));
++pullCount;
})
});
await Promise.resolve();
assert_equals(pullCount, 0, 'pull() must not have been called yet');
const reader = rs.getReader({ mode: 'byob' });
const read = reader.read(new Uint8Array(3), { min: 3 });
assert_equals(pullCount, 1, 'pull() must have been called once');
assert_not_equals(byobRequest, null, 'byobRequest should not be null');
assert_equals(viewInfos[0].byteLength, 3, 'byteLength before respond() should be 3');
assert_equals(viewInfos[1].byteLength, 2, 'byteLength after respond() should be 2');
reader.cancel().catch(t.unreached_func('cancel() should not reject'));
const result = await read;
assert_true(result.done, 'result.done');
assert_equals(result.value, undefined, 'result.value');
assert_equals(pullCount, 1, 'pull() must only be called once');
await reader.closed;
}, 'ReadableStream with byte source: cancel() with partially filled pending read({ min }) request');
promise_test(async () => {
let pullCalled = false;
const stream = new ReadableStream({
start(c) {
const view = new Uint8Array(16);
view[7] = 0x01;
view[15] = 0x02;
c.enqueue(view);
},
pull() {
pullCalled = true;
},
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const result1 = await reader.read(new Uint8Array(8), { min: 8 });
assert_false(result1.done, 'result1.done');
const view1 = result1.value;
assert_equals(view1.byteOffset, 0, 'result1.value.byteOffset');
assert_equals(view1.byteLength, 8, 'result1.value.byteLength');
assert_equals(view1[7], 0x01, 'result1.value[7]');
const result2 = await reader.read(new Uint8Array(8), { min: 8 });
assert_false(pullCalled, 'pull() must not have been called');
assert_false(result2.done, 'result2.done');
const view2 = result2.value;
assert_equals(view2.byteOffset, 0, 'result2.value.byteOffset');
assert_equals(view2.byteLength, 8, 'result2.value.byteLength');
assert_equals(view2[7], 0x02, 'result2.value[7]');
}, 'ReadableStream with byte source: enqueue(), then read({ min }) with smaller views');
promise_test(async t => {
const stream = new ReadableStream({
start(c) {
c.enqueue(new Uint8Array([0xaa, 0xbb, 0xcc]));
c.close();
},
pull: t.unreached_func('pull() should not be called'),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
await promise_rejects_js(t, TypeError, reader.read(new Uint16Array(2), { min: 2 }), 'read() must fail');
await promise_rejects_js(t, TypeError, reader.closed, 'reader.closed should reject');
}, 'ReadableStream with byte source: 3 byte enqueue(), then close(), then read({ min }) with 2-element Uint16Array must fail');
promise_test(async t => {
let controller;
const stream = new ReadableStream({
start(c) {
controller = c;
},
pull: t.unreached_func('pull() should not be called'),
type: 'bytes'
});
const reader = stream.getReader({ mode: 'byob' });
const readPromise = reader.read(new Uint16Array(2), { min: 2 });
controller.enqueue(new Uint8Array([0xaa, 0xbb, 0xcc]));
assert_throws_js(TypeError, () => controller.close(), 'controller.close() must throw');
await promise_rejects_js(t, TypeError, readPromise, 'read() must fail');
await promise_rejects_js(t, TypeError, reader.closed, 'reader.closed must reject');
}, 'ReadableStream with byte source: read({ min }) with 2-element Uint16Array, then 3 byte enqueue(), then close() must fail');
promise_test(async t => {
let pullCount = 0;
let controller;
const rs = new ReadableStream({
type: 'bytes',
start: t.step_func((c) => {
controller = c;
}),
pull: t.step_func((c) => {
++pullCount;
})
});
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
await Promise.resolve();
assert_equals(pullCount, 0, 'pull() must not have been called yet');
const read1 = reader1.read(new Uint8Array(3), { min: 3 });
const read2 = reader2.read(new Uint8Array(1));
assert_equals(pullCount, 1, 'pull() must have been called once');
const byobRequest1 = controller.byobRequest;
assert_equals(byobRequest1.view.byteLength, 3, 'first byobRequest.view.byteLength should be 3');
byobRequest1.view[0] = 0x01;
byobRequest1.respond(1);
const result2 = await read2;
assert_false(result2.done, 'branch2 first read() should not be done');
assert_typed_array_equals(result2.value, new Uint8Array([0x01]), 'branch2 first read() value');
assert_equals(pullCount, 2, 'pull() must have been called 2 times');
const byobRequest2 = controller.byobRequest;
assert_equals(byobRequest2.view.byteLength, 2, 'second byobRequest.view.byteLength should be 2');
byobRequest2.view[0] = 0x02;
byobRequest2.view[1] = 0x03;
byobRequest2.respond(2);
const result1 = await read1;
assert_false(result1.done, 'branch1 read() should not be done');
assert_typed_array_equals(result1.value, new Uint8Array([0x01, 0x02, 0x03]), 'branch1 read() value');
const result3 = await reader2.read(new Uint8Array(2));
assert_equals(pullCount, 2, 'pull() must only be called 2 times');
assert_false(result3.done, 'branch2 second read() should not be done');
assert_typed_array_equals(result3.value, new Uint8Array([0x02, 0x03]), 'branch2 second read() value');
}, 'ReadableStream with byte source: tee() with read({ min }) from branch1 and read() from branch2');

View File

@ -0,0 +1,55 @@
// META: global=window,worker,shadowrealm
'use strict';
// Repro for Blink bug https://crbug.com/1255762.
promise_test(async () => {
const rs = new ReadableStream({
type: 'bytes',
autoAllocateChunkSize: 10,
pull(controller) {
controller.enqueue(new Uint8Array([1, 2, 3]));
controller.byobRequest.respond(10);
}
});
const reader = rs.getReader();
const {value, done} = await reader.read();
assert_false(done, 'done should not be true');
assert_array_equals(value, [1, 2, 3], 'value should be 3 bytes');
}, 'byobRequest.respond() after enqueue() should not crash');
promise_test(async () => {
const rs = new ReadableStream({
type: 'bytes',
autoAllocateChunkSize: 10,
pull(controller) {
const byobRequest = controller.byobRequest;
controller.enqueue(new Uint8Array([1, 2, 3]));
byobRequest.respond(10);
}
});
const reader = rs.getReader();
const {value, done} = await reader.read();
assert_false(done, 'done should not be true');
assert_array_equals(value, [1, 2, 3], 'value should be 3 bytes');
}, 'byobRequest.respond() with cached byobRequest after enqueue() should not crash');
promise_test(async () => {
const rs = new ReadableStream({
type: 'bytes',
autoAllocateChunkSize: 10,
pull(controller) {
controller.enqueue(new Uint8Array([1, 2, 3]));
controller.byobRequest.respond(2);
}
});
const reader = rs.getReader();
const [read1, read2] = await Promise.all([reader.read(), reader.read()]);
assert_false(read1.done, 'read1.done should not be true');
assert_array_equals(read1.value, [1, 2, 3], 'read1.value should be 3 bytes');
assert_false(read2.done, 'read2.done should not be true');
assert_array_equals(read2.value, [0, 0], 'read2.value should be 2 bytes');
}, 'byobRequest.respond() after enqueue() with double read should not crash');

View File

@ -0,0 +1,969 @@
// META: global=window,worker,shadowrealm
// META: script=../resources/rs-utils.js
// META: script=../resources/test-utils.js
// META: script=../resources/recording-streams.js
// META: script=../resources/rs-test-templates.js
'use strict';
test(() => {
const rs = new ReadableStream({ type: 'bytes' });
const result = rs.tee();
assert_true(Array.isArray(result), 'return value should be an array');
assert_equals(result.length, 2, 'array should have length 2');
assert_equals(result[0].constructor, ReadableStream, '0th element should be a ReadableStream');
assert_equals(result[1].constructor, ReadableStream, '1st element should be a ReadableStream');
}, 'ReadableStream teeing with byte source: rs.tee() returns an array of two ReadableStreams');
promise_test(async t => {
const rs = new ReadableStream({
type: 'bytes',
start(c) {
c.enqueue(new Uint8Array([0x01]));
c.enqueue(new Uint8Array([0x02]));
c.close();
}
});
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader({ mode: 'byob' });
const reader2 = branch2.getReader({ mode: 'byob' });
reader2.closed.then(t.unreached_func('branch2 should not be closed'));
{
const result = await reader1.read(new Uint8Array(1));
assert_equals(result.done, false, 'done');
assert_typed_array_equals(result.value, new Uint8Array([0x01]), 'value');
}
{
const result = await reader1.read(new Uint8Array(1));
assert_equals(result.done, false, 'done');
assert_typed_array_equals(result.value, new Uint8Array([0x02]), 'value');
}
{
const result = await reader1.read(new Uint8Array(1));
assert_equals(result.done, true, 'done');
assert_typed_array_equals(result.value, new Uint8Array([0]).subarray(0, 0), 'value');
}
{
const result = await reader2.read(new Uint8Array(1));
assert_equals(result.done, false, 'done');
assert_typed_array_equals(result.value, new Uint8Array([0x01]), 'value');
}
await reader1.closed;
}, 'ReadableStream teeing with byte source: should be able to read one branch to the end without affecting the other');
promise_test(async () => {
let pullCount = 0;
const enqueuedChunk = new Uint8Array([0x01]);
const rs = new ReadableStream({
type: 'bytes',
pull(c) {
++pullCount;
if (pullCount === 1) {
c.enqueue(enqueuedChunk);
}
}
});
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader();
const reader2 = branch2.getReader();
const [result1, result2] = await Promise.all([reader1.read(), reader2.read()]);
assert_equals(result1.done, false, 'reader1 done');
assert_equals(result2.done, false, 'reader2 done');
const view1 = result1.value;
const view2 = result2.value;
assert_typed_array_equals(view1, new Uint8Array([0x01]), 'reader1 value');
assert_typed_array_equals(view2, new Uint8Array([0x01]), 'reader2 value');
assert_not_equals(view1.buffer, view2.buffer, 'chunks should have different buffers');
assert_not_equals(enqueuedChunk.buffer, view1.buffer, 'enqueued chunk and branch1\'s chunk should have different buffers');
assert_not_equals(enqueuedChunk.buffer, view2.buffer, 'enqueued chunk and branch2\'s chunk should have different buffers');
}, 'ReadableStream teeing with byte source: chunks should be cloned for each branch');
promise_test(async () => {
let pullCount = 0;
const rs = new ReadableStream({
type: 'bytes',
pull(c) {
++pullCount;
if (pullCount === 1) {
c.byobRequest.view[0] = 0x01;
c.byobRequest.respond(1);
}
}
});
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader({ mode: 'byob' });
const reader2 = branch2.getReader();
const buffer = new Uint8Array([42, 42, 42]).buffer;
{
const result = await reader1.read(new Uint8Array(buffer, 0, 1));
assert_equals(result.done, false, 'done');
assert_typed_array_equals(result.value, new Uint8Array([0x01, 42, 42]).subarray(0, 1), 'value');
}
{
const result = await reader2.read();
assert_equals(result.done, false, 'done');
assert_typed_array_equals(result.value, new Uint8Array([0x01]), 'value');
}
}, 'ReadableStream teeing with byte source: chunks for BYOB requests from branch 1 should be cloned to branch 2');
promise_test(async t => {
const theError = { name: 'boo!' };
const rs = new ReadableStream({
type: 'bytes',
start(c) {
c.enqueue(new Uint8Array([0x01]));
c.enqueue(new Uint8Array([0x02]));
},
pull() {
throw theError;
}
});
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader({ mode: 'byob' });
const reader2 = branch2.getReader({ mode: 'byob' });
{
const result = await reader1.read(new Uint8Array(1));
assert_equals(result.done, false, 'first read from branch1 should not be done');
assert_typed_array_equals(result.value, new Uint8Array([0x01]), 'first read from branch1');
}
{
const result = await reader1.read(new Uint8Array(1));
assert_equals(result.done, false, 'second read from branch1 should not be done');
assert_typed_array_equals(result.value, new Uint8Array([0x02]), 'second read from branch1');
}
await promise_rejects_exactly(t, theError, reader1.read(new Uint8Array(1)));
await promise_rejects_exactly(t, theError, reader2.read(new Uint8Array(1)));
await Promise.all([
promise_rejects_exactly(t, theError, reader1.closed),
promise_rejects_exactly(t, theError, reader2.closed)
]);
}, 'ReadableStream teeing with byte source: errors in the source should propagate to both branches');
promise_test(async () => {
const rs = new ReadableStream({
type: 'bytes',
start(c) {
c.enqueue(new Uint8Array([0x01]));
c.enqueue(new Uint8Array([0x02]));
c.close();
}
});
const [branch1, branch2] = rs.tee();
branch1.cancel();
const [chunks1, chunks2] = await Promise.all([readableStreamToArray(branch1), readableStreamToArray(branch2)]);
assert_array_equals(chunks1, [], 'branch1 should have no chunks');
assert_equals(chunks2.length, 2, 'branch2 should have two chunks');
assert_typed_array_equals(chunks2[0], new Uint8Array([0x01]), 'first chunk from branch2');
assert_typed_array_equals(chunks2[1], new Uint8Array([0x02]), 'second chunk from branch2');
}, 'ReadableStream teeing with byte source: canceling branch1 should not impact branch2');
promise_test(async () => {
const rs = new ReadableStream({
type: 'bytes',
start(c) {
c.enqueue(new Uint8Array([0x01]));
c.enqueue(new Uint8Array([0x02]));
c.close();
}
});
const [branch1, branch2] = rs.tee();
branch2.cancel();
const [chunks1, chunks2] = await Promise.all([readableStreamToArray(branch1), readableStreamToArray(branch2)]);
assert_equals(chunks1.length, 2, 'branch1 should have two chunks');
assert_typed_array_equals(chunks1[0], new Uint8Array([0x01]), 'first chunk from branch1');
assert_typed_array_equals(chunks1[1], new Uint8Array([0x02]), 'second chunk from branch1');
assert_array_equals(chunks2, [], 'branch2 should have no chunks');
}, 'ReadableStream teeing with byte source: canceling branch2 should not impact branch1');
templatedRSTeeCancel('ReadableStream teeing with byte source', (extras) => {
return new ReadableStream({ type: 'bytes', ...extras });
});
promise_test(async () => {
let controller;
const rs = new ReadableStream({
type: 'bytes',
start(c) {
controller = c;
}
});
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader({ mode: 'byob' });
const reader2 = branch2.getReader({ mode: 'byob' });
const promise = Promise.all([reader1.closed, reader2.closed]);
controller.close();
// The branches are created with HWM 0, so we need to read from at least one of them
// to observe the stream becoming closed.
const read1 = await reader1.read(new Uint8Array(1));
assert_equals(read1.done, true, 'first read from branch1 should be done');
await promise;
}, 'ReadableStream teeing with byte source: closing the original should close the branches');
promise_test(async t => {
let controller;
const rs = new ReadableStream({
type: 'bytes',
start(c) {
controller = c;
}
});
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader({ mode: 'byob' });
const reader2 = branch2.getReader({ mode: 'byob' });
const theError = { name: 'boo!' };
const promise = Promise.all([
promise_rejects_exactly(t, theError, reader1.closed),
promise_rejects_exactly(t, theError, reader2.closed)
]);
controller.error(theError);
await promise;
}, 'ReadableStream teeing with byte source: erroring the original should immediately error the branches');
promise_test(async t => {
let controller;
const rs = new ReadableStream({
type: 'bytes',
start(c) {
controller = c;
}
});
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader();
const reader2 = branch2.getReader();
const theError = { name: 'boo!' };
const promise = Promise.all([
promise_rejects_exactly(t, theError, reader1.read()),
promise_rejects_exactly(t, theError, reader2.read())
]);
controller.error(theError);
await promise;
}, 'ReadableStream teeing with byte source: erroring the original should error pending reads from default reader');
promise_test(async t => {
let controller;
const rs = new ReadableStream({
type: 'bytes',
start(c) {
controller = c;
}
});
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader({ mode: 'byob' });
const reader2 = branch2.getReader({ mode: 'byob' });
const theError = { name: 'boo!' };
const promise = Promise.all([
promise_rejects_exactly(t, theError, reader1.read(new Uint8Array(1))),
promise_rejects_exactly(t, theError, reader2.read(new Uint8Array(1)))
]);
controller.error(theError);
await promise;
}, 'ReadableStream teeing with byte source: erroring the original should error pending reads from BYOB reader');
promise_test(async () => {
let controller;
const rs = new ReadableStream({
type: 'bytes',
start(c) {
controller = c;
}
});
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader({ mode: 'byob' });
const reader2 = branch2.getReader({ mode: 'byob' });
const cancelPromise = reader2.cancel();
controller.enqueue(new Uint8Array([0x01]));
const read1 = await reader1.read(new Uint8Array(1));
assert_equals(read1.done, false, 'first read() from branch1 should not be done');
assert_typed_array_equals(read1.value, new Uint8Array([0x01]), 'first read() from branch1');
controller.close();
const read2 = await reader1.read(new Uint8Array(1));
assert_equals(read2.done, true, 'second read() from branch1 should be done');
await Promise.all([
reader1.closed,
cancelPromise
]);
}, 'ReadableStream teeing with byte source: canceling branch1 should finish when branch2 reads until end of stream');
promise_test(async t => {
let controller;
const theError = { name: 'boo!' };
const rs = new ReadableStream({
type: 'bytes',
start(c) {
controller = c;
}
});
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader({ mode: 'byob' });
const reader2 = branch2.getReader({ mode: 'byob' });
const cancelPromise = reader2.cancel();
controller.error(theError);
await Promise.all([
promise_rejects_exactly(t, theError, reader1.read(new Uint8Array(1))),
cancelPromise
]);
}, 'ReadableStream teeing with byte source: canceling branch1 should finish when original stream errors');
promise_test(async () => {
const rs = recordingReadableStream({ type: 'bytes' });
// Create two branches, each with a HWM of 0. This should result in no chunks being pulled.
rs.tee();
await flushAsyncEvents();
assert_array_equals(rs.events, [], 'pull should not be called');
}, 'ReadableStream teeing with byte source: should not pull any chunks if no branches are reading');
promise_test(async () => {
const rs = recordingReadableStream({
type: 'bytes',
pull(controller) {
controller.enqueue(new Uint8Array([0x01]));
}
});
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
await Promise.all([
reader1.read(new Uint8Array(1)),
reader2.read(new Uint8Array(1))
]);
assert_array_equals(rs.events, ['pull'], 'pull should be called once');
}, 'ReadableStream teeing with byte source: should only pull enough to fill the emptiest queue');
promise_test(async t => {
const rs = recordingReadableStream({ type: 'bytes' });
const theError = { name: 'boo!' };
rs.controller.error(theError);
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
await flushAsyncEvents();
assert_array_equals(rs.events, [], 'pull should not be called');
await Promise.all([
promise_rejects_exactly(t, theError, reader1.closed),
promise_rejects_exactly(t, theError, reader2.closed)
]);
}, 'ReadableStream teeing with byte source: should not pull when original is already errored');
for (const branch of [1, 2]) {
promise_test(async t => {
const rs = recordingReadableStream({ type: 'bytes' });
const theError = { name: 'boo!' };
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
await flushAsyncEvents();
assert_array_equals(rs.events, [], 'pull should not be called');
const reader = (branch === 1) ? reader1 : reader2;
const read1 = reader.read(new Uint8Array(1));
await flushAsyncEvents();
assert_array_equals(rs.events, ['pull'], 'pull should be called once');
rs.controller.error(theError);
await Promise.all([
promise_rejects_exactly(t, theError, read1),
promise_rejects_exactly(t, theError, reader1.closed),
promise_rejects_exactly(t, theError, reader2.closed)
]);
await flushAsyncEvents();
assert_array_equals(rs.events, ['pull'], 'pull should be called once');
}, `ReadableStream teeing with byte source: stops pulling when original stream errors while branch ${branch} is reading`);
}
promise_test(async t => {
const rs = recordingReadableStream({ type: 'bytes' });
const theError = { name: 'boo!' };
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
await flushAsyncEvents();
assert_array_equals(rs.events, [], 'pull should not be called');
const read1 = reader1.read(new Uint8Array(1));
const read2 = reader2.read(new Uint8Array(1));
await flushAsyncEvents();
assert_array_equals(rs.events, ['pull'], 'pull should be called once');
rs.controller.error(theError);
await Promise.all([
promise_rejects_exactly(t, theError, read1),
promise_rejects_exactly(t, theError, read2),
promise_rejects_exactly(t, theError, reader1.closed),
promise_rejects_exactly(t, theError, reader2.closed)
]);
await flushAsyncEvents();
assert_array_equals(rs.events, ['pull'], 'pull should be called once');
}, 'ReadableStream teeing with byte source: stops pulling when original stream errors while both branches are reading');
promise_test(async () => {
const rs = recordingReadableStream({ type: 'bytes' });
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
const read1 = reader1.read(new Uint8Array([0x11]));
const read2 = reader2.read(new Uint8Array([0x22]));
const cancel1 = reader1.cancel();
await flushAsyncEvents();
const cancel2 = reader2.cancel();
const result1 = await read1;
assert_object_equals(result1, { value: undefined, done: true });
const result2 = await read2;
assert_object_equals(result2, { value: undefined, done: true });
await Promise.all([cancel1, cancel2]);
}, 'ReadableStream teeing with byte source: canceling both branches in sequence with delay');
promise_test(async t => {
const theError = { name: 'boo!' };
const rs = new ReadableStream({
type: 'bytes',
cancel() {
throw theError;
}
});
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
const read1 = reader1.read(new Uint8Array([0x11]));
const read2 = reader2.read(new Uint8Array([0x22]));
const cancel1 = reader1.cancel();
await flushAsyncEvents();
const cancel2 = reader2.cancel();
const result1 = await read1;
assert_object_equals(result1, { value: undefined, done: true });
const result2 = await read2;
assert_object_equals(result2, { value: undefined, done: true });
await Promise.all([
promise_rejects_exactly(t, theError, cancel1),
promise_rejects_exactly(t, theError, cancel2)
]);
}, 'ReadableStream teeing with byte source: failing to cancel when canceling both branches in sequence with delay');
promise_test(async () => {
let cancelResolve;
const cancelCalled = new Promise((resolve) => {
cancelResolve = resolve;
});
const rs = recordingReadableStream({
type: 'bytes',
cancel() {
cancelResolve();
}
});
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
const read1 = reader1.read(new Uint8Array([0x11]));
await flushAsyncEvents();
const read2 = reader2.read(new Uint8Array([0x22]));
await flushAsyncEvents();
// We are reading into branch1's buffer.
const byobRequest1 = rs.controller.byobRequest;
assert_not_equals(byobRequest1, null);
assert_typed_array_equals(byobRequest1.view, new Uint8Array([0x11]), 'byobRequest1.view');
// Cancelling branch1 should not affect the BYOB request.
const cancel1 = reader1.cancel();
const result1 = await read1;
assert_equals(result1.done, true);
assert_equals(result1.value, undefined);
await flushAsyncEvents();
const byobRequest2 = rs.controller.byobRequest;
assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11]), 'byobRequest2.view');
// Cancelling branch1 should invalidate the BYOB request.
const cancel2 = reader2.cancel();
await cancelCalled;
const byobRequest3 = rs.controller.byobRequest;
assert_equals(byobRequest3, null);
const result2 = await read2;
assert_equals(result2.done, true);
assert_equals(result2.value, undefined);
await Promise.all([cancel1, cancel2]);
}, 'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch1, cancel branch2');
promise_test(async () => {
let cancelResolve;
const cancelCalled = new Promise((resolve) => {
cancelResolve = resolve;
});
const rs = recordingReadableStream({
type: 'bytes',
cancel() {
cancelResolve();
}
});
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
const read1 = reader1.read(new Uint8Array([0x11]));
await flushAsyncEvents();
const read2 = reader2.read(new Uint8Array([0x22]));
await flushAsyncEvents();
// We are reading into branch1's buffer.
const byobRequest1 = rs.controller.byobRequest;
assert_not_equals(byobRequest1, null);
assert_typed_array_equals(byobRequest1.view, new Uint8Array([0x11]), 'byobRequest1.view');
// Cancelling branch2 should not affect the BYOB request.
const cancel2 = reader2.cancel();
const result2 = await read2;
assert_equals(result2.done, true);
assert_equals(result2.value, undefined);
await flushAsyncEvents();
const byobRequest2 = rs.controller.byobRequest;
assert_typed_array_equals(byobRequest2.view, new Uint8Array([0x11]), 'byobRequest2.view');
// Cancelling branch1 should invalidate the BYOB request.
const cancel1 = reader1.cancel();
await cancelCalled;
const byobRequest3 = rs.controller.byobRequest;
assert_equals(byobRequest3, null);
const result1 = await read1;
assert_equals(result1.done, true);
assert_equals(result1.value, undefined);
await Promise.all([cancel1, cancel2]);
}, 'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch2, cancel branch1');
promise_test(async () => {
const rs = recordingReadableStream({ type: 'bytes' });
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
const read1 = reader1.read(new Uint8Array([0x11]));
await flushAsyncEvents();
const read2 = reader2.read(new Uint8Array([0x22]));
await flushAsyncEvents();
// We are reading into branch1's buffer.
assert_typed_array_equals(rs.controller.byobRequest.view, new Uint8Array([0x11]), 'first byobRequest.view');
// Cancelling branch2 should not affect the BYOB request.
reader2.cancel();
const result2 = await read2;
assert_equals(result2.done, true);
assert_equals(result2.value, undefined);
await flushAsyncEvents();
assert_typed_array_equals(rs.controller.byobRequest.view, new Uint8Array([0x11]), 'second byobRequest.view');
// Respond to the BYOB request.
rs.controller.byobRequest.view[0] = 0x33;
rs.controller.byobRequest.respond(1);
// branch1 should receive the read chunk.
const result1 = await read1;
assert_equals(result1.done, false);
assert_typed_array_equals(result1.value, new Uint8Array([0x33]), 'first read() from branch1');
}, 'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch2, enqueue to branch1');
promise_test(async () => {
const rs = recordingReadableStream({ type: 'bytes' });
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
const read1 = reader1.read(new Uint8Array([0x11]));
await flushAsyncEvents();
const read2 = reader2.read(new Uint8Array([0x22]));
await flushAsyncEvents();
// We are reading into branch1's buffer.
assert_typed_array_equals(rs.controller.byobRequest.view, new Uint8Array([0x11]), 'first byobRequest.view');
// Cancelling branch1 should not affect the BYOB request.
reader1.cancel();
const result1 = await read1;
assert_equals(result1.done, true);
assert_equals(result1.value, undefined);
await flushAsyncEvents();
assert_typed_array_equals(rs.controller.byobRequest.view, new Uint8Array([0x11]), 'second byobRequest.view');
// Respond to the BYOB request.
rs.controller.byobRequest.view[0] = 0x33;
rs.controller.byobRequest.respond(1);
// branch2 should receive the read chunk.
const result2 = await read2;
assert_equals(result2.done, false);
assert_typed_array_equals(result2.value, new Uint8Array([0x33]), 'first read() from branch2');
}, 'ReadableStream teeing with byte source: read from branch1 and branch2, cancel branch1, respond to branch2');
promise_test(async () => {
let pullCount = 0;
const byobRequestDefined = [];
const rs = new ReadableStream({
type: 'bytes',
pull(c) {
++pullCount;
byobRequestDefined.push(c.byobRequest !== null);
c.enqueue(new Uint8Array([pullCount]));
}
});
const [branch1, _] = rs.tee();
const reader1 = branch1.getReader({ mode: 'byob' });
const result1 = await reader1.read(new Uint8Array([0x11]));
assert_equals(result1.done, false, 'first read should not be done');
assert_typed_array_equals(result1.value, new Uint8Array([0x1]), 'first read');
assert_equals(pullCount, 1, 'pull() should be called once');
assert_equals(byobRequestDefined[0], true, 'should have created a BYOB request for first read');
reader1.releaseLock();
const reader2 = branch1.getReader();
const result2 = await reader2.read();
assert_equals(result2.done, false, 'second read should not be done');
assert_typed_array_equals(result2.value, new Uint8Array([0x2]), 'second read');
assert_equals(pullCount, 2, 'pull() should be called twice');
assert_equals(byobRequestDefined[1], false, 'should not have created a BYOB request for second read');
}, 'ReadableStream teeing with byte source: pull with BYOB reader, then pull with default reader');
promise_test(async () => {
let pullCount = 0;
const byobRequestDefined = [];
const rs = new ReadableStream({
type: 'bytes',
pull(c) {
++pullCount;
byobRequestDefined.push(c.byobRequest !== null);
c.enqueue(new Uint8Array([pullCount]));
}
});
const [branch1, _] = rs.tee();
const reader1 = branch1.getReader();
const result1 = await reader1.read();
assert_equals(result1.done, false, 'first read should not be done');
assert_typed_array_equals(result1.value, new Uint8Array([0x1]), 'first read');
assert_equals(pullCount, 1, 'pull() should be called once');
assert_equals(byobRequestDefined[0], false, 'should not have created a BYOB request for first read');
reader1.releaseLock();
const reader2 = branch1.getReader({ mode: 'byob' });
const result2 = await reader2.read(new Uint8Array([0x22]));
assert_equals(result2.done, false, 'second read should not be done');
assert_typed_array_equals(result2.value, new Uint8Array([0x2]), 'second read');
assert_equals(pullCount, 2, 'pull() should be called twice');
assert_equals(byobRequestDefined[1], true, 'should have created a BYOB request for second read');
}, 'ReadableStream teeing with byte source: pull with default reader, then pull with BYOB reader');
promise_test(async () => {
const rs = recordingReadableStream({
type: 'bytes'
});
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
// Wait for each branch's start() promise to resolve.
await flushAsyncEvents();
const read2 = reader2.read(new Uint8Array([0x22]));
const read1 = reader1.read(new Uint8Array([0x11]));
await flushAsyncEvents();
// branch2 should provide the BYOB request.
const byobRequest = rs.controller.byobRequest;
assert_typed_array_equals(byobRequest.view, new Uint8Array([0x22]), 'first BYOB request');
byobRequest.view[0] = 0x01;
byobRequest.respond(1);
const result1 = await read1;
assert_equals(result1.done, false, 'first read should not be done');
assert_typed_array_equals(result1.value, new Uint8Array([0x1]), 'first read');
const result2 = await read2;
assert_equals(result2.done, false, 'second read should not be done');
assert_typed_array_equals(result2.value, new Uint8Array([0x1]), 'second read');
}, 'ReadableStream teeing with byte source: read from branch2, then read from branch1');
promise_test(async () => {
const rs = recordingReadableStream({ type: 'bytes' });
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader();
const reader2 = branch2.getReader({ mode: 'byob' });
await flushAsyncEvents();
const read1 = reader1.read();
const read2 = reader2.read(new Uint8Array([0x22]));
await flushAsyncEvents();
// There should be no BYOB request.
assert_equals(rs.controller.byobRequest, null, 'first BYOB request');
// Close the stream.
rs.controller.close();
const result1 = await read1;
assert_equals(result1.done, true, 'read from branch1 should be done');
assert_equals(result1.value, undefined, 'read from branch1');
// branch2 should get its buffer back.
const result2 = await read2;
assert_equals(result2.done, true, 'read from branch2 should be done');
assert_typed_array_equals(result2.value, new Uint8Array([0x22]).subarray(0, 0), 'read from branch2');
}, 'ReadableStream teeing with byte source: read from branch1 with default reader, then close while branch2 has pending BYOB read');
promise_test(async () => {
const rs = recordingReadableStream({ type: 'bytes' });
const [branch1, branch2] = rs.tee();
const reader1 = branch1.getReader({ mode: 'byob' });
const reader2 = branch2.getReader();
await flushAsyncEvents();
const read2 = reader2.read();
const read1 = reader1.read(new Uint8Array([0x11]));
await flushAsyncEvents();
// There should be no BYOB request.
assert_equals(rs.controller.byobRequest, null, 'first BYOB request');
// Close the stream.
rs.controller.close();
const result2 = await read2;
assert_equals(result2.done, true, 'read from branch2 should be done');
assert_equals(result2.value, undefined, 'read from branch2');
// branch1 should get its buffer back.
const result1 = await read1;
assert_equals(result1.done, true, 'read from branch1 should be done');
assert_typed_array_equals(result1.value, new Uint8Array([0x11]).subarray(0, 0), 'read from branch1');
}, 'ReadableStream teeing with byte source: read from branch2 with default reader, then close while branch1 has pending BYOB read');
promise_test(async () => {
const rs = recordingReadableStream({ type: 'bytes' });
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
await flushAsyncEvents();
const read1 = reader1.read(new Uint8Array([0x11]));
const read2 = reader2.read(new Uint8Array([0x22]));
await flushAsyncEvents();
// branch1 should provide the BYOB request.
const byobRequest = rs.controller.byobRequest;
assert_typed_array_equals(byobRequest.view, new Uint8Array([0x11]), 'first BYOB request');
// Close the stream.
rs.controller.close();
byobRequest.respond(0);
// Both branches should get their buffers back.
const result1 = await read1;
assert_equals(result1.done, true, 'first read should be done');
assert_typed_array_equals(result1.value, new Uint8Array([0x11]).subarray(0, 0), 'first read');
const result2 = await read2;
assert_equals(result2.done, true, 'second read should be done');
assert_typed_array_equals(result2.value, new Uint8Array([0x22]).subarray(0, 0), 'second read');
}, 'ReadableStream teeing with byte source: close when both branches have pending BYOB reads');
promise_test(async () => {
const rs = recordingReadableStream({ type: 'bytes' });
const [reader1, reader2] = rs.tee().map(branch => branch.getReader());
const branch1Reads = [reader1.read(), reader1.read()];
const branch2Reads = [reader2.read(), reader2.read()];
await flushAsyncEvents();
rs.controller.enqueue(new Uint8Array([0x11]));
rs.controller.close();
const result1 = await branch1Reads[0];
assert_equals(result1.done, false, 'first read() from branch1 should be not done');
assert_typed_array_equals(result1.value, new Uint8Array([0x11]), 'first chunk from branch1 should be correct');
const result2 = await branch2Reads[0];
assert_equals(result2.done, false, 'first read() from branch2 should be not done');
assert_typed_array_equals(result2.value, new Uint8Array([0x11]), 'first chunk from branch2 should be correct');
assert_object_equals(await branch1Reads[1], { value: undefined, done: true }, 'second read() from branch1 should be done');
assert_object_equals(await branch2Reads[1], { value: undefined, done: true }, 'second read() from branch2 should be done');
}, 'ReadableStream teeing with byte source: enqueue() and close() while both branches are pulling');
promise_test(async () => {
const rs = recordingReadableStream({ type: 'bytes' });
const [reader1, reader2] = rs.tee().map(branch => branch.getReader({ mode: 'byob' }));
const branch1Reads = [reader1.read(new Uint8Array(1)), reader1.read(new Uint8Array(1))];
const branch2Reads = [reader2.read(new Uint8Array(1)), reader2.read(new Uint8Array(1))];
await flushAsyncEvents();
rs.controller.byobRequest.view[0] = 0x11;
rs.controller.byobRequest.respond(1);
rs.controller.close();
const result1 = await branch1Reads[0];
assert_equals(result1.done, false, 'first read() from branch1 should be not done');
assert_typed_array_equals(result1.value, new Uint8Array([0x11]), 'first chunk from branch1 should be correct');
const result2 = await branch2Reads[0];
assert_equals(result2.done, false, 'first read() from branch2 should be not done');
assert_typed_array_equals(result2.value, new Uint8Array([0x11]), 'first chunk from branch2 should be correct');
const result3 = await branch1Reads[1];
assert_equals(result3.done, true, 'second read() from branch1 should be done');
assert_typed_array_equals(result3.value, new Uint8Array([0]).subarray(0, 0), 'second chunk from branch1 should be correct');
const result4 = await branch2Reads[1];
assert_equals(result4.done, true, 'second read() from branch2 should be done');
assert_typed_array_equals(result4.value, new Uint8Array([0]).subarray(0, 0), 'second chunk from branch2 should be correct');
}, 'ReadableStream teeing with byte source: respond() and close() while both branches are pulling');
promise_test(async t => {
let pullCount = 0;
const arrayBuffer = new Uint8Array([0x01, 0x02, 0x03]).buffer;
const enqueuedChunk = new Uint8Array(arrayBuffer, 2);
assert_equals(enqueuedChunk.length, 1);
assert_equals(enqueuedChunk.byteOffset, 2);
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;
// The first stream has the transferred buffer, but the second stream has the
// cloned buffer.
const underlying = new Uint8Array([0x01, 0x02, 0x03]).buffer;
assert_typed_array_equals(view1, new Uint8Array(underlying, 2), 'reader1 value');
assert_typed_array_equals(view2, new Uint8Array([0x03]), 'reader2 value');
}, 'ReadableStream teeing with byte source: reading an array with a byte offset should clone correctly');