1
0
Fork 0
firefox/dom/streams/ReadableByteStreamController.cpp
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

2256 lines
80 KiB
C++
Raw Permalink 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 firstPendingPullIntos bytes
// filled by firstPendingPullIntos 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: pullIntoDescriptors reader type is "none".
MOZ_ASSERT(aPullIntoDescriptor->GetReaderType() == ReaderType::None);
// Step 2. If pullIntoDescriptors bytes filled > 0,
// perform ? ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller,
// pullIntoDescriptors buffer, pullIntoDescriptors byte offset,
// pullIntoDescriptors 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 readIntoRequests close steps, given
// chunk.
if (done) {
readIntoRequest->CloseSteps(aCx, aChunk, aRv);
return;
}
// Step 7. Otherwise, perform readIntoRequests 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 pullIntoDescriptors bytes
// filled by pullIntoDescriptors 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 pullIntoDescriptors 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: pullIntoDescriptors 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]]
// entrys 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%, « entrys buffer, entrys
// byte offset, entrys 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 readRequests 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 firstPendingPullIntos buffer to !
// TransferArrayBuffer(firstPendingPullIntos buffer).
pendingBuffer = TransferArrayBuffer(aCx, pendingBuffer);
if (!pendingBuffer) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
firstPendingPullInto->SetBuffer(pendingBuffer);
// Step 8.5. If firstPendingPullIntos 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 readRequests 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 firstPendingPullIntos 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 pullIntoDescriptors bytes filled.
uint64_t bytesFilled = pullIntoDescriptor->BytesFilled();
// Step 2. Let elementSize be pullIntoDescriptors element size.
uint64_t elementSize = pullIntoDescriptor->ElementSize();
// Step 3. Assert: bytesFilled ≤ pullIntoDescriptors 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(pullIntoDescriptors 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(pullIntoDescriptors view constructor,
// « buffer, pullIntoDescriptors 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 firstDescriptors bytes filled
// by firstDescriptors element size is 0.
MOZ_ASSERT(
(aFirstDescriptor->BytesFilled() % aFirstDescriptor->ElementSize()) == 0);
// Step 2. If firstDescriptors 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 pullIntoDescriptors 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: pullIntoDescriptors bytes filled + bytesWritten ≤
// pullIntoDescriptors byte length.
MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() + aBytesWritten <=
aPullIntoDescriptor->ByteLength());
// Step 2. Perform
// !ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
// bytesWritten, pullIntoDescriptor).
ReadableByteStreamControllerFillHeadPullIntoDescriptor(
aController, aBytesWritten, aPullIntoDescriptor);
// Step 3. If pullIntoDescriptors 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 pullIntoDescriptors bytes filled < pullIntoDescriptors 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
// pullIntoDescriptors bytes filled by pullIntoDescriptors element size.
size_t remainderSize =
aPullIntoDescriptor->BytesFilled() % aPullIntoDescriptor->ElementSize();
// Step 7. If remainderSize > 0,
if (remainderSize > 0) {
// Step 7.1. Let end be pullIntoDescriptors byte offset +
// pullIntoDescriptors bytes filled.
size_t end =
aPullIntoDescriptor->ByteOffset() + aPullIntoDescriptor->BytesFilled();
// Step 7.2. Perform ?
// ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller,
// pullIntoDescriptors 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 pullIntoDescriptors bytes filled to pullIntoDescriptors 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 firstDescriptors 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]],
// pullIntoDescriptors byte length pullIntoDescriptors 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 pullIntoDescriptors 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: pullIntoDescriptors bytes filled < pullIntoDescriptors
// minimum fill.
MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() <
aPullIntoDescriptor->MinimumFill());
// Step 7. Let remainderBytes be the remainder after dividing maxBytesFilled
// by pullIntoDescriptors element size.
size_t remainderBytes = maxBytesFilled % aPullIntoDescriptor->ElementSize();
// Step 8. Let maxAlignedBytes be maxBytesFilled remainderBytes.
size_t maxAlignedBytes = maxBytesFilled - remainderBytes;
// Step 9. If maxAlignedBytes ≥ pullIntoDescriptors minimum fill,
if (maxAlignedBytes >= aPullIntoDescriptor->MinimumFill()) {
// Step 9.1. Set totalBytesToCopyRemaining to maxAlignedBytes
// pullIntoDescriptors 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,
// headOfQueues byte length).
size_t bytesToCopy =
std::min(totalBytesToCopyRemaining, headOfQueue->ByteLength());
// Step 11.3. Let destStart be pullIntoDescriptors byte offset +
// pullIntoDescriptors bytes filled.
size_t destStart =
aPullIntoDescriptor->ByteOffset() + aPullIntoDescriptor->BytesFilled();
// Step 11.4. Let descriptorBuffer be pullIntoDescriptors buffer.
JS::Rooted<JSObject*> descriptorBuffer(aCx, aPullIntoDescriptor->Buffer());
// Step 11.5. Let queueBuffer be headOfQueues buffer.
JS::Rooted<JSObject*> queueBuffer(aCx, headOfQueue->Buffer());
// Step 11.6. Let queueByteOffset be headOfQueues 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 headOfQueues 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 headOfQueues byte offset to
// headOfQueues byte offset + bytesToCopy.
headOfQueue->SetByteOffset(headOfQueue->ByteOffset() + bytesToCopy);
// Step 11.10.2 Set headOfQueues byte length to
// headOfQueues 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: pullIntoDescriptors bytes filled > 0.
MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() > 0);
// Step 12.3. Assert: pullIntoDescriptors bytes filled <
// pullIntoDescriptors 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 readIntoRequests 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, « pullIntoDescriptors
// buffer, pullIntoDescriptors 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 readIntoRequests 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 readIntoRequests 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 readIntoRequests 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