diff --git a/index.bs b/index.bs index e05b6fd85..460afa8ec 100644 --- a/index.bs +++ b/index.bs @@ -451,7 +451,7 @@ particularly important for the data structure described in [[#queue-with-sizes]] let offset = 0; while (offset < buffer.byteLength) { - const {value: view, done} = + const { value: view, done } = await reader.read(new Uint8Array(buffer, offset, buffer.byteLength - offset)); buffer = view.buffer; if (done) { @@ -471,6 +471,16 @@ particularly important for the data structure described in [[#queue-with-sizes]] new {{Uint8Array}}, with that {{ArrayBuffer}} object as its buffer property, the offset that bytes were written to as its byteOffset property, and the number of bytes that were written as its byteLength property. + + Note that this example is mostly educational. For practical purposes, the + {{ReadableStreamBYOBReaderReadOptions/min}} option of {{ReadableStreamBYOBReader/read()}} + provides an easier and more direct way to read an exact number of bytes: + + + const reader = readableStream.getReader({ mode: "byob" }); + const { value: view, done } = await reader.read(new Uint8Array(1024), { min: 1024 }); + console.log("The first 1024 bytes: ", view); +

The {{ReadableStream}} class

@@ -1325,10 +1335,14 @@ The Web IDL definition for the {{ReadableStreamBYOBReader}} class is given as fo interface ReadableStreamBYOBReader { constructor(ReadableStream stream); - Promise read(ArrayBufferView view); + Promise read(ArrayBufferView view, optional ReadableStreamBYOBReaderReadOptions options = {}); undefined releaseLock(); }; ReadableStreamBYOBReader includes ReadableStreamGenericReader; + +dictionary ReadableStreamBYOBReaderReadOptions { + [EnforceRange] unsigned long long min = 1; +};

Internal slots

@@ -1388,19 +1402,19 @@ value: newViewOnSameMemory, done: true } for closed streams. If the strea

If the reader is [=active reader|active=], behaves the same |stream|.{{ReadableStream/cancel(reason)|cancel}}(reason). -

{ value, done } = await reader.{{ReadableStreamBYOBReader/read()|read}}(view) +
{ value, done } = await reader.{{ReadableStreamBYOBReader/read()|read}}(view[, { {{ReadableStreamBYOBReaderReadOptions/min}} }])
-

Attempts to reads bytes into |view|, and returns a promise resolved with the result: +

Attempts to read bytes into |view|, and returns a promise resolved with the result:

  • If the chunk does become available, the promise will be fulfilled with an object of the form - { value: theChunk, done: false }. In this case, |view| will be - [=ArrayBuffer/detached=] and no longer usable, but theChunk will be a new view (of + { value: newView, done: false }. In this case, |view| will be + [=ArrayBuffer/detached=] and no longer usable, but newView will be a new view (of the same type) onto the same backing memory region, with the chunk's data written into it.
  • If the stream becomes closed, the promise will be fulfilled with an object of the form - { value: theChunk, done: true }. In this case, |view| will be - [=ArrayBuffer/detached=] and no longer usable, but theChunk will be a new view (of + { value: newView, done: true }. In this case, |view| will be + [=ArrayBuffer/detached=] and no longer usable, but newView will be a new view (of the same type) onto the same backing memory region, with no modifications, to ensure the memory is returned to the caller. @@ -1414,6 +1428,14 @@ value: newViewOnSameMemory, done: true } for closed streams. If the strea

    If reading a chunk causes the queue to become empty, more data will be pulled from the [=underlying source=]. +

    If {{ReadableStreamBYOBReaderReadOptions/min}} is given, then the promise will only be + fulfilled as soon as the given minimum number of elements are available. Here, the "number of + elements" is given by newView's length (for typed arrays) or + newView's byteLength (for {{DataView}}s). If the stream becomes closed, + then the promise is fulfilled with the remaining elements in the stream, which might be fewer than + the initially requested amount. If not given, then the promise resolves when at least one element + is available. +

    reader.{{ReadableStreamBYOBReader/releaseLock()|releaseLock}}()

    [=release a read lock|Releases the reader's lock=] on the corresponding stream. After the lock @@ -1436,7 +1458,7 @@ value: newViewOnSameMemory, done: true } for closed streams. If the strea

    - The read(|view|) + The read(|view|, |options|) method steps are: 1. If |view|.\[[ByteLength]] is 0, return [=a promise rejected with=] a {{TypeError}} exception. @@ -1444,6 +1466,14 @@ value: newViewOnSameMemory, done: true } for closed streams. If the strea with=] a {{TypeError}} exception. 1. If ! [$IsDetachedBuffer$](|view|.\[[ViewedArrayBuffer]]) is true, return [=a promise rejected with=] a {{TypeError}} exception. + 1. If |options|["{{ReadableStreamBYOBReaderReadOptions/min}}"] is 0, return [=a promise + rejected with=] a {{TypeError}} exception. + 1. If |view| has a \[[TypedArrayName]] internal slot, + 1. If |options|["{{ReadableStreamBYOBReaderReadOptions/min}}"] > |view|.\[[ArrayLength]], + return [=a promise rejected with=] a {{RangeError}} exception. + 1. Otherwise (i.e., it is a {{DataView}}), + 1. If |options|["{{ReadableStreamBYOBReaderReadOptions/min}}"] > |view|.\[[ByteLength]], + return [=a promise rejected with=] a {{RangeError}} exception. 1. If [=this=].[=ReadableStreamGenericReader/[[stream]]=] is undefined, return [=a promise rejected with=] a {{TypeError}} exception. 1. Let |promise| be [=a new promise=]. @@ -1459,7 +1489,7 @@ value: newViewOnSameMemory, done: true } for closed streams. If the strea : [=read-into request/error steps=], given |e| :: 1. [=Reject=] |promise| with |e|. - 1. Perform ! [$ReadableStreamBYOBReaderRead$]([=this=], |view|, |readIntoRequest|). + 1. Perform ! [$ReadableStreamBYOBReaderRead$]([=this=], |view|, |options|["{{ReadableStreamBYOBReaderReadOptions/min}}"], |readIntoRequest|). 1. Return |promise|.
    @@ -1785,6 +1815,10 @@ has the following [=struct/items=]: : bytes filled :: A nonnegative integer number of bytes that have been written into the [=pull-into descriptor/buffer=] so far +: minimum fill +:: A positive integer representing the minimum number of bytes that must be written into the + [=pull-into descriptor/buffer=] before the associated {{ReadableStreamBYOBReader/read()}} request + may be fulfilled. By default, this equals the [=pull-into descriptor/element size=]. : element size :: A positive integer representing the number of bytes that can be written into the [=pull-into descriptor/buffer=] at a time, using views of the type described by the [=pull-into @@ -1911,12 +1945,35 @@ counterparts for default controllers, as discussed in [[#rs-abstract-ops-used-by 1. If |buffer| is an abrupt completion, 1. Perform |readRequest|'s [=read request/error steps=], given |buffer|.\[[Value]]. 1. Return. - 1. Let |pullIntoDescriptor| be a new [=pull-into descriptor=] with [=pull-into descriptor/buffer=] - |buffer|.\[[Value]], [=pull-into descriptor/buffer byte length=] |autoAllocateChunkSize|, - [=pull-into descriptor/byte offset=] 0, [=pull-into descriptor/byte length=] - |autoAllocateChunkSize|, [=pull-into descriptor/bytes filled=] 0, [=pull-into - descriptor/element size=] 1, [=pull-into descriptor/view constructor=] {{%Uint8Array%}}, and - [=pull-into descriptor/reader type=] "`default`". + 1. Let |pullIntoDescriptor| be a new [=pull-into descriptor=] with +
    +
    [=pull-into descriptor/buffer=] +
    |buffer|.\[[Value]] + +
    [=pull-into descriptor/buffer byte length=] +
    |autoAllocateChunkSize| + +
    [=pull-into descriptor/byte offset=] +
    0 + +
    [=pull-into descriptor/byte length=] +
    |autoAllocateChunkSize| + +
    [=pull-into descriptor/bytes filled=] +
    0 + +
    [=pull-into descriptor/minimum fill=] +
    1 + +
    [=pull-into descriptor/element size=] +
    1 + +
    [=pull-into descriptor/view constructor=] +
    {{%Uint8Array%}} + +
    [=pull-into descriptor/reader type=] +
    "`default`" +
    1. [=list/Append=] |pullIntoDescriptor| to [=this=].[=ReadableByteStreamController/[[pendingPullIntos]]=]. 1. Perform ! [$ReadableStreamAddReadRequest$](|stream|, |readRequest|). @@ -2560,7 +2617,7 @@ create them does not matter. : [=read-into request/error steps=] :: 1. Set |reading| to false. - 1. Perform ! [$ReadableStreamBYOBReaderRead$](|reader|, |view|, |readIntoRequest|). + 1. Perform ! [$ReadableStreamBYOBReaderRead$](|reader|, |view|, 1, |readIntoRequest|). 1. Let |pull1Algorithm| be the following steps: 1. If |reading| is true, 1. Set |readAgainForBranch1| to true. @@ -2860,7 +2917,7 @@ The following abstract operations support the implementation and manipulation of
    ReadableStreamBYOBReaderRead(|reader|, |view|, + id="readable-stream-byob-reader-read">ReadableStreamBYOBReaderRead(|reader|, |view|, |min|, |readIntoRequest|) performs the following steps: 1. Let |stream| be |reader|.[=ReadableStreamGenericReader/[[stream]]=]. @@ -2869,7 +2926,7 @@ The following abstract operations support the implementation and manipulation of 1. If |stream|.[=ReadableStream/[[state]]=] is "`errored`", perform |readIntoRequest|'s [=read-into request/error steps=] given |stream|.[=ReadableStream/[[storedError]]=]. 1. Otherwise, perform ! [$ReadableByteStreamControllerPullInto$](|stream|.[=ReadableStream/[[controller]]=], - |view|, |readIntoRequest|). + |view|, |min|, |readIntoRequest|).
    @@ -3217,7 +3274,8 @@ The following abstract operations support the implementation of the 1. If |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not empty, 1. Let |firstPendingPullInto| be |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=][0]. - 1. If |firstPendingPullInto|'s [=pull-into descriptor/bytes filled=] > 0, + 1. If the remainder after dividing |firstPendingPullInto|'s [=pull-into descriptor/bytes filled=] + by |firstPendingPullInto|'s [=pull-into descriptor/element size=] is not 0, 1. Let |e| be a new {{TypeError}} exception. 1. Perform ! [$ReadableByteStreamControllerError$](|controller|, |e|). 1. Throw |e|. @@ -3234,7 +3292,8 @@ The following abstract operations support the implementation of the 1. Assert: |pullIntoDescriptor|.[=pull-into descriptor/reader type=] is not "`none`". 1. Let |done| be false. 1. If |stream|.[=ReadableStream/[[state]]=] is "`closed`", - 1. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] is 0. + 1. Assert: the remainder after dividing |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] + by |pullIntoDescriptor|'s [=pull-into descriptor/element size=] is 0. 1. Set |done| to true. 1. Let |filledView| be ! [$ReadableByteStreamControllerConvertPullIntoDescriptor$](|pullIntoDescriptor|). @@ -3253,7 +3312,7 @@ The following abstract operations support the implementation of the 1. Let |bytesFilled| be |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=]. 1. Let |elementSize| be |pullIntoDescriptor|'s [=pull-into descriptor/element size=]. 1. Assert: |bytesFilled| ≤ |pullIntoDescriptor|'s [=pull-into descriptor/byte length=]. - 1. Assert: |bytesFilled| mod |elementSize| is 0. + 1. Assert: the remainder after dividing |bytesFilled| by |elementSize| is 0. 1. Let |buffer| be ! [$TransferArrayBuffer$](|pullIntoDescriptor|'s [=pull-into descriptor/buffer=]). 1. Return ! [$Construct$](|pullIntoDescriptor|'s [=pull-into descriptor/view constructor=], « |buffer|, |pullIntoDescriptor|'s [=pull-into descriptor/byte offset=], @@ -3383,21 +3442,25 @@ The following abstract operations support the implementation of the id="readable-byte-stream-controller-fill-pull-into-descriptor-from-queue">ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(|controller|, |pullIntoDescriptor|) performs the following steps: - 1. Let |elementSize| be |pullIntoDescriptor|.\[[elementSize]]. - 1. Let |currentAlignedBytes| be |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] − - (|pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] mod |elementSize|). 1. Let |maxBytesToCopy| be min(|controller|.[=ReadableByteStreamController/[[queueTotalSize]]=], |pullIntoDescriptor|'s [=pull-into descriptor/byte length=] − |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=]). 1. Let |maxBytesFilled| be |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] + |maxBytesToCopy|. - 1. Let |maxAlignedBytes| be |maxBytesFilled| − (|maxBytesFilled| mod |elementSize|). 1. Let |totalBytesToCopyRemaining| be |maxBytesToCopy|. 1. Let |ready| be false. - 1. If |maxAlignedBytes| > |currentAlignedBytes|, + 1. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] < |pullIntoDescriptor|'s + [=pull-into descriptor/minimum fill=]. + 1. Let |remainderBytes| be the remainder after dividing |maxBytesFilled| by |pullIntoDescriptor|'s + [=pull-into descriptor/element size=]. + 1. Let |maxAlignedBytes| be |maxBytesFilled| − |remainderBytes|. + 1. If |maxAlignedBytes| ≥ |pullIntoDescriptor|'s [=pull-into descriptor/minimum fill=], 1. Set |totalBytesToCopyRemaining| to |maxAlignedBytes| − |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=]. 1. Set |ready| to true. +

    A descriptor for a {{ReadableStreamBYOBReader/read()}} request + that is not yet filled up to its minimum length will stay at the head of the queue, so the + [=underlying source=] can keep filling it. 1. Let |queue| be |controller|.[=ReadableByteStreamController/[[queue]]=]. 1. [=While=] |totalBytesToCopyRemaining| > 0, 1. Let |headOfQueue| be |queue|[0]. @@ -3425,7 +3488,7 @@ The following abstract operations support the implementation of the 1. Assert: |controller|.[=ReadableByteStreamController/[[queueTotalSize]]=] is 0. 1. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] > 0. 1. Assert: |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] < - |pullIntoDescriptor|'s [=pull-into descriptor/element size=]. + |pullIntoDescriptor|'s [=pull-into descriptor/minimum fill=]. 1. Return |ready|.

    @@ -3544,7 +3607,7 @@ The following abstract operations support the implementation of the
    ReadableByteStreamControllerPullInto(|controller|, - |view|, |readIntoRequest|) performs the following steps: + |view|, |min|, |readIntoRequest|) performs the following steps: 1. Let |stream| be |controller|.[=ReadableByteStreamController/[[stream]]=]. 1. Let |elementSize| be 1. @@ -3554,6 +3617,9 @@ The following abstract operations support the implementation of the |view|.\[[TypedArrayName]]. 1. Set |ctor| to the constructor specified in [=the typed array constructors table=] for |view|.\[[TypedArrayName]]. + 1. Let |minimumFill| be |min| × |elementSize|. + 1. Assert: |minimumFill| ≥ 0 and |minimumFill| ≤ |view|.\[[ByteLength]]. + 1. Assert: the remainder after dividing |minimumFill| by |elementSize| is 0. 1. Let |byteOffset| be |view|.\[[ByteOffset]]. 1. Let |byteLength| be |view|.\[[ByteLength]]. 1. Let |bufferResult| be [$TransferArrayBuffer$](|view|.\[[ViewedArrayBuffer]]). @@ -3561,12 +3627,35 @@ The following abstract operations support the implementation of the 1. Perform |readIntoRequest|'s [=read-into request/error steps=], given |bufferResult|.\[[Value]]. 1. Return. 1. Let |buffer| be |bufferResult|.\[[Value]]. - 1. Let |pullIntoDescriptor| be a new [=pull-into descriptor=] with [=pull-into descriptor/buffer=] - |buffer|, [=pull-into descriptor/buffer byte length=] |buffer|.\[[ArrayBufferByteLength]], - [=pull-into descriptor/byte offset=] |byteOffset|, [=pull-into descriptor/byte length=] - |byteLength|, [=pull-into descriptor/bytes filled=] 0, [=pull-into descriptor/element size=] - |elementSize|, [=pull-into descriptor/view constructor=] |ctor|, and [=pull-into - descriptor/reader type=] "`byob`". + 1. Let |pullIntoDescriptor| be a new [=pull-into descriptor=] with +
    +
    [=pull-into descriptor/buffer=] +
    |buffer| + +
    [=pull-into descriptor/buffer byte length=] +
    |buffer|.\[[ArrayBufferByteLength]] + +
    [=pull-into descriptor/byte offset=] +
    |byteOffset| + +
    [=pull-into descriptor/byte length=] +
    |byteLength| + +
    [=pull-into descriptor/bytes filled=] +
    0 + +
    [=pull-into descriptor/minimum fill=] +
    |minimumFill| + +
    [=pull-into descriptor/element size=] +
    |elementSize| + +
    [=pull-into descriptor/view constructor=] +
    |ctor| + +
    [=pull-into descriptor/reader type=] +
    "`byob`" +
    1. If |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=] is not empty, 1. [=list/Append=] |pullIntoDescriptor| to |controller|.[=ReadableByteStreamController/[[pendingPullIntos]]=]. @@ -3622,7 +3711,8 @@ The following abstract operations support the implementation of the id="readable-byte-stream-controller-respond-in-closed-state">ReadableByteStreamControllerRespondInClosedState(|controller|, |firstDescriptor|) performs the following steps: - 1. Assert: |firstDescriptor|'s [=pull-into descriptor/bytes filled=] is 0. + 1. Assert: the remainder after dividing |firstDescriptor|'s [=pull-into descriptor/bytes filled=] + by |firstDescriptor|'s [=pull-into descriptor/element size=] is 0. 1. If |firstDescriptor|'s [=pull-into descriptor/reader type=] is "`none`", perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|). 1. Let |stream| be |controller|.[=ReadableByteStreamController/[[stream]]=]. @@ -3649,10 +3739,13 @@ The following abstract operations support the implementation of the 1. Perform ! [$ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue$](|controller|). 1. Return. 1. If |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] < |pullIntoDescriptor|'s - [=pull-into descriptor/element size=], return. + [=pull-into descriptor/minimum fill=], return. +

    A descriptor for a {{ReadableStreamBYOBReader/read()}} request + that is not yet filled up to its minimum length will stay at the head of the queue, so the + [=underlying source=] can keep filling it. 1. Perform ! [$ReadableByteStreamControllerShiftPendingPullInto$](|controller|). - 1. Let |remainderSize| be |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=] mod - |pullIntoDescriptor|'s [=pull-into descriptor/element size=]. + 1. Let |remainderSize| be the remainder after dividing |pullIntoDescriptor|'s + [=pull-into descriptor/bytes filled=] by |pullIntoDescriptor|'s [=pull-into descriptor/element size=]. 1. If |remainderSize| > 0, 1. Let |end| be |pullIntoDescriptor|'s [=pull-into descriptor/byte offset=] + |pullIntoDescriptor|'s [=pull-into descriptor/bytes filled=]. diff --git a/reference-implementation/lib/ReadableByteStreamController-impl.js b/reference-implementation/lib/ReadableByteStreamController-impl.js index 66cbe2278..eac1a028a 100644 --- a/reference-implementation/lib/ReadableByteStreamController-impl.js +++ b/reference-implementation/lib/ReadableByteStreamController-impl.js @@ -87,6 +87,7 @@ exports.implementation = class ReadableByteStreamControllerImpl { byteOffset: 0, byteLength: autoAllocateChunkSize, bytesFilled: 0, + minimumFill: 1, elementSize: 1, viewConstructor: Uint8Array, readerType: 'default' diff --git a/reference-implementation/lib/ReadableStreamBYOBReader-impl.js b/reference-implementation/lib/ReadableStreamBYOBReader-impl.js index 446846a45..954761162 100644 --- a/reference-implementation/lib/ReadableStreamBYOBReader-impl.js +++ b/reference-implementation/lib/ReadableStreamBYOBReader-impl.js @@ -11,7 +11,7 @@ class ReadableStreamBYOBReaderImpl { aos.SetUpReadableStreamBYOBReader(this, stream); } - read(view) { + read(view, options) { if (view.byteLength === 0) { return promiseRejectedWith(new TypeError('view must have non-zero byteLength')); } @@ -22,6 +22,23 @@ class ReadableStreamBYOBReaderImpl { return promiseRejectedWith(new TypeError('view\'s buffer has been detached')); } + if (options.min === 0) { + return promiseRejectedWith( + new TypeError('options.min must be greater than 0') + ); + } + if (view.constructor !== DataView) { + if (options.min > view.length) { + return promiseRejectedWith( + new RangeError('options.min must be less than or equal to view\'s length') + ); + } + } else if (options.min > view.byteLength) { + return promiseRejectedWith( + new RangeError('options.min must be less than or equal to view\'s byteLength') + ); + } + if (this._stream === undefined) { return promiseRejectedWith(readerLockException('read')); } @@ -32,7 +49,7 @@ class ReadableStreamBYOBReaderImpl { closeSteps: chunk => resolvePromise(promise, { value: chunk, done: true }), errorSteps: e => rejectPromise(promise, e) }; - aos.ReadableStreamBYOBReaderRead(this, view, readIntoRequest); + aos.ReadableStreamBYOBReaderRead(this, view, options.min, readIntoRequest); return promise; } diff --git a/reference-implementation/lib/ReadableStreamBYOBReader.webidl b/reference-implementation/lib/ReadableStreamBYOBReader.webidl index dfd3c25d8..6967c4311 100644 --- a/reference-implementation/lib/ReadableStreamBYOBReader.webidl +++ b/reference-implementation/lib/ReadableStreamBYOBReader.webidl @@ -2,7 +2,11 @@ interface ReadableStreamBYOBReader { constructor(ReadableStream stream); - Promise read(ArrayBufferView view); + Promise read(ArrayBufferView view, optional ReadableStreamBYOBReaderReadOptions options = {}); undefined releaseLock(); }; ReadableStreamBYOBReader includes ReadableStreamGenericReader; + +dictionary ReadableStreamBYOBReaderReadOptions { + [EnforceRange] unsigned long long min = 1; +}; diff --git a/reference-implementation/lib/abstract-ops/readable-streams.js b/reference-implementation/lib/abstract-ops/readable-streams.js index 285ac22e0..5cd4814b3 100644 --- a/reference-implementation/lib/abstract-ops/readable-streams.js +++ b/reference-implementation/lib/abstract-ops/readable-streams.js @@ -647,7 +647,7 @@ function ReadableByteStreamTee(stream) { reading = false; } }; - ReadableStreamBYOBReaderRead(reader, view, readIntoRequest); + ReadableStreamBYOBReaderRead(reader, view, 1, readIntoRequest); } function pull1Algorithm() { @@ -913,7 +913,7 @@ function ReadableStreamReaderGenericRelease(reader) { reader._stream = undefined; } -function ReadableStreamBYOBReaderRead(reader, view, readIntoRequest) { +function ReadableStreamBYOBReaderRead(reader, view, min, readIntoRequest) { const stream = reader._stream; assert(stream !== undefined); @@ -923,7 +923,7 @@ function ReadableStreamBYOBReaderRead(reader, view, readIntoRequest) { if (stream._state === 'errored') { readIntoRequest.errorSteps(stream._storedError); } else { - ReadableByteStreamControllerPullInto(stream._controller, view, readIntoRequest); + ReadableByteStreamControllerPullInto(stream._controller, view, min, readIntoRequest); } } @@ -1272,7 +1272,7 @@ function ReadableByteStreamControllerClose(controller) { if (controller._pendingPullIntos.length > 0) { const firstPendingPullInto = controller._pendingPullIntos[0]; - if (firstPendingPullInto.bytesFilled > 0) { + if (firstPendingPullInto.bytesFilled % firstPendingPullInto.elementSize !== 0) { const e = new TypeError('Insufficient bytes to fill elements in the given buffer'); ReadableByteStreamControllerError(controller, e); @@ -1290,7 +1290,7 @@ function ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDe let done = false; if (stream._state === 'closed') { - assert(pullIntoDescriptor.bytesFilled === 0); + assert(pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize === 0); done = true; } @@ -1419,18 +1419,18 @@ function ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, size } function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) { - const elementSize = pullIntoDescriptor.elementSize; - - const currentAlignedBytes = pullIntoDescriptor.bytesFilled - pullIntoDescriptor.bytesFilled % elementSize; - const maxBytesToCopy = Math.min(controller._queueTotalSize, pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled); const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy; - const maxAlignedBytes = maxBytesFilled - maxBytesFilled % elementSize; let totalBytesToCopyRemaining = maxBytesToCopy; let ready = false; - if (maxAlignedBytes > currentAlignedBytes) { + assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.minimumFill); + const remainderBytes = maxBytesFilled % pullIntoDescriptor.elementSize; + const maxAlignedBytes = maxBytesFilled - remainderBytes; + // A descriptor for a read() request that is not yet filled up to its minimum length will stay at the head + // of the queue, so the underlying source can keep filling it. + if (maxAlignedBytes >= pullIntoDescriptor.minimumFill) { totalBytesToCopyRemaining = maxAlignedBytes - pullIntoDescriptor.bytesFilled; ready = true; } @@ -1461,7 +1461,7 @@ function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, if (ready === false) { assert(controller._queueTotalSize === 0); assert(pullIntoDescriptor.bytesFilled > 0); - assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize); + assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.minimumFill); } return ready; @@ -1563,7 +1563,7 @@ function ReadableByteStreamControllerProcessReadRequestsUsingQueue(controller) { } } -function ReadableByteStreamControllerPullInto(controller, view, readIntoRequest) { +function ReadableByteStreamControllerPullInto(controller, view, min, readIntoRequest) { const stream = controller._stream; let elementSize = 1; @@ -1571,6 +1571,10 @@ function ReadableByteStreamControllerPullInto(controller, view, readIntoRequest) elementSize = view.constructor.BYTES_PER_ELEMENT; } + const minimumFill = min * elementSize; + assert(minimumFill >= elementSize && minimumFill <= view.byteLength); + assert(minimumFill % elementSize === 0); + const ctor = view.constructor; let buffer; @@ -1587,6 +1591,7 @@ function ReadableByteStreamControllerPullInto(controller, view, readIntoRequest) byteOffset: view.byteOffset, byteLength: view.byteLength, bytesFilled: 0, + minimumFill, elementSize, viewConstructor: ctor, readerType: 'byob' @@ -1660,7 +1665,7 @@ function ReadableByteStreamControllerRespond(controller, bytesWritten) { } function ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor) { - assert(firstDescriptor.bytesFilled === 0); + assert(firstDescriptor.bytesFilled % firstDescriptor.elementSize === 0); if (firstDescriptor.readerType === 'none') { ReadableByteStreamControllerShiftPendingPullInto(controller); @@ -1686,7 +1691,9 @@ function ReadableByteStreamControllerRespondInReadableState(controller, bytesWri return; } - if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) { + if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.minimumFill) { + // A descriptor for a read() request that is not yet filled up to its minimum length will stay at the head + // of the queue, so the underlying source can keep filling it. return; } diff --git a/reference-implementation/web-platform-tests b/reference-implementation/web-platform-tests index a8872d92b..7eaf605c3 160000 --- a/reference-implementation/web-platform-tests +++ b/reference-implementation/web-platform-tests @@ -1 +1 @@ -Subproject commit a8872d92b147fc87200eb0c14fe7a4a9e7cd4f73 +Subproject commit 7eaf605c38d80377c717828376deabad86b702b2