diff options
author | Moonchild <moonchild@palemoon.org> | 2020-10-31 03:19:54 +0000 |
---|---|---|
committer | Moonchild <moonchild@palemoon.org> | 2020-10-31 03:19:54 +0000 |
commit | bb35b25ef9f6137d3644690c8c4c72d095fbbfcd (patch) | |
tree | 97de47f3b422b440e35dac53d6ed260a6be02c2d | |
parent | 1059830a73474ae09eae7b5960f2cc94aeea2259 (diff) | |
download | uxp-bb35b25ef9f6137d3644690c8c4c72d095fbbfcd.tar.gz |
Issue #1442 - Part 2: Implement streams API as self-hosted JS.
-rw-r--r-- | js/src/builtin/Stream.cpp | 372 | ||||
-rw-r--r-- | js/src/builtin/Stream.h | 39 | ||||
-rw-r--r-- | js/src/builtin/Stream.js | 3220 | ||||
-rw-r--r-- | js/src/builtin/StreamDefines.h | 67 | ||||
-rw-r--r-- | js/src/builtin/Utilities.js | 17 | ||||
-rw-r--r-- | js/src/js.msg | 28 | ||||
-rw-r--r-- | js/src/jsprototypes.h | 23 | ||||
-rw-r--r-- | js/src/moz.build | 3 | ||||
-rw-r--r-- | js/src/vm/CommonPropertyNames.h | 1 | ||||
-rw-r--r-- | js/src/vm/GlobalObject.cpp | 1 | ||||
-rw-r--r-- | js/src/vm/SelfHosting.cpp | 56 |
11 files changed, 3820 insertions, 7 deletions
diff --git a/js/src/builtin/Stream.cpp b/js/src/builtin/Stream.cpp new file mode 100644 index 0000000000..a7c98141a4 --- /dev/null +++ b/js/src/builtin/Stream.cpp @@ -0,0 +1,372 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "builtin/Stream.h" + +#include "jscntxt.h" + +#include "gc/Heap.h" +#include "vm/SelfHosting.h" + +#include "jsobjinlines.h" + +#include "vm/NativeObject-inl.h" + +using namespace js; + +#define CLASS_SPEC(cls, nCtorArgs, methods, properties, nSlots, specFlags) \ +const ClassSpec cls::classSpec_ = { \ + GenericCreateConstructor<cls##Constructor, nCtorArgs, gc::AllocKind::FUNCTION>, \ + GenericCreatePrototype, \ + nullptr, \ + nullptr, \ + methods, \ + properties, \ + nullptr, \ + specFlags \ +}; \ +\ +const Class cls::class_ = { \ + #cls, \ + JSCLASS_HAS_RESERVED_SLOTS(nSlots) | \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##cls), \ + JS_NULL_CLASS_OPS, \ + &cls::classSpec_ \ +}; \ +\ +const Class cls::protoClass_ = { \ + "object", \ + JSCLASS_HAS_CACHED_PROTO(JSProto_##cls), \ + JS_NULL_CLASS_OPS, \ + &cls::classSpec_ \ +}; + +// TODO remove. See also https://bugzil.la/1226261 +static bool +ReadableStreamConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "ReadableStream")) + return false; + + Rooted<ReadableStream*> readableStream(cx, NewBuiltinClassInstance<ReadableStream>(cx)); + if (!readableStream) + return false; + + FixedInvokeArgs<2> initArgs(cx); + initArgs[0].set(args.get(0)); + initArgs[1].set(args.get(1)); + RootedValue thisv(cx, ObjectValue(*readableStream)); + if (!CallSelfHostedFunction(cx, cx->names().ReadableStream, thisv, initArgs, initArgs.rval())) + return false; + + args.rval().setObject(*readableStream); + return true; +} + +static const JSFunctionSpec ReadableStream_methods[] = { + JS_SELF_HOSTED_FN("cancel", "ReadableStream_cancel", 1, 0), + JS_SELF_HOSTED_FN("getReader", "ReadableStream_getReader", 1, 0), + JS_SELF_HOSTED_FN("pipeThrough", "ReadableStream_pipeThrough", 2, 0), + JS_SELF_HOSTED_FN("pipeTo", "ReadableStream_pipeTo", 2, 0), + JS_SELF_HOSTED_FN("tee", "ReadableStream_tee", 0, 0), + JS_FS_END +}; + +static const JSPropertySpec ReadableStream_properties[] = { + JS_SELF_HOSTED_GET("locked", "ReadableStream_locked", 0), + JS_PS_END +}; + +CLASS_SPEC(ReadableStream, 0, + ReadableStream_methods, ReadableStream_properties, + 4, 0); + + +static bool +ReadableStreamDefaultReaderConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultReader")) + return false; + + RootedObject reader(cx, NewBuiltinClassInstance<ReadableStreamDefaultReader>(cx)); + if (!reader) + return false; + + FixedInvokeArgs<1> initArgs(cx); + initArgs[0].set(args.get(0)); + RootedValue thisv(cx, ObjectValue(*reader)); + if (!CallSelfHostedFunction(cx, cx->names().ReadableStreamDefaultReader, thisv, initArgs, + initArgs.rval())) + { + return false; + } + + args.rval().setObject(*reader); + return true; +} + +static const JSFunctionSpec ReadableStreamDefaultReader_methods[] = { + JS_SELF_HOSTED_FN("cancel", "ReadableStreamDefaultReader_cancel", 1, 0), + JS_SELF_HOSTED_FN("read", "ReadableStreamDefaultReader_read", 0, 0), + JS_SELF_HOSTED_FN("releaseLock", "ReadableStreamDefaultReader_releaseLock", 0, 0), + JS_FS_END +}; + +static const JSPropertySpec ReadableStreamDefaultReader_properties[] = { + JS_SELF_HOSTED_GET("closed", "ReadableStreamDefaultReader_closed", 0), + JS_PS_END +}; + +CLASS_SPEC(ReadableStreamDefaultReader, 1, + ReadableStreamDefaultReader_methods, ReadableStreamDefaultReader_properties, + 3, ClassSpec::DontDefineConstructor); + + +static bool +ReadableStreamBYOBReaderConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "ReadableStreamBYOBReader")) + return false; + + RootedObject reader(cx, NewBuiltinClassInstance<ReadableStreamBYOBReader>(cx)); + if (!reader) + return false; + + FixedInvokeArgs<1> initArgs(cx); + initArgs[0].set(args.get(0)); + RootedValue thisv(cx, ObjectValue(*reader)); + if (!CallSelfHostedFunction(cx, cx->names().ReadableStreamBYOBReader, thisv, initArgs, + initArgs.rval())) + { + return false; + } + + args.rval().setObject(*reader); + return true; +} + +static const JSPropertySpec ReadableStreamBYOBReader_properties[] = { + JS_SELF_HOSTED_GET("closed", "ReadableStreamBYOBReader_closed", 0), + JS_PS_END +}; + +static const JSFunctionSpec ReadableStreamBYOBReader_methods[] = { + JS_SELF_HOSTED_FN("cancel", "ReadableStreamBYOBReader_cancel", 1, 0), + JS_SELF_HOSTED_FN("read", "ReadableStreamBYOBReader_read", 1, 0), + JS_SELF_HOSTED_FN("releaseLock", "ReadableStreamBYOBReader_releaseLock", 0, 0), + JS_FS_END +}; + +CLASS_SPEC(ReadableStreamBYOBReader, 1, + ReadableStreamBYOBReader_methods, ReadableStreamBYOBReader_properties, + 3, ClassSpec::DontDefineConstructor); + + +static bool +ReadableStreamDefaultControllerConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultController")) + return false; + + RootedObject controller(cx, NewBuiltinClassInstance<ReadableStreamDefaultController>(cx)); + if (!controller) + return false; + + FixedInvokeArgs<4> initArgs(cx); + initArgs[0].set(args.get(0)); + initArgs[1].set(args.get(1)); + initArgs[2].set(args.get(2)); + initArgs[3].set(args.get(3)); + RootedValue thisv(cx, ObjectValue(*controller)); + if (!CallSelfHostedFunction(cx, cx->names().ReadableStreamDefaultController, thisv, + initArgs, initArgs.rval())) + { + return false; + } + + args.rval().setObject(*controller); + return true; +} + +static const JSPropertySpec ReadableStreamDefaultController_properties[] = { + JS_SELF_HOSTED_GET("desiredSize", "ReadableStreamDefaultController_desiredSize", 0), + JS_PS_END +}; + +static const JSFunctionSpec ReadableStreamDefaultController_methods[] = { + JS_SELF_HOSTED_FN("close", "ReadableStreamDefaultController_close", 0, 0), + JS_SELF_HOSTED_FN("enqueue", "ReadableStreamDefaultController_enqueue", 1, 0), + JS_SELF_HOSTED_FN("error", "ReadableStreamDefaultController_error", 1, 0), + JS_FS_END +}; + +CLASS_SPEC(ReadableStreamDefaultController, 4, + ReadableStreamDefaultController_methods, ReadableStreamDefaultController_properties, + 7, ClassSpec::DontDefineConstructor); + + +static bool +ReadableByteStreamControllerConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "ReadableByteStreamController")) + return false; + + RootedObject controller(cx, NewBuiltinClassInstance<ReadableByteStreamController>(cx)); + if (!controller) + return false; + + FixedInvokeArgs<3> initArgs(cx); + initArgs[0].set(args.get(0)); + initArgs[1].set(args.get(1)); + initArgs[2].set(args.get(2)); + RootedValue thisv(cx, ObjectValue(*controller)); + if (!CallSelfHostedFunction(cx, cx->names().ReadableByteStreamController, thisv, initArgs, + initArgs.rval())) + { + return false; + } + + args.rval().setObject(*controller); + return true; +} + +static const JSPropertySpec ReadableByteStreamController_properties[] = { + JS_SELF_HOSTED_GET("byobRequest", "ReadableByteStreamController_byobRequest", 0), + JS_SELF_HOSTED_GET("desiredSize", "ReadableByteStreamController_desiredSize", 0), + JS_PS_END +}; + +static const JSFunctionSpec ReadableByteStreamController_methods[] = { + JS_SELF_HOSTED_FN("close", "ReadableByteStreamController_close", 0, 0), + JS_SELF_HOSTED_FN("enqueue", "ReadableByteStreamController_enqueue", 1, 0), + JS_SELF_HOSTED_FN("error", "ReadableByteStreamController_error", 1, 0), + JS_FS_END +}; + +CLASS_SPEC(ReadableByteStreamController, 3, + ReadableByteStreamController_methods, ReadableByteStreamController_properties, + 9, ClassSpec::DontDefineConstructor); + + +static bool +ReadableStreamBYOBRequestConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!ThrowIfNotConstructing(cx, args, "ReadableStreamBYOBRequest")) + return false; + + RootedObject request(cx, NewBuiltinClassInstance<ReadableStreamBYOBRequest>(cx)); + if (!request) + return false; + + FixedInvokeArgs<3> initArgs(cx); + initArgs[0].set(args.get(0)); + initArgs[1].set(args.get(1)); + initArgs[2].set(args.get(2)); + RootedValue thisv(cx, ObjectValue(*request)); + if (!CallSelfHostedFunction(cx, cx->names().ReadableStreamBYOBRequest, + thisv, initArgs, initArgs.rval())) + { + return false; + } + + args.rval().setObject(*request); + return true; +} + +static const JSPropertySpec ReadableStreamBYOBRequest_properties[] = { + JS_SELF_HOSTED_GET("view", "ReadableStreamBYOBRequest_view", 0), + JS_PS_END +}; + +static const JSFunctionSpec ReadableStreamBYOBRequest_methods[] = { + JS_SELF_HOSTED_FN("respond", "ReadableStreamBYOBRequest_respond", 1, 0), + JS_SELF_HOSTED_FN("respondWithNewView", "ReadableStreamBYOBRequest_respondWithNewView", 1, 0), + JS_FS_END +}; + +CLASS_SPEC(ReadableStreamBYOBRequest, 3, + ReadableStreamBYOBRequest_methods, ReadableStreamBYOBRequest_properties, + 2, ClassSpec::DontDefineConstructor); + + +static bool +ByteLengthQueuingStrategyConstructor(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; +} + +static const JSFunctionSpec ByteLengthQueuingStrategy_methods[] = { + JS_SELF_HOSTED_FN("size", "ByteLengthQueuingStrategy_size", 1, 0), + JS_FS_END +}; + +CLASS_SPEC(ByteLengthQueuingStrategy, 1, + ByteLengthQueuingStrategy_methods, nullptr, + 0, 0); + +static bool +CountQueuingStrategyConstructor(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; +} + +static const JSFunctionSpec CountQueuingStrategy_methods[] = { + JS_SELF_HOSTED_FN("size", "CountQueuingStrategy_size", 0, 0), + JS_FS_END +}; + +CLASS_SPEC(CountQueuingStrategy, 1, + CountQueuingStrategy_methods, nullptr, + 0, 0); + +#undef CLASS_SPEC diff --git a/js/src/builtin/Stream.h b/js/src/builtin/Stream.h new file mode 100644 index 0000000000..fcaf7c43d8 --- /dev/null +++ b/js/src/builtin/Stream.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef builtin_Stream_h +#define builtin_Stream_h + +#include "vm/NativeObject.h" + +namespace js { + +class AutoSetNewObjectMetadata; + +#define NATIVE_CLASS(name) \ +class name : public NativeObject \ +{ \ + public: \ + static const ClassSpec classSpec_; \ + static const Class class_; \ + static const ClassSpec protoClassSpec_; \ + static const Class protoClass_; \ +}; + +NATIVE_CLASS(ReadableStream) +NATIVE_CLASS(ReadableStreamDefaultReader) +NATIVE_CLASS(ReadableStreamBYOBReader) +NATIVE_CLASS(ReadableStreamDefaultController) +NATIVE_CLASS(ReadableByteStreamController) +NATIVE_CLASS(ReadableStreamBYOBRequest) + +NATIVE_CLASS(ByteLengthQueuingStrategy) +NATIVE_CLASS(CountQueuingStrategy) + +#undef NATIVE_CLASS + +} // namespace js + +#endif /* builtin_Stream_h */ diff --git a/js/src/builtin/Stream.js b/js/src/builtin/Stream.js new file mode 100644 index 0000000000..b2aa74e3ed --- /dev/null +++ b/js/src/builtin/Stream.js @@ -0,0 +1,3220 @@ +/* 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/. */ + +// Streams spec, 3.2.3. +function ReadableStream(underlyingSource = {}, {size, highWaterMark} = {}) { + if (!IsObject(this) || !IsReadableStream(this)) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStream", "ctor", typeof this); + + // Step 1: Set this.[[state]] to "readable". + // Step 3: Set this.[[disturbed]] to false. + UnsafeSetReservedSlot(this, READABLESTREAM_SLOT_STATE, READABLESTREAM_STATE_READABLE); + + // Step 2: Set this.[[reader]] and this.[[storedError]] to undefined (implicit). + // Step 4: Set this.[[readableStreamController]] to undefined (implicit). + + // Step 5: Let type be ? GetV(underlyingSource, "type"). + let type = underlyingSource.type; + + // Step 6: Let typeString be ? ToString(type). + // Omitted because this happens in the comparison in step 7. + + // Step 7: If typeString is "bytes", + if (type === "bytes") { + // Step a: If highWaterMark is undefined, let highWaterMark be 0. + if (highWaterMark === undefined) + highWaterMark = 0; + + // Step b: Set this.[[readableStreamController]] to + // ? Construct(ReadableByteStreamController, « this, underlyingSource, highWaterMark »). + let ReadableByteStreamControllerCtor = GetBuiltinConstructor("ReadableByteStreamController"); + UnsafeSetReservedSlot(this, READABLESTREAM_SLOT_CONTROLLER, + new ReadableByteStreamControllerCtor(this, + underlyingSource, + highWaterMark)); + } else if (type === undefined) { + // Step 8: Otherwise, if type is undefined, + // Step a: If highWaterMark is undefined, let highWaterMark be 1. + if (highWaterMark === undefined) + highWaterMark = 1; + + // Step b: Set this.[[readableStreamController]] to + // ? Construct(ReadableStreamDefaultController, « this, underlyingSource, size, highWaterMark »). + let ReadableStreamDefaultControllerCtor = GetBuiltinConstructor("ReadableStreamDefaultController"); + let controller = new ReadableStreamDefaultControllerCtor(this, underlyingSource, + size, highWaterMark); + UnsafeSetReservedSlot(this, READABLESTREAM_SLOT_CONTROLLER, controller); + } else { + // Step 9: Otherwise, throw a RangeError exception. + ThrowRangeError(JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG); + } +} + +// Streams spec, 3.2.4.1. get locked +function ReadableStream_locked() { + // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception. + if (!IsObject(this) || !IsReadableStream(this)) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStream", "locked", typeof this); + + // Step 2: Return ! IsReadableStreamLocked(this). + return IsReadableStreamLocked(this); +} + +// Streams spec, 3.2.4.2. cancel ( reason ) +function ReadableStream_cancel(reason) { + // Step 1: If ! IsReadableStream(this) is false, return a promise rejected + // with a TypeError exception. + if (!IsObject(this) || !IsReadableStream(this)) { + return CreatePromiseRejectedWith(GetTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStream", + "cancel", typeof this)); + } + + // Step 2: If ! IsReadableStreamLocked(this) is true, return a promise + // rejected with a TypeError exception. + if (IsReadableStreamLocked(this)) + return CreatePromiseRejectedWith(GetTypeError(JSMSG_READABLESTREAM_NOT_LOCKED, "cancel")); + + // Step 3: Return ! ReadableStreamCancel(this, reason). + return ReadableStreamCancel(this, reason); +} + +// Streams spec, 3.2.4.3. getReader() +function ReadableStream_getReader({ mode } = {}) { + // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception. + if (!IsObject(this) || !IsReadableStream(this)) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStream", "getReader", typeof this); + + // Step 2: If mode is "byob", return ? AcquireReadableStreamBYOBReader(this). + if (mode === "byob") + return AcquireReadableStreamBYOBReader(this); + + // Step 3: If mode is undefined, return + // ? AcquireReadableStreamDefaultReader(this). + if (mode === undefined) + return AcquireReadableStreamDefaultReader(this); + + ThrowRangeError(JSMSG_READABLESTREAM_INVALID_READER_MODE); +} + +// Streams spec, 3.2.4.4. pipeThrough({ writable, readable }, options) +function ReadableStream_pipeThrough({ writable, readable }, options) { + ThrowTypeError(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, "pipeThrough"); + // // Step 1: Perform ? Invoke(this, "pipeTo", « writable, options »). + // callContentFunction("pipeTo", this, 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. +function ReadableStream_pipeTo(dest, { preventClose, preventAbort, preventCancel } = {}) { + ThrowTypeError(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, "pipeTo"); +} + +// Streams spec, 3.2.4.6. tee() +function ReadableStream_tee() { + // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception + if (!IsObject(this) || !IsReadableStream(this)) + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStream", "tee", typeof this); + + // Step 2: Let branches be ? ReadableStreamTee(this, false). + let branches = ReadableStreamTee(this, false); + + // Step 3: Return ! CreateArrayFromList(branches). + assert(branches.length == 2, "ReadableStreamTee() must return two branches."); + return branches; +} + +// Streams spec, 3.3.1. AcquireReadableStreamBYOBReader ( stream ) +function AcquireReadableStreamBYOBReader(stream) { + // Step 1: Return ? Construct(ReadableStreamBYOBReader, « stream ») + let ReadableStreamBYOBReaderCtor = GetBuiltinConstructor("ReadableStreamBYOBReader"); + return new ReadableStreamBYOBReaderCtor(stream); +} + +// Streams spec, 3.3.2. AcquireReadableStreamDefaultReader ( stream ) +function AcquireReadableStreamDefaultReader(stream) { + // Step 1: Return ? Construct(ReadableStreamDefaultReader, « stream ») + let ReadableStreamDefaultReaderCtor = GetBuiltinConstructor("ReadableStreamDefaultReader"); + return new ReadableStreamDefaultReaderCtor(stream); +} + +// Streams spec, 3.3.3. IsReadableStream ( x ) +// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStream>() + +// Streams spec, 3.3.4. IsReadableStreamDisturbed ( stream ) +function IsReadableStreamDisturbed(stream) { + // Step 1: Assert: ! IsReadableStream(stream) is true. + assert(IsReadableStreamDefaultController(this), "IsReadableStream(stream)"); + + // Step 2: Return stream.[[disturbed]]. + return UnsafeGetBooleanFromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + READABLESTREAM_IS_DISTURBED; +} + +// Streams spec, 3.3.5. IsReadableStreamLocked ( stream ) +function IsReadableStreamLocked(stream) { + // Step 1: Assert: ! IsReadableStream(stream) is true. + assert(IsReadableStream(stream), "IsReadableStream(stream)"); + + // Step 2: If stream.[[reader]] is undefined, return false. + // Step 3: Return true. + return UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_READER) !== undefined; +} + +// Streams spec, 3.3.6. ReadableStreamTee ( stream, shouldClone ) +// TODO: This could be re-designed to use a class instead of closures +function ReadableStreamTee(stream, shouldClone) { + // Step 1: Assert: ! IsReadableStream(stream) is true. + assert(IsReadableStream(stream), "IsReadableStream(stream)"); + + // Step 2: Assert: Type(shouldClone) is Boolean. + assert(typeof shouldClone === "boolean", "Type(shouldClone) is Boolean"); + + // Step 3: Let reader be ? AcquireReadableStreamDefaultReader(stream). + let reader = AcquireReadableStreamDefaultReader(stream); + + // Step 4: Let teeState be Record {[[closedOrErrored]]: false, [[canceled1]]: false, [[canceled2]]: false, [[reason1]]: undefined, [[reason2]]: undefined, [[promise]]: a new promise}. + let teeState = { + __proto__: null, + closedOrErrored: false, + canceled1: false, + canceled2: false, + reason1: undefined, + reason2: undefined, + promise: CreatePendingPromise() + } + + let branch1; + let branch2; + + // Step 5: Let pull be a new ReadableStreamTee pull function. + // Step 6: Set pull.[[reader]] to reader, pull.[[teeState]] to teeState, and + // pull.[[shouldClone]] to shouldClone. + let pull = () => { + // ReadableStreamTee pull function + // Step 1: Let reader be F.[[reader]], branch1 be F.[[branch1]], + // branch2 be F.[[branch2]], teeState be F.[[teeState]], and + // shouldClone be F.[[shouldClone]]. + // Implemented via closure. + + // Step 2: Return the result of transforming + // ! ReadableStreamDefaultReaderRead(reader) by a fulfillment + // handler which takes the argument result and performs the + // following steps: + let readPromise = ReadableStreamDefaultReaderRead(reader); + return CallOriginalPromiseThen(readPromise, result => { + // Step a: Assert: Type(result) is Object. + assert(IsObject(result), "Read result should be an object"); + + // Step b: Let value be ? Get(result, "value"). + let value = result.value; + + // Step c: Let done be ? Get(result, "done"). + let done = result.done; + + // Step d: Assert: Type(done) is Boolean. + assert(typeof done === "boolean", "Read result.done should be a boolean"); + + // 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). + ReadableStreamDefaultControllerClose(branch1); + } + + // Step ii: If teeState.[[canceled2]] is false, + if (!teeState.canceled2) { + + // Step 1: Perform ! ReadableStreamDefaultControllerClose(branch2). + ReadableStreamDefaultControllerClose(branch2); + } + + // Step iii: Set teeState.[[closedOrErrored]] to true. + teeState.closedOrErrored = true; + } + + // Step f: If teeState.[[closedOrErrored]] is true, return. + if (teeState.closedOrErrored) + return; + + // Step g: If teeState.[[canceled1]] is false, + if (!teeState.canceled1) { + // Step i: Let value1 be value. + let value1 = value; + + // Step ii: If shouldClone is true, set value1 to ? StructuredClone(value). + // No way to trigger StructuredClone() and spec always passes false + // at the moment. + assert(!shouldClone, "tee(shouldClone=true) should not be exposed"); + + // Step iii: Perform ? ReadableStreamDefaultControllerEnqueue(branch1, value1). + ReadableStreamDefaultControllerEnqueue(branch1, value1); + } + + // Step h: If teeState.[[canceled2]] is false, + if (!teeState.canceled2) { + // Step i: Let value2 be value. + let value2 = value; + + // Step ii: If shouldClone is true, set value2 to ? StructuredClone(value). + // No way to trigger StructuredClone() and spec always passes false + // at the moment. + assert(!shouldClone, "tee(shouldClone=true) should not be exposed"); + + // Step iii: Perform ? ReadableStreamDefaultControllerEnqueue(branch1, value2). + ReadableStreamDefaultControllerEnqueue(branch2, value2); + } + }); + }; + + // Step 7: Let cancel1 be a new ReadableStreamTee branch 1 cancel function. + // Step 8: Set cancel1.[[stream]] to stream and cancel1.[[teeState]] to + // teeState. + let cancel1 = (reason) => { + // ReadableStreamTee cancel function + // Step 1: Let stream be F.[[stream]] and teeState be F.[[teeState]]. + // Implemented via closure + + // Step 2: Set teeState.[[canceled1]] to true. + teeState.canceled1 = true; + + // Step 3: Set teeState.[[reason1]] to reason. + teeState.reason1 = reason; + + // Step 4: If teeState.[[canceled2]] is true, + if (teeState.canceled2) { + // Step a: Let compositeReason be + // ! CreateArrayFromList(« teeState.[[reason1]], teeState.[[reason2]] »). + let compositeReason = [ teeState.reason1, teeState.reason2 ]; + + // Step b: Let cancelResult be ! ReadableStreamCancel(stream, compositeReason). + let cancelResult = ReadableStreamCancel(stream, compositeReason); + + // Step c: Resolve teeState.[[promise]] with cancelResult. + ResolvePromise(teeState.promise, cancelResult); + } + + // Step 5: Return teeState.[[promise]]. + return teeState.promise; + }; + + // Step 9: Let cancel2 be a new ReadableStreamTee branch 2 cancel function. + // Step 10: Set cancel2.[[stream]] to stream and cancel2.[[teeState]] to + // teeState. + let cancel2 = (reason) => { + // ReadableStreamTee cancel function + // Step 1: Let stream be F.[[stream]] and teeState be F.[[teeState]]. + // Implemented via closure + + // Step 2: Set teeState.[[canceled2]] to true. + teeState.canceled2 = true; + + // Step 3: Set teeState.[[reason2]] to reason. + teeState.reason2 = reason; + + // Step 4: If teeState.[[canceled1]] is true, + if (teeState.canceled1) { + // Step a: Let compositeReason be + // ! CreateArrayFromList(« teeState.[[reason1]], teeState.[[reason2]] »). + let compositeReason = [ teeState.reason1, teeState.reason2 ]; + + // Step b: Let cancelResult be ! ReadableStreamCancel(stream, compositeReason). + let cancelResult = ReadableStreamCancel(stream, compositeReason); + + // Step c: Resolve teeState.[[promise]] with cancelResult. + ResolvePromise(teeState.promise, cancelResult); + } + + // Step 5: Return teeState.[[promise]]. + return teeState.promise; + }; + + // Step 11: Let underlyingSource1 be ! ObjectCreate(%ObjectPrototype%). + // Step 12: Perform ! CreateDataProperty(underlyingSource1, "pull", pull). + // Step 13: Perform ! CreateDataProperty(underlyingSource1, "cancel", cancel1). + let underlyingSource1 = { + pull, + cancel: cancel1 + }; + + // Step 14: Let branch1Stream be ! Construct(ReadableStream, underlyingSource1). + let ReadableStreamCtor = GetBuiltinConstructor("ReadableStream"); + let branch1Stream = new ReadableStreamCtor(underlyingSource1); + + // Step 15: Let underlyingSource2 be ! ObjectCreate(%ObjectPrototype%). + // Step 16: Perform ! CreateDataProperty(underlyingSource2, "pull", pull). + // Step 17: Perform ! CreateDataProperty(underlyingSource2, "cancel", cancel2). + let underlyingSource2 = { + pull, + cancel: cancel2 + }; + + // Step 18: Let branch2Stream be ! Construct(ReadableStream, underlyingSource2). + let branch2Stream = new ReadableStreamCtor(underlyingSource2); + + // Step 19: Set pull.[[branch1]] to branch1Stream.[[readableStreamController]]. + branch1 = UnsafeGetObjectFromReservedSlot(branch1Stream, READABLESTREAM_SLOT_CONTROLLER); + + // Step 20: Set pull.[[branch2]] to branch2Stream.[[readableStreamController]]. + branch2 = UnsafeGetObjectFromReservedSlot(branch2Stream, READABLESTREAM_SLOT_CONTROLLER); + + // Step 21: Upon rejection of reader.[[closedPromise]] with reason r, + let promise = UnsafeGetObjectFromReservedSlot(reader, READABLESTREAMREADER_SLOT_CLOSED_PROMISE); + AddPromiseReactions(promise, undefined, reason => { + // Step a: If teeState.[[closedOrErrored]] is true, return. + if (teeState.closedOrErrored) + return; + + // Step b: Perform ! ReadableStreamDefaultControllerError(pull.[[branch1]], r). + ReadableStreamDefaultControllerError(branch1, reason); + + // Step c: Perform ! ReadableStreamDefaultControllerError(pull.[[branch2]], r). + ReadableStreamDefaultControllerError(branch2, reason); + + // Step d: Set teeState.[[closedOrErrored]] to true. + teeState.closedOrErrored = true; + }); + + // Step 22: Return « branch1, branch2 ». + // Changed to return an array that ReadableStream_tee can just return. + return [branch1Stream, branch2Stream]; +} + +// Streams spec, 3.4.1. ReadableStreamAddReadIntoRequest ( stream ) +function ReadableStreamAddReadIntoRequest(stream) { + assert(IsReadableStream(stream), + "ReadableStreamAddReadIntoRequest() must operate on a stream"); + + // Step 1: Assert: ! IsReadableStreamBYOBReader(stream.[[reader]]) is true. + let reader = UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_READER); + assert(IsReadableStreamBYOBReader(reader), + "ReadableStreamAddReadIntoRequest() must operate on a ReadableStreamBYOBReader"); + + // Step 2: Assert: stream.[[state]] is "readable" or "closed". + assert(UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + (READABLESTREAM_STATE_READABLE | READABLESTREAM_STATE_CLOSED), + "Invalid ReadableStream state"); + + // Step 3: Let promise be a new promise. + let promise = CreatePendingPromise(); + + // Step 4: Let readIntoRequest be Record {[[promise]]: promise}. + let readIntoRequest = {__proto__: null, promise}; + + // Step 5: Append readIntoRequest as the last element of stream.[[reader]].[[readIntoRequests]]. + let readIntoRequests = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_REQUESTS); + ArrayStaticPush(readIntoRequests, readIntoRequest); + + // Step 6: Return promise. + return promise; +} + +// Streams spec, 3.4.2. ReadableStreamAddReadRequest ( stream ) +function ReadableStreamAddReadRequest(stream) { + assert(IsReadableStream(stream), + "ReadableStreamAddReadRequest() must operate on a stream"); + + // Step 1: Assert: ! IsReadableStreamDefaultReader(stream.[[reader]]) is true. + let reader = UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_READER); + assert(IsReadableStreamDefaultReader(reader), + "ReadableStreamAddReadRequest() must operate on a ReadableStreamDefaultReader"); + + // Step 2: Assert: stream.[[state]] is "readable". + assert(UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + READABLESTREAM_STATE_READABLE, "Invalid ReadableStream state"); + + // Step 3: Let promise be a new promise. + let promise = CreatePendingPromise(); + + // Step 4: Let readIntoRequest be Record {[[promise]]: promise}. + let readRequest = {__proto__: null, promise}; + + // Step 5: Append readRequest as the last element of stream.[[reader]].[[readRequests]]. + let readRequests = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_REQUESTS); + ArrayStaticPush(readRequests, readRequest); + + // Step 6: Return promise. + return promise; +} + +// Streams spec, 3.4.3. ReadableStreamCancel ( stream, reason ) +function ReadableStreamCancel(stream, reason) { + // Step 1: Set stream.[[disturbed]] to true. + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + state |= READABLESTREAM_IS_DISTURBED; + UnsafeSetReservedSlot(stream, READABLESTREAM_SLOT_STATE, state); + + // Step 2: If stream.[[state]] is "closed", return a new promise resolved + // with undefined. + if (state & READABLESTREAM_STATE_CLOSED) + return CreatePromiseResolvedWith(undefined); + + // Step 3: If stream.[[state]] is "errored", return a new promise rejected + // with stream.[[storedError]]. + if (state & READABLESTREAM_STATE_ERRORED) { + let storedError = UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_STORED_ERROR); + return CreatePromiseRejectedWith(storedError); + } + + // Step 4: Perform ! ReadableStreamClose(stream). + ReadableStreamClose(stream); + + // Step 5: Let sourceCancelPromise be + // ! stream.[[readableStreamController]].[[Cancel]](reason). + // This part is a bit annoying: we don't actually want to store the `Cancel` + // method in a slot, so we have to check the type of the controller and call + // the right method manually. + let controller = UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_CONTROLLER); + let CancelMethod = IsReadableStreamDefaultController(controller) + ? ReadableStreamDefaultController_cancel + : ReadableByteStreamController_cancel; + let sourceCancelPromise = callFunction(CancelMethod, controller, reason); + + // Step 6: Return the result of transforming sourceCancelPromise by a + // fulfillment handler that returns undefined. + return CallOriginalPromiseThen(sourceCancelPromise, ReturnUndefined); +} + +function ReturnUndefined() { +} + +// Step 3.4.4. ReadableStreamClose ( stream ) +function ReadableStreamClose(stream) { + assert(IsReadableStream(stream), "ReadableStreamClose() must operate on a ReadableStream"); + + // Step 1: Assert: stream.[[state]] is "readable". + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + assert(state & READABLESTREAM_STATE_READABLE, + "ReadableStreamClose() must operate on readable ReadableStreams"); + + // Step 2: Set stream.[[state]] to "closed". + UnsafeSetReservedSlot(stream, READABLESTREAM_SLOT_STATE, + (state & READABLESTREAM_IS_DISTURBED) | READABLESTREAM_STATE_CLOSED); + + // Step 3: Let reader be stream.[[reader]]. + let reader = UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_READER); + + // Step 4: If reader is undefined, return. + if (reader === undefined) + return; + + // Step 5: If ! IsReadableStreamDefaultReader(reader) is true, + if (IsReadableStreamDefaultReader(reader)) { + // Step a: Repeat for each readRequest that is an element of + // reader.[[readRequests]], + let readRequests = UnsafeGetObjectFromReservedSlot(reader, READABLESTREAMREADER_SLOT_REQUESTS); + let len = readRequests.length; + for (let i = 0; i < len; i++) { + // Step i: Resolve readRequest.[[promise]] with + // ! CreateIterResultObject(undefined, true). + let readRequest = readRequests[i]; + ResolvePromise(readRequest.promise, {value: undefined, done: true}); + } + + // Step b: Set reader.[[readRequests]] to an empty List. + UnsafeSetReservedSlot(reader, READABLESTREAMREADER_SLOT_REQUESTS, new List()); + } + + // Step 6: Resolve reader.[[closedPromise]] with undefined. + let closedPromise = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_CLOSED_PROMISE); + ResolvePromise(closedPromise, undefined); + + // Step 7: Return (implicit). +} + +// Streams spec, 3.4.5. ReadableStreamError ( stream, e ) +function ReadableStreamError(stream, e) { + // Step 1: Assert: ! IsReadableStream(stream) is true. + assert(IsReadableStream(stream), "ReadableStreamError() must operate on a ReadableStream"); + + // Step 2: Assert: stream.[[state]] is "readable". + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + assert(state & READABLESTREAM_STATE_READABLE, + "ReadableStreamError() must operate on readable ReadableStreams"); + + // Step 3: Set stream.[[state]] to "errored". + UnsafeSetReservedSlot(stream, READABLESTREAM_SLOT_STATE, + (state & READABLESTREAM_IS_DISTURBED) | READABLESTREAM_STATE_ERRORED); + + // Step 4: Set stream.[[storedError]] to e. + UnsafeSetReservedSlot(stream, READABLESTREAM_SLOT_STORED_ERROR, e); + + // Step 5: Let reader be stream.[[reader]]. + let reader = UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_READER); + + // Step 6: If reader is undefined, return. + if (reader === undefined) + return; + + // Step 7: If ! IsReadableStreamDefaultReader(reader) is true, + if (IsReadableStreamDefaultReader(reader)) { + // Step a: Repeat for each readRequest that is an element of + // reader.[[readRequests]], + let readRequests = UnsafeGetObjectFromReservedSlot(reader, READABLESTREAMREADER_SLOT_REQUESTS); + let len = readRequests.length; + for (let i = 0; i < len; i++) { + // Step i: Reject readRequest.[[promise]] with e. + let readRequest = readRequests[i]; + RejectPromise(readRequest.promise, e); + } + + // Step b: Set reader.[[readRequests]] to a new empty List. + UnsafeSetReservedSlot(reader, READABLESTREAMREADER_SLOT_REQUESTS, new List()); + } else { + // Step 8: Otherwise, + // Step a: Assert: ! IsReadableStreamBYOBReader(reader). + assert(IsReadableStreamBYOBReader(reader), "Non-default reader must be a BYOB reader"); + + // Step b: Repeat for each readIntoRequest that is an element of + // reader.[[readIntoRequests]], + let readIntoRequests = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_REQUESTS); + let len = readIntoRequests.length; + for (let i = 0; i < len; i++) { + // Step i: Reject readIntoRequest.[[promise]] with e. + let readIntoRequest = readIntoRequests[i]; + RejectPromise(readIntoRequest.promise, e); + } + + // Step b: Set reader.[[readIntoRequests]] to a new empty List. + UnsafeSetReservedSlot(reader, READABLESTREAMREADER_SLOT_REQUESTS, new List()); + } + + // Step 9: Reject reader.[[closedPromise]] with e. + let closedPromise = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_CLOSED_PROMISE); + RejectPromise(closedPromise, e); +} + +// Streams spec, 3.4.6. ReadableStreamFulfillReadIntoRequest( stream, chunk, done ) +function ReadableStreamFulfillReadIntoRequest(stream, chunk, done) { + assert(IsReadableStream(stream), + "ReadableStreamFulfillReadIntoRequest() must operate on a ReadableStream"); + + // Step 1: Let reader be stream.[[reader]]. + let reader = UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_READER); + + assert(IsReadableStreamBYOBReader(reader), + "ReadableStreamFulfillReadIntoRequest() must operate on a ReadableStreamBYOBReader"); + + // 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). + let readIntoRequests = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_REQUESTS); + let readIntoRequest = ArrayStaticShift(readIntoRequests); + + // Step 4: Resolve readIntoRequest.[[promise]] with + // ! CreateIterResultObject(chunk, done). + ResolvePromise(readIntoRequest.promise, {value: chunk, done}); +} + +// Streams spec, 3.4.7. ReadableStreamFulfillReadRequest ( stream, chunk, done) +function ReadableStreamFulfillReadRequest(stream, chunk, done) { + assert(IsReadableStream(stream), + "ReadableStreamFulfillReadRequest() must operate on a ReadableStream"); + + // Step 1: Let reader be stream.[[reader]]. + let reader = UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_READER); + + assert(IsReadableStreamDefaultReader(reader), + "ReadableStreamFulfillReadRequest() must operate on a ReadableStreamDefaultReader"); + + // Step 2: Let readRequest be the first element of reader.[[readRequests]]. + // Step 3: Remove readRequest from reader.[[readRequests]], shifting all other + // elements downward (so that the second becomes the first, and so on). + let readRequests = UnsafeGetObjectFromReservedSlot(reader, READABLESTREAMREADER_SLOT_REQUESTS); + let readRequest = ArrayStaticShift(readRequests); + + // Step 4: Resolve readRequest.[[promise]] with + // ! CreateIterResultObject(chunk, done). + ResolvePromise(readRequest.promise, {value: chunk, done}); +} + +// Streams spec 3.4.8. ReadableStreamGetNumReadIntoRequests ( stream ) +function ReadableStreamGetNumReadIntoRequests(stream) { + assert(IsReadableStream(stream), + "ReadableStreamGetNumReadIntoRequests must operate on a ReadableStream"); + + let reader = UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_READER); + + assert(IsReadableStreamBYOBReader(reader), + "ReadableStreamGetNumReadIntoRequests must operate on a ReadableStreamBYOBReader"); + + let readIntoRequests = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_REQUESTS); + + // Step 1: Return the number of elements in + // stream.[[reader]].[[readIntoRequests]]. + return readIntoRequests.length; +} + +// Streams spec 3.4.9. ReadableStreamGetNumReadRequests ( stream ) +function ReadableStreamGetNumReadRequests(stream) { + assert(IsReadableStream(stream), + "ReadableStreamGetNumReadRequests must operate on a ReadableStream"); + + let reader = UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_READER); + + assert(IsReadableStreamDefaultReader(reader), + "ReadableStreamGetNumReadRequests must operate on a ReadableStreamDefaultReader"); + + let readRequests = UnsafeGetObjectFromReservedSlot(reader, READABLESTREAMREADER_SLOT_REQUESTS); + + // Step 1: Return the number of elements in + // stream.[[reader]].[[readRequests]]. + return readRequests.length; +} + +// Stream spec 3.4.10. ReadableStreamHasBYOBReader ( stream ) +function ReadableStreamHasBYOBReader(stream) { + assert(IsReadableStream(stream), + "ReadableStreamHasBYOBReader must operate on a ReadableStream"); + + // Step 1: Let reader be stream.[[reader]]. + let reader = UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_READER); + + // Step 2: If reader is undefined, return false. + if (reader === undefined) + return false; + + // Step 3: If ! IsReadableStreamBYOBReader(reader) is false, return false. + // Step 4: Return true. + return IsReadableStreamBYOBReader(reader); +} + +// Streap spec 3.4.11. ReadableStreamHasDefaultReader ( stream ) +function ReadableStreamHasDefaultReader(stream) { + assert(IsReadableStream(stream), + "ReadableStreamHasDefaultReader must operate on a ReadableStream"); + + // Step 1: Let reader be stream.[[reader]]. + let reader = UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_READER); + + // Step 2: If reader is undefined, return false. + if (reader === undefined) + return false; + + // Step 3: If ! IsReadableStreamBYOBReader(reader) is false, return false. + // Step 4: Return true. + return IsReadableStreamDefaultReader(reader); +} + +// Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream ) +function ReadableStreamDefaultReader(stream) { + if (!IsReadableStreamDefaultReader(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamDefaultReader", + "ctor", typeof this); + } + + // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception. + if (!IsObject(stream) || !IsReadableStream(stream)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStream", + "ReadableStreamDefaultReader", typeof stream); + } + + // Step 2: If ! IsReadableStreamLocked(stream) is true, throw a TypeError + // exception. + if (IsReadableStreamLocked(stream)) + ThrowTypeError(JSMSG_READABLESTREAM_LOCKED); + + // Step 3: Perform ! ReadableStreamReaderGenericInitialize(this, stream). + ReadableStreamReaderGenericInitialize(this, stream); + + // Step 4: Set this.[[readRequests]] to a new empty List. + UnsafeSetReservedSlot(this, READABLESTREAMREADER_SLOT_REQUESTS, new List()); +} + +// Streams spec, 3.5.4.1 get closed +function ReadableStreamDefaultReader_closed() { + // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!IsReadableStreamDefaultReader(this)) { + return CreatePromiseRejectedWith(GetTypeError(JSMSG_INCOMPATIBLE_PROTO, + "ReadableStreamDefaultReader", + "closed", typeof this)); + } + + // Step 2: Return this.[[closedPromise]]. + return UnsafeGetObjectFromReservedSlot(this, READABLESTREAMREADER_SLOT_CLOSED_PROMISE); +} + +// Streams spec, 3.5.4.2. cancel ( reason ) +function ReadableStreamDefaultReader_cancel(reason) { + // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!IsReadableStreamDefaultReader(this)) { + return CreatePromiseRejectedWith(GetTypeError(JSMSG_INCOMPATIBLE_PROTO, + "ReadableStreamDefaultReader", + "cancel", typeof this)); + } + + // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise + // rejected with a TypeError exception. + let ownerReadableStream = UnsafeGetReservedSlot(this, + READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM); + if (ownerReadableStream === undefined) + return CreatePromiseRejectedWith(GetTypeError(JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel")); + + // Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason). + return ReadableStreamReaderGenericCancel(this, reason); +} + +// Streams spec, 3.5.4.3 read ( ) +function ReadableStreamDefaultReader_read() { + // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!IsReadableStreamDefaultReader(this)) { + return CreatePromiseRejectedWith(GetTypeError(JSMSG_INCOMPATIBLE_PROTO, + "ReadableStreamDefaultReader", + "read", typeof this)); + } + + // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise + // rejected with a TypeError exception. + let ownerReadableStream = UnsafeGetReservedSlot(this, + READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM); + if (ownerReadableStream === undefined) + return CreatePromiseRejectedWith(GetTypeError(JSMSG_READABLESTREAMREADER_NOT_OWNED, "read")); + + // Step 3: Return ! ReadableStreamDefaultReaderRead(this). + return ReadableStreamDefaultReaderRead(this); +} + +// Streams spec, 3.5.4.4. releaseLock ( ) +function ReadableStreamDefaultReader_releaseLock() { + // Step 1: If ! IsReadableStreamDefaultReader(this) is false, throw a + // TypeError exception. + if (!IsReadableStreamDefaultReader(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamDefaultReader", + "releaseLock", typeof this); + } + + // Step 2: If this.[[ownerReadableStream]] is undefined, return. + let ownerReadableStream = UnsafeGetReservedSlot(this, + READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM); + if (ownerReadableStream === undefined) + return; + + // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception. + let readRequests = UnsafeGetObjectFromReservedSlot(this, READABLESTREAMREADER_SLOT_REQUESTS); + if (readRequests.length !== 0) + ThrowTypeError(JSMSG_READABLESTREAMREADER_NOT_EMPTY, "releaseLock"); + + // Step 4: Perform ! ReadableStreamReaderGenericRelease(this). + ReadableStreamReaderGenericRelease(this); +} + +// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream ) +function ReadableStreamBYOBReader(stream) { + if (!IsReadableStreamBYOBReader(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamBYOBReader", + "ctor", typeof this); + } + + // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception. + if (!IsObject(stream) || !IsReadableStream(stream)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStream", + "ReadableStreamBYOBReader", typeof stream); + } + + // Step 2: If ! IsReadableByteStreamController(stream.[[readableStreamController]]) + // is false, throw a TypeError exception. + let controller = UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_CONTROLLER); + if (!IsReadableByteStreamController(controller)) + ThrowTypeError(JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER); + + // Step 3: If ! IsReadableStreamLocked(stream) is true, throw a TypeError + // exception. + if (IsReadableStreamLocked(stream)) + ThrowTypeError(JSMSG_READABLESTREAM_LOCKED); + + // Step 4: Perform ! ReadableStreamReaderGenericInitialize(this, stream). + ReadableStreamReaderGenericInitialize(this, stream); + + // Step 5: Set this.[[readIntoRequests]] to a new empty List. + UnsafeSetReservedSlot(this, READABLESTREAMREADER_SLOT_REQUESTS, new List()); +} + +// Streams spec, 3.6.4.1 get closed +function ReadableStreamBYOBReader_closed() { + // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!IsReadableStreamBYOBReader(this)) { + return CreatePromiseRejectedWith(GetTypeError(JSMSG_INCOMPATIBLE_PROTO, + "ReadableStreamBYOBReader", + "closed", typeof this)); + } + + // Step 2: Return this.[[closedPromise]]. + return UnsafeGetObjectFromReservedSlot(this, READABLESTREAMREADER_SLOT_CLOSED_PROMISE); +} + +// Streams spec, 3.6.4.2. cancel ( reason ) +function ReadableStreamBYOBReader_cancel(reason) { + // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!IsReadableStreamBYOBReader(this)) { + return CreatePromiseRejectedWith(GetTypeError(JSMSG_INCOMPATIBLE_PROTO, + "ReadableStreamBYOBReader", + "cancel", typeof this)); + } + + // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise + // rejected with a TypeError exception. + let ownerReadableStream = UnsafeGetReservedSlot(this, + READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM); + if (ownerReadableStream === undefined) + return CreatePromiseRejectedWith(GetTypeError(JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel")); + + // Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason). + return ReadableStreamReaderGenericCancel(this, reason); +} + +// Streams spec, 3.6.4.3. read ( view ) +function ReadableStreamBYOBReader_read(view) { + // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise + // rejected with a TypeError exception. + if (!IsReadableStreamBYOBReader(this)) { + return CreatePromiseRejectedWith(GetTypeError(JSMSG_INCOMPATIBLE_PROTO, + "ReadableStreamBYOBReader", + "read", typeof this)); + } + + // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise + // rejected with a TypeError exception. + let ownerReadableStream = UnsafeGetReservedSlot(this, + READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM); + if (ownerReadableStream === undefined) + return CreatePromiseRejectedWith(GetTypeError(JSMSG_READABLESTREAMREADER_NOT_OWNED, "read")); + + // 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 (!IsObject(view) || !IsTypedArray(view)) { + return CreatePromiseRejectedWith(GetTypeError(JSMSG_NOT_EXPECTED_TYPE, + "ReadableStreamBYOBReader.read", + "Typed Array", typeof view)); + } + + // 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 (TypedArrayLength(view) === 0) + return CreatePromiseRejectedWith(GetTypeError(JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW)); + + // Step 6: Return ! ReadableStreamBYOBReaderRead(this, view). + return ReadableStreamBYOBReaderRead(this, view); +} + +// Streams spec, 3.6.4.4. releaseLock ( ) +function ReadableStreamBYOBReader_releaseLock() { + // Step 1: If ! IsReadableStreamBYOBReader(this) is false, throw a TypeError + // exception. + if (!IsReadableStreamBYOBReader(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamBYOBReader", + "releaseLock", typeof this); + } + + // Step 2: If this.[[ownerReadableStream]] is undefined, return. + let ownerReadableStream = UnsafeGetReservedSlot(this, + READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM); + if (ownerReadableStream === undefined) + return; + + // Step 3: If this.[[readIntoRequests]] is not empty, throw a TypeError + // exception. + let readIntoRequests = UnsafeGetObjectFromReservedSlot(this, READABLESTREAMREADER_SLOT_REQUESTS); + if (readIntoRequests.length !== 0) + ThrowTypeError(JSMSG_READABLESTREAMREADER_NOT_EMPTY, "releaseLock"); + + // Step 4: Perform ! ReadableStreamReaderGenericRelease(this). + ReadableStreamReaderGenericRelease(this); +} + +// 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 ) +function ReadableStreamReaderGenericCancel(reader, reason) { + assert(IsReadableStreamDefaultReader(reader) || + IsReadableStreamBYOBReader(reader), "must be either a default or byob reader"); + + // Step 1: Let stream be reader.[[ownerReadableStream]]. + let stream = UnsafeGetReservedSlot(reader, READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM); + + // Step 2: Assert: stream is not undefined. + assert(stream !== undefined, "owning stream must be defined"); + + // Step 3: Return ! ReadableStreamCancel(stream, reason). + return ReadableStreamCancel(stream, reason); +} + +// Streams spec, 3.7.4. ReadableStreamReaderGenericInitialize ( reader, stream ) +function ReadableStreamReaderGenericInitialize(reader, stream) { + assert(IsReadableStreamDefaultReader(reader) || + IsReadableStreamBYOBReader(reader), "must be either a default or byob reader"); + assert(IsReadableStream(stream), "must be initializing with a ReadableStream"); + + // Step 1: Set reader.[[ownerReadableStream]] to stream. + UnsafeSetReservedSlot(reader, READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM, stream); + + // Step 2: Set stream.[[reader]] to reader. + UnsafeSetReservedSlot(stream, READABLESTREAM_SLOT_READER, reader); + + // Step 3: If stream.[[state]] is "readable", + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (state & READABLESTREAM_STATE_READABLE) { + // Step a: Set reader.[[closedPromise]] to a new promise. + UnsafeSetReservedSlot(reader, READABLESTREAMREADER_SLOT_CLOSED_PROMISE, + CreatePendingPromise()); + } else if (state & READABLESTREAM_STATE_CLOSED) { + // Step 4: Otherwise + // Step a: If stream.[[state]] is "closed", + // Step i: Set reader.[[closedPromise]] to a new promise resolved with + // undefined. + UnsafeSetReservedSlot(reader, READABLESTREAMREADER_SLOT_CLOSED_PROMISE, + CreatePromiseResolvedWith(undefined)); + } else { + // Step b: Otherwise, + // Step i: Assert: stream.[[state]] is "errored". + assert(state & READABLESTREAM_STATE_ERRORED, "must be errored if not readable or closed"); + + // Step ii: Set reader.[[closedPromise]] to a new promise rejected with + // stream.[[storedError]]. + let storedError = UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_STORED_ERROR); + UnsafeSetReservedSlot(reader, READABLESTREAMREADER_SLOT_CLOSED_PROMISE, + CreatePromiseRejectedWith(storedError)); + } +} + +// Streams spec, 3.7.5. ReadableStreamReaderGenericRelease ( reader ) +function ReadableStreamReaderGenericRelease(reader) { + assert(IsReadableStreamDefaultReader(reader) || + IsReadableStreamBYOBReader(reader), "must be either a default or byob reader"); + + // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined. + let stream = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM); + assert(IsReadableStream(stream), "reader should be owned by a ReadableStream"); + + // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is not undefined. + assert(UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_READER) === reader, + "owning ReadableStream should reference the given reader"); + + let e = GetTypeError(JSMSG_READABLESTREAMREADER_RELEASED); + + // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject + // reader.[[closedPromise]] with a TypeError exception. + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (state & READABLESTREAM_STATE_READABLE) { + let closedPromise = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_CLOSED_PROMISE); + RejectPromise(closedPromise, e); + } else { + // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise rejected + // with a TypeError exception. + UnsafeSetReservedSlot(reader, READABLESTREAMREADER_SLOT_CLOSED_PROMISE, + CreatePromiseRejectedWith(e)); + } + + // Step 5: Set reader.[[ownerReadableStream]].[[reader]] to undefined. + UnsafeSetReservedSlot(stream, READABLESTREAM_SLOT_READER, undefined); + + // Step 6: Set reader.[[ownerReadableStream]] to undefined. + UnsafeSetReservedSlot(reader, READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM, undefined); +} + +// Streams spec, 3.7.6. ReadableStreamBYOBReaderRead ( reader, view ) +function ReadableStreamBYOBReaderRead(reader, view) { + assert(IsReadableStreamBYOBReader(reader), "must be a byob reader"); + + // Step 1: Let stream be reader.[[ownerReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM); + + // Step 2: Assert: stream is not undefined. + assert(IsReadableStream(stream), "must be owned by a ReadableStream"); + + // Step 3: Set stream.[[disturbed]] to true. + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + state |= READABLESTREAM_IS_DISTURBED; + UnsafeSetReservedSlot(stream, READABLESTREAM_SLOT_STATE, state); + + // Step 4: If stream.[[state]] is "errored", return a promise rejected with + // stream.[[storedError]]. + if (state & READABLESTREAM_STATE_ERRORED) { + let storedError = UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_STORED_ERROR); + return CreatePromiseRejectedWith(storedError); + } + + // Step 5: Return ! ReadableByteStreamControllerPullInto(stream.[[readableStreamController]], view). + let controller = UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_CONTROLLER); + return ReadableByteStreamControllerPullInto(controller, view); +} + +// Streams spec, 3.7.7. ReadableStreamDefaultReaderRead ( reader ) +function ReadableStreamDefaultReaderRead(reader) { + assert(IsReadableStreamDefaultReader(reader), "must be a default reader"); + + // Step 1: Let stream be reader.[[ownerReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(reader, + READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM); + + // Step 2: Assert: stream is not undefined. + assert(IsReadableStream(stream), "must be owned by a ReadableStream"); + + // Step 3: Set stream.[[disturbed]] to true. + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + state |= READABLESTREAM_IS_DISTURBED; + UnsafeSetReservedSlot(stream, READABLESTREAM_SLOT_STATE, state); + + // Step 4: If stream.[[state]] is "closed", return a new promise resolved with + // ! CreateIterResultObject(undefined, true). + if (state & READABLESTREAM_STATE_CLOSED) + return CreatePromiseResolvedWith({value: undefined, done: true}); + + // Step 5: If stream.[[state]] is "errored", return a new promise rejected with + // stream.[[storedError]]. + if (state & READABLESTREAM_STATE_ERRORED) { + let storedError = UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_STORED_ERROR); + return CreatePromiseRejectedWith(storedError); + } + + // Step 6: Assert: stream.[[state]] is "readable". + assert(state & READABLESTREAM_STATE_READABLE, "must be readable if not closed or errored"); + + // Step 7: Return ! stream.[[readableStreamController]].[[Pull]](). + // This part is a bit annoying: we don't actually want to store the `Pull` + // method in a slot, so we have to check the type of the controller and call + // the right method manually. + let controller = UnsafeGetObjectFromReservedSlot(stream, READABLESTREAM_SLOT_CONTROLLER); + let PullMethod = IsReadableStreamDefaultController(controller) + ? ReadableStreamDefaultController_pull + : ReadableByteStreamController_pull; + return callFunction(PullMethod, controller); +} + +// Streams spec, 3.8.3 new ReadableStreamDefaultController ( stream, underlyingSource, +// size, highWaterMark ) +function ReadableStreamDefaultController(stream, underlyingSource, size, highWaterMark) { + if (!IsReadableStreamDefaultController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamDefaultController", + "ctor", typeof this); + } + + // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception. + if (!IsObject(stream) || !IsReadableStream(stream)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStream", + "ReadableStreamDefaultController", typeof stream); + } + + // Step 2: If stream.[[readableStreamController]] is not undefined, throw a + // TypeError exception. + if (UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_CONTROLLER) !== undefined) + ThrowTypeError(JSMSG_READABLESTREAM_CONTROLLER_SET); + + // Step 3: Set this.[[controlledReadableStream]] to stream. + UnsafeSetReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM, stream); + + // Step 4: Set this.[[underlyingSource]] to underlyingSource. + UnsafeSetReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_UNDERLYING_SOURCE, + underlyingSource); + + // Step 5: Perform ! ResetQueue(this). + ResetQueue(this); + + // Step 6: Set this.[[started]], this.[[closeRequested]], this.[[pullAgain]], + // and this.[[pulling]] to false. + UnsafeSetReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_FLAGS, 0); + + // Step 7: Let normalizedStrategy be + // ? ValidateAndNormalizeQueuingStrategy(size, highWaterMark). + let normalizedStrategy = ValidateAndNormalizeQueuingStrategy(size, highWaterMark); + + // Step 8: Set this.[[strategySize]] to normalizedStrategy.[[size]] and + // this.[[strategyHWM]] to normalizedStrategy.[[highWaterMark]]. + UnsafeSetReservedSlot(this, READABLESTREAMDEFAULTCONTROLLER_SLOT_STRATEGY_SIZE, + normalizedStrategy.size); + UnsafeSetReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_STRATEGY_HWM, + normalizedStrategy.highWaterMark); + + // Step 9: Let controller be this. + let controller = this; + + // Step 10: Let startResult be + // ? InvokeOrNoop(underlyingSource, "start", « this »). + let startResult = InvokeOrNoop(underlyingSource, "start", [this]); + + // Step 11: Let startPromise be a promise resolved with startResult: + let startPromise = CreatePromiseResolvedWith(startResult); + AddPromiseReactions(startPromise, + // Step a: Upon fulfillment of startPromise, + () => { + // Step i: Set controller.[[started]] to true. + SET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_STARTED); + + // Step ii: Assert: controller.[[pulling]] is false. + assert(!(READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_PULLING), + "should not be pulling after start promise resolves"); + + // Step iii: Assert: controller.[[pullAgain]] is false. + assert(!(READABLESTREAMCONTROLLER_FLAGS(controller) & + READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN), + "should not need to pull again after start promise resolves"); + + // Step iv: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller). + ReadableStreamDefaultControllerCallPullIfNeeded(controller); + }, + + // Step b: Upon rejection with reason r, + r => { + // Step i: Perform ! ReadableStreamDefaultControllerErrorIfNeeded(controller, r). + ReadableStreamDefaultControllerErrorIfNeeded(controller, r); + } + ); +} + +// Streams spec, 3.8.4.1. get desiredSize +function ReadableStreamDefaultController_desiredSize() { + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + if (!IsReadableStreamDefaultController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamDefaultController", + "desiredSize", typeof this); + } + + // Step 2: Return ! ReadableStreamDefaultControllerGetDesiredSize(this). + return ReadableStreamDefaultControllerGetDesiredSize(this); +} + +// Streams spec, 3.8.4.2 close() +function ReadableStreamDefaultController_close() { + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + if (!IsReadableStreamDefaultController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamDefaultController", + "close", typeof this); + } + + // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception. + if (READABLESTREAMCONTROLLER_FLAGS(this) & READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED) + ThrowTypeError(JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close"); + + // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable", + // throw a TypeError exception. + let stream = UnsafeGetObjectFromReservedSlot(this, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (!(state & READABLESTREAM_STATE_READABLE)) + ThrowTypeError(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close"); + + // Step 4: Perform ! ReadableStreamDefaultControllerClose(this). + ReadableStreamDefaultControllerClose(this); +} + +// Streams spec, 3.8.4.3. enqueue ( chunk ) +function ReadableStreamDefaultController_enqueue(chunk) { + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + if (!IsReadableStreamDefaultController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamDefaultController", + "enqueue", typeof this); + } + + // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception. + if (READABLESTREAMCONTROLLER_FLAGS(this) & READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED) + ThrowTypeError(JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue"); + + // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable", + // throw a TypeError exception. + let stream = UnsafeGetObjectFromReservedSlot(this, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (!(state & READABLESTREAM_STATE_READABLE)) + ThrowTypeError(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "enqueue"); + + // Step 4: Return ! ReadableStreamDefaultControllerEnqueue(this, chunk). + return ReadableStreamDefaultControllerEnqueue(this, chunk); +} + +// Streams spec, 3.8.4.4. error ( e ) +function ReadableStreamDefaultController_error(e) { + // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a + // TypeError exception. + if (!IsReadableStreamDefaultController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamDefaultController", + "enqueue", typeof this); + } + + // Step 2: Let stream be this.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(this, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception. + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (!(state & READABLESTREAM_STATE_READABLE)) + ThrowTypeError(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error"); + + // Step 4: Perform ! ReadableStreamDefaultControllerError(this, e). + ReadableStreamDefaultControllerError(this, e); +} + +// Streams spec, 3.8.5.1. [[Cancel]] ( reason ) +function ReadableStreamDefaultController_cancel(reason) { + assert(IsReadableStreamDefaultController(this), + "must operate on a ReadableStreamDefaultController"); + + // Step 1: Perform ! ResetQueue(this). + ResetQueue(this); + + // Step 2: Return ! PromiseInvokeOrNoop(this.[[underlyingSource]], "cancel", « reason ») + let underlyingSource = + UnsafeGetObjectFromReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_UNDERLYING_SOURCE); + return PromiseInvokeOrNoop(underlyingSource, "cancel", [reason]); +} + +// Streams spec, 3.8.5.2. [[Pull]] ( ) +function ReadableStreamDefaultController_pull() { + assert(IsReadableStreamDefaultController(this), + "must operate on a ReadableStreamDefaultController"); + + // Step 1: Let stream be this.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(this, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 2: If this[[queue]] is not empty, + let queue = UnsafeGetObjectFromReservedSlot(this, QUEUE_CONTAINER_SLOT_QUEUE); + if (queue.length !== 0) { + // Step a: Let chunk be ! DequeueValue(this.[[queue]]). + let chunk = DequeueValue(this); + + // Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty, + // perform ! ReadableStreamClose(stream). + let closeRequested = READABLESTREAMCONTROLLER_FLAGS(this) & + READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED; + if (closeRequested && queue.length === 0) + ReadableStreamClose(stream); + + // Step c: Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this). + else + ReadableStreamDefaultControllerCallPullIfNeeded(this); + + // Step d: Return a promise resolved with ! CreateIterResultObject(chunk, false). + return CreatePromiseResolvedWith({value: chunk, done: false}); + } + + // Step 3: Let pendingPromise be ! ReadableStreamAddReadRequest(stream). + let pendingPromise = ReadableStreamAddReadRequest(stream); + + // Step 4: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this). + ReadableStreamDefaultControllerCallPullIfNeeded(this); + + // Step 5: Return pendingPromise. + return pendingPromise; +} + +// Streams spec, 3.9.1 IsReadableStreamDefaultController ( x ) +// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStreamDefaultController>() + +// Streams spec, 3.9.2 ReadableStreamDefaultControllerCallPullIfNeeded ( controller ) +function ReadableStreamDefaultControllerCallPullIfNeeded(controller) { + assert(IsReadableStreamDefaultController(controller), + "must operate on a ReadableStreamDefaultController"); + + // Step 1: Let shouldPull be ! ReadableStreamDefaultControllerShouldCallPull(controller). + let shouldPull = ReadableStreamDefaultControllerShouldCallPull(controller); + + // Step 2: If shouldPull is false, return. + if (!shouldPull) + return; + + // Step 3: If controller.[[pulling]] is true, + if (READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_PULLING) { + // Step a: Set controller.[[pullAgain]] to true. + SET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN); + + // Step b: Return. + return; + } + + // Step 4: Assert: controller.[[pullAgain]] is false. + assert(!READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN), + "Mustn't have the pullAgain flag set"); + + // Step 5: Set controller.[[pulling]] to true. + SET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_PULLING); + + // Step 6: Let pullPromise be + // ! PromiseInvokeOrNoop(controller.[[underlyingSource]], "pull", « controller »). + let underlyingSource = + UnsafeGetObjectFromReservedSlot(controller, READABLESTREAMCONTROLLER_SLOT_UNDERLYING_SOURCE); + let pullPromise = PromiseInvokeOrNoop(underlyingSource, "pull", [controller]); + + // Step 7: Upon fulfillment of pullPromise, + AddPromiseReactions(pullPromise, () => { + // Step a: Set controller.[[pulling]] to false. + UNSET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_PULLING); + + // Step b: If controller.[[pullAgain]] is true, + if (READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN) { + // Step i: Set controller.[[pullAgain]] to false. + UNSET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN); + + // Step ii: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller). + ReadableStreamDefaultControllerCallPullIfNeeded(controller); + } + }, + + // Step 8: Upon rejection of pullPromise with reason e, + e => { + // Step a: If controller.[[controlledReadableStream]].[[state]] is "readable", + // perform ! ReadableStreamDefaultControllerError(controller, e). + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (state & READABLESTREAM_STATE_READABLE) + ReadableStreamDefaultControllerError(controller, e); + }); +} + +// Streams spec, 3.9.3. ReadableStreamDefaultControllerShouldCallPull ( controller ) +function ReadableStreamDefaultControllerShouldCallPull(controller) { + assert(IsReadableStreamDefaultController(controller), + "must operate on a ReadableStreamDefaultController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 2: If stream.[[state]] is "closed" or stream.[[state]] is "errored", + // return false. + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (state & (READABLESTREAM_STATE_CLOSED | READABLESTREAM_STATE_ERRORED)) + return false; + + // Step 3: If controller.[[closeRequested]] is true, return false. + if (READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED) + return false; + + // Step 4: If controller.[[started]] is false, return false. + if (!(READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_STARTED)) + return false; + + // Step 5: If ! IsReadableStreamLocked(stream) is true and + // ! ReadableStreamGetNumReadRequests(stream) > 0, return true. + if (IsReadableStreamLocked(stream) && ReadableStreamGetNumReadRequests(stream) > 0) + return true; + + // Step 6: Let desiredSize be ReadableStreamDefaultControllerGetDesiredSize(controller). + let desiredSize = ReadableStreamDefaultControllerGetDesiredSize(controller); + + // Step 7: If desiredSize > 0, return true. + // Step 8: Return false. + return desiredSize > 0; +} + +// Streams spec, 3.9.4. ReadableStreamDefaultControllerClose ( controller ) +function ReadableStreamDefaultControllerClose(controller) { + assert(IsReadableStreamDefaultController(controller), + "must operate on a ReadableStreamDefaultController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 2: Assert: controller.[[closeRequested]] is false. + assert(!(READABLESTREAMCONTROLLER_FLAGS(controller) & + READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED), + "controller should not have a pending close operation"); + + // Step 3: Assert: stream.[[state]] is "readable". + assert(UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + READABLESTREAM_STATE_READABLE, + "stream should be in the readable state"); + + // Step 4: Set controller.[[closeRequested]] to true. + SET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED); + + // Step 5: If controller.[[queue]] is empty, perform ! ReadableStreamClose(stream). + let queue = UnsafeGetObjectFromReservedSlot(controller, QUEUE_CONTAINER_SLOT_QUEUE); + if (queue.length === 0) + ReadableStreamClose(stream); +} + +// Streams spec, 3.9.5. ReadableStreamDefaultControllerEnqueue ( controller, chunk ) +function ReadableStreamDefaultControllerEnqueue(controller, chunk) { + assert(IsReadableStreamDefaultController(controller), + "must operate on a ReadableStreamDefaultController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 2: Assert: controller.[[closeRequested]] is false. + assert(!(READABLESTREAMCONTROLLER_FLAGS(controller) & + READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED), + "controller should not have a pending close operation"); + + // Step 3: Assert: stream.[[state]] is "readable". + assert(UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + READABLESTREAM_STATE_READABLE, + "stream should be in the readable state"); + + // Step 4: If ! IsReadableStreamLocked(stream) is true and + // ! ReadableStreamGetNumReadRequests(stream) > 0, perform + // ! ReadableStreamFulfillReadRequest(stream, chunk, false). + if (IsReadableStreamLocked(stream) && ReadableStreamGetNumReadRequests(stream) > 0) { + ReadableStreamFulfillReadRequest(stream, chunk, false); + } else { + // Step 5: Otherwise, + // Step a: Let chunkSize be 1. + let chunkSize = 1; + + // Step b: If controller.[[strategySize]] is not undefined, + let strategySize = UnsafeGetReservedSlot(controller, + READABLESTREAMDEFAULTCONTROLLER_SLOT_STRATEGY_SIZE); + if (strategySize !== undefined) { + // Step i: Set chunkSize to Call(stream.[[strategySize]], undefined, chunk). + try { + chunkSize = callContentFunction(strategySize, undefined, chunk); + } catch (e) { + // Step ii: If chunkSize is an abrupt completion, + // Step i: Perform + // ! ReadableStreamDefaultControllerErrorIfNeeded(controller, + // chunkSize.[[Value]]). + ReadableStreamDefaultControllerErrorIfNeeded(controller, e); + + // Step 2: Return chunkSize. + throw e; + } + } + + // Step c: Let enqueueResult be + // ! EnqueueValueWithSize(controller, chunk, chunkSize). + try { + EnqueueValueWithSize(controller, chunk, chunkSize); + } catch (e) { + // Step d: If enqueueResult is an abrupt completion, + // Step i: Perform + // ! ReadableStreamDefaultControllerErrorIfNeeded(controller, + // enqueueResult.[[Value]]). + ReadableStreamDefaultControllerErrorIfNeeded(controller, e); + + // Step ii: Return enqueueResult. + throw e; + } + } + + // Step 6: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller). + ReadableStreamDefaultControllerCallPullIfNeeded(controller); + + // Step 7: Return (implicit). +} + +// Streams spec, 3.9.6. ReadableStreamDefaultControllerError ( controller, e ) +function ReadableStreamDefaultControllerError(controller, e) { + assert(IsReadableStreamDefaultController(controller), + "must operate on a ReadableStreamDefaultController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 2: Assert: stream.[[state]] is "readable". + assert(UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + READABLESTREAM_STATE_READABLE, + "stream should be in the readable state"); + + // Step 3: Perform ! ResetQueue(controller). + ResetQueue(controller); + + // Step 4: Perform ! ReadableStreamError(stream, e). + ReadableStreamError(stream, e); +} + +// Streams spec, 3.9.7. ReadableStreamDefaultControllerErrorIfNeeded ( controller, e ) nothrow +function ReadableStreamDefaultControllerErrorIfNeeded(controller, e) { + // Step 1: If controller.[[controlledReadableStream]].[[state]] is "readable", + // perform ! ReadableStreamDefaultControllerError(controller, e). + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + if (UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + READABLESTREAM_STATE_READABLE) + { + ReadableStreamDefaultControllerError(controller, e); + } +} + +// Streams spec, 3.9.8. ReadableStreamDefaultControllerGetDesiredSize ( controller ) +function ReadableStreamDefaultControllerGetDesiredSize(controller) { + assert(IsReadableStreamDefaultController(controller), + "must operate on a ReadableStreamDefaultController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + + // Step 2: Let state be stream.[[state]]. + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + + // Step 3: If state is "errored", return null. + if (state & READABLESTREAM_STATE_ERRORED) + return null; + + // Step 4: If state is "closed", return 0. + if (state & READABLESTREAM_STATE_CLOSED) + return 0; + + // Step 5: Return controller.[[strategyHWM]] − controller.[[queueTotalSize]]. + let strategyHWM = UnsafeGetReservedSlot(controller, READABLESTREAMCONTROLLER_SLOT_STRATEGY_HWM); + let queueSize = UnsafeGetReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + return strategyHWM - queueSize; +} + +// Streams spec, 3.10.3. new ReadableByteStreamController ( stream, underlyingByteSource, highWaterMark ) +function ReadableByteStreamController(stream, underlyingByteSource, highWaterMark) { + if (!IsReadableByteStreamController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableByteStreamController", + "ctor", typeof this); + } + + // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception. + if (!IsObject(stream) || !IsReadableStream(stream)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStream", + "ReadableByteStreamController", typeof this); + } + + // Step 2: If stream.[[readableStreamController]] is not undefined, throw a + // TypeError exception. + let controller = UnsafeGetReservedSlot(stream, READABLESTREAM_SLOT_CONTROLLER); + if (controller !== undefined) + ThrowTypeError(JSMSG_READABLESTREAM_CONTROLLER_SET); + + // Step 3: Set this.[[controlledReadableStream]] to stream. + UnsafeSetReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM, stream); + + // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource. + UnsafeSetReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_UNDERLYING_SOURCE, + underlyingByteSource); + + // Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false. + UnsafeSetReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_FLAGS, 0); + + // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this). + ReadableByteStreamControllerClearPendingPullIntos(this); + + // Step 7: Perform ! ResetQueue(this). + ResetQueue(this); + + // Step 8: Set this.[[started]] and this.[[closeRequested]] to false. + assert(UnsafeGetInt32FromReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_FLAGS) === 0, + "No flags should have been set since step 5."); + + // Step 9: Set this.[[strategyHWM]] to + // ? ValidateAndNormalizeHighWaterMark(highWaterMark). + UnsafeSetReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_STRATEGY_HWM, + ValidateAndNormalizeHighWaterMark(highWaterMark)); + + // Step 10: Let autoAllocateChunkSize be + // ? GetV(underlyingByteSource, "autoAllocateChunkSize"). + let autoAllocateChunkSize = underlyingByteSource.autoAllocateChunkSize; + + // Step 11: If autoAllocateChunkSize is not undefined, + if (autoAllocateChunkSize !== undefined) { + // Step a: If ! IsInteger(autoAllocateChunkSize) is false, or if + // autoAllocateChunkSize ≤ 0, throw a RangeError exception. + if (!IsInteger(autoAllocateChunkSize) || autoAllocateChunkSize <= 0) + ThrowRangeError(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE); + } + + // Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize. + UnsafeSetReservedSlot(this, READABLEBYTESTREAMCONTROLLER_SLOT_AUTO_ALLOCATE_CHUNK_SIZE, + autoAllocateChunkSize); + + // Step 13: Set this.[[pendingPullIntos]] to a new empty List. + UnsafeSetReservedSlot(this, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS, + new List()); + + // Step 14: Let controller be this. + controller = this; + + // Step 15: Let startResult be ? InvokeOrNoop(underlyingByteSource, "start", « this »). + let startResult = InvokeOrNoop(underlyingByteSource, "start", [this]); + + // Step 16: Let startPromise be a promise resolved with startResult: + let startPromise = CreatePromiseResolvedWith(startResult); + AddPromiseReactions(startPromise, + // Step a: Upon fulfillment, + () => { + // Step i: Set controller.[[started]] to true. + SET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_STARTED); + + // Step ii: Assert: controller.[[pulling]] is false. + assert(!(READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_PULLING), + "should not be pulling after start promise resolves"); + + // Step iii: Assert: controller.[[pullAgain]] is false. + assert(!(READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN), + "should not need to pull again after start promise resolves"); + + // Step iv: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + ReadableByteStreamControllerCallPullIfNeeded(controller); + }, + + // Step b: Upon rejection with reason r, + r => { + // Step i: If stream.[[state]] is "readable", perform + // ! ReadableByteStreamControllerError(controller, r). + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (state & READABLESTREAM_STATE_READABLE) + ReadableByteStreamControllerError(controller, r); + } + ); +} + +// Streams spec, 3.10.4.1. get byobRequest +function ReadableByteStreamController_byobRequest() { + // Step 1: If IsReadableByteStreamController(this) is false, throw a TypeError + // exception. + if (!IsReadableByteStreamController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableByteStreamController", + "byobRequest", typeof this); + } + + // Step 2: If this.[[byobRequest]] is undefined and this.[[pendingPullIntos]] + // is not empty, + let byobRequest = + UnsafeGetReservedSlot(this, READABLEBYTESTREAMCONTROLLER_SLOT_BYOB_REQUEST); + let pendingPullIntos = + UnsafeGetObjectFromReservedSlot(this, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS); + if (byobRequest === undefined && pendingPullIntos.length !== 0) { + // Step a: Let firstDescriptor be the first element of this.[[pendingPullIntos]]. + let firstDescriptor = pendingPullIntos[0]; + + // Step b: Let view be ! Construct(%Uint8Array%, + // « firstDescriptor.[[buffer]], + // firstDescriptor.[[byteOffset]] + firstDescriptor.[[bytesFilled]], + // firstDescriptor.[[byteLength]] − firstDescriptor.[[bytesFilled]] »). + let view = new Uint8Array(firstDescriptor.buffer, + firstDescriptor.byteOffset + firstDescriptor.bytesFilled, + firstDescriptor.byteLength - firstDescriptor.bytesFilled); + + // Step c: Set this.[[byobRequest]] to + // ! Construct(ReadableStreamBYOBRequest, « this, view »). + let ReadableStreamBYOBRequestCtor = GetBuiltinConstructor("ReadableStreamBYOBRequest"); + byobRequest = new ReadableStreamBYOBRequestCtor(this, view); + UnsafeSetReservedSlot(this, READABLEBYTESTREAMCONTROLLER_SLOT_BYOB_REQUEST, byobRequest); + } + + // Step 3: Return this.[[byobRequest]]. + return byobRequest; +} + +// Streams spec, 3.10.4.2. get desiredSize +function ReadableByteStreamController_desiredSize() { + // Step 1: If ! IsReadableByteStreamController(this) is false, throw a + // TypeError exception. + if (!IsReadableByteStreamController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableByteStreamController", + "desiredSize", typeof this); + } + + // Step 2: Return ! ReadableByteStreamControllerGetDesiredSize(this). + return ReadableByteStreamControllerGetDesiredSize(this); +} + +// Streams spec, 3.10.4.3. close() +function ReadableByteStreamController_close() { + // Step 1: If ! IsReadableByteStreamController(this) is false, throw a + // TypeError exception. + if (!IsReadableByteStreamController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableByteStreamController", + "close", typeof this); + } + + // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception. + if (READABLESTREAMCONTROLLER_FLAGS(this) & READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED) + ThrowTypeError(JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close"); + + // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable", + // throw a TypeError exception. + let stream = UnsafeGetObjectFromReservedSlot(this, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (!(state & READABLESTREAM_STATE_READABLE)) + ThrowTypeError(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close"); + + // Step 4: Perform ? ReadableByteStreamControllerClose(this). + ReadableByteStreamControllerClose(this); +} + +// Streams spec, 3.10.4.4. enqueue ( chunk ) +function ReadableByteStreamController_enqueue(chunk) { + // Step 1: If ! IsReadableByteStreamController(this) is false, throw a + // TypeError exception. + if (!IsReadableByteStreamController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableByteStreamController", + "close", typeof this); + } + + // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception. + if (READABLESTREAMCONTROLLER_FLAGS(this) & READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED) + ThrowTypeError(JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue"); + + // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable", + // throw a TypeError exception. + let stream = UnsafeGetObjectFromReservedSlot(this, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (!(state & READABLESTREAM_STATE_READABLE)) + ThrowTypeError(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "enqueue"); + + // 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 (!IsObject(chunk) || !IsTypedArray(chunk)) + ThrowTypeError(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK); + + // Step 6: Return ! ReadableByteStreamControllerEnqueue(this, chunk). + return ReadableByteStreamControllerEnqueue(this, chunk); +} + +// Streams spec, 3.10.4.5. error ( e ) +function ReadableByteStreamController_error(e) { + // Step 1: If ! IsReadableByteStreamController(this) is false, throw a + // TypeError exception. + if (!IsReadableByteStreamController(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableByteStreamController", + "close", typeof this); + } + + // Step 2: Let stream be this.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(this, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception. + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (!(state & READABLESTREAM_STATE_READABLE)) + ThrowTypeError(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error"); + + // Step 4: Perform ! ReadableByteStreamControllerError(this, e). + ReadableByteStreamControllerError(this, e); +} + +// Streams spec, 3.10.5.1. [[Cancel]] ( reason ) +function ReadableByteStreamController_cancel(reason) { + assert(IsReadableByteStreamController(this), "must operate on ReadableByteStreamController"); + + // Step 1: If this.[[pendingPullIntos]] is not empty, + let pendingPullIntos = + UnsafeGetObjectFromReservedSlot(this, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS); + if (pendingPullIntos.length !== 0) { + // Step a: Let firstDescriptor be the first element of + // this.[[pendingPullIntos]]. + // Step b: Set firstDescriptor.[[bytesFilled]] to 0. + pendingPullIntos[0].bytesFilled = 0; + } + + // Step 2: Perform ! ResetQueue(this). + ResetQueue(this); + + // Step 3: Return ! PromiseInvokeOrNoop(this.[[underlyingByteSource]], "cancel", « reason ») + let underlyingByteSource = + UnsafeGetObjectFromReservedSlot(this, READABLESTREAMCONTROLLER_SLOT_UNDERLYING_SOURCE); + return PromiseInvokeOrNoop(underlyingByteSource, "cancel", [reason]); +} + +// Streams spec, 3.10.5.2. [[Pull]] ( ) +function ReadableByteStreamController_pull() { + assert(IsReadableByteStreamController(this), "must operate on ReadableByteStreamController"); + + // Step 1: Let stream be this.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(this, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + + // Step 2: Assert: ! ReadableStreamHasDefaultReader(stream) is true. + assert(ReadableStreamHasDefaultReader(stream), "Stream should have a default reader"); + + // Step 3: If this.[[queueTotalSize]] > 0, + let queueTotalSize = UnsafeGetInt32FromReservedSlot(this, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + if (queueTotalSize > 0) { + // Step 3.a: Assert: ! ReadableStreamGetNumReadRequests(_stream_) is 0. + assert(ReadableStreamGetNumReadRequests(stream) === 0, "Invalid stream state"); + + // 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). + let queue = UnsafeGetObjectFromReservedSlot(this, QUEUE_CONTAINER_SLOT_QUEUE); + let entry = ArrayStaticShift(queue); + + // Step 3.d: Set this.[[queueTotalSize]] to this.[[queueTotalSize]] − entry.[[byteLength]]. + queueTotalSize = queueTotalSize - entry.byteLength; + UnsafeSetReservedSlot(this, QUEUE_CONTAINER_SLOT_TOTAL_SIZE, queueTotalSize); + + // Step 3.e: Perform ! ReadableByteStreamControllerHandleQueueDrain(this). + ReadableByteStreamControllerHandleQueueDrain(this); + + // Step 3.f: Let view be ! Construct(%Uint8Array%, « entry.[[buffer]], + // entry.[[byteOffset]], entry.[[byteLength]] »). + let Uint8Array = GetBuiltinConstructor("Uint8Array"); + let view = new Uint8Array(entry.buffer, entry.byteOffset, entry.byteLength); + + // Step 3.g: Return a promise resolved with ! CreateIterResultObject(view, false). + return CreatePromiseResolvedWith({value: view, done: false}); + } + + // Step 4: Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]]. + let autoAllocateChunkSize = + UnsafeGetReservedSlot(this, READABLEBYTESTREAMCONTROLLER_SLOT_AUTO_ALLOCATE_CHUNK_SIZE); + + // Step 5: If autoAllocateChunkSize is not undefined, + if (autoAllocateChunkSize !== undefined) { + // Step 5.a: Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize »). + let ArrayBuffer = GetBuiltinConstructor("ArrayBuffer"); + let buffer; + try { + buffer = new ArrayBuffer(autoAllocateChunkSize); + } + + // Step 5.b: If buffer is an abrupt completion, + // return a promise rejected with buffer.[[Value]]. + catch (e) { + return CreatePromiseRejectedWith(e); + } + // Step 5.c: Let pullIntoDescriptor be Record {[[buffer]]: buffer.[[Value]], + // [[byteOffset]]: 0, + // [[byteLength]]: autoAllocateChunkSize, + // [[bytesFilled]]: 0, [[elementSize]]: 1, + // [[ctor]]: %Uint8Array%, + // [[readerType]]: `"default"`}. + let pullIntoDescriptor = { + __proto__: null, + buffer, + byteOffset: 0, + byteLength: autoAllocateChunkSize, + bytesFilled: 0, + elementSize: 1, + ctor: GetBuiltinConstructor("Uint8Array"), + readerType: "default" + }; + + // Step 5.d: Append pullIntoDescriptor as the last element of this.[[pendingPullIntos]]. + let pendingPullIntos = + UnsafeGetObjectFromReservedSlot(this, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS); + ArrayStaticPush(pendingPullIntos, pullIntoDescriptor); + } + + // Step 6: Let promise be ! ReadableStreamAddReadRequest(stream). + let promise = ReadableStreamAddReadRequest(stream); + + // Step 7: Perform ! ReadableByteStreamControllerCallPullIfNeeded(this). + ReadableByteStreamControllerCallPullIfNeeded(this); + + // Step 8: Return promise. + return promise; +} + +// Streams spec, 3.11.3. new ReadableStreamBYOBRequest ( controller, view ) +function ReadableStreamBYOBRequest(controller, view) { + if (!IsReadableStreamBYOBRequest(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamBYOBRequest", + "ctor", typeof this); + } + + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + assert(IsObject(view), "view should be an object"); + + // Step 1: Set this.[[associatedReadableByteStreamController]] to controller. + UnsafeSetReservedSlot(this, READABLESTREAMBYOBREQUEST_SLOT_ASSOCIATED_READABLE_BYTESTREAM_CONTROLLER, + controller); + + // Step 2: Set this.[[view]] to view. + UnsafeSetReservedSlot(this, READABLESTREAMBYOBREQUEST_SLOT_VIEW, view); +} + +// Streams spec, 3.11.4.1 get view +function ReadableStreamBYOBRequest_view() { + // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError + // exception. + if (!IsReadableStreamBYOBRequest(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamBYOBRequest", + "view", typeof this); + } + + // Step 2: Return this.[[view]]. + return UnsafeGetObjectFromReservedSlot(this, READABLESTREAMBYOBREQUEST_SLOT_VIEW); +} + +// Streams spec, 3.11.4.2. respond ( bytesWritten ) +function ReadableStreamBYOBRequest_respond(bytesWritten) { + // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError + // exception. + if (!IsReadableStreamBYOBRequest(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamBYOBRequest", + "view", typeof this); + } + + // Step 2: If this.[[associatedReadableByteStreamController]] is undefined, + // throw a TypeError exception. + let controller = + UnsafeGetReservedSlot(this, READABLESTREAMBYOBREQUEST_SLOT_ASSOCIATED_READABLE_BYTESTREAM_CONTROLLER); + if (controller === undefined) + ThrowTypeError(JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, "respond"); + + // Step 3: Return ? ReadableByteStreamControllerRespond(this.[[associatedReadableByteStreamController]], + // bytesWritten). + return ReadableByteStreamControllerRespond(controller, bytesWritten); +} + +// Streams spec, 3.11.4.3. respondWithNewView ( view ) +function ReadableStreamBYOBRequest_respondWithNewView(view) { + // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError + // exception. + if (!IsReadableStreamBYOBRequest(this)) { + ThrowTypeError(JSMSG_INCOMPATIBLE_PROTO, "ReadableStreamBYOBRequest", + "respondWithView", typeof this); + } + + // Step 2: If this.[[associatedReadableByteStreamController]] is undefined, + // throw a TypeError exception. + let controller = + UnsafeGetReservedSlot(this, READABLESTREAMBYOBREQUEST_SLOT_ASSOCIATED_READABLE_BYTESTREAM_CONTROLLER); + if (controller === undefined) + ThrowTypeError(JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, "respond"); + + // 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 (!IsObject(view) || !IsTypedArray(view)) + ThrowTypeError(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK); + + // Step 5: Return ? ReadableByteStreamControllerRespondWithNewView(this.[[associatedReadableByteStreamController]], + // view). + return ReadableByteStreamControllerRespondWithNewView(controller, view); +} + +// Streams spec, 3.12.1. IsReadableStreamBYOBRequest ( x ) +// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStreamBYOBRequest>() + +// Streams spec, 3.12.2. IsReadableByteStreamController ( x ) +// Implemented via intrinsic_isInstanceOfBuiltin<ReadableByteStreamController>() + +// Streams spec, 3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller ) +function ReadableByteStreamControllerCallPullIfNeeded(controller) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Let shouldPull be + // ! ReadableByteStreamControllerShouldCallPull(controller). + let shouldPull = ReadableByteStreamControllerShouldCallPull(controller); + + // Step 2: If shouldPull is false, return. + if (!shouldPull) + return; + + // Step 3: If controller.[[pulling]] is true, + if (READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_PULLING) { + // Step a: Set controller.[[pullAgain]] to true. + SET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN); + + // Step b: Return. + return; + } + + // Step 4: Assert: controller.[[pullAgain]] is false. + assert(!(READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN), + "Controller's pullAgain flag must be false"); + + // Step 5: Set controller.[[pulling]] to true. + SET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_PULLING); + + // Step 6: Let pullPromise be + // ! PromiseInvokeOrNoop(controller.[[underlyingByteSource]], "pull", controller). + let underlyingSource = + UnsafeGetObjectFromReservedSlot(controller, READABLESTREAMCONTROLLER_SLOT_UNDERLYING_SOURCE); + let pullPromise = PromiseInvokeOrNoop(underlyingSource, "pull", [controller]); + + // Step 7: Upon fulfillment of pullPromise, + AddPromiseReactions(pullPromise, () => { + // Step a: Set controller.[[pulling]] to false. + UNSET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_PULLING); + + // Step b: If controller.[[pullAgain]] is true, + if (READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN) { + // Step i: Set controller.[[pullAgain]] to false. + UNSET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN); + + // Step ii: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + ReadableByteStreamControllerCallPullIfNeeded(controller); + } + }, + + // Step 8: Upon rejection of pullPromise with reason e, + e => { + // Step a: If controller.[[controlledReadableStream]].[[state]] is "readable", + // perform ! ReadableByteStreamControllerError(controller, e). + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (state & READABLESTREAM_STATE_READABLE) + ReadableByteStreamControllerError(controller, e); + }); +} + +// Streams spec, 3.12.4. ReadableByteStreamControllerClearPendingPullIntos ( controller ) +function ReadableByteStreamControllerClearPendingPullIntos(controller) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). + ReadableByteStreamControllerInvalidateBYOBRequest(controller); + + // Step 2: Set controller.[[pendingPullIntos]] to a new empty List. + UnsafeSetReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS, + new List()); +} + +// Streams spec, 3.12.5. ReadableByteStreamControllerClose ( controller ) +function ReadableByteStreamControllerClose(controller) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 2: Assert: controller.[[closeRequested]] is false. + assert(!(READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED), + "controller should not have a close requested"); + + // Step 3: Assert: stream.[[state]] is "readable". + assert(UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + READABLESTREAM_STATE_READABLE, + "controller's stream should be in the readable state"); + + // Step 4: If controller.[[queueTotalSize]] > 0, + let queueTotalSize = UnsafeGetInt32FromReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + if (queueTotalSize > 0) { + // Step a: Set controller.[[closeRequested]] to true. + SET_READABLESTREAMCONTROLLER_FLAGS(controller, READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED); + + // Step b: Return + return; + } + + // Step 5: If controller.[[pendingPullIntos]] is not empty, + let pendingPullIntos = + UnsafeGetObjectFromReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS); + if (pendingPullIntos.length !== 0) { + // Step a: Let firstPendingPullInto be the first element of + // controller.[[pendingPullIntos]]. + let firstPendingPullInto = pendingPullIntos[0]; + + // Step b: If firstPendingPullInto.[[bytesFilled]] > 0, + if (firstPendingPullInto.bytesFilled > 0) { + try { + // Step i: Let e be a new TypeError exception. + ThrowTypeError(JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL); + } catch (e) { + // Step ii: Perform ! ReadableByteStreamControllerError(controller, e). + ReadableByteStreamControllerError(controller, e); + + // Step iii: Throw e. + throw e; + } + } + } + + // Step 6: Perform ! ReadableStreamClose(stream). + ReadableStreamClose(stream); +} + +// Streams spec, 3.12.6. ReadableByteStreamControllerCommitPullIntoDescriptor ( stream, pullIntoDescriptor ) +function ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDescriptor) { + assert(IsReadableStream(stream), "must operate on a ReadableStream"); + + // Step 1: Assert: stream.[[state]] is not "errored". + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + assert(!(state & READABLESTREAM_STATE_ERRORED), "stream must not be in the errored state"); + + // Step 2: Let done be false. + let done = false; + + // Step 3: If stream.[[state]] is "closed", + if (state & READABLESTREAM_STATE_CLOSED) { + // Step a: Assert: pullIntoDescriptor.[[bytesFilled]] is 0. + assert(pullIntoDescriptor.bytesFilled === 0, "pullIntoDescriptor should be empty"); + + // Step b: Set done to true. + done = true; + } + + // Step 4: Let filledView be + // ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor). + let filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor); + + // Step 5: If pullIntoDescriptor.[[readerType]] is "default", + if (pullIntoDescriptor.readerType === "default") { + // Step a: Perform ! ReadableStreamFulfillReadRequest(stream, filledView, done). + ReadableStreamFulfillReadRequest(stream, filledView, done); + } else { + // Step 6: Otherwise, + // Step a: Assert: pullIntoDescriptor.[[readerType]] is "byob". + assert(pullIntoDescriptor.readerType === "byob", "must be byob if not default"); + + // Step b: Perform ! ReadableStreamFulfillReadIntoRequest(stream, filledView, done). + ReadableStreamFulfillReadIntoRequest(stream, filledView, done); + } +} + +// Streams spec, 3.12.7. ReadableByteStreamControllerConvertPullIntoDescriptor ( pullIntoDescriptor ) +function ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor) { + // Step 1: Let bytesFilled be pullIntoDescriptor.[[bytesFilled]]. + let bytesFilled = pullIntoDescriptor.bytesFilled; + + // Step 2: Let elementSize be pullIntoDescriptor.[[elementSize]]. + let elementSize = pullIntoDescriptor.elementSize; + + // Step 3: Assert: bytesFilled <= pullIntoDescriptor.[[byteLength]]. + assert(bytesFilled <= pullIntoDescriptor.byteLength, + `pullIntoDescriptor should not be filled beyond its capacity: ${bytesFilled} > ${pullIntoDescriptor.byteLength}`); + + // Step 4: Assert: bytesFilled mod elementSize is 0. + assert(bytesFilled % elementSize === 0, + "pullIntoDescriptor should be filled in multiples of its elementSize"); + + // Step 5: Return ! Construct(pullIntoDescriptor.[[ctor]], + // pullIntoDescriptor.[[buffer]], + // pullIntoDescriptor.[[byteOffset]], + // bytesFilled / elementSize). + let ctor = pullIntoDescriptor.ctor; + return new ctor(pullIntoDescriptor.buffer, pullIntoDescriptor.byteOffset, + bytesFilled / elementSize); +} + +// Streams spec, 3.12.8. ReadableByteStreamControllerEnqueue ( controller, chunk ) +function ReadableByteStreamControllerEnqueue(controller, chunk) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 2: Assert: controller.[[closeRequested]] is false. + assert(!(READABLESTREAMCONTROLLER_FLAGS(controller) & + READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED), + "controller should not have a close requested"); + + // Step 3: Assert: stream.[[state]] is "readable". + assert(UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + READABLESTREAM_STATE_READABLE, + "controller's stream should be in the readable state"); + + // Step 4: Let buffer be chunk.[[ViewedArrayBuffer]]. + let buffer = TypedArrayBuffer(chunk); + + // Step 5: Let byteOffset be chunk.[[ByteOffset]]. + let byteOffset = UnsafeGetInt32FromReservedSlot(chunk, JS_TYPEDARRAYLAYOUT_BYTEOFFSET_SLOT); + + // Step 6: Let byteLength be chunk.[[ByteLength]]. + let byteLength = ArrayBufferByteLength(buffer); + + // Step 7: Let transferredBuffer be ! Transfer(buffer, the current Realm Record). + let transferredBuffer = SameRealmTransfer(buffer); + + // 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). + ReadableByteStreamControllerEnqueueChunkToQueue(controller, + transferredBuffer, + byteOffset, byteLength); + } else { + // Step b: Otherwise, + // Step i: Assert: controller.[[queue]] is empty. + assert(UnsafeGetObjectFromReservedSlot(controller, QUEUE_CONTAINER_SLOT_QUEUE) .length === 0, + "controller queue must be empty"); + + // Step ii: Let transferredView be + // ! Construct(%Uint8Array%, transferredBuffer, byteOffset, byteLength). + let Uint8Array = GetBuiltinConstructor("Uint8Array"); + let transferredView = new Uint8Array(transferredBuffer, byteOffset, byteLength); + + // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false). + ReadableStreamFulfillReadRequest(stream, transferredView, false); + } + } else if (ReadableStreamHasBYOBReader(stream)) { + // Step 9: Otherwise, + // Step a: If ! ReadableStreamHasBYOBReader(stream) is true, + // Step i: Perform + // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, + // transferredBuffer, + // byteOffset, + // byteLength). + ReadableByteStreamControllerEnqueueChunkToQueue(controller, + transferredBuffer, + byteOffset, byteLength); + + // Step ii: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). + ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller); + } else { + // Step b: Otherwise, + // Step i: Assert: ! IsReadableStreamLocked(stream) is false. + assert(!IsReadableStreamLocked(stream), + "stream must be unlocked if there is neither a byob or default reader"); + + // Step ii: Perform + // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, + // transferredBuffer, + // byteOffset, + // byteLength). + ReadableByteStreamControllerEnqueueChunkToQueue(controller, + transferredBuffer, + byteOffset, byteLength); + } +} + +// Streams spec, 3.12.9. ReadableByteStreamControllerEnqueueChunkToQueue ( controller, buffer, byteOffset, byteLength ) +function ReadableByteStreamControllerEnqueueChunkToQueue(controller, buffer, + byteOffset, byteLength) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Append Record {[[buffer]]: buffer, + // [[byteOffset]]: byteOffset, + // [[byteLength]]: byteLength} + // as the last element of controller.[[queue]]. + let queue = UnsafeGetObjectFromReservedSlot(controller, QUEUE_CONTAINER_SLOT_QUEUE); + let record = { + __proto__: null, + buffer, + byteOffset, + byteLength + }; + ArrayStaticPush(queue, record); + + // Step 2: Add byteLength to controller.[[queueTotalSize]]. + let queueTotalSize = UnsafeGetInt32FromReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + queueTotalSize += byteLength; + UnsafeSetReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE, queueTotalSize); +} + +// Streams spec, 3.12.10. ReadableByteStreamControllerError ( controller, e ) +function ReadableByteStreamControllerError(controller, e) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 2: Assert: stream.[[state]] is "readable". + assert(UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + READABLESTREAM_STATE_READABLE, + "controller's stream should be in the readable state"); + + // Step 3: Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller). + ReadableByteStreamControllerClearPendingPullIntos(controller); + + // Step 4: Perform ! ResetQueue(controller). + ResetQueue(controller); + + // Step 5: Perform ! ReadableStreamError(stream, e). + ReadableStreamError(stream, e); +} + +// Streams spec, 3.12.11. ReadableByteStreamControllerFillHeadPullIntoDescriptor ( controler, size, pullIntoDescriptor ) +function ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, size, + pullIntoDescriptor) +{ + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Assert: either controller.[[pendingPullIntos]] is empty, or the + // first element of controller.[[pendingPullIntos]] is pullIntoDescriptor. + let pendingPullIntos = + UnsafeGetObjectFromReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS); + assert(pendingPullIntos.length === 0 || pendingPullIntos[0] === pullIntoDescriptor, + "pullIntoDescriptor should be first pending pull into"); + + // Step 2: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). + ReadableByteStreamControllerInvalidateBYOBRequest(controller); + + // Step 3: Set pullIntoDescriptor.[[bytesFilled]] to pullIntoDescriptor.[[bytesFilled]] + size. + assert(size === size, "size must not be NaN"); + pullIntoDescriptor.bytesFilled += size; +} + +// Streams spec, 3.12.12. ReadableByteStreamControllerFillPullIntoDescriptorFromQueue ( controller, pullIntoDescriptor ) +function ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Let elementSize be pullIntoDescriptor.[[elementSize]]. + let elementSize = pullIntoDescriptor.elementSize; + + // Step 2: Let currentAlignedBytes be pullIntoDescriptor.[[bytesFilled]] − + // (pullIntoDescriptor.[[bytesFilled]] mod elementSize). + let currentAlignedBytes = pullIntoDescriptor.bytesFilled - + (pullIntoDescriptor.bytesFilled % elementSize); + + // Step 3: Let maxBytesToCopy be min(controller.[[queueTotalSize]], + // pullIntoDescriptor.[[byteLength]] − pullIntoDescriptor.[[bytesFilled]]). + let queueTotalSize = UnsafeGetInt32FromReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + let maxBytesToCopy = std_Math_min(queueTotalSize, + pullIntoDescriptor.byteLength - + pullIntoDescriptor.bytesFilled); + + // Step 4: Let maxBytesFilled be pullIntoDescriptor.[[bytesFilled]] + maxBytesToCopy. + let maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy; + + // Step 5: Let maxAlignedBytes be maxBytesFilled − (maxBytesFilled mod elementSize). + let maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize); + + // Step 6: Let totalBytesToCopyRemaining be maxBytesToCopy. + let totalBytesToCopyRemaining = maxBytesToCopy; + + // Step 7: Let ready be false. + let ready = false; + + // Step 8: If maxAlignedBytes > currentAlignedBytes, + if (maxAlignedBytes > currentAlignedBytes) { + // Step a: Set totalBytesToCopyRemaining to maxAlignedBytes − + // pullIntoDescriptor.[[bytesFilled]]. + totalBytesToCopyRemaining = maxAlignedBytes - pullIntoDescriptor.bytesFilled; + + // Step b: Let ready be true. + ready = true; + } + + // Step 9: Let queue be controller.[[queue]]. + let queue = UnsafeGetObjectFromReservedSlot(controller, QUEUE_CONTAINER_SLOT_QUEUE); + + // Step 10: Repeat the following steps while totalBytesToCopyRemaining > 0, + while (totalBytesToCopyRemaining > 0) { + assert(queue.length !== 0, "queue must not be empty if we have bytes to copy"); + + // Step a: Let headOfQueue be the first element of queue. + let headOfQueue = queue[0]; + + // Step b: Let bytesToCopy be min(totalBytesToCopyRemaining, + // headOfQueue.[[byteLength]]). + let bytesToCopy = std_Math_min(totalBytesToCopyRemaining, + headOfQueue.byteLength); + + // Step c: Let destStart be pullIntoDescriptor.[[byteOffset]] + + // pullIntoDescriptor.[[bytesFilled]]. + let destStart = pullIntoDescriptor.byteOffset + pullIntoDescriptor.bytesFilled; + + // Step d: Perform ! CopyDataBlockBytes(headOfQueue.[[buffer]].[[ArrayBufferData]], + // headOfQueue.[[byteOffset]], + // pullIntoDescriptor.[[buffer]].[[ArrayBufferData]], + // destStart, bytesToCopy). + let sourceBuffer = headOfQueue.buffer; + let sourceOffset = headOfQueue.byteOffset; + let targetBuffer = pullIntoDescriptor.buffer; + ArrayBufferCopyData(targetBuffer, destStart | 0, + sourceBuffer, sourceOffset | 0, + bytesToCopy | 0, false); + + // Step e: If headOfQueue.[[byteLength]] is bytesToCopy, + if (headOfQueue.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). + ArrayStaticShift(queue); + } else { + // Step f: Otherwise, + // Step i: Set headOfQueue.[[byteOffset]] to headOfQueue.[[byteOffset]] + + // bytesToCopy. + headOfQueue.byteOffset += bytesToCopy; + + // Step ii: Set headOfQueue.[[byteLength]] to headOfQueue.[[byteLength]] − + // bytesToCopy. + headOfQueue.byteLength -= bytesToCopy; + } + + // Step g: Set controller.[[queueTotalSize]] to + // controller.[[queueTotalSize]] − bytesToCopy. + queueTotalSize = UnsafeGetInt32FromReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + queueTotalSize -= bytesToCopy; + UnsafeSetReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE, queueTotalSize); + + // Step h: Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, + // bytesToCopy, + // pullIntoDescriptor). + ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, + bytesToCopy, + pullIntoDescriptor); + + // Step i: Set totalBytesToCopyRemaining to totalBytesToCopyRemaining − bytesToCopy. + totalBytesToCopyRemaining -= bytesToCopy; + } + + // Step 11: If ready is false, + if (!ready) { + // Step a: Assert: controller.[[queueTotalSize]] is 0. + assert(UnsafeGetInt32FromReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE) === 0, + "should have no queued bytes"); + + // Step b: Assert: pullIntoDescriptor.[[bytesFilled]] > 0. + assert(pullIntoDescriptor.bytesFilled > 0, "should have filled some bytes"); + + // Step c: Assert: pullIntoDescriptor.[[bytesFilled]] < + // pullIntoDescriptor.[[elementSize]]. + assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize, + "if not ready, then should not have enough bytes for a single element"); + } + + // Step 12: Return ready. + return ready; +} + +// Streams spec 3.12.13. ReadableByteStreamControllerGetDesiredSize ( controller ) +function ReadableByteStreamControllerGetDesiredSize(controller) { + assert(IsReadableByteStreamController(controller), + "must operate on a ReadableByteStreamController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + + // Step 2: Let state be stream.[[state]]. + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + + // Step 3: If state is "errored", return null. + if (state & READABLESTREAM_STATE_ERRORED) + return null; + + // Step 4: If state is "closed", return 0. + if (state & READABLESTREAM_STATE_CLOSED) + return 0; + + // Step 5: Return controller.[[strategyHWM]] − controller.[[queueTotalSize]]. + let strategyHWM = UnsafeGetReservedSlot(controller, READABLESTREAMCONTROLLER_SLOT_STRATEGY_HWM); + let queueSize = UnsafeGetReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + return strategyHWM - queueSize; +} + +// Streams spec 3.12.14. ReadableByteStreamControllerHandleQueueDrain ( controller ) +function ReadableByteStreamControllerHandleQueueDrain(controller) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Assert: controller.[[controlledReadableStream]].[[state]] is "readable". + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + assert(UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE) & + READABLESTREAM_STATE_READABLE, + "controller's stream must be in the readable state"); + + // Step 2: If controller.[[queueTotalSize]] is 0 and + // controller.[[closeRequested]] is true, + let queueTotalSize = UnsafeGetInt32FromReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + let closeRequested = READABLESTREAMCONTROLLER_FLAGS(controller) & + READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED; + if (queueTotalSize === 0 && closeRequested) { + // Step a: Perform ! ReadableStreamClose(controller.[[controlledReadableStream]]). + ReadableStreamClose(stream); + } else { + // Step 3: Otherwise, + // Step a: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + ReadableByteStreamControllerCallPullIfNeeded(controller); + } +} + +// Streams spec 3.12.15. ReadableByteStreamControllerInvalidateBYOBRequest ( controller ) +function ReadableByteStreamControllerInvalidateBYOBRequest(controller) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: If controller.[[byobRequest]] is undefined, return. + let byobRequest = UnsafeGetReservedSlot(controller, + READABLEBYTESTREAMCONTROLLER_SLOT_BYOB_REQUEST); + if (byobRequest === undefined) + return; + + // Step 2: Set controller.[[byobRequest]].[[associatedReadableByteStreamController]] + // to undefined. + UnsafeSetReservedSlot(byobRequest, READABLESTREAMBYOBREQUEST_SLOT_ASSOCIATED_READABLE_BYTESTREAM_CONTROLLER, + undefined); + + // Step 3: Set controller.[[byobRequest]].[[view]] to undefined. + UnsafeSetReservedSlot(byobRequest, READABLESTREAMBYOBREQUEST_SLOT_VIEW, undefined); + + // Step 4: Set controller.[[byobRequest]] to undefined. + UnsafeSetReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_BYOB_REQUEST, undefined); +} + +// Streams spec 3.12.16. ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue ( controller ) +function ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Assert: controller.[[closeRequested]] is false. + assert(!(READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED), + "controller must not be closing"); + + // Step 2: Repeat the following steps while controller.[[pendingPullIntos]] + // is not empty, + let pendingPullIntos = + UnsafeGetObjectFromReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS); + while (pendingPullIntos.length !== 0) { + // Step a: If controller.[[queueTotalSize]] is 0, return. + let queueTotalSize = UnsafeGetInt32FromReservedSlot(controller, + QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + if (queueTotalSize === 0) + return; + + // Step b: Let pullIntoDescriptor be the first element of + // controller.[[pendingPullIntos]]. + let pullIntoDescriptor = pendingPullIntos[0]; + + // Step c: If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor) + // is true, + if (ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, + pullIntoDescriptor)) + { + // Step i: Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). + ReadableByteStreamControllerShiftPendingPullInto(controller); + + // Step ii: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[controlledReadableStream]], + // pullIntoDescriptor). + let stream = + UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDescriptor); + } + + assert(pendingPullIntos === + UnsafeGetObjectFromReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS), + "the pendingPullIntos list should not have been replaced"); + } +} + +// Streams spec, 3.12.17. ReadableByteStreamControllerPullInto ( controller, view ) +function ReadableByteStreamControllerPullInto(controller, view) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 2: Let elementSize be 1. + let elementSize = 1; + + // Step 3: Let ctor be %DataView%. + let ctor = GetBuiltinConstructor("DataView"); + + // Step 4: If view has a [[TypedArrayName]] internal slot (i.e., it is not a + // DataView), + if (IsTypedArray(view)) { + // Step a: Set elementSize to the element size specified in the typed array + // constructors table for view.[[TypedArrayName]]. + // Step b: Set ctor to the constructor specified in the typed array + // constructors table for view.[[TypedArrayName]]. + ctor = _ConstructorForTypedArray(view); + elementSize = 1 << TypedArrayElementShift(view); + } + + // Step 5: Let pullIntoDescriptor be Record {[[buffer]]: view.[[ViewedArrayBuffer]], + // [[byteOffset]]: view.[[ByteOffset]], + // [[byteLength]]: view.[[ByteLength]], + // [[bytesFilled]]: 0, + // [[elementSize]]: elementSize, + // [[ctor]]: ctor, + // [[readerType]]: "byob"}. + let buffer = TypedArrayBuffer(view); + let byteOffset = UnsafeGetInt32FromReservedSlot(view, JS_TYPEDARRAYLAYOUT_BYTEOFFSET_SLOT); + let byteLength = ArrayBufferByteLength(buffer); + let pullIntoDescriptor = { + __proto__: null, + buffer, + byteOffset, + byteLength, + bytesFilled: 0, + elementSize, + ctor, + readerType: "byob" + }; + + // Step 6: If controller.[[pendingPullIntos]] is not empty, + let pendingPullIntos = + UnsafeGetObjectFromReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS); + if (pendingPullIntos.length !== 0) { + // Step a: Set pullIntoDescriptor.[[buffer]] to + // ! SameRealmTransfer(pullIntoDescriptor.[[buffer]]). + pullIntoDescriptor.buffer = SameRealmTransfer(pullIntoDescriptor.buffer); + + // Step b: Append pullIntoDescriptor as the last element of + // controller.[[pendingPullIntos]]. + ArrayStaticPush(pendingPullIntos, pullIntoDescriptor); + + // Step c: Return ! ReadableStreamAddReadIntoRequest(stream). + return ReadableStreamAddReadIntoRequest(stream); + } + + // Step 7: If stream.[[state]] is "closed", + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (state & READABLESTREAM_STATE_CLOSED) { + // Step a: Let emptyView be ! Construct(ctor, pullIntoDescriptor.[[buffer]], + // pullIntoDescriptor.[[byteOffset]], 0). + let emptyView = new ctor(pullIntoDescriptor.buffer, pullIntoDescriptor.byteOffset, 0); + + // Step b: Return a promise resolved with + // ! CreateIterResultObject(emptyView, true). + return CreatePromiseResolvedWith({value: emptyView, done: true}); + } + + // Step 8: If controller.[[queueTotalSize]] > 0, + let queueTotalSize = UnsafeGetInt32FromReservedSlot(controller, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + if (queueTotalSize > 0) { + // Step a: If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, + // pullIntoDescriptor) + // is true, + if (ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, + pullIntoDescriptor)) { + // Step i: Let filledView be + // ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor). + let filledView = + ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor); + + // Step ii: Perform ! ReadableByteStreamControllerHandleQueueDrain(controller). + ReadableByteStreamControllerHandleQueueDrain(controller); + + // Step iii: Return a promise resolved with + // ! CreateIterResultObject(filledView, false). + return CreatePromiseResolvedWith({value: filledView, done: false}); + } + + // Step b: If controller.[[closeRequested]] is true, + if (READABLESTREAMCONTROLLER_FLAGS(controller) & + READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED) + { + // Step i: Let e be a TypeError exception. + let e = GetTypeError(JSMSG_READABLESTREAMCONTROLLER_CLOSED, "read"); + + // Step ii: Perform ! ReadableByteStreamControllerError(controller, e). + ReadableByteStreamControllerError(controller, e); + + // Step iii: Return a promise rejected with e. + return CreatePromiseRejectedWith(e); + } + } + + // Step 9: Set pullIntoDescriptor.[[buffer]] to + // ! SameRealmTransfer(pullIntoDescriptor.[[buffer]]). + pullIntoDescriptor.buffer = SameRealmTransfer(pullIntoDescriptor.buffer); + + // Step 10: Append pullIntoDescriptor as the last element of + // controller.[[pendingPullIntos]]. + ArrayStaticPush(pendingPullIntos, pullIntoDescriptor); + + // Step 11: Let promise be ! ReadableStreamAddReadIntoRequest(stream). + let promise = ReadableStreamAddReadIntoRequest(stream); + + // Step 12: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller). + ReadableByteStreamControllerCallPullIfNeeded(controller); + + // Step 13: Return promise. + return promise; +} + +// Streams spec 3.12.18. ReadableByteStreamControllerRespond( controller, bytesWritten ) +function ReadableByteStreamControllerRespond(controller, bytesWritten) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Let bytesWritten be ? ToNumber(bytesWritten). + bytesWritten = ToNumber(bytesWritten); + + // Step 2: If ! IsFiniteNonNegativeNumber(bytesWritten) is false, + if (!IsFiniteNonNegativeNumber(bytesWritten)) + ThrowRangeError(JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "bytesWritten"); + + // Step 3: Assert: controller.[[pendingPullIntos]] is not empty. + assert(UnsafeGetObjectFromReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS).length !== 0, + "controller must have at least one pending operation"); + + // Step 4: Perform ? ReadableByteStreamControllerRespondInternal(controller, bytesWritten). + ReadableByteStreamControllerRespondInternal(controller, bytesWritten); +} + +// Streams spec 3.12.19. ReadableByteStreamControllerRespondInClosedState( controller, firstDescriptor ) +function ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Set firstDescriptor.[[buffer]] to + // ! SameRealmTransfer(firstDescriptor.[[buffer]]). + firstDescriptor.buffer = SameRealmTransfer(firstDescriptor.buffer); + + // Step 2: Assert: firstDescriptor.[[bytesFilled]] is 0. + assert(firstDescriptor.bytesFilled === 0, "first descriptor should be empty"); + + // Step 3: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 4: Repeat the following steps while + // ! ReadableStreamGetNumReadIntoRequests(stream) > 0, + while (ReadableStreamGetNumReadIntoRequests(stream) > 0) { + // Step a: Let pullIntoDescriptor be + // ! ReadableByteStreamControllerShiftPendingPullInto(controller). + let pullIntoDescriptor = ReadableByteStreamControllerShiftPendingPullInto(controller); + + // Step b: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDescriptor). + ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDescriptor); + } +} + +// Streams spec 3.12.20. ReadableByteStreamControllerRespondInReadableState( controller, bytesWritten, pullIntoDescriptor ) +function ReadableByteStreamControllerRespondInReadableState(controller, + bytesWritten, + pullIntoDescriptor) +{ + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: If pullIntoDescriptor.[[bytesFilled]] + bytesWritten > pullIntoDescriptor.[[byteLength]], + // throw a RangeError exception. + if (pullIntoDescriptor.bytesFilled + bytesWritten > pullIntoDescriptor.byteLength) + ThrowRangeError(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN); + + // Step 2: Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, + // bytesWritten, + // pullIntoDescriptor). + ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesWritten, + pullIntoDescriptor); + + // Step 3: If pullIntoDescriptor.[[bytesFilled]] < + // pullIntoDescriptor.[[elementSize]], return. + if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) + return; + + // Step 4: Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller). + ReadableByteStreamControllerShiftPendingPullInto(controller); + + // Step 5: Let remainderSize be pullIntoDescriptor.[[bytesFilled]] mod + // pullIntoDescriptor.[[elementSize]]. + let remainderSize = pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize; + + // Step 6: If remainderSize > 0, + if (remainderSize > 0) { + // Step a: Let end be pullIntoDescriptor.[[byteOffset]] + + // pullIntoDescriptor.[[bytesFilled]]. + let end = pullIntoDescriptor.byteOffset + pullIntoDescriptor.bytesFilled; + + // Step b: Let remainder be ? CloneArrayBuffer(pullIntoDescriptor.[[buffer]], + // end − remainderSize, + // remainderSize, %ArrayBuffer%). + let remainder = callFunction(ArrayBufferSlice, pullIntoDescriptor.buffer, + end - remainderSize, end); + + // Step c: Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(controller, + // remainder, 0, + // remainder.[[ByteLength]]). + // Note: `remainderSize` is equivalent to remainder.[[ByteLength]]. + ReadableByteStreamControllerEnqueueChunkToQueue(controller, remainder, 0, remainderSize); + } + + // Step 7: Set pullIntoDescriptor.[[buffer]] to + // ! SameRealmTransfer(pullIntoDescriptor.[[buffer]]). + pullIntoDescriptor.buffer = SameRealmTransfer(pullIntoDescriptor.buffer); + + // Step 8: Set pullIntoDescriptor.[[bytesFilled]] to pullIntoDescriptor.[[bytesFilled]] − + // remainderSize. + assert(remainderSize === remainderSize, "remainderSize must not be NaN"); + pullIntoDescriptor.bytesFilled -= remainderSize; + + // Step 9: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[controlledReadableStream]], + // pullIntoDescriptor). + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + ReadableByteStreamControllerCommitPullIntoDescriptor(stream, pullIntoDescriptor); + + // Step 10: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller). + ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller); +} + +// Streams spec, 3.12.21. ReadableByteStreamControllerRespondInternal ( controller, bytesWritten ) +function ReadableByteStreamControllerRespondInternal(controller, bytesWritten) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Let firstDescriptor be the first element of controller.[[pendingPullIntos]]. + let pendingPullIntos = + UnsafeGetObjectFromReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS); + let firstDescriptor = pendingPullIntos[0]; + + // Step 2: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 3: If stream.[[state]] is "closed", + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (state & READABLESTREAM_STATE_CLOSED) { + // Step a: If bytesWritten is not 0, throw a TypeError exception. + if (bytesWritten !== 0) + ThrowTypeError(JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED); + + // Step b: Perform + // ! ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor). + ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor); + } else { + // Step 4: Otherwise, + // Step a: Assert: stream.[[state]] is "readable". + assert(state & READABLESTREAM_STATE_READABLE, "stream should be readable"); + + // Step b: Perform ? ReadableByteStreamControllerRespondInReadableState(controller, + // bytesWritten, + // firstDescriptor). + ReadableByteStreamControllerRespondInReadableState(controller, bytesWritten, + firstDescriptor); + } +} + +// Streams spec, 3.12.22. ReadableByteStreamControllerRespondWithNewView ( controller, view ) +function ReadableByteStreamControllerRespondWithNewView(controller, view) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Assert: controller.[[pendingPullIntos]] is not empty. + let pendingPullIntos = + UnsafeGetObjectFromReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS); + assert(pendingPullIntos.length !== 0, "controller should have a pending request"); + + // Step 2: Let firstDescriptor be the first element of controller.[[pendingPullIntos]]. + let firstDescriptor = pendingPullIntos[0]; + + // Step 3: If firstDescriptor.[[byteOffset]] + firstDescriptor.[[bytesFilled]] + // is not view.[[ByteOffset]], throw a RangeError exception. + let byteOffset = UnsafeGetInt32FromReservedSlot(view, JS_TYPEDARRAYLAYOUT_BYTEOFFSET_SLOT); + if (firstDescriptor.byteOffset + firstDescriptor.bytesFilled !== byteOffset) + ThrowRangeError(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET); + + // Step 4: If firstDescriptor.[[byteLength]] is not view.[[ByteLength]], + // throw a RangeError exception. + let buffer = TypedArrayBuffer(view); + let byteLength = ArrayBufferByteLength(buffer); + if (firstDescriptor.byteLength !== byteLength) + ThrowRangeError(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE); + + // Step 5: Set firstDescriptor.[[buffer]] to view.[[ViewedArrayBuffer]]. + firstDescriptor.buffer = buffer; + + // Step 6: Perform ? ReadableByteStreamControllerRespondInternal(controller, + // view.[[ByteLength]]). + ReadableByteStreamControllerRespondInternal(controller, byteLength); +} + +// Streams spec, 3.12.23. ReadableByteStreamControllerShiftPendingPullInto ( controller ) +function ReadableByteStreamControllerShiftPendingPullInto(controller) { + assert(IsReadableByteStreamController(controller), + "must operate on 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). + let pendingPullIntos = + UnsafeGetObjectFromReservedSlot(controller, READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS); + let descriptor = ArrayStaticShift(pendingPullIntos); + + // Step 3: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller). + ReadableByteStreamControllerInvalidateBYOBRequest(controller); + + // Step 4: Return descriptor. + return descriptor; +} + +// Streams spec, 3.12.24. ReadableByteStreamControllerShouldCallPull ( controller ) +function ReadableByteStreamControllerShouldCallPull(controller) { + assert(IsReadableByteStreamController(controller), + "must operate on ReadableByteStreamController"); + + // Step 1: Let stream be controller.[[controlledReadableStream]]. + let stream = UnsafeGetObjectFromReservedSlot(controller, + READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM); + assert(IsReadableStream(stream), "controller should have a ReadableStream"); + + // Step 2: If stream.[[state]] is not "readable", return false. + let state = UnsafeGetInt32FromReservedSlot(stream, READABLESTREAM_SLOT_STATE); + if (!(state & READABLESTREAM_STATE_READABLE)) + return false; + + // Step 3: If controller.[[closeRequested]] is true, return false. + if (READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED) + return false; + + // Step 4: If controller.[[started]] is false, return false. + if (!(READABLESTREAMCONTROLLER_FLAGS(controller) & READABLESTREAMCONTROLLER_FLAG_STARTED)) + return false; + + // Step 5: If ! ReadableStreamHasDefaultReader(stream) is true and + // ! ReadableStreamGetNumReadRequests(stream) > 0, return true. + if (ReadableStreamHasDefaultReader(stream) && ReadableStreamGetNumReadRequests(stream) > 0) + return true; + + // Step 6: If ! ReadableStreamHasBYOBReader(stream) is true and + // ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true. + if (ReadableStreamHasBYOBReader(stream) && ReadableStreamGetNumReadIntoRequests(stream) > 0) + return true; + + // Step 7: If ! ReadableByteStreamControllerGetDesiredSize(controller) > 0, + // return true. + if (ReadableByteStreamControllerGetDesiredSize(controller) > 0) + return true; + + // Step 8: Return false. + return false; +} + +// Streams spec 5, Transform Streams +// TODO: Implement after spec stabilizes + +// Streams spec 6.1.2, new ByteLengthQueuingStrategy ( { highWaterMark } ) +// Implemented in C++. + +// Streams spec 6.1.3.1. size ( chunk ) +function ByteLengthQueuingStrategy_size(chunk) { + // Step 1: Return ? GetV(chunk, "byteLength"). + return chunk.byteLength; +} + +// Streams spec 6.2.2. new CountQueuingStrategy ( { highWaterMark } ) +// Implemented in C++. + +// Streams spec 6.2.3.1. size () +function CountQueuingStrategy_size() { + // Step 1: Return 1. + return 1; +} + +// Streams spec, 6.3.1. DequeueValue ( container ) nothrow +function DequeueValue(container) { + // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal + // slots. + assert(IsReadableStreamDefaultController(container) || + IsReadableByteStreamController(container), + "DequeueValue can only operate on queue containers."); + + // Step 2: Assert: queue is not empty. + var queue = UnsafeGetObjectFromReservedSlot(container, QUEUE_CONTAINER_SLOT_QUEUE); + assert(queue.length !== 0, "queue must not be empty"); + + // 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). + var pair = ArrayStaticShift(queue); + + // 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.) + var totalSize = UnsafeGetReservedSlot(container, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + totalSize -= pair.size; + if (totalSize < 0) + totalSize = 0; + UnsafeSetReservedSlot(container, QUEUE_CONTAINER_SLOT_TOTAL_SIZE, totalSize); + + // Step 7: Return pair.[[value]]. + return pair.value; +} + +// Streams spec, 6.3.2. EnqueueValueWithSize ( container, value, size ) throws +function EnqueueValueWithSize(container, value, size) { + // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal + // slots. + assert(IsReadableStreamDefaultController(container) || + IsReadableByteStreamController(container), + "EnqueueValueWithSize can only operate on queue containers."); + + // Step 2: Let size be ? ToNumber(size). + size = ToNumber(size); + + // Step 3: If ! IsFiniteNonNegativeNumber(size) is false, throw a RangeError + // exception. + if (!IsFiniteNonNegativeNumber(size)) + ThrowRangeError(JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "size"); + + // Step 4: Append Record {[[value]]: value, [[size]]: size} as the last element + // of container.[[queue]]. + var queue = UnsafeGetObjectFromReservedSlot(container, QUEUE_CONTAINER_SLOT_QUEUE); + ArrayStaticPush(queue, {value, size}); + + // Step 5: Set container.[[queueTotalSize]] to + // container.[[queueTotalSize]] + size. + var totalSize = UnsafeGetReservedSlot(container, QUEUE_CONTAINER_SLOT_TOTAL_SIZE); + totalSize += size; + UnsafeSetReservedSlot(container, QUEUE_CONTAINER_SLOT_TOTAL_SIZE, totalSize); +} + +// Streams spec, 6.3.3. PeekQueueValue ( container ) nothrow +function PeekQueueValue(container) { + // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal + // slots. + assert(IsReadableStreamDefaultController(container) || + IsReadableByteStreamController(container), + "DequeueValue can only operate on queue containers."); + + // Step 2: Assert: queue is not empty. + var queue = UnsafeGetObjectFromReservedSlot(container, QUEUE_CONTAINER_SLOT_QUEUE); + assert(queue.length !== 0, "queue must not be empty"); + + // Step 3: Let pair be the first element of container.[[queue]]. + // Step 4: Return pair.[[value]]. + return queue[0].value; +} + +// Streams spec, 6.3.4. ResetQueue ( container ) nothrow +function ResetQueue(container) { + // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal + // slots. + assert(IsReadableStreamDefaultController(container) || + IsReadableByteStreamController(container), + "ResetQueue can only operate on queue containers."); + + // Step 2: Set container.[[queue]] to a new empty List. + UnsafeSetReservedSlot(container, QUEUE_CONTAINER_SLOT_QUEUE, new List()); + + // Step 3: Set container.[[queueTotalSize]] to 0. + UnsafeSetReservedSlot(container, QUEUE_CONTAINER_SLOT_TOTAL_SIZE, 0); +} + +// Streams spec, 6.4.1. InvokeOrNoop ( O, P, args ) +// TODO: Inline this, either manually or with an inlineable intrinsic. +function InvokeOrNoop(O, P, args) { + // Step 1: Assert: P is a valid property key. + assert(IsPropertyKey(P), "P is a valid property key"); + + // Step 2: If args was not passed, let args be a new empty List (implicit). + + // Step 3: Let method be ? GetV(O, P). + var method = O[P]; + + // Step 4: If method is undefined, return. + if (method === undefined) + return undefined; // Return value required by eslint. + + // Step 5: Return ? Call(method, O, args). + return FUN_APPLY(method, O, args); +} + +// Streams spec, 6.4.2. IsFiniteNonNegativeNumber ( v ) +function IsFiniteNonNegativeNumber(v) { + // Step 1: If v is NaN, return false. + if (Number_isNaN(v)) + return false; + + // Step 2: If v is +infinity, return false. + if (!Number_isFinite(v)) + return false; + + // Step 3: If v < 0, return false. + // Step 4: Return true. + return v >= 0; +} + +// Streams spec, 6.4.3. PromiseInvokeOrNoop ( O, P, args ) +function PromiseInvokeOrNoop(O, P, args) { + // Step 1: Assert: O is not undefined. + assert(O !== undefined, "Receiver for PromiseInvokeOrNoop must not be undefined"); + + // Step 2: Assert: ! IsPropertyKey(P) is true. + assert(IsPropertyKey(P), "P is a valid property key"); + + // Step 3: Assert: args is a List. + // Omitted. This is annoying to test and doesn't gain us much. + + // Step 4: Let returnValue be InvokeOrNoop(O, P, args). + try { + let returnValue = InvokeOrNoop(O, P, args); + // Step 6 (reordered): Otherwise, return a promise resolved with + // returnValue.[[Value]]. + return CreatePromiseResolvedWith(returnValue); + } catch (e) { + // Step 5: If returnValue is an abrupt completion, return a promise + // rejected with returnValue.[[Value]]. + return CreatePromiseRejectedWith(e); + } +} + +function SameRealmTransfer(O) { + return TransferBuffer(O, O); +} + +// Streams spec, 6.4.4. ValidateAndNormalizeHighWaterMark ( highWaterMark ) +function ValidateAndNormalizeHighWaterMark(highWaterMark) { + // Step 1: Set highWaterMark to ? ToNumber(highWaterMark). + highWaterMark = ToNumber(highWaterMark); + + // Step 2: If highWaterMark is NaN, throw a TypeError exception. + // Step 3: If highWaterMark < 0, throw a RangeError exception. + if (Number_isNaN(highWaterMark) || highWaterMark < 0) + ThrowRangeError(JSMSG_STREAM_INVALID_HIGHWATERMARK); + + // Step 4: Return highWaterMark. + return highWaterMark; +} + +// Streams spec, 6.4.5. ValidateAndNormalizeQueuingStrategy ( size, highWaterMark ) +function ValidateAndNormalizeQueuingStrategy(size, highWaterMark) { + // Step 1: If size is not undefined and ! IsCallable(size) is false, throw a + // TypeError exception. + if (size !== undefined && !IsCallable(size)) + ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(0, size)); + + // Step 2: Let highWaterMark be ? ValidateAndNormalizeHighWaterMark(highWaterMark). + highWaterMark = ValidateAndNormalizeHighWaterMark(highWaterMark); + + // Step 3: Return Record {[[size]]: size, [[highWaterMark]]: highWaterMark}. + return {__proto__: null, size, highWaterMark}; +} diff --git a/js/src/builtin/StreamDefines.h b/js/src/builtin/StreamDefines.h new file mode 100644 index 0000000000..e82073494b --- /dev/null +++ b/js/src/builtin/StreamDefines.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Specialized .h file to be used by both JS and C++ code. + +#ifndef builtin_StreamDefines_h +#define builtin_StreamDefines_h + +#define READABLESTREAM_STATE_READABLE 1 +#define READABLESTREAM_STATE_CLOSED 2 +#define READABLESTREAM_STATE_ERRORED 4 +#define READABLESTREAM_IS_DISTURBED 8 + +#define READABLESTREAM_SLOT_CONTROLLER 0 +#define READABLESTREAM_SLOT_READER 1 +#define READABLESTREAM_SLOT_STATE 2 +#define READABLESTREAM_SLOT_STORED_ERROR 3 + +#define READABLESTREAMREADER_SLOT_CLOSED_PROMISE 0 +#define READABLESTREAMREADER_SLOT_OWNER_READABLE_STREAM 1 +#define READABLESTREAMREADER_SLOT_REQUESTS 2 + +#define READABLESTREAMCONTROLLER_FLAG_STARTED 0x1 +#define READABLESTREAMCONTROLLER_FLAG_PULLING 0x2 +#define READABLESTREAMCONTROLLER_FLAG_PULL_AGAIN 0x4 +#define READABLESTREAMCONTROLLER_FLAG_CLOSE_REQUESTED 0x8 + +#define READABLESTREAMCONTROLLER_FLAGS(controller)\ +UnsafeGetInt32FromReservedSlot(controller, READABLESTREAMCONTROLLER_SLOT_FLAGS) + +#define SET_READABLESTREAMCONTROLLER_FLAGS(controller, flags)\ +UnsafeSetReservedSlot(controller, READABLESTREAMCONTROLLER_SLOT_FLAGS,\ + UnsafeGetInt32FromReservedSlot(controller,\ + READABLESTREAMCONTROLLER_SLOT_FLAGS) |\ + (flags)) + +#define UNSET_READABLESTREAMCONTROLLER_FLAGS(controller, flags)\ +UnsafeSetReservedSlot(controller, READABLESTREAMCONTROLLER_SLOT_FLAGS,\ + UnsafeGetInt32FromReservedSlot(controller,\ + READABLESTREAMCONTROLLER_SLOT_FLAGS) &\ + ~(flags)) + +// ReadableStreamDefaultController and ReadableByteStreamController are both +// queue containers and must have these slots at identical offsets. +#define QUEUE_CONTAINER_SLOT_QUEUE 0 +#define QUEUE_CONTAINER_SLOT_TOTAL_SIZE 1 + +// These three slots are identical between the two types of ReadableStream +// controllers. +// The first two slots are reserved for use as queue container. +#define READABLESTREAMCONTROLLER_SLOT_CONTROLLED_STREAM 2 +#define READABLESTREAMCONTROLLER_SLOT_UNDERLYING_SOURCE 3 +#define READABLESTREAMCONTROLLER_SLOT_STRATEGY_HWM 4 +#define READABLESTREAMCONTROLLER_SLOT_FLAGS 5 + +#define READABLESTREAMDEFAULTCONTROLLER_SLOT_STRATEGY_SIZE 6 + +#define READABLEBYTESTREAMCONTROLLER_SLOT_BYOB_REQUEST 6 +#define READABLEBYTESTREAMCONTROLLER_SLOT_PENDING_PULL_INTOS 7 +#define READABLEBYTESTREAMCONTROLLER_SLOT_AUTO_ALLOCATE_CHUNK_SIZE 8 + +#define READABLESTREAMBYOBREQUEST_SLOT_ASSOCIATED_READABLE_BYTESTREAM_CONTROLLER 0 +#define READABLESTREAMBYOBREQUEST_SLOT_VIEW 1 + +#endif // builtin_StreamDefines_h diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index dae1d49cdd..e23c368c6f 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -23,6 +23,7 @@ */ #include "SelfHostingDefines.h" +#include "StreamDefines.h" #include "TypedObjectConstants.h" // Assertions and debug printing, defined here instead of in the header above @@ -96,6 +97,22 @@ function ToNumber(v) { return +v; } +// ES2016 7.2.6 IsInteger ( argument ) +function IsInteger(argument) { + // Step 1: If Type(argument) is not Number, return false. + if (typeof argument !== "number") + return false; + + // Step 2: If argument is NaN, +∞, or -∞, return false. + if (argument !== argument || argument - argument !== 0) + return false; + + // Step 3: If floor(abs(argument)) ≠ abs(argument), return false. + // Step 4: Return true. + argument = std_Math_abs(argument); + return std_Math_floor(argument) === argument; +} + // ES6 7.2.1 (previously, ES5 9.10 under the name "CheckObjectCoercible"). function RequireObjectCoercible(v) { diff --git a/js/src/js.msg b/js/src/js.msg index 587481864d..1fd9d79da3 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -597,3 +597,31 @@ MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_TYPEERR, "'for await' loop shoul MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator") MSG_DEF(JSMSG_NOT_AN_ASYNC_ITERATOR, 0, JSEXN_TYPEERR, "Not an async from sync iterator") MSG_DEF(JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.asyncIterator]() returned a non-object value") + + +// ReadableStream +MSG_DEF(JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG,0, JSEXN_RANGEERR,"'underlyingSource.type' must be \"bytes\" or undefined.") +MSG_DEF(JSMSG_READABLESTREAM_INVALID_READER_MODE, 0, JSEXN_RANGEERR,"'mode' must be \"byob\" or undefined.") +MSG_DEF(JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, 1, JSEXN_RANGEERR, "'{0}' must be a finite, non-negative number.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN, 0, JSEXN_RANGEERR, "'bytesWritten' exceeds remaining length.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE, 0, JSEXN_RANGEERR, "view size does not match requested data.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET, 0, JSEXN_RANGEERR, "view offset does not match requested position.") +MSG_DEF(JSMSG_READABLESTREAM_NOT_LOCKED, 1, JSEXN_TYPEERR, "The ReadableStream method '{0}' may only be called on a locked stream.") +MSG_DEF(JSMSG_READABLESTREAM_LOCKED, 0, JSEXN_TYPEERR, "A Reader may only be created for an unlocked ReadableStream.") +MSG_DEF(JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER, 0, JSEXN_TYPEERR, "ReadableStream.getReader('byob') requires a ReadableByteStreamController.") +MSG_DEF(JSMSG_READABLESTREAM_CONTROLLER_SET, 0, JSEXN_TYPEERR, "The ReadableStream already has a controller defined.") +MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_OWNED, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may only be called on a reader owned by a stream.") +MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_EMPTY, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may not be called on a reader with read requests.") +MSG_DEF(JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW, 0, JSEXN_TYPEERR, "ReadableStreamBYOBReader.read() was passed an empty TypedArrayBuffer view.") +MSG_DEF(JSMSG_READABLESTREAMREADER_RELEASED, 0, JSEXN_TYPEERR, "The ReadableStream reader was released.") +MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_CLOSED, 1, JSEXN_TYPEERR, "The ReadableStream controller method '{0}' called on a stream already closing.") +MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, 1, JSEXN_TYPEERR, "The ReadableStream controller method '{0}' may only be called on a stream in the 'readable' state.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE,0, JSEXN_RANGEERR, "ReadableByteStreamController requires a positive integer or undefined for 'autoAllocateChunkSize'.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK, 0, JSEXN_TYPEERR, "ReadableByteStreamController passed a bad chunk.") +MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL, 0, JSEXN_TYPEERR, "The ReadableByteStreamController cannot be closed while the buffer is being filled.") +MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, 1, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method '{0}' called on a request with no controller.") +MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED, 0, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method 'respond' called with non-zero number of bytes with a closed controller.") +MSG_DEF(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, 1, JSEXN_TYPEERR, "ReadableStream method {0} not yet implemented") + +// Other Stream-related +MSG_DEF(JSMSG_STREAM_INVALID_HIGHWATERMARK, 0, JSEXN_RANGEERR, "'highWaterMark' must be a non-negative, non-NaN number.") diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index 615368a847..91226269ba 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -106,12 +106,23 @@ IF_SIMD(real,imaginary)(SIMD, 44, InitSimdClass, OCLASP(Si real(TypedArray, 46, InitViaClassSpec, &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \ IF_SAB(real,imaginary)(Atomics, 47, InitAtomicsClass, OCLASP(Atomics)) \ real(SavedFrame, 48, InitViaClassSpec, &js::SavedFrame::class_) \ - real(WebAssembly, 49, InitWebAssemblyClass, CLASP(WebAssembly)) \ - imaginary(WasmModule, 50, dummy, dummy) \ - imaginary(WasmInstance, 51, dummy, dummy) \ - imaginary(WasmMemory, 52, dummy, dummy) \ - imaginary(WasmTable, 53, dummy, dummy) \ - real(Promise, 54, InitViaClassSpec, OCLASP(Promise)) \ + real(Promise, 49, InitViaClassSpec, OCLASP(Promise)) \ + real(ReadableStream, 50, InitViaClassSpec, &js::ReadableStream::class_) \ + real(ReadableStreamDefaultReader, 51, InitViaClassSpec, &js::ReadableStreamDefaultReader::class_) \ + real(ReadableStreamBYOBReader, 52, InitViaClassSpec, &js::ReadableStreamBYOBReader::class_) \ + real(ReadableStreamDefaultController, 53, InitViaClassSpec, &js::ReadableStreamDefaultController::class_) \ + real(ReadableByteStreamController, 54, InitViaClassSpec, &js::ReadableByteStreamController::class_) \ + real(ReadableStreamBYOBRequest, 55, InitViaClassSpec, &js::ReadableStreamBYOBRequest::class_) \ + imaginary(WritableStream, 56, dummy, dummy) \ + imaginary(WritableStreamDefaultWriter, 57, dummy, dummy) \ + imaginary(WritableStreamDefaultController, 58, dummy, dummy) \ + real(ByteLengthQueuingStrategy, 59, InitViaClassSpec, &js::ByteLengthQueuingStrategy::class_) \ + real(CountQueuingStrategy, 60, InitViaClassSpec, &js::CountQueuingStrategy::class_) \ + real(WebAssembly, 61, InitWebAssemblyClass, CLASP(WebAssembly)) \ + imaginary(WasmModule, 62, dummy, dummy) \ + imaginary(WasmInstance, 63, dummy, dummy) \ + imaginary(WasmMemory, 64, dummy, dummy) \ + imaginary(WasmTable, 65, dummy, dummy) \ #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro) diff --git a/js/src/moz.build b/js/src/moz.build index 46a44603e2..eda0b0ee9d 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -166,6 +166,7 @@ UNIFIED_SOURCES += [ 'builtin/Reflect.cpp', 'builtin/ReflectParse.cpp', 'builtin/SIMD.cpp', + 'builtin/Stream.cpp', 'builtin/SymbolObject.cpp', 'builtin/TestingFunctions.cpp', 'builtin/TypedObject.cpp', @@ -744,6 +745,7 @@ selfhosted = GENERATED_FILES[('selfhosted.out.h', 'selfhosted.js')] selfhosted.script = 'builtin/embedjs.py:generate_selfhosted' selfhosted.inputs = [ 'js.msg', + 'builtin/StreamDefines.h', 'builtin/TypedObjectConstants.h', 'builtin/SelfHostingDefines.h', 'builtin/Utilities.js', @@ -766,6 +768,7 @@ selfhosted.inputs = [ 'builtin/RegExp.js', 'builtin/RegExpGlobalReplaceOpt.h.js', 'builtin/RegExpLocalReplaceOpt.h.js', + 'builtin/Stream.js', 'builtin/String.js', 'builtin/Set.js', 'builtin/Sorting.js', diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 789a573dcc..7fcdc8f4b7 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -164,6 +164,7 @@ macro(has, has, "has") \ macro(hasOwn, hasOwn, "hasOwn") \ macro(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \ + macro(highWaterMark, highWaterMark, "highWaterMark") \ macro(hour, hour, "hour") \ macro(if, if_, "if") \ macro(ignoreCase, ignoreCase, "ignoreCase") \ diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index de7e247495..74fae6b0f2 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -23,6 +23,7 @@ #include "builtin/Promise.h" #include "builtin/RegExp.h" #include "builtin/SelfHostingDefines.h" +#include "builtin/Stream.h" #include "builtin/SymbolObject.h" #include "builtin/TypedObject.h" #include "builtin/WeakMapObject.h" diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index b921f31b3a..731ae3e167 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -30,6 +30,7 @@ #include "builtin/Reflect.h" #include "builtin/SelfHostingDefines.h" #include "builtin/SIMD.h" +#include "builtin/Stream.h" #include "builtin/TypedObject.h" #include "builtin/WeakSetObject.h" #include "gc/Marking.h" @@ -64,7 +65,6 @@ using JS::AutoCheckCannotGC; using mozilla::IsInRange; using mozilla::Maybe; using mozilla::PodMove; -using mozilla::Maybe; static void selfHosting_WarningReporter(JSContext* cx, JSErrorReport* report) @@ -2256,6 +2256,51 @@ intrinsic_AddPromiseReactions(JSContext* cx, unsigned argc, Value* vp) return true; } +/** + * Implements 2.7.6 Transfer ( input, targetRealm ) from + * https://html.spec.whatwg.org/multipage/infrastructure.html + * + * The targetRealm can be an arbitrary object from the desired global. + * + * Note: For now, this only handles Typed Arrays and ArrayBuffer instances. + */ +static bool +intrinsic_TransferBuffer(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + + RootedObject buffer(cx, &args[0].toObject()); + MOZ_ASSERT(buffer->is<ArrayBufferObject>()); + RootedObject realm(cx, &args[1].toObject().global()); + + uint32_t size = buffer->as<ArrayBufferObject>().byteLength(); + + void* contents = JS_StealArrayBufferContents(cx, buffer); + if (!contents) + return false; + + RootedObject transferredBuffer(cx); + { + Maybe<AutoCompartment> ac; + if (IsWrapper(realm)) { + RootedObject unwrappedRealm(cx, CheckedUnwrap(realm)); + if (!unwrappedRealm) + return false; + ac.emplace(cx, unwrappedRealm); + } + + transferredBuffer = JS_NewArrayBufferWithContents(cx, size, contents); + if (!transferredBuffer) + return false; + } + if (!cx->compartment()->wrap(cx, &transferredBuffer)) + return false; + + args.rval().setObject(*transferredBuffer); + return true; +} + // The self-hosting global isn't initialized with the normal set of builtins. // Instead, individual C++-implemented functions that're required by // self-hosted code are defined as global functions. Accessing these @@ -2547,6 +2592,14 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("Promise_static_reject", Promise_reject, 1, 0), JS_FN("Promise_then", Promise_then, 2, 0), + JS_FN("IsReadableStream", intrinsic_IsInstanceOfBuiltin<ReadableStream>, 1, 0), + JS_FN("IsReadableStreamDefaultController", + intrinsic_IsInstanceOfBuiltin<ReadableStreamDefaultController>, 1, 0), + JS_FN("IsReadableStreamDefaultReader", intrinsic_IsInstanceOfBuiltin<ReadableStreamDefaultReader>, 1, 0), + JS_FN("IsReadableStreamBYOBReader", intrinsic_IsInstanceOfBuiltin<ReadableStreamBYOBReader>, 1, 0), + JS_FN("IsReadableStreamBYOBRequest", intrinsic_IsInstanceOfBuiltin<ReadableStreamBYOBRequest>, 1, 0), + JS_FN("IsReadableByteStreamController", intrinsic_IsInstanceOfBuiltin<ReadableByteStreamController>, 1, 0), + // See builtin/TypedObject.h for descriptors of the typedobj functions. JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0), JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0), @@ -2664,6 +2717,7 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("RejectPromise", intrinsic_RejectPromise, 2, 0), JS_FN("AddPromiseReactions", intrinsic_AddPromiseReactions, 3, 0), JS_FN("CallOriginalPromiseThen", intrinsic_CallOriginalPromiseThen, 3, 0), + JS_FN("TransferBuffer", intrinsic_TransferBuffer, 2, 0), JS_FN("IsPromiseObject", intrinsic_IsInstanceOfBuiltin<PromiseObject>, 1, 0), JS_FN("CallPromiseMethodIfWrapped", CallNonGenericSelfhostedMethod<Is<PromiseObject>>, 2, 0), |