stream: implement min
option for ReadableStreamBYOBReader.read
PR-URL: https://github.com/nodejs/node/pull/50888 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Debadree Chatterjee <debadree333@gmail.com>
This commit is contained in:
parent
515b007fae
commit
2e81415d9a
@ -492,7 +492,7 @@ added: v16.5.0
|
||||
-->
|
||||
|
||||
* Returns: A promise fulfilled with an object:
|
||||
* `value` {ArrayBuffer}
|
||||
* `value` {any}
|
||||
* `done` {boolean}
|
||||
|
||||
Requests the next chunk of data from the underlying {ReadableStream}
|
||||
@ -617,15 +617,24 @@ added: v16.5.0
|
||||
{ReadableStream} is closed or rejected if the stream errors or the reader's
|
||||
lock is released before the stream finishes closing.
|
||||
|
||||
#### `readableStreamBYOBReader.read(view)`
|
||||
#### `readableStreamBYOBReader.read(view[, options])`
|
||||
|
||||
<!-- YAML
|
||||
added: v16.5.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/50888
|
||||
description: Added `min` option.
|
||||
-->
|
||||
|
||||
* `view` {Buffer|TypedArray|DataView}
|
||||
* `options` {Object}
|
||||
* `min` {number} When set, the returned promise will only be
|
||||
fulfilled as soon as `min` number of elements are available.
|
||||
When not set, the promise fulfills when at least one element
|
||||
is available.
|
||||
* Returns: A promise fulfilled with an object:
|
||||
* `value` {ArrayBuffer}
|
||||
* `value` {TypedArray|DataView}
|
||||
* `done` {boolean}
|
||||
|
||||
Requests the next chunk of data from the underlying {ReadableStream}
|
||||
|
@ -47,9 +47,7 @@ const {
|
||||
const {
|
||||
validateString,
|
||||
validateObject,
|
||||
kValidateObjectAllowNullable,
|
||||
kValidateObjectAllowArray,
|
||||
kValidateObjectAllowFunction,
|
||||
kValidateObjectAllowObjectsAndNull,
|
||||
} = require('internal/validators');
|
||||
const binding = internalBinding('encoding_binding');
|
||||
const {
|
||||
@ -393,10 +391,6 @@ const TextDecoder =
|
||||
makeTextDecoderICU() :
|
||||
makeTextDecoderJS();
|
||||
|
||||
const kValidateObjectAllowObjectsAndNull = kValidateObjectAllowNullable |
|
||||
kValidateObjectAllowArray |
|
||||
kValidateObjectAllowFunction;
|
||||
|
||||
function makeTextDecoderICU() {
|
||||
const {
|
||||
decode: _decode,
|
||||
|
@ -222,6 +222,11 @@ const kValidateObjectNone = 0;
|
||||
const kValidateObjectAllowNullable = 1 << 0;
|
||||
const kValidateObjectAllowArray = 1 << 1;
|
||||
const kValidateObjectAllowFunction = 1 << 2;
|
||||
const kValidateObjectAllowObjects = kValidateObjectAllowArray |
|
||||
kValidateObjectAllowFunction;
|
||||
const kValidateObjectAllowObjectsAndNull = kValidateObjectAllowNullable |
|
||||
kValidateObjectAllowArray |
|
||||
kValidateObjectAllowFunction;
|
||||
|
||||
/**
|
||||
* @callback validateObject
|
||||
@ -583,6 +588,8 @@ module.exports = {
|
||||
kValidateObjectAllowNullable,
|
||||
kValidateObjectAllowArray,
|
||||
kValidateObjectAllowFunction,
|
||||
kValidateObjectAllowObjects,
|
||||
kValidateObjectAllowObjectsAndNull,
|
||||
validateOneOf,
|
||||
validatePlainFunction,
|
||||
validatePort,
|
||||
|
@ -22,6 +22,7 @@ const {
|
||||
SymbolAsyncIterator,
|
||||
SymbolDispose,
|
||||
SymbolToStringTag,
|
||||
TypedArrayPrototypeGetLength,
|
||||
Uint8Array,
|
||||
} = primordials;
|
||||
|
||||
@ -33,6 +34,7 @@ const {
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_INVALID_STATE,
|
||||
ERR_INVALID_THIS,
|
||||
ERR_OUT_OF_RANGE,
|
||||
},
|
||||
} = require('internal/errors');
|
||||
|
||||
@ -58,8 +60,8 @@ const {
|
||||
validateAbortSignal,
|
||||
validateBuffer,
|
||||
validateObject,
|
||||
kValidateObjectAllowNullable,
|
||||
kValidateObjectAllowFunction,
|
||||
kValidateObjectAllowObjects,
|
||||
kValidateObjectAllowObjectsAndNull,
|
||||
} = require('internal/validators');
|
||||
|
||||
const {
|
||||
@ -246,10 +248,10 @@ class ReadableStream {
|
||||
* @param {UnderlyingSource} [source]
|
||||
* @param {QueuingStrategy} [strategy]
|
||||
*/
|
||||
constructor(source = {}, strategy = kEmptyObject) {
|
||||
constructor(source = kEmptyObject, strategy = kEmptyObject) {
|
||||
markTransferMode(this, false, true);
|
||||
if (source === null)
|
||||
throw new ERR_INVALID_ARG_VALUE('source', 'Object', source);
|
||||
validateObject(source, 'source', kValidateObjectAllowObjects);
|
||||
validateObject(strategy, 'strategy', kValidateObjectAllowObjectsAndNull);
|
||||
this[kState] = createReadableStreamState();
|
||||
|
||||
this[kIsClosedPromise] = createDeferredPromise();
|
||||
@ -332,7 +334,7 @@ class ReadableStream {
|
||||
getReader(options = kEmptyObject) {
|
||||
if (!isReadableStream(this))
|
||||
throw new ERR_INVALID_THIS('ReadableStream');
|
||||
validateObject(options, 'options', kValidateObjectAllowNullable | kValidateObjectAllowFunction);
|
||||
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
|
||||
const mode = options?.mode;
|
||||
|
||||
if (mode === undefined)
|
||||
@ -370,6 +372,7 @@ class ReadableStream {
|
||||
|
||||
// The web platform tests require that these be handled one at a
|
||||
// time and in a specific order. options can be null or undefined.
|
||||
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
|
||||
const preventAbort = options?.preventAbort;
|
||||
const preventCancel = options?.preventCancel;
|
||||
const preventClose = options?.preventClose;
|
||||
@ -412,6 +415,7 @@ class ReadableStream {
|
||||
destination);
|
||||
}
|
||||
|
||||
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
|
||||
const preventAbort = options?.preventAbort;
|
||||
const preventCancel = options?.preventCancel;
|
||||
const preventClose = options?.preventClose;
|
||||
@ -456,10 +460,8 @@ class ReadableStream {
|
||||
values(options = kEmptyObject) {
|
||||
if (!isReadableStream(this))
|
||||
throw new ERR_INVALID_THIS('ReadableStream');
|
||||
validateObject(options, 'options');
|
||||
const {
|
||||
preventCancel = false,
|
||||
} = options;
|
||||
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
|
||||
const preventCancel = !!(options?.preventCancel);
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
const reader = new ReadableStreamDefaultReader(this);
|
||||
@ -931,47 +933,62 @@ class ReadableStreamBYOBReader {
|
||||
|
||||
/**
|
||||
* @param {ArrayBufferView} view
|
||||
* @param {{
|
||||
* min? : number
|
||||
* }} [options]
|
||||
* @returns {Promise<{
|
||||
* view : ArrayBufferView,
|
||||
* value : ArrayBufferView,
|
||||
* done : boolean,
|
||||
* }>}
|
||||
*/
|
||||
read(view) {
|
||||
async read(view, options = kEmptyObject) {
|
||||
if (!isReadableStreamBYOBReader(this))
|
||||
return PromiseReject(new ERR_INVALID_THIS('ReadableStreamBYOBReader'));
|
||||
throw new ERR_INVALID_THIS('ReadableStreamBYOBReader');
|
||||
if (!isArrayBufferView(view)) {
|
||||
return PromiseReject(
|
||||
new ERR_INVALID_ARG_TYPE(
|
||||
'view',
|
||||
[
|
||||
'Buffer',
|
||||
'TypedArray',
|
||||
'DataView',
|
||||
],
|
||||
view));
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
'view',
|
||||
[
|
||||
'Buffer',
|
||||
'TypedArray',
|
||||
'DataView',
|
||||
],
|
||||
view,
|
||||
);
|
||||
}
|
||||
validateObject(options, 'options', kValidateObjectAllowObjectsAndNull);
|
||||
|
||||
const viewByteLength = ArrayBufferViewGetByteLength(view);
|
||||
const viewBuffer = ArrayBufferViewGetBuffer(view);
|
||||
const viewBufferByteLength = ArrayBufferPrototypeGetByteLength(viewBuffer);
|
||||
|
||||
if (viewByteLength === 0 || viewBufferByteLength === 0) {
|
||||
return PromiseReject(
|
||||
new ERR_INVALID_STATE.TypeError(
|
||||
'View or Viewed ArrayBuffer is zero-length or detached',
|
||||
),
|
||||
);
|
||||
throw new ERR_INVALID_STATE.TypeError(
|
||||
'View or Viewed ArrayBuffer is zero-length or detached');
|
||||
}
|
||||
|
||||
// Supposed to assert here that the view's buffer is not
|
||||
// detached, but there's no API available to use to check that.
|
||||
|
||||
const min = options?.min ?? 1;
|
||||
if (typeof min !== 'number')
|
||||
throw new ERR_INVALID_ARG_TYPE('options.min', 'number', min);
|
||||
if (!NumberIsInteger(min))
|
||||
throw new ERR_INVALID_ARG_VALUE('options.min', min, 'must be an integer');
|
||||
if (min <= 0)
|
||||
throw new ERR_INVALID_ARG_VALUE('options.min', min, 'must be greater than 0');
|
||||
if (!isDataView(view)) {
|
||||
if (min > TypedArrayPrototypeGetLength(view)) {
|
||||
throw new ERR_OUT_OF_RANGE('options.min', '<= view.length', min);
|
||||
}
|
||||
} else if (min > viewByteLength) {
|
||||
throw new ERR_OUT_OF_RANGE('options.min', '<= view.byteLength', min);
|
||||
}
|
||||
|
||||
if (this[kState].stream === undefined) {
|
||||
return PromiseReject(
|
||||
new ERR_INVALID_STATE.TypeError(
|
||||
'The reader is not attached to a stream'));
|
||||
throw new ERR_INVALID_STATE.TypeError('The reader is not attached to a stream');
|
||||
}
|
||||
const readIntoRequest = new ReadIntoRequest();
|
||||
readableStreamBYOBReaderRead(this, view, readIntoRequest);
|
||||
readableStreamBYOBReaderRead(this, view, min, readIntoRequest);
|
||||
return readIntoRequest.promise;
|
||||
}
|
||||
|
||||
@ -1885,7 +1902,7 @@ function readableByteStreamTee(stream) {
|
||||
reading = false;
|
||||
},
|
||||
};
|
||||
readableStreamBYOBReaderRead(reader, view, readIntoRequest);
|
||||
readableStreamBYOBReaderRead(reader, view, 1, readIntoRequest);
|
||||
}
|
||||
|
||||
function pull1Algorithm() {
|
||||
@ -2212,7 +2229,7 @@ function readableStreamReaderGenericRelease(reader) {
|
||||
reader[kState].stream = undefined;
|
||||
}
|
||||
|
||||
function readableStreamBYOBReaderRead(reader, view, readIntoRequest) {
|
||||
function readableStreamBYOBReaderRead(reader, view, min, readIntoRequest) {
|
||||
const {
|
||||
stream,
|
||||
} = reader[kState];
|
||||
@ -2225,6 +2242,7 @@ function readableStreamBYOBReaderRead(reader, view, readIntoRequest) {
|
||||
readableByteStreamControllerPullInto(
|
||||
stream[kState].controller,
|
||||
view,
|
||||
min,
|
||||
readIntoRequest);
|
||||
}
|
||||
|
||||
@ -2497,7 +2515,7 @@ function readableByteStreamControllerClose(controller) {
|
||||
|
||||
if (pendingPullIntos.length) {
|
||||
const firstPendingPullInto = pendingPullIntos[0];
|
||||
if (firstPendingPullInto.bytesFilled > 0) {
|
||||
if (firstPendingPullInto.bytesFilled % firstPendingPullInto.elementSize !== 0) {
|
||||
const error = new ERR_INVALID_STATE.TypeError('Partial read');
|
||||
readableByteStreamControllerError(controller, error);
|
||||
throw error;
|
||||
@ -2514,7 +2532,7 @@ function readableByteStreamControllerCommitPullIntoDescriptor(stream, desc) {
|
||||
|
||||
let done = false;
|
||||
if (stream[kState].state === 'closed') {
|
||||
desc.bytesFilled = 0;
|
||||
assert(desc.bytesFilled % desc.elementSize === 0);
|
||||
done = true;
|
||||
}
|
||||
|
||||
@ -2603,6 +2621,7 @@ function readableByteStreamControllerHandleQueueDrain(controller) {
|
||||
function readableByteStreamControllerPullInto(
|
||||
controller,
|
||||
view,
|
||||
min,
|
||||
readIntoRequest) {
|
||||
const {
|
||||
closeRequested,
|
||||
@ -2615,6 +2634,11 @@ function readableByteStreamControllerPullInto(
|
||||
elementSize = view.constructor.BYTES_PER_ELEMENT;
|
||||
ctor = view.constructor;
|
||||
}
|
||||
|
||||
const minimumFill = min * elementSize;
|
||||
assert(minimumFill >= elementSize && minimumFill <= view.byteLength);
|
||||
assert(minimumFill % elementSize === 0);
|
||||
|
||||
const buffer = ArrayBufferViewGetBuffer(view);
|
||||
const byteOffset = ArrayBufferViewGetByteOffset(view);
|
||||
const byteLength = ArrayBufferViewGetByteLength(view);
|
||||
@ -2633,6 +2657,7 @@ function readableByteStreamControllerPullInto(
|
||||
byteOffset,
|
||||
byteLength,
|
||||
bytesFilled: 0,
|
||||
minimumFill,
|
||||
elementSize,
|
||||
ctor,
|
||||
type: 'byob',
|
||||
@ -2720,7 +2745,7 @@ function readableByteStreamControllerRespond(controller, bytesWritten) {
|
||||
}
|
||||
|
||||
function readableByteStreamControllerRespondInClosedState(controller, desc) {
|
||||
assert(!desc.bytesFilled);
|
||||
assert(desc.bytesFilled % desc.elementSize === 0);
|
||||
if (desc.type === 'none') {
|
||||
readableByteStreamControllerShiftPendingPullInto(controller);
|
||||
}
|
||||
@ -2897,9 +2922,9 @@ function readableByteStreamControllerFillPullIntoDescriptorFromQueue(
|
||||
byteLength,
|
||||
byteOffset,
|
||||
bytesFilled,
|
||||
minimumFill,
|
||||
elementSize,
|
||||
} = desc;
|
||||
const currentAlignedBytes = bytesFilled - (bytesFilled % elementSize);
|
||||
const maxBytesToCopy = MathMin(
|
||||
controller[kState].queueTotalSize,
|
||||
byteLength - bytesFilled);
|
||||
@ -2907,7 +2932,8 @@ function readableByteStreamControllerFillPullIntoDescriptorFromQueue(
|
||||
const maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize);
|
||||
let totalBytesToCopyRemaining = maxBytesToCopy;
|
||||
let ready = false;
|
||||
if (maxAlignedBytes > currentAlignedBytes) {
|
||||
assert(bytesFilled < minimumFill);
|
||||
if (maxAlignedBytes >= minimumFill) {
|
||||
totalBytesToCopyRemaining = maxAlignedBytes - bytesFilled;
|
||||
ready = true;
|
||||
}
|
||||
@ -2950,7 +2976,7 @@ function readableByteStreamControllerFillPullIntoDescriptorFromQueue(
|
||||
if (!ready) {
|
||||
assert(!controller[kState].queueTotalSize);
|
||||
assert(desc.bytesFilled > 0);
|
||||
assert(desc.bytesFilled < elementSize);
|
||||
assert(desc.bytesFilled < minimumFill);
|
||||
}
|
||||
return ready;
|
||||
}
|
||||
@ -3006,7 +3032,7 @@ function readableByteStreamControllerRespondInReadableState(
|
||||
return;
|
||||
}
|
||||
|
||||
if (desc.bytesFilled < desc.elementSize)
|
||||
if (desc.bytesFilled < desc.minimumFill)
|
||||
return;
|
||||
|
||||
readableByteStreamControllerShiftPendingPullInto(controller);
|
||||
@ -3191,6 +3217,7 @@ function readableByteStreamControllerPullSteps(controller, readRequest) {
|
||||
byteOffset: 0,
|
||||
byteLength: autoAllocateChunkSize,
|
||||
bytesFilled: 0,
|
||||
minimumFill: 1,
|
||||
elementSize: 1,
|
||||
ctor: Uint8Array,
|
||||
type: 'default',
|
||||
|
@ -29,6 +29,12 @@ const {
|
||||
kEnumerableProperty,
|
||||
} = require('internal/util');
|
||||
|
||||
const {
|
||||
validateObject,
|
||||
kValidateObjectAllowObjects,
|
||||
kValidateObjectAllowObjectsAndNull,
|
||||
} = require('internal/validators');
|
||||
|
||||
const {
|
||||
kDeserialize,
|
||||
kTransfer,
|
||||
@ -119,10 +125,13 @@ class TransformStream {
|
||||
* @param {QueuingStrategy} [readableStrategy]
|
||||
*/
|
||||
constructor(
|
||||
transformer = null,
|
||||
transformer = kEmptyObject,
|
||||
writableStrategy = kEmptyObject,
|
||||
readableStrategy = kEmptyObject) {
|
||||
markTransferMode(this, false, true);
|
||||
validateObject(transformer, 'transformer', kValidateObjectAllowObjects);
|
||||
validateObject(writableStrategy, 'writableStrategy', kValidateObjectAllowObjectsAndNull);
|
||||
validateObject(readableStrategy, 'readableStrategy', kValidateObjectAllowObjectsAndNull);
|
||||
const readableType = transformer?.readableType;
|
||||
const writableType = transformer?.writableType;
|
||||
const start = transformer?.start;
|
||||
|
@ -37,6 +37,12 @@ const {
|
||||
SideEffectFreeRegExpPrototypeSymbolReplace,
|
||||
} = require('internal/util');
|
||||
|
||||
const {
|
||||
validateObject,
|
||||
kValidateObjectAllowObjects,
|
||||
kValidateObjectAllowObjectsAndNull,
|
||||
} = require('internal/validators');
|
||||
|
||||
const {
|
||||
MessageChannel,
|
||||
} = require('internal/worker/io');
|
||||
@ -154,8 +160,10 @@ class WritableStream {
|
||||
* @param {UnderlyingSink} [sink]
|
||||
* @param {QueuingStrategy} [strategy]
|
||||
*/
|
||||
constructor(sink = null, strategy = kEmptyObject) {
|
||||
constructor(sink = kEmptyObject, strategy = kEmptyObject) {
|
||||
markTransferMode(this, false, true);
|
||||
validateObject(sink, 'sink', kValidateObjectAllowObjects);
|
||||
validateObject(strategy, 'strategy', kValidateObjectAllowObjectsAndNull);
|
||||
const type = sink?.type;
|
||||
if (type !== undefined)
|
||||
throw new ERR_INVALID_ARG_VALUE.RangeError('type', type);
|
||||
|
2
test/fixtures/wpt/README.md
vendored
2
test/fixtures/wpt/README.md
vendored
@ -27,7 +27,7 @@ Last update:
|
||||
- performance-timeline: https://github.com/web-platform-tests/wpt/tree/17ebc3aea0/performance-timeline
|
||||
- resource-timing: https://github.com/web-platform-tests/wpt/tree/22d38586d0/resource-timing
|
||||
- resources: https://github.com/web-platform-tests/wpt/tree/1e140d63ec/resources
|
||||
- streams: https://github.com/web-platform-tests/wpt/tree/a8872d92b1/streams
|
||||
- streams: https://github.com/web-platform-tests/wpt/tree/3df6d94318/streams
|
||||
- url: https://github.com/web-platform-tests/wpt/tree/c2d7e70b52/url
|
||||
- user-timing: https://github.com/web-platform-tests/wpt/tree/5ae85bf826/user-timing
|
||||
- wasm/jsapi: https://github.com/web-platform-tests/wpt/tree/cde25e7e3c/wasm/jsapi
|
||||
|
@ -1,5 +1,4 @@
|
||||
// META: global=window,worker,shadowrealm
|
||||
// META: script=../resources/test-utils.js
|
||||
// META: script=../resources/recording-streams.js
|
||||
'use strict';
|
||||
|
||||
@ -39,7 +38,8 @@ promise_test(t => {
|
||||
const fakeRS = Object.create(ReadableStream.prototype);
|
||||
const ws = new WritableStream();
|
||||
|
||||
return methodRejects(t, ReadableStream.prototype, 'pipeTo', fakeRS, [ws]);
|
||||
return promise_rejects_js(t, TypeError, ReadableStream.prototype.pipeTo.apply(fakeRS, [ws]),
|
||||
'pipeTo should reject with a TypeError');
|
||||
|
||||
}, 'pipeTo must check the brand of its ReadableStream this value');
|
||||
|
||||
@ -48,7 +48,8 @@ promise_test(t => {
|
||||
const rs = new ReadableStream();
|
||||
const fakeWS = Object.create(WritableStream.prototype);
|
||||
|
||||
return methodRejects(t, ReadableStream.prototype, 'pipeTo', rs, [fakeWS]);
|
||||
return promise_rejects_js(t, TypeError, ReadableStream.prototype.pipeTo.apply(rs, [fakeWS]),
|
||||
'pipeTo should reject with a TypeError');
|
||||
|
||||
}, 'pipeTo must check the brand of its WritableStream argument');
|
||||
|
||||
|
@ -13,6 +13,18 @@ promise_test(async t => {
|
||||
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({
|
||||
|
774
test/fixtures/wpt/streams/readable-byte-streams/read-min.any.js
vendored
Normal file
774
test/fixtures/wpt/streams/readable-byte-streams/read-min.any.js
vendored
Normal 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');
|
@ -1,52 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
self.getterRejects = (t, obj, getterName, target) => {
|
||||
const getter = Object.getOwnPropertyDescriptor(obj, getterName).get;
|
||||
|
||||
return promise_rejects_js(t, TypeError, getter.call(target), getterName + ' should reject with a TypeError');
|
||||
};
|
||||
|
||||
self.getterRejectsForAll = (t, obj, getterName, targets) => {
|
||||
return Promise.all(targets.map(target => self.getterRejects(t, obj, getterName, target)));
|
||||
};
|
||||
|
||||
self.methodRejects = (t, obj, methodName, target, args) => {
|
||||
const method = obj[methodName];
|
||||
|
||||
return promise_rejects_js(t, TypeError, method.apply(target, args),
|
||||
methodName + ' should reject with a TypeError');
|
||||
};
|
||||
|
||||
self.methodRejectsForAll = (t, obj, methodName, targets, args) => {
|
||||
return Promise.all(targets.map(target => self.methodRejects(t, obj, methodName, target, args)));
|
||||
};
|
||||
|
||||
self.getterThrows = (obj, getterName, target) => {
|
||||
const getter = Object.getOwnPropertyDescriptor(obj, getterName).get;
|
||||
|
||||
assert_throws_js(TypeError, () => getter.call(target), getterName + ' should throw a TypeError');
|
||||
};
|
||||
|
||||
self.getterThrowsForAll = (obj, getterName, targets) => {
|
||||
targets.forEach(target => self.getterThrows(obj, getterName, target));
|
||||
};
|
||||
|
||||
self.methodThrows = (obj, methodName, target, args) => {
|
||||
const method = obj[methodName];
|
||||
assert_equals(typeof method, 'function', methodName + ' should exist');
|
||||
|
||||
assert_throws_js(TypeError, () => method.apply(target, args), methodName + ' should throw a TypeError');
|
||||
};
|
||||
|
||||
self.methodThrowsForAll = (obj, methodName, targets, args) => {
|
||||
targets.forEach(target => self.methodThrows(obj, methodName, target, args));
|
||||
};
|
||||
|
||||
self.constructorThrowsForAll = (constructor, firstArgs) => {
|
||||
firstArgs.forEach(firstArg => assert_throws_js(TypeError, () => new constructor(firstArg),
|
||||
'constructor should throw a TypeError'));
|
||||
};
|
||||
|
||||
self.delay = ms => new Promise(resolve => step_timeout(resolve, ms));
|
||||
|
||||
// For tests which verify that the implementation doesn't do something it shouldn't, it's better not to use a
|
||||
|
2
test/fixtures/wpt/versions.json
vendored
2
test/fixtures/wpt/versions.json
vendored
@ -68,7 +68,7 @@
|
||||
"path": "resources"
|
||||
},
|
||||
"streams": {
|
||||
"commit": "a8872d92b147fc87200eb0c14fe7a4a9e7cd4f73",
|
||||
"commit": "3df6d94318b225845a0c8e4c7718484f41c9b8ce",
|
||||
"path": "streams"
|
||||
},
|
||||
"url": {
|
||||
|
@ -181,17 +181,29 @@ const {
|
||||
}
|
||||
|
||||
{
|
||||
// These are silly but they should all work per spec
|
||||
new ReadableStream(1);
|
||||
new ReadableStream('hello');
|
||||
new ReadableStream(false);
|
||||
new ReadableStream({});
|
||||
new ReadableStream([]);
|
||||
new ReadableStream(1, 1);
|
||||
new ReadableStream(1, 'hello');
|
||||
new ReadableStream(1, false);
|
||||
new ReadableStream(1, []);
|
||||
new ReadableStream({}, null);
|
||||
new ReadableStream({}, {});
|
||||
new ReadableStream({}, []);
|
||||
}
|
||||
|
||||
['a', false, 1, null].forEach((source) => {
|
||||
assert.throws(() => {
|
||||
new ReadableStream(source);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
});
|
||||
|
||||
['a', false, 1].forEach((strategy) => {
|
||||
assert.throws(() => {
|
||||
new ReadableStream({}, strategy);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
});
|
||||
|
||||
['a', {}, false].forEach((size) => {
|
||||
assert.throws(() => {
|
||||
new ReadableStream({}, { size });
|
||||
|
@ -30,6 +30,40 @@ assert.throws(() => new TransformStream({ writableType: 1 }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
});
|
||||
|
||||
{
|
||||
new TransformStream({});
|
||||
new TransformStream([]);
|
||||
new TransformStream({}, null);
|
||||
new TransformStream({}, {});
|
||||
new TransformStream({}, []);
|
||||
new TransformStream({}, {}, null);
|
||||
new TransformStream({}, {}, {});
|
||||
new TransformStream({}, {}, []);
|
||||
}
|
||||
|
||||
['a', false, 1, null].forEach((transform) => {
|
||||
assert.throws(() => {
|
||||
new TransformStream(transform);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
});
|
||||
|
||||
['a', false, 1].forEach((writableStrategy) => {
|
||||
assert.throws(() => {
|
||||
new TransformStream({}, writableStrategy);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
});
|
||||
|
||||
['a', false, 1].forEach((readableStrategy) => {
|
||||
assert.throws(() => {
|
||||
new TransformStream({}, {}, readableStrategy);
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
});
|
||||
|
||||
{
|
||||
const stream = new TransformStream();
|
||||
|
@ -60,6 +60,18 @@ class Sink {
|
||||
assert.strictEqual(typeof stream.getWriter, 'function');
|
||||
}
|
||||
|
||||
['a', false, 1, null].forEach((sink) => {
|
||||
assert.throws(() => new WritableStream(sink), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
});
|
||||
|
||||
['a', false, 1].forEach((strategy) => {
|
||||
assert.throws(() => new WritableStream({}, strategy), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
});
|
||||
|
||||
[1, false, ''].forEach((type) => {
|
||||
assert.throws(() => new WritableStream({ type }), {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
@ -79,9 +91,11 @@ class Sink {
|
||||
});
|
||||
|
||||
{
|
||||
new WritableStream({}, 1);
|
||||
new WritableStream({}, 'a');
|
||||
new WritableStream({});
|
||||
new WritableStream([]);
|
||||
new WritableStream({}, null);
|
||||
new WritableStream({}, {});
|
||||
new WritableStream({}, []);
|
||||
}
|
||||
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user