gecko-dev/dom/streams/ReadableByteStreamController.cpp
Kagami Sascha Rosylight e6535142a8 Bug 1957934 - Part 1: Rename ReadableStreamController to ReadableStreamControllerBase r=webidl,smaug
The name ReadableStreamController collides with the same-named Web IDL union type.

Differential Revision: https://phabricator.services.mozilla.com/D244385
2025-04-04 09:59:36 +00:00

2256 lines
80 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/ReadableByteStreamController.h"
#include "ReadIntoRequest.h"
#include "js/ArrayBuffer.h"
#include "js/ErrorReport.h"
#include "js/Exception.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "js/ValueArray.h"
#include "js/experimental/TypedData.h"
#include "js/friend/ErrorMessages.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Attributes.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/dom/ByteStreamHelpers.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/ReadableByteStreamControllerBinding.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/ReadableStreamBYOBReader.h"
#include "mozilla/dom/ReadableStreamBYOBRequest.h"
#include "mozilla/dom/ReadableStreamControllerBase.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;
// https://streams.spec.whatwg.org/#readable-byte-stream-queue-entry
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,
ReadableStreamControllerBase)
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,
ReadableStreamControllerBase)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mByobRequest, mQueue, mPendingPullIntos)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ReadableByteStreamController,
ReadableStreamControllerBase)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_ADDREF_INHERITED(ReadableByteStreamController,
ReadableStreamControllerBase)
NS_IMPL_RELEASE_INHERITED(ReadableByteStreamController,
ReadableStreamControllerBase)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableByteStreamController)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_END_INHERITING(ReadableStreamControllerBase)
ReadableByteStreamController::ReadableByteStreamController(
nsIGlobalObject* aGlobal)
: ReadableStreamControllerBase(aGlobal) {}
ReadableByteStreamController::~ReadableByteStreamController() = default;
void ReadableByteStreamController::ClearQueue() { mQueue.clear(); }
void ReadableByteStreamController::ClearPendingPullIntos() {
mPendingPullIntos.clear();
}
namespace streams_abstract {
// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollergetbyobrequest
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);
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-get-desired-size
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 {
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-invalidate-byob-request
static void ReadableByteStreamControllerInvalidateBYOBRequest(
ReadableByteStreamController* aController) {
// Step 1.
if (!aController->GetByobRequest()) {
return;
}
// Step 2.
aController->GetByobRequest()->SetController(nullptr);
// Step 3.
aController->GetByobRequest()->SetView(nullptr);
// Step 4.
aController->SetByobRequest(nullptr);
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-clear-pending-pull-intos
void ReadableByteStreamControllerClearPendingPullIntos(
ReadableByteStreamController* aController) {
// Step 1.
ReadableByteStreamControllerInvalidateBYOBRequest(aController);
// Step 2.
aController->ClearPendingPullIntos();
}
// https://streams.spec.whatwg.org/#reset-queue
void ResetQueue(ReadableByteStreamController* aContainer) {
// Step 1. Implied by type.
// Step 2.
aContainer->ClearQueue();
// Step 3.
aContainer->SetQueueTotalSize(0);
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-clear-algorithms
void ReadableByteStreamControllerClearAlgorithms(
ReadableByteStreamController* aController) {
// Step 1. Set controller.[[pullAlgorithm]] to undefined.
// Step 2. Set controller.[[cancelAlgorithm]] to undefined.
aController->ClearAlgorithms();
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-error
void ReadableByteStreamControllerError(
ReadableByteStreamController* aController, JS::Handle<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);
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-close
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
// https://streams.spec.whatwg.org/#rbs-controller-close
void ReadableByteStreamController::Close(JSContext* aCx, ErrorResult& aRv) {
// Step 1.
if (mCloseRequested) {
aRv.ThrowTypeError("Close already requested");
return;
}
// Step 2.
if (Stream()->State() != ReadableStream::ReaderState::Readable) {
aRv.ThrowTypeError("Closing un-readable stream controller");
return;
}
// Step 3.
ReadableByteStreamControllerClose(aCx, this, aRv);
}
namespace streams_abstract {
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-enqueue-chunk-to-queue
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));
}
// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollerenqueueclonedchunktoqueue
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);
// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollerenqueuedetachedpullintotoqueue
void ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(
JSContext* aCx, ReadableByteStreamController* aController,
PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv) {
// Step 1. Assert: pullIntoDescriptor’s reader type is "none".
MOZ_ASSERT(aPullIntoDescriptor->GetReaderType() == ReaderType::None);
// Step 2. If pullIntoDescriptor’s bytes filled > 0,
// perform ? ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller,
// pullIntoDescriptor’s buffer, pullIntoDescriptor’s byte offset,
// pullIntoDescriptor’s bytes filled).
if (aPullIntoDescriptor->BytesFilled() > 0) {
JS::Rooted<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;
}
// https://streams.spec.whatwg.org/#readable-stream-get-num-read-into-requests
static size_t ReadableStreamGetNumReadIntoRequests(ReadableStream* aStream) {
// Step 1.
MOZ_ASSERT(ReadableStreamHasBYOBReader(aStream));
// Step 2.
return aStream->GetReader()->AsBYOB()->ReadIntoRequests().length();
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-should-call-pull
bool ReadableByteStreamControllerShouldCallPull(
ReadableByteStreamController* aController) {
// Step 1. Let stream be controller.[[stream]].
ReadableStream* stream = aController->Stream();
// Step 2. If stream.[[state]] is not "readable", return false.
if (stream->State() != ReadableStream::ReaderState::Readable) {
return false;
}
// Step 3. If controller.[[closeRequested]] is true, return false.
if (aController->CloseRequested()) {
return false;
}
// Step 4. If controller.[[started]] is false, return false.
if (!aController->Started()) {
return false;
}
// Step 5. If ! ReadableStreamHasDefaultReader(stream) is true
// and ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
if (ReadableStreamHasDefaultReader(stream) &&
ReadableStreamGetNumReadRequests(stream) > 0) {
return true;
}
// Step 6. If ! ReadableStreamHasBYOBReader(stream) is true
// and ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true.
if (ReadableStreamHasBYOBReader(stream) &&
ReadableStreamGetNumReadIntoRequests(stream) > 0) {
return true;
}
// Step 7. Let desiredSize be
// ! ReadableByteStreamControllerGetDesiredSize(controller).
Nullable<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;
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-call-pull-if-needed
void ReadableByteStreamControllerCallPullIfNeeded(
JSContext* aCx, ReadableByteStreamController* aController,
ErrorResult& aRv) {
// Step 1.
bool shouldPull = ReadableByteStreamControllerShouldCallPull(aController);
// Step 2.
if (!shouldPull) {
return;
}
// Step 3.
if (aController->Pulling()) {
aController->SetPullAgain(true);
return;
}
// Step 4.
MOZ_ASSERT(!aController->PullAgain());
// Step 5.
aController->SetPulling(true);
// Step 6.
RefPtr<ReadableStreamControllerBase> 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);
// https://streams.spec.whatwg.org/#readable-stream-fulfill-read-into-request
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);
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-commit-pull-into-descriptor
MOZ_CAN_RUN_SCRIPT
void ReadableByteStreamControllerCommitPullIntoDescriptor(
JSContext* aCx, ReadableStream* aStream,
PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv) {
// Step 1. Assert: stream.[[state]] is not "errored".
MOZ_ASSERT(aStream->State() != ReadableStream::ReaderState::Errored);
// Step 2. Assert: pullIntoDescriptor.reader type is not "none".
MOZ_ASSERT(pullIntoDescriptor->GetReaderType() != ReaderType::None);
// Step 3. Let done be false.
bool done = false;
// Step 4. If stream.[[state]] is "closed",
if (aStream->State() == ReadableStream::ReaderState::Closed) {
// Step 4.1. Assert: 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);
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-process-pull-into-descriptors-using-queue
MOZ_CAN_RUN_SCRIPT
void ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
JSContext* aCx, ReadableByteStreamController* aController,
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;
}
}
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-enqueue
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. Let filledPullIntos be the result of performing !
// ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
nsTArray<RefPtr<PullIntoDescriptor>> filledPullIntos;
ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
aCx, aController, filledPullIntos, aRv);
if (aRv.Failed()) {
return;
}
// Step 10.3. For each filledPullInto of filledPullIntos,
for (auto& filledPullInto : filledPullIntos) {
// Step 10.3.1. Perform !
// ReadableByteStreamControllerCommitPullIntoDescriptor(stream,
// filledPullInto).
ReadableByteStreamControllerCommitPullIntoDescriptor(
aCx, stream, MOZ_KnownLive(filledPullInto), 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
// https://streams.spec.whatwg.org/#rbs-controller-enqueue
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);
}
// https://streams.spec.whatwg.org/#rbs-controller-error
void ReadableByteStreamController::Error(JSContext* aCx,
JS::Handle<JS::Value> aErrorValue,
ErrorResult& aRv) {
// Step 1.
ReadableByteStreamControllerError(this, aErrorValue, aRv);
}
// https://streams.spec.whatwg.org/#rbs-controller-private-cancel
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 {
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-handle-queue-drain
void ReadableByteStreamControllerHandleQueueDrain(
JSContext* aCx, ReadableByteStreamController* aController,
ErrorResult& aRv) {
// Step 1.
MOZ_ASSERT(aController->Stream()->State() ==
ReadableStream::ReaderState::Readable);
// Step 2.
if (aController->QueueTotalSize() == 0 && aController->CloseRequested()) {
// Step 2.1
ReadableByteStreamControllerClearAlgorithms(aController);
// Step 2.2
RefPtr<ReadableStream> stream = aController->Stream();
ReadableStreamClose(aCx, stream, aRv);
return;
}
// Step 3.1
ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv);
}
} // namespace streams_abstract
// https://streams.spec.whatwg.org/#rbs-controller-private-pull
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);
}
// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontroller-releasesteps
void ReadableByteStreamController::ReleaseSteps() {
// Step 1. If this.[[pendingPullIntos]] is not empty,
if (!PendingPullIntos().isEmpty()) {
// Step 1.1. Let firstPendingPullInto be this.[[pendingPullIntos]][0].
RefPtr<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 {
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-shift-pending-pull-into
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;
}
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-convert-pull-into-descriptor
JSObject* ReadableByteStreamControllerConvertPullIntoDescriptor(
JSContext* aCx, PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv) {
// Step 1. Let bytesFilled be pullIntoDescriptor’s bytes filled.
uint64_t bytesFilled = pullIntoDescriptor->BytesFilled();
// Step 2. Let elementSize be pullIntoDescriptor’s element size.
uint64_t elementSize = pullIntoDescriptor->ElementSize();
// Step 3. Assert: bytesFilled ≤ pullIntoDescriptor’s byte length.
MOZ_ASSERT(bytesFilled <= pullIntoDescriptor->ByteLength());
// Step 4. Assert: 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;
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-in-closed-state
MOZ_CAN_RUN_SCRIPT
static void ReadableByteStreamControllerRespondInClosedState(
JSContext* aCx, ReadableByteStreamController* aController,
RefPtr<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. Let filledPullIntos be a new empty list.
nsTArray<RefPtr<PullIntoDescriptor>> filledPullIntos;
// Step 4.2. While |filledPullInto|'s [=list/size=] < !
// ReadableStreamGetNumReadIntoRequests(stream),
while (filledPullIntos.Length() <
ReadableStreamGetNumReadIntoRequests(stream)) {
// Step 4.2.1. Let pullIntoDescriptor be !
// ReadableByteStreamControllerShiftPendingPullInto(controller).
RefPtr<PullIntoDescriptor> pullIntoDescriptor =
ReadableByteStreamControllerShiftPendingPullInto(aController);
// Step 4.2.2. Append pullIntoDescriptor to filledPullIntos.
filledPullIntos.AppendElement(pullIntoDescriptor);
}
// Step 4.3. For each filledPullInto of filledPullIntos,
for (auto& filledPullInto : filledPullIntos) {
// Step 4.3.1. Perform !
// ReadableByteStreamControllerCommitPullIntoDescriptor(stream,
// filledPullInto).
ReadableByteStreamControllerCommitPullIntoDescriptor(
aCx, stream, MOZ_KnownLive(filledPullInto), aRv);
if (aRv.Failed()) {
return;
}
}
}
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-fill-head-pull-into-descriptor
void ReadableByteStreamControllerFillHeadPullIntoDescriptor(
ReadableByteStreamController* aController, size_t aSize,
PullIntoDescriptor* aPullIntoDescriptor) {
// Step 1. Assert: either controller.[[pendingPullIntos]] is empty, or
// controller.[[pendingPullIntos]][0] is pullIntoDescriptor.
MOZ_ASSERT(aController->PendingPullIntos().isEmpty() ||
aController->PendingPullIntos().getFirst() == aPullIntoDescriptor);
// Step 2. Assert: controller.[[byobRequest]] is null.
MOZ_ASSERT(!aController->GetByobRequest());
// Step 3. Set pullIntoDescriptor’s bytes filled to bytes filled + size.
aPullIntoDescriptor->SetBytesFilled(aPullIntoDescriptor->BytesFilled() +
aSize);
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-in-readable-state
MOZ_CAN_RUN_SCRIPT
static void ReadableByteStreamControllerRespondInReadableState(
JSContext* aCx, ReadableByteStreamController* aController,
uint64_t aBytesWritten, PullIntoDescriptor* aPullIntoDescriptor,
ErrorResult& aRv) {
// Step 1. Assert: pullIntoDescriptor’s bytes filled + bytesWritten ≤
// pullIntoDescriptor’s byte length.
MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() + aBytesWritten <=
aPullIntoDescriptor->ByteLength());
// Step 2. Perform
// !ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
// bytesWritten, pullIntoDescriptor).
ReadableByteStreamControllerFillHeadPullIntoDescriptor(
aController, aBytesWritten, aPullIntoDescriptor);
// Step 3. If pullIntoDescriptor’s reader type is "none",
if (aPullIntoDescriptor->GetReaderType() == ReaderType::None) {
// Step 3.1. Perform ?
// ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller,
// pullIntoDescriptor).
ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(
aCx, aController, aPullIntoDescriptor, aRv);
if (aRv.Failed()) {
return;
}
// Step 3.2. Let filledPullIntos be the result of performing !
// ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
nsTArray<RefPtr<PullIntoDescriptor>> filledPullIntos;
ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
aCx, aController, filledPullIntos, aRv);
if (aRv.Failed()) {
return;
}
// Step 3.3. For each filledPullInto of filledPullIntos,
for (auto& filledPullInto : filledPullIntos) {
// Step 3.3.1. Perform !
// ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]],
// filledPullInto).
ReadableByteStreamControllerCommitPullIntoDescriptor(
aCx, MOZ_KnownLive(aController->Stream()),
MOZ_KnownLive(filledPullInto), aRv);
if (aRv.Failed()) {
return;
}
}
// Step 3.4. 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. Let filledPullIntos be the result of performing !
// ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
nsTArray<RefPtr<PullIntoDescriptor>> filledPullIntos;
ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
aCx, aController, filledPullIntos, aRv);
if (aRv.Failed()) {
return;
}
// Step 10. Perform
// !ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]],
// pullIntoDescriptor).
RefPtr<ReadableStream> stream(aController->Stream());
ReadableByteStreamControllerCommitPullIntoDescriptor(
aCx, stream, aPullIntoDescriptor, aRv);
if (aRv.Failed()) {
return;
}
// Step 11. For each filledPullInto of filledPullIntos,
for (auto& filledPullInto : filledPullIntos) {
// Step 11.1. Perform !
// ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]],
// filledPullInto).
ReadableByteStreamControllerCommitPullIntoDescriptor(
aCx, stream, MOZ_KnownLive(filledPullInto), aRv);
if (aRv.Failed()) {
return;
}
}
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-internal
void ReadableByteStreamControllerRespondInternal(
JSContext* aCx, ReadableByteStreamController* aController,
uint64_t aBytesWritten, ErrorResult& aRv) {
// Step 1.
RefPtr<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);
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond
void ReadableByteStreamControllerRespond(
JSContext* aCx, ReadableByteStreamController* aController,
uint64_t aBytesWritten, ErrorResult& aRv) {
// Step 1.
MOZ_ASSERT(!aController->PendingPullIntos().isEmpty());
// Step 2.
PullIntoDescriptor* firstDescriptor =
aController->PendingPullIntos().getFirst();
// Step 3.
auto state = aController->Stream()->State();
// Step 4.
if (state == ReadableStream::ReaderState::Closed) {
// Step 4.1
if (aBytesWritten != 0) {
aRv.ThrowTypeError("bytesWritten not zero on closed stream");
return;
}
} else {
// Step 5.1
MOZ_ASSERT(state == ReadableStream::ReaderState::Readable);
// Step 5.2
if (aBytesWritten == 0) {
aRv.ThrowTypeError("bytesWritten 0");
return;
}
// Step 5.3
if (firstDescriptor->BytesFilled() + aBytesWritten >
firstDescriptor->ByteLength()) {
aRv.ThrowRangeError("bytesFilled + bytesWritten > byteLength");
return;
}
}
// Step 6.
aRv.MightThrowJSException();
JS::Rooted<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);
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-with-new-view
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);
}
#ifdef DEBUG
// https://streams.spec.whatwg.org/#abstract-opdef-cancopydatablockbytes
bool CanCopyDataBlockBytes(JS::Handle<JSObject*> aToBuffer, size_t aToIndex,
JS::Handle<JSObject*> aFromBuffer, size_t aFromIndex,
size_t aCount) {
// Step 1. Assert: toBuffer is an Object.
// Step 2. Assert: toBuffer has an [[ArrayBufferData]] internal slot.
MOZ_ASSERT(JS::IsArrayBufferObject(aToBuffer));
// Step 3. Assert: fromBuffer is an Object.
// Step 4. Assert: fromBuffer has an [[ArrayBufferData]] internal slot.
MOZ_ASSERT(JS::IsArrayBufferObject(aFromBuffer));
// Step 5. If toBuffer is fromBuffer, return false.
// Note: JS API makes it safe to just compare the pointers.
if (aToBuffer == aFromBuffer) {
return false;
}
// Step 6. If ! IsDetachedBuffer(toBuffer) is true, return false.
if (JS::IsDetachedArrayBufferObject(aToBuffer)) {
return false;
}
// Step 7. If ! IsDetachedBuffer(fromBuffer) is true, return false.
if (JS::IsDetachedArrayBufferObject(aFromBuffer)) {
return false;
}
// Step 8. If toIndex + count > toBuffer.[[ArrayBufferByteLength]], return
// false.
if (aToIndex + aCount > JS::GetArrayBufferByteLength(aToBuffer)) {
return false;
}
// (Not in the spec) also check overflow.
if (aToIndex + aCount < aToIndex) {
return false;
}
// Step 9. If fromIndex + count > fromBuffer.[[ArrayBufferByteLength]], return
// false.
if (aFromIndex + aCount > JS::GetArrayBufferByteLength(aFromBuffer)) {
return false;
}
// (Not in the spec) also check overflow.
if (aFromIndex + aCount < aFromIndex) {
return false;
}
// Step 10. Return true.
return true;
}
#endif
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-fill-pull-into-descriptor-from-queue
bool ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(
JSContext* aCx, ReadableByteStreamController* aController,
PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv) {
// Step 1. Let 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: ! IsDetachedBuffer(pullIntoDescriptor ’s buffer) is false.
MOZ_ASSERT(!JS::IsDetachedArrayBufferObject(aPullIntoDescriptor->Buffer()));
// Step 6. Assert: pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s
// minimum fill.
MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() <
aPullIntoDescriptor->MinimumFill());
// Step 7. Let remainderBytes be the remainder after dividing maxBytesFilled
// by pullIntoDescriptor’s element size.
size_t remainderBytes = maxBytesFilled % aPullIntoDescriptor->ElementSize();
// Step 8. Let maxAlignedBytes be maxBytesFilled − remainderBytes.
size_t maxAlignedBytes = maxBytesFilled - remainderBytes;
// Step 9. If maxAlignedBytes ≥ pullIntoDescriptor’s minimum fill,
if (maxAlignedBytes >= aPullIntoDescriptor->MinimumFill()) {
// Step 9.1. Set totalBytesToCopyRemaining to maxAlignedBytes −
// pullIntoDescriptor’s bytes filled.
totalBytesToCopyRemaining =
maxAlignedBytes - aPullIntoDescriptor->BytesFilled();
// Step 9.2. Set ready to true.
ready = true;
}
// Step 10. Let queue be controller.[[queue]].
LinkedList<RefPtr<ReadableByteStreamQueueEntry>>& queue =
aController->Queue();
// Step 11. While totalBytesToCopyRemaining > 0,
while (totalBytesToCopyRemaining > 0) {
// Step 11.1 Let headOfQueue be queue[0].
ReadableByteStreamQueueEntry* headOfQueue = queue.getFirst();
// Step 11.2. Let bytesToCopy be min(totalBytesToCopyRemaining,
// headOfQueue’s byte length).
size_t bytesToCopy =
std::min(totalBytesToCopyRemaining, headOfQueue->ByteLength());
// Step 11.3. Let destStart be pullIntoDescriptor’s byte offset +
// pullIntoDescriptor’s bytes filled.
size_t destStart =
aPullIntoDescriptor->ByteOffset() + aPullIntoDescriptor->BytesFilled();
// Step 11.4. Let descriptorBuffer be pullIntoDescriptor’s buffer.
JS::Rooted<JSObject*> descriptorBuffer(aCx, aPullIntoDescriptor->Buffer());
// Step 11.5. Let queueBuffer be headOfQueue’s buffer.
JS::Rooted<JSObject*> queueBuffer(aCx, headOfQueue->Buffer());
// Step 11.6. Let queueByteOffset be headOfQueue’s byte offset.
size_t queueByteOffset = headOfQueue->ByteOffset();
// Step 11.7. Assert: ! CanCopyDataBlockBytes(descriptorBuffer, destStart,
// queueBuffer, queueByteOffset, bytesToCopy) is true.
MOZ_ASSERT(CanCopyDataBlockBytes(descriptorBuffer, destStart, queueBuffer,
queueByteOffset, bytesToCopy));
// Step 11.8. Perform !
// CopyDataBlockBytes(descriptorBuffer.[[ArrayBufferData]], destStart,
// queueBuffer.[[ArrayBufferData]], queueByteOffset, bytesToCopy).
if (!JS::ArrayBufferCopyData(aCx, descriptorBuffer, destStart, queueBuffer,
queueByteOffset, bytesToCopy)) {
aRv.StealExceptionFromJSContext(aCx);
return false;
}
// Step 11.9. If headOfQueue’s byte length is bytesToCopy,
if (headOfQueue->ByteLength() == bytesToCopy) {
// Step 11.9.1. Remove queue[0].
queue.popFirst();
} else {
// Step 11.10. Otherwise,
// Step 11.10.1 Set headOfQueue’s byte offset to
// headOfQueue’s byte offset + bytesToCopy.
headOfQueue->SetByteOffset(headOfQueue->ByteOffset() + bytesToCopy);
// Step 11.10.2 Set headOfQueue’s byte length to
// headOfQueue’s byte length − bytesToCopy.
headOfQueue->SetByteLength(headOfQueue->ByteLength() - bytesToCopy);
}
// Step 11.11. Set controller.[[queueTotalSize]] to
// controller.[[queueTotalSize]] − bytesToCopy.
aController->SetQueueTotalSize(aController->QueueTotalSize() -
(double)bytesToCopy);
// Step 11.12, Perform
// !ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
// bytesToCopy, pullIntoDescriptor).
ReadableByteStreamControllerFillHeadPullIntoDescriptor(
aController, bytesToCopy, aPullIntoDescriptor);
// Step 11.13. Set totalBytesToCopyRemaining to totalBytesToCopyRemaining −
// bytesToCopy.
totalBytesToCopyRemaining = totalBytesToCopyRemaining - bytesToCopy;
}
// Step 12. If ready is false,
if (!ready) {
// Step 12.1. Assert: controller.[[queueTotalSize]] is 0.
MOZ_ASSERT(aController->QueueTotalSize() == 0);
// Step 12.2. Assert: pullIntoDescriptor’s bytes filled > 0.
MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() > 0);
// Step 12.3. Assert: pullIntoDescriptor’s bytes filled <
// pullIntoDescriptor’s minimum fill.
MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() <
aPullIntoDescriptor->MinimumFill());
}
// Step 13. Return ready.
return ready;
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-pull-into
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);
}
// https://streams.spec.whatwg.org/#set-up-readable-byte-stream-controller
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<ReadableStreamControllerBase> 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));
}
// https://streams.spec.whatwg.org/#set-up-readable-byte-stream-controller-from-underlying-source
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