summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorBrian Smith <brian@dbsoft.org>2023-09-27 16:00:10 -0500
committerBrian Smith <brian@dbsoft.org>2023-09-27 16:00:10 -0500
commit8ce17f02ee4d9b40a576911c4bb82fa4802fb167 (patch)
treefa990df784d806638e9ac030b491c9af942c4b5c /js
parent582fd0dd81af880f7f71dfb1fc9e7f8df448a56e (diff)
downloaduxp-8ce17f02ee4d9b40a576911c4bb82fa4802fb167.tar.gz
Issue #1442 - Part 3: Implement ReadableStream and associated classes in the JS engine.
https://bugzilla.mozilla.org/show_bug.cgi?id=1272697
Diffstat (limited to 'js')
-rw-r--r--js/src/builtin/Stream.cpp4957
-rw-r--r--js/src/builtin/Stream.h118
-rw-r--r--js/src/builtin/TestingFunctions.cpp12
-rw-r--r--js/src/js.msg29
-rw-r--r--js/src/jsfriendapi.h6
-rw-r--r--js/src/jsnum.cpp11
-rw-r--r--js/src/jsnum.h4
-rw-r--r--js/src/jsprototypes.h11
-rw-r--r--js/src/moz.build1
-rw-r--r--js/src/vm/ArrayBufferObject.cpp11
-rw-r--r--js/src/vm/CommonPropertyNames.h7
-rw-r--r--js/src/vm/GlobalObject.cpp11
-rw-r--r--js/src/vm/SelfHosting.cpp5
13 files changed, 5178 insertions, 5 deletions
diff --git a/js/src/builtin/Stream.cpp b/js/src/builtin/Stream.cpp
new file mode 100644
index 0000000000..8eae95bcf7
--- /dev/null
+++ b/js/src/builtin/Stream.cpp
@@ -0,0 +1,4957 @@
+/* -*- 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 "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
+};
+
+enum BYOBRequestSlots {
+ BYOBRequestSlot_Controller,
+ BYOBRequestSlot_View,
+ BYOBRequestSlotCount
+};
+
+template<class T>
+MOZ_ALWAYS_INLINE bool
+Is(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 bool
+IsReadableStreamReader(const JSObject* reader)
+{
+ return reader->is<ReadableStreamDefaultReader>() ||
+ reader->is<ReadableStreamBYOBReader>();
+}
+
+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));
+}
+
+inline bool
+ReadableStream::readable() const
+{
+ return StreamState(this) & Readable;
+}
+
+inline bool
+ReadableStream::closed() const
+{
+ return StreamState(this) & Closed;
+}
+
+inline bool
+ReadableStream::errored() const
+{
+ return StreamState(this) & Errored;
+}
+
+inline bool
+ReadableStream::disturbed() const
+{
+ return StreamState(this) & Disturbed;
+}
+
+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(ReadableStream* stream)
+{
+ Value controllerVal = stream->getFixedSlot(StreamSlot_Controller);
+ MOZ_ASSERT(IsReadableStreamController(&controllerVal.toObject()));
+ return &controllerVal.toObject().as<NativeObject>();
+}
+
+inline static MOZ_MUST_USE ReadableStream*
+StreamFromReader(const NativeObject* reader)
+{
+ MOZ_ASSERT(IsReadableStreamReader(reader));
+ return &reader->getFixedSlot(ReaderSlot_Stream).toObject().as<ReadableStream>();
+}
+
+inline static MOZ_MUST_USE NativeObject*
+ReaderFromStream(NativeObject* stream)
+{
+ Value readerVal = stream->getFixedSlot(StreamSlot_Reader);
+ MOZ_ASSERT(IsReadableStreamReader(&readerVal.toObject()));
+ return &readerVal.toObject().as<NativeObject>();
+}
+
+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
+RejectWithPendingError(JSContext* cx, Handle<PromiseObject*> promise) {
+ // Not much we can do about uncatchable exceptions, just bail.
+ RootedValue exn(cx);
+ if (!GetAndClearException(cx, &exn))
+ return false;
+ return PromiseObject::reject(cx, promise, exn);
+}
+
+static MOZ_MUST_USE bool
+ReturnPromiseRejectedWithPendingError(JSContext* cx, const CallArgs& args)
+{
+ JSObject* promise = PromiseRejectedWithPendingError(cx);
+ if (!promise)
+ return false;
+
+ args.rval().setObject(*promise);
+ return true;
+}
+
+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) \
+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), \
+ JS_NULL_CLASS_OPS, \
+ &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)
+{
+ Rooted<ReadableStream*> stream(cx, NewBuiltinClassInstance<ReadableStream>(cx));
+ if (!stream)
+ return nullptr;
+
+ // Step 1 (reordered): 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)
+{
+
+ Rooted<ReadableStream*> stream(cx, createStream(cx));
+ if (!stream)
+ return nullptr;
+
+ // Step 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)
+{
+
+ Rooted<ReadableStream*> stream(cx, createStream(cx));
+ if (!stream)
+ return nullptr;
+
+ // Step 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;
+}
+
+// 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, &notByteStream))
+ return false;
+
+ // Step 7 & 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) {
+ stream = createByteStream(cx, underlyingSource, highWaterMark);
+ } else if (typeVal.isUndefined()) {
+ // Step 8: Otherwise, if type is undefined,
+ 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;
+}
+
+static MOZ_ALWAYS_INLINE bool
+IsReadableStreamLocked(ReadableStream* stream);
+
+// 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(IsReadableStreamLocked(stream));
+ 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);
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamCancel(JSContext* cx, Handle<ReadableStream*> stream, HandleValue reason);
+
+// 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 (IsReadableStreamLocked(stream)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_NOT_LOCKED, "cancel");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamCancel(this, reason).
+ RootedObject cancelPromise(cx, ReadableStreamCancel(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, &notByob))
+ 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);
+
+// 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 )
+static MOZ_ALWAYS_INLINE bool
+IsReadableStreamDisturbed(ReadableStream* stream)
+{
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+ // Step 2: Return stream.[[disturbed]].
+ return stream->disturbed();
+}
+
+// Streams spec, 3.3.5. IsReadableStreamLocked ( stream )
+static MOZ_ALWAYS_INLINE bool
+IsReadableStreamLocked(ReadableStream* stream)
+{
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+ // Step 2: If stream.[[reader]] is undefined, return false.
+ // Step 3: Return true.
+ return !stream->getFixedSlot(StreamSlot_Reader).isUndefined();
+}
+
+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*
+ReadableStreamDefaultReaderRead(JSContext* cx, HandleNativeObject reader);
+
+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, ReadableStreamDefaultReaderRead(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, ReadableStreamCancel(cx, stream, compositeReasonVal));
+ if (!cancelResult) {
+ if (!RejectWithPendingError(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 bool
+ReadableStreamClose(JSContext* cx, Handle<ReadableStream*> stream);
+
+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;
+}
+
+// Streams spec, 3.4.3. ReadableStreamCancel ( stream, reason )
+static MOZ_MUST_USE JSObject*
+ReadableStreamCancel(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 (!ReadableStreamClose(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 )
+static MOZ_MUST_USE bool
+ReadableStreamClose(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());
+ return ResolvePromise(cx, closedPromise, UndefinedHandleValue);
+}
+
+// Streams spec, 3.4.5. ReadableStreamError ( stream, e )
+static MOZ_MUST_USE bool
+ReadableStreamError(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>());
+ return PromiseObject::reject(cx, closedPromise, e);
+}
+
+// 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(NativeObject* stream)
+{
+ MOZ_ASSERT(stream->is<ReadableStream>());
+
+ // Step 1: Return the number of elements in
+ // stream.[[reader]].[[readRequests]].
+ Value readerVal = stream->getFixedSlot(StreamSlot_Reader);
+ NativeObject* reader = &readerVal.toObject().as<NativeObject>();
+ MOZ_ASSERT(reader->is<ReadableStreamDefaultReader>() ||
+ reader->is<ReadableStreamBYOBReader>());
+ 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 (IsReadableStreamLocked(stream)) {
+ 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 (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ 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.
+ RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
+ if (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamDefaultReaderRead(this).
+ JSObject* readPromise = ReadableStreamDefaultReaderRead(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 (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ 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);
+
+
+// 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);
+ return nullptr;
+ }
+
+ // Step 3: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
+ // exception.
+ if (IsReadableStreamLocked(stream)) {
+ 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 (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ 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;
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamBYOBReaderRead(JSContext* cx, HandleNativeObject reader,
+ Handle<TypedArrayObject*> view);
+
+// 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.
+ RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
+ if (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ 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<TypedArrayObject*> view(cx, &viewVal.toObject().as<TypedArrayObject>());
+
+ // 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 (view->length() == 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW);
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 6: Return ! ReadableStreamBYOBReaderRead(this, view).
+ JSObject* readPromise = ReadableStreamBYOBReaderRead(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 (reader->getFixedSlot(ReaderSlot_Stream).isUndefined()) {
+ 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);
+
+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);
+}
+
+// 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,
+ HandleNativeObject view);
+
+// Streams spec, 3.7.6. ReadableStreamBYOBReaderRead ( reader, view )
+static MOZ_MUST_USE JSObject*
+ReadableStreamBYOBReaderRead(JSContext* cx, HandleNativeObject reader,
+ Handle<TypedArrayObject*> 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 )
+static MOZ_MUST_USE JSObject*
+ReadableStreamDefaultReaderRead(JSContext* cx, HandleNativeObject reader)
+{
+ MOZ_ASSERT(reader->is<ReadableStreamDefaultReader>());
+
+ // 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 (!stream->getFixedSlot(StreamSlot_Controller).isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_CONTROLLER_SET);
+ return false;
+ }
+
+ 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);
+
+// 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>();
+
+ // 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: 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]].
+ ReadableStream* stream = StreamFromController(controller);
+
+ // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
+ if (!stream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close");
+ 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);
+
+/**
+ * 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);
+ }
+
+ 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 (!ReadableStreamClose(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);
+
+// 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 {
+ 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 (IsReadableStreamLocked(stream) && 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 ReadableStreamClose(cx, stream);
+
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue chunk, bool done);
+
+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 (IsReadableStreamLocked(stream) && 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
+ReadableStreamError(JSContext* cx, Handle<ReadableStream*> stream, HandleValue e);
+
+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 ReadableStreamError(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;
+}
+
+// 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 (!stream->getFixedSlot(StreamSlot_Controller).isUndefined()) {
+ 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;
+}
+
+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>();
+
+ // 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.
+ if (!StreamFromController(controller)->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close");
+ 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);
+ 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
+};
+
+CLASS_SPEC(ReadableByteStreamController, 3, 9, ClassSpec::DontDefineConstructor);
+
+// 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);
+
+ // 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);
+
+ // Step 3.d: Set this.[[queueTotalSize]] to this.[[queueTotalSize]] − entry.[[byteLength]].
+ uint32_t byteLength = entry->byteLength();
+ queueTotalSize = queueTotalSize - byteLength;
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(queueTotalSize));
+
+ // Step 3.e: Perform ! ReadableByteStreamControllerHandleQueueDrain(this).
+ if (!ReadableByteStreamControllerHandleQueueDrain(cx, controller))
+ return nullptr;
+
+ // Step 3.f: Let view be ! Construct(%Uint8Array%, « entry.[[buffer]],
+ // entry.[[byteOffset]], entry.[[byteLength]] »).
+ RootedObject buffer(cx, entry->buffer());
+
+ uint32_t byteOffset = entry->byteOffset();
+ RootedObject view(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, byteOffset, byteLength));
+ if (!view)
+ 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);
+ 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);
+
+// 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 ReadableStreamClose(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());
+
+ // Step 4: Let buffer be chunk.[[ViewedArrayBuffer]].
+ bool dummy;
+ RootedObject buffer(cx, JS_GetArrayBufferViewBuffer(cx, chunk, &dummy));
+ if (!buffer)
+ return false;
+
+ // Step 5: Let byteOffset be chunk.[[ByteOffset]].
+ uint32_t byteOffset = JS_GetArrayBufferViewByteOffset(chunk);
+
+ // Step 6: Let byteLength be chunk.[[ByteLength]].
+ uint32_t 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(!IsReadableStreamLocked(stream));
+
+ // 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;
+ }
+
+ // 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 ReadableStreamClose(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,
+ HandleNativeObject view)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+ MOZ_ASSERT(JS_IsArrayBufferViewObject(view));
+
+ // 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);
+
+// 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);
+
+#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;
+}
diff --git a/js/src/builtin/Stream.h b/js/src/builtin/Stream.h
new file mode 100644
index 0000000000..90b61b49af
--- /dev/null
+++ b/js/src/builtin/Stream.h
@@ -0,0 +1,118 @@
+/* -*- 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 "vm/NativeObject.h"
+
+namespace js {
+
+class AutoSetNewObjectMetadata;
+
+class ReadableStream : public NativeObject
+{
+ public:
+ static ReadableStream* createDefaultStream(JSContext* cx, HandleValue underlyingSource,
+ HandleValue size, HandleValue highWaterMark);
+ static ReadableStream* createByteStream(JSContext* cx, HandleValue underlyingSource,
+ HandleValue highWaterMark);
+
+ inline bool readable() const;
+ inline bool closed() const;
+ inline bool errored() const;
+ inline bool disturbed() const;
+
+ enum State {
+ Readable = 1 << 0,
+ Closed = 1 << 1,
+ Errored = 1 << 2,
+ Disturbed = 1 << 3
+ };
+
+ private:
+ static ReadableStream* createStream(JSContext* cx);
+
+ 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 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 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 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:
+ 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/js.msg b/js/src/js.msg
index e651b10137..3c7f12ae5e 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -613,11 +613,38 @@ 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, "The ReadableStream method '{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, 0, JSEXN_TYPEERR, "ReadableStream.getReader('byob') requires a ReadableByteStreamController.")
+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, "The ReadableStream controller method '{0}' called on a stream already closing.")
+MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, 1, JSEXN_TYPEERR, "The ReadableStream controller method '{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, 0, JSEXN_TYPEERR, "ReadableByteStreamController 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/jsfriendapi.h b/js/src/jsfriendapi.h
index b4c4e15801..76537dc739 100644
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2005,6 +2005,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..adc8aa3e0f 100644
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -384,6 +384,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/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp
index 9e0bf3f4da..6ace034adb 100644
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -1891,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/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index f05a4db9be..2a217e357f 100644
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -35,6 +35,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 +49,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 +61,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 +183,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 +254,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,6 +317,7 @@
macro(prototype, prototype, "prototype") \
macro(proxy, proxy, "proxy") \
macro(public, public_, "public") \
+ macro(pull, pull, "pull") \
macro(raw, raw, "raw") \
macro(reason, reason, "reason") \
macro(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter") \
@@ -347,6 +353,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/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp
index e8e6ad937a..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)
@@ -2490,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),