js/src/builtin/Stream.cpp
author Till Schneidereit <till@tillschneidereit.net>
Mon, 29 May 2017 16:26:32 +0200
changeset 420432 2693a863dabdd783e32e7b8ac6164e25c30fed4c
parent 420431 862ba8683d196e7d194ab72d2a1680ff707c47d3
child 420446 3d6b96a16105bed25679a46d2ea163a538e37998
child 420906 d7d20b3b2f303590edba39d9d86943e6e757a243
permissions -rw-r--r--
Bug 1272697 - Part 4: Add JSAPI functions for working with ReadableStream. r=shu This adds a ton of JSAPI functions for creating and querying the state of ReadableStreams, and support for creating ReadableStream instances whose source is supplied by the embedding. MozReview-Commit-ID: 9uDWOazPaUI

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "builtin/Stream.h"

#include "js/Stream.h"

#include "jscntxt.h"

#include "gc/Heap.h"
#include "vm/SelfHosting.h"

#include "jsobjinlines.h"

#include "vm/NativeObject-inl.h"

using namespace js;

enum StreamSlots {
    StreamSlot_Controller,
    StreamSlot_Reader,
    StreamSlot_State,
    StreamSlot_StoredError,
    StreamSlotCount
};

enum ReaderSlots {
    ReaderSlot_Stream,
    ReaderSlot_Requests,
    ReaderSlot_ClosedPromise,
    ReaderSlotCount,
};

enum ReaderType {
    ReaderType_Default,
    ReaderType_BYOB
};

// ReadableStreamDefaultController and ReadableByteStreamController are both
// queue containers and must have these slots at identical offsets.
enum QueueContainerSlots {
    QueueContainerSlot_Queue,
    QueueContainerSlot_TotalSize,
    QueueContainerSlotCount
};

// These slots are identical between the two types of ReadableStream
// controllers.
enum ControllerSlots {
    ControllerSlot_Stream = QueueContainerSlotCount,
    ControllerSlot_UnderlyingSource,
    ControllerSlot_StrategyHWM,
    ControllerSlot_Flags,
    ControllerSlotCount
};

enum DefaultControllerSlots {
    DefaultControllerSlot_StrategySize = ControllerSlotCount,
    DefaultControllerSlotCount
};

enum ByteControllerSlots {
    ByteControllerSlot_BYOBRequest = ControllerSlotCount,
    ByteControllerSlot_PendingPullIntos,
    ByteControllerSlot_AutoAllocateSize,
    ByteControllerSlotCount
};

enum ControllerFlags {
    ControllerFlag_Started        = 1 << 0,
    ControllerFlag_Pulling        = 1 << 1,
    ControllerFlag_PullAgain      = 1 << 2,
    ControllerFlag_CloseRequested = 1 << 3,
    ControllerFlag_TeeBranch      = 1 << 4,
    ControllerFlag_TeeBranch1     = 1 << 5,
    ControllerFlag_TeeBranch2     = 1 << 6,
    ControllerFlag_ExternalSource = 1 << 7,
    ControllerFlag_SourceLocked   = 1 << 8,
};

// Offset at which embedding flags are stored.
constexpr uint8_t ControllerEmbeddingFlagsOffset = 24;

enum BYOBRequestSlots {
    BYOBRequestSlot_Controller,
    BYOBRequestSlot_View,
    BYOBRequestSlotCount
};

template<class T>
MOZ_ALWAYS_INLINE bool
Is(const HandleValue v)
{
    return v.isObject() && v.toObject().is<T>();
}

#ifdef DEBUG
static bool
IsReadableStreamController(const JSObject* controller)
{
    return controller->is<ReadableStreamDefaultController>() ||
           controller->is<ReadableByteStreamController>();
}
#endif // DEBUG

static inline uint32_t
ControllerFlags(const NativeObject* controller)
{
    MOZ_ASSERT(IsReadableStreamController(controller));
    return controller->getFixedSlot(ControllerSlot_Flags).toInt32();
}

static inline void
AddControllerFlags(NativeObject* controller, uint32_t flags)
{
    MOZ_ASSERT(IsReadableStreamController(controller));
    controller->setFixedSlot(ControllerSlot_Flags,
                             Int32Value(ControllerFlags(controller) | flags));
}

static inline void
RemoveControllerFlags(NativeObject* controller, uint32_t flags)
{
    MOZ_ASSERT(IsReadableStreamController(controller));
    controller->setFixedSlot(ControllerSlot_Flags,
                             Int32Value(ControllerFlags(controller) & ~flags));
}

static inline uint32_t
StreamState(const ReadableStream* stream)
{
    return stream->getFixedSlot(StreamSlot_State).toInt32();
}

static inline void
SetStreamState(ReadableStream* stream, uint32_t state)
{
    MOZ_ASSERT_IF(stream->disturbed(), state & ReadableStream::Disturbed);
    MOZ_ASSERT_IF(stream->closed() || stream->errored(), !(state & ReadableStream::Readable));
    stream->setFixedSlot(StreamSlot_State, Int32Value(state));
}

bool
ReadableStream::readable() const
{
    return StreamState(this) & Readable;
}

bool
ReadableStream::closed() const
{
    return StreamState(this) & Closed;
}

bool
ReadableStream::errored() const
{
    return StreamState(this) & Errored;
}

bool
ReadableStream::disturbed() const
{
    return StreamState(this) & Disturbed;
}

inline static bool
ReaderHasStream(const NativeObject* reader)
{
    MOZ_ASSERT(JS::IsReadableStreamReader(reader));
    return !reader->getFixedSlot(ReaderSlot_Stream).isUndefined();
}

bool
js::ReadableStreamReaderIsClosed(const JSObject* reader)
{
    return !ReaderHasStream(&reader->as<NativeObject>());
}

inline static MOZ_MUST_USE ReadableStream*
StreamFromController(const NativeObject* controller)
{
    MOZ_ASSERT(IsReadableStreamController(controller));
    return &controller->getFixedSlot(ControllerSlot_Stream).toObject().as<ReadableStream>();
}

inline static MOZ_MUST_USE NativeObject*
ControllerFromStream(const ReadableStream* stream)
{
    Value controllerVal = stream->getFixedSlot(StreamSlot_Controller);
    MOZ_ASSERT(IsReadableStreamController(&controllerVal.toObject()));
    return &controllerVal.toObject().as<NativeObject>();
}

inline static bool
HasController(const ReadableStream* stream)
{
    return !stream->getFixedSlot(StreamSlot_Controller).isUndefined();
}

JS::ReadableStreamMode
ReadableStream::mode() const
{
    NativeObject* controller = ControllerFromStream(this);
    if (controller->is<ReadableStreamDefaultController>())
        return JS::ReadableStreamMode::Default;
    return controller->as<ReadableByteStreamController>().hasExternalSource()
           ? JS::ReadableStreamMode::ExternalSource
           : JS::ReadableStreamMode::Byte;
}

inline static MOZ_MUST_USE ReadableStream*
StreamFromReader(const NativeObject* reader)
{
    MOZ_ASSERT(ReaderHasStream(reader));
    return &reader->getFixedSlot(ReaderSlot_Stream).toObject().as<ReadableStream>();
}

inline static MOZ_MUST_USE NativeObject*
ReaderFromStream(const NativeObject* stream)
{
    Value readerVal = stream->getFixedSlot(StreamSlot_Reader);
    MOZ_ASSERT(JS::IsReadableStreamReader(&readerVal.toObject()));
    return &readerVal.toObject().as<NativeObject>();
}

inline static bool
HasReader(const ReadableStream* stream)
{
    return !stream->getFixedSlot(StreamSlot_Reader).isUndefined();
}

inline static MOZ_MUST_USE JSFunction*
NewHandler(JSContext *cx, Native handler, HandleObject target)
{
    RootedAtom funName(cx, cx->names().empty);
    RootedFunction handlerFun(cx, NewNativeFunction(cx, handler, 0, funName,
                                                    gc::AllocKind::FUNCTION_EXTENDED,
                                                    GenericObject));
    if (!handlerFun)
        return nullptr;
    handlerFun->setExtendedSlot(0, ObjectValue(*target));
    return handlerFun;
}

template<class T>
inline static MOZ_MUST_USE T*
TargetFromHandler(JSObject& handler)
{
    return &handler.as<JSFunction>().getExtendedSlot(0).toObject().as<T>();
}

inline static MOZ_MUST_USE bool
ResetQueue(JSContext* cx, HandleNativeObject container);

inline static MOZ_MUST_USE bool
InvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg,
             MutableHandleValue rval);

static MOZ_MUST_USE JSObject*
PromiseInvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg);

static MOZ_MUST_USE JSObject*
PromiseRejectedWithPendingError(JSContext* cx) {
    // Not much we can do about uncatchable exceptions, just bail.
    RootedValue exn(cx);
    if (!GetAndClearException(cx, &exn))
        return nullptr;
    return PromiseObject::unforgeableReject(cx, exn);
}

static bool
ReportArgTypeError(JSContext* cx, const char* funName, const char* expectedType,
                   HandleValue arg)
{
    UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, arg, nullptr);
    if (!bytes)
        return false;

    return JS_ReportErrorFlagsAndNumberLatin1(cx, JSREPORT_ERROR, GetErrorMessage,
                                              nullptr, JSMSG_NOT_EXPECTED_TYPE,
                                              funName, expectedType, bytes.get());
}

static MOZ_MUST_USE bool
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>());
    if (!list->tryShiftDenseElements(1)) {
        list->moveDenseElements(0, 1, length - 1);
        list->shrinkElements(cx, length - 1);
    }

    list->setDenseInitializedLength(length - 1);

    return entry;
}

class ByteStreamChunk : public NativeObject
{
  private:
    enum Slots {
        Slot_Buffer = 0,
        Slot_ByteOffset,
        Slot_ByteLength,
        SlotCount
    };

  public:
    static const Class class_;

    ArrayBufferObject* buffer() {
        return &getFixedSlot(Slot_Buffer).toObject().as<ArrayBufferObject>();
    }
    uint32_t byteOffset() { return getFixedSlot(Slot_ByteOffset).toInt32(); }
    void SetByteOffset(uint32_t offset) {
        setFixedSlot(Slot_ByteOffset, Int32Value(offset));
    }
    uint32_t byteLength() { return getFixedSlot(Slot_ByteLength).toInt32(); }
    void SetByteLength(uint32_t length) {
        setFixedSlot(Slot_ByteLength, Int32Value(length));
    }

    static ByteStreamChunk* create(JSContext* cx, HandleObject buffer, uint32_t byteOffset,
                                   uint32_t byteLength)
   {
        Rooted<ByteStreamChunk*> chunk(cx, NewObjectWithClassProto<ByteStreamChunk>(cx));
        if (!chunk)
            return nullptr;

        chunk->setFixedSlot(Slot_Buffer, ObjectValue(*buffer));
        chunk->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset));
        chunk->setFixedSlot(Slot_ByteLength, Int32Value(byteLength));
        return chunk;
   }
};

const Class ByteStreamChunk::class_ = {
    "ByteStreamChunk",
    JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
};

class PullIntoDescriptor : public NativeObject
{
  private:
    enum Slots {
        Slot_buffer,
        Slot_ByteOffset,
        Slot_ByteLength,
        Slot_BytesFilled,
        Slot_ElementSize,
        Slot_Ctor,
        Slot_ReaderType,
        SlotCount
    };
  public:
    static const Class class_;

    ArrayBufferObject* buffer() {
        return &getFixedSlot(Slot_buffer).toObject().as<ArrayBufferObject>();
    }
    void setBuffer(ArrayBufferObject* buffer) { setFixedSlot(Slot_buffer, ObjectValue(*buffer)); }
    JSObject* ctor() { return getFixedSlot(Slot_Ctor).toObjectOrNull(); }
    uint32_t byteOffset() const { return getFixedSlot(Slot_ByteOffset).toInt32(); }
    uint32_t byteLength() const { return getFixedSlot(Slot_ByteLength).toInt32(); }
    uint32_t bytesFilled() const { return getFixedSlot(Slot_BytesFilled).toInt32(); }
    void setBytesFilled(int32_t bytes) { setFixedSlot(Slot_BytesFilled, Int32Value(bytes)); }
    uint32_t elementSize() const { return getFixedSlot(Slot_ElementSize).toInt32(); }
    uint32_t readerType() const { return getFixedSlot(Slot_ReaderType).toInt32(); }

    static PullIntoDescriptor* create(JSContext* cx, HandleArrayBufferObject buffer,
                                      uint32_t byteOffset, uint32_t byteLength,
                                      uint32_t bytesFilled, uint32_t elementSize,
                                      HandleObject ctor, uint32_t readerType)
   {
        Rooted<PullIntoDescriptor*> descriptor(cx, NewObjectWithClassProto<PullIntoDescriptor>(cx));
        if (!descriptor)
            return nullptr;

        descriptor->setFixedSlot(Slot_buffer, ObjectValue(*buffer));
        descriptor->setFixedSlot(Slot_Ctor, ObjectOrNullValue(ctor));
        descriptor->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset));
        descriptor->setFixedSlot(Slot_ByteLength, Int32Value(byteLength));
        descriptor->setFixedSlot(Slot_BytesFilled, Int32Value(bytesFilled));
        descriptor->setFixedSlot(Slot_ElementSize, Int32Value(elementSize));
        descriptor->setFixedSlot(Slot_ReaderType, Int32Value(readerType));
        return descriptor;
   }
};

const Class PullIntoDescriptor::class_ = {
    "PullIntoDescriptor",
    JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
};

class QueueEntry : public NativeObject
{
  private:
    enum Slots {
        Slot_Value = 0,
        Slot_Size,
        SlotCount
    };

  public:
    static const Class class_;

    Value value() { return getFixedSlot(Slot_Value); }
    double size() { return getFixedSlot(Slot_Size).toNumber(); }

    static QueueEntry* create(JSContext* cx, HandleValue value, double size)
   {
        Rooted<QueueEntry*> entry(cx, NewObjectWithClassProto<QueueEntry>(cx));
        if (!entry)
            return nullptr;

        entry->setFixedSlot(Slot_Value, value);
        entry->setFixedSlot(Slot_Size, NumberValue(size));

        return entry;
   }
};

const Class QueueEntry::class_ = {
    "QueueEntry",
    JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
};

class TeeState : public NativeObject
{
  private:
    enum Slots {
        Slot_Flags = 0,
        Slot_Reason1,
        Slot_Reason2,
        Slot_Promise,
        Slot_Stream,
        Slot_Branch1,
        Slot_Branch2,
        SlotCount
    };

    enum Flags
    {
        Flag_ClosedOrErrored = 1 << 0,
        Flag_Canceled1 =       1 << 1,
        Flag_Canceled2 =       1 << 2,
        Flag_CloneForBranch2 = 1 << 3,
    };
    uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
    void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); }

  public:
    static const Class class_;

    bool cloneForBranch2() const { return flags() & Flag_CloneForBranch2; }

    bool closedOrErrored() const { return flags() & Flag_ClosedOrErrored; }
    void setClosedOrErrored() {
        MOZ_ASSERT(!(flags() & Flag_ClosedOrErrored));
        setFlags(flags() | Flag_ClosedOrErrored);
    }

    bool canceled1() const { return flags() & Flag_Canceled1; }
    void setCanceled1(HandleValue reason) {
        MOZ_ASSERT(!(flags() & Flag_Canceled1));
        setFlags(flags() | Flag_Canceled1);
        setFixedSlot(Slot_Reason1, reason);
    }

    bool canceled2() const { return flags() & Flag_Canceled2; }
    void setCanceled2(HandleValue reason) {
        MOZ_ASSERT(!(flags() & Flag_Canceled2));
        setFlags(flags() | Flag_Canceled2);
        setFixedSlot(Slot_Reason2, reason);
    }

    Value reason1() const {
        MOZ_ASSERT(canceled1());
        return getFixedSlot(Slot_Reason1);
    }

    Value reason2() const {
        MOZ_ASSERT(canceled2());
        return getFixedSlot(Slot_Reason2);
    }

    PromiseObject* promise() {
        return &getFixedSlot(Slot_Promise).toObject().as<PromiseObject>();
    }
    ReadableStream* stream() {
        return &getFixedSlot(Slot_Stream).toObject().as<ReadableStream>();
    }
    ReadableStreamDefaultReader* reader() {
        return &ReaderFromStream(stream())->as<ReadableStreamDefaultReader>();
    }

    ReadableStreamDefaultController* branch1() {
        ReadableStreamDefaultController* controller = &getFixedSlot(Slot_Branch1).toObject()
                                                       .as<ReadableStreamDefaultController>();
        MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
        MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch1);
        return controller;
    }
    void setBranch1(ReadableStreamDefaultController* controller) {
        MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
        MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch1);
        setFixedSlot(Slot_Branch1, ObjectValue(*controller));
    }

    ReadableStreamDefaultController* branch2() {
        ReadableStreamDefaultController* controller = &getFixedSlot(Slot_Branch2).toObject()
                                                       .as<ReadableStreamDefaultController>();
        MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
        MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch2);
        return controller;
    }
    void setBranch2(ReadableStreamDefaultController* controller) {
        MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
        MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch2);
        setFixedSlot(Slot_Branch2, ObjectValue(*controller));
    }

    static TeeState* create(JSContext* cx, Handle<ReadableStream*> stream) {
        Rooted<TeeState*> state(cx, NewObjectWithClassProto<TeeState>(cx));
        if (!state)
            return nullptr;

        Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
        if (!promise)
            return nullptr;

        state->setFixedSlot(Slot_Flags, Int32Value(0));
        state->setFixedSlot(Slot_Promise, ObjectValue(*promise));
        state->setFixedSlot(Slot_Stream, ObjectValue(*stream));

        return state;
   }
};

const Class TeeState::class_ = {
    "TeeState",
    JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
};

#define CLASS_SPEC(cls, nCtorArgs, nSlots, specFlags, classFlags, classOps) \
const ClassSpec cls::classSpec_ = { \
    GenericCreateConstructor<cls::constructor, nCtorArgs, gc::AllocKind::FUNCTION>, \
    GenericCreatePrototype, \
    nullptr, \
    nullptr, \
    cls##_methods, \
    cls##_properties, \
    nullptr, \
    specFlags \
}; \
\
const Class cls::class_ = { \
    #cls, \
    JSCLASS_HAS_RESERVED_SLOTS(nSlots) | \
    JSCLASS_HAS_CACHED_PROTO(JSProto_##cls) | \
    classFlags, \
    classOps, \
    &cls::classSpec_ \
}; \
\
const Class cls::protoClass_ = { \
    "object", \
    JSCLASS_HAS_CACHED_PROTO(JSProto_##cls), \
    JS_NULL_CLASS_OPS, \
    &cls::classSpec_ \
};

// Streams spec, 3.2.3., steps 1-4.
ReadableStream*
ReadableStream::createStream(JSContext* cx, HandleObject proto /* = nullptr */)
{
    Rooted<ReadableStream*> stream(cx, NewObjectWithClassProto<ReadableStream>(cx, proto));
    if (!stream)
        return nullptr;

    // Step 1: Set this.[[state]] to "readable".
    // Step 2: Set this.[[reader]] and this.[[storedError]] to undefined (implicit).
    // Step 3: Set this.[[disturbed]] to false (implicit).
    // Step 4: Set this.[[readableStreamController]] to undefined (implicit).
    stream->setFixedSlot(StreamSlot_State, Int32Value(Readable));

    return stream;
}

static MOZ_MUST_USE ReadableStreamDefaultController*
CreateReadableStreamDefaultController(JSContext* cx, Handle<ReadableStream*> stream,
                                      HandleValue underlyingSource, HandleValue size,
                                      HandleValue highWaterMarkVal);

// Streams spec, 3.2.3., steps 1-4, 8.
ReadableStream*
ReadableStream::createDefaultStream(JSContext* cx, HandleValue underlyingSource,
                                    HandleValue size, HandleValue highWaterMark,
                                    HandleObject proto /* = nullptr */)
{
    // Steps 1-4.
    Rooted<ReadableStream*> stream(cx, createStream(cx));
    if (!stream)
        return nullptr;

    // Step 8.b: Set this.[[readableStreamController]] to
    //           ? Construct(ReadableStreamDefaultController,
    //                       « this, underlyingSource, size,
    //                         highWaterMark »).
    RootedObject controller(cx, CreateReadableStreamDefaultController(cx, stream,
                                                                      underlyingSource,
                                                                      size,
                                                                      highWaterMark));
    if (!controller)
        return nullptr;

    stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller));

    return stream;
}

static MOZ_MUST_USE ReadableByteStreamController*
CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
                                   HandleValue underlyingByteSource,
                                   HandleValue highWaterMarkVal);

// Streams spec, 3.2.3., steps 1-4, 7.
ReadableStream*
ReadableStream::createByteStream(JSContext* cx, HandleValue underlyingSource,
                                 HandleValue highWaterMark, HandleObject proto /* = nullptr */)
{
    // Steps 1-4.
    Rooted<ReadableStream*> stream(cx, createStream(cx, proto));
    if (!stream)
        return nullptr;

    // Step 7.b: Set this.[[readableStreamController]] to
    //           ? Construct(ReadableByteStreamController,
    //                       « this, underlyingSource, highWaterMark »).
    RootedObject controller(cx, CreateReadableByteStreamController(cx, stream,
                                                                   underlyingSource,
                                                                   highWaterMark));
    if (!controller)
        return nullptr;

    stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller));

    return stream;
}

static MOZ_MUST_USE ReadableByteStreamController*
CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
                                   void* underlyingSource);

ReadableStream*
ReadableStream::createExternalSourceStream(JSContext* cx, void* underlyingSource,
                                           uint8_t flags, HandleObject proto /* = nullptr */)
{
    Rooted<ReadableStream*> stream(cx, createStream(cx, proto));
    if (!stream)
        return nullptr;

    RootedNativeObject controller(cx, CreateReadableByteStreamController(cx, stream,
                                                                         underlyingSource));
    if (!controller)
        return nullptr;

    stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller));
    AddControllerFlags(controller, flags << ControllerEmbeddingFlagsOffset);

    return stream;
}

// Streams spec, 3.2.3.
bool
ReadableStream::constructor(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    RootedValue val(cx, args.get(0));
    RootedValue underlyingSource(cx, args.get(0));
    RootedValue options(cx, args.get(1));

    // Do argument handling first to keep the right order of error reporting.
    if (underlyingSource.isUndefined()) {
        RootedObject sourceObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
        if (!sourceObj)
            return false;
        underlyingSource = ObjectValue(*sourceObj);
    }
    RootedValue size(cx);
    RootedValue highWaterMark(cx);

    if (!options.isUndefined()) {
        if (!GetProperty(cx, options, cx->names().size, &size))
            return false;

        if (!GetProperty(cx, options, cx->names().highWaterMark, &highWaterMark))
            return false;
    }

    if (!ThrowIfNotConstructing(cx, args, "ReadableStream"))
        return false;

    // Step 5: Let type be ? GetV(underlyingSource, "type").
    RootedValue typeVal(cx);
    if (!GetProperty(cx, underlyingSource, cx->names().type, &typeVal))
        return false;

    // Step 6: Let typeString be ? ToString(type).
    RootedString type(cx, ToString<CanGC>(cx, typeVal));
    if (!type)
        return false;

    int32_t notByteStream;
    if (!CompareStrings(cx, type, cx->names().bytes, &notByteStream))
        return false;

    // Step 7.a & 8.a (reordered): If highWaterMark is undefined, let
    //                             highWaterMark be 1 (or 0 for byte streams).
    if (highWaterMark.isUndefined())
        highWaterMark = Int32Value(notByteStream ? 1 : 0);

    Rooted<ReadableStream*> stream(cx);

    // Step 7: If typeString is "bytes",
    if (!notByteStream) {
        // Step 7.b: Set this.[[readableStreamController]] to
        //           ? Construct(ReadableByteStreamController,
        //                       « this, underlyingSource, highWaterMark »).
        stream = createByteStream(cx, underlyingSource, highWaterMark);
    } else if (typeVal.isUndefined()) {
        // Step 8: Otherwise, if type is undefined,
        // Step 8.b: Set this.[[readableStreamController]] to
        //           ? Construct(ReadableStreamDefaultController,
        //                       « this, underlyingSource, size, highWaterMark »).
        stream = createDefaultStream(cx, underlyingSource, size, highWaterMark);
    } else {
        // Step 9: Otherwise, throw a RangeError exception.
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG);
        return false;
    }
    if (!stream)
        return false;

    args.rval().setObject(*stream);
    return true;
}

// Streams spec, 3.2.4.1. get locked
static MOZ_MUST_USE bool
ReadableStream_locked_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());

    // Step 2: Return ! IsReadableStreamLocked(this).
    args.rval().setBoolean(stream->locked());
    return true;
}

static bool
ReadableStream_locked(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_locked_impl>(cx, args);
}

// Streams spec, 3.2.4.2. cancel ( reason )
static MOZ_MUST_USE bool
ReadableStream_cancel(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    // Step 1: If ! IsReadableStream(this) is false, return a promise rejected
    //         with a TypeError exception.
    if (!Is<ReadableStream>(args.thisv())) {
        ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
                          nullptr, "cancel", "");
        return ReturnPromiseRejectedWithPendingError(cx, args);
    }

    Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());

    // Step 2: If ! IsReadableStreamLocked(this) is true, return a promise
    //         rejected with a TypeError exception.
    if (stream->locked()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAM_NOT_LOCKED, "cancel");
        return ReturnPromiseRejectedWithPendingError(cx, args);
    }

    // Step 3: Return ! ReadableStreamCancel(this, reason).
    RootedObject cancelPromise(cx, ReadableStream::cancel(cx, stream, args.get(0)));
    if (!cancelPromise)
        return false;
    args.rval().setObject(*cancelPromise);
    return true;
}

static MOZ_MUST_USE ReadableStreamDefaultReader*
CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> stream);

static MOZ_MUST_USE ReadableStreamBYOBReader*
CreateReadableStreamBYOBReader(JSContext* cx, Handle<ReadableStream*> stream);

// Streams spec, 3.2.4.3. getReader()
static MOZ_MUST_USE bool
ReadableStream_getReader_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());
    RootedObject reader(cx);

    // Step 2: If mode is undefined, return
    //         ? AcquireReadableStreamDefaultReader(this).
    RootedValue modeVal(cx);
    HandleValue optionsVal = args.get(0);
    if (!optionsVal.isUndefined()) {
        if (!GetProperty(cx, optionsVal, cx->names().mode, &modeVal))
            return false;
    }

    if (modeVal.isUndefined()) {
        reader = CreateReadableStreamDefaultReader(cx, stream);
    } else {
        // Step 3: Set mode to ? ToString(mode) (implicit).
        RootedString mode(cx, ToString<CanGC>(cx, modeVal));
        if (!mode)
            return false;

        // Step 4: If mode is "byob", return ? AcquireReadableStreamBYOBReader(this).
        int32_t notByob;
        if (!CompareStrings(cx, mode, cx->names().byob, &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, 0, JS_NULL_CLASS_OPS);

// Streams spec, 3.3.1. AcquireReadableStreamBYOBReader ( stream )
// Always inlined.

// Streams spec, 3.3.2. AcquireReadableStreamDefaultReader ( stream )
// Always inlined.

// Streams spec, 3.3.3. IsReadableStream ( x )
// Using is<T> instead.

// Streams spec, 3.3.4. IsReadableStreamDisturbed ( stream )
// Using stream->disturbed() instead.

// Streams spec, 3.3.5. IsReadableStreamLocked ( stream )
bool
ReadableStream::locked() const
{
    // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
    // Step 2: If stream.[[reader]] is undefined, return false.
    // Step 3: Return true.
    // Special-casing for streams with external sources. Those can be locked
    // explicitly via JSAPI, which is indicated by a controller flag.
    // IsReadableStreamLocked is called from the controller's constructor, at
    // which point we can't yet call ControllerFromStream(stream), but the
    // source also can't be locked yet.
    if (HasController(this) &&
        (ControllerFlags(ControllerFromStream(this)) & ControllerFlag_SourceLocked))
    {
        return true;
    }
    return HasReader(this);
}

static MOZ_MUST_USE bool
ReadableStreamDefaultControllerClose(JSContext* cx,
                                     Handle<ReadableStreamDefaultController*> controller);
static MOZ_MUST_USE bool
ReadableStreamDefaultControllerEnqueue(JSContext* cx,
                                       Handle<ReadableStreamDefaultController*> controller,
                                       HandleValue chunk);

static bool
TeeReaderReadHandler(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args.callee()));
    HandleValue resultVal = args.get(0);

    // Step a: Assert: Type(result) is Object.
    RootedObject result(cx, &resultVal.toObject());

    // Step b: Let value be ? Get(result, "value").
    RootedValue value(cx);
    if (!GetPropertyPure(cx, result, NameToId(cx->names().value), value.address()))
        return false;

    // Step c: Let done be ? Get(result, "done").
    RootedValue doneVal(cx);
    if (!GetPropertyPure(cx, result, NameToId(cx->names().done), doneVal.address()))
        return false;

    // Step d: Assert: Type(done) is Boolean.
    bool done = doneVal.toBoolean();

    // Step e: If done is true and teeState.[[closedOrErrored]] is false,
    if (done && !teeState->closedOrErrored()) {
        // Step i: If teeState.[[canceled1]] is false,
        if (!teeState->canceled1()) {
            // Step 1: Perform ! ReadableStreamDefaultControllerClose(branch1).
            Rooted<ReadableStreamDefaultController*> branch1(cx, teeState->branch1());
            if (!ReadableStreamDefaultControllerClose(cx, branch1))
                return false;
        }

        // Step ii: If teeState.[[canceled2]] is false,
        if (!teeState->canceled2()) {
            // Step 1: Perform ! ReadableStreamDefaultControllerClose(branch1).
            Rooted<ReadableStreamDefaultController*> branch2(cx, teeState->branch2());
            if (!ReadableStreamDefaultControllerClose(cx, branch2))
                return false;
        }

        // Step iii: Set teeState.[[closedOrErrored]] to true.
        teeState->setClosedOrErrored();
    }

    // Step f: If teeState.[[closedOrErrored]] is true, return.
    if (teeState->closedOrErrored())
        return true;

    // Step g: Let value1 and value2 be value.
    RootedValue value1(cx, value);
    RootedValue value2(cx, value);

    // Step h: If teeState.[[canceled2]] is false and cloneForBranch2 is
    //         true, set value2 to
    //         ? StructuredDeserialize(StructuredSerialize(value2),
    //                                 the current Realm Record).
    // TODO: add StructuredClone() intrinsic.
    MOZ_ASSERT(!teeState->cloneForBranch2(), "tee(cloneForBranch2=true) should not be exposed");

    // Step i: If teeState.[[canceled1]] is false, perform
    //         ? ReadableStreamDefaultControllerEnqueue(branch1, value1).
    Rooted<ReadableStreamDefaultController*> controller(cx);
    if (!teeState->canceled1()) {
        controller = teeState->branch1();
        if (!ReadableStreamDefaultControllerEnqueue(cx, controller, value1))
            return false;
    }

    // Step j: If teeState.[[canceled2]] is false,
    //         perform ? ReadableStreamDefaultControllerEnqueue(branch2, value2).
    if (!teeState->canceled2()) {
        controller = teeState->branch2();
        if (!ReadableStreamDefaultControllerEnqueue(cx, controller, value2))
            return false;
    }

    args.rval().setUndefined();
    return true;
}

static MOZ_MUST_USE JSObject*
ReadableStreamTee_Pull(JSContext* cx, Handle<TeeState*> teeState,
                       Handle<ReadableStream*> branchStream)
{
    // Step 1: Let reader be F.[[reader]], branch1 be F.[[branch1]],
    //         branch2 be F.[[branch2]], teeState be F.[[teeState]], and
    //         cloneForBranch2 be F.[[cloneForBranch2]].

    // Step 2: Return the result of transforming
    //         ! ReadableStreamDefaultReaderRead(reader) by a fulfillment
    //         handler which takes the argument result and performs the
    //         following steps:
    Rooted<ReadableStreamDefaultReader*> reader(cx, teeState->reader());
    RootedObject readPromise(cx, ReadableStreamDefaultReader::read(cx, reader));
    if (!readPromise)
        return nullptr;

    RootedObject onFulfilled(cx, NewHandler(cx, TeeReaderReadHandler, teeState));
    if (!onFulfilled)
        return nullptr;

    return JS::CallOriginalPromiseThen(cx, readPromise, onFulfilled, nullptr);
}

static MOZ_MUST_USE JSObject*
ReadableStreamTee_Cancel(JSContext* cx, Handle<TeeState*> teeState,
                         Handle<ReadableStreamDefaultController*> branch, HandleValue reason)
{
    // Step 1: Let stream be F.[[stream]] and teeState be F.[[teeState]].
    Rooted<ReadableStream*> stream(cx, teeState->stream());

    bool bothBranchesCanceled = false;

    // Step 2: Set teeState.[[canceled1]] to true.
    // Step 3: Set teeState.[[reason1]] to reason.
    if (ControllerFlags(branch) & ControllerFlag_TeeBranch1) {
        teeState->setCanceled1(reason);
        bothBranchesCanceled = teeState->canceled2();
    } else {
        MOZ_ASSERT(ControllerFlags(branch) & ControllerFlag_TeeBranch2);
        teeState->setCanceled2(reason);
        bothBranchesCanceled = teeState->canceled1();
    }

    // Step 4: If teeState.[[canceled1]] is true,
    // Step 4: If teeState.[[canceled2]] is true,
    if (bothBranchesCanceled) {
        // Step a: Let compositeReason be
        //         ! CreateArrayFromList(« teeState.[[reason1]], teeState.[[reason2]] »).
        RootedNativeObject compositeReason(cx, NewDenseFullyAllocatedArray(cx, 2));
        if (!compositeReason)
            return nullptr;

        compositeReason->setDenseInitializedLength(2);
        compositeReason->initDenseElement(0, teeState->reason1());
        compositeReason->initDenseElement(1, teeState->reason2());
        RootedValue compositeReasonVal(cx, ObjectValue(*compositeReason));

        Rooted<PromiseObject*> promise(cx, teeState->promise());

        // Step b: Let cancelResult be ! ReadableStreamCancel(stream, compositeReason).
        RootedObject cancelResult(cx, ReadableStream::cancel(cx, stream, compositeReasonVal));
        if (!cancelResult) {
            if (!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 JSObject*
ReadableStreamControllerCancelSteps(JSContext* cx,
                                    HandleNativeObject controller, HandleValue reason);

// Used for transforming the result of promise fulfillment/rejection.
static bool
ReturnUndefined(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    args.rval().setUndefined();
    return true;
}

MOZ_MUST_USE bool
ReadableStreamCloseInternal(JSContext* cx, Handle<ReadableStream*> stream);

// Streams spec, 3.4.3. ReadableStreamCancel ( stream, reason )
/* static */ MOZ_MUST_USE JSObject*
ReadableStream::cancel(JSContext* cx, Handle<ReadableStream*> stream, HandleValue reason)
{
    // Step 1: Set stream.[[disturbed]] to true.
    uint32_t state = StreamState(stream) | ReadableStream::Disturbed;
    SetStreamState(stream, state);

    // Step 2: If stream.[[state]] is "closed", return a new promise resolved
    //         with undefined.
    if (stream->closed())
        return PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);

    // Step 3: If stream.[[state]] is "errored", return a new promise rejected
    //         with stream.[[storedError]].
    if (stream->errored()) {
        RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
        return PromiseObject::unforgeableReject(cx, storedError);
    }

    // Step 4: Perform ! ReadableStreamClose(stream).
    if (!ReadableStreamCloseInternal(cx, stream))
        return nullptr;

    // Step 5: Let sourceCancelPromise be
    //         ! stream.[[readableStreamController]].[[CancelSteps]](reason).
    RootedNativeObject controller(cx, ControllerFromStream(stream));
    RootedObject sourceCancelPromise(cx);
    sourceCancelPromise = ReadableStreamControllerCancelSteps(cx, controller, reason);
    if (!sourceCancelPromise)
        return nullptr;

    // Step 6: Return the result of transforming sourceCancelPromise by a
    //         fulfillment handler that returns undefined.
    RootedAtom funName(cx, cx->names().empty);
    RootedFunction returnUndefined(cx, NewNativeFunction(cx, ReturnUndefined, 0, funName));
    if (!returnUndefined)
        return nullptr;
    return JS::CallOriginalPromiseThen(cx, sourceCancelPromise, returnUndefined, nullptr);
}

// Streams spec, 3.4.4. ReadableStreamClose ( stream )
MOZ_MUST_USE bool
ReadableStreamCloseInternal(JSContext* cx, Handle<ReadableStream*> stream)
{
  // Step 1: Assert: stream.[[state]] is "readable".
  MOZ_ASSERT(stream->readable());

  uint32_t state = StreamState(stream);
  // Step 2: Set stream.[[state]] to "closed".
  SetStreamState(stream, (state & ReadableStream::Disturbed) | ReadableStream::Closed);

  // Step 3: Let reader be stream.[[reader]].
  RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));

  // Step 4: If reader is undefined, return.
  if (val.isUndefined())
      return true;

  // Step 5: If ! IsReadableStreamDefaultReader(reader) is true,
  RootedNativeObject reader(cx, &val.toObject().as<NativeObject>());
  if (reader->is<ReadableStreamDefaultReader>()) {
      // Step a: Repeat for each readRequest that is an element of
      //         reader.[[readRequests]],
      val = reader->getFixedSlot(ReaderSlot_Requests);
      if (!val.isUndefined()) {
          RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>());
          uint32_t len = readRequests->getDenseInitializedLength();
          RootedObject readRequest(cx);
          RootedObject resultObj(cx);
          RootedValue resultVal(cx);
          for (uint32_t i = 0; i < len; i++) {
              // Step i: Resolve readRequest.[[promise]] with
              //         ! CreateIterResultObject(undefined, true).
              readRequest = &readRequests->getDenseElement(i).toObject();
              resultObj = CreateIterResultObject(cx, UndefinedHandleValue, true);
              if (!resultObj)
                  return false;
              resultVal = ObjectValue(*resultObj);
              if (!ResolvePromise(cx, readRequest, resultVal))
                  return false;
          }

          // Step b: Set reader.[[readRequests]] to an empty List.
          reader->setFixedSlot(ReaderSlot_Requests, UndefinedValue());
      }
  }

  // Step 6: Resolve reader.[[closedPromise]] with undefined.
  // Step 7: Return (implicit).
  RootedObject closedPromise(cx, &reader->getFixedSlot(ReaderSlot_ClosedPromise).toObject());
  if (!ResolvePromise(cx, closedPromise, UndefinedHandleValue))
      return false;

  if (stream->mode() == JS::ReadableStreamMode::ExternalSource &&
      cx->runtime()->readableStreamClosedCallback)
  {
      NativeObject* controller = ControllerFromStream(stream);
      void* source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
      cx->runtime()->readableStreamClosedCallback(cx, stream, source, stream->embeddingFlags());
  }

  return true;
}

// Streams spec, 3.4.5. ReadableStreamError ( stream, e )
MOZ_MUST_USE bool
ReadableStreamErrorInternal(JSContext* cx, Handle<ReadableStream*> stream, HandleValue e)
{
    // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).

    // Step 2: Assert: stream.[[state]] is "readable".
    MOZ_ASSERT(stream->readable());

    // Step 3: Set stream.[[state]] to "errored".
    uint32_t state = StreamState(stream);
    SetStreamState(stream, (state & ReadableStream::Disturbed) | ReadableStream::Errored);

    // Step 4: Set stream.[[storedError]] to e.
    stream->setFixedSlot(StreamSlot_StoredError, e);

    // Step 5: Let reader be stream.[[reader]].
    RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));

    // Step 6: If reader is undefined, return.
    if (val.isUndefined())
        return true;
    RootedNativeObject reader(cx, &val.toObject().as<NativeObject>());

    // Steps 7,8: (Identical in our implementation.)
    // Step a: Repeat for each readRequest that is an element of
    //         reader.[[readRequests]],
    val = reader->getFixedSlot(ReaderSlot_Requests);
    RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>());
    Rooted<PromiseObject*> readRequest(cx);
    uint32_t len = readRequests->getDenseInitializedLength();
    for (uint32_t i = 0; i < len; i++) {
        // Step i: Reject readRequest.[[promise]] with e.
        val = readRequests->getDenseElement(i);
        readRequest = &val.toObject().as<PromiseObject>();
        if (!PromiseObject::reject(cx, readRequest, e))
            return false;
    }

    // Step b: Set reader.[[readRequests]] to a new empty List.
    if (!SetNewList(cx, reader, ReaderSlot_Requests))
        return false;

    // Step 9: Reject reader.[[closedPromise]] with e.
    val = reader->getFixedSlot(ReaderSlot_ClosedPromise);
    Rooted<PromiseObject*> closedPromise(cx, &val.toObject().as<PromiseObject>());
    if (!PromiseObject::reject(cx, closedPromise, e))
        return false;

    if (stream->mode() == JS::ReadableStreamMode::ExternalSource &&
        cx->runtime()->readableStreamErroredCallback)
    {
        NativeObject* controller = ControllerFromStream(stream);
        void* source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
        cx->runtime()->readableStreamErroredCallback(cx, stream, source,
                                                     stream->embeddingFlags(), e);
    }

    return true;
}

// Streams spec, 3.4.6. ReadableStreamFulfillReadIntoRequest( stream, chunk, done )
// Streams spec, 3.4.7. ReadableStreamFulfillReadRequest ( stream, chunk, done )
// These two spec functions are identical in our implementation.
static MOZ_MUST_USE bool
ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream,
                                           HandleValue chunk, bool done)
{
    // Step 1: Let reader be stream.[[reader]].
    RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
    RootedNativeObject reader(cx, &val.toObject().as<NativeObject>());

    // Step 2: Let readIntoRequest be the first element of
    //         reader.[[readIntoRequests]].
    // Step 3: Remove readIntoRequest from reader.[[readIntoRequests]], shifting
    //         all other elements downward (so that the second becomes the first,
    //         and so on).
    val = reader->getFixedSlot(ReaderSlot_Requests);
    RootedNativeObject readIntoRequests(cx, &val.toObject().as<NativeObject>());
    Rooted<PromiseObject*> readIntoRequest(cx);
    readIntoRequest = ShiftFromList<PromiseObject>(cx, readIntoRequests);
    MOZ_ASSERT(readIntoRequest);

    // Step 4: Resolve readIntoRequest.[[promise]] with
    //         ! CreateIterResultObject(chunk, done).
    RootedObject iterResult(cx, CreateIterResultObject(cx, chunk, done));
    if (!iterResult)
        return false;
    val = ObjectValue(*iterResult);
    return PromiseObject::resolve(cx, readIntoRequest, val);
}

// Streams spec, 3.4.8. ReadableStreamGetNumReadIntoRequests ( stream )
// Streams spec, 3.4.9. ReadableStreamGetNumReadRequests ( stream )
// (Identical implementation.)
static uint32_t
ReadableStreamGetNumReadRequests(ReadableStream* stream)
{
    // Step 1: Return the number of elements in
    //         stream.[[reader]].[[readRequests]].
    if (!HasReader(stream))
        return 0;
    NativeObject* reader = ReaderFromStream(stream);
    Value readRequests = reader->getFixedSlot(ReaderSlot_Requests);
    return readRequests.toObject().as<NativeObject>().getDenseInitializedLength();
}

// Stream spec 3.4.10. ReadableStreamHasBYOBReader ( stream )
static MOZ_MUST_USE bool
ReadableStreamHasBYOBReader(ReadableStream* stream)
{
    // Step 1: Let reader be stream.[[reader]].
    // Step 2: If reader is undefined, return false.
    // Step 3: If ! IsReadableStreamBYOBReader(reader) is false, return false.
    // Step 4: Return true.
    Value reader = stream->getFixedSlot(StreamSlot_Reader);
    return reader.isObject() && reader.toObject().is<ReadableStreamBYOBReader>();
}

// Streap spec 3.4.11. ReadableStreamHasDefaultReader ( stream )
static MOZ_MUST_USE bool
ReadableStreamHasDefaultReader(ReadableStream* stream)
{
    // Step 1: Let reader be stream.[[reader]].
    // Step 2: If reader is undefined, return false.
    // Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
    // Step 4: Return true.
    Value reader = stream->getFixedSlot(StreamSlot_Reader);
    return reader.isObject() && reader.toObject().is<ReadableStreamDefaultReader>();
}

static MOZ_MUST_USE bool
ReadableStreamReaderGenericInitialize(JSContext* cx,
                                      HandleNativeObject reader,
                                      Handle<ReadableStream*> stream);

// Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream )
// Steps 2-4.
static MOZ_MUST_USE ReadableStreamDefaultReader*
CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> stream)
{
    Rooted<ReadableStreamDefaultReader*> reader(cx);
    reader = NewBuiltinClassInstance<ReadableStreamDefaultReader>(cx);
    if (!reader)
        return nullptr;

    // Step 2: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
    //         exception.
    if (stream->locked()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAM_LOCKED);
        return nullptr;
    }

    // Step 3: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
    if (!ReadableStreamReaderGenericInitialize(cx, reader, stream))
        return nullptr;

    // Step 4: Set this.[[readRequests]] to a new empty List.
    if (!SetNewList(cx, reader, ReaderSlot_Requests))
        return nullptr;

    return reader;
}

// Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream )
bool
ReadableStreamDefaultReader::constructor(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultReader"))
        return false;

    // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
    if (!Is<ReadableStream>(args.get(0))) {
        ReportArgTypeError(cx, "ReadableStreamDefaultReader", "ReadableStream",
                           args.get(0));
        return false;
    }

    Rooted<ReadableStream*> stream(cx, &args.get(0).toObject().as<ReadableStream>());

    RootedObject reader(cx, CreateReadableStreamDefaultReader(cx, stream));
    if (!reader)
        return false;

    args.rval().setObject(*reader);
    return true;
}

// Streams spec, 3.5.4.1 get closed
static MOZ_MUST_USE bool
ReadableStreamDefaultReader_closed(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
    //         rejected with a TypeError exception.
    if (!Is<ReadableStreamDefaultReader>(args.thisv()))
        return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "get closed");

    // Step 2: Return this.[[closedPromise]].
    NativeObject* reader = &args.thisv().toObject().as<NativeObject>();
    args.rval().set(reader->getFixedSlot(ReaderSlot_ClosedPromise));
    return true;
}

static MOZ_MUST_USE JSObject*
ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, HandleValue reason);

// Streams spec, 3.5.4.2. cancel ( reason )
static MOZ_MUST_USE bool
ReadableStreamDefaultReader_cancel(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
    //         rejected with a TypeError exception.
    if (!Is<ReadableStreamDefaultReader>(args.thisv()))
        return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "cancel");

    // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
    //         rejected with a TypeError exception.
    RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
    if (!ReaderHasStream(reader)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
        return ReturnPromiseRejectedWithPendingError(cx, args);
    }

    // Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason).
    JSObject* cancelPromise = ReadableStreamReaderGenericCancel(cx, reader, args.get(0));
    if (!cancelPromise)
        return false;
    args.rval().setObject(*cancelPromise);
    return true;
}

// Streams spec, 3.5.4.3 read ( )
static MOZ_MUST_USE bool
ReadableStreamDefaultReader_read(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
    //         rejected with a TypeError exception.
    if (!Is<ReadableStreamDefaultReader>(args.thisv()))
        return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "read");

    // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
    //         rejected with a TypeError exception.
    Rooted<ReadableStreamDefaultReader*> reader(cx);
    reader = &args.thisv().toObject().as<ReadableStreamDefaultReader>();
    if (!ReaderHasStream(reader)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
        return ReturnPromiseRejectedWithPendingError(cx, args);
    }

    // Step 3: Return ! ReadableStreamDefaultReaderRead(this).
    JSObject* readPromise = ReadableStreamDefaultReader::read(cx, reader);
    if (!readPromise)
        return false;
    args.rval().setObject(*readPromise);
    return true;
}

static MOZ_MUST_USE bool
ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader);

// Streams spec, 3.5.4.4. releaseLock ( )
static MOZ_MUST_USE bool
ReadableStreamDefaultReader_releaseLock_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableStreamDefaultReader*> reader(cx);
    reader = &args.thisv().toObject().as<ReadableStreamDefaultReader>();

    // Step 2: If this.[[ownerReadableStream]] is undefined, return.
    if (!ReaderHasStream(reader)) {
        args.rval().setUndefined();
        return true;
    }

    // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception.
    Value val = reader->getFixedSlot(ReaderSlot_Requests);
    if (!val.isUndefined()) {
        NativeObject* readRequests = &val.toObject().as<NativeObject>();
        uint32_t len = readRequests->getDenseInitializedLength();
        if (len != 0) {
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                      JSMSG_READABLESTREAMREADER_NOT_EMPTY,
                                      "releaseLock");
            return false;
        }
    }

    // Step 4: Perform ! ReadableStreamReaderGenericRelease(this).
    return ReadableStreamReaderGenericRelease(cx, reader);
}

static bool
ReadableStreamDefaultReader_releaseLock(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableStreamDefaultReader(this) is false,
    //         throw a TypeError exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableStreamDefaultReader>,
                                ReadableStreamDefaultReader_releaseLock_impl>(cx, args);
}

static const JSFunctionSpec ReadableStreamDefaultReader_methods[] = {
    JS_FN("cancel",         ReadableStreamDefaultReader_cancel,         1, 0),
    JS_FN("read",           ReadableStreamDefaultReader_read,           0, 0),
    JS_FN("releaseLock",    ReadableStreamDefaultReader_releaseLock,    0, 0),
    JS_FS_END
};

static const JSPropertySpec ReadableStreamDefaultReader_properties[] = {
    JS_PSG("closed", ReadableStreamDefaultReader_closed, 0),
    JS_PS_END
};

CLASS_SPEC(ReadableStreamDefaultReader, 1, ReaderSlotCount, ClassSpec::DontDefineConstructor, 0,
           JS_NULL_CLASS_OPS);


// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream )
// Steps 2-5.
static MOZ_MUST_USE ReadableStreamBYOBReader*
CreateReadableStreamBYOBReader(JSContext* cx, Handle<ReadableStream*> stream)
{
    // Step 2: If ! IsReadableByteStreamController(stream.[[readableStreamController]])
    //         is false, throw a TypeError exception.
    if (!ControllerFromStream(stream)->is<ReadableByteStreamController>()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER,
                                  "ReadableStream.getReader('byob')");
        return nullptr;
    }

    // Step 3: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
    //         exception.
    if (stream->locked()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAM_LOCKED);
        return nullptr;
    }

    Rooted<ReadableStreamBYOBReader*> reader(cx);
    reader = NewBuiltinClassInstance<ReadableStreamBYOBReader>(cx);
    if (!reader)
        return nullptr;

    // Step 4: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
    if (!ReadableStreamReaderGenericInitialize(cx, reader, stream))
        return nullptr;

    // Step 5: Set this.[[readIntoRequests]] to a new empty List.
    if (!SetNewList(cx, reader, ReaderSlot_Requests))
        return nullptr;

    return reader;
}

// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream )
bool
ReadableStreamBYOBReader::constructor(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    if (!ThrowIfNotConstructing(cx, args, "ReadableStreamBYOBReader"))
        return false;

    // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
    if (!Is<ReadableStream>(args.get(0))) {
        ReportArgTypeError(cx, "ReadableStreamBYOBReader", "ReadableStream", args.get(0));
        return false;
    }

    Rooted<ReadableStream*> stream(cx, &args.get(0).toObject().as<ReadableStream>());
    RootedObject reader(cx, CreateReadableStreamBYOBReader(cx, stream));
    if (!reader)
        return false;

    args.rval().setObject(*reader);
    return true;
}

// Streams spec, 3.6.4.1 get closed
static MOZ_MUST_USE bool
ReadableStreamBYOBReader_closed(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise
    //         rejected with a TypeError exception.
    if (!Is<ReadableStreamBYOBReader>(args.thisv()))
        return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "get closed");

    // Step 2: Return this.[[closedPromise]].
    NativeObject* reader = &args.thisv().toObject().as<NativeObject>();
    args.rval().set(reader->getFixedSlot(ReaderSlot_ClosedPromise));
    return true;
}

// Streams spec, 3.6.4.2. cancel ( reason )
static MOZ_MUST_USE bool
ReadableStreamBYOBReader_cancel(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise
    //         rejected with a TypeError exception.
    if (!Is<ReadableStreamBYOBReader>(args.thisv()))
        return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "cancel");

    // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
    //         rejected with a TypeError exception.
    RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
    if (!ReaderHasStream(reader)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
        return ReturnPromiseRejectedWithPendingError(cx, args);
    }

    // Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason).
    JSObject* cancelPromise = ReadableStreamReaderGenericCancel(cx, reader, args.get(0));
    if (!cancelPromise)
        return false;
    args.rval().setObject(*cancelPromise);
    return true;
}

// Streams spec, 3.6.4.3 read ( )
static MOZ_MUST_USE bool
ReadableStreamBYOBReader_read(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    HandleValue viewVal = args.get(0);

    // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise
    //         rejected with a TypeError exception.
    if (!Is<ReadableStreamBYOBReader>(args.thisv()))
        return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "read");

    // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
    //         rejected with a TypeError exception.
    Rooted<ReadableStreamBYOBReader*> reader(cx);
    reader = &args.thisv().toObject().as<ReadableStreamBYOBReader>();
    if (!ReaderHasStream(reader)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
        return ReturnPromiseRejectedWithPendingError(cx, args);
    }

    // Step 3: If Type(view) is not Object, return a promise rejected with a
    //         TypeError exception.
    // Step 4: If view does not have a [[ViewedArrayBuffer]] internal slot,
    //         return a promise rejected with a TypeError exception.
    if (!Is<ArrayBufferViewObject>(viewVal)) {
        ReportArgTypeError(cx, "ReadableStreamBYOBReader.read", "Typed Array", viewVal);
        return ReturnPromiseRejectedWithPendingError(cx, args);
    }

    Rooted<ArrayBufferViewObject*> view(cx, &viewVal.toObject().as<ArrayBufferViewObject>());

    // Step 5: If view.[[ByteLength]] is 0, return a promise rejected with a
    //         TypeError exception.
    // Note: It's ok to use the length in number of elements here because all we
    // want to know is whether it's < 0.
    if (JS_GetArrayBufferViewByteLength(view) == 0) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW);
        return ReturnPromiseRejectedWithPendingError(cx, args);
    }

    // Step 6: Return ! ReadableStreamBYOBReaderRead(this, view).
    JSObject* readPromise = ReadableStreamBYOBReader::read(cx, reader, view);
    if (!readPromise)
        return false;
    args.rval().setObject(*readPromise);
    return true;
}

static MOZ_MUST_USE bool
ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader);

// Streams spec, 3.6.4.4. releaseLock ( )
static MOZ_MUST_USE bool
ReadableStreamBYOBReader_releaseLock_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableStreamBYOBReader*> reader(cx);
    reader = &args.thisv().toObject().as<ReadableStreamBYOBReader>();

    // Step 2: If this.[[ownerReadableStream]] is undefined, return.
    if (!ReaderHasStream(reader)) {
        args.rval().setUndefined();
        return true;
    }

    // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception.
    Value val = reader->getFixedSlot(ReaderSlot_Requests);
    if (!val.isUndefined()) {
        NativeObject* readRequests = &val.toObject().as<NativeObject>();
        uint32_t len = readRequests->getDenseInitializedLength();
        if (len != 0) {
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                      JSMSG_READABLESTREAMREADER_NOT_EMPTY, "releaseLock");
            return false;
        }
    }

    // Step 4: Perform ! ReadableStreamReaderGenericRelease(this).
    return ReadableStreamReaderGenericRelease(cx, reader);
}

static bool
ReadableStreamBYOBReader_releaseLock(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableStreamBYOBReader(this) is false,
    //         throw a TypeError exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableStreamBYOBReader>,
                                ReadableStreamBYOBReader_releaseLock_impl>(cx, args);
}

static const JSPropertySpec ReadableStreamBYOBReader_properties[] = {
    JS_PSG("closed", ReadableStreamBYOBReader_closed, 0),
    JS_PS_END
};

static const JSFunctionSpec ReadableStreamBYOBReader_methods[] = {
    JS_FN("cancel",         ReadableStreamBYOBReader_cancel,        1, 0),
    JS_FN("read",           ReadableStreamBYOBReader_read,          1, 0),
    JS_FN("releaseLock",    ReadableStreamBYOBReader_releaseLock,   0, 0),
    JS_FS_END
};

CLASS_SPEC(ReadableStreamBYOBReader, 1, 3, ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);

inline static MOZ_MUST_USE bool
ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject controller);

// Streams spec, 3.7.1. IsReadableStreamDefaultReader ( x )
// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStreamDefaultReader>()

// Streams spec, 3.7.2. IsReadableStreamBYOBReader ( x )
// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStreamBYOBReader>()

// Streams spec, 3.7.3. ReadableStreamReaderGenericCancel ( reader, reason )
static MOZ_MUST_USE JSObject*
ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, HandleValue reason)
{
    // Step 1: Let stream be reader.[[ownerReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));

    // Step 2: Assert: stream is not undefined (implicit).

    // Step 3: Return ! ReadableStreamCancel(stream, reason).
    return &ReadableStreamCancel(cx, stream, reason)->as<PromiseObject>();
}

// Streams spec, 3.7.4. ReadableStreamReaderGenericInitialize ( reader, stream )
static MOZ_MUST_USE bool
ReadableStreamReaderGenericInitialize(JSContext* cx, HandleNativeObject reader,
                                      Handle<ReadableStream*> stream)
{
    // Step 1: Set reader.[[ownerReadableStream]] to stream.
    reader->setFixedSlot(ReaderSlot_Stream, ObjectValue(*stream));

    // Step 2: Set stream.[[reader]] to reader.
    stream->setFixedSlot(StreamSlot_Reader, ObjectValue(*reader));

    // Step 3: If stream.[[state]] is "readable",
    RootedObject promise(cx);
    if (stream->readable()) {
        // Step a: Set reader.[[closedPromise]] to a new promise.
        promise = PromiseObject::createSkippingExecutor(cx);
    } else if (stream->closed()) {
        // Step 4: Otherwise
        // Step a: If stream.[[state]] is "closed",
        // Step i: Set reader.[[closedPromise]] to a new promise resolved with
        //         undefined.
        promise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
    } else {
        // Step b: Otherwise,
        // Step i: Assert: stream.[[state]] is "errored".
        MOZ_ASSERT(stream->errored());

        // Step ii: Set reader.[[closedPromise]] to a new promise rejected with
        //          stream.[[storedError]].
        RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
        promise = PromiseObject::unforgeableReject(cx, storedError);
    }

    if (!promise)
        return false;

    reader->setFixedSlot(ReaderSlot_ClosedPromise, ObjectValue(*promise));
    return true;
}

// Streams spec, 3.7.5. ReadableStreamReaderGenericRelease ( reader )
static MOZ_MUST_USE bool
ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader)
{
    // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined.
    Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));

    // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader.
    MOZ_ASSERT(&stream->getFixedSlot(StreamSlot_Reader).toObject() == reader);

    // Create an exception to reject promises with below. We don't have a
    // clean way to do this, unfortunately.
    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAMREADER_RELEASED);
    RootedValue exn(cx);
    // Not much we can do about uncatchable exceptions, just bail.
    if (!GetAndClearException(cx, &exn))
        return false;

    // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject
    //         reader.[[closedPromise]] with a TypeError exception.
    if (stream->readable()) {
            Value val = reader->getFixedSlot(ReaderSlot_ClosedPromise);
            Rooted<PromiseObject*> closedPromise(cx, &val.toObject().as<PromiseObject>());
            if (!PromiseObject::reject(cx, closedPromise, exn))
                return false;
    } else {
        // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise rejected
        //         with a TypeError exception.
        RootedObject closedPromise(cx, PromiseObject::unforgeableReject(cx, exn));
        if (!closedPromise)
            return false;
        reader->setFixedSlot(ReaderSlot_ClosedPromise, ObjectValue(*closedPromise));
    }

    // Step 5: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
    stream->setFixedSlot(StreamSlot_Reader, UndefinedValue());

    // Step 6: Set reader.[[ownerReadableStream]] to undefined.
    reader->setFixedSlot(ReaderSlot_Stream, UndefinedValue());

    return true;
}

static MOZ_MUST_USE JSObject*
ReadableByteStreamControllerPullInto(JSContext* cx,
                                     Handle<ReadableByteStreamController*> controller,
                                     Handle<ArrayBufferViewObject*> view);

// Streams spec, 3.7.6. ReadableStreamBYOBReaderRead ( reader, view )
/* static */ MOZ_MUST_USE JSObject*
ReadableStreamBYOBReader::read(JSContext* cx, Handle<ReadableStreamBYOBReader*> reader,
                               Handle<ArrayBufferViewObject*> view)
{
    MOZ_ASSERT(reader->is<ReadableStreamBYOBReader>());

    // Step 1: Let stream be reader.[[ownerReadableStream]].
    // Step 2: Assert: stream is not undefined.
    Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));

    // Step 3: Set stream.[[disturbed]] to true.
    SetStreamState(stream, StreamState(stream) | ReadableStream::Disturbed);

    // Step 4: If stream.[[state]] is "errored", return a promise rejected with
    //         stream.[[storedError]].
    if (stream->errored()) {
        RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
        return PromiseObject::unforgeableReject(cx, storedError);
    }

    // Step 5: Return ! ReadableByteStreamControllerPullInto(stream.[[readableStreamController]], view).
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();
    return ReadableByteStreamControllerPullInto(cx, controller, view);
}

static MOZ_MUST_USE JSObject*
ReadableStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller);

// Streams spec, 3.7.7. ReadableStreamDefaultReaderRead ( reader )
MOZ_MUST_USE JSObject*
ReadableStreamDefaultReader::read(JSContext* cx, Handle<ReadableStreamDefaultReader*> reader)
{
    // Step 1: Let stream be reader.[[ownerReadableStream]].
    // Step 2: Assert: stream is not undefined.
    Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));

    // Step 3: Set stream.[[disturbed]] to true.
    SetStreamState(stream, StreamState(stream) | ReadableStream::Disturbed);

    // Step 4: If stream.[[state]] is "closed", return a new promise resolved with
    //         ! CreateIterResultObject(undefined, true).
    if (stream->closed()) {
        RootedObject iterResult(cx, CreateIterResultObject(cx, UndefinedHandleValue, true));
        if (!iterResult)
            return nullptr;
        RootedValue iterResultVal(cx, ObjectValue(*iterResult));
        return PromiseObject::unforgeableResolve(cx, iterResultVal);
    }

    // Step 5: If stream.[[state]] is "errored", return a new promise rejected with
    //         stream.[[storedError]].
    if (stream->errored()) {
        RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
        return PromiseObject::unforgeableReject(cx, storedError);
    }

    // Step 6: Assert: stream.[[state]] is "readable".
    MOZ_ASSERT(stream->readable());

    // Step 7: Return ! stream.[[readableStreamController]].[[PullSteps]]().
    RootedNativeObject controller(cx, ControllerFromStream(stream));
    return ReadableStreamControllerPullSteps(cx, controller);
}

// Streams spec, 3.8.3, step 11.a.
// and
// Streams spec, 3.10.3, step 16.a.
static bool
ControllerStartHandler(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee()));

    // Step i: Set controller.[[started]] to true.
    AddControllerFlags(controller, ControllerFlag_Started);

    // Step ii: Assert: controller.[[pulling]] is false.
    // Step iii: Assert: controller.[[pullAgain]] is false.
    MOZ_ASSERT(!(ControllerFlags(controller) &
                 (ControllerFlag_Pulling | ControllerFlag_PullAgain)));

    // Step iv: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
    // or
    // Step iv: Perform ! ReadableByteStreamControllerCallPullIfNeeded((controller).
    if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
        return false;
    args.rval().setUndefined();
    return true;
}

static MOZ_MUST_USE bool
ReadableStreamDefaultControllerErrorIfNeeded(JSContext* cx,
                                             Handle<ReadableStreamDefaultController*> controller,
                                             HandleValue e);

static MOZ_MUST_USE bool
ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e);

// Streams spec, 3.8.3, step 11.b.
// and
// Streams spec, 3.10.3, step 16.b.
static bool
ControllerStartFailedHandler(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    RootedNativeObject controllerObj(cx, TargetFromHandler<NativeObject>(args.callee()));

    // 3.8.3, Step 11.b.i:
    // Perform ! ReadableStreamDefaultControllerErrorIfNeeded(controller, r).
    if (controllerObj->is<ReadableStreamDefaultController>()) {
        Rooted<ReadableStreamDefaultController*> controller(cx);
        controller = &controllerObj->as<ReadableStreamDefaultController>();
        return ReadableStreamDefaultControllerErrorIfNeeded(cx, controller, args.get(0));
    }

    // 3.10.3, Step 16.b.i: If stream.[[state]] is "readable", perform
    //                      ! ReadableByteStreamControllerError(controller, r).
    if (StreamFromController(controllerObj)->readable())
        return ReadableStreamControllerError(cx, controllerObj, args.get(0));

    args.rval().setUndefined();
    return true;
}

static MOZ_MUST_USE bool
ValidateAndNormalizeHighWaterMark(JSContext* cx,
                                  HandleValue highWaterMarkVal,
                                  double* highWaterMark);

static MOZ_MUST_USE bool
ValidateAndNormalizeQueuingStrategy(JSContext* cx,
                                    HandleValue size,
                                    HandleValue highWaterMarkVal,
                                    double* highWaterMark);

// Streams spec, 3.8.3 new ReadableStreamDefaultController ( stream, underlyingSource,
//                                                           size, highWaterMark )
// Steps 3 - 11.
static MOZ_MUST_USE ReadableStreamDefaultController*
CreateReadableStreamDefaultController(JSContext* cx, Handle<ReadableStream*> stream,
                                      HandleValue underlyingSource, HandleValue size,
                                      HandleValue highWaterMarkVal)
{
    Rooted<ReadableStreamDefaultController*> controller(cx);
    controller = NewBuiltinClassInstance<ReadableStreamDefaultController>(cx);
    if (!controller)
        return nullptr;

    // Step 3: Set this.[[controlledReadableStream]] to stream.
    controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream));

    // Step 4: Set this.[[underlyingSource]] to underlyingSource.
    controller->setFixedSlot(ControllerSlot_UnderlyingSource, underlyingSource);

    // Step 5: Perform ! ResetQueue(this).
    if (!ResetQueue(cx, controller))
        return nullptr;

    // Step 6: Set this.[[started]], this.[[closeRequested]], this.[[pullAgain]],
    //         and this.[[pulling]] to false.
    controller->setFixedSlot(ControllerSlot_Flags, Int32Value(0));

    // Step 7: Let normalizedStrategy be
    //         ? ValidateAndNormalizeQueuingStrategy(size, highWaterMark).
    double highWaterMark;
    if (!ValidateAndNormalizeQueuingStrategy(cx, size, highWaterMarkVal, &highWaterMark))
        return nullptr;

    // Step 8: Set this.[[strategySize]] to normalizedStrategy.[[size]] and
    //         this.[[strategyHWM]] to normalizedStrategy.[[highWaterMark]].
    controller->setFixedSlot(DefaultControllerSlot_StrategySize, size);
    controller->setFixedSlot(ControllerSlot_StrategyHWM, NumberValue(highWaterMark));

    // Step 9: Let controller be this (implicit).

    // Step 10: Let startResult be
    //          ? InvokeOrNoop(underlyingSource, "start", « this »).
    RootedValue startResult(cx);
    RootedValue controllerVal(cx, ObjectValue(*controller));
    if (!InvokeOrNoop(cx, underlyingSource, cx->names().start, controllerVal, &startResult))
        return nullptr;

    // Step 11: Let startPromise be a promise resolved with startResult:
    RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, startResult));
    if (!startPromise)
        return nullptr;

    RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller));
    if (!onStartFulfilled)
        return nullptr;

    RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller));
    if (!onStartRejected)
        return nullptr;

    if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected))
        return nullptr;

    return controller;
}

// Streams spec, 3.8.3.
// new ReadableStreamDefaultController( stream, underlyingSource, size,
//                                      highWaterMark )
bool
ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultController"))
        return false;

    // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
    HandleValue streamVal = args.get(0);
    if (!Is<ReadableStream>(streamVal)) {
        ReportArgTypeError(cx, "ReadableStreamDefaultController", "ReadableStream",
                           args.get(0));
        return false;
    }

    Rooted<ReadableStream*> stream(cx, &streamVal.toObject().as<ReadableStream>());

    // Step 2: If stream.[[readableStreamController]] is not undefined, throw a
    //         TypeError exception.
    if (HasController(stream)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAM_CONTROLLER_SET);
        return false;
    }

    // Steps 3-11.
    RootedObject controller(cx, CreateReadableStreamDefaultController(cx, stream, args.get(1),
                                                                      args.get(2), args.get(3)));
    if (!controller)
        return false;

    args.rval().setObject(*controller);
    return true;
}

static MOZ_MUST_USE double
ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller);

// Streams spec, 3.8.4.1. get desiredSize
// and
// Streams spec, 3.10.4.2. get desiredSize
static MOZ_MUST_USE bool
ReadableStreamController_desiredSize_impl(JSContext* cx, const CallArgs& args)
{
    RootedNativeObject controller(cx);
    controller = &args.thisv().toObject().as<NativeObject>();

    // Streams spec, 3.9.8. steps 1-4.
    // 3.9.8. Step 1: Let stream be controller.[[controlledReadableStream]].
    ReadableStream* stream = StreamFromController(controller);

    // 3.9.8. Step 2: Let state be stream.[[state]].
    // 3.9.8. Step 3: If state is "errored", return null.
    if (stream->errored()) {
        args.rval().setNull();
        return true;
    }

    // 3.9.8. Step 4: If state is "closed", return 0.
    if (stream->closed()) {
        args.rval().setInt32(0);
        return true;
    }

    // Step 2: Return ! ReadableStreamDefaultControllerGetDesiredSize(this).
    args.rval().setNumber(ReadableStreamControllerGetDesiredSizeUnchecked(controller));
    return true;
}

static bool
ReadableStreamDefaultController_desiredSize(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
    //         TypeError exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
                                ReadableStreamController_desiredSize_impl>(cx, args);
}

static MOZ_MUST_USE bool
ReadableStreamDefaultControllerClose(JSContext* cx,
                                     Handle<ReadableStreamDefaultController*> controller);

// Unified implementation of steps 2-3 of 3.8.4.2 and 3.10.4.3.
static MOZ_MUST_USE bool
VerifyControllerStateForClosing(JSContext* cx, HandleNativeObject controller)
{
    // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
    if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close");
        return false;
    }

    // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
    //         throw a TypeError exception.
    ReadableStream* stream = StreamFromController(controller);
    if (!stream->readable()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close");
        return false;
    }

    return true;
}

// Streams spec, 3.8.4.2 close()
static MOZ_MUST_USE bool
ReadableStreamDefaultController_close_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableStreamDefaultController*> controller(cx);
    controller = &args.thisv().toObject().as<ReadableStreamDefaultController>();

    // Steps 2-3.
    if (!VerifyControllerStateForClosing(cx, controller))
        return false;

    // Step 4: Perform ! ReadableStreamDefaultControllerClose(this).
    if (!ReadableStreamDefaultControllerClose(cx, controller))
        return false;
    args.rval().setUndefined();
    return true;
}

static bool
ReadableStreamDefaultController_close(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
    //         TypeError exception.

    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
                                ReadableStreamDefaultController_close_impl>(cx, args);
}

static MOZ_MUST_USE bool
ReadableStreamDefaultControllerEnqueue(JSContext* cx,
                                       Handle<ReadableStreamDefaultController*> controller,
                                       HandleValue chunk);

// Streams spec, 3.8.4.3. enqueue ( chunk )
static MOZ_MUST_USE bool
ReadableStreamDefaultController_enqueue_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableStreamDefaultController*> controller(cx);
    controller = &args.thisv().toObject().as<ReadableStreamDefaultController>();

    // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
    if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close");
        return false;
    }

    // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
    //         throw a TypeError exception.
    ReadableStream* stream = StreamFromController(controller);
    if (!stream->readable()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close");
        return false;
    }

    // Step 4: Return ! ReadableStreamDefaultControllerEnqueue(this, chunk).
    if (!ReadableStreamDefaultControllerEnqueue(cx, controller, args.get(0)))
        return false;
    args.rval().setUndefined();
    return true;
}

static bool
ReadableStreamDefaultController_enqueue(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
    //         TypeError exception.

    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
                                ReadableStreamDefaultController_enqueue_impl>(cx, args);
}

// Streams spec, 3.8.4.4. error ( e )
static MOZ_MUST_USE bool
ReadableStreamDefaultController_error_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableStreamDefaultController*> controller(cx);
    controller = &args.thisv().toObject().as<ReadableStreamDefaultController>();

    // Step 2: Let stream be this.[[controlledReadableStream]].
    // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
    if (!StreamFromController(controller)->readable()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
        return false;
    }

    // Step 4: Perform ! ReadableStreamDefaultControllerError(this, e).
    if (!ReadableStreamControllerError(cx, controller, args.get(0)))
        return false;
    args.rval().setUndefined();
    return true;
}

static bool
ReadableStreamDefaultController_error(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
    //         TypeError exception.

    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
                                ReadableStreamDefaultController_error_impl>(cx, args);
}

static const JSPropertySpec ReadableStreamDefaultController_properties[] = {
    JS_PSG("desiredSize", ReadableStreamDefaultController_desiredSize, 0),
    JS_PS_END
};

static const JSFunctionSpec ReadableStreamDefaultController_methods[] = {
    JS_FN("close",      ReadableStreamDefaultController_close,      0, 0),
    JS_FN("enqueue",    ReadableStreamDefaultController_enqueue,    1, 0),
    JS_FN("error",      ReadableStreamDefaultController_error,      1, 0),
    JS_FS_END
};

CLASS_SPEC(ReadableStreamDefaultController, 4, 7, ClassSpec::DontDefineConstructor, 0,
           JS_NULL_CLASS_OPS);

/**
 * Unified implementation of ReadableStream controllers' [[CancelSteps]] internal
 * methods.
 * Streams spec, 3.8.5.1. [[CancelSteps]] ( reason )
 * and
 * Streams spec, 3.10.5.1. [[CancelSteps]] ( reason )
 */
static MOZ_MUST_USE JSObject*
ReadableStreamControllerCancelSteps(JSContext* cx, HandleNativeObject controller,
                                    HandleValue reason)
{
    MOZ_ASSERT(IsReadableStreamController(controller));

    // Step 1 of 3.10.5.1: If this.[[pendingPullIntos]] is not empty,
    if (!controller->is<ReadableStreamDefaultController>()) {
        Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
        RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());

        if (pendingPullIntos->getDenseInitializedLength() != 0) {
            // Step a: Let firstDescriptor be the first element of
            //         this.[[pendingPullIntos]].
            // Step b: Set firstDescriptor.[[bytesFilled]] to 0.
            Rooted<PullIntoDescriptor*> firstDescriptor(cx);
            firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
            firstDescriptor->setBytesFilled(0);
        }
    }

    // Step 1 of 3.8.5.1, step 2 of 3.10.5.1: Perform ! ResetQueue(this).
    if (!ResetQueue(cx, controller))
        return nullptr;

    // Step 2 of 3.8.5.1, step 3 of 3.10.5.1:
    // Return ! PromiseInvokeOrNoop(this.[[underlying(Byte)Source]],
    //                              "cancel", « reason »)
    RootedValue underlyingSource(cx);
    underlyingSource = controller->getFixedSlot(ControllerSlot_UnderlyingSource);

    if (Is<TeeState>(underlyingSource)) {
        Rooted<TeeState*> teeState(cx, &underlyingSource.toObject().as<TeeState>());
        Rooted<ReadableStreamDefaultController*> defaultController(cx);
        defaultController = &controller->as<ReadableStreamDefaultController>();
        return ReadableStreamTee_Cancel(cx, teeState, defaultController, reason);
    }

    if (ControllerFlags(controller) & ControllerFlag_ExternalSource) {
        void* source = underlyingSource.toPrivate();
        Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
        RootedValue rval(cx);
        rval = cx->runtime()->readableStreamCancelCallback(cx, stream, source,
                                                           stream->embeddingFlags(), reason);
        return PromiseObject::unforgeableResolve(cx, rval);
    }

    return PromiseInvokeOrNoop(cx, underlyingSource, cx->names().cancel, reason);
}

inline static MOZ_MUST_USE bool
DequeueValue(JSContext* cx, HandleNativeObject container, MutableHandleValue chunk);

// Streams spec, 3.8.5.2. ReadableStreamDefaultController [[PullSteps]]()
static JSObject*
ReadableStreamDefaultControllerPullSteps(JSContext* cx, HandleNativeObject controller)
{
    MOZ_ASSERT(controller->is<ReadableStreamDefaultController>());

    // Step 1: Let stream be this.[[controlledReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 2: If this.[[queue]] is not empty,
    RootedNativeObject queue(cx);
    RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
    if (val.isObject())
        queue = &val.toObject().as<NativeObject>();

    if (queue && queue->getDenseInitializedLength() != 0) {
        // Step a: Let chunk be ! DequeueValue(this.[[queue]]).
        RootedValue chunk(cx);
        if (!DequeueValue(cx, controller, &chunk))
            return nullptr;

        // Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty,
        //         perform ! ReadableStreamClose(stream).
        bool closeRequested = ControllerFlags(controller) & ControllerFlag_CloseRequested;
        if (closeRequested && queue->getDenseInitializedLength() == 0) {
            if (!ReadableStreamCloseInternal(cx, stream))
                return nullptr;
        }

        // Step c: Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
        else {
        if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
            return nullptr;
        }

        // Step d: Return a promise resolved with ! CreateIterResultObject(chunk, false).
        RootedObject iterResultObj(cx, CreateIterResultObject(cx, chunk, false));
        if (!iterResultObj)
          return nullptr;
        RootedValue iterResult(cx, ObjectValue(*iterResultObj));
        return PromiseObject::unforgeableResolve(cx, iterResult);
    }

    // Step 3: Let pendingPromise be ! ReadableStreamAddReadRequest(stream).
    Rooted<PromiseObject*> pendingPromise(cx, ReadableStreamAddReadRequest(cx, stream));
    if (!pendingPromise)
        return nullptr;

    // Step 4: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
    if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
        return nullptr;

    // Step 5: Return pendingPromise.
    return pendingPromise;
}

// Streams spec, 3.9.2 and 3.12.3. step 7:
// Upon fulfillment of pullPromise,
static bool
ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee()));
    uint32_t flags = ControllerFlags(controller);

    // Step a: Set controller.[[pulling]] to false.
    // Step b.i: Set controller.[[pullAgain]] to false.
    RemoveControllerFlags(controller, ControllerFlag_Pulling | ControllerFlag_PullAgain);

    // Step b: If controller.[[pullAgain]] is true,
    if (flags & ControllerFlag_PullAgain) {
        // Step ii: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
        if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
            return false;
    }

    args.rval().setUndefined();
    return true;
}

// Streams spec, 3.9.2 and 3.12.3. step 8:
// Upon rejection of pullPromise with reason e,
static bool
ControllerPullFailedHandler(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee()));
    HandleValue e = args.get(0);

    // Step a: If controller.[[controlledReadableStream]].[[state]] is "readable",
    //         perform ! ReadableByteStreamControllerError(controller, e).
    if (StreamFromController(controller)->readable()) {
        if (!ReadableStreamControllerError(cx, controller, e))
            return false;
    }

    args.rval().setUndefined();
    return true;
}

static bool
ReadableStreamControllerShouldCallPull(NativeObject* controller);

static MOZ_MUST_USE double
ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller);

// Streams spec, 3.9.2 ReadableStreamDefaultControllerCallPullIfNeeded ( controller )
// and
// Streams spec, 3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller )
inline static MOZ_MUST_USE bool
ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject controller)
{
    // Step 1: Let shouldPull be
    //         ! ReadableByteStreamControllerShouldCallPull(controller).
    bool shouldPull = ReadableStreamControllerShouldCallPull(controller);

    // Step 2: If shouldPull is false, return.
    if (!shouldPull)
        return true;

    // Step 3: If controller.[[pulling]] is true,
    if (ControllerFlags(controller) & ControllerFlag_Pulling) {
        // Step a: Set controller.[[pullAgain]] to true.
        AddControllerFlags(controller, ControllerFlag_PullAgain);

        // Step b: Return.
        return true;
    }

    // Step 4: Assert: controller.[[pullAgain]] is false.
    MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_PullAgain));

    // Step 5: Set controller.[[pulling]] to true.
    AddControllerFlags(controller, ControllerFlag_Pulling);

    // Step 6: Let pullPromise be
    //         ! PromiseInvokeOrNoop(controller.[[underlyingByteSource]], "pull", controller).
    RootedObject pullPromise(cx);
    RootedValue underlyingSource(cx);
    underlyingSource = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
    RootedValue controllerVal(cx, ObjectValue(*controller));

    if (Is<TeeState>(underlyingSource)) {
        Rooted<TeeState*> teeState(cx, &underlyingSource.toObject().as<TeeState>());
        Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
        pullPromise = ReadableStreamTee_Pull(cx, teeState, stream);
    } else if (ControllerFlags(controller) & ControllerFlag_ExternalSource) {
        void* source = underlyingSource.toPrivate();
        Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
        double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(controller);
        cx->runtime()->readableStreamDataRequestCallback(cx, stream, source,
                                                         stream->embeddingFlags(), desiredSize);
        pullPromise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
    } else {
        pullPromise = PromiseInvokeOrNoop(cx, underlyingSource, cx->names().pull, controllerVal);
    }
    if (!pullPromise)
        return false;

    RootedObject onPullFulfilled(cx, NewHandler(cx, ControllerPullHandler, controller));
    if (!onPullFulfilled)
        return false;

    RootedObject onPullRejected(cx, NewHandler(cx, ControllerPullFailedHandler, controller));
    if (!onPullRejected)
        return false;

    return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled, onPullRejected);

    // Steps 7-8 implemented in functions above.
}

// Streams spec, 3.9.3. ReadableStreamDefaultControllerShouldCallPull ( controller )
// and
// Streams spec, 3.12.24. ReadableByteStreamControllerShouldCallPull ( controller )
static bool
ReadableStreamControllerShouldCallPull(NativeObject* controller)
{
    // Step 1: Let stream be controller.[[controlledReadableStream]].
    ReadableStream* stream = StreamFromController(controller);

    // Step 2: If stream.[[state]] is "closed" or stream.[[state]] is "errored",
    //         return false.
    // or, equivalently
    // Step 2: If stream.[[state]] is not "readable", return false.
    if (!stream->readable())
        return false;

    // Step 3: If controller.[[closeRequested]] is true, return false.
    uint32_t flags = ControllerFlags(controller);
    if (flags & ControllerFlag_CloseRequested)
        return false;

    // Step 4: If controller.[[started]] is false, return false.
    if (!(flags & ControllerFlag_Started))
        return false;

    // Step 5: If ! IsReadableStreamLocked(stream) is true and
    //         ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
    // Steps 5-6 of 3.12.24 are equivalent in our implementation.
    if (stream->locked() && ReadableStreamGetNumReadRequests(stream) > 0)
        return true;

    // Step 6: Let desiredSize be ReadableStreamDefaultControllerGetDesiredSize(controller).
    double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(controller);

    // Step 7: If desiredSize > 0, return true.
    // Step 8: Return false.
    // Steps 7-8 of 3.12.24 are equivalent in our implementation.
    return desiredSize > 0;
}

// Streams spec, 3.9.4. ReadableStreamDefaultControllerClose ( controller )
static MOZ_MUST_USE bool
ReadableStreamDefaultControllerClose(JSContext* cx,
                                     Handle<ReadableStreamDefaultController*> controller)
{
    // Step 1: Let stream be controller.[[controlledReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 2: Assert: controller.[[closeRequested]] is false.
    MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));

    // Step 3: Assert: stream.[[state]] is "readable".
    MOZ_ASSERT(stream->readable());

    // Step 4: Set controller.[[closeRequested]] to true.
    AddControllerFlags(controller, ControllerFlag_CloseRequested);

    // Step 5: If controller.[[queue]] is empty, perform ! ReadableStreamClose(stream).
    RootedNativeObject queue(cx);
    queue = &controller->getFixedSlot(QueueContainerSlot_Queue).toObject().as<NativeObject>();
    if (queue->getDenseInitializedLength() == 0)
        return ReadableStreamCloseInternal(cx, stream);

    return true;
}

static MOZ_MUST_USE bool
EnqueueValueWithSize(JSContext* cx, HandleNativeObject container, HandleValue value,
                     HandleValue sizeVal);

// Streams spec, 3.9.5. ReadableStreamDefaultControllerEnqueue ( controller, chunk )
static MOZ_MUST_USE bool
ReadableStreamDefaultControllerEnqueue(JSContext* cx,
                                       Handle<ReadableStreamDefaultController*> controller,
                                       HandleValue chunk)
{
    // Step 1: Let stream be controller.[[controlledReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 2: Assert: controller.[[closeRequested]] is false.
    MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));

    // Step 3: Assert: stream.[[state]] is "readable".
    MOZ_ASSERT(stream->readable());

    // Step 4: If ! IsReadableStreamLocked(stream) is true and
    //         ! ReadableStreamGetNumReadRequests(stream) > 0, perform
    //         ! ReadableStreamFulfillReadRequest(stream, chunk, false).
    if (stream->locked() && ReadableStreamGetNumReadRequests(stream) > 0) {
        if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false))
            return false;
    } else {
        // Step 5: Otherwise,
        // Step a: Let chunkSize be 1.
        RootedValue chunkSize(cx, NumberValue(1));
        bool success = true;

        // Step b: If controller.[[strategySize]] is not undefined,
        RootedValue strategySize(cx);
        strategySize = controller->getFixedSlot(DefaultControllerSlot_StrategySize);
        if (!strategySize.isUndefined()) {
            // Step i: Set chunkSize to Call(stream.[[strategySize]], undefined, chunk).
            success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize);
        }

        // Step c: Let enqueueResult be
        //         EnqueueValueWithSize(controller, chunk, chunkSize).
        if (success)
            success = EnqueueValueWithSize(cx, controller, chunk, chunkSize);

        if (!success) {
            // Step b.ii: If chunkSize is an abrupt completion,
            // and
            // Step d: If enqueueResult is an abrupt completion,
            RootedValue exn(cx);
            if (!cx->getPendingException(&exn))
                return false;

            // Step b.ii.1: Perform
            //         ! ReadableStreamDefaultControllerErrorIfNeeded(controller,
            //                                                        chunkSize.[[Value]]).
            if (!ReadableStreamDefaultControllerErrorIfNeeded(cx, controller, exn))
                return false;

            // Step b.ii.2: Return chunkSize.
            return false;
        }
    }

    // Step 6: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
    if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
        return false;

    // Step 7: Return.
    return true;
}

static MOZ_MUST_USE bool
ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx, HandleNativeObject controller);

// Streams spec, 3.9.6. ReadableStreamDefaultControllerError ( controller, e )
// and
// Streams spec, 3.12.10. ReadableByteStreamControllerError ( controller, e )
static MOZ_MUST_USE bool
ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e)
{
    MOZ_ASSERT(IsReadableStreamController(controller));

    // Step 1: Let stream be controller.[[controlledReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 2: Assert: stream.[[state]] is "readable".
    MOZ_ASSERT(stream->readable());

    // Step 3 of 3.12.10:
    // Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller).
    if (controller->is<ReadableByteStreamController>()) {
        Rooted<ReadableByteStreamController*> byteStreamController(cx);
        byteStreamController = &controller->as<ReadableByteStreamController>();
        if (!ReadableByteStreamControllerClearPendingPullIntos(cx, byteStreamController))
            return false;
    }

    // Step 3 (or 4): Perform ! ResetQueue(controller).
    if (!ResetQueue(cx, controller))
        return false;

    // Step 4 (or 5): Perform ! ReadableStreamError(stream, e).
    return ReadableStreamErrorInternal(cx, stream, e);
}

// Streams spec, 3.9.7. ReadableStreamDefaultControllerErrorIfNeeded ( controller, e ) nothrow
static MOZ_MUST_USE bool
ReadableStreamDefaultControllerErrorIfNeeded(JSContext* cx,
                                             Handle<ReadableStreamDefaultController*> controller,
                                             HandleValue e)
{
    // Step 1: If controller.[[controlledReadableStream]].[[state]] is "readable",
    //         perform ! ReadableStreamDefaultControllerError(controller, e).
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
    if (stream->readable())
        return ReadableStreamControllerError(cx, controller, e);
    return true;
}

// Streams spec, 3.9.8. ReadableStreamDefaultControllerGetDesiredSize ( controller )
// and
// Streams spec 3.12.13. ReadableByteStreamControllerGetDesiredSize ( controller )
static MOZ_MUST_USE double
ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller)
{
    // Steps 1-4 done at callsites, so only assert that they have been done.
#if DEBUG
    ReadableStream* stream = StreamFromController(controller);
    MOZ_ASSERT(!(stream->errored() || stream->closed()));
#endif // DEBUG

    // Step 5: Return controller.[[strategyHWM]] − controller.[[queueTotalSize]].
    double strategyHWM = controller->getFixedSlot(ControllerSlot_StrategyHWM).toNumber();
    double queueSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
    return strategyHWM - queueSize;
}

// Streams spec, 3.10.3 new ReadableByteStreamController ( stream, underlyingSource,
//                                                         highWaterMark )
// Steps 3 - 16.
static MOZ_MUST_USE ReadableByteStreamController*
CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
                                   HandleValue underlyingByteSource,
                                   HandleValue highWaterMarkVal)
{
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = NewBuiltinClassInstance<ReadableByteStreamController>(cx);
    if (!controller)
        return nullptr;

    // Step 3: Set this.[[controlledReadableStream]] to stream.
    controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream));

    // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource.
    controller->setFixedSlot(ControllerSlot_UnderlyingSource, underlyingByteSource);

    // Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false.
    controller->setFixedSlot(ControllerSlot_Flags, Int32Value(0));

    // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).
    if (!ReadableByteStreamControllerClearPendingPullIntos(cx, controller))
        return nullptr;

    // Step 7: Perform ! ResetQueue(this).
    if (!ResetQueue(cx, controller))
        return nullptr;

    // Step 8: Set this.[[started]] and this.[[closeRequested]] to false.
    // These should be false by default, unchanged since step 5.
    MOZ_ASSERT(ControllerFlags(controller) == 0);

    // Step 9: Set this.[[strategyHWM]] to
    //         ? ValidateAndNormalizeHighWaterMark(highWaterMark).
    double highWaterMark;
    if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal, &highWaterMark))
        return nullptr;
    controller->setFixedSlot(ControllerSlot_StrategyHWM, NumberValue(highWaterMark));

    // Step 10: Let autoAllocateChunkSize be
    //          ? GetV(underlyingByteSource, "autoAllocateChunkSize").
    RootedValue autoAllocateChunkSize(cx);
    if (!GetProperty(cx, underlyingByteSource, cx->names().autoAllocateChunkSize,
                     &autoAllocateChunkSize))
    {
        return nullptr;
    }

    // Step 11: If autoAllocateChunkSize is not undefined,
    if (!autoAllocateChunkSize.isUndefined()) {
        // Step a: If ! IsInteger(autoAllocateChunkSize) is false, or if
        //         autoAllocateChunkSize ≤ 0, throw a RangeError exception.
        if (!IsInteger(autoAllocateChunkSize) || autoAllocateChunkSize.toNumber() <= 0) {
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                      JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE);
            return nullptr;
    }
    }

    // Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize.
    controller->setFixedSlot(ByteControllerSlot_AutoAllocateSize, autoAllocateChunkSize);

    // Step 13: Set this.[[pendingPullIntos]] to a new empty List.
    if (!SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos))
        return nullptr;

    // Step 14: Let controller be this (implicit).

    // Step 15: Let startResult be
    //          ? InvokeOrNoop(underlyingSource, "start", « this »).
    RootedValue startResult(cx);
    RootedValue controllerVal(cx, ObjectValue(*controller));
    if (!InvokeOrNoop(cx, underlyingByteSource, cx->names().start, controllerVal, &startResult))
        return nullptr;

    // Step 16: Let startPromise be a promise resolved with startResult:
    RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, startResult));
    if (!startPromise)
        return nullptr;

    RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller));
    if (!onStartFulfilled)
        return nullptr;

    RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller));
    if (!onStartRejected)
        return nullptr;

    if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected))
        return nullptr;

    return controller;
}

bool
ReadableByteStreamController::hasExternalSource() {
    return ControllerFlags(this) & ControllerFlag_ExternalSource;
}

// Streams spec, 3.10.3.
// new ReadableByteStreamController ( stream, underlyingByteSource,
//                                    highWaterMark )
bool
ReadableByteStreamController::constructor(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    if (!ThrowIfNotConstructing(cx, args, "ReadableByteStreamController"))
        return false;

    // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
    HandleValue streamVal = args.get(0);
    if (!Is<ReadableStream>(streamVal)) {
        ReportArgTypeError(cx, "ReadableStreamDefaultController", "ReadableStream",
                           args.get(0));
        return false;
    }

    Rooted<ReadableStream*> stream(cx, &streamVal.toObject().as<ReadableStream>());

    // Step 2: If stream.[[readableStreamController]] is not undefined, throw a
    //         TypeError exception.
    if (HasController(stream)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAM_CONTROLLER_SET);
        return false;
    }

    RootedObject controller(cx, CreateReadableByteStreamController(cx, stream, args.get(1),
                                                                   args.get(2)));
    if (!controller)
        return false;

    args.rval().setObject(*controller);
    return true;
}

// Version of the ReadableByteStreamConstructor that's specialized for
// handling external, embedding-provided, underlying sources.
static MOZ_MUST_USE ReadableByteStreamController*
CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
                                   void* underlyingSource)
{
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = NewBuiltinClassInstance<ReadableByteStreamController>(cx);
    if (!controller)
        return nullptr;

    // Step 3: Set this.[[controlledReadableStream]] to stream.
    controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream));

    // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource.
    controller->setFixedSlot(ControllerSlot_UnderlyingSource, PrivateValue(underlyingSource));

    // Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false.
    controller->setFixedSlot(ControllerSlot_Flags, Int32Value(ControllerFlag_ExternalSource));

    // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).
    // Omitted.

    // Step 7: Perform ! ResetQueue(this).
    controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(0));

    // Step 8: Set this.[[started]] and this.[[closeRequested]] to false.
    // Step 9: Set this.[[strategyHWM]] to
    //         ? ValidateAndNormalizeHighWaterMark(highWaterMark).
    controller->setFixedSlot(ControllerSlot_StrategyHWM, Int32Value(0));

    // Step 10: Let autoAllocateChunkSize be
    //          ? GetV(underlyingByteSource, "autoAllocateChunkSize").
    // Step 11: If autoAllocateChunkSize is not undefined,
    // Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize.
    // Omitted.

    // Step 13: Set this.[[pendingPullIntos]] to a new empty List.
    if (!SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos))
        return nullptr;

    // Step 14: Let controller be this (implicit).
    // Step 15: Let startResult be
    //          ? InvokeOrNoop(underlyingSource, "start", « this »).
    // Omitted.

    // Step 16: Let startPromise be a promise resolved with startResult:
    RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, UndefinedHandleValue));
    if (!startPromise)
        return nullptr;

    RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller));
    if (!onStartFulfilled)
        return nullptr;

    RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller));
    if (!onStartRejected)
        return nullptr;

    if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected))
        return nullptr;

    return controller;
}

static MOZ_MUST_USE ReadableStreamBYOBRequest*
CreateReadableStreamBYOBRequest(JSContext* cx, Handle<ReadableByteStreamController*> controller,
                                HandleObject view);

// Streams spec, 3.10.4.1. get byobRequest
static MOZ_MUST_USE bool
ReadableByteStreamController_byobRequest_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &args.thisv().toObject().as<ReadableByteStreamController>();

    // Step 2: If this.[[byobRequest]] is undefined and this.[[pendingPullIntos]]
    //         is not empty,
    Value val = controller->getFixedSlot(ByteControllerSlot_BYOBRequest);
    RootedValue byobRequest(cx, val);
    val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
    RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());

    if (byobRequest.isUndefined() && pendingPullIntos->getDenseInitializedLength() != 0) {
        // Step a: Let firstDescriptor be the first element of this.[[pendingPullIntos]].
        Rooted<PullIntoDescriptor*> firstDescriptor(cx);
        firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);

        // Step b: Let view be ! Construct(%Uint8Array%,
        //  « firstDescriptor.[[buffer]],
        //  firstDescriptor.[[byteOffset]] + firstDescriptor.[[bytesFilled]],
        //  firstDescriptor.[[byteLength]] − firstDescriptor.[[bytesFilled]] »).
        RootedArrayBufferObject buffer(cx, firstDescriptor->buffer());
        uint32_t bytesFilled = firstDescriptor->bytesFilled();
        RootedObject view(cx, JS_NewUint8ArrayWithBuffer(cx, buffer,
                                                         firstDescriptor->byteOffset() + bytesFilled,
                                                         firstDescriptor->byteLength() - bytesFilled));
        if (!view)
            return false;

        // Step c: Set this.[[byobRequest]] to
        //         ! Construct(ReadableStreamBYOBRequest, « this, view »).
        RootedObject request(cx, CreateReadableStreamBYOBRequest(cx, controller, view));
        if (!request)
            return false;
        byobRequest = ObjectValue(*request);
        controller->setFixedSlot(ByteControllerSlot_BYOBRequest, byobRequest);
    }

    // Step 3: Return this.[[byobRequest]].
    args.rval().set(byobRequest);
    return true;
}

static bool
ReadableByteStreamController_byobRequest(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If IsReadableByteStreamController(this) is false, throw a TypeError
    //         exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableByteStreamController>,
                                ReadableByteStreamController_byobRequest_impl>(cx, args);
}

// Streams spec, 3.10.4.2. get desiredSize
// Combined with 3.8.4.1 above.

static bool
ReadableByteStreamController_desiredSize(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
    //         TypeError exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableByteStreamController>,
                                ReadableStreamController_desiredSize_impl>(cx, args);
}

static MOZ_MUST_USE bool
ReadableByteStreamControllerClose(JSContext* cx, Handle<ReadableByteStreamController*> controller);

// Streams spec, 3.10.4.3. close()
static MOZ_MUST_USE bool
ReadableByteStreamController_close_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &args.thisv().toObject().as<ReadableByteStreamController>();

    // Steps 2-3.
    if (!VerifyControllerStateForClosing(cx, controller))
        return false;

    // Step 4: Perform ? ReadableByteStreamControllerClose(this).
    if (!ReadableByteStreamControllerClose(cx, controller))
        return false;
    args.rval().setUndefined();
    return true;
}

static bool
ReadableByteStreamController_close(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
    //         TypeError exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableByteStreamController>,
                                ReadableByteStreamController_close_impl>(cx, args);
}

static MOZ_MUST_USE bool
ReadableByteStreamControllerEnqueue(JSContext* cx,
                                    Handle<ReadableByteStreamController*> controller,
                                    HandleObject chunk);

// Streams spec, 3.10.4.4. enqueue ( chunk )
static MOZ_MUST_USE bool
ReadableByteStreamController_enqueue_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &args.thisv().toObject().as<ReadableByteStreamController>();
    HandleValue chunkVal = args.get(0);

    // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
    if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue");
        return false;
    }

    // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
    //         throw a TypeError exception.
    if (!StreamFromController(controller)->readable()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "enqueue");
        return false;
    }

    // Step 4: If Type(chunk) is not Object, throw a TypeError exception.
    // Step 5: If chunk does not have a [[ViewedArrayBuffer]] internal slot,
    //         throw a TypeError exception.
    if (!chunkVal.isObject() || !JS_IsArrayBufferViewObject(&chunkVal.toObject())) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
                                  "ReadableByteStreamController#enqueue");
        return false;
    }
    RootedObject chunk(cx, &chunkVal.toObject());

    // Step 6: Return ! ReadableByteStreamControllerEnqueue(this, chunk).
    if (!ReadableByteStreamControllerEnqueue(cx, controller, chunk))
        return false;
    args.rval().setUndefined();
    return true;
}

static bool
ReadableByteStreamController_enqueue(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
    //         TypeError exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableByteStreamController>,
                                ReadableByteStreamController_enqueue_impl>(cx, args);
}

// Streams spec, 3.10.4.5. error ( e )
static MOZ_MUST_USE bool
ReadableByteStreamController_error_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &args.thisv().toObject().as<ReadableByteStreamController>();
    HandleValue e = args.get(0);

    // Step 2: Let stream be this.[[controlledReadableStream]].
    // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
    if (!StreamFromController(controller)->readable()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
        return false;
    }

    // Step 4: Perform ! ReadableByteStreamControllerError(this, e).
    if (!ReadableStreamControllerError(cx, controller, e))
        return false;
    args.rval().setUndefined();
    return true;
}

static bool
ReadableByteStreamController_error(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
    //         TypeError exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableByteStreamController>,
                                ReadableByteStreamController_error_impl>(cx, args);
}

static const JSPropertySpec ReadableByteStreamController_properties[] = {
    JS_PSG("byobRequest", ReadableByteStreamController_byobRequest, 0),
    JS_PSG("desiredSize", ReadableByteStreamController_desiredSize, 0),
    JS_PS_END
};

static const JSFunctionSpec ReadableByteStreamController_methods[] = {
    JS_FN("close",      ReadableByteStreamController_close,     0, 0),
    JS_FN("enqueue",    ReadableByteStreamController_enqueue,   1, 0),
    JS_FN("error",      ReadableByteStreamController_error,     1, 0),
    JS_FS_END
};

static void
ReadableByteStreamControllerFinalize(FreeOp* fop, JSObject* obj)
{
    ReadableByteStreamController& controller = obj->as<ReadableByteStreamController>();

    if (controller.getFixedSlot(ControllerSlot_Flags).isUndefined())
        return;

    uint32_t flags = ControllerFlags(&controller);
    if (!(flags & ControllerFlag_ExternalSource))
        return;

    uint8_t embeddingFlags = flags >> ControllerEmbeddingFlagsOffset;

    void* underlyingSource = controller.getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
    obj->runtimeFromAnyThread()->readableStreamFinalizeCallback(underlyingSource, embeddingFlags);
}

static const ClassOps ReadableByteStreamControllerClassOps = {
    nullptr,        /* addProperty */
    nullptr,        /* delProperty */
    nullptr,        /* getProperty */
    nullptr,        /* setProperty */
    nullptr,        /* enumerate */
    nullptr,        /* newEnumerate */
    nullptr,        /* resolve */
    nullptr,        /* mayResolve */
    ReadableByteStreamControllerFinalize,
    nullptr,        /* call        */
    nullptr,        /* hasInstance */
    nullptr,        /* construct   */
    nullptr,        /* trace   */
};

CLASS_SPEC(ReadableByteStreamController, 3, 9, ClassSpec::DontDefineConstructor,
           JSCLASS_BACKGROUND_FINALIZE, &ReadableByteStreamControllerClassOps);

// Streams spec, 3.10.5.1. [[PullSteps]] ()
// Unified with 3.8.5.1 above.

static MOZ_MUST_USE bool
ReadableByteStreamControllerHandleQueueDrain(JSContext* cx, HandleNativeObject controller);

// Streams spec, 3.10.5.2. [[PullSteps]] ()
static JSObject*
ReadableByteStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller)
{
    // Step 1: Let stream be this.[[controlledReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 2: MOZ_ASSERT: ! ReadableStreamHasDefaultReader(stream) is true.
    MOZ_ASSERT(ReadableStreamHasDefaultReader(stream));

    RootedValue val(cx);
    // Step 3: If this.[[queueTotalSize]] > 0,
    double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
    if (queueTotalSize > 0) {
        // Step 3.a: MOZ_ASSERT: ! ReadableStreamGetNumReadRequests(_stream_) is 0.
        MOZ_ASSERT(ReadableStreamGetNumReadRequests(stream) == 0);

        RootedObject view(cx);

        if (stream->mode() == JS::ReadableStreamMode::ExternalSource) {
            val = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
            void* underlyingSource = val.toPrivate();

            view = JS_NewUint8Array(cx, queueTotalSize);
            if (!view)
                return nullptr;

            size_t bytesWritten;
            {
                JS::AutoSuppressGCAnalysis noGC(cx);
                bool dummy;
                void* buffer = JS_GetArrayBufferViewData(view, &dummy, noGC);
                auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
                MOZ_ASSERT(cb);
                // TODO: use bytesWritten to correctly update the request's state.
                cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer,
                   queueTotalSize, &bytesWritten);
            }

            queueTotalSize = queueTotalSize - bytesWritten;
        } else {
            // Step 3.b: Let entry be the first element of this.[[queue]].
            // Step 3.c: Remove entry from this.[[queue]], shifting all other elements
            //           downward (so that the second becomes the first, and so on).
            val = controller->getFixedSlot(QueueContainerSlot_Queue);
            RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
            Rooted<ByteStreamChunk*> entry(cx, ShiftFromList<ByteStreamChunk>(cx, queue));
            MOZ_ASSERT(entry);

            queueTotalSize = queueTotalSize - entry->byteLength();

            // Step 3.f: Let view be ! Construct(%Uint8Array%, « entry.[[buffer]],
            //                                   entry.[[byteOffset]], entry.[[byteLength]] »).
            // (reordered)
            RootedObject buffer(cx, entry->buffer());

            uint32_t byteOffset = entry->byteOffset();
            view = JS_NewUint8ArrayWithBuffer(cx, buffer, byteOffset, entry->byteLength());
            if (!view)
                return nullptr;
        }

        // Step 3.d: Set this.[[queueTotalSize]] to
        //           this.[[queueTotalSize]] − entry.[[byteLength]].
        // (reordered)
        controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(queueTotalSize));

        // Step 3.e: Perform ! ReadableByteStreamControllerHandleQueueDrain(this).
        // (reordered)
        if (!ReadableByteStreamControllerHandleQueueDrain(cx, controller))
            return nullptr;

        // Step 3.g: Return a promise resolved with ! CreateIterResultObject(view, false).
        val.setObject(*view);
        RootedObject iterResult(cx, CreateIterResultObject(cx, val, false));
        if (!iterResult)
            return nullptr;
        val.setObject(*iterResult);

        return PromiseObject::unforgeableResolve(cx, val);
    }

    // Step 4: Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]].
    val = controller->getFixedSlot(ByteControllerSlot_AutoAllocateSize);

    // Step 5: If autoAllocateChunkSize is not undefined,
    if (!val.isUndefined()) {
        double autoAllocateChunkSize = val.toNumber();

        // Step 5.a: Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize »).
        RootedObject bufferObj(cx, JS_NewArrayBuffer(cx, autoAllocateChunkSize));

        // Step 5.b: If buffer is an abrupt completion,
        //           return a promise rejected with buffer.[[Value]].
        if (!bufferObj)
            return PromiseRejectedWithPendingError(cx);

        RootedArrayBufferObject buffer(cx, &bufferObj->as<ArrayBufferObject>());

        // Step 5.c: Let pullIntoDescriptor be Record {[[buffer]]: buffer.[[Value]],
        //                                             [[byteOffset]]: 0,
        //                                             [[byteLength]]: autoAllocateChunkSize,
        //                                             [[bytesFilled]]: 0, [[elementSize]]: 1,
        //                                             [[ctor]]: %Uint8Array%,
        //                                             [[readerType]]: `"default"`}.
        RootedObject pullIntoDescriptor(cx);
        pullIntoDescriptor = PullIntoDescriptor::create(cx, buffer, 0,
                                                        autoAllocateChunkSize, 0, 1,
                                                        nullptr,
                                                        ReaderType_Default);
        if (!pullIntoDescriptor)
            return PromiseRejectedWithPendingError(cx);

        // Step 5.d: Append pullIntoDescriptor as the last element of this.[[pendingPullIntos]].
        val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
        RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
        val = ObjectValue(*pullIntoDescriptor);
        if (!AppendToList(cx, pendingPullIntos, val))
            return nullptr;
    }

    // Step 6: Let promise be ! ReadableStreamAddReadRequest(stream).
    Rooted<PromiseObject*> promise(cx, ReadableStreamAddReadRequest(cx, stream));
    if (!promise)
        return nullptr;

    // Step 7: Perform ! ReadableByteStreamControllerCallPullIfNeeded(this).
    if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
        return nullptr;

    // Step 8: Return promise.
    return promise;
}

/**
 * Unified implementation of ReadableStream controllers' [[PullSteps]] internal
 * methods.
 * Streams spec, 3.8.5.2. [[PullSteps]] ()
 * and
 * Streams spec, 3.10.5.2. [[PullSteps]] ()
 */
static MOZ_MUST_USE JSObject*
ReadableStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller)
{
    MOZ_ASSERT(IsReadableStreamController(controller));

    if (controller->is<ReadableStreamDefaultController>())
        return ReadableStreamDefaultControllerPullSteps(cx, controller);

    return ReadableByteStreamControllerPullSteps(cx, controller);
}


static MOZ_MUST_USE ReadableStreamBYOBRequest*
CreateReadableStreamBYOBRequest(JSContext* cx, Handle<ReadableByteStreamController*> controller,
                                HandleObject view)
{
    MOZ_ASSERT(controller);
    MOZ_ASSERT(JS_IsArrayBufferViewObject(view));

    Rooted<ReadableStreamBYOBRequest*> request(cx);
    request = NewBuiltinClassInstance<ReadableStreamBYOBRequest>(cx);
    if (!request)
        return nullptr;

  // Step 1: Set this.[[associatedReadableByteStreamController]] to controller.
  request->setFixedSlot(BYOBRequestSlot_Controller, ObjectValue(*controller));

  // Step 2: Set this.[[view]] to view.
  request->setFixedSlot(BYOBRequestSlot_View, ObjectValue(*view));

  return request;
}

// Streams spec, 3.11.3. new ReadableStreamBYOBRequest ( controller, view )
bool
ReadableStreamBYOBRequest::constructor(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);
    HandleValue controllerVal = args.get(0);
    HandleValue viewVal = args.get(1);

    if (!ThrowIfNotConstructing(cx, args, "ReadableStreamBYOBRequest"))
        return false;

    // TODO: open PR against spec to add these checks.
    // They're expected to have happened in code using requests.
    if (!Is<ReadableByteStreamController>(controllerVal)) {
        ReportArgTypeError(cx, "ReadableStreamBYOBRequest",
                           "ReadableByteStreamController", args.get(0));
        return false;
    }

    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &controllerVal.toObject().as<ReadableByteStreamController>();

    if (!viewVal.isObject() || !JS_IsArrayBufferViewObject(&viewVal.toObject())) {
        ReportArgTypeError(cx, "ReadableStreamBYOBRequest", "ArrayBuffer view",
                           args.get(1));
        return false;
    }

    RootedArrayBufferObject view(cx, &viewVal.toObject().as<ArrayBufferObject>());

    RootedObject request(cx, CreateReadableStreamBYOBRequest(cx, controller, view));
    if (!request)
        return false;

    args.rval().setObject(*request);
    return true;
}

// Streams spec, 3.11.4.1 get view
static MOZ_MUST_USE bool
ReadableStreamBYOBRequest_view_impl(JSContext* cx, const CallArgs& args)
{
    // Step 2: Return this.[[view]].
    NativeObject* request = &args.thisv().toObject().as<NativeObject>();
    args.rval().set(request->getFixedSlot(BYOBRequestSlot_View));
    return true;
}

static bool
ReadableStreamBYOBRequest_view(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError
    //         exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>,
                                ReadableStreamBYOBRequest_view_impl>(cx, args);
}

static MOZ_MUST_USE bool
ReadableByteStreamControllerRespond(JSContext* cx,
                                    Handle<ReadableByteStreamController*> controller,
                                    HandleValue bytesWrittenVal);

// Streams spec, 3.11.4.2. respond ( bytesWritten )
static MOZ_MUST_USE bool
ReadableStreamBYOBRequest_respond_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableStreamBYOBRequest*> request(cx);
    request = &args.thisv().toObject().as<ReadableStreamBYOBRequest>();
    HandleValue bytesWritten = args.get(0);

    // Step 2: If this.[[associatedReadableByteStreamController]] is undefined,
    //         throw a TypeError exception.
    RootedValue controllerVal(cx, request->getFixedSlot(BYOBRequestSlot_Controller));
    if (controllerVal.isUndefined()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, "respond");
        return false;
    }

    // Step 3: Return ?
    // ReadableByteStreamControllerRespond(this.[[associatedReadableByteStreamController]],
    //                                     bytesWritten).
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &controllerVal.toObject().as<ReadableByteStreamController>();

    if (!ReadableByteStreamControllerRespond(cx, controller, bytesWritten))
        return false;

    args.rval().setUndefined();
    return true;
}

static bool
ReadableStreamBYOBRequest_respond(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError
    //         exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>,
                                ReadableStreamBYOBRequest_respond_impl>(cx, args);
}

static MOZ_MUST_USE bool
ReadableByteStreamControllerRespondWithNewView(JSContext* cx,
                                               Handle<ReadableByteStreamController*> controller,
                                               HandleObject view);

// Streams spec, 3.11.4.3. respondWithNewView ( view )
static MOZ_MUST_USE bool
ReadableStreamBYOBRequest_respondWithNewView_impl(JSContext* cx, const CallArgs& args)
{
    Rooted<ReadableStreamBYOBRequest*> request(cx);
    request = &args.thisv().toObject().as<ReadableStreamBYOBRequest>();
    HandleValue viewVal = args.get(0);

    // Step 2: If this.[[associatedReadableByteStreamController]] is undefined,
    //         throw a TypeError exception.
    RootedValue controllerVal(cx, request->getFixedSlot(BYOBRequestSlot_Controller));
    if (controllerVal.isUndefined()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, "respondWithNewView");
        return false;
    }

    // Step 3: If Type(chunk) is not Object, throw a TypeError exception.
    // Step 4: If view does not have a [[ViewedArrayBuffer]] internal slot, throw
    //         a TypeError exception.
    if (!viewVal.isObject() || !JS_IsArrayBufferViewObject(&viewVal.toObject())) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
                                  "ReadableStreamBYOBRequest#respondWithNewView");
        return false;
    }

    // Step 5: Return ?
    // ReadableByteStreamControllerRespondWithNewView(this.[[associatedReadableByteStreamController]],
    //                                                view).
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &controllerVal.toObject().as<ReadableByteStreamController>();
    RootedObject view(cx, &viewVal.toObject());

    if (!ReadableByteStreamControllerRespondWithNewView(cx, controller, view))
        return false;

    args.rval().setUndefined();
    return true;
}

static bool
ReadableStreamBYOBRequest_respondWithNewView(JSContext* cx, unsigned argc, Value* vp)
{
    // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError
    //         exception.
    CallArgs args = CallArgsFromVp(argc, vp);
    return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>,
                                ReadableStreamBYOBRequest_respondWithNewView_impl>(cx, args);
}

static const JSPropertySpec ReadableStreamBYOBRequest_properties[] = {
    JS_PSG("view", ReadableStreamBYOBRequest_view, 0),
    JS_PS_END
};

static const JSFunctionSpec ReadableStreamBYOBRequest_methods[] = {
    JS_FN("respond",            ReadableStreamBYOBRequest_respond,            1, 0),
    JS_FN("respondWithNewView", ReadableStreamBYOBRequest_respondWithNewView, 1, 0),
    JS_FS_END
};

CLASS_SPEC(ReadableStreamBYOBRequest, 3, 2, ClassSpec::DontDefineConstructor, 0,
           JS_NULL_CLASS_OPS);

// Streams spec, 3.12.1. IsReadableStreamBYOBRequest ( x )
// Implemented via is<ReadableStreamBYOBRequest>()

// Streams spec, 3.12.2. IsReadableByteStreamController ( x )
// Implemented via is<ReadableByteStreamController>()

// Streams spec, 3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller )
// Unified with 3.9.2 above.

static void
ReadableByteStreamControllerInvalidateBYOBRequest(NativeObject* controller);

// Streams spec, 3.12.4. ReadableByteStreamControllerClearPendingPullIntos ( controller )
static MOZ_MUST_USE bool
ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx, HandleNativeObject controller)
{
    MOZ_ASSERT(controller->is<ReadableByteStreamController>());

    // Step 1: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
    ReadableByteStreamControllerInvalidateBYOBRequest(controller);

    // Step 2: Set controller.[[pendingPullIntos]] to a new empty List.
    return SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos);
}

// Streams spec, 3.12.5. ReadableByteStreamControllerClose ( controller )
static MOZ_MUST_USE bool
ReadableByteStreamControllerClose(JSContext* cx, Handle<ReadableByteStreamController*> controller)
{
    // Step 1: Let stream be controller.[[controlledReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 2: Assert: controller.[[closeRequested]] is false.
    MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));

    // Step 3: Assert: stream.[[state]] is "readable".
    MOZ_ASSERT(stream->readable());

    // Step 4: If controller.[[queueTotalSize]] > 0,
    double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
    if (queueTotalSize > 0) {
        // Step a: Set controller.[[closeRequested]] to true.
        AddControllerFlags(controller, ControllerFlag_CloseRequested);

        // Step b: Return
        return true;
    }

    // Step 5: If controller.[[pendingPullIntos]] is not empty,
    RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
    RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
    if (pendingPullIntos->getDenseInitializedLength() != 0) {
        // Step a: Let firstPendingPullInto be the first element of
        //         controller.[[pendingPullIntos]].
        Rooted<PullIntoDescriptor*> firstPendingPullInto(cx);
        firstPendingPullInto = PeekList<PullIntoDescriptor>(pendingPullIntos);

        // Step b: If firstPendingPullInto.[[bytesFilled]] > 0,
        if (firstPendingPullInto->bytesFilled() > 0) {
            // Step i: Let e be a new TypeError exception. {
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                      JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL);
            RootedValue e(cx);
            // Not much we can do about uncatchable exceptions, just bail.
            if (!cx->getPendingException(&e))
                return false;
            // Step ii: Perform ! ReadableByteStreamControllerError(controller, e).
            if (!ReadableStreamControllerError(cx, controller, e))
                return false;

            // Step iii: Throw e.
            return false;
        }
    }

    // Step 6: Perform ! ReadableStreamClose(stream).
    return ReadableStreamCloseInternal(cx, stream);
}

static MOZ_MUST_USE JSObject*
ReadableByteStreamControllerConvertPullIntoDescriptor(JSContext* cx,
                                                      Handle<PullIntoDescriptor*> pullIntoDescriptor);

// Streams spec, 3.12.6. ReadableByteStreamControllerCommitPullIntoDescriptor ( stream, pullIntoDescriptor )
static MOZ_MUST_USE bool
ReadableByteStreamControllerCommitPullIntoDescriptor(JSContext* cx, Handle<ReadableStream*> stream,
                                                     Handle<PullIntoDescriptor*> pullIntoDescriptor)
{
    // Step 1: MOZ_ASSERT: stream.[[state]] is not "errored".
    MOZ_ASSERT(!stream->errored());

    // Step 2: Let done be false.
    bool done = false;

    // Step 3: If stream.[[state]] is "closed",
    if (stream->closed()) {
        // Step a: MOZ_ASSERT: pullIntoDescriptor.[[bytesFilled]] is 0.
        MOZ_ASSERT(pullIntoDescriptor->bytesFilled() == 0);

        // Step b: Set done to true.
        done = true;
    }

    // Step 4: Let filledView be
    //         ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
    RootedObject filledView(cx);
    filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(cx, pullIntoDescriptor);
    if (!filledView)
        return false;

    // Step 5: If pullIntoDescriptor.[[readerType]] is "default",
    uint32_t readerType = pullIntoDescriptor->readerType();
    RootedValue filledViewVal(cx, ObjectValue(*filledView));
    if (readerType == ReaderType_Default) {
        // Step a: Perform ! ReadableStreamFulfillReadRequest(stream, filledView, done).
        if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, filledViewVal, done))
            return false;
    } else {
        // Step 6: Otherwise,
        // Step a: MOZ_ASSERT: pullIntoDescriptor.[[readerType]] is "byob".
        MOZ_ASSERT(readerType == ReaderType_BYOB);

        // Step b: Perform ! ReadableStreamFulfillReadIntoRequest(stream, filledView, done).
        if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, filledViewVal, done))
            return false;
    }

    return true;
}

// Streams spec, 3.12.7. ReadableByteStreamControllerConvertPullIntoDescriptor ( pullIntoDescriptor )
static MOZ_MUST_USE JSObject*
ReadableByteStreamControllerConvertPullIntoDescriptor(JSContext* cx,
                                                      Handle<PullIntoDescriptor*> pullIntoDescriptor)
{
    // Step 1: Let bytesFilled be pullIntoDescriptor.[[bytesFilled]].
    uint32_t bytesFilled = pullIntoDescriptor->bytesFilled();

    // Step 2: Let elementSize be pullIntoDescriptor.[[elementSize]].
    uint32_t elementSize = pullIntoDescriptor->elementSize();

    // Step 3: Assert: bytesFilled <= pullIntoDescriptor.[[byteLength]].
    MOZ_ASSERT(bytesFilled <= pullIntoDescriptor->byteLength());

    // Step 4: Assert: bytesFilled mod elementSize is 0.
    MOZ_ASSERT(bytesFilled % elementSize == 0);

    // Step 5: Return ! Construct(pullIntoDescriptor.[[ctor]],
    //                            pullIntoDescriptor.[[buffer]],
    //                            pullIntoDescriptor.[[byteOffset]],
    //                            bytesFilled / elementSize).
    RootedObject ctor(cx, pullIntoDescriptor->ctor());
    if (!ctor) {
        if (!GetBuiltinConstructor(cx, JSProto_Uint8Array, &ctor))
            return nullptr;
    }
    RootedObject buffer(cx, pullIntoDescriptor->buffer());
    uint32_t byteOffset = pullIntoDescriptor->byteOffset();
    FixedConstructArgs<3> args(cx);
    args[0].setObject(*buffer);
    args[1].setInt32(byteOffset);
    args[2].setInt32(bytesFilled / elementSize);
    return JS_New(cx, ctor, args);
}

static MOZ_MUST_USE bool
ReadableByteStreamControllerEnqueueChunkToQueue(JSContext* cx,
                                                Handle<ReadableByteStreamController*> controller,
                                                HandleObject buffer, uint32_t byteOffset,
                                                uint32_t byteLength);

static MOZ_MUST_USE ArrayBufferObject*
TransferArrayBuffer(JSContext* cx, HandleObject buffer);

static MOZ_MUST_USE bool
ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(JSContext* cx,
                                                                 Handle<ReadableByteStreamController*> controller);

// Streams spec, 3.12.8. ReadableByteStreamControllerEnqueue ( controller, chunk )
static MOZ_MUST_USE bool
ReadableByteStreamControllerEnqueue(JSContext* cx,
                                    Handle<ReadableByteStreamController*> controller,
                                    HandleObject chunk)
{
    // Step 1: Let stream be controller.[[controlledReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 2: Assert: controller.[[closeRequested]] is false.
    MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));

    // Step 3: Assert: stream.[[state]] is "readable".
    MOZ_ASSERT(stream->readable());

    // To make enqueuing chunks via JSAPI nicer, we want to be able to deal
    // with ArrayBuffer objects in addition to ArrayBuffer views here.
    // This cannot happen when enqueuing happens via
    // ReadableByteStreamController_enqueue because that throws if invoked
    // with anything but an ArrayBuffer view.

    Rooted<ArrayBufferObject*> buffer(cx);
    uint32_t byteOffset;
    uint32_t byteLength;

    if (chunk->is<ArrayBufferObject>()) {
        // Steps 4-6 for ArrayBuffer objects.
        buffer = &chunk->as<ArrayBufferObject>();
        byteOffset = 0;
        byteLength = buffer->byteLength();
    } else {
        // Step 4: Let buffer be chunk.[[ViewedArrayBuffer]].
        bool dummy;
        JSObject* bufferObj = JS_GetArrayBufferViewBuffer(cx, chunk, &dummy);
        if (!bufferObj)
            return false;
        buffer = &bufferObj->as<ArrayBufferObject>();

        // Step 5: Let byteOffset be chunk.[[ByteOffset]].
        byteOffset = JS_GetArrayBufferViewByteOffset(chunk);

        // Step 6: Let byteLength be chunk.[[ByteLength]].
        byteLength = JS_GetArrayBufferViewByteLength(chunk);
    }

    // Step 7: Let transferredBuffer be ! TransferArrayBuffer(buffer).
    RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
    if (!transferredBuffer)
        return false;

    // Step 8: If ! ReadableStreamHasDefaultReader(stream) is true
    if (ReadableStreamHasDefaultReader(stream)) {
        // Step a: If ! ReadableStreamGetNumReadRequests(stream) is 0,
        if (ReadableStreamGetNumReadRequests(stream) == 0) {
            // Step i: Perform
            //         ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
            //                                                           transferredBuffer,
            //                                                           byteOffset,
            //                                                           byteLength).
            if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer,
                                                                 byteOffset, byteLength))
            {
                return false;
            }
        } else {
            // Step b: Otherwise,
            // Step i: Assert: controller.[[queue]] is empty.
#if DEBUG
            RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
            RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
            MOZ_ASSERT(queue->getDenseInitializedLength() == 0);
#endif // DEBUG

            // Step ii: Let transferredView be
            //          ! Construct(%Uint8Array%, transferredBuffer, byteOffset, byteLength).
            RootedObject transferredView(cx, JS_NewUint8ArrayWithBuffer(cx, transferredBuffer,
                                                                        byteOffset, byteLength));
            if (!transferredView)
                return false;

            // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false).
            RootedValue chunk(cx, ObjectValue(*transferredView));
            if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false))
                return false;
        }
    } else if (ReadableStreamHasBYOBReader(stream)) {
        // Step 9: Otherwise,
        // Step a: If ! ReadableStreamHasBYOBReader(stream) is true,
        // Step i: Perform
        //         ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
        //                                                           transferredBuffer,
        //                                                           byteOffset,
        //                                                           byteLength).
        if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer,
                                                             byteOffset, byteLength))
        {
            return false;
        }

        // Step ii: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
        if (!ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller))
            return false;
    } else {
        // Step b: Otherwise,
        // Step i: Assert: ! IsReadableStreamLocked(stream) is false.
        MOZ_ASSERT(!stream->locked());

        // Step ii: Perform
        //          ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
        //                                                            transferredBuffer,
        //                                                            byteOffset,
        //                                                            byteLength).
        if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer,
                                                            byteOffset, byteLength))
        {
            return false;
        }
    }

    return true;
}

// Streams spec, 3.12.9.
// ReadableByteStreamControllerEnqueueChunkToQueue ( controller, buffer,
//                                                   byteOffset, byteLength )
static MOZ_MUST_USE bool
ReadableByteStreamControllerEnqueueChunkToQueue(JSContext* cx,
                                                Handle<ReadableByteStreamController*> controller,
                                                HandleObject buffer, uint32_t byteOffset,
                                                uint32_t byteLength)
{
    MOZ_ASSERT(controller->is<ReadableByteStreamController>(), "must operate on ReadableByteStreamController");

    // Step 1: Append Record {[[buffer]]: buffer,
    //                        [[byteOffset]]: byteOffset,
    //                        [[byteLength]]: byteLength}
    //         as the last element of controller.[[queue]].
    RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
    RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());

    Rooted<ByteStreamChunk*> chunk(cx);
    chunk = ByteStreamChunk::create(cx, buffer, byteOffset, byteLength);
    if (!chunk)
        return false;

    RootedValue chunkVal(cx, ObjectValue(*chunk));
    if (!AppendToList(cx, queue, chunkVal))
        return false;

    // Step 2: Add byteLength to controller.[[queueTotalSize]].
    double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
    controller->setFixedSlot(QueueContainerSlot_TotalSize,
                             NumberValue(queueTotalSize + byteLength));

    return true;
}

// Streams spec, 3.12.10. ReadableByteStreamControllerError ( controller, e )
// Unified with 3.9.6 above.

// Streams spec, 3.12.11. ReadableByteStreamControllerFillHeadPullIntoDescriptor ( controler, size, pullIntoDescriptor )
static void
ReadableByteStreamControllerFillHeadPullIntoDescriptor(ReadableByteStreamController* controller, uint32_t size,
                                                       Handle<PullIntoDescriptor*> pullIntoDescriptor)
{
    // Step 1: Assert: either controller.[[pendingPullIntos]] is empty, or the
    //         first element of controller.[[pendingPullIntos]] is pullIntoDescriptor.
#if DEBUG
    Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
    NativeObject* pendingPullIntos = &val.toObject().as<NativeObject>();
    MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() == 0 ||
               &pendingPullIntos->getDenseElement(0).toObject() == pullIntoDescriptor);
#endif // DEBUG

    // Step 2: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
    ReadableByteStreamControllerInvalidateBYOBRequest(controller);

    // Step 3: Set pullIntoDescriptor.[[bytesFilled]] to pullIntoDescriptor.[[bytesFilled]] + size.
    pullIntoDescriptor->setBytesFilled(pullIntoDescriptor->bytesFilled() + size);
}

// Streams spec, 3.12.12. ReadableByteStreamControllerFillPullIntoDescriptorFromQueue ( controller, pullIntoDescriptor )
static MOZ_MUST_USE bool
ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(JSContext* cx,
                                                            Handle<ReadableByteStreamController*> controller,
                                                            Handle<PullIntoDescriptor*> pullIntoDescriptor,
                                                            bool* ready)
{
    *ready = false;

    // Step 1: Let elementSize be pullIntoDescriptor.[[elementSize]].
    uint32_t elementSize = pullIntoDescriptor->elementSize();

    // Step 2: Let currentAlignedBytes be pullIntoDescriptor.[[bytesFilled]] −
    //         (pullIntoDescriptor.[[bytesFilled]] mod elementSize).
    uint32_t bytesFilled = pullIntoDescriptor->bytesFilled();
    uint32_t currentAlignedBytes = bytesFilled - (bytesFilled % elementSize);

    // Step 3: Let maxBytesToCopy be min(controller.[[queueTotalSize]],
    //         pullIntoDescriptor.[[byteLength]] − pullIntoDescriptor.[[bytesFilled]]).
    uint32_t byteLength = pullIntoDescriptor->byteLength();

    // The queue size could be negative or overflow uint32_t. We cannot
    // validly have a maxBytesToCopy value that'd overflow uint32_t, though,
    // so just clamp to that.
    Value sizeVal = controller->getFixedSlot(QueueContainerSlot_TotalSize);
    uint32_t queueTotalSize = JS::ToUint32(sizeVal.toNumber());
    uint32_t maxBytesToCopy = std::min(queueTotalSize, byteLength - bytesFilled);

    // Step 4: Let maxBytesFilled be pullIntoDescriptor.[[bytesFilled]] + maxBytesToCopy.
    uint32_t maxBytesFilled = bytesFilled + maxBytesToCopy;

    // Step 5: Let maxAlignedBytes be maxBytesFilled − (maxBytesFilled mod elementSize).
    uint32_t maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize);

    // Step 6: Let totalBytesToCopyRemaining be maxBytesToCopy.
    uint32_t totalBytesToCopyRemaining = maxBytesToCopy;

    // Step 7: Let ready be false (implicit).

    // Step 8: If maxAlignedBytes > currentAlignedBytes,
    if (maxAlignedBytes > currentAlignedBytes) {
        // Step a: Set totalBytesToCopyRemaining to maxAlignedBytes −
        //         pullIntoDescriptor.[[bytesFilled]].
        totalBytesToCopyRemaining = maxAlignedBytes - bytesFilled;

        // Step b: Let ready be true.
        *ready = true;
    }

    if (ControllerFlags(controller) & ControllerFlag_ExternalSource) {
        // TODO: it probably makes sense to eagerly drain the underlying source.
        // We have a buffer lying around anyway, whereas the source might be
        // able to free or reuse buffers once their content is copied into
        // our buffer.
        if (!ready)
            return true;

        Value val = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
        void* underlyingSource = val.toPrivate();

        RootedArrayBufferObject targetBuffer(cx, pullIntoDescriptor->buffer());
        Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

        size_t bytesWritten;
        {
            JS::AutoSuppressGCAnalysis noGC(cx);
            bool dummy;
            uint8_t* buffer = JS_GetArrayBufferData(targetBuffer, &dummy, noGC);
            buffer += bytesFilled;
            auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
            MOZ_ASSERT(cb);
            cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer,
               totalBytesToCopyRemaining, &bytesWritten);
            pullIntoDescriptor->setBytesFilled(bytesFilled + bytesWritten);
        }

        queueTotalSize -= bytesWritten;
        controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(queueTotalSize));

        return true;
    }

    // Step 9: Let queue be controller.[[queue]].
    RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
    RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());

    // Step 10: Repeat the following steps while totalBytesToCopyRemaining > 0,
    Rooted<ByteStreamChunk*> headOfQueue(cx);
    while (totalBytesToCopyRemaining > 0) {
        MOZ_ASSERT(queue->getDenseInitializedLength() != 0);

        // Step a: Let headOfQueue be the first element of queue.
        headOfQueue = PeekList<ByteStreamChunk>(queue);

        // Step b: Let bytesToCopy be min(totalBytesToCopyRemaining,
        //                                headOfQueue.[[byteLength]]).
        uint32_t byteLength = headOfQueue->byteLength();
        uint32_t bytesToCopy = std::min(totalBytesToCopyRemaining, byteLength);

        // Step c: Let destStart be pullIntoDescriptor.[[byteOffset]] +
        //         pullIntoDescriptor.[[bytesFilled]].
        uint32_t destStart = pullIntoDescriptor->byteOffset() + bytesFilled;

        // Step d: Perform ! CopyDataBlockBytes(pullIntoDescriptor.[[buffer]].[[ArrayBufferData]],
        //                                      destStart,
        //                                      headOfQueue.[[buffer]].[[ArrayBufferData]],
        //                                      headOfQueue.[[byteOffset]],
        //                                      bytesToCopy).
        RootedArrayBufferObject sourceBuffer(cx, headOfQueue->buffer());
        uint32_t sourceOffset = headOfQueue->byteOffset();
        RootedArrayBufferObject targetBuffer(cx, pullIntoDescriptor->buffer());
        ArrayBufferObject::copyData(targetBuffer, destStart, sourceBuffer, sourceOffset,
                                    bytesToCopy);

        // Step e: If headOfQueue.[[byteLength]] is bytesToCopy,
        if (byteLength == bytesToCopy) {
            // Step i: Remove the first element of queue, shifting all other elements
            //         downward (so that the second becomes the first, and so on).
            headOfQueue = ShiftFromList<ByteStreamChunk>(cx, queue);
            MOZ_ASSERT(headOfQueue);
        } else {
            // Step f: Otherwise,
            // Step i: Set headOfQueue.[[byteOffset]] to headOfQueue.[[byteOffset]] +
            //         bytesToCopy.
            headOfQueue->SetByteOffset(sourceOffset + bytesToCopy);

            // Step ii: Set headOfQueue.[[byteLength]] to headOfQueue.[[byteLength]] −
            //          bytesToCopy.
            headOfQueue->SetByteLength(byteLength - bytesToCopy);
        }

        // Step g: Set controller.[[queueTotalSize]] to
        //         controller.[[queueTotalSize]] − bytesToCopy.
        queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
        queueTotalSize -= bytesToCopy;
        controller->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(queueTotalSize));

        // Step h: Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
        //                                                                          bytesToCopy,
        //                                                                          pullIntoDescriptor).
        ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesToCopy,
                                                               pullIntoDescriptor);
        bytesFilled += bytesToCopy;
        MOZ_ASSERT(bytesFilled == pullIntoDescriptor->bytesFilled());

        // Step i: Set totalBytesToCopyRemaining to totalBytesToCopyRemaining − bytesToCopy.
        totalBytesToCopyRemaining -= bytesToCopy;
    }

    // Step 11: If ready is false,
    if (!*ready) {
        // Step a: Assert: controller.[[queueTotalSize]] is 0.
        MOZ_ASSERT(controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber() == 0);

        // Step b: Assert: pullIntoDescriptor.[[bytesFilled]] > 0.
        MOZ_ASSERT(bytesFilled > 0, "should have filled some bytes");

        // Step c: Assert: pullIntoDescriptor.[[bytesFilled]] <
        //         pullIntoDescriptor.[[elementSize]].
        MOZ_ASSERT(bytesFilled < elementSize);
    }

    // Step 12: Return ready.
    return true;
}

// Streams spec 3.12.13. ReadableByteStreamControllerGetDesiredSize ( controller )
// Unified with 3.9.8 above.

// Streams spec, 3.12.14. ReadableByteStreamControllerHandleQueueDrain ( controller )
static MOZ_MUST_USE bool
ReadableByteStreamControllerHandleQueueDrain(JSContext* cx, HandleNativeObject controller)
{
    MOZ_ASSERT(controller->is<ReadableByteStreamController>());

    // Step 1: Assert: controller.[[controlledReadableStream]].[[state]] is "readable".
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
    MOZ_ASSERT(stream->readable());

    // Step 2: If controller.[[queueTotalSize]] is 0 and
    //         controller.[[closeRequested]] is true,
    double totalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
    bool closeRequested = ControllerFlags(controller) & ControllerFlag_CloseRequested;
    if (totalSize == 0 && closeRequested) {
      // Step a: Perform ! ReadableStreamClose(controller.[[controlledReadableStream]]).
      return ReadableStreamCloseInternal(cx, stream);
    }

    // Step 3: Otherwise,
    // Step a: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
    return ReadableStreamControllerCallPullIfNeeded(cx, controller);
}

// Streams spec 3.12.15. ReadableByteStreamControllerInvalidateBYOBRequest ( controller )
static void
ReadableByteStreamControllerInvalidateBYOBRequest(NativeObject* controller)
{
    MOZ_ASSERT(controller->is<ReadableByteStreamController>());

    // Step 1: If controller.[[byobRequest]] is undefined, return.
    Value byobRequestVal = controller->getFixedSlot(ByteControllerSlot_BYOBRequest);
    if (byobRequestVal.isUndefined())
        return;

    NativeObject* byobRequest = &byobRequestVal.toObject().as<NativeObject>();
    // Step 2: Set controller.[[byobRequest]].[[associatedReadableByteStreamController]]
    //         to undefined.
    byobRequest->setFixedSlot(BYOBRequestSlot_Controller, UndefinedValue());

    // Step 3: Set controller.[[byobRequest]].[[view]] to undefined.
    byobRequest->setFixedSlot(BYOBRequestSlot_View, UndefinedValue());

    // Step 4: Set controller.[[byobRequest]] to undefined.
    controller->setFixedSlot(ByteControllerSlot_BYOBRequest, UndefinedValue());
}

static MOZ_MUST_USE PullIntoDescriptor*
ReadableByteStreamControllerShiftPendingPullInto(JSContext* cx, HandleNativeObject controller);

// Streams spec 3.12.16. ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue ( controller )
static MOZ_MUST_USE bool
ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(JSContext* cx,
                                                                 Handle<ReadableByteStreamController*> controller)
{
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 1: Assert: controller.[[closeRequested]] is false.
    MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));

    // Step 2: Repeat the following steps while controller.[[pendingPullIntos]]
    //         is not empty,
    RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
    RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
    Rooted<PullIntoDescriptor*> pullIntoDescriptor(cx);
    while (pendingPullIntos->getDenseInitializedLength() != 0) {
        // Step a: If controller.[[queueTotalSize]] is 0, return.
        double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
        if (queueTotalSize == 0)
            return true;

        // Step b: Let pullIntoDescriptor be the first element of
        //         controller.[[pendingPullIntos]].
        pullIntoDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);

        // Step c: If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor)
        //         is true,
        bool ready;
        if (!ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(cx, controller,
                                                                         pullIntoDescriptor,
                                                                         &ready))
        {
            return false;
        }
        if (ready) {
            // Step i: Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller).
            if (!ReadableByteStreamControllerShiftPendingPullInto(cx, controller))
                return false;

            // Step ii: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[controlledReadableStream]],
            //                                                                         pullIntoDescriptor).
            if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream,
                                                                      pullIntoDescriptor))
            {
                return false;
            }
        }
    }

    return true;
}

// Streams spec, 3.12.17. ReadableByteStreamControllerPullInto ( controller, view )
static MOZ_MUST_USE JSObject*
ReadableByteStreamControllerPullInto(JSContext* cx,
                                     Handle<ReadableByteStreamController*> controller,
                                     Handle<ArrayBufferViewObject*> view)
{
    MOZ_ASSERT(controller->is<ReadableByteStreamController>());

    // Step 1: Let stream be controller.[[controlledReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 2: Let elementSize be 1.
    uint32_t elementSize = 1;

    RootedObject ctor(cx);
    // Step 4: If view has a [[TypedArrayName]] internal slot (i.e., it is not a
    //         DataView),
    if (view->is<TypedArrayObject>()) {
        JSProtoKey protoKey = StandardProtoKeyOrNull(view);
        MOZ_ASSERT(protoKey);

        if (!GetBuiltinConstructor(cx, protoKey, &ctor))
            return nullptr;
        elementSize = 1 << TypedArrayShift(view->as<TypedArrayObject>().type());
    } else {
        // Step 3: Let ctor be %DataView% (reordered).
        if (!GetBuiltinConstructor(cx, JSProto_DataView, &ctor))
            return nullptr;
    }

    // Step 5: Let pullIntoDescriptor be Record {[[buffer]]: view.[[ViewedArrayBuffer]],
    //                                           [[byteOffset]]: view.[[ByteOffset]],
    //                                           [[byteLength]]: view.[[ByteLength]],
    //                                           [[bytesFilled]]: 0,
    //                                           [[elementSize]]: elementSize,
    //                                           [[ctor]]: ctor,
    //                                           [[readerType]]: "byob"}.
    bool dummy;
    RootedArrayBufferObject buffer(cx, &JS_GetArrayBufferViewBuffer(cx, view, &dummy)
                                       ->as<ArrayBufferObject>());
    if (!buffer)
        return nullptr;

    uint32_t byteOffset = JS_GetArrayBufferViewByteOffset(view);
    uint32_t byteLength = JS_GetArrayBufferViewByteLength(view);
    Rooted<PullIntoDescriptor*> pullIntoDescriptor(cx);
    pullIntoDescriptor = PullIntoDescriptor::create(cx, buffer, byteOffset, byteLength, 0,
                                                    elementSize, ctor,
                                                    ReaderType_BYOB);
    if (!pullIntoDescriptor)
        return nullptr;

    // Step 6: If controller.[[pendingPullIntos]] is not empty,
    RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
    RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
    if (pendingPullIntos->getDenseInitializedLength() != 0) {
        // Step a: Set pullIntoDescriptor.[[buffer]] to
        //         ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]).
        RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
        if (!transferredBuffer)
            return nullptr;
        pullIntoDescriptor->setBuffer(transferredBuffer);

        // Step b: Append pullIntoDescriptor as the last element of
        //         controller.[[pendingPullIntos]].
        val = ObjectValue(*pullIntoDescriptor);
        if (!AppendToList(cx, pendingPullIntos, val))
            return nullptr;

        // Step c: Return ! ReadableStreamAddReadIntoRequest(stream).
        return ReadableStreamAddReadIntoRequest(cx, stream);
    }

    // Step 7: If stream.[[state]] is "closed",
    if (stream->closed()) {
        // Step a: Let emptyView be ! Construct(ctor, pullIntoDescriptor.[[buffer]],
        //                                            pullIntoDescriptor.[[byteOffset]], 0).
        FixedConstructArgs<3> args(cx);
        args[0].setObject(*buffer);
        args[1].setInt32(byteOffset);
        args[2].setInt32(0);
        RootedObject emptyView(cx, JS_New(cx, ctor, args));
        if (!emptyView)
            return nullptr;

        // Step b: Return a promise resolved with
        //         ! CreateIterResultObject(emptyView, true).
        RootedValue val(cx, ObjectValue(*emptyView));
        RootedObject iterResult(cx, CreateIterResultObject(cx, val, true));
        if (!iterResult)
            return nullptr;
        val = ObjectValue(*iterResult);
        return PromiseObject::unforgeableResolve(cx, val);
    }

    // Step 8: If controller.[[queueTotalSize]] > 0,
    double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
    if (queueTotalSize > 0) {
        // Step a: If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
        //                                                                          pullIntoDescriptor)
        //         is true,
        bool ready;
        if (!ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(cx, controller,
                                                                         pullIntoDescriptor, &ready))
        {
            return nullptr;
        }

        if (ready) {
            // Step i: Let filledView be
            //         ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
            RootedObject filledView(cx);
            filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(cx,
                                                                               pullIntoDescriptor);
            if (!filledView)
                return nullptr;

            // Step ii: Perform ! ReadableByteStreamControllerHandleQueueDrain(controller).
            if (!ReadableByteStreamControllerHandleQueueDrain(cx, controller))
                return nullptr;

            // Step iii: Return a promise resolved with
            //           ! CreateIterResultObject(filledView, false).
            val = ObjectValue(*filledView);
            RootedObject iterResult(cx, CreateIterResultObject(cx, val, false));
            if (!iterResult)
                return nullptr;
            val = ObjectValue(*iterResult);
            return PromiseObject::unforgeableResolve(cx, val);
        }

        // Step b: If controller.[[closeRequested]] is true,
        if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
            // Step i: Let e be a TypeError exception.
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                      JSMSG_READABLESTREAMCONTROLLER_CLOSED, "read");

            // Not much we can do about uncatchable exceptions, just bail.
            RootedValue e(cx);
            if (!GetAndClearException(cx, &e))
                return nullptr;

            // Step ii: Perform ! ReadableByteStreamControllerError(controller, e).
            if (!ReadableStreamControllerError(cx, controller, e))
                return nullptr;

            // Step iii: Return a promise rejected with e.
            return PromiseObject::unforgeableReject(cx, e);
        }
    }

    // Step 9: Set pullIntoDescriptor.[[buffer]] to
    //         ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]).
    RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
    if (!transferredBuffer)
        return nullptr;
    pullIntoDescriptor->setBuffer(transferredBuffer);

    // Step 10: Append pullIntoDescriptor as the last element of
    //          controller.[[pendingPullIntos]].
    val = ObjectValue(*pullIntoDescriptor);
    if (!AppendToList(cx, pendingPullIntos, val))
        return nullptr;

    // Step 11: Let promise be ! ReadableStreamAddReadIntoRequest(stream).
    Rooted<PromiseObject*> promise(cx, ReadableStreamAddReadIntoRequest(cx, stream));
    if (!promise)
        return nullptr;

    // Step 12: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
    if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
        return nullptr;

    // Step 13: Return promise.
    return promise;
}

static MOZ_MUST_USE bool
ReadableByteStreamControllerRespondInternal(JSContext* cx,
                                            Handle<ReadableByteStreamController*> controller,
                                            double bytesWritten);

// Streams spec 3.12.18. ReadableByteStreamControllerRespond( controller, bytesWritten )
static MOZ_MUST_USE bool
ReadableByteStreamControllerRespond(JSContext* cx,
                                    Handle<ReadableByteStreamController*> controller,
                                    HandleValue bytesWrittenVal)
{
    MOZ_ASSERT(controller->is<ReadableByteStreamController>());

    // Step 1: Let bytesWritten be ? ToNumber(bytesWritten).
    double bytesWritten;
    if (!ToNumber(cx, bytesWrittenVal, &bytesWritten))
        return false;

    // Step 2: If ! IsFiniteNonNegativeNumber(bytesWritten) is false,
    if (bytesWritten < 0 || mozilla::IsNaN(bytesWritten) || mozilla::IsInfinite(bytesWritten)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "bytesWritten");
        return false;
    }

    // Step 3: Assert: controller.[[pendingPullIntos]] is not empty.
#if DEBUG
    Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
    RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
    MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() != 0);
#endif // DEBUG

    // Step 4: Perform ? ReadableByteStreamControllerRespondInternal(controller, bytesWritten).
    return ReadableByteStreamControllerRespondInternal(cx, controller, bytesWritten);
}

// Streams spec 3.12.19. ReadableByteStreamControllerRespondInClosedState( controller, firstDescriptor )
static MOZ_MUST_USE bool
ReadableByteStreamControllerRespondInClosedState(JSContext* cx,
                                                   Handle<ReadableByteStreamController*> controller,
                                                   Handle<PullIntoDescriptor*> firstDescriptor)
{
    // Step 1: Set firstDescriptor.[[buffer]] to
    //         ! TransferArrayBuffer(firstDescriptor.[[buffer]]).
    RootedArrayBufferObject buffer(cx, firstDescriptor->buffer());
    RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
    if (!transferredBuffer)
        return false;
    firstDescriptor->setBuffer(transferredBuffer);

    // Step 2: Assert: firstDescriptor.[[bytesFilled]] is 0.
    MOZ_ASSERT(firstDescriptor->bytesFilled() == 0);

    // Step 3: Let stream be controller.[[controlledReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 4: If ReadableStreamHasBYOBReader(stream) is true,
    if (ReadableStreamHasBYOBReader(stream)) {
        // Step a: Repeat the following steps while
        //         ! ReadableStreamGetNumReadIntoRequests(stream) > 0,
        Rooted<PullIntoDescriptor*> descriptor(cx);
        while (ReadableStreamGetNumReadRequests(stream) > 0) {
            // Step i: Let pullIntoDescriptor be
            //         ! ReadableByteStreamControllerShiftPendingPullInto(controller).
            descriptor = ReadableByteStreamControllerShiftPendingPullInto(cx, controller);
            if (!descriptor)
                return false;

            // Step ii: Perform !
            //          ReadableByteStreamControllerCommitPullIntoDescriptor(stream,
            //                                                               pullIntoDescriptor).
            if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream, descriptor))
                return false;
        }
    }

    return true;
}

// Streams spec 3.12.20.
// ReadableByteStreamControllerRespondInReadableState( controller, bytesWritten, pullIntoDescriptor )
static MOZ_MUST_USE bool
ReadableByteStreamControllerRespondInReadableState(JSContext* cx,
                                                   Handle<ReadableByteStreamController*> controller,
                                                   uint32_t bytesWritten,
                                                   Handle<PullIntoDescriptor*> pullIntoDescriptor)
{
    // Step 1: If pullIntoDescriptor.[[bytesFilled]] + bytesWritten > pullIntoDescriptor.[[byteLength]],
    //         throw a RangeError exception.
    uint32_t bytesFilled = pullIntoDescriptor->bytesFilled();
    uint32_t byteLength = pullIntoDescriptor->byteLength();
    if (bytesFilled + bytesWritten > byteLength) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN);
        return false;
    }

    // Step 2: Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
    //                                                                          bytesWritten,
    //                                                                          pullIntoDescriptor).
    ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesWritten,
                                                           pullIntoDescriptor);
    bytesFilled += bytesWritten;

    // Step 3: If pullIntoDescriptor.[[bytesFilled]] <
    //         pullIntoDescriptor.[[elementSize]], return.
    uint32_t elementSize = pullIntoDescriptor->elementSize();
    if (bytesFilled < elementSize)
        return true;

    // Step 4: Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller).
    if (!ReadableByteStreamControllerShiftPendingPullInto(cx, controller))
        return false;

    // Step 5: Let remainderSize be pullIntoDescriptor.[[bytesFilled]] mod
    //         pullIntoDescriptor.[[elementSize]].
    uint32_t remainderSize = bytesFilled % elementSize;

    // Step 6: If remainderSize > 0,
    RootedArrayBufferObject buffer(cx, pullIntoDescriptor->buffer());
    if (remainderSize > 0) {
        // Step a: Let end be pullIntoDescriptor.[[byteOffset]] +
        //         pullIntoDescriptor.[[bytesFilled]].
        uint32_t end = pullIntoDescriptor->byteOffset() + bytesFilled;

        // Step b: Let remainder be ? CloneArrayBuffer(pullIntoDescriptor.[[buffer]],
        //                                             end − remainderSize,
        //                                             remainderSize, %ArrayBuffer%).
        // TODO: this really, really should just use a slot to store the remainder.
        RootedObject remainderObj(cx, JS_NewArrayBuffer(cx, remainderSize));
        if (!remainderObj)
            return false;
        RootedArrayBufferObject remainder(cx, &remainderObj->as<ArrayBufferObject>());
        ArrayBufferObject::copyData(remainder, 0, buffer, end - remainderSize, remainderSize);

        // Step c: Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
        //                                                                   remainder, 0,
        //                                                                   remainder.[[ByteLength]]).
        // Note: `remainderSize` is equivalent to remainder.[[ByteLength]].
        if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, remainder, 0,
                                                             remainderSize))
        {
            return false;
        }
    }

    // Step 7: Set pullIntoDescriptor.[[buffer]] to
    //         ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]).
    RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
    if (!transferredBuffer)
        return false;
    pullIntoDescriptor->setBuffer(transferredBuffer);

    // Step 8: Set pullIntoDescriptor.[[bytesFilled]] to pullIntoDescriptor.[[bytesFilled]] −
    //         remainderSize.
    pullIntoDescriptor->setBytesFilled(bytesFilled - remainderSize);

    // Step 9: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[controlledReadableStream]],
    //                                                                        pullIntoDescriptor).
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
    if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream, pullIntoDescriptor))
        return false;

    // Step 10: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
    return ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller);
}

// Streams spec, 3.12.21. ReadableByteStreamControllerRespondInternal ( controller, bytesWritten )
static MOZ_MUST_USE bool
ReadableByteStreamControllerRespondInternal(JSContext* cx,
                                            Handle<ReadableByteStreamController*> controller,
                                            double bytesWritten)
{
    // Step 1: Let firstDescriptor be the first element of controller.[[pendingPullIntos]].
    RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
    RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
    Rooted<PullIntoDescriptor*> firstDescriptor(cx);
    firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);

    // Step 2: Let stream be controller.[[controlledReadableStream]].
    Rooted<ReadableStream*> stream(cx, StreamFromController(controller));

    // Step 3: If stream.[[state]] is "closed",
    if (stream->closed()) {
        // Step a: If bytesWritten is not 0, throw a TypeError exception.
        if (bytesWritten != 0) {
            JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                      JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED);
            return false;
        }

        // Step b: Perform
        //         ! ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor).
        return ReadableByteStreamControllerRespondInClosedState(cx, controller, firstDescriptor);
    }

    // Step 4: Otherwise,
    // Step a: Assert: stream.[[state]] is "readable".
    MOZ_ASSERT(stream->readable());

    // Step b: Perform ? ReadableByteStreamControllerRespondInReadableState(controller,
    //                                                                      bytesWritten,
    //                                                                      firstDescriptor).
    return ReadableByteStreamControllerRespondInReadableState(cx, controller, bytesWritten,
                                                              firstDescriptor);
}

// Streams spec, 3.12.22. ReadableByteStreamControllerRespondWithNewView ( controller, view )
static MOZ_MUST_USE bool
ReadableByteStreamControllerRespondWithNewView(JSContext* cx,
                                               Handle<ReadableByteStreamController*> controller,
                                               HandleObject view)
{
    // Step 1: Assert: controller.[[pendingPullIntos]] is not empty.
    RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
    RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
    MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() != 0);

    // Step 2: Let firstDescriptor be the first element of controller.[[pendingPullIntos]].
    Rooted<PullIntoDescriptor*> firstDescriptor(cx);
    firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);

    // Step 3: If firstDescriptor.[[byteOffset]] + firstDescriptor.[[bytesFilled]]
    //         is not view.[[ByteOffset]], throw a RangeError exception.
    uint32_t byteOffset = uint32_t(JS_GetArrayBufferViewByteOffset(view));
    if (firstDescriptor->byteOffset() + firstDescriptor->bytesFilled() != byteOffset) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET);
        return false;
    }

    // Step 4: If firstDescriptor.[[byteLength]] is not view.[[ByteLength]],
    //         throw a RangeError exception.
    uint32_t byteLength = JS_GetArrayBufferViewByteLength(view);
    if (firstDescriptor->byteLength() != byteLength) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE);
        return false;
    }

    // Step 5: Set firstDescriptor.[[buffer]] to view.[[ViewedArrayBuffer]].
    bool dummy;
    RootedArrayBufferObject buffer(cx,
                                   &AsArrayBuffer(JS_GetArrayBufferViewBuffer(cx, view, &dummy)));
    if (!buffer)
        return false;
    firstDescriptor->setBuffer(buffer);

    // Step 6: Perform ? ReadableByteStreamControllerRespondInternal(controller,
    //                                                               view.[[ByteLength]]).
    return ReadableByteStreamControllerRespondInternal(cx, controller, byteLength);
}

// Streams spec, 3.12.23. ReadableByteStreamControllerShiftPendingPullInto ( controller )
static MOZ_MUST_USE PullIntoDescriptor*
ReadableByteStreamControllerShiftPendingPullInto(JSContext* cx, HandleNativeObject controller)
{
    MOZ_ASSERT(controller->is<ReadableByteStreamController>());

    // Step 1: Let descriptor be the first element of controller.[[pendingPullIntos]].
    // Step 2: Remove descriptor from controller.[[pendingPullIntos]], shifting
    //         all other elements downward (so that the second becomes the first,
    //         and so on).
    RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
    RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
    Rooted<PullIntoDescriptor*> descriptor(cx);
    descriptor = ShiftFromList<PullIntoDescriptor>(cx, pendingPullIntos);
    MOZ_ASSERT(descriptor);

    // Step 3: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
    ReadableByteStreamControllerInvalidateBYOBRequest(controller);

    // Step 4: Return descriptor.
    return descriptor;
}

// Streams spec, 3.12.24. ReadableByteStreamControllerShouldCallPull ( controller )
// Unified with 3.9.3 above.

// Streams spec, 6.1.2. new ByteLengthQueuingStrategy({ highWaterMark })
bool
js::ByteLengthQueuingStrategy::constructor(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    RootedObject strategy(cx, NewBuiltinClassInstance<ByteLengthQueuingStrategy>(cx));
    if (!strategy)
        return false;

    RootedObject argObj(cx, ToObject(cx, args.get(0)));
    if (!argObj)
      return false;

    RootedValue highWaterMark(cx);
    if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark, &highWaterMark))
      return false;

    if (!SetProperty(cx, strategy, cx->names().highWaterMark, highWaterMark))
      return false;

    args.rval().setObject(*strategy);
    return true;
}

// Streams spec 6.1.3.1. size ( chunk )
bool
ByteLengthQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    // Step 1: Return ? GetV(chunk, "byteLength").
    return GetProperty(cx, args.get(0), cx->names().byteLength, args.rval());
}

static const JSPropertySpec ByteLengthQueuingStrategy_properties[] = {
    JS_PS_END
};

static const JSFunctionSpec ByteLengthQueuingStrategy_methods[] = {
    JS_FN("size", ByteLengthQueuingStrategy_size, 1, 0),
    JS_FS_END
};

CLASS_SPEC(ByteLengthQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);

// Streams spec, 6.2.2. new CountQueuingStrategy({ highWaterMark })
bool
js::CountQueuingStrategy::constructor(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    Rooted<CountQueuingStrategy*> strategy(cx, NewBuiltinClassInstance<CountQueuingStrategy>(cx));
    if (!strategy)
        return false;

    RootedObject argObj(cx, ToObject(cx, args.get(0)));
    if (!argObj)
      return false;

    RootedValue highWaterMark(cx);
    if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark, &highWaterMark))
      return false;

    if (!SetProperty(cx, strategy, cx->names().highWaterMark, highWaterMark))
      return false;

    args.rval().setObject(*strategy);
    return true;
}

// Streams spec 6.2.3.1. size ( chunk )
bool
CountQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp)
{
    CallArgs args = CallArgsFromVp(argc, vp);

    // Step 1: Return 1.
    args.rval().setInt32(1);
    return true;
}

static const JSPropertySpec CountQueuingStrategy_properties[] = {
    JS_PS_END
};

static const JSFunctionSpec CountQueuingStrategy_methods[] = {
    JS_FN("size", CountQueuingStrategy_size, 0, 0),
    JS_FS_END
};

CLASS_SPEC(CountQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);

#undef CLASS_SPEC

// Streams spec, 6.3.1. DequeueValue ( container ) nothrow
inline static MOZ_MUST_USE bool
DequeueValue(JSContext* cx, HandleNativeObject container, MutableHandleValue chunk)
{
    // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
    //         slots.
    MOZ_ASSERT(IsReadableStreamController(container));

    // Step 2: Assert: queue is not empty.
    RootedValue val(cx, container->getFixedSlot(QueueContainerSlot_Queue));
    RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
    MOZ_ASSERT(queue->getDenseInitializedLength() > 0);

    // Step 3. Let pair be the first element of queue.
    // Step 4. Remove pair from queue, shifting all other elements downward
    //         (so that the second becomes the first, and so on).
    Rooted<QueueEntry*> pair(cx, ShiftFromList<QueueEntry>(cx, queue));
    MOZ_ASSERT(pair);

    // Step 5: Set container.[[queueTotalSize]] to
    //         container.[[queueTotalSize]] − pair.[[size]].
    // Step 6: If container.[[queueTotalSize]] < 0, set
    //         container.[[queueTotalSize]] to 0.
    //         (This can occur due to rounding errors.)
    double totalSize = container->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();

    totalSize -= pair->size();
    if (totalSize < 0)
        totalSize = 0;
    container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize));

    // Step 7: Return pair.[[value]].
    chunk.set(pair->value());
    return true;
}

// Streams spec, 6.3.2. EnqueueValueWithSize ( container, value, size ) throws
static MOZ_MUST_USE bool
EnqueueValueWithSize(JSContext* cx, HandleNativeObject container, HandleValue value,
                     HandleValue sizeVal)
{
    // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
    //         slots.
    MOZ_ASSERT(IsReadableStreamController(container));

    // Step 2: Let size be ? ToNumber(size).
    double size;
    if (!ToNumber(cx, sizeVal, &size))
        return false;

    // Step 3: If ! IsFiniteNonNegativeNumber(size) is false, throw a RangeError
    //         exception.
    if (size < 0 || mozilla::IsNaN(size) || mozilla::IsInfinite(size)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "size");
        return false;
    }

    // Step 4: Append Record {[[value]]: value, [[size]]: size} as the last element
    //         of container.[[queue]].
    RootedValue val(cx, container->getFixedSlot(QueueContainerSlot_Queue));
    RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());

    QueueEntry* entry = QueueEntry::create(cx, value, size);
    if (!entry)
        return false;
    val = ObjectValue(*entry);
    if (!AppendToList(cx, queue, val))
        return false;

    // Step 5: Set container.[[queueTotalSize]] to
    //         container.[[queueTotalSize]] + size.
    double totalSize = container->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
    container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize + size));

    return true;
}

// Streams spec, 6.3.3. PeekQueueValue ( container ) nothrow
// Used by WritableStream.
// static MOZ_MUST_USE Value
// PeekQueueValue(NativeObject* container)
// {
//     // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
//     //         slots.
//     MOZ_ASSERT(IsReadableStreamController(container));

//     // Step 2: Assert: queue is not empty.
//     Value val = container->getFixedSlot(QueueContainerSlot_Queue);
//     NativeObject* queue = &val.toObject().as<NativeObject>();
//     MOZ_ASSERT(queue->getDenseInitializedLength() > 0);

//     // Step 3: Let pair be the first element of container.[[queue]].
//     QueueEntry* pair = PeekList<QueueEntry>(queue);

//     // Step 4: Return pair.[[value]].
//     return pair->value();
// }

/**
 * Streams spec, 6.3.4. ResetQueue ( container ) nothrow
 */
inline static MOZ_MUST_USE bool
ResetQueue(JSContext* cx, HandleNativeObject container)
{
    // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
    //         slots.
    MOZ_ASSERT(IsReadableStreamController(container));

    // Step 2: Set container.[[queue]] to a new empty List.
    if (!SetNewList(cx, container, QueueContainerSlot_Queue))
        return false;

    // Step 3: Set container.[[queueTotalSize]] to 0.
    container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(0));

    return true;
}


/**
 * Streams spec, 6.4.1. InvokeOrNoop ( O, P, args )
 */
inline static MOZ_MUST_USE bool
InvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg,
             MutableHandleValue rval)
{
    // Step 1: Assert: P is a valid property key (omitted).
    // Step 2: If args was not passed, let args be a new empty List (omitted).
    // Step 3: Let method be ? GetV(O, P).
    RootedValue method(cx);
    if (!GetProperty(cx, O, P, &method))
        return false;

    // Step 4: If method is undefined, return.
    if (method.isUndefined())
        return true;

    // Step 5: Return ? Call(method, O, args).
    return Call(cx, method, O, arg, rval);
}

/**
 * Streams spec, 6.4.3. PromiseInvokeOrNoop ( O, P, args )
 * Specialized to one arg, because that's what all stream related callers use.
 */
static MOZ_MUST_USE JSObject*
PromiseInvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg)
{
    // Step 1: Assert: O is not undefined.
    MOZ_ASSERT(!O.isUndefined());

    // Step 2: Assert: ! IsPropertyKey(P) is true (implicit).
    // Step 3: Assert: args is a List (omitted).

    // Step 4: Let returnValue be InvokeOrNoop(O, P, args).
    // Step 5: If returnValue is an abrupt completion, return a promise
    //         rejected with returnValue.[[Value]].
    RootedValue returnValue(cx);
    if (!InvokeOrNoop(cx, O, P, arg, &returnValue))
        return PromiseRejectedWithPendingError(cx);

    // Step 6: Otherwise, return a promise resolved with returnValue.[[Value]].
    return PromiseObject::unforgeableResolve(cx, returnValue);
}

/**
 * Streams spec, 6.4.4 TransferArrayBuffer ( O )
 */
static MOZ_MUST_USE ArrayBufferObject*
TransferArrayBuffer(JSContext* cx, HandleObject buffer)
{
    // Step 1 (implicit).

    // Step 2.
    MOZ_ASSERT(buffer->is<ArrayBufferObject>());

    // Step 3.
    MOZ_ASSERT(!JS_IsDetachedArrayBufferObject(buffer));

    // Step 5 (reordered).
    uint32_t size = buffer->as<ArrayBufferObject>().byteLength();

    // Steps 4, 6.
    void* contents = JS_StealArrayBufferContents(cx, buffer);
    if (!contents)
        return nullptr;
    MOZ_ASSERT(JS_IsDetachedArrayBufferObject(buffer));

    // Step 7.
    RootedObject transferredBuffer(cx, JS_NewArrayBufferWithContents(cx, size, contents));
    if (!transferredBuffer)
        return nullptr;
    return &transferredBuffer->as<ArrayBufferObject>();
}

// Streams spec, 6.4.5. ValidateAndNormalizeHighWaterMark ( highWaterMark )
static MOZ_MUST_USE bool
ValidateAndNormalizeHighWaterMark(JSContext* cx, HandleValue highWaterMarkVal, double* highWaterMark)
{
    // Step 1: Set highWaterMark to ? ToNumber(highWaterMark).
    if (!ToNumber(cx, highWaterMarkVal, highWaterMark))
        return false;

    // Step 2: If highWaterMark is NaN, throw a TypeError exception.
    // Step 3: If highWaterMark < 0, throw a RangeError exception.
    if (mozilla::IsNaN(*highWaterMark) || *highWaterMark < 0) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_STREAM_INVALID_HIGHWATERMARK);
        return false;
    }

    // Step 4: Return highWaterMark.
    return true;
}

// Streams spec, 6.4.6. ValidateAndNormalizeQueuingStrategy ( size, highWaterMark )
static MOZ_MUST_USE bool
ValidateAndNormalizeQueuingStrategy(JSContext* cx, HandleValue size,
                                    HandleValue highWaterMarkVal, double* highWaterMark)
{
    // Step 1: If size is not undefined and ! IsCallable(size) is false, throw a
    //         TypeError exception.
    if (!size.isUndefined() && !IsCallable(size)) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION,
                                  "ReadableStream argument options.size");
        return false;
    }

    // Step 2: Let highWaterMark be ? ValidateAndNormalizeHighWaterMark(highWaterMark).
    if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal, highWaterMark))
        return false;

    // Step 3: Return Record {[[size]]: size, [[highWaterMark]]: highWaterMark}.
    return true;
}

MOZ_MUST_USE bool
js::ReadableStreamReaderCancel(JSContext* cx, HandleObject readerObj, HandleValue reason)
{
    MOZ_ASSERT(IsReadableStreamReader(readerObj));
    RootedNativeObject reader(cx, &readerObj->as<NativeObject>());
    MOZ_ASSERT(StreamFromReader(reader));
    return ReadableStreamReaderGenericCancel(cx, reader, reason);
}

MOZ_MUST_USE bool
js::ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject readerObj)
{
    MOZ_ASSERT(IsReadableStreamReader(readerObj));
    RootedNativeObject reader(cx, &readerObj->as<NativeObject>());
    MOZ_ASSERT(ReadableStreamGetNumReadRequests(StreamFromReader(reader)) == 0);
    return ReadableStreamReaderGenericRelease(cx, reader);
}

MOZ_MUST_USE bool
ReadableStream::enqueue(JSContext* cx, Handle<ReadableStream*> stream, HandleValue chunk)
{
    Rooted<ReadableStreamDefaultController*> controller(cx);
    controller = &ControllerFromStream(stream)->as<ReadableStreamDefaultController>();

    MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
    MOZ_ASSERT(stream->readable());

    return ReadableStreamDefaultControllerEnqueue(cx, controller, chunk);
}

MOZ_MUST_USE bool
ReadableStream::enqueueBuffer(JSContext* cx, Handle<ReadableStream*> stream,
                              Handle<ArrayBufferObject*> chunk)
{
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();

    MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
    MOZ_ASSERT(stream->readable());

    return ReadableByteStreamControllerEnqueue(cx, controller, chunk);
}

void
ReadableStream::desiredSize(bool* hasSize, double* size) const
{
    if (errored()) {
        *hasSize = false;
        return;
    }

    *hasSize = true;

    if (closed()) {
        *size = 0;
        return;
    }

    NativeObject* controller = ControllerFromStream(this);
    *size = ReadableStreamControllerGetDesiredSizeUnchecked(controller);
}

/*static */ bool
ReadableStream::getExternalSource(JSContext* cx, Handle<ReadableStream*> stream, void** source)
{
    MOZ_ASSERT(stream->mode() == JS::ReadableStreamMode::ExternalSource);
    if (stream->locked()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAM_LOCKED);
        return false;
    }
    if (!stream->readable()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
                                  "ReadableStreamGetExternalUnderlyingSource");
        return false;
    }

    auto controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();
    AddControllerFlags(controller, ControllerFlag_SourceLocked);
    *source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
    return true;
}

void
ReadableStream::releaseExternalSource()
{
    MOZ_ASSERT(mode() == JS::ReadableStreamMode::ExternalSource);
    MOZ_ASSERT(locked());
    auto controller = ControllerFromStream(this);
    MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_SourceLocked);
    RemoveControllerFlags(controller, ControllerFlag_SourceLocked);
}

uint8_t
ReadableStream::embeddingFlags() const
{
    uint8_t flags = ControllerFlags(ControllerFromStream(this)) >> ControllerEmbeddingFlagsOffset;
    MOZ_ASSERT_IF(flags, mode() == JS::ReadableStreamMode::ExternalSource);
    return flags;
}

// Streams spec, 3.10.4.4. steps 1-3
// and
// Streams spec, 3.12.8. steps 8-9
//
// Adapted to handling updates signaled by the embedding for streams with
// external underlying sources.
//
// The remaining steps of those two functions perform checks and asserts that
// don't apply to streams with external underlying sources.
MOZ_MUST_USE bool
ReadableStream::updateDataAvailableFromSource(JSContext* cx, Handle<ReadableStream*> stream,
                                              uint32_t availableData)
{
    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();

    // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
    if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue");
        return false;
    }

    // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
    //         throw a TypeError exception.
    if (!StreamFromController(controller)->readable()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "enqueue");
        return false;
    }

    RemoveControllerFlags(controller, ControllerFlag_Pulling | ControllerFlag_PullAgain);

#if DEBUG
    uint32_t oldAvailableData = controller->getFixedSlot(QueueContainerSlot_TotalSize).toInt32();
#endif // DEBUG
    controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(availableData));

    // Step 8.a: If ! ReadableStreamGetNumReadRequests(stream) is 0,
    // Reordered because for externally-sourced streams it applies regardless
    // of reader type.
    if (ReadableStreamGetNumReadRequests(stream) == 0)
        return true;

    // Step 8: If ! ReadableStreamHasDefaultReader(stream) is true
    if (ReadableStreamHasDefaultReader(stream)) {
        // Step b: Otherwise,
        // Step i: Assert: controller.[[queue]] is empty.
        MOZ_ASSERT(oldAvailableData == 0);

        // Step ii: Let transferredView be
        //          ! Construct(%Uint8Array%, transferredBuffer, byteOffset, byteLength).
        JSObject* viewObj = JS_NewUint8Array(cx, availableData);
        Rooted<ArrayBufferViewObject*> transferredView(cx, &viewObj->as<ArrayBufferViewObject>());
        if (!transferredView)
            return false;

        Value val = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
        void* underlyingSource = val.toPrivate();

        size_t bytesWritten;
        {
            JS::AutoSuppressGCAnalysis noGC(cx);
            bool dummy;
            void* buffer = JS_GetArrayBufferViewData(transferredView, &dummy, noGC);
            auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
            MOZ_ASSERT(cb);
            // TODO: use bytesWritten to correctly update the request's state.
            cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer,
               availableData, &bytesWritten);
        }

        // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false).
        RootedValue chunk(cx, ObjectValue(*transferredView));
        if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false))
            return false;

        controller->setFixedSlot(QueueContainerSlot_TotalSize,
                                 Int32Value(availableData - bytesWritten));
    } else if (ReadableStreamHasBYOBReader(stream)) {
        // Step 9: Otherwise,
        // Step a: If ! ReadableStreamHasBYOBReader(stream) is true,
        // Step i: Perform
        // (Not needed for external underlying sources.)

        // Step ii: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
        if (!ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller))
            return false;
    } else {
        // Step b: Otherwise,
        // Step i: Assert: ! IsReadableStreamLocked(stream) is false.
        MOZ_ASSERT(!stream->locked());

        // Step ii: Perform
        //          ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
        //                                                            transferredBuffer,
        //                                                            byteOffset,
        //                                                            byteLength).
        // (Not needed for external underlying sources.)
    }

    return true;
}

MOZ_MUST_USE bool
ReadableStream::close(JSContext* cx, Handle<ReadableStream*> stream)
{
    RootedNativeObject controllerObj(cx, ControllerFromStream(stream));
    if (!VerifyControllerStateForClosing(cx, controllerObj))
        return false;

    if (controllerObj->is<ReadableStreamDefaultController>()) {
        Rooted<ReadableStreamDefaultController*> controller(cx);
        controller = &controllerObj->as<ReadableStreamDefaultController>();
        return ReadableStreamDefaultControllerClose(cx, controller);
    }

    Rooted<ReadableByteStreamController*> controller(cx);
    controller = &controllerObj->as<ReadableByteStreamController>();
    return ReadableByteStreamControllerClose(cx, controller);
}

MOZ_MUST_USE bool
ReadableStream::error(JSContext* cx, Handle<ReadableStream*> stream, HandleValue reason)
{
    // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
    if (!stream->readable()) {
        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                  JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
        return false;
    }

    // Step 4: Perform ! ReadableStreamDefaultControllerError(this, e).
    RootedNativeObject controller(cx, ControllerFromStream(stream));
    return ReadableStreamControllerError(cx, controller, reason);
}

MOZ_MUST_USE bool
ReadableStream::tee(JSContext* cx, Handle<ReadableStream*> stream, bool cloneForBranch2,
                    MutableHandle<ReadableStream*> branch1Stream,
                    MutableHandle<ReadableStream*> branch2Stream)
{
    return ReadableStreamTee(cx, stream, false, branch1Stream, branch2Stream);
}

MOZ_MUST_USE NativeObject*
ReadableStream::getReader(JSContext* cx, Handle<ReadableStream*> stream,
                          JS::ReadableStreamReaderMode mode)
{
    if (mode == JS::ReadableStreamReaderMode::Default)
        return CreateReadableStreamDefaultReader(cx, stream);
    return CreateReadableStreamBYOBReader(cx, stream);
}