From a140dbde6ffcec150218f69409320bd1ba1b4701 Mon Sep 17 00:00:00 2001 From: Tom Schuster Date: Tue, 29 Oct 2024 10:15:13 +0000 Subject: [PATCH] Bug 1864406 - Add ReadableStreamBYOBReader.prototype.read(view, { min }). r=saschanaz,webidl,smaug Implements https://github.com/whatwg/streams/pull/1145 Differential Revision: https://phabricator.services.mozilla.com/D226225 --- dom/streams/ReadableByteStreamController.cpp | 222 ++++++++++-------- dom/streams/ReadableByteStreamController.h | 4 +- dom/streams/ReadableStreamBYOBReader.cpp | 55 ++++- dom/streams/ReadableStreamBYOBReader.h | 8 +- dom/streams/ReadableStreamTee.cpp | 5 +- dom/webidl/ReadableStreamBYOBReader.webidl | 6 +- .../readable-byte-streams/read-min.any.js.ini | 212 ----------------- 7 files changed, 187 insertions(+), 325 deletions(-) diff --git a/dom/streams/ReadableByteStreamController.cpp b/dom/streams/ReadableByteStreamController.cpp index 1ce2ae4331f6..ff57152de33a 100644 --- a/dom/streams/ReadableByteStreamController.cpp +++ b/dom/streams/ReadableByteStreamController.cpp @@ -115,13 +115,15 @@ struct PullIntoDescriptor final PullIntoDescriptor(JS::Handle aBuffer, uint64_t aBufferByteLength, uint64_t aByteOffset, uint64_t aByteLength, - uint64_t aBytesFilled, uint64_t aElementSize, - Constructor aViewConstructor, ReaderType aReaderType) + uint64_t aBytesFilled, uint64_t aMinimumFill, + uint64_t aElementSize, Constructor aViewConstructor, + ReaderType aReaderType) : mBuffer(aBuffer), mBufferByteLength(aBufferByteLength), mByteOffset(aByteOffset), mByteLength(aByteLength), mBytesFilled(aBytesFilled), + mMinimumFill(aMinimumFill), mElementSize(aElementSize), mViewConstructor(aViewConstructor), mReaderType(aReaderType) { @@ -147,6 +149,8 @@ struct PullIntoDescriptor final mBytesFilled = aBytesFilled; } + uint64_t MinimumFill() const { return mMinimumFill; } + uint64_t ElementSize() const { return mElementSize; } void SetElementSize(const uint64_t aElementSize) { mElementSize = aElementSize; @@ -166,6 +170,7 @@ struct PullIntoDescriptor final uint64_t mByteOffset = 0; uint64_t mByteLength = 0; uint64_t mBytesFilled = 0; + uint64_t mMinimumFill = 0; uint64_t mElementSize = 0; Constructor mViewConstructor; ReaderType mReaderType; @@ -394,11 +399,14 @@ void ReadableByteStreamControllerClose( // Step 4. if (!aController->PendingPullIntos().isEmpty()) { - // Step 4.1 + // Step 4.1. Let firstPendingPullInto be controller.[[pendingPullIntos]][0]. PullIntoDescriptor* firstPendingPullInto = aController->PendingPullIntos().getFirst(); - // Step 4.2 - if (firstPendingPullInto->BytesFilled() > 0) { + + // Step 4.2. If the remainder after dividing firstPendingPullInto’s bytes + // filled by firstPendingPullInto’s element size is not 0, + if ((firstPendingPullInto->BytesFilled() % + firstPendingPullInto->ElementSize()) != 0) { // Step 4.2.1 ErrorResult rv; rv.ThrowTypeError("Leftover Bytes"); @@ -703,8 +711,10 @@ void ReadableByteStreamControllerCommitPullIntoDescriptor( // Step 4. If stream.[[state]] is "closed", if (aStream->State() == ReadableStream::ReaderState::Closed) { - // Step 4.1. Assert: pullIntoDescriptor’s bytes filled is 0. - MOZ_ASSERT(pullIntoDescriptor->BytesFilled() == 0); + // Step 4.1. Assert: the remainder after dividing pullIntoDescriptor’s bytes + // filled by pullIntoDescriptor’s element size is 0. + MOZ_ASSERT((pullIntoDescriptor->BytesFilled() % + pullIntoDescriptor->ElementSize()) == 0); // Step 4.2. Set done to true. done = true; @@ -1144,13 +1154,13 @@ void ReadableByteStreamControllerHandleQueueDrain( void ReadableByteStreamController::PullSteps(JSContext* aCx, ReadRequest* aReadRequest, ErrorResult& aRv) { - // Step 1. + // Step 1. Let stream be this.[[stream]]. ReadableStream* stream = Stream(); - // Step 2. + // Step 2. Assert: ! ReadableStreamHasDefaultReader(stream) is true. MOZ_ASSERT(ReadableStreamHasDefaultReader(stream)); - // Step 3. + // Step 3. If this.[[queueTotalSize]] > 0, if (QueueTotalSize() > 0) { // Step 3.1. Assert: ! ReadableStreamGetNumReadRequests ( stream ) is 0. MOZ_ASSERT(ReadableStreamGetNumReadRequests(stream) == 0); @@ -1164,18 +1174,20 @@ void ReadableByteStreamController::PullSteps(JSContext* aCx, return; } - // Step 4. + // Step 4. Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]]. Maybe autoAllocateChunkSize = AutoAllocateChunkSize(); - // Step 5. + // Step 5. If autoAllocateChunkSize is not undefined, if (autoAllocateChunkSize) { - // Step 5.1 + // Step 5.1. Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize + // »). aRv.MightThrowJSException(); JS::Rooted buffer( aCx, JS::NewArrayBuffer(aCx, *autoAllocateChunkSize)); - // Step 5.2 + + // Step 5.2. If buffer is an abrupt completion, if (!buffer) { - // Step 5.2.1 + // Step 5.2.1. Perform readRequest’s error steps, given buffer.[[Value]]. JS::Rooted bufferError(aCx); if (!JS_GetPendingException(aCx, &bufferError)) { // Uncatchable exception; we should mark aRv and return. @@ -1183,29 +1195,40 @@ void ReadableByteStreamController::PullSteps(JSContext* aCx, return; } - // It's not expliclitly stated, but I assume the intention here is that + // It's not explicitly stated, but I assume the intention here is that // we perform a normal completion here. JS_ClearPendingException(aCx); aReadRequest->ErrorSteps(aCx, bufferError, aRv); - // Step 5.2.2. + // Step 5.2.2. Return. return; } - // Step 5.3 + // Step 5.3 Let pullIntoDescriptor be a new pull-into descriptor with + // buffer buffer.[[Value]] + // buffer byte length autoAllocateChunkSize + // byte offset 0 + // byte length autoAllocateChunkSize + // bytes filled 0 + // minimum fill 1 + // element size 1 + // view constructor %Uint8Array% + // reader type "default" RefPtr pullIntoDescriptor = new PullIntoDescriptor( - buffer, *autoAllocateChunkSize, 0, *autoAllocateChunkSize, 0, 1, + buffer, /* aBufferByteLength */ *autoAllocateChunkSize, + /*aByteOffset */ 0, /* aByteLength */ *autoAllocateChunkSize, + /* aBytesFilled */ 0, /* aMinimumFill */ 1, /* aElementSize */ 1, PullIntoDescriptor::Constructor::Uint8, ReaderType::Default); - // Step 5.4 + // Step 5.4. Append pullIntoDescriptor to this.[[pendingPullIntos]]. PendingPullIntos().insertBack(pullIntoDescriptor); } - // Step 6. + // Step 6. Perform ! ReadableStreamAddReadRequest(stream, readRequest). ReadableStreamAddReadRequest(stream, aReadRequest); - // Step 7. + // Step 7. Perform ! ReadableByteStreamControllerCallPullIfNeeded(this). ReadableByteStreamControllerCallPullIfNeeded(aCx, this, aRv); } @@ -1280,7 +1303,8 @@ JSObject* ReadableByteStreamControllerConvertPullIntoDescriptor( // Step 3. Assert: bytesFilled ≤ pullIntoDescriptor’s byte length. MOZ_ASSERT(bytesFilled <= pullIntoDescriptor->ByteLength()); - // Step 4. Assert: bytesFilled mod elementSize is 0. + // Step 4. Assert: the remainder after dividing bytesFilled by elementSize is + // 0. MOZ_ASSERT(bytesFilled % elementSize == 0); // Step 5. Let buffer be ! TransferArrayBuffer(pullIntoDescriptor’s buffer). @@ -1310,8 +1334,10 @@ MOZ_CAN_RUN_SCRIPT static void ReadableByteStreamControllerRespondInClosedState( JSContext* aCx, ReadableByteStreamController* aController, RefPtr& aFirstDescriptor, ErrorResult& aRv) { - // Step 1. Assert: firstDescriptor ’s bytes filled is 0. - MOZ_ASSERT(aFirstDescriptor->BytesFilled() == 0); + // Step 1. Assert: the remainder after dividing firstDescriptor’s bytes filled + // by firstDescriptor’s element size is 0. + MOZ_ASSERT( + (aFirstDescriptor->BytesFilled() % aFirstDescriptor->ElementSize()) == 0); // Step 2. If firstDescriptor’s reader type is "none", // perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). @@ -1396,9 +1422,9 @@ static void ReadableByteStreamControllerRespondInReadableState( return; } - // Step 4. If pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s element - // size, return. - if (aPullIntoDescriptor->BytesFilled() < aPullIntoDescriptor->ElementSize()) { + // Step 4. If pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s minimum + // fill, return. + if (aPullIntoDescriptor->BytesFilled() < aPullIntoDescriptor->MinimumFill()) { return; } @@ -1408,8 +1434,8 @@ static void ReadableByteStreamControllerRespondInReadableState( ReadableByteStreamControllerShiftPendingPullInto(aController); (void)pullIntoDescriptor; - // Step 6. Let remainderSize be pullIntoDescriptor’s bytes filled mod - // pullIntoDescriptor’s element size. + // Step 6. Let remainderSize be the remainder after dividing + // pullIntoDescriptor’s bytes filled by pullIntoDescriptor’s element size. size_t remainderSize = aPullIntoDescriptor->BytesFilled() % aPullIntoDescriptor->ElementSize(); @@ -1647,38 +1673,37 @@ void ReadableByteStreamControllerRespondWithNewView( bool ReadableByteStreamControllerFillPullIntoDescriptorFromQueue( JSContext* aCx, ReadableByteStreamController* aController, PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv) { - // Step 1. Let elementSize be pullIntoDescriptor.[[elementSize]]. - size_t elementSize = aPullIntoDescriptor->ElementSize(); - - // Step 2. Let currentAlignedBytes be pullIntoDescriptor’s bytes filled − - // (pullIntoDescriptor’s bytes filled mod elementSize). - size_t currentAlignedBytes = - aPullIntoDescriptor->BytesFilled() - - (aPullIntoDescriptor->BytesFilled() % elementSize); - - // Step 3. Let maxBytesToCopy be min(controller.[[queueTotalSize]], + // Step 1. Let maxBytesToCopy be min(controller.[[queueTotalSize]], // pullIntoDescriptor’s byte length − pullIntoDescriptor’s bytes filled). size_t maxBytesToCopy = std::min(static_cast(aController->QueueTotalSize()), static_cast((aPullIntoDescriptor->ByteLength() - aPullIntoDescriptor->BytesFilled()))); - // Step 4. Let maxBytesFilled be pullIntoDescriptor’s bytes filled + + // Step 2. Let maxBytesFilled be pullIntoDescriptor’s bytes filled + // maxBytesToCopy. size_t maxBytesFilled = aPullIntoDescriptor->BytesFilled() + maxBytesToCopy; - // Step 5. Let maxAlignedBytes be maxBytesFilled − (maxBytesFilled mod - // elementSize). - size_t maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize); - - // Step 6. Let totalBytesToCopyRemaining be maxBytesToCopy. + // Step 3. Let totalBytesToCopyRemaining be maxBytesToCopy. size_t totalBytesToCopyRemaining = maxBytesToCopy; - // Step 7. Let ready be false. + // Step 4. Let ready be false. bool ready = false; - // Step 8. If maxAlignedBytes > currentAlignedBytes, - if (maxAlignedBytes > currentAlignedBytes) { + // Step 5. Assert: pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s + // minimum fill. + MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() < + aPullIntoDescriptor->MinimumFill()); + + // Step 6. Let remainderBytes be the remainder after dividing maxBytesFilled + // by pullIntoDescriptor’s element size. + size_t remainderBytes = maxBytesFilled % aPullIntoDescriptor->ElementSize(); + + // Step 7. Let maxAlignedBytes be maxBytesFilled − remainderBytes. + size_t maxAlignedBytes = maxBytesFilled - remainderBytes; + + // Step 8. If maxAlignedBytes ≥ pullIntoDescriptor’s minimum fill, + if (maxAlignedBytes >= aPullIntoDescriptor->MinimumFill()) { // Step 8.1. Set totalBytesToCopyRemaining to maxAlignedBytes − // pullIntoDescriptor’s bytes filled. totalBytesToCopyRemaining = @@ -1758,10 +1783,9 @@ bool ReadableByteStreamControllerFillPullIntoDescriptorFromQueue( MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() > 0); // Step 11.3. Assert: pullIntoDescriptor’s bytes filled < - // pullIntoDescriptor’s - // element size. + // pullIntoDescriptor’s minimum fill. MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() < - aPullIntoDescriptor->ElementSize()); + aPullIntoDescriptor->MinimumFill()); } // Step 12. Return ready. @@ -1771,8 +1795,8 @@ bool ReadableByteStreamControllerFillPullIntoDescriptorFromQueue( // https://streams.spec.whatwg.org/#readable-byte-stream-controller-pull-into void ReadableByteStreamControllerPullInto( JSContext* aCx, ReadableByteStreamController* aController, - JS::Handle aView, ReadIntoRequest* aReadIntoRequest, - ErrorResult& aRv) { + JS::Handle aView, uint64_t aMin, + ReadIntoRequest* aReadIntoRequest, ErrorResult& aRv) { aRv.MightThrowJSException(); // Step 1. Let stream be controller.[[stream]]. @@ -1798,13 +1822,23 @@ void ReadableByteStreamControllerPullInto( ctor = PullIntoDescriptor::constructorFromScalar(type); } - // Step 5. Let byteOffset be view.[[ByteOffset]]. + // Step 5. Let minimumFill be min × elementSize. + uint64_t minimumFill = aMin * elementSize; + + // Step 6. Assert: minimumFill ≥ 0 and minimumFill ≤ view.[[ByteLength]]. + MOZ_ASSERT(minimumFill <= JS_GetArrayBufferViewByteLength(aView)); + + // Step 7. Assert: the remainder after dividing minimumFill by elementSize is + // 0. + MOZ_ASSERT((minimumFill % elementSize) == 0); + + // Step 8. Let byteOffset be view.[[ByteOffset]]. size_t byteOffset = JS_GetArrayBufferViewByteOffset(aView); - // Step 6. Let byteLength be view.[[ByteLength]]. + // Step 9. Let byteLength be view.[[ByteLength]]. size_t byteLength = JS_GetArrayBufferViewByteLength(aView); - // Step 7. Let bufferResult be + // Step 10. Let bufferResult be // TransferArrayBuffer(view.[[ViewedArrayBuffer]]). bool isShared; JS::Rooted viewedArrayBuffer( @@ -1816,7 +1850,7 @@ void ReadableByteStreamControllerPullInto( JS::Rooted bufferResult( aCx, TransferArrayBuffer(aCx, viewedArrayBuffer)); - // Step 8. If bufferResult is an abrupt completion, + // Step 11. If bufferResult is an abrupt completion, if (!bufferResult) { JS::Rooted pendingException(aCx); if (!JS_GetPendingException(aCx, &pendingException)) { @@ -1831,46 +1865,47 @@ void ReadableByteStreamControllerPullInto( // exception state anyhow to succesfully run ErrorSteps. JS_ClearPendingException(aCx); - // Step 8.1. Perform readIntoRequest’s error steps, given + // Step 11.1. Perform readIntoRequest’s error steps, given // bufferResult.[[Value]]. aReadIntoRequest->ErrorSteps(aCx, pendingException, aRv); - // Step 8.2. Return. + // Step 11.2. Return. return; } - // Step 9. Let buffer be bufferResult.[[Value]]. + // Step 12. Let buffer be bufferResult.[[Value]]. JS::Rooted buffer(aCx, bufferResult); - // Step 10. Let pullIntoDescriptor be a new pull-into descriptor with - // buffer: buffer, - // buffer byte length: buffer.[[ArrayBufferByteLength]], - // byte offset: byteOffset, - // byte length: byteLength, - // bytes filled: 0, - // element size: elementSize, - // view constructor: ctor, - // and reader type: "byob". + // Step 13. Let pullIntoDescriptor be a new pull-into descriptor with + // buffer: buffer + // buffer byte length: buffer.[[ArrayBufferByteLength]] + // byte offset: byteOffset + // byte length: byteLength + // bytes filled: 0 + // minimum fill: minimumFill + // element size: elementSize + // view constructor: ctor + // reader type: "byob" RefPtr pullIntoDescriptor = new PullIntoDescriptor( buffer, JS::GetArrayBufferByteLength(buffer), byteOffset, byteLength, 0, - elementSize, ctor, ReaderType::BYOB); + minimumFill, elementSize, ctor, ReaderType::BYOB); - // Step 11. If controller.[[pendingPullIntos]] is not empty, + // Step 14. If controller.[[pendingPullIntos]] is not empty, if (!aController->PendingPullIntos().isEmpty()) { - // Step 11.1. Append pullIntoDescriptor to controller.[[pendingPullIntos]]. + // Step 14.1. Append pullIntoDescriptor to controller.[[pendingPullIntos]]. aController->PendingPullIntos().insertBack(pullIntoDescriptor); - // Step 11.2. Perform !ReadableStreamAddReadIntoRequest(stream, + // Step 14.2. Perform !ReadableStreamAddReadIntoRequest(stream, // readIntoRequest). ReadableStreamAddReadIntoRequest(stream, aReadIntoRequest); - // Step 11.3. Return. + // Step 14.3. Return. return; } - // Step 12. If stream.[[state]] is "closed", + // Step 15. If stream.[[state]] is "closed", if (stream->State() == ReadableStream::ReaderState::Closed) { - // Step 12.1. Let emptyView be !Construct(ctor, « pullIntoDescriptor’s + // Step 15.1. Let emptyView be !Construct(ctor, « pullIntoDescriptor’s // buffer, pullIntoDescriptor’s byte offset, 0 »). JS::Rooted pullIntoBuffer(aCx, pullIntoDescriptor->Buffer()); JS::Rooted emptyView( @@ -1882,17 +1917,17 @@ void ReadableByteStreamControllerPullInto( return; } - // Step 12.2. Perform readIntoRequest’s close steps, given emptyView. + // Step 15.2. Perform readIntoRequest’s close steps, given emptyView. JS::Rooted emptyViewValue(aCx, JS::ObjectValue(*emptyView)); aReadIntoRequest->CloseSteps(aCx, emptyViewValue, aRv); - // Step 12.3. Return. + // Step 15.3. Return. return; } - // Step 13,. If controller.[[queueTotalSize]] > 0, + // Step 16. If controller.[[queueTotalSize]] > 0, if (aController->QueueTotalSize() > 0) { - // Step 13.1 If + // Step 16.1 If // !ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, // pullIntoDescriptor) is true, bool ready = ReadableByteStreamControllerFillPullIntoDescriptorFromQueue( @@ -1901,7 +1936,7 @@ void ReadableByteStreamControllerPullInto( return; } if (ready) { - // Step 13.1.1 Let filledView be + // Step 16.1.1 Let filledView be // !ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor). JS::Rooted filledView( aCx, ReadableByteStreamControllerConvertPullIntoDescriptor( @@ -1909,51 +1944,50 @@ void ReadableByteStreamControllerPullInto( if (aRv.Failed()) { return; } - // Step 13.1.2. Perform + // Step 16.1.2. Perform // !ReadableByteStreamControllerHandleQueueDrain(controller). ReadableByteStreamControllerHandleQueueDrain(aCx, aController, aRv); if (aRv.Failed()) { return; } - // Step 13.1.3. Perform readIntoRequest’s chunk steps, given filledView. + // Step 16.1.3. Perform readIntoRequest’s chunk steps, given filledView. JS::Rooted filledViewValue(aCx, JS::ObjectValue(*filledView)); aReadIntoRequest->ChunkSteps(aCx, filledViewValue, aRv); - // Step 13.1.4. Return. + // Step 16.1.4. Return. return; } - // Step 13.2 If controller.[[closeRequested]] is true, + // Step 16.2 If controller.[[closeRequested]] is true, if (aController->CloseRequested()) { - // Step 13.2.1. Let e be a TypeError exception. + // Step 16.2.1. Let e be a TypeError exception. ErrorResult typeError; typeError.ThrowTypeError("Close Requested True during Pull Into"); JS::Rooted e(aCx); MOZ_RELEASE_ASSERT(ToJSValue(aCx, std::move(typeError), &e)); - // Step 13.2.2. Perform !ReadableByteStreamControllerError(controller, e). + // Step 16.2.2. Perform !ReadableByteStreamControllerError(controller, e). ReadableByteStreamControllerError(aController, e, aRv); if (aRv.Failed()) { return; } - // Step 13.2.3. Perform readIntoRequest’s error steps, given e. + // Step 16.2.3. Perform readIntoRequest’s error steps, given e. aReadIntoRequest->ErrorSteps(aCx, e, aRv); - // Step 13.2.4. Return. + // Step 16.2.4. Return. return; } } - // Step 14. Append pullIntoDescriptor to controller.[[pendingPullIntos]]. + // Step 17. Append pullIntoDescriptor to controller.[[pendingPullIntos]]. aController->PendingPullIntos().insertBack(pullIntoDescriptor); - // Step 15. Perform !ReadableStreamAddReadIntoRequest(stream, + // Step 18. Perform !ReadableStreamAddReadIntoRequest(stream, // readIntoRequest). ReadableStreamAddReadIntoRequest(stream, aReadIntoRequest); - // Step 16, Perform - // !ReadableByteStreamControllerCallPullIfNeeded(controller). + // Step 19. Perform !ReadableByteStreamControllerCallPullIfNeeded(controller). ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv); } diff --git a/dom/streams/ReadableByteStreamController.h b/dom/streams/ReadableByteStreamController.h index 45e03ba3b4d5..3260aa05c8ed 100644 --- a/dom/streams/ReadableByteStreamController.h +++ b/dom/streams/ReadableByteStreamController.h @@ -187,8 +187,8 @@ MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerRespondWithNewView( MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerPullInto( JSContext* aCx, ReadableByteStreamController* aController, - JS::Handle aView, ReadIntoRequest* aReadIntoRequest, - ErrorResult& aRv); + JS::Handle aView, uint64_t aMin, + ReadIntoRequest* aReadIntoRequest, ErrorResult& aRv); void ReadableByteStreamControllerError( ReadableByteStreamController* aController, JS::Handle aValue, diff --git a/dom/streams/ReadableStreamBYOBReader.cpp b/dom/streams/ReadableStreamBYOBReader.cpp index f40d98bb93e6..a8a632fbe8de 100644 --- a/dom/streams/ReadableStreamBYOBReader.cpp +++ b/dom/streams/ReadableStreamBYOBReader.cpp @@ -173,7 +173,7 @@ namespace streams_abstract { // https://streams.spec.whatwg.org/#readable-stream-byob-reader-read void ReadableStreamBYOBReaderRead(JSContext* aCx, ReadableStreamBYOBReader* aReader, - JS::Handle aView, + JS::Handle aView, uint64_t aMin, ReadIntoRequest* aReadIntoRequest, ErrorResult& aRv) { // Step 1.Let stream be reader.[[stream]]. @@ -195,19 +195,20 @@ void ReadableStreamBYOBReaderRead(JSContext* aCx, } // Step 5. Otherwise, perform - // !ReadableByteStreamControllerPullInto(stream.[[controller]], view, + // !ReadableByteStreamControllerPullInto(stream.[[controller]], view, min, // readIntoRequest). MOZ_ASSERT(stream->Controller()->IsByte()); RefPtr controller( stream->Controller()->AsByte()); - ReadableByteStreamControllerPullInto(aCx, controller, aView, aReadIntoRequest, - aRv); + ReadableByteStreamControllerPullInto(aCx, controller, aView, aMin, + aReadIntoRequest, aRv); } } // namespace streams_abstract // https://streams.spec.whatwg.org/#byob-reader-read already_AddRefed ReadableStreamBYOBReader::Read( - const ArrayBufferView& aArray, ErrorResult& aRv) { + const ArrayBufferView& aArray, + const ReadableStreamBYOBReaderReadOptions& aOptions, ErrorResult& aRv) { AutoJSAPI jsapi; if (!jsapi.Init(GetParentObject())) { aRv.ThrowUnknownError("Internal error"); @@ -247,28 +248,60 @@ already_AddRefed ReadableStreamBYOBReader::Read( return nullptr; } - // Step 4. If this.[[stream]] is undefined, return a promise rejected with a + // Step 4. If options["min"] is 0, return a promise rejected with a TypeError + // exception. + if (aOptions.mMin == 0) { + aRv.ThrowTypeError( + "Zero is not a valid value for 'min' member of " + "ReadableStreamBYOBReaderReadOptions."); + return nullptr; + } + + // Step 5. If view has a [[TypedArrayName]] internal slot, + if (JS_IsTypedArrayObject(view)) { + // Step 5.1. If options["min"] > view.[[ArrayLength]], return a promise + // rejected with a RangeError exception. + if (aOptions.mMin > JS_GetTypedArrayLength(view)) { + aRv.ThrowRangeError( + "Array length exceeded by 'min' member of " + "ReadableStreamBYOBReaderReadOptions."); + return nullptr; + } + } else { + // Step 6. Otherwise (i.e., it is a DataView), + // Step 6.1. If options["min"] > view.[[ByteLength]], return a promise + // rejected with a RangeError exception. + if (aOptions.mMin > JS_GetArrayBufferViewByteLength(view)) { + aRv.ThrowRangeError( + "byteLength exceeded by 'min' member of " + "ReadableStreamBYOBReaderReadOptions."); + return nullptr; + } + } + + // Step 7. If this.[[stream]] is undefined, return a promise rejected with a // TypeError exception. if (!GetStream()) { aRv.ThrowTypeError("Reader has undefined stream"); return nullptr; } - // Step 5. + // Step 8. Let promise be a new promise. RefPtr promise = Promise::CreateInfallible(GetParentObject()); - // Step 6. Let readIntoRequest be a new read-into request with the following + // Step 9. Let readIntoRequest be a new read-into request with the following // items: RefPtr readIntoRequest = new Read_ReadIntoRequest(promise); - // Step 7. Perform ! ReadableStreamBYOBReaderRead(this, view, + // Step 10. Perform ! ReadableStreamBYOBReaderRead(this, view, options["min"], // readIntoRequest). - ReadableStreamBYOBReaderRead(cx, this, view, readIntoRequest, aRv); + ReadableStreamBYOBReaderRead(cx, this, view, aOptions.mMin, readIntoRequest, + aRv); if (aRv.Failed()) { return nullptr; } - // Step 8. Return promise. + // Step 11. Return promise. return promise.forget(); } diff --git a/dom/streams/ReadableStreamBYOBReader.h b/dom/streams/ReadableStreamBYOBReader.h index 896f1acbf9e7..4daa2674ca6d 100644 --- a/dom/streams/ReadableStreamBYOBReader.h +++ b/dom/streams/ReadableStreamBYOBReader.h @@ -22,6 +22,7 @@ namespace mozilla::dom { class Promise; struct ReadIntoRequest; class ReadableStream; +struct ReadableStreamBYOBReaderReadOptions; } // namespace mozilla::dom @@ -52,7 +53,8 @@ class ReadableStreamBYOBReader final : public ReadableStreamGenericReader, const GlobalObject& global, ReadableStream& stream, ErrorResult& rv); MOZ_CAN_RUN_SCRIPT already_AddRefed Read( - const ArrayBufferView& aArray, ErrorResult& rv); + const ArrayBufferView& aArray, + const ReadableStreamBYOBReaderReadOptions& aOptions, ErrorResult& rv); void ReleaseLock(ErrorResult& rv); @@ -73,8 +75,8 @@ already_AddRefed AcquireReadableStreamBYOBReader( MOZ_CAN_RUN_SCRIPT void ReadableStreamBYOBReaderRead( JSContext* aCx, ReadableStreamBYOBReader* aReader, - JS::Handle aView, ReadIntoRequest* aReadIntoRequest, - ErrorResult& aRv); + JS::Handle aView, uint64_t aMin, + ReadIntoRequest* aReadIntoRequest, ErrorResult& aRv); void ReadableStreamBYOBReaderErrorReadIntoRequests( JSContext* aCx, ReadableStreamBYOBReader* aReader, diff --git a/dom/streams/ReadableStreamTee.cpp b/dom/streams/ReadableStreamTee.cpp index 9bb30d885933..40b320bf511a 100644 --- a/dom/streams/ReadableStreamTee.cpp +++ b/dom/streams/ReadableStreamTee.cpp @@ -911,10 +911,11 @@ void PullWithBYOBReader(JSContext* aCx, TeeState* aTeeState, RefPtr readIntoRequest = new PullWithBYOBReader_ReadIntoRequest(aTeeState, aForBranch); - // Step 16.5. + // Step 16.5. Perform ! ReadableStreamBYOBReaderRead(reader, view, 1, + // readIntoRequest). RefPtr byobReader = aTeeState->GetReader()->AsBYOB(); - ReadableStreamBYOBReaderRead(aCx, byobReader, aView, readIntoRequest, aRv); + ReadableStreamBYOBReaderRead(aCx, byobReader, aView, 1, readIntoRequest, aRv); } // See https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee diff --git a/dom/webidl/ReadableStreamBYOBReader.webidl b/dom/webidl/ReadableStreamBYOBReader.webidl index d19167b4add0..1e4e51e5285d 100644 --- a/dom/webidl/ReadableStreamBYOBReader.webidl +++ b/dom/webidl/ReadableStreamBYOBReader.webidl @@ -13,9 +13,13 @@ interface ReadableStreamBYOBReader { constructor(ReadableStream stream); [NewObject] - Promise read(ArrayBufferView view); + Promise read(ArrayBufferView view, optional ReadableStreamBYOBReaderReadOptions options = {}); [Throws] undefined releaseLock(); }; ReadableStreamBYOBReader includes ReadableStreamGenericReader; + +dictionary ReadableStreamBYOBReaderReadOptions { + [EnforceRange] unsigned long long min = 1; +}; diff --git a/testing/web-platform/meta/streams/readable-byte-streams/read-min.any.js.ini b/testing/web-platform/meta/streams/readable-byte-streams/read-min.any.js.ini index 0776488b10ce..ff6a14c2bd67 100644 --- a/testing/web-platform/meta/streams/readable-byte-streams/read-min.any.js.ini +++ b/testing/web-platform/meta/streams/readable-byte-streams/read-min.any.js.ini @@ -1,214 +1,2 @@ -[read-min.any.worker.html] - [ReadableStream with byte source: read({ min }) rejects if min is 0] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is negative] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (Uint8Array)] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (Uint16Array)] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (DataView)] - expected: FAIL - - [ReadableStream with byte source: read({ min }), then read()] - expected: FAIL - - [ReadableStream with byte source: read({ min }) with a DataView] - expected: FAIL - - [ReadableStream with byte source: enqueue(), then read({ min })] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 3-byte Uint8Array, then multiple enqueue() up to 3 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 3 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 4 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min }) when closed before view is filled] - expected: FAIL - - [ReadableStream with byte source: read({ min }) when closed immediately after view is filled] - expected: FAIL - - [ReadableStream with byte source: cancel() with partially filled pending read({ min }) request] - expected: FAIL - - [ReadableStream with byte source: 3 byte enqueue(), then close(), then read({ min }) with 2-element Uint16Array must fail] - expected: FAIL - - [ReadableStream with byte source: read({ min }) with 2-element Uint16Array, then 3 byte enqueue(), then close() must fail] - expected: FAIL - - [ReadableStream with byte source: tee() with read({ min }) from branch1 and read() from branch2] - expected: FAIL - - -[read-min.any.html] - [ReadableStream with byte source: read({ min }) rejects if min is 0] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is negative] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (Uint8Array)] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (Uint16Array)] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (DataView)] - expected: FAIL - - [ReadableStream with byte source: read({ min }), then read()] - expected: FAIL - - [ReadableStream with byte source: read({ min }) with a DataView] - expected: FAIL - - [ReadableStream with byte source: enqueue(), then read({ min })] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 3-byte Uint8Array, then multiple enqueue() up to 3 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 3 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 4 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min }) when closed before view is filled] - expected: FAIL - - [ReadableStream with byte source: read({ min }) when closed immediately after view is filled] - expected: FAIL - - [ReadableStream with byte source: cancel() with partially filled pending read({ min }) request] - expected: FAIL - - [ReadableStream with byte source: 3 byte enqueue(), then close(), then read({ min }) with 2-element Uint16Array must fail] - expected: FAIL - - [ReadableStream with byte source: read({ min }) with 2-element Uint16Array, then 3 byte enqueue(), then close() must fail] - expected: FAIL - - [ReadableStream with byte source: tee() with read({ min }) from branch1 and read() from branch2] - expected: FAIL - - -[read-min.any.serviceworker.html] - [ReadableStream with byte source: read({ min }) rejects if min is 0] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is negative] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (Uint8Array)] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (Uint16Array)] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (DataView)] - expected: FAIL - - [ReadableStream with byte source: read({ min }), then read()] - expected: FAIL - - [ReadableStream with byte source: read({ min }) with a DataView] - expected: FAIL - - [ReadableStream with byte source: enqueue(), then read({ min })] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 3-byte Uint8Array, then multiple enqueue() up to 3 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 3 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 4 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min }) when closed before view is filled] - expected: FAIL - - [ReadableStream with byte source: read({ min }) when closed immediately after view is filled] - expected: FAIL - - [ReadableStream with byte source: cancel() with partially filled pending read({ min }) request] - expected: FAIL - - [ReadableStream with byte source: 3 byte enqueue(), then close(), then read({ min }) with 2-element Uint16Array must fail] - expected: FAIL - - [ReadableStream with byte source: read({ min }) with 2-element Uint16Array, then 3 byte enqueue(), then close() must fail] - expected: FAIL - - [ReadableStream with byte source: tee() with read({ min }) from branch1 and read() from branch2] - expected: FAIL - - -[read-min.any.sharedworker.html] - [ReadableStream with byte source: read({ min }) rejects if min is 0] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is negative] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (Uint8Array)] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (Uint16Array)] - expected: FAIL - - [ReadableStream with byte source: read({ min }) rejects if min is larger than view's length (DataView)] - expected: FAIL - - [ReadableStream with byte source: read({ min }), then read()] - expected: FAIL - - [ReadableStream with byte source: read({ min }) with a DataView] - expected: FAIL - - [ReadableStream with byte source: enqueue(), then read({ min })] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 3-byte Uint8Array, then multiple enqueue() up to 3 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 3 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min: 3 }) on a 5-byte Uint8Array, then multiple enqueue() up to 4 bytes] - expected: FAIL - - [ReadableStream with byte source: read({ min }) when closed before view is filled] - expected: FAIL - - [ReadableStream with byte source: read({ min }) when closed immediately after view is filled] - expected: FAIL - - [ReadableStream with byte source: cancel() with partially filled pending read({ min }) request] - expected: FAIL - - [ReadableStream with byte source: 3 byte enqueue(), then close(), then read({ min }) with 2-element Uint16Array must fail] - expected: FAIL - - [ReadableStream with byte source: read({ min }) with 2-element Uint16Array, then 3 byte enqueue(), then close() must fail] - expected: FAIL - - [ReadableStream with byte source: tee() with read({ min }) from branch1 and read() from branch2] - expected: FAIL - - [read-min.any.shadowrealm.html] expected: ERROR