Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/ReadableByteStreamController.h"
#include "ReadIntoRequest.h"
#include "js/ArrayBuffer.h"
#include "js/ErrorReport.h"
#include "js/Exception.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "js/ValueArray.h"
#include "js/experimental/TypedData.h"
#include "js/friend/ErrorMessages.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Attributes.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/dom/ByteStreamHelpers.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/Promise-inl.h"
#include "mozilla/dom/ReadableByteStreamControllerBinding.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/ReadableStreamBYOBReader.h"
#include "mozilla/dom/ReadableStreamBYOBRequest.h"
#include "mozilla/dom/ReadableStreamController.h"
#include "mozilla/dom/ReadableStreamDefaultController.h"
#include "mozilla/dom/ReadableStreamDefaultReader.h"
#include "mozilla/dom/ReadableStreamGenericReader.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIGlobalObject.h"
#include "nsISupports.h"
#include <algorithm> // std::min
namespace mozilla::dom {
using namespace streams_abstract;
struct ReadableByteStreamQueueEntry
: LinkedListElement<RefPtr<ReadableByteStreamQueueEntry>> {
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(
ReadableByteStreamQueueEntry)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(
ReadableByteStreamQueueEntry)
ReadableByteStreamQueueEntry(JS::Handle<JSObject*> aBuffer,
size_t aByteOffset, size_t aByteLength)
: mBuffer(aBuffer), mByteOffset(aByteOffset), mByteLength(aByteLength) {
mozilla::HoldJSObjects(this);
}
JSObject* Buffer() const { return mBuffer; }
void SetBuffer(JS::Handle<JSObject*> aBuffer) { mBuffer = aBuffer; }
size_t ByteOffset() const { return mByteOffset; }
void SetByteOffset(size_t aByteOffset) { mByteOffset = aByteOffset; }
size_t ByteLength() const { return mByteLength; }
void SetByteLength(size_t aByteLength) { mByteLength = aByteLength; }
private:
// An ArrayBuffer, which will be a transferred version of the one originally
// supplied by the underlying byte source.
JS::Heap<JSObject*> mBuffer;
// A nonnegative integer number giving the byte offset derived from the view
// originally supplied by the underlying byte source
size_t mByteOffset = 0;
// A nonnegative integer number giving the byte length derived from the view
// originally supplied by the underlying byte source
size_t mByteLength = 0;
~ReadableByteStreamQueueEntry() { mozilla::DropJSObjects(this); }
};
NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(ReadableByteStreamQueueEntry, (),
(mBuffer));
struct PullIntoDescriptor final
: LinkedListElement<RefPtr<PullIntoDescriptor>> {
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PullIntoDescriptor)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PullIntoDescriptor)
enum Constructor {
DataView,
#define DEFINE_TYPED_CONSTRUCTOR_ENUM_NAMES(ExternalT, NativeT, Name) Name,
JS_FOR_EACH_TYPED_ARRAY(DEFINE_TYPED_CONSTRUCTOR_ENUM_NAMES)
#undef DEFINE_TYPED_CONSTRUCTOR_ENUM_NAMES
};
static Constructor constructorFromScalar(JS::Scalar::Type type) {
switch (type) {
#define REMAP_PULL_INTO_DESCRIPTOR_TYPE(ExternalT, NativeT, Name) \
case JS::Scalar::Name: \
return Constructor::Name;
JS_FOR_EACH_TYPED_ARRAY(REMAP_PULL_INTO_DESCRIPTOR_TYPE)
#undef REMAP
case JS::Scalar::Int64:
case JS::Scalar::Simd128:
case JS::Scalar::MaxTypedArrayViewType:
break;
}
MOZ_CRASH("Unexpected Scalar::Type");
}
PullIntoDescriptor(JS::Handle<JSObject*> aBuffer, uint64_t aBufferByteLength,
uint64_t aByteOffset, uint64_t aByteLength,
uint64_t aBytesFilled, uint64_t aElementSize,
Constructor aViewConstructor, ReaderType aReaderType)
: mBuffer(aBuffer),
mBufferByteLength(aBufferByteLength),
mByteOffset(aByteOffset),
mByteLength(aByteLength),
mBytesFilled(aBytesFilled),
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 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 mElementSize = 0;
Constructor mViewConstructor;
ReaderType mReaderType;
~PullIntoDescriptor() { mozilla::DropJSObjects(this); }
};
NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(PullIntoDescriptor, (), (mBuffer));
NS_IMPL_CYCLE_COLLECTION_CLASS(ReadableByteStreamController)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ReadableByteStreamController,
ReadableStreamController)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mByobRequest, mQueue, mPendingPullIntos)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ReadableByteStreamController,
ReadableStreamController)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mByobRequest, mQueue, mPendingPullIntos)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ReadableByteStreamController,
ReadableStreamController)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_ADDREF_INHERITED(ReadableByteStreamController, ReadableStreamController)
NS_IMPL_RELEASE_INHERITED(ReadableByteStreamController,
ReadableStreamController)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableByteStreamController)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_END_INHERITING(ReadableStreamController)
ReadableByteStreamController::ReadableByteStreamController(
nsIGlobalObject* aGlobal)
: ReadableStreamController(aGlobal) {}
ReadableByteStreamController::~ReadableByteStreamController() = default;
void ReadableByteStreamController::ClearQueue() { mQueue.clear(); }
void ReadableByteStreamController::ClearPendingPullIntos() {
mPendingPullIntos.clear();
}
namespace streams_abstract {
already_AddRefed<ReadableStreamBYOBRequest>
ReadableByteStreamControllerGetBYOBRequest(
JSContext* aCx, ReadableByteStreamController* aController,
ErrorResult& aRv) {
// Step 1.
if (!aController->GetByobRequest() &&
!aController->PendingPullIntos().isEmpty()) {
// Step 1.1:
PullIntoDescriptor* firstDescriptor =
aController->PendingPullIntos().getFirst();
// Step 1.2:
aRv.MightThrowJSException();
JS::Rooted<JSObject*> buffer(aCx, firstDescriptor->Buffer());
JS::Rooted<JSObject*> view(
aCx, JS_NewUint8ArrayWithBuffer(
aCx, buffer,
firstDescriptor->ByteOffset() + firstDescriptor->BytesFilled(),
int64_t(firstDescriptor->ByteLength() -
firstDescriptor->BytesFilled())));
if (!view) {
aRv.StealExceptionFromJSContext(aCx);
return nullptr;
}
// Step 1.3:
RefPtr<ReadableStreamBYOBRequest> byobRequest =
new ReadableStreamBYOBRequest(aController->GetParentObject());
// Step 1.4:
byobRequest->SetController(aController);
// Step 1.5:
byobRequest->SetView(view);
// Step 1.6:
aController->SetByobRequest(byobRequest);
}
// Step 2.
RefPtr<ReadableStreamBYOBRequest> request(aController->GetByobRequest());
return request.forget();
}
} // namespace streams_abstract
already_AddRefed<ReadableStreamBYOBRequest>
ReadableByteStreamController::GetByobRequest(JSContext* aCx, ErrorResult& aRv) {
return ReadableByteStreamControllerGetBYOBRequest(aCx, this, aRv);
}
Nullable<double> ReadableByteStreamControllerGetDesiredSize(
const ReadableByteStreamController* aController) {
// Step 1.
ReadableStream::ReaderState state = aController->Stream()->State();
// Step 2.
if (state == ReadableStream::ReaderState::Errored) {
return nullptr;
}
// Step 3.
if (state == ReadableStream::ReaderState::Closed) {
return 0.0;
}
// Step 4.
return aController->StrategyHWM() - aController->QueueTotalSize();
}
Nullable<double> ReadableByteStreamController::GetDesiredSize() const {
// Step 1.
return ReadableByteStreamControllerGetDesiredSize(this);
}
JSObject* ReadableByteStreamController::WrapObject(
JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
return ReadableByteStreamController_Binding::Wrap(aCx, this, aGivenProto);
}
namespace streams_abstract {
static void ReadableByteStreamControllerInvalidateBYOBRequest(
ReadableByteStreamController* aController) {
// Step 1.
if (!aController->GetByobRequest()) {
return;
}
// Step 2.
aController->GetByobRequest()->SetController(nullptr);
// Step 3.
aController->GetByobRequest()->SetView(nullptr);
// Step 4.
aController->SetByobRequest(nullptr);
}
void ReadableByteStreamControllerClearPendingPullIntos(
ReadableByteStreamController* aController) {
// Step 1.
ReadableByteStreamControllerInvalidateBYOBRequest(aController);
// Step 2.
aController->ClearPendingPullIntos();
}
void ResetQueue(ReadableByteStreamController* aContainer) {
// Step 1. Implied by type.
// Step 2.
aContainer->ClearQueue();
// Step 3.
aContainer->SetQueueTotalSize(0);
}
void ReadableByteStreamControllerClearAlgorithms(
ReadableByteStreamController* aController) {
// Step 1. Set controller.[[pullAlgorithm]] to undefined.
// Step 2. Set controller.[[cancelAlgorithm]] to undefined.
aController->ClearAlgorithms();
}
void ReadableByteStreamControllerError(
ReadableByteStreamController* aController, JS::Handle<JS::Value> aValue,
ErrorResult& aRv) {
// Step 1. Let stream be controller.[[stream]].
ReadableStream* stream = aController->Stream();
// Step 2. If stream.[[state]] is not "readable", return.
if (stream->State() != ReadableStream::ReaderState::Readable) {
return;
}
// Step 3. Perform
// !ReadableByteStreamControllerClearPendingPullIntos(controller).
ReadableByteStreamControllerClearPendingPullIntos(aController);
// Step 4. Perform !ResetQueue(controller).
ResetQueue(aController);
// Step 5. Perform !ReadableByteStreamControllerClearAlgorithms(controller).
ReadableByteStreamControllerClearAlgorithms(aController);
// Step 6. Perform !ReadableStreamError(stream, e).
AutoJSAPI jsapi;
if (!jsapi.Init(aController->GetParentObject())) {
return;
}
ReadableStreamError(jsapi.cx(), stream, aValue, aRv);
}
void ReadableByteStreamControllerClose(
JSContext* aCx, ReadableByteStreamController* aController,
ErrorResult& aRv) {
// Step 1.
RefPtr<ReadableStream> stream = aController->Stream();
// Step 2.
if (aController->CloseRequested() ||
stream->State() != ReadableStream::ReaderState::Readable) {
return;
}
// Step 3.
if (aController->QueueTotalSize() > 0) {
// Step 3.1
aController->SetCloseRequested(true);
// Step 3.2
return;
}
// Step 4.
if (!aController->PendingPullIntos().isEmpty()) {
// Step 4.1
PullIntoDescriptor* firstPendingPullInto =
aController->PendingPullIntos().getFirst();
// Step 4.2
if (firstPendingPullInto->BytesFilled() > 0) {
// Step 4.2.1
ErrorResult rv;
rv.ThrowTypeError("Leftover Bytes");
JS::Rooted<JS::Value> exception(aCx);
MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &exception));
// Step 4.2.2
ReadableByteStreamControllerError(aController, exception, aRv);
if (aRv.Failed()) {
return;
}
aRv.MightThrowJSException();
aRv.ThrowJSException(aCx, exception);
return;
}
}
// Step 5.
ReadableByteStreamControllerClearAlgorithms(aController);
// Step 6.
ReadableStreamClose(aCx, stream, aRv);
}
} // namespace streams_abstract
void ReadableByteStreamController::Close(JSContext* aCx, ErrorResult& aRv) {
// Step 1.
if (mCloseRequested) {
aRv.ThrowTypeError("Close already requested");
return;
}
// Step 2.
if (Stream()->State() != ReadableStream::ReaderState::Readable) {
aRv.ThrowTypeError("Closing un-readable stream controller");
return;
}
// Step 3.
ReadableByteStreamControllerClose(aCx, this, aRv);
}
namespace streams_abstract {
void ReadableByteStreamControllerEnqueueChunkToQueue(
ReadableByteStreamController* aController,
JS::Handle<JSObject*> aTransferredBuffer, size_t aByteOffset,
size_t aByteLength) {
// Step 1.
RefPtr<ReadableByteStreamQueueEntry> queueEntry =
new ReadableByteStreamQueueEntry(aTransferredBuffer, aByteOffset,
aByteLength);
aController->Queue().insertBack(queueEntry);
// Step 2.
aController->AddToQueueTotalSize(double(aByteLength));
}
void ReadableByteStreamControllerEnqueueClonedChunkToQueue(
JSContext* aCx, ReadableByteStreamController* aController,
JS::Handle<JSObject*> aBuffer, size_t aByteOffset, size_t aByteLength,
ErrorResult& aRv) {
// Step 1. Let cloneResult be CloneArrayBuffer(buffer, byteOffset, byteLength,
// %ArrayBuffer%).
aRv.MightThrowJSException();
JS::Rooted<JSObject*> cloneResult(
aCx, JS::ArrayBufferClone(aCx, aBuffer, aByteOffset, aByteLength));
// Step 2. If cloneResult is an abrupt completion,
if (!cloneResult) {
JS::Rooted<JS::Value> exception(aCx);
if (!JS_GetPendingException(aCx, &exception)) {
// Uncatchable exception; we should mark aRv and return.
aRv.StealExceptionFromJSContext(aCx);
return;
}
JS_ClearPendingException(aCx);
// Step 2.1. Perform ! ReadableByteStreamControllerError(controller,
// cloneResult.[[Value]]).
ReadableByteStreamControllerError(aController, exception, aRv);
if (aRv.Failed()) {
return;
}
// Step 2.2. Return cloneResult.
aRv.ThrowJSException(aCx, exception);
return;
}
// Step 3. Perform !
// ReadableByteStreamControllerEnqueueChunkToQueue(controller,
// cloneResult.[[Value]], 0, byteLength).
ReadableByteStreamControllerEnqueueChunkToQueue(aController, cloneResult, 0,
aByteLength);
}
already_AddRefed<PullIntoDescriptor>
ReadableByteStreamControllerShiftPendingPullInto(
ReadableByteStreamController* aController);
void ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(
JSContext* aCx, ReadableByteStreamController* aController,
PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv) {
// Step 1. Assert: pullIntoDescriptor’s reader type is "none".
MOZ_ASSERT(aPullIntoDescriptor->GetReaderType() == ReaderType::None);
// Step 2. If pullIntoDescriptor’s bytes filled > 0,
// perform ? ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller,
// pullIntoDescriptor’s buffer, pullIntoDescriptor’s byte offset,
// pullIntoDescriptor’s bytes filled).
if (aPullIntoDescriptor->BytesFilled() > 0) {
JS::Rooted<JSObject*> buffer(aCx, aPullIntoDescriptor->Buffer());
ReadableByteStreamControllerEnqueueClonedChunkToQueue(
aCx, aController, buffer, aPullIntoDescriptor->ByteOffset(),
aPullIntoDescriptor->BytesFilled(), aRv);
if (aRv.Failed()) {
return;
}
}
// Step 3. Perform !
// ReadableByteStreamControllerShiftPendingPullInto(controller).
RefPtr<PullIntoDescriptor> discarded =
ReadableByteStreamControllerShiftPendingPullInto(aController);
(void)discarded;
}
static size_t ReadableStreamGetNumReadIntoRequests(ReadableStream* aStream) {
// Step 1.
MOZ_ASSERT(ReadableStreamHasBYOBReader(aStream));
// Step 2.
return aStream->GetReader()->AsBYOB()->ReadIntoRequests().length();
}
bool ReadableByteStreamControllerShouldCallPull(
ReadableByteStreamController* aController) {
// Step 1. Let stream be controller.[[stream]].
ReadableStream* stream = aController->Stream();
// Step 2. If stream.[[state]] is not "readable", return false.
if (stream->State() != ReadableStream::ReaderState::Readable) {
return false;
}
// Step 3. If controller.[[closeRequested]] is true, return false.
if (aController->CloseRequested()) {
return false;
}
// Step 4. If controller.[[started]] is false, return false.
if (!aController->Started()) {
return false;
}
// Step 5. If ! ReadableStreamHasDefaultReader(stream) is true
// and ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
if (ReadableStreamHasDefaultReader(stream) &&
ReadableStreamGetNumReadRequests(stream) > 0) {
return true;
}
// Step 6. If ! ReadableStreamHasBYOBReader(stream) is true
// and ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true.
if (ReadableStreamHasBYOBReader(stream) &&
ReadableStreamGetNumReadIntoRequests(stream) > 0) {
return true;
}
// Step 7. Let desiredSize be
// ! ReadableByteStreamControllerGetDesiredSize(controller).
Nullable<double> desiredSize =
ReadableByteStreamControllerGetDesiredSize(aController);
// Step 8. Assert: desiredSize is not null.
MOZ_ASSERT(!desiredSize.IsNull());
// Step 9. If desiredSize > 0, return true.
// Step 10. Return false.
return desiredSize.Value() > 0;
}
void ReadableByteStreamControllerCallPullIfNeeded(
JSContext* aCx, ReadableByteStreamController* aController,
ErrorResult& aRv) {
// Step 1.
bool shouldPull = ReadableByteStreamControllerShouldCallPull(aController);
// Step 2.
if (!shouldPull) {
return;
}
// Step 3.
if (aController->Pulling()) {
aController->SetPullAgain(true);
return;
}
// Step 4.
MOZ_ASSERT(!aController->PullAgain());
// Step 5.
aController->SetPulling(true);
// Step 6.
RefPtr<ReadableStreamController> controller(aController);
RefPtr<UnderlyingSourceAlgorithmsBase> algorithms =
aController->GetAlgorithms();
RefPtr<Promise> pullPromise = algorithms->PullCallback(aCx, *controller, aRv);
if (aRv.Failed()) {
return;
}
// Steps 7+8
pullPromise->AddCallbacksWithCycleCollectedArgs(
[](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
ReadableByteStreamController* aController)
MOZ_CAN_RUN_SCRIPT_BOUNDARY {
// Step 7.1
aController->SetPulling(false);
// Step 7.2
if (aController->PullAgain()) {
// Step 7.2.1
aController->SetPullAgain(false);
// Step 7.2.2
ReadableByteStreamControllerCallPullIfNeeded(
aCx, MOZ_KnownLive(aController), aRv);
}
},
[](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
ReadableByteStreamController* aController) {
// Step 8.1
ReadableByteStreamControllerError(aController, aValue, aRv);
},
RefPtr(aController));
}
bool ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(
JSContext* aCx, ReadableByteStreamController* aController,
PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv);
JSObject* ReadableByteStreamControllerConvertPullIntoDescriptor(
JSContext* aCx, PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv);
MOZ_CAN_RUN_SCRIPT
void ReadableStreamFulfillReadIntoRequest(JSContext* aCx,
ReadableStream* aStream,
JS::Handle<JS::Value> aChunk,
bool done, ErrorResult& aRv) {
// Step 1. Assert: !ReadableStreamHasBYOBReader(stream) is true.
MOZ_ASSERT(ReadableStreamHasBYOBReader(aStream));
// Step 2. Let reader be stream.[[reader]].
ReadableStreamBYOBReader* reader = aStream->GetReader()->AsBYOB();
// Step 3. Assert: reader.[[readIntoRequests]] is not empty.
MOZ_ASSERT(!reader->ReadIntoRequests().isEmpty());
// Step 4. Let readIntoRequest be reader.[[readIntoRequests]][0].
// Step 5. Remove readIntoRequest from reader.[[readIntoRequests]].
RefPtr<ReadIntoRequest> readIntoRequest =
reader->ReadIntoRequests().popFirst();
// Step 6. If done is true, perform readIntoRequest’s close steps, given
// chunk.
if (done) {
readIntoRequest->CloseSteps(aCx, aChunk, aRv);
return;
}
// Step 7. Otherwise, perform readIntoRequest’s chunk steps, given chunk.
readIntoRequest->ChunkSteps(aCx, aChunk, aRv);
}
MOZ_CAN_RUN_SCRIPT
void ReadableByteStreamControllerCommitPullIntoDescriptor(
JSContext* aCx, ReadableStream* aStream,
PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv) {
// Step 1. Assert: stream.[[state]] is not "errored".
MOZ_ASSERT(aStream->State() != ReadableStream::ReaderState::Errored);
// Step 2. Assert: pullIntoDescriptor.reader type is not "none".
MOZ_ASSERT(pullIntoDescriptor->GetReaderType() != ReaderType::None);
// Step 3. Let done be false.
bool done = false;
// Step 4. If stream.[[state]] is "closed",
if (aStream->State() == ReadableStream::ReaderState::Closed) {
// Step 4.1. Assert: pullIntoDescriptor’s bytes filled is 0.
MOZ_ASSERT(pullIntoDescriptor->BytesFilled() == 0);
// Step 4.2. Set done to true.
done = true;
}
// Step 5. Let filledView be !
// ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
JS::Rooted<JSObject*> filledView(
aCx, ReadableByteStreamControllerConvertPullIntoDescriptor(
aCx, pullIntoDescriptor, aRv));
if (aRv.Failed()) {
return;
}
JS::Rooted<JS::Value> filledViewValue(aCx, JS::ObjectValue(*filledView));
// Step 6. If pullIntoDescriptor’s reader type is "default",
if (pullIntoDescriptor->GetReaderType() == ReaderType::Default) {
// Step 6.1. Perform !ReadableStreamFulfillReadRequest(stream, filledView,
// done).
ReadableStreamFulfillReadRequest(aCx, aStream, filledViewValue, done, aRv);
return;
}
// Step 7.1. Assert: pullIntoDescriptor’s reader type is "byob".
MOZ_ASSERT(pullIntoDescriptor->GetReaderType() == ReaderType::BYOB);
// Step 7.2 Perform !ReadableStreamFulfillReadIntoRequest(stream, filledView,
// done).
ReadableStreamFulfillReadIntoRequest(aCx, aStream, filledViewValue, done,
aRv);
}
MOZ_CAN_RUN_SCRIPT
void ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
JSContext* aCx, ReadableByteStreamController* aController,
ErrorResult& aRv) {
// Step 1. Assert: controller.[[closeRequested]] is false.
MOZ_ASSERT(!aController->CloseRequested());
// Step 2. While controller.[[pendingPullIntos]] is not empty,
while (!aController->PendingPullIntos().isEmpty()) {
// Step 2.1 If controller.[[queueTotalSize]] is 0, return.
if (aController->QueueTotalSize() == 0) {
return;
}
// Step 2.2. Let pullIntoDescriptor be controller.[[pendingPullIntos]][0].
RefPtr<PullIntoDescriptor> pullIntoDescriptor =
aController->PendingPullIntos().getFirst();
// Step 2.3. If
// !ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
// pullIntoDescriptor) is true,
bool ready = ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(
aCx, aController, pullIntoDescriptor, aRv);
if (aRv.Failed()) {
return;
}
if (ready) {
// Step 2.3.1. Perform
// !ReadableByteStreamControllerShiftPendingPullInto(controller).
RefPtr<PullIntoDescriptor> discardedPullIntoDescriptor =
ReadableByteStreamControllerShiftPendingPullInto(aController);
// Step 2.3.2. Perform
// !ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]],
// pullIntoDescriptor).
RefPtr<ReadableStream> stream(aController->Stream());
ReadableByteStreamControllerCommitPullIntoDescriptor(
aCx, stream, pullIntoDescriptor, aRv);
if (aRv.Failed()) {
return;
}
}
}
}
MOZ_CAN_RUN_SCRIPT
void ReadableByteStreamControllerHandleQueueDrain(
JSContext* aCx, ReadableByteStreamController* aController,
ErrorResult& aRv);
MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerFillReadRequestFromQueue(
JSContext* aCx, ReadableByteStreamController* aController,
ReadRequest* aReadRequest, ErrorResult& aRv) {
// Step 1. Assert: controller.[[queueTotalSize]] > 0.
MOZ_ASSERT(aController->QueueTotalSize() > 0);
// Also assert that the queue has a non-zero length;
MOZ_ASSERT(aController->Queue().length() > 0);
// Step 2. Let entry be controller.[[queue]][0].
// Step 3. Remove entry from controller.[[queue]].
RefPtr<ReadableByteStreamQueueEntry> entry = aController->Queue().popFirst();
// Assert that we actually got an entry.
MOZ_ASSERT(entry);
// Step 4. Set controller.[[queueTotalSize]] to controller.[[queueTotalSize]]
// − entry’s byte length.
aController->SetQueueTotalSize(aController->QueueTotalSize() -
double(entry->ByteLength()));
// Step 5. Perform ! ReadableByteStreamControllerHandleQueueDrain(controller).
ReadableByteStreamControllerHandleQueueDrain(aCx, aController, aRv);
if (aRv.Failed()) {
return;
}
// Step 6. Let view be ! Construct(%Uint8Array%, « entry’s buffer, entry’s
// byte offset, entry’s byte length »).
aRv.MightThrowJSException();
JS::Rooted<JSObject*> buffer(aCx, entry->Buffer());
JS::Rooted<JSObject*> view(
aCx, JS_NewUint8ArrayWithBuffer(aCx, buffer, entry->ByteOffset(),
int64_t(entry->ByteLength())));
if (!view) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
// Step 7. Perform readRequest’s chunk steps, given view.
JS::Rooted<JS::Value> viewValue(aCx, JS::ObjectValue(*view));
aReadRequest->ChunkSteps(aCx, viewValue, aRv);
}
MOZ_CAN_RUN_SCRIPT void
ReadableByteStreamControllerProcessReadRequestsUsingQueue(
JSContext* aCx, ReadableByteStreamController* aController,
ErrorResult& aRv) {
// Step 1. Let reader be controller.[[stream]].[[reader]].
// Step 2. Assert: reader implements ReadableStreamDefaultReader.
RefPtr<ReadableStreamDefaultReader> reader =
aController->Stream()->GetDefaultReader();
// Step 3. While reader.[[readRequests]] is not empty,
while (!reader->ReadRequests().isEmpty()) {
// Step 3.1. If controller.[[queueTotalSize]] is 0, return.
if (aController->QueueTotalSize() == 0) {
return;
}
// Step 3.2. Let readRequest be reader.[[readRequests]][0].
// Step 3.3. Remove readRequest from reader.[[readRequests]].
RefPtr<ReadRequest> readRequest = reader->ReadRequests().popFirst();
// Step 3.4. Perform !
// ReadableByteStreamControllerFillReadRequestFromQueue(controller,
// readRequest).
ReadableByteStreamControllerFillReadRequestFromQueue(aCx, aController,
readRequest, aRv);
if (aRv.Failed()) {
return;
}
}
}
void ReadableByteStreamControllerEnqueue(
JSContext* aCx, ReadableByteStreamController* aController,
JS::Handle<JSObject*> aChunk, ErrorResult& aRv) {
aRv.MightThrowJSException();
// Step 1.
RefPtr<ReadableStream> stream = aController->Stream();
// Step 2.
if (aController->CloseRequested() ||
stream->State() != ReadableStream::ReaderState::Readable) {
return;
}
// Step 3.
bool isShared;
JS::Rooted<JSObject*> buffer(
aCx, JS_GetArrayBufferViewBuffer(aCx, aChunk, &isShared));
if (!buffer) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
// Step 4.
size_t byteOffset = JS_GetArrayBufferViewByteOffset(aChunk);
// Step 5.
size_t byteLength = JS_GetArrayBufferViewByteLength(aChunk);
// Step 6.
if (JS::IsDetachedArrayBufferObject(buffer)) {
aRv.ThrowTypeError("Detached Array Buffer");
return;
}
// Step 7.
JS::Rooted<JSObject*> transferredBuffer(aCx,
TransferArrayBuffer(aCx, buffer));
if (!transferredBuffer) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
// Step 8.
if (!aController->PendingPullIntos().isEmpty()) {
// Step 8.1
RefPtr<PullIntoDescriptor> firstPendingPullInto =
aController->PendingPullIntos().getFirst();
// Step 8.2
JS::Rooted<JSObject*> pendingBuffer(aCx, firstPendingPullInto->Buffer());
if (JS::IsDetachedArrayBufferObject(pendingBuffer)) {
aRv.ThrowTypeError("Pending PullInto has detached buffer");
return;
}
// Step 8.3. Perform !
// ReadableByteStreamControllerInvalidateBYOBRequest(controller).
ReadableByteStreamControllerInvalidateBYOBRequest(aController);
// Step 8.4. Set firstPendingPullInto’s buffer to !
// TransferArrayBuffer(firstPendingPullInto’s buffer).
pendingBuffer = TransferArrayBuffer(aCx, pendingBuffer);
if (!pendingBuffer) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
firstPendingPullInto->SetBuffer(pendingBuffer);
// Step 8.5. If firstPendingPullInto’s reader type is "none", perform ?
// ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller,
// firstPendingPullInto).
if (firstPendingPullInto->GetReaderType() == ReaderType::None) {
ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(
aCx, aController, firstPendingPullInto, aRv);
if (aRv.Failed()) {
return;
}
}
}
// Step 9. If ! ReadableStreamHasDefaultReader(stream) is true,
if (ReadableStreamHasDefaultReader(stream)) {
// Step 9.1. Perform !
// ReadableByteStreamControllerProcessReadRequestsUsingQueue(controller).
ReadableByteStreamControllerProcessReadRequestsUsingQueue(aCx, aController,
aRv);
if (aRv.Failed()) {
return;
}
// Step 9.2. If ! ReadableStreamGetNumReadRequests(stream) is 0,
if (ReadableStreamGetNumReadRequests(stream) == 0) {
// Step 9.2.1 Assert: controller.[[pendingPullIntos]] is empty.
MOZ_ASSERT(aController->PendingPullIntos().isEmpty());
// Step 9.2.2. Perform !
// ReadableByteStreamControllerEnqueueChunkToQueue(controller,
// transferredBuffer, byteOffset, byteLength).
ReadableByteStreamControllerEnqueueChunkToQueue(
aController, transferredBuffer, byteOffset, byteLength);
// Step 9.3. Otherwise,
} else {
// Step 9.3.1 Assert: controller.[[queue]] is empty.
MOZ_ASSERT(aController->Queue().isEmpty());
// Step 9.3.2. If controller.[[pendingPullIntos]] is not empty,
if (!aController->PendingPullIntos().isEmpty()) {
// Step 9.3.2.1. Assert: controller.[[pendingPullIntos]][0]'s reader
// type is "default".
MOZ_ASSERT(
aController->PendingPullIntos().getFirst()->GetReaderType() ==
ReaderType::Default);
// Step 9.3.2.2. Perform !
// ReadableByteStreamControllerShiftPendingPullInto(controller).
RefPtr<PullIntoDescriptor> pullIntoDescriptor =
ReadableByteStreamControllerShiftPendingPullInto(aController);
(void)pullIntoDescriptor;
}
// Step 9.3.3. Let transferredView be ! Construct(%Uint8Array%, «
// transferredBuffer, byteOffset, byteLength »).
JS::Rooted<JSObject*> transferredView(
aCx, JS_NewUint8ArrayWithBuffer(aCx, transferredBuffer, byteOffset,
int64_t(byteLength)));
if (!transferredView) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
// Step 9.3.4. Perform ! ReadableStreamFulfillReadRequest(stream,
// transferredView, false).
JS::Rooted<JS::Value> transferredViewValue(
aCx, JS::ObjectValue(*transferredView));
ReadableStreamFulfillReadRequest(aCx, stream, transferredViewValue, false,
aRv);
if (aRv.Failed()) {
return;
}
}
// Step 10. Otherwise, if ! ReadableStreamHasBYOBReader(stream) is true,
} else if (ReadableStreamHasBYOBReader(stream)) {
// Step 10.1. Perform !
// ReadableByteStreamControllerEnqueueChunkToQueue(controller,
// transferredBuffer, byteOffset, byteLength).
ReadableByteStreamControllerEnqueueChunkToQueue(
aController, transferredBuffer, byteOffset, byteLength);
// Step 10.2 Perform !
// ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
aCx, aController, aRv);
if (aRv.Failed()) {
return;
}
// Step 11. Otherwise,
} else {
// Step 11.1. Assert: ! IsReadableStreamLocked(stream) is false.
MOZ_ASSERT(!IsReadableStreamLocked(stream));
// Step 11.2. Perform !
// ReadableByteStreamControllerEnqueueChunkToQueue(controller,
// transferredBuffer, byteOffset, byteLength).
ReadableByteStreamControllerEnqueueChunkToQueue(
aController, transferredBuffer, byteOffset, byteLength);
}
// Step 12. Perform !
// ReadableByteStreamControllerCallPullIfNeeded(controller).
ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv);
}
} // namespace streams_abstract
void ReadableByteStreamController::Enqueue(JSContext* aCx,
const ArrayBufferView& aChunk,
ErrorResult& aRv) {
// Step 1.
JS::Rooted<JSObject*> chunk(aCx, aChunk.Obj());
if (JS_GetArrayBufferViewByteLength(chunk) == 0) {
aRv.ThrowTypeError("Zero Length View");
return;
}
// Step 2.
bool isShared;
JS::Rooted<JSObject*> viewedArrayBuffer(
aCx, JS_GetArrayBufferViewBuffer(aCx, chunk, &isShared));
if (!viewedArrayBuffer) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
if (JS::GetArrayBufferByteLength(viewedArrayBuffer) == 0) {
aRv.ThrowTypeError("Zero Length Buffer");
return;
}
// Step 3.
if (CloseRequested()) {
aRv.ThrowTypeError("close requested");
return;
}
// Step 4.
if (Stream()->State() != ReadableStream::ReaderState::Readable) {
aRv.ThrowTypeError("Not Readable");
return;
}
// Step 5.
ReadableByteStreamControllerEnqueue(aCx, this, chunk, aRv);
}
void ReadableByteStreamController::Error(JSContext* aCx,
JS::Handle<JS::Value> aErrorValue,
ErrorResult& aRv) {
// Step 1.
ReadableByteStreamControllerError(this, aErrorValue, aRv);
}
already_AddRefed<Promise> ReadableByteStreamController::CancelSteps(
JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
// Step 1.
ReadableByteStreamControllerClearPendingPullIntos(this);
// Step 2.
ResetQueue(this);
// Step 3.
Optional<JS::Handle<JS::Value>> reason(aCx, aReason);
RefPtr<UnderlyingSourceAlgorithmsBase> algorithms = mAlgorithms;
RefPtr<Promise> result = algorithms->CancelCallback(aCx, reason, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Step 4.
ReadableByteStreamControllerClearAlgorithms(this);
// Step 5.
return result.forget();
}
namespace streams_abstract {
void ReadableByteStreamControllerHandleQueueDrain(
JSContext* aCx, ReadableByteStreamController* aController,
ErrorResult& aRv) {
// Step 1.
MOZ_ASSERT(aController->Stream()->State() ==
ReadableStream::ReaderState::Readable);
// Step 2.
if (aController->QueueTotalSize() == 0 && aController->CloseRequested()) {
// Step 2.1
ReadableByteStreamControllerClearAlgorithms(aController);
// Step 2.2
RefPtr<ReadableStream> stream = aController->Stream();
ReadableStreamClose(aCx, stream, aRv);
return;
}
// Step 3.1
ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv);
}
} // namespace streams_abstract
void ReadableByteStreamController::PullSteps(JSContext* aCx,
ReadRequest* aReadRequest,
ErrorResult& aRv) {
// Step 1.
ReadableStream* stream = Stream();
// Step 2.
MOZ_ASSERT(ReadableStreamHasDefaultReader(stream));
// Step 3.
if (QueueTotalSize() > 0) {
// Step 3.1. Assert: ! ReadableStreamGetNumReadRequests ( stream ) is 0.
MOZ_ASSERT(ReadableStreamGetNumReadRequests(stream) == 0);
// Step 3.2. Perform !
// ReadableByteStreamControllerFillReadRequestFromQueue(this, readRequest).
ReadableByteStreamControllerFillReadRequestFromQueue(aCx, this,
aReadRequest, aRv);
// Step 3.3. Return.
return;
}
// Step 4.
Maybe<uint64_t> autoAllocateChunkSize = AutoAllocateChunkSize();
// Step 5.
if (autoAllocateChunkSize) {
// Step 5.1
aRv.MightThrowJSException();
JS::Rooted<JSObject*> buffer(
aCx, JS::NewArrayBuffer(aCx, *autoAllocateChunkSize));
// Step 5.2
if (!buffer) {
// Step 5.2.1
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 expliclitly stated, but I assume the intention here is that
// we perform a normal completion here.
JS_ClearPendingException(aCx);
aReadRequest->ErrorSteps(aCx, bufferError, aRv);
// Step 5.2.2.
return;
}
// Step 5.3
RefPtr<PullIntoDescriptor> pullIntoDescriptor = new PullIntoDescriptor(
buffer, *autoAllocateChunkSize, 0, *autoAllocateChunkSize, 0, 1,
PullIntoDescriptor::Constructor::Uint8, ReaderType::Default);
// Step 5.4
PendingPullIntos().insertBack(pullIntoDescriptor);
}
// Step 6.
ReadableStreamAddReadRequest(stream, aReadRequest);
// Step 7.
ReadableByteStreamControllerCallPullIfNeeded(aCx, this, aRv);
}
void ReadableByteStreamController::ReleaseSteps() {
// Step 1. If this.[[pendingPullIntos]] is not empty,
if (!PendingPullIntos().isEmpty()) {
// Step 1.1. Let firstPendingPullInto be this.[[pendingPullIntos]][0].
RefPtr<PullIntoDescriptor> firstPendingPullInto =
PendingPullIntos().popFirst();
// Step 1.2. Set firstPendingPullInto’s reader type to "none".
firstPendingPullInto->SetReaderType(ReaderType::None);
// Step 1.3. Set this.[[pendingPullIntos]] to the list «
// firstPendingPullInto ».
PendingPullIntos().clear();
PendingPullIntos().insertBack(firstPendingPullInto);
}
}
namespace streams_abstract {
already_AddRefed<PullIntoDescriptor>
ReadableByteStreamControllerShiftPendingPullInto(
ReadableByteStreamController* aController) {
// Step 1.
MOZ_ASSERT(!aController->GetByobRequest());
// Step 2 + 3
RefPtr<PullIntoDescriptor> descriptor =
aController->PendingPullIntos().popFirst();
// Step 4.
return descriptor.forget();
}
JSObject* ConstructFromPullIntoConstructor(
JSContext* aCx, PullIntoDescriptor::Constructor constructor,
JS::Handle<JSObject*> buffer, size_t byteOffset, size_t length) {
switch (constructor) {
case PullIntoDescriptor::Constructor::DataView:
return JS_NewDataView(aCx, buffer, byteOffset, length);
break;
#define CONSTRUCT_TYPED_ARRAY_TYPE(ExternalT, NativeT, Name) \
case PullIntoDescriptor::Constructor::Name: \
return JS_New##Name##ArrayWithBuffer(aCx, buffer, byteOffset, \
int64_t(length)); \
break;
JS_FOR_EACH_TYPED_ARRAY(CONSTRUCT_TYPED_ARRAY_TYPE)
#undef CONSTRUCT_TYPED_ARRAY_TYPE
default:
MOZ_ASSERT_UNREACHABLE("Unknown PullIntoDescriptor::Constructor");
return nullptr;
}
}
JSObject* ReadableByteStreamControllerConvertPullIntoDescriptor(
JSContext* aCx, PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv) {
// Step 1. Let bytesFilled be pullIntoDescriptor’s bytes filled.
uint64_t bytesFilled = pullIntoDescriptor->BytesFilled();
// Step 2. Let elementSize be pullIntoDescriptor’s element size.
uint64_t elementSize = pullIntoDescriptor->ElementSize();
// Step 3. Assert: bytesFilled ≤ pullIntoDescriptor’s byte length.
MOZ_ASSERT(bytesFilled <= pullIntoDescriptor->ByteLength());
// Step 4. Assert: bytesFilled mod elementSize is 0.
MOZ_ASSERT(bytesFilled % elementSize == 0);
// Step 5. Let buffer be ! TransferArrayBuffer(pullIntoDescriptor’s buffer).
aRv.MightThrowJSException();
JS::Rooted<JSObject*> srcBuffer(aCx, pullIntoDescriptor->Buffer());
JS::Rooted<JSObject*> buffer(aCx, TransferArrayBuffer(aCx, srcBuffer));
if (!buffer) {
aRv.StealExceptionFromJSContext(aCx);
return nullptr;
}
// Step 6. Return ! Construct(pullIntoDescriptor’s view constructor,
// « buffer, pullIntoDescriptor’s byte offset, bytesFilled ÷ elementSize »).
JS::Rooted<JSObject*> res(
aCx, ConstructFromPullIntoConstructor(
aCx, pullIntoDescriptor->ViewConstructor(), buffer,
pullIntoDescriptor->ByteOffset(), bytesFilled / elementSize));
if (!res) {
aRv.StealExceptionFromJSContext(aCx);
return nullptr;
}
return res;
}
MOZ_CAN_RUN_SCRIPT
static void ReadableByteStreamControllerRespondInClosedState(
JSContext* aCx, ReadableByteStreamController* aController,
RefPtr<PullIntoDescriptor>& aFirstDescriptor, ErrorResult& aRv) {
// Step 1. Assert: firstDescriptor ’s bytes filled is 0.
MOZ_ASSERT(aFirstDescriptor->BytesFilled() == 0);
// Step 2. If firstDescriptor’s reader type is "none",
// perform ! ReadableByteStreamControllerShiftPendingPullInto(controller).
if (aFirstDescriptor->GetReaderType() == ReaderType::None) {
RefPtr<PullIntoDescriptor> discarded =
ReadableByteStreamControllerShiftPendingPullInto(aController);
(void)discarded;
}
// Step 3. Let stream be controller.[[stream]].
RefPtr<ReadableStream> stream = aController->Stream();
// Step 4. If ! ReadableStreamHasBYOBReader(stream) is true,
if (ReadableStreamHasBYOBReader(stream)) {
// Step 4.1. While ! ReadableStreamGetNumReadIntoRequests(stream) > 0,
while (ReadableStreamGetNumReadIntoRequests(stream) > 0) {
// Step 4.1.1. Let pullIntoDescriptor be !
// ReadableByteStreamControllerShiftPendingPullInto(controller).
RefPtr<PullIntoDescriptor> pullIntoDescriptor =
ReadableByteStreamControllerShiftPendingPullInto(aController);
// Step 4.1.2. Perform !
// ReadableByteStreamControllerCommitPullIntoDescriptor(stream,
// pullIntoDescriptor).
ReadableByteStreamControllerCommitPullIntoDescriptor(
aCx, stream, pullIntoDescriptor, aRv);
}
}
}
void ReadableByteStreamControllerFillHeadPullIntoDescriptor(
ReadableByteStreamController* aController, size_t aSize,
PullIntoDescriptor* aPullIntoDescriptor) {
// Step 1. Assert: either controller.[[pendingPullIntos]] is empty, or
// controller.[[pendingPullIntos]][0] is pullIntoDescriptor.
MOZ_ASSERT(aController->PendingPullIntos().isEmpty() ||
aController->PendingPullIntos().getFirst() == aPullIntoDescriptor);
// Step 2. Assert: controller.[[byobRequest]] is null.
MOZ_ASSERT(!aController->GetByobRequest());
// Step 3. Set pullIntoDescriptor’s bytes filled to bytes filled + size.
aPullIntoDescriptor->SetBytesFilled(aPullIntoDescriptor->BytesFilled() +
aSize);
}
MOZ_CAN_RUN_SCRIPT
static void ReadableByteStreamControllerRespondInReadableState(
JSContext* aCx, ReadableByteStreamController* aController,
uint64_t aBytesWritten, PullIntoDescriptor* aPullIntoDescriptor,
ErrorResult& aRv) {
// Step 1. Assert: pullIntoDescriptor’s bytes filled + bytesWritten ≤
// pullIntoDescriptor’s byte length.
MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() + aBytesWritten <=
aPullIntoDescriptor->ByteLength());
// Step 2. Perform
// !ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
// bytesWritten, pullIntoDescriptor).
ReadableByteStreamControllerFillHeadPullIntoDescriptor(
aController, aBytesWritten, aPullIntoDescriptor);
// Step 3. If pullIntoDescriptor’s reader type is "none",
if (aPullIntoDescriptor->GetReaderType() == ReaderType::None) {
// Step 3.1. Perform ?
// ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller,
// pullIntoDescriptor).
ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(
aCx, aController, aPullIntoDescriptor, aRv);
if (aRv.Failed()) {
return;
}
// Step 3.2. Perform !
// ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
aCx, aController, aRv);
// Step 3.3. Return.
return;
}
// Step 4. If pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s element
// size, return.
if (aPullIntoDescriptor->BytesFilled() < aPullIntoDescriptor->ElementSize()) {
return;
}
// Step 5. Perform
// !ReadableByteStreamControllerShiftPendingPullInto(controller).
RefPtr<PullIntoDescriptor> pullIntoDescriptor =
ReadableByteStreamControllerShiftPendingPullInto(aController);
(void)pullIntoDescriptor;
// Step 6. Let remainderSize be pullIntoDescriptor’s bytes filled mod
// pullIntoDescriptor’s element size.
size_t remainderSize =
aPullIntoDescriptor->BytesFilled() % aPullIntoDescriptor->ElementSize();
// Step 7. If remainderSize > 0,
if (remainderSize > 0) {
// Step 7.1. Let end be pullIntoDescriptor’s byte offset +
// pullIntoDescriptor’s bytes filled.
size_t end =
aPullIntoDescriptor->ByteOffset() + aPullIntoDescriptor->BytesFilled();
// Step 7.2. Perform ?
// ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller,
// pullIntoDescriptor’s buffer, end − remainderSize, remainderSize).
JS::Rooted<JSObject*> pullIntoBuffer(aCx, aPullIntoDescriptor->Buffer());
ReadableByteStreamControllerEnqueueClonedChunkToQueue(
aCx, aController, pullIntoBuffer, end - remainderSize, remainderSize,
aRv);
if (aRv.Failed()) {
return;
}
}
// Step 8. Set pullIntoDescriptor’s bytes filled to pullIntoDescriptor’s bytes
// filled − remainderSize.
aPullIntoDescriptor->SetBytesFilled(aPullIntoDescriptor->BytesFilled() -
remainderSize);
// Step 9. Perform
// !ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]],
// pullIntoDescriptor).
RefPtr<ReadableStream> stream(aController->Stream());
ReadableByteStreamControllerCommitPullIntoDescriptor(
aCx, stream, aPullIntoDescriptor, aRv);
if (aRv.Failed()) {
return;
}
// Step 10. Perform
// !ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
aCx, aController, aRv);
}
void ReadableByteStreamControllerRespondInternal(
JSContext* aCx, ReadableByteStreamController* aController,
uint64_t aBytesWritten, ErrorResult& aRv) {
// Step 1.
RefPtr<PullIntoDescriptor> firstDescriptor =
aController->PendingPullIntos().getFirst();
// Step 2.
JS::Rooted<JSObject*> buffer(aCx, firstDescriptor->Buffer());
#ifdef DEBUG
bool canTransferBuffer = CanTransferArrayBuffer(aCx, buffer, aRv);
MOZ_ASSERT(!aRv.Failed());
MOZ_ASSERT(canTransferBuffer);
#endif
// Step 3.
ReadableByteStreamControllerInvalidateBYOBRequest(aController);
// Step 4.
auto state = aController->Stream()->State();
// Step 5.
if (state == ReadableStream::ReaderState::Closed) {
// Step 5.1
MOZ_ASSERT(aBytesWritten == 0);
// Step 5.2
ReadableByteStreamControllerRespondInClosedState(aCx, aController,
firstDescriptor, aRv);
if (aRv.Failed()) {
return;
}
} else {
// Step 6.1
MOZ_ASSERT(state == ReadableStream::ReaderState::Readable);
// Step 6.2.
MOZ_ASSERT(aBytesWritten > 0);
// Step 6.3
ReadableByteStreamControllerRespondInReadableState(
aCx, aController, aBytesWritten, firstDescriptor, aRv);
if (aRv.Failed()) {
return;
}
}
// Step 7.
ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv);
}
void ReadableByteStreamControllerRespond(
JSContext* aCx, ReadableByteStreamController* aController,
uint64_t aBytesWritten, ErrorResult& aRv) {
// Step 1.
MOZ_ASSERT(!aController->PendingPullIntos().isEmpty());
// Step 2.
PullIntoDescriptor* firstDescriptor =
aController->PendingPullIntos().getFirst();
// Step 3.
auto state = aController->Stream()->State();
// Step 4.
if (state == ReadableStream::ReaderState::Closed) {
// Step 4.1
if (aBytesWritten != 0) {
aRv.ThrowTypeError("bytesWritten not zero on closed stream");
return;
}
} else {
// Step 5.1
MOZ_ASSERT(state == ReadableStream::ReaderState::Readable);
// Step 5.2
if (aBytesWritten == 0) {
aRv.ThrowTypeError("bytesWritten 0");
return;
}
// Step 5.3
if (firstDescriptor->BytesFilled() + aBytesWritten >
firstDescriptor->ByteLength()) {
aRv.ThrowRangeError("bytesFilled + bytesWritten > byteLength");
return;
}
}
// Step 6.
aRv.MightThrowJSException();
JS::Rooted<JSObject*> buffer(aCx, firstDescriptor->Buffer());
JS::Rooted<JSObject*> transferredBuffer(aCx,
TransferArrayBuffer(aCx, buffer));
if (!transferredBuffer) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
firstDescriptor->SetBuffer(