#include "builtin/Stream.h"
#include "js/Stream.h"
#include "gc/Heap.h"
#include "js/ArrayBuffer.h"
#include "js/PropertySpec.h"
#include "vm/Interpreter.h"
#include "vm/JSContext.h"
#include "vm/SelfHosting.h"
#include "vm/Compartment-inl.h"
#include "vm/List-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
enum ReaderType { ReaderType_Default, ReaderType_BYOB };
template <class T>
bool Is(const HandleValue v) {
return v.isObject() && v.toObject().is<T>();
}
template <class T>
bool IsMaybeWrapped(const HandleValue v) {
return v.isObject() && v.toObject().canUnwrapAs<T>();
}
JS::ReadableStreamMode ReadableStream::mode() const {
ReadableStreamController* controller = this->controller();
if (controller->is<ReadableStreamDefaultController>()) {
return JS::ReadableStreamMode::Default;
}
return controller->as<ReadableByteStreamController>().hasExternalSource()
? JS::ReadableStreamMode::ExternalSource
: JS::ReadableStreamMode::Byte;
}
static MOZ_MUST_USE ReadableStream* UnwrapStreamFromReader(
JSContext* cx, Handle<ReadableStreamReader*> reader) {
MOZ_ASSERT(reader->hasStream());
return UnwrapInternalSlot<ReadableStream>(cx, reader,
ReadableStreamReader::Slot_Stream);
}
static MOZ_MUST_USE ReadableStreamReader* UnwrapReaderFromStream(
JSContext* cx, Handle<ReadableStream*> stream) {
return UnwrapInternalSlot<ReadableStreamReader>(cx, stream,
ReadableStream::Slot_Reader);
}
static MOZ_MUST_USE ReadableStreamReader* UnwrapReaderFromStreamNoThrow(
ReadableStream* stream) {
JSObject* readerObj =
&stream->getFixedSlot(ReadableStream::Slot_Reader).toObject();
if (IsProxy(readerObj)) {
if (JS_IsDeadWrapper(readerObj)) {
return nullptr;
}
readerObj = readerObj->maybeUnwrapAs<ReadableStreamReader>();
if (!readerObj) {
return nullptr;
}
}
return &readerObj->as<ReadableStreamReader>();
}
constexpr size_t StreamHandlerFunctionSlot_Target = 0;
inline static MOZ_MUST_USE JSFunction* NewHandler(JSContext* cx, Native handler,
HandleObject target) {
cx->check(target);
HandlePropertyName funName = cx->names().empty;
RootedFunction handlerFun(
cx, NewNativeFunction(cx, handler, 0, funName,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject));
if (!handlerFun) {
return nullptr;
}
handlerFun->setExtendedSlot(StreamHandlerFunctionSlot_Target,
ObjectValue(*target));
return handlerFun;
}
template <class T>
inline static MOZ_MUST_USE T* TargetFromHandler(CallArgs& args) {
JSFunction& func = args.callee().as<JSFunction>();
return &func.getExtendedSlot(StreamHandlerFunctionSlot_Target)
.toObject()
.as<T>();
}
inline static MOZ_MUST_USE bool ResetQueue(
JSContext* cx, Handle<ReadableStreamController*> unwrappedContainer);
inline static MOZ_MUST_USE bool InvokeOrNoop(JSContext* cx, HandleValue O,
HandlePropertyName P,
HandleValue arg,
MutableHandleValue rval);
static MOZ_MUST_USE JSObject* PromiseRejectedWithPendingError(JSContext* cx) {
RootedValue exn(cx);
if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
return nullptr;
}
return PromiseObject::unforgeableReject(cx, exn);
}
static MOZ_MUST_USE bool ReturnPromiseRejectedWithPendingError(
JSContext* cx, const CallArgs& args) {
JSObject* promise = PromiseRejectedWithPendingError(cx);
if (!promise) {
return false;
}
args.rval().setObject(*promise);
return true;
}
inline static MOZ_MUST_USE bool SetNewList(
JSContext* cx, HandleNativeObject unwrappedContainer, uint32_t slot) {
AutoRealm ar(cx, unwrappedContainer);
ListObject* list = ListObject::create(cx);
if (!list) {
return false;
}
unwrappedContainer->setFixedSlot(slot, ObjectValue(*list));
return true;
}
#if 0#endif
class PullIntoDescriptor : public NativeObject {
private:
enum Slots {
Slot_buffer,
Slot_ByteOffset,
Slot_ByteLength,
Slot_BytesFilled,
Slot_ElementSize,
Slot_Ctor,
Slot_ReaderType,
SlotCount
};
public:
static const Class class_;
ArrayBufferObject* buffer() {
return &getFixedSlot(Slot_buffer).toObject().as<ArrayBufferObject>();
}
void setBuffer(ArrayBufferObject* buffer) {
setFixedSlot(Slot_buffer, ObjectValue(*buffer));
}
JSObject* ctor() { return getFixedSlot(Slot_Ctor).toObjectOrNull(); }
uint32_t byteOffset() const {
return getFixedSlot(Slot_ByteOffset).toInt32();
}
uint32_t byteLength() const {
return getFixedSlot(Slot_ByteLength).toInt32();
}
uint32_t bytesFilled() const {
return getFixedSlot(Slot_BytesFilled).toInt32();
}
void setBytesFilled(int32_t bytes) {
setFixedSlot(Slot_BytesFilled, Int32Value(bytes));
}
uint32_t elementSize() const {
return getFixedSlot(Slot_ElementSize).toInt32();
}
uint32_t readerType() const {
return getFixedSlot(Slot_ReaderType).toInt32();
}
static PullIntoDescriptor* create(JSContext* cx,
HandleArrayBufferObject buffer,
uint32_t byteOffset, uint32_t byteLength,
uint32_t bytesFilled, uint32_t elementSize,
HandleObject ctor, uint32_t readerType) {
Rooted<PullIntoDescriptor*> descriptor(
cx, NewBuiltinClassInstance<PullIntoDescriptor>(cx));
if (!descriptor) {
return nullptr;
}
descriptor->setFixedSlot(Slot_buffer, ObjectValue(*buffer));
descriptor->setFixedSlot(Slot_Ctor, ObjectOrNullValue(ctor));
descriptor->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset));
descriptor->setFixedSlot(Slot_ByteLength, Int32Value(byteLength));
descriptor->setFixedSlot(Slot_BytesFilled, Int32Value(bytesFilled));
descriptor->setFixedSlot(Slot_ElementSize, Int32Value(elementSize));
descriptor->setFixedSlot(Slot_ReaderType, Int32Value(readerType));
return descriptor;
}
};
const Class PullIntoDescriptor::class_ = {
"PullIntoDescriptor", JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
class QueueEntry : public NativeObject {
private:
enum Slots { Slot_Value = 0, Slot_Size, SlotCount };
public:
static const Class class_;
Value value() { return getFixedSlot(Slot_Value); }
double size() { return getFixedSlot(Slot_Size).toNumber(); }
static QueueEntry* create(JSContext* cx, HandleValue value, double size) {
Rooted<QueueEntry*> entry(cx, NewBuiltinClassInstance<QueueEntry>(cx));
if (!entry) {
return nullptr;
}
entry->setFixedSlot(Slot_Value, value);
entry->setFixedSlot(Slot_Size, NumberValue(size));
return entry;
}
};
const Class QueueEntry::class_ = {"QueueEntry",
JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
class TeeState : public NativeObject {
public:
enum Slots {
Slot_Flags = 0,
Slot_Reason1,
Slot_Reason2,
Slot_CancelPromise,
Slot_Stream,
Slot_Branch1,
Slot_Branch2,
SlotCount
};
private:
enum Flags {
Flag_ClosedOrErrored = 1 << 0,
Flag_Canceled1 = 1 << 1,
Flag_Canceled2 = 1 << 2,
Flag_CloneForBranch2 = 1 << 3,
};
uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); }
public:
static const Class class_;
bool cloneForBranch2() const { return flags() & Flag_CloneForBranch2; }
bool closedOrErrored() const { return flags() & Flag_ClosedOrErrored; }
void setClosedOrErrored() {
MOZ_ASSERT(!(flags() & Flag_ClosedOrErrored));
setFlags(flags() | Flag_ClosedOrErrored);
}
bool canceled1() const { return flags() & Flag_Canceled1; }
void setCanceled1(HandleValue reason) {
MOZ_ASSERT(!(flags() & Flag_Canceled1));
setFlags(flags() | Flag_Canceled1);
setFixedSlot(Slot_Reason1, reason);
}
bool canceled2() const { return flags() & Flag_Canceled2; }
void setCanceled2(HandleValue reason) {
MOZ_ASSERT(!(flags() & Flag_Canceled2));
setFlags(flags() | Flag_Canceled2);
setFixedSlot(Slot_Reason2, reason);
}
Value reason1() const {
MOZ_ASSERT(canceled1());
return getFixedSlot(Slot_Reason1);
}
Value reason2() const {
MOZ_ASSERT(canceled2());
return getFixedSlot(Slot_Reason2);
}
PromiseObject* cancelPromise() {
return &getFixedSlot(Slot_CancelPromise).toObject().as<PromiseObject>();
}
ReadableStreamDefaultController* branch1() {
ReadableStreamDefaultController* controller =
&getFixedSlot(Slot_Branch1)
.toObject()
.as<ReadableStreamDefaultController>();
MOZ_ASSERT(controller->isTeeBranch1());
return controller;
}
void setBranch1(ReadableStreamDefaultController* controller) {
MOZ_ASSERT(controller->isTeeBranch1());
setFixedSlot(Slot_Branch1, ObjectValue(*controller));
}
ReadableStreamDefaultController* branch2() {
ReadableStreamDefaultController* controller =
&getFixedSlot(Slot_Branch2)
.toObject()
.as<ReadableStreamDefaultController>();
MOZ_ASSERT(controller->isTeeBranch2());
return controller;
}
void setBranch2(ReadableStreamDefaultController* controller) {
MOZ_ASSERT(controller->isTeeBranch2());
setFixedSlot(Slot_Branch2, ObjectValue(*controller));
}
static TeeState* create(JSContext* cx,
Handle<ReadableStream*> unwrappedStream) {
Rooted<TeeState*> state(cx, NewBuiltinClassInstance<TeeState>(cx));
if (!state) {
return nullptr;
}
Rooted<PromiseObject*> cancelPromise(
cx, PromiseObject::createSkippingExecutor(cx));
if (!cancelPromise) {
return nullptr;
}
state->setFixedSlot(Slot_Flags, Int32Value(0));
state->setFixedSlot(Slot_CancelPromise, ObjectValue(*cancelPromise));
RootedObject wrappedStream(cx, unwrappedStream);
if (!cx->compartment()->wrap(cx, &wrappedStream)) {
return nullptr;
}
state->setFixedSlot(Slot_Stream, ObjectValue(*wrappedStream));
return state;
}
};
const Class TeeState::class_ = {"TeeState",
JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
#define CLASS_SPEC(cls, nCtorArgs, nSlots, specFlags, classFlags, classOps) \
const ClassSpec cls::classSpec_ = { \
GenericCreateConstructor<cls::constructor, nCtorArgs, \
gc::AllocKind::FUNCTION>, \
GenericCreatePrototype<cls>, \
nullptr, \
nullptr, \
cls##_methods, \
cls##_properties, \
nullptr, \
specFlags}; \
\
const Class cls::class_ = {#cls, \
JSCLASS_HAS_RESERVED_SLOTS(nSlots) | \
JSCLASS_HAS_CACHED_PROTO(JSProto_##cls) | \
classFlags, \
classOps, &cls::classSpec_}; \
\
const Class cls::protoClass_ = {"object", \
JSCLASS_HAS_CACHED_PROTO(JSProto_##cls), \
JS_NULL_CLASS_OPS, &cls::classSpec_};
static MOZ_MUST_USE bool SetUpExternalReadableByteStreamController(
JSContext* cx, Handle<ReadableStream*> stream,
JS::ReadableStreamUnderlyingSource* source);
ReadableStream* ReadableStream::createExternalSourceStream(
JSContext* cx, JS::ReadableStreamUnderlyingSource* source,
HandleObject proto ) {
Rooted<ReadableStream*> stream(cx, create(cx, proto));
if (!stream) {
return nullptr;
}
if (!SetUpExternalReadableByteStreamController(cx, stream, source)) {
return nullptr;
}
return stream;
}
static MOZ_MUST_USE bool MakeSizeAlgorithmFromSizeFunction(JSContext* cx,
HandleValue size);
static MOZ_MUST_USE bool ValidateAndNormalizeHighWaterMark(
JSContext* cx, HandleValue highWaterMarkVal, double* highWaterMark);
static MOZ_MUST_USE bool
SetUpReadableStreamDefaultControllerFromUnderlyingSource(
JSContext* cx, Handle<ReadableStream*> stream, HandleValue underlyingSource,
double highWaterMark, HandleValue sizeAlgorithm);
bool ReadableStream::constructor(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "ReadableStream")) {
return false;
}
RootedValue underlyingSource(cx, args.get(0));
if (underlyingSource.isUndefined()) {
JSObject* emptyObj = NewBuiltinClassInstance<PlainObject>(cx);
if (!emptyObj) {
return false;
}
underlyingSource = ObjectValue(*emptyObj);
}
RootedValue strategy(cx, args.get(1));
if (strategy.isUndefined()) {
JSObject* emptyObj = NewBuiltinClassInstance<PlainObject>(cx);
if (!emptyObj) {
return false;
}
strategy = ObjectValue(*emptyObj);
}
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ReadableStream,
&proto)) {
return false;
}
Rooted<ReadableStream*> stream(cx, ReadableStream::create(cx, proto));
if (!stream) {
return false;
}
RootedValue size(cx);
if (!GetProperty(cx, strategy, cx->names().size, &size)) {
return false;
}
RootedValue highWaterMarkVal(cx);
if (!GetProperty(cx, strategy, cx->names().highWaterMark,
&highWaterMarkVal)) {
return false;
}
RootedValue type(cx);
if (!GetProperty(cx, underlyingSource, cx->names().type, &type)) {
return false;
}
RootedString typeString(cx, ToString<CanGC>(cx, type));
if (!typeString) {
return false;
}
bool equal;
if (!EqualStrings(cx, typeString, cx->names().bytes, &equal)) {
return false;
}
if (equal) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED);
return false;
}
if (type.isUndefined()) {
if (!MakeSizeAlgorithmFromSizeFunction(cx, size)) {
return false;
}
double highWaterMark;
if (highWaterMarkVal.isUndefined()) {
highWaterMark = 1;
} else {
if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal,
&highWaterMark)) {
return false;
}
}
if (!SetUpReadableStreamDefaultControllerFromUnderlyingSource(
cx, stream, underlyingSource, highWaterMark, size)) {
return false;
}
args.rval().setObject(*stream);
return true;
}
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG);
return false;
}
static bool ReadableStream_locked(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "get locked"));
if (!unwrappedStream) {
return false;
}
args.rval().setBoolean(unwrappedStream->locked());
return true;
}
static MOZ_MUST_USE JSObject* ReadableStreamCancel(
JSContext* cx, Handle<ReadableStream*> unwrappedStream, HandleValue reason);
static MOZ_MUST_USE bool ReadableStream_cancel(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "cancel"));
if (!unwrappedStream) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}
if (unwrappedStream->locked()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_LOCKED_METHOD, "cancel");
return ReturnPromiseRejectedWithPendingError(cx, args);
}
RootedObject cancelPromise(
cx, ::ReadableStreamCancel(cx, unwrappedStream, args.get(0)));
if (!cancelPromise) {
return false;
}
args.rval().setObject(*cancelPromise);
return true;
}
static MOZ_MUST_USE ReadableStreamDefaultReader*
CreateReadableStreamDefaultReader(
JSContext* cx, Handle<ReadableStream*> unwrappedStream,
ForAuthorCodeBool forAuthorCode = ForAuthorCodeBool::No,
HandleObject proto = nullptr);
static bool ReadableStream_getReader(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
RootedValue optionsVal(cx, args.get(0));
if (optionsVal.isUndefined()) {
JSObject* emptyObj = NewBuiltinClassInstance<PlainObject>(cx);
if (!emptyObj) {
return false;
}
optionsVal.setObject(*emptyObj);
}
RootedValue modeVal(cx);
if (!GetProperty(cx, optionsVal, cx->names().mode, &modeVal)) {
return false;
}
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "getReader"));
if (!unwrappedStream) {
return false;
}
RootedObject reader(cx);
if (modeVal.isUndefined()) {
reader = CreateReadableStreamDefaultReader(cx, unwrappedStream,
ForAuthorCodeBool::Yes);
} else {
RootedString mode(cx, ToString<CanGC>(cx, modeVal));
if (!mode) {
return false;
}
bool equal;
if (!EqualStrings(cx, mode, cx->names().byob, &equal)) {
return false;
}
if (equal) {
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED);
return false;
}
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_INVALID_READER_MODE);
return false;
}
if (!reader) {
return false;
}
args.rval().setObject(*reader);
return true;
}
static MOZ_MUST_USE bool ReadableStreamTee(
JSContext* cx, Handle<ReadableStream*> unwrappedStream,
bool cloneForBranch2, MutableHandle<ReadableStream*> branch1,
MutableHandle<ReadableStream*> branch2);
static bool ReadableStream_tee(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "tee"));
if (!unwrappedStream) {
return false;
}
Rooted<ReadableStream*> branch1(cx);
Rooted<ReadableStream*> branch2(cx);
if (!ReadableStreamTee(cx, unwrappedStream, false, &branch1, &branch2)) {
return false;
}
RootedNativeObject branches(cx, NewDenseFullyAllocatedArray(cx, 2));
if (!branches) {
return false;
}
branches->setDenseInitializedLength(2);
branches->initDenseElement(0, ObjectValue(*branch1));
branches->initDenseElement(1, ObjectValue(*branch2));
args.rval().setObject(*branches);
return true;
}
static const JSFunctionSpec ReadableStream_methods[] = {
JS_FN("cancel", ReadableStream_cancel, 1, 0),
JS_FN("getReader", ReadableStream_getReader, 0, 0),
JS_FN("tee", ReadableStream_tee, 0, 0), JS_FS_END};
static const JSPropertySpec ReadableStream_properties[] = {
JS_PSG("locked", ReadableStream_locked, 0), JS_PS_END};
CLASS_SPEC(ReadableStream, 0, SlotCount, 0, 0, JS_NULL_CLASS_OPS);
enum class SourceAlgorithms {
Script,
Tee,
};
static MOZ_MUST_USE bool SetUpReadableStreamDefaultController(
JSContext* cx, Handle<ReadableStream*> stream, SourceAlgorithms algorithms,
HandleValue underlyingSource, HandleValue pullMethod,
HandleValue cancelMethod, double highWaterMark, HandleValue size);
MOZ_MUST_USE ReadableStream* CreateReadableStream(
JSContext* cx, SourceAlgorithms sourceAlgorithms,
HandleValue underlyingSource, HandleValue pullMethod = UndefinedHandleValue,
HandleValue cancelMethod = UndefinedHandleValue, double highWaterMark = 1,
HandleValue sizeAlgorithm = UndefinedHandleValue,
HandleObject proto = nullptr) {
cx->check(underlyingSource, sizeAlgorithm, proto);
MOZ_ASSERT(sizeAlgorithm.isUndefined() || IsCallable(sizeAlgorithm));
MOZ_ASSERT(highWaterMark >= 0);
Rooted<ReadableStream*> stream(cx, ReadableStream::create(cx, proto));
if (!stream) {
return nullptr;
}
if (!SetUpReadableStreamDefaultController(
cx, stream, sourceAlgorithms, underlyingSource, pullMethod,
cancelMethod, highWaterMark, sizeAlgorithm)) {
return nullptr;
}
return stream;
}
MOZ_MUST_USE
ReadableStream*
ReadableStream::create(JSContext* cx, HandleObject proto ) {
Rooted<ReadableStream*> stream(
cx, NewObjectWithClassProto<ReadableStream>(cx, proto));
if (!stream) {
return nullptr;
}
stream->initStateBits(Readable);
MOZ_ASSERT(stream->readable());
MOZ_ASSERT(!stream->hasReader());
MOZ_ASSERT(stream->storedError().isUndefined());
MOZ_ASSERT(!stream->disturbed());
return stream;
}
bool ReadableStream::locked() const {
if (hasController() && controller()->sourceLocked()) {
return true;
}
return hasReader();
}
static MOZ_MUST_USE bool ReadableStreamDefaultControllerClose(
JSContext* cx,
Handle<ReadableStreamDefaultController*> unwrappedController);
static MOZ_MUST_USE bool ReadableStreamDefaultControllerEnqueue(
JSContext* cx, Handle<ReadableStreamDefaultController*> unwrappedController,
HandleValue chunk);
static bool TeeReaderReadHandler(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<TeeState*> unwrappedTeeState(cx,
UnwrapCalleeSlot<TeeState>(cx, args, 0));
HandleValue resultVal = args.get(0);
RootedObject result(cx, &resultVal.toObject());
RootedValue value(cx);
if (!GetProperty(cx, result, result, cx->names().value, &value)) {
return false;
}
RootedValue doneVal(cx);
if (!GetProperty(cx, result, result, cx->names().done, &doneVal)) {
return false;
}
bool done = doneVal.toBoolean();
if (done && !unwrappedTeeState->closedOrErrored()) {
if (!unwrappedTeeState->canceled1()) {
Rooted<ReadableStreamDefaultController*> unwrappedBranch1(
cx, unwrappedTeeState->branch1());
if (!ReadableStreamDefaultControllerClose(cx, unwrappedBranch1)) {
return false;
}
}
if (!unwrappedTeeState->canceled2()) {
Rooted<ReadableStreamDefaultController*> unwrappedBranch2(
cx, unwrappedTeeState->branch2());
if (!ReadableStreamDefaultControllerClose(cx, unwrappedBranch2)) {
return false;
}
}
unwrappedTeeState->setClosedOrErrored();
}
if (unwrappedTeeState->closedOrErrored()) {
return true;
}
RootedValue value1(cx, value);
RootedValue value2(cx, value);
MOZ_ASSERT(!unwrappedTeeState->cloneForBranch2());
Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
if (!unwrappedTeeState->canceled1()) {
unwrappedController = unwrappedTeeState->branch1();
if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
value1)) {
return false;
}
}
if (!unwrappedTeeState->canceled2()) {
unwrappedController = unwrappedTeeState->branch2();
if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
value2)) {
return false;
}
}
args.rval().setUndefined();
return true;
}
static MOZ_MUST_USE JSObject* ReadableStreamDefaultReaderRead(
JSContext* cx, Handle<ReadableStreamDefaultReader*> unwrappedReader);
static MOZ_MUST_USE JSObject* ReadableStreamTee_Pull(
JSContext* cx, Handle<TeeState*> unwrappedTeeState) {
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapInternalSlot<ReadableStream>(cx, unwrappedTeeState,
TeeState::Slot_Stream));
if (!unwrappedStream) {
return nullptr;
}
Rooted<ReadableStreamReader*> unwrappedReaderObj(
cx, UnwrapReaderFromStream(cx, unwrappedStream));
if (!unwrappedReaderObj) {
return nullptr;
}
Rooted<ReadableStreamDefaultReader*> unwrappedReader(
cx, &unwrappedReaderObj->as<ReadableStreamDefaultReader>());
RootedObject readPromise(
cx, ::ReadableStreamDefaultReaderRead(cx, unwrappedReader));
if (!readPromise) {
return nullptr;
}
RootedObject teeState(cx, unwrappedTeeState);
if (!cx->compartment()->wrap(cx, &teeState)) {
return nullptr;
}
RootedObject onFulfilled(cx, NewHandler(cx, TeeReaderReadHandler, teeState));
if (!onFulfilled) {
return nullptr;
}
return JS::CallOriginalPromiseThen(cx, readPromise, onFulfilled, nullptr);
}
static MOZ_MUST_USE JSObject* ReadableStreamTee_Cancel(
JSContext* cx, Handle<TeeState*> unwrappedTeeState,
Handle<ReadableStreamDefaultController*> unwrappedBranch,
HandleValue reason) {
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapInternalSlot<ReadableStream>(cx, unwrappedTeeState,
TeeState::Slot_Stream));
if (!unwrappedStream) {
return nullptr;
}
bool bothBranchesCanceled = false;
{
RootedValue unwrappedReason(cx, reason);
{
AutoRealm ar(cx, unwrappedTeeState);
if (!cx->compartment()->wrap(cx, &unwrappedReason)) {
return nullptr;
}
}
if (unwrappedBranch->isTeeBranch1()) {
unwrappedTeeState->setCanceled1(unwrappedReason);
bothBranchesCanceled = unwrappedTeeState->canceled2();
} else {
MOZ_ASSERT(unwrappedBranch->isTeeBranch2());
unwrappedTeeState->setCanceled2(unwrappedReason);
bothBranchesCanceled = unwrappedTeeState->canceled1();
}
}
if (bothBranchesCanceled) {
RootedNativeObject compositeReason(cx, NewDenseFullyAllocatedArray(cx, 2));
if (!compositeReason) {
return nullptr;
}
compositeReason->setDenseInitializedLength(2);
RootedValue reason1(cx, unwrappedTeeState->reason1());
RootedValue reason2(cx, unwrappedTeeState->reason2());
if (!cx->compartment()->wrap(cx, &reason1) ||
!cx->compartment()->wrap(cx, &reason2)) {
return nullptr;
}
compositeReason->initDenseElement(0, reason1);
compositeReason->initDenseElement(1, reason2);
RootedValue compositeReasonVal(cx, ObjectValue(*compositeReason));
RootedObject cancelResult(
cx, ::ReadableStreamCancel(cx, unwrappedStream, compositeReasonVal));
{
Rooted<PromiseObject*> cancelPromise(cx,
unwrappedTeeState->cancelPromise());
AutoRealm ar(cx, cancelPromise);
if (!cancelResult) {
if (!RejectPromiseWithPendingError(cx, cancelPromise)) {
return nullptr;
}
} else {
RootedValue resultVal(cx, ObjectValue(*cancelResult));
if (!cx->compartment()->wrap(cx, &resultVal)) {
return nullptr;
}
if (!PromiseObject::resolve(cx, cancelPromise, resultVal)) {
return nullptr;
}
}
}
}
RootedObject cancelPromise(cx, unwrappedTeeState->cancelPromise());
if (!cx->compartment()->wrap(cx, &cancelPromise)) {
return nullptr;
}
return cancelPromise;
}
static MOZ_MUST_USE bool ReadableStreamControllerError(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
HandleValue e);
static bool TeeReaderErroredHandler(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args));
HandleValue reason = args.get(0);
if (!teeState->closedOrErrored()) {
teeState->setClosedOrErrored();
Rooted<ReadableStreamDefaultController*> branch1(cx, teeState->branch1());
if (!ReadableStreamControllerError(cx, branch1, reason)) {
return false;
}
Rooted<ReadableStreamDefaultController*> branch2(cx, teeState->branch2());
if (!ReadableStreamControllerError(cx, branch2, reason)) {
return false;
}
}
args.rval().setUndefined();
return true;
}
static MOZ_MUST_USE bool ReadableStreamTee(
JSContext* cx, Handle<ReadableStream*> unwrappedStream,
bool cloneForBranch2, MutableHandle<ReadableStream*> branch1Stream,
MutableHandle<ReadableStream*> branch2Stream) {
Rooted<ReadableStreamDefaultReader*> reader(
cx, CreateReadableStreamDefaultReader(cx, unwrappedStream));
if (!reader) {
return false;
}
Rooted<TeeState*> teeState(cx, TeeState::create(cx, unwrappedStream));
if (!teeState) {
return false;
}
RootedValue underlyingSource(cx, ObjectValue(*teeState));
branch1Stream.set(
CreateReadableStream(cx, SourceAlgorithms::Tee, underlyingSource));
if (!branch1Stream) {
return false;
}
Rooted<ReadableStreamDefaultController*> branch1(cx);
branch1 = &branch1Stream->controller()->as<ReadableStreamDefaultController>();
branch1->setTeeBranch1();
teeState->setBranch1(branch1);
branch2Stream.set(
CreateReadableStream(cx, SourceAlgorithms::Tee, underlyingSource));
if (!branch2Stream) {
return false;
}
Rooted<ReadableStreamDefaultController*> branch2(cx);
branch2 = &branch2Stream->controller()->as<ReadableStreamDefaultController>();
branch2->setTeeBranch2();
teeState->setBranch2(branch2);
RootedObject closedPromise(cx, reader->closedPromise());
RootedObject onRejected(cx,
NewHandler(cx, TeeReaderErroredHandler, teeState));
if (!onRejected) {
return false;
}
if (!JS::AddPromiseReactions(cx, closedPromise, nullptr, onRejected)) {
return false;
}
return true;
}
inline static MOZ_MUST_USE bool AppendToListAtSlot(
JSContext* cx, HandleNativeObject unwrappedContainer, uint32_t slot,
HandleObject obj);
static MOZ_MUST_USE JSObject* ReadableStreamAddReadOrReadIntoRequest(
JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
Rooted<ReadableStreamReader*> unwrappedReader(
cx, UnwrapReaderFromStream(cx, unwrappedStream));
if (!unwrappedReader) {
return nullptr;
}
MOZ_ASSERT(unwrappedReader->is<ReadableStreamDefaultReader>());
MOZ_ASSERT(unwrappedStream->readable() || unwrappedStream->closed());
MOZ_ASSERT_IF(unwrappedReader->is<ReadableStreamDefaultReader>(),
unwrappedStream->readable());
RootedObject promise(cx, PromiseObject::createSkippingExecutor(cx));
if (!promise) {
return nullptr;
}
if (!AppendToListAtSlot(cx, unwrappedReader,
ReadableStreamReader::Slot_Requests, promise)) {
return nullptr;
}
return promise;
}
static MOZ_MUST_USE JSObject* ReadableStreamControllerCancelSteps(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
HandleValue reason);
static bool ReturnUndefined(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setUndefined();
return true;
}
MOZ_MUST_USE bool ReadableStreamCloseInternal(
JSContext* cx, Handle<ReadableStream*> unwrappedStream);
static MOZ_MUST_USE JSObject* ReadableStreamCancel(
JSContext* cx, Handle<ReadableStream*> unwrappedStream,
HandleValue reason) {
AssertSameCompartment(cx, reason);
unwrappedStream->setDisturbed();
if (unwrappedStream->closed()) {
return PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
}
if (unwrappedStream->errored()) {
RootedValue storedError(cx, unwrappedStream->storedError());
if (!cx->compartment()->wrap(cx, &storedError)) {
return nullptr;
}
return PromiseObject::unforgeableReject(cx, storedError);
}
if (!ReadableStreamCloseInternal(cx, unwrappedStream)) {
return nullptr;
}
Rooted<ReadableStreamController*> unwrappedController(
cx, unwrappedStream->controller());
RootedObject sourceCancelPromise(
cx, ReadableStreamControllerCancelSteps(cx, unwrappedController, reason));
if (!sourceCancelPromise) {
return nullptr;
}
HandlePropertyName funName = cx->names().empty;
RootedFunction returnUndefined(
cx, NewNativeFunction(cx, ReturnUndefined, 0, funName,
gc::AllocKind::FUNCTION, GenericObject));
if (!returnUndefined) {
return nullptr;
}
return JS::CallOriginalPromiseThen(cx, sourceCancelPromise, returnUndefined,
nullptr);
}
static MOZ_MUST_USE JSObject* ReadableStreamCreateReadResult(
JSContext* cx, HandleValue value, bool done,
ForAuthorCodeBool forAuthorCode);
MOZ_MUST_USE bool ReadableStreamCloseInternal(
JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
MOZ_ASSERT(unwrappedStream->readable());
unwrappedStream->setClosed();
if (!unwrappedStream->hasReader()) {
return true;
}
Rooted<ReadableStreamReader*> unwrappedReader(
cx, UnwrapReaderFromStream(cx, unwrappedStream));
if (!unwrappedReader) {
return false;
}
if (unwrappedReader->is<ReadableStreamDefaultReader>()) {
ForAuthorCodeBool forAuthorCode = unwrappedReader->forAuthorCode();
Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
uint32_t len = unwrappedReadRequests->length();
RootedObject readRequest(cx);
RootedObject resultObj(cx);
RootedValue resultVal(cx);
for (uint32_t i = 0; i < len; i++) {
readRequest = &unwrappedReadRequests->getAs<JSObject>(i);
if (!cx->compartment()->wrap(cx, &readRequest)) {
return false;
}
resultObj = ReadableStreamCreateReadResult(cx, UndefinedHandleValue, true,
forAuthorCode);
if (!resultObj) {
return false;
}
resultVal = ObjectValue(*resultObj);
if (!ResolvePromise(cx, readRequest, resultVal)) {
return false;
}
}
unwrappedReader->clearRequests();
}
RootedObject closedPromise(cx, unwrappedReader->closedPromise());
if (!cx->compartment()->wrap(cx, &closedPromise)) {
return false;
}
if (!ResolvePromise(cx, closedPromise, UndefinedHandleValue)) {
return false;
}
if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
AutoRealm ar(cx, unwrappedStream);
JS::ReadableStreamUnderlyingSource* source =
unwrappedStream->controller()->externalSource();
source->onClosed(cx, unwrappedStream);
}
return true;
}
static MOZ_MUST_USE JSObject* ReadableStreamCreateReadResult(
JSContext* cx, HandleValue value, bool done,
ForAuthorCodeBool forAuthorCode) {
RootedObject templateObject(
cx,
forAuthorCode == ForAuthorCodeBool::Yes
? cx->realm()->getOrCreateIterResultTemplateObject(cx)
: cx->realm()->getOrCreateIterResultWithoutPrototypeTemplateObject(
cx));
if (!templateObject) {
return nullptr;
}
NativeObject* obj;
JS_TRY_VAR_OR_RETURN_NULL(
cx, obj,
NativeObject::createWithTemplate(cx, gc::DefaultHeap, templateObject));
obj->setSlot(Realm::IterResultObjectValueSlot, value);
obj->setSlot(Realm::IterResultObjectDoneSlot,
done ? TrueHandleValue : FalseHandleValue);
return obj;
}
MOZ_MUST_USE bool ReadableStreamErrorInternal(
JSContext* cx, Handle<ReadableStream*> unwrappedStream, HandleValue e) {
MOZ_ASSERT(unwrappedStream->readable());
unwrappedStream->setErrored();
{
AutoRealm ar(cx, unwrappedStream);
RootedValue wrappedError(cx, e);
if (!cx->compartment()->wrap(cx, &wrappedError)) {
return false;
}
unwrappedStream->setStoredError(wrappedError);
}
if (!unwrappedStream->hasReader()) {
return true;
}
Rooted<ReadableStreamReader*> unwrappedReader(
cx, UnwrapReaderFromStream(cx, unwrappedStream));
if (!unwrappedReader) {
return false;
}
Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
RootedObject readRequest(cx);
RootedValue val(cx);
uint32_t len = unwrappedReadRequests->length();
for (uint32_t i = 0; i < len; i++) {
val = unwrappedReadRequests->get(i);
readRequest = &val.toObject();
if (!cx->compartment()->wrap(cx, &readRequest)) {
return false;
}
if (!RejectPromise(cx, readRequest, e)) {
return false;
}
}
if (!SetNewList(cx, unwrappedReader, ReadableStreamReader::Slot_Requests)) {
return false;
}
RootedObject closedPromise(cx, unwrappedReader->closedPromise());
if (!cx->compartment()->wrap(cx, &closedPromise)) {
return false;
}
if (!RejectPromise(cx, closedPromise, e)) {
return false;
}
if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
AutoRealm ar(cx, unwrappedStream);
JS::ReadableStreamUnderlyingSource* source =
unwrappedStream->controller()->externalSource();
RootedValue error(cx, e);
if (!cx->compartment()->wrap(cx, &error)) {
return false;
}
source->onErrored(cx, unwrappedStream, error);
}
return true;
}
static MOZ_MUST_USE bool ReadableStreamFulfillReadOrReadIntoRequest(
JSContext* cx, Handle<ReadableStream*> unwrappedStream, HandleValue chunk,
bool done) {
cx->check(chunk);
Rooted<ReadableStreamReader*> unwrappedReader(
cx, UnwrapReaderFromStream(cx, unwrappedStream));
if (!unwrappedReader) {
return false;
}
Rooted<ListObject*> unwrappedReadIntoRequests(cx,
unwrappedReader->requests());
RootedObject readIntoRequest(
cx, &unwrappedReadIntoRequests->popFirstAs<JSObject>(cx));
MOZ_ASSERT(readIntoRequest);
if (!cx->compartment()->wrap(cx, &readIntoRequest)) {
return false;
}
RootedObject iterResult(
cx, ReadableStreamCreateReadResult(cx, chunk, done,
unwrappedReader->forAuthorCode()));
if (!iterResult) {
return false;
}
RootedValue val(cx, ObjectValue(*iterResult));
return ResolvePromise(cx, readIntoRequest, val);
}
static uint32_t ReadableStreamGetNumReadRequests(ReadableStream* stream) {
if (!stream->hasReader()) {
return 0;
}
JS::AutoSuppressGCAnalysis nogc;
ReadableStreamReader* reader = UnwrapReaderFromStreamNoThrow(stream);
if (!reader) {
return 0;
}
return reader->requests()->length();
}
static MOZ_MUST_USE bool ReadableStreamHasDefaultReader(
JSContext* cx, Handle<ReadableStream*> unwrappedStream, bool* result) {
if (!unwrappedStream->hasReader()) {
*result = false;
return true;
}
Rooted<ReadableStreamReader*> unwrappedReader(
cx, UnwrapReaderFromStream(cx, unwrappedStream));
if (!unwrappedReader) {
return false;
}
*result = unwrappedReader->is<ReadableStreamDefaultReader>();
return true;
}
static MOZ_MUST_USE bool ReadableStreamReaderGenericInitialize(
JSContext* cx, Handle<ReadableStreamReader*> reader,
Handle<ReadableStream*> unwrappedStream, ForAuthorCodeBool forAuthorCode);
static MOZ_MUST_USE ReadableStreamDefaultReader*
CreateReadableStreamDefaultReader(JSContext* cx,
Handle<ReadableStream*> unwrappedStream,
ForAuthorCodeBool forAuthorCode,
HandleObject proto ) {
Rooted<ReadableStreamDefaultReader*> reader(
cx, NewObjectWithClassProto<ReadableStreamDefaultReader>(cx, proto));
if (!reader) {
return nullptr;
}
if (unwrappedStream->locked()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_LOCKED);
return nullptr;
}
if (!ReadableStreamReaderGenericInitialize(cx, reader, unwrappedStream,
forAuthorCode)) {
return nullptr;
}
return reader;
}
bool ReadableStreamDefaultReader::constructor(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultReader")) {
return false;
}
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
return false;
}
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapAndTypeCheckArgument<ReadableStream>(
cx, args, "ReadableStreamDefaultReader constructor", 0));
if (!unwrappedStream) {
return false;
}
RootedObject reader(
cx, CreateReadableStreamDefaultReader(cx, unwrappedStream,
ForAuthorCodeBool::Yes, proto));
if (!reader) {
return false;
}
args.rval().setObject(*reader);
return true;
}
static MOZ_MUST_USE bool ReadableStreamDefaultReader_closed(JSContext* cx,
unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamDefaultReader*> unwrappedReader(
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args,
"get closed"));
if (!unwrappedReader) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}
RootedObject closedPromise(cx, unwrappedReader->closedPromise());
if (!cx->compartment()->wrap(cx, &closedPromise)) {
return false;
}
args.rval().setObject(*closedPromise);
return true;
}
static MOZ_MUST_USE JSObject* ReadableStreamReaderGenericCancel(
JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader,
HandleValue reason);
static MOZ_MUST_USE bool ReadableStreamDefaultReader_cancel(JSContext* cx,
unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamDefaultReader*> unwrappedReader(
cx,
UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args, "cancel"));
if (!unwrappedReader) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}
if (!unwrappedReader->hasStream()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
return ReturnPromiseRejectedWithPendingError(cx, args);
}
JSObject* cancelPromise =
ReadableStreamReaderGenericCancel(cx, unwrappedReader, args.get(0));
if (!cancelPromise) {
return false;
}
args.rval().setObject(*cancelPromise);
return true;
}
static MOZ_MUST_USE bool ReadableStreamDefaultReader_read(JSContext* cx,
unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamDefaultReader*> unwrappedReader(
cx,
UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args, "read"));
if (!unwrappedReader) {
return ReturnPromiseRejectedWithPendingError(cx, args);
}
if (!unwrappedReader->hasStream()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
return ReturnPromiseRejectedWithPendingError(cx, args);
}
JSObject* readPromise =
::ReadableStreamDefaultReaderRead(cx, unwrappedReader);
if (!readPromise) {
return false;
}
args.rval().setObject(*readPromise);
return true;
}
static MOZ_MUST_USE bool ReadableStreamReaderGenericRelease(
JSContext* cx, Handle<ReadableStreamReader*> reader);
static bool ReadableStreamDefaultReader_releaseLock(JSContext* cx,
unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamDefaultReader*> reader(
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args,
"releaseLock"));
if (!reader) {
return false;
}
if (!reader->hasStream()) {
args.rval().setUndefined();
return true;
}
Value val = reader->getFixedSlot(ReadableStreamReader::Slot_Requests);
if (!val.isUndefined()) {
ListObject* readRequests = &val.toObject().as<ListObject>();
if (readRequests->length() != 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMREADER_NOT_EMPTY,
"releaseLock");
return false;
}
}
if (!ReadableStreamReaderGenericRelease(cx, reader)) {
return false;
}
args.rval().setUndefined();
return true;
}
static const JSFunctionSpec ReadableStreamDefaultReader_methods[] = {
JS_FN("cancel", ReadableStreamDefaultReader_cancel, 1, 0),
JS_FN("read", ReadableStreamDefaultReader_read, 0, 0),
JS_FN("releaseLock", ReadableStreamDefaultReader_releaseLock, 0, 0),
JS_FS_END};
static const JSPropertySpec ReadableStreamDefaultReader_properties[] = {
JS_PSG("closed", ReadableStreamDefaultReader_closed, 0), JS_PS_END};
const Class ReadableStreamReader::class_ = {"ReadableStreamReader"};
CLASS_SPEC(ReadableStreamDefaultReader, 1, SlotCount,
ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
static MOZ_MUST_USE JSObject* ReadableStreamReaderGenericCancel(
JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader,
HandleValue reason) {
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapStreamFromReader(cx, unwrappedReader));
if (!unwrappedStream) {
return nullptr;
}
return ::ReadableStreamCancel(cx, unwrappedStream, reason);
}
static MOZ_MUST_USE bool ReadableStreamReaderGenericInitialize(
JSContext* cx, Handle<ReadableStreamReader*> reader,
Handle<ReadableStream*> unwrappedStream, ForAuthorCodeBool forAuthorCode) {
cx->check(reader);
{
RootedObject readerCompartmentStream(cx, unwrappedStream);
if (!cx->compartment()->wrap(cx, &readerCompartmentStream)) {
return false;
}
reader->setStream(readerCompartmentStream);
}
RootedObject promise(cx);
if (unwrappedStream->readable()) {
promise = PromiseObject::createSkippingExecutor(cx);
} else if (unwrappedStream->closed()) {
promise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
} else {
MOZ_ASSERT(unwrappedStream->errored());
RootedValue storedError(cx, unwrappedStream->storedError());
if (!cx->compartment()->wrap(cx, &storedError)) {
return false;
}
promise = PromiseObject::unforgeableReject(cx, storedError);
if (!promise) {
return false;
}
promise->as<PromiseObject>().setHandled();
cx->runtime()->removeUnhandledRejectedPromise(cx, promise);
}
if (!promise) {
return false;
}
reader->setClosedPromise(promise);
reader->setForAuthorCode(forAuthorCode);
if (!SetNewList(cx, reader, ReadableStreamReader::Slot_Requests)) {
return false;
}
{
AutoRealm ar(cx, unwrappedStream);
RootedObject streamCompartmentReader(cx, reader);
if (!cx->compartment()->wrap(cx, &streamCompartmentReader)) {
return false;
}
unwrappedStream->setReader(streamCompartmentReader);
}
return true;
}
static MOZ_MUST_USE bool ReadableStreamReaderGenericRelease(
JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader) {
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapStreamFromReader(cx, unwrappedReader));
if (!unwrappedStream) {
return false;
}
#ifdef DEBUG
ReadableStreamReader* unwrappedReader2 =
UnwrapReaderFromStreamNoThrow(unwrappedStream);
MOZ_ASSERT_IF(unwrappedReader2, unwrappedReader2 == unwrappedReader);
#endif
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMREADER_RELEASED);
RootedValue exn(cx);
if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
return false;
}
Rooted<PromiseObject*> unwrappedClosedPromise(cx);
if (unwrappedStream->readable()) {
unwrappedClosedPromise = UnwrapInternalSlot<PromiseObject>(
cx, unwrappedReader, ReadableStreamReader::Slot_ClosedPromise);
if (!unwrappedClosedPromise) {
return false;
}
AutoRealm ar(cx, unwrappedClosedPromise);
if (!cx->compartment()->wrap(cx, &exn)) {
return false;
}
if (!PromiseObject::reject(cx, unwrappedClosedPromise, exn)) {
return false;
}
} else {
RootedObject closedPromise(cx, PromiseObject::unforgeableReject(cx, exn));
if (!closedPromise) {
return false;
}
unwrappedClosedPromise = &closedPromise->as<PromiseObject>();
AutoRealm ar(cx, unwrappedReader);
if (!cx->compartment()->wrap(cx, &closedPromise)) {
return false;
}
unwrappedReader->setClosedPromise(closedPromise);
}
unwrappedClosedPromise->setHandled();
cx->runtime()->removeUnhandledRejectedPromise(cx, unwrappedClosedPromise);
unwrappedStream->clearReader();
unwrappedReader->clearStream();
return true;
}
static MOZ_MUST_USE JSObject* ReadableStreamControllerPullSteps(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController);
static MOZ_MUST_USE JSObject* ReadableStreamDefaultReaderRead(
JSContext* cx, Handle<ReadableStreamDefaultReader*> unwrappedReader) {
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapStreamFromReader(cx, unwrappedReader));
if (!unwrappedStream) {
return nullptr;
}
unwrappedStream->setDisturbed();
if (unwrappedStream->closed()) {
RootedObject iterResult(
cx, ReadableStreamCreateReadResult(cx, UndefinedHandleValue, true,
unwrappedReader->forAuthorCode()));
if (!iterResult) {
return nullptr;
}
RootedValue iterResultVal(cx, ObjectValue(*iterResult));
return PromiseObject::unforgeableResolve(cx, iterResultVal);
}
if (unwrappedStream->errored()) {
RootedValue storedError(cx, unwrappedStream->storedError());
if (!cx->compartment()->wrap(cx, &storedError)) {
return nullptr;
}
return PromiseObject::unforgeableReject(cx, storedError);
}
MOZ_ASSERT(unwrappedStream->readable());
Rooted<ReadableStreamController*> unwrappedController(
cx, unwrappedStream->controller());
return ReadableStreamControllerPullSteps(cx, unwrappedController);
}
inline static MOZ_MUST_USE bool ReadableStreamControllerCallPullIfNeeded(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController);
static bool ControllerStartHandler(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamController*> controller(
cx, TargetFromHandler<ReadableStreamController>(args));
controller->setStarted();
MOZ_ASSERT(!controller->pulling());
MOZ_ASSERT(!controller->pullAgain());
if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool ControllerStartFailedHandler(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamController*> controller(
cx, TargetFromHandler<ReadableStreamController>(args));
if (!ReadableStreamControllerError(cx, controller, args.get(0))) {
return false;
}
args.rval().setUndefined();
return true;
}
bool ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc,
Value* vp) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BOGUS_CONSTRUCTOR,
"ReadableStreamDefaultController");
return false;
}
static MOZ_MUST_USE double ReadableStreamControllerGetDesiredSizeUnchecked(
ReadableStreamController* controller);
static bool ReadableStreamDefaultController_desiredSize(JSContext* cx,
unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamController*> unwrappedController(
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(
cx, args, "get desiredSize"));
if (!unwrappedController) {
return false;
}
ReadableStream* unwrappedStream = unwrappedController->stream();
if (unwrappedStream->errored()) {
args.rval().setNull();
return true;
}
if (unwrappedStream->closed()) {
args.rval().setInt32(0);
return true;
}
args.rval().setNumber(
ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController));
return true;
}
static MOZ_MUST_USE bool ReadableStreamDefaultControllerClose(
JSContext* cx,
Handle<ReadableStreamDefaultController*> unwrappedController);
static MOZ_MUST_USE bool CheckReadableStreamControllerCanCloseOrEnqueue(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
const char* action) {
if (unwrappedController->closeRequested()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMCONTROLLER_CLOSED, action);
return false;
}
ReadableStream* unwrappedStream = unwrappedController->stream();
if (!unwrappedStream->readable()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
action);
return false;
}
return true;
}
static bool ReadableStreamDefaultController_close(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamDefaultController*> unwrappedController(
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
"close"));
if (!unwrappedController) {
return false;
}
if (!CheckReadableStreamControllerCanCloseOrEnqueue(cx, unwrappedController,
"close")) {
return false;
}
if (!ReadableStreamDefaultControllerClose(cx, unwrappedController)) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool ReadableStreamDefaultController_enqueue(JSContext* cx,
unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamDefaultController*> unwrappedController(
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
"enqueue"));
if (!unwrappedController) {
return false;
}
if (!CheckReadableStreamControllerCanCloseOrEnqueue(cx, unwrappedController,
"enqueue")) {
return false;
}
if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
args.get(0))) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool ReadableStreamDefaultController_error(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamDefaultController*> unwrappedController(
cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
"enqueue"));
if (!unwrappedController) {
return false;
}
if (!ReadableStreamControllerError(cx, unwrappedController, args.get(0))) {
return false;
}
args.rval().setUndefined();
return true;
}
static const JSPropertySpec ReadableStreamDefaultController_properties[] = {
JS_PSG("desiredSize", ReadableStreamDefaultController_desiredSize, 0),
JS_PS_END};
static const JSFunctionSpec ReadableStreamDefaultController_methods[] = {
JS_FN("close", ReadableStreamDefaultController_close, 0, 0),
JS_FN("enqueue", ReadableStreamDefaultController_enqueue, 1, 0),
JS_FN("error", ReadableStreamDefaultController_error, 1, 0), JS_FS_END};
CLASS_SPEC(ReadableStreamDefaultController, 0, SlotCount,
ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
static MOZ_MUST_USE JSObject* PromiseCall(JSContext* cx, HandleValue F,
HandleValue V, HandleValue arg);
static void ReadableStreamControllerClearAlgorithms(
Handle<ReadableStreamController*> controller);
static MOZ_MUST_USE JSObject* ReadableStreamControllerCancelSteps(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
HandleValue reason) {
AssertSameCompartment(cx, reason);
if (!unwrappedController->is<ReadableStreamDefaultController>()) {
Rooted<ListObject*> unwrappedPendingPullIntos(
cx, unwrappedController->as<ReadableByteStreamController>()
.pendingPullIntos());
if (unwrappedPendingPullIntos->length() != 0) {
PullIntoDescriptor* unwrappedDescriptor =
UnwrapAndDowncastObject<PullIntoDescriptor>(
cx, &unwrappedPendingPullIntos->get(0).toObject());
if (!unwrappedDescriptor) {
return nullptr;
}
unwrappedDescriptor->setBytesFilled(0);
}
}
RootedValue unwrappedUnderlyingSource(cx);
unwrappedUnderlyingSource = unwrappedController->underlyingSource();
if (!ResetQueue(cx, unwrappedController)) {
return nullptr;
}
RootedObject result(cx);
if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) {
MOZ_ASSERT(unwrappedUnderlyingSource.toObject().is<TeeState>(),
"tee streams and controllers are always same-compartment with "
"the TeeState object");
Rooted<TeeState*> unwrappedTeeState(
cx, &unwrappedUnderlyingSource.toObject().as<TeeState>());
Rooted<ReadableStreamDefaultController*> unwrappedDefaultController(
cx, &unwrappedController->as<ReadableStreamDefaultController>());
result = ReadableStreamTee_Cancel(cx, unwrappedTeeState,
unwrappedDefaultController, reason);
} else if (unwrappedController->hasExternalSource()) {
RootedValue rval(cx);
{
AutoRealm ar(cx, unwrappedController);
JS::ReadableStreamUnderlyingSource* source =
unwrappedController->externalSource();
Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
RootedValue wrappedReason(cx, reason);
if (!cx->compartment()->wrap(cx, &wrappedReason)) {
return nullptr;
}
cx->check(stream, wrappedReason);
rval = source->cancel(cx, stream, wrappedReason);
}
if (!cx->compartment()->wrap(cx, &rval)) {
result = nullptr;
} else {
result = PromiseObject::unforgeableResolve(cx, rval);
}
} else {
RootedValue unwrappedCancelMethod(cx, unwrappedController->cancelMethod());
if (unwrappedCancelMethod.isUndefined()) {
result = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
} else {
{
AutoRealm ar(cx, &unwrappedCancelMethod.toObject());
RootedValue underlyingSource(cx, unwrappedUnderlyingSource);
if (!cx->compartment()->wrap(cx, &underlyingSource)) {
return nullptr;
}
RootedValue wrappedReason(cx, reason);
if (!cx->compartment()->wrap(cx, &wrappedReason)) {
return nullptr;
}
result = PromiseCall(cx, unwrappedCancelMethod, underlyingSource,
wrappedReason);
}
if (!cx->compartment()->wrap(cx, &result)) {
result = nullptr;
}
}
}
ReadableStreamControllerClearAlgorithms(unwrappedController);
return result;
}
inline static MOZ_MUST_USE bool DequeueValue(
JSContext* cx, Handle<ReadableStreamController*> unwrappedContainer,
MutableHandleValue chunk);
static JSObject* ReadableStreamDefaultControllerPullSteps(
JSContext* cx,
Handle<ReadableStreamDefaultController*> unwrappedController) {
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
Rooted<ListObject*> unwrappedQueue(cx);
RootedValue val(
cx, unwrappedController->getFixedSlot(StreamController::Slot_Queue));
if (val.isObject()) {
unwrappedQueue = &val.toObject().as<ListObject>();
}
if (unwrappedQueue && unwrappedQueue->length() != 0) {
RootedValue chunk(cx);
if (!DequeueValue(cx, unwrappedController, &chunk)) {
return nullptr;
}
if (unwrappedController->closeRequested() &&
unwrappedQueue->length() == 0) {
ReadableStreamControllerClearAlgorithms(unwrappedController);
if (!ReadableStreamCloseInternal(cx, unwrappedStream)) {
return nullptr;
}
}
else {
if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
return nullptr;
}
}
cx->check(chunk);
ReadableStreamReader* unwrappedReader =
UnwrapReaderFromStream(cx, unwrappedStream);
if (!unwrappedReader) {
return nullptr;
}
RootedObject readResultObj(
cx, ReadableStreamCreateReadResult(cx, chunk, false,
unwrappedReader->forAuthorCode()));
if (!readResultObj) {
return nullptr;
}
RootedValue readResult(cx, ObjectValue(*readResultObj));
return PromiseObject::unforgeableResolve(cx, readResult);
}
RootedObject pendingPromise(
cx, ReadableStreamAddReadOrReadIntoRequest(cx, unwrappedStream));
if (!pendingPromise) {
return nullptr;
}
if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
return nullptr;
}
return pendingPromise;
}
static bool ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<ReadableStreamController*> unwrappedController(
cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0));
if (!unwrappedController) {
return false;
}
bool pullAgain = unwrappedController->pullAgain();
unwrappedController->clearPullFlags();
if (pullAgain) {
if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
return false;
}
}
args.rval().setUndefined();
return true;
}
static bool ControllerPullFailedHandler(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
HandleValue e = args.get(0);
Rooted<ReadableStreamController*> controller(
cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0));
if (!controller) {
return false;
}
if (!ReadableStreamControllerError(cx, controller, e)) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool ReadableStreamControllerShouldCallPull(
ReadableStreamController* unwrappedController);
static MOZ_MUST_USE double ReadableStreamControllerGetDesiredSizeUnchecked(
ReadableStreamController* unwrappedController);
inline static MOZ_MUST_USE bool ReadableStreamControllerCallPullIfNeeded(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController) {
bool shouldPull = ReadableStreamControllerShouldCallPull(unwrappedController);
if (!shouldPull) {
return true;
}
if (unwrappedController->pulling()) {
unwrappedController->setPullAgain();
return true;
}
MOZ_ASSERT(!unwrappedController->pullAgain());
unwrappedController->setPulling();
RootedObject wrappedController(cx, unwrappedController);
if (!cx->compartment()->wrap(cx, &wrappedController)) {
return false;
}
RootedObject pullPromise(cx);
RootedValue unwrappedUnderlyingSource(
cx, unwrappedController->underlyingSource());
if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) {
MOZ_ASSERT(unwrappedUnderlyingSource.toObject().is<TeeState>(),
"tee streams and controllers are always same-compartment with "
"the TeeState object");
Rooted<TeeState*> unwrappedTeeState(
cx, &unwrappedUnderlyingSource.toObject().as<TeeState>());
pullPromise = ReadableStreamTee_Pull(cx, unwrappedTeeState);
} else if (unwrappedController->hasExternalSource()) {
{
AutoRealm ar(cx, unwrappedController);
JS::ReadableStreamUnderlyingSource* source =
unwrappedController->externalSource();
Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
double desiredSize =
ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
source->requestData(cx, stream, desiredSize);
}
pullPromise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
} else {
RootedValue unwrappedPullMethod(cx, unwrappedController->pullMethod());
if (unwrappedPullMethod.isUndefined()) {
pullPromise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
} else {
{
AutoRealm ar(cx, &unwrappedPullMethod.toObject());
RootedValue underlyingSource(cx, unwrappedUnderlyingSource);
if (!cx->compartment()->wrap(cx, &underlyingSource)) {
return false;
}
RootedValue controller(cx, ObjectValue(*unwrappedController));
if (!cx->compartment()->wrap(cx, &controller)) {
return false;
}
pullPromise =
PromiseCall(cx, unwrappedPullMethod, underlyingSource, controller);
if (!pullPromise) {
return false;
}
}
if (!cx->compartment()->wrap(cx, &pullPromise)) {
return false;
}
}
}
if (!pullPromise) {
return false;
}
RootedObject onPullFulfilled(
cx, NewHandler(cx, ControllerPullHandler, wrappedController));
if (!onPullFulfilled) {
return false;
}
RootedObject onPullRejected(
cx, NewHandler(cx, ControllerPullFailedHandler, wrappedController));
if (!onPullRejected) {
return false;
}
return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled,
onPullRejected);
}
static bool ReadableStreamControllerShouldCallPull(
ReadableStreamController* unwrappedController) {
ReadableStream* unwrappedStream = unwrappedController->stream();
if (!unwrappedStream->readable()) {
return false;
}
if (unwrappedController->closeRequested()) {
return false;
}
if (!unwrappedController->started()) {
return false;
}
if (unwrappedStream->locked() &&
ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
return true;
}
double desiredSize =
ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
return desiredSize > 0;
}
static void ReadableStreamControllerClearAlgorithms(
Handle<ReadableStreamController*> controller) {
controller->setPullMethod(UndefinedHandleValue);
controller->setCancelMethod(UndefinedHandleValue);
ReadableStreamController::clearUnderlyingSource(controller);
if (controller->is<ReadableStreamDefaultController>()) {
controller->as<ReadableStreamDefaultController>().setStrategySize(
UndefinedHandleValue);
}
}
static MOZ_MUST_USE bool ReadableStreamDefaultControllerClose(
JSContext* cx,
Handle<ReadableStreamDefaultController*> unwrappedController) {
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
MOZ_ASSERT(!unwrappedController->closeRequested());
MOZ_ASSERT(unwrappedStream->readable());
unwrappedController->setCloseRequested();
Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue());
if (unwrappedQueue->length() == 0) {
ReadableStreamControllerClearAlgorithms(unwrappedController);
return ReadableStreamCloseInternal(cx, unwrappedStream);
}
return true;
}
static MOZ_MUST_USE bool EnqueueValueWithSize(
JSContext* cx, Handle<ReadableStreamController*> unwrappedContainer,
HandleValue value, HandleValue sizeVal);
static MOZ_MUST_USE bool ReadableStreamDefaultControllerEnqueue(
JSContext* cx, Handle<ReadableStreamDefaultController*> unwrappedController,
HandleValue chunk) {
AssertSameCompartment(cx, chunk);
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
MOZ_ASSERT(!unwrappedController->closeRequested());
MOZ_ASSERT(unwrappedStream->readable());
if (unwrappedStream->locked() &&
ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk,
false)) {
return false;
}
} else {
RootedValue chunkSize(cx, NumberValue(1));
bool success = true;
RootedValue strategySize(cx, unwrappedController->strategySize());
if (!strategySize.isUndefined()) {
if (!cx->compartment()->wrap(cx, &strategySize)) {
return false;
}
success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize);
}
if (success) {
success = EnqueueValueWithSize(cx, unwrappedController, chunk, chunkSize);
}
if (!success) {
RootedValue exn(cx);
if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
return false;
}
if (!ReadableStreamControllerError(cx, unwrappedController, exn)) {
return false;
}
cx->setPendingException(exn);
return false;
}
}
return ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController);
}
static MOZ_MUST_USE bool ReadableByteStreamControllerClearPendingPullIntos(
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController);
static MOZ_MUST_USE bool ReadableStreamControllerError(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
HandleValue e) {
MOZ_ASSERT(!cx->isExceptionPending());
AssertSameCompartment(cx, e);
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
if (!unwrappedStream->readable()) {
return true;
}
if (unwrappedController->is<ReadableByteStreamController>()) {
Rooted<ReadableByteStreamController*> unwrappedByteStreamController(
cx, &unwrappedController->as<ReadableByteStreamController>());
if (!ReadableByteStreamControllerClearPendingPullIntos(
cx, unwrappedByteStreamController)) {
return false;
}
}
if (!ResetQueue(cx, unwrappedController)) {
return false;
}
ReadableStreamControllerClearAlgorithms(unwrappedController);
return ReadableStreamErrorInternal(cx, unwrappedStream, e);
}
static MOZ_MUST_USE double ReadableStreamControllerGetDesiredSizeUnchecked(
ReadableStreamController* controller) {
#if DEBUG
ReadableStream* stream = controller->stream();
MOZ_ASSERT(!(stream->errored() || stream->closed()));
#endif
return controller->strategyHWM() - controller->queueTotalSize();
}
static MOZ_MUST_USE bool SetUpReadableStreamDefaultController(
JSContext* cx, Handle<ReadableStream*> stream,
SourceAlgorithms sourceAlgorithms, HandleValue underlyingSource,
HandleValue pullMethod, HandleValue cancelMethod, double highWaterMark,
HandleValue size) {
cx->check(stream, underlyingSource, size);
MOZ_ASSERT(pullMethod.isUndefined() || IsCallable(pullMethod));
MOZ_ASSERT(cancelMethod.isUndefined() || IsCallable(cancelMethod));
MOZ_ASSERT_IF(sourceAlgorithms != SourceAlgorithms::Script,
pullMethod.isUndefined());
MOZ_ASSERT_IF(sourceAlgorithms != SourceAlgorithms::Script,
cancelMethod.isUndefined());
MOZ_ASSERT(highWaterMark >= 0);
MOZ_ASSERT(size.isUndefined() || IsCallable(size));
Rooted<ReadableStreamDefaultController*> controller(
cx, NewBuiltinClassInstance<ReadableStreamDefaultController>(cx));
if (!controller) {
return false;
}
MOZ_ASSERT(!stream->hasController());
controller->setStream(stream);
if (!ResetQueue(cx, controller)) {
return false;
}
controller->setFlags(0);
controller->setStrategySize(size);
controller->setStrategyHWM(highWaterMark);
controller->setUnderlyingSource(underlyingSource);
controller->setPullMethod(pullMethod);
controller->setCancelMethod(cancelMethod);
stream->setController(controller);
RootedValue startResult(cx);
if (sourceAlgorithms == SourceAlgorithms::Script) {
RootedValue controllerVal(cx, ObjectValue(*controller));
if (!InvokeOrNoop(cx, underlyingSource, cx->names().start, controllerVal,
&startResult)) {
return false;
}
}
RootedObject startPromise(cx,
PromiseObject::unforgeableResolve(cx, startResult));
if (!startPromise) {
return false;
}
RootedObject onStartFulfilled(
cx, NewHandler(cx, ControllerStartHandler, controller));
if (!onStartFulfilled) {
return false;
}
RootedObject onStartRejected(
cx, NewHandler(cx, ControllerStartFailedHandler, controller));
if (!onStartRejected) {
return false;
}
if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled,
onStartRejected)) {
return false;
}
return true;
}
static MOZ_MUST_USE bool CreateAlgorithmFromUnderlyingMethod(
JSContext* cx, HandleValue underlyingObject,
const char* methodNameForErrorMessage, HandlePropertyName methodName,
MutableHandleValue method);
static MOZ_MUST_USE bool
SetUpReadableStreamDefaultControllerFromUnderlyingSource(
JSContext* cx, Handle<ReadableStream*> stream, HandleValue underlyingSource,
double highWaterMark, HandleValue sizeAlgorithm) {
MOZ_ASSERT(!underlyingSource.isUndefined());
SourceAlgorithms sourceAlgorithms = SourceAlgorithms::Script;
RootedValue pullMethod(cx);
if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSource,
"ReadableStream source.pull method",
cx->names().pull, &pullMethod)) {
return false;
}
RootedValue cancelMethod(cx);
if (!CreateAlgorithmFromUnderlyingMethod(
cx, underlyingSource, "ReadableStream source.cancel method",
cx->names().cancel, &cancelMethod)) {
return false;
}
return SetUpReadableStreamDefaultController(
cx, stream, sourceAlgorithms, underlyingSource, pullMethod, cancelMethod,
highWaterMark, sizeAlgorithm);
}
#if 0#endif
bool ReadableByteStreamController::constructor(JSContext* cx, unsigned argc,
Value* vp) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BOGUS_CONSTRUCTOR,
"ReadableByteStreamController");
return false;
}
static MOZ_MUST_USE bool SetUpExternalReadableByteStreamController(
JSContext* cx, Handle<ReadableStream*> stream,
JS::ReadableStreamUnderlyingSource* source) {
Rooted<ReadableByteStreamController*> controller(
cx, NewBuiltinClassInstance<ReadableByteStreamController>(cx));
if (!controller) {
return false;
}
MOZ_ASSERT(!stream->hasController());
controller->setStream(stream);
controller->setFlags(0);
MOZ_ASSERT(!controller->pullAgain());
MOZ_ASSERT(!controller->pulling());
controller->setQueueTotalSize(0);
MOZ_ASSERT(!controller->closeRequested());
MOZ_ASSERT(!controller->started());
controller->setStrategyHWM(0);
controller->setExternalSource(source);
MOZ_ASSERT(controller->autoAllocateChunkSize().isUndefined());
if (!SetNewList(cx, controller,
ReadableByteStreamController::Slot_PendingPullIntos)) {
return false;
}
stream->setController(controller);
RootedObject startPromise(
cx, PromiseObject::unforgeableResolve(cx, UndefinedHandleValue));
if (!startPromise) {
return false;
}
RootedObject onStartFulfilled(
cx, NewHandler(cx, ControllerStartHandler, controller));
if (!onStartFulfilled) {
return false;
}
RootedObject onStartRejected(
cx, NewHandler(cx, ControllerStartFailedHandler, controller));
if (!onStartRejected) {
return false;
}
if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled,
onStartRejected)) {
return false;
}
return true;
}
static const JSPropertySpec ReadableByteStreamController_properties[] = {
JS_PS_END};
static const JSFunctionSpec ReadableByteStreamController_methods[] = {
JS_FS_END};
static void ReadableByteStreamControllerFinalize(FreeOp* fop, JSObject* obj) {
ReadableByteStreamController& controller =
obj->as<ReadableByteStreamController>();
if (controller.getFixedSlot(ReadableStreamController::Slot_Flags)
.isUndefined()) {
return;
}
if (!controller.hasExternalSource()) {
return;
}
controller.externalSource()->finalize();
}
static const ClassOps ReadableByteStreamControllerClassOps = {
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
ReadableByteStreamControllerFinalize,
nullptr,
nullptr,
nullptr,
nullptr,
};
CLASS_SPEC(ReadableByteStreamController, 0, SlotCount,
ClassSpec::DontDefineConstructor, JSCLASS_BACKGROUND_FINALIZE,
&ReadableByteStreamControllerClassOps);
static MOZ_MUST_USE bool ReadableByteStreamControllerHandleQueueDrain(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController);
static MOZ_MUST_USE JSObject* ReadableByteStreamControllerPullSteps(
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController) {
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
#ifdef DEBUG
bool result;
if (!ReadableStreamHasDefaultReader(cx, unwrappedStream, &result)) {
return nullptr;
}
MOZ_ASSERT(result);
#endif
RootedValue val(cx);
double queueTotalSize = unwrappedController->queueTotalSize();
if (queueTotalSize > 0) {
MOZ_ASSERT(ReadableStreamGetNumReadRequests(unwrappedStream) == 0);
RootedObject view(cx);
MOZ_RELEASE_ASSERT(unwrappedStream->mode() ==
JS::ReadableStreamMode::ExternalSource);
#if 0#endif {
JS::ReadableStreamUnderlyingSource* source =
unwrappedController->externalSource();
view = JS_NewUint8Array(cx, queueTotalSize);
if (!view) {
return nullptr;
}
size_t bytesWritten;
{
AutoRealm ar(cx, unwrappedStream);
JS::AutoSuppressGCAnalysis suppressGC(cx);
JS::AutoCheckCannotGC noGC;
bool dummy;
void* buffer = JS_GetArrayBufferViewData(view, &dummy, noGC);
source->writeIntoReadRequestBuffer(cx, unwrappedStream, buffer,
queueTotalSize, &bytesWritten);
}
queueTotalSize = queueTotalSize - bytesWritten;
}
#if 0#endif
unwrappedController->setQueueTotalSize(queueTotalSize);
if (!ReadableByteStreamControllerHandleQueueDrain(cx,
unwrappedController)) {
return nullptr;
}
val.setObject(*view);
ReadableStreamReader* unwrappedReader =
UnwrapReaderFromStream(cx, unwrappedStream);
if (!unwrappedReader) {
return nullptr;
}
RootedObject readResult(
cx, ReadableStreamCreateReadResult(cx, val, false,
unwrappedReader->forAuthorCode()));
if (!readResult) {
return nullptr;
}
val.setObject(*readResult);
return PromiseObject::unforgeableResolve(cx, val);
}
val = unwrappedController->autoAllocateChunkSize();
if (!val.isUndefined()) {
double autoAllocateChunkSize = val.toNumber();
JSObject* bufferObj = JS::NewArrayBuffer(cx, autoAllocateChunkSize);
if (!bufferObj) {
return PromiseRejectedWithPendingError(cx);
}
RootedArrayBufferObject buffer(cx, &bufferObj->as<ArrayBufferObject>());
RootedObject pullIntoDescriptor(
cx, PullIntoDescriptor::create(cx, buffer, 0, autoAllocateChunkSize, 0,
1, nullptr, ReaderType_Default));
if (!pullIntoDescriptor) {
return PromiseRejectedWithPendingError(cx);
}
if (!AppendToListAtSlot(cx, unwrappedController,
ReadableByteStreamController::Slot_PendingPullIntos,
pullIntoDescriptor)) {
return nullptr;
}
}
RootedObject promise(
cx, ReadableStreamAddReadOrReadIntoRequest(cx, unwrappedStream));
if (!promise) {
return nullptr;
}
if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
return nullptr;
}
return promise;
}
static MOZ_MUST_USE JSObject* ReadableStreamControllerPullSteps(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController) {
if (unwrappedController->is<ReadableStreamDefaultController>()) {
Rooted<ReadableStreamDefaultController*> unwrappedDefaultController(
cx, &unwrappedController->as<ReadableStreamDefaultController>());
return ReadableStreamDefaultControllerPullSteps(cx,
unwrappedDefaultController);
}
Rooted<ReadableByteStreamController*> unwrappedByteController(
cx, &unwrappedController->as<ReadableByteStreamController>());
return ReadableByteStreamControllerPullSteps(cx, unwrappedByteController);
}
static MOZ_MUST_USE bool ReadableByteStreamControllerInvalidateBYOBRequest(
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController);
static MOZ_MUST_USE bool ReadableByteStreamControllerClearPendingPullIntos(
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController) {
if (!ReadableByteStreamControllerInvalidateBYOBRequest(cx,
unwrappedController)) {
return false;
}
return SetNewList(cx, unwrappedController,
ReadableByteStreamController::Slot_PendingPullIntos);
}
static MOZ_MUST_USE bool ReadableByteStreamControllerClose(
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController) {
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
MOZ_ASSERT(!unwrappedController->closeRequested());
MOZ_ASSERT(unwrappedStream->readable());
if (unwrappedController->queueTotalSize() > 0) {
unwrappedController->setCloseRequested();
return true;
}
Rooted<ListObject*> unwrappedPendingPullIntos(
cx, unwrappedController->pendingPullIntos());
if (unwrappedPendingPullIntos->length() != 0) {
Rooted<PullIntoDescriptor*> unwrappedFirstPendingPullInto(
cx, UnwrapAndDowncastObject<PullIntoDescriptor>(
cx, &unwrappedPendingPullIntos->get(0).toObject()));
if (!unwrappedFirstPendingPullInto) {
return false;
}
if (unwrappedFirstPendingPullInto->bytesFilled() > 0) {
JS_ReportErrorNumberASCII(
cx, GetErrorMessage, nullptr,
JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL);
RootedValue e(cx);
if (!cx->isExceptionPending() || !GetAndClearException(cx, &e)) {
return false;
}
if (!ReadableStreamControllerError(cx, unwrappedController, e)) {
return false;
}
cx->setPendingException(e);
return false;
}
}
ReadableStreamControllerClearAlgorithms(unwrappedController);
return ReadableStreamCloseInternal(cx, unwrappedStream);
}
static MOZ_MUST_USE bool ReadableByteStreamControllerHandleQueueDrain(
JSContext* cx, Handle<ReadableStreamController*> unwrappedController) {
MOZ_ASSERT(unwrappedController->is<ReadableByteStreamController>());
Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
MOZ_ASSERT(unwrappedStream->readable());
if (unwrappedController->queueTotalSize() == 0 &&
unwrappedController->closeRequested()) {
ReadableStreamControllerClearAlgorithms(unwrappedController);
return ReadableStreamCloseInternal(cx, unwrappedStream);
}
return ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController);
}
enum BYOBRequestSlots {
BYOBRequestSlot_Controller,
BYOBRequestSlot_View,
BYOBRequestSlotCount
};
static MOZ_MUST_USE bool ReadableByteStreamControllerInvalidateBYOBRequest(
JSContext* cx, Handle<ReadableByteStreamController*> unwrappedController) {
RootedValue unwrappedBYOBRequestVal(cx, unwrappedController->byobRequest());
if (unwrappedBYOBRequestVal.isUndefined()) {
return true;
}
RootedNativeObject unwrappedBYOBRequest(
cx, UnwrapAndDowncastValue<NativeObject>(cx, unwrappedBYOBRequestVal));
if (!unwrappedBYOBRequest) {
return false;
}
unwrappedBYOBRequest->setFixedSlot(BYOBRequestSlot_Controller,
UndefinedValue());
unwrappedBYOBRequest->setFixedSlot(BYOBRequestSlot_View, UndefinedValue());
unwrappedController->clearBYOBRequest();
return true;
}
static MOZ_MUST_USE bool CreateDataProperty(JSContext* cx, HandleObject obj,
HandlePropertyName key,
HandleValue value,
ObjectOpResult& result) {
RootedId id(cx, NameToId(key));
Rooted<PropertyDescriptor> desc(cx);
desc.setDataDescriptor(value, JSPROP_ENUMERATE);
return DefineProperty(cx, obj, id, desc, result);
}
bool js::ByteLengthQueuingStrategy::constructor(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "ByteLengthQueuingStrategy")) {
return false;
}
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(
cx, args, JSProto_ByteLengthQueuingStrategy, &proto)) {
return false;
}
RootedObject strategy(
cx, NewObjectWithClassProto<ByteLengthQueuingStrategy>(cx, proto));
if (!strategy) {
return false;
}
RootedObject argObj(cx, ToObject(cx, args.get(0)));
if (!argObj) {
return false;
}
RootedValue highWaterMark(cx);
if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark,
&highWaterMark)) {
return false;
}
ObjectOpResult ignored;
if (!CreateDataProperty(cx, strategy, cx->names().highWaterMark,
highWaterMark, ignored)) {
return false;
}
args.rval().setObject(*strategy);
return true;
}
bool ByteLengthQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return GetProperty(cx, args.get(0), cx->names().byteLength, args.rval());
}
static const JSPropertySpec ByteLengthQueuingStrategy_properties[] = {
JS_PS_END};
static const JSFunctionSpec ByteLengthQueuingStrategy_methods[] = {
JS_FN("size", ByteLengthQueuingStrategy_size, 1, 0), JS_FS_END};
CLASS_SPEC(ByteLengthQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);
bool js::CountQueuingStrategy::constructor(JSContext* cx, unsigned argc,
Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (!ThrowIfNotConstructing(cx, args, "CountQueuingStrategy")) {
return false;
}
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(
cx, args, JSProto_CountQueuingStrategy, &proto)) {
return false;
}
Rooted<CountQueuingStrategy*> strategy(
cx, NewObjectWithClassProto<CountQueuingStrategy>(cx, proto));
if (!strategy) {
return false;
}
RootedObject argObj(cx, ToObject(cx, args.get(0)));
if (!argObj) {
return false;
}
RootedValue highWaterMark(cx);
if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark,
&highWaterMark)) {
return false;
}
ObjectOpResult ignored;
if (!CreateDataProperty(cx, strategy, cx->names().highWaterMark,
highWaterMark, ignored)) {
return false;
}
args.rval().setObject(*strategy);
return true;
}
bool CountQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setInt32(1);
return true;
}
static const JSPropertySpec CountQueuingStrategy_properties[] = {JS_PS_END};
static const JSFunctionSpec CountQueuingStrategy_methods[] = {
JS_FN("size", CountQueuingStrategy_size, 0, 0), JS_FS_END};
CLASS_SPEC(CountQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);
#undef CLASS_SPEC
inline static MOZ_MUST_USE bool DequeueValue(
JSContext* cx, Handle<ReadableStreamController*> unwrappedContainer,
MutableHandleValue chunk) {
Rooted<ListObject*> unwrappedQueue(cx, unwrappedContainer->queue());
MOZ_ASSERT(unwrappedQueue->length() > 0);
Rooted<QueueEntry*> unwrappedPair(
cx, &unwrappedQueue->popFirstAs<QueueEntry>(cx));
MOZ_ASSERT(unwrappedPair);
double totalSize = unwrappedContainer->queueTotalSize();
totalSize -= unwrappedPair->size();
if (totalSize < 0) {
totalSize = 0;
}
unwrappedContainer->setQueueTotalSize(totalSize);
RootedValue val(cx, unwrappedPair->value());
if (!cx->compartment()->wrap(cx, &val)) {
return false;
}
chunk.set(val);
return true;
}
static MOZ_MUST_USE bool EnqueueValueWithSize(
JSContext* cx, Handle<ReadableStreamController*> unwrappedContainer,
HandleValue value, HandleValue sizeVal) {
cx->check(value, sizeVal);
double size;
if (!ToNumber(cx, sizeVal, &size)) {
return false;
}
if (size < 0 || mozilla::IsNaN(size) || mozilla::IsInfinite(size)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "size");
return false;
}
{
AutoRealm ar(cx, unwrappedContainer);
Rooted<ListObject*> queue(cx, unwrappedContainer->queue());
RootedValue wrappedVal(cx, value);
if (!cx->compartment()->wrap(cx, &wrappedVal)) {
return false;
}
QueueEntry* entry = QueueEntry::create(cx, wrappedVal, size);
if (!entry) {
return false;
}
RootedValue val(cx, ObjectValue(*entry));
if (!queue->append(cx, val)) {
return false;
}
}
unwrappedContainer->setQueueTotalSize(unwrappedContainer->queueTotalSize() +
size);
return true;
}
inline static MOZ_MUST_USE bool ResetQueue(
JSContext* cx, Handle<ReadableStreamController*> unwrappedContainer) {
if (!SetNewList(cx, unwrappedContainer, StreamController::Slot_Queue)) {
return false;
}
unwrappedContainer->setQueueTotalSize(0);
return true;
}
inline static MOZ_MUST_USE bool AppendToListAtSlot(
JSContext* cx, HandleNativeObject unwrappedContainer, uint32_t slot,
HandleObject obj) {
Rooted<ListObject*> list(
cx, &unwrappedContainer->getFixedSlot(slot).toObject().as<ListObject>());
AutoRealm ar(cx, list);
RootedValue val(cx, ObjectValue(*obj));
if (!cx->compartment()->wrap(cx, &val)) {
return false;
}
return list->append(cx, val);
}
static MOZ_MUST_USE bool CreateAlgorithmFromUnderlyingMethod(
JSContext* cx, HandleValue underlyingObject,
const char* methodNameForErrorMessage, HandlePropertyName methodName,
MutableHandleValue method) {
MOZ_ASSERT(!underlyingObject.isUndefined());
if (!GetProperty(cx, underlyingObject, methodName, method)) {
return false;
}
if (!method.isUndefined()) {
if (!IsCallable(method)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_NOT_FUNCTION, methodNameForErrorMessage);
return false;
}
return true;
}
return true;
}
inline static MOZ_MUST_USE bool InvokeOrNoop(JSContext* cx, HandleValue O,
HandlePropertyName P,
HandleValue arg,
MutableHandleValue rval) {
cx->check(O, P, arg);
MOZ_ASSERT(!O.isUndefined());
RootedValue method(cx);
if (!GetProperty(cx, O, P, &method)) {
return false;
}
if (method.isUndefined()) {
return true;
}
return Call(cx, method, O, arg, rval);
}
static MOZ_MUST_USE JSObject* PromiseCall(JSContext* cx, HandleValue F,
HandleValue V, HandleValue arg) {
cx->check(F, V, arg);
MOZ_ASSERT(IsCallable(F));
MOZ_ASSERT(!V.isUndefined());
RootedValue rval(cx);
if (!Call(cx, F, V, arg, &rval)) {
return PromiseRejectedWithPendingError(cx);
}
return PromiseObject::unforgeableResolve(cx, rval);
}
static MOZ_MUST_USE bool ValidateAndNormalizeHighWaterMark(
JSContext* cx, HandleValue highWaterMarkVal, double* highWaterMark) {
if (!ToNumber(cx, highWaterMarkVal, highWaterMark)) {
return false;
}
if (mozilla::IsNaN(*highWaterMark) || *highWaterMark < 0) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_STREAM_INVALID_HIGHWATERMARK);
return false;
}
return true;
}
static MOZ_MUST_USE bool MakeSizeAlgorithmFromSizeFunction(JSContext* cx,
HandleValue size) {
if (size.isUndefined()) {
return true;
}
if (!IsCallable(size)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION,
"ReadableStream argument options.size");
return false;
}
return true;
}
JS_FRIEND_API JSObject* js::UnwrapReadableStream(JSObject* obj) {
return obj->maybeUnwrapIf<ReadableStream>();
}
JS_PUBLIC_API JSObject* JS::NewReadableDefaultStreamObject(
JSContext* cx, JS::HandleObject underlyingSource ,
JS::HandleFunction size , double highWaterMark ,
JS::HandleObject proto ) {
MOZ_ASSERT(!cx->zone()->isAtomsZone());
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(underlyingSource, size, proto);
MOZ_ASSERT(highWaterMark >= 0);
Rooted<ReadableStream*> stream(cx, ReadableStream::create(cx));
if (!stream) {
return nullptr;
}
RootedValue sourceVal(cx);
if (underlyingSource) {
sourceVal.setObject(*underlyingSource);
} else {
JSObject* source = NewBuiltinClassInstance<PlainObject>(cx);
if (!source) {
return nullptr;
}
sourceVal.setObject(*source);
}
RootedValue sizeVal(cx, size ? ObjectValue(*size) : UndefinedValue());
if (!SetUpReadableStreamDefaultControllerFromUnderlyingSource(
cx, stream, sourceVal, highWaterMark, sizeVal)) {
return nullptr;
}
return stream;
}
JS_PUBLIC_API JSObject* JS::NewReadableExternalSourceStreamObject(
JSContext* cx, JS::ReadableStreamUnderlyingSource* underlyingSource,
HandleObject proto ) {
MOZ_ASSERT(!cx->zone()->isAtomsZone());
AssertHeapIsIdle();
CHECK_THREAD(cx);
MOZ_ASSERT(underlyingSource);
MOZ_ASSERT((uintptr_t(underlyingSource) & 1) == 0,
"external underlying source pointers must be aligned");
cx->check(proto);
return ReadableStream::createExternalSourceStream(cx, underlyingSource,
proto);
}
JS_PUBLIC_API bool JS::IsReadableStream(JSObject* obj) {
return obj->canUnwrapAs<ReadableStream>();
}
JS_PUBLIC_API bool JS::IsReadableStreamReader(JSObject* obj) {
return obj->canUnwrapAs<ReadableStreamDefaultReader>();
}
JS_PUBLIC_API bool JS::IsReadableStreamDefaultReader(JSObject* obj) {
return obj->canUnwrapAs<ReadableStreamDefaultReader>();
}
template <class T>
static MOZ_MUST_USE T* APIUnwrapAndDowncast(JSContext* cx, JSObject* obj) {
cx->check(obj);
return UnwrapAndDowncastObject<T>(cx, obj);
}
JS_PUBLIC_API bool JS::ReadableStreamIsReadable(JSContext* cx,
HandleObject streamObj,
bool* result) {
ReadableStream* unwrappedStream =
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
if (!unwrappedStream) {
return false;
}
*result = unwrappedStream->readable();
return true;
}
JS_PUBLIC_API bool JS::ReadableStreamIsLocked(JSContext* cx,
HandleObject streamObj,
bool* result) {
ReadableStream* unwrappedStream =
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
if (!unwrappedStream) {
return false;
}
*result = unwrappedStream->locked();
return true;
}
JS_PUBLIC_API bool JS::ReadableStreamIsDisturbed(JSContext* cx,
HandleObject streamObj,
bool* result) {
ReadableStream* unwrappedStream =
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
if (!unwrappedStream) {
return false;
}
*result = unwrappedStream->disturbed();
return true;
}
JS_PUBLIC_API JSObject* JS::ReadableStreamCancel(JSContext* cx,
HandleObject streamObj,
HandleValue reason) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(reason);
Rooted<ReadableStream*> unwrappedStream(
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
if (!unwrappedStream) {
return nullptr;
}
return ::ReadableStreamCancel(cx, unwrappedStream, reason);
}
JS_PUBLIC_API bool JS::ReadableStreamGetMode(JSContext* cx,
HandleObject streamObj,
JS::ReadableStreamMode* mode) {
ReadableStream* unwrappedStream =
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
if (!unwrappedStream) {
return false;
}
*mode = unwrappedStream->mode();
return true;
}
JS_PUBLIC_API JSObject* JS::ReadableStreamGetReader(
JSContext* cx, HandleObject streamObj, ReadableStreamReaderMode mode) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
Rooted<ReadableStream*> unwrappedStream(
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
if (!unwrappedStream) {
return nullptr;
}
JSObject* result = CreateReadableStreamDefaultReader(cx, unwrappedStream);
MOZ_ASSERT_IF(result, IsObjectInContextCompartment(result, cx));
return result;
}
JS_PUBLIC_API bool JS::ReadableStreamGetExternalUnderlyingSource(
JSContext* cx, HandleObject streamObj,
JS::ReadableStreamUnderlyingSource** source) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
Rooted<ReadableStream*> unwrappedStream(
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
if (!unwrappedStream) {
return false;
}
MOZ_ASSERT(unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource);
if (unwrappedStream->locked()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_LOCKED);
return false;
}
if (!unwrappedStream->readable()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
"ReadableStreamGetExternalUnderlyingSource");
return false;
}
auto unwrappedController =
&unwrappedStream->controller()->as<ReadableByteStreamController>();
unwrappedController->setSourceLocked();
*source = unwrappedController->externalSource();
return true;
}
JS_PUBLIC_API bool JS::ReadableStreamReleaseExternalUnderlyingSource(
JSContext* cx, HandleObject streamObj) {
ReadableStream* unwrappedStream =
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
if (!unwrappedStream) {
return false;
}
MOZ_ASSERT(unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource);
MOZ_ASSERT(unwrappedStream->locked());
MOZ_ASSERT(unwrappedStream->controller()->sourceLocked());
unwrappedStream->controller()->clearSourceLocked();
return true;
}
JS_PUBLIC_API bool JS::ReadableStreamUpdateDataAvailableFromSource(
JSContext* cx, JS::HandleObject streamObj, uint32_t availableData) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
Rooted<ReadableStream*> unwrappedStream(
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
if (!unwrappedStream) {
return false;
}
Rooted<ReadableByteStreamController*> unwrappedController(
cx, &unwrappedStream->controller()->as<ReadableByteStreamController>());
if (unwrappedController->closeRequested()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue");
return false;
}
if (!unwrappedController->stream()->readable()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
"enqueue");
return false;
}
unwrappedController->clearPullFlags();
#if DEBUG
uint32_t oldAvailableData =
unwrappedController->getFixedSlot(StreamController::Slot_TotalSize)
.toInt32();
#endif unwrappedController->setQueueTotalSize(availableData);
if (ReadableStreamGetNumReadRequests(unwrappedStream) == 0) {
return true;
}
bool hasDefaultReader;
if (!ReadableStreamHasDefaultReader(cx, unwrappedStream, &hasDefaultReader)) {
return false;
}
if (hasDefaultReader) {
MOZ_ASSERT(oldAvailableData == 0);
JSObject* viewObj = JS_NewUint8Array(cx, availableData);
if (!viewObj) {
return false;
}
Rooted<ArrayBufferViewObject*> transferredView(
cx, &viewObj->as<ArrayBufferViewObject>());
if (!transferredView) {
return false;
}
JS::ReadableStreamUnderlyingSource* source =
unwrappedController->externalSource();
size_t bytesWritten;
{
AutoRealm ar(cx, unwrappedStream);
JS::AutoSuppressGCAnalysis suppressGC(cx);
JS::AutoCheckCannotGC noGC;
bool dummy;
void* buffer = JS_GetArrayBufferViewData(transferredView, &dummy, noGC);
source->writeIntoReadRequestBuffer(cx, unwrappedStream, buffer,
availableData, &bytesWritten);
}
RootedValue chunk(cx, ObjectValue(*transferredView));
if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk,
false)) {
return false;
}
unwrappedController->setQueueTotalSize(availableData - bytesWritten);
} else {
MOZ_ASSERT(!unwrappedStream->locked());
}
return true;
}
JS_PUBLIC_API bool JS::ReadableStreamTee(JSContext* cx, HandleObject streamObj,
MutableHandleObject branch1Obj,
MutableHandleObject branch2Obj) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
Rooted<ReadableStream*> unwrappedStream(
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
if (!unwrappedStream) {
return false;
}
Rooted<ReadableStream*> branch1Stream(cx);
Rooted<ReadableStream*> branch2Stream(cx);
if (!ReadableStreamTee(cx, unwrappedStream, false, &branch1Stream,
&branch2Stream)) {
return false;
}
branch1Obj.set(branch1Stream);
branch2Obj.set(branch2Stream);
return true;
}
JS_PUBLIC_API bool JS::ReadableStreamGetDesiredSize(JSContext* cx,
JSObject* streamObj,
bool* hasValue,
double* value) {
ReadableStream* unwrappedStream =
APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
if (!unwrappedStream) {
return false;
}
if (unwrappedStream->errored()) {
*hasValue = false;
return true;
}
*hasValue = true;
if (unwrappedStream->closed()) {
*value = 0;
return true;
}
*value = ReadableStreamControllerGetDesiredSizeUnchecked(
unwrappedStream->controller());
return true;
}
JS_PUBLIC_API bool JS::ReadableStreamClose(JSContext* cx,
HandleObject streamObj) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
Rooted<ReadableStream*> unwrappedStream(
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
if (!unwrappedStream) {
return false;
}
Rooted<ReadableStreamController*> unwrappedControllerObj(
cx, unwrappedStream->controller());
if (!CheckReadableStreamControllerCanCloseOrEnqueue(
cx, unwrappedControllerObj, "close")) {
return false;
}
if (unwrappedControllerObj->is<ReadableStreamDefaultController>()) {
Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
unwrappedController =
&unwrappedControllerObj->as<ReadableStreamDefaultController>();
return ReadableStreamDefaultControllerClose(cx, unwrappedController);
}
Rooted<ReadableByteStreamController*> unwrappedController(cx);
unwrappedController =
&unwrappedControllerObj->as<ReadableByteStreamController>();
return ReadableByteStreamControllerClose(cx, unwrappedController);
}
JS_PUBLIC_API bool JS::ReadableStreamEnqueue(JSContext* cx,
HandleObject streamObj,
HandleValue chunk) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(chunk);
Rooted<ReadableStream*> unwrappedStream(
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
if (!unwrappedStream) {
return false;
}
if (unwrappedStream->mode() != JS::ReadableStreamMode::Default) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER,
"JS::ReadableStreamEnqueue");
return false;
}
Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
unwrappedController =
&unwrappedStream->controller()->as<ReadableStreamDefaultController>();
MOZ_ASSERT(!unwrappedController->closeRequested());
MOZ_ASSERT(unwrappedStream->readable());
return ReadableStreamDefaultControllerEnqueue(cx, unwrappedController, chunk);
}
JS_PUBLIC_API bool JS::ReadableStreamError(JSContext* cx,
HandleObject streamObj,
HandleValue error) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(error);
Rooted<ReadableStream*> unwrappedStream(
cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
if (!unwrappedStream) {
return false;
}
Rooted<ReadableStreamController*> unwrappedController(
cx, unwrappedStream->controller());
return ReadableStreamControllerError(cx, unwrappedController, error);
}
JS_PUBLIC_API bool JS::ReadableStreamReaderIsClosed(JSContext* cx,
HandleObject readerObj,
bool* result) {
Rooted<ReadableStreamReader*> unwrappedReader(
cx, APIUnwrapAndDowncast<ReadableStreamReader>(cx, readerObj));
if (!unwrappedReader) {
return false;
}
*result = unwrappedReader->isClosed();
return true;
}
JS_PUBLIC_API bool JS::ReadableStreamReaderCancel(JSContext* cx,
HandleObject readerObj,
HandleValue reason) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
cx->check(reason);
Rooted<ReadableStreamReader*> unwrappedReader(
cx, APIUnwrapAndDowncast<ReadableStreamReader>(cx, readerObj));
if (!unwrappedReader) {
return false;
}
MOZ_ASSERT(unwrappedReader->forAuthorCode() == ForAuthorCodeBool::No,
"C++ code should not touch readers created by scripts");
return ReadableStreamReaderGenericCancel(cx, unwrappedReader, reason);
}
JS_PUBLIC_API bool JS::ReadableStreamReaderReleaseLock(JSContext* cx,
HandleObject readerObj) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
Rooted<ReadableStreamReader*> unwrappedReader(
cx, APIUnwrapAndDowncast<ReadableStreamReader>(cx, readerObj));
if (!unwrappedReader) {
return false;
}
MOZ_ASSERT(unwrappedReader->forAuthorCode() == ForAuthorCodeBool::No,
"C++ code should not touch readers created by scripts");
#ifdef DEBUG
Rooted<ReadableStream*> unwrappedStream(
cx, UnwrapStreamFromReader(cx, unwrappedReader));
if (!unwrappedStream) {
return false;
}
MOZ_ASSERT(ReadableStreamGetNumReadRequests(unwrappedStream) == 0);
#endif
return ReadableStreamReaderGenericRelease(cx, unwrappedReader);
}
JS_PUBLIC_API JSObject* JS::ReadableStreamDefaultReaderRead(
JSContext* cx, HandleObject readerObj) {
AssertHeapIsIdle();
CHECK_THREAD(cx);
Rooted<ReadableStreamDefaultReader*> unwrappedReader(
cx, APIUnwrapAndDowncast<ReadableStreamDefaultReader>(cx, readerObj));
if (!unwrappedReader) {
return nullptr;
}
MOZ_ASSERT(unwrappedReader->forAuthorCode() == ForAuthorCodeBool::No,
"C++ code should not touch readers created by scripts");
return ::ReadableStreamDefaultReaderRead(cx, unwrappedReader);
}