Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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 <algorithm> // std::min
namespace mozilla::dom {
using namespace streams_abstract;
struct ReadableByteStreamQueueEntry
: LinkedListElement<RefPtr<ReadableByteStreamQueueEntry>> {
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(
ReadableByteStreamQueueEntry)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(
ReadableByteStreamQueueEntry)
ReadableByteStreamQueueEntry(JS::Handle<JSObject*> aBuffer,
size_t aByteOffset, size_t aByteLength)
: mBuffer(aBuffer), mByteOffset(aByteOffset), mByteLength(aByteLength) {
mozilla::HoldJSObjects(this);
}
JSObject* Buffer() const { return mBuffer; }
void SetBuffer(JS::Handle<JSObject*> aBuffer) { mBuffer = aBuffer; }
size_t ByteOffset() const { return mByteOffset; }
void SetByteOffset(size_t aByteOffset) { mByteOffset = aByteOffset; }
size_t ByteLength() const { return mByteLength; }
void SetByteLength(size_t aByteLength) { mByteLength = aByteLength; }
private:
// An ArrayBuffer, which will be a transferred version of the one originally
// supplied by the underlying byte source.
JS::Heap<JSObject*> mBuffer;
// A nonnegative integer number giving the byte offset derived from the view
// originally supplied by the underlying byte source
size_t mByteOffset = 0;
// A nonnegative integer number giving the byte length derived from the view
// originally supplied by the underlying byte source
size_t mByteLength = 0;
~ReadableByteStreamQueueEntry() { mozilla::DropJSObjects(this); }
};
NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(ReadableByteStreamQueueEntry, (),
(mBuffer));
struct PullIntoDescriptor final
: LinkedListElement<RefPtr<PullIntoDescriptor>> {
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PullIntoDescriptor)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PullIntoDescriptor)
enum Constructor {
DataView,
#define DEFINE_TYPED_CONSTRUCTOR_ENUM_NAMES(ExternalT, NativeT, Name) Name,
JS_FOR_EACH_TYPED_ARRAY(DEFINE_TYPED_CONSTRUCTOR_ENUM_NAMES)
#undef DEFINE_TYPED_CONSTRUCTOR_ENUM_NAMES
};
static Constructor constructorFromScalar(JS::Scalar::Type type) {
switch (type) {
#define REMAP_PULL_INTO_DESCRIPTOR_TYPE(ExternalT, NativeT, Name) \
case JS::Scalar::Name: \
return Constructor::Name;
JS_FOR_EACH_TYPED_ARRAY(REMAP_PULL_INTO_DESCRIPTOR_TYPE)
#undef REMAP
case JS::Scalar::Int64:
case JS::Scalar::Simd128:
case JS::Scalar::MaxTypedArrayViewType:
break;
}
MOZ_CRASH("Unexpected Scalar::Type");
}
PullIntoDescriptor(JS::Handle<JSObject*> aBuffer, uint64_t aBufferByteLength,
uint64_t aByteOffset, uint64_t aByteLength,
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) {
mozilla::HoldJSObjects(this);
}
JSObject* Buffer() const { return mBuffer; }
void SetBuffer(JS::Handle<JSObject*> aBuffer) { mBuffer = aBuffer; }
uint64_t BufferByteLength() const { return mBufferByteLength; }
void SetBufferByteLength(const uint64_t aBufferByteLength) {
mBufferByteLength = aBufferByteLength;
}
uint64_t ByteOffset() const { return mByteOffset; }
void SetByteOffset(const uint64_t aByteOffset) { mByteOffset = aByteOffset; }
uint64_t ByteLength() const { return mByteLength; }
void SetByteLength(const uint64_t aByteLength) { mByteLength = aByteLength; }
uint64_t BytesFilled() const { return mBytesFilled; }
void SetBytesFilled(const uint64_t aBytesFilled) {
mBytesFilled = aBytesFilled;
}
uint64_t MinimumFill() const { return mMinimumFill; }
uint64_t ElementSize() const { return mElementSize; }
void SetElementSize(const uint64_t aElementSize) {
mElementSize = aElementSize;
}
Constructor ViewConstructor() const { return mViewConstructor; }
// Note: Named GetReaderType to avoid name conflict with type.
ReaderType GetReaderType() const { return mReaderType; }
void SetReaderType(const ReaderType aReaderType) {
mReaderType = aReaderType;
}
private:
JS::Heap<JSObject*> mBuffer;
uint64_t mBufferByteLength = 0;
uint64_t mByteOffset = 0;
uint64_t mByteLength = 0;
uint64_t mBytesFilled = 0;
uint64_t mMinimumFill = 0;
uint64_t mElementSize = 0;
Constructor mViewConstructor;
ReaderType mReaderType;
~PullIntoDescriptor() { mozilla::DropJSObjects(this); }
};
NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(PullIntoDescriptor, (), (mBuffer));
NS_IMPL_CYCLE_COLLECTION_CLASS(ReadableByteStreamController)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ReadableByteStreamController,
ReadableStreamController)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mByobRequest, mQueue, mPendingPullIntos)
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, mQueue, mPendingPullIntos)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ReadableByteStreamController,
ReadableStreamController)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
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) {}
ReadableByteStreamController::~ReadableByteStreamController() = default;
void ReadableByteStreamController::ClearQueue() { mQueue.clear(); }
void ReadableByteStreamController::ClearPendingPullIntos() {
mPendingPullIntos.clear();
}
namespace streams_abstract {
already_AddRefed<ReadableStreamBYOBRequest>
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<JSObject*> buffer(aCx, firstDescriptor->Buffer());
JS::Rooted<JSObject*> 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<ReadableStreamBYOBRequest> 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<ReadableStreamBYOBRequest> request(aController->GetByobRequest());
return request.forget();
}
} // namespace streams_abstract
already_AddRefed<ReadableStreamBYOBRequest>
ReadableByteStreamController::GetByobRequest(JSContext* aCx, ErrorResult& aRv) {
return ReadableByteStreamControllerGetBYOBRequest(aCx, this, aRv);
}
Nullable<double> 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<double> ReadableByteStreamController::GetDesiredSize() const {
// Step 1.
return ReadableByteStreamControllerGetDesiredSize(this);
}
JSObject* ReadableByteStreamController::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return ReadableByteStreamController_Binding::Wrap(aCx, this, aGivenProto);
}
namespace streams_abstract {
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);
}
void ReadableByteStreamControllerClearPendingPullIntos(
ReadableByteStreamController* aController) {
// Step 1.
ReadableByteStreamControllerInvalidateBYOBRequest(aController);
// Step 2.
aController->ClearPendingPullIntos();
}
void ResetQueue(ReadableByteStreamController* aContainer) {
// Step 1. Implied by type.
// Step 2.
aContainer->ClearQueue();
// Step 3.
aContainer->SetQueueTotalSize(0);
}
void ReadableByteStreamControllerClearAlgorithms(
ReadableByteStreamController* aController) {
// Step 1. Set controller.[[pullAlgorithm]] to undefined.
// Step 2. Set controller.[[cancelAlgorithm]] to undefined.
aController->ClearAlgorithms();
}
void ReadableByteStreamControllerError(
ReadableByteStreamController* aController, JS::Handle<JS::Value> 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);
}
void ReadableByteStreamControllerClose(
JSContext* aCx, ReadableByteStreamController* aController,
ErrorResult& aRv) {
// Step 1.
RefPtr<ReadableStream> 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. Let firstPendingPullInto be controller.[[pendingPullIntos]][0].
PullIntoDescriptor* firstPendingPullInto =
aController->PendingPullIntos().getFirst();
// 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");
JS::Rooted<JS::Value> 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);
}
} // namespace streams_abstract
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);
}
namespace streams_abstract {
void ReadableByteStreamControllerEnqueueChunkToQueue(
ReadableByteStreamController* aController,
JS::Handle<JSObject*> aTransferredBuffer, size_t aByteOffset,
size_t aByteLength) {
// Step 1.
RefPtr<ReadableByteStreamQueueEntry> queueEntry =
new ReadableByteStreamQueueEntry(aTransferredBuffer, aByteOffset,
aByteLength);
aController->Queue().insertBack(queueEntry);
// Step 2.
aController->AddToQueueTotalSize(double(aByteLength));
}
void ReadableByteStreamControllerEnqueueClonedChunkToQueue(
JSContext* aCx, ReadableByteStreamController* aController,
JS::Handle<JSObject*> aBuffer, size_t aByteOffset, size_t aByteLength,
ErrorResult& aRv) {
// Step 1. Let cloneResult be CloneArrayBuffer(buffer, byteOffset, byteLength,
// %ArrayBuffer%).
aRv.MightThrowJSException();
JS::Rooted<JSObject*> cloneResult(
aCx, JS::ArrayBufferClone(aCx, aBuffer, aByteOffset, aByteLength));
// Step 2. If cloneResult is an abrupt completion,
if (!cloneResult) {
JS::Rooted<JS::Value> 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<PullIntoDescriptor>
ReadableByteStreamControllerShiftPendingPullInto(
ReadableByteStreamController* aController);
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<JSObject*> buffer(aCx, aPullIntoDescriptor->Buffer());
ReadableByteStreamControllerEnqueueClonedChunkToQueue(
aCx, aController, buffer, aPullIntoDescriptor->ByteOffset(),
aPullIntoDescriptor->BytesFilled(), aRv);
if (aRv.Failed()) {
return;
}
}
// Step 3. Perform !
// ReadableByteStreamControllerShiftPendingPullInto(controller).
RefPtr<PullIntoDescriptor> discarded =
ReadableByteStreamControllerShiftPendingPullInto(aController);
(void)discarded;
}
static size_t ReadableStreamGetNumReadIntoRequests(ReadableStream* aStream) {
// Step 1.
MOZ_ASSERT(ReadableStreamHasBYOBReader(aStream));
// Step 2.
return aStream->GetReader()->AsBYOB()->ReadIntoRequests().length();
}
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<double> 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;
}
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<ReadableStreamController> controller(aController);
RefPtr<UnderlyingSourceAlgorithmsBase> algorithms =
aController->GetAlgorithms();
RefPtr<Promise> pullPromise = algorithms->PullCallback(aCx, *controller, aRv);
if (aRv.Failed()) {
return;
}
// Steps 7+8
pullPromise->AddCallbacksWithCycleCollectedArgs(
[](JSContext* aCx, JS::Handle<JS::Value> 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<JS::Value> 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);
MOZ_CAN_RUN_SCRIPT
void ReadableStreamFulfillReadIntoRequest(JSContext* aCx,
ReadableStream* aStream,
JS::Handle<JS::Value> 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> 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);
}
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: 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;
}
// Step 5. Let filledView be !
// ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
JS::Rooted<JSObject*> filledView(
aCx, ReadableByteStreamControllerConvertPullIntoDescriptor(
aCx, pullIntoDescriptor, aRv));
if (aRv.Failed()) {
return;
}
JS::Rooted<JS::Value> 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);
}
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> 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<PullIntoDescriptor> discardedPullIntoDescriptor =
ReadableByteStreamControllerShiftPendingPullInto(aController);
// Step 2.3.2. Perform
// !ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]],
// pullIntoDescriptor).
RefPtr<ReadableStream> 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<ReadableByteStreamQueueEntry> 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<JSObject*> buffer(aCx, entry->Buffer());
JS::Rooted<JSObject*> 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<JS::Value> 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<ReadableStreamDefaultReader> 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> readRequest = reader->ReadRequests().popFirst();
// Step 3.4. Perform !
// ReadableByteStreamControllerFillReadRequestFromQueue(controller,
// readRequest).
ReadableByteStreamControllerFillReadRequestFromQueue(aCx, aController,
readRequest, aRv);
if (aRv.Failed()) {
return;
}
}
}
void ReadableByteStreamControllerEnqueue(
JSContext* aCx, ReadableByteStreamController* aController,
JS::Handle<JSObject*> aChunk, ErrorResult& aRv) {
aRv.MightThrowJSException();
// Step 1.
RefPtr<ReadableStream> stream = aController->Stream();
// Step 2.
if (aController->CloseRequested() ||
stream->State() != ReadableStream::ReaderState::Readable) {
return;
}
// Step 3.
bool isShared;
JS::Rooted<JSObject*> 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("Detached Array Buffer");
return;
}
// Step 7.
JS::Rooted<JSObject*> transferredBuffer(aCx,
TransferArrayBuffer(aCx, buffer));
if (!transferredBuffer) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
// Step 8.
if (!aController->PendingPullIntos().isEmpty()) {
// Step 8.1
RefPtr<PullIntoDescriptor> firstPendingPullInto =
aController->PendingPullIntos().getFirst();
// Step 8.2
JS::Rooted<JSObject*> 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> pullIntoDescriptor =
ReadableByteStreamControllerShiftPendingPullInto(aController);
(void)pullIntoDescriptor;
}
// Step 9.3.3. Let transferredView be ! Construct(%Uint8Array%, «
// transferredBuffer, byteOffset, byteLength »).
JS::Rooted<JSObject*> 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<JS::Value> 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);
}
} // namespace streams_abstract
void ReadableByteStreamController::Enqueue(JSContext* aCx,
const ArrayBufferView& aChunk,
ErrorResult& aRv) {
// Step 1.
JS::Rooted<JSObject*> chunk(aCx, aChunk.Obj());
if (JS_GetArrayBufferViewByteLength(chunk) == 0) {
aRv.ThrowTypeError("Zero Length View");
return;
}
// Step 2.
bool isShared;
JS::Rooted<JSObject*> 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);
}
void ReadableByteStreamController::Error(JSContext* aCx,
JS::Handle<JS::Value> aErrorValue,
ErrorResult& aRv) {
// Step 1.
ReadableByteStreamControllerError(this, aErrorValue, aRv);
}
already_AddRefed<Promise> ReadableByteStreamController::CancelSteps(
JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
// Step 1.
ReadableByteStreamControllerClearPendingPullIntos(this);
// Step 2.
ResetQueue(this);
// Step 3.
Optional<JS::Handle<JS::Value>> reason(aCx, aReason);
RefPtr<UnderlyingSourceAlgorithmsBase> algorithms = mAlgorithms;
RefPtr<Promise> result = algorithms->CancelCallback(aCx, reason, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Step 4.
ReadableByteStreamControllerClearAlgorithms(this);
// Step 5.
return result.forget();
}
namespace streams_abstract {
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<ReadableStream> stream = aController->Stream();
ReadableStreamClose(aCx, stream, aRv);
return;
}
// Step 3.1
ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv);
}
} // namespace streams_abstract
void ReadableByteStreamController::PullSteps(JSContext* aCx,
ReadRequest* aReadRequest,
ErrorResult& aRv) {
// Step 1. Let stream be this.[[stream]].
ReadableStream* stream = Stream();
// Step 2. Assert: ! ReadableStreamHasDefaultReader(stream) is true.
MOZ_ASSERT(ReadableStreamHasDefaultReader(stream));
// Step 3. If this.[[queueTotalSize]] > 0,
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. Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]].
Maybe<uint64_t> autoAllocateChunkSize = AutoAllocateChunkSize();
// Step 5. If autoAllocateChunkSize is not undefined,
if (autoAllocateChunkSize) {
// Step 5.1. Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize
// »).
aRv.MightThrowJSException();
JS::Rooted<JSObject*> buffer(
aCx, JS::NewArrayBuffer(aCx, *autoAllocateChunkSize));
// Step 5.2. If buffer is an abrupt completion,
if (!buffer) {
// Step 5.2.1. Perform readRequest’s error steps, given buffer.[[Value]].
JS::Rooted<JS::Value> bufferError(aCx);
if (!JS_GetPendingException(aCx, &bufferError)) {
// Uncatchable exception; we should mark aRv and return.
aRv.StealExceptionFromJSContext(aCx);
return;
}
// 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. Return.
return;
}
// 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> pullIntoDescriptor = new PullIntoDescriptor(
buffer, /* aBufferByteLength */ *autoAllocateChunkSize,
/*aByteOffset */ 0, /* aByteLength */ *autoAllocateChunkSize,
/* aBytesFilled */ 0, /* aMinimumFill */ 1, /* aElementSize */ 1,
PullIntoDescriptor::Constructor::Uint8, ReaderType::Default);
// Step 5.4. Append pullIntoDescriptor to this.[[pendingPullIntos]].
PendingPullIntos().insertBack(pullIntoDescriptor);
}
// Step 6. Perform ! ReadableStreamAddReadRequest(stream, readRequest).
ReadableStreamAddReadRequest(stream, aReadRequest);
// Step 7. Perform ! ReadableByteStreamControllerCallPullIfNeeded(this).
ReadableByteStreamControllerCallPullIfNeeded(aCx, this, aRv);
}
void ReadableByteStreamController::ReleaseSteps() {
// Step 1. If this.[[pendingPullIntos]] is not empty,
if (!PendingPullIntos().isEmpty()) {
// Step 1.1. Let firstPendingPullInto be this.[[pendingPullIntos]][0].
RefPtr<PullIntoDescriptor> 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);
}
}
namespace streams_abstract {
already_AddRefed<PullIntoDescriptor>
ReadableByteStreamControllerShiftPendingPullInto(
ReadableByteStreamController* aController) {
// Step 1.
MOZ_ASSERT(!aController->GetByobRequest());
// Step 2 + 3
RefPtr<PullIntoDescriptor> descriptor =
aController->PendingPullIntos().popFirst();
// Step 4.
return descriptor.forget();
}
JSObject* ConstructFromPullIntoConstructor(
JSContext* aCx, PullIntoDescriptor::Constructor constructor,
JS::Handle<JSObject*> 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;
}
}
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: the remainder after dividing bytesFilled by elementSize is
// 0.
MOZ_ASSERT(bytesFilled % elementSize == 0);
// Step 5. Let buffer be ! TransferArrayBuffer(pullIntoDescriptor’s buffer).
aRv.MightThrowJSException();
JS::Rooted<JSObject*> srcBuffer(aCx, pullIntoDescriptor->Buffer());
JS::Rooted<JSObject*> 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<JSObject*> res(
aCx, ConstructFromPullIntoConstructor(
aCx, pullIntoDescriptor->ViewConstructor(), buffer,
pullIntoDescriptor->ByteOffset(), bytesFilled / elementSize));
if (!res) {
aRv.StealExceptionFromJSContext(aCx);
return nullptr;
}
return res;
}
MOZ_CAN_RUN_SCRIPT
static void ReadableByteStreamControllerRespondInClosedState(
JSContext* aCx, ReadableByteStreamController* aController,
RefPtr<PullIntoDescriptor>& aFirstDescriptor, ErrorResult& aRv) {
// 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).
if (aFirstDescriptor->GetReaderType() == ReaderType::None) {
RefPtr<PullIntoDescriptor> discarded =
ReadableByteStreamControllerShiftPendingPullInto(aController);
(void)discarded;
}
// Step 3. Let stream be controller.[[stream]].
RefPtr<ReadableStream> 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> pullIntoDescriptor =
ReadableByteStreamControllerShiftPendingPullInto(aController);
// Step 4.1.2. Perform !
// ReadableByteStreamControllerCommitPullIntoDescriptor(stream,
// pullIntoDescriptor).
ReadableByteStreamControllerCommitPullIntoDescriptor(
aCx, stream, pullIntoDescriptor, aRv);
}
}
}
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);
}
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 minimum
// fill, return.
if (aPullIntoDescriptor->BytesFilled() < aPullIntoDescriptor->MinimumFill()) {
return;
}
// Step 5. Perform
// !ReadableByteStreamControllerShiftPendingPullInto(controller).
RefPtr<PullIntoDescriptor> pullIntoDescriptor =
ReadableByteStreamControllerShiftPendingPullInto(aController);
(void)pullIntoDescriptor;
// 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();
// 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<JSObject*> 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<ReadableStream> stream(aController->Stream());
ReadableByteStreamControllerCommitPullIntoDescriptor(
aCx, stream, aPullIntoDescriptor, aRv);
if (aRv.Failed()) {
return;
}
// Step 10. Perform
// !ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
aCx, aController, aRv);
}
void ReadableByteStreamControllerRespondInternal(
JSContext* aCx, ReadableByteStreamController* aController,
uint64_t aBytesWritten, ErrorResult& aRv) {
// Step 1.
RefPtr<PullIntoDescriptor> firstDescriptor =
aController->PendingPullIntos().getFirst();
// Step 2.
JS::Rooted<JSObject*> 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);
}
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<JSObject*> buffer(aCx, firstDescriptor->Buffer());
JS::Rooted<JSObject*> transferredBuffer(aCx,
TransferArrayBuffer(aCx, buffer));
if (!transferredBuffer) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
firstDescriptor->SetBuffer(transferredBuffer);
// Step 7.
ReadableByteStreamControllerRespondInternal(aCx, aController, aBytesWritten,
aRv);
}
void ReadableByteStreamControllerRespondWithNewView(
JSContext* aCx, ReadableByteStreamController* aController,
JS::Handle<JSObject*> aView, ErrorResult& aRv) {
aRv.MightThrowJSException();
// Step 1.
MOZ_ASSERT(!aController->PendingPullIntos().isEmpty());
// Step 2.
bool isSharedMemory;
JS::Rooted<JSObject*> viewedArrayBuffer(
aCx, JS_GetArrayBufferViewBuffer(aCx, aView, &isSharedMemory));
if (!viewedArrayBuffer) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
MOZ_ASSERT(!JS::IsDetachedArrayBufferObject(viewedArrayBuffer));
// Step 3.
RefPtr<PullIntoDescriptor> 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<JSObject*> 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);
}
bool ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(
JSContext* aCx, ReadableByteStreamController* aController,
PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv) {
// Step 1. Let maxBytesToCopy be min(controller.[[queueTotalSize]],
// pullIntoDescriptor’s byte length − pullIntoDescriptor’s bytes filled).
size_t maxBytesToCopy =
std::min(static_cast<size_t>(aController->QueueTotalSize()),
static_cast<size_t>((aPullIntoDescriptor->ByteLength() -
aPullIntoDescriptor->BytesFilled())));
// Step 2. Let maxBytesFilled be pullIntoDescriptor’s bytes filled +
// maxBytesToCopy.
size_t maxBytesFilled = aPullIntoDescriptor->BytesFilled() + maxBytesToCopy;
// Step 3. Let totalBytesToCopyRemaining be maxBytesToCopy.
size_t totalBytesToCopyRemaining = maxBytesToCopy;
// Step 4. Let ready be false.
bool ready = false;
// 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 =
maxAlignedBytes - aPullIntoDescriptor->BytesFilled();
// Step 8.2. Set ready to true.
ready = true;
}
// Step 9. Let queue be controller.[[queue]].
LinkedList<RefPtr<ReadableByteStreamQueueEntry>>& 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<JSObject*> descriptorBuffer(aCx, aPullIntoDescriptor->Buffer());
JS::Rooted<JSObject*> 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 minimum fill.
MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() <
aPullIntoDescriptor->MinimumFill());
}
// Step 12. Return ready.
return ready;
}
void ReadableByteStreamControllerPullInto(
JSContext* aCx, ReadableByteStreamController* aController,
JS::Handle<JSObject*> aView, uint64_t aMin,
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 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 9. Let byteLength be view.[[ByteLength]].
size_t byteLength = JS_GetArrayBufferViewByteLength(aView);
// Step 10. Let bufferResult be
// TransferArrayBuffer(view.[[ViewedArrayBuffer]]).
bool isShared;
JS::Rooted<JSObject*> viewedArrayBuffer(
aCx, JS_GetArrayBufferViewBuffer(aCx, aView, &isShared));
if (!viewedArrayBuffer) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
JS::Rooted<JSObject*> bufferResult(
aCx, TransferArrayBuffer(aCx, viewedArrayBuffer));
// Step 11. If bufferResult is an abrupt completion,
if (!bufferResult) {
JS::Rooted<JS::Value> 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 11.1. Perform readIntoRequest’s error steps, given
// bufferResult.[[Value]].
aReadIntoRequest->ErrorSteps(aCx, pendingException, aRv);
// Step 11.2. Return.
return;
}
// Step 12. Let buffer be bufferResult.[[Value]].
JS::Rooted<JSObject*> buffer(aCx, bufferResult);
// 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> pullIntoDescriptor = new PullIntoDescriptor(
buffer, JS::GetArrayBufferByteLength(buffer), byteOffset, byteLength, 0,
minimumFill, elementSize, ctor, ReaderType::BYOB);
// Step 14. If controller.[[pendingPullIntos]] is not empty,
if (!aController->PendingPullIntos().isEmpty()) {
// Step 14.1. Append pullIntoDescriptor to controller.[[pendingPullIntos]].
aController->PendingPullIntos().insertBack(pullIntoDescriptor);
// Step 14.2. Perform !ReadableStreamAddReadIntoRequest(stream,
// readIntoRequest).
ReadableStreamAddReadIntoRequest(stream, aReadIntoRequest);
// Step 14.3. Return.
return;
}
// Step 15. If stream.[[state]] is "closed",
if (stream->State() == ReadableStream::ReaderState::Closed) {
// Step 15.1. Let emptyView be !Construct(ctor, « pullIntoDescriptor’s
// buffer, pullIntoDescriptor’s byte offset, 0 »).
JS::Rooted<JSObject*> pullIntoBuffer(aCx, pullIntoDescriptor->Buffer());
JS::Rooted<JSObject*> emptyView(
aCx,
ConstructFromPullIntoConstructor(aCx, ctor, pullIntoBuffer,
pullIntoDescriptor->ByteOffset(), 0));
if (!emptyView) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
// Step 15.2. Perform readIntoRequest’s close steps, given emptyView.
JS::Rooted<JS::Value> emptyViewValue(aCx, JS::ObjectValue(*emptyView));
aReadIntoRequest->CloseSteps(aCx, emptyViewValue, aRv);
// Step 15.3. Return.
return;
}
// Step 16. If controller.[[queueTotalSize]] > 0,
if (aController->QueueTotalSize() > 0) {
// Step 16.1 If
// !ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
// pullIntoDescriptor) is true,
bool ready = ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(
aCx, aController, pullIntoDescriptor, aRv);
if (aRv.Failed()) {
return;
}
if (ready) {
// Step 16.1.1 Let filledView be
// !ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
JS::Rooted<JSObject*> filledView(
aCx, ReadableByteStreamControllerConvertPullIntoDescriptor(
aCx, pullIntoDescriptor, aRv));
if (aRv.Failed()) {
return;
}
// Step 16.1.2. Perform
// !ReadableByteStreamControllerHandleQueueDrain(controller).
ReadableByteStreamControllerHandleQueueDrain(aCx, aController, aRv);
if (aRv.Failed()) {
return;
}
// Step 16.1.3. Perform readIntoRequest’s chunk steps, given filledView.
JS::Rooted<JS::Value> filledViewValue(aCx, JS::ObjectValue(*filledView));
aReadIntoRequest->ChunkSteps(aCx, filledViewValue, aRv);
// Step 16.1.4. Return.
return;
}
// Step 16.2 If controller.[[closeRequested]] is true,
if (aController->CloseRequested()) {
// Step 16.2.1. Let e be a TypeError exception.
ErrorResult typeError;
typeError.ThrowTypeError("Close Requested True during Pull Into");
JS::Rooted<JS::Value> e(aCx);
MOZ_RELEASE_ASSERT(ToJSValue(aCx, std::move(typeError), &e));
// Step 16.2.2. Perform !ReadableByteStreamControllerError(controller, e).
ReadableByteStreamControllerError(aController, e, aRv);
if (aRv.Failed()) {
return;
}
// Step 16.2.3. Perform readIntoRequest’s error steps, given e.
aReadIntoRequest->ErrorSteps(aCx, e, aRv);
// Step 16.2.4. Return.
return;
}
}
// Step 17. Append pullIntoDescriptor to controller.[[pendingPullIntos]].
aController->PendingPullIntos().insertBack(pullIntoDescriptor);
// Step 18. Perform !ReadableStreamAddReadIntoRequest(stream,
// readIntoRequest).
ReadableStreamAddReadIntoRequest(stream, aReadIntoRequest);
// Step 19. Perform !ReadableByteStreamControllerCallPullIfNeeded(controller).
ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv);
}
void SetUpReadableByteStreamController(
JSContext* aCx, ReadableStream* aStream,
ReadableByteStreamController* aController,
UnderlyingSourceAlgorithmsBase* aAlgorithms, double aHighWaterMark,
Maybe<uint64_t> 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<JS::Value> startResult(aCx, JS::UndefinedValue());
RefPtr<ReadableStreamController> controller = aController;
aAlgorithms->StartCallback(aCx, *controller, &startResult, aRv);
if (aRv.Failed()) {
return;
}
// Let startPromise be a promise resolved with startResult.
RefPtr<Promise> startPromise =
Promise::CreateInfallible(aStream->GetParentObject());
startPromise->MaybeResolve(startResult);
// Step 16+17
startPromise->AddCallbacksWithCycleCollectedArgs(
[](JSContext* aCx, JS::Handle<JS::Value> 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<JS::Value> aValue, ErrorResult& aRv,
ReadableByteStreamController* aController) {
// Step 17.1
ReadableByteStreamControllerError(aController, aValue, aRv);
},
RefPtr(aController));
}
void SetUpReadableByteStreamControllerFromUnderlyingSource(
JSContext* aCx, ReadableStream* aStream,
JS::Handle<JSObject*> aUnderlyingSource,
UnderlyingSource& aUnderlyingSourceDict, double aHighWaterMark,
ErrorResult& aRv) {
// Step 1. Let controller be a new ReadableByteStreamController.
auto controller =
MakeRefPtr<ReadableByteStreamController>(aStream->GetParentObject());
// Step 2 - 7
auto algorithms = MakeRefPtr<UnderlyingSourceAlgorithms>(
aStream->GetParentObject(), aUnderlyingSource, aUnderlyingSourceDict);
// Step 8. Let autoAllocateChunkSize be
// underlyingSourceDict["autoAllocateChunkSize"], if it exists, or undefined
// otherwise.
Maybe<uint64_t> 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;
}
if constexpr (sizeof(size_t) == sizeof(uint32_t)) {
if (value > uint64_t(UINT32_MAX)) {
aRv.ThrowRangeError("autoAllocateChunkSize too large");
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 streams_abstract
} // namespace mozilla::dom