feat(streams): ReadableStream.read min option (#20849)

This commit is contained in:
Leo Kettmeir 2023-11-24 23:24:41 +01:00 committed by GitHub
parent 998d9061ef
commit 6f02fa1abf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 72 additions and 15 deletions

View file

@ -79,6 +79,7 @@ const {
TypedArrayPrototypeGetBuffer, TypedArrayPrototypeGetBuffer,
TypedArrayPrototypeGetByteLength, TypedArrayPrototypeGetByteLength,
TypedArrayPrototypeGetByteOffset, TypedArrayPrototypeGetByteOffset,
TypedArrayPrototypeGetLength,
TypedArrayPrototypeGetSymbolToStringTag, TypedArrayPrototypeGetSymbolToStringTag,
TypedArrayPrototypeSet, TypedArrayPrototypeSet,
TypedArrayPrototypeSlice, TypedArrayPrototypeSlice,
@ -1303,7 +1304,9 @@ function readableByteStreamControllerClose(controller) {
} }
if (controller[_pendingPullIntos].length !== 0) { if (controller[_pendingPullIntos].length !== 0) {
const firstPendingPullInto = controller[_pendingPullIntos][0]; const firstPendingPullInto = controller[_pendingPullIntos][0];
if (firstPendingPullInto.bytesFilled > 0) { if (
firstPendingPullInto.bytesFilled % firstPendingPullInto.elementSize !== 0
) {
const e = new TypeError( const e = new TypeError(
"Insufficient bytes to fill elements in the given buffer", "Insufficient bytes to fill elements in the given buffer",
); );
@ -1847,10 +1850,11 @@ function readableStreamDefaultcontrollerShouldCallPull(controller) {
/** /**
* @param {ReadableStreamBYOBReader} reader * @param {ReadableStreamBYOBReader} reader
* @param {ArrayBufferView} view * @param {ArrayBufferView} view
* @param {number} min
* @param {ReadIntoRequest} readIntoRequest * @param {ReadIntoRequest} readIntoRequest
* @returns {void} * @returns {void}
*/ */
function readableStreamBYOBReaderRead(reader, view, readIntoRequest) { function readableStreamBYOBReaderRead(reader, view, min, readIntoRequest) {
const stream = reader[_stream]; const stream = reader[_stream];
assert(stream); assert(stream);
stream[_disturbed] = true; stream[_disturbed] = true;
@ -1860,6 +1864,7 @@ function readableStreamBYOBReaderRead(reader, view, readIntoRequest) {
readableByteStreamControllerPullInto( readableByteStreamControllerPullInto(
stream[_controller], stream[_controller],
view, view,
min,
readIntoRequest, readIntoRequest,
); );
} }
@ -1935,12 +1940,14 @@ function readableByteStreamControllerProcessReadRequestsUsingQueue(
/** /**
* @param {ReadableByteStreamController} controller * @param {ReadableByteStreamController} controller
* @param {ArrayBufferView} view * @param {ArrayBufferView} view
* @param {number} min
* @param {ReadIntoRequest} readIntoRequest * @param {ReadIntoRequest} readIntoRequest
* @returns {void} * @returns {void}
*/ */
function readableByteStreamControllerPullInto( function readableByteStreamControllerPullInto(
controller, controller,
view, view,
min,
readIntoRequest, readIntoRequest,
) { ) {
const stream = controller[_stream]; const stream = controller[_stream];
@ -2010,6 +2017,10 @@ function readableByteStreamControllerPullInto(
); );
} }
const minimumFill = min * elementSize;
assert(minimumFill >= 0 && minimumFill <= byteLength);
assert(minimumFill % elementSize === 0);
try { try {
buffer = transferArrayBuffer(buffer); buffer = transferArrayBuffer(buffer);
} catch (e) { } catch (e) {
@ -2024,6 +2035,7 @@ function readableByteStreamControllerPullInto(
byteOffset, byteOffset,
byteLength, byteLength,
bytesFilled: 0, bytesFilled: 0,
minimumFill,
elementSize, elementSize,
viewConstructor: ctor, viewConstructor: ctor,
readerType: "byob", readerType: "byob",
@ -2139,7 +2151,7 @@ function readableByteStreamControllerRespondInReadableState(
); );
return; return;
} }
if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) { if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.minimumFill) {
return; return;
} }
readableByteStreamControllerShiftPendingPullInto(controller); readableByteStreamControllerShiftPendingPullInto(controller);
@ -2219,7 +2231,7 @@ function readableByteStreamControllerRespondInClosedState(
controller, controller,
firstDescriptor, firstDescriptor,
) { ) {
assert(firstDescriptor.bytesFilled === 0); assert(firstDescriptor.bytesFilled % firstDescriptor.elementSize === 0);
if (firstDescriptor.readerType === "none") { if (firstDescriptor.readerType === "none") {
readableByteStreamControllerShiftPendingPullInto(controller); readableByteStreamControllerShiftPendingPullInto(controller);
} }
@ -2249,7 +2261,9 @@ function readableByteStreamControllerCommitPullIntoDescriptor(
assert(pullIntoDescriptor.readerType !== "none"); assert(pullIntoDescriptor.readerType !== "none");
let done = false; let done = false;
if (stream[_state] === "closed") { if (stream[_state] === "closed") {
assert(pullIntoDescriptor.bytesFilled === 0); assert(
pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize === 0,
);
done = true; done = true;
} }
const filledView = readableByteStreamControllerConvertPullIntoDescriptor( const filledView = readableByteStreamControllerConvertPullIntoDescriptor(
@ -2340,19 +2354,18 @@ function readableByteStreamControllerFillPullIntoDescriptorFromQueue(
controller, controller,
pullIntoDescriptor, pullIntoDescriptor,
) { ) {
const elementSize = pullIntoDescriptor.elementSize;
const currentAlignedBytes = pullIntoDescriptor.bytesFilled -
(pullIntoDescriptor.bytesFilled % elementSize);
const maxBytesToCopy = MathMin( const maxBytesToCopy = MathMin(
controller[_queueTotalSize], controller[_queueTotalSize],
// deno-lint-ignore prefer-primordials // deno-lint-ignore prefer-primordials
pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled, pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled,
); );
const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy; const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy;
const maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize);
let totalBytesToCopyRemaining = maxBytesToCopy; let totalBytesToCopyRemaining = maxBytesToCopy;
let ready = false; let ready = false;
if (maxAlignedBytes > currentAlignedBytes) { assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.minimumFill);
const maxAlignedBytes = maxBytesFilled -
(maxBytesFilled % pullIntoDescriptor.elementSize);
if (maxAlignedBytes >= pullIntoDescriptor.minimumFill) {
totalBytesToCopyRemaining = maxAlignedBytes - totalBytesToCopyRemaining = maxAlignedBytes -
pullIntoDescriptor.bytesFilled; pullIntoDescriptor.bytesFilled;
ready = true; ready = true;
@ -2402,7 +2415,7 @@ function readableByteStreamControllerFillPullIntoDescriptorFromQueue(
if (!ready) { if (!ready) {
assert(controller[_queueTotalSize] === 0); assert(controller[_queueTotalSize] === 0);
assert(pullIntoDescriptor.bytesFilled > 0); assert(pullIntoDescriptor.bytesFilled > 0);
assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize); assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.minimumFill);
} }
return ready; return ready;
} }
@ -3375,7 +3388,7 @@ function readableByteStreamTee(stream) {
reading = false; reading = false;
}, },
}; };
readableStreamBYOBReaderRead(reader, view, readIntoRequest); readableStreamBYOBReaderRead(reader, view, 1, readIntoRequest);
} }
function pull1Algorithm() { function pull1Algorithm() {
@ -5543,13 +5556,19 @@ class ReadableStreamBYOBReader {
/** /**
* @param {ArrayBufferView} view * @param {ArrayBufferView} view
* @param {ReadableStreamBYOBReaderReadOptions} options
* @returns {Promise<ReadableStreamBYOBReadResult>} * @returns {Promise<ReadableStreamBYOBReadResult>}
*/ */
read(view) { read(view, options = {}) {
try { try {
webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype); webidl.assertBranded(this, ReadableStreamBYOBReaderPrototype);
const prefix = "Failed to execute 'read' on 'ReadableStreamBYOBReader'"; const prefix = "Failed to execute 'read' on 'ReadableStreamBYOBReader'";
view = webidl.converters.ArrayBufferView(view, prefix, "Argument 1"); view = webidl.converters.ArrayBufferView(view, prefix, "Argument 1");
options = webidl.converters.ReadableStreamBYOBReaderReadOptions(
options,
prefix,
"Argument 2",
);
} catch (err) { } catch (err) {
return PromiseReject(err); return PromiseReject(err);
} }
@ -5584,6 +5603,23 @@ class ReadableStreamBYOBReader {
); );
} }
if (options.min === 0) {
return PromiseReject(new TypeError("options.min must be non-zero"));
}
if (TypedArrayPrototypeGetSymbolToStringTag(view) !== undefined) {
if (options.min > TypedArrayPrototypeGetLength(view)) {
return PromiseReject(
new RangeError("options.min must be smaller or equal to view's size"),
);
}
} else {
if (options.min > DataViewPrototypeGetByteLength(view)) {
return PromiseReject(
new RangeError("options.min must be smaller or equal to view's size"),
);
}
}
if (this[_stream] === undefined) { if (this[_stream] === undefined) {
return PromiseReject( return PromiseReject(
new TypeError("Reader has no associated stream."), new TypeError("Reader has no associated stream."),
@ -5603,7 +5639,7 @@ class ReadableStreamBYOBReader {
promise.reject(e); promise.reject(e);
}, },
}; };
readableStreamBYOBReaderRead(this, view, readIntoRequest); readableStreamBYOBReaderRead(this, view, options.min, readIntoRequest);
return promise.promise; return promise.promise;
} }
@ -5929,6 +5965,7 @@ class ReadableByteStreamController {
byteLength: autoAllocateChunkSize, byteLength: autoAllocateChunkSize,
bytesFilled: 0, bytesFilled: 0,
elementSize: 1, elementSize: 1,
minimumFill: 1,
viewConstructor: Uint8Array, viewConstructor: Uint8Array,
readerType: "default", readerType: "default",
}; };
@ -6799,6 +6836,17 @@ webidl.converters.ReadableStreamGetReaderOptions = webidl
converter: webidl.converters.ReadableStreamReaderMode, converter: webidl.converters.ReadableStreamReaderMode,
}]); }]);
webidl.converters.ReadableStreamBYOBReaderReadOptions = webidl
.createDictionaryConverter("ReadableStreamBYOBReaderReadOptions", [{
key: "min",
converter: (V, prefix, context, opts) =>
webidl.converters["unsigned long long"](V, prefix, context, {
...opts,
enforceRange: true,
}),
defaultValue: 1,
}]);
webidl.converters.ReadableWritablePair = webidl webidl.converters.ReadableWritablePair = webidl
.createDictionaryConverter("ReadableWritablePair", [ .createDictionaryConverter("ReadableWritablePair", [
{ {

View file

@ -30,6 +30,7 @@ interface PullIntoDescriptor {
byteOffset: number; byteOffset: number;
byteLength: number; byteLength: number;
bytesFilled: number; bytesFilled: number;
minimumFill: number;
elementSize: number; elementSize: number;
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
viewConstructor: any; viewConstructor: any;

View file

@ -623,12 +623,18 @@ declare type ReadableStreamBYOBReadResult<V extends ArrayBufferView> =
| ReadableStreamBYOBReadDoneResult<V> | ReadableStreamBYOBReadDoneResult<V>
| ReadableStreamBYOBReadValueResult<V>; | ReadableStreamBYOBReadValueResult<V>;
/** @category Streams API */
declare interface ReadableStreamBYOBReaderReadOptions {
min?: number;
}
/** @category Streams API */ /** @category Streams API */
declare interface ReadableStreamBYOBReader { declare interface ReadableStreamBYOBReader {
readonly closed: Promise<void>; readonly closed: Promise<void>;
cancel(reason?: any): Promise<void>; cancel(reason?: any): Promise<void>;
read<V extends ArrayBufferView>( read<V extends ArrayBufferView>(
view: V, view: V,
options?: ReadableStreamBYOBReaderReadOptions,
): Promise<ReadableStreamBYOBReadResult<V>>; ): Promise<ReadableStreamBYOBReadResult<V>>;
releaseLock(): void; releaseLock(): void;
} }

View file

@ -3204,7 +3204,9 @@
"respond-after-enqueue.any.html": true, "respond-after-enqueue.any.html": true,
"respond-after-enqueue.any.worker.html": true, "respond-after-enqueue.any.worker.html": true,
"enqueue-with-detached-buffer.any.html": true, "enqueue-with-detached-buffer.any.html": true,
"enqueue-with-detached-buffer.any.worker.html": true "enqueue-with-detached-buffer.any.worker.html": true,
"read-min.any.html": true,
"read-min.any.worker.html": true
}, },
"readable-streams": { "readable-streams": {
"async-iterator.any.html": true, "async-iterator.any.html": true,