diff options
Diffstat (limited to 'js/src')
27 files changed, 6922 insertions, 35 deletions
diff --git a/js/src/builtin/Stream.cpp b/js/src/builtin/Stream.cpp new file mode 100644 index 0000000000..c8d8e3e324 --- /dev/null +++ b/js/src/builtin/Stream.cpp @@ -0,0 +1,5488 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "builtin/Stream.h" + +#include "js/Stream.h" + +#include "jscntxt.h" + +#include "gc/Heap.h" +#include "vm/SelfHosting.h" + +#include "jsobjinlines.h" + +#include "vm/NativeObject-inl.h" + +using namespace js; + +enum StreamSlots { + StreamSlot_Controller, + StreamSlot_Reader, + StreamSlot_State, + StreamSlot_StoredError, + StreamSlotCount +}; + +enum ReaderSlots { + ReaderSlot_Stream, + ReaderSlot_Requests, + ReaderSlot_ClosedPromise, + ReaderSlotCount, +}; + +enum ReaderType { + ReaderType_Default, + ReaderType_BYOB +}; + +// ReadableStreamDefaultController and ReadableByteStreamController are both +// queue containers and must have these slots at identical offsets. +enum QueueContainerSlots { + QueueContainerSlot_Queue, + QueueContainerSlot_TotalSize, + QueueContainerSlotCount +}; + +// These slots are identical between the two types of ReadableStream +// controllers. +enum ControllerSlots { + ControllerSlot_Stream = QueueContainerSlotCount, + ControllerSlot_UnderlyingSource, + ControllerSlot_StrategyHWM, + ControllerSlot_Flags, + ControllerSlotCount +}; + +enum DefaultControllerSlots { + DefaultControllerSlot_StrategySize = ControllerSlotCount, + DefaultControllerSlotCount +}; + +enum ByteControllerSlots { + ByteControllerSlot_BYOBRequest = ControllerSlotCount, + ByteControllerSlot_PendingPullIntos, + ByteControllerSlot_AutoAllocateSize, + ByteControllerSlotCount +}; + +enum ControllerFlags { + ControllerFlag_Started = 1 << 0, + ControllerFlag_Pulling = 1 << 1, + ControllerFlag_PullAgain = 1 << 2, + ControllerFlag_CloseRequested = 1 << 3, + ControllerFlag_TeeBranch = 1 << 4, + ControllerFlag_TeeBranch1 = 1 << 5, + ControllerFlag_TeeBranch2 = 1 << 6, + ControllerFlag_ExternalSource = 1 << 7, + ControllerFlag_SourceLocked = 1 << 8, +}; + +// Offset at which embedding flags are stored. +constexpr uint8_t ControllerEmbeddingFlagsOffset = 24; + +enum BYOBRequestSlots { + BYOBRequestSlot_Controller, + BYOBRequestSlot_View, + BYOBRequestSlotCount +}; + +template<class T> +MOZ_ALWAYS_INLINE bool +Is(const HandleValue v) +{ + return v.isObject() && v.toObject().is<T>(); +} + +#ifdef DEBUG +static bool +IsReadableStreamController(const JSObject* controller) +{ + return controller->is<ReadableStreamDefaultController>() || + controller->is<ReadableByteStreamController>(); +} +#endif // DEBUG + +static inline uint32_t +ControllerFlags(const NativeObject* controller) +{ + MOZ_ASSERT(IsReadableStreamController(controller)); + return controller->getFixedSlot(ControllerSlot_Flags).toInt32(); +} + +static inline void +AddControllerFlags(NativeObject* controller, uint32_t flags) +{ + MOZ_ASSERT(IsReadableStreamController(controller)); + controller->setFixedSlot(ControllerSlot_Flags, + Int32Value(ControllerFlags(controller) | flags)); +} + +static inline void +RemoveControllerFlags(NativeObject* controller, uint32_t flags) +{ + MOZ_ASSERT(IsReadableStreamController(controller)); + controller->setFixedSlot(ControllerSlot_Flags, + Int32Value(ControllerFlags(controller) & ~flags)); +} + +static inline uint32_t +StreamState(const ReadableStream* stream) +{ + return stream->getFixedSlot(StreamSlot_State).toInt32(); +} + +static inline void +SetStreamState(ReadableStream* stream, uint32_t state) +{ + MOZ_ASSERT_IF(stream->disturbed(), state & ReadableStream::Disturbed); + MOZ_ASSERT_IF(stream->closed() || stream->errored(), !(state & ReadableStream::Readable)); + stream->setFixedSlot(StreamSlot_State, Int32Value(state)); +} + +bool +ReadableStream::readable() const +{ + return StreamState(this) & Readable; +} + +bool +ReadableStream::closed() const +{ + return StreamState(this) & Closed; +} + +bool +ReadableStream::errored() const +{ + return StreamState(this) & Errored; +} + +bool +ReadableStream::disturbed() const +{ + return StreamState(this) & Disturbed; +} + +inline static bool +ReaderHasStream(const NativeObject* reader) +{ + MOZ_ASSERT(JS::IsReadableStreamReader(reader)); + return !reader->getFixedSlot(ReaderSlot_Stream).isUndefined(); +} + +bool +js::ReadableStreamReaderIsClosed(const JSObject* reader) +{ + return !ReaderHasStream(&reader->as<NativeObject>()); +} + +inline static MOZ_MUST_USE ReadableStream* +StreamFromController(const NativeObject* controller) +{ + MOZ_ASSERT(IsReadableStreamController(controller)); + return &controller->getFixedSlot(ControllerSlot_Stream).toObject().as<ReadableStream>(); +} + +inline static MOZ_MUST_USE NativeObject* +ControllerFromStream(const ReadableStream* stream) +{ + Value controllerVal = stream->getFixedSlot(StreamSlot_Controller); + MOZ_ASSERT(IsReadableStreamController(&controllerVal.toObject())); + return &controllerVal.toObject().as<NativeObject>(); +} + +inline static bool +HasController(const ReadableStream* stream) +{ + return !stream->getFixedSlot(StreamSlot_Controller).isUndefined(); +} + +JS::ReadableStreamMode +ReadableStream::mode() const +{ + NativeObject* controller = ControllerFromStream(this); + if (controller->is<ReadableStreamDefaultController>()) + return JS::ReadableStreamMode::Default; + return controller->as<ReadableByteStreamController>().hasExternalSource() + ? JS::ReadableStreamMode::ExternalSource + : JS::ReadableStreamMode::Byte; +} + +inline static MOZ_MUST_USE ReadableStream* +StreamFromReader(const NativeObject* reader) +{ + MOZ_ASSERT(ReaderHasStream(reader)); + return &reader->getFixedSlot(ReaderSlot_Stream).toObject().as<ReadableStream>(); +} + +inline static MOZ_MUST_USE NativeObject* +ReaderFromStream(const NativeObject* stream) +{ + Value readerVal = stream->getFixedSlot(StreamSlot_Reader); + MOZ_ASSERT(JS::IsReadableStreamReader(&readerVal.toObject())); + return &readerVal.toObject().as<NativeObject>(); +} + +inline static bool +HasReader(const ReadableStream* stream) +{ + return !stream->getFixedSlot(StreamSlot_Reader).isUndefined(); +} + +inline static MOZ_MUST_USE JSFunction* +NewHandler(JSContext *cx, Native handler, HandleObject target) +{ + RootedAtom funName(cx, cx->names().empty); + RootedFunction handlerFun(cx, NewNativeFunction(cx, handler, 0, funName, + gc::AllocKind::FUNCTION_EXTENDED, + GenericObject)); + if (!handlerFun) + return nullptr; + handlerFun->setExtendedSlot(0, ObjectValue(*target)); + return handlerFun; +} + +template<class T> +inline static MOZ_MUST_USE T* +TargetFromHandler(JSObject& handler) +{ + return &handler.as<JSFunction>().getExtendedSlot(0).toObject().as<T>(); +} + +inline static MOZ_MUST_USE bool +ResetQueue(JSContext* cx, HandleNativeObject container); + +inline static MOZ_MUST_USE bool +InvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg, + MutableHandleValue rval); + +static MOZ_MUST_USE JSObject* +PromiseInvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg); + +static MOZ_MUST_USE JSObject* +PromiseRejectedWithPendingError(JSContext* cx) { + // Not much we can do about uncatchable exceptions, just bail. + RootedValue exn(cx); + if (!GetAndClearException(cx, &exn)) + return nullptr; + return PromiseObject::unforgeableReject(cx, exn); +} + +static bool +ReportArgTypeError(JSContext* cx, const char* funName, const char* expectedType, + HandleValue arg) +{ + UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, arg, nullptr); + if (!bytes) + return false; + + return JS_ReportErrorFlagsAndNumberLatin1(cx, JSREPORT_ERROR, GetErrorMessage, + nullptr, JSMSG_NOT_EXPECTED_TYPE, + funName, expectedType, bytes.get()); +} + +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; +} + +static MOZ_MUST_USE bool +RejectNonGenericMethod(JSContext* cx, const CallArgs& args, + const char* className, const char* methodName) +{ + ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(), + nullptr, className, methodName); + + return ReturnPromiseRejectedWithPendingError(cx, args); +} + +inline static MOZ_MUST_USE NativeObject* +SetNewList(JSContext* cx, HandleNativeObject container, uint32_t slot) +{ + NativeObject* list = NewObjectWithNullTaggedProto<PlainObject>(cx); + if (!list) + return nullptr; + container->setFixedSlot(slot, ObjectValue(*list)); + return list; +} + +inline static MOZ_MUST_USE bool +AppendToList(JSContext* cx, HandleNativeObject list, HandleValue value) +{ + uint32_t length = list->getDenseInitializedLength(); + + if (!list->ensureElements(cx, length + 1)) + return false; + + list->ensureDenseInitializedLength(cx, length, 1); + list->setDenseElement(length, value); + + return true; +} + +template<class T> +inline static MOZ_MUST_USE T* +PeekList(NativeObject* list) +{ + MOZ_ASSERT(list->getDenseInitializedLength() > 0); + return &list->getDenseElement(0).toObject().as<T>(); +} + +template<class T> +inline static MOZ_MUST_USE T* +ShiftFromList(JSContext* cx, HandleNativeObject list) +{ + uint32_t length = list->getDenseInitializedLength(); + MOZ_ASSERT(length > 0); + + Rooted<T*> entry(cx, &list->getDenseElement(0).toObject().as<T>()); + list->moveDenseElements(0, 1, length - 1); + list->shrinkElements(cx, length - 1); + + list->setDenseInitializedLength(length - 1); + + return entry; +} + +class ByteStreamChunk : public NativeObject +{ + private: + enum Slots { + Slot_Buffer = 0, + Slot_ByteOffset, + Slot_ByteLength, + SlotCount + }; + + public: + static const Class class_; + + ArrayBufferObject* buffer() { + return &getFixedSlot(Slot_Buffer).toObject().as<ArrayBufferObject>(); + } + uint32_t byteOffset() { return getFixedSlot(Slot_ByteOffset).toInt32(); } + void SetByteOffset(uint32_t offset) { + setFixedSlot(Slot_ByteOffset, Int32Value(offset)); + } + uint32_t byteLength() { return getFixedSlot(Slot_ByteLength).toInt32(); } + void SetByteLength(uint32_t length) { + setFixedSlot(Slot_ByteLength, Int32Value(length)); + } + + static ByteStreamChunk* create(JSContext* cx, HandleObject buffer, uint32_t byteOffset, + uint32_t byteLength) + { + Rooted<ByteStreamChunk*> chunk(cx, NewObjectWithClassProto<ByteStreamChunk>(cx)); + if (!chunk) + return nullptr; + + chunk->setFixedSlot(Slot_Buffer, ObjectValue(*buffer)); + chunk->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset)); + chunk->setFixedSlot(Slot_ByteLength, Int32Value(byteLength)); + return chunk; + } +}; + +const Class ByteStreamChunk::class_ = { + "ByteStreamChunk", + JSCLASS_HAS_RESERVED_SLOTS(SlotCount) +}; + +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, NewObjectWithClassProto<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, NewObjectWithClassProto<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 +{ + private: + enum Slots { + Slot_Flags = 0, + Slot_Reason1, + Slot_Reason2, + Slot_Promise, + Slot_Stream, + Slot_Branch1, + Slot_Branch2, + SlotCount + }; + + 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* promise() { + return &getFixedSlot(Slot_Promise).toObject().as<PromiseObject>(); + } + ReadableStream* stream() { + return &getFixedSlot(Slot_Stream).toObject().as<ReadableStream>(); + } + ReadableStreamDefaultReader* reader() { + return &ReaderFromStream(stream())->as<ReadableStreamDefaultReader>(); + } + + ReadableStreamDefaultController* branch1() { + ReadableStreamDefaultController* controller = &getFixedSlot(Slot_Branch1).toObject() + .as<ReadableStreamDefaultController>(); + MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch); + MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch1); + return controller; + } + void setBranch1(ReadableStreamDefaultController* controller) { + MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch); + MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch1); + setFixedSlot(Slot_Branch1, ObjectValue(*controller)); + } + + ReadableStreamDefaultController* branch2() { + ReadableStreamDefaultController* controller = &getFixedSlot(Slot_Branch2).toObject() + .as<ReadableStreamDefaultController>(); + MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch); + MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch2); + return controller; + } + void setBranch2(ReadableStreamDefaultController* controller) { + MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch); + MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch2); + setFixedSlot(Slot_Branch2, ObjectValue(*controller)); + } + + static TeeState* create(JSContext* cx, Handle<ReadableStream*> stream) { + Rooted<TeeState*> state(cx, NewObjectWithClassProto<TeeState>(cx)); + if (!state) + return nullptr; + + Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx)); + if (!promise) + return nullptr; + + state->setFixedSlot(Slot_Flags, Int32Value(0)); + state->setFixedSlot(Slot_Promise, ObjectValue(*promise)); + state->setFixedSlot(Slot_Stream, ObjectValue(*stream)); + + 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, \ + 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_ \ +}; + +// Streams spec, 3.2.3., steps 1-4. +ReadableStream* +ReadableStream::createStream(JSContext* cx, HandleObject proto /* = nullptr */) +{ + Rooted<ReadableStream*> stream(cx, NewObjectWithClassProto<ReadableStream>(cx, proto)); + if (!stream) + return nullptr; + + // Step 1: Set this.[[state]] to "readable". + // Step 2: Set this.[[reader]] and this.[[storedError]] to undefined (implicit). + // Step 3: Set this.[[disturbed]] to false (implicit). + // Step 4: Set this.[[readableStreamController]] to undefined (implicit). + stream->setFixedSlot(StreamSlot_State, Int32Value(Readable)); + + return stream; +} + +static MOZ_MUST_USE ReadableStreamDefaultController* +CreateReadableStreamDefaultController(JSContext* cx, Handle<ReadableStream*> stream, + HandleValue underlyingSource, HandleValue size, + HandleValue highWaterMarkVal); + +// Streams spec, 3.2.3., steps 1-4, 8. +ReadableStream* +ReadableStream::createDefaultStream(JSContext* cx, HandleValue underlyingSource, + HandleValue size, HandleValue highWaterMark, + HandleObject proto /* = nullptr */) +{ + // Steps 1-4. + Rooted<ReadableStream*> stream(cx, createStream(cx)); + if (!stream) + return nullptr; + + // Step 8.b: Set this.[[readableStreamController]] to + // ? Construct(ReadableStreamDefaultController, + // « this, underlyingSource, size, + // highWaterMark »). + RootedObject controller(cx, CreateReadableStreamDefaultController(cx, stream, + underlyingSource, + size, + highWaterMark)); + if (!controller) + return nullptr; + + stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller)); + + return stream; +} + +static MOZ_MUST_USE ReadableByteStreamController* +CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream, + HandleValue underlyingByteSource, + HandleValue highWaterMarkVal); + +// Streams spec, 3.2.3., steps 1-4, 7. +ReadableStream* +ReadableStream::createByteStream(JSContext* cx, HandleValue underlyingSource, + HandleValue highWaterMark, HandleObject proto /* = nullptr */) +{ + // Steps 1-4. + Rooted<ReadableStream*> stream(cx, createStream(cx, proto)); + if (!stream) + return nullptr; + + // Step 7.b: Set this.[[readableStreamController]] to + // ? Construct(ReadableByteStreamController, + // « this, underlyingSource, highWaterMark »). + RootedObject controller(cx, CreateReadableByteStreamController(cx, stream, + underlyingSource, + highWaterMark)); + if (!controller) + return nullptr; + + stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller)); + + return stream; +} + +static MOZ_MUST_USE ReadableByteStreamController* +CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream, + void* underlyingSource); + +ReadableStream* +ReadableStream::createExternalSourceStream(JSContext* cx, void* underlyingSource, + uint8_t flags, HandleObject proto /* = nullptr */) +{ + Rooted<ReadableStream*> stream(cx, createStream(cx, proto)); + if (!stream) + return nullptr; + + RootedNativeObject controller(cx, CreateReadableByteStreamController(cx, stream, + underlyingSource)); + if (!controller) + return nullptr; + + stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller)); + AddControllerFlags(controller, flags << ControllerEmbeddingFlagsOffset); + + return stream; +} + +// Streams spec, 3.2.3. +bool +ReadableStream::constructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedValue val(cx, args.get(0)); + RootedValue underlyingSource(cx, args.get(0)); + RootedValue options(cx, args.get(1)); + + // Do argument handling first to keep the right order of error reporting. + if (underlyingSource.isUndefined()) { + RootedObject sourceObj(cx, NewBuiltinClassInstance<PlainObject>(cx)); + if (!sourceObj) + return false; + underlyingSource = ObjectValue(*sourceObj); + } + RootedValue size(cx); + RootedValue highWaterMark(cx); + + if (!options.isUndefined()) { + if (!GetProperty(cx, options, cx->names().size, &size)) + return false; + + if (!GetProperty(cx, options, cx->names().highWaterMark, &highWaterMark)) + return false; + } + + if (!ThrowIfNotConstructing(cx, args, "ReadableStream")) + return false; + + // Step 5: Let type be ? GetV(underlyingSource, "type"). + RootedValue typeVal(cx); + if (!GetProperty(cx, underlyingSource, cx->names().type, &typeVal)) + return false; + + // Step 6: Let typeString be ? ToString(type). + RootedString type(cx, ToString<CanGC>(cx, typeVal)); + if (!type) + return false; + + int32_t notByteStream; + if (!CompareStrings(cx, type, cx->names().bytes, ¬ByteStream)) + return false; + + // Step 7.a & 8.a (reordered): If highWaterMark is undefined, let + // highWaterMark be 1 (or 0 for byte streams). + if (highWaterMark.isUndefined()) + highWaterMark = Int32Value(notByteStream ? 1 : 0); + + Rooted<ReadableStream*> stream(cx); + + // Step 7: If typeString is "bytes", + if (!notByteStream) { + // Step 7.b: Set this.[[readableStreamController]] to + // ? Construct(ReadableByteStreamController, + // « this, underlyingSource, highWaterMark »). + stream = createByteStream(cx, underlyingSource, highWaterMark); + } else if (typeVal.isUndefined()) { + // Step 8: Otherwise, if type is undefined, + // Step 8.b: Set this.[[readableStreamController]] to + // ? Construct(ReadableStreamDefaultController, + // « this, underlyingSource, size, highWaterMark »). + stream = createDefaultStream(cx, underlyingSource, size, highWaterMark); + } else { + // Step 9: Otherwise, throw a RangeError exception. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG); + return false; + } + if (!stream) + return false; + + args.rval().setObject(*stream); + return true; +} + +// Streams spec, 3.2.4.1. get locked +static MOZ_MUST_USE bool +ReadableStream_locked_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>()); + + // Step 2: Return ! IsReadableStreamLocked(this). + args.rval().setBoolean(stream->locked()); + return true; +} + +static bool +ReadableStream_locked(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_locked_impl>(cx, args); +} + +// Streams spec, 3.2.4.2. cancel ( reason ) +static MOZ_MUST_USE bool +ReadableStream_cancel(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + // Step 1: If ! IsReadableStream(this) is false, return a promise rejected + // with a TypeError exception. + if (!Is<ReadableStream>(args.thisv())) { + ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(), + nullptr, "cancel", ""); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>()); + + // Step 2: If ! IsReadableStreamLocked(this) is true, return a promise + // rejected with a TypeError exception. + if (stream->locked()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_NOT_LOCKED, "cancel"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 3: Return ! ReadableStreamCancel(this, reason). + RootedObject cancelPromise(cx, ReadableStream::cancel(cx, stream, args.get(0))); + if (!cancelPromise) + return false; + args.rval().setObject(*cancelPromise); + return true; +} + +static MOZ_MUST_USE ReadableStreamDefaultReader* +CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> stream); + +static MOZ_MUST_USE ReadableStreamBYOBReader* +CreateReadableStreamBYOBReader(JSContext* cx, Handle<ReadableStream*> stream); + +// Streams spec, 3.2.4.3. getReader() +static MOZ_MUST_USE bool +ReadableStream_getReader_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>()); + RootedObject reader(cx); + + // Step 2: If mode is undefined, return + // ? AcquireReadableStreamDefaultReader(this). + RootedValue modeVal(cx); + HandleValue optionsVal = args.get(0); + if (!optionsVal.isUndefined()) { + if (!GetProperty(cx, optionsVal, cx->names().mode, &modeVal)) + return false; + } + + if (modeVal.isUndefined()) { + reader = CreateReadableStreamDefaultReader(cx, stream); + } else { + // Step 3: Set mode to ? ToString(mode) (implicit). + RootedString mode(cx, ToString<CanGC>(cx, modeVal)); + if (!mode) + return false; + + // Step 4: If mode is "byob", return ? AcquireReadableStreamBYOBReader(this). + int32_t notByob; + if (!CompareStrings(cx, mode, cx->names().byob, ¬Byob)) + return false; + if (notByob) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_INVALID_READER_MODE); + // Step 5: Throw a RangeError exception. + return false; + + } + reader = CreateReadableStreamBYOBReader(cx, stream); + } + + // Reordered second part of steps 2 and 4. + if (!reader) + return false; + args.rval().setObject(*reader); + return true; +} + +static bool +ReadableStream_getReader(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_getReader_impl>(cx, args); +} + +// Streams spec, 3.2.4.4. pipeThrough({ writable, readable }, options) +static MOZ_MUST_USE bool +ReadableStream_pipeThrough(JSContext* cx, unsigned argc, Value* vp) +{ + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, "pipeThrough"); + return false; + // // Step 1: Perform ? Invoke(this, "pipeTo", « writable, options »). + + // // Step 2: Return readable. + // return readable; +} + +// Streams spec, 3.2.4.5. pipeTo(dest, { preventClose, preventAbort, preventCancel } = {}) +// TODO: Unimplemented since spec is not complete yet. +static MOZ_MUST_USE bool +ReadableStream_pipeTo(JSContext* cx, unsigned argc, Value* vp) +{ + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, "pipeTo"); + return false; +} + +static MOZ_MUST_USE bool +ReadableStreamTee(JSContext* cx, Handle<ReadableStream*> stream, bool cloneForBranch2, + MutableHandle<ReadableStream*> branch1, MutableHandle<ReadableStream*> branch2); + +// Streams spec, 3.2.4.6. tee() +static MOZ_MUST_USE bool +ReadableStream_tee_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>()); + + // Step 2: Let branches be ? ReadableStreamTee(this, false). + Rooted<ReadableStream*> branch1(cx); + Rooted<ReadableStream*> branch2(cx); + if (!ReadableStreamTee(cx, stream, false, &branch1, &branch2)) + return false; + + // Step 3: Return ! CreateArrayFromList(branches). + 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 bool +ReadableStream_tee(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_tee_impl>(cx, args); +} + +static const JSFunctionSpec ReadableStream_methods[] = { + JS_FN("cancel", ReadableStream_cancel, 1, 0), + JS_FN("getReader", ReadableStream_getReader, 0, 0), + JS_FN("pipeThrough", ReadableStream_pipeThrough, 2, 0), + JS_FN("pipeTo", ReadableStream_pipeTo, 1, 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, StreamSlotCount, 0, 0, JS_NULL_CLASS_OPS); + +// Streams spec, 3.3.1. AcquireReadableStreamBYOBReader ( stream ) +// Always inlined. + +// Streams spec, 3.3.2. AcquireReadableStreamDefaultReader ( stream ) +// Always inlined. + +// Streams spec, 3.3.3. IsReadableStream ( x ) +// Using is<T> instead. + +// Streams spec, 3.3.4. IsReadableStreamDisturbed ( stream ) +// Using stream->disturbed() instead. + +// Streams spec, 3.3.5. IsReadableStreamLocked ( stream ) +bool +ReadableStream::locked() const +{ + // Step 1: Assert: ! IsReadableStream(stream) is true (implicit). + // Step 2: If stream.[[reader]] is undefined, return false. + // Step 3: Return true. + // Special-casing for streams with external sources. Those can be locked + // explicitly via JSAPI, which is indicated by a controller flag. + // IsReadableStreamLocked is called from the controller's constructor, at + // which point we can't yet call ControllerFromStream(stream), but the + // source also can't be locked yet. + if (HasController(this) && + (ControllerFlags(ControllerFromStream(this)) & ControllerFlag_SourceLocked)) + { + return true; + } + return HasReader(this); +} + +static MOZ_MUST_USE bool +ReadableStreamDefaultControllerClose(JSContext* cx, + Handle<ReadableStreamDefaultController*> controller); +static MOZ_MUST_USE bool +ReadableStreamDefaultControllerEnqueue(JSContext* cx, + Handle<ReadableStreamDefaultController*> controller, + HandleValue chunk); + +static bool +TeeReaderReadHandler(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args.callee())); + HandleValue resultVal = args.get(0); + + // Step a: Assert: Type(result) is Object. + RootedObject result(cx, &resultVal.toObject()); + + // Step b: Let value be ? Get(result, "value"). + RootedValue value(cx); + if (!GetPropertyPure(cx, result, NameToId(cx->names().value), value.address())) + return false; + + // Step c: Let done be ? Get(result, "done"). + RootedValue doneVal(cx); + if (!GetPropertyPure(cx, result, NameToId(cx->names().done), doneVal.address())) + return false; + + // Step d: Assert: Type(done) is Boolean. + bool done = doneVal.toBoolean(); + + // Step e: If done is true and teeState.[[closedOrErrored]] is false, + if (done && !teeState->closedOrErrored()) { + // Step i: If teeState.[[canceled1]] is false, + if (!teeState->canceled1()) { + // Step 1: Perform ! ReadableStreamDefaultControllerClose(branch1). + Rooted<ReadableStreamDefaultController*> branch1(cx, teeState->branch1()); + if (!ReadableStreamDefaultControllerClose(cx, branch1)) + return false; + } + + // Step ii: If teeState.[[canceled2]] is false, + if (!teeState->canceled2()) { + // Step 1: Perform ! ReadableStreamDefaultControllerClose(branch1). + Rooted<ReadableStreamDefaultController*> branch2(cx, teeState->branch2()); + if (!ReadableStreamDefaultControllerClose(cx, branch2)) + return false; + } + + // Step iii: Set teeState.[[closedOrErrored]] to true. + teeState->setClosedOrErrored(); + } + + // Step f: If teeState.[[closedOrErrored]] is true, return. + if (teeState->closedOrErrored()) + return true; + + // Step g: Let value1 and value2 be value. + RootedValue value1(cx, value); + RootedValue value2(cx, value); + + // Step h: If teeState.[[canceled2]] is false and cloneForBranch2 is + // true, set value2 to + // ? StructuredDeserialize(StructuredSerialize(value2), + // the current Realm Record). + // TODO: add StructuredClone() intrinsic. + MOZ_ASSERT(!teeState->cloneForBranch2(), "tee(cloneForBranch2=true) should not be exposed"); + + // Step i: If teeState.[[canceled1]] is false, perform + // ? ReadableStreamDefaultControllerEnqueue(branch1, value1). + Rooted<ReadableStreamDefaultController*> controller(cx); + if (!teeState->canceled1()) { + controller = teeState->branch1(); + if (!ReadableStreamDefaultControllerEnqueue(cx, controller, value1)) + return false; + } + + // Step j: If teeState.[[canceled2]] is false, + // perform ? ReadableStreamDefaultControllerEnqueue(branch2, value2). + if (!teeState->canceled2()) { + controller = teeState->branch2(); + if (!ReadableStreamDefaultControllerEnqueue(cx, controller, value2)) + return false; + } + + args.rval().setUndefined(); + return true; +} + +static MOZ_MUST_USE JSObject* +ReadableStreamTee_Pull(JSContext* cx, Handle<TeeState*> teeState, + Handle<ReadableStream*> branchStream) +{ + // Step 1: Let reader be F.[[reader]], branch1 be F.[[branch1]], + // branch2 be F.[[branch2]], teeState be F.[[teeState]], and + // cloneForBranch2 be F.[[cloneForBranch2]]. + + // Step 2: Return the result of transforming + // ! ReadableStreamDefaultReaderRead(reader) by a fulfillment + // handler which takes the argument result and performs the + // following steps: + Rooted<ReadableStreamDefaultReader*> reader(cx, teeState->reader()); + RootedObject readPromise(cx, ReadableStreamDefaultReader::read(cx, reader)); + if (!readPromise) + 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*> teeState, + Handle<ReadableStreamDefaultController*> branch, HandleValue reason) +{ + // Step 1: Let stream be F.[[stream]] and teeState be F.[[teeState]]. + Rooted<ReadableStream*> stream(cx, teeState->stream()); + + bool bothBranchesCanceled = false; + + // Step 2: Set teeState.[[canceled1]] to true. + // Step 3: Set teeState.[[reason1]] to reason. + if (ControllerFlags(branch) & ControllerFlag_TeeBranch1) { + teeState->setCanceled1(reason); + bothBranchesCanceled = teeState->canceled2(); + } else { + MOZ_ASSERT(ControllerFlags(branch) & ControllerFlag_TeeBranch2); + teeState->setCanceled2(reason); + bothBranchesCanceled = teeState->canceled1(); + } + + // Step 4: If teeState.[[canceled1]] is true, + // Step 4: If teeState.[[canceled2]] is true, + if (bothBranchesCanceled) { + // Step a: Let compositeReason be + // ! CreateArrayFromList(« teeState.[[reason1]], teeState.[[reason2]] »). + RootedNativeObject compositeReason(cx, NewDenseFullyAllocatedArray(cx, 2)); + if (!compositeReason) + return nullptr; + + compositeReason->setDenseInitializedLength(2); + compositeReason->initDenseElement(0, teeState->reason1()); + compositeReason->initDenseElement(1, teeState->reason2()); + RootedValue compositeReasonVal(cx, ObjectValue(*compositeReason)); + + Rooted<PromiseObject*> promise(cx, teeState->promise()); + + // Step b: Let cancelResult be ! ReadableStreamCancel(stream, compositeReason). + RootedObject cancelResult(cx, ReadableStream::cancel(cx, stream, compositeReasonVal)); + if (!cancelResult) { + if (!RejectPromiseWithPendingError(cx, promise)) + return nullptr; + } else { + // Step c: Resolve teeState.[[promise]] with cancelResult. + RootedValue resultVal(cx, ObjectValue(*cancelResult)); + if (!PromiseObject::resolve(cx, promise, resultVal)) + return nullptr; + } + } + + // Step 5: Return teeState.[[promise]]. + return teeState->promise(); +} + +static MOZ_MUST_USE bool +ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e); + +// Streams spec, 3.3.6. step 21: +// Upon rejection of reader.[[closedPromise]] with reason r, +static bool +TeeReaderClosedHandler(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args.callee())); + HandleValue reason = args.get(0); + + // Step a: If teeState.[[closedOrErrored]] is false, then: + if (!teeState->closedOrErrored()) { + // Step a.i: Perform ! ReadableStreamDefaultControllerError(pull.[[branch1]], r). + Rooted<ReadableStreamDefaultController*> branch1(cx, teeState->branch1()); + if (!ReadableStreamControllerError(cx, branch1, reason)) + return false; + + // Step a.ii: Perform ! ReadableStreamDefaultControllerError(pull.[[branch2]], r). + Rooted<ReadableStreamDefaultController*> branch2(cx, teeState->branch2()); + if (!ReadableStreamControllerError(cx, branch2, reason)) + return false; + + // Step a.iii: Set teeState.[[closedOrErrored]] to true. + teeState->setClosedOrErrored(); + } + + return true; +} + +// Streams spec, 3.3.6. ReadableStreamTee ( stream, cloneForBranch2 ) +static MOZ_MUST_USE bool +ReadableStreamTee(JSContext* cx, Handle<ReadableStream*> stream, bool cloneForBranch2, + MutableHandle<ReadableStream*> branch1Stream, + MutableHandle<ReadableStream*> branch2Stream) +{ + // Step 1: Assert: ! IsReadableStream(stream) is true (implicit). + // Step 2: Assert: Type(cloneForBranch2) is Boolean (implicit). + + // Step 3: Let reader be ? AcquireReadableStreamDefaultReader(stream). + Rooted<ReadableStreamDefaultReader*> reader(cx, CreateReadableStreamDefaultReader(cx, stream)); + if (!reader) + return false; + + // Step 4: Let teeState be Record {[[closedOrErrored]]: false, + // [[canceled1]]: false, + // [[canceled2]]: false, + // [[reason1]]: undefined, + // [[reason2]]: undefined, + // [[promise]]: a new promise}. + Rooted<TeeState*> teeState(cx, TeeState::create(cx, stream)); + if (!teeState) + return false; + + // Steps 5-10 omitted because our implementation works differently. + + // Step 5: Let pull be a new ReadableStreamTee pull function. + // Step 6: Set pull.[[reader]] to reader, pull.[[teeState]] to teeState, and + // pull.[[cloneForBranch2]] to cloneForBranch2. + // Step 7: Let cancel1 be a new ReadableStreamTee branch 1 cancel function. + // Step 8: Set cancel1.[[stream]] to stream and cancel1.[[teeState]] to + // teeState. + + // Step 9: Let cancel2 be a new ReadableStreamTee branch 2 cancel function. + // Step 10: Set cancel2.[[stream]] to stream and cancel2.[[teeState]] to + // teeState. + + // Step 11: Let underlyingSource1 be ! ObjectCreate(%ObjectPrototype%). + // Step 12: Perform ! CreateDataProperty(underlyingSource1, "pull", pull). + // Step 13: Perform ! CreateDataProperty(underlyingSource1, "cancel", cancel1). + + // Step 14: Let branch1Stream be ! Construct(ReadableStream, underlyingSource1). + RootedValue hwmValue(cx, NumberValue(1)); + RootedValue underlyingSource(cx, ObjectValue(*teeState)); + branch1Stream.set(ReadableStream::createDefaultStream(cx, underlyingSource, + UndefinedHandleValue, + hwmValue)); + if (!branch1Stream) + return false; + + Rooted<ReadableStreamDefaultController*> branch1(cx); + branch1 = &ControllerFromStream(branch1Stream)->as<ReadableStreamDefaultController>(); + AddControllerFlags(branch1, ControllerFlag_TeeBranch | ControllerFlag_TeeBranch1); + teeState->setBranch1(branch1); + + // Step 15: Let underlyingSource2 be ! ObjectCreate(%ObjectPrototype%). + // Step 16: Perform ! CreateDataProperty(underlyingSource2, "pull", pull). + // Step 17: Perform ! CreateDataProperty(underlyingSource2, "cancel", cancel2). + + // Step 18: Let branch2Stream be ! Construct(ReadableStream, underlyingSource2). + branch2Stream.set(ReadableStream::createDefaultStream(cx, underlyingSource, + UndefinedHandleValue, + hwmValue)); + if (!branch2Stream) + return false; + + Rooted<ReadableStreamDefaultController*> branch2(cx); + branch2 = &ControllerFromStream(branch2Stream)->as<ReadableStreamDefaultController>(); + AddControllerFlags(branch2, ControllerFlag_TeeBranch | ControllerFlag_TeeBranch2); + teeState->setBranch2(branch2); + + // Step 19: Set pull.[[branch1]] to branch1Stream.[[readableStreamController]]. + // Step 20: Set pull.[[branch2]] to branch2Stream.[[readableStreamController]]. + + // Step 21: Upon rejection of reader.[[closedPromise]] with reason r, + RootedObject closedPromise(cx, &reader->getFixedSlot(ReaderSlot_ClosedPromise).toObject()); + + RootedObject onRejected(cx, NewHandler(cx, TeeReaderClosedHandler, teeState)); + if (!onRejected) + return false; + + if (!JS::AddPromiseReactions(cx, closedPromise, nullptr, onRejected)) + return false; + + // Step 22: Return « branch1, branch2 ». + return true; +} + +// Streams spec, 3.4.1. ReadableStreamAddReadIntoRequest ( stream ) +static MOZ_MUST_USE PromiseObject* +ReadableStreamAddReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream) +{ + // Step 1: MOZ_ASSERT: ! IsReadableStreamBYOBReader(stream.[[reader]]) is true. + RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader)); + RootedNativeObject reader(cx, &val.toObject().as<ReadableStreamBYOBReader>()); + + // Step 2: MOZ_ASSERT: stream.[[state]] is "readable" or "closed". + MOZ_ASSERT(stream->readable() || stream->closed()); + + // Step 3: Let promise be a new promise. + Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx)); + if (!promise) + return nullptr; + + // Step 4: Let readIntoRequest be Record {[[promise]]: promise}. + // Step 5: Append readIntoRequest as the last element of stream.[[reader]].[[readIntoRequests]]. + val = reader->getFixedSlot(ReaderSlot_Requests); + RootedNativeObject readIntoRequests(cx, &val.toObject().as<NativeObject>()); + // Since [[promise]] is the Record's only field, we store it directly. + val = ObjectValue(*promise); + if (!AppendToList(cx, readIntoRequests, val)) + return nullptr; + + // Step 6: Return promise. + return promise; +} + +// Streams spec, 3.4.2. ReadableStreamAddReadRequest ( stream ) +static MOZ_MUST_USE PromiseObject* +ReadableStreamAddReadRequest(JSContext* cx, Handle<ReadableStream*> stream) +{ + MOZ_ASSERT(stream->is<ReadableStream>()); + + // Step 1: Assert: ! IsReadableStreamDefaultReader(stream.[[reader]]) is true. + RootedNativeObject reader(cx, ReaderFromStream(stream)); + + // Step 2: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(stream->readable()); + + // Step 3: Let promise be a new promise. + Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx)); + if (!promise) + return nullptr; + + // Step 4: Let readRequest be Record {[[promise]]: promise}. + // Step 5: Append readRequest as the last element of stream.[[reader]].[[readRequests]]. + RootedValue val(cx, reader->getFixedSlot(ReaderSlot_Requests)); + RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>()); + + // Since [[promise]] is the Record's only field, we store it directly. + val = ObjectValue(*promise); + if (!AppendToList(cx, readRequests, val)) + return nullptr; + + // Step 6: Return promise. + return promise; +} + +static MOZ_MUST_USE JSObject* +ReadableStreamControllerCancelSteps(JSContext* cx, + HandleNativeObject controller, HandleValue reason); + +// Used for transforming the result of promise fulfillment/rejection. +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*> stream); + +// Streams spec, 3.4.3. ReadableStreamCancel ( stream, reason ) +/* static */ MOZ_MUST_USE JSObject* +ReadableStream::cancel(JSContext* cx, Handle<ReadableStream*> stream, HandleValue reason) +{ + // Step 1: Set stream.[[disturbed]] to true. + uint32_t state = StreamState(stream) | ReadableStream::Disturbed; + SetStreamState(stream, state); + + // Step 2: If stream.[[state]] is "closed", return a new promise resolved + // with undefined. + if (stream->closed()) + return PromiseObject::unforgeableResolve(cx, UndefinedHandleValue); + + // Step 3: If stream.[[state]] is "errored", return a new promise rejected + // with stream.[[storedError]]. + if (stream->errored()) { + RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError)); + return PromiseObject::unforgeableReject(cx, storedError); + } + + // Step 4: Perform ! ReadableStreamClose(stream). + if (!ReadableStreamCloseInternal(cx, stream)) + return nullptr; + + // Step 5: Let sourceCancelPromise be + // ! stream.[[readableStreamController]].[[CancelSteps]](reason). + RootedNativeObject controller(cx, ControllerFromStream(stream)); + RootedObject sourceCancelPromise(cx); + sourceCancelPromise = ReadableStreamControllerCancelSteps(cx, controller, reason); + if (!sourceCancelPromise) + return nullptr; + + // Step 6: Return the result of transforming sourceCancelPromise by a + // fulfillment handler that returns undefined. + RootedAtom funName(cx, cx->names().empty); + RootedFunction returnUndefined(cx, NewNativeFunction(cx, ReturnUndefined, 0, funName)); + if (!returnUndefined) + return nullptr; + return JS::CallOriginalPromiseThen(cx, sourceCancelPromise, returnUndefined, nullptr); +} + +// Streams spec, 3.4.4. ReadableStreamClose ( stream ) +MOZ_MUST_USE bool +ReadableStreamCloseInternal(JSContext* cx, Handle<ReadableStream*> stream) +{ + // Step 1: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(stream->readable()); + + uint32_t state = StreamState(stream); + // Step 2: Set stream.[[state]] to "closed". + SetStreamState(stream, (state & ReadableStream::Disturbed) | ReadableStream::Closed); + + // Step 3: Let reader be stream.[[reader]]. + RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader)); + + // Step 4: If reader is undefined, return. + if (val.isUndefined()) + return true; + + // Step 5: If ! IsReadableStreamDefaultReader(reader) is true, + RootedNativeObject reader(cx, &val.toObject().as<NativeObject>()); + if (reader->is<ReadableStreamDefaultReader>()) { + // Step a: Repeat for each readRequest that is an element of + // reader.[[readRequests]], + val = reader->getFixedSlot(ReaderSlot_Requests); + if (!val.isUndefined()) { + RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>()); + uint32_t len = readRequests->getDenseInitializedLength(); + RootedObject readRequest(cx); + RootedObject resultObj(cx); + RootedValue resultVal(cx); + for (uint32_t i = 0; i < len; i++) { + // Step i: Resolve readRequest.[[promise]] with + // ! CreateIterResultObject(undefined, true). + readRequest = &readRequests->getDenseElement(i).toObject(); + resultObj = CreateIterResultObject(cx, UndefinedHandleValue, true); + if (!resultObj) + return false; + resultVal = ObjectValue(*resultObj); + if (!ResolvePromise(cx, readRequest, resultVal)) + return false; + } + + // Step b: Set reader.[[readRequests]] to an empty List. + reader->setFixedSlot(ReaderSlot_Requests, UndefinedValue()); + } + } + + // Step 6: Resolve reader.[[closedPromise]] with undefined. + // Step 7: Return (implicit). + RootedObject closedPromise(cx, &reader->getFixedSlot(ReaderSlot_ClosedPromise).toObject()); + if (!ResolvePromise(cx, closedPromise, UndefinedHandleValue)) + return false; + + if (stream->mode() == JS::ReadableStreamMode::ExternalSource && + cx->runtime()->readableStreamClosedCallback) + { + NativeObject* controller = ControllerFromStream(stream); + void* source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate(); + cx->runtime()->readableStreamClosedCallback(cx, stream, source, stream->embeddingFlags()); + } + + return true; +} + +// Streams spec, 3.4.5. ReadableStreamError ( stream, e ) +MOZ_MUST_USE bool +ReadableStreamErrorInternal(JSContext* cx, Handle<ReadableStream*> stream, HandleValue e) +{ + // Step 1: Assert: ! IsReadableStream(stream) is true (implicit). + + // Step 2: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(stream->readable()); + + // Step 3: Set stream.[[state]] to "errored". + uint32_t state = StreamState(stream); + SetStreamState(stream, (state & ReadableStream::Disturbed) | ReadableStream::Errored); + + // Step 4: Set stream.[[storedError]] to e. + stream->setFixedSlot(StreamSlot_StoredError, e); + + // Step 5: Let reader be stream.[[reader]]. + RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader)); + + // Step 6: If reader is undefined, return. + if (val.isUndefined()) + return true; + RootedNativeObject reader(cx, &val.toObject().as<NativeObject>()); + + // Steps 7,8: (Identical in our implementation.) + // Step a: Repeat for each readRequest that is an element of + // reader.[[readRequests]], + val = reader->getFixedSlot(ReaderSlot_Requests); + RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>()); + Rooted<PromiseObject*> readRequest(cx); + uint32_t len = readRequests->getDenseInitializedLength(); + for (uint32_t i = 0; i < len; i++) { + // Step i: Reject readRequest.[[promise]] with e. + val = readRequests->getDenseElement(i); + readRequest = &val.toObject().as<PromiseObject>(); + if (!PromiseObject::reject(cx, readRequest, e)) + return false; + } + + // Step b: Set reader.[[readRequests]] to a new empty List. + if (!SetNewList(cx, reader, ReaderSlot_Requests)) + return false; + + // Step 9: Reject reader.[[closedPromise]] with e. + val = reader->getFixedSlot(ReaderSlot_ClosedPromise); + Rooted<PromiseObject*> closedPromise(cx, &val.toObject().as<PromiseObject>()); + if (!PromiseObject::reject(cx, closedPromise, e)) + return false; + + if (stream->mode() == JS::ReadableStreamMode::ExternalSource && + cx->runtime()->readableStreamErroredCallback) + { + NativeObject* controller = ControllerFromStream(stream); + void* source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate(); + cx->runtime()->readableStreamErroredCallback(cx, stream, source, + stream->embeddingFlags(), e); + } + + return true; +} + +// Streams spec, 3.4.6. ReadableStreamFulfillReadIntoRequest( stream, chunk, done ) +// Streams spec, 3.4.7. ReadableStreamFulfillReadRequest ( stream, chunk, done ) +// These two spec functions are identical in our implementation. +static MOZ_MUST_USE bool +ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream, + HandleValue chunk, bool done) +{ + // Step 1: Let reader be stream.[[reader]]. + RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader)); + RootedNativeObject reader(cx, &val.toObject().as<NativeObject>()); + + // Step 2: Let readIntoRequest be the first element of + // reader.[[readIntoRequests]]. + // Step 3: Remove readIntoRequest from reader.[[readIntoRequests]], shifting + // all other elements downward (so that the second becomes the first, + // and so on). + val = reader->getFixedSlot(ReaderSlot_Requests); + RootedNativeObject readIntoRequests(cx, &val.toObject().as<NativeObject>()); + Rooted<PromiseObject*> readIntoRequest(cx); + readIntoRequest = ShiftFromList<PromiseObject>(cx, readIntoRequests); + MOZ_ASSERT(readIntoRequest); + + // Step 4: Resolve readIntoRequest.[[promise]] with + // ! CreateIterResultObject(chunk, done). + RootedObject iterResult(cx, CreateIterResultObject(cx, chunk, done)); + if (!iterResult) + return false; + val = ObjectValue(*iterResult); + return PromiseObject::resolve(cx, readIntoRequest, val); +} + +// Streams spec, 3.4.8. ReadableStreamGetNumReadIntoRequests ( stream ) +// Streams spec, 3.4.9. ReadableStreamGetNumReadRequests ( stream ) +// (Identical implementation.) +static uint32_t +ReadableStreamGetNumReadRequests(ReadableStream* stream) +{ + // Step 1: Return the number of elements in + // stream.[[reader]].[[readRequests]]. + if (!HasReader(stream)) + return 0; + NativeObject* reader = ReaderFromStream(stream); + Value readRequests = reader->getFixedSlot(ReaderSlot_Requests); + return readRequests.toObject().as<NativeObject>().getDenseInitializedLength(); +} + +// Stream spec 3.4.10. ReadableStreamHasBYOBReader ( stream ) +static MOZ_MUST_USE bool +ReadableStreamHasBYOBReader(ReadableStream* stream) +{ + // Step 1: Let reader be stream.[[reader]]. + // Step 2: If reader is undefined, return false. + // Step 3: If ! IsReadableStreamBYOBReader(reader) is false, return false. + // Step 4: Return true. + Value reader = stream->getFixedSlot(StreamSlot_Reader); + return reader.isObject() && reader.toObject().is<ReadableStreamBYOBReader>(); +} + +// Streap spec 3.4.11. ReadableStreamHasDefaultReader ( stream ) +static MOZ_MUST_USE bool +ReadableStreamHasDefaultReader(ReadableStream* stream) +{ + // Step 1: Let reader be stream.[[reader]]. + // Step 2: If reader is undefined, return false. + // Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false. + // Step 4: Return true. + Value reader = stream->getFixedSlot(StreamSlot_Reader); + return reader.isObject() && reader.toObject().is<ReadableStreamDefaultReader>(); +} + +static MOZ_MUST_USE bool +ReadableStreamReaderGenericInitialize(JSContext* cx, + HandleNativeObject reader, + Handle<ReadableStream*> stream); + +// Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream ) +// Steps 2-4. +static MOZ_MUST_USE ReadableStreamDefaultReader* +CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> stream) +{ + Rooted<ReadableStreamDefaultReader*> reader(cx); + reader = NewBuiltinClassInstance<ReadableStreamDefaultReader>(cx); + if (!reader) + return nullptr; + + // Step 2: If ! IsReadableStreamLocked(stream) is true, throw a TypeError + // exception. + if (stream->locked()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_LOCKED); + return nullptr; + } + + // Step 3: Perform ! ReadableStreamReaderGenericInitialize(this, stream). + if (!ReadableStreamReaderGenericInitialize(cx, reader, stream)) + return nullptr; + + // Step 4: Set this.[[readRequests]] to a new empty List. + if (!SetNewList(cx, reader, ReaderSlot_Requests)) + return nullptr; + + return reader; +} + +// Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream ) +bool +ReadableStreamDefaultReader::constructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultReader")) + return false; + + // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception. + if (!Is<ReadableStream>(args.get(0))) { + ReportArgTypeError(cx, "ReadableStreamDefaultReader", "ReadableStream", + args.get(0)); + return false; + } + + Rooted<ReadableStream*> stream(cx, &args.get(0).toObject().as<ReadableStream>()); + + RootedObject reader(cx, CreateReadableStreamDefaultReader(cx, stream)); + if (!reader) + return false; + + args.rval().setObject(*reader); + return true; +} + +// Streams spec, 3.5.4.1 get closed +static MOZ_MUST_USE bool +ReadableStreamDefaultReader_closed(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!Is<ReadableStreamDefaultReader>(args.thisv())) + return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "get closed"); + + // Step 2: Return this.[[closedPromise]]. + NativeObject* reader = &args.thisv().toObject().as<NativeObject>(); + args.rval().set(reader->getFixedSlot(ReaderSlot_ClosedPromise)); + return true; +} + +static MOZ_MUST_USE JSObject* +ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, HandleValue reason); + +// Streams spec, 3.5.4.2. cancel ( reason ) +static MOZ_MUST_USE bool +ReadableStreamDefaultReader_cancel(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!Is<ReadableStreamDefaultReader>(args.thisv())) + return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "cancel"); + + // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise + // rejected with a TypeError exception. + RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>()); + if (!ReaderHasStream(reader)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason). + JSObject* cancelPromise = ReadableStreamReaderGenericCancel(cx, reader, args.get(0)); + if (!cancelPromise) + return false; + args.rval().setObject(*cancelPromise); + return true; +} + +// Streams spec, 3.5.4.3 read ( ) +static MOZ_MUST_USE bool +ReadableStreamDefaultReader_read(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!Is<ReadableStreamDefaultReader>(args.thisv())) + return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "read"); + + // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise + // rejected with a TypeError exception. + Rooted<ReadableStreamDefaultReader*> reader(cx); + reader = &args.thisv().toObject().as<ReadableStreamDefaultReader>(); + if (!ReaderHasStream(reader)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMREADER_NOT_OWNED, "read"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 3: Return ! ReadableStreamDefaultReaderRead(this). + JSObject* readPromise = ReadableStreamDefaultReader::read(cx, reader); + if (!readPromise) + return false; + args.rval().setObject(*readPromise); + return true; +} + +static MOZ_MUST_USE bool +ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader); + +// Streams spec, 3.5.4.4. releaseLock ( ) +static MOZ_MUST_USE bool +ReadableStreamDefaultReader_releaseLock_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableStreamDefaultReader*> reader(cx); + reader = &args.thisv().toObject().as<ReadableStreamDefaultReader>(); + + // Step 2: If this.[[ownerReadableStream]] is undefined, return. + if (!ReaderHasStream(reader)) { + args.rval().setUndefined(); + return true; + } + + // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception. + Value val = reader->getFixedSlot(ReaderSlot_Requests); + if (!val.isUndefined()) { + NativeObject* readRequests = &val.toObject().as<NativeObject>(); + uint32_t len = readRequests->getDenseInitializedLength(); + if (len != 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMREADER_NOT_EMPTY, + "releaseLock"); + return false; + } + } + + // Step 4: Perform ! ReadableStreamReaderGenericRelease(this). + return ReadableStreamReaderGenericRelease(cx, reader); +} + +static bool +ReadableStreamDefaultReader_releaseLock(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStreamDefaultReader(this) is false, + // throw a TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStreamDefaultReader>, + ReadableStreamDefaultReader_releaseLock_impl>(cx, args); +} + +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 +}; + +CLASS_SPEC(ReadableStreamDefaultReader, 1, ReaderSlotCount, ClassSpec::DontDefineConstructor, 0, + JS_NULL_CLASS_OPS); + + +// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream ) +// Steps 2-5. +static MOZ_MUST_USE ReadableStreamBYOBReader* +CreateReadableStreamBYOBReader(JSContext* cx, Handle<ReadableStream*> stream) +{ + // Step 2: If ! IsReadableByteStreamController(stream.[[readableStreamController]]) + // is false, throw a TypeError exception. + if (!ControllerFromStream(stream)->is<ReadableByteStreamController>()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER, + "ReadableStream.getReader('byob')"); + return nullptr; + } + + // Step 3: If ! IsReadableStreamLocked(stream) is true, throw a TypeError + // exception. + if (stream->locked()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAM_LOCKED); + return nullptr; + } + + Rooted<ReadableStreamBYOBReader*> reader(cx); + reader = NewBuiltinClassInstance<ReadableStreamBYOBReader>(cx); + if (!reader) + return nullptr; + + // Step 4: Perform ! ReadableStreamReaderGenericInitialize(this, stream). + if (!ReadableStreamReaderGenericInitialize(cx, reader, stream)) + return nullptr; + + // Step 5: Set this.[[readIntoRequests]] to a new empty List. + if (!SetNewList(cx, reader, ReaderSlot_Requests)) + return nullptr; + + return reader; +} + +// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream ) +bool +ReadableStreamBYOBReader::constructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "ReadableStreamBYOBReader")) + return false; + + // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception. + if (!Is<ReadableStream>(args.get(0))) { + ReportArgTypeError(cx, "ReadableStreamBYOBReader", "ReadableStream", args.get(0)); + return false; + } + + Rooted<ReadableStream*> stream(cx, &args.get(0).toObject().as<ReadableStream>()); + RootedObject reader(cx, CreateReadableStreamBYOBReader(cx, stream)); + if (!reader) + return false; + + args.rval().setObject(*reader); + return true; +} + +// Streams spec, 3.6.4.1 get closed +static MOZ_MUST_USE bool +ReadableStreamBYOBReader_closed(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!Is<ReadableStreamBYOBReader>(args.thisv())) + return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "get closed"); + + // Step 2: Return this.[[closedPromise]]. + NativeObject* reader = &args.thisv().toObject().as<NativeObject>(); + args.rval().set(reader->getFixedSlot(ReaderSlot_ClosedPromise)); + return true; +} + +// Streams spec, 3.6.4.2. cancel ( reason ) +static MOZ_MUST_USE bool +ReadableStreamBYOBReader_cancel(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!Is<ReadableStreamBYOBReader>(args.thisv())) + return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "cancel"); + + // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise + // rejected with a TypeError exception. + RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>()); + if (!ReaderHasStream(reader)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason). + JSObject* cancelPromise = ReadableStreamReaderGenericCancel(cx, reader, args.get(0)); + if (!cancelPromise) + return false; + args.rval().setObject(*cancelPromise); + return true; +} + +// Streams spec, 3.6.4.3 read ( ) +static MOZ_MUST_USE bool +ReadableStreamBYOBReader_read(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + HandleValue viewVal = args.get(0); + + // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!Is<ReadableStreamBYOBReader>(args.thisv())) + return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "read"); + + // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise + // rejected with a TypeError exception. + Rooted<ReadableStreamBYOBReader*> reader(cx); + reader = &args.thisv().toObject().as<ReadableStreamBYOBReader>(); + if (!ReaderHasStream(reader)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMREADER_NOT_OWNED, "read"); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 3: If Type(view) is not Object, return a promise rejected with a + // TypeError exception. + // Step 4: If view does not have a [[ViewedArrayBuffer]] internal slot, + // return a promise rejected with a TypeError exception. + if (!Is<ArrayBufferViewObject>(viewVal)) { + ReportArgTypeError(cx, "ReadableStreamBYOBReader.read", "Typed Array", viewVal); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + Rooted<ArrayBufferViewObject*> view(cx, &viewVal.toObject().as<ArrayBufferViewObject>()); + + // Step 5: If view.[[ByteLength]] is 0, return a promise rejected with a + // TypeError exception. + // Note: It's ok to use the length in number of elements here because all we + // want to know is whether it's < 0. + if (JS_GetArrayBufferViewByteLength(view) == 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW); + return ReturnPromiseRejectedWithPendingError(cx, args); + } + + // Step 6: Return ! ReadableStreamBYOBReaderRead(this, view). + JSObject* readPromise = ReadableStreamBYOBReader::read(cx, reader, view); + if (!readPromise) + return false; + args.rval().setObject(*readPromise); + return true; +} + +static MOZ_MUST_USE bool +ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader); + +// Streams spec, 3.6.4.4. releaseLock ( ) +static MOZ_MUST_USE bool +ReadableStreamBYOBReader_releaseLock_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableStreamBYOBReader*> reader(cx); + reader = &args.thisv().toObject().as<ReadableStreamBYOBReader>(); + + // Step 2: If this.[[ownerReadableStream]] is undefined, return. + if (!ReaderHasStream(reader)) { + args.rval().setUndefined(); + return true; + } + + // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception. + Value val = reader->getFixedSlot(ReaderSlot_Requests); + if (!val.isUndefined()) { + NativeObject* readRequests = &val.toObject().as<NativeObject>(); + uint32_t len = readRequests->getDenseInitializedLength(); + if (len != 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMREADER_NOT_EMPTY, "releaseLock"); + return false; + } + } + + // Step 4: Perform ! ReadableStreamReaderGenericRelease(this). + return ReadableStreamReaderGenericRelease(cx, reader); +} + +static bool +ReadableStreamBYOBReader_releaseLock(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStreamBYOBReader(this) is false, + // throw a TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStreamBYOBReader>, + ReadableStreamBYOBReader_releaseLock_impl>(cx, args); +} + +static const JSPropertySpec ReadableStreamBYOBReader_properties[] = { + JS_PSG("closed", ReadableStreamBYOBReader_closed, 0), + JS_PS_END +}; + +static const JSFunctionSpec ReadableStreamBYOBReader_methods[] = { + JS_FN("cancel", ReadableStreamBYOBReader_cancel, 1, 0), + JS_FN("read", ReadableStreamBYOBReader_read, 1, 0), + JS_FN("releaseLock", ReadableStreamBYOBReader_releaseLock, 0, 0), + JS_FS_END +}; + +CLASS_SPEC(ReadableStreamBYOBReader, 1, 3, ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS); + +inline static MOZ_MUST_USE bool +ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject controller); + +// Streams spec, 3.7.1. IsReadableStreamDefaultReader ( x ) +// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStreamDefaultReader>() + +// Streams spec, 3.7.2. IsReadableStreamBYOBReader ( x ) +// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStreamBYOBReader>() + +// Streams spec, 3.7.3. ReadableStreamReaderGenericCancel ( reader, reason ) +static MOZ_MUST_USE JSObject* +ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, HandleValue reason) +{ + // Step 1: Let stream be reader.[[ownerReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromReader(reader)); + + // Step 2: Assert: stream is not undefined (implicit). + + // Step 3: Return ! ReadableStreamCancel(stream, reason). + return &ReadableStreamCancel(cx, stream, reason)->as<PromiseObject>(); +} + +// Streams spec, 3.7.4. ReadableStreamReaderGenericInitialize ( reader, stream ) +static MOZ_MUST_USE bool +ReadableStreamReaderGenericInitialize(JSContext* cx, HandleNativeObject reader, + Handle<ReadableStream*> stream) +{ + // Step 1: Set reader.[[ownerReadableStream]] to stream. + reader->setFixedSlot(ReaderSlot_Stream, ObjectValue(*stream)); + + // Step 2: Set stream.[[reader]] to reader. + stream->setFixedSlot(StreamSlot_Reader, ObjectValue(*reader)); + + // Step 3: If stream.[[state]] is "readable", + RootedObject promise(cx); + if (stream->readable()) { + // Step a: Set reader.[[closedPromise]] to a new promise. + promise = PromiseObject::createSkippingExecutor(cx); + } else if (stream->closed()) { + // Step 4: Otherwise + // Step a: If stream.[[state]] is "closed", + // Step i: Set reader.[[closedPromise]] to a new promise resolved with + // undefined. + promise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue); + } else { + // Step b: Otherwise, + // Step i: Assert: stream.[[state]] is "errored". + MOZ_ASSERT(stream->errored()); + + // Step ii: Set reader.[[closedPromise]] to a new promise rejected with + // stream.[[storedError]]. + RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError)); + promise = PromiseObject::unforgeableReject(cx, storedError); + } + + if (!promise) + return false; + + reader->setFixedSlot(ReaderSlot_ClosedPromise, ObjectValue(*promise)); + return true; +} + +// Streams spec, 3.7.5. ReadableStreamReaderGenericRelease ( reader ) +static MOZ_MUST_USE bool +ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader) +{ + // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined. + Rooted<ReadableStream*> stream(cx, StreamFromReader(reader)); + + // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader. + MOZ_ASSERT(&stream->getFixedSlot(StreamSlot_Reader).toObject() == reader); + + // Create an exception to reject promises with below. We don't have a + // clean way to do this, unfortunately. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAMREADER_RELEASED); + RootedValue exn(cx); + // Not much we can do about uncatchable exceptions, just bail. + if (!GetAndClearException(cx, &exn)) + return false; + + // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject + // reader.[[closedPromise]] with a TypeError exception. + if (stream->readable()) { + Value val = reader->getFixedSlot(ReaderSlot_ClosedPromise); + Rooted<PromiseObject*> closedPromise(cx, &val.toObject().as<PromiseObject>()); + if (!PromiseObject::reject(cx, closedPromise, exn)) + return false; + } else { + // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise rejected + // with a TypeError exception. + RootedObject closedPromise(cx, PromiseObject::unforgeableReject(cx, exn)); + if (!closedPromise) + return false; + reader->setFixedSlot(ReaderSlot_ClosedPromise, ObjectValue(*closedPromise)); + } + + // Step 5: Set reader.[[ownerReadableStream]].[[reader]] to undefined. + stream->setFixedSlot(StreamSlot_Reader, UndefinedValue()); + + // Step 6: Set reader.[[ownerReadableStream]] to undefined. + reader->setFixedSlot(ReaderSlot_Stream, UndefinedValue()); + + return true; +} + +static MOZ_MUST_USE JSObject* +ReadableByteStreamControllerPullInto(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + Handle<ArrayBufferViewObject*> view); + +// Streams spec, 3.7.6. ReadableStreamBYOBReaderRead ( reader, view ) +/* static */ MOZ_MUST_USE JSObject* +ReadableStreamBYOBReader::read(JSContext* cx, Handle<ReadableStreamBYOBReader*> reader, + Handle<ArrayBufferViewObject*> view) +{ + MOZ_ASSERT(reader->is<ReadableStreamBYOBReader>()); + + // Step 1: Let stream be reader.[[ownerReadableStream]]. + // Step 2: Assert: stream is not undefined. + Rooted<ReadableStream*> stream(cx, StreamFromReader(reader)); + + // Step 3: Set stream.[[disturbed]] to true. + SetStreamState(stream, StreamState(stream) | ReadableStream::Disturbed); + + // Step 4: If stream.[[state]] is "errored", return a promise rejected with + // stream.[[storedError]]. + if (stream->errored()) { + RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError)); + return PromiseObject::unforgeableReject(cx, storedError); + } + + // Step 5: Return ! ReadableByteStreamControllerPullInto(stream.[[readableStreamController]], view). + Rooted<ReadableByteStreamController*> controller(cx); + controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>(); + return ReadableByteStreamControllerPullInto(cx, controller, view); +} + +static MOZ_MUST_USE JSObject* +ReadableStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller); + +// Streams spec, 3.7.7. ReadableStreamDefaultReaderRead ( reader ) +MOZ_MUST_USE JSObject* +ReadableStreamDefaultReader::read(JSContext* cx, Handle<ReadableStreamDefaultReader*> reader) +{ + // Step 1: Let stream be reader.[[ownerReadableStream]]. + // Step 2: Assert: stream is not undefined. + Rooted<ReadableStream*> stream(cx, StreamFromReader(reader)); + + // Step 3: Set stream.[[disturbed]] to true. + SetStreamState(stream, StreamState(stream) | ReadableStream::Disturbed); + + // Step 4: If stream.[[state]] is "closed", return a new promise resolved with + // ! CreateIterResultObject(undefined, true). + if (stream->closed()) { + RootedObject iterResult(cx, CreateIterResultObject(cx, UndefinedHandleValue, true)); + if (!iterResult) + return nullptr; + RootedValue iterResultVal(cx, ObjectValue(*iterResult)); + return PromiseObject::unforgeableResolve(cx, iterResultVal); + } + + // Step 5: If stream.[[state]] is "errored", return a new promise rejected with + // stream.[[storedError]]. + if (stream->errored()) { + RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError)); + return PromiseObject::unforgeableReject(cx, storedError); + } + + // Step 6: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(stream->readable()); + + // Step 7: Return ! stream.[[readableStreamController]].[[PullSteps]](). + RootedNativeObject controller(cx, ControllerFromStream(stream)); + return ReadableStreamControllerPullSteps(cx, controller); +} + +// Streams spec, 3.8.3, step 11.a. +// and +// Streams spec, 3.10.3, step 16.a. +static bool +ControllerStartHandler(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee())); + + // Step i: Set controller.[[started]] to true. + AddControllerFlags(controller, ControllerFlag_Started); + + // Step ii: Assert: controller.[[pulling]] is false. + // Step iii: Assert: controller.[[pullAgain]] is false. + MOZ_ASSERT(!(ControllerFlags(controller) & + (ControllerFlag_Pulling | ControllerFlag_PullAgain))); + + // Step iv: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller). + // or + // Step iv: Perform ! ReadableByteStreamControllerCallPullIfNeeded((controller). + if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) + return false; + args.rval().setUndefined(); + return true; +} + +static MOZ_MUST_USE bool +ReadableStreamDefaultControllerErrorIfNeeded(JSContext* cx, + Handle<ReadableStreamDefaultController*> controller, + HandleValue e); + +static MOZ_MUST_USE bool +ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e); + +// Streams spec, 3.8.3, step 11.b. +// and +// Streams spec, 3.10.3, step 16.b. +static bool +ControllerStartFailedHandler(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedNativeObject controllerObj(cx, TargetFromHandler<NativeObject>(args.callee())); + + // 3.8.3, Step 11.b.i: + // Perform ! ReadableStreamDefaultControllerErrorIfNeeded(controller, r). + if (controllerObj->is<ReadableStreamDefaultController>()) { + Rooted<ReadableStreamDefaultController*> controller(cx); + controller = &controllerObj->as<ReadableStreamDefaultController>(); + return ReadableStreamDefaultControllerErrorIfNeeded(cx, controller, args.get(0)); + } + + // 3.10.3, Step 16.b.i: If stream.[[state]] is "readable", perform + // ! ReadableByteStreamControllerError(controller, r). + if (StreamFromController(controllerObj)->readable()) + return ReadableStreamControllerError(cx, controllerObj, args.get(0)); + + args.rval().setUndefined(); + return true; +} + +static MOZ_MUST_USE bool +ValidateAndNormalizeHighWaterMark(JSContext* cx, + HandleValue highWaterMarkVal, + double* highWaterMark); + +static MOZ_MUST_USE bool +ValidateAndNormalizeQueuingStrategy(JSContext* cx, + HandleValue size, + HandleValue highWaterMarkVal, + double* highWaterMark); + +// Streams spec, 3.8.3 new ReadableStreamDefaultController ( stream, underlyingSource, +// size, highWaterMark ) +// Steps 3 - 11. +static MOZ_MUST_USE ReadableStreamDefaultController* +CreateReadableStreamDefaultController(JSContext* cx, Handle<ReadableStream*> stream, + HandleValue underlyingSource, HandleValue size, + HandleValue highWaterMarkVal) +{ + Rooted<ReadableStreamDefaultController*> controller(cx); + controller = NewBuiltinClassInstance<ReadableStreamDefaultController>(cx); + if (!controller) + return nullptr; + + // Step 3: Set this.[[controlledReadableStream]] to stream. + controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream)); + + // Step 4: Set this.[[underlyingSource]] to underlyingSource. + controller->setFixedSlot(ControllerSlot_UnderlyingSource, underlyingSource); + + // Step 5: Perform ! ResetQueue(this). + if (!ResetQueue(cx, controller)) + return nullptr; + + // Step 6: Set this.[[started]], this.[[closeRequested]], this.[[pullAgain]], + // and this.[[pulling]] to false. + controller->setFixedSlot(ControllerSlot_Flags, Int32Value(0)); + + // Step 7: Let normalizedStrategy be + // ? ValidateAndNormalizeQueuingStrategy(size, highWaterMark). + double highWaterMark; + if (!ValidateAndNormalizeQueuingStrategy(cx, size, highWaterMarkVal, &highWaterMark)) + return nullptr; + + // Step 8: Set this.[[strategySize]] to normalizedStrategy.[[size]] and + // this.[[strategyHWM]] to normalizedStrategy.[[highWaterMark]]. + controller->setFixedSlot(DefaultControllerSlot_StrategySize, size); + controller->setFixedSlot(ControllerSlot_StrategyHWM, NumberValue(highWaterMark)); + + // Step 9: Let controller be this (implicit). + + // Step 10: Let startResult be + // ? InvokeOrNoop(underlyingSource, "start", « this »). + RootedValue startResult(cx); + RootedValue controllerVal(cx, ObjectValue(*controller)); + if (!InvokeOrNoop(cx, underlyingSource, cx->names().start, controllerVal, &startResult)) + return nullptr; + + // Step 11: Let startPromise be a promise resolved with startResult: + RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, startResult)); + if (!startPromise) + return nullptr; + + RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller)); + if (!onStartFulfilled) + return nullptr; + + RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller)); + if (!onStartRejected) + return nullptr; + + if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected)) + return nullptr; + + return controller; +} + +// Streams spec, 3.8.3. +// new ReadableStreamDefaultController( stream, underlyingSource, size, +// highWaterMark ) +bool +ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultController")) + return false; + + // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception. + HandleValue streamVal = args.get(0); + if (!Is<ReadableStream>(streamVal)) { + ReportArgTypeError(cx, "ReadableStreamDefaultController", "ReadableStream", + args.get(0)); + return false; + } + + Rooted<ReadableStream*> stream(cx, &streamVal.toObject().as<ReadableStream>()); + + // Step 2: If stream.[[readableStreamController]] is not undefined, throw a + // TypeError exception. + if (HasController(stream)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_CONTROLLER_SET); + return false; + } + + // Steps 3-11. + RootedObject controller(cx, CreateReadableStreamDefaultController(cx, stream, args.get(1), + args.get(2), args.get(3))); + if (!controller) + return false; + + args.rval().setObject(*controller); + return true; +} + +static MOZ_MUST_USE double +ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller); + +// Streams spec, 3.8.4.1. get desiredSize +// and +// Streams spec, 3.10.4.2. get desiredSize +static MOZ_MUST_USE bool +ReadableStreamController_desiredSize_impl(JSContext* cx, const CallArgs& args) +{ + RootedNativeObject controller(cx); + controller = &args.thisv().toObject().as<NativeObject>(); + + // Streams spec, 3.9.8. steps 1-4. + // 3.9.8. Step 1: Let stream be controller.[[controlledReadableStream]]. + ReadableStream* stream = StreamFromController(controller); + + // 3.9.8. Step 2: Let state be stream.[[state]]. + // 3.9.8. Step 3: If state is "errored", return null. + if (stream->errored()) { + args.rval().setNull(); + return true; + } + + // 3.9.8. Step 4: If state is "closed", return 0. + if (stream->closed()) { + args.rval().setInt32(0); + return true; + } + + // Step 2: Return ! ReadableStreamDefaultControllerGetDesiredSize(this). + args.rval().setNumber(ReadableStreamControllerGetDesiredSizeUnchecked(controller)); + return true; +} + +static bool +ReadableStreamDefaultController_desiredSize(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStreamDefaultController>, + ReadableStreamController_desiredSize_impl>(cx, args); +} + +static MOZ_MUST_USE bool +ReadableStreamDefaultControllerClose(JSContext* cx, + Handle<ReadableStreamDefaultController*> controller); + +// Unified implementation of steps 2-3 of 3.8.4.2 and 3.10.4.3. +static MOZ_MUST_USE bool +VerifyControllerStateForClosing(JSContext* cx, HandleNativeObject controller) +{ + // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception. + if (ControllerFlags(controller) & ControllerFlag_CloseRequested) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close"); + return false; + } + + // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable", + // throw a TypeError exception. + ReadableStream* stream = StreamFromController(controller); + if (!stream->readable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close"); + return false; + } + + return true; +} + +// Streams spec, 3.8.4.2 close() +static MOZ_MUST_USE bool +ReadableStreamDefaultController_close_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableStreamDefaultController*> controller(cx); + controller = &args.thisv().toObject().as<ReadableStreamDefaultController>(); + + // Steps 2-3. + if (!VerifyControllerStateForClosing(cx, controller)) + return false; + + // Step 4: Perform ! ReadableStreamDefaultControllerClose(this). + if (!ReadableStreamDefaultControllerClose(cx, controller)) + return false; + args.rval().setUndefined(); + return true; +} + +static bool +ReadableStreamDefaultController_close(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStreamDefaultController>, + ReadableStreamDefaultController_close_impl>(cx, args); +} + +static MOZ_MUST_USE bool +ReadableStreamDefaultControllerEnqueue(JSContext* cx, + Handle<ReadableStreamDefaultController*> controller, + HandleValue chunk); + +// Streams spec, 3.8.4.3. enqueue ( chunk ) +static MOZ_MUST_USE bool +ReadableStreamDefaultController_enqueue_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableStreamDefaultController*> controller(cx); + controller = &args.thisv().toObject().as<ReadableStreamDefaultController>(); + + // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception. + if (ControllerFlags(controller) & ControllerFlag_CloseRequested) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close"); + return false; + } + + // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable", + // throw a TypeError exception. + ReadableStream* stream = StreamFromController(controller); + if (!stream->readable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close"); + return false; + } + + // Step 4: Return ! ReadableStreamDefaultControllerEnqueue(this, chunk). + if (!ReadableStreamDefaultControllerEnqueue(cx, controller, args.get(0))) + return false; + args.rval().setUndefined(); + return true; +} + +static bool +ReadableStreamDefaultController_enqueue(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStreamDefaultController>, + ReadableStreamDefaultController_enqueue_impl>(cx, args); +} + +// Streams spec, 3.8.4.4. error ( e ) +static MOZ_MUST_USE bool +ReadableStreamDefaultController_error_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableStreamDefaultController*> controller(cx); + controller = &args.thisv().toObject().as<ReadableStreamDefaultController>(); + + // Step 2: Let stream be this.[[controlledReadableStream]]. + // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception. + if (!StreamFromController(controller)->readable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error"); + return false; + } + + // Step 4: Perform ! ReadableStreamDefaultControllerError(this, e). + if (!ReadableStreamControllerError(cx, controller, args.get(0))) + return false; + args.rval().setUndefined(); + return true; +} + +static bool +ReadableStreamDefaultController_error(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStreamDefaultController>, + ReadableStreamDefaultController_error_impl>(cx, args); +} + +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, 4, 7, ClassSpec::DontDefineConstructor, 0, + JS_NULL_CLASS_OPS); + +/** + * Unified implementation of ReadableStream controllers' [[CancelSteps]] internal + * methods. + * Streams spec, 3.8.5.1. [[CancelSteps]] ( reason ) + * and + * Streams spec, 3.10.5.1. [[CancelSteps]] ( reason ) + */ +static MOZ_MUST_USE JSObject* +ReadableStreamControllerCancelSteps(JSContext* cx, HandleNativeObject controller, + HandleValue reason) +{ + MOZ_ASSERT(IsReadableStreamController(controller)); + + // Step 1 of 3.10.5.1: If this.[[pendingPullIntos]] is not empty, + if (!controller->is<ReadableStreamDefaultController>()) { + Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos); + RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>()); + + if (pendingPullIntos->getDenseInitializedLength() != 0) { + // Step a: Let firstDescriptor be the first element of + // this.[[pendingPullIntos]]. + // Step b: Set firstDescriptor.[[bytesFilled]] to 0. + Rooted<PullIntoDescriptor*> firstDescriptor(cx); + firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos); + firstDescriptor->setBytesFilled(0); + } + } + + // Step 1 of 3.8.5.1, step 2 of 3.10.5.1: Perform ! ResetQueue(this). + if (!ResetQueue(cx, controller)) + return nullptr; + + // Step 2 of 3.8.5.1, step 3 of 3.10.5.1: + // Return ! PromiseInvokeOrNoop(this.[[underlying(Byte)Source]], + // "cancel", « reason ») + RootedValue underlyingSource(cx); + underlyingSource = controller->getFixedSlot(ControllerSlot_UnderlyingSource); + + if (Is<TeeState>(underlyingSource)) { + Rooted<TeeState*> teeState(cx, &underlyingSource.toObject().as<TeeState>()); + Rooted<ReadableStreamDefaultController*> defaultController(cx); + defaultController = &controller->as<ReadableStreamDefaultController>(); + return ReadableStreamTee_Cancel(cx, teeState, defaultController, reason); + } + + if (ControllerFlags(controller) & ControllerFlag_ExternalSource) { + void* source = underlyingSource.toPrivate(); + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + RootedValue rval(cx); + rval = cx->runtime()->readableStreamCancelCallback(cx, stream, source, + stream->embeddingFlags(), reason); + return PromiseObject::unforgeableResolve(cx, rval); + } + + return PromiseInvokeOrNoop(cx, underlyingSource, cx->names().cancel, reason); +} + +inline static MOZ_MUST_USE bool +DequeueValue(JSContext* cx, HandleNativeObject container, MutableHandleValue chunk); + +// Streams spec, 3.8.5.2. ReadableStreamDefaultController [[PullSteps]]() +static JSObject* +ReadableStreamDefaultControllerPullSteps(JSContext* cx, HandleNativeObject controller) +{ + MOZ_ASSERT(controller->is<ReadableStreamDefaultController>()); + + // Step 1: Let stream be this.[[controlledReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 2: If this.[[queue]] is not empty, + RootedNativeObject queue(cx); + RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue)); + if (val.isObject()) + queue = &val.toObject().as<NativeObject>(); + + if (queue && queue->getDenseInitializedLength() != 0) { + // Step a: Let chunk be ! DequeueValue(this.[[queue]]). + RootedValue chunk(cx); + if (!DequeueValue(cx, controller, &chunk)) + return nullptr; + + // Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty, + // perform ! ReadableStreamClose(stream). + bool closeRequested = ControllerFlags(controller) & ControllerFlag_CloseRequested; + if (closeRequested && queue->getDenseInitializedLength() == 0) { + if (!ReadableStreamCloseInternal(cx, stream)) + return nullptr; + } + + // Step c: Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this). + else { + if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) + return nullptr; + } + + // Step d: Return a promise resolved with ! CreateIterResultObject(chunk, false). + RootedObject iterResultObj(cx, CreateIterResultObject(cx, chunk, false)); + if (!iterResultObj) + return nullptr; + RootedValue iterResult(cx, ObjectValue(*iterResultObj)); + return PromiseObject::unforgeableResolve(cx, iterResult); + } + + // Step 3: Let pendingPromise be ! ReadableStreamAddReadRequest(stream). + Rooted<PromiseObject*> pendingPromise(cx, ReadableStreamAddReadRequest(cx, stream)); + if (!pendingPromise) + return nullptr; + + // Step 4: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this). + if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) + return nullptr; + + // Step 5: Return pendingPromise. + return pendingPromise; +} + +// Streams spec, 3.9.2 and 3.12.3. step 7: +// Upon fulfillment of pullPromise, +static bool +ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee())); + uint32_t flags = ControllerFlags(controller); + + // Step a: Set controller.[[pulling]] to false. + // Step b.i: Set controller.[[pullAgain]] to false. + RemoveControllerFlags(controller, ControllerFlag_Pulling | ControllerFlag_PullAgain); + + // Step b: If controller.[[pullAgain]] is true, + if (flags & ControllerFlag_PullAgain) { + // Step ii: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) + return false; + } + + args.rval().setUndefined(); + return true; +} + +// Streams spec, 3.9.2 and 3.12.3. step 8: +// Upon rejection of pullPromise with reason e, +static bool +ControllerPullFailedHandler(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee())); + HandleValue e = args.get(0); + + // Step a: If controller.[[controlledReadableStream]].[[state]] is "readable", + // perform ! ReadableByteStreamControllerError(controller, e). + if (StreamFromController(controller)->readable()) { + if (!ReadableStreamControllerError(cx, controller, e)) + return false; + } + + args.rval().setUndefined(); + return true; +} + +static bool +ReadableStreamControllerShouldCallPull(NativeObject* controller); + +static MOZ_MUST_USE double +ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller); + +// Streams spec, 3.9.2 ReadableStreamDefaultControllerCallPullIfNeeded ( controller ) +// and +// Streams spec, 3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller ) +inline static MOZ_MUST_USE bool +ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject controller) +{ + // Step 1: Let shouldPull be + // ! ReadableByteStreamControllerShouldCallPull(controller). + bool shouldPull = ReadableStreamControllerShouldCallPull(controller); + + // Step 2: If shouldPull is false, return. + if (!shouldPull) + return true; + + // Step 3: If controller.[[pulling]] is true, + if (ControllerFlags(controller) & ControllerFlag_Pulling) { + // Step a: Set controller.[[pullAgain]] to true. + AddControllerFlags(controller, ControllerFlag_PullAgain); + + // Step b: Return. + return true; + } + + // Step 4: Assert: controller.[[pullAgain]] is false. + MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_PullAgain)); + + // Step 5: Set controller.[[pulling]] to true. + AddControllerFlags(controller, ControllerFlag_Pulling); + + // Step 6: Let pullPromise be + // ! PromiseInvokeOrNoop(controller.[[underlyingByteSource]], "pull", controller). + RootedObject pullPromise(cx); + RootedValue underlyingSource(cx); + underlyingSource = controller->getFixedSlot(ControllerSlot_UnderlyingSource); + RootedValue controllerVal(cx, ObjectValue(*controller)); + + if (Is<TeeState>(underlyingSource)) { + Rooted<TeeState*> teeState(cx, &underlyingSource.toObject().as<TeeState>()); + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + pullPromise = ReadableStreamTee_Pull(cx, teeState, stream); + } else if (ControllerFlags(controller) & ControllerFlag_ExternalSource) { + void* source = underlyingSource.toPrivate(); + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(controller); + cx->runtime()->readableStreamDataRequestCallback(cx, stream, source, + stream->embeddingFlags(), desiredSize); + pullPromise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue); + } else { + pullPromise = PromiseInvokeOrNoop(cx, underlyingSource, cx->names().pull, controllerVal); + } + if (!pullPromise) + return false; + + RootedObject onPullFulfilled(cx, NewHandler(cx, ControllerPullHandler, controller)); + if (!onPullFulfilled) + return false; + + RootedObject onPullRejected(cx, NewHandler(cx, ControllerPullFailedHandler, controller)); + if (!onPullRejected) + return false; + + return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled, onPullRejected); + + // Steps 7-8 implemented in functions above. +} + +// Streams spec, 3.9.3. ReadableStreamDefaultControllerShouldCallPull ( controller ) +// and +// Streams spec, 3.12.24. ReadableByteStreamControllerShouldCallPull ( controller ) +static bool +ReadableStreamControllerShouldCallPull(NativeObject* controller) +{ + // Step 1: Let stream be controller.[[controlledReadableStream]]. + ReadableStream* stream = StreamFromController(controller); + + // Step 2: If stream.[[state]] is "closed" or stream.[[state]] is "errored", + // return false. + // or, equivalently + // Step 2: If stream.[[state]] is not "readable", return false. + if (!stream->readable()) + return false; + + // Step 3: If controller.[[closeRequested]] is true, return false. + uint32_t flags = ControllerFlags(controller); + if (flags & ControllerFlag_CloseRequested) + return false; + + // Step 4: If controller.[[started]] is false, return false. + if (!(flags & ControllerFlag_Started)) + return false; + + // Step 5: If ! IsReadableStreamLocked(stream) is true and + // ! ReadableStreamGetNumReadRequests(stream) > 0, return true. + // Steps 5-6 of 3.12.24 are equivalent in our implementation. + if (stream->locked() && ReadableStreamGetNumReadRequests(stream) > 0) + return true; + + // Step 6: Let desiredSize be ReadableStreamDefaultControllerGetDesiredSize(controller). + double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(controller); + + // Step 7: If desiredSize > 0, return true. + // Step 8: Return false. + // Steps 7-8 of 3.12.24 are equivalent in our implementation. + return desiredSize > 0; +} + +// Streams spec, 3.9.4. ReadableStreamDefaultControllerClose ( controller ) +static MOZ_MUST_USE bool +ReadableStreamDefaultControllerClose(JSContext* cx, + Handle<ReadableStreamDefaultController*> controller) +{ + // Step 1: Let stream be controller.[[controlledReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 2: Assert: controller.[[closeRequested]] is false. + MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested)); + + // Step 3: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(stream->readable()); + + // Step 4: Set controller.[[closeRequested]] to true. + AddControllerFlags(controller, ControllerFlag_CloseRequested); + + // Step 5: If controller.[[queue]] is empty, perform ! ReadableStreamClose(stream). + RootedNativeObject queue(cx); + queue = &controller->getFixedSlot(QueueContainerSlot_Queue).toObject().as<NativeObject>(); + if (queue->getDenseInitializedLength() == 0) + return ReadableStreamCloseInternal(cx, stream); + + return true; +} + +static MOZ_MUST_USE bool +EnqueueValueWithSize(JSContext* cx, HandleNativeObject container, HandleValue value, + HandleValue sizeVal); + +// Streams spec, 3.9.5. ReadableStreamDefaultControllerEnqueue ( controller, chunk ) +static MOZ_MUST_USE bool +ReadableStreamDefaultControllerEnqueue(JSContext* cx, + Handle<ReadableStreamDefaultController*> controller, + HandleValue chunk) +{ + // Step 1: Let stream be controller.[[controlledReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 2: Assert: controller.[[closeRequested]] is false. + MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested)); + + // Step 3: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(stream->readable()); + + // Step 4: If ! IsReadableStreamLocked(stream) is true and + // ! ReadableStreamGetNumReadRequests(stream) > 0, perform + // ! ReadableStreamFulfillReadRequest(stream, chunk, false). + if (stream->locked() && ReadableStreamGetNumReadRequests(stream) > 0) { + if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false)) + return false; + } else { + // Step 5: Otherwise, + // Step a: Let chunkSize be 1. + RootedValue chunkSize(cx, NumberValue(1)); + bool success = true; + + // Step b: If controller.[[strategySize]] is not undefined, + RootedValue strategySize(cx); + strategySize = controller->getFixedSlot(DefaultControllerSlot_StrategySize); + if (!strategySize.isUndefined()) { + // Step i: Set chunkSize to Call(stream.[[strategySize]], undefined, chunk). + success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize); + } + + // Step c: Let enqueueResult be + // EnqueueValueWithSize(controller, chunk, chunkSize). + if (success) + success = EnqueueValueWithSize(cx, controller, chunk, chunkSize); + + if (!success) { + // Step b.ii: If chunkSize is an abrupt completion, + // and + // Step d: If enqueueResult is an abrupt completion, + RootedValue exn(cx); + if (!cx->getPendingException(&exn)) + return false; + + // Step b.ii.1: Perform + // ! ReadableStreamDefaultControllerErrorIfNeeded(controller, + // chunkSize.[[Value]]). + if (!ReadableStreamDefaultControllerErrorIfNeeded(cx, controller, exn)) + return false; + + // Step b.ii.2: Return chunkSize. + return false; + } + } + + // Step 6: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller). + if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) + return false; + + // Step 7: Return. + return true; +} + +static MOZ_MUST_USE bool +ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx, HandleNativeObject controller); + +// Streams spec, 3.9.6. ReadableStreamDefaultControllerError ( controller, e ) +// and +// Streams spec, 3.12.10. ReadableByteStreamControllerError ( controller, e ) +static MOZ_MUST_USE bool +ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e) +{ + MOZ_ASSERT(IsReadableStreamController(controller)); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 2: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(stream->readable()); + + // Step 3 of 3.12.10: + // Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller). + if (controller->is<ReadableByteStreamController>()) { + Rooted<ReadableByteStreamController*> byteStreamController(cx); + byteStreamController = &controller->as<ReadableByteStreamController>(); + if (!ReadableByteStreamControllerClearPendingPullIntos(cx, byteStreamController)) + return false; + } + + // Step 3 (or 4): Perform ! ResetQueue(controller). + if (!ResetQueue(cx, controller)) + return false; + + // Step 4 (or 5): Perform ! ReadableStreamError(stream, e). + return ReadableStreamErrorInternal(cx, stream, e); +} + +// Streams spec, 3.9.7. ReadableStreamDefaultControllerErrorIfNeeded ( controller, e ) nothrow +static MOZ_MUST_USE bool +ReadableStreamDefaultControllerErrorIfNeeded(JSContext* cx, + Handle<ReadableStreamDefaultController*> controller, + HandleValue e) +{ + // Step 1: If controller.[[controlledReadableStream]].[[state]] is "readable", + // perform ! ReadableStreamDefaultControllerError(controller, e). + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + if (stream->readable()) + return ReadableStreamControllerError(cx, controller, e); + return true; +} + +// Streams spec, 3.9.8. ReadableStreamDefaultControllerGetDesiredSize ( controller ) +// and +// Streams spec 3.12.13. ReadableByteStreamControllerGetDesiredSize ( controller ) +static MOZ_MUST_USE double +ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller) +{ + // Steps 1-4 done at callsites, so only assert that they have been done. +#if DEBUG + ReadableStream* stream = StreamFromController(controller); + MOZ_ASSERT(!(stream->errored() || stream->closed())); +#endif // DEBUG + + // Step 5: Return controller.[[strategyHWM]] − controller.[[queueTotalSize]]. + double strategyHWM = controller->getFixedSlot(ControllerSlot_StrategyHWM).toNumber(); + double queueSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber(); + return strategyHWM - queueSize; +} + +// Streams spec, 3.10.3 new ReadableByteStreamController ( stream, underlyingSource, +// highWaterMark ) +// Steps 3 - 16. +static MOZ_MUST_USE ReadableByteStreamController* +CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream, + HandleValue underlyingByteSource, + HandleValue highWaterMarkVal) +{ + Rooted<ReadableByteStreamController*> controller(cx); + controller = NewBuiltinClassInstance<ReadableByteStreamController>(cx); + if (!controller) + return nullptr; + + // Step 3: Set this.[[controlledReadableStream]] to stream. + controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream)); + + // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource. + controller->setFixedSlot(ControllerSlot_UnderlyingSource, underlyingByteSource); + + // Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false. + controller->setFixedSlot(ControllerSlot_Flags, Int32Value(0)); + + // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this). + if (!ReadableByteStreamControllerClearPendingPullIntos(cx, controller)) + return nullptr; + + // Step 7: Perform ! ResetQueue(this). + if (!ResetQueue(cx, controller)) + return nullptr; + + // Step 8: Set this.[[started]] and this.[[closeRequested]] to false. + // These should be false by default, unchanged since step 5. + MOZ_ASSERT(ControllerFlags(controller) == 0); + + // Step 9: Set this.[[strategyHWM]] to + // ? ValidateAndNormalizeHighWaterMark(highWaterMark). + double highWaterMark; + if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal, &highWaterMark)) + return nullptr; + controller->setFixedSlot(ControllerSlot_StrategyHWM, NumberValue(highWaterMark)); + + // Step 10: Let autoAllocateChunkSize be + // ? GetV(underlyingByteSource, "autoAllocateChunkSize"). + RootedValue autoAllocateChunkSize(cx); + if (!GetProperty(cx, underlyingByteSource, cx->names().autoAllocateChunkSize, + &autoAllocateChunkSize)) + { + return nullptr; + } + + // Step 11: If autoAllocateChunkSize is not undefined, + if (!autoAllocateChunkSize.isUndefined()) { + // Step a: If ! IsInteger(autoAllocateChunkSize) is false, or if + // autoAllocateChunkSize ≤ 0, throw a RangeError exception. + if (!IsInteger(autoAllocateChunkSize) || autoAllocateChunkSize.toNumber() <= 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE); + return nullptr; + } + } + + // Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize. + controller->setFixedSlot(ByteControllerSlot_AutoAllocateSize, autoAllocateChunkSize); + + // Step 13: Set this.[[pendingPullIntos]] to a new empty List. + if (!SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos)) + return nullptr; + + // Step 14: Let controller be this (implicit). + + // Step 15: Let startResult be + // ? InvokeOrNoop(underlyingSource, "start", « this »). + RootedValue startResult(cx); + RootedValue controllerVal(cx, ObjectValue(*controller)); + if (!InvokeOrNoop(cx, underlyingByteSource, cx->names().start, controllerVal, &startResult)) + return nullptr; + + // Step 16: Let startPromise be a promise resolved with startResult: + RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, startResult)); + if (!startPromise) + return nullptr; + + RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller)); + if (!onStartFulfilled) + return nullptr; + + RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller)); + if (!onStartRejected) + return nullptr; + + if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected)) + return nullptr; + + return controller; +} + +bool +ReadableByteStreamController::hasExternalSource() { + return ControllerFlags(this) & ControllerFlag_ExternalSource; +} + +// Streams spec, 3.10.3. +// new ReadableByteStreamController ( stream, underlyingByteSource, +// highWaterMark ) +bool +ReadableByteStreamController::constructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "ReadableByteStreamController")) + return false; + + // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception. + HandleValue streamVal = args.get(0); + if (!Is<ReadableStream>(streamVal)) { + ReportArgTypeError(cx, "ReadableStreamDefaultController", "ReadableStream", + args.get(0)); + return false; + } + + Rooted<ReadableStream*> stream(cx, &streamVal.toObject().as<ReadableStream>()); + + // Step 2: If stream.[[readableStreamController]] is not undefined, throw a + // TypeError exception. + if (HasController(stream)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_CONTROLLER_SET); + return false; + } + + RootedObject controller(cx, CreateReadableByteStreamController(cx, stream, args.get(1), + args.get(2))); + if (!controller) + return false; + + args.rval().setObject(*controller); + return true; +} + +// Version of the ReadableByteStreamConstructor that's specialized for +// handling external, embedding-provided, underlying sources. +static MOZ_MUST_USE ReadableByteStreamController* +CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream, + void* underlyingSource) +{ + Rooted<ReadableByteStreamController*> controller(cx); + controller = NewBuiltinClassInstance<ReadableByteStreamController>(cx); + if (!controller) + return nullptr; + + // Step 3: Set this.[[controlledReadableStream]] to stream. + controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream)); + + // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource. + controller->setFixedSlot(ControllerSlot_UnderlyingSource, PrivateValue(underlyingSource)); + + // Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false. + controller->setFixedSlot(ControllerSlot_Flags, Int32Value(ControllerFlag_ExternalSource)); + + // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this). + // Omitted. + + // Step 7: Perform ! ResetQueue(this). + controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(0)); + + // Step 8: Set this.[[started]] and this.[[closeRequested]] to false. + // Step 9: Set this.[[strategyHWM]] to + // ? ValidateAndNormalizeHighWaterMark(highWaterMark). + controller->setFixedSlot(ControllerSlot_StrategyHWM, Int32Value(0)); + + // Step 10: Let autoAllocateChunkSize be + // ? GetV(underlyingByteSource, "autoAllocateChunkSize"). + // Step 11: If autoAllocateChunkSize is not undefined, + // Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize. + // Omitted. + + // Step 13: Set this.[[pendingPullIntos]] to a new empty List. + if (!SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos)) + return nullptr; + + // Step 14: Let controller be this (implicit). + // Step 15: Let startResult be + // ? InvokeOrNoop(underlyingSource, "start", « this »). + // Omitted. + + // Step 16: Let startPromise be a promise resolved with startResult: + RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, UndefinedHandleValue)); + if (!startPromise) + return nullptr; + + RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller)); + if (!onStartFulfilled) + return nullptr; + + RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller)); + if (!onStartRejected) + return nullptr; + + if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected)) + return nullptr; + + return controller; +} + +static MOZ_MUST_USE ReadableStreamBYOBRequest* +CreateReadableStreamBYOBRequest(JSContext* cx, Handle<ReadableByteStreamController*> controller, + HandleObject view); + +// Streams spec, 3.10.4.1. get byobRequest +static MOZ_MUST_USE bool +ReadableByteStreamController_byobRequest_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableByteStreamController*> controller(cx); + controller = &args.thisv().toObject().as<ReadableByteStreamController>(); + + // Step 2: If this.[[byobRequest]] is undefined and this.[[pendingPullIntos]] + // is not empty, + Value val = controller->getFixedSlot(ByteControllerSlot_BYOBRequest); + RootedValue byobRequest(cx, val); + val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos); + RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>()); + + if (byobRequest.isUndefined() && pendingPullIntos->getDenseInitializedLength() != 0) { + // Step a: Let firstDescriptor be the first element of this.[[pendingPullIntos]]. + Rooted<PullIntoDescriptor*> firstDescriptor(cx); + firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos); + + // Step b: Let view be ! Construct(%Uint8Array%, + // « firstDescriptor.[[buffer]], + // firstDescriptor.[[byteOffset]] + firstDescriptor.[[bytesFilled]], + // firstDescriptor.[[byteLength]] − firstDescriptor.[[bytesFilled]] »). + RootedArrayBufferObject buffer(cx, firstDescriptor->buffer()); + uint32_t bytesFilled = firstDescriptor->bytesFilled(); + RootedObject view(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, + firstDescriptor->byteOffset() + bytesFilled, + firstDescriptor->byteLength() - bytesFilled)); + if (!view) + return false; + + // Step c: Set this.[[byobRequest]] to + // ! Construct(ReadableStreamBYOBRequest, « this, view »). + RootedObject request(cx, CreateReadableStreamBYOBRequest(cx, controller, view)); + if (!request) + return false; + byobRequest = ObjectValue(*request); + controller->setFixedSlot(ByteControllerSlot_BYOBRequest, byobRequest); + } + + // Step 3: Return this.[[byobRequest]]. + args.rval().set(byobRequest); + return true; +} + +static bool +ReadableByteStreamController_byobRequest(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If IsReadableByteStreamController(this) is false, throw a TypeError + // exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableByteStreamController>, + ReadableByteStreamController_byobRequest_impl>(cx, args); +} + +// Streams spec, 3.10.4.2. get desiredSize +// Combined with 3.8.4.1 above. + +static bool +ReadableByteStreamController_desiredSize(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableByteStreamController(this) is false, throw a + // TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableByteStreamController>, + ReadableStreamController_desiredSize_impl>(cx, args); +} + +static MOZ_MUST_USE bool +ReadableByteStreamControllerClose(JSContext* cx, Handle<ReadableByteStreamController*> controller); + +// Streams spec, 3.10.4.3. close() +static MOZ_MUST_USE bool +ReadableByteStreamController_close_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableByteStreamController*> controller(cx); + controller = &args.thisv().toObject().as<ReadableByteStreamController>(); + + // Steps 2-3. + if (!VerifyControllerStateForClosing(cx, controller)) + return false; + + // Step 4: Perform ? ReadableByteStreamControllerClose(this). + if (!ReadableByteStreamControllerClose(cx, controller)) + return false; + args.rval().setUndefined(); + return true; +} + +static bool +ReadableByteStreamController_close(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableByteStreamController(this) is false, throw a + // TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableByteStreamController>, + ReadableByteStreamController_close_impl>(cx, args); +} + +static MOZ_MUST_USE bool +ReadableByteStreamControllerEnqueue(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + HandleObject chunk); + +// Streams spec, 3.10.4.4. enqueue ( chunk ) +static MOZ_MUST_USE bool +ReadableByteStreamController_enqueue_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableByteStreamController*> controller(cx); + controller = &args.thisv().toObject().as<ReadableByteStreamController>(); + HandleValue chunkVal = args.get(0); + + // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception. + if (ControllerFlags(controller) & ControllerFlag_CloseRequested) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue"); + return false; + } + + // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable", + // throw a TypeError exception. + if (!StreamFromController(controller)->readable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "enqueue"); + return false; + } + + // Step 4: If Type(chunk) is not Object, throw a TypeError exception. + // Step 5: If chunk does not have a [[ViewedArrayBuffer]] internal slot, + // throw a TypeError exception. + if (!chunkVal.isObject() || !JS_IsArrayBufferViewObject(&chunkVal.toObject())) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK, + "ReadableByteStreamController#enqueue"); + return false; + } + RootedObject chunk(cx, &chunkVal.toObject()); + + // Step 6: Return ! ReadableByteStreamControllerEnqueue(this, chunk). + if (!ReadableByteStreamControllerEnqueue(cx, controller, chunk)) + return false; + args.rval().setUndefined(); + return true; +} + +static bool +ReadableByteStreamController_enqueue(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableByteStreamController(this) is false, throw a + // TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableByteStreamController>, + ReadableByteStreamController_enqueue_impl>(cx, args); +} + +// Streams spec, 3.10.4.5. error ( e ) +static MOZ_MUST_USE bool +ReadableByteStreamController_error_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableByteStreamController*> controller(cx); + controller = &args.thisv().toObject().as<ReadableByteStreamController>(); + HandleValue e = args.get(0); + + // Step 2: Let stream be this.[[controlledReadableStream]]. + // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception. + if (!StreamFromController(controller)->readable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error"); + return false; + } + + // Step 4: Perform ! ReadableByteStreamControllerError(this, e). + if (!ReadableStreamControllerError(cx, controller, e)) + return false; + args.rval().setUndefined(); + return true; +} + +static bool +ReadableByteStreamController_error(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableByteStreamController(this) is false, throw a + // TypeError exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableByteStreamController>, + ReadableByteStreamController_error_impl>(cx, args); +} + +static const JSPropertySpec ReadableByteStreamController_properties[] = { + JS_PSG("byobRequest", ReadableByteStreamController_byobRequest, 0), + JS_PSG("desiredSize", ReadableByteStreamController_desiredSize, 0), + JS_PS_END +}; + +static const JSFunctionSpec ReadableByteStreamController_methods[] = { + JS_FN("close", ReadableByteStreamController_close, 0, 0), + JS_FN("enqueue", ReadableByteStreamController_enqueue, 1, 0), + JS_FN("error", ReadableByteStreamController_error, 1, 0), + JS_FS_END +}; + +static void +ReadableByteStreamControllerFinalize(FreeOp* fop, JSObject* obj) +{ + ReadableByteStreamController& controller = obj->as<ReadableByteStreamController>(); + + if (controller.getFixedSlot(ControllerSlot_Flags).isUndefined()) + return; + + uint32_t flags = ControllerFlags(&controller); + if (!(flags & ControllerFlag_ExternalSource)) + return; + + uint8_t embeddingFlags = flags >> ControllerEmbeddingFlagsOffset; + + void* underlyingSource = controller.getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate(); + obj->runtimeFromAnyThread()->readableStreamFinalizeCallback(underlyingSource, embeddingFlags); +} + +static const ClassOps ReadableByteStreamControllerClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + ReadableByteStreamControllerFinalize, + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + nullptr, /* trace */ +}; + +CLASS_SPEC(ReadableByteStreamController, 3, 9, ClassSpec::DontDefineConstructor, + JSCLASS_BACKGROUND_FINALIZE, &ReadableByteStreamControllerClassOps); + +// Streams spec, 3.10.5.1. [[PullSteps]] () +// Unified with 3.8.5.1 above. + +static MOZ_MUST_USE bool +ReadableByteStreamControllerHandleQueueDrain(JSContext* cx, HandleNativeObject controller); + +// Streams spec, 3.10.5.2. [[PullSteps]] () +static JSObject* +ReadableByteStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller) +{ + // Step 1: Let stream be this.[[controlledReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 2: MOZ_ASSERT: ! ReadableStreamHasDefaultReader(stream) is true. + MOZ_ASSERT(ReadableStreamHasDefaultReader(stream)); + + RootedValue val(cx); + // Step 3: If this.[[queueTotalSize]] > 0, + double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber(); + if (queueTotalSize > 0) { + // Step 3.a: MOZ_ASSERT: ! ReadableStreamGetNumReadRequests(_stream_) is 0. + MOZ_ASSERT(ReadableStreamGetNumReadRequests(stream) == 0); + + RootedObject view(cx); + + if (stream->mode() == JS::ReadableStreamMode::ExternalSource) { + val = controller->getFixedSlot(ControllerSlot_UnderlyingSource); + void* underlyingSource = val.toPrivate(); + + view = JS_NewUint8Array(cx, queueTotalSize); + if (!view) + return nullptr; + + size_t bytesWritten; + { + JS::AutoCheckCannotGC noGC(cx); + bool dummy; + void* buffer = JS_GetArrayBufferViewData(view, &dummy, noGC); + auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback; + MOZ_ASSERT(cb); + // TODO: use bytesWritten to correctly update the request's state. + cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer, + queueTotalSize, &bytesWritten); + } + + queueTotalSize = queueTotalSize - bytesWritten; + } else { + // Step 3.b: Let entry be the first element of this.[[queue]]. + // Step 3.c: Remove entry from this.[[queue]], shifting all other elements + // downward (so that the second becomes the first, and so on). + val = controller->getFixedSlot(QueueContainerSlot_Queue); + RootedNativeObject queue(cx, &val.toObject().as<NativeObject>()); + Rooted<ByteStreamChunk*> entry(cx, ShiftFromList<ByteStreamChunk>(cx, queue)); + MOZ_ASSERT(entry); + + queueTotalSize = queueTotalSize - entry->byteLength(); + + // Step 3.f: Let view be ! Construct(%Uint8Array%, « entry.[[buffer]], + // entry.[[byteOffset]], entry.[[byteLength]] »). + // (reordered) + RootedObject buffer(cx, entry->buffer()); + + uint32_t byteOffset = entry->byteOffset(); + view = JS_NewUint8ArrayWithBuffer(cx, buffer, byteOffset, entry->byteLength()); + if (!view) + return nullptr; + } + + // Step 3.d: Set this.[[queueTotalSize]] to + // this.[[queueTotalSize]] − entry.[[byteLength]]. + // (reordered) + controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(queueTotalSize)); + + // Step 3.e: Perform ! ReadableByteStreamControllerHandleQueueDrain(this). + // (reordered) + if (!ReadableByteStreamControllerHandleQueueDrain(cx, controller)) + return nullptr; + + // Step 3.g: Return a promise resolved with ! CreateIterResultObject(view, false). + val.setObject(*view); + RootedObject iterResult(cx, CreateIterResultObject(cx, val, false)); + if (!iterResult) + return nullptr; + val.setObject(*iterResult); + + return PromiseObject::unforgeableResolve(cx, val); + } + + // Step 4: Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]]. + val = controller->getFixedSlot(ByteControllerSlot_AutoAllocateSize); + + // Step 5: If autoAllocateChunkSize is not undefined, + if (!val.isUndefined()) { + double autoAllocateChunkSize = val.toNumber(); + + // Step 5.a: Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize »). + RootedObject bufferObj(cx, JS_NewArrayBuffer(cx, autoAllocateChunkSize)); + + // Step 5.b: If buffer is an abrupt completion, + // return a promise rejected with buffer.[[Value]]. + if (!bufferObj) + return PromiseRejectedWithPendingError(cx); + + RootedArrayBufferObject buffer(cx, &bufferObj->as<ArrayBufferObject>()); + + // Step 5.c: Let pullIntoDescriptor be Record {[[buffer]]: buffer.[[Value]], + // [[byteOffset]]: 0, + // [[byteLength]]: autoAllocateChunkSize, + // [[bytesFilled]]: 0, [[elementSize]]: 1, + // [[ctor]]: %Uint8Array%, + // [[readerType]]: `"default"`}. + RootedObject pullIntoDescriptor(cx); + pullIntoDescriptor = PullIntoDescriptor::create(cx, buffer, 0, + autoAllocateChunkSize, 0, 1, + nullptr, + ReaderType_Default); + if (!pullIntoDescriptor) + return PromiseRejectedWithPendingError(cx); + + // Step 5.d: Append pullIntoDescriptor as the last element of this.[[pendingPullIntos]]. + val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos); + RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>()); + val = ObjectValue(*pullIntoDescriptor); + if (!AppendToList(cx, pendingPullIntos, val)) + return nullptr; + } + + // Step 6: Let promise be ! ReadableStreamAddReadRequest(stream). + Rooted<PromiseObject*> promise(cx, ReadableStreamAddReadRequest(cx, stream)); + if (!promise) + return nullptr; + + // Step 7: Perform ! ReadableByteStreamControllerCallPullIfNeeded(this). + if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) + return nullptr; + + // Step 8: Return promise. + return promise; +} + +/** + * Unified implementation of ReadableStream controllers' [[PullSteps]] internal + * methods. + * Streams spec, 3.8.5.2. [[PullSteps]] () + * and + * Streams spec, 3.10.5.2. [[PullSteps]] () + */ +static MOZ_MUST_USE JSObject* +ReadableStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller) +{ + MOZ_ASSERT(IsReadableStreamController(controller)); + + if (controller->is<ReadableStreamDefaultController>()) + return ReadableStreamDefaultControllerPullSteps(cx, controller); + + return ReadableByteStreamControllerPullSteps(cx, controller); +} + + +static MOZ_MUST_USE ReadableStreamBYOBRequest* +CreateReadableStreamBYOBRequest(JSContext* cx, Handle<ReadableByteStreamController*> controller, + HandleObject view) +{ + MOZ_ASSERT(controller); + MOZ_ASSERT(JS_IsArrayBufferViewObject(view)); + + Rooted<ReadableStreamBYOBRequest*> request(cx); + request = NewBuiltinClassInstance<ReadableStreamBYOBRequest>(cx); + if (!request) + return nullptr; + + // Step 1: Set this.[[associatedReadableByteStreamController]] to controller. + request->setFixedSlot(BYOBRequestSlot_Controller, ObjectValue(*controller)); + + // Step 2: Set this.[[view]] to view. + request->setFixedSlot(BYOBRequestSlot_View, ObjectValue(*view)); + + return request; +} + +// Streams spec, 3.11.3. new ReadableStreamBYOBRequest ( controller, view ) +bool +ReadableStreamBYOBRequest::constructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + HandleValue controllerVal = args.get(0); + HandleValue viewVal = args.get(1); + + if (!ThrowIfNotConstructing(cx, args, "ReadableStreamBYOBRequest")) + return false; + + // TODO: open PR against spec to add these checks. + // They're expected to have happened in code using requests. + if (!Is<ReadableByteStreamController>(controllerVal)) { + ReportArgTypeError(cx, "ReadableStreamBYOBRequest", + "ReadableByteStreamController", args.get(0)); + return false; + } + + Rooted<ReadableByteStreamController*> controller(cx); + controller = &controllerVal.toObject().as<ReadableByteStreamController>(); + + if (!viewVal.isObject() || !JS_IsArrayBufferViewObject(&viewVal.toObject())) { + ReportArgTypeError(cx, "ReadableStreamBYOBRequest", "ArrayBuffer view", + args.get(1)); + return false; + } + + RootedArrayBufferObject view(cx, &viewVal.toObject().as<ArrayBufferObject>()); + + RootedObject request(cx, CreateReadableStreamBYOBRequest(cx, controller, view)); + if (!request) + return false; + + args.rval().setObject(*request); + return true; +} + +// Streams spec, 3.11.4.1 get view +static MOZ_MUST_USE bool +ReadableStreamBYOBRequest_view_impl(JSContext* cx, const CallArgs& args) +{ + // Step 2: Return this.[[view]]. + NativeObject* request = &args.thisv().toObject().as<NativeObject>(); + args.rval().set(request->getFixedSlot(BYOBRequestSlot_View)); + return true; +} + +static bool +ReadableStreamBYOBRequest_view(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError + // exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>, + ReadableStreamBYOBRequest_view_impl>(cx, args); +} + +static MOZ_MUST_USE bool +ReadableByteStreamControllerRespond(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + HandleValue bytesWrittenVal); + +// Streams spec, 3.11.4.2. respond ( bytesWritten ) +static MOZ_MUST_USE bool +ReadableStreamBYOBRequest_respond_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableStreamBYOBRequest*> request(cx); + request = &args.thisv().toObject().as<ReadableStreamBYOBRequest>(); + HandleValue bytesWritten = args.get(0); + + // Step 2: If this.[[associatedReadableByteStreamController]] is undefined, + // throw a TypeError exception. + RootedValue controllerVal(cx, request->getFixedSlot(BYOBRequestSlot_Controller)); + if (controllerVal.isUndefined()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, "respond"); + return false; + } + + // Step 3: Return ? + // ReadableByteStreamControllerRespond(this.[[associatedReadableByteStreamController]], + // bytesWritten). + Rooted<ReadableByteStreamController*> controller(cx); + controller = &controllerVal.toObject().as<ReadableByteStreamController>(); + + if (!ReadableByteStreamControllerRespond(cx, controller, bytesWritten)) + return false; + + args.rval().setUndefined(); + return true; +} + +static bool +ReadableStreamBYOBRequest_respond(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError + // exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>, + ReadableStreamBYOBRequest_respond_impl>(cx, args); +} + +static MOZ_MUST_USE bool +ReadableByteStreamControllerRespondWithNewView(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + HandleObject view); + +// Streams spec, 3.11.4.3. respondWithNewView ( view ) +static MOZ_MUST_USE bool +ReadableStreamBYOBRequest_respondWithNewView_impl(JSContext* cx, const CallArgs& args) +{ + Rooted<ReadableStreamBYOBRequest*> request(cx); + request = &args.thisv().toObject().as<ReadableStreamBYOBRequest>(); + HandleValue viewVal = args.get(0); + + // Step 2: If this.[[associatedReadableByteStreamController]] is undefined, + // throw a TypeError exception. + RootedValue controllerVal(cx, request->getFixedSlot(BYOBRequestSlot_Controller)); + if (controllerVal.isUndefined()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, "respondWithNewView"); + return false; + } + + // Step 3: If Type(chunk) is not Object, throw a TypeError exception. + // Step 4: If view does not have a [[ViewedArrayBuffer]] internal slot, throw + // a TypeError exception. + if (!viewVal.isObject() || !JS_IsArrayBufferViewObject(&viewVal.toObject())) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK, + "ReadableStreamBYOBRequest#respondWithNewView"); + return false; + } + + // Step 5: Return ? + // ReadableByteStreamControllerRespondWithNewView(this.[[associatedReadableByteStreamController]], + // view). + Rooted<ReadableByteStreamController*> controller(cx); + controller = &controllerVal.toObject().as<ReadableByteStreamController>(); + RootedObject view(cx, &viewVal.toObject()); + + if (!ReadableByteStreamControllerRespondWithNewView(cx, controller, view)) + return false; + + args.rval().setUndefined(); + return true; +} + +static bool +ReadableStreamBYOBRequest_respondWithNewView(JSContext* cx, unsigned argc, Value* vp) +{ + // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError + // exception. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>, + ReadableStreamBYOBRequest_respondWithNewView_impl>(cx, args); +} + +static const JSPropertySpec ReadableStreamBYOBRequest_properties[] = { + JS_PSG("view", ReadableStreamBYOBRequest_view, 0), + JS_PS_END +}; + +static const JSFunctionSpec ReadableStreamBYOBRequest_methods[] = { + JS_FN("respond", ReadableStreamBYOBRequest_respond, 1, 0), + JS_FN("respondWithNewView", ReadableStreamBYOBRequest_respondWithNewView, 1, 0), + JS_FS_END +}; + +CLASS_SPEC(ReadableStreamBYOBRequest, 3, 2, ClassSpec::DontDefineConstructor, 0, + JS_NULL_CLASS_OPS); + +// Streams spec, 3.12.1. IsReadableStreamBYOBRequest ( x ) +// Implemented via is<ReadableStreamBYOBRequest>() + +// Streams spec, 3.12.2. IsReadableByteStreamController ( x ) +// Implemented via is<ReadableByteStreamController>() + +// Streams spec, 3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller ) +// Unified with 3.9.2 above. + +static void +ReadableByteStreamControllerInvalidateBYOBRequest(NativeObject* controller); + +// Streams spec, 3.12.4. ReadableByteStreamControllerClearPendingPullIntos ( controller ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx, HandleNativeObject controller) +{ + MOZ_ASSERT(controller->is<ReadableByteStreamController>()); + + // Step 1: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). + ReadableByteStreamControllerInvalidateBYOBRequest(controller); + + // Step 2: Set controller.[[pendingPullIntos]] to a new empty List. + return SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos); +} + +// Streams spec, 3.12.5. ReadableByteStreamControllerClose ( controller ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerClose(JSContext* cx, Handle<ReadableByteStreamController*> controller) +{ + // Step 1: Let stream be controller.[[controlledReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 2: Assert: controller.[[closeRequested]] is false. + MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested)); + + // Step 3: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(stream->readable()); + + // Step 4: If controller.[[queueTotalSize]] > 0, + double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber(); + if (queueTotalSize > 0) { + // Step a: Set controller.[[closeRequested]] to true. + AddControllerFlags(controller, ControllerFlag_CloseRequested); + + // Step b: Return + return true; + } + + // Step 5: If controller.[[pendingPullIntos]] is not empty, + RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos)); + RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>()); + if (pendingPullIntos->getDenseInitializedLength() != 0) { + // Step a: Let firstPendingPullInto be the first element of + // controller.[[pendingPullIntos]]. + Rooted<PullIntoDescriptor*> firstPendingPullInto(cx); + firstPendingPullInto = PeekList<PullIntoDescriptor>(pendingPullIntos); + + // Step b: If firstPendingPullInto.[[bytesFilled]] > 0, + if (firstPendingPullInto->bytesFilled() > 0) { + // Step i: Let e be a new TypeError exception. { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL); + RootedValue e(cx); + // Not much we can do about uncatchable exceptions, just bail. + if (!cx->getPendingException(&e)) + return false; + // Step ii: Perform ! ReadableByteStreamControllerError(controller, e). + if (!ReadableStreamControllerError(cx, controller, e)) + return false; + + // Step iii: Throw e. + return false; + } + } + + // Step 6: Perform ! ReadableStreamClose(stream). + return ReadableStreamCloseInternal(cx, stream); +} + +static MOZ_MUST_USE JSObject* +ReadableByteStreamControllerConvertPullIntoDescriptor(JSContext* cx, + Handle<PullIntoDescriptor*> pullIntoDescriptor); + +// Streams spec, 3.12.6. ReadableByteStreamControllerCommitPullIntoDescriptor ( stream, pullIntoDescriptor ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerCommitPullIntoDescriptor(JSContext* cx, Handle<ReadableStream*> stream, + Handle<PullIntoDescriptor*> pullIntoDescriptor) +{ + // Step 1: MOZ_ASSERT: stream.[[state]] is not "errored". + MOZ_ASSERT(!stream->errored()); + + // Step 2: Let done be false. + bool done = false; + + // Step 3: If stream.[[state]] is "closed", + if (stream->closed()) { + // Step a: MOZ_ASSERT: pullIntoDescriptor.[[bytesFilled]] is 0. + MOZ_ASSERT(pullIntoDescriptor->bytesFilled() == 0); + + // Step b: Set done to true. + done = true; + } + + // Step 4: Let filledView be + // ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor). + RootedObject filledView(cx); + filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(cx, pullIntoDescriptor); + if (!filledView) + return false; + + // Step 5: If pullIntoDescriptor.[[readerType]] is "default", + uint32_t readerType = pullIntoDescriptor->readerType(); + RootedValue filledViewVal(cx, ObjectValue(*filledView)); + if (readerType == ReaderType_Default) { + // Step a: Perform ! ReadableStreamFulfillReadRequest(stream, filledView, done). + if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, filledViewVal, done)) + return false; + } else { + // Step 6: Otherwise, + // Step a: MOZ_ASSERT: pullIntoDescriptor.[[readerType]] is "byob". + MOZ_ASSERT(readerType == ReaderType_BYOB); + + // Step b: Perform ! ReadableStreamFulfillReadIntoRequest(stream, filledView, done). + if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, filledViewVal, done)) + return false; + } + + return true; +} + +// Streams spec, 3.12.7. ReadableByteStreamControllerConvertPullIntoDescriptor ( pullIntoDescriptor ) +static MOZ_MUST_USE JSObject* +ReadableByteStreamControllerConvertPullIntoDescriptor(JSContext* cx, + Handle<PullIntoDescriptor*> pullIntoDescriptor) +{ + // Step 1: Let bytesFilled be pullIntoDescriptor.[[bytesFilled]]. + uint32_t bytesFilled = pullIntoDescriptor->bytesFilled(); + + // Step 2: Let elementSize be pullIntoDescriptor.[[elementSize]]. + uint32_t elementSize = pullIntoDescriptor->elementSize(); + + // Step 3: Assert: bytesFilled <= pullIntoDescriptor.[[byteLength]]. + MOZ_ASSERT(bytesFilled <= pullIntoDescriptor->byteLength()); + + // Step 4: Assert: bytesFilled mod elementSize is 0. + MOZ_ASSERT(bytesFilled % elementSize == 0); + + // Step 5: Return ! Construct(pullIntoDescriptor.[[ctor]], + // pullIntoDescriptor.[[buffer]], + // pullIntoDescriptor.[[byteOffset]], + // bytesFilled / elementSize). + RootedObject ctor(cx, pullIntoDescriptor->ctor()); + if (!ctor) { + if (!GetBuiltinConstructor(cx, JSProto_Uint8Array, &ctor)) + return nullptr; + } + RootedObject buffer(cx, pullIntoDescriptor->buffer()); + uint32_t byteOffset = pullIntoDescriptor->byteOffset(); + FixedConstructArgs<3> args(cx); + args[0].setObject(*buffer); + args[1].setInt32(byteOffset); + args[2].setInt32(bytesFilled / elementSize); + return JS_New(cx, ctor, args); +} + +static MOZ_MUST_USE bool +ReadableByteStreamControllerEnqueueChunkToQueue(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + HandleObject buffer, uint32_t byteOffset, + uint32_t byteLength); + +static MOZ_MUST_USE ArrayBufferObject* +TransferArrayBuffer(JSContext* cx, HandleObject buffer); + +static MOZ_MUST_USE bool +ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(JSContext* cx, + Handle<ReadableByteStreamController*> controller); + +// Streams spec, 3.12.8. ReadableByteStreamControllerEnqueue ( controller, chunk ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerEnqueue(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + HandleObject chunk) +{ + // Step 1: Let stream be controller.[[controlledReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 2: Assert: controller.[[closeRequested]] is false. + MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested)); + + // Step 3: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(stream->readable()); + + // To make enqueuing chunks via JSAPI nicer, we want to be able to deal + // with ArrayBuffer objects in addition to ArrayBuffer views here. + // This cannot happen when enqueuing happens via + // ReadableByteStreamController_enqueue because that throws if invoked + // with anything but an ArrayBuffer view. + + Rooted<ArrayBufferObject*> buffer(cx); + uint32_t byteOffset; + uint32_t byteLength; + + if (chunk->is<ArrayBufferObject>()) { + // Steps 4-6 for ArrayBuffer objects. + buffer = &chunk->as<ArrayBufferObject>(); + byteOffset = 0; + byteLength = buffer->byteLength(); + } else { + // Step 4: Let buffer be chunk.[[ViewedArrayBuffer]]. + bool dummy; + JSObject* bufferObj = JS_GetArrayBufferViewBuffer(cx, chunk, &dummy); + if (!bufferObj) + return false; + buffer = &bufferObj->as<ArrayBufferObject>(); + + // Step 5: Let byteOffset be chunk.[[ByteOffset]]. + byteOffset = JS_GetArrayBufferViewByteOffset(chunk); + + // Step 6: Let byteLength be chunk.[[ByteLength]]. + byteLength = JS_GetArrayBufferViewByteLength(chunk); + } + + // Step 7: Let transferredBuffer be ! TransferArrayBuffer(buffer). + RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer)); + if (!transferredBuffer) + return false; + + // Step 8: If ! ReadableStreamHasDefaultReader(stream) is true + if (ReadableStreamHasDefaultReader(stream)) { + // Step a: If ! ReadableStreamGetNumReadRequests(stream) is 0, + if (ReadableStreamGetNumReadRequests(stream) == 0) { + // Step i: Perform + // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, + // transferredBuffer, + // byteOffset, + // byteLength). + if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer, + byteOffset, byteLength)) + { + return false; + } + } else { + // Step b: Otherwise, + // Step i: Assert: controller.[[queue]] is empty. +#if DEBUG + RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue)); + RootedNativeObject queue(cx, &val.toObject().as<NativeObject>()); + MOZ_ASSERT(queue->getDenseInitializedLength() == 0); +#endif // DEBUG + + // Step ii: Let transferredView be + // ! Construct(%Uint8Array%, transferredBuffer, byteOffset, byteLength). + RootedObject transferredView(cx, JS_NewUint8ArrayWithBuffer(cx, transferredBuffer, + byteOffset, byteLength)); + if (!transferredView) + return false; + + // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false). + RootedValue chunk(cx, ObjectValue(*transferredView)); + if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false)) + return false; + } + } else if (ReadableStreamHasBYOBReader(stream)) { + // Step 9: Otherwise, + // Step a: If ! ReadableStreamHasBYOBReader(stream) is true, + // Step i: Perform + // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, + // transferredBuffer, + // byteOffset, + // byteLength). + if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer, + byteOffset, byteLength)) + { + return false; + } + + // Step ii: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). + if (!ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller)) + return false; + } else { + // Step b: Otherwise, + // Step i: Assert: ! IsReadableStreamLocked(stream) is false. + MOZ_ASSERT(!stream->locked()); + + // Step ii: Perform + // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, + // transferredBuffer, + // byteOffset, + // byteLength). + if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer, + byteOffset, byteLength)) + { + return false; + } + } + + return true; +} + +// Streams spec, 3.12.9. +// ReadableByteStreamControllerEnqueueChunkToQueue ( controller, buffer, +// byteOffset, byteLength ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerEnqueueChunkToQueue(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + HandleObject buffer, uint32_t byteOffset, + uint32_t byteLength) +{ + MOZ_ASSERT(controller->is<ReadableByteStreamController>(), "must operate on ReadableByteStreamController"); + + // Step 1: Append Record {[[buffer]]: buffer, + // [[byteOffset]]: byteOffset, + // [[byteLength]]: byteLength} + // as the last element of controller.[[queue]]. + RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue)); + RootedNativeObject queue(cx, &val.toObject().as<NativeObject>()); + + Rooted<ByteStreamChunk*> chunk(cx); + chunk = ByteStreamChunk::create(cx, buffer, byteOffset, byteLength); + if (!chunk) + return false; + + RootedValue chunkVal(cx, ObjectValue(*chunk)); + if (!AppendToList(cx, queue, chunkVal)) + return false; + + // Step 2: Add byteLength to controller.[[queueTotalSize]]. + double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber(); + controller->setFixedSlot(QueueContainerSlot_TotalSize, + NumberValue(queueTotalSize + byteLength)); + + return true; +} + +// Streams spec, 3.12.10. ReadableByteStreamControllerError ( controller, e ) +// Unified with 3.9.6 above. + +// Streams spec, 3.12.11. ReadableByteStreamControllerFillHeadPullIntoDescriptor ( controler, size, pullIntoDescriptor ) +static void +ReadableByteStreamControllerFillHeadPullIntoDescriptor(ReadableByteStreamController* controller, uint32_t size, + Handle<PullIntoDescriptor*> pullIntoDescriptor) +{ + // Step 1: Assert: either controller.[[pendingPullIntos]] is empty, or the + // first element of controller.[[pendingPullIntos]] is pullIntoDescriptor. +#if DEBUG + Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos); + NativeObject* pendingPullIntos = &val.toObject().as<NativeObject>(); + MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() == 0 || + &pendingPullIntos->getDenseElement(0).toObject() == pullIntoDescriptor); +#endif // DEBUG + + // Step 2: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). + ReadableByteStreamControllerInvalidateBYOBRequest(controller); + + // Step 3: Set pullIntoDescriptor.[[bytesFilled]] to pullIntoDescriptor.[[bytesFilled]] + size. + pullIntoDescriptor->setBytesFilled(pullIntoDescriptor->bytesFilled() + size); +} + +// Streams spec, 3.12.12. ReadableByteStreamControllerFillPullIntoDescriptorFromQueue ( controller, pullIntoDescriptor ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + Handle<PullIntoDescriptor*> pullIntoDescriptor, + bool* ready) +{ + *ready = false; + + // Step 1: Let elementSize be pullIntoDescriptor.[[elementSize]]. + uint32_t elementSize = pullIntoDescriptor->elementSize(); + + // Step 2: Let currentAlignedBytes be pullIntoDescriptor.[[bytesFilled]] − + // (pullIntoDescriptor.[[bytesFilled]] mod elementSize). + uint32_t bytesFilled = pullIntoDescriptor->bytesFilled(); + uint32_t currentAlignedBytes = bytesFilled - (bytesFilled % elementSize); + + // Step 3: Let maxBytesToCopy be min(controller.[[queueTotalSize]], + // pullIntoDescriptor.[[byteLength]] − pullIntoDescriptor.[[bytesFilled]]). + uint32_t byteLength = pullIntoDescriptor->byteLength(); + + // The queue size could be negative or overflow uint32_t. We cannot + // validly have a maxBytesToCopy value that'd overflow uint32_t, though, + // so just clamp to that. + Value sizeVal = controller->getFixedSlot(QueueContainerSlot_TotalSize); + uint32_t queueTotalSize = JS::ToUint32(sizeVal.toNumber()); + uint32_t maxBytesToCopy = std::min(queueTotalSize, byteLength - bytesFilled); + + // Step 4: Let maxBytesFilled be pullIntoDescriptor.[[bytesFilled]] + maxBytesToCopy. + uint32_t maxBytesFilled = bytesFilled + maxBytesToCopy; + + // Step 5: Let maxAlignedBytes be maxBytesFilled − (maxBytesFilled mod elementSize). + uint32_t maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize); + + // Step 6: Let totalBytesToCopyRemaining be maxBytesToCopy. + uint32_t totalBytesToCopyRemaining = maxBytesToCopy; + + // Step 7: Let ready be false (implicit). + + // Step 8: If maxAlignedBytes > currentAlignedBytes, + if (maxAlignedBytes > currentAlignedBytes) { + // Step a: Set totalBytesToCopyRemaining to maxAlignedBytes − + // pullIntoDescriptor.[[bytesFilled]]. + totalBytesToCopyRemaining = maxAlignedBytes - bytesFilled; + + // Step b: Let ready be true. + *ready = true; + } + + if (ControllerFlags(controller) & ControllerFlag_ExternalSource) { + // TODO: it probably makes sense to eagerly drain the underlying source. + // We have a buffer lying around anyway, whereas the source might be + // able to free or reuse buffers once their content is copied into + // our buffer. + if (!ready) + return true; + + Value val = controller->getFixedSlot(ControllerSlot_UnderlyingSource); + void* underlyingSource = val.toPrivate(); + + RootedArrayBufferObject targetBuffer(cx, pullIntoDescriptor->buffer()); + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + size_t bytesWritten; + { + JS::AutoCheckCannotGC noGC(cx); + bool dummy; + uint8_t* buffer = JS_GetArrayBufferData(targetBuffer, &dummy, noGC); + buffer += bytesFilled; + auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback; + MOZ_ASSERT(cb); + cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer, + totalBytesToCopyRemaining, &bytesWritten); + pullIntoDescriptor->setBytesFilled(bytesFilled + bytesWritten); + } + + queueTotalSize -= bytesWritten; + controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(queueTotalSize)); + + return true; + } + + // Step 9: Let queue be controller.[[queue]]. + RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue)); + RootedNativeObject queue(cx, &val.toObject().as<NativeObject>()); + + // Step 10: Repeat the following steps while totalBytesToCopyRemaining > 0, + Rooted<ByteStreamChunk*> headOfQueue(cx); + while (totalBytesToCopyRemaining > 0) { + MOZ_ASSERT(queue->getDenseInitializedLength() != 0); + + // Step a: Let headOfQueue be the first element of queue. + headOfQueue = PeekList<ByteStreamChunk>(queue); + + // Step b: Let bytesToCopy be min(totalBytesToCopyRemaining, + // headOfQueue.[[byteLength]]). + uint32_t byteLength = headOfQueue->byteLength(); + uint32_t bytesToCopy = std::min(totalBytesToCopyRemaining, byteLength); + + // Step c: Let destStart be pullIntoDescriptor.[[byteOffset]] + + // pullIntoDescriptor.[[bytesFilled]]. + uint32_t destStart = pullIntoDescriptor->byteOffset() + bytesFilled; + + // Step d: Perform ! CopyDataBlockBytes(pullIntoDescriptor.[[buffer]].[[ArrayBufferData]], + // destStart, + // headOfQueue.[[buffer]].[[ArrayBufferData]], + // headOfQueue.[[byteOffset]], + // bytesToCopy). + RootedArrayBufferObject sourceBuffer(cx, headOfQueue->buffer()); + uint32_t sourceOffset = headOfQueue->byteOffset(); + RootedArrayBufferObject targetBuffer(cx, pullIntoDescriptor->buffer()); + ArrayBufferObject::copyData(targetBuffer, destStart, sourceBuffer, sourceOffset, + bytesToCopy); + + // Step e: If headOfQueue.[[byteLength]] is bytesToCopy, + if (byteLength == bytesToCopy) { + // Step i: Remove the first element of queue, shifting all other elements + // downward (so that the second becomes the first, and so on). + headOfQueue = ShiftFromList<ByteStreamChunk>(cx, queue); + MOZ_ASSERT(headOfQueue); + } else { + // Step f: Otherwise, + // Step i: Set headOfQueue.[[byteOffset]] to headOfQueue.[[byteOffset]] + + // bytesToCopy. + headOfQueue->SetByteOffset(sourceOffset + bytesToCopy); + + // Step ii: Set headOfQueue.[[byteLength]] to headOfQueue.[[byteLength]] − + // bytesToCopy. + headOfQueue->SetByteLength(byteLength - bytesToCopy); + } + + // Step g: Set controller.[[queueTotalSize]] to + // controller.[[queueTotalSize]] − bytesToCopy. + queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber(); + queueTotalSize -= bytesToCopy; + controller->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(queueTotalSize)); + + // Step h: Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, + // bytesToCopy, + // pullIntoDescriptor). + ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesToCopy, + pullIntoDescriptor); + bytesFilled += bytesToCopy; + MOZ_ASSERT(bytesFilled == pullIntoDescriptor->bytesFilled()); + + // Step i: Set totalBytesToCopyRemaining to totalBytesToCopyRemaining − bytesToCopy. + totalBytesToCopyRemaining -= bytesToCopy; + } + + // Step 11: If ready is false, + if (!*ready) { + // Step a: Assert: controller.[[queueTotalSize]] is 0. + MOZ_ASSERT(controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber() == 0); + + // Step b: Assert: pullIntoDescriptor.[[bytesFilled]] > 0. + MOZ_ASSERT(bytesFilled > 0, "should have filled some bytes"); + + // Step c: Assert: pullIntoDescriptor.[[bytesFilled]] < + // pullIntoDescriptor.[[elementSize]]. + MOZ_ASSERT(bytesFilled < elementSize); + } + + // Step 12: Return ready. + return true; +} + +// Streams spec 3.12.13. ReadableByteStreamControllerGetDesiredSize ( controller ) +// Unified with 3.9.8 above. + +// Streams spec, 3.12.14. ReadableByteStreamControllerHandleQueueDrain ( controller ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerHandleQueueDrain(JSContext* cx, HandleNativeObject controller) +{ + MOZ_ASSERT(controller->is<ReadableByteStreamController>()); + + // Step 1: Assert: controller.[[controlledReadableStream]].[[state]] is "readable". + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + MOZ_ASSERT(stream->readable()); + + // Step 2: If controller.[[queueTotalSize]] is 0 and + // controller.[[closeRequested]] is true, + double totalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber(); + bool closeRequested = ControllerFlags(controller) & ControllerFlag_CloseRequested; + if (totalSize == 0 && closeRequested) { + // Step a: Perform ! ReadableStreamClose(controller.[[controlledReadableStream]]). + return ReadableStreamCloseInternal(cx, stream); + } + + // Step 3: Otherwise, + // Step a: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + return ReadableStreamControllerCallPullIfNeeded(cx, controller); +} + +// Streams spec 3.12.15. ReadableByteStreamControllerInvalidateBYOBRequest ( controller ) +static void +ReadableByteStreamControllerInvalidateBYOBRequest(NativeObject* controller) +{ + MOZ_ASSERT(controller->is<ReadableByteStreamController>()); + + // Step 1: If controller.[[byobRequest]] is undefined, return. + Value byobRequestVal = controller->getFixedSlot(ByteControllerSlot_BYOBRequest); + if (byobRequestVal.isUndefined()) + return; + + NativeObject* byobRequest = &byobRequestVal.toObject().as<NativeObject>(); + // Step 2: Set controller.[[byobRequest]].[[associatedReadableByteStreamController]] + // to undefined. + byobRequest->setFixedSlot(BYOBRequestSlot_Controller, UndefinedValue()); + + // Step 3: Set controller.[[byobRequest]].[[view]] to undefined. + byobRequest->setFixedSlot(BYOBRequestSlot_View, UndefinedValue()); + + // Step 4: Set controller.[[byobRequest]] to undefined. + controller->setFixedSlot(ByteControllerSlot_BYOBRequest, UndefinedValue()); +} + +static MOZ_MUST_USE PullIntoDescriptor* +ReadableByteStreamControllerShiftPendingPullInto(JSContext* cx, HandleNativeObject controller); + +// Streams spec 3.12.16. ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue ( controller ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(JSContext* cx, + Handle<ReadableByteStreamController*> controller) +{ + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 1: Assert: controller.[[closeRequested]] is false. + MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested)); + + // Step 2: Repeat the following steps while controller.[[pendingPullIntos]] + // is not empty, + RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos)); + RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>()); + Rooted<PullIntoDescriptor*> pullIntoDescriptor(cx); + while (pendingPullIntos->getDenseInitializedLength() != 0) { + // Step a: If controller.[[queueTotalSize]] is 0, return. + double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber(); + if (queueTotalSize == 0) + return true; + + // Step b: Let pullIntoDescriptor be the first element of + // controller.[[pendingPullIntos]]. + pullIntoDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos); + + // Step c: If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) + // is true, + bool ready; + if (!ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(cx, controller, + pullIntoDescriptor, + &ready)) + { + return false; + } + if (ready) { + // Step i: Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). + if (!ReadableByteStreamControllerShiftPendingPullInto(cx, controller)) + return false; + + // Step ii: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[controlledReadableStream]], + // pullIntoDescriptor). + if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream, + pullIntoDescriptor)) + { + return false; + } + } + } + + return true; +} + +// Streams spec, 3.12.17. ReadableByteStreamControllerPullInto ( controller, view ) +static MOZ_MUST_USE JSObject* +ReadableByteStreamControllerPullInto(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + Handle<ArrayBufferViewObject*> view) +{ + MOZ_ASSERT(controller->is<ReadableByteStreamController>()); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 2: Let elementSize be 1. + uint32_t elementSize = 1; + + RootedObject ctor(cx); + // Step 4: If view has a [[TypedArrayName]] internal slot (i.e., it is not a + // DataView), + if (view->is<TypedArrayObject>()) { + JSProtoKey protoKey = StandardProtoKeyOrNull(view); + MOZ_ASSERT(protoKey); + + if (!GetBuiltinConstructor(cx, protoKey, &ctor)) + return nullptr; + elementSize = 1 << TypedArrayShift(view->as<TypedArrayObject>().type()); + } else { + // Step 3: Let ctor be %DataView% (reordered). + if (!GetBuiltinConstructor(cx, JSProto_DataView, &ctor)) + return nullptr; + } + + // Step 5: Let pullIntoDescriptor be Record {[[buffer]]: view.[[ViewedArrayBuffer]], + // [[byteOffset]]: view.[[ByteOffset]], + // [[byteLength]]: view.[[ByteLength]], + // [[bytesFilled]]: 0, + // [[elementSize]]: elementSize, + // [[ctor]]: ctor, + // [[readerType]]: "byob"}. + bool dummy; + RootedArrayBufferObject buffer(cx, &JS_GetArrayBufferViewBuffer(cx, view, &dummy) + ->as<ArrayBufferObject>()); + if (!buffer) + return nullptr; + + uint32_t byteOffset = JS_GetArrayBufferViewByteOffset(view); + uint32_t byteLength = JS_GetArrayBufferViewByteLength(view); + Rooted<PullIntoDescriptor*> pullIntoDescriptor(cx); + pullIntoDescriptor = PullIntoDescriptor::create(cx, buffer, byteOffset, byteLength, 0, + elementSize, ctor, + ReaderType_BYOB); + if (!pullIntoDescriptor) + return nullptr; + + // Step 6: If controller.[[pendingPullIntos]] is not empty, + RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos)); + RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>()); + if (pendingPullIntos->getDenseInitializedLength() != 0) { + // Step a: Set pullIntoDescriptor.[[buffer]] to + // ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]). + RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer)); + if (!transferredBuffer) + return nullptr; + pullIntoDescriptor->setBuffer(transferredBuffer); + + // Step b: Append pullIntoDescriptor as the last element of + // controller.[[pendingPullIntos]]. + val = ObjectValue(*pullIntoDescriptor); + if (!AppendToList(cx, pendingPullIntos, val)) + return nullptr; + + // Step c: Return ! ReadableStreamAddReadIntoRequest(stream). + return ReadableStreamAddReadIntoRequest(cx, stream); + } + + // Step 7: If stream.[[state]] is "closed", + if (stream->closed()) { + // Step a: Let emptyView be ! Construct(ctor, pullIntoDescriptor.[[buffer]], + // pullIntoDescriptor.[[byteOffset]], 0). + FixedConstructArgs<3> args(cx); + args[0].setObject(*buffer); + args[1].setInt32(byteOffset); + args[2].setInt32(0); + RootedObject emptyView(cx, JS_New(cx, ctor, args)); + if (!emptyView) + return nullptr; + + // Step b: Return a promise resolved with + // ! CreateIterResultObject(emptyView, true). + RootedValue val(cx, ObjectValue(*emptyView)); + RootedObject iterResult(cx, CreateIterResultObject(cx, val, true)); + if (!iterResult) + return nullptr; + val = ObjectValue(*iterResult); + return PromiseObject::unforgeableResolve(cx, val); + } + + // Step 8: If controller.[[queueTotalSize]] > 0, + double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber(); + if (queueTotalSize > 0) { + // Step a: If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, + // pullIntoDescriptor) + // is true, + bool ready; + if (!ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(cx, controller, + pullIntoDescriptor, &ready)) + { + return nullptr; + } + + if (ready) { + // Step i: Let filledView be + // ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor). + RootedObject filledView(cx); + filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(cx, + pullIntoDescriptor); + if (!filledView) + return nullptr; + + // Step ii: Perform ! ReadableByteStreamControllerHandleQueueDrain(controller). + if (!ReadableByteStreamControllerHandleQueueDrain(cx, controller)) + return nullptr; + + // Step iii: Return a promise resolved with + // ! CreateIterResultObject(filledView, false). + val = ObjectValue(*filledView); + RootedObject iterResult(cx, CreateIterResultObject(cx, val, false)); + if (!iterResult) + return nullptr; + val = ObjectValue(*iterResult); + return PromiseObject::unforgeableResolve(cx, val); + } + + // Step b: If controller.[[closeRequested]] is true, + if (ControllerFlags(controller) & ControllerFlag_CloseRequested) { + // Step i: Let e be a TypeError exception. + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_CLOSED, "read"); + + // Not much we can do about uncatchable exceptions, just bail. + RootedValue e(cx); + if (!GetAndClearException(cx, &e)) + return nullptr; + + // Step ii: Perform ! ReadableByteStreamControllerError(controller, e). + if (!ReadableStreamControllerError(cx, controller, e)) + return nullptr; + + // Step iii: Return a promise rejected with e. + return PromiseObject::unforgeableReject(cx, e); + } + } + + // Step 9: Set pullIntoDescriptor.[[buffer]] to + // ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]). + RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer)); + if (!transferredBuffer) + return nullptr; + pullIntoDescriptor->setBuffer(transferredBuffer); + + // Step 10: Append pullIntoDescriptor as the last element of + // controller.[[pendingPullIntos]]. + val = ObjectValue(*pullIntoDescriptor); + if (!AppendToList(cx, pendingPullIntos, val)) + return nullptr; + + // Step 11: Let promise be ! ReadableStreamAddReadIntoRequest(stream). + Rooted<PromiseObject*> promise(cx, ReadableStreamAddReadIntoRequest(cx, stream)); + if (!promise) + return nullptr; + + // Step 12: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) + return nullptr; + + // Step 13: Return promise. + return promise; +} + +static MOZ_MUST_USE bool +ReadableByteStreamControllerRespondInternal(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + double bytesWritten); + +// Streams spec 3.12.18. ReadableByteStreamControllerRespond( controller, bytesWritten ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerRespond(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + HandleValue bytesWrittenVal) +{ + MOZ_ASSERT(controller->is<ReadableByteStreamController>()); + + // Step 1: Let bytesWritten be ? ToNumber(bytesWritten). + double bytesWritten; + if (!ToNumber(cx, bytesWrittenVal, &bytesWritten)) + return false; + + // Step 2: If ! IsFiniteNonNegativeNumber(bytesWritten) is false, + if (bytesWritten < 0 || mozilla::IsNaN(bytesWritten) || mozilla::IsInfinite(bytesWritten)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "bytesWritten"); + return false; + } + + // Step 3: Assert: controller.[[pendingPullIntos]] is not empty. +#if DEBUG + Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos); + RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>()); + MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() != 0); +#endif // DEBUG + + // Step 4: Perform ? ReadableByteStreamControllerRespondInternal(controller, bytesWritten). + return ReadableByteStreamControllerRespondInternal(cx, controller, bytesWritten); +} + +// Streams spec 3.12.19. ReadableByteStreamControllerRespondInClosedState( controller, firstDescriptor ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerRespondInClosedState(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + Handle<PullIntoDescriptor*> firstDescriptor) +{ + // Step 1: Set firstDescriptor.[[buffer]] to + // ! TransferArrayBuffer(firstDescriptor.[[buffer]]). + RootedArrayBufferObject buffer(cx, firstDescriptor->buffer()); + RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer)); + if (!transferredBuffer) + return false; + firstDescriptor->setBuffer(transferredBuffer); + + // Step 2: Assert: firstDescriptor.[[bytesFilled]] is 0. + MOZ_ASSERT(firstDescriptor->bytesFilled() == 0); + + // Step 3: Let stream be controller.[[controlledReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 4: If ReadableStreamHasBYOBReader(stream) is true, + if (ReadableStreamHasBYOBReader(stream)) { + // Step a: Repeat the following steps while + // ! ReadableStreamGetNumReadIntoRequests(stream) > 0, + Rooted<PullIntoDescriptor*> descriptor(cx); + while (ReadableStreamGetNumReadRequests(stream) > 0) { + // Step i: Let pullIntoDescriptor be + // ! ReadableByteStreamControllerShiftPendingPullInto(controller). + descriptor = ReadableByteStreamControllerShiftPendingPullInto(cx, controller); + if (!descriptor) + return false; + + // Step ii: Perform ! + // ReadableByteStreamControllerCommitPullIntoDescriptor(stream, + // pullIntoDescriptor). + if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream, descriptor)) + return false; + } + } + + return true; +} + +// Streams spec 3.12.20. +// ReadableByteStreamControllerRespondInReadableState( controller, bytesWritten, pullIntoDescriptor ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerRespondInReadableState(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + uint32_t bytesWritten, + Handle<PullIntoDescriptor*> pullIntoDescriptor) +{ + // Step 1: If pullIntoDescriptor.[[bytesFilled]] + bytesWritten > pullIntoDescriptor.[[byteLength]], + // throw a RangeError exception. + uint32_t bytesFilled = pullIntoDescriptor->bytesFilled(); + uint32_t byteLength = pullIntoDescriptor->byteLength(); + if (bytesFilled + bytesWritten > byteLength) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN); + return false; + } + + // Step 2: Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, + // bytesWritten, + // pullIntoDescriptor). + ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesWritten, + pullIntoDescriptor); + bytesFilled += bytesWritten; + + // Step 3: If pullIntoDescriptor.[[bytesFilled]] < + // pullIntoDescriptor.[[elementSize]], return. + uint32_t elementSize = pullIntoDescriptor->elementSize(); + if (bytesFilled < elementSize) + return true; + + // Step 4: Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). + if (!ReadableByteStreamControllerShiftPendingPullInto(cx, controller)) + return false; + + // Step 5: Let remainderSize be pullIntoDescriptor.[[bytesFilled]] mod + // pullIntoDescriptor.[[elementSize]]. + uint32_t remainderSize = bytesFilled % elementSize; + + // Step 6: If remainderSize > 0, + RootedArrayBufferObject buffer(cx, pullIntoDescriptor->buffer()); + if (remainderSize > 0) { + // Step a: Let end be pullIntoDescriptor.[[byteOffset]] + + // pullIntoDescriptor.[[bytesFilled]]. + uint32_t end = pullIntoDescriptor->byteOffset() + bytesFilled; + + // Step b: Let remainder be ? CloneArrayBuffer(pullIntoDescriptor.[[buffer]], + // end − remainderSize, + // remainderSize, %ArrayBuffer%). + // TODO: this really, really should just use a slot to store the remainder. + RootedObject remainderObj(cx, JS_NewArrayBuffer(cx, remainderSize)); + if (!remainderObj) + return false; + RootedArrayBufferObject remainder(cx, &remainderObj->as<ArrayBufferObject>()); + ArrayBufferObject::copyData(remainder, 0, buffer, end - remainderSize, remainderSize); + + // Step c: Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, + // remainder, 0, + // remainder.[[ByteLength]]). + // Note: `remainderSize` is equivalent to remainder.[[ByteLength]]. + if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, remainder, 0, + remainderSize)) + { + return false; + } + } + + // Step 7: Set pullIntoDescriptor.[[buffer]] to + // ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]). + RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer)); + if (!transferredBuffer) + return false; + pullIntoDescriptor->setBuffer(transferredBuffer); + + // Step 8: Set pullIntoDescriptor.[[bytesFilled]] to pullIntoDescriptor.[[bytesFilled]] − + // remainderSize. + pullIntoDescriptor->setBytesFilled(bytesFilled - remainderSize); + + // Step 9: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[controlledReadableStream]], + // pullIntoDescriptor). + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream, pullIntoDescriptor)) + return false; + + // Step 10: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). + return ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller); +} + +// Streams spec, 3.12.21. ReadableByteStreamControllerRespondInternal ( controller, bytesWritten ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerRespondInternal(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + double bytesWritten) +{ + // Step 1: Let firstDescriptor be the first element of controller.[[pendingPullIntos]]. + RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos)); + RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>()); + Rooted<PullIntoDescriptor*> firstDescriptor(cx); + firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos); + + // Step 2: Let stream be controller.[[controlledReadableStream]]. + Rooted<ReadableStream*> stream(cx, StreamFromController(controller)); + + // Step 3: If stream.[[state]] is "closed", + if (stream->closed()) { + // Step a: If bytesWritten is not 0, throw a TypeError exception. + if (bytesWritten != 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED); + return false; + } + + // Step b: Perform + // ! ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor). + return ReadableByteStreamControllerRespondInClosedState(cx, controller, firstDescriptor); + } + + // Step 4: Otherwise, + // Step a: Assert: stream.[[state]] is "readable". + MOZ_ASSERT(stream->readable()); + + // Step b: Perform ? ReadableByteStreamControllerRespondInReadableState(controller, + // bytesWritten, + // firstDescriptor). + return ReadableByteStreamControllerRespondInReadableState(cx, controller, bytesWritten, + firstDescriptor); +} + +// Streams spec, 3.12.22. ReadableByteStreamControllerRespondWithNewView ( controller, view ) +static MOZ_MUST_USE bool +ReadableByteStreamControllerRespondWithNewView(JSContext* cx, + Handle<ReadableByteStreamController*> controller, + HandleObject view) +{ + // Step 1: Assert: controller.[[pendingPullIntos]] is not empty. + RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos)); + RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>()); + MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() != 0); + + // Step 2: Let firstDescriptor be the first element of controller.[[pendingPullIntos]]. + Rooted<PullIntoDescriptor*> firstDescriptor(cx); + firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos); + + // Step 3: If firstDescriptor.[[byteOffset]] + firstDescriptor.[[bytesFilled]] + // is not view.[[ByteOffset]], throw a RangeError exception. + uint32_t byteOffset = uint32_t(JS_GetArrayBufferViewByteOffset(view)); + if (firstDescriptor->byteOffset() + firstDescriptor->bytesFilled() != byteOffset) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET); + return false; + } + + // Step 4: If firstDescriptor.[[byteLength]] is not view.[[ByteLength]], + // throw a RangeError exception. + uint32_t byteLength = JS_GetArrayBufferViewByteLength(view); + if (firstDescriptor->byteLength() != byteLength) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE); + return false; + } + + // Step 5: Set firstDescriptor.[[buffer]] to view.[[ViewedArrayBuffer]]. + bool dummy; + RootedArrayBufferObject buffer(cx, + &AsArrayBuffer(JS_GetArrayBufferViewBuffer(cx, view, &dummy))); + if (!buffer) + return false; + firstDescriptor->setBuffer(buffer); + + // Step 6: Perform ? ReadableByteStreamControllerRespondInternal(controller, + // view.[[ByteLength]]). + return ReadableByteStreamControllerRespondInternal(cx, controller, byteLength); +} + +// Streams spec, 3.12.23. ReadableByteStreamControllerShiftPendingPullInto ( controller ) +static MOZ_MUST_USE PullIntoDescriptor* +ReadableByteStreamControllerShiftPendingPullInto(JSContext* cx, HandleNativeObject controller) +{ + MOZ_ASSERT(controller->is<ReadableByteStreamController>()); + + // Step 1: Let descriptor be the first element of controller.[[pendingPullIntos]]. + // Step 2: Remove descriptor from controller.[[pendingPullIntos]], shifting + // all other elements downward (so that the second becomes the first, + // and so on). + RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos)); + RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>()); + Rooted<PullIntoDescriptor*> descriptor(cx); + descriptor = ShiftFromList<PullIntoDescriptor>(cx, pendingPullIntos); + MOZ_ASSERT(descriptor); + + // Step 3: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). + ReadableByteStreamControllerInvalidateBYOBRequest(controller); + + // Step 4: Return descriptor. + return descriptor; +} + +// Streams spec, 3.12.24. ReadableByteStreamControllerShouldCallPull ( controller ) +// Unified with 3.9.3 above. + +// Streams spec, 6.1.2. new ByteLengthQueuingStrategy({ highWaterMark }) +bool +js::ByteLengthQueuingStrategy::constructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject strategy(cx, NewBuiltinClassInstance<ByteLengthQueuingStrategy>(cx)); + 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; + + if (!SetProperty(cx, strategy, cx->names().highWaterMark, highWaterMark)) + return false; + + args.rval().setObject(*strategy); + return true; +} + +// Streams spec 6.1.3.1. size ( chunk ) +bool +ByteLengthQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: Return ? GetV(chunk, "byteLength"). + 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); + +// Streams spec, 6.2.2. new CountQueuingStrategy({ highWaterMark }) +bool +js::CountQueuingStrategy::constructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + Rooted<CountQueuingStrategy*> strategy(cx, NewBuiltinClassInstance<CountQueuingStrategy>(cx)); + 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; + + if (!SetProperty(cx, strategy, cx->names().highWaterMark, highWaterMark)) + return false; + + args.rval().setObject(*strategy); + return true; +} + +// Streams spec 6.2.3.1. size ( chunk ) +bool +CountQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1: Return 1. + 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 + +// Streams spec, 6.3.1. DequeueValue ( container ) nothrow +inline static MOZ_MUST_USE bool +DequeueValue(JSContext* cx, HandleNativeObject container, MutableHandleValue chunk) +{ + // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal + // slots. + MOZ_ASSERT(IsReadableStreamController(container)); + + // Step 2: Assert: queue is not empty. + RootedValue val(cx, container->getFixedSlot(QueueContainerSlot_Queue)); + RootedNativeObject queue(cx, &val.toObject().as<NativeObject>()); + MOZ_ASSERT(queue->getDenseInitializedLength() > 0); + + // Step 3. Let pair be the first element of queue. + // Step 4. Remove pair from queue, shifting all other elements downward + // (so that the second becomes the first, and so on). + Rooted<QueueEntry*> pair(cx, ShiftFromList<QueueEntry>(cx, queue)); + MOZ_ASSERT(pair); + + // Step 5: Set container.[[queueTotalSize]] to + // container.[[queueTotalSize]] − pair.[[size]]. + // Step 6: If container.[[queueTotalSize]] < 0, set + // container.[[queueTotalSize]] to 0. + // (This can occur due to rounding errors.) + double totalSize = container->getFixedSlot(QueueContainerSlot_TotalSize).toNumber(); + + totalSize -= pair->size(); + if (totalSize < 0) + totalSize = 0; + container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize)); + + // Step 7: Return pair.[[value]]. + chunk.set(pair->value()); + return true; +} + +// Streams spec, 6.3.2. EnqueueValueWithSize ( container, value, size ) throws +static MOZ_MUST_USE bool +EnqueueValueWithSize(JSContext* cx, HandleNativeObject container, HandleValue value, + HandleValue sizeVal) +{ + // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal + // slots. + MOZ_ASSERT(IsReadableStreamController(container)); + + // Step 2: Let size be ? ToNumber(size). + double size; + if (!ToNumber(cx, sizeVal, &size)) + return false; + + // Step 3: If ! IsFiniteNonNegativeNumber(size) is false, throw a RangeError + // exception. + if (size < 0 || mozilla::IsNaN(size) || mozilla::IsInfinite(size)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "size"); + return false; + } + + // Step 4: Append Record {[[value]]: value, [[size]]: size} as the last element + // of container.[[queue]]. + RootedValue val(cx, container->getFixedSlot(QueueContainerSlot_Queue)); + RootedNativeObject queue(cx, &val.toObject().as<NativeObject>()); + + QueueEntry* entry = QueueEntry::create(cx, value, size); + if (!entry) + return false; + val = ObjectValue(*entry); + if (!AppendToList(cx, queue, val)) + return false; + + // Step 5: Set container.[[queueTotalSize]] to + // container.[[queueTotalSize]] + size. + double totalSize = container->getFixedSlot(QueueContainerSlot_TotalSize).toNumber(); + container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize + size)); + + return true; +} + +// Streams spec, 6.3.3. PeekQueueValue ( container ) nothrow +// Used by WritableStream. +// static MOZ_MUST_USE Value +// PeekQueueValue(NativeObject* container) +// { +// // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal +// // slots. +// MOZ_ASSERT(IsReadableStreamController(container)); + +// // Step 2: Assert: queue is not empty. +// Value val = container->getFixedSlot(QueueContainerSlot_Queue); +// NativeObject* queue = &val.toObject().as<NativeObject>(); +// MOZ_ASSERT(queue->getDenseInitializedLength() > 0); + +// // Step 3: Let pair be the first element of container.[[queue]]. +// QueueEntry* pair = PeekList<QueueEntry>(queue); + +// // Step 4: Return pair.[[value]]. +// return pair->value(); +// } + +/** + * Streams spec, 6.3.4. ResetQueue ( container ) nothrow + */ +inline static MOZ_MUST_USE bool +ResetQueue(JSContext* cx, HandleNativeObject container) +{ + // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal + // slots. + MOZ_ASSERT(IsReadableStreamController(container)); + + // Step 2: Set container.[[queue]] to a new empty List. + if (!SetNewList(cx, container, QueueContainerSlot_Queue)) + return false; + + // Step 3: Set container.[[queueTotalSize]] to 0. + container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(0)); + + return true; +} + + +/** + * Streams spec, 6.4.1. InvokeOrNoop ( O, P, args ) + */ +inline static MOZ_MUST_USE bool +InvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg, + MutableHandleValue rval) +{ + // Step 1: Assert: P is a valid property key (omitted). + // Step 2: If args was not passed, let args be a new empty List (omitted). + // Step 3: Let method be ? GetV(O, P). + RootedValue method(cx); + if (!GetProperty(cx, O, P, &method)) + return false; + + // Step 4: If method is undefined, return. + if (method.isUndefined()) + return true; + + // Step 5: Return ? Call(method, O, args). + return Call(cx, method, O, arg, rval); +} + +/** + * Streams spec, 6.4.3. PromiseInvokeOrNoop ( O, P, args ) + * Specialized to one arg, because that's what all stream related callers use. + */ +static MOZ_MUST_USE JSObject* +PromiseInvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg) +{ + // Step 1: Assert: O is not undefined. + MOZ_ASSERT(!O.isUndefined()); + + // Step 2: Assert: ! IsPropertyKey(P) is true (implicit). + // Step 3: Assert: args is a List (omitted). + + // Step 4: Let returnValue be InvokeOrNoop(O, P, args). + // Step 5: If returnValue is an abrupt completion, return a promise + // rejected with returnValue.[[Value]]. + RootedValue returnValue(cx); + if (!InvokeOrNoop(cx, O, P, arg, &returnValue)) + return PromiseRejectedWithPendingError(cx); + + // Step 6: Otherwise, return a promise resolved with returnValue.[[Value]]. + return PromiseObject::unforgeableResolve(cx, returnValue); +} + +/** + * Streams spec, 6.4.4 TransferArrayBuffer ( O ) + */ +static MOZ_MUST_USE ArrayBufferObject* +TransferArrayBuffer(JSContext* cx, HandleObject buffer) +{ + // Step 1 (implicit). + + // Step 2. + MOZ_ASSERT(buffer->is<ArrayBufferObject>()); + + // Step 3. + MOZ_ASSERT(!JS_IsDetachedArrayBufferObject(buffer)); + + // Step 5 (reordered). + uint32_t size = buffer->as<ArrayBufferObject>().byteLength(); + + // Steps 4, 6. + void* contents = JS_StealArrayBufferContents(cx, buffer); + if (!contents) + return nullptr; + MOZ_ASSERT(JS_IsDetachedArrayBufferObject(buffer)); + + // Step 7. + RootedObject transferredBuffer(cx, JS_NewArrayBufferWithContents(cx, size, contents)); + if (!transferredBuffer) + return nullptr; + return &transferredBuffer->as<ArrayBufferObject>(); +} + +// Streams spec, 6.4.5. ValidateAndNormalizeHighWaterMark ( highWaterMark ) +static MOZ_MUST_USE bool +ValidateAndNormalizeHighWaterMark(JSContext* cx, HandleValue highWaterMarkVal, double* highWaterMark) +{ + // Step 1: Set highWaterMark to ? ToNumber(highWaterMark). + if (!ToNumber(cx, highWaterMarkVal, highWaterMark)) + return false; + + // Step 2: If highWaterMark is NaN, throw a TypeError exception. + // Step 3: If highWaterMark < 0, throw a RangeError exception. + if (mozilla::IsNaN(*highWaterMark) || *highWaterMark < 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_STREAM_INVALID_HIGHWATERMARK); + return false; + } + + // Step 4: Return highWaterMark. + return true; +} + +// Streams spec, 6.4.6. ValidateAndNormalizeQueuingStrategy ( size, highWaterMark ) +static MOZ_MUST_USE bool +ValidateAndNormalizeQueuingStrategy(JSContext* cx, HandleValue size, + HandleValue highWaterMarkVal, double* highWaterMark) +{ + // Step 1: If size is not undefined and ! IsCallable(size) is false, throw a + // TypeError exception. + if (!size.isUndefined() && !IsCallable(size)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, + "ReadableStream argument options.size"); + return false; + } + + // Step 2: Let highWaterMark be ? ValidateAndNormalizeHighWaterMark(highWaterMark). + if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal, highWaterMark)) + return false; + + // Step 3: Return Record {[[size]]: size, [[highWaterMark]]: highWaterMark}. + return true; +} + +MOZ_MUST_USE bool +js::ReadableStreamReaderCancel(JSContext* cx, HandleObject readerObj, HandleValue reason) +{ + MOZ_ASSERT(IsReadableStreamReader(readerObj)); + RootedNativeObject reader(cx, &readerObj->as<NativeObject>()); + MOZ_ASSERT(StreamFromReader(reader)); + return ReadableStreamReaderGenericCancel(cx, reader, reason); +} + +MOZ_MUST_USE bool +js::ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject readerObj) +{ + MOZ_ASSERT(IsReadableStreamReader(readerObj)); + RootedNativeObject reader(cx, &readerObj->as<NativeObject>()); + MOZ_ASSERT(ReadableStreamGetNumReadRequests(StreamFromReader(reader)) == 0); + return ReadableStreamReaderGenericRelease(cx, reader); +} + +MOZ_MUST_USE bool +ReadableStream::enqueue(JSContext* cx, Handle<ReadableStream*> stream, HandleValue chunk) +{ + Rooted<ReadableStreamDefaultController*> controller(cx); + controller = &ControllerFromStream(stream)->as<ReadableStreamDefaultController>(); + + MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested)); + MOZ_ASSERT(stream->readable()); + + return ReadableStreamDefaultControllerEnqueue(cx, controller, chunk); +} + +MOZ_MUST_USE bool +ReadableStream::enqueueBuffer(JSContext* cx, Handle<ReadableStream*> stream, + Handle<ArrayBufferObject*> chunk) +{ + Rooted<ReadableByteStreamController*> controller(cx); + controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>(); + + MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested)); + MOZ_ASSERT(stream->readable()); + + return ReadableByteStreamControllerEnqueue(cx, controller, chunk); +} + +void +ReadableStream::desiredSize(bool* hasSize, double* size) const +{ + if (errored()) { + *hasSize = false; + return; + } + + *hasSize = true; + + if (closed()) { + *size = 0; + return; + } + + NativeObject* controller = ControllerFromStream(this); + *size = ReadableStreamControllerGetDesiredSizeUnchecked(controller); +} + +/*static */ bool +ReadableStream::getExternalSource(JSContext* cx, Handle<ReadableStream*> stream, void** source) +{ + MOZ_ASSERT(stream->mode() == JS::ReadableStreamMode::ExternalSource); + if (stream->locked()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAM_LOCKED); + return false; + } + if (!stream->readable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, + "ReadableStreamGetExternalUnderlyingSource"); + return false; + } + + auto controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>(); + AddControllerFlags(controller, ControllerFlag_SourceLocked); + *source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate(); + return true; +} + +void +ReadableStream::releaseExternalSource() +{ + MOZ_ASSERT(mode() == JS::ReadableStreamMode::ExternalSource); + MOZ_ASSERT(locked()); + auto controller = ControllerFromStream(this); + MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_SourceLocked); + RemoveControllerFlags(controller, ControllerFlag_SourceLocked); +} + +uint8_t +ReadableStream::embeddingFlags() const +{ + uint8_t flags = ControllerFlags(ControllerFromStream(this)) >> ControllerEmbeddingFlagsOffset; + MOZ_ASSERT_IF(flags, mode() == JS::ReadableStreamMode::ExternalSource); + return flags; +} + +// Streams spec, 3.10.4.4. steps 1-3 +// and +// Streams spec, 3.12.8. steps 8-9 +// +// Adapted to handling updates signaled by the embedding for streams with +// external underlying sources. +// +// The remaining steps of those two functions perform checks and asserts that +// don't apply to streams with external underlying sources. +MOZ_MUST_USE bool +ReadableStream::updateDataAvailableFromSource(JSContext* cx, Handle<ReadableStream*> stream, + uint32_t availableData) +{ + Rooted<ReadableByteStreamController*> controller(cx); + controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>(); + + // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception. + if (ControllerFlags(controller) & ControllerFlag_CloseRequested) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue"); + return false; + } + + // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable", + // throw a TypeError exception. + if (!StreamFromController(controller)->readable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "enqueue"); + return false; + } + + RemoveControllerFlags(controller, ControllerFlag_Pulling | ControllerFlag_PullAgain); + +#if DEBUG + uint32_t oldAvailableData = controller->getFixedSlot(QueueContainerSlot_TotalSize).toInt32(); +#endif // DEBUG + controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(availableData)); + + // Step 8.a: If ! ReadableStreamGetNumReadRequests(stream) is 0, + // Reordered because for externally-sourced streams it applies regardless + // of reader type. + if (ReadableStreamGetNumReadRequests(stream) == 0) + return true; + + // Step 8: If ! ReadableStreamHasDefaultReader(stream) is true + if (ReadableStreamHasDefaultReader(stream)) { + // Step b: Otherwise, + // Step i: Assert: controller.[[queue]] is empty. + MOZ_ASSERT(oldAvailableData == 0); + + // Step ii: Let transferredView be + // ! Construct(%Uint8Array%, transferredBuffer, byteOffset, byteLength). + JSObject* viewObj = JS_NewUint8Array(cx, availableData); + Rooted<ArrayBufferViewObject*> transferredView(cx, &viewObj->as<ArrayBufferViewObject>()); + if (!transferredView) + return false; + + Value val = controller->getFixedSlot(ControllerSlot_UnderlyingSource); + void* underlyingSource = val.toPrivate(); + + size_t bytesWritten; + { + JS::AutoCheckCannotGC noGC(cx); + bool dummy; + void* buffer = JS_GetArrayBufferViewData(transferredView, &dummy, noGC); + auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback; + MOZ_ASSERT(cb); + // TODO: use bytesWritten to correctly update the request's state. + cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer, + availableData, &bytesWritten); + } + + // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false). + RootedValue chunk(cx, ObjectValue(*transferredView)); + if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false)) + return false; + + controller->setFixedSlot(QueueContainerSlot_TotalSize, + Int32Value(availableData - bytesWritten)); + } else if (ReadableStreamHasBYOBReader(stream)) { + // Step 9: Otherwise, + // Step a: If ! ReadableStreamHasBYOBReader(stream) is true, + // Step i: Perform + // (Not needed for external underlying sources.) + + // Step ii: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). + if (!ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller)) + return false; + } else { + // Step b: Otherwise, + // Step i: Assert: ! IsReadableStreamLocked(stream) is false. + MOZ_ASSERT(!stream->locked()); + + // Step ii: Perform + // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, + // transferredBuffer, + // byteOffset, + // byteLength). + // (Not needed for external underlying sources.) + } + + return true; +} + +MOZ_MUST_USE bool +ReadableStream::close(JSContext* cx, Handle<ReadableStream*> stream) +{ + RootedNativeObject controllerObj(cx, ControllerFromStream(stream)); + if (!VerifyControllerStateForClosing(cx, controllerObj)) + return false; + + if (controllerObj->is<ReadableStreamDefaultController>()) { + Rooted<ReadableStreamDefaultController*> controller(cx); + controller = &controllerObj->as<ReadableStreamDefaultController>(); + return ReadableStreamDefaultControllerClose(cx, controller); + } + + Rooted<ReadableByteStreamController*> controller(cx); + controller = &controllerObj->as<ReadableByteStreamController>(); + return ReadableByteStreamControllerClose(cx, controller); +} + +MOZ_MUST_USE bool +ReadableStream::error(JSContext* cx, Handle<ReadableStream*> stream, HandleValue reason) +{ + // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception. + if (!stream->readable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error"); + return false; + } + + // Step 4: Perform ! ReadableStreamDefaultControllerError(this, e). + RootedNativeObject controller(cx, ControllerFromStream(stream)); + return ReadableStreamControllerError(cx, controller, reason); +} + +MOZ_MUST_USE bool +ReadableStream::tee(JSContext* cx, Handle<ReadableStream*> stream, bool cloneForBranch2, + MutableHandle<ReadableStream*> branch1Stream, + MutableHandle<ReadableStream*> branch2Stream) +{ + return ReadableStreamTee(cx, stream, false, branch1Stream, branch2Stream); +} + +MOZ_MUST_USE NativeObject* +ReadableStream::getReader(JSContext* cx, Handle<ReadableStream*> stream, + JS::ReadableStreamReaderMode mode) +{ + if (mode == JS::ReadableStreamReaderMode::Default) + return CreateReadableStreamDefaultReader(cx, stream); + return CreateReadableStreamBYOBReader(cx, stream); +} + +JS_FRIEND_API(JSObject*) +js::UnwrapReadableStream(JSObject* obj) +{ + if (JSObject* unwrapped = CheckedUnwrap(obj)) + return unwrapped->is<ReadableStream>() ? unwrapped : nullptr; + return nullptr; +} diff --git a/js/src/builtin/Stream.h b/js/src/builtin/Stream.h new file mode 100644 index 0000000000..0726e78361 --- /dev/null +++ b/js/src/builtin/Stream.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef builtin_Stream_h +#define builtin_Stream_h + +#include "builtin/Promise.h" +#include "vm/NativeObject.h" + + +namespace js { + +class AutoSetNewObjectMetadata; + +class ReadableStream : public NativeObject +{ + public: + static ReadableStream* createDefaultStream(JSContext* cx, HandleValue underlyingSource, + HandleValue size, HandleValue highWaterMark, + HandleObject proto = nullptr); + static ReadableStream* createByteStream(JSContext* cx, HandleValue underlyingSource, + HandleValue highWaterMark, + HandleObject proto = nullptr); + static ReadableStream* createExternalSourceStream(JSContext* cx, void* underlyingSource, + uint8_t flags, HandleObject proto = nullptr); + + bool readable() const; + bool closed() const; + bool errored() const; + bool disturbed() const; + + bool locked() const; + + void desiredSize(bool* hasSize, double* size) const; + + JS::ReadableStreamMode mode() const; + + static MOZ_MUST_USE bool close(JSContext* cx, Handle<ReadableStream*> stream); + static MOZ_MUST_USE JSObject* cancel(JSContext* cx, Handle<ReadableStream*> stream, + HandleValue reason); + static MOZ_MUST_USE bool error(JSContext* cx, Handle<ReadableStream*> stream, + HandleValue error); + + static MOZ_MUST_USE NativeObject* getReader(JSContext* cx, Handle<ReadableStream*> stream, + JS::ReadableStreamReaderMode mode); + + static MOZ_MUST_USE bool tee(JSContext* cx, + Handle<ReadableStream*> stream, bool cloneForBranch2, + MutableHandle<ReadableStream*> branch1Stream, + MutableHandle<ReadableStream*> branch2Stream); + + static MOZ_MUST_USE bool enqueue(JSContext* cx, Handle<ReadableStream*> stream, + HandleValue chunk); + static MOZ_MUST_USE bool enqueueBuffer(JSContext* cx, Handle<ReadableStream*> stream, + Handle<ArrayBufferObject*> chunk); + static MOZ_MUST_USE bool getExternalSource(JSContext* cx, Handle<ReadableStream*> stream, + void** source); + void releaseExternalSource(); + uint8_t embeddingFlags() const; + static MOZ_MUST_USE bool updateDataAvailableFromSource(JSContext* cx, + Handle<ReadableStream*> stream, + uint32_t availableData); + + enum State { + Readable = 1 << 0, + Closed = 1 << 1, + Errored = 1 << 2, + Disturbed = 1 << 3 + }; + + private: + static MOZ_MUST_USE ReadableStream* createStream(JSContext* cx, HandleObject proto = nullptr); + + public: + static bool constructor(JSContext* cx, unsigned argc, Value* vp); + static const ClassSpec classSpec_; + static const Class class_; + static const ClassSpec protoClassSpec_; + static const Class protoClass_; +}; + +class ReadableStreamDefaultReader : public NativeObject +{ + public: + static MOZ_MUST_USE JSObject* read(JSContext* cx, Handle<ReadableStreamDefaultReader*> reader); + + static bool constructor(JSContext* cx, unsigned argc, Value* vp); + static const ClassSpec classSpec_; + static const Class class_; + static const ClassSpec protoClassSpec_; + static const Class protoClass_; +}; + +class ReadableStreamBYOBReader : public NativeObject +{ + public: + static MOZ_MUST_USE JSObject* read(JSContext* cx, Handle<ReadableStreamBYOBReader*> reader, + Handle<ArrayBufferViewObject*> view); + + static bool constructor(JSContext* cx, unsigned argc, Value* vp); + static const ClassSpec classSpec_; + static const Class class_; + static const ClassSpec protoClassSpec_; + static const Class protoClass_; +}; + +bool ReadableStreamReaderIsClosed(const JSObject* reader); + +MOZ_MUST_USE bool ReadableStreamReaderCancel(JSContext* cx, HandleObject reader, + HandleValue reason); + +MOZ_MUST_USE bool ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader); + +class ReadableStreamDefaultController : public NativeObject +{ + public: + static bool constructor(JSContext* cx, unsigned argc, Value* vp); + static const ClassSpec classSpec_; + static const Class class_; + static const ClassSpec protoClassSpec_; + static const Class protoClass_; +}; + +class ReadableByteStreamController : public NativeObject +{ + public: + bool hasExternalSource(); + + static bool constructor(JSContext* cx, unsigned argc, Value* vp); + static const ClassSpec classSpec_; + static const Class class_; + static const ClassSpec protoClassSpec_; + static const Class protoClass_; +}; + +class ReadableStreamBYOBRequest : public NativeObject +{ + public: + static bool constructor(JSContext* cx, unsigned argc, Value* vp); + static const ClassSpec classSpec_; + static const Class class_; + static const ClassSpec protoClassSpec_; + static const Class protoClass_; +}; + +class ByteLengthQueuingStrategy : public NativeObject +{ + public: + static bool constructor(JSContext* cx, unsigned argc, Value* vp); + static const ClassSpec classSpec_; + static const Class class_; + static const ClassSpec protoClassSpec_; + static const Class protoClass_; +}; + +class CountQueuingStrategy : public NativeObject +{ + public: + static bool constructor(JSContext* cx, unsigned argc, Value* vp); + static const ClassSpec classSpec_; + static const Class class_; + static const ClassSpec protoClassSpec_; + static const Class protoClass_; +}; + +} // namespace js + +#endif /* builtin_Stream_h */ diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 2608733853..f7af8b6fe9 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1441,6 +1441,14 @@ RejectPromise(JSContext* cx, unsigned argc, Value* vp) return result; } +static bool +StreamsAreEnabled(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setBoolean(cx->options().streams()); + return true; +} + static unsigned finalizeCount = 0; static void @@ -4144,6 +4152,10 @@ JS_FN_HELP("rejectPromise", RejectPromise, 2, 0, "rejectPromise(promise, reason)", " Reject a Promise by calling the JSAPI function JS::RejectPromise."), +JS_FN_HELP("streamsAreEnabled", StreamsAreEnabled, 0, 0, +"streamsAreEnabled()", +" Returns a boolean indicating whether WHATWG Streams are enabled for the current compartment."), + JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0, "makeFinalizeObserver()", " Get a special object whose finalization increases the counter returned\n" diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 57f6d738ca..a8510faa71 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -2,8 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "TypedObjectConstants.h" - function ViewedArrayBufferIfReified(tarray) { assert(IsTypedArray(tarray), "non-typed array asked for its buffer"); @@ -1781,7 +1779,7 @@ function ArrayBufferSlice(start, end) { ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED); // Steps 19-21. - ArrayBufferCopyData(new_, O, first | 0, newLen | 0, isWrapped); + ArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped); // Step 22. return new_; @@ -1863,7 +1861,7 @@ function SharedArrayBufferSlice(start, end) { ThrowTypeError(JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, newLen, actualLen); // Steps 16-18. - SharedArrayBufferCopyData(new_, O, first | 0, newLen | 0, isWrapped); + SharedArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped); // Step 19. return new_; diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 51c5a574fd..1cc15ed68e 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -23,6 +23,7 @@ */ #include "SelfHostingDefines.h" +#include "TypedObjectConstants.h" // Assertions and debug printing, defined here instead of in the header above // to make `assert` invisible to C++. diff --git a/js/src/js.msg b/js/src/js.msg index e651b10137..b8aa77902d 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -613,11 +613,39 @@ MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of i MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have a 'throw' method") // Async Iteration -MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_TYPEERR, "'for await' loop should be used with 'of'") +MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_SYNTAXERR, "'for await' loop should be used with 'of'") MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator") MSG_DEF(JSMSG_NOT_AN_ASYNC_ITERATOR, 0, JSEXN_TYPEERR, "Not an async from sync iterator") MSG_DEF(JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.asyncIterator]() returned a non-object value") +// ReadableStream +MSG_DEF(JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG,0, JSEXN_RANGEERR,"'underlyingSource.type' must be \"bytes\" or undefined.") +MSG_DEF(JSMSG_READABLESTREAM_INVALID_READER_MODE, 0, JSEXN_RANGEERR,"'mode' must be \"byob\" or undefined.") +MSG_DEF(JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, 1, JSEXN_RANGEERR, "'{0}' must be a finite, non-negative number.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN, 0, JSEXN_RANGEERR, "'bytesWritten' exceeds remaining length.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE, 0, JSEXN_RANGEERR, "view size does not match requested data.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET, 0, JSEXN_RANGEERR, "view offset does not match requested position.") +MSG_DEF(JSMSG_READABLESTREAM_NOT_LOCKED, 1, JSEXN_TYPEERR, "'{0}' may only be called on a locked stream.") +MSG_DEF(JSMSG_READABLESTREAM_LOCKED, 0, JSEXN_TYPEERR, "A Reader may only be created for an unlocked ReadableStream.") +MSG_DEF(JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableByteStreamController.") +MSG_DEF(JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableStreamDefaultController.") +MSG_DEF(JSMSG_READABLESTREAM_CONTROLLER_SET, 0, JSEXN_TYPEERR, "The ReadableStream already has a controller defined.") +MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_OWNED, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may only be called on a reader owned by a stream.") +MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_EMPTY, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may not be called on a reader with read requests.") +MSG_DEF(JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW, 0, JSEXN_TYPEERR, "ReadableStreamBYOBReader.read() was passed an empty TypedArrayBuffer view.") +MSG_DEF(JSMSG_READABLESTREAMREADER_RELEASED, 0, JSEXN_TYPEERR, "The ReadableStream reader was released.") +MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_CLOSED, 1, JSEXN_TYPEERR, "'{0}' called on a stream already closing.") +MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, 1, JSEXN_TYPEERR, "'{0}' may only be called on a stream in the 'readable' state.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE,0, JSEXN_RANGEERR, "ReadableByteStreamController requires a positive integer or undefined for 'autoAllocateChunkSize'.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK, 1, JSEXN_TYPEERR, "{0} passed a bad chunk.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL, 0, JSEXN_TYPEERR, "The ReadableByteStreamController cannot be closed while the buffer is being filled.") +MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, 1, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method '{0}' called on a request with no controller.") +MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED, 0, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method 'respond' called with non-zero number of bytes with a closed controller.") +MSG_DEF(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, 1, JSEXN_TYPEERR, "ReadableStream method {0} not yet implemented") + +// Other Stream-related +MSG_DEF(JSMSG_STREAM_INVALID_HIGHWATERMARK, 0, JSEXN_RANGEERR, "'highWaterMark' must be a non-negative, non-NaN number.") + // BigInt MSG_DEF(JSMSG_BIGINT_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert BigInt to number") MSG_DEF(JSMSG_NUMBER_TO_BIGINT, 0, JSEXN_RANGEERR, "can't convert non-finite number to BigInt") diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index 35bc69b85b..1e5f43aa5a 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -126,6 +126,11 @@ if CONFIG['ENABLE_ION']: 'testJitRValueAlloc.cpp', ] +if CONFIG['ENABLE_STREAMS']: + UNIFIED_SOURCES += [ + 'testReadableStream.cpp', + ] + DEFINES['EXPORT_JS_API'] = True LOCAL_INCLUDES += [ diff --git a/js/src/jsapi-tests/testIntTypesABI.cpp b/js/src/jsapi-tests/testIntTypesABI.cpp index 066e7ad1b8..420a07c7f8 100644 --- a/js/src/jsapi-tests/testIntTypesABI.cpp +++ b/js/src/jsapi-tests/testIntTypesABI.cpp @@ -29,6 +29,7 @@ #include "js/RequiredDefines.h" #include "js/RootingAPI.h" #include "js/SliceBudget.h" +#include "js/Stream.h" #include "js/StructuredClone.h" #include "js/TracingAPI.h" #include "js/TrackedOptimizationInfo.h" diff --git a/js/src/jsapi-tests/testReadableStream.cpp b/js/src/jsapi-tests/testReadableStream.cpp new file mode 100644 index 0000000000..f1d373d5d2 --- /dev/null +++ b/js/src/jsapi-tests/testReadableStream.cpp @@ -0,0 +1,676 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jsapi.h" + +#include "jsapi-tests/tests.h" + +using namespace JS; + +char test_buffer_data[] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static JSObject* +NewDefaultStream(JSContext* cx, HandleObject source = nullptr, HandleFunction size = nullptr, + double highWaterMark = 1, HandleObject proto = nullptr) +{ + RootedObject stream(cx, NewReadableDefaultStreamObject(cx, source, size, highWaterMark, + proto)); + MOZ_ASSERT_IF(stream, IsReadableStream(stream)); + return stream; +} + +static JSObject* +NewByteStream(JSContext* cx, double highWaterMark = 0, HandleObject proto = nullptr) +{ + RootedObject source(cx, JS_NewPlainObject(cx)); + MOZ_ASSERT(source); + + RootedObject stream(cx, NewReadableByteStreamObject(cx, source, highWaterMark, proto)); + MOZ_ASSERT_IF(stream, IsReadableStream(stream)); + return stream; +} + +static bool dataRequestCBCalled = false; +static void +DataRequestCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags, + size_t desiredSize) +{ + MOZ_ASSERT(!dataRequestCBCalled, "Invalid test setup"); + dataRequestCBCalled = true; +} + +static bool writeIntoRequestBufferCBCalled = false; +static void +WriteIntoRequestBufferCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags, + void* buffer, size_t length, size_t* bytesWritten) +{ + MOZ_ASSERT(!writeIntoRequestBufferCBCalled, "Invalid test setup"); + MOZ_ASSERT(length <= sizeof(test_buffer_data)); + memcpy(buffer, test_buffer_data, length); + writeIntoRequestBufferCBCalled = true; + *bytesWritten = length; +} + +static bool cancelStreamCBCalled = false; +static Value cancelStreamReason; +static Value +CancelStreamCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags, + HandleValue reason) +{ + MOZ_ASSERT(!cancelStreamCBCalled, "Invalid test setup"); + cancelStreamCBCalled = true; + cancelStreamReason = reason; + return reason; +} + +static bool streamClosedCBCalled = false; +static Value streamClosedReason; +static void +StreamClosedCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags) +{ + MOZ_ASSERT(!streamClosedCBCalled, "Invalid test setup"); + streamClosedCBCalled = true; +} + +static bool streamErroredCBCalled = false; +static Value streamErroredReason; +static void +StreamErroredCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags, + HandleValue reason) +{ + MOZ_ASSERT(!streamErroredCBCalled, "Invalid test setup"); + streamErroredCBCalled = true; + streamErroredReason = reason; +} + +static bool finalizeStreamCBCalled = false; +static void* finalizedStreamUnderlyingSource; +static void +FinalizeStreamCB(void* underlyingSource, uint8_t flags) +{ + MOZ_ASSERT(!finalizeStreamCBCalled, "Invalid test setup"); + finalizeStreamCBCalled = true; + finalizedStreamUnderlyingSource = underlyingSource; +} + +static void +ResetCallbacks() +{ + dataRequestCBCalled = false; + writeIntoRequestBufferCBCalled = false; + cancelStreamReason = UndefinedValue(); + cancelStreamCBCalled = false; + streamClosedCBCalled = false; + streamErroredCBCalled = false; + finalizeStreamCBCalled = false; +} + +static bool +GetIterResult(JSContext* cx, HandleObject promise, MutableHandleValue value, bool* done) +{ + RootedObject iterResult(cx, &GetPromiseResult(promise).toObject()); + + bool found; + if (!JS_HasProperty(cx, iterResult, "value", &found)) + return false; + MOZ_ASSERT(found); + if (!JS_HasProperty(cx, iterResult, "done", &found)) + return false; + MOZ_ASSERT(found); + + RootedValue doneVal(cx); + if (!JS_GetProperty(cx, iterResult, "value", value)) + return false; + if (!JS_GetProperty(cx, iterResult, "done", &doneVal)) + return false; + + *done = doneVal.toBoolean(); + MOZ_ASSERT_IF(*done, value.isUndefined()); + + return true; +} + +static JSObject* +GetReadChunk(JSContext* cx, HandleObject readRequest) +{ + MOZ_ASSERT(GetPromiseState(readRequest) == PromiseState::Fulfilled); + RootedValue resultVal(cx, GetPromiseResult(readRequest)); + MOZ_ASSERT(resultVal.isObject()); + RootedObject result(cx, &resultVal.toObject()); + RootedValue chunkVal(cx); + JS_GetProperty(cx, result, "value", &chunkVal); + return &chunkVal.toObject(); +} + +BEGIN_TEST(testReadableStream_NewReadableStream) +{ + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + CHECK(ReadableStreamGetMode(stream) == ReadableStreamMode::Default); + return true; +} +END_TEST(testReadableStream_NewReadableStream) + +BEGIN_TEST(testReadableStream_NewReadableByteStream) +{ + RootedObject stream(cx, NewByteStream(cx)); + CHECK(stream); + CHECK(ReadableStreamGetMode(stream) == ReadableStreamMode::Byte); + return true; +} +END_TEST(testReadableStream_NewReadableByteStream) + +BEGIN_TEST(testReadableStream_ReadableStreamGetReaderDefault) +{ + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + CHECK(IsReadableStreamDefaultReader(reader)); + CHECK(ReadableStreamIsLocked(stream)); + CHECK(!ReadableStreamReaderIsClosed(reader)); + + return true; +} +END_TEST(testReadableStream_ReadableStreamGetReaderDefault) + +BEGIN_TEST(testReadableStream_ReadableStreamGetReaderBYOB) +{ + RootedObject stream(cx, NewByteStream(cx)); + CHECK(stream); + + RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::BYOB)); + CHECK(reader); + CHECK(IsReadableStreamBYOBReader(reader)); + CHECK(ReadableStreamIsLocked(stream)); + CHECK(!ReadableStreamReaderIsClosed(reader)); + + return true; +} +END_TEST(testReadableStream_ReadableStreamGetReaderBYOB) + +BEGIN_TEST(testReadableStream_ReadableStreamTee) +{ + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedObject leftStream(cx); + RootedObject rightStream(cx); + CHECK(ReadableStreamTee(cx, stream, &leftStream, &rightStream)); + CHECK(ReadableStreamIsLocked(stream)); + CHECK(leftStream); + CHECK(IsReadableStream(leftStream)); + CHECK(rightStream); + CHECK(IsReadableStream(rightStream)); + + return true; +} +END_TEST(testReadableStream_ReadableStreamTee) + +BEGIN_TEST(testReadableStream_ReadableStreamEnqueue) +{ + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedObject chunk(cx, JS_NewPlainObject(cx)); + CHECK(chunk); + RootedValue chunkVal(cx, ObjectValue(*chunk)); + CHECK(ReadableStreamEnqueue(cx, stream, chunkVal)); + + return true; +} +END_TEST(testReadableStream_ReadableStreamEnqueue) + +BEGIN_TEST(testReadableStream_ReadableByteStreamEnqueue) +{ + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + + RootedObject chunk(cx, JS_NewUint8Array(cx, 42)); + CHECK(chunk); + CHECK(!ReadableByteStreamEnqueueBuffer(cx, stream, chunk)); + CHECK(JS_IsExceptionPending(cx)); + + return true; +} +END_TEST(testReadableStream_ReadableByteStreamEnqueue) + +BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderRead) +{ + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + + RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader)); + CHECK(request); + CHECK(IsPromiseObject(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + + RootedObject chunk(cx, JS_NewPlainObject(cx)); + CHECK(chunk); + RootedValue chunkVal(cx, ObjectValue(*chunk)); + CHECK(ReadableStreamEnqueue(cx, stream, chunkVal)); + + CHECK(GetReadChunk(cx, request) == chunk); + + return true; +} +END_TEST(testReadableStream_ReadableStreamDefaultReaderRead) + +BEGIN_TEST(testReadableStream_ReadableByteStreamDefaultReaderRead) +{ + RootedObject stream(cx, NewByteStream(cx)); + CHECK(stream); + + RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + + RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader)); + CHECK(request); + CHECK(IsPromiseObject(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + + size_t length = sizeof(test_buffer_data); + RootedObject buffer(cx, JS_NewArrayBufferWithExternalContents(cx, length, test_buffer_data)); + CHECK(buffer); + RootedObject chunk(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, length)); + CHECK(chunk); + bool isShared; + CHECK(!JS_IsDetachedArrayBufferObject(buffer)); + + CHECK(ReadableByteStreamEnqueueBuffer(cx, stream, chunk)); + + CHECK(JS_IsDetachedArrayBufferObject(buffer)); + RootedObject readChunk(cx, GetReadChunk(cx, request)); + CHECK(JS_IsUint8Array(readChunk)); + void* readBufferData; + { + JS::AutoCheckCannotGC autoNoGC(cx); + readBufferData = JS_GetArrayBufferViewData(readChunk, &isShared, autoNoGC); + } + CHECK(readBufferData); + CHECK(!memcmp(test_buffer_data, readBufferData, length)); + + return true; +} +END_TEST(testReadableStream_ReadableByteStreamDefaultReaderRead) + +BEGIN_TEST(testReadableStream_ReadableByteStreamBYOBReaderRead) +{ + RootedObject stream(cx, NewByteStream(cx)); + CHECK(stream); + + RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::BYOB)); + CHECK(reader); + + size_t length = sizeof(test_buffer_data); + RootedObject targetArray(cx, JS_NewUint8Array(cx, length)); + bool isShared; + + RootedObject request(cx, ReadableStreamBYOBReaderRead(cx, reader, targetArray)); + CHECK(request); + CHECK(IsPromiseObject(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + CHECK(JS_IsDetachedArrayBufferObject(JS_GetArrayBufferViewBuffer(cx, targetArray, &isShared))); + + RootedObject buffer(cx, JS_NewArrayBufferWithExternalContents(cx, length, test_buffer_data)); + CHECK(buffer); + CHECK(!JS_IsDetachedArrayBufferObject(buffer)); + + CHECK(ReadableByteStreamEnqueueBuffer(cx, stream, buffer)); + + CHECK(JS_IsDetachedArrayBufferObject(buffer)); + RootedObject readChunk(cx, GetReadChunk(cx, request)); + CHECK(JS_IsUint8Array(readChunk)); + void* readBufferData; + { + JS::AutoCheckCannotGC autoNoGC(cx); + readBufferData = JS_GetArrayBufferViewData(readChunk, &isShared, autoNoGC); + } + CHECK(readBufferData); + CHECK(!memcmp(test_buffer_data, readBufferData, length)); + // TODO: eliminate the memcpy that happens here. +// CHECK(readBufferData == test_buffer_data); + + return true; +} +END_TEST(testReadableStream_ReadableByteStreamBYOBReaderRead) + +BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderClose) +{ + SetReadableStreamCallbacks(cx, &DataRequestCB, &WriteIntoRequestBufferCB, + &CancelStreamCB, &StreamClosedCB, &StreamErroredCB, + &FinalizeStreamCB); + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + + RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader)); + CHECK(request); + CHECK(IsPromiseObject(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + + CHECK(ReadableStreamClose(cx, stream)); + + bool done; + RootedValue value(cx); + CHECK(GetPromiseState(request) == PromiseState::Fulfilled); + CHECK(GetIterResult(cx, request, &value, &done)); + CHECK(value.isUndefined()); + CHECK(done); + + // The callbacks are only invoked for external streams. + CHECK(!streamClosedCBCalled); + + return true; +} +END_TEST(testReadableStream_ReadableStreamDefaultReaderClose) + +BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderError) +{ + ResetCallbacks(); + SetReadableStreamCallbacks(cx, &DataRequestCB, &WriteIntoRequestBufferCB, + &CancelStreamCB, &StreamClosedCB, &StreamErroredCB, + &FinalizeStreamCB); + RootedObject stream(cx, NewDefaultStream(cx)); + CHECK(stream); + RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default)); + CHECK(reader); + + RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader)); + CHECK(request); + CHECK(IsPromiseObject(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + + CHECK(ReadableStreamIsLocked(stream)); + CHECK(ReadableStreamIsReadable(stream)); + RootedValue error(cx, Int32Value(42)); + CHECK(ReadableStreamError(cx, stream, error)); + + CHECK(GetPromiseState(request) == PromiseState::Rejected); + RootedValue reason(cx, GetPromiseResult(request)); + CHECK(reason.isInt32()); + CHECK(reason.toInt32() == 42); + + // The callbacks are only invoked for external streams. + CHECK(!streamErroredCBCalled); + + return true; +} +END_TEST(testReadableStream_ReadableStreamDefaultReaderError) + +static JSObject* +NewExternalSourceStream(JSContext* cx, void* underlyingSource, + RequestReadableStreamDataCallback dataRequestCallback, + WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback, + CancelReadableStreamCallback cancelCallback, + ReadableStreamClosedCallback closedCallback, + ReadableStreamErroredCallback erroredCallback, + ReadableStreamFinalizeCallback finalizeCallback) +{ + SetReadableStreamCallbacks(cx, dataRequestCallback, writeIntoReadRequestCallback, + cancelCallback, closedCallback, erroredCallback, + finalizeCallback); + RootedObject stream(cx, NewReadableExternalSourceStreamObject(cx, underlyingSource)); + MOZ_ASSERT_IF(stream, IsReadableStream(stream)); + return stream; +} + +BEGIN_TEST(testReadableStream_CreateReadableByteStreamWithExternalSource) +{ + ResetCallbacks(); + + RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB, + &WriteIntoRequestBufferCB, &CancelStreamCB, + &StreamClosedCB, &StreamErroredCB, + &FinalizeStreamCB)); + CHECK(stream); + CHECK(ReadableStreamGetMode(stream) == JS::ReadableStreamMode::ExternalSource); + void* underlyingSource; + CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource)); + CHECK(underlyingSource == &test_buffer_data); + CHECK(ReadableStreamIsLocked(stream)); + ReadableStreamReleaseExternalUnderlyingSource(stream); + + return true; +} +END_TEST(testReadableStream_CreateReadableByteStreamWithExternalSource) + +BEGIN_TEST(testReadableStream_ExternalSourceCancel) +{ + ResetCallbacks(); + + RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB, + &WriteIntoRequestBufferCB, &CancelStreamCB, + &StreamClosedCB, &StreamErroredCB, + &FinalizeStreamCB)); + CHECK(stream); + RootedValue reason(cx, Int32Value(42)); + CHECK(ReadableStreamCancel(cx, stream, reason)); + CHECK(cancelStreamCBCalled); + CHECK(cancelStreamReason == reason); + + return true; +} +END_TEST(testReadableStream_ExternalSourceCancel) + +BEGIN_TEST(testReadableStream_ExternalSourceGetReader) +{ + ResetCallbacks(); + + RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB, + &WriteIntoRequestBufferCB, &CancelStreamCB, + &StreamClosedCB, &StreamErroredCB, + &FinalizeStreamCB)); + CHECK(stream); + + RootedValue streamVal(cx, ObjectValue(*stream)); + CHECK(JS_SetProperty(cx, global, "stream", streamVal)); + RootedValue rval(cx); + EVAL("stream.getReader()", &rval); + CHECK(rval.isObject()); + RootedObject reader(cx, &rval.toObject()); + CHECK(IsReadableStreamDefaultReader(reader)); + + return true; +} +END_TEST(testReadableStream_ExternalSourceGetReader) + +BEGIN_TEST(testReadableStream_ExternalSourceUpdateAvailableData) +{ + ResetCallbacks(); + + RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB, + &WriteIntoRequestBufferCB, &CancelStreamCB, + &StreamClosedCB, &StreamErroredCB, + &FinalizeStreamCB)); + CHECK(stream); + + ReadableStreamUpdateDataAvailableFromSource(cx, stream, 1024); + + return true; +} +END_TEST(testReadableStream_ExternalSourceUpdateAvailableData) + +struct ReadFromExternalSourceFixture : public JSAPITest +{ + virtual ~ReadFromExternalSourceFixture() {} + + bool readWithoutDataAvailable(const char* evalSrc, const char* evalSrc2, + uint32_t writtenLength) + { + ResetCallbacks(); + definePrint(); + + RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB, + &WriteIntoRequestBufferCB, + &CancelStreamCB, + &StreamClosedCB, &StreamErroredCB, + &FinalizeStreamCB)); + CHECK(stream); + js::RunJobs(cx); + void* underlyingSource; + CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource)); + CHECK(underlyingSource == &test_buffer_data); + CHECK(ReadableStreamIsLocked(stream)); + ReadableStreamReleaseExternalUnderlyingSource(stream); + + RootedValue streamVal(cx, ObjectValue(*stream)); + CHECK(JS_SetProperty(cx, global, "stream", streamVal)); + + RootedValue rval(cx); + EVAL(evalSrc, &rval); + CHECK(dataRequestCBCalled); + CHECK(!writeIntoRequestBufferCBCalled); + CHECK(rval.isObject()); + RootedObject promise(cx, &rval.toObject()); + CHECK(IsPromiseObject(promise)); + CHECK(GetPromiseState(promise) == PromiseState::Pending); + + size_t length = sizeof(test_buffer_data); + ReadableStreamUpdateDataAvailableFromSource(cx, stream, length); + + CHECK(writeIntoRequestBufferCBCalled); + CHECK(GetPromiseState(promise) == PromiseState::Fulfilled); + RootedValue iterVal(cx); + bool done; + if (!GetIterResult(cx, promise, &iterVal, &done)) + return false; + + CHECK(!done); + RootedObject chunk(cx, &iterVal.toObject()); + CHECK(JS_IsUint8Array(chunk)); + + { + JS::AutoCheckCannotGC noGC(cx); + bool dummy; + void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC); + CHECK(!memcmp(buffer, test_buffer_data, writtenLength)); + } + + dataRequestCBCalled = false; + writeIntoRequestBufferCBCalled = false; + EVAL(evalSrc2, &rval); + CHECK(dataRequestCBCalled); + CHECK(!writeIntoRequestBufferCBCalled); + + return true; + } + + bool readWithDataAvailable(const char* evalSrc, uint32_t writtenLength) { + ResetCallbacks(); + definePrint(); + + RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB, + &WriteIntoRequestBufferCB, &CancelStreamCB, + &StreamClosedCB, &StreamErroredCB, + &FinalizeStreamCB)); + CHECK(stream); + void* underlyingSource; + CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource)); + CHECK(underlyingSource == &test_buffer_data); + CHECK(ReadableStreamIsLocked(stream)); + ReadableStreamReleaseExternalUnderlyingSource(stream); + + size_t length = sizeof(test_buffer_data); + ReadableStreamUpdateDataAvailableFromSource(cx, stream, length); + + RootedValue streamVal(cx, ObjectValue(*stream)); + CHECK(JS_SetProperty(cx, global, "stream", streamVal)); + + RootedValue rval(cx); + EVAL(evalSrc, &rval); + CHECK(writeIntoRequestBufferCBCalled); + CHECK(rval.isObject()); + RootedObject promise(cx, &rval.toObject()); + CHECK(IsPromiseObject(promise)); + CHECK(GetPromiseState(promise) == PromiseState::Fulfilled); + RootedValue iterVal(cx); + bool done; + if (!GetIterResult(cx, promise, &iterVal, &done)) + return false; + + CHECK(!done); + RootedObject chunk(cx, &iterVal.toObject()); + CHECK(JS_IsUint8Array(chunk)); + + { + JS::AutoCheckCannotGC noGC(cx); + bool dummy; + void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC); + CHECK(!memcmp(buffer, test_buffer_data, writtenLength)); + } + + return true; + } +}; + +BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable) +{ + return readWithoutDataAvailable("r = stream.getReader(); r.read()", "r.read()", sizeof(test_buffer_data)); +} +END_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable) + +BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceCloseWithPendingRead) +{ + CHECK(readWithoutDataAvailable("r = stream.getReader(); request0 = r.read(); " + "request1 = r.read(); request0", "r.read()", + sizeof(test_buffer_data))); + + RootedValue val(cx); + CHECK(JS_GetProperty(cx, global, "request1", &val)); + CHECK(val.isObject()); + RootedObject request(cx, &val.toObject()); + CHECK(IsPromiseObject(request)); + CHECK(GetPromiseState(request) == PromiseState::Pending); + + CHECK(JS_GetProperty(cx, global, "stream", &val)); + RootedObject stream(cx, &val.toObject()); + ReadableStreamClose(cx, stream); + + val = GetPromiseResult(request); + MOZ_ASSERT(val.isObject()); + RootedObject result(cx, &val.toObject()); + + JS_GetProperty(cx, result, "done", &val); + CHECK(val.isBoolean()); + CHECK(val.toBoolean() == true); + + JS_GetProperty(cx, result, "value", &val); + CHECK(val.isUndefined()); + return true; +} +END_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceCloseWithPendingRead) + +BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithDataAvailable) +{ + return readWithDataAvailable("r = stream.getReader(); r.read()", sizeof(test_buffer_data)); +} +END_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadDefaultWithDataAvailable) + +BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadBYOBWithoutDataAvailable) +{ + return readWithoutDataAvailable("r = stream.getReader({mode: 'byob'}); r.read(new Uint8Array(63))", "r.read(new Uint8Array(10))", 10); +} +END_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadBYOBWithoutDataAvailable) + +BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadBYOBWithDataAvailable) +{ + return readWithDataAvailable("r = stream.getReader({mode: 'byob'}); r.read(new Uint8Array(10))", 10); +} +END_FIXTURE_TEST(ReadFromExternalSourceFixture, + testReadableStream_ExternalSourceReadBYOBWithDataAvailable) diff --git a/js/src/jsapi-tests/tests.cpp b/js/src/jsapi-tests/tests.cpp index b75fa6602d..61e79eab6d 100644 --- a/js/src/jsapi-tests/tests.cpp +++ b/js/src/jsapi-tests/tests.cpp @@ -81,6 +81,10 @@ JSObject* JSAPITest::createGlobal(JSPrincipals* principals) /* Create the global object. */ JS::RootedObject newGlobal(cx); JS::CompartmentOptions options; +#ifdef ENABLE_STREAMS + options.creationOptions().setStreamsEnabled(true); +#endif + printf("enabled\n"); options.behaviors().setVersion(JSVERSION_LATEST); newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), principals, JS::FireOnNewGlobalHook, options); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 0fd82f7c2d..9067ca3d3d 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -46,6 +46,7 @@ #include "builtin/MapObject.h" #include "builtin/Promise.h" #include "builtin/RegExp.h" +#include "builtin/Stream.h" #include "builtin/SymbolObject.h" #ifdef ENABLE_BINARYDATA # include "builtin/TypedObject.h" @@ -5095,7 +5096,6 @@ CallOriginalPromiseThenImpl(JSContext* cx, JS::HandleObject promiseObj, return false; } return true; - } JS_PUBLIC_API(JSObject*) @@ -5126,7 +5126,7 @@ JS::AddPromiseReactions(JSContext* cx, JS::HandleObject promiseObj, * Unforgeable version of Promise.all for internal use. * * Takes a dense array of Promise objects and returns a promise that's - * resolved with an array of resolution values when all those promises ahve + * resolved with an array of resolution values when all those promises have * been resolved, or rejected with the rejection value of the first rejected * promise. * @@ -5141,6 +5141,368 @@ JS::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) return js::GetWaitForAllPromise(cx, promises); } +JS_PUBLIC_API(JSObject*) +JS::NewReadableDefaultStreamObject(JSContext* cx, + JS::HandleObject underlyingSource /* = nullptr */, + JS::HandleFunction size /* = nullptr */, + double highWaterMark /* = 1 */, + JS::HandleObject proto /* = nullptr */) +{ + MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment())); + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + + RootedObject source(cx, underlyingSource); + if (!source) { + source = NewBuiltinClassInstance<PlainObject>(cx); + if (!source) + return nullptr; + } + RootedValue sourceVal(cx, ObjectValue(*source)); + RootedValue sizeVal(cx, size ? ObjectValue(*size) : UndefinedValue()); + RootedValue highWaterMarkVal(cx, NumberValue(highWaterMark)); + return ReadableStream::createDefaultStream(cx, sourceVal, sizeVal, highWaterMarkVal, proto); +} + +JS_PUBLIC_API(JSObject*) +JS::NewReadableByteStreamObject(JSContext* cx, + JS::HandleObject underlyingSource /* = nullptr */, + double highWaterMark /* = 1 */, + JS::HandleObject proto /* = nullptr */) +{ + MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment())); + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + + RootedObject source(cx, underlyingSource); + if (!source) { + source = NewBuiltinClassInstance<PlainObject>(cx); + if (!source) + return nullptr; + } + RootedValue sourceVal(cx, ObjectValue(*source)); + RootedValue highWaterMarkVal(cx, NumberValue(highWaterMark)); + return ReadableStream::createByteStream(cx, sourceVal, highWaterMarkVal, proto); +} + +extern JS_PUBLIC_API(void) +JS::SetReadableStreamCallbacks(JSContext* cx, + JS::RequestReadableStreamDataCallback dataRequestCallback, + JS::WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback, + JS::CancelReadableStreamCallback cancelCallback, + JS::ReadableStreamClosedCallback closedCallback, + JS::ReadableStreamErroredCallback erroredCallback, + JS::ReadableStreamFinalizeCallback finalizeCallback) +{ + MOZ_ASSERT(dataRequestCallback); + MOZ_ASSERT(writeIntoReadRequestCallback); + MOZ_ASSERT(cancelCallback); + MOZ_ASSERT(closedCallback); + MOZ_ASSERT(erroredCallback); + MOZ_ASSERT(finalizeCallback); + + JSRuntime* rt = cx->runtime(); + + MOZ_ASSERT(!rt->readableStreamDataRequestCallback); + MOZ_ASSERT(!rt->readableStreamWriteIntoReadRequestCallback); + MOZ_ASSERT(!rt->readableStreamCancelCallback); + MOZ_ASSERT(!rt->readableStreamClosedCallback); + MOZ_ASSERT(!rt->readableStreamErroredCallback); + MOZ_ASSERT(!rt->readableStreamFinalizeCallback); + + rt->readableStreamDataRequestCallback = dataRequestCallback; + rt->readableStreamWriteIntoReadRequestCallback = writeIntoReadRequestCallback; + rt->readableStreamCancelCallback = cancelCallback; + rt->readableStreamClosedCallback = closedCallback; + rt->readableStreamErroredCallback = erroredCallback; + rt->readableStreamFinalizeCallback = finalizeCallback; +} + +JS_PUBLIC_API(bool) +JS::HasReadableStreamCallbacks(JSContext* cx) +{ + return cx->runtime()->readableStreamDataRequestCallback; +} + +JS_PUBLIC_API(JSObject*) +JS::NewReadableExternalSourceStreamObject(JSContext* cx, void* underlyingSource, + uint8_t flags /* = 0 */, + HandleObject proto /* = nullptr */) +{ + MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment())); + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + +#ifdef DEBUG + JSRuntime* rt = cx->runtime(); + MOZ_ASSERT(rt->readableStreamDataRequestCallback); + MOZ_ASSERT(rt->readableStreamWriteIntoReadRequestCallback); + MOZ_ASSERT(rt->readableStreamCancelCallback); + MOZ_ASSERT(rt->readableStreamClosedCallback); + MOZ_ASSERT(rt->readableStreamErroredCallback); + MOZ_ASSERT(rt->readableStreamFinalizeCallback); +#endif // DEBUG + + return ReadableStream::createExternalSourceStream(cx, underlyingSource, flags, proto); +} + +JS_PUBLIC_API(uint8_t) +JS::ReadableStreamGetEmbeddingFlags(const JSObject* stream) +{ + return stream->as<ReadableStream>().embeddingFlags(); +} + +JS_PUBLIC_API(bool) +JS::IsReadableStream(const JSObject* obj) +{ + return obj->is<ReadableStream>(); +} + +JS_PUBLIC_API(bool) +JS::IsReadableStreamReader(const JSObject* obj) +{ + return obj->is<ReadableStreamDefaultReader>() || obj->is<ReadableStreamBYOBReader>(); +} + +JS_PUBLIC_API(bool) +JS::IsReadableStreamDefaultReader(const JSObject* obj) +{ + return obj->is<ReadableStreamDefaultReader>(); +} + +JS_PUBLIC_API(bool) +JS::IsReadableStreamBYOBReader(const JSObject* obj) +{ + return obj->is<ReadableStreamBYOBReader>(); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamIsReadable(const JSObject* stream) +{ + return stream->as<ReadableStream>().readable(); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamIsLocked(const JSObject* stream) +{ + return stream->as<ReadableStream>().locked(); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamIsDisturbed(const JSObject* stream) +{ + return stream->as<ReadableStream>().disturbed(); +} + +JS_PUBLIC_API(JSObject*) +JS::ReadableStreamCancel(JSContext* cx, HandleObject streamObj, HandleValue reason) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, streamObj); + assertSameCompartment(cx, reason); + + Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>()); + return ReadableStream::cancel(cx, stream, reason); +} + +JS_PUBLIC_API(JS::ReadableStreamMode) +JS::ReadableStreamGetMode(const JSObject* stream) +{ + return stream->as<ReadableStream>().mode(); +} + +JS_PUBLIC_API(JSObject*) +JS::ReadableStreamGetReader(JSContext* cx, HandleObject streamObj, ReadableStreamReaderMode mode) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, streamObj); + + Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>()); + return ReadableStream::getReader(cx, stream, mode); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject streamObj, void** source) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, streamObj); + + Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>()); + return ReadableStream::getExternalSource(cx, stream, source); +} + +JS_PUBLIC_API(void) +JS::ReadableStreamReleaseExternalUnderlyingSource(JSObject* stream) +{ + stream->as<ReadableStream>().releaseExternalSource(); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamUpdateDataAvailableFromSource(JSContext* cx, JS::HandleObject streamObj, + uint32_t availableData) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, streamObj); + + Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>()); + return ReadableStream::updateDataAvailableFromSource(cx, stream, availableData); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamTee(JSContext* cx, HandleObject streamObj, + MutableHandleObject branch1Obj, MutableHandleObject branch2Obj) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, streamObj); + + Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>()); + Rooted<ReadableStream*> branch1Stream(cx); + Rooted<ReadableStream*> branch2Stream(cx); + + if (!ReadableStream::tee(cx, stream, false, &branch1Stream, &branch2Stream)) + return false; + + branch1Obj.set(branch1Stream); + branch2Obj.set(branch2Stream); + + return true; +} + +JS_PUBLIC_API(void) +JS::ReadableStreamGetDesiredSize(JSObject* streamObj, bool* hasValue, double* value) +{ + streamObj->as<ReadableStream>().desiredSize(hasValue, value); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamClose(JSContext* cx, HandleObject streamObj) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, streamObj); + + Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>()); + return ReadableStream::close(cx, stream); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamEnqueue(JSContext* cx, HandleObject streamObj, HandleValue chunk) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, streamObj); + assertSameCompartment(cx, chunk); + + Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>()); + if (stream->mode() != JS::ReadableStreamMode::Default) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER, + "JS::ReadableStreamEnqueue"); + return false; + } + return ReadableStream::enqueue(cx, stream, chunk); +} + +JS_PUBLIC_API(bool) +JS::ReadableByteStreamEnqueueBuffer(JSContext* cx, HandleObject streamObj, HandleObject chunkObj) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, streamObj); + assertSameCompartment(cx, chunkObj); + + Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>()); + if (stream->mode() != JS::ReadableStreamMode::Byte) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER, + "JS::ReadableByteStreamEnqueueBuffer"); + return false; + } + + Rooted<ArrayBufferObject*> buffer(cx); + if (chunkObj->is<ArrayBufferViewObject>()) { + bool dummy; + buffer = &JS_GetArrayBufferViewBuffer(cx, chunkObj, &dummy)->as<ArrayBufferObject>(); + } else if (chunkObj->is<ArrayBufferObject>()) { + buffer = &chunkObj->as<ArrayBufferObject>(); + } else { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK, + "JS::ReadableByteStreamEnqueueBuffer"); + return false; + } + + return ReadableStream::enqueueBuffer(cx, stream, buffer); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamError(JSContext* cx, HandleObject streamObj, HandleValue error) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, streamObj); + assertSameCompartment(cx, error); + + Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>()); + return js::ReadableStream::error(cx, stream, error); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamReaderIsClosed(const JSObject* reader) +{ + return js::ReadableStreamReaderIsClosed(reader); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamReaderCancel(JSContext* cx, HandleObject reader, HandleValue reason) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, reader); + assertSameCompartment(cx, reason); + + return js::ReadableStreamReaderCancel(cx, reader, reason); +} + +JS_PUBLIC_API(bool) +JS::ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, reader); + + return js::ReadableStreamReaderReleaseLock(cx, reader); +} + +JS_PUBLIC_API(JSObject*) +JS::ReadableStreamDefaultReaderRead(JSContext* cx, HandleObject readerObj) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, readerObj); + + Rooted<ReadableStreamDefaultReader*> reader(cx, &readerObj->as<ReadableStreamDefaultReader>()); + return js::ReadableStreamDefaultReader::read(cx, reader); +} + +JS_PUBLIC_API(JSObject*) +JS::ReadableStreamBYOBReaderRead(JSContext* cx, HandleObject readerObj, HandleObject viewObj) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + assertSameCompartment(cx, readerObj); + assertSameCompartment(cx, viewObj); + + Rooted<ReadableStreamBYOBReader*> reader(cx, &readerObj->as<ReadableStreamBYOBReader>()); + Rooted<ArrayBufferViewObject*> view(cx, &viewObj->as<ArrayBufferViewObject>()); + return js::ReadableStreamBYOBReader::read(cx, reader, view); +} + JS_PUBLIC_API(void) JS::SetAsyncTaskCallbacks(JSContext* cx, JS::StartAsyncTaskCallback start, JS::FinishAsyncTaskCallback finish) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index f80d2602e6..951b612502 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -35,6 +35,7 @@ #include "js/Principals.h" #include "js/Realm.h" #include "js/RootingAPI.h" +#include "js/Stream.h" #include "js/TracingAPI.h" #include "js/UniquePtr.h" #include "js/Utility.h" @@ -1161,6 +1162,16 @@ class JS_PUBLIC_API(ContextOptions) { return *this; } + bool streams() const { return streams_; } + ContextOptions& setStreams(bool flag) { + streams_ = flag; + return *this; + } + ContextOptions& toggleStreams() { + streams_ = !streams_; + return *this; + } + bool nativeRegExp() const { return nativeRegExp_; } ContextOptions& setNativeRegExp(bool flag) { nativeRegExp_ = flag; @@ -1243,6 +1254,7 @@ class JS_PUBLIC_API(ContextOptions) { bool strictMode_ : 1; bool extraWarnings_ : 1; bool arrayProtoValues_ : 1; + bool streams_ : 1; }; JS_PUBLIC_API(ContextOptions&) diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index b4c4e15801..c463019dac 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1787,6 +1787,9 @@ UnwrapArrayBufferView(JSObject* obj); extern JS_FRIEND_API(JSObject*) UnwrapSharedArrayBuffer(JSObject* obj); +extern JS_FRIEND_API(JSObject*) +UnwrapReadableStream(JSObject* obj); + namespace detail { @@ -2005,6 +2008,12 @@ JS_IsArrayBufferViewObject(JSObject* obj); extern JS_FRIEND_API(uint32_t) JS_GetArrayBufferViewByteLength(JSObject* obj); +/** + * More generic name for JS_GetTypedArrayByteOffset to cover DataViews as well + */ +extern JS_FRIEND_API(uint32_t) +JS_GetArrayBufferViewByteOffset(JSObject* obj); + /* * Return a pointer to the start of the data referenced by a typed array. The * data is still owned by the typed array, and should not be modified on diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index fd23e6ccd5..beff701bd0 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -1003,6 +1003,13 @@ static const JSFunctionSpec number_methods[] = { JS_FS_END }; +bool +js::IsInteger(const Value& val) +{ + return val.isInt32() || + (mozilla::IsFinite(val.toDouble()) && JS::ToInteger(val.toDouble()) == val.toDouble()); +} + // ES6 draft ES6 15.7.3.12 static bool Number_isInteger(JSContext* cx, unsigned argc, Value* vp) @@ -1013,9 +1020,7 @@ Number_isInteger(JSContext* cx, unsigned argc, Value* vp) return true; } Value val = args[0]; - args.rval().setBoolean(val.isInt32() || - (mozilla::IsFinite(val.toDouble()) && - JS::ToInteger(val.toDouble()) == val.toDouble())); + args.rval().setBoolean(js::IsInteger(val)); return true; } diff --git a/js/src/jsnum.h b/js/src/jsnum.h index ee07d0a35d..7c96bfb652 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -72,6 +72,10 @@ Int32ToString(ExclusiveContext* cx, int32_t i); extern JSAtom* Int32ToAtom(ExclusiveContext* cx, int32_t si); +// ES6 15.7.3.12 +extern bool +IsInteger(const Value& val); + /* * Convert an integer or double (contained in the given value) to a string and * append to the given buffer. diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index 52d0d8ea4a..9822a47b6c 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -108,6 +108,17 @@ IF_SAB(real,imaginary)(Atomics, InitAtomicsClass, OCLASP(Atomics)) \ imaginary(WasmMemory, dummy, dummy) \ imaginary(WasmTable, dummy, dummy) \ real(Promise, InitViaClassSpec, OCLASP(Promise)) \ + real(ReadableStream, InitViaClassSpec, &js::ReadableStream::class_) \ + real(ReadableStreamDefaultReader, InitViaClassSpec, &js::ReadableStreamDefaultReader::class_) \ + real(ReadableStreamBYOBReader, InitViaClassSpec, &js::ReadableStreamBYOBReader::class_) \ + real(ReadableStreamDefaultController, InitViaClassSpec, &js::ReadableStreamDefaultController::class_) \ + real(ReadableByteStreamController, InitViaClassSpec, &js::ReadableByteStreamController::class_) \ + real(ReadableStreamBYOBRequest, InitViaClassSpec, &js::ReadableStreamBYOBRequest::class_) \ + imaginary(WritableStream, dummy, dummy) \ + imaginary(WritableStreamDefaultWriter, dummy, dummy) \ + imaginary(WritableStreamDefaultController,dummy, dummy) \ + real(ByteLengthQueuingStrategy, InitViaClassSpec, &js::ByteLengthQueuingStrategy::class_) \ + real(CountQueuingStrategy, InitViaClassSpec, &js::CountQueuingStrategy::class_) \ #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro) diff --git a/js/src/moz.build b/js/src/moz.build index 5429888a2c..d42cf59730 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -92,6 +92,7 @@ EXPORTS.js += [ '../public/Result.h', '../public/RootingAPI.h', '../public/SliceBudget.h', + '../public/Stream.h', '../public/StructuredClone.h', '../public/SweepingAPI.h', '../public/TraceKind.h', @@ -384,6 +385,7 @@ main_deunified_sources = [ SOURCES += [ 'builtin/BigInt.cpp', 'builtin/RegExp.cpp', + 'builtin/Stream.cpp', 'frontend/Parser.cpp', 'gc/StoreBuffer.cpp', 'jsarray.cpp', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index a77e128a52..1f89e38482 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -318,6 +318,7 @@ static bool enableUnboxedArrays = false; static bool enableSharedMemory = SHARED_MEMORY_DEFAULT; static bool enableWasmAlwaysBaseline = false; static bool enableArrayProtoValues = true; +static bool enableStreams = false; static bool printTiming = false; static const char* jsCacheDir = nullptr; static const char* jsCacheAsmJSPath = nullptr; @@ -7565,6 +7566,7 @@ SetContextOptions(JSContext* cx, const OptionParser& op) enableUnboxedArrays = op.getBoolOption("unboxed-arrays"); enableWasmAlwaysBaseline = op.getBoolOption("wasm-always-baseline"); enableArrayProtoValues = !op.getBoolOption("no-array-proto-values"); + enableStreams = op.getBoolOption("enable-streams"); JS::ContextOptionsRef(cx).setBaseline(enableBaseline) .setIon(enableIon) @@ -7573,7 +7575,8 @@ SetContextOptions(JSContext* cx, const OptionParser& op) .setWasmAlwaysBaseline(enableWasmAlwaysBaseline) .setNativeRegExp(enableNativeRegExp) .setUnboxedArrays(enableUnboxedArrays) - .setArrayProtoValues(enableArrayProtoValues); + .setArrayProtoValues(enableArrayProtoValues) + .setStreams(enableStreams); if (op.getBoolOption("wasm-check-bce")) jit::JitOptions.wasmAlwaysCheckBounds = true; @@ -7845,7 +7848,8 @@ SetWorkerContextOptions(JSContext* cx) .setWasmAlwaysBaseline(enableWasmAlwaysBaseline) .setNativeRegExp(enableNativeRegExp) .setUnboxedArrays(enableUnboxedArrays) - .setArrayProtoValues(enableArrayProtoValues); + .setArrayProtoValues(enableArrayProtoValues) + .setStreams(enableStreams); cx->setOffthreadIonCompilationEnabled(offthreadCompilation); cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps; @@ -8019,6 +8023,7 @@ main(int argc, char** argv, char** envp) || !op.addBoolOption('\0', "wasm-always-baseline", "Enable wasm baseline compiler when possible") || !op.addBoolOption('\0', "wasm-check-bce", "Always generate wasm bounds check, even redundant ones.") || !op.addBoolOption('\0', "no-array-proto-values", "Remove Array.prototype.values") + || !op.addBoolOption('\0', "enable-streams", "Enable WHATWG Streams") #ifdef ENABLE_SHARED_ARRAY_BUFFER || !op.addStringOption('\0', "shared-memory", "on/off", "SharedArrayBuffer and Atomics " diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index db1d7c798f..6ace034adb 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -1255,15 +1255,16 @@ ArrayBufferObject::finalize(FreeOp* fop, JSObject* obj) } /* static */ void -ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer, - Handle<ArrayBufferObject*> fromBuffer, - uint32_t fromIndex, uint32_t count) +ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex, + Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex, + uint32_t count) { MOZ_ASSERT(toBuffer->byteLength() >= count); + MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count); MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex); MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count); - memcpy(toBuffer->dataPointer(), fromBuffer->dataPointer() + fromIndex, count); + memcpy(toBuffer->dataPointer() + toIndex, fromBuffer->dataPointer() + fromIndex, count); } /* static */ void @@ -1890,6 +1891,17 @@ JS_GetArrayBufferViewByteLength(JSObject* obj) : obj->as<TypedArrayObject>().byteLength(); } +JS_FRIEND_API(uint32_t) +JS_GetArrayBufferViewByteOffset(JSObject* obj) +{ + obj = CheckedUnwrap(obj); + if (!obj) + return 0; + return obj->is<DataViewObject>() + ? obj->as<DataViewObject>().byteOffset() + : obj->as<TypedArrayObject>().byteOffset(); +} + JS_FRIEND_API(JSObject*) JS_GetObjectAsArrayBufferView(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data) { diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h index 4ff7962cfb..f4010c6c77 100644 --- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -261,9 +261,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared template<typename T> static bool createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp); - static void copyData(Handle<ArrayBufferObject*> toBuffer, - Handle<ArrayBufferObject*> fromBuffer, - uint32_t fromIndex, uint32_t count); + static void copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex, + Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex, + uint32_t count); static void trace(JSTracer* trc, JSObject* obj); static void objectMoved(JSObject* obj, const JSObject* old); diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index f05a4db9be..ebfad89fa0 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -18,6 +18,8 @@ macro(apply, apply, "apply") \ macro(args, args, "args") \ macro(arguments, arguments, "arguments") \ + macro(AcquireReadableStreamBYOBReader, AcquireReadableStreamBYOBReader, "AcquireReadableStreamBYOBReader") \ + macro(AcquireReadableStreamDefaultReader, AcquireReadableStreamDefaultReader, "AcquireReadableStreamDefaultReader") \ macro(ArrayBufferSpecies, ArrayBufferSpecies, "ArrayBufferSpecies") \ macro(ArrayIterator, ArrayIterator, "Array Iterator") \ macro(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext") \ @@ -35,6 +37,7 @@ macro(AsyncGeneratorFunction, AsyncGeneratorFunction, "AsyncGeneratorFunction") \ macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \ macro(async, async, "async") \ + macro(autoAllocateChunkSize, autoAllocateChunkSize, "autoAllocateChunkSize") \ macro(await, await, "await") \ macro(bigint64, bigint64, "bigint64") \ macro(biguint64, biguint64, "biguint64") \ @@ -48,6 +51,7 @@ macro(buffer, buffer, "buffer") \ macro(builder, builder, "builder") \ macro(by, by, "by") \ + macro(byob, byob, "byob") \ macro(byteAlignment, byteAlignment, "byteAlignment") \ macro(byteLength, byteLength, "byteLength") \ macro(byteOffset, byteOffset, "byteOffset") \ @@ -59,6 +63,7 @@ macro(callee, callee, "callee") \ macro(caller, caller, "caller") \ macro(callFunction, callFunction, "callFunction") \ + macro(cancel, cancel, "cancel") \ macro(case, case_, "case") \ macro(caseFirst, caseFirst, "caseFirst") \ macro(catch, catch_, "catch") \ @@ -180,6 +185,7 @@ macro(has, has, "has") \ macro(hasOwn, hasOwn, "hasOwn") \ macro(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \ + macro(highWaterMark, highWaterMark, "highWaterMark") \ macro(hour, hour, "hour") \ macro(hourCycle, hourCycle, "hourCycle") \ macro(if, if_, "if") \ @@ -250,6 +256,7 @@ macro(minusSign, minusSign, "minusSign") \ macro(minute, minute, "minute") \ macro(missingArguments, missingArguments, "missingArguments") \ + macro(mode, mode, "mode") \ macro(module, module, "module") \ macro(Module, Module, "Module") \ macro(ModuleInstantiate, ModuleInstantiate, "ModuleInstantiate") \ @@ -312,7 +319,52 @@ macro(prototype, prototype, "prototype") \ macro(proxy, proxy, "proxy") \ macro(public, public_, "public") \ + macro(pull, pull, "pull") \ macro(raw, raw, "raw") \ + macro(ReadableByteStreamControllerGetDesiredSize, \ + ReadableByteStreamControllerGetDesiredSize, \ + "ReadableByteStreamControllerGetDesiredSize") \ + macro(ReadableByteStreamController_close, \ + ReadableByteStreamController_close, \ + "ReadableByteStreamController_close") \ + macro(ReadableByteStreamController_enqueue, \ + ReadableByteStreamController_enqueue, \ + "ReadableByteStreamController_enqueue") \ + macro(ReadableByteStreamController_error, \ + ReadableByteStreamController_error, \ + "ReadableByteStreamController_error") \ + macro(ReadableStreamBYOBReader_cancel, \ + ReadableStreamBYOBReader_cancel, \ + "ReadableStreamBYOBReader_cancel") \ + macro(ReadableStreamBYOBReader_read, \ + ReadableStreamBYOBReader_read, \ + "ReadableStreamBYOBReader_read") \ + macro(ReadableStreamBYOBReader_releaseLock, \ + ReadableStreamBYOBReader_releaseLock, \ + "ReadableStreamBYOBReader_releaseLock") \ + macro(ReadableStream_cancel, ReadableStream_cancel, "ReadableStream_cancel") \ + macro(ReadableStreamDefaultControllerGetDesiredSize, \ + ReadableStreamDefaultControllerGetDesiredSize, \ + "ReadableStreamDefaultControllerGetDesiredSize") \ + macro(ReadableStreamDefaultController_close, \ + ReadableStreamDefaultController_close, \ + "ReadableStreamDefaultController_close") \ + macro(ReadableStreamDefaultController_enqueue, \ + ReadableStreamDefaultController_enqueue, \ + "ReadableStreamDefaultController_enqueue") \ + macro(ReadableStreamDefaultController_error, \ + ReadableStreamDefaultController_error, \ + "ReadableStreamDefaultController_error") \ + macro(ReadableStreamDefaultReader_cancel, \ + ReadableStreamDefaultReader_cancel, \ + "ReadableStreamDefaultReader_cancel") \ + macro(ReadableStreamDefaultReader_read, \ + ReadableStreamDefaultReader_read, \ + "ReadableStreamDefaultReader_read") \ + macro(ReadableStreamDefaultReader_releaseLock, \ + ReadableStreamDefaultReader_releaseLock, \ + "ReadableStreamDefaultReader_releaseLock") \ + macro(ReadableStreamTee, ReadableStreamTee, "ReadableStreamTee") \ macro(reason, reason, "reason") \ macro(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter") \ macro(RegExpStringIterator, RegExpStringIterator, "RegExp String Iterator") \ @@ -347,6 +399,7 @@ macro(StarGeneratorNext, StarGeneratorNext, "StarGeneratorNext") \ macro(StarGeneratorReturn, StarGeneratorReturn, "StarGeneratorReturn") \ macro(StarGeneratorThrow, StarGeneratorThrow, "StarGeneratorThrow") \ + macro(start, start, "start") \ macro(startTimestamp, startTimestamp, "startTimestamp") \ macro(state, state, "state") \ macro(static, static_, "static") \ diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index b7d3344b3e..1321be9e9b 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -23,6 +23,7 @@ #include "builtin/Promise.h" #include "builtin/RegExp.h" #include "builtin/SelfHostingDefines.h" +#include "builtin/Stream.h" #include "builtin/SymbolObject.h" #include "builtin/TypedObject.h" #include "builtin/WeakMapObject.h" @@ -98,6 +99,16 @@ GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key) case JSProto_WebAssembly: return !wasm::HasSupport(cx); + case JSProto_ReadableStream: + case JSProto_ReadableStreamDefaultReader: + case JSProto_ReadableStreamBYOBReader: + case JSProto_ReadableStreamDefaultController: + case JSProto_ReadableByteStreamController: + case JSProto_ReadableStreamBYOBRequest: + case JSProto_ByteLengthQueuingStrategy: + case JSProto_CountQueuingStrategy: + return !cx->options().streams(); + #ifdef ENABLE_SHARED_ARRAY_BUFFER case JSProto_Atomics: case JSProto_SharedArrayBuffer: diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 053b7c44b0..c5deff940c 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -156,6 +156,12 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime) startAsyncTaskCallback(nullptr), finishAsyncTaskCallback(nullptr), promiseTasksToDestroy(mutexid::PromiseTaskPtrVector), + readableStreamDataRequestCallback(nullptr), + readableStreamWriteIntoReadRequestCallback(nullptr), + readableStreamCancelCallback(nullptr), + readableStreamClosedCallback(nullptr), + readableStreamErroredCallback(nullptr), + readableStreamFinalizeCallback(nullptr), exclusiveAccessLock(mutexid::RuntimeExclusiveAccess), #ifdef DEBUG mainThreadHasExclusiveAccess(false), diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 962f0e1a1e..13523844c1 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -655,6 +655,13 @@ struct JSRuntime : public JS::shadow::Runtime, JS::FinishAsyncTaskCallback finishAsyncTaskCallback; js::ExclusiveData<js::PromiseTaskPtrVector> promiseTasksToDestroy; + JS::RequestReadableStreamDataCallback readableStreamDataRequestCallback; + JS::WriteIntoReadRequestBufferCallback readableStreamWriteIntoReadRequestCallback; + JS::CancelReadableStreamCallback readableStreamCancelCallback; + JS::ReadableStreamClosedCallback readableStreamClosedCallback; + JS::ReadableStreamErroredCallback readableStreamErroredCallback; + JS::ReadableStreamFinalizeCallback readableStreamFinalizeCallback; + private: /* * Lock taken when using per-runtime or per-zone data that could otherwise diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index e9c72d1bf1..e006846752 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -36,6 +36,7 @@ #include "builtin/Promise.h" #include "builtin/Reflect.h" #include "builtin/SelfHostingDefines.h" +#include "builtin/Stream.h" #include "builtin/TypedObject.h" #include "builtin/WeakSetObject.h" #include "gc/Marking.h" @@ -71,7 +72,6 @@ using JS::AutoCheckCannotGC; using mozilla::IsInRange; using mozilla::Maybe; using mozilla::PodMove; -using mozilla::Maybe; static void selfHosting_WarningReporter(JSContext* cx, JSErrorReport* report) @@ -1098,9 +1098,9 @@ static bool intrinsic_ArrayBufferCopyData(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); - MOZ_ASSERT(args.length() == 5); + MOZ_ASSERT(args.length() == 6); - bool isWrapped = args[4].toBoolean(); + bool isWrapped = args[5].toBoolean(); Rooted<T*> toBuffer(cx); if (!isWrapped) { toBuffer = &args[0].toObject().as<T>(); @@ -1114,11 +1114,12 @@ intrinsic_ArrayBufferCopyData(JSContext* cx, unsigned argc, Value* vp) } toBuffer = toBufferObj.as<T>(); } - Rooted<T*> fromBuffer(cx, &args[1].toObject().as<T>()); - uint32_t fromIndex = uint32_t(args[2].toInt32()); - uint32_t count = uint32_t(args[3].toInt32()); + uint32_t toIndex = uint32_t(args[1].toInt32()); + Rooted<T*> fromBuffer(cx, &args[2].toObject().as<T>()); + uint32_t fromIndex = uint32_t(args[3].toInt32()); + uint32_t count = uint32_t(args[4].toInt32()); - T::copyData(toBuffer, fromBuffer, fromIndex, count); + T::copyData(toBuffer, toIndex, fromBuffer, fromIndex, count); args.rval().setUndefined(); return true; @@ -2426,14 +2427,14 @@ static const JSFunctionSpec intrinsic_functions[] = { intrinsic_PossiblyWrappedArrayBufferByteLength<ArrayBufferObject>, 1,0, IntrinsicPossiblyWrappedArrayBufferByteLength), JS_FN("ArrayBufferCopyData", - intrinsic_ArrayBufferCopyData<ArrayBufferObject>, 5,0), + intrinsic_ArrayBufferCopyData<ArrayBufferObject>, 6,0), JS_FN("SharedArrayBufferByteLength", intrinsic_ArrayBufferByteLength<SharedArrayBufferObject>, 1,0), JS_FN("PossiblyWrappedSharedArrayBufferByteLength", intrinsic_PossiblyWrappedArrayBufferByteLength<SharedArrayBufferObject>, 1,0), JS_FN("SharedArrayBufferCopyData", - intrinsic_ArrayBufferCopyData<SharedArrayBufferObject>, 5,0), + intrinsic_ArrayBufferCopyData<SharedArrayBufferObject>, 6,0), JS_FN("IsUint8TypedArray", intrinsic_IsUint8TypedArray, 1,0), JS_FN("IsInt8TypedArray", intrinsic_IsInt8TypedArray, 1,0), @@ -2489,6 +2490,9 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("CallWeakSetMethodIfWrapped", CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0), + JS_FN("IsReadableStreamBYOBRequest", + intrinsic_IsInstanceOfBuiltin<ReadableStreamBYOBRequest>, 1, 0), + // See builtin/TypedObject.h for descriptors of the typedobj functions. JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0), JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0), diff --git a/js/src/vm/SharedArrayObject.cpp b/js/src/vm/SharedArrayObject.cpp index 8e55001d13..44fe3b790d 100644 --- a/js/src/vm/SharedArrayObject.cpp +++ b/js/src/vm/SharedArrayObject.cpp @@ -331,15 +331,16 @@ SharedArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSi } /* static */ void -SharedArrayBufferObject::copyData(Handle<SharedArrayBufferObject*> toBuffer, - Handle<SharedArrayBufferObject*> fromBuffer, - uint32_t fromIndex, uint32_t count) +SharedArrayBufferObject::copyData(Handle<SharedArrayBufferObject*> toBuffer, uint32_t toIndex, + Handle<SharedArrayBufferObject*> fromBuffer, uint32_t fromIndex, + uint32_t count) { MOZ_ASSERT(toBuffer->byteLength() >= count); + MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count); MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex); MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count); - jit::AtomicOperations::memcpySafeWhenRacy(toBuffer->dataPointerShared(), + jit::AtomicOperations::memcpySafeWhenRacy(toBuffer->dataPointerShared() + toIndex, fromBuffer->dataPointerShared() + fromIndex, count); } diff --git a/js/src/vm/SharedArrayObject.h b/js/src/vm/SharedArrayObject.h index 05db688184..19048336cb 100644 --- a/js/src/vm/SharedArrayObject.h +++ b/js/src/vm/SharedArrayObject.h @@ -146,9 +146,9 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared static void addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf, JS::ClassInfo* info); - static void copyData(Handle<SharedArrayBufferObject*> toBuffer, - Handle<SharedArrayBufferObject*> fromBuffer, - uint32_t fromIndex, uint32_t count); + static void copyData(Handle<SharedArrayBufferObject*> toBuffer, uint32_t toIndex, + Handle<SharedArrayBufferObject*> fromBuffer, uint32_t fromIndex, + uint32_t count); SharedArrayRawBuffer* rawBufferObject() const; |