/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ReadableByteStreamController.h" #include "ReadIntoRequest.h" #include "js/ArrayBuffer.h" #include "js/ErrorReport.h" #include "js/Exception.h" #include "js/TypeDecls.h" #include "js/Value.h" #include "js/ValueArray.h" #include "js/experimental/TypedData.h" #include "js/friend/ErrorMessages.h" #include "mozilla/AlreadyAddRefed.h" #include "mozilla/Attributes.h" #include "mozilla/ErrorResult.h" #include "mozilla/HoldDropJSObjects.h" #include "mozilla/dom/ByteStreamHelpers.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/Promise-inl.h" #include "mozilla/dom/ReadableByteStreamControllerBinding.h" #include "mozilla/dom/ReadableStream.h" #include "mozilla/dom/ReadableStreamBYOBReader.h" #include "mozilla/dom/ReadableStreamBYOBRequest.h" #include "mozilla/dom/ReadableStreamController.h" #include "mozilla/dom/ReadableStreamDefaultController.h" #include "mozilla/dom/ReadableStreamDefaultReader.h" #include "mozilla/dom/ReadableStreamGenericReader.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/UnderlyingSourceCallbackHelpers.h" #include "nsCycleCollectionParticipant.h" #include "nsIGlobalObject.h" #include "nsISupports.h" #include // std::min namespace mozilla::dom { NS_IMPL_CYCLE_COLLECTION_CLASS(ReadableByteStreamController) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ReadableByteStreamController, ReadableStreamController) NS_IMPL_CYCLE_COLLECTION_UNLINK(mByobRequest, mStream) tmp->ClearPendingPullIntos(); tmp->ClearQueue(); NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ReadableByteStreamController, ReadableStreamController) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mByobRequest, mStream) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ReadableByteStreamController, ReadableStreamController) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER // Trace the associated queue + list for (const auto& queueEntry : tmp->mQueue) { aCallbacks.Trace(&queueEntry->mBuffer, "mQueue.mBuffer", aClosure); } for (const auto& pullInto : tmp->mPendingPullIntos) { aCallbacks.Trace(&pullInto->mBuffer, "mPendingPullIntos.mBuffer", aClosure); } NS_IMPL_CYCLE_COLLECTION_TRACE_END NS_IMPL_ADDREF_INHERITED(ReadableByteStreamController, ReadableStreamController) NS_IMPL_RELEASE_INHERITED(ReadableByteStreamController, ReadableStreamController) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableByteStreamController) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_END_INHERITING(ReadableStreamController) ReadableByteStreamController::ReadableByteStreamController( nsIGlobalObject* aGlobal) : ReadableStreamController(aGlobal) { mozilla::HoldJSObjects(this); } ReadableByteStreamController::~ReadableByteStreamController() { ClearPendingPullIntos(); ClearQueue(); mozilla::DropJSObjects(this); } void ReadableByteStreamController::ClearQueue() { // Since the pull intos are traced only by the owning // ReadableByteStreamController, when clearning the list we also clear JS // references to avoid dangling JS references. for (auto* queueEntry : mQueue) { queueEntry->ClearBuffer(); } mQueue.clear(); } void ReadableByteStreamController::ClearPendingPullIntos() { // Since the pull intos are traced only by the owning // ReadableByteStreamController, when clearning the list we also clear JS // references to avoid dangling JS references. for (auto* pullInto : mPendingPullIntos) { pullInto->ClearBuffer(); } mPendingPullIntos.clear(); } // https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollergetbyobrequest already_AddRefed ReadableByteStreamControllerGetBYOBRequest( JSContext* aCx, ReadableByteStreamController* aController, ErrorResult& aRv) { // Step 1. if (!aController->GetByobRequest() && !aController->PendingPullIntos().isEmpty()) { // Step 1.1: PullIntoDescriptor* firstDescriptor = aController->PendingPullIntos().getFirst(); // Step 1.2: aRv.MightThrowJSException(); JS::Rooted buffer(aCx, firstDescriptor->Buffer()); JS::Rooted view( aCx, JS_NewUint8ArrayWithBuffer( aCx, buffer, firstDescriptor->ByteOffset() + firstDescriptor->BytesFilled(), int64_t(firstDescriptor->ByteLength() - firstDescriptor->BytesFilled()))); if (!view) { aRv.StealExceptionFromJSContext(aCx); return nullptr; } // Step 1.3: RefPtr byobRequest = new ReadableStreamBYOBRequest(aController->GetParentObject()); // Step 1.4: byobRequest->SetController(aController); // Step 1.5: byobRequest->SetView(view); // Step 1.6: aController->SetByobRequest(byobRequest); } // Step 2. RefPtr request(aController->GetByobRequest()); return request.forget(); } already_AddRefed ReadableByteStreamController::GetByobRequest(JSContext* aCx, ErrorResult& aRv) { return ReadableByteStreamControllerGetBYOBRequest(aCx, this, aRv); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-get-desired-size Nullable ReadableByteStreamControllerGetDesiredSize( const ReadableByteStreamController* aController) { // Step 1. ReadableStream::ReaderState state = aController->Stream()->State(); // Step 2. if (state == ReadableStream::ReaderState::Errored) { return nullptr; } // Step 3. if (state == ReadableStream::ReaderState::Closed) { return 0.0; } // Step 4. return aController->StrategyHWM() - aController->QueueTotalSize(); } Nullable ReadableByteStreamController::GetDesiredSize() const { // Step 1. return ReadableByteStreamControllerGetDesiredSize(this); } JSObject* ReadableByteStreamController::WrapObject( JSContext* aCx, JS::Handle aGivenProto) { return ReadableByteStreamController_Binding::Wrap(aCx, this, aGivenProto); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-invalidate-byob-request static void ReadableByteStreamControllerInvalidateBYOBRequest( ReadableByteStreamController* aController) { // Step 1. if (!aController->GetByobRequest()) { return; } // Step 2. aController->GetByobRequest()->SetController(nullptr); // Step 3. aController->GetByobRequest()->SetView(nullptr); // Step 4. aController->SetByobRequest(nullptr); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-clear-pending-pull-intos void ReadableByteStreamControllerClearPendingPullIntos( ReadableByteStreamController* aController) { // Step 1. ReadableByteStreamControllerInvalidateBYOBRequest(aController); // Step 2. aController->ClearPendingPullIntos(); } // https://streams.spec.whatwg.org/#reset-queue void ResetQueue(ReadableByteStreamController* aContainer) { // Step 1. Implied by type. // Step 2. aContainer->ClearQueue(); // Step 3. aContainer->SetQueueTotalSize(0); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-clear-algorithms void ReadableByteStreamControllerClearAlgorithms( ReadableByteStreamController* aController) { // Step 1. Set controller.[[pullAlgorithm]] to undefined. // Step 2. Set controller.[[cancelAlgorithm]] to undefined. aController->ClearAlgorithms(); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-error void ReadableByteStreamControllerError( ReadableByteStreamController* aController, JS::Handle aValue, ErrorResult& aRv) { // Step 1. Let stream be controller.[[stream]]. ReadableStream* stream = aController->Stream(); // Step 2. If stream.[[state]] is not "readable", return. if (stream->State() != ReadableStream::ReaderState::Readable) { return; } // Step 3. Perform // !ReadableByteStreamControllerClearPendingPullIntos(controller). ReadableByteStreamControllerClearPendingPullIntos(aController); // Step 4. Perform !ResetQueue(controller). ResetQueue(aController); // Step 5. Perform !ReadableByteStreamControllerClearAlgorithms(controller). ReadableByteStreamControllerClearAlgorithms(aController); // Step 6. Perform !ReadableStreamError(stream, e). AutoJSAPI jsapi; if (!jsapi.Init(aController->GetParentObject())) { return; } ReadableStreamError(jsapi.cx(), stream, aValue, aRv); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-close void ReadableByteStreamControllerClose( JSContext* aCx, ReadableByteStreamController* aController, ErrorResult& aRv) { // Step 1. RefPtr stream = aController->Stream(); // Step 2. if (aController->CloseRequested() || stream->State() != ReadableStream::ReaderState::Readable) { return; } // Step 3. if (aController->QueueTotalSize() > 0) { // Step 3.1 aController->SetCloseRequested(true); // Step 3.2 return; } // Step 4. if (!aController->PendingPullIntos().isEmpty()) { // Step 4.1 PullIntoDescriptor* firstPendingPullInto = aController->PendingPullIntos().getFirst(); // Step 4.2 if (firstPendingPullInto->BytesFilled() > 0) { // Step 4.2.1 ErrorResult rv; rv.ThrowTypeError("Leftover Bytes"); JS::Rooted exception(aCx); MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &exception)); // Step 4.2.2 ReadableByteStreamControllerError(aController, exception, aRv); if (aRv.Failed()) { return; } aRv.MightThrowJSException(); aRv.ThrowJSException(aCx, exception); return; } } // Step 5. ReadableByteStreamControllerClearAlgorithms(aController); // Step 6. ReadableStreamClose(aCx, stream, aRv); } // https://streams.spec.whatwg.org/#rbs-controller-close void ReadableByteStreamController::Close(JSContext* aCx, ErrorResult& aRv) { // Step 1. if (mCloseRequested) { aRv.ThrowTypeError("Close already requested"); return; } // Step 2. if (Stream()->State() != ReadableStream::ReaderState::Readable) { aRv.ThrowTypeError("Closing un-readable stream controller"); return; } // Step 3. ReadableByteStreamControllerClose(aCx, this, aRv); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-enqueue-chunk-to-queue void ReadableByteStreamControllerEnqueueChunkToQueue( ReadableByteStreamController* aController, JS::Handle aTransferredBuffer, size_t aByteOffset, size_t aByteLength) { // Step 1. RefPtr queueEntry = new ReadableByteStreamQueueEntry(aTransferredBuffer, aByteOffset, aByteLength); aController->Queue().insertBack(queueEntry); // Step 2. aController->AddToQueueTotalSize(double(aByteLength)); } // https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollerenqueueclonedchunktoqueue void ReadableByteStreamControllerEnqueueClonedChunkToQueue( JSContext* aCx, ReadableByteStreamController* aController, JS::Handle aBuffer, size_t aByteOffset, size_t aByteLength, ErrorResult& aRv) { // Step 1. Let cloneResult be CloneArrayBuffer(buffer, byteOffset, byteLength, // %ArrayBuffer%). aRv.MightThrowJSException(); JS::Rooted cloneResult( aCx, JS::ArrayBufferClone(aCx, aBuffer, aByteOffset, aByteLength)); // Step 2. If cloneResult is an abrupt completion, if (!cloneResult) { JS::Rooted exception(aCx); if (!JS_GetPendingException(aCx, &exception)) { // Uncatchable exception; we should mark aRv and return. aRv.StealExceptionFromJSContext(aCx); return; } JS_ClearPendingException(aCx); // Step 2.1. Perform ! ReadableByteStreamControllerError(controller, // cloneResult.[[Value]]). ReadableByteStreamControllerError(aController, exception, aRv); if (aRv.Failed()) { return; } // Step 2.2. Return cloneResult. aRv.ThrowJSException(aCx, exception); return; } // Step 3. Perform ! // ReadableByteStreamControllerEnqueueChunkToQueue(controller, // cloneResult.[[Value]], 0, byteLength). ReadableByteStreamControllerEnqueueChunkToQueue(aController, cloneResult, 0, aByteLength); } already_AddRefed ReadableByteStreamControllerShiftPendingPullInto( ReadableByteStreamController* aController); // https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollerenqueuedetachedpullintotoqueue void ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue( JSContext* aCx, ReadableByteStreamController* aController, PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv) { // Step 1. Assert: pullIntoDescriptor’s reader type is "none". MOZ_ASSERT(aPullIntoDescriptor->GetReaderType() == ReaderType::None); // Step 2. If pullIntoDescriptor’s bytes filled > 0, // perform ? ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller, // pullIntoDescriptor’s buffer, pullIntoDescriptor’s byte offset, // pullIntoDescriptor’s bytes filled). if (aPullIntoDescriptor->BytesFilled() > 0) { JS::Rooted buffer(aCx, aPullIntoDescriptor->Buffer()); ReadableByteStreamControllerEnqueueClonedChunkToQueue( aCx, aController, buffer, aPullIntoDescriptor->ByteOffset(), aPullIntoDescriptor->BytesFilled(), aRv); if (aRv.Failed()) { return; } } // Step 3. Perform ! // ReadableByteStreamControllerShiftPendingPullInto(controller). RefPtr discarded = ReadableByteStreamControllerShiftPendingPullInto(aController); (void)discarded; } // https://streams.spec.whatwg.org/#readable-stream-get-num-read-into-requests static size_t ReadableStreamGetNumReadIntoRequests(ReadableStream* aStream) { // Step 1. MOZ_ASSERT(ReadableStreamHasBYOBReader(aStream)); // Step 2. return aStream->GetReader()->AsBYOB()->ReadIntoRequests().length(); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-should-call-pull bool ReadableByteStreamControllerShouldCallPull( ReadableByteStreamController* aController) { // Step 1. Let stream be controller.[[stream]]. ReadableStream* stream = aController->Stream(); // Step 2. If stream.[[state]] is not "readable", return false. if (stream->State() != ReadableStream::ReaderState::Readable) { return false; } // Step 3. If controller.[[closeRequested]] is true, return false. if (aController->CloseRequested()) { return false; } // Step 4. If controller.[[started]] is false, return false. if (!aController->Started()) { return false; } // Step 5. If ! ReadableStreamHasDefaultReader(stream) is true // and ! ReadableStreamGetNumReadRequests(stream) > 0, return true. if (ReadableStreamHasDefaultReader(stream) && ReadableStreamGetNumReadRequests(stream) > 0) { return true; } // Step 6. If ! ReadableStreamHasBYOBReader(stream) is true // and ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true. if (ReadableStreamHasBYOBReader(stream) && ReadableStreamGetNumReadIntoRequests(stream) > 0) { return true; } // Step 7. Let desiredSize be // ! ReadableByteStreamControllerGetDesiredSize(controller). Nullable desiredSize = ReadableByteStreamControllerGetDesiredSize(aController); // Step 8. Assert: desiredSize is not null. MOZ_ASSERT(!desiredSize.IsNull()); // Step 9. If desiredSize > 0, return true. // Step 10. Return false. return desiredSize.Value() > 0; } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-call-pull-if-needed void ReadableByteStreamControllerCallPullIfNeeded( JSContext* aCx, ReadableByteStreamController* aController, ErrorResult& aRv) { // Step 1. bool shouldPull = ReadableByteStreamControllerShouldCallPull(aController); // Step 2. if (!shouldPull) { return; } // Step 3. if (aController->Pulling()) { aController->SetPullAgain(true); return; } // Step 4. MOZ_ASSERT(!aController->PullAgain()); // Step 5. aController->SetPulling(true); // Step 6. RefPtr controller(aController); RefPtr algorithms = aController->GetAlgorithms(); RefPtr pullPromise = algorithms->PullCallback(aCx, *controller, aRv); if (aRv.Failed()) { return; } // Steps 7+8 pullPromise->AddCallbacksWithCycleCollectedArgs( [](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, ReadableByteStreamController* aController) MOZ_CAN_RUN_SCRIPT_BOUNDARY { // Step 7.1 aController->SetPulling(false); // Step 7.2 if (aController->PullAgain()) { // Step 7.2.1 aController->SetPullAgain(false); // Step 7.2.2 ReadableByteStreamControllerCallPullIfNeeded( aCx, MOZ_KnownLive(aController), aRv); } }, [](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, ReadableByteStreamController* aController) { // Step 8.1 ReadableByteStreamControllerError(aController, aValue, aRv); }, RefPtr(aController)); } bool ReadableByteStreamControllerFillPullIntoDescriptorFromQueue( JSContext* aCx, ReadableByteStreamController* aController, PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv); JSObject* ReadableByteStreamControllerConvertPullIntoDescriptor( JSContext* aCx, PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv); // https://streams.spec.whatwg.org/#readable-stream-fulfill-read-into-request MOZ_CAN_RUN_SCRIPT void ReadableStreamFulfillReadIntoRequest(JSContext* aCx, ReadableStream* aStream, JS::Handle aChunk, bool done, ErrorResult& aRv) { // Step 1. Assert: !ReadableStreamHasBYOBReader(stream) is true. MOZ_ASSERT(ReadableStreamHasBYOBReader(aStream)); // Step 2. Let reader be stream.[[reader]]. ReadableStreamBYOBReader* reader = aStream->GetReader()->AsBYOB(); // Step 3. Assert: reader.[[readIntoRequests]] is not empty. MOZ_ASSERT(!reader->ReadIntoRequests().isEmpty()); // Step 4. Let readIntoRequest be reader.[[readIntoRequests]][0]. // Step 5. Remove readIntoRequest from reader.[[readIntoRequests]]. RefPtr readIntoRequest = reader->ReadIntoRequests().popFirst(); // Step 6. If done is true, perform readIntoRequest’s close steps, given // chunk. if (done) { readIntoRequest->CloseSteps(aCx, aChunk, aRv); return; } // Step 7. Otherwise, perform readIntoRequest’s chunk steps, given chunk. readIntoRequest->ChunkSteps(aCx, aChunk, aRv); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-commit-pull-into-descriptor MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerCommitPullIntoDescriptor( JSContext* aCx, ReadableStream* aStream, PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv) { // Step 1. Assert: stream.[[state]] is not "errored". MOZ_ASSERT(aStream->State() != ReadableStream::ReaderState::Errored); // Step 2. Assert: pullIntoDescriptor.reader type is not "none". MOZ_ASSERT(pullIntoDescriptor->GetReaderType() != ReaderType::None); // Step 3. Let done be false. bool done = false; // 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.2. Set done to true. done = true; } // Step 5. Let filledView be ! // ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor). JS::Rooted filledView( aCx, ReadableByteStreamControllerConvertPullIntoDescriptor( aCx, pullIntoDescriptor, aRv)); if (aRv.Failed()) { return; } JS::Rooted filledViewValue(aCx, JS::ObjectValue(*filledView)); // Step 6. If pullIntoDescriptor’s reader type is "default", if (pullIntoDescriptor->GetReaderType() == ReaderType::Default) { // Step 6.1. Perform !ReadableStreamFulfillReadRequest(stream, filledView, // done). ReadableStreamFulfillReadRequest(aCx, aStream, filledViewValue, done, aRv); return; } // Step 7.1. Assert: pullIntoDescriptor’s reader type is "byob". MOZ_ASSERT(pullIntoDescriptor->GetReaderType() == ReaderType::BYOB); // Step 7.2 Perform !ReadableStreamFulfillReadIntoRequest(stream, filledView, // done). ReadableStreamFulfillReadIntoRequest(aCx, aStream, filledViewValue, done, aRv); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-process-pull-into-descriptors-using-queue MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( JSContext* aCx, ReadableByteStreamController* aController, ErrorResult& aRv) { // Step 1. Assert: controller.[[closeRequested]] is false. MOZ_ASSERT(!aController->CloseRequested()); // Step 2. While controller.[[pendingPullIntos]] is not empty, while (!aController->PendingPullIntos().isEmpty()) { // Step 2.1 If controller.[[queueTotalSize]] is 0, return. if (aController->QueueTotalSize() == 0) { return; } // Step 2.2. Let pullIntoDescriptor be controller.[[pendingPullIntos]][0]. RefPtr pullIntoDescriptor = aController->PendingPullIntos().getFirst(); // Step 2.3. If // !ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, // pullIntoDescriptor) is true, bool ready = ReadableByteStreamControllerFillPullIntoDescriptorFromQueue( aCx, aController, pullIntoDescriptor, aRv); if (aRv.Failed()) { return; } if (ready) { // Step 2.3.1. Perform // !ReadableByteStreamControllerShiftPendingPullInto(controller). RefPtr discardedPullIntoDescriptor = ReadableByteStreamControllerShiftPendingPullInto(aController); // Step 2.3.2. Perform // !ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]], // pullIntoDescriptor). RefPtr stream(aController->Stream()); ReadableByteStreamControllerCommitPullIntoDescriptor( aCx, stream, pullIntoDescriptor, aRv); if (aRv.Failed()) { return; } } } } MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerHandleQueueDrain( JSContext* aCx, ReadableByteStreamController* aController, ErrorResult& aRv); // https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollerfillreadrequestfromqueue MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerFillReadRequestFromQueue( JSContext* aCx, ReadableByteStreamController* aController, ReadRequest* aReadRequest, ErrorResult& aRv) { // Step 1. Assert: controller.[[queueTotalSize]] > 0. MOZ_ASSERT(aController->QueueTotalSize() > 0); // Also assert that the queue has a non-zero length; MOZ_ASSERT(aController->Queue().length() > 0); // Step 2. Let entry be controller.[[queue]][0]. // Step 3. Remove entry from controller.[[queue]]. RefPtr entry = aController->Queue().popFirst(); // Assert that we actually got an entry. MOZ_ASSERT(entry); // Step 4. Set controller.[[queueTotalSize]] to controller.[[queueTotalSize]] // − entry’s byte length. aController->SetQueueTotalSize(aController->QueueTotalSize() - double(entry->ByteLength())); // Step 5. Perform ! ReadableByteStreamControllerHandleQueueDrain(controller). ReadableByteStreamControllerHandleQueueDrain(aCx, aController, aRv); if (aRv.Failed()) { return; } // Step 6. Let view be ! Construct(%Uint8Array%, « entry’s buffer, entry’s // byte offset, entry’s byte length »). aRv.MightThrowJSException(); JS::Rooted buffer(aCx, entry->Buffer()); JS::Rooted view( aCx, JS_NewUint8ArrayWithBuffer(aCx, buffer, entry->ByteOffset(), int64_t(entry->ByteLength()))); if (!view) { aRv.StealExceptionFromJSContext(aCx); return; } // Step 7. Perform readRequest’s chunk steps, given view. JS::Rooted viewValue(aCx, JS::ObjectValue(*view)); aReadRequest->ChunkSteps(aCx, viewValue, aRv); } MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerProcessReadRequestsUsingQueue( JSContext* aCx, ReadableByteStreamController* aController, ErrorResult& aRv) { // Step 1. Let reader be controller.[[stream]].[[reader]]. // Step 2. Assert: reader implements ReadableStreamDefaultReader. RefPtr reader = aController->Stream()->GetDefaultReader(); // Step 3. While reader.[[readRequests]] is not empty, while (!reader->ReadRequests().isEmpty()) { // Step 3.1. If controller.[[queueTotalSize]] is 0, return. if (aController->QueueTotalSize() == 0) { return; } // Step 3.2. Let readRequest be reader.[[readRequests]][0]. // Step 3.3. Remove readRequest from reader.[[readRequests]]. RefPtr readRequest = reader->ReadRequests().popFirst(); // Step 3.4. Perform ! // ReadableByteStreamControllerFillReadRequestFromQueue(controller, // readRequest). ReadableByteStreamControllerFillReadRequestFromQueue(aCx, aController, readRequest, aRv); if (aRv.Failed()) { return; } } } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-enqueue void ReadableByteStreamControllerEnqueue( JSContext* aCx, ReadableByteStreamController* aController, JS::Handle aChunk, ErrorResult& aRv) { aRv.MightThrowJSException(); // Step 1. RefPtr stream = aController->Stream(); // Step 2. if (aController->CloseRequested() || stream->State() != ReadableStream::ReaderState::Readable) { return; } // Step 3. bool isShared; JS::Rooted buffer( aCx, JS_GetArrayBufferViewBuffer(aCx, aChunk, &isShared)); if (!buffer) { aRv.StealExceptionFromJSContext(aCx); return; } // Step 4. size_t byteOffset = JS_GetArrayBufferViewByteOffset(aChunk); // Step 5. size_t byteLength = JS_GetArrayBufferViewByteLength(aChunk); // Step 6. if (JS::IsDetachedArrayBufferObject(buffer)) { aRv.ThrowTypeError("Detatched Array Buffer"); return; } // Step 7. JS::Rooted transferredBuffer(aCx, TransferArrayBuffer(aCx, buffer)); if (!transferredBuffer) { aRv.StealExceptionFromJSContext(aCx); return; } // Step 8. if (!aController->PendingPullIntos().isEmpty()) { // Step 8.1 RefPtr firstPendingPullInto = aController->PendingPullIntos().getFirst(); // Step 8.2 JS::Rooted pendingBuffer(aCx, firstPendingPullInto->Buffer()); if (JS::IsDetachedArrayBufferObject(pendingBuffer)) { aRv.ThrowTypeError("Pending PullInto has detached buffer"); return; } // Step 8.3. Perform ! // ReadableByteStreamControllerInvalidateBYOBRequest(controller). ReadableByteStreamControllerInvalidateBYOBRequest(aController); // Step 8.4. Set firstPendingPullInto’s buffer to ! // TransferArrayBuffer(firstPendingPullInto’s buffer). pendingBuffer = TransferArrayBuffer(aCx, pendingBuffer); if (!pendingBuffer) { aRv.StealExceptionFromJSContext(aCx); return; } firstPendingPullInto->SetBuffer(pendingBuffer); // Step 8.5. If firstPendingPullInto’s reader type is "none", perform ? // ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller, // firstPendingPullInto). if (firstPendingPullInto->GetReaderType() == ReaderType::None) { ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue( aCx, aController, firstPendingPullInto, aRv); if (aRv.Failed()) { return; } } } // Step 9. If ! ReadableStreamHasDefaultReader(stream) is true, if (ReadableStreamHasDefaultReader(stream)) { // Step 9.1. Perform ! // ReadableByteStreamControllerProcessReadRequestsUsingQueue(controller). ReadableByteStreamControllerProcessReadRequestsUsingQueue(aCx, aController, aRv); if (aRv.Failed()) { return; } // Step 9.2. If ! ReadableStreamGetNumReadRequests(stream) is 0, if (ReadableStreamGetNumReadRequests(stream) == 0) { // Step 9.2.1 Assert: controller.[[pendingPullIntos]] is empty. MOZ_ASSERT(aController->PendingPullIntos().isEmpty()); // Step 9.2.2. Perform ! // ReadableByteStreamControllerEnqueueChunkToQueue(controller, // transferredBuffer, byteOffset, byteLength). ReadableByteStreamControllerEnqueueChunkToQueue( aController, transferredBuffer, byteOffset, byteLength); // Step 9.3. Otherwise, } else { // Step 9.3.1 Assert: controller.[[queue]] is empty. MOZ_ASSERT(aController->Queue().isEmpty()); // Step 9.3.2. If controller.[[pendingPullIntos]] is not empty, if (!aController->PendingPullIntos().isEmpty()) { // Step 9.3.2.1. Assert: controller.[[pendingPullIntos]][0]'s reader // type is "default". MOZ_ASSERT( aController->PendingPullIntos().getFirst()->GetReaderType() == ReaderType::Default); // Step 9.3.2.2. Perform ! // ReadableByteStreamControllerShiftPendingPullInto(controller). RefPtr pullIntoDescriptor = ReadableByteStreamControllerShiftPendingPullInto(aController); (void)pullIntoDescriptor; } // Step 9.3.3. Let transferredView be ! Construct(%Uint8Array%, « // transferredBuffer, byteOffset, byteLength »). JS::Rooted transferredView( aCx, JS_NewUint8ArrayWithBuffer(aCx, transferredBuffer, byteOffset, int64_t(byteLength))); if (!transferredView) { aRv.StealExceptionFromJSContext(aCx); return; } // Step 9.3.4. Perform ! ReadableStreamFulfillReadRequest(stream, // transferredView, false). JS::Rooted transferredViewValue( aCx, JS::ObjectValue(*transferredView)); ReadableStreamFulfillReadRequest(aCx, stream, transferredViewValue, false, aRv); if (aRv.Failed()) { return; } } // Step 10. Otherwise, if ! ReadableStreamHasBYOBReader(stream) is true, } else if (ReadableStreamHasBYOBReader(stream)) { // Step 10.1. Perform ! // ReadableByteStreamControllerEnqueueChunkToQueue(controller, // transferredBuffer, byteOffset, byteLength). ReadableByteStreamControllerEnqueueChunkToQueue( aController, transferredBuffer, byteOffset, byteLength); // Step 10.2 Perform ! // ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( aCx, aController, aRv); if (aRv.Failed()) { return; } // Step 11. Otherwise, } else { // Step 11.1. Assert: ! IsReadableStreamLocked(stream) is false. MOZ_ASSERT(!IsReadableStreamLocked(stream)); // Step 11.2. Perform ! // ReadableByteStreamControllerEnqueueChunkToQueue(controller, // transferredBuffer, byteOffset, byteLength). ReadableByteStreamControllerEnqueueChunkToQueue( aController, transferredBuffer, byteOffset, byteLength); } // Step 12. Perform ! // ReadableByteStreamControllerCallPullIfNeeded(controller). ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv); } // https://streams.spec.whatwg.org/#rbs-controller-enqueue void ReadableByteStreamController::Enqueue(JSContext* aCx, const ArrayBufferView& aChunk, ErrorResult& aRv) { // Step 1. JS::Rooted chunk(aCx, aChunk.Obj()); if (JS_GetArrayBufferViewByteLength(chunk) == 0) { aRv.ThrowTypeError("Zero Length View"); return; } // Step 2. bool isShared; JS::Rooted viewedArrayBuffer( aCx, JS_GetArrayBufferViewBuffer(aCx, chunk, &isShared)); if (!viewedArrayBuffer) { aRv.StealExceptionFromJSContext(aCx); return; } if (JS::GetArrayBufferByteLength(viewedArrayBuffer) == 0) { aRv.ThrowTypeError("Zero Length Buffer"); return; } // Step 3. if (CloseRequested()) { aRv.ThrowTypeError("close requested"); return; } // Step 4. if (Stream()->State() != ReadableStream::ReaderState::Readable) { aRv.ThrowTypeError("Not Readable"); return; } // Step 5. ReadableByteStreamControllerEnqueue(aCx, this, chunk, aRv); } // https://streams.spec.whatwg.org/#rbs-controller-error void ReadableByteStreamController::Error(JSContext* aCx, JS::Handle aErrorValue, ErrorResult& aRv) { // Step 1. ReadableByteStreamControllerError(this, aErrorValue, aRv); } // https://streams.spec.whatwg.org/#rbs-controller-private-cancel already_AddRefed ReadableByteStreamController::CancelSteps( JSContext* aCx, JS::Handle aReason, ErrorResult& aRv) { // Step 1. ReadableByteStreamControllerClearPendingPullIntos(this); // Step 2. ResetQueue(this); // Step 3. Optional> reason(aCx, aReason); RefPtr algorithms = mAlgorithms; RefPtr result = algorithms->CancelCallback(aCx, reason, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Step 4. ReadableByteStreamControllerClearAlgorithms(this); // Step 5. return result.forget(); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-handle-queue-drain void ReadableByteStreamControllerHandleQueueDrain( JSContext* aCx, ReadableByteStreamController* aController, ErrorResult& aRv) { // Step 1. MOZ_ASSERT(aController->Stream()->State() == ReadableStream::ReaderState::Readable); // Step 2. if (aController->QueueTotalSize() == 0 && aController->CloseRequested()) { // Step 2.1 ReadableByteStreamControllerClearAlgorithms(aController); // Step 2.2 RefPtr stream = aController->Stream(); ReadableStreamClose(aCx, stream, aRv); return; } // Step 3.1 ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv); } // https://streams.spec.whatwg.org/#rbs-controller-private-pull void ReadableByteStreamController::PullSteps(JSContext* aCx, ReadRequest* aReadRequest, ErrorResult& aRv) { // Step 1. ReadableStream* stream = Stream(); // Step 2. MOZ_ASSERT(ReadableStreamHasDefaultReader(stream)); // Step 3. if (QueueTotalSize() > 0) { // Step 3.1. Assert: ! ReadableStreamGetNumReadRequests ( stream ) is 0. MOZ_ASSERT(ReadableStreamGetNumReadRequests(stream) == 0); // Step 3.2. Perform ! // ReadableByteStreamControllerFillReadRequestFromQueue(this, readRequest). ReadableByteStreamControllerFillReadRequestFromQueue(aCx, this, aReadRequest, aRv); // Step 3.3. Return. return; } // Step 4. Maybe autoAllocateChunkSize = AutoAllocateChunkSize(); // Step 5. if (autoAllocateChunkSize) { // Step 5.1 aRv.MightThrowJSException(); JS::Rooted buffer( aCx, JS::NewArrayBuffer(aCx, *autoAllocateChunkSize)); // Step 5.2 if (!buffer) { // Step 5.2.1 JS::Rooted bufferError(aCx); if (!JS_GetPendingException(aCx, &bufferError)) { // Uncatchable exception; we should mark aRv and return. aRv.StealExceptionFromJSContext(aCx); return; } // It's not expliclitly 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. return; } // Step 5.3 RefPtr pullIntoDescriptor = new PullIntoDescriptor( buffer, *autoAllocateChunkSize, 0, *autoAllocateChunkSize, 0, 1, PullIntoDescriptor::Constructor::Uint8, ReaderType::Default); // Step 5.4 PendingPullIntos().insertBack(pullIntoDescriptor); } // Step 6. ReadableStreamAddReadRequest(stream, aReadRequest); // Step 7. ReadableByteStreamControllerCallPullIfNeeded(aCx, this, aRv); } // https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontroller-releasesteps void ReadableByteStreamController::ReleaseSteps() { // Step 1. If this.[[pendingPullIntos]] is not empty, if (!PendingPullIntos().isEmpty()) { // Step 1.1. Let firstPendingPullInto be this.[[pendingPullIntos]][0]. RefPtr firstPendingPullInto = PendingPullIntos().popFirst(); // Step 1.2. Set firstPendingPullInto’s reader type to "none". firstPendingPullInto->SetReaderType(ReaderType::None); // Step 1.3. Set this.[[pendingPullIntos]] to the list « // firstPendingPullInto ». PendingPullIntos().clear(); PendingPullIntos().insertBack(firstPendingPullInto); } } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-shift-pending-pull-into already_AddRefed ReadableByteStreamControllerShiftPendingPullInto( ReadableByteStreamController* aController) { // Step 1. MOZ_ASSERT(!aController->GetByobRequest()); // Step 2 + 3 RefPtr descriptor = aController->PendingPullIntos().popFirst(); // Step 4. return descriptor.forget(); } JSObject* ConstructFromPullIntoConstructor( JSContext* aCx, PullIntoDescriptor::Constructor constructor, JS::Handle buffer, size_t byteOffset, size_t length) { switch (constructor) { case PullIntoDescriptor::Constructor::DataView: return JS_NewDataView(aCx, buffer, byteOffset, length); break; #define CONSTRUCT_TYPED_ARRAY_TYPE(ExternalT, NativeT, Name) \ case PullIntoDescriptor::Constructor::Name: \ return JS_New##Name##ArrayWithBuffer(aCx, buffer, byteOffset, \ int64_t(length)); \ break; JS_FOR_EACH_TYPED_ARRAY(CONSTRUCT_TYPED_ARRAY_TYPE) #undef CONSTRUCT_TYPED_ARRAY_TYPE default: MOZ_ASSERT_UNREACHABLE("Unknown PullIntoDescriptor::Constructor"); return nullptr; } } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-convert-pull-into-descriptor JSObject* ReadableByteStreamControllerConvertPullIntoDescriptor( JSContext* aCx, PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv) { // Step 1. Let bytesFilled be pullIntoDescriptor’s bytes filled. uint64_t bytesFilled = pullIntoDescriptor->BytesFilled(); // Step 2. Let elementSize be pullIntoDescriptor’s element size. uint64_t elementSize = pullIntoDescriptor->ElementSize(); // Step 3. Assert: bytesFilled ≤ pullIntoDescriptor’s byte length. MOZ_ASSERT(bytesFilled <= pullIntoDescriptor->ByteLength()); // Step 4. Assert: bytesFilled mod elementSize is 0. MOZ_ASSERT(bytesFilled % elementSize == 0); // Step 5. Let buffer be ! TransferArrayBuffer(pullIntoDescriptor’s buffer). aRv.MightThrowJSException(); JS::Rooted srcBuffer(aCx, pullIntoDescriptor->Buffer()); JS::Rooted buffer(aCx, TransferArrayBuffer(aCx, srcBuffer)); if (!buffer) { aRv.StealExceptionFromJSContext(aCx); return nullptr; } // Step 6. Return ! Construct(pullIntoDescriptor’s view constructor, // « buffer, pullIntoDescriptor’s byte offset, bytesFilled ÷ elementSize »). JS::Rooted res( aCx, ConstructFromPullIntoConstructor( aCx, pullIntoDescriptor->ViewConstructor(), buffer, pullIntoDescriptor->ByteOffset(), bytesFilled / elementSize)); if (!res) { aRv.StealExceptionFromJSContext(aCx); return nullptr; } return res; } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-in-closed-state 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 2. If firstDescriptor’s reader type is "none", // perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). if (aFirstDescriptor->GetReaderType() == ReaderType::None) { RefPtr discarded = ReadableByteStreamControllerShiftPendingPullInto(aController); (void)discarded; } // Step 3. Let stream be controller.[[stream]]. RefPtr stream = aController->Stream(); // Step 4. If ! ReadableStreamHasBYOBReader(stream) is true, if (ReadableStreamHasBYOBReader(stream)) { // Step 4.1. While ! ReadableStreamGetNumReadIntoRequests(stream) > 0, while (ReadableStreamGetNumReadIntoRequests(stream) > 0) { // Step 4.1.1. Let pullIntoDescriptor be ! // ReadableByteStreamControllerShiftPendingPullInto(controller). RefPtr pullIntoDescriptor = ReadableByteStreamControllerShiftPendingPullInto(aController); // Step 4.1.2. Perform ! // ReadableByteStreamControllerCommitPullIntoDescriptor(stream, // pullIntoDescriptor). ReadableByteStreamControllerCommitPullIntoDescriptor( aCx, stream, pullIntoDescriptor, aRv); } } } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-fill-head-pull-into-descriptor void ReadableByteStreamControllerFillHeadPullIntoDescriptor( ReadableByteStreamController* aController, size_t aSize, PullIntoDescriptor* aPullIntoDescriptor) { // Step 1. Assert: either controller.[[pendingPullIntos]] is empty, or // controller.[[pendingPullIntos]][0] is pullIntoDescriptor. MOZ_ASSERT(aController->PendingPullIntos().isEmpty() || aController->PendingPullIntos().getFirst() == aPullIntoDescriptor); // Step 2. Assert: controller.[[byobRequest]] is null. MOZ_ASSERT(!aController->GetByobRequest()); // Step 3. Set pullIntoDescriptor’s bytes filled to bytes filled + size. aPullIntoDescriptor->SetBytesFilled(aPullIntoDescriptor->BytesFilled() + aSize); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-in-readable-state MOZ_CAN_RUN_SCRIPT static void ReadableByteStreamControllerRespondInReadableState( JSContext* aCx, ReadableByteStreamController* aController, uint64_t aBytesWritten, PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv) { // Step 1. Assert: pullIntoDescriptor’s bytes filled + bytesWritten ≤ // pullIntoDescriptor’s byte length. MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() + aBytesWritten <= aPullIntoDescriptor->ByteLength()); // Step 2. Perform // !ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, // bytesWritten, pullIntoDescriptor). ReadableByteStreamControllerFillHeadPullIntoDescriptor( aController, aBytesWritten, aPullIntoDescriptor); // Step 3. If pullIntoDescriptor’s reader type is "none", if (aPullIntoDescriptor->GetReaderType() == ReaderType::None) { // Step 3.1. Perform ? // ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller, // pullIntoDescriptor). ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue( aCx, aController, aPullIntoDescriptor, aRv); if (aRv.Failed()) { return; } // Step 3.2. Perform ! // ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( aCx, aController, aRv); // Step 3.3. Return. return; } // Step 4. If pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s element // size, return. if (aPullIntoDescriptor->BytesFilled() < aPullIntoDescriptor->ElementSize()) { return; } // Step 5. Perform // !ReadableByteStreamControllerShiftPendingPullInto(controller). RefPtr pullIntoDescriptor = ReadableByteStreamControllerShiftPendingPullInto(aController); (void)pullIntoDescriptor; // Step 6. Let remainderSize be pullIntoDescriptor’s bytes filled mod // pullIntoDescriptor’s element size. size_t remainderSize = aPullIntoDescriptor->BytesFilled() % aPullIntoDescriptor->ElementSize(); // Step 7. If remainderSize > 0, if (remainderSize > 0) { // Step 7.1. Let end be pullIntoDescriptor’s byte offset + // pullIntoDescriptor’s bytes filled. size_t end = aPullIntoDescriptor->ByteOffset() + aPullIntoDescriptor->BytesFilled(); // Step 7.2. Perform ? // ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller, // pullIntoDescriptor’s buffer, end − remainderSize, remainderSize). JS::Rooted pullIntoBuffer(aCx, aPullIntoDescriptor->Buffer()); ReadableByteStreamControllerEnqueueClonedChunkToQueue( aCx, aController, pullIntoBuffer, end - remainderSize, remainderSize, aRv); if (aRv.Failed()) { return; } } // Step 8. Set pullIntoDescriptor’s bytes filled to pullIntoDescriptor’s bytes // filled − remainderSize. aPullIntoDescriptor->SetBytesFilled(aPullIntoDescriptor->BytesFilled() - remainderSize); // Step 9. Perform // !ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]], // pullIntoDescriptor). RefPtr stream(aController->Stream()); ReadableByteStreamControllerCommitPullIntoDescriptor( aCx, stream, aPullIntoDescriptor, aRv); if (aRv.Failed()) { return; } // Step 10. Perform // !ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue( aCx, aController, aRv); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-internal void ReadableByteStreamControllerRespondInternal( JSContext* aCx, ReadableByteStreamController* aController, uint64_t aBytesWritten, ErrorResult& aRv) { // Step 1. RefPtr firstDescriptor = aController->PendingPullIntos().getFirst(); // Step 2. JS::Rooted buffer(aCx, firstDescriptor->Buffer()); #ifdef DEBUG bool canTransferBuffer = CanTransferArrayBuffer(aCx, buffer, aRv); MOZ_ASSERT(!aRv.Failed()); MOZ_ASSERT(canTransferBuffer); #endif // Step 3. ReadableByteStreamControllerInvalidateBYOBRequest(aController); // Step 4. auto state = aController->Stream()->State(); // Step 5. if (state == ReadableStream::ReaderState::Closed) { // Step 5.1 MOZ_ASSERT(aBytesWritten == 0); // Step 5.2 ReadableByteStreamControllerRespondInClosedState(aCx, aController, firstDescriptor, aRv); if (aRv.Failed()) { return; } } else { // Step 6.1 MOZ_ASSERT(state == ReadableStream::ReaderState::Readable); // Step 6.2. MOZ_ASSERT(aBytesWritten > 0); // Step 6.3 ReadableByteStreamControllerRespondInReadableState( aCx, aController, aBytesWritten, firstDescriptor, aRv); if (aRv.Failed()) { return; } } // Step 7. ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond void ReadableByteStreamControllerRespond( JSContext* aCx, ReadableByteStreamController* aController, uint64_t aBytesWritten, ErrorResult& aRv) { // Step 1. MOZ_ASSERT(!aController->PendingPullIntos().isEmpty()); // Step 2. PullIntoDescriptor* firstDescriptor = aController->PendingPullIntos().getFirst(); // Step 3. auto state = aController->Stream()->State(); // Step 4. if (state == ReadableStream::ReaderState::Closed) { // Step 4.1 if (aBytesWritten != 0) { aRv.ThrowTypeError("bytesWritten not zero on closed stream"); return; } } else { // Step 5.1 MOZ_ASSERT(state == ReadableStream::ReaderState::Readable); // Step 5.2 if (aBytesWritten == 0) { aRv.ThrowTypeError("bytesWritten 0"); return; } // Step 5.3 if (firstDescriptor->BytesFilled() + aBytesWritten > firstDescriptor->ByteLength()) { aRv.ThrowRangeError("bytesFilled + bytesWritten > byteLength"); return; } } // Step 6. aRv.MightThrowJSException(); JS::Rooted buffer(aCx, firstDescriptor->Buffer()); JS::Rooted transferredBuffer(aCx, TransferArrayBuffer(aCx, buffer)); if (!transferredBuffer) { aRv.StealExceptionFromJSContext(aCx); return; } firstDescriptor->SetBuffer(transferredBuffer); // Step 7. ReadableByteStreamControllerRespondInternal(aCx, aController, aBytesWritten, aRv); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-with-new-view void ReadableByteStreamControllerRespondWithNewView( JSContext* aCx, ReadableByteStreamController* aController, JS::Handle aView, ErrorResult& aRv) { aRv.MightThrowJSException(); // Step 1. MOZ_ASSERT(!aController->PendingPullIntos().isEmpty()); // Step 2. bool isSharedMemory; JS::Rooted viewedArrayBuffer( aCx, JS_GetArrayBufferViewBuffer(aCx, aView, &isSharedMemory)); if (!viewedArrayBuffer) { aRv.StealExceptionFromJSContext(aCx); return; } MOZ_ASSERT(!JS::IsDetachedArrayBufferObject(viewedArrayBuffer)); // Step 3. RefPtr firstDescriptor = aController->PendingPullIntos().getFirst(); // Step 4. ReadableStream::ReaderState state = aController->Stream()->State(); // Step 5. if (state == ReadableStream::ReaderState::Closed) { // Step 5.1 if (JS_GetArrayBufferViewByteLength(aView) != 0) { aRv.ThrowTypeError("View has non-zero length in closed stream"); return; } } else { // Step 6.1 MOZ_ASSERT(state == ReadableStream::ReaderState::Readable); // Step 6.2 if (JS_GetArrayBufferViewByteLength(aView) == 0) { aRv.ThrowTypeError("View has zero length in readable stream"); return; } } // Step 7. if (firstDescriptor->ByteOffset() + firstDescriptor->BytesFilled() != JS_GetArrayBufferViewByteOffset(aView)) { aRv.ThrowRangeError("Invalid Offset"); return; } // Step 8. if (firstDescriptor->BufferByteLength() != JS::GetArrayBufferByteLength(viewedArrayBuffer)) { aRv.ThrowRangeError("Mismatched buffer byte lengths"); return; } // Step 9. if (firstDescriptor->BytesFilled() + JS_GetArrayBufferViewByteLength(aView) > firstDescriptor->ByteLength()) { aRv.ThrowRangeError("Too many bytes"); return; } // Step 10. Let viewByteLength be view.[[ByteLength]]. size_t viewByteLength = JS_GetArrayBufferViewByteLength(aView); // Step 11. Set firstDescriptor’s buffer to ? // TransferArrayBuffer(view.[[ViewedArrayBuffer]]). JS::Rooted transferedBuffer( aCx, TransferArrayBuffer(aCx, viewedArrayBuffer)); if (!transferedBuffer) { aRv.StealExceptionFromJSContext(aCx); return; } firstDescriptor->SetBuffer(transferedBuffer); // Step 12. Perform ? ReadableByteStreamControllerRespondInternal(controller, // viewByteLength). ReadableByteStreamControllerRespondInternal(aCx, aController, viewByteLength, aRv); } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-fill-pull-into-descriptor-from-queue 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]], // 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 + // 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. size_t totalBytesToCopyRemaining = maxBytesToCopy; // Step 7. Let ready be false. bool ready = false; // Step 8. If maxAlignedBytes > currentAlignedBytes, if (maxAlignedBytes > currentAlignedBytes) { // Step 8.1. Set totalBytesToCopyRemaining to maxAlignedBytes − // pullIntoDescriptor’s bytes filled. totalBytesToCopyRemaining = maxAlignedBytes - aPullIntoDescriptor->BytesFilled(); // Step 8.2. Set ready to true. ready = true; } // Step 9. Let queue be controller.[[queue]]. LinkedList>& queue = aController->Queue(); // Step 10. While totalBytesToCopyRemaining > 0, while (totalBytesToCopyRemaining > 0) { // Step 10.1 Let headOfQueue be queue[0]. ReadableByteStreamQueueEntry* headOfQueue = queue.getFirst(); // Step 10.2. Let bytesToCopy be min(totalBytesToCopyRemaining, // headOfQueue’s byte length). size_t bytesToCopy = std::min(totalBytesToCopyRemaining, headOfQueue->ByteLength()); // Step 10.3. Let destStart be pullIntoDescriptor’s byte offset + // pullIntoDescriptor’s bytes filled. size_t destStart = aPullIntoDescriptor->ByteOffset() + aPullIntoDescriptor->BytesFilled(); // Step 10.4. Perform !CopyDataBlockBytes(pullIntoDescriptor’s // buffer.[[ArrayBufferData]], destStart, headOfQueue’s // buffer.[[ArrayBufferData]], headOfQueue’s byte offset, // bytesToCopy). JS::Rooted descriptorBuffer(aCx, aPullIntoDescriptor->Buffer()); JS::Rooted queueBuffer(aCx, headOfQueue->Buffer()); if (!JS::ArrayBufferCopyData(aCx, descriptorBuffer, destStart, queueBuffer, headOfQueue->ByteOffset(), bytesToCopy)) { aRv.StealExceptionFromJSContext(aCx); return false; } // Step 10.5. If headOfQueue’s byte length is bytesToCopy, if (headOfQueue->ByteLength() == bytesToCopy) { // Step 10.5.1. Remove queue[0]. queue.popFirst(); } else { // Step 10.6. Otherwise, // Step 10.6.1 Set headOfQueue’s byte offset to // headOfQueue’s byte offset + bytesToCopy. headOfQueue->SetByteOffset(headOfQueue->ByteOffset() + bytesToCopy); // Step 10.6.2 Set headOfQueue’s byte length to // headOfQueue’s byte length − bytesToCopy. headOfQueue->SetByteLength(headOfQueue->ByteLength() - bytesToCopy); } // Step 10.7. Set controller.[[queueTotalSize]] to // controller.[[queueTotalSize]] − bytesToCopy. aController->SetQueueTotalSize(aController->QueueTotalSize() - (double)bytesToCopy); // Step 10.8, Perform // !ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, // bytesToCopy, pullIntoDescriptor). ReadableByteStreamControllerFillHeadPullIntoDescriptor( aController, bytesToCopy, aPullIntoDescriptor); // Step 10.9. Set totalBytesToCopyRemaining to totalBytesToCopyRemaining − // bytesToCopy. totalBytesToCopyRemaining = totalBytesToCopyRemaining - bytesToCopy; } // Step 11. If ready is false, if (!ready) { // Step 11.1. Assert: controller.[[queueTotalSize]] is 0. MOZ_ASSERT(aController->QueueTotalSize() == 0); // Step 11.2. Assert: pullIntoDescriptor’s bytes filled > 0. MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() > 0); // Step 11.3. Assert: pullIntoDescriptor’s bytes filled < // pullIntoDescriptor’s // element size. MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() < aPullIntoDescriptor->ElementSize()); } // Step 12. Return ready. return ready; } // https://streams.spec.whatwg.org/#readable-byte-stream-controller-pull-into void ReadableByteStreamControllerPullInto( JSContext* aCx, ReadableByteStreamController* aController, JS::Handle aView, ReadIntoRequest* aReadIntoRequest, ErrorResult& aRv) { aRv.MightThrowJSException(); // Step 1. Let stream be controller.[[stream]]. ReadableStream* stream = aController->Stream(); // Step 2. Let elementSize be 1. size_t elementSize = 1; // Step 3. Let ctor be %DataView%. PullIntoDescriptor::Constructor ctor = PullIntoDescriptor::Constructor::DataView; // Step 4. If view has a [[TypedArrayName]] internal slot (i.e., it is not a // DataView), if (JS_IsTypedArrayObject(aView)) { // Step 4.1. Set elementSize to the element size specified in the typed // array constructors table for view.[[TypedArrayName]]. JS::Scalar::Type type = JS_GetArrayBufferViewType(aView); elementSize = JS::Scalar::byteSize(type); // Step 4.2 Set ctor to the constructor specified in the typed array // constructors table for view.[[TypedArrayName]]. ctor = PullIntoDescriptor::constructorFromScalar(type); } // Step 5. Let byteOffset be view.[[ByteOffset]]. size_t byteOffset = JS_GetArrayBufferViewByteOffset(aView); // Step 6. Let byteLength be view.[[ByteLength]]. size_t byteLength = JS_GetArrayBufferViewByteLength(aView); // Step 7. Let bufferResult be // TransferArrayBuffer(view.[[ViewedArrayBuffer]]). bool isShared; JS::Rooted viewedArrayBuffer( aCx, JS_GetArrayBufferViewBuffer(aCx, aView, &isShared)); if (!viewedArrayBuffer) { aRv.StealExceptionFromJSContext(aCx); return; } JS::Rooted bufferResult( aCx, TransferArrayBuffer(aCx, viewedArrayBuffer)); // Step 8. If bufferResult is an abrupt completion, if (!bufferResult) { JS::Rooted pendingException(aCx); if (!JS_GetPendingException(aCx, &pendingException)) { // This means an un-catchable exception. Use StealExceptionFromJSContext // to setup aRv properly. aRv.StealExceptionFromJSContext(aCx); return; } // It's not expliclitly stated, but I assume the intention here is that // we perform a normal completion here; we also need to clear the // exception state anyhow to succesfully run ErrorSteps. JS_ClearPendingException(aCx); // Step 8.1. Perform readIntoRequest’s error steps, given // bufferResult.[[Value]]. aReadIntoRequest->ErrorSteps(aCx, pendingException, aRv); // Step 8.2. Return. return; } // Step 9. 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". RefPtr pullIntoDescriptor = new PullIntoDescriptor( buffer, JS::GetArrayBufferByteLength(buffer), byteOffset, byteLength, 0, elementSize, ctor, ReaderType::BYOB); // Step 11. If controller.[[pendingPullIntos]] is not empty, if (!aController->PendingPullIntos().isEmpty()) { // Step 11.1. Append pullIntoDescriptor to controller.[[pendingPullIntos]]. aController->PendingPullIntos().insertBack(pullIntoDescriptor); // Step 11.2. Perform !ReadableStreamAddReadIntoRequest(stream, // readIntoRequest). ReadableStreamAddReadIntoRequest(stream, aReadIntoRequest); // Step 11.3. Return. return; } // Step 12. If stream.[[state]] is "closed", if (stream->State() == ReadableStream::ReaderState::Closed) { // Step 12.1. Let emptyView be !Construct(ctor, « pullIntoDescriptor’s // buffer, pullIntoDescriptor’s byte offset, 0 »). JS::Rooted pullIntoBuffer(aCx, pullIntoDescriptor->Buffer()); JS::Rooted emptyView( aCx, ConstructFromPullIntoConstructor(aCx, ctor, pullIntoBuffer, pullIntoDescriptor->ByteOffset(), 0)); if (!emptyView) { aRv.StealExceptionFromJSContext(aCx); return; } // Step 12.2. Perform readIntoRequest’s close steps, given emptyView. JS::Rooted emptyViewValue(aCx, JS::ObjectValue(*emptyView)); aReadIntoRequest->CloseSteps(aCx, emptyViewValue, aRv); // Step 12.3. Return. return; } // Step 13,. If controller.[[queueTotalSize]] > 0, if (aController->QueueTotalSize() > 0) { // Step 13.1 If // !ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, // pullIntoDescriptor) is true, bool ready = ReadableByteStreamControllerFillPullIntoDescriptorFromQueue( aCx, aController, pullIntoDescriptor, aRv); if (aRv.Failed()) { return; } if (ready) { // Step 13.1.1 Let filledView be // !ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor). JS::Rooted filledView( aCx, ReadableByteStreamControllerConvertPullIntoDescriptor( aCx, pullIntoDescriptor, aRv)); if (aRv.Failed()) { return; } // Step 13.1.2. Perform // !ReadableByteStreamControllerHandleQueueDrain(controller). ReadableByteStreamControllerHandleQueueDrain(aCx, aController, aRv); if (aRv.Failed()) { return; } // Step 13.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. return; } // Step 13.2 If controller.[[closeRequested]] is true, if (aController->CloseRequested()) { // Step 13.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). ReadableByteStreamControllerError(aController, e, aRv); if (aRv.Failed()) { return; } // Step 13.2.3. Perform readIntoRequest’s error steps, given e. aReadIntoRequest->ErrorSteps(aCx, e, aRv); // Step 13.2.4. Return. return; } } // Step 14. Append pullIntoDescriptor to controller.[[pendingPullIntos]]. aController->PendingPullIntos().insertBack(pullIntoDescriptor); // Step 15. Perform !ReadableStreamAddReadIntoRequest(stream, // readIntoRequest). ReadableStreamAddReadIntoRequest(stream, aReadIntoRequest); // Step 16, Perform // !ReadableByteStreamControllerCallPullIfNeeded(controller). ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv); } // https://streams.spec.whatwg.org/#set-up-readable-byte-stream-controller void SetUpReadableByteStreamController( JSContext* aCx, ReadableStream* aStream, ReadableByteStreamController* aController, UnderlyingSourceAlgorithmsBase* aAlgorithms, double aHighWaterMark, Maybe aAutoAllocateChunkSize, ErrorResult& aRv) { // Step 1. Assert: stream.[[controller]] is undefined. MOZ_ASSERT(!aStream->Controller()); // Step 2. If autoAllocateChunkSize is not undefined, // Step 2.1. Assert: ! IsInteger(autoAllocateChunkSize) is true. Implicit // Step 2.2. Assert: autoAllocateChunkSize is positive. (Implicit by // type.) // Step 3. Set controller.[[stream]] to stream. aController->SetStream(aStream); // Step 4. Set controller.[[pullAgain]] and controller.[[pulling]] to false. aController->SetPullAgain(false); aController->SetPulling(false); // Step 5. Set controller.[[byobRequest]] to null. aController->SetByobRequest(nullptr); // Step 6. Perform !ResetQueue(controller). ResetQueue(aController); // Step 7. Set controller.[[closeRequested]] and controller.[[started]] to // false. aController->SetCloseRequested(false); aController->SetStarted(false); // Step 8. Set controller.[[strategyHWM]] to highWaterMark. aController->SetStrategyHWM(aHighWaterMark); // Step 9. Set controller.[[pullAlgorithm]] to pullAlgorithm. // Step 10. Set controller.[[cancelAlgorithm]] to cancelAlgorithm. aController->SetAlgorithms(*aAlgorithms); // Step 11. Set controller.[[autoAllocateChunkSize]] to autoAllocateChunkSize. aController->SetAutoAllocateChunkSize(aAutoAllocateChunkSize); // Step 12. Set controller.[[pendingPullIntos]] to a new empty list. aController->PendingPullIntos().clear(); // Step 13. Set stream.[[controller]] to controller. aStream->SetController(*aController); // Step 14. Let startResult be the result of performing startAlgorithm. JS::Rooted startResult(aCx, JS::UndefinedValue()); RefPtr controller = aController; aAlgorithms->StartCallback(aCx, *controller, &startResult, aRv); if (aRv.Failed()) { return; } // Let startPromise be a promise resolved with startResult. RefPtr startPromise = Promise::Create(GetIncumbentGlobal(), aRv); if (aRv.Failed()) { return; } startPromise->MaybeResolve(startResult); // Step 16+17 startPromise->AddCallbacksWithCycleCollectedArgs( [](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, ReadableByteStreamController* aController) MOZ_CAN_RUN_SCRIPT_BOUNDARY { MOZ_ASSERT(aController); // Step 16.1 aController->SetStarted(true); // Step 16.2 aController->SetPulling(false); // Step 16.3 aController->SetPullAgain(false); // Step 16.4: ReadableByteStreamControllerCallPullIfNeeded( aCx, MOZ_KnownLive(aController), aRv); }, [](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, ReadableByteStreamController* aController) { // Step 17.1 ReadableByteStreamControllerError(aController, aValue, aRv); }, RefPtr(aController)); } // https://streams.spec.whatwg.org/#set-up-readable-byte-stream-controller-from-underlying-source void SetUpReadableByteStreamControllerFromUnderlyingSource( JSContext* aCx, ReadableStream* aStream, JS::Handle aUnderlyingSource, UnderlyingSource& aUnderlyingSourceDict, double aHighWaterMark, ErrorResult& aRv) { // Step 1. Let controller be a new ReadableByteStreamController. auto controller = MakeRefPtr(aStream->GetParentObject()); // Step 2 - 7 auto algorithms = MakeRefPtr( aStream->GetParentObject(), aUnderlyingSource, aUnderlyingSourceDict); // Step 8. Let autoAllocateChunkSize be // underlyingSourceDict["autoAllocateChunkSize"], if it exists, or undefined // otherwise. Maybe autoAllocateChunkSize = mozilla::Nothing(); if (aUnderlyingSourceDict.mAutoAllocateChunkSize.WasPassed()) { uint64_t value = aUnderlyingSourceDict.mAutoAllocateChunkSize.Value(); // Step 9. If autoAllocateChunkSize is 0, then throw a TypeError // exception. if (value == 0) { aRv.ThrowTypeError("autoAllocateChunkSize can not be zero."); return; } autoAllocateChunkSize = mozilla::Some(value); } // Step 10. Perform ? SetUpReadableByteStreamController(stream, controller, // startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark, // autoAllocateChunkSize). SetUpReadableByteStreamController(aCx, aStream, controller, algorithms, aHighWaterMark, autoAllocateChunkSize, aRv); } } // namespace mozilla::dom