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,
nsTArray<RefPtr<PullIntoDescriptor>>& aFilledPullIntos, ErrorResult& aRv) {
// Step 1. Assert: controller.[[closeRequested]] is false.
MOZ_ASSERT(!aController->CloseRequested());
// Step 2. Let filledPullIntos be a new empty list.
MOZ_ASSERT(aFilledPullIntos.IsEmpty());
// Step 3. While controller.[[pendingPullIntos]] is not empty,
while (!aController->PendingPullIntos().isEmpty()) {
// Step 3.1. If controller.[[queueTotalSize]] is 0, then break.
if (aController->QueueTotalSize() == 0) {
break;
}
// Step 3.2. Let pullIntoDescriptor be controller.[[pendingPullIntos]][0].
RefPtr<PullIntoDescriptor> pullIntoDescriptor =
aController->PendingPullIntos().getFirst();
// Step 3.3. If
// !ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
// pullIntoDescriptor) is true,
bool ready = ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(
aCx, aController, pullIntoDescriptor, aRv);
if (aRv.Failed()) {
return;
}
if (ready) {
// Step 3.3.1. Perform
// !ReadableByteStreamControllerShiftPendingPullInto(controller).
RefPtr<PullIntoDescriptor> discardedPullIntoDescriptor =
ReadableByteStreamControllerShiftPendingPullInto(aController);
// Step 3.3.2. Append pullIntoDescriptor to filledPullIntos.
aFilledPullIntos.AppendElement(pullIntoDescriptor);
}
}
// Step 4: Return filledPullIntos. (Implicit)
}
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);