summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/streams
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/streams')
-rw-r--r--js/src/builtin/streams/ClassSpecMacro.h40
-rw-r--r--js/src/builtin/streams/MiscellaneousOperations-inl.h115
-rw-r--r--js/src/builtin/streams/MiscellaneousOperations.cpp194
-rw-r--r--js/src/builtin/streams/MiscellaneousOperations.h86
-rw-r--r--js/src/builtin/streams/PipeToState-inl.h53
-rw-r--r--js/src/builtin/streams/PipeToState.cpp1271
-rw-r--r--js/src/builtin/streams/PipeToState.h291
-rw-r--r--js/src/builtin/streams/PullIntoDescriptor.cpp48
-rw-r--r--js/src/builtin/streams/PullIntoDescriptor.h77
-rw-r--r--js/src/builtin/streams/QueueWithSizes-inl.h77
-rw-r--r--js/src/builtin/streams/QueueWithSizes.cpp173
-rw-r--r--js/src/builtin/streams/QueueWithSizes.h65
-rw-r--r--js/src/builtin/streams/QueueingStrategies.cpp171
-rw-r--r--js/src/builtin/streams/QueueingStrategies.h39
-rw-r--r--js/src/builtin/streams/ReadableStream.cpp552
-rw-r--r--js/src/builtin/streams/ReadableStream.h139
-rw-r--r--js/src/builtin/streams/ReadableStreamBYOBReader.cpp51
-rw-r--r--js/src/builtin/streams/ReadableStreamController.h266
-rw-r--r--js/src/builtin/streams/ReadableStreamDefaultController.cpp514
-rw-r--r--js/src/builtin/streams/ReadableStreamDefaultControllerOperations.cpp682
-rw-r--r--js/src/builtin/streams/ReadableStreamDefaultControllerOperations.h73
-rw-r--r--js/src/builtin/streams/ReadableStreamDefaultReader.cpp265
-rw-r--r--js/src/builtin/streams/ReadableStreamInternals.cpp473
-rw-r--r--js/src/builtin/streams/ReadableStreamInternals.h57
-rw-r--r--js/src/builtin/streams/ReadableStreamOperations.cpp653
-rw-r--r--js/src/builtin/streams/ReadableStreamOperations.h47
-rw-r--r--js/src/builtin/streams/ReadableStreamReader-inl.h71
-rw-r--r--js/src/builtin/streams/ReadableStreamReader.cpp276
-rw-r--r--js/src/builtin/streams/ReadableStreamReader.h156
-rw-r--r--js/src/builtin/streams/StreamAPI.cpp612
-rw-r--r--js/src/builtin/streams/StreamController-inl.h24
-rw-r--r--js/src/builtin/streams/StreamController.h51
-rw-r--r--js/src/builtin/streams/TeeState.cpp52
-rw-r--r--js/src/builtin/streams/TeeState.h155
-rw-r--r--js/src/builtin/streams/WritableStream-inl.h47
-rw-r--r--js/src/builtin/streams/WritableStream.cpp282
-rw-r--r--js/src/builtin/streams/WritableStream.h425
-rw-r--r--js/src/builtin/streams/WritableStreamDefaultController.cpp83
-rw-r--r--js/src/builtin/streams/WritableStreamDefaultController.h186
-rw-r--r--js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp1014
-rw-r--r--js/src/builtin/streams/WritableStreamDefaultControllerOperations.h108
-rw-r--r--js/src/builtin/streams/WritableStreamDefaultWriter-inl.h40
-rw-r--r--js/src/builtin/streams/WritableStreamDefaultWriter.cpp529
-rw-r--r--js/src/builtin/streams/WritableStreamDefaultWriter.h113
-rw-r--r--js/src/builtin/streams/WritableStreamOperations.cpp923
-rw-r--r--js/src/builtin/streams/WritableStreamOperations.h78
-rw-r--r--js/src/builtin/streams/WritableStreamWriterOperations.cpp446
-rw-r--r--js/src/builtin/streams/WritableStreamWriterOperations.h56
48 files changed, 12199 insertions, 0 deletions
diff --git a/js/src/builtin/streams/ClassSpecMacro.h b/js/src/builtin/streams/ClassSpecMacro.h
new file mode 100644
index 0000000000..17d1fda930
--- /dev/null
+++ b/js/src/builtin/streams/ClassSpecMacro.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* A JS_STREAMS_CLASS_SPEC macro for defining streams classes. */
+
+#ifndef builtin_streams_ClassSpecMacro_h
+#define builtin_streams_ClassSpecMacro_h
+
+#include "gc/AllocKind.h" // js::gc::AllocKind
+#include "js/Class.h" // js::ClassSpec, JSClass, JSCLASS_HAS_{CACHED_PROTO,RESERVED_SLOTS}, JS_NULL_CLASS_OPS
+#include "js/ProtoKey.h" // JSProto_*
+#include "vm/GlobalObject.h" // js::GenericCreate{Constructor,Prototype}
+
+#define JS_STREAMS_CLASS_SPEC(cls, nCtorArgs, nSlots, specFlags, classFlags, \
+ classOps) \
+ const js::ClassSpec cls::classSpec_ = { \
+ js::GenericCreateConstructor<cls::constructor, nCtorArgs, \
+ js::gc::AllocKind::FUNCTION>, \
+ js::GenericCreatePrototype<cls>, \
+ nullptr, \
+ nullptr, \
+ cls##_methods, \
+ cls##_properties, \
+ nullptr, \
+ specFlags}; \
+ \
+ const JSClass cls::class_ = {#cls, \
+ JSCLASS_HAS_RESERVED_SLOTS(nSlots) | \
+ JSCLASS_HAS_CACHED_PROTO(JSProto_##cls) | \
+ classFlags, \
+ classOps, &cls::classSpec_}; \
+ \
+ const JSClass cls::protoClass_ = {#cls ".prototype", \
+ JSCLASS_HAS_CACHED_PROTO(JSProto_##cls), \
+ JS_NULL_CLASS_OPS, &cls::classSpec_};
+
+#endif // builtin_streams_ClassSpecMacro_h
diff --git a/js/src/builtin/streams/MiscellaneousOperations-inl.h b/js/src/builtin/streams/MiscellaneousOperations-inl.h
new file mode 100644
index 0000000000..1592a2bdc5
--- /dev/null
+++ b/js/src/builtin/streams/MiscellaneousOperations-inl.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Miscellaneous operations. */
+
+#ifndef builtin_streams_MiscellaneousOperations_inl_h
+#define builtin_streams_MiscellaneousOperations_inl_h
+
+#include "builtin/streams/MiscellaneousOperations.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "js/Promise.h" // JS::{Resolve,Reject}Promise
+#include "js/RootingAPI.h" // JS::Rooted, JS::{,Mutable}Handle
+#include "js/Value.h" // JS::UndefinedHandleValue, JS::Value
+#include "vm/Compartment.h" // JS::Compartment
+#include "vm/Interpreter.h" // js::Call
+#include "vm/JSContext.h" // JSContext
+#include "vm/JSObject.h" // JSObject
+#include "vm/PromiseObject.h" // js::PromiseObject
+
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/JSObject-inl.h" // js::IsCallable
+
+namespace js {
+
+/**
+ * Streams spec, 6.3.5. PromiseCall ( F, V, args )
+ * There must be 0-2 |args| arguments, all convertible to JS::Handle<JS::Value>.
+ */
+template <class... Args>
+inline MOZ_MUST_USE JSObject* PromiseCall(JSContext* cx,
+ JS::Handle<JS::Value> F,
+ JS::Handle<JS::Value> V,
+ Args&&... args) {
+ cx->check(F);
+ cx->check(V);
+ cx->check(args...);
+
+ // Step 1: Assert: ! IsCallable(F) is true.
+ MOZ_ASSERT(IsCallable(F));
+
+ // Step 2: Assert: V is not undefined.
+ MOZ_ASSERT(!V.isUndefined());
+
+ // Step 3: Assert: args is a List (implicit).
+ // Step 4: Let returnValue be Call(F, V, args).
+ JS::Rooted<JS::Value> rval(cx);
+ if (!Call(cx, F, V, args..., &rval)) {
+ // Step 5: If returnValue is an abrupt completion, return a promise rejected
+ // with returnValue.[[Value]].
+ return PromiseRejectedWithPendingError(cx);
+ }
+
+ // Step 6: Otherwise, return a promise resolved with returnValue.[[Value]].
+ return PromiseObject::unforgeableResolve(cx, rval);
+}
+
+/**
+ * Resolve the unwrapped promise |unwrappedPromise| with |value|.
+ */
+inline MOZ_MUST_USE bool ResolveUnwrappedPromiseWithValue(
+ JSContext* cx, JSObject* unwrappedPromise, JS::Handle<JS::Value> value) {
+ cx->check(value);
+
+ JS::Rooted<JSObject*> promise(cx, unwrappedPromise);
+ if (!cx->compartment()->wrap(cx, &promise)) {
+ return false;
+ }
+
+ return JS::ResolvePromise(cx, promise, value);
+}
+
+/**
+ * Resolve the unwrapped promise |unwrappedPromise| with |undefined|.
+ */
+inline MOZ_MUST_USE bool ResolveUnwrappedPromiseWithUndefined(
+ JSContext* cx, JSObject* unwrappedPromise) {
+ return ResolveUnwrappedPromiseWithValue(cx, unwrappedPromise,
+ JS::UndefinedHandleValue);
+}
+
+/**
+ * Reject the unwrapped promise |unwrappedPromise| with |error|, overwriting
+ * |*unwrappedPromise| with its wrapped form.
+ */
+inline MOZ_MUST_USE bool RejectUnwrappedPromiseWithError(
+ JSContext* cx, JS::MutableHandle<JSObject*> unwrappedPromise,
+ JS::Handle<JS::Value> error) {
+ cx->check(error);
+
+ if (!cx->compartment()->wrap(cx, unwrappedPromise)) {
+ return false;
+ }
+
+ return JS::RejectPromise(cx, unwrappedPromise, error);
+}
+
+/**
+ * Reject the unwrapped promise |unwrappedPromise| with |error|.
+ */
+inline MOZ_MUST_USE bool RejectUnwrappedPromiseWithError(
+ JSContext* cx, JSObject* unwrappedPromise, JS::Handle<JS::Value> error) {
+ JS::Rooted<JSObject*> promise(cx, unwrappedPromise);
+ return RejectUnwrappedPromiseWithError(cx, &promise, error);
+}
+
+} // namespace js
+
+#endif // builtin_streams_MiscellaneousOperations_inl_h
diff --git a/js/src/builtin/streams/MiscellaneousOperations.cpp b/js/src/builtin/streams/MiscellaneousOperations.cpp
new file mode 100644
index 0000000000..3d08886dcb
--- /dev/null
+++ b/js/src/builtin/streams/MiscellaneousOperations.cpp
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Miscellaneous operations. */
+
+#include "builtin/streams/MiscellaneousOperations.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+#include "mozilla/FloatingPoint.h" // mozilla::IsNaN
+
+#include "jsapi.h" // JS_ReportErrorNumberASCII
+
+#include "js/Conversions.h" // JS::ToNumber
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted
+#include "vm/Interpreter.h" // js::{Call,GetAndClearException}
+#include "vm/JSContext.h" // JSContext
+#include "vm/ObjectOperations.h" // js::GetProperty
+#include "vm/PromiseObject.h" // js::PromiseObject
+#include "vm/StringType.h" // js::PropertyName
+
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/JSObject-inl.h" // js::IsCallable
+
+using JS::Handle;
+using JS::MutableHandle;
+using JS::ToNumber;
+using JS::Value;
+
+MOZ_MUST_USE js::PromiseObject* js::PromiseRejectedWithPendingError(
+ JSContext* cx) {
+ Rooted<Value> exn(cx);
+ if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
+ // Uncatchable error. This happens when a slow script is killed or a
+ // worker is terminated. Propagate the uncatchable error. This will
+ // typically kill off the calling asynchronous process: the caller
+ // can't hook its continuation to the new rejected promise.
+ return nullptr;
+ }
+ return PromiseObject::unforgeableReject(cx, exn);
+}
+
+/*** 6.3. Miscellaneous operations ******************************************/
+
+/**
+ * Streams spec, 6.3.1.
+ * CreateAlgorithmFromUnderlyingMethod ( underlyingObject, methodName,
+ * algoArgCount, extraArgs )
+ *
+ * This function only partly implements the standard algorithm. We do not
+ * actually create a new JSFunction completely encapsulating the new algorithm.
+ * Instead, this just gets the specified method and checks for errors. It's the
+ * caller's responsibility to make sure that later, when the algorithm is
+ * "performed", the appropriate steps are carried out.
+ */
+MOZ_MUST_USE bool js::CreateAlgorithmFromUnderlyingMethod(
+ JSContext* cx, Handle<Value> underlyingObject,
+ const char* methodNameForErrorMessage, Handle<PropertyName*> methodName,
+ MutableHandle<Value> method) {
+ cx->check(underlyingObject);
+ cx->check(methodName);
+ cx->check(method);
+
+ // Step 1: Assert: underlyingObject is not undefined.
+ MOZ_ASSERT(!underlyingObject.isUndefined());
+
+ // Step 2: Assert: ! IsPropertyKey(methodName) is true (implicit).
+ // Step 3: Assert: algoArgCount is 0 or 1 (omitted).
+ // Step 4: Assert: extraArgs is a List (omitted).
+
+ // Step 5: Let method be ? GetV(underlyingObject, methodName).
+ if (!GetProperty(cx, underlyingObject, methodName, method)) {
+ return false;
+ }
+
+ // Step 6: If method is not undefined,
+ if (!method.isUndefined()) {
+ // Step a: If ! IsCallable(method) is false, throw a TypeError
+ // exception.
+ if (!IsCallable(method)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NOT_FUNCTION, methodNameForErrorMessage);
+ return false;
+ }
+
+ // Step b: If algoArgCount is 0, return an algorithm that performs the
+ // following steps:
+ // Step i: Return ! PromiseCall(method, underlyingObject,
+ // extraArgs).
+ // Step c: Otherwise, return an algorithm that performs the following
+ // steps, taking an arg argument:
+ // Step i: Let fullArgs be a List consisting of arg followed by the
+ // elements of extraArgs in order.
+ // Step ii: Return ! PromiseCall(method, underlyingObject,
+ // fullArgs).
+ // (These steps are deferred to the code that performs the algorithm.
+ // See Perform{Write,Close}Algorithm, ReadableStreamControllerCancelSteps,
+ // and ReadableStreamControllerCallPullIfNeeded.)
+ return true;
+ }
+
+ // Step 7: Return an algorithm which returns a promise resolved with
+ // undefined (implicit).
+ return true;
+}
+
+/**
+ * Streams spec, 6.3.2. InvokeOrNoop ( O, P, args )
+ * As it happens, all callers pass exactly one argument.
+ */
+MOZ_MUST_USE bool js::InvokeOrNoop(JSContext* cx, Handle<Value> O,
+ Handle<PropertyName*> P, Handle<Value> arg,
+ MutableHandle<Value> rval) {
+ cx->check(O, P, arg);
+
+ // Step 1: Assert: O is not undefined.
+ MOZ_ASSERT(!O.isUndefined());
+
+ // Step 2: Assert: ! IsPropertyKey(P) is true (implicit).
+ // Step 3: Assert: args is a List (implicit).
+ // Step 4: Let method be ? GetV(O, P).
+ Rooted<Value> method(cx);
+ if (!GetProperty(cx, O, P, &method)) {
+ return false;
+ }
+
+ // Step 5: If method is undefined, return.
+ if (method.isUndefined()) {
+ return true;
+ }
+
+ // Step 6: Return ? Call(method, O, args).
+ return Call(cx, method, O, arg, rval);
+}
+
+/**
+ * Streams spec, 6.3.7. ValidateAndNormalizeHighWaterMark ( highWaterMark )
+ */
+MOZ_MUST_USE bool js::ValidateAndNormalizeHighWaterMark(
+ JSContext* cx, Handle<Value> highWaterMarkVal, double* highWaterMark) {
+ // Step 1: Set highWaterMark to ? ToNumber(highWaterMark).
+ if (!ToNumber(cx, highWaterMarkVal, highWaterMark)) {
+ return false;
+ }
+
+ // Step 2: If highWaterMark is NaN or highWaterMark < 0, throw a RangeError
+ // exception.
+ if (mozilla::IsNaN(*highWaterMark) || *highWaterMark < 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_STREAM_INVALID_HIGHWATERMARK);
+ return false;
+ }
+
+ // Step 3: Return highWaterMark.
+ return true;
+}
+
+/**
+ * Streams spec, 6.3.8. MakeSizeAlgorithmFromSizeFunction ( size )
+ *
+ * The standard makes a big deal of turning JavaScript functions (grubby,
+ * touched by users, covered with germs) into algorithms (pristine,
+ * respectable, purposeful). We don't bother. Here we only check for errors and
+ * leave `size` unchanged. Then, in ReadableStreamDefaultControllerEnqueue and
+ * WritableStreamDefaultControllerGetChunkSize where this value is used, we
+ * check for undefined and behave as if we had "made" an "algorithm" for it.
+ */
+MOZ_MUST_USE bool js::MakeSizeAlgorithmFromSizeFunction(JSContext* cx,
+ Handle<Value> size) {
+ cx->check(size);
+
+ // Step 1: If size is undefined, return an algorithm that returns 1.
+ if (size.isUndefined()) {
+ // Deferred. Size algorithm users must check for undefined.
+ return true;
+ }
+
+ // Step 2: If ! IsCallable(size) is false, throw a TypeError exception.
+ if (!IsCallable(size)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION,
+ "ReadableStream argument options.size");
+ return false;
+ }
+
+ // Step 3: Return an algorithm that performs the following steps, taking a
+ // chunk argument:
+ // a. Return ? Call(size, undefined, « chunk »).
+ // Deferred. Size algorithm users must know how to call the size function.
+ return true;
+}
diff --git a/js/src/builtin/streams/MiscellaneousOperations.h b/js/src/builtin/streams/MiscellaneousOperations.h
new file mode 100644
index 0000000000..93dbedbfbc
--- /dev/null
+++ b/js/src/builtin/streams/MiscellaneousOperations.h
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Miscellaneous operations. */
+
+#ifndef builtin_streams_MiscellaneousOperations_h
+#define builtin_streams_MiscellaneousOperations_h
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jstypes.h" // JS_PUBLIC_API
+#include "js/CallArgs.h" // JS::CallArgs
+#include "js/RootingAPI.h" // JS::{,Mutable}Handle
+#include "js/Value.h" // JS::Value
+#include "vm/JSObject.h" // JSObject
+#include "vm/PromiseObject.h" // js::PromiseObject
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+class PropertyName;
+
+extern MOZ_MUST_USE PromiseObject* PromiseRejectedWithPendingError(
+ JSContext* cx);
+
+inline MOZ_MUST_USE bool ReturnPromiseRejectedWithPendingError(
+ JSContext* cx, const JS::CallArgs& args) {
+ PromiseObject* promise = PromiseRejectedWithPendingError(cx);
+ if (!promise) {
+ return false;
+ }
+
+ args.rval().setObject(*promise);
+ return true;
+}
+
+/**
+ * Streams spec, 6.3.1.
+ * CreateAlgorithmFromUnderlyingMethod ( underlyingObject, methodName,
+ * algoArgCount, extraArgs )
+ *
+ * This function only partly implements the standard algorithm. We do not
+ * actually create a new JSFunction completely encapsulating the new algorithm.
+ * Instead, this just gets the specified method and checks for errors. It's the
+ * caller's responsibility to make sure that later, when the algorithm is
+ * "performed", the appropriate steps are carried out.
+ */
+extern MOZ_MUST_USE bool CreateAlgorithmFromUnderlyingMethod(
+ JSContext* cx, JS::Handle<JS::Value> underlyingObject,
+ const char* methodNameForErrorMessage, JS::Handle<PropertyName*> methodName,
+ JS::MutableHandle<JS::Value> method);
+
+/**
+ * Streams spec, 6.3.2. InvokeOrNoop ( O, P, args )
+ * As it happens, all callers pass exactly one argument.
+ */
+extern MOZ_MUST_USE bool InvokeOrNoop(JSContext* cx, JS::Handle<JS::Value> O,
+ JS::Handle<PropertyName*> P,
+ JS::Handle<JS::Value> arg,
+ JS::MutableHandle<JS::Value> rval);
+
+/**
+ * Streams spec, 6.3.7. ValidateAndNormalizeHighWaterMark ( highWaterMark )
+ */
+extern MOZ_MUST_USE bool ValidateAndNormalizeHighWaterMark(
+ JSContext* cx, JS::Handle<JS::Value> highWaterMarkVal,
+ double* highWaterMark);
+
+/**
+ * Streams spec, 6.3.8. MakeSizeAlgorithmFromSizeFunction ( size )
+ */
+extern MOZ_MUST_USE bool MakeSizeAlgorithmFromSizeFunction(
+ JSContext* cx, JS::Handle<JS::Value> size);
+
+template <class T>
+inline bool IsMaybeWrapped(const JS::Handle<JS::Value> v) {
+ return v.isObject() && v.toObject().canUnwrapAs<T>();
+}
+
+} // namespace js
+
+#endif // builtin_streams_MiscellaneousOperations_h
diff --git a/js/src/builtin/streams/PipeToState-inl.h b/js/src/builtin/streams/PipeToState-inl.h
new file mode 100644
index 0000000000..5006185aec
--- /dev/null
+++ b/js/src/builtin/streams/PipeToState-inl.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* ReadableStream pipe-to operation captured state. */
+
+#ifndef builtin_streams_PipeToState_inl_h
+#define builtin_streams_PipeToState_inl_h
+
+#include "builtin/streams/PipeToState.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jstypes.h" // JS_PUBLIC_API
+
+#include "js/RootingAPI.h" // JS::Handle
+#include "vm/JSContext.h" // JSContext
+#include "vm/Runtime.h" // JSRuntime
+
+#include "vm/Compartment-inl.h" // js::UnwrapAndDowncastValue
+#include "vm/JSContext-inl.h" // JSContext::check
+
+struct JS_PUBLIC_API JSContext;
+class JS_PUBLIC_API JSObject;
+
+namespace js {
+
+/**
+ * Returns the unwrapped |AbortSignal| instance associated with a given pipe-to
+ * operation.
+ *
+ * The pipe-to operation must be known to have had an |AbortSignal| associated
+ * with it.
+ *
+ * If the signal is a wrapper, it will be unwrapped, so the result might not be
+ * an object from the currently active compartment.
+ */
+inline MOZ_MUST_USE JSObject* UnwrapSignalFromPipeToState(
+ JSContext* cx, JS::Handle<PipeToState*> pipeToState) {
+ cx->check(pipeToState);
+
+ MOZ_ASSERT(pipeToState->hasSignal());
+ return UnwrapAndDowncastValue(
+ cx, pipeToState->getFixedSlot(PipeToState::Slot_Signal),
+ cx->runtime()->maybeAbortSignalClass());
+}
+
+} // namespace js
+
+#endif // builtin_streams_PipeToState_inl_h
diff --git a/js/src/builtin/streams/PipeToState.cpp b/js/src/builtin/streams/PipeToState.cpp
new file mode 100644
index 0000000000..f79e81d3fe
--- /dev/null
+++ b/js/src/builtin/streams/PipeToState.cpp
@@ -0,0 +1,1271 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* ReadableStream.prototype.pipeTo state. */
+
+#include "builtin/streams/PipeToState-inl.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Nothing, mozilla::Some
+
+#include "jsapi.h" // JS_ReportErrorNumberASCII
+
+#include "builtin/Promise.h" // js::RejectPromiseWithPendingError
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream
+#include "builtin/streams/ReadableStreamReader.h" // js::CreateReadableStreamDefaultReader, js::ForAuthorCodeBool, js::ReadableStreamDefaultReader, js::ReadableStreamReaderGenericRelease
+#include "builtin/streams/WritableStream.h" // js::WritableStream
+#include "builtin/streams/WritableStreamDefaultWriter.h" // js::CreateWritableStreamDefaultWriter, js::WritableStreamDefaultWriter
+#include "builtin/streams/WritableStreamOperations.h" // js::WritableStreamCloseQueuedOrInFlight
+#include "builtin/streams/WritableStreamWriterOperations.h" // js::WritableStreamDefaultWriter{GetDesiredSize,Release,Write}
+#include "js/CallArgs.h" // JS::CallArgsFromVp, JS::CallArgs
+#include "js/Class.h" // JSClass, JSCLASS_HAS_RESERVED_SLOTS
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Promise.h" // JS::AddPromiseReactions
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "js/Value.h" // JS::{,Int32,Magic,Object}Value, JS::UndefinedHandleValue
+#include "vm/JSContext.h" // JSContext
+#include "vm/PromiseObject.h" // js::PromiseObject
+#include "vm/Runtime.h" // JSRuntime
+
+#include "builtin/HandlerFunction-inl.h" // js::ExtraValueFromHandler, js::NewHandler{,WithExtraValue}, js::TargetFromHandler
+#include "builtin/streams/ReadableStreamReader-inl.h" // js::UnwrapReaderFromStream, js::UnwrapStreamFromReader
+#include "builtin/streams/WritableStream-inl.h" // js::UnwrapWriterFromStream
+#include "builtin/streams/WritableStreamDefaultWriter-inl.h" // js::UnwrapStreamFromWriter
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using mozilla::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::Int32Value;
+using JS::MagicValue;
+using JS::ObjectValue;
+using JS::Rooted;
+using JS::UndefinedHandleValue;
+using JS::Value;
+
+using js::ExtraValueFromHandler;
+using js::GetErrorMessage;
+using js::NewHandler;
+using js::NewHandlerWithExtraValue;
+using js::PipeToState;
+using js::PromiseObject;
+using js::ReadableStream;
+using js::ReadableStreamDefaultReader;
+using js::ReadableStreamReaderGenericRelease;
+using js::TargetFromHandler;
+using js::UnwrapReaderFromStream;
+using js::UnwrapStreamFromWriter;
+using js::UnwrapWriterFromStream;
+using js::WritableStream;
+using js::WritableStreamDefaultWriter;
+using js::WritableStreamDefaultWriterRelease;
+using js::WritableStreamDefaultWriterWrite;
+
+static ReadableStream* GetUnwrappedSource(JSContext* cx,
+ Handle<PipeToState*> state) {
+ cx->check(state);
+
+ Rooted<ReadableStreamDefaultReader*> reader(cx, state->reader());
+ cx->check(reader);
+
+ return UnwrapStreamFromReader(cx, reader);
+}
+
+static WritableStream* GetUnwrappedDest(JSContext* cx,
+ Handle<PipeToState*> state) {
+ cx->check(state);
+
+ Rooted<WritableStreamDefaultWriter*> writer(cx, state->writer());
+ cx->check(writer);
+
+ return UnwrapStreamFromWriter(cx, writer);
+}
+
+static bool WritableAndNotClosing(const WritableStream* unwrappedDest) {
+ return unwrappedDest->writable() &&
+ WritableStreamCloseQueuedOrInFlight(unwrappedDest);
+}
+
+static MOZ_MUST_USE bool Finalize(JSContext* cx, Handle<PipeToState*> state,
+ Handle<Maybe<Value>> error) {
+ cx->check(state);
+ cx->check(error);
+
+ // Step 1: Perform ! WritableStreamDefaultWriterRelease(writer).
+ Rooted<WritableStreamDefaultWriter*> writer(cx, state->writer());
+ cx->check(writer);
+ if (!WritableStreamDefaultWriterRelease(cx, writer)) {
+ return false;
+ }
+
+ // Step 2: Perform ! ReadableStreamReaderGenericRelease(reader).
+ Rooted<ReadableStreamDefaultReader*> reader(cx, state->reader());
+ cx->check(reader);
+ if (!ReadableStreamReaderGenericRelease(cx, reader)) {
+ return false;
+ }
+
+ // Step 3: If signal is not undefined, remove abortAlgorithm from signal.
+ // XXX
+
+ Rooted<PromiseObject*> promise(cx, state->promise());
+ cx->check(promise);
+
+ // Step 4: If error was given, reject promise with error.
+ if (error.isSome()) {
+ Rooted<Value> errorVal(cx, *error.get());
+ return PromiseObject::reject(cx, promise, errorVal);
+ }
+
+ // Step 5: Otherwise, resolve promise with undefined.
+ return PromiseObject::resolve(cx, promise, UndefinedHandleValue);
+}
+
+static MOZ_MUST_USE bool Finalize(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<PipeToState*> state(cx, TargetFromHandler<PipeToState>(args));
+ cx->check(state);
+
+ Rooted<Maybe<Value>> optionalError(cx, Nothing());
+ if (Value maybeError = ExtraValueFromHandler(args);
+ !maybeError.isMagic(JS_READABLESTREAM_PIPETO_FINALIZE_WITHOUT_ERROR)) {
+ optionalError = Some(maybeError);
+ }
+ cx->check(optionalError);
+
+ if (!Finalize(cx, state, optionalError)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+// Shutdown with an action, steps d-f:
+// d. Let p be the result of performing action.
+// e. Upon fulfillment of p, finalize, passing along originalError if it was
+// given.
+// f. Upon rejection of p with reason newError, finalize with newError.
+static MOZ_MUST_USE bool ActAndFinalize(JSContext* cx,
+ Handle<PipeToState*> state,
+ Handle<Maybe<Value>> error) {
+ // Step d: Let p be the result of performing action.
+ Rooted<JSObject*> p(cx);
+ switch (state->shutdownAction()) {
+ // This corresponds to the action performed by |abortAlgorithm| in
+ // ReadableStreamPipeTo step 14.1.5.
+ case PipeToState::ShutdownAction::AbortAlgorithm: {
+ MOZ_ASSERT(error.get().isSome());
+
+ // From ReadableStreamPipeTo:
+ // Step 14.1.2: Let actions be an empty ordered set.
+ // Step 14.1.3: If preventAbort is false, append the following action to
+ // actions:
+ // Step 14.1.3.1: If dest.[[state]] is "writable", return
+ // ! WritableStreamAbort(dest, error).
+ // Step 14.1.3.2: Otherwise, return a promise resolved with undefined.
+ // Step 14.1.4: If preventCancel is false, append the following action
+ // action to actions:
+ // Step 14.1.4.1.: If source.[[state]] is "readable", return
+ // ! ReadableStreamCancel(source, error).
+ // Step 14.1.4.2: Otherwise, return a promise resolved with undefined.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED,
+ "any required actions during abortAlgorithm");
+ return false;
+ }
+
+ // This corresponds to the action in "shutdown with an action of
+ // ! WritableStreamAbort(dest, source.[[storedError]]) and with
+ // source.[[storedError]]."
+ case PipeToState::ShutdownAction::AbortDestStream: {
+ MOZ_ASSERT(error.get().isSome());
+
+ Rooted<WritableStream*> unwrappedDest(cx, GetUnwrappedDest(cx, state));
+ if (!unwrappedDest) {
+ return false;
+ }
+
+ Rooted<Value> sourceStoredError(cx, *error.get());
+ cx->check(sourceStoredError);
+
+ p = WritableStreamAbort(cx, unwrappedDest, sourceStoredError);
+ break;
+ }
+
+ // This corresponds to two actions:
+ //
+ // * The action in "shutdown with an action of
+ // ! ReadableStreamCancel(source, dest.[[storedError]]) and with
+ // dest.[[storedError]]" as used in "Errors must be propagated backward:
+ // if dest.[[state]] is or becomes 'errored'".
+ // * The action in "shutdown with an action of
+ // ! ReadableStreamCancel(source, destClosed) and with destClosed" as used
+ // in "Closing must be propagated backward: if
+ // ! WritableStreamCloseQueuedOrInFlight(dest) is true or dest.[[state]]
+ // is 'closed'".
+ //
+ // The different reason-values are passed as |error|.
+ case PipeToState::ShutdownAction::CancelSource: {
+ MOZ_ASSERT(error.get().isSome());
+
+ Rooted<ReadableStream*> unwrappedSource(cx,
+ GetUnwrappedSource(cx, state));
+ if (!unwrappedSource) {
+ return false;
+ }
+
+ Rooted<Value> reason(cx, *error.get());
+ cx->check(reason);
+
+ p = ReadableStreamCancel(cx, unwrappedSource, reason);
+ break;
+ }
+
+ // This corresponds to the action in "shutdown with an action of
+ // ! WritableStreamDefaultWriterCloseWithErrorPropagation(writer)" as done
+ // in "Closing must be propagated forward: if source.[[state]] is or becomes
+ // 'closed'".
+ case PipeToState::ShutdownAction::CloseWriterWithErrorPropagation: {
+ MOZ_ASSERT(error.get().isNothing());
+
+ Rooted<WritableStreamDefaultWriter*> writer(cx, state->writer());
+ cx->check(writer); // just for good measure: we don't depend on this
+
+ p = WritableStreamDefaultWriterCloseWithErrorPropagation(cx, writer);
+ break;
+ }
+ }
+ if (!p) {
+ return false;
+ }
+
+ // Step e: Upon fulfillment of p, finalize, passing along originalError if it
+ // was given.
+ Rooted<JSFunction*> onFulfilled(cx);
+ {
+ Rooted<Value> optionalError(
+ cx, error.isSome()
+ ? *error.get()
+ : MagicValue(JS_READABLESTREAM_PIPETO_FINALIZE_WITHOUT_ERROR));
+ onFulfilled = NewHandlerWithExtraValue(cx, Finalize, state, optionalError);
+ if (!onFulfilled) {
+ return false;
+ }
+ }
+
+ // Step f: Upon rejection of p with reason newError, finalize with newError.
+ auto OnRejected = [](JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<PipeToState*> state(cx, TargetFromHandler<PipeToState>(args));
+ cx->check(state);
+
+ Rooted<Maybe<Value>> newError(cx, Some(args[0]));
+ cx->check(newError);
+ if (!Finalize(cx, state, newError)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+ };
+
+ Rooted<JSFunction*> onRejected(cx, NewHandler(cx, OnRejected, state));
+ if (!onRejected) {
+ return false;
+ }
+
+ return JS::AddPromiseReactions(cx, p, onFulfilled, onRejected);
+}
+
+static MOZ_MUST_USE bool ActAndFinalize(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<PipeToState*> state(cx, TargetFromHandler<PipeToState>(args));
+ cx->check(state);
+
+ Rooted<Maybe<Value>> optionalError(cx, Nothing());
+ if (Value maybeError = ExtraValueFromHandler(args);
+ !maybeError.isMagic(JS_READABLESTREAM_PIPETO_FINALIZE_WITHOUT_ERROR)) {
+ optionalError = Some(maybeError);
+ }
+ cx->check(optionalError);
+
+ if (!ActAndFinalize(cx, state, optionalError)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+// Shutdown with an action: if any of the above requirements ask to shutdown
+// with an action action, optionally with an error originalError, then:
+static MOZ_MUST_USE bool ShutdownWithAction(
+ JSContext* cx, Handle<PipeToState*> state,
+ PipeToState::ShutdownAction action, Handle<Maybe<Value>> originalError) {
+ cx->check(state);
+ cx->check(originalError);
+
+ // Step a: If shuttingDown is true, abort these substeps.
+ if (state->shuttingDown()) {
+ return true;
+ }
+
+ // Step b: Set shuttingDown to true.
+ state->setShuttingDown();
+
+ // Save the action away for later, potentially asynchronous, use.
+ state->setShutdownAction(action);
+
+ // Step c: If dest.[[state]] is "writable" and
+ // ! WritableStreamCloseQueuedOrInFlight(dest) is false,
+ WritableStream* unwrappedDest = GetUnwrappedDest(cx, state);
+ if (!unwrappedDest) {
+ return false;
+ }
+ if (WritableAndNotClosing(unwrappedDest)) {
+ // Step c.i: If any chunks have been read but not yet written, write them
+ // to dest.
+ //
+ // Any chunk that has been read, will have been processed and a pending
+ // write for it created by this point. (A pending read has not been "read".
+ // And any pending read, will not be processed into a pending write because
+ // of the |state->setShuttingDown()| above in concert with the early exit
+ // in this case in |ReadFulfilled|.)
+
+ // Step c.ii: Wait until every chunk that has been read has been written
+ // (i.e. the corresponding promises have settled).
+ if (PromiseObject* p = state->lastWriteRequest()) {
+ Rooted<PromiseObject*> lastWriteRequest(cx, p);
+
+ Rooted<Value> extra(
+ cx,
+ originalError.isSome()
+ ? *originalError.get()
+ : MagicValue(JS_READABLESTREAM_PIPETO_FINALIZE_WITHOUT_ERROR));
+
+ Rooted<JSFunction*> actAndfinalize(
+ cx, NewHandlerWithExtraValue(cx, ActAndFinalize, state, extra));
+ if (!actAndfinalize) {
+ return false;
+ }
+
+ return JS::AddPromiseReactions(cx, lastWriteRequest, actAndfinalize,
+ actAndfinalize);
+ }
+
+ // If no last write request was ever created, we can fall through and
+ // synchronously perform the remaining steps.
+ }
+
+ // Step d: Let p be the result of performing action.
+ // Step e: Upon fulfillment of p, finalize, passing along originalError if it
+ // was given.
+ // Step f: Upon rejection of p with reason newError, finalize with newError.
+ return ActAndFinalize(cx, state, originalError);
+}
+
+// Shutdown: if any of the above requirements or steps ask to shutdown,
+// optionally with an error error, then:
+static MOZ_MUST_USE bool Shutdown(JSContext* cx, Handle<PipeToState*> state,
+ Handle<Maybe<Value>> error) {
+ cx->check(state);
+ cx->check(error);
+
+ // Step a: If shuttingDown is true, abort these substeps.
+ if (state->shuttingDown()) {
+ return true;
+ }
+
+ // Step b: Set shuttingDown to true.
+ state->setShuttingDown();
+
+ // Step c: If dest.[[state]] is "writable" and
+ // ! WritableStreamCloseQueuedOrInFlight(dest) is false,
+ WritableStream* unwrappedDest = GetUnwrappedDest(cx, state);
+ if (!unwrappedDest) {
+ return false;
+ }
+ if (WritableAndNotClosing(unwrappedDest)) {
+ // Step 1: If any chunks have been read but not yet written, write them to
+ // dest.
+ //
+ // Any chunk that has been read, will have been processed and a pending
+ // write for it created by this point. (A pending read has not been "read".
+ // And any pending read, will not be processed into a pending write because
+ // of the |state->setShuttingDown()| above in concert with the early exit
+ // in this case in |ReadFulfilled|.)
+
+ // Step 2: Wait until every chunk that has been read has been written
+ // (i.e. the corresponding promises have settled).
+ if (PromiseObject* p = state->lastWriteRequest()) {
+ Rooted<PromiseObject*> lastWriteRequest(cx, p);
+
+ Rooted<Value> extra(
+ cx,
+ error.isSome()
+ ? *error.get()
+ : MagicValue(JS_READABLESTREAM_PIPETO_FINALIZE_WITHOUT_ERROR));
+
+ Rooted<JSFunction*> finalize(
+ cx, NewHandlerWithExtraValue(cx, Finalize, state, extra));
+ if (!finalize) {
+ return false;
+ }
+
+ return JS::AddPromiseReactions(cx, lastWriteRequest, finalize, finalize);
+ }
+
+ // If no last write request was ever created, we can fall through and
+ // synchronously perform the remaining steps.
+ }
+
+ // Step d: Finalize, passing along error if it was given.
+ return Finalize(cx, state, error);
+}
+
+/**
+ * Streams spec, 3.4.11. ReadableStreamPipeTo step 14:
+ * "a. Errors must be propagated forward: if source.[[state]] is or becomes
+ * 'errored', then..."
+ */
+static MOZ_MUST_USE bool OnSourceErrored(
+ JSContext* cx, Handle<PipeToState*> state,
+ Handle<ReadableStream*> unwrappedSource) {
+ cx->check(state);
+
+ Rooted<Maybe<Value>> storedError(cx, Some(unwrappedSource->storedError()));
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return false;
+ }
+
+ // If |source| becomes errored not during a pending read, it's clear we must
+ // react immediately.
+ //
+ // But what if |source| becomes errored *during* a pending read? Should this
+ // first error, or the pending-read second error, predominate? Two semantics
+ // are possible when |source|/|dest| become closed or errored while there's a
+ // pending read:
+ //
+ // 1. Wait until the read fulfills or rejects, then respond to the
+ // closure/error without regard to the read having fulfilled or rejected.
+ // (This will simply not react to the read being rejected, or it will
+ // queue up the read chunk to be written during shutdown.)
+ // 2. React to the closure/error immediately per "Error and close states
+ // must be propagated". Then when the read fulfills or rejects later, do
+ // nothing.
+ //
+ // The spec doesn't clearly require either semantics. It requires that
+ // *already-read* chunks be written (at least if |dest| didn't become errored
+ // or closed such that no further writes can occur). But it's silent as to
+ // not-fully-read chunks. (These semantic differences may only be observable
+ // with very carefully constructed readable/writable streams.)
+ //
+ // It seems best, generally, to react to the temporally-earliest problem that
+ // arises, so we implement option #2. (Blink, in contrast, currently
+ // implements option #1.)
+ //
+ // All specified reactions to a closure/error invoke either the shutdown, or
+ // shutdown with an action, algorithms. Those algorithms each abort if either
+ // shutdown algorithm has already been invoked. So we don't need to do
+ // anything special here to deal with a pending read.
+
+ // ii. Otherwise (if preventAbort is true), shutdown with
+ // source.[[storedError]].
+ if (state->preventAbort()) {
+ if (!Shutdown(cx, state, storedError)) {
+ return false;
+ }
+ }
+ // i. (If preventAbort is false,) shutdown with an action of
+ // ! WritableStreamAbort(dest, source.[[storedError]]) and with
+ // source.[[storedError]].
+ else {
+ if (!ShutdownWithAction(cx, state,
+ PipeToState::ShutdownAction::AbortDestStream,
+ storedError)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.4.11. ReadableStreamPipeTo step 14:
+ * "b. Errors must be propagated backward: if dest.[[state]] is or becomes
+ * 'errored', then..."
+ */
+static MOZ_MUST_USE bool OnDestErrored(JSContext* cx,
+ Handle<PipeToState*> state,
+ Handle<WritableStream*> unwrappedDest) {
+ cx->check(state);
+
+ Rooted<Maybe<Value>> storedError(cx, Some(unwrappedDest->storedError()));
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return false;
+ }
+
+ // As in |OnSourceErrored| above, we must deal with the case of |dest|
+ // erroring before a pending read has fulfilled or rejected.
+ //
+ // As noted there, we handle the *first* error that arises. And because this
+ // algorithm immediately invokes a shutdown algorithm, and shutting down will
+ // inhibit future shutdown attempts, we don't need to do anything special
+ // *here*, either.
+
+ // ii. Otherwise (if preventCancel is true), shutdown with
+ // dest.[[storedError]].
+ if (state->preventCancel()) {
+ if (!Shutdown(cx, state, storedError)) {
+ return false;
+ }
+ }
+ // i. If preventCancel is false, shutdown with an action of
+ // ! ReadableStreamCancel(source, dest.[[storedError]]) and with
+ // dest.[[storedError]].
+ else {
+ if (!ShutdownWithAction(cx, state,
+ PipeToState::ShutdownAction::CancelSource,
+ storedError)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.4.11. ReadableStreamPipeTo step 14:
+ * "c. Closing must be propagated forward: if source.[[state]] is or becomes
+ * 'closed', then..."
+ */
+static MOZ_MUST_USE bool OnSourceClosed(JSContext* cx,
+ Handle<PipeToState*> state) {
+ cx->check(state);
+
+ Rooted<Maybe<Value>> noError(cx, Nothing());
+
+ // It shouldn't be possible for |source| to become closed *during* a pending
+ // read: such spontaneous closure *should* be enqueued for processing *after*
+ // the settling of the pending read. (Note also that a [[closedPromise]]
+ // resolution in |ReadableStreamClose| occurs only after all pending reads are
+ // resolved.) So we need not do anything to handle a source closure while a
+ // read is in progress.
+
+ // ii. Otherwise (if preventClose is true), shutdown.
+ if (state->preventClose()) {
+ if (!Shutdown(cx, state, noError)) {
+ return false;
+ }
+ }
+ // i. If preventClose is false, shutdown with an action of
+ // ! WritableStreamDefaultWriterCloseWithErrorPropagation(writer).
+ else {
+ if (!ShutdownWithAction(
+ cx, state,
+ PipeToState::ShutdownAction::CloseWriterWithErrorPropagation,
+ noError)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.4.11. ReadableStreamPipeTo step 14:
+ * "d. Closing must be propagated backward: if
+ * ! WritableStreamCloseQueuedOrInFlight(dest) is true or dest.[[state]] is
+ * 'closed', then..."
+ */
+static MOZ_MUST_USE bool OnDestClosed(JSContext* cx,
+ Handle<PipeToState*> state) {
+ cx->check(state);
+
+ // i. Assert: no chunks have been read or written.
+ //
+ // This assertion holds when this function is called by
+ // |SourceOrDestErroredOrClosed|, before any async internal piping operations
+ // happen.
+ //
+ // But it wouldn't hold for streams that can spontaneously close of their own
+ // accord, like say a hypothetical DOM TCP socket. I think?
+ //
+ // XXX Add this assertion if it really does hold (and is easily performed),
+ // else report a spec bug.
+
+ // ii. Let destClosed be a new TypeError.
+ Rooted<Maybe<Value>> destClosed(cx, Nothing());
+ {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_WRITE_CLOSING_OR_CLOSED);
+ Rooted<Value> v(cx);
+ if (!cx->isExceptionPending() || !GetAndClearException(cx, &v)) {
+ return false;
+ }
+
+ destClosed = Some(v.get());
+ }
+
+ // As in all the |On{Source,Dest}{Closed,Errored}| above, we must consider the
+ // possibility that we're in the middle of a pending read. |state->writer()|
+ // has a lock on |dest| here, so we know only we can be writing chunks to
+ // |dest| -- but there's no reason why |dest| couldn't become closed of its
+ // own accord here (for example, a socket might become closed on its own), and
+ // such closure may or may not be equivalent to error.
+ //
+ // For the reasons noted in |OnSourceErrored|, we process closure in the
+ // middle of a pending read immediately, without delaying for that read to
+ // fulfill or reject. We trigger a shutdown operation below, which will
+ // ensure shutdown only occurs once, so we need not do anything special here.
+
+ // iv. Otherwise (if preventCancel is true), shutdown with destClosed.
+ if (state->preventCancel()) {
+ if (!Shutdown(cx, state, destClosed)) {
+ return false;
+ }
+ }
+ // iii. If preventCancel is false, shutdown with an action of
+ // ! ReadableStreamCancel(source, destClosed) and with destClosed.
+ else {
+ if (!ShutdownWithAction(
+ cx, state, PipeToState::ShutdownAction::CancelSource, destClosed)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.4.11. ReadableStreamPipeTo step 14:
+ * "Error and close states must be propagated: the following conditions must be
+ * applied in order.", as applied at the very start of piping, before any reads
+ * from source or writes to dest have been triggered.
+ */
+static MOZ_MUST_USE bool SourceOrDestErroredOrClosed(
+ JSContext* cx, Handle<PipeToState*> state,
+ Handle<ReadableStream*> unwrappedSource,
+ Handle<WritableStream*> unwrappedDest, bool* erroredOrClosed) {
+ cx->check(state);
+
+ *erroredOrClosed = true;
+
+ // a. Errors must be propagated forward: if source.[[state]] is or becomes
+ // "errored", then
+ if (unwrappedSource->errored()) {
+ return OnSourceErrored(cx, state, unwrappedSource);
+ }
+
+ // b. Errors must be propagated backward: if dest.[[state]] is or becomes
+ // "errored", then
+ if (unwrappedDest->errored()) {
+ return OnDestErrored(cx, state, unwrappedDest);
+ }
+
+ // c. Closing must be propagated forward: if source.[[state]] is or becomes
+ // "closed", then
+ if (unwrappedSource->closed()) {
+ return OnSourceClosed(cx, state);
+ }
+
+ // d. Closing must be propagated backward: if
+ // ! WritableStreamCloseQueuedOrInFlight(dest) is true or dest.[[state]] is
+ // "closed", then
+ if (WritableStreamCloseQueuedOrInFlight(unwrappedDest) ||
+ unwrappedDest->closed()) {
+ return OnDestClosed(cx, state);
+ }
+
+ *erroredOrClosed = false;
+ return true;
+}
+
+static MOZ_MUST_USE bool OnSourceClosed(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<PipeToState*> state(cx, TargetFromHandler<PipeToState>(args));
+ cx->check(state);
+
+ if (!OnSourceClosed(cx, state)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE bool OnSourceErrored(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<PipeToState*> state(cx, TargetFromHandler<PipeToState>(args));
+ cx->check(state);
+
+ Rooted<ReadableStream*> unwrappedSource(cx, GetUnwrappedSource(cx, state));
+ if (!unwrappedSource) {
+ return false;
+ }
+
+ if (!OnSourceErrored(cx, state, unwrappedSource)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE bool OnDestClosed(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<PipeToState*> state(cx, TargetFromHandler<PipeToState>(args));
+ cx->check(state);
+
+ if (!OnDestClosed(cx, state)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE bool OnDestErrored(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<PipeToState*> state(cx, TargetFromHandler<PipeToState>(args));
+ cx->check(state);
+
+ Rooted<WritableStream*> unwrappedDest(cx, GetUnwrappedDest(cx, state));
+ if (!unwrappedDest) {
+ return false;
+ }
+
+ if (!OnDestErrored(cx, state, unwrappedDest)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+template <class StreamAccessor, class Stream>
+static inline JSObject* GetClosedPromise(
+ JSContext* cx, Handle<Stream*> unwrappedStream,
+ StreamAccessor* (&unwrapAccessorFromStream)(JSContext*, Handle<Stream*>)) {
+ StreamAccessor* unwrappedAccessor =
+ unwrapAccessorFromStream(cx, unwrappedStream);
+ if (!unwrappedAccessor) {
+ return nullptr;
+ }
+
+ return unwrappedAccessor->closedPromise();
+}
+
+static MOZ_MUST_USE bool ReadFromSource(JSContext* cx,
+ Handle<PipeToState*> state);
+
+static bool ReadFulfilled(JSContext* cx, Handle<PipeToState*> state,
+ Handle<JSObject*> result) {
+ cx->check(state);
+ cx->check(result);
+
+ state->clearPendingRead();
+
+ // "Shutdown must stop activity: if shuttingDown becomes true, the user agent
+ // must not initiate further reads from reader, and must only perform writes
+ // of already-read chunks".
+ //
+ // We may reach this point after |On{Source,Dest}{Clos,Error}ed| has responded
+ // to an out-of-band change. Per the comment in |OnSourceErrored|, we want to
+ // allow the implicated shutdown to proceed, and we don't want to interfere
+ // with or additionally alter its operation. Particularly, we don't want to
+ // queue up the successfully-read chunk (if there was one, and this isn't just
+ // reporting "done") to be written: it wasn't "already-read" when that
+ // error/closure happened.
+ //
+ // All specified reactions to a closure/error invoke either the shutdown, or
+ // shutdown with an action, algorithms. Those algorithms each abort if either
+ // shutdown algorithm has already been invoked. So we check for shutdown here
+ // in case of asynchronous closure/error and abort if shutdown has already
+ // started (and possibly finished).
+ if (state->shuttingDown()) {
+ return true;
+ }
+
+ {
+ bool done;
+ {
+ Rooted<Value> doneVal(cx);
+ if (!GetProperty(cx, result, result, cx->names().done, &doneVal)) {
+ return false;
+ }
+ done = doneVal.toBoolean();
+ }
+
+ if (done) {
+ // All chunks have been read from |reader| and written to |writer| (but
+ // not necessarily fulfilled yet, in the latter case). Proceed as if
+ // |source| is now closed. (This will asynchronously wait until any
+ // pending writes have fulfilled.)
+ return OnSourceClosed(cx, state);
+ }
+ }
+
+ // A chunk was read, and *at the time the read was requested*, |dest| was
+ // ready to accept a write. (Only one read is processed at a time per
+ // |state->hasPendingRead()|, so this condition remains true now.) Write the
+ // chunk to |dest|.
+ {
+ Rooted<Value> chunk(cx);
+ if (!GetProperty(cx, result, result, cx->names().value, &chunk)) {
+ return false;
+ }
+
+ Rooted<WritableStreamDefaultWriter*> writer(cx, state->writer());
+ cx->check(writer);
+
+ PromiseObject* writeRequest =
+ WritableStreamDefaultWriterWrite(cx, writer, chunk);
+ if (!writeRequest) {
+ return false;
+ }
+
+ // Stash away this new last write request. (The shutdown process will react
+ // to this write request to finish shutdown only once all pending writes are
+ // completed.)
+ state->updateLastWriteRequest(writeRequest);
+ }
+
+ // Read another chunk if this write didn't fill up |dest|.
+ //
+ // While we (properly) ignored |state->shuttingDown()| earlier, this call will
+ // *not* initiate a fresh read if |!state->shuttingDown()|.
+ return ReadFromSource(cx, state);
+}
+
+static bool ReadFulfilled(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+
+ Rooted<PipeToState*> state(cx, TargetFromHandler<PipeToState>(args));
+ cx->check(state);
+
+ Rooted<JSObject*> result(cx, &args[0].toObject());
+ cx->check(result);
+
+ if (!ReadFulfilled(cx, state, result)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool ReadFromSource(JSContext* cx, unsigned argc, Value* vp);
+
+static MOZ_MUST_USE bool ReadFromSource(JSContext* cx,
+ Handle<PipeToState*> state) {
+ cx->check(state);
+
+ MOZ_ASSERT(!state->hasPendingRead(),
+ "should only have one read in flight at a time, because multiple "
+ "reads could cause the latter read to ignore backpressure "
+ "signals");
+
+ // "Shutdown must stop activity: if shuttingDown becomes true, the user agent
+ // must not initiate further reads from reader..."
+ if (state->shuttingDown()) {
+ return true;
+ }
+
+ Rooted<WritableStreamDefaultWriter*> writer(cx, state->writer());
+ cx->check(writer);
+
+ // "While WritableStreamDefaultWriterGetDesiredSize(writer) is ≤ 0 or is null,
+ // the user agent must not read from reader."
+ Rooted<Value> desiredSize(cx);
+ if (!WritableStreamDefaultWriterGetDesiredSize(cx, writer, &desiredSize)) {
+ return false;
+ }
+
+ // If we're in the middle of erroring or are fully errored, either way the
+ // |dest|-closed reaction queued up in |StartPiping| will do the right
+ // thing, so do nothing here.
+ if (desiredSize.isNull()) {
+#ifdef DEBUG
+ {
+ WritableStream* unwrappedDest = GetUnwrappedDest(cx, state);
+ if (!unwrappedDest) {
+ return false;
+ }
+
+ MOZ_ASSERT(unwrappedDest->erroring() || unwrappedDest->errored());
+ }
+#endif
+
+ return true;
+ }
+
+ // If |dest| isn't ready to receive writes yet (i.e. backpressure applies),
+ // resume when it is.
+ MOZ_ASSERT(desiredSize.isNumber());
+ if (desiredSize.toNumber() <= 0) {
+ Rooted<JSObject*> readyPromise(cx, writer->readyPromise());
+ cx->check(readyPromise);
+
+ Rooted<JSFunction*> readFromSource(cx,
+ NewHandler(cx, ReadFromSource, state));
+ if (!readFromSource) {
+ return false;
+ }
+
+ // Resume when there's writable capacity. Don't bother handling rejection:
+ // if this happens, the stream is going to be errored shortly anyway, and
+ // |StartPiping| has us ready to react to that already.
+ //
+ // XXX Double-check the claim that we need not handle rejections and that a
+ // rejection of [[readyPromise]] *necessarily* is always followed by
+ // rejection of [[closedPromise]].
+ return JS::AddPromiseReactionsIgnoringUnhandledRejection(
+ cx, readyPromise, readFromSource, nullptr);
+ }
+
+ // |dest| is ready to receive at least one write. Read one chunk from the
+ // reader now that we're not subject to backpressure.
+ Rooted<ReadableStreamDefaultReader*> reader(cx, state->reader());
+ cx->check(reader);
+
+ Rooted<PromiseObject*> readRequest(
+ cx, js::ReadableStreamDefaultReaderRead(cx, reader));
+ if (!readRequest) {
+ return false;
+ }
+
+ Rooted<JSFunction*> readFulfilled(cx, NewHandler(cx, ReadFulfilled, state));
+ if (!readFulfilled) {
+ return false;
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(!state->pendingReadWouldBeRejected());
+
+ // The specification for ReadableStreamError ensures that rejecting a read or
+ // read-into request is immediately followed by rejecting the reader's
+ // [[closedPromise]]. Therefore, it does not appear *necessary* to handle the
+ // rejected case -- the [[closedPromise]] reaction will do so for us.
+ //
+ // However, this is all very stateful and gnarly, so we implement a rejection
+ // handler that sets a flag to indicate the read was rejected. Then if the
+ // [[closedPromise]] reaction function is invoked, we can assert that *if*
+ // a read is recorded as pending at that instant, a reject handler would have
+ // been invoked for it.
+ auto ReadRejected = [](JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+
+ Rooted<PipeToState*> state(cx, TargetFromHandler<PipeToState>(args));
+ cx->check(state);
+
+ state->setPendingReadWouldBeRejected();
+
+ args.rval().setUndefined();
+ return true;
+ };
+
+ Rooted<JSFunction*> readRejected(cx, NewHandler(cx, ReadRejected, state));
+ if (!readRejected) {
+ return false;
+ }
+#else
+ auto readRejected = nullptr;
+#endif
+
+ // Once the chunk is read, immediately write it and attempt to read more.
+ // Don't bother handling a rejection: |source| will be closed/errored, and
+ // |StartPiping| poised us to react to that already.
+ if (!JS::AddPromiseReactionsIgnoringUnhandledRejection(
+ cx, readRequest, readFulfilled, readRejected)) {
+ return false;
+ }
+
+ // The spec is clear that a write started before an error/stream-closure is
+ // encountered must be completed before shutdown. It is *not* clear that a
+ // read that hasn't yet fulfilled should delay shutdown (or until that read's
+ // successive write is completed).
+ //
+ // It seems easiest to explain, both from a user perspective (no read is ever
+ // just dropped on the ground) and an implementer perspective (if we *don't*
+ // delay, then a read could be started, a shutdown could be started, then the
+ // read could finish but we can't write it which arguably conflicts with the
+ // requirement that chunks that have been read must be written before shutdown
+ // completes), to delay. XXX file a spec issue to require this!
+ state->setPendingRead();
+ return true;
+}
+
+static bool ReadFromSource(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<PipeToState*> state(cx, TargetFromHandler<PipeToState>(args));
+ cx->check(state);
+
+ if (!ReadFromSource(cx, state)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE bool StartPiping(JSContext* cx, Handle<PipeToState*> state,
+ Handle<ReadableStream*> unwrappedSource,
+ Handle<WritableStream*> unwrappedDest) {
+ cx->check(state);
+
+ // "Shutdown must stop activity: if shuttingDown becomes true, the user agent
+ // must not initiate further reads from reader..."
+ MOZ_ASSERT(!state->shuttingDown(), "can't be shutting down when starting");
+
+ // "Error and close states must be propagated: the following conditions must
+ // be applied in order."
+ //
+ // Before piping has started, we have to check for source/dest being errored
+ // or closed manually.
+ bool erroredOrClosed;
+ if (!SourceOrDestErroredOrClosed(cx, state, unwrappedSource, unwrappedDest,
+ &erroredOrClosed)) {
+ return false;
+ }
+ if (erroredOrClosed) {
+ return true;
+ }
+
+ // *After* piping has started, add reactions to respond to source/dest
+ // becoming errored or closed.
+ {
+ Rooted<JSObject*> unwrappedClosedPromise(cx);
+ Rooted<JSObject*> onClosed(cx);
+ Rooted<JSObject*> onErrored(cx);
+
+ auto ReactWhenClosedOrErrored =
+ [&unwrappedClosedPromise, &onClosed, &onErrored, &state](
+ JSContext* cx, JSNative onClosedFunc, JSNative onErroredFunc) {
+ onClosed = NewHandler(cx, onClosedFunc, state);
+ if (!onClosed) {
+ return false;
+ }
+
+ onErrored = NewHandler(cx, onErroredFunc, state);
+ if (!onErrored) {
+ return false;
+ }
+
+ return JS::AddPromiseReactions(cx, unwrappedClosedPromise, onClosed,
+ onErrored);
+ };
+
+ unwrappedClosedPromise =
+ GetClosedPromise(cx, unwrappedSource, UnwrapReaderFromStream);
+ if (!unwrappedClosedPromise) {
+ return false;
+ }
+
+ if (!ReactWhenClosedOrErrored(cx, OnSourceClosed, OnSourceErrored)) {
+ return false;
+ }
+
+ unwrappedClosedPromise =
+ GetClosedPromise(cx, unwrappedDest, UnwrapWriterFromStream);
+ if (!unwrappedClosedPromise) {
+ return false;
+ }
+
+ if (!ReactWhenClosedOrErrored(cx, OnDestClosed, OnDestErrored)) {
+ return false;
+ }
+ }
+
+ return ReadFromSource(cx, state);
+}
+
+/**
+ * Stream spec, 4.8.1. ReadableStreamPipeTo ( source, dest,
+ * preventClose, preventAbort,
+ * preventCancel[, signal] )
+ * Step 14.1 abortAlgorithm.
+ */
+static MOZ_MUST_USE bool PerformAbortAlgorithm(JSContext* cx,
+ Handle<PipeToState*> state) {
+ cx->check(state);
+
+ // Step 14.1: Let abortAlgorithm be the following steps:
+ // Step 14.1.1: Let error be a new "AbortError" DOMException.
+ // Step 14.1.2: Let actions be an empty ordered set.
+ // Step 14.1.3: If preventAbort is false, append the following action to
+ // actions:
+ // Step 14.1.3.1: If dest.[[state]] is "writable", return
+ // ! WritableStreamAbort(dest, error).
+ // Step 14.1.3.2: Otherwise, return a promise resolved with undefined.
+ // Step 14.1.4: If preventCancel is false, append the following action action
+ // to actions:
+ // Step 14.1.4.1: If source.[[state]] is "readable", return
+ // ! ReadableStreamCancel(source, error).
+ // Step 14.1.4.2: Otherwise, return a promise resolved with undefined.
+ // Step 14.1.5: Shutdown with an action consisting of getting a promise to
+ // wait for all of the actions in actions, and with error.
+ // XXX jwalden
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED,
+ "abortAlgorithm steps");
+ return false;
+}
+
+/**
+ * Stream spec, 3.4.11. ReadableStreamPipeTo ( source, dest,
+ * preventClose, preventAbort,
+ * preventCancel, signal )
+ * Steps 4-11, 13-14.
+ */
+/* static */ PipeToState* PipeToState::create(
+ JSContext* cx, Handle<PromiseObject*> promise,
+ Handle<ReadableStream*> unwrappedSource,
+ Handle<WritableStream*> unwrappedDest, bool preventClose, bool preventAbort,
+ bool preventCancel, Handle<JSObject*> signal) {
+ cx->check(promise);
+ cx->check(signal);
+
+ Rooted<PipeToState*> state(cx,
+ NewTenuredBuiltinClassInstance<PipeToState>(cx));
+ if (!state) {
+ return nullptr;
+ }
+
+ // Step 4. Assert: signal is undefined or signal is an instance of the
+ // AbortSignal interface.
+ MOZ_ASSERT(state->getFixedSlot(Slot_Signal).isUndefined());
+ if (signal) {
+ // |signal| is double-checked to be an |AbortSignal| further down.
+ state->initFixedSlot(Slot_Signal, ObjectValue(*signal));
+ }
+
+ // Step 5: Assert: ! IsReadableStreamLocked(source) is false.
+ MOZ_ASSERT(!unwrappedSource->locked());
+
+ // Step 6: Assert: ! IsWritableStreamLocked(dest) is false.
+ MOZ_ASSERT(!unwrappedDest->isLocked());
+
+ MOZ_ASSERT(state->getFixedSlot(Slot_Promise).isUndefined());
+ state->initFixedSlot(Slot_Promise, ObjectValue(*promise));
+
+ // Step 7: If ! IsReadableByteStreamController(
+ // source.[[readableStreamController]]) is true, let reader
+ // be either ! AcquireReadableStreamBYOBReader(source) or
+ // ! AcquireReadableStreamDefaultReader(source), at the user agent’s
+ // discretion.
+ // Step 8: Otherwise, let reader be
+ // ! AcquireReadableStreamDefaultReader(source).
+ // We don't implement byte streams, so we always acquire a default reader.
+ {
+ ReadableStreamDefaultReader* reader = CreateReadableStreamDefaultReader(
+ cx, unwrappedSource, ForAuthorCodeBool::No);
+ if (!reader) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(state->getFixedSlot(Slot_Reader).isUndefined());
+ state->initFixedSlot(Slot_Reader, ObjectValue(*reader));
+ }
+
+ // Step 9: Let writer be ! AcquireWritableStreamDefaultWriter(dest).
+ {
+ WritableStreamDefaultWriter* writer =
+ CreateWritableStreamDefaultWriter(cx, unwrappedDest);
+ if (!writer) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(state->getFixedSlot(Slot_Writer).isUndefined());
+ state->initFixedSlot(Slot_Writer, ObjectValue(*writer));
+ }
+
+ // Step 10: Set source.[[disturbed]] to true.
+ unwrappedSource->setDisturbed();
+
+ state->initFlags(preventClose, preventAbort, preventCancel);
+ MOZ_ASSERT(state->preventClose() == preventClose);
+ MOZ_ASSERT(state->preventAbort() == preventAbort);
+ MOZ_ASSERT(state->preventCancel() == preventCancel);
+
+ // Step 11: Let shuttingDown be false.
+ MOZ_ASSERT(!state->shuttingDown(), "should be set to false by initFlags");
+
+ // Step 12 ("Let promise be a new promise.") was performed by the caller and
+ // |promise| was its result.
+
+ // XXX This used to be step 13 but is now step 14, all the step-comments of
+ // the overall algorithm need renumbering.
+ // Step 13: If signal is not undefined,
+ if (signal) {
+ // Step 14.2: If signal’s aborted flag is set, perform abortAlgorithm and
+ // return promise.
+ bool aborted;
+ {
+ // Sadly, we can't assert |signal| is an |AbortSignal| here because it
+ // could have become a nuked CCW since it was type-checked.
+ JSObject* unwrappedSignal = UnwrapSignalFromPipeToState(cx, state);
+ if (!unwrappedSignal) {
+ return nullptr;
+ }
+
+ JSRuntime* rt = cx->runtime();
+ MOZ_ASSERT(unwrappedSignal->hasClass(rt->maybeAbortSignalClass()));
+
+ AutoRealm ar(cx, unwrappedSignal);
+ aborted = rt->abortSignalIsAborted(unwrappedSignal);
+ }
+ if (aborted) {
+ if (!PerformAbortAlgorithm(cx, state)) {
+ return nullptr;
+ }
+
+ // Returning |state| here will cause |promise| to be returned by the
+ // overall algorithm.
+ return state;
+ }
+
+ // Step 14.3: Add abortAlgorithm to signal.
+ // XXX jwalden need JSAPI to add an algorithm/steps to an AbortSignal
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED,
+ "adding abortAlgorithm to signal");
+ return nullptr;
+ }
+
+ // Step 14: In parallel, using reader and writer, read all chunks from source
+ // and write them to dest.
+ if (!StartPiping(cx, state, unwrappedSource, unwrappedDest)) {
+ return nullptr;
+ }
+
+ return state;
+}
+
+const JSClass PipeToState::class_ = {"PipeToState",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
diff --git a/js/src/builtin/streams/PipeToState.h b/js/src/builtin/streams/PipeToState.h
new file mode 100644
index 0000000000..b15cacb28f
--- /dev/null
+++ b/js/src/builtin/streams/PipeToState.h
@@ -0,0 +1,291 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* ReadableStream.prototype.pipeTo state. */
+
+#ifndef builtin_streams_PipeToState_h
+#define builtin_streams_PipeToState_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/WrappingOperations.h" // mozilla::WrapToSigned
+
+#include <stdint.h> // uint32_t
+
+#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStreamDefaultReader
+#include "builtin/streams/WritableStreamDefaultWriter.h" // js::WritableStreamDefaultWriter
+#include "js/Class.h" // JSClass
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::Int32Value, JS::ObjectValue
+#include "vm/NativeObject.h" // js::NativeObject
+#include "vm/PromiseObject.h" // js::PromiseObject
+
+class JS_PUBLIC_API JSObject;
+
+namespace js {
+
+class ReadableStream;
+class WritableStream;
+
+/**
+ * PipeToState objects implement the local variables in Streams spec 3.4.11
+ * ReadableStreamPipeTo across all sub-operations that occur in that algorithm.
+ */
+class PipeToState : public NativeObject {
+ public:
+ /**
+ * Memory layout for PipeToState instances.
+ */
+ enum Slots {
+ /** Integer bit field of various flags. */
+ Slot_Flags = 0,
+
+ /**
+ * The promise resolved or rejected when the overall pipe-to operation
+ * completes.
+ *
+ * This promise is created directly under |ReadableStreamPipeTo|, at the
+ * same time the corresponding |PipeToState| is created, so it is always
+ * same-compartment with this and is guaranteed to hold a |PromiseObject*|
+ * if initialization succeeded.
+ */
+ Slot_Promise,
+
+ /**
+ * A |ReadableStreamDefaultReader| used to read from the readable stream
+ * being piped from.
+ *
+ * This reader is created at the same time as its |PipeToState|, so this
+ * reader is same-compartment with this and is guaranteed to be a
+ * |ReadableStreamDefaultReader*| if initialization succeeds.
+ */
+ Slot_Reader,
+
+ /**
+ * A |WritableStreamDefaultWriter| used to write to the writable stream
+ * being piped to.
+ *
+ * This writer is created at the same time as its |PipeToState|, so this
+ * writer is same-compartment with this and is guaranteed to be a
+ * |WritableStreamDefaultWriter*| if initialization succeeds.
+ */
+ Slot_Writer,
+
+ /**
+ * The |PromiseObject*| of the last write performed to the destinationg
+ * |WritableStream| using the writer in |Slot_Writer|. If no writes have
+ * yet been performed, this slot contains |undefined|.
+ *
+ * This promise is created inside a handler function in the same compartment
+ * and realm as this |PipeToState|, so it is always a |PromiseObject*| and
+ * never a wrapper around one.
+ */
+ Slot_LastWriteRequest,
+
+ /**
+ * Either |undefined| or an |AbortSignal| instance specified by the user,
+ * whose controller may be used to externally abort the piping algorithm.
+ *
+ * This signal is user-provided, so it may be a wrapper around an
+ * |AbortSignal| not from the same compartment as this.
+ */
+ Slot_Signal,
+
+ SlotCount,
+ };
+
+ // The set of possible actions to be passed to the "shutdown with an action"
+ // algorithm.
+ //
+ // We store actions as numbers because 1) handler functions already devote
+ // their extra slots to target and extra value; and 2) storing a full function
+ // pointer would require an extra slot, while storing as number packs into
+ // existing flag storage.
+ enum class ShutdownAction {
+ /** The action used during |abortAlgorithm|.*/
+ AbortAlgorithm,
+
+ /**
+ * The action taken when |source| errors and aborting is not prevented, to
+ * abort |dest| with |source|'s error.
+ */
+ AbortDestStream,
+
+ /**
+ * The action taken when |dest| becomes errored or closed and canceling is
+ * not prevented, to cancel |source| with |dest|'s error.
+ */
+ CancelSource,
+
+ /**
+ * The action taken when |source| closes and closing is not prevented, to
+ * close the writer while propagating any error in it.
+ */
+ CloseWriterWithErrorPropagation,
+
+ };
+
+ private:
+ enum Flags : uint32_t {
+ /**
+ * The action passed to the "shutdown with an action" algorithm.
+ *
+ * Note that because only the first "shutdown" and "shutdown with an action"
+ * operation has any effect, we can store this action in |PipeToState| in
+ * the first invocation of either operation without worrying about it being
+ * overwritten.
+ *
+ * Purely for convenience, we encode this in the lowest bits so that the
+ * result of a mask is the underlying value of the correct |ShutdownAction|.
+ */
+ Flag_ShutdownActionBits = 0b0000'0011,
+
+ Flag_ShuttingDown = 0b0000'0100,
+
+ Flag_PendingRead = 0b0000'1000,
+#ifdef DEBUG
+ Flag_PendingReadWouldBeRejected = 0b0001'0000,
+#endif
+
+ Flag_PreventClose = 0b0010'0000,
+ Flag_PreventAbort = 0b0100'0000,
+ Flag_PreventCancel = 0b1000'0000,
+ };
+
+ uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
+ void setFlags(uint32_t flags) {
+ setFixedSlot(Slot_Flags, JS::Int32Value(mozilla::WrapToSigned(flags)));
+ }
+
+ // Flags start out zeroed, so the initially-stored shutdown action value will
+ // be this value. (This is also the value of an *initialized* shutdown
+ // action, but it doesn't seem worth the trouble to store an extra bit to
+ // detect this specific action being recorded multiple times, purely for
+ // assertions.)
+ static constexpr ShutdownAction UninitializedAction =
+ ShutdownAction::AbortAlgorithm;
+
+ static_assert(Flag_ShutdownActionBits & 1,
+ "shutdown action bits must be low-order bits so that we can "
+ "cast ShutdownAction values directly to bits to store");
+
+ static constexpr uint32_t MaxAction =
+ static_cast<uint32_t>(ShutdownAction::CloseWriterWithErrorPropagation);
+
+ static_assert(MaxAction <= Flag_ShutdownActionBits,
+ "max action shouldn't overflow available bits to store it");
+
+ public:
+ static const JSClass class_;
+
+ PromiseObject* promise() const {
+ return &getFixedSlot(Slot_Promise).toObject().as<PromiseObject>();
+ }
+
+ ReadableStreamDefaultReader* reader() const {
+ return &getFixedSlot(Slot_Reader)
+ .toObject()
+ .as<ReadableStreamDefaultReader>();
+ }
+
+ WritableStreamDefaultWriter* writer() const {
+ return &getFixedSlot(Slot_Writer)
+ .toObject()
+ .as<WritableStreamDefaultWriter>();
+ }
+
+ PromiseObject* lastWriteRequest() const {
+ const auto& slot = getFixedSlot(Slot_LastWriteRequest);
+ if (slot.isUndefined()) {
+ return nullptr;
+ }
+
+ return &slot.toObject().as<PromiseObject>();
+ }
+
+ void updateLastWriteRequest(PromiseObject* writeRequest) {
+ MOZ_ASSERT(writeRequest != nullptr);
+ setFixedSlot(Slot_LastWriteRequest, JS::ObjectValue(*writeRequest));
+ }
+
+ bool hasSignal() const {
+ JS::Value v = getFixedSlot(Slot_Signal);
+ MOZ_ASSERT(v.isObject() || v.isUndefined());
+ return v.isObject();
+ }
+
+ bool shuttingDown() const { return flags() & Flag_ShuttingDown; }
+ void setShuttingDown() {
+ MOZ_ASSERT(!shuttingDown());
+ setFlags(flags() | Flag_ShuttingDown);
+ }
+
+ ShutdownAction shutdownAction() const {
+ MOZ_ASSERT(shuttingDown(),
+ "must be shutting down to have a shutdown action");
+
+ uint32_t bits = flags() & Flag_ShutdownActionBits;
+ static_assert(Flag_ShutdownActionBits & 1,
+ "shutdown action bits are assumed to be low-order bits that "
+ "don't have to be shifted down to ShutdownAction's range");
+
+ MOZ_ASSERT(bits <= MaxAction, "bits must encode a valid action");
+
+ return static_cast<ShutdownAction>(bits);
+ }
+
+ void setShutdownAction(ShutdownAction action) {
+ MOZ_ASSERT(shuttingDown(),
+ "must be protected by the |shuttingDown| boolean to save the "
+ "shutdown action");
+ MOZ_ASSERT(shutdownAction() == UninitializedAction,
+ "should only set shutdown action once");
+
+ setFlags(flags() | static_cast<uint32_t>(action));
+ }
+
+ bool preventClose() const { return flags() & Flag_PreventClose; }
+ bool preventAbort() const { return flags() & Flag_PreventAbort; }
+ bool preventCancel() const { return flags() & Flag_PreventCancel; }
+
+ bool hasPendingRead() const { return flags() & Flag_PendingRead; }
+ void setPendingRead() {
+ MOZ_ASSERT(!hasPendingRead());
+ setFlags(flags() | Flag_PendingRead);
+ }
+ void clearPendingRead() {
+ MOZ_ASSERT(hasPendingRead());
+ setFlags(flags() & ~Flag_PendingRead);
+ }
+
+#ifdef DEBUG
+ bool pendingReadWouldBeRejected() const {
+ return flags() & Flag_PendingReadWouldBeRejected;
+ }
+ void setPendingReadWouldBeRejected() {
+ MOZ_ASSERT(!pendingReadWouldBeRejected());
+ setFlags(flags() | Flag_PendingReadWouldBeRejected);
+ }
+#endif
+
+ void initFlags(bool preventClose, bool preventAbort, bool preventCancel) {
+ MOZ_ASSERT(getFixedSlot(Slot_Flags).isUndefined());
+
+ uint32_t flagBits = (preventClose ? Flag_PreventClose : 0) |
+ (preventAbort ? Flag_PreventAbort : 0) |
+ (preventCancel ? Flag_PreventCancel : 0);
+ setFlags(flagBits);
+ }
+
+ static PipeToState* create(JSContext* cx, JS::Handle<PromiseObject*> promise,
+ JS::Handle<ReadableStream*> unwrappedSource,
+ JS::Handle<WritableStream*> unwrappedDest,
+ bool preventClose, bool preventAbort,
+ bool preventCancel, JS::Handle<JSObject*> signal);
+};
+
+} // namespace js
+
+#endif // builtin_streams_PipeToState_h
diff --git a/js/src/builtin/streams/PullIntoDescriptor.cpp b/js/src/builtin/streams/PullIntoDescriptor.cpp
new file mode 100644
index 0000000000..1b6c99a45b
--- /dev/null
+++ b/js/src/builtin/streams/PullIntoDescriptor.cpp
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Pull descriptor objects for tracking byte stream pull-into requests. */
+
+#include "builtin/streams/PullIntoDescriptor.h"
+
+#include <stdint.h> // uint32_t
+
+#include "js/Class.h" // JSClass, JSCLASS_HAS_RESERVED_SLOTS
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+
+#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
+
+using js::PullIntoDescriptor;
+
+using JS::Handle;
+using JS::Int32Value;
+using JS::ObjectOrNullValue;
+using JS::ObjectValue;
+using JS::Rooted;
+
+/* static */ PullIntoDescriptor* PullIntoDescriptor::create(
+ JSContext* cx, Handle<ArrayBufferObject*> buffer, uint32_t byteOffset,
+ uint32_t byteLength, uint32_t bytesFilled, uint32_t elementSize,
+ Handle<JSObject*> ctor, ReaderType readerType) {
+ Rooted<PullIntoDescriptor*> descriptor(
+ cx, NewBuiltinClassInstance<PullIntoDescriptor>(cx));
+ if (!descriptor) {
+ return nullptr;
+ }
+
+ descriptor->setFixedSlot(Slot_buffer, ObjectValue(*buffer));
+ descriptor->setFixedSlot(Slot_Ctor, ObjectOrNullValue(ctor));
+ descriptor->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset));
+ descriptor->setFixedSlot(Slot_ByteLength, Int32Value(byteLength));
+ descriptor->setFixedSlot(Slot_BytesFilled, Int32Value(bytesFilled));
+ descriptor->setFixedSlot(Slot_ElementSize, Int32Value(elementSize));
+ descriptor->setFixedSlot(Slot_ReaderType,
+ Int32Value(static_cast<int32_t>(readerType)));
+ return descriptor;
+}
+
+const JSClass PullIntoDescriptor::class_ = {
+ "PullIntoDescriptor", JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
diff --git a/js/src/builtin/streams/PullIntoDescriptor.h b/js/src/builtin/streams/PullIntoDescriptor.h
new file mode 100644
index 0000000000..41378dd92e
--- /dev/null
+++ b/js/src/builtin/streams/PullIntoDescriptor.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Pull descriptor objects for tracking byte stream pull-into requests. */
+
+#ifndef builtin_streams_PullIntoDescriptor_h
+#define builtin_streams_PullIntoDescriptor_h
+
+#include <stdint.h> // int32_t, uint32_t
+
+#include "js/Class.h" // JSClass
+#include "vm/ArrayBufferObject.h" // js::ArrayBufferObject;
+#include "vm/NativeObject.h" // js::NativeObject
+
+namespace js {
+
+enum class ReaderType : int32_t { Default = 0, BYOB = 1 };
+
+class PullIntoDescriptor : public NativeObject {
+ private:
+ enum Slots {
+ Slot_buffer,
+ Slot_ByteOffset,
+ Slot_ByteLength,
+ Slot_BytesFilled,
+ Slot_ElementSize,
+ Slot_Ctor,
+ Slot_ReaderType,
+ SlotCount
+ };
+
+ public:
+ static const JSClass class_;
+
+ ArrayBufferObject* buffer() {
+ return &getFixedSlot(Slot_buffer).toObject().as<ArrayBufferObject>();
+ }
+ void setBuffer(ArrayBufferObject* buffer) {
+ setFixedSlot(Slot_buffer, ObjectValue(*buffer));
+ }
+ JSObject* ctor() { return getFixedSlot(Slot_Ctor).toObjectOrNull(); }
+ uint32_t byteOffset() const {
+ return getFixedSlot(Slot_ByteOffset).toInt32();
+ }
+ uint32_t byteLength() const {
+ return getFixedSlot(Slot_ByteLength).toInt32();
+ }
+ uint32_t bytesFilled() const {
+ return getFixedSlot(Slot_BytesFilled).toInt32();
+ }
+ void setBytesFilled(int32_t bytes) {
+ setFixedSlot(Slot_BytesFilled, Int32Value(bytes));
+ }
+ uint32_t elementSize() const {
+ return getFixedSlot(Slot_ElementSize).toInt32();
+ }
+ ReaderType readerType() const {
+ int32_t n = getFixedSlot(Slot_ReaderType).toInt32();
+ MOZ_ASSERT(n == int32_t(ReaderType::Default) ||
+ n == int32_t(ReaderType::BYOB));
+ return ReaderType(n);
+ }
+
+ static PullIntoDescriptor* create(JSContext* cx,
+ JS::Handle<ArrayBufferObject*> buffer,
+ uint32_t byteOffset, uint32_t byteLength,
+ uint32_t bytesFilled, uint32_t elementSize,
+ JS::Handle<JSObject*> ctor,
+ ReaderType readerType);
+};
+
+} // namespace js
+
+#endif // builtin_streams_PullIntoDescriptor_h
diff --git a/js/src/builtin/streams/QueueWithSizes-inl.h b/js/src/builtin/streams/QueueWithSizes-inl.h
new file mode 100644
index 0000000000..deb1aabbd5
--- /dev/null
+++ b/js/src/builtin/streams/QueueWithSizes-inl.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Queue-with-sizes operations. */
+
+#ifndef builtin_streams_QueueWithSizes_inl_h
+#define builtin_streams_QueueWithSizes_inl_h
+
+#include "builtin/streams/QueueWithSizes.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::Value
+#include "vm/List.h" // js::ListObject
+
+#include "vm/List-inl.h" // js::ListObject::*
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+namespace detail {
+
+// The *internal* representation of a queue-with-sizes is a List of even length
+// where elements (2 * n, 2 * n + 1) represent the nth (value, size) element in
+// the queue.
+
+inline JS::Value QueueFirstValue(ListObject* unwrappedQueue) {
+ MOZ_ASSERT(!unwrappedQueue->isEmpty(),
+ "can't examine first value in an empty queue-with-sizes");
+ MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
+ "queue-with-sizes must consist of (value, size) element pairs and "
+ "so must have even length");
+ return unwrappedQueue->get(0);
+}
+
+inline double QueueFirstSize(ListObject* unwrappedQueue) {
+ MOZ_ASSERT(!unwrappedQueue->isEmpty(),
+ "can't examine first value in an empty queue-with-sizes");
+ MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
+ "queue-with-sizes must consist of (value, size) element pairs and "
+ "so must have even length");
+ return unwrappedQueue->get(1).toDouble();
+}
+
+inline void QueueRemoveFirstValueAndSize(ListObject* unwrappedQueue,
+ JSContext* cx) {
+ MOZ_ASSERT(!unwrappedQueue->isEmpty(),
+ "can't remove first value from an empty queue-with-sizes");
+ MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
+ "queue-with-sizes must consist of (value, size) element pairs and "
+ "so must have even length");
+ unwrappedQueue->popFirstPair(cx);
+}
+
+inline MOZ_MUST_USE bool QueueAppendValueAndSize(
+ JSContext* cx, JS::Handle<ListObject*> unwrappedQueue,
+ JS::Handle<JS::Value> value, double size) {
+ return unwrappedQueue->appendValueAndSize(cx, value, size);
+}
+
+} // namespace detail
+
+/**
+ * Streams spec, 6.2.3. PeekQueueValue ( container ) nothrow
+ */
+inline JS::Value PeekQueueValue(ListObject* queue) {
+ return detail::QueueFirstValue(queue);
+}
+
+} // namespace js
+
+#endif // builtin_streams_QueueWithSizes_inl_h
diff --git a/js/src/builtin/streams/QueueWithSizes.cpp b/js/src/builtin/streams/QueueWithSizes.cpp
new file mode 100644
index 0000000000..4b2ce84084
--- /dev/null
+++ b/js/src/builtin/streams/QueueWithSizes.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Queue-with-sizes operations. */
+
+#include "builtin/streams/QueueWithSizes-inl.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+#include "mozilla/FloatingPoint.h" // mozilla::Is{Infinite,NaN}
+
+#include "jsapi.h" // JS_ReportErrorNumberASCII
+
+#include "builtin/streams/StreamController.h" // js::StreamController
+#include "js/Class.h" // JSClass, JSCLASS_HAS_RESERVED_SLOTS
+#include "js/Conversions.h" // JS::ToNumber
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/RootingAPI.h" // JS::Rooted
+#include "js/Value.h" // JS::Value, JS::{Number,Object}Value
+#include "vm/Compartment.h" // JSCompartment
+#include "vm/JSContext.h" // JSContext
+#include "vm/List.h" // js::ListObject
+#include "vm/NativeObject.h" // js::NativeObject
+
+#include "vm/Compartment-inl.h" // JSCompartment::wrap
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
+#include "vm/List-inl.h" // js::ListObject::*, js::StoreNewListInFixedSlot
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using JS::Handle;
+using JS::MutableHandle;
+using JS::NumberValue;
+using JS::ObjectValue;
+using JS::Rooted;
+using JS::ToNumber;
+using JS::Value;
+
+/*** 6.2. Queue-with-sizes operations ***************************************/
+
+/**
+ * Streams spec, 6.2.1. DequeueValue ( container ) nothrow
+ */
+MOZ_MUST_USE bool js::DequeueValue(JSContext* cx,
+ Handle<StreamController*> unwrappedContainer,
+ MutableHandle<Value> chunk) {
+ // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots (implicit).
+ // Step 2: Assert: queue is not empty.
+ Rooted<ListObject*> unwrappedQueue(cx, unwrappedContainer->queue());
+
+ // Step 3. Let pair be the first element of queue.
+ double chunkSize = detail::QueueFirstSize(unwrappedQueue);
+ chunk.set(detail::QueueFirstValue(unwrappedQueue));
+
+ // Step 4. Remove pair from queue, shifting all other elements downward
+ // (so that the second becomes the first, and so on).
+ detail::QueueRemoveFirstValueAndSize(unwrappedQueue, cx);
+
+ // Step 5: Set container.[[queueTotalSize]] to
+ // container.[[queueTotalSize]] − pair.[[size]].
+ // Step 6: If container.[[queueTotalSize]] < 0, set
+ // container.[[queueTotalSize]] to 0.
+ // (This can occur due to rounding errors.)
+ double totalSize = unwrappedContainer->queueTotalSize();
+ totalSize -= chunkSize;
+ if (totalSize < 0) {
+ totalSize = 0;
+ }
+ unwrappedContainer->setQueueTotalSize(totalSize);
+
+ // Step 7: Return pair.[[value]].
+ return cx->compartment()->wrap(cx, chunk);
+}
+
+void js::DequeueValue(StreamController* unwrappedContainer, JSContext* cx) {
+ // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots (implicit).
+ // Step 2: Assert: queue is not empty.
+ ListObject* unwrappedQueue = unwrappedContainer->queue();
+
+ // Step 3. Let pair be the first element of queue.
+ // (The value is being discarded, so all we must extract is the size.)
+ double chunkSize = detail::QueueFirstSize(unwrappedQueue);
+
+ // Step 4. Remove pair from queue, shifting all other elements downward
+ // (so that the second becomes the first, and so on).
+ detail::QueueRemoveFirstValueAndSize(unwrappedQueue, cx);
+
+ // Step 5: Set container.[[queueTotalSize]] to
+ // container.[[queueTotalSize]] − pair.[[size]].
+ // Step 6: If container.[[queueTotalSize]] < 0, set
+ // container.[[queueTotalSize]] to 0.
+ // (This can occur due to rounding errors.)
+ double totalSize = unwrappedContainer->queueTotalSize();
+ totalSize -= chunkSize;
+ if (totalSize < 0) {
+ totalSize = 0;
+ }
+ unwrappedContainer->setQueueTotalSize(totalSize);
+
+ // Step 7: Return pair.[[value]]. (omitted because not used)
+}
+
+/**
+ * Streams spec, 6.2.2. EnqueueValueWithSize ( container, value, size ) throws
+ */
+MOZ_MUST_USE bool js::EnqueueValueWithSize(
+ JSContext* cx, Handle<StreamController*> unwrappedContainer,
+ Handle<Value> value, Handle<Value> sizeVal) {
+ cx->check(value, sizeVal);
+
+ // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots (implicit).
+ // Step 2: Let size be ? ToNumber(size).
+ double size;
+ if (!ToNumber(cx, sizeVal, &size)) {
+ return false;
+ }
+
+ // Step 3: If ! IsFiniteNonNegativeNumber(size) is false, throw a RangeError
+ // exception.
+ if (size < 0 || mozilla::IsNaN(size) || mozilla::IsInfinite(size)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "size");
+ return false;
+ }
+
+ // Step 4: Append Record {[[value]]: value, [[size]]: size} as the last
+ // element of container.[[queue]].
+ {
+ AutoRealm ar(cx, unwrappedContainer);
+ Rooted<ListObject*> unwrappedQueue(cx, unwrappedContainer->queue());
+ Rooted<Value> wrappedVal(cx, value);
+ if (!cx->compartment()->wrap(cx, &wrappedVal)) {
+ return false;
+ }
+
+ if (!detail::QueueAppendValueAndSize(cx, unwrappedQueue, wrappedVal,
+ size)) {
+ return false;
+ }
+ }
+
+ // Step 5: Set container.[[queueTotalSize]] to
+ // container.[[queueTotalSize]] + size.
+ unwrappedContainer->setQueueTotalSize(unwrappedContainer->queueTotalSize() +
+ size);
+
+ return true;
+}
+
+/**
+ * Streams spec, 6.2.4. ResetQueue ( container ) nothrow
+ */
+MOZ_MUST_USE bool js::ResetQueue(JSContext* cx,
+ Handle<StreamController*> unwrappedContainer) {
+ // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots (implicit).
+ // Step 2: Set container.[[queue]] to a new empty List.
+ if (!StoreNewListInFixedSlot(cx, unwrappedContainer,
+ StreamController::Slot_Queue)) {
+ return false;
+ }
+
+ // Step 3: Set container.[[queueTotalSize]] to 0.
+ unwrappedContainer->setQueueTotalSize(0);
+
+ return true;
+}
diff --git a/js/src/builtin/streams/QueueWithSizes.h b/js/src/builtin/streams/QueueWithSizes.h
new file mode 100644
index 0000000000..c781a46bfd
--- /dev/null
+++ b/js/src/builtin/streams/QueueWithSizes.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Queue-with-sizes operations. */
+
+#ifndef builtin_streams_QueueWithSizes_h
+#define builtin_streams_QueueWithSizes_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jstypes.h" // JS_PUBLIC_API
+#include "js/RootingAPI.h" // JS::{,Mutable}Handle
+#include "js/Value.h" // JS::Value
+#include "vm/List.h" // js::ListObject
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+class StreamController;
+
+/**
+ * Streams spec, 6.2.1. DequeueValue ( container ) nothrow
+ */
+extern MOZ_MUST_USE bool DequeueValue(
+ JSContext* cx, JS::Handle<StreamController*> unwrappedContainer,
+ JS::MutableHandle<JS::Value> chunk);
+
+/**
+ * Streams spec, 6.2.1. DequeueValue ( container ) nothrow
+ * when the dequeued value is ignored.
+ */
+extern void DequeueValue(StreamController* unwrappedContainer, JSContext* cx);
+
+/**
+ * Streams spec, 6.2.2. EnqueueValueWithSize ( container, value, size ) throws
+ */
+extern MOZ_MUST_USE bool EnqueueValueWithSize(
+ JSContext* cx, JS::Handle<StreamController*> unwrappedContainer,
+ JS::Handle<JS::Value> value, JS::Handle<JS::Value> sizeVal);
+
+/**
+ * Streams spec, 6.2.4. ResetQueue ( container ) nothrow
+ */
+extern MOZ_MUST_USE bool ResetQueue(
+ JSContext* cx, JS::Handle<StreamController*> unwrappedContainer);
+
+inline bool QueueIsEmpty(ListObject* unwrappedQueue) {
+ if (unwrappedQueue->isEmpty()) {
+ return true;
+ }
+
+ MOZ_ASSERT((unwrappedQueue->length() % 2) == 0,
+ "queue-with-sizes must consist of (value, size) element pairs and "
+ "so must have even length");
+ return false;
+}
+
+} // namespace js
+
+#endif // builtin_streams_QueueWithSizes_h
diff --git a/js/src/builtin/streams/QueueingStrategies.cpp b/js/src/builtin/streams/QueueingStrategies.cpp
new file mode 100644
index 0000000000..e6b625e24e
--- /dev/null
+++ b/js/src/builtin/streams/QueueingStrategies.cpp
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Queuing strategies. */
+
+#include "builtin/streams/QueueingStrategies.h"
+
+#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/Class.h" // JS::ObjectOpResult, JS_NULL_CLASS_OPS
+#include "js/PropertySpec.h" // JS{Property,Function}Spec, JS_FN, JS_FS_END, JS_PS_END
+#include "js/ProtoKey.h" // JSProto_{ByteLength,Count}QueuingStrategy
+#include "js/RootingAPI.h" // JS::{Handle,Rooted}
+#include "vm/JSObject.h" // js::GetPrototypeFromBuiltinConstructor
+#include "vm/ObjectOperations.h" // js::{Define,Get}Property
+#include "vm/Runtime.h" // JSAtomState
+#include "vm/StringType.h" // js::NameToId, PropertyName
+
+#include "vm/JSObject-inl.h" // js::NewObjectWithClassProto
+#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing
+
+using js::ByteLengthQueuingStrategy;
+using js::CountQueuingStrategy;
+using js::PropertyName;
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::ObjectOpResult;
+using JS::Rooted;
+using JS::ToObject;
+using JS::Value;
+
+/*** 6.1. Queuing strategies ************************************************/
+
+/**
+ * ECMA-262 7.3.4 CreateDataProperty(O, P, V)
+ */
+static MOZ_MUST_USE bool CreateDataProperty(JSContext* cx,
+ Handle<JSObject*> obj,
+ Handle<PropertyName*> key,
+ Handle<Value> value,
+ ObjectOpResult& result) {
+ Rooted<jsid> id(cx, js::NameToId(key));
+ Rooted<JS::PropertyDescriptor> desc(cx);
+ desc.setDataDescriptor(value, JSPROP_ENUMERATE);
+ return js::DefineProperty(cx, obj, id, desc, result);
+}
+
+// Streams spec, 6.1.2.2. new ByteLengthQueuingStrategy({ highWaterMark })
+bool js::ByteLengthQueuingStrategy::constructor(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ByteLengthQueuingStrategy")) {
+ return false;
+ }
+
+ // Implicit in the spec: Create the new strategy object.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(
+ cx, args, JSProto_ByteLengthQueuingStrategy, &proto)) {
+ return false;
+ }
+ Rooted<JSObject*> strategy(
+ cx, NewObjectWithClassProto<ByteLengthQueuingStrategy>(cx, proto));
+ if (!strategy) {
+ return false;
+ }
+
+ // Implicit in the spec: Argument destructuring.
+ Rooted<JSObject*> argObj(cx, ToObject(cx, args.get(0)));
+ if (!argObj) {
+ return false;
+ }
+ Rooted<Value> highWaterMark(cx);
+ if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark,
+ &highWaterMark)) {
+ return false;
+ }
+
+ // Step 1: Perform ! CreateDataProperty(this, "highWaterMark",
+ // highWaterMark).
+ ObjectOpResult ignored;
+ if (!CreateDataProperty(cx, strategy, cx->names().highWaterMark,
+ highWaterMark, ignored)) {
+ return false;
+ }
+
+ args.rval().setObject(*strategy);
+ return true;
+}
+
+// Streams spec 6.1.2.3.1. size ( chunk )
+static bool ByteLengthQueuingStrategy_size(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: Return ? GetV(chunk, "byteLength").
+ return GetProperty(cx, args.get(0), cx->names().byteLength, args.rval());
+}
+
+static const JSPropertySpec ByteLengthQueuingStrategy_properties[] = {
+ JS_PS_END};
+
+static const JSFunctionSpec ByteLengthQueuingStrategy_methods[] = {
+ JS_FN("size", ByteLengthQueuingStrategy_size, 1, 0), JS_FS_END};
+
+JS_STREAMS_CLASS_SPEC(ByteLengthQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);
+
+// Streams spec, 6.1.3.2. new CountQueuingStrategy({ highWaterMark })
+bool js::CountQueuingStrategy::constructor(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "CountQueuingStrategy")) {
+ return false;
+ }
+
+ // Implicit in the spec: Create the new strategy object.
+ RootedObject proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(
+ cx, args, JSProto_CountQueuingStrategy, &proto)) {
+ return false;
+ }
+ Rooted<CountQueuingStrategy*> strategy(
+ cx, NewObjectWithClassProto<CountQueuingStrategy>(cx, proto));
+ if (!strategy) {
+ return false;
+ }
+
+ // Implicit in the spec: Argument destructuring.
+ 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;
+ }
+
+ // Step 1: Perform ! CreateDataProperty(this, "highWaterMark", highWaterMark).
+ ObjectOpResult ignored;
+ if (!CreateDataProperty(cx, strategy, cx->names().highWaterMark,
+ highWaterMark, ignored)) {
+ return false;
+ }
+
+ args.rval().setObject(*strategy);
+ return true;
+}
+
+// Streams spec 6.1.3.3.1. size ( chunk )
+static bool CountQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: Return 1.
+ args.rval().setInt32(1);
+ return true;
+}
+
+static const JSPropertySpec CountQueuingStrategy_properties[] = {JS_PS_END};
+
+static const JSFunctionSpec CountQueuingStrategy_methods[] = {
+ JS_FN("size", CountQueuingStrategy_size, 0, 0), JS_FS_END};
+
+JS_STREAMS_CLASS_SPEC(CountQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);
diff --git a/js/src/builtin/streams/QueueingStrategies.h b/js/src/builtin/streams/QueueingStrategies.h
new file mode 100644
index 0000000000..c9c0db5d63
--- /dev/null
+++ b/js/src/builtin/streams/QueueingStrategies.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Queuing strategies. */
+
+#ifndef builtin_stream_QueueingStrategies_h
+#define builtin_stream_QueueingStrategies_h
+
+#include "js/Class.h" // JSClass, js::ClassSpec
+#include "js/Value.h" // JS::Value
+#include "vm/JSContext.h" // JSContext
+#include "vm/NativeObject.h" // js::NativeObject
+
+namespace js {
+
+class ByteLengthQueuingStrategy : public NativeObject {
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
+ static const ClassSpec classSpec_;
+ static const JSClass class_;
+ static const ClassSpec protoClassSpec_;
+ static const JSClass protoClass_;
+};
+
+class CountQueuingStrategy : public NativeObject {
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
+ static const ClassSpec classSpec_;
+ static const JSClass class_;
+ static const ClassSpec protoClassSpec_;
+ static const JSClass protoClass_;
+};
+
+} // namespace js
+
+#endif // builtin_stream_QueueingStrategies_h
diff --git a/js/src/builtin/streams/ReadableStream.cpp b/js/src/builtin/streams/ReadableStream.cpp
new file mode 100644
index 0000000000..ba167178c5
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStream.cpp
@@ -0,0 +1,552 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class ReadableStream. */
+
+#include "builtin/streams/ReadableStream.h"
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsapi.h" // JS_ReportErrorNumberASCII
+#include "jspubtd.h" // JSProto_ReadableStream
+
+#include "builtin/Array.h" // js::NewDenseFullyAllocatedArray
+#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
+#include "builtin/streams/MiscellaneousOperations.h" // js::MakeSizeAlgorithmFromSizeFunction, js::ValidateAndNormalizeHighWaterMark, js::ReturnPromiseRejectedWithPendingError
+#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller, js::ReadableByteStreamController
+#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::SetUpReadableStreamDefaultControllerFromUnderlyingSource
+#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStreamCancel
+#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStream{PipeTo,Tee}
+#include "builtin/streams/ReadableStreamReader.h" // js::CreateReadableStream{BYOB,Default}Reader, js::ForAuthorCodeBool
+#include "builtin/streams/WritableStream.h" // js::WritableStream
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/Class.h" // JSCLASS_PRIVATE_IS_NSISUPPORTS, JSCLASS_HAS_PRIVATE, JS_NULL_CLASS_OPS
+#include "js/Conversions.h" // JS::ToBoolean
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/PropertySpec.h" // JS{Function,Property}Spec, JS_FN, JS_PSG, JS_{FS,PS}_END
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted, js::CanGC
+#include "js/Stream.h" // JS::ReadableStream{Mode,UnderlyingSource}
+#include "js/Value.h" // JS::Value
+#include "vm/JSContext.h" // JSContext
+#include "vm/JSObject.h" // js::GetPrototypeFromBuiltinConstructor
+#include "vm/ObjectOperations.h" // js::GetProperty
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/Runtime.h" // JSAtomState, JSRuntime
+#include "vm/StringType.h" // js::EqualStrings, js::ToString
+
+#include "vm/Compartment-inl.h" // js::UnwrapAndTypeCheck{Argument,This,Value}
+#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
+#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing
+
+using js::CanGC;
+using js::ClassSpec;
+using js::CreateReadableStreamDefaultReader;
+using js::EqualStrings;
+using js::ForAuthorCodeBool;
+using js::GetErrorMessage;
+using js::NativeObject;
+using js::NewBuiltinClassInstance;
+using js::NewDenseFullyAllocatedArray;
+using js::PlainObject;
+using js::ReadableStream;
+using js::ReadableStreamPipeTo;
+using js::ReadableStreamTee;
+using js::ReturnPromiseRejectedWithPendingError;
+using js::ToString;
+using js::UnwrapAndTypeCheckArgument;
+using js::UnwrapAndTypeCheckThis;
+using js::UnwrapAndTypeCheckValue;
+using js::WritableStream;
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::ObjectValue;
+using JS::Rooted;
+using JS::Value;
+
+/*** 3.2. Class ReadableStream **********************************************/
+
+JS::ReadableStreamMode ReadableStream::mode() const {
+ ReadableStreamController* controller = this->controller();
+ if (controller->is<ReadableStreamDefaultController>()) {
+ return JS::ReadableStreamMode::Default;
+ }
+ return controller->as<ReadableByteStreamController>().hasExternalSource()
+ ? JS::ReadableStreamMode::ExternalSource
+ : JS::ReadableStreamMode::Byte;
+}
+
+ReadableStream* ReadableStream::createExternalSourceStream(
+ JSContext* cx, JS::ReadableStreamUnderlyingSource* source,
+ void* nsISupportsObject_alreadyAddreffed /* = nullptr */,
+ Handle<JSObject*> proto /* = nullptr */) {
+ Rooted<ReadableStream*> stream(
+ cx, create(cx, nsISupportsObject_alreadyAddreffed, proto));
+ if (!stream) {
+ return nullptr;
+ }
+
+ if (!SetUpExternalReadableByteStreamController(cx, stream, source)) {
+ return nullptr;
+ }
+
+ return stream;
+}
+
+/**
+ * Streams spec, 3.2.3. new ReadableStream(underlyingSource = {}, strategy = {})
+ */
+bool ReadableStream::constructor(JSContext* cx, unsigned argc, JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStream")) {
+ return false;
+ }
+
+ // Implicit in the spec: argument default values.
+ Rooted<Value> underlyingSource(cx, args.get(0));
+ if (underlyingSource.isUndefined()) {
+ JSObject* emptyObj = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!emptyObj) {
+ return false;
+ }
+ underlyingSource = ObjectValue(*emptyObj);
+ }
+
+ Rooted<Value> strategy(cx, args.get(1));
+ if (strategy.isUndefined()) {
+ JSObject* emptyObj = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!emptyObj) {
+ return false;
+ }
+ strategy = ObjectValue(*emptyObj);
+ }
+
+ // Implicit in the spec: Set this to
+ // OrdinaryCreateFromConstructor(NewTarget, ...).
+ // Step 1: Perform ! InitializeReadableStream(this).
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ReadableStream,
+ &proto)) {
+ return false;
+ }
+ Rooted<ReadableStream*> stream(cx,
+ ReadableStream::create(cx, nullptr, proto));
+ if (!stream) {
+ return false;
+ }
+
+ // Step 2: Let size be ? GetV(strategy, "size").
+ Rooted<Value> size(cx);
+ if (!GetProperty(cx, strategy, cx->names().size, &size)) {
+ return false;
+ }
+
+ // Step 3: Let highWaterMark be ? GetV(strategy, "highWaterMark").
+ Rooted<Value> highWaterMarkVal(cx);
+ if (!GetProperty(cx, strategy, cx->names().highWaterMark,
+ &highWaterMarkVal)) {
+ return false;
+ }
+
+ // Step 4: Let type be ? GetV(underlyingSource, "type").
+ Rooted<Value> type(cx);
+ if (!GetProperty(cx, underlyingSource, cx->names().type, &type)) {
+ return false;
+ }
+
+ // Step 5: Let typeString be ? ToString(type).
+ Rooted<JSString*> typeString(cx, ToString<CanGC>(cx, type));
+ if (!typeString) {
+ return false;
+ }
+
+ // Step 6: If typeString is "bytes",
+ bool equal;
+ if (!EqualStrings(cx, typeString, cx->names().bytes, &equal)) {
+ return false;
+ }
+ if (equal) {
+ // The rest of step 6 is unimplemented, since we don't support
+ // user-defined byte streams yet.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED);
+ return false;
+ }
+
+ // Step 7: Otherwise, if type is undefined,
+ if (type.isUndefined()) {
+ // Step 7.a: Let sizeAlgorithm be ? MakeSizeAlgorithmFromSizeFunction(size).
+ if (!MakeSizeAlgorithmFromSizeFunction(cx, size)) {
+ return false;
+ }
+
+ // Step 7.b: If highWaterMark is undefined, let highWaterMark be 1.
+ double highWaterMark;
+ if (highWaterMarkVal.isUndefined()) {
+ highWaterMark = 1;
+ } else {
+ // Step 7.c: Set highWaterMark to ?
+ // ValidateAndNormalizeHighWaterMark(highWaterMark).
+ if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal,
+ &highWaterMark)) {
+ return false;
+ }
+ }
+
+ // Step 7.d: Perform
+ // ? SetUpReadableStreamDefaultControllerFromUnderlyingSource(
+ // this, underlyingSource, highWaterMark, sizeAlgorithm).
+ if (!SetUpReadableStreamDefaultControllerFromUnderlyingSource(
+ cx, stream, underlyingSource, highWaterMark, size)) {
+ return false;
+ }
+
+ args.rval().setObject(*stream);
+ return true;
+ }
+
+ // Step 8: Otherwise, throw a RangeError exception.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG);
+ return false;
+}
+
+/**
+ * Streams spec, 3.2.5.1. get locked
+ */
+static MOZ_MUST_USE bool ReadableStream_locked(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "get locked"));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 2: Return ! IsReadableStreamLocked(this).
+ args.rval().setBoolean(unwrappedStream->locked());
+ return true;
+}
+
+/**
+ * Streams spec, 3.2.5.2. cancel ( reason )
+ */
+static MOZ_MUST_USE bool ReadableStream_cancel(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStream(this) is false, return a promise rejected
+ // with a TypeError exception.
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "cancel"));
+ if (!unwrappedStream) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: If ! IsReadableStreamLocked(this) is true, return a promise
+ // rejected with a TypeError exception.
+ if (unwrappedStream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_LOCKED_METHOD, "cancel");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamCancel(this, reason).
+ Rooted<JSObject*> cancelPromise(
+ cx, js::ReadableStreamCancel(cx, unwrappedStream, args.get(0)));
+ if (!cancelPromise) {
+ return false;
+ }
+ args.rval().setObject(*cancelPromise);
+ return true;
+}
+
+// Streams spec, 3.2.5.3.
+// getIterator({ preventCancel } = {})
+//
+// Not implemented.
+
+/**
+ * Streams spec, 3.2.5.4. getReader({ mode } = {})
+ */
+static MOZ_MUST_USE bool ReadableStream_getReader(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Implicit in the spec: Argument defaults and destructuring.
+ Rooted<Value> optionsVal(cx, args.get(0));
+ if (optionsVal.isUndefined()) {
+ JSObject* emptyObj = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!emptyObj) {
+ return false;
+ }
+ optionsVal.setObject(*emptyObj);
+ }
+ Rooted<Value> modeVal(cx);
+ if (!GetProperty(cx, optionsVal, cx->names().mode, &modeVal)) {
+ return false;
+ }
+
+ // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "getReader"));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 2: If mode is undefined, return
+ // ? AcquireReadableStreamDefaultReader(this, true).
+ Rooted<JSObject*> reader(cx);
+ if (modeVal.isUndefined()) {
+ reader = CreateReadableStreamDefaultReader(cx, unwrappedStream,
+ ForAuthorCodeBool::Yes);
+ } else {
+ // Step 3: Set mode to ? ToString(mode) (implicit).
+ Rooted<JSString*> mode(cx, ToString<CanGC>(cx, modeVal));
+ if (!mode) {
+ return false;
+ }
+
+ // Step 5: (If mode is not "byob",) Throw a RangeError exception.
+ bool equal;
+ if (!EqualStrings(cx, mode, cx->names().byob, &equal)) {
+ return false;
+ }
+ if (!equal) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_INVALID_READER_MODE);
+ return false;
+ }
+
+ // Step 4: If mode is "byob",
+ // return ? AcquireReadableStreamBYOBReader(this, true).
+ reader = CreateReadableStreamBYOBReader(cx, unwrappedStream,
+ ForAuthorCodeBool::Yes);
+ }
+
+ // Reordered second part of steps 2 and 4.
+ if (!reader) {
+ return false;
+ }
+ args.rval().setObject(*reader);
+ return true;
+}
+
+// Streams spec, 3.2.5.5.
+// pipeThrough({ writable, readable },
+// { preventClose, preventAbort, preventCancel, signal })
+//
+// Not implemented.
+
+/**
+ * Streams spec, 3.2.5.6.
+ * pipeTo(dest, { preventClose, preventAbort, preventCancel, signal } = {})
+ */
+static bool ReadableStream_pipeTo(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Implicit in the spec: argument default values.
+ Rooted<Value> options(cx, args.get(1));
+ if (options.isUndefined()) {
+ JSObject* emptyObj = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!emptyObj) {
+ return false;
+ }
+ options.setObject(*emptyObj);
+ }
+ // Step 3 (reordered).
+ // Implicit in the spec: get the values of the named parameters inside the
+ // second argument destructuring pattern. But as |ToBoolean| is infallible
+ // and has no observable side effects, we may as well do step 3 here too.
+ bool preventClose, preventAbort, preventCancel;
+ Rooted<Value> signalVal(cx);
+ {
+ // (P)(Re)use the |signal| root.
+ auto& v = signalVal;
+
+ if (!GetProperty(cx, options, cx->names().preventClose, &v)) {
+ return false;
+ }
+ preventClose = JS::ToBoolean(v);
+
+ if (!GetProperty(cx, options, cx->names().preventAbort, &v)) {
+ return false;
+ }
+ preventAbort = JS::ToBoolean(v);
+
+ if (!GetProperty(cx, options, cx->names().preventCancel, &v)) {
+ return false;
+ }
+ preventCancel = JS::ToBoolean(v);
+ }
+ if (!GetProperty(cx, options, cx->names().signal, &signalVal)) {
+ return false;
+ }
+
+ // Step 1: If ! IsReadableStream(this) is false, return a promise rejected
+ // with a TypeError exception.
+ Rooted<ReadableStream*> unwrappedThis(
+ cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "pipeTo"));
+ if (!unwrappedThis) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: If ! IsWritableStream(dest) is false, return a promise rejected
+ // with a TypeError exception.
+ Rooted<WritableStream*> unwrappedDest(
+ cx, UnwrapAndTypeCheckArgument<WritableStream>(cx, args, "pipeTo", 0));
+ if (!unwrappedDest) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Set preventClose to ! ToBoolean(preventClose), set preventAbort to
+ // ! ToBoolean(preventAbort), and set preventCancel to
+ // ! ToBoolean(preventCancel).
+ // This already happened above.
+
+ // Step 4: If signal is not undefined, and signal is not an instance of the
+ // AbortSignal interface, return a promise rejected with a TypeError
+ // exception.
+ Rooted<JSObject*> signal(cx, nullptr);
+ if (!signalVal.isUndefined()) {
+ if (!UnwrapAndTypeCheckValue(
+ cx, signalVal, cx->runtime()->maybeAbortSignalClass(), [cx] {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_PIPETO_BAD_SIGNAL);
+ })) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Note: |signal| can be a wrapper.
+ signal = &signalVal.toObject();
+ }
+
+ // Step 5: If ! IsReadableStreamLocked(this) is true, return a promise
+ // rejected with a TypeError exception.
+ if (unwrappedThis->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_LOCKED_METHOD, "pipeTo");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 6: If ! IsWritableStreamLocked(dest) is true, return a promise
+ // rejected with a TypeError exception.
+ if (unwrappedDest->isLocked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_ALREADY_LOCKED);
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 7: Return
+ // ! ReadableStreamPipeTo(this, dest, preventClose, preventAbort,
+ // preventCancel, signal).
+ JSObject* promise =
+ ReadableStreamPipeTo(cx, unwrappedThis, unwrappedDest, preventClose,
+ preventAbort, preventCancel, signal);
+ if (!promise) {
+ return false;
+ }
+
+ args.rval().setObject(*promise);
+ return true;
+}
+
+/**
+ * Streams spec, 3.2.5.7. tee()
+ */
+static bool ReadableStream_tee(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "tee"));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 2: Let branches be ? ReadableStreamTee(this, false).
+ Rooted<ReadableStream*> branch1(cx);
+ Rooted<ReadableStream*> branch2(cx);
+ if (!ReadableStreamTee(cx, unwrappedStream, false, &branch1, &branch2)) {
+ return false;
+ }
+
+ // Step 3: Return ! CreateArrayFromList(branches).
+ Rooted<NativeObject*> branches(cx, NewDenseFullyAllocatedArray(cx, 2));
+ if (!branches) {
+ return false;
+ }
+ branches->setDenseInitializedLength(2);
+ branches->initDenseElement(0, ObjectValue(*branch1));
+ branches->initDenseElement(1, ObjectValue(*branch2));
+
+ args.rval().setObject(*branches);
+ return true;
+}
+
+// Streams spec, 3.2.5.8.
+// [@@asyncIterator]({ preventCancel } = {})
+//
+// Not implemented.
+
+static const JSFunctionSpec ReadableStream_methods[] = {
+ JS_FN("cancel", ReadableStream_cancel, 1, 0),
+ JS_FN("getReader", ReadableStream_getReader, 0, 0),
+ // pipeTo is only conditionally supported right now, so it must be manually
+ // added below if desired.
+ JS_FN("tee", ReadableStream_tee, 0, 0), JS_FS_END};
+
+static const JSPropertySpec ReadableStream_properties[] = {
+ JS_PSG("locked", ReadableStream_locked, 0), JS_PS_END};
+
+static bool FinishReadableStreamClassInit(JSContext* cx, Handle<JSObject*> ctor,
+ Handle<JSObject*> proto) {
+ // This function and everything below should be replaced with
+ //
+ // JS_STREAMS_CLASS_SPEC(ReadableStream, 0, SlotCount, 0,
+ // JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_HAS_PRIVATE,
+ // JS_NULL_CLASS_OPS);
+ //
+ // when "pipeTo" is always enabled.
+ const auto& rco = cx->realm()->creationOptions();
+ if (rco.getStreamsEnabled() && rco.getWritableStreamsEnabled() &&
+ rco.getReadableStreamPipeToEnabled()) {
+ Rooted<jsid> pipeTo(cx, NameToId(cx->names().pipeTo));
+ if (!DefineFunction(cx, proto, pipeTo, ReadableStream_pipeTo, 2,
+ JSPROP_RESOLVING)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+const ClassSpec ReadableStream::classSpec_ = {
+ js::GenericCreateConstructor<ReadableStream::constructor, 2,
+ js::gc::AllocKind::FUNCTION>,
+ js::GenericCreatePrototype<ReadableStream>,
+ nullptr,
+ nullptr,
+ ReadableStream_methods,
+ ReadableStream_properties,
+ FinishReadableStreamClassInit,
+ 0};
+
+const JSClass ReadableStream::class_ = {
+ "ReadableStream",
+ JSCLASS_HAS_RESERVED_SLOTS(ReadableStream::SlotCount) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_ReadableStream) |
+ JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_HAS_PRIVATE,
+ JS_NULL_CLASS_OPS, &ReadableStream::classSpec_};
+
+const JSClass ReadableStream::protoClass_ = {
+ "ReadableStream.prototype",
+ JSCLASS_HAS_CACHED_PROTO(JSProto_ReadableStream), JS_NULL_CLASS_OPS,
+ &ReadableStream::classSpec_};
diff --git a/js/src/builtin/streams/ReadableStream.h b/js/src/builtin/streams/ReadableStream.h
new file mode 100644
index 0000000000..c11596d356
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStream.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class ReadableStream. */
+
+#ifndef builtin_streams_ReadableStream_h
+#define builtin_streams_ReadableStream_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include <stdint.h> // uint32_t
+
+#include "jstypes.h" // JS_PUBLIC_API
+#include "js/Class.h" // JSClass, js::ClassSpec
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Stream.h" // JS::ReadableStream{Mode,UnderlyingSource}
+#include "js/Value.h" // JS::Int32Value, JS::ObjectValue, JS::UndefinedValue
+#include "vm/NativeObject.h" // js::NativeObject
+
+class JS_PUBLIC_API JSObject;
+
+namespace js {
+
+class ReadableStreamController;
+
+class ReadableStream : public NativeObject {
+ public:
+ /**
+ * Memory layout of Stream instances.
+ *
+ * See https://streams.spec.whatwg.org/#rs-internal-slots for details on
+ * the stored state. [[state]] and [[disturbed]] are stored in
+ * StreamSlot_State as ReadableStream::State enum values.
+ *
+ * Of the stored values, Reader and StoredError might be cross-compartment
+ * wrappers. This can happen if the Reader was created by applying a
+ * different compartment's ReadableStream.prototype.getReader method.
+ *
+ * A stream's associated controller is always created from under the
+ * stream's constructor and thus cannot be in a different compartment.
+ */
+ enum Slots {
+ Slot_Controller,
+ Slot_Reader,
+ Slot_State,
+ Slot_StoredError,
+ SlotCount
+ };
+
+ private:
+ enum StateBits {
+ Readable = 0,
+ Closed = 1,
+ Errored = 2,
+ StateMask = 0x000000ff,
+ Disturbed = 0x00000100
+ };
+
+ uint32_t stateBits() const { return getFixedSlot(Slot_State).toInt32(); }
+ void initStateBits(uint32_t stateBits) {
+ MOZ_ASSERT((stateBits & ~Disturbed) <= Errored);
+ setFixedSlot(Slot_State, JS::Int32Value(stateBits));
+ }
+ void setStateBits(uint32_t stateBits) {
+#ifdef DEBUG
+ bool wasDisturbed = disturbed();
+ bool wasClosedOrErrored = closed() || errored();
+#endif
+ initStateBits(stateBits);
+ MOZ_ASSERT_IF(wasDisturbed, disturbed());
+ MOZ_ASSERT_IF(wasClosedOrErrored, !readable());
+ }
+
+ StateBits state() const { return StateBits(stateBits() & StateMask); }
+ void setState(StateBits state) {
+ MOZ_ASSERT(state <= Errored);
+ uint32_t current = stateBits() & ~StateMask;
+ setStateBits(current | state);
+ }
+
+ public:
+ bool readable() const { return state() == Readable; }
+ bool closed() const { return state() == Closed; }
+ void setClosed() { setState(Closed); }
+ bool errored() const { return state() == Errored; }
+ void setErrored() { setState(Errored); }
+ bool disturbed() const { return stateBits() & Disturbed; }
+ void setDisturbed() { setStateBits(stateBits() | Disturbed); }
+
+ bool hasController() const {
+ return !getFixedSlot(Slot_Controller).isUndefined();
+ }
+ inline ReadableStreamController* controller() const;
+ inline void setController(ReadableStreamController* controller);
+ void clearController() {
+ setFixedSlot(Slot_Controller, JS::UndefinedValue());
+ }
+
+ bool hasReader() const { return !getFixedSlot(Slot_Reader).isUndefined(); }
+ void setReader(JSObject* reader) {
+ setFixedSlot(Slot_Reader, JS::ObjectValue(*reader));
+ }
+ void clearReader() { setFixedSlot(Slot_Reader, JS::UndefinedValue()); }
+
+ JS::Value storedError() const { return getFixedSlot(Slot_StoredError); }
+ void setStoredError(JS::Handle<JS::Value> value) {
+ setFixedSlot(Slot_StoredError, value);
+ }
+
+ JS::ReadableStreamMode mode() const;
+
+ bool locked() const;
+
+ static MOZ_MUST_USE ReadableStream* create(
+ JSContext* cx, void* nsISupportsObject_alreadyAddreffed = nullptr,
+ JS::Handle<JSObject*> proto = nullptr);
+ static ReadableStream* createExternalSourceStream(
+ JSContext* cx, JS::ReadableStreamUnderlyingSource* source,
+ void* nsISupportsObject_alreadyAddreffed = nullptr,
+ JS::Handle<JSObject*> proto = nullptr);
+
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const JSClass class_;
+ static const ClassSpec protoClassSpec_;
+ static const JSClass protoClass_;
+};
+
+extern MOZ_MUST_USE bool SetUpExternalReadableByteStreamController(
+ JSContext* cx, JS::Handle<ReadableStream*> stream,
+ JS::ReadableStreamUnderlyingSource* source);
+
+} // namespace js
+
+#endif // builtin_streams_ReadableStream_h
diff --git a/js/src/builtin/streams/ReadableStreamBYOBReader.cpp b/js/src/builtin/streams/ReadableStreamBYOBReader.cpp
new file mode 100644
index 0000000000..9d8225dde4
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamBYOBReader.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/*
+ * Class ReadableStreamBYOBReader.
+ *
+ * Byte streams and BYOB readers are unimplemented, so this is skeletal -- yet
+ * helpful to ensure certain trivial tests of the functionality in wpt, that
+ * don't actually test fully-constructed byte streams/BYOB readers, pass. 🙄
+ */
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsapi.h" // JS_ReportErrorNumberASCII
+
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream
+#include "builtin/streams/ReadableStreamReader.h" // js::CreateReadableStreamBYOBReader, js::ForAuthorCodeBool
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+
+using JS::Handle;
+
+/*** 3.7. Class ReadableStreamBYOBReader *********************************/
+
+/**
+ * Stream spec, 3.7.3. new ReadableStreamBYOBReader ( stream )
+ * Steps 2-5.
+ */
+MOZ_MUST_USE JSObject* js::CreateReadableStreamBYOBReader(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream,
+ ForAuthorCodeBool forAuthorCode, Handle<JSObject*> proto /* = nullptr */) {
+ // Step 2: If ! IsReadableByteStreamController(
+ // stream.[[readableStreamController]]) is false, throw a
+ // TypeError exception.
+ // We don't implement byte stream controllers yet, so always throw here. Note
+ // that JSMSG_READABLESTREAM_BYTES_TYPE_NOT_IMPLEMENTED can't be used here
+ // because it's a RangeError (and sadly wpt actually tests this and we have a
+ // spurious failure if we don't make this a TypeError).
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_BYOB_READER_FOR_NON_BYTE_STREAM);
+
+ // Step 3: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
+ // exception.
+ // Step 4: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
+ // Step 5: Set this.[[readIntoRequests]] to a new empty List.
+ // Steps 3-5 are presently unreachable.
+ return nullptr;
+}
diff --git a/js/src/builtin/streams/ReadableStreamController.h b/js/src/builtin/streams/ReadableStreamController.h
new file mode 100644
index 0000000000..2cb1fc9e2a
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamController.h
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* ReadableStream controller classes and functions. */
+
+#ifndef builtin_streams_ReadableStreamController_h
+#define builtin_streams_ReadableStreamController_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include <stdint.h> // uint32_t
+
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream
+#include "builtin/streams/StreamController.h" // js::StreamController
+#include "js/Class.h" // JSClass, js::ClassSpec
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Stream.h" // JS::ReadableStreamUnderlyingSource
+#include "js/Value.h" // JS::Value, JS::{Number,Object,Private,Undefined}Value, JS::UndefinedHandleValue
+#include "vm/List.h" // js::ListObject
+#include "vm/NativeObject.h" // js::NativeObject
+
+namespace js {
+
+class PromiseObject;
+
+class ReadableStreamController : public StreamController {
+ public:
+ /**
+ * Memory layout for ReadableStream controllers, starting after the slots
+ * reserved for queue container usage.
+ *
+ * Storage of the internal slots listed in the standard is fairly
+ * straightforward except for [[pullAlgorithm]] and [[cancelAlgorithm]].
+ * These algorithms are not stored as JSFunction objects. Rather, there are
+ * three cases:
+ *
+ * - Streams created with `new ReadableStream`: The methods are stored
+ * in Slot_PullMethod and Slot_CancelMethod. The underlying source
+ * object (`this` for these methods) is in Slot_UnderlyingSource.
+ *
+ * - External source streams. Slot_UnderlyingSource is a PrivateValue
+ * pointing to the JS::ReadableStreamUnderlyingSource object. The
+ * algorithms are implemented using the .pull() and .cancel() methods
+ * of that object. Slot_Pull/CancelMethod are undefined.
+ *
+ * - Tee streams. Slot_UnderlyingSource is a TeeState object. The
+ * pull/cancel algorithms are implemented as separate functions in
+ * Stream.cpp. Slot_Pull/CancelMethod are undefined.
+ *
+ * UnderlyingSource, PullMethod, and CancelMethod can be wrappers to objects
+ * in other compartments.
+ *
+ * StrategyHWM and Flags are both primitive (numeric) values.
+ */
+ enum Slots {
+ Slot_Stream = StreamController::SlotCount,
+ Slot_UnderlyingSource,
+ Slot_PullMethod,
+ Slot_CancelMethod,
+ Slot_StrategyHWM,
+ Slot_Flags,
+ SlotCount
+ };
+
+ enum ControllerFlags {
+ Flag_Started = 1 << 0,
+ Flag_Pulling = 1 << 1,
+ Flag_PullAgain = 1 << 2,
+ Flag_CloseRequested = 1 << 3,
+ Flag_TeeBranch1 = 1 << 4,
+ Flag_TeeBranch2 = 1 << 5,
+ Flag_ExternalSource = 1 << 6,
+ Flag_SourceLocked = 1 << 7,
+ };
+
+ ReadableStream* stream() const {
+ return &getFixedSlot(Slot_Stream).toObject().as<ReadableStream>();
+ }
+ void setStream(ReadableStream* stream) {
+ setFixedSlot(Slot_Stream, JS::ObjectValue(*stream));
+ }
+ JS::Value underlyingSource() const {
+ return getFixedSlot(Slot_UnderlyingSource);
+ }
+ void setUnderlyingSource(const JS::Value& underlyingSource) {
+ setFixedSlot(Slot_UnderlyingSource, underlyingSource);
+ }
+ JS::Value pullMethod() const { return getFixedSlot(Slot_PullMethod); }
+ void setPullMethod(const JS::Value& pullMethod) {
+ setFixedSlot(Slot_PullMethod, pullMethod);
+ }
+ JS::Value cancelMethod() const { return getFixedSlot(Slot_CancelMethod); }
+ void setCancelMethod(const JS::Value& cancelMethod) {
+ setFixedSlot(Slot_CancelMethod, cancelMethod);
+ }
+ JS::ReadableStreamUnderlyingSource* externalSource() const {
+ static_assert(alignof(JS::ReadableStreamUnderlyingSource) >= 2,
+ "External underling sources are stored as PrivateValues, "
+ "so they must have even addresses");
+ MOZ_ASSERT(hasExternalSource());
+ return static_cast<JS::ReadableStreamUnderlyingSource*>(
+ underlyingSource().toPrivate());
+ }
+ void setExternalSource(JS::ReadableStreamUnderlyingSource* underlyingSource) {
+ setUnderlyingSource(JS::PrivateValue(underlyingSource));
+ addFlags(Flag_ExternalSource);
+ }
+ static void clearUnderlyingSource(
+ JS::Handle<ReadableStreamController*> controller,
+ bool finalizeSource = true) {
+ if (controller->hasExternalSource()) {
+ if (finalizeSource) {
+ controller->externalSource()->finalize();
+ }
+ controller->setFlags(controller->flags() & ~Flag_ExternalSource);
+ }
+ controller->setUnderlyingSource(JS::UndefinedHandleValue);
+ }
+ double strategyHWM() const {
+ return getFixedSlot(Slot_StrategyHWM).toNumber();
+ }
+ void setStrategyHWM(double highWaterMark) {
+ setFixedSlot(Slot_StrategyHWM, NumberValue(highWaterMark));
+ }
+ uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
+ void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); }
+ void addFlags(uint32_t flags) { setFlags(this->flags() | flags); }
+ void removeFlags(uint32_t flags) { setFlags(this->flags() & ~flags); }
+ bool started() const { return flags() & Flag_Started; }
+ void setStarted() { addFlags(Flag_Started); }
+ bool pulling() const { return flags() & Flag_Pulling; }
+ void setPulling() { addFlags(Flag_Pulling); }
+ void clearPullFlags() { removeFlags(Flag_Pulling | Flag_PullAgain); }
+ bool pullAgain() const { return flags() & Flag_PullAgain; }
+ void setPullAgain() { addFlags(Flag_PullAgain); }
+ bool closeRequested() const { return flags() & Flag_CloseRequested; }
+ void setCloseRequested() { addFlags(Flag_CloseRequested); }
+ bool isTeeBranch1() const { return flags() & Flag_TeeBranch1; }
+ void setTeeBranch1() {
+ MOZ_ASSERT(!isTeeBranch2());
+ addFlags(Flag_TeeBranch1);
+ }
+ bool isTeeBranch2() const { return flags() & Flag_TeeBranch2; }
+ void setTeeBranch2() {
+ MOZ_ASSERT(!isTeeBranch1());
+ addFlags(Flag_TeeBranch2);
+ }
+ bool hasExternalSource() const { return flags() & Flag_ExternalSource; }
+ bool sourceLocked() const { return flags() & Flag_SourceLocked; }
+ void setSourceLocked() { addFlags(Flag_SourceLocked); }
+ void clearSourceLocked() { removeFlags(Flag_SourceLocked); }
+};
+
+class ReadableStreamDefaultController : public ReadableStreamController {
+ private:
+ /**
+ * Memory layout for ReadableStreamDefaultControllers, starting after the
+ * slots shared among all types of controllers.
+ *
+ * StrategySize is treated as an opaque value when stored. The only use site
+ * ensures that it's wrapped into the current cx compartment.
+ */
+ enum Slots {
+ Slot_StrategySize = ReadableStreamController::SlotCount,
+ SlotCount
+ };
+
+ public:
+ JS::Value strategySize() const { return getFixedSlot(Slot_StrategySize); }
+ void setStrategySize(const JS::Value& size) {
+ setFixedSlot(Slot_StrategySize, size);
+ }
+
+ static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
+ static const ClassSpec classSpec_;
+ static const JSClass class_;
+ static const ClassSpec protoClassSpec_;
+ static const JSClass protoClass_;
+};
+
+class ReadableByteStreamController : public ReadableStreamController {
+ public:
+ /**
+ * Memory layout for ReadableByteStreamControllers, starting after the
+ * slots shared among all types of controllers.
+ *
+ * PendingPullIntos is guaranteed to be in the same compartment as the
+ * controller, but might contain wrappers for objects from other
+ * compartments.
+ *
+ * AutoAllocateSize is a primitive (numeric) value.
+ */
+ enum Slots {
+ Slot_BYOBRequest = ReadableStreamController::SlotCount,
+ Slot_PendingPullIntos,
+ Slot_AutoAllocateSize,
+ SlotCount
+ };
+
+ JS::Value byobRequest() const { return getFixedSlot(Slot_BYOBRequest); }
+ void clearBYOBRequest() {
+ setFixedSlot(Slot_BYOBRequest, JS::UndefinedValue());
+ }
+ ListObject* pendingPullIntos() const {
+ return &getFixedSlot(Slot_PendingPullIntos).toObject().as<ListObject>();
+ }
+ JS::Value autoAllocateChunkSize() const {
+ return getFixedSlot(Slot_AutoAllocateSize);
+ }
+ void setAutoAllocateChunkSize(const JS::Value& size) {
+ setFixedSlot(Slot_AutoAllocateSize, size);
+ }
+
+ static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
+ static const ClassSpec classSpec_;
+ static const JSClass class_;
+ static const ClassSpec protoClassSpec_;
+ static const JSClass protoClass_;
+};
+
+extern MOZ_MUST_USE bool CheckReadableStreamControllerCanCloseOrEnqueue(
+ JSContext* cx, JS::Handle<ReadableStreamController*> unwrappedController,
+ const char* action);
+
+extern MOZ_MUST_USE JSObject* ReadableStreamControllerCancelSteps(
+ JSContext* cx, JS::Handle<ReadableStreamController*> unwrappedController,
+ JS::Handle<JS::Value> reason);
+
+extern PromiseObject* ReadableStreamDefaultControllerPullSteps(
+ JSContext* cx,
+ JS::Handle<ReadableStreamDefaultController*> unwrappedController);
+
+extern bool ReadableStreamControllerStartHandler(JSContext* cx, unsigned argc,
+ JS::Value* vp);
+
+extern bool ReadableStreamControllerStartFailedHandler(JSContext* cx,
+ unsigned argc,
+ JS::Value* vp);
+
+} // namespace js
+
+template <>
+inline bool JSObject::is<js::ReadableStreamController>() const {
+ return is<js::ReadableStreamDefaultController>() ||
+ is<js::ReadableByteStreamController>();
+}
+
+namespace js {
+
+inline ReadableStreamController* ReadableStream::controller() const {
+ return &getFixedSlot(Slot_Controller)
+ .toObject()
+ .as<ReadableStreamController>();
+}
+
+inline void ReadableStream::setController(
+ ReadableStreamController* controller) {
+ setFixedSlot(Slot_Controller, JS::ObjectValue(*controller));
+}
+
+} // namespace js
+
+#endif // builtin_streams_ReadableStreamController_h
diff --git a/js/src/builtin/streams/ReadableStreamDefaultController.cpp b/js/src/builtin/streams/ReadableStreamDefaultController.cpp
new file mode 100644
index 0000000000..e1765c2518
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamDefaultController.cpp
@@ -0,0 +1,514 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class ReadableStreamDefaultController. */
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsapi.h" // JS_ReportErrorNumberASCII
+#include "jsfriendapi.h" // js::AssertSameCompartment
+
+#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
+#include "builtin/streams/MiscellaneousOperations.h" // js::IsMaybeWrapped
+#include "builtin/streams/PullIntoDescriptor.h" // js::PullIntoDescriptor
+#include "builtin/streams/QueueWithSizes.h" // js::{DequeueValue,ResetQueue}
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream, js::SetUpExternalReadableByteStreamController
+#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller, js::ReadableByteStreamController, js::CheckReadableStreamControllerCanCloseOrEnqueue, js::ReadableStreamControllerCancelSteps, js::ReadableStreamDefaultControllerPullSteps, js::ReadableStreamControllerStart{,Failed}Handler
+#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::ReadableStreamController{CallPullIfNeeded,ClearAlgorithms,Error,GetDesiredSizeUnchecked}, js::ReadableStreamDefaultController{Close,Enqueue}
+#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{AddReadOrReadIntoRequest,CloseInternal,CreateReadResult}
+#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStreamTee_Cancel
+#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStream{,Default}Reader
+#include "builtin/streams/StreamController.h" // js::StreamController
+#include "builtin/streams/TeeState.h" // js::TeeState
+#include "js/ArrayBuffer.h" // JS::NewArrayBuffer
+#include "js/Class.h" // js::ClassSpec
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/PropertySpec.h"
+#include "vm/Interpreter.h"
+#include "vm/JSContext.h"
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+#include "vm/SelfHosting.h"
+
+#include "builtin/HandlerFunction-inl.h" // js::TargetFromHandler
+#include "builtin/streams/MiscellaneousOperations-inl.h" // js::PromiseCall
+#include "builtin/streams/ReadableStreamReader-inl.h" // js::UnwrapReaderFromStream
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapAnd{DowncastObject,TypeCheckThis}
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using js::ClassSpec;
+using js::PromiseObject;
+using js::ReadableStream;
+using js::ReadableStreamController;
+using js::ReadableStreamControllerCallPullIfNeeded;
+using js::ReadableStreamControllerClearAlgorithms;
+using js::ReadableStreamControllerError;
+using js::ReadableStreamControllerGetDesiredSizeUnchecked;
+using js::ReadableStreamDefaultController;
+using js::ReadableStreamDefaultControllerClose;
+using js::ReadableStreamDefaultControllerEnqueue;
+using js::TargetFromHandler;
+using js::UnwrapAndTypeCheckThis;
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::ObjectValue;
+using JS::Rooted;
+using JS::Value;
+
+/*** 3.9. Class ReadableStreamDefaultController *****************************/
+
+/**
+ * Streams spec, 3.10.11. SetUpReadableStreamDefaultController, step 11
+ * and
+ * Streams spec, 3.13.26. SetUpReadableByteStreamController, step 16:
+ * Upon fulfillment of startPromise, [...]
+ */
+bool js::ReadableStreamControllerStartHandler(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<ReadableStreamController*> controller(
+ cx, TargetFromHandler<ReadableStreamController>(args));
+
+ // Step a: Set controller.[[started]] to true.
+ controller->setStarted();
+
+ // Step b: Assert: controller.[[pulling]] is false.
+ MOZ_ASSERT(!controller->pulling());
+
+ // Step c: Assert: controller.[[pullAgain]] is false.
+ MOZ_ASSERT(!controller->pullAgain());
+
+ // Step d: Perform
+ // ! ReadableStreamDefaultControllerCallPullIfNeeded(controller)
+ // (or ReadableByteStreamControllerCallPullIfNeeded(controller)).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) {
+ return false;
+ }
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 3.10.11. SetUpReadableStreamDefaultController, step 12
+ * and
+ * Streams spec, 3.13.26. SetUpReadableByteStreamController, step 17:
+ * Upon rejection of startPromise with reason r, [...]
+ */
+bool js::ReadableStreamControllerStartFailedHandler(JSContext* cx,
+ unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<ReadableStreamController*> controller(
+ cx, TargetFromHandler<ReadableStreamController>(args));
+
+ // Step a: Perform
+ // ! ReadableStreamDefaultControllerError(controller, r)
+ // (or ReadableByteStreamControllerError(controller, r)).
+ if (!ReadableStreamControllerError(cx, controller, args.get(0))) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 3.9.3.
+ * new ReadableStreamDefaultController( stream, underlyingSource, size,
+ * highWaterMark )
+ */
+bool ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Step 1: Throw a TypeError.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_BOGUS_CONSTRUCTOR,
+ "ReadableStreamDefaultController");
+ return false;
+}
+
+/**
+ * Streams spec, 3.9.4.1. get desiredSize
+ */
+static bool ReadableStreamDefaultController_desiredSize(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<ReadableStreamController*> unwrappedController(
+ cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(
+ cx, args, "get desiredSize"));
+ if (!unwrappedController) {
+ return false;
+ }
+
+ // 3.10.8. ReadableStreamDefaultControllerGetDesiredSize, steps 1-4.
+ // 3.10.8. Step 1: Let stream be controller.[[controlledReadableStream]].
+ ReadableStream* unwrappedStream = unwrappedController->stream();
+
+ // 3.10.8. Step 2: Let state be stream.[[state]].
+ // 3.10.8. Step 3: If state is "errored", return null.
+ if (unwrappedStream->errored()) {
+ args.rval().setNull();
+ return true;
+ }
+
+ // 3.10.8. Step 4: If state is "closed", return 0.
+ if (unwrappedStream->closed()) {
+ args.rval().setInt32(0);
+ return true;
+ }
+
+ // Step 2: Return ! ReadableStreamDefaultControllerGetDesiredSize(this).
+ args.rval().setNumber(
+ ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController));
+ return true;
+}
+
+/**
+ * Unified implementation of step 2 of 3.9.4.2 and 3.9.4.3,
+ * and steps 2-3 of 3.11.4.3.
+ */
+MOZ_MUST_USE bool js::CheckReadableStreamControllerCanCloseOrEnqueue(
+ JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
+ const char* action) {
+ // 3.9.4.2. close(), step 2, and
+ // 3.9.4.3. enqueue(chunk), step 2:
+ // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is false,
+ // throw a TypeError exception.
+ // RSDCCanCloseOrEnqueue returns false in two cases: (1)
+ // controller.[[closeRequested]] is true; (2) the stream is not readable,
+ // i.e. already closed or errored. This amounts to exactly the same thing as
+ // 3.11.4.3 steps 2-3 below, and we want different error messages for the two
+ // cases anyway.
+
+ // 3.11.4.3. Step 2: If this.[[closeRequested]] is true, throw a TypeError
+ // exception.
+ if (unwrappedController->closeRequested()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, action);
+ return false;
+ }
+
+ // 3.11.4.3. Step 3: If this.[[controlledReadableByteStream]].[[state]] is
+ // not "readable", throw a TypeError exception.
+ ReadableStream* unwrappedStream = unwrappedController->stream();
+ if (!unwrappedStream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
+ action);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.9.4.2 close()
+ */
+static bool ReadableStreamDefaultController_close(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<ReadableStreamDefaultController*> unwrappedController(
+ cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
+ "close"));
+ if (!unwrappedController) {
+ return false;
+ }
+
+ // Step 2: If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is
+ // false, throw a TypeError exception.
+ if (!CheckReadableStreamControllerCanCloseOrEnqueue(cx, unwrappedController,
+ "close")) {
+ return false;
+ }
+
+ // Step 3: Perform ! ReadableStreamDefaultControllerClose(this).
+ if (!ReadableStreamDefaultControllerClose(cx, unwrappedController)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 3.9.4.3. enqueue ( chunk )
+ */
+static bool ReadableStreamDefaultController_enqueue(JSContext* cx,
+ unsigned argc, Value* vp) {
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<ReadableStreamDefaultController*> unwrappedController(
+ cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
+ "enqueue"));
+ if (!unwrappedController) {
+ return false;
+ }
+
+ // Step 2: If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(this) is
+ // false, throw a TypeError exception.
+ if (!CheckReadableStreamControllerCanCloseOrEnqueue(cx, unwrappedController,
+ "enqueue")) {
+ return false;
+ }
+
+ // Step 3: Return ! ReadableStreamDefaultControllerEnqueue(this, chunk).
+ if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
+ args.get(0))) {
+ return false;
+ }
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 3.9.4.4. error ( e )
+ */
+static bool ReadableStreamDefaultController_error(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<ReadableStreamDefaultController*> unwrappedController(
+ cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args,
+ "enqueue"));
+ if (!unwrappedController) {
+ return false;
+ }
+
+ // Step 2: Perform ! ReadableStreamDefaultControllerError(this, e).
+ if (!ReadableStreamControllerError(cx, unwrappedController, args.get(0))) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static const JSPropertySpec ReadableStreamDefaultController_properties[] = {
+ JS_PSG("desiredSize", ReadableStreamDefaultController_desiredSize, 0),
+ JS_PS_END};
+
+static const JSFunctionSpec ReadableStreamDefaultController_methods[] = {
+ JS_FN("close", ReadableStreamDefaultController_close, 0, 0),
+ JS_FN("enqueue", ReadableStreamDefaultController_enqueue, 1, 0),
+ JS_FN("error", ReadableStreamDefaultController_error, 1, 0), JS_FS_END};
+
+JS_STREAMS_CLASS_SPEC(ReadableStreamDefaultController, 0, SlotCount,
+ ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
+
+/**
+ * Unified implementation of ReadableStream controllers' [[CancelSteps]]
+ * internal methods.
+ * Streams spec, 3.9.5.1. [[CancelSteps]] ( reason )
+ * and
+ * Streams spec, 3.11.5.1. [[CancelSteps]] ( reason )
+ */
+MOZ_MUST_USE JSObject* js::ReadableStreamControllerCancelSteps(
+ JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
+ Handle<Value> reason) {
+ AssertSameCompartment(cx, reason);
+
+ // Step 1 of 3.11.5.1: If this.[[pendingPullIntos]] is not empty,
+ if (!unwrappedController->is<ReadableStreamDefaultController>()) {
+ Rooted<ListObject*> unwrappedPendingPullIntos(
+ cx, unwrappedController->as<ReadableByteStreamController>()
+ .pendingPullIntos());
+
+ if (unwrappedPendingPullIntos->length() != 0) {
+ // Step a: Let firstDescriptor be the first element of
+ // this.[[pendingPullIntos]].
+ PullIntoDescriptor* unwrappedDescriptor =
+ UnwrapAndDowncastObject<PullIntoDescriptor>(
+ cx, &unwrappedPendingPullIntos->get(0).toObject());
+ if (!unwrappedDescriptor) {
+ return nullptr;
+ }
+
+ // Step b: Set firstDescriptor.[[bytesFilled]] to 0.
+ unwrappedDescriptor->setBytesFilled(0);
+ }
+ }
+
+ Rooted<Value> unwrappedUnderlyingSource(
+ cx, unwrappedController->underlyingSource());
+
+ // Step 1 of 3.9.5.1, step 2 of 3.11.5.1: Perform ! ResetQueue(this).
+ if (!ResetQueue(cx, unwrappedController)) {
+ return nullptr;
+ }
+
+ // Step 2 of 3.9.5.1, step 3 of 3.11.5.1: Let result be the result of
+ // performing this.[[cancelAlgorithm]], passing reason.
+ //
+ // Our representation of cancel algorithms is a bit awkward, for
+ // performance, so we must figure out which algorithm is being invoked.
+ Rooted<JSObject*> result(cx);
+ if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) {
+ // The cancel algorithm given in ReadableStreamTee step 13 or 14.
+ MOZ_ASSERT(unwrappedUnderlyingSource.toObject().is<TeeState>(),
+ "tee streams and controllers are always same-compartment with "
+ "the TeeState object");
+ Rooted<TeeState*> unwrappedTeeState(
+ cx, &unwrappedUnderlyingSource.toObject().as<TeeState>());
+ Rooted<ReadableStreamDefaultController*> unwrappedDefaultController(
+ cx, &unwrappedController->as<ReadableStreamDefaultController>());
+ result = ReadableStreamTee_Cancel(cx, unwrappedTeeState,
+ unwrappedDefaultController, reason);
+ } else if (unwrappedController->hasExternalSource()) {
+ // An embedding-provided cancel algorithm.
+ Rooted<Value> rval(cx);
+ {
+ AutoRealm ar(cx, unwrappedController);
+ JS::ReadableStreamUnderlyingSource* source =
+ unwrappedController->externalSource();
+ Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
+ Rooted<Value> wrappedReason(cx, reason);
+ if (!cx->compartment()->wrap(cx, &wrappedReason)) {
+ return nullptr;
+ }
+
+ cx->check(stream, wrappedReason);
+ rval = source->cancel(cx, stream, wrappedReason);
+ }
+
+ // Make sure the ReadableStreamControllerClearAlgorithms call below is
+ // reached, even on error.
+ if (!cx->compartment()->wrap(cx, &rval)) {
+ result = nullptr;
+ } else {
+ result = PromiseObject::unforgeableResolve(cx, rval);
+ }
+ } else {
+ // The algorithm created in
+ // SetUpReadableByteStreamControllerFromUnderlyingSource step 5.
+ Rooted<Value> unwrappedCancelMethod(cx,
+ unwrappedController->cancelMethod());
+ if (unwrappedCancelMethod.isUndefined()) {
+ // CreateAlgorithmFromUnderlyingMethod step 7.
+ result = PromiseResolvedWithUndefined(cx);
+ } else {
+ // CreateAlgorithmFromUnderlyingMethod steps 6.c.i-ii.
+ {
+ AutoRealm ar(cx, unwrappedController);
+
+ // |unwrappedCancelMethod| and |unwrappedUnderlyingSource| come directly
+ // from |unwrappedController| slots so must be same-compartment with it.
+ cx->check(unwrappedCancelMethod);
+ cx->check(unwrappedUnderlyingSource);
+
+ Rooted<Value> wrappedReason(cx, reason);
+ if (!cx->compartment()->wrap(cx, &wrappedReason)) {
+ return nullptr;
+ }
+
+ // If PromiseCall fails, don't bail out until after the
+ // ReadableStreamControllerClearAlgorithms call below.
+ result = PromiseCall(cx, unwrappedCancelMethod,
+ unwrappedUnderlyingSource, wrappedReason);
+ }
+ if (!cx->compartment()->wrap(cx, &result)) {
+ result = nullptr;
+ }
+ }
+ }
+
+ // Step 3 (or 4): Perform
+ // ! ReadableStreamDefaultControllerClearAlgorithms(this)
+ // (or ReadableByteStreamControllerClearAlgorithms(this)).
+ ReadableStreamControllerClearAlgorithms(unwrappedController);
+
+ // Step 4 (or 5): Return result.
+ return result;
+}
+
+/**
+ * Streams spec, 3.9.5.2.
+ * ReadableStreamDefaultController [[PullSteps]]( forAuthorCode )
+ */
+PromiseObject* js::ReadableStreamDefaultControllerPullSteps(
+ JSContext* cx,
+ Handle<ReadableStreamDefaultController*> unwrappedController) {
+ // Step 1: Let stream be this.[[controlledReadableStream]].
+ Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
+
+ // Step 2: If this.[[queue]] is not empty,
+ Rooted<ListObject*> unwrappedQueue(cx);
+ Rooted<Value> val(
+ cx, unwrappedController->getFixedSlot(StreamController::Slot_Queue));
+ if (val.isObject()) {
+ unwrappedQueue = &val.toObject().as<ListObject>();
+ }
+
+ if (unwrappedQueue && unwrappedQueue->length() != 0) {
+ // Step a: Let chunk be ! DequeueValue(this).
+ Rooted<Value> chunk(cx);
+ if (!DequeueValue(cx, unwrappedController, &chunk)) {
+ return nullptr;
+ }
+
+ // Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty,
+ if (unwrappedController->closeRequested() &&
+ unwrappedQueue->length() == 0) {
+ // Step i: Perform ! ReadableStreamDefaultControllerClearAlgorithms(this).
+ ReadableStreamControllerClearAlgorithms(unwrappedController);
+
+ // Step ii: Perform ! ReadableStreamClose(stream).
+ if (!ReadableStreamCloseInternal(cx, unwrappedStream)) {
+ return nullptr;
+ }
+ }
+
+ // Step c: Otherwise, perform
+ // ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
+ else {
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
+ return nullptr;
+ }
+ }
+
+ // Step d: Return a promise resolved with
+ // ! ReadableStreamCreateReadResult(chunk, false, forAuthorCode).
+ cx->check(chunk);
+ ReadableStreamReader* unwrappedReader =
+ UnwrapReaderFromStream(cx, unwrappedStream);
+ if (!unwrappedReader) {
+ return nullptr;
+ }
+
+ PlainObject* readResultObj = ReadableStreamCreateReadResult(
+ cx, chunk, false, unwrappedReader->forAuthorCode());
+ if (!readResultObj) {
+ return nullptr;
+ }
+
+ Rooted<Value> readResult(cx, ObjectValue(*readResultObj));
+ return PromiseObject::unforgeableResolveWithNonPromise(cx, readResult);
+ }
+
+ // Step 3: Let pendingPromise be
+ // ! ReadableStreamAddReadRequest(stream, forAuthorCode).
+ Rooted<PromiseObject*> pendingPromise(
+ cx, ReadableStreamAddReadOrReadIntoRequest(cx, unwrappedStream));
+ if (!pendingPromise) {
+ return nullptr;
+ }
+
+ // Step 4: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
+ return nullptr;
+ }
+
+ // Step 5: Return pendingPromise.
+ return pendingPromise;
+}
diff --git a/js/src/builtin/streams/ReadableStreamDefaultControllerOperations.cpp b/js/src/builtin/streams/ReadableStreamDefaultControllerOperations.cpp
new file mode 100644
index 0000000000..1be4073498
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamDefaultControllerOperations.cpp
@@ -0,0 +1,682 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Readable stream default controller abstract operations. */
+
+#include "builtin/streams/ReadableStreamDefaultControllerOperations.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsfriendapi.h" // js::AssertSameCompartment
+
+#include "builtin/Stream.h" // js::ReadableByteStreamControllerClearPendingPullIntos
+#include "builtin/streams/MiscellaneousOperations.h" // js::CreateAlgorithmFromUnderlyingMethod, js::InvokeOrNoop, js::IsMaybeWrapped
+#include "builtin/streams/QueueWithSizes.h" // js::EnqueueValueWithSize, js::ResetQueue
+#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller, js::ReadableByteStreamController, js::ReadableStreamControllerStart{,Failed}Handler
+#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{CloseInternal,ErrorInternal,FulfillReadOrReadIntoRequest,GetNumReadRequests}
+#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStreamTee_Pull, js::SetUpReadableStreamDefaultController
+#include "builtin/streams/TeeState.h" // js::TeeState
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/Promise.h" // JS::AddPromiseReactions
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "js/Stream.h" // JS::ReadableStreamUnderlyingSource
+#include "js/Value.h" // JS::{,Int32,Object}Value, JS::UndefinedHandleValue
+#include "vm/Compartment.h" // JS::Compartment
+#include "vm/Interpreter.h" // js::Call, js::GetAndClearExceptionAndStack
+#include "vm/JSContext.h" // JSContext
+#include "vm/JSObject.h" // JSObject
+#include "vm/List.h" // js::ListObject
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+#include "vm/Runtime.h" // JSAtomState
+#include "vm/SavedFrame.h" // js::SavedFrame
+
+#include "builtin/HandlerFunction-inl.h" // js::NewHandler
+#include "builtin/streams/MiscellaneousOperations-inl.h" // js::PromiseCall
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapCalleeSlot
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/JSObject-inl.h" // js::IsCallable, js::NewBuiltinClassInstance
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using js::ReadableByteStreamController;
+using js::ReadableStream;
+using js::ReadableStreamController;
+using js::ReadableStreamControllerCallPullIfNeeded;
+using js::ReadableStreamControllerError;
+using js::ReadableStreamGetNumReadRequests;
+using js::UnwrapCalleeSlot;
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::Rooted;
+using JS::UndefinedHandleValue;
+using JS::Value;
+
+/*** 3.10. Readable stream default controller abstract operations ***********/
+
+// Streams spec, 3.10.1. IsReadableStreamDefaultController ( x )
+// Implemented via is<ReadableStreamDefaultController>()
+
+/**
+ * Streams spec, 3.10.2 and 3.13.3. step 7:
+ * Upon fulfillment of pullPromise, [...]
+ */
+static bool ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<ReadableStreamController*> unwrappedController(
+ cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0));
+ if (!unwrappedController) {
+ return false;
+ }
+
+ bool pullAgain = unwrappedController->pullAgain();
+
+ // Step a: Set controller.[[pulling]] to false.
+ // Step b.i: Set controller.[[pullAgain]] to false.
+ unwrappedController->clearPullFlags();
+
+ // Step b: If controller.[[pullAgain]] is true,
+ if (pullAgain) {
+ // Step ii: Perform
+ // ! ReadableStreamDefaultControllerCallPullIfNeeded(controller)
+ // (or ReadableByteStreamControllerCallPullIfNeeded(controller)).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController)) {
+ return false;
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 3.10.2 and 3.13.3. step 8:
+ * Upon rejection of pullPromise with reason e,
+ */
+static bool ControllerPullFailedHandler(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Handle<Value> e = args.get(0);
+
+ Rooted<ReadableStreamController*> controller(
+ cx, UnwrapCalleeSlot<ReadableStreamController>(cx, args, 0));
+ if (!controller) {
+ return false;
+ }
+
+ // Step a: Perform ! ReadableStreamDefaultControllerError(controller, e).
+ // (ReadableByteStreamControllerError in 3.12.3.)
+ if (!ReadableStreamControllerError(cx, controller, e)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool ReadableStreamControllerShouldCallPull(
+ ReadableStreamController* unwrappedController);
+
+/**
+ * Streams spec, 3.10.2
+ * ReadableStreamDefaultControllerCallPullIfNeeded ( controller )
+ * Streams spec, 3.13.3.
+ * ReadableByteStreamControllerCallPullIfNeeded ( controller )
+ */
+MOZ_MUST_USE bool js::ReadableStreamControllerCallPullIfNeeded(
+ JSContext* cx, Handle<ReadableStreamController*> unwrappedController) {
+ // Step 1: Let shouldPull be
+ // ! ReadableStreamDefaultControllerShouldCallPull(controller).
+ // (ReadableByteStreamDefaultControllerShouldCallPull in 3.13.3.)
+ bool shouldPull = ReadableStreamControllerShouldCallPull(unwrappedController);
+
+ // Step 2: If shouldPull is false, return.
+ if (!shouldPull) {
+ return true;
+ }
+
+ // Step 3: If controller.[[pulling]] is true,
+ if (unwrappedController->pulling()) {
+ // Step a: Set controller.[[pullAgain]] to true.
+ unwrappedController->setPullAgain();
+
+ // Step b: Return.
+ return true;
+ }
+
+ // Step 4: Assert: controller.[[pullAgain]] is false.
+ MOZ_ASSERT(!unwrappedController->pullAgain());
+
+ // Step 5: Set controller.[[pulling]] to true.
+ unwrappedController->setPulling();
+
+ // We use this variable in step 7. For ease of error-handling, we wrap it
+ // early.
+ Rooted<JSObject*> wrappedController(cx, unwrappedController);
+ if (!cx->compartment()->wrap(cx, &wrappedController)) {
+ return false;
+ }
+
+ // Step 6: Let pullPromise be the result of performing
+ // controller.[[pullAlgorithm]].
+ // Our representation of pull algorithms is a bit awkward, for performance,
+ // so we must figure out which algorithm is being invoked.
+ Rooted<JSObject*> pullPromise(cx);
+ Rooted<Value> unwrappedUnderlyingSource(
+ cx, unwrappedController->underlyingSource());
+
+ if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) {
+ // The pull algorithm given in ReadableStreamTee step 12.
+ MOZ_ASSERT(unwrappedUnderlyingSource.toObject().is<TeeState>(),
+ "tee streams and controllers are always same-compartment with "
+ "the TeeState object");
+ Rooted<TeeState*> unwrappedTeeState(
+ cx, &unwrappedUnderlyingSource.toObject().as<TeeState>());
+ pullPromise = ReadableStreamTee_Pull(cx, unwrappedTeeState);
+ } else if (unwrappedController->hasExternalSource()) {
+ // An embedding-provided pull algorithm.
+ {
+ AutoRealm ar(cx, unwrappedController);
+ JS::ReadableStreamUnderlyingSource* source =
+ unwrappedController->externalSource();
+ Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
+ double desiredSize =
+ ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
+ source->requestData(cx, stream, desiredSize);
+ }
+ pullPromise = PromiseResolvedWithUndefined(cx);
+ } else {
+ // The pull algorithm created in
+ // SetUpReadableStreamDefaultControllerFromUnderlyingSource step 4.
+ Rooted<Value> unwrappedPullMethod(cx, unwrappedController->pullMethod());
+ if (unwrappedPullMethod.isUndefined()) {
+ // CreateAlgorithmFromUnderlyingMethod step 7.
+ pullPromise = PromiseResolvedWithUndefined(cx);
+ } else {
+ // CreateAlgorithmFromUnderlyingMethod step 6.b.i.
+ {
+ AutoRealm ar(cx, unwrappedController);
+
+ // |unwrappedPullMethod| and |unwrappedUnderlyingSource| come directly
+ // from |unwrappedController| slots so must be same-compartment with it.
+ cx->check(unwrappedPullMethod);
+ cx->check(unwrappedUnderlyingSource);
+
+ Rooted<Value> controller(cx, ObjectValue(*unwrappedController));
+ cx->check(controller);
+
+ pullPromise = PromiseCall(cx, unwrappedPullMethod,
+ unwrappedUnderlyingSource, controller);
+ if (!pullPromise) {
+ return false;
+ }
+ }
+ if (!cx->compartment()->wrap(cx, &pullPromise)) {
+ return false;
+ }
+ }
+ }
+ if (!pullPromise) {
+ return false;
+ }
+
+ // Step 7: Upon fulfillment of pullPromise, [...]
+ // Step 8. Upon rejection of pullPromise with reason e, [...]
+ Rooted<JSObject*> onPullFulfilled(
+ cx, NewHandler(cx, ControllerPullHandler, wrappedController));
+ if (!onPullFulfilled) {
+ return false;
+ }
+ Rooted<JSObject*> onPullRejected(
+ cx, NewHandler(cx, ControllerPullFailedHandler, wrappedController));
+ if (!onPullRejected) {
+ return false;
+ }
+ return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled,
+ onPullRejected);
+}
+
+/**
+ * Streams spec, 3.10.3.
+ * ReadableStreamDefaultControllerShouldCallPull ( controller )
+ * Streams spec, 3.13.25.
+ * ReadableByteStreamControllerShouldCallPull ( controller )
+ */
+static bool ReadableStreamControllerShouldCallPull(
+ ReadableStreamController* unwrappedController) {
+ // Step 1: Let stream be controller.[[controlledReadableStream]]
+ // (or [[controlledReadableByteStream]]).
+ ReadableStream* unwrappedStream = unwrappedController->stream();
+
+ // 3.10.3. Step 2:
+ // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller)
+ // is false, return false.
+ // This turns out to be the same as 3.13.25 steps 2-3.
+
+ // 3.13.25 Step 2: If stream.[[state]] is not "readable", return false.
+ if (!unwrappedStream->readable()) {
+ return false;
+ }
+
+ // 3.13.25 Step 3: If controller.[[closeRequested]] is true, return false.
+ if (unwrappedController->closeRequested()) {
+ return false;
+ }
+
+ // Step 3 (or 4):
+ // If controller.[[started]] is false, return false.
+ if (!unwrappedController->started()) {
+ return false;
+ }
+
+ // 3.10.3.
+ // Step 4: If ! IsReadableStreamLocked(stream) is true and
+ // ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
+ //
+ // 3.13.25.
+ // Step 5: If ! ReadableStreamHasDefaultReader(stream) is true and
+ // ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
+ // Step 6: If ! ReadableStreamHasBYOBReader(stream) is true and
+ // ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true.
+ //
+ // All of these amount to the same thing in this implementation:
+ if (unwrappedStream->locked() &&
+ ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
+ return true;
+ }
+
+ // Step 5 (or 7):
+ // Let desiredSize be
+ // ! ReadableStreamDefaultControllerGetDesiredSize(controller).
+ // (ReadableByteStreamControllerGetDesiredSize in 3.13.25.)
+ double desiredSize =
+ ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
+
+ // Step 6 (or 8): Assert: desiredSize is not null (implicit).
+ // Step 7 (or 9): If desiredSize > 0, return true.
+ // Step 8 (or 10): Return false.
+ return desiredSize > 0;
+}
+
+/**
+ * Streams spec, 3.10.4.
+ * ReadableStreamDefaultControllerClearAlgorithms ( controller )
+ * and 3.13.4.
+ * ReadableByteStreamControllerClearAlgorithms ( controller )
+ */
+void js::ReadableStreamControllerClearAlgorithms(
+ Handle<ReadableStreamController*> controller) {
+ // Step 1: Set controller.[[pullAlgorithm]] to undefined.
+ // Step 2: Set controller.[[cancelAlgorithm]] to undefined.
+ // (In this implementation, the UnderlyingSource slot is part of the
+ // representation of these algorithms.)
+ controller->setPullMethod(UndefinedHandleValue);
+ controller->setCancelMethod(UndefinedHandleValue);
+ ReadableStreamController::clearUnderlyingSource(controller);
+
+ // Step 3 (of 3.10.4 only) : Set controller.[[strategySizeAlgorithm]] to
+ // undefined.
+ if (controller->is<ReadableStreamDefaultController>()) {
+ controller->as<ReadableStreamDefaultController>().setStrategySize(
+ UndefinedHandleValue);
+ }
+}
+
+/**
+ * Streams spec, 3.10.5. ReadableStreamDefaultControllerClose ( controller )
+ */
+MOZ_MUST_USE bool js::ReadableStreamDefaultControllerClose(
+ JSContext* cx,
+ Handle<ReadableStreamDefaultController*> unwrappedController) {
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
+
+ // Step 2: Assert:
+ // ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller)
+ // is true.
+ MOZ_ASSERT(!unwrappedController->closeRequested());
+ MOZ_ASSERT(unwrappedStream->readable());
+
+ // Step 3: Set controller.[[closeRequested]] to true.
+ unwrappedController->setCloseRequested();
+
+ // Step 4: If controller.[[queue]] is empty,
+ Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue());
+ if (unwrappedQueue->length() == 0) {
+ // Step a: Perform
+ // ! ReadableStreamDefaultControllerClearAlgorithms(controller).
+ ReadableStreamControllerClearAlgorithms(unwrappedController);
+
+ // Step b: Perform ! ReadableStreamClose(stream).
+ return ReadableStreamCloseInternal(cx, unwrappedStream);
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.10.6.
+ * ReadableStreamDefaultControllerEnqueue ( controller, chunk )
+ */
+MOZ_MUST_USE bool js::ReadableStreamDefaultControllerEnqueue(
+ JSContext* cx, Handle<ReadableStreamDefaultController*> unwrappedController,
+ Handle<Value> chunk) {
+ AssertSameCompartment(cx, chunk);
+
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
+
+ // Step 2: Assert:
+ // ! ReadableStreamDefaultControllerCanCloseOrEnqueue(controller) is
+ // true.
+ MOZ_ASSERT(!unwrappedController->closeRequested());
+ MOZ_ASSERT(unwrappedStream->readable());
+
+ // Step 3: If ! IsReadableStreamLocked(stream) is true and
+ // ! ReadableStreamGetNumReadRequests(stream) > 0, perform
+ // ! ReadableStreamFulfillReadRequest(stream, chunk, false).
+ if (unwrappedStream->locked() &&
+ ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk,
+ false)) {
+ return false;
+ }
+ } else {
+ // Step 4: Otherwise,
+ // Step a: Let result be the result of performing
+ // controller.[[strategySizeAlgorithm]], passing in chunk, and
+ // interpreting the result as an ECMAScript completion value.
+ // Step c: (on success) Let chunkSize be result.[[Value]].
+ Rooted<Value> chunkSize(cx, Int32Value(1));
+ bool success = true;
+ Rooted<Value> strategySize(cx, unwrappedController->strategySize());
+ if (!strategySize.isUndefined()) {
+ if (!cx->compartment()->wrap(cx, &strategySize)) {
+ return false;
+ }
+ success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize);
+ }
+
+ // Step d: Let enqueueResult be
+ // EnqueueValueWithSize(controller, chunk, chunkSize).
+ if (success) {
+ success = EnqueueValueWithSize(cx, unwrappedController, chunk, chunkSize);
+ }
+
+ // Step b: If result is an abrupt completion,
+ // and
+ // Step e: If enqueueResult is an abrupt completion,
+ if (!success) {
+ Rooted<Value> exn(cx);
+ Rooted<SavedFrame*> stack(cx);
+ if (!cx->isExceptionPending() ||
+ !GetAndClearExceptionAndStack(cx, &exn, &stack)) {
+ // Uncatchable error. Die immediately without erroring the
+ // stream.
+ return false;
+ }
+
+ // Step b.i: Perform ! ReadableStreamDefaultControllerError(
+ // controller, result.[[Value]]).
+ // Step e.i: Perform ! ReadableStreamDefaultControllerError(
+ // controller, enqueueResult.[[Value]]).
+ if (!ReadableStreamControllerError(cx, unwrappedController, exn)) {
+ return false;
+ }
+
+ // Step b.ii: Return result.
+ // Step e.ii: Return enqueueResult.
+ // (I.e., propagate the exception.)
+ cx->setPendingException(exn, stack);
+ return false;
+ }
+ }
+
+ // Step 5: Perform
+ // ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
+ return ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController);
+}
+
+/**
+ * Streams spec, 3.10.7. ReadableStreamDefaultControllerError ( controller, e )
+ * Streams spec, 3.13.11. ReadableByteStreamControllerError ( controller, e )
+ */
+MOZ_MUST_USE bool js::ReadableStreamControllerError(
+ JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
+ Handle<Value> e) {
+ MOZ_ASSERT(!cx->isExceptionPending());
+ AssertSameCompartment(cx, e);
+
+ // Step 1: Let stream be controller.[[controlledReadableStream]]
+ // (or controller.[[controlledReadableByteStream]]).
+ Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
+
+ // Step 2: If stream.[[state]] is not "readable", return.
+ if (!unwrappedStream->readable()) {
+ return true;
+ }
+
+ // Step 3 of 3.13.10:
+ // Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller).
+ if (unwrappedController->is<ReadableByteStreamController>()) {
+ Rooted<ReadableByteStreamController*> unwrappedByteStreamController(
+ cx, &unwrappedController->as<ReadableByteStreamController>());
+ if (!ReadableByteStreamControllerClearPendingPullIntos(
+ cx, unwrappedByteStreamController)) {
+ return false;
+ }
+ }
+
+ // Step 3 (or 4): Perform ! ResetQueue(controller).
+ if (!ResetQueue(cx, unwrappedController)) {
+ return false;
+ }
+
+ // Step 4 (or 5):
+ // Perform ! ReadableStreamDefaultControllerClearAlgorithms(controller)
+ // (or ReadableByteStreamControllerClearAlgorithms(controller)).
+ ReadableStreamControllerClearAlgorithms(unwrappedController);
+
+ // Step 5 (or 6): Perform ! ReadableStreamError(stream, e).
+ return ReadableStreamErrorInternal(cx, unwrappedStream, e);
+}
+
+/**
+ * Streams spec, 3.10.8.
+ * ReadableStreamDefaultControllerGetDesiredSize ( controller )
+ * Streams spec 3.13.14.
+ * ReadableByteStreamControllerGetDesiredSize ( controller )
+ */
+MOZ_MUST_USE double js::ReadableStreamControllerGetDesiredSizeUnchecked(
+ ReadableStreamController* controller) {
+ // Steps 1-4 done at callsites, so only assert that they have been done.
+#if DEBUG
+ ReadableStream* stream = controller->stream();
+ MOZ_ASSERT(!(stream->errored() || stream->closed()));
+#endif // DEBUG
+
+ // Step 5: Return controller.[[strategyHWM]] − controller.[[queueTotalSize]].
+ return controller->strategyHWM() - controller->queueTotalSize();
+}
+
+/**
+ * Streams spec, 3.10.11.
+ * SetUpReadableStreamDefaultController(stream, controller,
+ * startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark,
+ * sizeAlgorithm )
+ *
+ * The standard algorithm takes a `controller` argument which must be a new,
+ * blank object. This implementation creates a new controller instead.
+ *
+ * In the spec, three algorithms (startAlgorithm, pullAlgorithm,
+ * cancelAlgorithm) are passed as arguments to this routine. This
+ * implementation passes these "algorithms" as data, using four arguments:
+ * sourceAlgorithms, underlyingSource, pullMethod, and cancelMethod. The
+ * sourceAlgorithms argument tells how to interpret the other three:
+ *
+ * - SourceAlgorithms::Script - We're creating a stream from a JS source.
+ * The caller is `new ReadableStream(underlyingSource)` or
+ * `JS::NewReadableDefaultStreamObject`. `underlyingSource` is the
+ * source; `pullMethod` and `cancelMethod` are its .pull and
+ * .cancel methods, which the caller has already extracted and
+ * type-checked: each one must be either a callable JS object or undefined.
+ *
+ * Script streams use the start/pull/cancel algorithms defined in
+ * 3.10.12. SetUpReadableStreamDefaultControllerFromUnderlyingSource, which
+ * call JS methods of the underlyingSource.
+ *
+ * - SourceAlgorithms::Tee - We're creating a tee stream. `underlyingSource`
+ * is a TeeState object. `pullMethod` and `cancelMethod` are undefined.
+ *
+ * Tee streams use the start/pull/cancel algorithms given in
+ * 3.4.10. ReadableStreamTee.
+ *
+ * Note: All arguments must be same-compartment with cx. ReadableStream
+ * controllers are always created in the same compartment as the stream.
+ */
+MOZ_MUST_USE bool js::SetUpReadableStreamDefaultController(
+ JSContext* cx, Handle<ReadableStream*> stream,
+ SourceAlgorithms sourceAlgorithms, Handle<Value> underlyingSource,
+ Handle<Value> pullMethod, Handle<Value> cancelMethod, double highWaterMark,
+ Handle<Value> size) {
+ cx->check(stream, underlyingSource, size);
+ MOZ_ASSERT(pullMethod.isUndefined() || IsCallable(pullMethod));
+ MOZ_ASSERT(cancelMethod.isUndefined() || IsCallable(cancelMethod));
+ MOZ_ASSERT_IF(sourceAlgorithms != SourceAlgorithms::Script,
+ pullMethod.isUndefined());
+ MOZ_ASSERT_IF(sourceAlgorithms != SourceAlgorithms::Script,
+ cancelMethod.isUndefined());
+ MOZ_ASSERT(highWaterMark >= 0);
+ MOZ_ASSERT(size.isUndefined() || IsCallable(size));
+
+ // Done elsewhere in the standard: Create the new controller.
+ Rooted<ReadableStreamDefaultController*> controller(
+ cx, NewBuiltinClassInstance<ReadableStreamDefaultController>(cx));
+ if (!controller) {
+ return false;
+ }
+
+ // Step 1: Assert: stream.[[readableStreamController]] is undefined.
+ MOZ_ASSERT(!stream->hasController());
+
+ // Step 2: Set controller.[[controlledReadableStream]] to stream.
+ controller->setStream(stream);
+
+ // Step 3: Set controller.[[queue]] and controller.[[queueTotalSize]] to
+ // undefined (implicit), then perform ! ResetQueue(controller).
+ if (!ResetQueue(cx, controller)) {
+ return false;
+ }
+
+ // Step 4: Set controller.[[started]], controller.[[closeRequested]],
+ // controller.[[pullAgain]], and controller.[[pulling]] to false.
+ controller->setFlags(0);
+
+ // Step 5: Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm
+ // and controller.[[strategyHWM]] to highWaterMark.
+ controller->setStrategySize(size);
+ controller->setStrategyHWM(highWaterMark);
+
+ // Step 6: Set controller.[[pullAlgorithm]] to pullAlgorithm.
+ // (In this implementation, the pullAlgorithm is determined by the
+ // underlyingSource in combination with the pullMethod field.)
+ controller->setUnderlyingSource(underlyingSource);
+ controller->setPullMethod(pullMethod);
+
+ // Step 7: Set controller.[[cancelAlgorithm]] to cancelAlgorithm.
+ controller->setCancelMethod(cancelMethod);
+
+ // Step 8: Set stream.[[readableStreamController]] to controller.
+ stream->setController(controller);
+
+ // Step 9: Let startResult be the result of performing startAlgorithm.
+ Rooted<Value> startResult(cx);
+ if (sourceAlgorithms == SourceAlgorithms::Script) {
+ Rooted<Value> controllerVal(cx, ObjectValue(*controller));
+ if (!InvokeOrNoop(cx, underlyingSource, cx->names().start, controllerVal,
+ &startResult)) {
+ return false;
+ }
+ }
+
+ // Step 10: Let startPromise be a promise resolved with startResult.
+ Rooted<JSObject*> startPromise(
+ cx, PromiseObject::unforgeableResolve(cx, startResult));
+ if (!startPromise) {
+ return false;
+ }
+
+ // Step 11: Upon fulfillment of startPromise, [...]
+ // Step 12: Upon rejection of startPromise with reason r, [...]
+ Rooted<JSObject*> onStartFulfilled(
+ cx, NewHandler(cx, ReadableStreamControllerStartHandler, controller));
+ if (!onStartFulfilled) {
+ return false;
+ }
+ Rooted<JSObject*> onStartRejected(
+ cx,
+ NewHandler(cx, ReadableStreamControllerStartFailedHandler, controller));
+ if (!onStartRejected) {
+ return false;
+ }
+ if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled,
+ onStartRejected)) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.10.12.
+ * SetUpReadableStreamDefaultControllerFromUnderlyingSource( stream,
+ * underlyingSource, highWaterMark, sizeAlgorithm )
+ */
+MOZ_MUST_USE bool js::SetUpReadableStreamDefaultControllerFromUnderlyingSource(
+ JSContext* cx, Handle<ReadableStream*> stream,
+ Handle<Value> underlyingSource, double highWaterMark,
+ Handle<Value> sizeAlgorithm) {
+ // Step 1: Assert: underlyingSource is not undefined.
+ MOZ_ASSERT(!underlyingSource.isUndefined());
+
+ // Step 2: Let controller be ObjectCreate(the original value of
+ // ReadableStreamDefaultController's prototype property).
+ // (Deferred to SetUpReadableStreamDefaultController.)
+
+ // Step 3: Let startAlgorithm be the following steps:
+ // a. Return ? InvokeOrNoop(underlyingSource, "start",
+ // « controller »).
+ SourceAlgorithms sourceAlgorithms = SourceAlgorithms::Script;
+
+ // Step 4: Let pullAlgorithm be
+ // ? CreateAlgorithmFromUnderlyingMethod(underlyingSource, "pull",
+ // 0, « controller »).
+ Rooted<Value> pullMethod(cx);
+ if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSource,
+ "ReadableStream source.pull method",
+ cx->names().pull, &pullMethod)) {
+ return false;
+ }
+
+ // Step 5. Let cancelAlgorithm be
+ // ? CreateAlgorithmFromUnderlyingMethod(underlyingSource,
+ // "cancel", 1, « »).
+ Rooted<Value> cancelMethod(cx);
+ if (!CreateAlgorithmFromUnderlyingMethod(
+ cx, underlyingSource, "ReadableStream source.cancel method",
+ cx->names().cancel, &cancelMethod)) {
+ return false;
+ }
+
+ // Step 6. Perform ? SetUpReadableStreamDefaultController(stream,
+ // controller, startAlgorithm, pullAlgorithm, cancelAlgorithm,
+ // highWaterMark, sizeAlgorithm).
+ return SetUpReadableStreamDefaultController(
+ cx, stream, sourceAlgorithms, underlyingSource, pullMethod, cancelMethod,
+ highWaterMark, sizeAlgorithm);
+}
diff --git a/js/src/builtin/streams/ReadableStreamDefaultControllerOperations.h b/js/src/builtin/streams/ReadableStreamDefaultControllerOperations.h
new file mode 100644
index 0000000000..9d17a855ae
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamDefaultControllerOperations.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Readable stream default controller abstract operations. */
+
+#ifndef builtin_streams_ReadableStreamDefaultControllerOperations_h
+#define builtin_streams_ReadableStreamDefaultControllerOperations_h
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jstypes.h" // JS_PUBLIC_API
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::Value
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+class ReadableStream;
+class ReadableStreamController;
+class ReadableStreamDefaultController;
+
+extern MOZ_MUST_USE bool ReadableStreamDefaultControllerEnqueue(
+ JSContext* cx,
+ JS::Handle<ReadableStreamDefaultController*> unwrappedController,
+ JS::Handle<JS::Value> chunk);
+
+extern MOZ_MUST_USE bool ReadableStreamControllerError(
+ JSContext* cx, JS::Handle<ReadableStreamController*> unwrappedController,
+ JS::Handle<JS::Value> e);
+
+extern MOZ_MUST_USE bool ReadableStreamDefaultControllerClose(
+ JSContext* cx,
+ JS::Handle<ReadableStreamDefaultController*> unwrappedController);
+
+extern MOZ_MUST_USE double ReadableStreamControllerGetDesiredSizeUnchecked(
+ ReadableStreamController* controller);
+
+extern MOZ_MUST_USE bool ReadableStreamControllerCallPullIfNeeded(
+ JSContext* cx, JS::Handle<ReadableStreamController*> unwrappedController);
+
+extern void ReadableStreamControllerClearAlgorithms(
+ JS::Handle<ReadableStreamController*> controller);
+
+/**
+ * Characterizes the family of algorithms, (startAlgorithm, pullAlgorithm,
+ * cancelAlgorithm), associated with a readable stream.
+ *
+ * See the comment on SetUpReadableStreamDefaultController().
+ */
+enum class SourceAlgorithms {
+ Script,
+ Tee,
+};
+
+extern MOZ_MUST_USE bool SetUpReadableStreamDefaultController(
+ JSContext* cx, JS::Handle<ReadableStream*> stream,
+ SourceAlgorithms sourceAlgorithms, JS::Handle<JS::Value> underlyingSource,
+ JS::Handle<JS::Value> pullMethod, JS::Handle<JS::Value> cancelMethod,
+ double highWaterMark, JS::Handle<JS::Value> size);
+
+extern MOZ_MUST_USE bool
+SetUpReadableStreamDefaultControllerFromUnderlyingSource(
+ JSContext* cx, JS::Handle<ReadableStream*> stream,
+ JS::Handle<JS::Value> underlyingSource, double highWaterMark,
+ JS::Handle<JS::Value> sizeAlgorithm);
+
+} // namespace js
+
+#endif // builtin_streams_ReadableStreamDefaultControllerOperations_h
diff --git a/js/src/builtin/streams/ReadableStreamDefaultReader.cpp b/js/src/builtin/streams/ReadableStreamDefaultReader.cpp
new file mode 100644
index 0000000000..cf7cda48f6
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamDefaultReader.cpp
@@ -0,0 +1,265 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class ReadableStreamDefaultReader. */
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsapi.h" // JS_ReportErrorNumberASCII
+
+#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
+#include "builtin/streams/MiscellaneousOperations.h" // js::ReturnPromiseRejectedWithPendingError
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream
+#include "builtin/streams/ReadableStreamReader.h" // js::ForAuthorCodeBool, js::ReadableStream{,Default}Reader
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/Class.h" // JSClass, JS_NULL_CLASS_OPS
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "vm/PromiseObject.h" // js::PromiseObject
+
+#include "vm/Compartment-inl.h" // js::UnwrapAndTypeCheckThis
+#include "vm/JSObject-inl.h" // js::NewObjectWithClassProto
+#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::Rooted;
+using JS::Value;
+
+using js::ForAuthorCodeBool;
+using js::GetErrorMessage;
+using js::ListObject;
+using js::NewObjectWithClassProto;
+using js::PromiseObject;
+using js::ReadableStream;
+using js::ReadableStreamDefaultReader;
+using js::ReadableStreamReader;
+using js::UnwrapAndTypeCheckThis;
+
+/*** 3.6. Class ReadableStreamDefaultReader *********************************/
+
+/**
+ * Stream spec, 3.6.3. new ReadableStreamDefaultReader ( stream )
+ * Steps 2-4.
+ */
+MOZ_MUST_USE ReadableStreamDefaultReader* js::CreateReadableStreamDefaultReader(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream,
+ ForAuthorCodeBool forAuthorCode, Handle<JSObject*> proto /* = nullptr */) {
+ Rooted<ReadableStreamDefaultReader*> reader(
+ cx, NewObjectWithClassProto<ReadableStreamDefaultReader>(cx, proto));
+ if (!reader) {
+ return nullptr;
+ }
+
+ // Step 2: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
+ // exception.
+ if (unwrappedStream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_LOCKED);
+ return nullptr;
+ }
+
+ // Step 3: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
+ // Step 4: Set this.[[readRequests]] to a new empty List.
+ if (!ReadableStreamReaderGenericInitialize(cx, reader, unwrappedStream,
+ forAuthorCode)) {
+ return nullptr;
+ }
+
+ return reader;
+}
+
+/**
+ * Stream spec, 3.6.3. new ReadableStreamDefaultReader ( stream )
+ */
+bool ReadableStreamDefaultReader::constructor(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultReader")) {
+ return false;
+ }
+
+ // Implicit in the spec: Find the prototype object to use.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
+ return false;
+ }
+
+ // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError
+ // exception.
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapAndTypeCheckArgument<ReadableStream>(
+ cx, args, "ReadableStreamDefaultReader constructor", 0));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ Rooted<JSObject*> reader(
+ cx, CreateReadableStreamDefaultReader(cx, unwrappedStream,
+ ForAuthorCodeBool::Yes, proto));
+ if (!reader) {
+ return false;
+ }
+
+ args.rval().setObject(*reader);
+ return true;
+}
+
+/**
+ * Streams spec, 3.6.4.1 get closed
+ */
+static MOZ_MUST_USE bool ReadableStreamDefaultReader_closed(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ Rooted<ReadableStreamDefaultReader*> unwrappedReader(
+ cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args,
+ "get closed"));
+ if (!unwrappedReader) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: Return this.[[closedPromise]].
+ Rooted<JSObject*> closedPromise(cx, unwrappedReader->closedPromise());
+ if (!cx->compartment()->wrap(cx, &closedPromise)) {
+ return false;
+ }
+
+ args.rval().setObject(*closedPromise);
+ return true;
+}
+
+/**
+ * Streams spec, 3.6.4.2. cancel ( reason )
+ */
+static MOZ_MUST_USE bool ReadableStreamDefaultReader_cancel(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ Rooted<ReadableStreamDefaultReader*> unwrappedReader(
+ cx,
+ UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args, "cancel"));
+ if (!unwrappedReader) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ if (!unwrappedReader->hasStream()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason).
+ JSObject* cancelPromise =
+ ReadableStreamReaderGenericCancel(cx, unwrappedReader, args.get(0));
+ if (!cancelPromise) {
+ return false;
+ }
+ args.rval().setObject(*cancelPromise);
+ return true;
+}
+
+/**
+ * Streams spec, 3.6.4.3 read ( )
+ */
+static MOZ_MUST_USE bool ReadableStreamDefaultReader_read(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ Rooted<ReadableStreamDefaultReader*> unwrappedReader(
+ cx,
+ UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args, "read"));
+ if (!unwrappedReader) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ if (!unwrappedReader->hasStream()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamDefaultReaderRead(this, true).
+ PromiseObject* readPromise =
+ js::ReadableStreamDefaultReaderRead(cx, unwrappedReader);
+ if (!readPromise) {
+ return false;
+ }
+ args.rval().setObject(*readPromise);
+ return true;
+}
+
+/**
+ * Streams spec, 3.6.4.4. releaseLock ( )
+ */
+static bool ReadableStreamDefaultReader_releaseLock(JSContext* cx,
+ unsigned argc, Value* vp) {
+ // Step 1: If ! IsReadableStreamDefaultReader(this) is false,
+ // throw a TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<ReadableStreamDefaultReader*> reader(
+ cx, UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args,
+ "releaseLock"));
+ if (!reader) {
+ return false;
+ }
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return.
+ if (!reader->hasStream()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception.
+ Value val = reader->getFixedSlot(ReadableStreamReader::Slot_Requests);
+ if (!val.isUndefined()) {
+ ListObject* readRequests = &val.toObject().as<ListObject>();
+ if (readRequests->length() != 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_EMPTY,
+ "releaseLock");
+ return false;
+ }
+ }
+
+ // Step 4: Perform ! ReadableStreamReaderGenericRelease(this).
+ if (!js::ReadableStreamReaderGenericRelease(cx, reader)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static const JSFunctionSpec ReadableStreamDefaultReader_methods[] = {
+ JS_FN("cancel", ReadableStreamDefaultReader_cancel, 1, 0),
+ JS_FN("read", ReadableStreamDefaultReader_read, 0, 0),
+ JS_FN("releaseLock", ReadableStreamDefaultReader_releaseLock, 0, 0),
+ JS_FS_END};
+
+static const JSPropertySpec ReadableStreamDefaultReader_properties[] = {
+ JS_PSG("closed", ReadableStreamDefaultReader_closed, 0), JS_PS_END};
+
+const JSClass ReadableStreamReader::class_ = {"ReadableStreamReader"};
+
+JS_STREAMS_CLASS_SPEC(ReadableStreamDefaultReader, 1, SlotCount,
+ js::ClassSpec::DontDefineConstructor, 0,
+ JS_NULL_CLASS_OPS);
diff --git a/js/src/builtin/streams/ReadableStreamInternals.cpp b/js/src/builtin/streams/ReadableStreamInternals.cpp
new file mode 100644
index 0000000000..a6f3fbc826
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamInternals.cpp
@@ -0,0 +1,473 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* The interface between readable streams and controllers. */
+
+#include "builtin/streams/ReadableStreamInternals.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include <stdint.h> // uint32_t
+
+#include "jsfriendapi.h" // js::AssertSameCompartment
+
+#include "builtin/streams/ReadableStreamController.h" // js::ReadableStreamController{,CancelSteps}
+#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStream{,Default}Reader, js::ForAuthorCodeBool
+#include "gc/AllocKind.h" // js::gc::AllocKind
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/GCAPI.h" // JS::AutoSuppressGCAnalysis
+#include "js/Promise.h" // JS::CallOriginalPromiseThen, JS::ResolvePromise
+#include "js/Result.h" // JS_TRY_VAR_OR_RETURN_NULL
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "js/Stream.h" // JS::ReadableStreamUnderlyingSource, JS::ReadableStreamMode
+#include "js/Value.h" // JS::Value, JS::{Boolean,Object}Value, JS::UndefinedHandleValue
+#include "vm/JSContext.h" // JSContext
+#include "vm/JSFunction.h" // JSFunction, js::NewNativeFunction
+#include "vm/NativeObject.h" // js::NativeObject, js::PlainObject
+#include "vm/ObjectGroup.h" // js::GenericObject
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+#include "vm/Realm.h" // JS::Realm
+#include "vm/StringType.h" // js::PropertyName
+
+#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
+#include "builtin/streams/MiscellaneousOperations-inl.h" // js::{Reject,Resolve}UnwrappedPromiseWithUndefined
+#include "builtin/streams/ReadableStreamReader-inl.h" // js::js::UnwrapReaderFromStream{,NoThrow}
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/List-inl.h" // js::ListObject, js::AppendToListInFixedSlot, js::StoreNewListInFixedSlot
+#include "vm/PlainObject-inl.h" // js::PlainObject::createWithTemplate
+#include "vm/Realm-inl.h" // JS::Realm
+
+using JS::BooleanValue;
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::ObjectValue;
+using JS::ResolvePromise;
+using JS::Rooted;
+using JS::UndefinedHandleValue;
+using JS::Value;
+
+using js::PlainObject;
+using js::ReadableStream;
+
+/*** 3.5. The interface between readable streams and controllers ************/
+
+/**
+ * Streams spec, 3.5.1.
+ * ReadableStreamAddReadIntoRequest ( stream, forAuthorCode )
+ * Streams spec, 3.5.2.
+ * ReadableStreamAddReadRequest ( stream, forAuthorCode )
+ *
+ * Our implementation does not pass around forAuthorCode parameters in the same
+ * places as the standard, but the effect is the same. See the comment on
+ * `ReadableStreamReader::forAuthorCode()`.
+ */
+MOZ_MUST_USE js::PromiseObject* js::ReadableStreamAddReadOrReadIntoRequest(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
+ // Step 1: Assert: ! IsReadableStream{BYOB,Default}Reader(stream.[[reader]])
+ // is true.
+ // (Only default readers exist so far.)
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, UnwrapReaderFromStream(cx, unwrappedStream));
+ if (!unwrappedReader) {
+ return nullptr;
+ }
+ MOZ_ASSERT(unwrappedReader->is<ReadableStreamDefaultReader>());
+
+ // Step 2 of 3.5.1: Assert: stream.[[state]] is "readable" or "closed".
+ // Step 2 of 3.5.2: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(unwrappedStream->readable() || unwrappedStream->closed());
+ MOZ_ASSERT_IF(unwrappedReader->is<ReadableStreamDefaultReader>(),
+ unwrappedStream->readable());
+
+ // Step 3: Let promise be a new promise.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 4: Let read{Into}Request be
+ // Record {[[promise]]: promise, [[forAuthorCode]]: forAuthorCode}.
+ // Step 5: Append read{Into}Request as the last element of
+ // stream.[[reader]].[[read{Into}Requests]].
+ // Since we don't need the [[forAuthorCode]] field (see the comment on
+ // `ReadableStreamReader::forAuthorCode()`), we elide the Record and store
+ // only the promise.
+ if (!AppendToListInFixedSlot(cx, unwrappedReader,
+ ReadableStreamReader::Slot_Requests, promise)) {
+ return nullptr;
+ }
+
+ // Step 6: Return promise.
+ return promise;
+}
+
+/**
+ * Used for transforming the result of promise fulfillment/rejection.
+ */
+static bool ReturnUndefined(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 3.5.3. ReadableStreamCancel ( stream, reason )
+ */
+MOZ_MUST_USE JSObject* js::ReadableStreamCancel(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream,
+ Handle<Value> reason) {
+ AssertSameCompartment(cx, reason);
+
+ // Step 1: Set stream.[[disturbed]] to true.
+ unwrappedStream->setDisturbed();
+
+ // Step 2: If stream.[[state]] is "closed", return a promise resolved with
+ // undefined.
+ if (unwrappedStream->closed()) {
+ return PromiseResolvedWithUndefined(cx);
+ }
+
+ // Step 3: If stream.[[state]] is "errored", return a promise rejected with
+ // stream.[[storedError]].
+ if (unwrappedStream->errored()) {
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return nullptr;
+ }
+ return PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ // Step 4: Perform ! ReadableStreamClose(stream).
+ if (!ReadableStreamCloseInternal(cx, unwrappedStream)) {
+ return nullptr;
+ }
+
+ // Step 5: Let sourceCancelPromise be
+ // ! stream.[[readableStreamController]].[[CancelSteps]](reason).
+ Rooted<ReadableStreamController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ Rooted<JSObject*> sourceCancelPromise(
+ cx, ReadableStreamControllerCancelSteps(cx, unwrappedController, reason));
+ if (!sourceCancelPromise) {
+ return nullptr;
+ }
+
+ // Step 6: Return the result of reacting to sourceCancelPromise with a
+ // fulfillment step that returns undefined.
+ Handle<PropertyName*> funName = cx->names().empty;
+ Rooted<JSFunction*> returnUndefined(
+ cx, NewNativeFunction(cx, ReturnUndefined, 0, funName,
+ gc::AllocKind::FUNCTION, GenericObject));
+ if (!returnUndefined) {
+ return nullptr;
+ }
+ return JS::CallOriginalPromiseThen(cx, sourceCancelPromise, returnUndefined,
+ nullptr);
+}
+
+/**
+ * Streams spec, 3.5.4. ReadableStreamClose ( stream )
+ */
+MOZ_MUST_USE bool js::ReadableStreamCloseInternal(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
+ // Step 1: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(unwrappedStream->readable());
+
+ // Step 2: Set stream.[[state]] to "closed".
+ unwrappedStream->setClosed();
+
+ // Step 4: If reader is undefined, return (reordered).
+ if (!unwrappedStream->hasReader()) {
+ return true;
+ }
+
+ // Step 3: Let reader be stream.[[reader]].
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, UnwrapReaderFromStream(cx, unwrappedStream));
+ if (!unwrappedReader) {
+ return false;
+ }
+
+ // Step 5: If ! IsReadableStreamDefaultReader(reader) is true,
+ if (unwrappedReader->is<ReadableStreamDefaultReader>()) {
+ ForAuthorCodeBool forAuthorCode = unwrappedReader->forAuthorCode();
+
+ // Step a: Repeat for each readRequest that is an element of
+ // reader.[[readRequests]],
+ Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
+ uint32_t len = unwrappedReadRequests->length();
+ Rooted<JSObject*> readRequest(cx);
+ Rooted<JSObject*> resultObj(cx);
+ Rooted<Value> resultVal(cx);
+ for (uint32_t i = 0; i < len; i++) {
+ // Step i: Resolve readRequest.[[promise]] with
+ // ! ReadableStreamCreateReadResult(undefined, true,
+ // readRequest.[[forAuthorCode]]).
+ readRequest = &unwrappedReadRequests->getAs<JSObject>(i);
+ if (!cx->compartment()->wrap(cx, &readRequest)) {
+ return false;
+ }
+
+ resultObj = js::ReadableStreamCreateReadResult(cx, UndefinedHandleValue,
+ true, forAuthorCode);
+ if (!resultObj) {
+ return false;
+ }
+ resultVal = ObjectValue(*resultObj);
+ if (!ResolvePromise(cx, readRequest, resultVal)) {
+ return false;
+ }
+ }
+
+ // Step b: Set reader.[[readRequests]] to an empty List.
+ unwrappedReader->clearRequests();
+ }
+
+ // Step 6: Resolve reader.[[closedPromise]] with undefined.
+ if (!ResolveUnwrappedPromiseWithUndefined(cx,
+ unwrappedReader->closedPromise())) {
+ return false;
+ }
+
+ if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
+ // Make sure we're in the stream's compartment.
+ AutoRealm ar(cx, unwrappedStream);
+ JS::ReadableStreamUnderlyingSource* source =
+ unwrappedStream->controller()->externalSource();
+ source->onClosed(cx, unwrappedStream);
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.5.5. ReadableStreamCreateReadResult ( value, done,
+ * forAuthorCode )
+ */
+MOZ_MUST_USE PlainObject* js::ReadableStreamCreateReadResult(
+ JSContext* cx, Handle<Value> value, bool done,
+ ForAuthorCodeBool forAuthorCode) {
+ // Step 1: Let prototype be null.
+ // Step 2: If forAuthorCode is true, set prototype to %ObjectPrototype%.
+ Rooted<PlainObject*> templateObject(
+ cx,
+ forAuthorCode == ForAuthorCodeBool::Yes
+ ? cx->realm()->getOrCreateIterResultTemplateObject(cx)
+ : cx->realm()->getOrCreateIterResultWithoutPrototypeTemplateObject(
+ cx));
+ if (!templateObject) {
+ return nullptr;
+ }
+
+ // Step 3: Assert: Type(done) is Boolean (implicit).
+
+ // Step 4: Let obj be ObjectCreate(prototype).
+ PlainObject* obj;
+ JS_TRY_VAR_OR_RETURN_NULL(
+ cx, obj, PlainObject::createWithTemplate(cx, templateObject));
+
+ // Step 5: Perform CreateDataProperty(obj, "value", value).
+ obj->setSlot(Realm::IterResultObjectValueSlot, value);
+
+ // Step 6: Perform CreateDataProperty(obj, "done", done).
+ obj->setSlot(Realm::IterResultObjectDoneSlot, BooleanValue(done));
+
+ // Step 7: Return obj.
+ return obj;
+}
+
+/**
+ * Streams spec, 3.5.6. ReadableStreamError ( stream, e )
+ */
+MOZ_MUST_USE bool js::ReadableStreamErrorInternal(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream, Handle<Value> e) {
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+
+ // Step 2: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(unwrappedStream->readable());
+
+ // Step 3: Set stream.[[state]] to "errored".
+ unwrappedStream->setErrored();
+
+ // Step 4: Set stream.[[storedError]] to e.
+ {
+ AutoRealm ar(cx, unwrappedStream);
+ Rooted<Value> wrappedError(cx, e);
+ if (!cx->compartment()->wrap(cx, &wrappedError)) {
+ return false;
+ }
+ unwrappedStream->setStoredError(wrappedError);
+ }
+
+ // Step 6: If reader is undefined, return (reordered).
+ if (!unwrappedStream->hasReader()) {
+ return true;
+ }
+
+ // Step 5: Let reader be stream.[[reader]].
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, UnwrapReaderFromStream(cx, unwrappedStream));
+ if (!unwrappedReader) {
+ return false;
+ }
+
+ // Steps 7-8: (Identical in our implementation.)
+ // Step 7.a/8.b: Repeat for each read{Into}Request that is an element of
+ // reader.[[read{Into}Requests]],
+ {
+ Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
+ Rooted<JSObject*> readRequest(cx);
+ uint32_t len = unwrappedReadRequests->length();
+ for (uint32_t i = 0; i < len; i++) {
+ // Step i: Reject read{Into}Request.[[promise]] with e.
+ // Responses have to be created in the compartment from which the error
+ // was triggered, which might not be the same as the one the request was
+ // created in, so we have to wrap requests here.
+ readRequest = &unwrappedReadRequests->get(i).toObject();
+ if (!RejectUnwrappedPromiseWithError(cx, &readRequest, e)) {
+ return false;
+ }
+ }
+ }
+
+ // Step 7.b/8.c: Set reader.[[read{Into}Requests]] to a new empty List.
+ if (!StoreNewListInFixedSlot(cx, unwrappedReader,
+ ReadableStreamReader::Slot_Requests)) {
+ return false;
+ }
+
+ // Step 9: Reject reader.[[closedPromise]] with e.
+ if (!RejectUnwrappedPromiseWithError(cx, unwrappedReader->closedPromise(),
+ e)) {
+ return false;
+ }
+
+ // Step 10: Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+ //
+ // `closedPromise` can return a CCW, but that case is filtered out by step 6,
+ // given the only place that can set [[closedPromise]] to a CCW is
+ // 3.8.5 ReadableStreamReaderGenericRelease step 4, and
+ // 3.8.5 ReadableStreamReaderGenericRelease step 6 sets
+ // stream.[[reader]] to undefined.
+ Rooted<JSObject*> closedPromise(cx, unwrappedReader->closedPromise());
+ js::SetSettledPromiseIsHandled(cx, closedPromise.as<PromiseObject>());
+
+ if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) {
+ // Make sure we're in the stream's compartment.
+ AutoRealm ar(cx, unwrappedStream);
+ JS::ReadableStreamUnderlyingSource* source =
+ unwrappedStream->controller()->externalSource();
+
+ // Ensure that the embedding doesn't have to deal with
+ // mixed-compartment arguments to the callback.
+ Rooted<Value> error(cx, e);
+ if (!cx->compartment()->wrap(cx, &error)) {
+ return false;
+ }
+ source->onErrored(cx, unwrappedStream, error);
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.5.7.
+ * ReadableStreamFulfillReadIntoRequest( stream, chunk, done )
+ * Streams spec, 3.5.8.
+ * ReadableStreamFulfillReadRequest ( stream, chunk, done )
+ * These two spec functions are identical in our implementation.
+ */
+MOZ_MUST_USE bool js::ReadableStreamFulfillReadOrReadIntoRequest(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream, Handle<Value> chunk,
+ bool done) {
+ cx->check(chunk);
+
+ // Step 1: Let reader be stream.[[reader]].
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, UnwrapReaderFromStream(cx, unwrappedStream));
+ if (!unwrappedReader) {
+ return false;
+ }
+
+ // Step 2: Let read{Into}Request be the first element of
+ // reader.[[read{Into}Requests]].
+ // Step 3: Remove read{Into}Request from reader.[[read{Into}Requests]],
+ // shifting all other elements downward (so that the second becomes
+ // the first, and so on).
+ Rooted<ListObject*> unwrappedReadIntoRequests(cx,
+ unwrappedReader->requests());
+ Rooted<JSObject*> readIntoRequest(
+ cx, &unwrappedReadIntoRequests->popFirstAs<JSObject>(cx));
+ MOZ_ASSERT(readIntoRequest);
+ if (!cx->compartment()->wrap(cx, &readIntoRequest)) {
+ return false;
+ }
+
+ // Step 4: Resolve read{Into}Request.[[promise]] with
+ // ! ReadableStreamCreateReadResult(chunk, done,
+ // readIntoRequest.[[forAuthorCode]]).
+ PlainObject* iterResult = ReadableStreamCreateReadResult(
+ cx, chunk, done, unwrappedReader->forAuthorCode());
+ if (!iterResult) {
+ return false;
+ }
+
+ Rooted<Value> val(cx, ObjectValue(*iterResult));
+ return ResolvePromise(cx, readIntoRequest, val);
+}
+
+/**
+ * Streams spec, 3.5.9. ReadableStreamGetNumReadIntoRequests ( stream )
+ * Streams spec, 3.5.10. ReadableStreamGetNumReadRequests ( stream )
+ * (Identical implementation.)
+ */
+uint32_t js::ReadableStreamGetNumReadRequests(ReadableStream* stream) {
+ // Step 1: Return the number of elements in
+ // stream.[[reader]].[[read{Into}Requests]].
+ if (!stream->hasReader()) {
+ return 0;
+ }
+
+ JS::AutoSuppressGCAnalysis nogc;
+ ReadableStreamReader* reader = UnwrapReaderFromStreamNoThrow(stream);
+
+ // Reader is a dead wrapper, treat it as non-existent.
+ if (!reader) {
+ return 0;
+ }
+
+ return reader->requests()->length();
+}
+
+// Streams spec, 3.5.11. ReadableStreamHasBYOBReader ( stream )
+//
+// Not implemented.
+
+/**
+ * Streams spec 3.5.12. ReadableStreamHasDefaultReader ( stream )
+ */
+MOZ_MUST_USE bool js::ReadableStreamHasDefaultReader(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream, bool* result) {
+ // Step 1: Let reader be stream.[[reader]].
+ // Step 2: If reader is undefined, return false.
+ if (!unwrappedStream->hasReader()) {
+ *result = false;
+ return true;
+ }
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, UnwrapReaderFromStream(cx, unwrappedStream));
+ if (!unwrappedReader) {
+ return false;
+ }
+
+ // Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
+ // Step 4: Return true.
+ *result = unwrappedReader->is<ReadableStreamDefaultReader>();
+ return true;
+}
diff --git a/js/src/builtin/streams/ReadableStreamInternals.h b/js/src/builtin/streams/ReadableStreamInternals.h
new file mode 100644
index 0000000000..3fe6400ec7
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamInternals.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* The interface between readable streams and controllers. */
+
+#ifndef builtin_streams_ReadableStreamInternals_h
+#define builtin_streams_ReadableStreamInternals_h
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jstypes.h" // JS_PUBLIC_API
+#include "builtin/streams/ReadableStreamReader.h" // js::ForAuthorCodeBool
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::Value
+
+struct JS_PUBLIC_API JSContext;
+class JS_PUBLIC_API JSObject;
+
+namespace js {
+
+class PlainObject;
+class PromiseObject;
+class ReadableStream;
+
+extern MOZ_MUST_USE PromiseObject* ReadableStreamAddReadOrReadIntoRequest(
+ JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream);
+
+extern MOZ_MUST_USE JSObject* ReadableStreamCancel(
+ JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
+ JS::Handle<JS::Value> reason);
+
+extern MOZ_MUST_USE bool ReadableStreamCloseInternal(
+ JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream);
+
+extern MOZ_MUST_USE PlainObject* ReadableStreamCreateReadResult(
+ JSContext* cx, JS::Handle<JS::Value> value, bool done,
+ ForAuthorCodeBool forAuthorCode);
+
+extern MOZ_MUST_USE bool ReadableStreamErrorInternal(
+ JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
+ JS::Handle<JS::Value> e);
+
+extern MOZ_MUST_USE bool ReadableStreamFulfillReadOrReadIntoRequest(
+ JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
+ JS::Handle<JS::Value> chunk, bool done);
+
+extern uint32_t ReadableStreamGetNumReadRequests(ReadableStream* stream);
+
+extern MOZ_MUST_USE bool ReadableStreamHasDefaultReader(
+ JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream, bool* result);
+
+} // namespace js
+
+#endif // builtin_streams_ReadableStreamInternals_h
diff --git a/js/src/builtin/streams/ReadableStreamOperations.cpp b/js/src/builtin/streams/ReadableStreamOperations.cpp
new file mode 100644
index 0000000000..f40e375749
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamOperations.cpp
@@ -0,0 +1,653 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* General readable stream abstract operations. */
+
+#include "builtin/streams/ReadableStreamOperations.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "builtin/Array.h" // js::NewDenseFullyAllocatedArray
+#include "builtin/Promise.h" // js::RejectPromiseWithPendingError
+#include "builtin/streams/PipeToState.h" // js::PipeToState
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream
+#include "builtin/streams/ReadableStreamController.h" // js::ReadableStream{,Default}Controller
+#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::ReadableStreamDefaultController{Close,Enqueue}, js::ReadableStreamControllerError, js::SourceAlgorithms
+#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStreamCancel
+#include "builtin/streams/ReadableStreamReader.h" // js::CreateReadableStreamDefaultReader, js::ForAuthorCodeBool, js::ReadableStream{,Default}Reader, js::ReadableStreamDefaultReaderRead
+#include "builtin/streams/TeeState.h" // js::TeeState
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/Promise.h" // JS::CallOriginalPromiseThen, JS::AddPromiseReactions
+#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted
+#include "js/Value.h" // JS::Value, JS::UndefinedHandleValue
+#include "vm/JSContext.h" // JSContext
+#include "vm/NativeObject.h" // js::NativeObject
+#include "vm/ObjectOperations.h" // js::GetProperty
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+
+#include "builtin/HandlerFunction-inl.h" // js::NewHandler, js::TargetFromHandler
+#include "builtin/streams/MiscellaneousOperations-inl.h" // js::ResolveUnwrappedPromiseWithValue
+#include "builtin/streams/ReadableStreamReader-inl.h" // js::UnwrapReaderFromStream
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::Unwrap{Callee,Internal}Slot
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/JSObject-inl.h" // js::IsCallable, js::NewObjectWithClassProto
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using js::IsCallable;
+using js::NewHandler;
+using js::NewObjectWithClassProto;
+using js::PromiseObject;
+using js::ReadableStream;
+using js::ReadableStreamDefaultController;
+using js::ReadableStreamDefaultControllerEnqueue;
+using js::ReadableStreamDefaultReader;
+using js::ReadableStreamReader;
+using js::SourceAlgorithms;
+using js::TargetFromHandler;
+using js::TeeState;
+using js::UnwrapCalleeSlot;
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::MutableHandle;
+using JS::ObjectValue;
+using JS::Rooted;
+using JS::UndefinedHandleValue;
+using JS::Value;
+
+/*** 3.4. General readable stream abstract operations ***********************/
+
+// Streams spec, 3.4.1. AcquireReadableStreamBYOBReader ( stream )
+// Always inlined.
+
+// Streams spec, 3.4.2. AcquireReadableStreamDefaultReader ( stream )
+// Always inlined. See CreateReadableStreamDefaultReader.
+
+/**
+ * Streams spec, 3.4.3. CreateReadableStream (
+ * startAlgorithm, pullAlgorithm, cancelAlgorithm
+ * [, highWaterMark [, sizeAlgorithm ] ] )
+ *
+ * The start/pull/cancelAlgorithm arguments are represented instead as four
+ * arguments: sourceAlgorithms, underlyingSource, pullMethod, cancelMethod.
+ * See the comment on SetUpReadableStreamDefaultController.
+ */
+static MOZ_MUST_USE ReadableStream* CreateReadableStream(
+ JSContext* cx, SourceAlgorithms sourceAlgorithms,
+ Handle<Value> underlyingSource,
+ Handle<Value> pullMethod = UndefinedHandleValue,
+ Handle<Value> cancelMethod = UndefinedHandleValue, double highWaterMark = 1,
+ Handle<Value> sizeAlgorithm = UndefinedHandleValue,
+ Handle<JSObject*> proto = nullptr) {
+ cx->check(underlyingSource, sizeAlgorithm, proto);
+ MOZ_ASSERT(sizeAlgorithm.isUndefined() || IsCallable(sizeAlgorithm));
+
+ // Step 1: If highWaterMark was not passed, set it to 1 (implicit).
+ // Step 2: If sizeAlgorithm was not passed, set it to an algorithm that
+ // returns 1 (implicit).
+ // Step 3: Assert: ! IsNonNegativeNumber(highWaterMark) is true.
+ MOZ_ASSERT(highWaterMark >= 0);
+
+ // Step 4: Let stream be ObjectCreate(the original value of ReadableStream's
+ // prototype property).
+ // Step 5: Perform ! InitializeReadableStream(stream).
+ Rooted<ReadableStream*> stream(cx,
+ ReadableStream::create(cx, nullptr, proto));
+ if (!stream) {
+ return nullptr;
+ }
+
+ // Step 6: Let controller be ObjectCreate(the original value of
+ // ReadableStreamDefaultController's prototype property).
+ // Step 7: Perform ? SetUpReadableStreamDefaultController(stream,
+ // controller, startAlgorithm, pullAlgorithm, cancelAlgorithm,
+ // highWaterMark, sizeAlgorithm).
+ if (!SetUpReadableStreamDefaultController(
+ cx, stream, sourceAlgorithms, underlyingSource, pullMethod,
+ cancelMethod, highWaterMark, sizeAlgorithm)) {
+ return nullptr;
+ }
+
+ // Step 8: Return stream.
+ return stream;
+}
+
+// Streams spec, 3.4.4. CreateReadableByteStream (
+// startAlgorithm, pullAlgorithm, cancelAlgorithm
+// [, highWaterMark [, autoAllocateChunkSize ] ] )
+// Not implemented.
+
+/**
+ * Streams spec, 3.4.5. InitializeReadableStream ( stream )
+ */
+/* static */ MOZ_MUST_USE ReadableStream* ReadableStream::create(
+ JSContext* cx, void* nsISupportsObject_alreadyAddreffed /* = nullptr */,
+ Handle<JSObject*> proto /* = nullptr */) {
+ // In the spec, InitializeReadableStream is always passed a newly created
+ // ReadableStream object. We instead create it here and return it below.
+ Rooted<ReadableStream*> stream(
+ cx, NewObjectWithClassProto<ReadableStream>(cx, proto));
+ if (!stream) {
+ return nullptr;
+ }
+
+ stream->setPrivate(nsISupportsObject_alreadyAddreffed);
+
+ // Step 1: Set stream.[[state]] to "readable".
+ stream->initStateBits(Readable);
+ MOZ_ASSERT(stream->readable());
+
+ // Step 2: Set stream.[[reader]] and stream.[[storedError]] to
+ // undefined (implicit).
+ MOZ_ASSERT(!stream->hasReader());
+ MOZ_ASSERT(stream->storedError().isUndefined());
+
+ // Step 3: Set stream.[[disturbed]] to false (done in step 1).
+ MOZ_ASSERT(!stream->disturbed());
+
+ return stream;
+}
+
+// Streams spec, 3.4.6. IsReadableStream ( x )
+// Using UnwrapAndTypeCheck templates instead.
+
+// Streams spec, 3.4.7. IsReadableStreamDisturbed ( stream )
+// Using stream->disturbed() instead.
+
+/**
+ * Streams spec, 3.4.8. IsReadableStreamLocked ( stream )
+ */
+bool ReadableStream::locked() const {
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+ // Step 2: If stream.[[reader]] is undefined, return false.
+ // Step 3: Return true.
+ // Special-casing for streams with external sources. Those can be locked
+ // explicitly via JSAPI, which is indicated by a controller flag.
+ // IsReadableStreamLocked is called from the controller's constructor, at
+ // which point we can't yet call stream->controller(), but the source also
+ // can't be locked yet.
+ if (hasController() && controller()->sourceLocked()) {
+ return true;
+ }
+ return hasReader();
+}
+
+// Streams spec, 3.4.9. IsReadableStreamAsyncIterator ( x )
+//
+// Not implemented.
+
+/**
+ * Streams spec, 3.4.10. ReadableStreamTee steps 12.c.i-x.
+ */
+static bool TeeReaderReadHandler(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<TeeState*> unwrappedTeeState(cx,
+ UnwrapCalleeSlot<TeeState>(cx, args, 0));
+ if (!unwrappedTeeState) {
+ return false;
+ }
+
+ Handle<Value> resultVal = args.get(0);
+
+ // Step 12.c.i: Set reading to false.
+ unwrappedTeeState->unsetReading();
+
+ // Step 12.c.ii: Assert: Type(result) is Object.
+ Rooted<JSObject*> result(cx, &resultVal.toObject());
+
+ bool done;
+ {
+ // Step 12.c.iii: Let done be ? Get(result, "done").
+ // (This can fail only if `result` was nuked.)
+ Rooted<Value> doneVal(cx);
+ if (!GetProperty(cx, result, result, cx->names().done, &doneVal)) {
+ return false;
+ }
+
+ // Step 12.c.iv: Assert: Type(done) is Boolean.
+ done = doneVal.toBoolean();
+ }
+
+ // Step 12.c.v: If done is true,
+ if (done) {
+ // Step 12.c.v.1: If canceled1 is false,
+ if (!unwrappedTeeState->canceled1()) {
+ // Step 12.c.v.1.a: Perform
+ // ! ReadableStreamDefaultControllerClose(
+ // branch1.[[readableStreamController]]).
+ Rooted<ReadableStreamDefaultController*> unwrappedBranch1(
+ cx, unwrappedTeeState->branch1());
+ if (!ReadableStreamDefaultControllerClose(cx, unwrappedBranch1)) {
+ return false;
+ }
+ }
+
+ // Step 12.c.v.2: If canceled2 is false,
+ if (!unwrappedTeeState->canceled2()) {
+ // Step 12.c.v.2.a: Perform
+ // ! ReadableStreamDefaultControllerClose(
+ // branch2.[[readableStreamController]]).
+ Rooted<ReadableStreamDefaultController*> unwrappedBranch2(
+ cx, unwrappedTeeState->branch2());
+ if (!ReadableStreamDefaultControllerClose(cx, unwrappedBranch2)) {
+ return false;
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 12.c.vi: Let value be ! Get(result, "value").
+ // (This can fail only if `result` was nuked.)
+ Rooted<Value> value(cx);
+ if (!GetProperty(cx, result, result, cx->names().value, &value)) {
+ return false;
+ }
+
+ // Step 12.c.vii: Let value1 and value2 be value.
+ // Step 12.c.viii: If canceled2 is false and cloneForBranch2 is true, set
+ // value2 to
+ // ? StructuredDeserialize(? StructuredSerialize(value2),
+ // the current Realm Record).
+ // We don't yet support any specifications that use cloneForBranch2, and
+ // the Streams spec doesn't offer any way for author code to enable it,
+ // so it's always false here.
+ auto& value1 = value;
+ MOZ_ASSERT(!unwrappedTeeState->cloneForBranch2(),
+ "support for cloneForBranch2=true is not yet implemented");
+ auto& value2 = value;
+
+ Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
+
+ // Step 12.c.ix: If canceled1 is false, perform
+ // ? ReadableStreamDefaultControllerEnqueue(
+ // branch1.[[readableStreamController]], value1).
+ if (!unwrappedTeeState->canceled1()) {
+ unwrappedController = unwrappedTeeState->branch1();
+ if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
+ value1)) {
+ return false;
+ }
+ }
+
+ // Step 12.c.x: If canceled2 is false, perform
+ // ? ReadableStreamDefaultControllerEnqueue(
+ // branch2.[[readableStreamController]], value2).
+ if (!unwrappedTeeState->canceled2()) {
+ unwrappedController = unwrappedTeeState->branch2();
+ if (!ReadableStreamDefaultControllerEnqueue(cx, unwrappedController,
+ value2)) {
+ return false;
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 3.4.10. ReadableStreamTee step 12, "Let pullAlgorithm be the
+ * following steps:"
+ */
+MOZ_MUST_USE PromiseObject* js::ReadableStreamTee_Pull(
+ JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState) {
+ // Combine step 12.a/12.e far below, and handle steps 12.b-12.d after
+ // inverting step 12.a's "If reading is true" condition.
+ if (!unwrappedTeeState->reading()) {
+ // Step 12.b: Set reading to true.
+ unwrappedTeeState->setReading();
+
+ // Implicit in the spec: Unpack `reader` from the TeeState (by way of the
+ // stream stored in one of its slots).
+ Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx);
+ {
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapInternalSlot<ReadableStream>(cx, unwrappedTeeState,
+ TeeState::Slot_Stream));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+ ReadableStreamReader* unwrappedReaderObj =
+ UnwrapReaderFromStream(cx, unwrappedStream);
+ if (!unwrappedReaderObj) {
+ return nullptr;
+ }
+
+ unwrappedReader = &unwrappedReaderObj->as<ReadableStreamDefaultReader>();
+ }
+
+ // Step 12.c: Let readPromise be the result of reacting to
+ // ! ReadableStreamDefaultReaderRead(reader) with the following
+ // fulfillment steps given the argument result: [...]
+ // Step 12.d: Set readPromise.[[PromiseIsHandled]] to true.
+
+ // First, perform |ReadableStreamDefaultReaderRead(reader)|.
+ Rooted<PromiseObject*> readerReadResultPromise(
+ cx, js::ReadableStreamDefaultReaderRead(cx, unwrappedReader));
+ if (!readerReadResultPromise) {
+ return nullptr;
+ }
+
+ // Next, create a function to perform the fulfillment steps under step 12.c
+ // (implemented in the |TeeReaderReadHandler| C++ function).
+ Rooted<JSObject*> teeState(cx, unwrappedTeeState);
+ if (!cx->compartment()->wrap(cx, &teeState)) {
+ return nullptr;
+ }
+
+ Rooted<JSObject*> onFulfilled(
+ cx, NewHandler(cx, TeeReaderReadHandler, teeState));
+ if (!onFulfilled) {
+ return nullptr;
+ }
+
+ // Finally, perform those fulfillment steps when |readerReadResultPromise|
+ // fulfills. (Step 12.c doesn't provide rejection steps, so don't handle
+ // rejection.)
+ //
+ // The spec's |readPromise| promise is unobservable, so implement this using
+ // a JSAPI function that acts as if it created |readPromise| but doesn't
+ // actually do so.
+ //
+ // Step 12.d causes |readPromise| to be treated as handled, even if it
+ // rejects. Use |JS::AddPromiseReactionsIgnoringUnhandledRejection|, not
+ // |JS::AddPromiseReactions|, to avoid reporting a freshly-consed-up promise
+ // as rejected if |readerReadResultPromise| rejects.
+ if (!JS::AddPromiseReactionsIgnoringUnhandledRejection(
+ cx, readerReadResultPromise, onFulfilled, nullptr)) {
+ return nullptr;
+ }
+ }
+
+ // Step 12.a: (If reading is true,) return a promise resolved with undefined.
+ // Step 12.e: Return a promise resolved with undefined.
+ return PromiseResolvedWithUndefined(cx);
+}
+
+/**
+ * Cancel one branch of a tee'd stream with the given |reason_|.
+ *
+ * Streams spec, 3.4.10. ReadableStreamTee steps 13 and 14: "Let
+ * cancel1Algorithm/cancel2Algorithm be the following steps, taking a reason
+ * argument:"
+ */
+MOZ_MUST_USE JSObject* js::ReadableStreamTee_Cancel(
+ JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState,
+ JS::Handle<ReadableStreamDefaultController*> unwrappedBranch,
+ JS::Handle<Value> reason) {
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapInternalSlot<ReadableStream>(cx, unwrappedTeeState,
+ TeeState::Slot_Stream));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ bool bothBranchesCanceled = false;
+
+ // Step 13/14.a: Set canceled1/canceled2 to true.
+ // Step 13/14.b: Set reason1/reason2 to reason.
+ {
+ AutoRealm ar(cx, unwrappedTeeState);
+
+ Rooted<Value> unwrappedReason(cx, reason);
+ if (!cx->compartment()->wrap(cx, &unwrappedReason)) {
+ return nullptr;
+ }
+
+ if (unwrappedBranch->isTeeBranch1()) {
+ unwrappedTeeState->setCanceled1(unwrappedReason);
+ bothBranchesCanceled = unwrappedTeeState->canceled2();
+ } else {
+ MOZ_ASSERT(unwrappedBranch->isTeeBranch2());
+ unwrappedTeeState->setCanceled2(unwrappedReason);
+ bothBranchesCanceled = unwrappedTeeState->canceled1();
+ }
+ }
+
+ Rooted<PromiseObject*> unwrappedCancelPromise(
+ cx, unwrappedTeeState->cancelPromise());
+ MOZ_ASSERT(unwrappedCancelPromise != nullptr);
+
+ // Step 13/14.c: If canceled2/canceled1 is true,
+ if (bothBranchesCanceled) {
+ // Step 13/14.c.i: Let compositeReason be
+ // ! CreateArrayFromList(« reason1, reason2 »).
+ Rooted<Value> compositeReason(cx);
+ {
+ Rooted<Value> reason1(cx, unwrappedTeeState->reason1());
+ Rooted<Value> reason2(cx, unwrappedTeeState->reason2());
+ if (!cx->compartment()->wrap(cx, &reason1) ||
+ !cx->compartment()->wrap(cx, &reason2)) {
+ return nullptr;
+ }
+
+ ArrayObject* reasonArray = NewDenseFullyAllocatedArray(cx, 2);
+ if (!reasonArray) {
+ return nullptr;
+ }
+ reasonArray->setDenseInitializedLength(2);
+ reasonArray->initDenseElement(0, reason1);
+ reasonArray->initDenseElement(1, reason2);
+
+ compositeReason = ObjectValue(*reasonArray);
+ }
+
+ // Step 13/14.c.ii: Let cancelResult be
+ // ! ReadableStreamCancel(stream, compositeReason).
+ // In our implementation, this can fail with OOM. The best course then
+ // is to reject cancelPromise with an OOM error.
+ Rooted<JSObject*> cancelResult(
+ cx, js::ReadableStreamCancel(cx, unwrappedStream, compositeReason));
+ if (!cancelResult) {
+ // Handle the OOM case mentioned above.
+ AutoRealm ar(cx, unwrappedCancelPromise);
+ if (!RejectPromiseWithPendingError(cx, unwrappedCancelPromise)) {
+ return nullptr;
+ }
+ } else {
+ // Step 13/14.c.iii: Resolve cancelPromise with cancelResult.
+ Rooted<Value> cancelResultVal(cx, ObjectValue(*cancelResult));
+ if (!ResolveUnwrappedPromiseWithValue(cx, unwrappedCancelPromise,
+ cancelResultVal)) {
+ return nullptr;
+ }
+ }
+ }
+
+ // Step 13/14.d: Return cancelPromise.
+ Rooted<JSObject*> cancelPromise(cx, unwrappedCancelPromise);
+ if (!cx->compartment()->wrap(cx, &cancelPromise)) {
+ return nullptr;
+ }
+
+ return cancelPromise;
+}
+
+/**
+ * Streams spec, 3.4.10. step 18:
+ * Upon rejection of reader.[[closedPromise]] with reason r,
+ */
+static bool TeeReaderErroredHandler(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args));
+ Handle<Value> reason = args.get(0);
+
+ Rooted<ReadableStreamDefaultController*> unwrappedBranchController(cx);
+
+ // Step 18.a.i: Perform
+ // ! ReadableStreamDefaultControllerError(
+ // branch1.[[readableStreamController]], r).
+ unwrappedBranchController = teeState->branch1();
+ if (!ReadableStreamControllerError(cx, unwrappedBranchController, reason)) {
+ return false;
+ }
+
+ // Step a.ii: Perform
+ // ! ReadableStreamDefaultControllerError(
+ // branch2.[[readableStreamController]], r).
+ unwrappedBranchController = teeState->branch2();
+ if (!ReadableStreamControllerError(cx, unwrappedBranchController, reason)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 3.4.10. ReadableStreamTee ( stream, cloneForBranch2 )
+ */
+MOZ_MUST_USE bool js::ReadableStreamTee(
+ JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
+ bool cloneForBranch2, JS::MutableHandle<ReadableStream*> branch1Stream,
+ JS::MutableHandle<ReadableStream*> branch2Stream) {
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+
+ // Step 2: Assert: Type(cloneForBranch2) is Boolean (implicit).
+ //
+ // The streams spec only ever passes |cloneForBranch2 = false|. It's expected
+ // that external specs that pass |cloneForBranch2 = true| will at some point
+ // come into existence, but we don't presently implement any such specs.
+ MOZ_ASSERT(!cloneForBranch2,
+ "support for cloneForBranch2=true is not yet implemented");
+
+ // Step 3: Let reader be ? AcquireReadableStreamDefaultReader(stream).
+ Rooted<ReadableStreamDefaultReader*> reader(
+ cx, CreateReadableStreamDefaultReader(cx, unwrappedStream,
+ ForAuthorCodeBool::No));
+ if (!reader) {
+ return false;
+ }
+
+ // Several algorithms close over the variables initialized in the next few
+ // steps, so we allocate them in an object, the TeeState. The algorithms
+ // also close over `stream` and `reader`, so TeeState gets a reference to
+ // the stream.
+ //
+ // Step 4: Let reading be false.
+ // Step 5: Let canceled1 be false.
+ // Step 6: Let canceled2 be false.
+ // Step 7: Let reason1 be undefined.
+ // Step 8: Let reason2 be undefined.
+ // Step 9: Let branch1 be undefined.
+ // Step 10: Let branch2 be undefined.
+ // Step 11: Let cancelPromise be a new promise.
+ Rooted<TeeState*> teeState(cx, TeeState::create(cx, unwrappedStream));
+ if (!teeState) {
+ return false;
+ }
+
+ MOZ_ASSERT(!teeState->reading());
+ MOZ_ASSERT(!teeState->canceled1());
+ MOZ_ASSERT(!teeState->canceled2());
+
+ // Step 12: Let pullAlgorithm be the following steps: [...]
+ // Step 13: Let cancel1Algorithm be the following steps: [...]
+ // Step 14: Let cancel2Algorithm be the following steps: [...]
+ // Step 15: Let startAlgorithm be an algorithm that returns undefined.
+ //
+ // Implicit. Our implementation does not use objects to represent
+ // [[pullAlgorithm]], [[cancelAlgorithm]], and so on. Instead, we decide
+ // which one to perform based on class checks. For example, our
+ // implementation of ReadableStreamControllerCallPullIfNeeded checks
+ // whether the stream's underlyingSource is a TeeState object.
+
+ // Step 16: Set branch1 to
+ // ! CreateReadableStream(startAlgorithm, pullAlgorithm,
+ // cancel1Algorithm).
+ Rooted<Value> underlyingSource(cx, ObjectValue(*teeState));
+ branch1Stream.set(
+ CreateReadableStream(cx, SourceAlgorithms::Tee, underlyingSource));
+ if (!branch1Stream) {
+ return false;
+ }
+
+ Rooted<ReadableStreamDefaultController*> branch1(cx);
+ branch1 = &branch1Stream->controller()->as<ReadableStreamDefaultController>();
+ branch1->setTeeBranch1();
+ teeState->setBranch1(branch1);
+
+ // Step 17: Set branch2 to
+ // ! CreateReadableStream(startAlgorithm, pullAlgorithm,
+ // cancel2Algorithm).
+ branch2Stream.set(
+ CreateReadableStream(cx, SourceAlgorithms::Tee, underlyingSource));
+ if (!branch2Stream) {
+ return false;
+ }
+
+ Rooted<ReadableStreamDefaultController*> branch2(cx);
+ branch2 = &branch2Stream->controller()->as<ReadableStreamDefaultController>();
+ branch2->setTeeBranch2();
+ teeState->setBranch2(branch2);
+
+ // Step 18: Upon rejection of reader.[[closedPromise]] with reason r, [...]
+ Rooted<JSObject*> closedPromise(cx, reader->closedPromise());
+
+ Rooted<JSObject*> onRejected(
+ cx, NewHandler(cx, TeeReaderErroredHandler, teeState));
+ if (!onRejected) {
+ return false;
+ }
+
+ if (!JS::AddPromiseReactions(cx, closedPromise, nullptr, onRejected)) {
+ return false;
+ }
+
+ // Step 19: Return « branch1, branch2 ».
+ return true;
+}
+
+/**
+ * Streams spec, 3.4.10.
+ * ReadableStreamPipeTo ( source, dest, preventClose, preventAbort,
+ * preventCancel, signal )
+ */
+PromiseObject* js::ReadableStreamPipeTo(JSContext* cx,
+ Handle<ReadableStream*> unwrappedSource,
+ Handle<WritableStream*> unwrappedDest,
+ bool preventClose, bool preventAbort,
+ bool preventCancel,
+ Handle<JSObject*> signal) {
+ cx->check(signal);
+
+ // Step 1. Assert: ! IsReadableStream(source) is true.
+ // Step 2. Assert: ! IsWritableStream(dest) is true.
+ // Step 3. Assert: Type(preventClose) is Boolean, Type(preventAbort) is
+ // Boolean, and Type(preventCancel) is Boolean.
+ // (These are guaranteed by the type system.)
+
+ // Step 12: Let promise be a new promise.
+ //
+ // We reorder this so that this promise can be rejected and returned in case
+ // of internal error.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Steps 4-11, 13-14.
+ Rooted<PipeToState*> pipeToState(
+ cx,
+ PipeToState::create(cx, promise, unwrappedSource, unwrappedDest,
+ preventClose, preventAbort, preventCancel, signal));
+ if (!pipeToState) {
+ if (!RejectPromiseWithPendingError(cx, promise)) {
+ return nullptr;
+ }
+
+ return promise;
+ }
+
+ // Step 15.
+ return promise;
+}
diff --git a/js/src/builtin/streams/ReadableStreamOperations.h b/js/src/builtin/streams/ReadableStreamOperations.h
new file mode 100644
index 0000000000..963fffe882
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamOperations.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* General readable stream abstract operations. */
+
+#ifndef builtin_streams_ReadableStreamOperations_h
+#define builtin_streams_ReadableStreamOperations_h
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::Value
+
+class JS_PUBLIC_API JSObject;
+
+namespace js {
+
+class PromiseObject;
+class ReadableStream;
+class ReadableStreamDefaultController;
+class TeeState;
+class WritableStream;
+
+extern MOZ_MUST_USE PromiseObject* ReadableStreamTee_Pull(
+ JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState);
+
+extern MOZ_MUST_USE JSObject* ReadableStreamTee_Cancel(
+ JSContext* cx, JS::Handle<TeeState*> unwrappedTeeState,
+ JS::Handle<ReadableStreamDefaultController*> unwrappedBranch,
+ JS::Handle<JS::Value> reason);
+
+extern MOZ_MUST_USE bool ReadableStreamTee(
+ JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
+ bool cloneForBranch2, JS::MutableHandle<ReadableStream*> branch1Stream,
+ JS::MutableHandle<ReadableStream*> branch2Stream);
+
+extern MOZ_MUST_USE PromiseObject* ReadableStreamPipeTo(
+ JSContext* cx, JS::Handle<ReadableStream*> unwrappedSource,
+ JS::Handle<WritableStream*> unwrappedDest, bool preventClose,
+ bool preventAbort, bool preventCancel, JS::Handle<JSObject*> signal);
+
+} // namespace js
+
+#endif // builtin_streams_ReadableStreamOperations_h
diff --git a/js/src/builtin/streams/ReadableStreamReader-inl.h b/js/src/builtin/streams/ReadableStreamReader-inl.h
new file mode 100644
index 0000000000..5d084c3a76
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamReader-inl.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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_streams_ReadableStreamReader_inl_h
+#define builtin_streams_ReadableStreamReader_inl_h
+
+#include "builtin/streams/ReadableStreamReader.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsfriendapi.h" // JS_IsDeadWrapper
+
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream
+#include "js/Proxy.h" // js::IsProxy
+#include "js/RootingAPI.h" // JS::Handle
+#include "vm/NativeObject.h" // js::NativeObject::getFixedSlot
+
+#include "vm/Compartment-inl.h" // js::UnwrapInternalSlot
+
+namespace js {
+
+/**
+ * Returns the stream associated with the given reader.
+ */
+inline MOZ_MUST_USE ReadableStream* UnwrapStreamFromReader(
+ JSContext* cx, JS::Handle<ReadableStreamReader*> reader) {
+ MOZ_ASSERT(reader->hasStream());
+ return UnwrapInternalSlot<ReadableStream>(cx, reader,
+ ReadableStreamReader::Slot_Stream);
+}
+
+/**
+ * Returns the reader associated with the given stream.
+ *
+ * Must only be called on ReadableStreams that already have a reader
+ * associated with them.
+ *
+ * If the reader is a wrapper, it will be unwrapped, so the result might not be
+ * an object from the currently active compartment.
+ */
+inline MOZ_MUST_USE ReadableStreamReader* UnwrapReaderFromStream(
+ JSContext* cx, JS::Handle<ReadableStream*> stream) {
+ return UnwrapInternalSlot<ReadableStreamReader>(cx, stream,
+ ReadableStream::Slot_Reader);
+}
+
+inline MOZ_MUST_USE ReadableStreamReader* UnwrapReaderFromStreamNoThrow(
+ ReadableStream* stream) {
+ JSObject* readerObj =
+ &stream->getFixedSlot(ReadableStream::Slot_Reader).toObject();
+ if (IsProxy(readerObj)) {
+ if (JS_IsDeadWrapper(readerObj)) {
+ return nullptr;
+ }
+
+ readerObj = readerObj->maybeUnwrapAs<ReadableStreamReader>();
+ if (!readerObj) {
+ return nullptr;
+ }
+ }
+
+ return &readerObj->as<ReadableStreamReader>();
+}
+
+} // namespace js
+
+#endif // builtin_streams_ReadableStreamReader_inl_h
diff --git a/js/src/builtin/streams/ReadableStreamReader.cpp b/js/src/builtin/streams/ReadableStreamReader.cpp
new file mode 100644
index 0000000000..f384bce83e
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamReader.cpp
@@ -0,0 +1,276 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* ReadableStream reader abstract operations. */
+
+#include "builtin/streams/ReadableStreamReader-inl.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsfriendapi.h" // JS_ReportErrorNumberASCII
+
+#include "builtin/Stream.h" // js::ReadableStreamController, js::ReadableStreamControllerPullSteps
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream
+#include "builtin/streams/ReadableStreamController.h" // js::ReadableStreamController
+#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{Cancel,CreateReadResult}
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "js/Value.h" // JS::Value, JS::UndefinedHandleValue
+#include "vm/Interpreter.h" // js::GetAndClearException
+#include "vm/JSContext.h" // JSContext
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+#include "vm/Runtime.h" // JSRuntime
+
+#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapInternalSlot
+#include "vm/List-inl.h" // js::StoreNewListInFixedSlot
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using JS::Handle;
+using JS::Rooted;
+using JS::Value;
+
+using js::PromiseObject;
+using js::ReadableStreamController;
+using js::UnwrapStreamFromReader;
+
+/*** 3.8. Readable stream reader abstract operations ************************/
+
+// Streams spec, 3.8.1. IsReadableStreamDefaultReader ( x )
+// Implemented via is<ReadableStreamDefaultReader>()
+
+// Streams spec, 3.8.2. IsReadableStreamBYOBReader ( x )
+// Implemented via is<ReadableStreamBYOBReader>()
+
+/**
+ * Streams spec, 3.8.3. ReadableStreamReaderGenericCancel ( reader, reason )
+ */
+MOZ_MUST_USE JSObject* js::ReadableStreamReaderGenericCancel(
+ JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader,
+ Handle<Value> reason) {
+ // Step 1: Let stream be reader.[[ownerReadableStream]].
+ // Step 2: Assert: stream is not undefined (implicit).
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapStreamFromReader(cx, unwrappedReader));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ // Step 3: Return ! ReadableStreamCancel(stream, reason).
+ return js::ReadableStreamCancel(cx, unwrappedStream, reason);
+}
+
+/**
+ * Streams spec, 3.8.4.
+ * ReadableStreamReaderGenericInitialize ( reader, stream )
+ */
+MOZ_MUST_USE bool js::ReadableStreamReaderGenericInitialize(
+ JSContext* cx, Handle<ReadableStreamReader*> reader,
+ Handle<ReadableStream*> unwrappedStream, ForAuthorCodeBool forAuthorCode) {
+ cx->check(reader);
+
+ // Step 1: Set reader.[[forAuthorCode]] to true.
+ reader->setForAuthorCode(forAuthorCode);
+
+ // Step 2: Set reader.[[ownerReadableStream]] to stream.
+ {
+ Rooted<JSObject*> readerCompartmentStream(cx, unwrappedStream);
+ if (!cx->compartment()->wrap(cx, &readerCompartmentStream)) {
+ return false;
+ }
+ reader->setStream(readerCompartmentStream);
+ }
+
+ // Step 3 is moved to the end.
+
+ // Step 4: If stream.[[state]] is "readable",
+ Rooted<PromiseObject*> promise(cx);
+ if (unwrappedStream->readable()) {
+ // Step a: Set reader.[[closedPromise]] to a new promise.
+ promise = PromiseObject::createSkippingExecutor(cx);
+ } else if (unwrappedStream->closed()) {
+ // Step 5: Otherwise, if stream.[[state]] is "closed",
+ // Step a: Set reader.[[closedPromise]] to a promise resolved with
+ // undefined.
+ promise = PromiseResolvedWithUndefined(cx);
+ } else {
+ // Step 6: Otherwise,
+ // Step a: Assert: stream.[[state]] is "errored".
+ MOZ_ASSERT(unwrappedStream->errored());
+
+ // Step b: Set reader.[[closedPromise]] to a promise rejected with
+ // stream.[[storedError]].
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return false;
+ }
+ promise = PromiseObject::unforgeableReject(cx, storedError);
+ if (!promise) {
+ return false;
+ }
+
+ // Step c. Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+ js::SetSettledPromiseIsHandled(cx, promise);
+ }
+
+ if (!promise) {
+ return false;
+ }
+
+ reader->setClosedPromise(promise);
+
+ // Step 4 of caller 3.6.3. new ReadableStreamDefaultReader(stream):
+ // Step 5 of caller 3.7.3. new ReadableStreamBYOBReader(stream):
+ // Set this.[[read{Into}Requests]] to a new empty List.
+ if (!StoreNewListInFixedSlot(cx, reader,
+ ReadableStreamReader::Slot_Requests)) {
+ return false;
+ }
+
+ // Step 3: Set stream.[[reader]] to reader.
+ // Doing this last prevents a partially-initialized reader from being
+ // attached to the stream (and possibly left there on OOM).
+ {
+ AutoRealm ar(cx, unwrappedStream);
+ Rooted<JSObject*> streamCompartmentReader(cx, reader);
+ if (!cx->compartment()->wrap(cx, &streamCompartmentReader)) {
+ return false;
+ }
+ unwrappedStream->setReader(streamCompartmentReader);
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.8.5. ReadableStreamReaderGenericRelease ( reader )
+ */
+MOZ_MUST_USE bool js::ReadableStreamReaderGenericRelease(
+ JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader) {
+ // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined.
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapStreamFromReader(cx, unwrappedReader));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader.
+#ifdef DEBUG
+ // The assertion is weakened a bit to allow for nuked wrappers.
+ ReadableStreamReader* unwrappedReader2 =
+ UnwrapReaderFromStreamNoThrow(unwrappedStream);
+ MOZ_ASSERT_IF(unwrappedReader2, unwrappedReader2 == unwrappedReader);
+#endif
+
+ // Create an exception to reject promises with below. We don't have a
+ // clean way to do this, unfortunately.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_RELEASED);
+ Rooted<Value> exn(cx);
+ if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
+ // Uncatchable error. Die immediately without resolving
+ // reader.[[closedPromise]].
+ return false;
+ }
+
+ // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject
+ // reader.[[closedPromise]] with a TypeError exception.
+ Rooted<PromiseObject*> unwrappedClosedPromise(cx);
+ if (unwrappedStream->readable()) {
+ unwrappedClosedPromise = UnwrapInternalSlot<PromiseObject>(
+ cx, unwrappedReader, ReadableStreamReader::Slot_ClosedPromise);
+ if (!unwrappedClosedPromise) {
+ return false;
+ }
+
+ AutoRealm ar(cx, unwrappedClosedPromise);
+ if (!cx->compartment()->wrap(cx, &exn)) {
+ return false;
+ }
+ if (!PromiseObject::reject(cx, unwrappedClosedPromise, exn)) {
+ return false;
+ }
+ } else {
+ // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise
+ // rejected with a TypeError exception.
+ Rooted<JSObject*> closedPromise(cx,
+ PromiseObject::unforgeableReject(cx, exn));
+ if (!closedPromise) {
+ return false;
+ }
+ unwrappedClosedPromise = &closedPromise->as<PromiseObject>();
+
+ AutoRealm ar(cx, unwrappedReader);
+ if (!cx->compartment()->wrap(cx, &closedPromise)) {
+ return false;
+ }
+ unwrappedReader->setClosedPromise(closedPromise);
+ }
+
+ // Step 5: Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+ js::SetSettledPromiseIsHandled(cx, unwrappedClosedPromise);
+
+ // Step 6: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
+ unwrappedStream->clearReader();
+
+ // Step 7: Set reader.[[ownerReadableStream]] to undefined.
+ unwrappedReader->clearStream();
+
+ return true;
+}
+
+/**
+ * Streams spec, 3.8.7.
+ * ReadableStreamDefaultReaderRead ( reader [, forAuthorCode ] )
+ */
+MOZ_MUST_USE PromiseObject* js::ReadableStreamDefaultReaderRead(
+ JSContext* cx, Handle<ReadableStreamDefaultReader*> unwrappedReader) {
+ // Step 1: If forAuthorCode was not passed, set it to false (implicit).
+
+ // Step 2: Let stream be reader.[[ownerReadableStream]].
+ // Step 3: Assert: stream is not undefined.
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapStreamFromReader(cx, unwrappedReader));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ // Step 4: Set stream.[[disturbed]] to true.
+ unwrappedStream->setDisturbed();
+
+ // Step 5: If stream.[[state]] is "closed", return a promise resolved with
+ // ! ReadableStreamCreateReadResult(undefined, true, forAuthorCode).
+ if (unwrappedStream->closed()) {
+ PlainObject* iterResult = ReadableStreamCreateReadResult(
+ cx, UndefinedHandleValue, true, unwrappedReader->forAuthorCode());
+ if (!iterResult) {
+ return nullptr;
+ }
+
+ Rooted<Value> iterResultVal(cx, JS::ObjectValue(*iterResult));
+ return PromiseObject::unforgeableResolveWithNonPromise(cx, iterResultVal);
+ }
+
+ // Step 6: If stream.[[state]] is "errored", return a promise rejected
+ // with stream.[[storedError]].
+ if (unwrappedStream->errored()) {
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return nullptr;
+ }
+ return PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ // Step 7: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(unwrappedStream->readable());
+
+ // Step 8: Return ! stream.[[readableStreamController]].[[PullSteps]]().
+ Rooted<ReadableStreamController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ return ReadableStreamControllerPullSteps(cx, unwrappedController);
+}
diff --git a/js/src/builtin/streams/ReadableStreamReader.h b/js/src/builtin/streams/ReadableStreamReader.h
new file mode 100644
index 0000000000..96dd2efed2
--- /dev/null
+++ b/js/src/builtin/streams/ReadableStreamReader.h
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* ReadableStream readers and generic reader operations. */
+
+#ifndef builtin_streams_ReadableStreamReader_h
+#define builtin_streams_ReadableStreamReader_h
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jstypes.h" // JS_PUBLIC_API
+#include "js/Class.h" // JSClass, js::ClassSpec
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::{,Boolean,Object,Undefined}Value
+#include "vm/JSObject.h" // JSObject::is
+#include "vm/List.h" // js::ListObject
+#include "vm/NativeObject.h" // js::NativeObject
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+class PromiseObject;
+class ReadableStream;
+
+/**
+ * Tells whether or not read() result objects inherit from Object.prototype.
+ * Generally, they should do so only if the reader was created by author code.
+ * See <https://streams.spec.whatwg.org/#readable-stream-create-read-result>.
+ */
+enum class ForAuthorCodeBool { No, Yes };
+
+class ReadableStreamReader : public NativeObject {
+ public:
+ /**
+ * Memory layout of Stream Reader instances.
+ *
+ * See https://streams.spec.whatwg.org/#default-reader-internal-slots and
+ * https://streams.spec.whatwg.org/#byob-reader-internal-slots for details.
+ *
+ * Note that [[readRequests]] and [[readIntoRequests]] are treated the same
+ * in our implementation.
+ *
+ * Of the stored values, Stream and ClosedPromise might be
+ * cross-compartment wrapper wrappers.
+ *
+ * For Stream, this can happen if the Reader was created by applying a
+ * different compartment's ReadableStream.prototype.getReader method.
+ *
+ * For ClosedPromise, it can be caused by applying a different
+ * compartment's ReadableStream*Reader.prototype.releaseLock method.
+ *
+ * Requests is guaranteed to be in the same compartment as the Reader, but
+ * can contain wrapped request objects from other globals.
+ */
+ enum Slots {
+ Slot_Stream,
+ Slot_Requests,
+ Slot_ClosedPromise,
+ Slot_ForAuthorCode,
+ SlotCount,
+ };
+
+ bool hasStream() const { return !getFixedSlot(Slot_Stream).isUndefined(); }
+ void setStream(JSObject* stream) {
+ setFixedSlot(Slot_Stream, JS::ObjectValue(*stream));
+ }
+ void clearStream() { setFixedSlot(Slot_Stream, JS::UndefinedValue()); }
+ bool isClosed() { return !hasStream(); }
+
+ /**
+ * Tells whether this reader was created by author code.
+ *
+ * This returns Yes for readers created using `stream.getReader()`, and No
+ * for readers created for the internal use of algorithms like
+ * `stream.tee()` and `new Response(stream)`.
+ *
+ * The standard does not have this field. Instead, eight algorithms take a
+ * forAuthorCode parameter, and a [[forAuthorCode]] field is part of each
+ * read request. But the behavior is always equivalent to treating readers
+ * created by author code as having a bit set on them. We implement it that
+ * way for simplicity.
+ */
+ ForAuthorCodeBool forAuthorCode() const {
+ return getFixedSlot(Slot_ForAuthorCode).toBoolean() ? ForAuthorCodeBool::Yes
+ : ForAuthorCodeBool::No;
+ }
+ void setForAuthorCode(ForAuthorCodeBool value) {
+ setFixedSlot(Slot_ForAuthorCode,
+ JS::BooleanValue(value == ForAuthorCodeBool::Yes));
+ }
+
+ ListObject* requests() const {
+ return &getFixedSlot(Slot_Requests).toObject().as<ListObject>();
+ }
+ void clearRequests() { setFixedSlot(Slot_Requests, JS::UndefinedValue()); }
+
+ JSObject* closedPromise() const {
+ return &getFixedSlot(Slot_ClosedPromise).toObject();
+ }
+ void setClosedPromise(JSObject* wrappedPromise) {
+ setFixedSlot(Slot_ClosedPromise, JS::ObjectValue(*wrappedPromise));
+ }
+
+ static const JSClass class_;
+};
+
+class ReadableStreamDefaultReader : public ReadableStreamReader {
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
+ static const ClassSpec classSpec_;
+ static const JSClass class_;
+ static const ClassSpec protoClassSpec_;
+ static const JSClass protoClass_;
+};
+
+extern MOZ_MUST_USE ReadableStreamDefaultReader*
+CreateReadableStreamDefaultReader(JSContext* cx,
+ JS::Handle<ReadableStream*> unwrappedStream,
+ ForAuthorCodeBool forAuthorCode,
+ JS::Handle<JSObject*> proto = nullptr);
+
+extern MOZ_MUST_USE JSObject* ReadableStreamReaderGenericCancel(
+ JSContext* cx, JS::Handle<ReadableStreamReader*> unwrappedReader,
+ JS::Handle<JS::Value> reason);
+
+extern MOZ_MUST_USE bool ReadableStreamReaderGenericInitialize(
+ JSContext* cx, JS::Handle<ReadableStreamReader*> reader,
+ JS::Handle<ReadableStream*> unwrappedStream,
+ ForAuthorCodeBool forAuthorCode);
+
+extern MOZ_MUST_USE bool ReadableStreamReaderGenericRelease(
+ JSContext* cx, JS::Handle<ReadableStreamReader*> unwrappedReader);
+
+extern MOZ_MUST_USE PromiseObject* ReadableStreamDefaultReaderRead(
+ JSContext* cx, JS::Handle<ReadableStreamDefaultReader*> unwrappedReader);
+
+} // namespace js
+
+template <>
+inline bool JSObject::is<js::ReadableStreamReader>() const {
+ return is<js::ReadableStreamDefaultReader>();
+}
+
+namespace js {
+
+extern MOZ_MUST_USE JSObject* CreateReadableStreamBYOBReader(
+ JSContext* cx, JS::Handle<ReadableStream*> unwrappedStream,
+ ForAuthorCodeBool forAuthorCode, JS::Handle<JSObject*> proto = nullptr);
+
+} // namespace js
+
+#endif // builtin_streams_ReadableStreamReader_h
diff --git a/js/src/builtin/streams/StreamAPI.cpp b/js/src/builtin/streams/StreamAPI.cpp
new file mode 100644
index 0000000000..0a9abd168c
--- /dev/null
+++ b/js/src/builtin/streams/StreamAPI.cpp
@@ -0,0 +1,612 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Public and friend stream APIs for external use. */
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT{,_IF}
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include <stdint.h> // uint32_t, uintptr_t
+
+#include "jsapi.h" // js::AssertHeapIsIdle, JS_ReportErrorNumberASCII
+#include "jsfriendapi.h" // js::IsObjectInContextCompartment
+#include "jstypes.h" // JS_{FRIEND,PUBLIC}_API
+
+#include "builtin/Stream.h" // js::ReadableByteStreamController{,Close}, js::ReadableStreamDefaultController{,Close}, js::StreamController
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream
+#include "builtin/streams/ReadableStreamController.h" // js::CheckReadableStreamControllerCanCloseOrEnqueue
+#include "builtin/streams/ReadableStreamDefaultControllerOperations.h" // js::ReadableStreamController{Error,GetDesiredSizeUnchecked}, js::SetUpReadableStreamDefaultControllerFromUnderlyingSource
+#include "builtin/streams/ReadableStreamInternals.h" // js::ReadableStream{Cancel,FulfillReadOrReadIntoRequest,GetNumReadRequests,HasDefaultReader}
+#include "builtin/streams/ReadableStreamOperations.h" // js::ReadableStreamTee
+#include "builtin/streams/ReadableStreamReader.h" // js::ReadableStream{,Default}Reader, js::ForAuthorCodeBool
+#include "builtin/streams/StreamController.h" // js::StreamController
+#include "gc/Zone.h" // JS::Zone
+#include "js/experimental/TypedData.h" // JS_GetArrayBufferViewData, JS_NewUint8Array
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/GCAPI.h" // JS::AutoCheckCannotGC, JS::AutoSuppressGCAnalysis
+#include "js/Object.h" // JS::SetPrivate
+#include "js/RootingAPI.h" // JS::{,Mutable}Handle, JS::Rooted
+#include "js/Stream.h" // JS::ReadableStreamUnderlyingSource
+#include "js/Value.h" // JS::{,Object,Undefined}Value
+#include "vm/ArrayBufferViewObject.h" // js::ArrayBufferViewObject
+#include "vm/JSContext.h" // JSContext, CHECK_THREAD
+#include "vm/JSObject.h" // JSObject
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/PromiseObject.h" // js::PromiseObject
+
+#include "builtin/streams/ReadableStreamReader-inl.h" // js::UnwrapStreamFromReader
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapAndDowncastObject
+#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using js::ArrayBufferViewObject;
+using js::AssertHeapIsIdle;
+using js::AutoRealm;
+using js::CheckReadableStreamControllerCanCloseOrEnqueue;
+using js::ForAuthorCodeBool;
+using js::GetErrorMessage;
+using js::IsObjectInContextCompartment;
+using js::NewBuiltinClassInstance;
+using js::PlainObject;
+using js::ReadableByteStreamController;
+using js::ReadableByteStreamControllerClose;
+using js::ReadableStream;
+using js::ReadableStreamController;
+using js::ReadableStreamControllerError;
+using js::ReadableStreamControllerGetDesiredSizeUnchecked;
+using js::ReadableStreamDefaultController;
+using js::ReadableStreamDefaultControllerClose;
+using js::ReadableStreamDefaultReader;
+using js::ReadableStreamFulfillReadOrReadIntoRequest;
+using js::ReadableStreamGetNumReadRequests;
+using js::ReadableStreamHasDefaultReader;
+using js::ReadableStreamReader;
+using js::ReadableStreamTee;
+using js::SetUpReadableStreamDefaultControllerFromUnderlyingSource;
+using js::StreamController;
+using js::UnwrapAndDowncastObject;
+using js::UnwrapStreamFromReader;
+
+JS_FRIEND_API JSObject* js::UnwrapReadableStream(JSObject* obj) {
+ return obj->maybeUnwrapIf<ReadableStream>();
+}
+
+JS_PUBLIC_API JSObject* JS::NewReadableDefaultStreamObject(
+ JSContext* cx, JS::Handle<JSObject*> underlyingSource /* = nullptr */,
+ JS::Handle<JSFunction*> size /* = nullptr */,
+ double highWaterMark /* = 1 */,
+ JS::Handle<JSObject*> proto /* = nullptr */) {
+ MOZ_ASSERT(!cx->zone()->isAtomsZone());
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(underlyingSource, size, proto);
+ MOZ_ASSERT(highWaterMark >= 0);
+
+ // A copy of ReadableStream::constructor, with most of the
+ // argument-checking done implicitly by C++ type checking.
+ Rooted<ReadableStream*> stream(cx, ReadableStream::create(cx));
+ if (!stream) {
+ return nullptr;
+ }
+ Rooted<Value> sourceVal(cx);
+ if (underlyingSource) {
+ sourceVal.setObject(*underlyingSource);
+ } else {
+ JSObject* source = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!source) {
+ return nullptr;
+ }
+ sourceVal.setObject(*source);
+ }
+ Rooted<Value> sizeVal(cx, size ? ObjectValue(*size) : UndefinedValue());
+
+ if (!SetUpReadableStreamDefaultControllerFromUnderlyingSource(
+ cx, stream, sourceVal, highWaterMark, sizeVal)) {
+ return nullptr;
+ }
+
+ return stream;
+}
+
+JS_PUBLIC_API JSObject* JS::NewReadableExternalSourceStreamObject(
+ JSContext* cx, JS::ReadableStreamUnderlyingSource* underlyingSource,
+ void* nsISupportsObject_alreadyAddreffed /* = nullptr */,
+ Handle<JSObject*> proto /* = nullptr */) {
+ MOZ_ASSERT(!cx->zone()->isAtomsZone());
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ MOZ_ASSERT(underlyingSource);
+ MOZ_ASSERT((uintptr_t(underlyingSource) & 1) == 0,
+ "external underlying source pointers must be aligned");
+ cx->check(proto);
+
+ return ReadableStream::createExternalSourceStream(
+ cx, underlyingSource, nsISupportsObject_alreadyAddreffed, proto);
+}
+
+JS_PUBLIC_API bool JS::IsReadableStream(JSObject* obj) {
+ return obj->canUnwrapAs<ReadableStream>();
+}
+
+JS_PUBLIC_API bool JS::IsReadableStreamReader(JSObject* obj) {
+ return obj->canUnwrapAs<ReadableStreamDefaultReader>();
+}
+
+JS_PUBLIC_API bool JS::IsReadableStreamDefaultReader(JSObject* obj) {
+ return obj->canUnwrapAs<ReadableStreamDefaultReader>();
+}
+
+template <class T>
+static MOZ_MUST_USE T* APIUnwrapAndDowncast(JSContext* cx, JSObject* obj) {
+ cx->check(obj);
+ return UnwrapAndDowncastObject<T>(cx, obj);
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamIsReadable(JSContext* cx,
+ Handle<JSObject*> streamObj,
+ bool* result) {
+ ReadableStream* unwrappedStream =
+ APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ *result = unwrappedStream->readable();
+ return true;
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamIsLocked(JSContext* cx,
+ Handle<JSObject*> streamObj,
+ bool* result) {
+ ReadableStream* unwrappedStream =
+ APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ *result = unwrappedStream->locked();
+ return true;
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamIsDisturbed(JSContext* cx,
+ Handle<JSObject*> streamObj,
+ bool* result) {
+ ReadableStream* unwrappedStream =
+ APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ *result = unwrappedStream->disturbed();
+ return true;
+}
+
+JS_PUBLIC_API JSObject* JS::ReadableStreamCancel(JSContext* cx,
+ Handle<JSObject*> streamObj,
+ Handle<Value> reason) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(reason);
+
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ return js::ReadableStreamCancel(cx, unwrappedStream, reason);
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamGetMode(JSContext* cx,
+ Handle<JSObject*> streamObj,
+ JS::ReadableStreamMode* mode) {
+ ReadableStream* unwrappedStream =
+ APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ *mode = unwrappedStream->mode();
+ return true;
+}
+
+JS_PUBLIC_API JSObject* JS::ReadableStreamGetReader(
+ JSContext* cx, Handle<JSObject*> streamObj, ReadableStreamReaderMode mode) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ JSObject* result = CreateReadableStreamDefaultReader(cx, unwrappedStream,
+ ForAuthorCodeBool::No);
+ MOZ_ASSERT_IF(result, IsObjectInContextCompartment(result, cx));
+ return result;
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamGetExternalUnderlyingSource(
+ JSContext* cx, Handle<JSObject*> streamObj,
+ JS::ReadableStreamUnderlyingSource** source) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ MOZ_ASSERT(unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource);
+ if (unwrappedStream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_LOCKED);
+ return false;
+ }
+ if (!unwrappedStream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
+ "ReadableStreamGetExternalUnderlyingSource");
+ return false;
+ }
+
+ auto unwrappedController =
+ &unwrappedStream->controller()->as<ReadableByteStreamController>();
+ unwrappedController->setSourceLocked();
+ *source = unwrappedController->externalSource();
+ return true;
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamReleaseExternalUnderlyingSource(
+ JSContext* cx, Handle<JSObject*> streamObj) {
+ ReadableStream* unwrappedStream =
+ APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ MOZ_ASSERT(unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource);
+ MOZ_ASSERT(unwrappedStream->locked());
+ MOZ_ASSERT(unwrappedStream->controller()->sourceLocked());
+ unwrappedStream->controller()->clearSourceLocked();
+ return true;
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamUpdateDataAvailableFromSource(
+ JSContext* cx, JS::Handle<JSObject*> streamObj, uint32_t availableData) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // This is based on Streams spec 3.11.4.4. enqueue(chunk) steps 1-3 and
+ // 3.13.9. ReadableByteStreamControllerEnqueue(controller, chunk) steps
+ // 8-9.
+ //
+ // Adapted to handling updates signaled by the embedding for streams with
+ // external underlying sources.
+ //
+ // The remaining steps of those two functions perform checks and asserts
+ // that don't apply to streams with external underlying sources.
+
+ Rooted<ReadableByteStreamController*> unwrappedController(
+ cx, &unwrappedStream->controller()->as<ReadableByteStreamController>());
+
+ // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
+ if (unwrappedController->closeRequested()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue");
+ return false;
+ }
+
+ // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
+ // throw a TypeError exception.
+ if (!unwrappedController->stream()->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
+ "enqueue");
+ return false;
+ }
+
+ unwrappedController->clearPullFlags();
+
+#if DEBUG
+ uint32_t oldAvailableData =
+ unwrappedController->getFixedSlot(StreamController::Slot_TotalSize)
+ .toInt32();
+#endif // DEBUG
+ unwrappedController->setQueueTotalSize(availableData);
+
+ // 3.139. ReadableByteStreamControllerEnqueue
+ // Step 8.a: If ! ReadableStreamGetNumReadRequests(stream) is 0,
+ // Reordered because for externally-sourced streams it applies regardless
+ // of reader type.
+ if (ReadableStreamGetNumReadRequests(unwrappedStream) == 0) {
+ return true;
+ }
+
+ // Step 8: If ! ReadableStreamHasDefaultReader(stream) is true
+ bool hasDefaultReader;
+ if (!ReadableStreamHasDefaultReader(cx, unwrappedStream, &hasDefaultReader)) {
+ return false;
+ }
+ if (hasDefaultReader) {
+ // Step b: Otherwise,
+ // Step i: Assert: controller.[[queue]] is empty.
+ MOZ_ASSERT(oldAvailableData == 0);
+
+ // Step ii: Let transferredView be
+ // ! Construct(%Uint8Array%, transferredBuffer,
+ // byteOffset, byteLength).
+ JSObject* viewObj = JS_NewUint8Array(cx, availableData);
+ if (!viewObj) {
+ return false;
+ }
+ Rooted<ArrayBufferViewObject*> transferredView(
+ cx, &viewObj->as<ArrayBufferViewObject>());
+ if (!transferredView) {
+ return false;
+ }
+
+ JS::ReadableStreamUnderlyingSource* source =
+ unwrappedController->externalSource();
+
+ size_t bytesWritten;
+ {
+ AutoRealm ar(cx, unwrappedStream);
+ JS::AutoSuppressGCAnalysis suppressGC(cx);
+ JS::AutoCheckCannotGC noGC;
+ bool dummy;
+ void* buffer = JS_GetArrayBufferViewData(transferredView, &dummy, noGC);
+ source->writeIntoReadRequestBuffer(cx, unwrappedStream, buffer,
+ availableData, &bytesWritten);
+ }
+
+ // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream,
+ // transferredView,
+ // false).
+ Rooted<Value> chunk(cx, ObjectValue(*transferredView));
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk,
+ false)) {
+ return false;
+ }
+
+ unwrappedController->setQueueTotalSize(availableData - bytesWritten);
+ } else {
+ // Step 9: Otherwise, if ! ReadableStreamHasBYOBReader(stream) is true,
+ // [...]
+ // (Omitted. BYOB readers are not implemented.)
+
+ // Step 10: Otherwise,
+ // Step a: Assert: ! IsReadableStreamLocked(stream) is false.
+ MOZ_ASSERT(!unwrappedStream->locked());
+
+ // Step b: Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(
+ // controller, transferredBuffer, byteOffset, byteLength).
+ // (Not needed for external underlying sources.)
+ }
+
+ return true;
+}
+
+JS_PUBLIC_API void JS::ReadableStreamReleaseCCObject(JSObject* streamObj) {
+ MOZ_ASSERT(JS::IsReadableStream(streamObj));
+ JS::SetPrivate(streamObj, nullptr);
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamTee(JSContext* cx,
+ Handle<JSObject*> streamObj,
+ MutableHandle<JSObject*> branch1Obj,
+ MutableHandle<JSObject*> branch2Obj) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ Rooted<ReadableStream*> branch1Stream(cx);
+ Rooted<ReadableStream*> branch2Stream(cx);
+ if (!ReadableStreamTee(cx, unwrappedStream, false, &branch1Stream,
+ &branch2Stream)) {
+ return false;
+ }
+
+ branch1Obj.set(branch1Stream);
+ branch2Obj.set(branch2Stream);
+ return true;
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamGetDesiredSize(JSContext* cx,
+ JSObject* streamObj,
+ bool* hasValue,
+ double* value) {
+ ReadableStream* unwrappedStream =
+ APIUnwrapAndDowncast<ReadableStream>(cx, streamObj);
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ if (unwrappedStream->errored()) {
+ *hasValue = false;
+ return true;
+ }
+
+ *hasValue = true;
+
+ if (unwrappedStream->closed()) {
+ *value = 0;
+ return true;
+ }
+
+ *value = ReadableStreamControllerGetDesiredSizeUnchecked(
+ unwrappedStream->controller());
+ return true;
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamClose(JSContext* cx,
+ Handle<JSObject*> streamObj) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ Rooted<ReadableStreamController*> unwrappedControllerObj(
+ cx, unwrappedStream->controller());
+ if (!CheckReadableStreamControllerCanCloseOrEnqueue(
+ cx, unwrappedControllerObj, "close")) {
+ return false;
+ }
+
+ if (unwrappedControllerObj->is<ReadableStreamDefaultController>()) {
+ Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
+ unwrappedController =
+ &unwrappedControllerObj->as<ReadableStreamDefaultController>();
+ return ReadableStreamDefaultControllerClose(cx, unwrappedController);
+ }
+
+ Rooted<ReadableByteStreamController*> unwrappedController(cx);
+ unwrappedController =
+ &unwrappedControllerObj->as<ReadableByteStreamController>();
+ return ReadableByteStreamControllerClose(cx, unwrappedController);
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamEnqueue(JSContext* cx,
+ Handle<JSObject*> streamObj,
+ Handle<Value> chunk) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(chunk);
+
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ if (unwrappedStream->mode() != JS::ReadableStreamMode::Default) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER,
+ "JS::ReadableStreamEnqueue");
+ return false;
+ }
+
+ Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
+ unwrappedController =
+ &unwrappedStream->controller()->as<ReadableStreamDefaultController>();
+
+ MOZ_ASSERT(!unwrappedController->closeRequested());
+ MOZ_ASSERT(unwrappedStream->readable());
+
+ return ReadableStreamDefaultControllerEnqueue(cx, unwrappedController, chunk);
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamError(JSContext* cx,
+ Handle<JSObject*> streamObj,
+ Handle<Value> error) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(error);
+
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, APIUnwrapAndDowncast<ReadableStream>(cx, streamObj));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ Rooted<ReadableStreamController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ return ReadableStreamControllerError(cx, unwrappedController, error);
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamReaderIsClosed(JSContext* cx,
+ Handle<JSObject*> readerObj,
+ bool* result) {
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, APIUnwrapAndDowncast<ReadableStreamReader>(cx, readerObj));
+ if (!unwrappedReader) {
+ return false;
+ }
+
+ *result = unwrappedReader->isClosed();
+ return true;
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamReaderCancel(JSContext* cx,
+ Handle<JSObject*> readerObj,
+ Handle<Value> reason) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+ cx->check(reason);
+
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, APIUnwrapAndDowncast<ReadableStreamReader>(cx, readerObj));
+ if (!unwrappedReader) {
+ return false;
+ }
+ MOZ_ASSERT(unwrappedReader->forAuthorCode() == ForAuthorCodeBool::No,
+ "C++ code should not touch readers created by scripts");
+
+ return ReadableStreamReaderGenericCancel(cx, unwrappedReader, reason);
+}
+
+JS_PUBLIC_API bool JS::ReadableStreamReaderReleaseLock(
+ JSContext* cx, Handle<JSObject*> readerObj) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+
+ Rooted<ReadableStreamReader*> unwrappedReader(
+ cx, APIUnwrapAndDowncast<ReadableStreamReader>(cx, readerObj));
+ if (!unwrappedReader) {
+ return false;
+ }
+ MOZ_ASSERT(unwrappedReader->forAuthorCode() == ForAuthorCodeBool::No,
+ "C++ code should not touch readers created by scripts");
+
+#ifdef DEBUG
+ Rooted<ReadableStream*> unwrappedStream(
+ cx, UnwrapStreamFromReader(cx, unwrappedReader));
+ if (!unwrappedStream) {
+ return false;
+ }
+ MOZ_ASSERT(ReadableStreamGetNumReadRequests(unwrappedStream) == 0);
+#endif // DEBUG
+
+ return ReadableStreamReaderGenericRelease(cx, unwrappedReader);
+}
+
+JS_PUBLIC_API JSObject* JS::ReadableStreamDefaultReaderRead(
+ JSContext* cx, Handle<JSObject*> readerObj) {
+ AssertHeapIsIdle();
+ CHECK_THREAD(cx);
+
+ Rooted<ReadableStreamDefaultReader*> unwrappedReader(
+ cx, APIUnwrapAndDowncast<ReadableStreamDefaultReader>(cx, readerObj));
+ if (!unwrappedReader) {
+ return nullptr;
+ }
+ MOZ_ASSERT(unwrappedReader->forAuthorCode() == ForAuthorCodeBool::No,
+ "C++ code should not touch readers created by scripts");
+
+ return js::ReadableStreamDefaultReaderRead(cx, unwrappedReader);
+}
+
+void JS::InitPipeToHandling(const JSClass* abortSignalClass,
+ AbortSignalIsAborted isAborted, JSContext* cx) {
+ cx->runtime()->initPipeToHandling(abortSignalClass, isAborted);
+}
diff --git a/js/src/builtin/streams/StreamController-inl.h b/js/src/builtin/streams/StreamController-inl.h
new file mode 100644
index 0000000000..4672004772
--- /dev/null
+++ b/js/src/builtin/streams/StreamController-inl.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Base stream controller inlines. */
+
+#ifndef builtin_streams_StreamController_inl_h
+#define builtin_streams_StreamController_inl_h
+
+#include "builtin/streams/StreamController.h" // js::StreamController
+#include "builtin/streams/ReadableStreamController.h" // js::Readable{ByteStream,StreamDefault}Controller
+#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStreamDefaultController
+#include "vm/JSObject.h" // JSObject
+
+template <>
+inline bool JSObject::is<js::StreamController>() const {
+ return is<js::ReadableStreamDefaultController>() ||
+ is<js::ReadableByteStreamController>() ||
+ is<js::WritableStreamDefaultController>();
+}
+
+#endif // builtin_streams_ReadableStreamController_inl_h
diff --git a/js/src/builtin/streams/StreamController.h b/js/src/builtin/streams/StreamController.h
new file mode 100644
index 0000000000..4986421ab0
--- /dev/null
+++ b/js/src/builtin/streams/StreamController.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Base class for readable and writable stream controllers. */
+
+#ifndef builtin_streams_StreamController_h
+#define builtin_streams_StreamController_h
+
+#include "js/Value.h" // JS::Value, JS::NumberValue
+#include "vm/JSObject.h" // JSObject
+#include "vm/List.h" // js::ListObject
+#include "vm/NativeObject.h" // js::NativeObject
+
+namespace js {
+
+/**
+ * Common base class of both readable and writable stream controllers.
+ */
+class StreamController : public NativeObject {
+ public:
+ /**
+ * Memory layout for stream controllers.
+ *
+ * Both ReadableStreamDefaultController and ReadableByteStreamController
+ * are queue containers and must have these slots at identical offsets.
+ *
+ * The queue is guaranteed to be in the same compartment as the container,
+ * but might contain wrappers for objects from other compartments.
+ */
+ enum Slots { Slot_Queue, Slot_TotalSize, SlotCount };
+
+ ListObject* queue() const {
+ return &getFixedSlot(Slot_Queue).toObject().as<ListObject>();
+ }
+ double queueTotalSize() const {
+ return getFixedSlot(Slot_TotalSize).toNumber();
+ }
+ void setQueueTotalSize(double size) {
+ setFixedSlot(Slot_TotalSize, JS::NumberValue(size));
+ }
+};
+
+} // namespace js
+
+template <>
+inline bool JSObject::is<js::StreamController>() const;
+
+#endif // builtin_streams_StreamController_h
diff --git a/js/src/builtin/streams/TeeState.cpp b/js/src/builtin/streams/TeeState.cpp
new file mode 100644
index 0000000000..c8c151fd8a
--- /dev/null
+++ b/js/src/builtin/streams/TeeState.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Stream teeing state. */
+
+#include "builtin/streams/TeeState.h"
+
+#include "builtin/streams/ReadableStream.h" // js::ReadableStream
+#include "js/Class.h" // JSClass, JSCLASS_HAS_RESERVED_SLOTS
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "vm/JSContext.h" // JSContext
+#include "vm/PromiseObject.h" // js::PromiseObject
+
+#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
+
+using js::ReadableStream;
+using js::TeeState;
+
+using JS::Handle;
+using JS::Int32Value;
+using JS::ObjectValue;
+using JS::Rooted;
+
+/* static */ TeeState* TeeState::create(
+ JSContext* cx, Handle<ReadableStream*> unwrappedStream) {
+ Rooted<TeeState*> state(cx, NewBuiltinClassInstance<TeeState>(cx));
+ if (!state) {
+ return nullptr;
+ }
+
+ Rooted<PromiseObject*> cancelPromise(
+ cx, PromiseObject::createSkippingExecutor(cx));
+ if (!cancelPromise) {
+ return nullptr;
+ }
+
+ state->setFixedSlot(Slot_Flags, Int32Value(0));
+ state->setFixedSlot(Slot_CancelPromise, ObjectValue(*cancelPromise));
+ Rooted<JSObject*> wrappedStream(cx, unwrappedStream);
+ if (!cx->compartment()->wrap(cx, &wrappedStream)) {
+ return nullptr;
+ }
+ state->setFixedSlot(Slot_Stream, JS::ObjectValue(*wrappedStream));
+
+ return state;
+}
+
+const JSClass TeeState::class_ = {"TeeState",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount)};
diff --git a/js/src/builtin/streams/TeeState.h b/js/src/builtin/streams/TeeState.h
new file mode 100644
index 0000000000..beb42ff8e6
--- /dev/null
+++ b/js/src/builtin/streams/TeeState.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Stream teeing state. */
+
+#ifndef builtin_streams_TeeState_h
+#define builtin_streams_TeeState_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include <stdint.h> // uint32_t
+
+#include "builtin/streams/ReadableStreamController.h" // js::ReadableStreamDefaultController
+#include "js/Class.h" // JSClass
+#include "js/Value.h" // JS::{Int32,Object}Value
+#include "vm/NativeObject.h" // js::NativeObject
+#include "vm/PromiseObject.h" // js::PromiseObject
+
+namespace js {
+
+/**
+ * TeeState objects implement the local variables in Streams spec 3.3.9
+ * ReadableStreamTee, which are accessed by several algorithms.
+ */
+class TeeState : public NativeObject {
+ public:
+ /**
+ * Memory layout for TeeState instances.
+ *
+ * The Reason1 and Reason2 slots store opaque values, which might be
+ * wrapped objects from other compartments. Since we don't treat them as
+ * objects in Streams-specific code, we don't have to worry about that
+ * apart from ensuring that the values are properly wrapped before storing
+ * them.
+ *
+ * CancelPromise is always created in TeeState::create below, so is
+ * guaranteed to be in the same compartment as the TeeState instance
+ * itself.
+ *
+ * Stream can be from another compartment. It is automatically wrapped
+ * before storing it and unwrapped upon retrieval. That means that
+ * TeeState consumers need to be able to deal with unwrapped
+ * ReadableStream instances from non-current compartments.
+ *
+ * Branch1 and Branch2 are always created in the same compartment as the
+ * TeeState instance, so cannot be from another compartment.
+ */
+ enum Slots {
+ Slot_Flags = 0,
+ Slot_Reason1,
+ Slot_Reason2,
+ Slot_CancelPromise,
+ Slot_Stream,
+ Slot_Branch1,
+ Slot_Branch2,
+ SlotCount
+ };
+
+ private:
+ enum Flags {
+ Flag_Reading = 1 << 0,
+ Flag_Canceled1 = 1 << 1,
+ Flag_Canceled2 = 1 << 2,
+
+ // No internal user ever sets the cloneForBranch2 flag to true, and the
+ // streams spec doesn't expose a way to set the flag to true. So for the
+ // moment, don't even reserve flag-space to store it.
+ // Flag_CloneForBranch2 = 1 << 3,
+ };
+ uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
+ void setFlags(uint32_t flags) {
+ setFixedSlot(Slot_Flags, JS::Int32Value(flags));
+ }
+
+ public:
+ static const JSClass class_;
+
+ // Consistent with not even storing this always-false flag, expose it as
+ // compile-time constant false.
+ static constexpr bool cloneForBranch2() { return false; }
+
+ bool reading() const { return flags() & Flag_Reading; }
+ void setReading() {
+ MOZ_ASSERT(!(flags() & Flag_Reading));
+ setFlags(flags() | Flag_Reading);
+ }
+ void unsetReading() {
+ MOZ_ASSERT(flags() & Flag_Reading);
+ setFlags(flags() & ~Flag_Reading);
+ }
+
+ bool canceled1() const { return flags() & Flag_Canceled1; }
+ void setCanceled1(HandleValue reason) {
+ MOZ_ASSERT(!(flags() & Flag_Canceled1));
+ setFlags(flags() | Flag_Canceled1);
+ setFixedSlot(Slot_Reason1, reason);
+ }
+
+ bool canceled2() const { return flags() & Flag_Canceled2; }
+ void setCanceled2(HandleValue reason) {
+ MOZ_ASSERT(!(flags() & Flag_Canceled2));
+ setFlags(flags() | Flag_Canceled2);
+ setFixedSlot(Slot_Reason2, reason);
+ }
+
+ JS::Value reason1() const {
+ MOZ_ASSERT(canceled1());
+ return getFixedSlot(Slot_Reason1);
+ }
+
+ JS::Value reason2() const {
+ MOZ_ASSERT(canceled2());
+ return getFixedSlot(Slot_Reason2);
+ }
+
+ PromiseObject* cancelPromise() {
+ return &getFixedSlot(Slot_CancelPromise).toObject().as<PromiseObject>();
+ }
+
+ ReadableStreamDefaultController* branch1() {
+ ReadableStreamDefaultController* controller =
+ &getFixedSlot(Slot_Branch1)
+ .toObject()
+ .as<ReadableStreamDefaultController>();
+ MOZ_ASSERT(controller->isTeeBranch1());
+ return controller;
+ }
+ void setBranch1(ReadableStreamDefaultController* controller) {
+ MOZ_ASSERT(controller->isTeeBranch1());
+ setFixedSlot(Slot_Branch1, JS::ObjectValue(*controller));
+ }
+
+ ReadableStreamDefaultController* branch2() {
+ ReadableStreamDefaultController* controller =
+ &getFixedSlot(Slot_Branch2)
+ .toObject()
+ .as<ReadableStreamDefaultController>();
+ MOZ_ASSERT(controller->isTeeBranch2());
+ return controller;
+ }
+ void setBranch2(ReadableStreamDefaultController* controller) {
+ MOZ_ASSERT(controller->isTeeBranch2());
+ setFixedSlot(Slot_Branch2, JS::ObjectValue(*controller));
+ }
+
+ static TeeState* create(JSContext* cx,
+ Handle<ReadableStream*> unwrappedStream);
+};
+
+} // namespace js
+
+#endif // builtin_streams_TeeState_h
diff --git a/js/src/builtin/streams/WritableStream-inl.h b/js/src/builtin/streams/WritableStream-inl.h
new file mode 100644
index 0000000000..f2832d1ec1
--- /dev/null
+++ b/js/src/builtin/streams/WritableStream-inl.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class WritableStream. */
+
+#ifndef builtin_streams_WritableStream_inl_h
+#define builtin_streams_WritableStream_inl_h
+
+#include "builtin/streams/WritableStream.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jstypes.h" // JS_PUBLIC_API
+
+#include "builtin/streams/WritableStreamDefaultWriter.h" // js::WritableStreamDefaultWriter
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::{,Object}Value
+
+#include "vm/Compartment-inl.h" // js::UnwrapInternalSlot
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+/**
+ * Returns the writer associated with the given stream.
+ *
+ * Must only be called on WritableStreams that already have a writer
+ * associated with them.
+ *
+ * If the writer is a wrapper, it will be unwrapped, so the result might not be
+ * an object from the currently active compartment.
+ */
+inline MOZ_MUST_USE WritableStreamDefaultWriter* UnwrapWriterFromStream(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream) {
+ MOZ_ASSERT(unwrappedStream->hasWriter());
+ return UnwrapInternalSlot<WritableStreamDefaultWriter>(
+ cx, unwrappedStream, WritableStream::Slot_Writer);
+}
+
+} // namespace js
+
+#endif // builtin_streams_WritableStream_inl_h
diff --git a/js/src/builtin/streams/WritableStream.cpp b/js/src/builtin/streams/WritableStream.cpp
new file mode 100644
index 0000000000..2a021334ad
--- /dev/null
+++ b/js/src/builtin/streams/WritableStream.cpp
@@ -0,0 +1,282 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class WritableStream. */
+
+#include "builtin/streams/WritableStream.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsapi.h" // JS_ReportErrorNumberASCII
+#include "jspubtd.h" // JSProto_WritableStream
+
+#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
+#include "builtin/streams/MiscellaneousOperations.h" // js::MakeSizeAlgorithmFromSizeFunction, js::ReturnPromiseRejectedWithPendingError, js::ValidateAndNormalizeHighWaterMark
+#include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::SetUpWritableStreamDefaultControllerFromUnderlyingSink
+#include "builtin/streams/WritableStreamDefaultWriter.h" // js::CreateWritableStreamDefaultWriter
+#include "builtin/streams/WritableStreamOperations.h" // js::WritableStream{Abort,Close{,QueuedOrInFlight}}
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/Class.h" // JS{Function,Property}Spec, JS_{FS,PS}_END, JSCLASS_PRIVATE_IS_NSISUPPORTS, JSCLASS_HAS_PRIVATE, JS_NULL_CLASS_OPS
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/RealmOptions.h" // JS::RealmCreationOptions
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "js/Value.h" // JS::{,Object}Value
+#include "vm/JSContext.h" // JSContext
+#include "vm/JSObject.h" // js::GetPrototypeFromBuiltinConstructor
+#include "vm/ObjectOperations.h" // js::GetProperty
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/Realm.h" // JS::Realm
+
+#include "vm/Compartment-inl.h" // js::UnwrapAndTypeCheckThis
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/JSObject-inl.h" // js::NewBuiltinClassInstance
+#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing
+
+using js::CreateWritableStreamDefaultWriter;
+using js::GetErrorMessage;
+using js::ReturnPromiseRejectedWithPendingError;
+using js::UnwrapAndTypeCheckThis;
+using js::WritableStream;
+using js::WritableStreamAbort;
+using js::WritableStreamClose;
+using js::WritableStreamCloseQueuedOrInFlight;
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::ObjectValue;
+using JS::Rooted;
+using JS::Value;
+
+/*** 4.2. Class WritableStream **********************************************/
+
+/**
+ * Streams spec, 4.2.3. new WritableStream(underlyingSink = {}, strategy = {})
+ */
+bool WritableStream::constructor(JSContext* cx, unsigned argc, Value* vp) {
+ MOZ_ASSERT(cx->realm()->creationOptions().getWritableStreamsEnabled(),
+ "WritableStream should be enabled in this realm if we reach here");
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "WritableStream")) {
+ return false;
+ }
+
+ // Implicit in the spec: argument default values.
+ Rooted<Value> underlyingSink(cx, args.get(0));
+ if (underlyingSink.isUndefined()) {
+ JSObject* emptyObj = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!emptyObj) {
+ return false;
+ }
+ underlyingSink = ObjectValue(*emptyObj);
+ }
+
+ Rooted<Value> strategy(cx, args.get(1));
+ if (strategy.isUndefined()) {
+ JSObject* emptyObj = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!emptyObj) {
+ return false;
+ }
+ strategy = ObjectValue(*emptyObj);
+ }
+
+ // Implicit in the spec: Set this to
+ // OrdinaryCreateFromConstructor(NewTarget, ...).
+ // Step 1: Perform ! InitializeWritableStream(this).
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_WritableStream,
+ &proto)) {
+ return false;
+ }
+ Rooted<WritableStream*> stream(cx,
+ WritableStream::create(cx, nullptr, proto));
+ if (!stream) {
+ return false;
+ }
+
+ // Step 2: Let size be ? GetV(strategy, "size").
+ Rooted<Value> size(cx);
+ if (!GetProperty(cx, strategy, cx->names().size, &size)) {
+ return false;
+ }
+
+ // Step 3: Let highWaterMark be ? GetV(strategy, "highWaterMark").
+ Rooted<Value> highWaterMarkVal(cx);
+ if (!GetProperty(cx, strategy, cx->names().highWaterMark,
+ &highWaterMarkVal)) {
+ return false;
+ }
+
+ // Step 4: Let type be ? GetV(underlyingSink, "type").
+ Rooted<Value> type(cx);
+ if (!GetProperty(cx, underlyingSink, cx->names().type, &type)) {
+ return false;
+ }
+
+ // Step 5: If type is not undefined, throw a RangeError exception.
+ if (!type.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_UNDERLYINGSINK_TYPE_WRONG);
+ return false;
+ }
+
+ // Step 6: Let sizeAlgorithm be ? MakeSizeAlgorithmFromSizeFunction(size).
+ if (!MakeSizeAlgorithmFromSizeFunction(cx, size)) {
+ return false;
+ }
+
+ // Step 7: If highWaterMark is undefined, let highWaterMark be 1.
+ double highWaterMark;
+ if (highWaterMarkVal.isUndefined()) {
+ highWaterMark = 1;
+ } else {
+ // Step 8: Set highWaterMark to ?
+ // ValidateAndNormalizeHighWaterMark(highWaterMark).
+ if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal,
+ &highWaterMark)) {
+ return false;
+ }
+ }
+
+ // Step 9: Perform
+ // ? SetUpWritableStreamDefaultControllerFromUnderlyingSink(
+ // this, underlyingSink, highWaterMark, sizeAlgorithm).
+ if (!SetUpWritableStreamDefaultControllerFromUnderlyingSink(
+ cx, stream, underlyingSink, highWaterMark, size)) {
+ return false;
+ }
+
+ args.rval().setObject(*stream);
+ return true;
+}
+
+/**
+ * Streams spec, 4.2.5.1. get locked
+ */
+static bool WritableStream_locked(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! WritableStream(this) is false, throw a TypeError exception.
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapAndTypeCheckThis<WritableStream>(cx, args, "get locked"));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 2: Return ! IsWritableStreamLocked(this).
+ args.rval().setBoolean(unwrappedStream->isLocked());
+ return true;
+}
+
+/**
+ * Streams spec, 4.2.5.2. abort(reason)
+ */
+static bool WritableStream_abort(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsWritableStream(this) is false, return a promise rejected
+ // with a TypeError exception.
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapAndTypeCheckThis<WritableStream>(cx, args, "abort"));
+ if (!unwrappedStream) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: If ! IsWritableStreamLocked(this) is true, return a promise
+ // rejected with a TypeError exception.
+ if (unwrappedStream->isLocked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_CANT_USE_LOCKED_WRITABLESTREAM, "abort");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! WritableStreamAbort(this, reason).
+ JSObject* promise = WritableStreamAbort(cx, unwrappedStream, args.get(0));
+ if (!promise) {
+ return false;
+ }
+ cx->check(promise);
+
+ args.rval().setObject(*promise);
+ return true;
+}
+
+/**
+ * Streams spec, 4.2.5.3. close()
+ */
+static bool WritableStream_close(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsWritableStream(this) is false, return a promise rejected
+ // with a TypeError exception.
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapAndTypeCheckThis<WritableStream>(cx, args, "close"));
+ if (!unwrappedStream) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: If ! IsWritableStreamLocked(this) is true, return a promise
+ // rejected with a TypeError exception.
+ if (unwrappedStream->isLocked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_CANT_USE_LOCKED_WRITABLESTREAM, "close");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: If ! WritableStreamCloseQueuedOrInFlight(this) is true, return a
+ // promise rejected with a TypeError exception.
+ if (WritableStreamCloseQueuedOrInFlight(unwrappedStream)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_CLOSE_CLOSING_OR_CLOSED);
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 4: Return ! WritableStreamClose(this).
+ JSObject* promise = WritableStreamClose(cx, unwrappedStream);
+ if (!promise) {
+ return false;
+ }
+
+ args.rval().setObject(*promise);
+ return true;
+}
+
+/**
+ * Streams spec, 4.2.5.4. getWriter()
+ */
+static bool WritableStream_getWriter(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! WritableStream(this) is false, throw a TypeError exception.
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapAndTypeCheckThis<WritableStream>(cx, args, "getWriter"));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ auto* writer = CreateWritableStreamDefaultWriter(cx, unwrappedStream);
+ if (!writer) {
+ return false;
+ }
+
+ args.rval().setObject(*writer);
+ return true;
+}
+
+static const JSFunctionSpec WritableStream_methods[] = {
+ JS_FN("abort", WritableStream_abort, 1, 0),
+ JS_FN("close", WritableStream_close, 0, 0),
+ JS_FN("getWriter", WritableStream_getWriter, 0, 0), JS_FS_END};
+
+static const JSPropertySpec WritableStream_properties[] = {
+ JS_PSG("locked", WritableStream_locked, 0), JS_PS_END};
+
+JS_STREAMS_CLASS_SPEC(WritableStream, 0, SlotCount, 0,
+ JSCLASS_PRIVATE_IS_NSISUPPORTS | JSCLASS_HAS_PRIVATE,
+ JS_NULL_CLASS_OPS);
diff --git a/js/src/builtin/streams/WritableStream.h b/js/src/builtin/streams/WritableStream.h
new file mode 100644
index 0000000000..3ed6cd12f4
--- /dev/null
+++ b/js/src/builtin/streams/WritableStream.h
@@ -0,0 +1,425 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class WritableStream. */
+
+#ifndef builtin_streams_WritableStream_h
+#define builtin_streams_WritableStream_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+#include "mozilla/Casting.h" // mozilla::AssertedCast
+#include "mozilla/MathAlgorithms.h" // mozilla::IsPowerOfTwo
+
+#include <stdint.h> // uint32_t
+
+#include "jstypes.h" // JS_PUBLIC_API
+#include "js/Class.h" // JSClass, js::ClassSpec
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::{,Int32,Object,Undefined}Value
+#include "vm/List.h" // js::ListObject
+#include "vm/NativeObject.h" // js::NativeObject
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+class PromiseObject;
+class WritableStreamDefaultController;
+class WritableStreamDefaultWriter;
+
+class WritableStream : public NativeObject {
+ public:
+ enum Slots {
+ /**
+ * A WritableStream's associated controller is always created from under the
+ * stream's constructor and thus cannot be in a different compartment.
+ */
+ Slot_Controller,
+
+ /**
+ * Either |undefined| if no writer has been created yet for |this|, or a
+ * |WritableStreamDefaultWriter| object that writes to this. Writers are
+ * created under |WritableStream.prototype.getWriter|, which may not be
+ * same-compartment with |this|, so this object may be a wrapper.
+ */
+ Slot_Writer,
+
+ /**
+ * A bit field that stores both [[state]] and the [[backpressure]] spec
+ * fields in a WritableStream::State 32-bit integer.
+ */
+ Slot_State,
+
+ /**
+ * Either |undefined| if this stream hasn't yet started erroring, or an
+ * arbitrary value indicating the reason for the error (e.g. the
+ * reason-value passed to a related |abort(reason)| or |error(e)| function).
+ *
+ * This value can be an arbitrary user-provided value, so it might be a
+ * cross-comaprtment wrapper.
+ */
+ Slot_StoredError,
+
+ /**
+ * Very briefly for newborn writable streams before they are initialized,
+ * |undefined|.
+ *
+ * After initialization, a |ListObject| consisting of the value of the
+ * [[inFlightWriteRequest]] spec field (if it is not |undefined|) followed
+ * by the elements of the [[queue]] List. |this| and the |ListObject| are
+ * same-compartment.
+ *
+ * After a stream has gone irrevocably into an error state (specifically,
+ * |stream.[[state]]| is "errored") and requests can no longer be enqueued,
+ * |undefined| yet again.
+ *
+ * If the |HaveInFlightWriteRequest| flag is set, the first element of this
+ * List is the non-|undefined| value of [[inFlightWriteRequest]]. If it is
+ * unset, [[inFlightWriteRequest]] has the value |undefined|.
+ */
+ Slot_WriteRequests,
+
+ /**
+ * A slot storing both [[closeRequest]] and [[inFlightCloseRequest]]. This
+ * value is created under |WritableStreamDefaultWriterClose|, so it may be a
+ * wrapper around a promise rather than directly a |PromiseObject|.
+ *
+ * If this slot has the value |undefined|, then [[inFlightCloseRequest]]
+ * and [[closeRequest]] are both |undefined|. Otherwise one field has the
+ * value |undefined| and the other has the value of this slot, and the value
+ * of the |HaveInFlightCloseRequest| flag indicates which field is set.
+ */
+ Slot_CloseRequest,
+
+ /**
+ * In the spec the [[pendingAbortRequest]] field is either |undefined| or
+ * Record { [[promise]]: Object, [[reason]]: value, [[wasAlreadyErroring]]:
+ * boolean }. We represent this as follows:
+ *
+ * 1) If Slot_PendingAbortRequestPromise contains |undefined|, then the
+ * spec field is |undefined|;
+ * 2) Otherwise Slot_PendingAbortRequestPromise contains the value of
+ * [[pendingAbortRequest]].[[promise]], Slot_PendingAbortRequestReason
+ * contains the value of [[pendingAbortRequest]].[[reason]], and the
+ * |PendingAbortRequestWasAlreadyErroring| flag stores the value of
+ * [[pendingAbortRequest]].[[wasAlreadyErroring]].
+ */
+ Slot_PendingAbortRequestPromise,
+ Slot_PendingAbortRequestReason,
+
+ SlotCount
+ };
+
+ private:
+ enum State : uint32_t {
+ Writable = 0x0000'0000,
+ Closed = 0x0000'0001,
+ Erroring = 0x0000'0002,
+ Errored = 0x0000'0003,
+ StateBits = 0x0000'0003,
+ StateMask = 0x0000'00ff,
+
+ Backpressure = 0x0000'0100,
+ HaveInFlightWriteRequest = 0x0000'0200,
+ HaveInFlightCloseRequest = 0x0000'0400,
+ PendingAbortRequestWasAlreadyErroring = 0x0000'0800,
+ FlagBits = Backpressure | HaveInFlightWriteRequest |
+ HaveInFlightCloseRequest | PendingAbortRequestWasAlreadyErroring,
+ FlagMask = 0x0000'ff00,
+
+ SettableBits = uint32_t(StateBits | FlagBits)
+ };
+
+ bool stateIsInitialized() const { return getFixedSlot(Slot_State).isInt32(); }
+
+ State state() const {
+ MOZ_ASSERT(stateIsInitialized());
+
+ uint32_t v = getFixedSlot(Slot_State).toInt32();
+ MOZ_ASSERT((v & ~SettableBits) == 0);
+
+ return static_cast<State>(v & StateMask);
+ }
+
+ State flags() const {
+ MOZ_ASSERT(stateIsInitialized());
+
+ uint32_t v = getFixedSlot(Slot_State).toInt32();
+ MOZ_ASSERT((v & ~SettableBits) == 0);
+
+ return static_cast<State>(v & FlagMask);
+ }
+
+ void initWritableState() {
+ MOZ_ASSERT(!stateIsInitialized());
+
+ setFixedSlot(Slot_State, JS::Int32Value(Writable));
+
+ MOZ_ASSERT(writable());
+ MOZ_ASSERT(!backpressure());
+ }
+
+ void setState(State newState) {
+ MOZ_ASSERT(stateIsInitialized());
+ MOZ_ASSERT((newState & ~StateBits) == 0);
+ MOZ_ASSERT(newState <= Errored);
+
+#ifdef DEBUG
+ {
+ auto current = state();
+ if (current == Writable) {
+ MOZ_ASSERT(newState == Closed || newState == Erroring);
+ } else if (current == Erroring) {
+ MOZ_ASSERT(newState == Errored || newState == Closed);
+ } else if (current == Closed || current == Errored) {
+ MOZ_ASSERT_UNREACHABLE(
+ "closed/errored stream shouldn't undergo state transitions");
+ } else {
+ MOZ_ASSERT_UNREACHABLE("smashed state bits?");
+ }
+ }
+#endif
+
+ uint32_t newValue = static_cast<uint32_t>(newState) |
+ (getFixedSlot(Slot_State).toInt32() & FlagMask);
+ setFixedSlot(Slot_State,
+ JS::Int32Value(mozilla::AssertedCast<int32_t>(newValue)));
+ }
+
+ void setFlag(State flag, bool set) {
+ MOZ_ASSERT(stateIsInitialized());
+ MOZ_ASSERT(mozilla::IsPowerOfTwo(uint32_t(flag)));
+ MOZ_ASSERT((flag & FlagBits) != 0);
+
+ uint32_t v = getFixedSlot(Slot_State).toInt32();
+ MOZ_ASSERT((v & ~SettableBits) == 0);
+
+ uint32_t newValue = set ? (v | flag) : (v & ~flag);
+ setFixedSlot(Slot_State,
+ JS::Int32Value(mozilla::AssertedCast<int32_t>(newValue)));
+ }
+
+ public:
+ bool writable() const { return state() == Writable; }
+
+ bool closed() const { return state() == Closed; }
+ void setClosed() { setState(Closed); }
+
+ bool erroring() const { return state() == Erroring; }
+ void setErroring() { setState(Erroring); }
+
+ bool errored() const { return state() == Errored; }
+ void setErrored() { setState(Errored); }
+
+ bool backpressure() const { return flags() & Backpressure; }
+ void setBackpressure(bool pressure) { setFlag(Backpressure, pressure); }
+
+ bool haveInFlightWriteRequest() const {
+ return flags() & HaveInFlightWriteRequest;
+ }
+ void setHaveInFlightWriteRequest() {
+ MOZ_ASSERT(!haveInFlightWriteRequest());
+ MOZ_ASSERT(writeRequests()->length() > 0);
+ setFlag(HaveInFlightWriteRequest, true);
+ }
+
+ bool haveInFlightCloseRequest() const {
+ return flags() & HaveInFlightCloseRequest;
+ }
+
+ bool hasController() const {
+ return !getFixedSlot(Slot_Controller).isUndefined();
+ }
+ inline WritableStreamDefaultController* controller() const;
+ inline void setController(WritableStreamDefaultController* controller);
+ void clearController() {
+ setFixedSlot(Slot_Controller, JS::UndefinedValue());
+ }
+
+ bool hasWriter() const { return !getFixedSlot(Slot_Writer).isUndefined(); }
+ bool isLocked() const { return hasWriter(); }
+ void setWriter(JSObject* writer) {
+ MOZ_ASSERT(!hasWriter());
+ setFixedSlot(Slot_Writer, JS::ObjectValue(*writer));
+ }
+ void clearWriter() { setFixedSlot(Slot_Writer, JS::UndefinedValue()); }
+
+ JS::Value storedError() const { return getFixedSlot(Slot_StoredError); }
+ void setStoredError(JS::Handle<JS::Value> value) {
+ setFixedSlot(Slot_StoredError, value);
+ }
+ void clearStoredError() {
+ setFixedSlot(Slot_StoredError, JS::UndefinedValue());
+ }
+
+ JS::Value inFlightWriteRequest() const {
+ MOZ_ASSERT(stateIsInitialized());
+
+ // The in-flight write request is the first element of |writeRequests()| --
+ // if there is a request in flight.
+ if (haveInFlightWriteRequest()) {
+ MOZ_ASSERT(writeRequests()->length() > 0);
+ return writeRequests()->get(0);
+ }
+
+ return JS::UndefinedValue();
+ }
+
+ void clearInFlightWriteRequest(JSContext* cx);
+
+ JS::Value closeRequest() const {
+ JS::Value v = getFixedSlot(Slot_CloseRequest);
+ if (v.isUndefined()) {
+ // In principle |haveInFlightCloseRequest()| only distinguishes whether
+ // the close-request slot is [[closeRequest]] or [[inFlightCloseRequest]].
+ // In practice, for greater implementation strictness to try to head off
+ // more bugs, we require that the HaveInFlightCloseRequest flag be unset
+ // when [[closeRequest]] and [[inFlightCloseRequest]] are both undefined.
+ MOZ_ASSERT(!haveInFlightCloseRequest());
+ return JS::UndefinedValue();
+ }
+
+ if (!haveInFlightCloseRequest()) {
+ return v;
+ }
+
+ return JS::UndefinedValue();
+ }
+
+ void setCloseRequest(JSObject* closeRequest) {
+ MOZ_ASSERT(!haveCloseRequestOrInFlightCloseRequest());
+ setFixedSlot(Slot_CloseRequest, JS::ObjectValue(*closeRequest));
+ MOZ_ASSERT(!haveInFlightCloseRequest());
+ }
+
+ void clearCloseRequest() {
+ MOZ_ASSERT(!haveInFlightCloseRequest());
+ MOZ_ASSERT(!getFixedSlot(Slot_CloseRequest).isUndefined());
+ setFixedSlot(Slot_CloseRequest, JS::UndefinedValue());
+ }
+
+ JS::Value inFlightCloseRequest() const {
+ JS::Value v = getFixedSlot(Slot_CloseRequest);
+ if (v.isUndefined()) {
+ // In principle |haveInFlightCloseRequest()| only distinguishes whether
+ // the close-request slot is [[closeRequest]] or [[inFlightCloseRequest]].
+ // In practice, for greater implementation strictness to try to head off
+ // more bugs, we require that the HaveInFlightCloseRequest flag be unset
+ // when [[closeRequest]] and [[inFlightCloseRequest]] are both undefined.
+ MOZ_ASSERT(!haveInFlightCloseRequest());
+ return JS::UndefinedValue();
+ }
+
+ if (haveInFlightCloseRequest()) {
+ return v;
+ }
+
+ return JS::UndefinedValue();
+ }
+
+ bool haveCloseRequestOrInFlightCloseRequest() const {
+ // Slot_CloseRequest suffices to store both [[closeRequest]] and
+ // [[inFlightCloseRequest]], with the precisely-set field determined by
+ // |haveInFlightCloseRequest()|. If both are undefined, then per above, for
+ // extra implementation rigor, |haveInFlightCloseRequest()| will be false,
+ // so additionally assert that.
+ if (getFixedSlot(Slot_CloseRequest).isUndefined()) {
+ MOZ_ASSERT(!haveInFlightCloseRequest());
+ return false;
+ }
+
+ return true;
+ }
+
+ void convertCloseRequestToInFlightCloseRequest() {
+ MOZ_ASSERT(stateIsInitialized());
+ MOZ_ASSERT(!haveInFlightCloseRequest());
+ setFlag(HaveInFlightCloseRequest, true);
+ MOZ_ASSERT(haveInFlightCloseRequest());
+ }
+
+ void clearInFlightCloseRequest() {
+ MOZ_ASSERT(stateIsInitialized());
+ MOZ_ASSERT(haveInFlightCloseRequest());
+ MOZ_ASSERT(!getFixedSlot(Slot_CloseRequest).isUndefined());
+
+ // As noted above, for greater rigor we require HaveInFlightCloseRequest be
+ // unset when [[closeRequest]] and [[inFlightCloseRequest]] are both
+ // undefined.
+ setFlag(HaveInFlightCloseRequest, false);
+ setFixedSlot(Slot_CloseRequest, JS::UndefinedValue());
+ }
+
+ ListObject* writeRequests() const {
+ MOZ_ASSERT(!getFixedSlot(Slot_WriteRequests).isUndefined(),
+ "shouldn't be accessing [[writeRequests]] on a newborn and "
+ "uninitialized stream, or on a stream that's errored and no "
+ "longer has any write requests");
+ return &getFixedSlot(Slot_WriteRequests).toObject().as<ListObject>();
+ }
+ void clearWriteRequests() {
+ // Setting [[writeRequests]] to an empty List in the irrevocably-in-error
+ // case (in which [[writeRequests]] is never again accessed) is optimized to
+ // just clearing the field. See the comment on the slot constant above.
+ MOZ_ASSERT(stateIsInitialized());
+ MOZ_ASSERT(!haveInFlightWriteRequest(),
+ "must clear the in-flight request flag before clearing "
+ "requests");
+ setFixedSlot(Slot_WriteRequests, JS::UndefinedValue());
+ }
+
+ bool hasPendingAbortRequest() const {
+ MOZ_ASSERT(stateIsInitialized());
+ return !getFixedSlot(Slot_PendingAbortRequestPromise).isUndefined();
+ }
+ JSObject* pendingAbortRequestPromise() const {
+ MOZ_ASSERT(hasPendingAbortRequest());
+ return &getFixedSlot(Slot_PendingAbortRequestPromise).toObject();
+ }
+ JS::Value pendingAbortRequestReason() const {
+ MOZ_ASSERT(hasPendingAbortRequest());
+ return getFixedSlot(Slot_PendingAbortRequestReason);
+ }
+ bool pendingAbortRequestWasAlreadyErroring() const {
+ MOZ_ASSERT(hasPendingAbortRequest());
+ return flags() & PendingAbortRequestWasAlreadyErroring;
+ }
+
+ void setPendingAbortRequest(JSObject* promise, const JS::Value& reason,
+ bool wasAlreadyErroring) {
+ MOZ_ASSERT(!hasPendingAbortRequest());
+ MOZ_ASSERT(!(flags() & PendingAbortRequestWasAlreadyErroring));
+ setFixedSlot(Slot_PendingAbortRequestPromise, JS::ObjectValue(*promise));
+ setFixedSlot(Slot_PendingAbortRequestReason, reason);
+ setFlag(PendingAbortRequestWasAlreadyErroring, wasAlreadyErroring);
+ }
+
+ void clearPendingAbortRequest() {
+ MOZ_ASSERT(stateIsInitialized());
+ MOZ_ASSERT(hasPendingAbortRequest());
+
+ // [[pendingAbortRequest]] is { [[promise]], [[reason]] } in the spec but
+ // separate slots in our implementation, so both must be cleared.
+ setFixedSlot(Slot_PendingAbortRequestPromise, JS::UndefinedValue());
+ setFixedSlot(Slot_PendingAbortRequestReason, JS::UndefinedValue());
+ }
+
+ static MOZ_MUST_USE WritableStream* create(
+ JSContext* cx, void* nsISupportsObject_alreadyAddreffed = nullptr,
+ JS::Handle<JSObject*> proto = nullptr);
+
+ static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
+
+ static const ClassSpec classSpec_;
+ static const JSClass class_;
+ static const ClassSpec protoClassSpec_;
+ static const JSClass protoClass_;
+};
+
+} // namespace js
+
+#endif // builtin_streams_WritableStream_h
diff --git a/js/src/builtin/streams/WritableStreamDefaultController.cpp b/js/src/builtin/streams/WritableStreamDefaultController.cpp
new file mode 100644
index 0000000000..8f9022459f
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamDefaultController.cpp
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class WritableStreamDefaultController. */
+
+#include "builtin/streams/WritableStreamDefaultController.h"
+
+#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
+#include "builtin/streams/WritableStream.h" // js::WritableStream
+#include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::WritableStreamDefaultControllerError
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/Class.h" // js::ClassSpec, JS_NULL_CLASS_OPS
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/PropertySpec.h" // JS{Function,Property}Spec, JS_{FS,PS}_END
+#include "js/Value.h" // JS::Value
+
+#include "vm/Compartment-inl.h" // js::UnwrapAndTypeCheckThis
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Rooted;
+using JS::Value;
+
+using js::ClassSpec;
+using js::UnwrapAndTypeCheckThis;
+using js::WritableStreamDefaultController;
+using js::WritableStreamDefaultControllerError;
+
+/*** 4.7. Class WritableStreamDefaultController *****************************/
+
+/**
+ * Streams spec, 4.7.3.
+ * new WritableStreamDefaultController()
+ */
+bool WritableStreamDefaultController::constructor(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Step 1: Throw a TypeError.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_BOGUS_CONSTRUCTOR,
+ "WritableStreamDefaultController");
+ return false;
+}
+
+/**
+ * Streams spec, 4.7.4.1. error(e)
+ */
+static bool WritableStreamDefaultController_error(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Step 1: If ! IsWritableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, UnwrapAndTypeCheckThis<WritableStreamDefaultController>(cx, args,
+ "error"));
+ if (!unwrappedController) {
+ return false;
+ }
+
+ // Step 2: Let state be this.[[controlledWritableStream]].[[state]].
+ // Step 3: If state is not "writable", return.
+ if (unwrappedController->stream()->writable()) {
+ // Step 4: Perform ! WritableStreamDefaultControllerError(this, e).
+ if (!WritableStreamDefaultControllerError(cx, unwrappedController,
+ args.get(0))) {
+ return false;
+ }
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static const JSPropertySpec WritableStreamDefaultController_properties[] = {
+ JS_PS_END};
+
+static const JSFunctionSpec WritableStreamDefaultController_methods[] = {
+ JS_FN("error", WritableStreamDefaultController_error, 1, 0), JS_FS_END};
+
+JS_STREAMS_CLASS_SPEC(WritableStreamDefaultController, 0, SlotCount,
+ ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
diff --git a/js/src/builtin/streams/WritableStreamDefaultController.h b/js/src/builtin/streams/WritableStreamDefaultController.h
new file mode 100644
index 0000000000..44f3d237a9
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamDefaultController.h
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* WritableStream controller classes and functions. */
+
+#ifndef builtin_streams_WritableStreamDefaultController_h
+#define builtin_streams_WritableStreamDefaultController_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+
+#include <stdint.h> // uint32_t
+
+#include "builtin/streams/StreamController.h" // js::StreamController
+#include "builtin/streams/WritableStream.h" // js::WritableStream
+#include "js/Class.h" // JSClass, js::ClassSpec
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Stream.h" // JS::WritableStreamUnderlyingSink
+#include "js/Value.h" // JS::Value, JS::{Number,Object,Private,Undefined}Value, JS::UndefinedHandleValue
+#include "vm/NativeObject.h" // js::NativeObject
+
+namespace js {
+
+class WritableStreamDefaultController : public StreamController {
+ public:
+ /**
+ * Memory layout for WritableStream default controllers, starting after the
+ * slots reserved for queue container usage. (Note that this is the only
+ * writable stream controller class in the spec: ReadableByteStreamController
+ * exists, but WritableByteStreamController does not.)
+ */
+ enum Slots {
+ /**
+ * The stream that this controller controls. Stream and controller are
+ * initialized at the same time underneath the |WritableStream| constructor,
+ * so they are same-compartment with each other.
+ */
+ Slot_Stream = StreamController::SlotCount,
+
+ /**
+ * The underlying sink object that this controller and its associated stream
+ * write to.
+ *
+ * This is a user-provided value, the first argument passed to
+ * |new WritableStream|, so it may be a cross-compartment wrapper around an
+ * object from another realm.
+ */
+ Slot_UnderlyingSink,
+
+ /** Number stored as DoubleValue. */
+ Slot_StrategyHWM,
+
+ /**
+ * Either undefined if each chunk has size 1, or a callable object to be
+ * invoked on each chunk to determine its size. See
+ * MakeSizeAlgorithmFromSizeFunction.
+ */
+ Slot_StrategySize,
+
+ /**
+ * Slots containing the core of each of the write/close/abort algorithms the
+ * spec creates from the underlying sink passed in when creating a
+ * |WritableStream|. ("core", as in the value produced by
+ * |CreateAlgorithmFromUnderlyingMethod| after validating the user-provided
+ * input.)
+ *
+ * These slots are initialized underneath the |WritableStream| constructor,
+ * so they are same-compartment with both stream and controller. (They
+ * could be wrappers around arbitrary callable objects from other
+ * compartments, tho.)
+ */
+ Slot_WriteMethod,
+ Slot_CloseMethod,
+ Slot_AbortMethod,
+
+ /** Bit field stored as Int32Value. */
+ Slot_Flags,
+
+ SlotCount
+ };
+
+ enum ControllerFlags {
+ Flag_Started = 0b0001,
+ Flag_ExternalSink = 0b0010,
+ };
+
+ WritableStream* stream() const {
+ return &getFixedSlot(Slot_Stream).toObject().as<WritableStream>();
+ }
+ void setStream(WritableStream* stream) {
+ setFixedSlot(Slot_Stream, JS::ObjectValue(*stream));
+ }
+
+ JS::Value underlyingSink() const { return getFixedSlot(Slot_UnderlyingSink); }
+ void setUnderlyingSink(const JS::Value& underlyingSink) {
+ setFixedSlot(Slot_UnderlyingSink, underlyingSink);
+ }
+
+ JS::WritableStreamUnderlyingSink* externalSink() const {
+ static_assert(alignof(JS::WritableStreamUnderlyingSink) >= 2,
+ "external underling sinks are stored as PrivateValues, so "
+ "they must have even addresses");
+ MOZ_ASSERT(hasExternalSink());
+ return static_cast<JS::WritableStreamUnderlyingSink*>(
+ underlyingSink().toPrivate());
+ }
+ void setExternalSink(JS::WritableStreamUnderlyingSink* underlyingSink) {
+ setUnderlyingSink(JS::PrivateValue(underlyingSink));
+ addFlags(Flag_ExternalSink);
+ }
+ static void clearUnderlyingSink(
+ JS::Handle<WritableStreamDefaultController*> controller,
+ bool finalizeSink = true) {
+ if (controller->hasExternalSink()) {
+ if (finalizeSink) {
+ controller->externalSink()->finalize();
+ }
+ controller->setFlags(controller->flags() & ~Flag_ExternalSink);
+ }
+ controller->setUnderlyingSink(JS::UndefinedHandleValue);
+ }
+
+ JS::Value writeMethod() const { return getFixedSlot(Slot_WriteMethod); }
+ void setWriteMethod(const JS::Value& writeMethod) {
+ setFixedSlot(Slot_WriteMethod, writeMethod);
+ }
+ void clearWriteMethod() { setWriteMethod(JS::UndefinedValue()); }
+
+ JS::Value closeMethod() const { return getFixedSlot(Slot_CloseMethod); }
+ void setCloseMethod(const JS::Value& closeMethod) {
+ setFixedSlot(Slot_CloseMethod, closeMethod);
+ }
+ void clearCloseMethod() { setCloseMethod(JS::UndefinedValue()); }
+
+ JS::Value abortMethod() const { return getFixedSlot(Slot_AbortMethod); }
+ void setAbortMethod(const JS::Value& abortMethod) {
+ setFixedSlot(Slot_AbortMethod, abortMethod);
+ }
+ void clearAbortMethod() { setAbortMethod(JS::UndefinedValue()); }
+
+ double strategyHWM() const {
+ return getFixedSlot(Slot_StrategyHWM).toDouble();
+ }
+ void setStrategyHWM(double highWaterMark) {
+ setFixedSlot(Slot_StrategyHWM, DoubleValue(highWaterMark));
+ }
+
+ JS::Value strategySize() const { return getFixedSlot(Slot_StrategySize); }
+ void setStrategySize(const JS::Value& size) {
+ setFixedSlot(Slot_StrategySize, size);
+ }
+ void clearStrategySize() { setStrategySize(JS::UndefinedValue()); }
+
+ uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
+ void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); }
+ void addFlags(uint32_t flags) { setFlags(this->flags() | flags); }
+ void removeFlags(uint32_t flags) { setFlags(this->flags() & ~flags); }
+
+ bool started() const { return flags() & Flag_Started; }
+ void setStarted() { addFlags(Flag_Started); }
+
+ bool hasExternalSink() const { return flags() & Flag_ExternalSink; }
+
+ static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
+ static const ClassSpec classSpec_;
+ static const JSClass class_;
+ static const ClassSpec protoClassSpec_;
+ static const JSClass protoClass_;
+};
+
+inline WritableStreamDefaultController* WritableStream::controller() const {
+ return &getFixedSlot(Slot_Controller)
+ .toObject()
+ .as<WritableStreamDefaultController>();
+}
+
+inline void WritableStream::setController(
+ WritableStreamDefaultController* controller) {
+ setFixedSlot(Slot_Controller, JS::ObjectValue(*controller));
+}
+
+} // namespace js
+
+#endif // builtin_streams_WritableStreamDefaultController_h
diff --git a/js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp b/js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp
new file mode 100644
index 0000000000..704d4e4dac
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamDefaultControllerOperations.cpp
@@ -0,0 +1,1014 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Writable stream default controller abstract operations. */
+
+#include "builtin/streams/WritableStreamDefaultControllerOperations.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsapi.h" // JS_ReportErrorASCII
+
+#include "builtin/streams/MiscellaneousOperations.h" // js::CreateAlgorithmFromUnderlyingMethod, js::InvokeOrNoop
+#include "builtin/streams/QueueWithSizes.h" // js::{EnqueueValueWithSize,QueueIsEmpty,ResetQueue}
+#include "builtin/streams/WritableStream.h" // js::WritableStream
+#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStreamDefaultController
+#include "builtin/streams/WritableStreamOperations.h" // js::WritableStream{CloseQueuedOrInFlight,DealWithRejection,{Start,Finish}Erroring,UpdateBackpressure,Mark{Close,FirstWrite}RequestInFlight}
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/Promise.h" // JS::AddPromiseReactions
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "js/Value.h" // JS::{,Int32,Magic,Object}Value, JS::UndefinedHandleValue, JS_WRITABLESTREAM_CLOSE_RECORD
+#include "vm/Compartment.h" // JS::Compartment
+#include "vm/JSContext.h" // JSContext
+#include "vm/JSObject.h" // JSObject
+#include "vm/List.h" // js::ListObject
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+#include "vm/Runtime.h" // JSAtomState
+
+#include "builtin/HandlerFunction-inl.h" // js::TargetFromHandler
+#include "builtin/streams/MiscellaneousOperations-inl.h" // js::PromiseCall
+#include "builtin/streams/QueueWithSizes-inl.h" // js::PeekQueueValue
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/JSObject-inl.h" // js::IsCallable, js::NewBuiltinClassInstance, js::NewObjectWithClassProto
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::Int32Value;
+using JS::MagicValue;
+using JS::ObjectValue;
+using JS::Rooted;
+using JS::UndefinedHandleValue;
+using JS::Value;
+
+using js::IsCallable;
+using js::ListObject;
+using js::NewHandler;
+using js::PeekQueueValue;
+using js::PromiseObject;
+using js::PromiseResolvedWithUndefined;
+using js::TargetFromHandler;
+using js::WritableStream;
+using js::WritableStreamCloseQueuedOrInFlight;
+using js::WritableStreamDefaultController;
+using js::WritableStreamFinishErroring;
+using js::WritableStreamMarkCloseRequestInFlight;
+using js::WritableStreamMarkFirstWriteRequestInFlight;
+using js::WritableStreamUpdateBackpressure;
+
+/*** 4.7. Writable stream default controller internal methods ***************/
+
+/**
+ * Streams spec, 4.7.5.1.
+ * [[AbortSteps]]( reason )
+ */
+JSObject* js::WritableStreamControllerAbortSteps(
+ JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController,
+ Handle<Value> reason) {
+ cx->check(reason);
+
+ // Step 1: Let result be the result of performing this.[[abortAlgorithm]],
+ // passing reason.
+ // CreateAlgorithmFromUnderlyingMethod(underlyingSink, "abort", 1, « »)
+ Rooted<Value> unwrappedAbortMethod(cx, unwrappedController->abortMethod());
+ Rooted<JSObject*> result(cx);
+ if (unwrappedAbortMethod.isUndefined()) {
+ // CreateAlgorithmFromUnderlyingMethod step 7.
+ result = PromiseResolvedWithUndefined(cx);
+ if (!result) {
+ return nullptr;
+ }
+ } else {
+ // CreateAlgorithmFromUnderlyingMethod step 6.c.i-ii.
+ {
+ AutoRealm ar(cx, unwrappedController);
+ cx->check(unwrappedAbortMethod);
+
+ Rooted<Value> underlyingSink(cx, unwrappedController->underlyingSink());
+ cx->check(underlyingSink);
+
+ Rooted<Value> wrappedReason(cx, reason);
+ if (!cx->compartment()->wrap(cx, &wrappedReason)) {
+ return nullptr;
+ }
+
+ result =
+ PromiseCall(cx, unwrappedAbortMethod, underlyingSink, wrappedReason);
+ if (!result) {
+ return nullptr;
+ }
+ }
+ if (!cx->compartment()->wrap(cx, &result)) {
+ return nullptr;
+ }
+ }
+
+ // Step 2: Perform ! WritableStreamDefaultControllerClearAlgorithms(this).
+ WritableStreamDefaultControllerClearAlgorithms(unwrappedController);
+
+ // Step 3: Return result.
+ return result;
+}
+
+/**
+ * Streams spec, 4.7.5.2.
+ * [[ErrorSteps]]()
+ */
+bool js::WritableStreamControllerErrorSteps(
+ JSContext* cx,
+ Handle<WritableStreamDefaultController*> unwrappedController) {
+ // Step 1: Perform ! ResetQueue(this).
+ return ResetQueue(cx, unwrappedController);
+}
+
+/*** 4.8. Writable stream default controller abstract operations ************/
+
+static MOZ_MUST_USE bool WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ JSContext* cx,
+ Handle<WritableStreamDefaultController*> unwrappedController);
+
+/**
+ * Streams spec, 4.8.2. SetUpWritableStreamDefaultController, step 16:
+ * Upon fulfillment of startPromise, [...]
+ */
+bool js::WritableStreamControllerStartHandler(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, TargetFromHandler<WritableStreamDefaultController>(args));
+
+ // Step a: Assert: stream.[[state]] is "writable" or "erroring".
+#ifdef DEBUG
+ const auto* unwrappedStream = unwrappedController->stream();
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+#endif
+
+ // Step b: Set controller.[[started]] to true.
+ unwrappedController->setStarted();
+
+ // Step c: Perform
+ // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
+ if (!WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ cx, unwrappedController)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 4.8.2. SetUpWritableStreamDefaultController, step 17:
+ * Upon rejection of startPromise with reason r, [...]
+ */
+bool js::WritableStreamControllerStartFailedHandler(JSContext* cx,
+ unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, TargetFromHandler<WritableStreamDefaultController>(args));
+
+ Rooted<WritableStream*> unwrappedStream(cx, unwrappedController->stream());
+
+ // Step a: Assert: stream.[[state]] is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step b: Set controller.[[started]] to true.
+ unwrappedController->setStarted();
+
+ // Step c: Perform ! WritableStreamDealWithRejection(stream, r).
+ if (!WritableStreamDealWithRejection(cx, unwrappedStream, args.get(0))) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 4.8.2.
+ * SetUpWritableStreamDefaultController(stream, controller,
+ * startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm,
+ * highWaterMark, sizeAlgorithm )
+ *
+ * The standard algorithm takes a `controller` argument which must be a new,
+ * blank object. This implementation creates a new controller instead.
+ *
+ * In the spec, four algorithms (startAlgorithm, writeAlgorithm, closeAlgorithm,
+ * abortAlgorithm) are passed as arguments to this routine. This implementation
+ * passes these "algorithms" as data, using five arguments: sinkAlgorithms,
+ * underlyingSink, writeMethod, closeMethod, and abortMethod. The sinkAlgorithms
+ * argument tells how to interpret the other three:
+ *
+ * - SinkAlgorithms::Script - We're creating a stream from a JS source. The
+ * caller is `new WritableStream(underlyingSink)` or
+ * `JS::NewWritableDefaultStreamObject`. `underlyingSink` is the sink;
+ * `writeMethod`, `closeMethod`, and `abortMethod` are its .write, .close,
+ * and .abort methods, which the caller has already extracted and
+ * type-checked: each one must be either a callable JS object or undefined.
+ *
+ * Script streams use the start/write/close/abort algorithms defined in
+ * 4.8.3. SetUpWritableStreamDefaultControllerFromUnderlyingSink, which
+ * call JS methods of the underlyingSink.
+ *
+ * - SinkAlgorithms::Transform - We're creating a transform stream.
+ * `underlyingSink` is a Transform object. `writeMethod`, `closeMethod, and
+ * `abortMethod` are undefined.
+ *
+ * Transform streams use the write/close/abort algorithms given in
+ * 5.3.2 InitializeTransformStream.
+ *
+ * An additional sizeAlgorithm in the spec is an algorithm used to compute the
+ * size of a chunk. Per MakeSizeAlgorithmFromSizeFunction, we just save the
+ * |size| value used to create that algorithm, then -- inline -- perform the
+ * requisite algorithm steps. (Hence the unadorned name |size|.)
+ *
+ * Note: All arguments must be same-compartment with cx. WritableStream
+ * controllers are always created in the same compartment as the stream.
+ */
+MOZ_MUST_USE bool js::SetUpWritableStreamDefaultController(
+ JSContext* cx, Handle<WritableStream*> stream,
+ SinkAlgorithms sinkAlgorithms, Handle<Value> underlyingSink,
+ Handle<Value> writeMethod, Handle<Value> closeMethod,
+ Handle<Value> abortMethod, double highWaterMark, Handle<Value> size) {
+ cx->check(stream);
+ cx->check(underlyingSink);
+ cx->check(writeMethod);
+ MOZ_ASSERT(writeMethod.isUndefined() || IsCallable(writeMethod));
+ cx->check(closeMethod);
+ MOZ_ASSERT(closeMethod.isUndefined() || IsCallable(closeMethod));
+ cx->check(abortMethod);
+ MOZ_ASSERT(abortMethod.isUndefined() || IsCallable(abortMethod));
+ MOZ_ASSERT(highWaterMark >= 0);
+ cx->check(size);
+ MOZ_ASSERT(size.isUndefined() || IsCallable(size));
+
+ // Done elsewhere in the standard: Create the new controller.
+ Rooted<WritableStreamDefaultController*> controller(
+ cx, NewBuiltinClassInstance<WritableStreamDefaultController>(cx));
+ if (!controller) {
+ return false;
+ }
+
+ // Step 1: Assert: ! IsWritableStream(stream) is true.
+ // (guaranteed by |stream|'s type)
+
+ // Step 2: Assert: stream.[[writableStreamController]] is undefined.
+ MOZ_ASSERT(!stream->hasController());
+
+ // Step 3: Set controller.[[controlledWritableStream]] to stream.
+ controller->setStream(stream);
+
+ // Step 4: Set stream.[[writableStreamController]] to controller.
+ stream->setController(controller);
+
+ // Step 5: Perform ! ResetQueue(controller).
+ if (!ResetQueue(cx, controller)) {
+ return false;
+ }
+
+ // Step 6: Set controller.[[started]] to false.
+ controller->setFlags(0);
+ MOZ_ASSERT(!controller->started());
+
+ // Step 7: Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm.
+ controller->setStrategySize(size);
+
+ // Step 8: Set controller.[[strategyHWM]] to highWaterMark.
+ controller->setStrategyHWM(highWaterMark);
+
+ // Step 9: Set controller.[[writeAlgorithm]] to writeAlgorithm.
+ // Step 10: Set controller.[[closeAlgorithm]] to closeAlgorithm.
+ // Step 11: Set controller.[[abortAlgorithm]] to abortAlgorithm.
+ // (In this implementation, all [[*Algorithm]] are determined by the
+ // underlyingSink in combination with the corresponding *Method field.)
+ controller->setUnderlyingSink(underlyingSink);
+ controller->setWriteMethod(writeMethod);
+ controller->setCloseMethod(closeMethod);
+ controller->setAbortMethod(abortMethod);
+
+ // Step 12: Let backpressure be
+ // ! WritableStreamDefaultControllerGetBackpressure(controller).
+ bool backpressure =
+ WritableStreamDefaultControllerGetBackpressure(controller);
+
+ // Step 13: Perform ! WritableStreamUpdateBackpressure(stream, backpressure).
+ if (!WritableStreamUpdateBackpressure(cx, stream, backpressure)) {
+ return false;
+ }
+
+ // Step 14: Let startResult be the result of performing startAlgorithm. (This
+ // may throw an exception.)
+ Rooted<Value> startResult(cx);
+ if (sinkAlgorithms == SinkAlgorithms::Script) {
+ Rooted<Value> controllerVal(cx, ObjectValue(*controller));
+ if (!InvokeOrNoop(cx, underlyingSink, cx->names().start, controllerVal,
+ &startResult)) {
+ return false;
+ }
+ }
+
+ // Step 15: Let startPromise be a promise resolved with startResult.
+ Rooted<JSObject*> startPromise(
+ cx, PromiseObject::unforgeableResolve(cx, startResult));
+ if (!startPromise) {
+ return false;
+ }
+
+ // Step 16: Upon fulfillment of startPromise,
+ // Assert: stream.[[state]] is "writable" or "erroring".
+ // Set controller.[[started]] to true.
+ // Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
+ // Step 17: Upon rejection of startPromise with reason r,
+ // Assert: stream.[[state]] is "writable" or "erroring".
+ // Set controller.[[started]] to true.
+ // Perform ! WritableStreamDealWithRejection(stream, r).
+ Rooted<JSObject*> onStartFulfilled(
+ cx, NewHandler(cx, WritableStreamControllerStartHandler, controller));
+ if (!onStartFulfilled) {
+ return false;
+ }
+ Rooted<JSObject*> onStartRejected(
+ cx,
+ NewHandler(cx, WritableStreamControllerStartFailedHandler, controller));
+ if (!onStartRejected) {
+ return false;
+ }
+
+ return JS::AddPromiseReactions(cx, startPromise, onStartFulfilled,
+ onStartRejected);
+}
+
+/**
+ * Streams spec, 4.8.3.
+ * SetUpWritableStreamDefaultControllerFromUnderlyingSink( stream,
+ * underlyingSink, highWaterMark, sizeAlgorithm )
+ */
+MOZ_MUST_USE bool js::SetUpWritableStreamDefaultControllerFromUnderlyingSink(
+ JSContext* cx, Handle<WritableStream*> stream, Handle<Value> underlyingSink,
+ double highWaterMark, Handle<Value> sizeAlgorithm) {
+ cx->check(stream);
+ cx->check(underlyingSink);
+ cx->check(sizeAlgorithm);
+
+ // Step 1: Assert: underlyingSink is not undefined.
+ MOZ_ASSERT(!underlyingSink.isUndefined());
+
+ // Step 2: Let controller be ObjectCreate(the original value of
+ // WritableStreamDefaultController's prototype property).
+ // (Deferred to SetUpWritableStreamDefaultController.)
+
+ // Step 3: Let startAlgorithm be the following steps:
+ // a. Return ? InvokeOrNoop(underlyingSink, "start",
+ // « controller »).
+ SinkAlgorithms sinkAlgorithms = SinkAlgorithms::Script;
+
+ // Step 4: Let writeAlgorithm be
+ // ? CreateAlgorithmFromUnderlyingMethod(underlyingSink, "write", 1,
+ // « controller »).
+ Rooted<Value> writeMethod(cx);
+ if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSink,
+ "WritableStream sink.write method",
+ cx->names().write, &writeMethod)) {
+ return false;
+ }
+
+ // Step 5: Let closeAlgorithm be
+ // ? CreateAlgorithmFromUnderlyingMethod(underlyingSink, "close", 0,
+ // « »).
+ Rooted<Value> closeMethod(cx);
+ if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSink,
+ "WritableStream sink.close method",
+ cx->names().close, &closeMethod)) {
+ return false;
+ }
+
+ // Step 6: Let abortAlgorithm be
+ // ? CreateAlgorithmFromUnderlyingMethod(underlyingSink, "abort", 1,
+ // « »).
+ Rooted<Value> abortMethod(cx);
+ if (!CreateAlgorithmFromUnderlyingMethod(cx, underlyingSink,
+ "WritableStream sink.abort method",
+ cx->names().abort, &abortMethod)) {
+ return false;
+ }
+
+ // Step 6. Perform ? SetUpWritableStreamDefaultController(stream,
+ // controller, startAlgorithm, writeAlgorithm, closeAlgorithm,
+ // abortAlgorithm, highWaterMark, sizeAlgorithm).
+ return SetUpWritableStreamDefaultController(
+ cx, stream, sinkAlgorithms, underlyingSink, writeMethod, closeMethod,
+ abortMethod, highWaterMark, sizeAlgorithm);
+}
+
+/**
+ * Streams spec, 4.8.4.
+ * WritableStreamDefaultControllerClearAlgorithms ( controller )
+ */
+void js::WritableStreamDefaultControllerClearAlgorithms(
+ WritableStreamDefaultController* unwrappedController) {
+ // Note: This operation will be performed multiple times in some edge cases,
+ // so it can't assert that the various algorithms initially haven't been
+ // cleared.
+
+ // Step 1: Set controller.[[writeAlgorithm]] to undefined.
+ unwrappedController->clearWriteMethod();
+
+ // Step 2: Set controller.[[closeAlgorithm]] to undefined.
+ unwrappedController->clearCloseMethod();
+
+ // Step 3: Set controller.[[abortAlgorithm]] to undefined.
+ unwrappedController->clearAbortMethod();
+
+ // Step 4: Set controller.[[strategySizeAlgorithm]] to undefined.
+ unwrappedController->clearStrategySize();
+}
+
+/**
+ * Streams spec, 4.8.5.
+ * WritableStreamDefaultControllerClose ( controller )
+ */
+bool js::WritableStreamDefaultControllerClose(
+ JSContext* cx,
+ Handle<WritableStreamDefaultController*> unwrappedController) {
+ // Step 1: Perform ! EnqueueValueWithSize(controller, "close", 0).
+ {
+ Rooted<Value> v(cx, MagicValue(JS_WRITABLESTREAM_CLOSE_RECORD));
+ Rooted<Value> size(cx, Int32Value(0));
+ if (!EnqueueValueWithSize(cx, unwrappedController, v, size)) {
+ return false;
+ }
+ }
+
+ // Step 2: Perform
+ // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
+ return WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ cx, unwrappedController);
+}
+
+/**
+ * Streams spec, 4.8.6.
+ * WritableStreamDefaultControllerGetChunkSize ( controller, chunk )
+ */
+bool js::WritableStreamDefaultControllerGetChunkSize(
+ JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController,
+ Handle<Value> chunk, MutableHandle<Value> returnValue) {
+ cx->check(chunk);
+
+ // Step 1: Let returnValue be the result of performing
+ // controller.[[strategySizeAlgorithm]], passing in chunk, and
+ // interpreting the result as an ECMAScript completion value.
+
+ // We don't store a literal [[strategySizeAlgorithm]], only the value that if
+ // passed through |MakeSizeAlgorithmFromSizeFunction| wouldn't have triggered
+ // an error. Perform the algorithm that function would return.
+ Rooted<Value> unwrappedStrategySize(cx, unwrappedController->strategySize());
+ if (unwrappedStrategySize.isUndefined()) {
+ // 6.3.8 step 1: If size is undefined, return an algorithm that returns 1.
+ // ...and then from this function...
+ // Step 3: Return returnValue.[[Value]].
+ returnValue.setInt32(1);
+ return true;
+ }
+
+ MOZ_ASSERT(IsCallable(unwrappedStrategySize));
+
+ {
+ bool success;
+ {
+ AutoRealm ar(cx, unwrappedController);
+ cx->check(unwrappedStrategySize);
+
+ Rooted<Value> wrappedChunk(cx, chunk);
+ if (!cx->compartment()->wrap(cx, &wrappedChunk)) {
+ return false;
+ }
+
+ // 6.3.8 step 3 (of |MakeSizeAlgorithmFromSizeFunction|):
+ // Return an algorithm that performs the following steps, taking a
+ // chunk argument:
+ // a. Return ? Call(size, undefined, « chunk »).
+ success = Call(cx, unwrappedStrategySize, UndefinedHandleValue,
+ wrappedChunk, returnValue);
+ }
+
+ // Step 3: (If returnValue is [not] an abrupt completion, )
+ // Return returnValue.[[Value]]. (reordered for readability)
+ if (success) {
+ return cx->compartment()->wrap(cx, returnValue);
+ }
+ }
+
+ // Step 2: If returnValue is an abrupt completion,
+ if (!cx->isExceptionPending() || !cx->getPendingException(returnValue)) {
+ // Uncatchable error. Die immediately without erroring the stream.
+ return false;
+ }
+ cx->check(returnValue);
+
+ cx->clearPendingException();
+
+ // Step 2.a: Perform
+ // ! WritableStreamDefaultControllerErrorIfNeeded(
+ // controller, returnValue.[[Value]]).
+ if (!WritableStreamDefaultControllerErrorIfNeeded(cx, unwrappedController,
+ returnValue)) {
+ return false;
+ }
+
+ // Step 2.b: Return 1.
+ returnValue.setInt32(1);
+ return true;
+}
+
+/**
+ * Streams spec, 4.8.7.
+ * WritableStreamDefaultControllerGetDesiredSize ( controller )
+ */
+double js::WritableStreamDefaultControllerGetDesiredSize(
+ const WritableStreamDefaultController* controller) {
+ return controller->strategyHWM() - controller->queueTotalSize();
+}
+
+/**
+ * Streams spec, 4.8.8.
+ * WritableStreamDefaultControllerWrite ( controller, chunk, chunkSize )
+ */
+bool js::WritableStreamDefaultControllerWrite(
+ JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController,
+ Handle<Value> chunk, Handle<Value> chunkSize) {
+ MOZ_ASSERT(!chunk.isMagic());
+ cx->check(chunk);
+ cx->check(chunkSize);
+
+ // Step 1: Let writeRecord be Record {[[chunk]]: chunk}.
+ // Step 2: Let enqueueResult be
+ // EnqueueValueWithSize(controller, writeRecord, chunkSize).
+ bool succeeded =
+ EnqueueValueWithSize(cx, unwrappedController, chunk, chunkSize);
+
+ // Step 3: If enqueueResult is an abrupt completion,
+ if (!succeeded) {
+ Rooted<Value> enqueueResult(cx);
+ if (!cx->isExceptionPending() || !cx->getPendingException(&enqueueResult)) {
+ // Uncatchable error. Die immediately without erroring the stream.
+ return false;
+ }
+ cx->check(enqueueResult);
+
+ cx->clearPendingException();
+
+ // Step 3.a: Perform ! WritableStreamDefaultControllerErrorIfNeeded(
+ // controller, enqueueResult.[[Value]]).
+ // Step 3.b: Return.
+ return WritableStreamDefaultControllerErrorIfNeeded(cx, unwrappedController,
+ enqueueResult);
+ }
+
+ // Step 4: Let stream be controller.[[controlledWritableStream]].
+ Rooted<WritableStream*> unwrappedStream(cx, unwrappedController->stream());
+
+ // Step 5: If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
+ // stream.[[state]] is "writable",
+ if (!WritableStreamCloseQueuedOrInFlight(unwrappedStream) &&
+ unwrappedStream->writable()) {
+ // Step 5.a: Let backpressure be
+ // ! WritableStreamDefaultControllerGetBackpressure(controller).
+ bool backpressure =
+ WritableStreamDefaultControllerGetBackpressure(unwrappedController);
+
+ // Step 5.b: Perform
+ // ! WritableStreamUpdateBackpressure(stream, backpressure).
+ if (!WritableStreamUpdateBackpressure(cx, unwrappedStream, backpressure)) {
+ return false;
+ }
+ }
+
+ // Step 6: Perform
+ // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
+ return WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ cx, unwrappedController);
+}
+
+static MOZ_MUST_USE bool WritableStreamDefaultControllerProcessIfNeeded(
+ JSContext* cx,
+ Handle<WritableStreamDefaultController*> unwrappedController);
+
+/**
+ * Streams spec, 4.8.9.
+ * WritableStreamDefaultControllerAdvanceQueueIfNeeded ( controller )
+ */
+MOZ_MUST_USE bool WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ JSContext* cx,
+ Handle<WritableStreamDefaultController*> unwrappedController) {
+ // Step 2: If controller.[[started]] is false, return.
+ if (!unwrappedController->started()) {
+ return true;
+ }
+
+ // Step 1: Let stream be controller.[[controlledWritableStream]].
+ Rooted<WritableStream*> unwrappedStream(cx, unwrappedController->stream());
+
+ // Step 3: If stream.[[inFlightWriteRequest]] is not undefined, return.
+ if (!unwrappedStream->inFlightWriteRequest().isUndefined()) {
+ return true;
+ }
+
+ // Step 4: Let state be stream.[[state]].
+ // Step 5: Assert: state is not "closed" or "errored".
+ // Step 6: If state is "erroring",
+ MOZ_ASSERT(!unwrappedStream->closed());
+ MOZ_ASSERT(!unwrappedStream->errored());
+ if (unwrappedStream->erroring()) {
+ // Step 6a: Perform ! WritableStreamFinishErroring(stream).
+ // Step 6b: Return.
+ return WritableStreamFinishErroring(cx, unwrappedStream);
+ }
+
+ // Step 7: If controller.[[queue]] is empty, return.
+ // Step 8: Let writeRecord be ! PeekQueueValue(controller).
+ // Step 9: If writeRecord is "close", perform
+ // ! WritableStreamDefaultControllerProcessClose(controller).
+ // Step 10: Otherwise, perform
+ // ! WritableStreamDefaultControllerProcessWrite(
+ // controller, writeRecord.[[chunk]]).
+ return WritableStreamDefaultControllerProcessIfNeeded(cx,
+ unwrappedController);
+}
+
+/**
+ * Streams spec, 4.8.10.
+ * WritableStreamDefaultControllerErrorIfNeeded ( controller, error )
+ */
+bool js::WritableStreamDefaultControllerErrorIfNeeded(
+ JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController,
+ Handle<Value> error) {
+ cx->check(error);
+
+ // Step 1: If controller.[[controlledWritableStream]].[[state]] is "writable",
+ // perform ! WritableStreamDefaultControllerError(controller, error).
+ if (unwrappedController->stream()->writable()) {
+ if (!WritableStreamDefaultControllerError(cx, unwrappedController, error)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// 4.8.11 step 5: Let sinkClosePromise be the result of performing
+// controller.[[closeAlgorithm]].
+static MOZ_MUST_USE JSObject* PerformCloseAlgorithm(
+ JSContext* cx,
+ Handle<WritableStreamDefaultController*> unwrappedController) {
+ // 4.8.3 step 5: Let closeAlgorithm be
+ // ? CreateAlgorithmFromUnderlyingMethod(underlyingSink,
+ // "close", 0, « »).
+
+ // Step 1: Assert: underlyingObject is not undefined.
+ // Step 2: Assert: ! IsPropertyKey(methodName) is true (implicit).
+ // Step 3: Assert: algoArgCount is 0 or 1 (omitted).
+ // Step 4: Assert: extraArgs is a List (omitted).
+ // Step 5: Let method be ? GetV(underlyingObject, methodName).
+ //
+ // These steps were performed in |CreateAlgorithmFromUnderlyingMethod|. The
+ // spec stores away algorithms for later invocation; we instead store the
+ // value that determines the algorithm to be created -- either |undefined|, or
+ // a callable object that's called with context-specific arguments.
+
+ // Step 7: (If method is undefined,) Return an algorithm which returns a
+ // promise resolved with undefined (implicit).
+ if (unwrappedController->closeMethod().isUndefined()) {
+ return PromiseResolvedWithUndefined(cx);
+ }
+
+ // Step 6: If method is not undefined,
+
+ // Step 6.a: If ! IsCallable(method) is false, throw a TypeError exception.
+ MOZ_ASSERT(IsCallable(unwrappedController->closeMethod()));
+
+ // Step 6.b: If algoArgCount is 0, return an algorithm that performs the
+ // following steps:
+ // Step 6.b.ii: Return ! PromiseCall(method, underlyingObject, extraArgs).
+ Rooted<Value> closeMethod(cx, unwrappedController->closeMethod());
+ if (!cx->compartment()->wrap(cx, &closeMethod)) {
+ return nullptr;
+ }
+
+ Rooted<Value> underlyingSink(cx, unwrappedController->underlyingSink());
+ if (!cx->compartment()->wrap(cx, &underlyingSink)) {
+ return nullptr;
+ }
+
+ return PromiseCall(cx, closeMethod, underlyingSink);
+}
+
+// 4.8.12 step 3: Let sinkWritePromise be the result of performing
+// controller.[[writeAlgorithm]], passing in chunk.
+static MOZ_MUST_USE JSObject* PerformWriteAlgorithm(
+ JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController,
+ Handle<Value> chunk) {
+ cx->check(chunk);
+
+ // 4.8.3 step 4: Let writeAlgorithm be
+ // ? CreateAlgorithmFromUnderlyingMethod(underlyingSink,
+ // "write", 1,
+ // « controller »).
+
+ // Step 1: Assert: underlyingObject is not undefined.
+ // Step 2: Assert: ! IsPropertyKey(methodName) is true (implicit).
+ // Step 3: Assert: algoArgCount is 0 or 1 (omitted).
+ // Step 4: Assert: extraArgs is a List (omitted).
+ // Step 5: Let method be ? GetV(underlyingObject, methodName).
+ //
+ // These steps were performed in |CreateAlgorithmFromUnderlyingMethod|. The
+ // spec stores away algorithms for later invocation; we instead store the
+ // value that determines the algorithm to be created -- either |undefined|, or
+ // a callable object that's called with context-specific arguments.
+
+ // Step 7: (If method is undefined,) Return an algorithm which returns a
+ // promise resolved with undefined (implicit).
+ if (unwrappedController->writeMethod().isUndefined()) {
+ return PromiseResolvedWithUndefined(cx);
+ }
+
+ // Step 6: If method is not undefined,
+
+ // Step 6.a: If ! IsCallable(method) is false, throw a TypeError exception.
+ MOZ_ASSERT(IsCallable(unwrappedController->writeMethod()));
+
+ // Step 6.c: Otherwise (if algoArgCount is not 0), return an algorithm that
+ // performs the following steps, taking an arg argument:
+ // Step 6.c.i: Let fullArgs be a List consisting of arg followed by the
+ // elements of extraArgs in order.
+ // Step 6.c.ii: Return ! PromiseCall(method, underlyingObject, fullArgs).
+ Rooted<Value> writeMethod(cx, unwrappedController->writeMethod());
+ if (!cx->compartment()->wrap(cx, &writeMethod)) {
+ return nullptr;
+ }
+
+ Rooted<Value> underlyingSink(cx, unwrappedController->underlyingSink());
+ if (!cx->compartment()->wrap(cx, &underlyingSink)) {
+ return nullptr;
+ }
+
+ Rooted<Value> controller(cx, ObjectValue(*unwrappedController));
+ if (!cx->compartment()->wrap(cx, &controller)) {
+ return nullptr;
+ }
+
+ return PromiseCall(cx, writeMethod, underlyingSink, chunk, controller);
+}
+
+/**
+ * Streams spec, 4.8.11 step 7:
+ * Upon fulfillment of sinkClosePromise,
+ */
+static MOZ_MUST_USE bool WritableStreamCloseHandler(JSContext* cx,
+ unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<WritableStream*> unwrappedStream(
+ cx, TargetFromHandler<WritableStream>(args));
+
+ // Step 7.a: Perform ! WritableStreamFinishInFlightClose(stream).
+ if (!WritableStreamFinishInFlightClose(cx, unwrappedStream)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 4.8.11 step 8:
+ * Upon rejection of sinkClosePromise with reason reason,
+ */
+static MOZ_MUST_USE bool WritableStreamCloseFailedHandler(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<WritableStream*> unwrappedStream(
+ cx, TargetFromHandler<WritableStream>(args));
+
+ // Step 8.a: Perform
+ // ! WritableStreamFinishInFlightCloseWithError(stream, reason).
+ if (!WritableStreamFinishInFlightCloseWithError(cx, unwrappedStream,
+ args.get(0))) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 4.8.12 step 4:
+ * Upon fulfillment of sinkWritePromise,
+ */
+static MOZ_MUST_USE bool WritableStreamWriteHandler(JSContext* cx,
+ unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<WritableStream*> unwrappedStream(
+ cx, TargetFromHandler<WritableStream>(args));
+
+ // Step 4.a: Perform ! WritableStreamFinishInFlightWrite(stream).
+ if (!WritableStreamFinishInFlightWrite(cx, unwrappedStream)) {
+ return false;
+ }
+
+ // Step 4.b: Let state be stream.[[state]].
+ // Step 4.c: Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step 4.d: Perform ! DequeueValue(controller).
+ DequeueValue(unwrappedStream->controller(), cx);
+
+ // Step 4.e: If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
+ // state is "writable",
+ if (!WritableStreamCloseQueuedOrInFlight(unwrappedStream) &&
+ unwrappedStream->writable()) {
+ // Step 4.e.i: Let backpressure be
+ // ! WritableStreamDefaultControllerGetBackpressure(
+ // controller).
+ bool backpressure = WritableStreamDefaultControllerGetBackpressure(
+ unwrappedStream->controller());
+
+ // Step 4.e.ii: Perform
+ // ! WritableStreamUpdateBackpressure(stream, backpressure).
+ if (!WritableStreamUpdateBackpressure(cx, unwrappedStream, backpressure)) {
+ return false;
+ }
+ }
+
+ // Step 4.f: Perform
+ // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ // controller).
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ if (!WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ cx, unwrappedController)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 4.8.12 step 5:
+ * Upon rejection of sinkWritePromise with reason,
+ */
+static MOZ_MUST_USE bool WritableStreamWriteFailedHandler(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<WritableStream*> unwrappedStream(
+ cx, TargetFromHandler<WritableStream>(args));
+
+ // Step 5.a: If stream.[[state]] is "writable", perform
+ // ! WritableStreamDefaultControllerClearAlgorithms(controller).
+ if (unwrappedStream->writable()) {
+ WritableStreamDefaultControllerClearAlgorithms(
+ unwrappedStream->controller());
+ }
+
+ // Step 5.b: Perform
+ // ! WritableStreamFinishInFlightWriteWithError(stream, reason).
+ if (!WritableStreamFinishInFlightWriteWithError(cx, unwrappedStream,
+ args.get(0))) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 4.8.9 (steps 7-10),
+ * WritableStreamDefaultControllerAdvanceQueueIfNeeded ( controller )
+ * Streams spec, 4.8.11.
+ * WritableStreamDefaultControllerProcessClose ( controller )
+ * Streams spec, 4.8.12.
+ * WritableStreamDefaultControllerProcessWrite ( controller, chunk )
+ */
+bool WritableStreamDefaultControllerProcessIfNeeded(
+ JSContext* cx,
+ Handle<WritableStreamDefaultController*> unwrappedController) {
+ // Step 7: If controller.[[queue]] is empty, return.
+ ListObject* unwrappedQueue = unwrappedController->queue();
+ if (QueueIsEmpty(unwrappedQueue)) {
+ return true;
+ }
+
+ // Step 8: Let writeRecord be ! PeekQueueValue(controller).
+ // Step 9: If writeRecord is "close", perform
+ // ! WritableStreamDefaultControllerProcessClose(controller).
+ // Step 10: Otherwise, perform
+ // ! WritableStreamDefaultControllerProcessWrite(
+ // controller, writeRecord.[[chunk]]).
+ Rooted<JSObject*> sinkWriteOrClosePromise(cx);
+ JSNative onFulfilledFunc, onRejectedFunc;
+ if (PeekQueueValue(unwrappedQueue).isMagic(JS_WRITABLESTREAM_CLOSE_RECORD)) {
+ MOZ_ASSERT(unwrappedQueue->length() == 2);
+
+ onFulfilledFunc = WritableStreamCloseHandler;
+ onRejectedFunc = WritableStreamCloseFailedHandler;
+
+ // 4.8.11 step 1: Let stream be controller.[[controlledWritableStream]].
+ // 4.8.11 step 2: Perform ! WritableStreamMarkCloseRequestInFlight(stream).
+ WritableStreamMarkCloseRequestInFlight(unwrappedController->stream());
+
+ // 4.8.11 step 3: Perform ! DequeueValue(controller).
+ DequeueValue(unwrappedController, cx);
+
+ // 4.8.11 step 4: Assert: controller.[[queue]] is empty.
+ MOZ_ASSERT(unwrappedQueue->isEmpty());
+
+ // 4.8.11 step 5: Let sinkClosePromise be the result of performing
+ // controller.[[closeAlgorithm]].
+ sinkWriteOrClosePromise = PerformCloseAlgorithm(cx, unwrappedController);
+ } else {
+ onFulfilledFunc = WritableStreamWriteHandler;
+ onRejectedFunc = WritableStreamWriteFailedHandler;
+
+ Rooted<Value> chunk(cx, PeekQueueValue(unwrappedQueue));
+ if (!cx->compartment()->wrap(cx, &chunk)) {
+ return false;
+ }
+
+ // 4.8.12 step 1: Let stream be controller.[[controlledWritableStream]].
+ // 4.8.12 step 2: Perform
+ // ! WritableStreamMarkFirstWriteRequestInFlight(stream).
+ WritableStreamMarkFirstWriteRequestInFlight(unwrappedController->stream());
+
+ // 4.8.12 step 3: Let sinkWritePromise be the result of performing
+ // controller.[[writeAlgorithm]], passing in chunk.
+ sinkWriteOrClosePromise =
+ PerformWriteAlgorithm(cx, unwrappedController, chunk);
+ }
+ if (!sinkWriteOrClosePromise) {
+ return false;
+ }
+
+ Rooted<JSObject*> stream(cx, unwrappedController->stream());
+ if (!cx->compartment()->wrap(cx, &stream)) {
+ return false;
+ }
+
+ // Step 7: Upon fulfillment of sinkClosePromise,
+ // Step 4: Upon fulfillment of sinkWritePromise,
+ // Step 8: Upon rejection of sinkClosePromise with reason reason,
+ // Step 5: Upon rejection of sinkWritePromise with reason,
+ Rooted<JSObject*> onFulfilled(cx, NewHandler(cx, onFulfilledFunc, stream));
+ if (!onFulfilled) {
+ return false;
+ }
+ Rooted<JSObject*> onRejected(cx, NewHandler(cx, onRejectedFunc, stream));
+ if (!onRejected) {
+ return false;
+ }
+ return JS::AddPromiseReactions(cx, sinkWriteOrClosePromise, onFulfilled,
+ onRejected);
+}
+
+/**
+ * Streams spec, 4.8.13.
+ * WritableStreamDefaultControllerGetBackpressure ( controller )
+ */
+bool js::WritableStreamDefaultControllerGetBackpressure(
+ const WritableStreamDefaultController* unwrappedController) {
+ return WritableStreamDefaultControllerGetDesiredSize(unwrappedController) <=
+ 0.0;
+}
+
+/**
+ * Streams spec, 4.8.14.
+ * WritableStreamDefaultControllerError ( controller, error )
+ */
+bool js::WritableStreamDefaultControllerError(
+ JSContext* cx, Handle<WritableStreamDefaultController*> unwrappedController,
+ Handle<Value> error) {
+ cx->check(error);
+
+ // Step 1: Let stream be controller.[[controlledWritableStream]].
+ Rooted<WritableStream*> unwrappedStream(cx, unwrappedController->stream());
+
+ // Step 2: Assert: stream.[[state]] is "writable".
+ MOZ_ASSERT(unwrappedStream->writable());
+
+ // Step 3: Perform
+ // ! WritableStreamDefaultControllerClearAlgorithms(controller).
+ WritableStreamDefaultControllerClearAlgorithms(unwrappedController);
+
+ // Step 4: Perform ! WritableStreamStartErroring(stream, error).
+ return WritableStreamStartErroring(cx, unwrappedStream, error);
+}
diff --git a/js/src/builtin/streams/WritableStreamDefaultControllerOperations.h b/js/src/builtin/streams/WritableStreamDefaultControllerOperations.h
new file mode 100644
index 0000000000..90756b25bc
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamDefaultControllerOperations.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Writable stream default controller abstract operations. */
+
+#ifndef builtin_streams_WritableStreamDefaultControllerOperations_h
+#define builtin_streams_WritableStreamDefaultControllerOperations_h
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jstypes.h" // JS_PUBLIC_API
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::Value
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+class WritableStream;
+class WritableStreamDefaultController;
+
+extern JSObject* WritableStreamControllerAbortSteps(
+ JSContext* cx,
+ JS::Handle<WritableStreamDefaultController*> unwrappedController,
+ JS::Handle<JS::Value> reason);
+
+extern MOZ_MUST_USE bool WritableStreamControllerErrorSteps(
+ JSContext* cx,
+ JS::Handle<WritableStreamDefaultController*> unwrappedController);
+
+extern MOZ_MUST_USE bool WritableStreamControllerStartHandler(JSContext* cx,
+ unsigned argc,
+ JS::Value* vp);
+
+extern MOZ_MUST_USE bool WritableStreamControllerStartFailedHandler(
+ JSContext* cx, unsigned argc, JS::Value* vp);
+
+/**
+ * Characterizes the family of algorithms, (startAlgorithm, writeAlgorithm,
+ * closeAlgorithm, abortAlgorithm), associated with a writable stream.
+ *
+ * See the comment on SetUpWritableStreamDefaultController().
+ */
+enum class SinkAlgorithms {
+ Script,
+ Transform,
+};
+
+extern MOZ_MUST_USE bool SetUpWritableStreamDefaultController(
+ JSContext* cx, JS::Handle<WritableStream*> stream,
+ SinkAlgorithms algorithms, JS::Handle<JS::Value> underlyingSink,
+ JS::Handle<JS::Value> writeMethod, JS::Handle<JS::Value> closeMethod,
+ JS::Handle<JS::Value> abortMethod, double highWaterMark,
+ JS::Handle<JS::Value> size);
+
+extern MOZ_MUST_USE bool SetUpWritableStreamDefaultControllerFromUnderlyingSink(
+ JSContext* cx, JS::Handle<WritableStream*> stream,
+ JS::Handle<JS::Value> underlyingSink, double highWaterMark,
+ JS::Handle<JS::Value> sizeAlgorithm);
+
+extern void WritableStreamDefaultControllerClearAlgorithms(
+ WritableStreamDefaultController* unwrappedController);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultControllerClose(
+ JSContext* cx,
+ JS::Handle<WritableStreamDefaultController*> unwrappedController);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultControllerGetChunkSize(
+ JSContext* cx,
+ JS::Handle<WritableStreamDefaultController*> unwrappedController,
+ JS::Handle<JS::Value> chunk, JS::MutableHandle<JS::Value> returnValue);
+
+extern double WritableStreamDefaultControllerGetDesiredSize(
+ const WritableStreamDefaultController* controller);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultControllerWrite(
+ JSContext* cx,
+ JS::Handle<WritableStreamDefaultController*> unwrappedController,
+ JS::Handle<JS::Value> chunk, JS::Handle<JS::Value> chunkSize);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultControllerErrorIfNeeded(
+ JSContext* cx,
+ JS::Handle<WritableStreamDefaultController*> unwrappedController,
+ JS::Handle<JS::Value> error);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultControllerProcessClose(
+ JSContext* cx,
+ JS::Handle<WritableStreamDefaultController*> unwrappedController);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultControllerProcessWrite(
+ JSContext* cx,
+ JS::Handle<WritableStreamDefaultController*> unwrappedController,
+ JS::Handle<JS::Value> chunk);
+
+extern bool WritableStreamDefaultControllerGetBackpressure(
+ const WritableStreamDefaultController* unwrappedController);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultControllerError(
+ JSContext* cx,
+ JS::Handle<WritableStreamDefaultController*> unwrappedController,
+ JS::Handle<JS::Value> error);
+
+} // namespace js
+
+#endif // builtin_streams_WritableStreamDefaultControllerOperations_h
diff --git a/js/src/builtin/streams/WritableStreamDefaultWriter-inl.h b/js/src/builtin/streams/WritableStreamDefaultWriter-inl.h
new file mode 100644
index 0000000000..fe85f35bf8
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamDefaultWriter-inl.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class WritableStreamDefaultWriter. */
+
+#ifndef builtin_streams_WritableStreamDefaultWriter_inl_h
+#define builtin_streams_WritableStreamDefaultWriter_inl_h
+
+#include "builtin/streams/WritableStreamDefaultWriter.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "builtin/streams/WritableStream.h" // js::WritableStream
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::ObjectValue
+#include "vm/NativeObject.h" // js::NativeObject
+
+#include "vm/Compartment-inl.h" // js::UnwrapInternalSlot
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+/**
+ * Returns the stream associated with the given reader.
+ */
+inline MOZ_MUST_USE WritableStream* UnwrapStreamFromWriter(
+ JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
+ MOZ_ASSERT(unwrappedWriter->hasStream());
+ return UnwrapInternalSlot<WritableStream>(
+ cx, unwrappedWriter, WritableStreamDefaultWriter::Slot_Stream);
+}
+
+} // namespace js
+
+#endif // builtin_streams_WritableStreamDefaultWriter_inl_h
diff --git a/js/src/builtin/streams/WritableStreamDefaultWriter.cpp b/js/src/builtin/streams/WritableStreamDefaultWriter.cpp
new file mode 100644
index 0000000000..c45948c64d
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamDefaultWriter.cpp
@@ -0,0 +1,529 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class WritableStreamDefaultWriter. */
+
+#include "builtin/streams/WritableStreamDefaultWriter-inl.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsapi.h" // JS_ReportErrorASCII, JS_ReportErrorNumberASCII
+
+#include "builtin/streams/ClassSpecMacro.h" // JS_STREAMS_CLASS_SPEC
+#include "builtin/streams/MiscellaneousOperations.h" // js::ReturnPromiseRejectedWithPendingError
+#include "builtin/streams/WritableStream.h" // js::WritableStream
+#include "builtin/streams/WritableStreamOperations.h" // js::WritableStreamCloseQueuedOrInFlight
+#include "builtin/streams/WritableStreamWriterOperations.h" // js::WritableStreamDefaultWriter{Abort,GetDesiredSize,Release,Write}
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/Class.h" // js::ClassSpec, JS_NULL_CLASS_OPS
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/PropertySpec.h" // JS{Function,Property}Spec, JS_{FS,PS}_END, JS_{FN,PSG}
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::Value
+#include "vm/Compartment.h" // JS::Compartment
+#include "vm/JSContext.h" // JSContext
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+
+#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapAndTypeCheck{Argument,This}
+#include "vm/JSObject-inl.h" // js::NewObjectWithClassProto
+#include "vm/NativeObject-inl.h" // js::ThrowIfNotConstructing
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::Rooted;
+using JS::Value;
+
+using js::ClassSpec;
+using js::GetErrorMessage;
+using js::PromiseObject;
+using js::ReturnPromiseRejectedWithPendingError;
+using js::UnwrapAndTypeCheckArgument;
+using js::UnwrapAndTypeCheckThis;
+using js::WritableStream;
+using js::WritableStreamCloseQueuedOrInFlight;
+using js::WritableStreamDefaultWriter;
+using js::WritableStreamDefaultWriterGetDesiredSize;
+using js::WritableStreamDefaultWriterRelease;
+using js::WritableStreamDefaultWriterWrite;
+
+/*** 4.5. Class WritableStreamDefaultWriter *********************************/
+
+/**
+ * Stream spec, 4.5.3. new WritableStreamDefaultWriter(stream)
+ * Steps 3-9.
+ */
+MOZ_MUST_USE WritableStreamDefaultWriter* js::CreateWritableStreamDefaultWriter(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream,
+ Handle<JSObject*> proto /* = nullptr */) {
+ Rooted<WritableStreamDefaultWriter*> writer(
+ cx, NewObjectWithClassProto<WritableStreamDefaultWriter>(cx, proto));
+ if (!writer) {
+ return nullptr;
+ }
+
+ // Step 3: Set this.[[ownerWritableStream]] to stream.
+ {
+ Rooted<JSObject*> stream(cx, unwrappedStream);
+ if (!cx->compartment()->wrap(cx, &stream)) {
+ return nullptr;
+ }
+ writer->setStream(stream);
+ }
+
+ // Step 4 is moved to the end.
+
+ // Step 5: Let state be stream.[[state]].
+ // Step 6: If state is "writable",
+ if (unwrappedStream->writable()) {
+ // Step 6.a: If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
+ // stream.[[backpressure]] is true, set this.[[readyPromise]] to a
+ // new promise.
+ PromiseObject* promise;
+ if (!WritableStreamCloseQueuedOrInFlight(unwrappedStream) &&
+ unwrappedStream->backpressure()) {
+ promise = PromiseObject::createSkippingExecutor(cx);
+ }
+ // Step 6.b: Otherwise, set this.[[readyPromise]] to a promise resolved with
+ // undefined.
+ else {
+ promise = PromiseResolvedWithUndefined(cx);
+ }
+ if (!promise) {
+ return nullptr;
+ }
+ writer->setReadyPromise(promise);
+
+ // Step 6.c: Set this.[[closedPromise]] to a new promise.
+ promise = PromiseObject::createSkippingExecutor(cx);
+ if (!promise) {
+ return nullptr;
+ }
+
+ writer->setClosedPromise(promise);
+ }
+ // Step 8: Otherwise, if state is "closed",
+ else if (unwrappedStream->closed()) {
+ // Step 8.a: Set this.[[readyPromise]] to a promise resolved with undefined.
+ PromiseObject* readyPromise = PromiseResolvedWithUndefined(cx);
+ if (!readyPromise) {
+ return nullptr;
+ }
+
+ writer->setReadyPromise(readyPromise);
+
+ // Step 8.b: Set this.[[closedPromise]] to a promise resolved with
+ // undefined.
+ PromiseObject* closedPromise = PromiseResolvedWithUndefined(cx);
+ if (!closedPromise) {
+ return nullptr;
+ }
+
+ writer->setClosedPromise(closedPromise);
+ } else {
+ // Wrap stream.[[StoredError]] just once for either step 7 or step 9.
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return nullptr;
+ }
+
+ // Step 7: Otherwise, if state is "erroring",
+ if (unwrappedStream->erroring()) {
+ // Step 7.a: Set this.[[readyPromise]] to a promise rejected with
+ // stream.[[storedError]].
+ Rooted<JSObject*> promise(
+ cx, PromiseObject::unforgeableReject(cx, storedError));
+ if (!promise) {
+ return nullptr;
+ }
+
+ writer->setReadyPromise(promise);
+
+ // Step 7.b: Set this.[[readyPromise]].[[PromiseIsHandled]] to true.
+ js::SetSettledPromiseIsHandled(cx, promise.as<PromiseObject>());
+
+ // Step 7.c: Set this.[[closedPromise]] to a new promise.
+ JSObject* closedPromise = PromiseObject::createSkippingExecutor(cx);
+ if (!closedPromise) {
+ return nullptr;
+ }
+
+ writer->setClosedPromise(closedPromise);
+ }
+ // Step 9: Otherwise,
+ else {
+ // Step 9.a: Assert: state is "errored".
+ MOZ_ASSERT(unwrappedStream->errored());
+
+ Rooted<JSObject*> promise(cx);
+
+ // Step 9.b: Let storedError be stream.[[storedError]].
+ // Step 9.c: Set this.[[readyPromise]] to a promise rejected with
+ // storedError.
+ promise = PromiseObject::unforgeableReject(cx, storedError);
+ if (!promise) {
+ return nullptr;
+ }
+
+ writer->setReadyPromise(promise);
+
+ // Step 9.d: Set this.[[readyPromise]].[[PromiseIsHandled]] to true.
+ js::SetSettledPromiseIsHandled(cx, promise.as<PromiseObject>());
+
+ // Step 9.e: Set this.[[closedPromise]] to a promise rejected with
+ // storedError.
+ promise = PromiseObject::unforgeableReject(cx, storedError);
+ if (!promise) {
+ return nullptr;
+ }
+
+ writer->setClosedPromise(promise);
+
+ // Step 9.f: Set this.[[closedPromise]].[[PromiseIsHandled]] to true.
+ js::SetSettledPromiseIsHandled(cx, promise.as<PromiseObject>());
+ }
+ }
+
+ // Step 4 (reordered): Set stream.[[writer]] to this.
+ // Doing this last prevents a partially-initialized writer from being attached
+ // to the stream (and possibly left there on OOM).
+ {
+ AutoRealm ar(cx, unwrappedStream);
+ Rooted<JSObject*> wrappedWriter(cx, writer);
+ if (!cx->compartment()->wrap(cx, &wrappedWriter)) {
+ return nullptr;
+ }
+ unwrappedStream->setWriter(wrappedWriter);
+ }
+
+ return writer;
+}
+
+/**
+ * Streams spec, 4.5.3.
+ * new WritableStreamDefaultWriter(stream)
+ */
+bool WritableStreamDefaultWriter::constructor(JSContext* cx, unsigned argc,
+ Value* vp) {
+ MOZ_ASSERT(cx->realm()->creationOptions().getWritableStreamsEnabled(),
+ "WritableStream should be enabled in this realm if we reach here");
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "WritableStreamDefaultWriter")) {
+ return false;
+ }
+
+ // Step 1: If ! IsWritableStream(stream) is false, throw a TypeError
+ // exception.
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapAndTypeCheckArgument<WritableStream>(
+ cx, args, "WritableStreamDefaultWriter constructor", 0));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 2: If ! IsWritableStreamLocked(stream) is true, throw a TypeError
+ // exception.
+ if (unwrappedStream->isLocked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_ALREADY_LOCKED);
+ return false;
+ }
+
+ // Implicit in the spec: Find the prototype object to use.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Null, &proto)) {
+ return false;
+ }
+
+ // Steps 3-9.
+ Rooted<WritableStreamDefaultWriter*> writer(
+ cx, CreateWritableStreamDefaultWriter(cx, unwrappedStream, proto));
+ if (!writer) {
+ return false;
+ }
+
+ args.rval().setObject(*writer);
+ return true;
+}
+
+/**
+ * Streams spec, 4.5.4.1. get closed
+ */
+static MOZ_MUST_USE bool WritableStreamDefaultWriter_closed(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
+ // rejected with a TypeError exception.
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx, UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args,
+ "get closed"));
+ if (!unwrappedWriter) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: Return this.[[closedPromise]].
+ Rooted<JSObject*> closedPromise(cx, unwrappedWriter->closedPromise());
+ if (!cx->compartment()->wrap(cx, &closedPromise)) {
+ return false;
+ }
+
+ args.rval().setObject(*closedPromise);
+ return true;
+}
+
+/**
+ * Streams spec, 4.5.4.2. get desiredSize
+ */
+static MOZ_MUST_USE bool WritableStreamDefaultWriter_desiredSize(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, throw a
+ // TypeError exception.
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx, UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(
+ cx, args, "get desiredSize"));
+ if (!unwrappedWriter) {
+ return false;
+ }
+
+ // Step 2: If this.[[ownerWritableStream]] is undefined, throw a TypeError
+ // exception.
+ if (!unwrappedWriter->hasStream()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAMWRITER_NOT_OWNED,
+ "get desiredSize");
+ return false;
+ }
+
+ // Step 3: Return ! WritableStreamDefaultWriterGetDesiredSize(this).
+ if (!WritableStreamDefaultWriterGetDesiredSize(cx, unwrappedWriter,
+ args.rval())) {
+ return false;
+ }
+
+ MOZ_ASSERT(args.rval().isNull() || args.rval().isNumber(),
+ "expected a type that'll never require wrapping");
+ return true;
+}
+
+/**
+ * Streams spec, 4.5.4.3. get ready
+ */
+static MOZ_MUST_USE bool WritableStreamDefaultWriter_ready(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
+ // rejected with a TypeError exception.
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx, UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args,
+ "get ready"));
+ if (!unwrappedWriter) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: Return this.[[readyPromise]].
+ Rooted<JSObject*> readyPromise(cx, unwrappedWriter->readyPromise());
+ if (!cx->compartment()->wrap(cx, &readyPromise)) {
+ return false;
+ }
+
+ args.rval().setObject(*readyPromise);
+ return true;
+}
+
+/**
+ * Streams spec, 4.5.4.4. abort(reason)
+ */
+static MOZ_MUST_USE bool WritableStreamDefaultWriter_abort(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
+ // rejected with a TypeError exception.
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx,
+ UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "abort"));
+ if (!unwrappedWriter) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: If this.[[ownerWritableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ if (!unwrappedWriter->hasStream()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, "abort");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! WritableStreamDefaultWriterAbort(this, reason).
+ JSObject* promise =
+ WritableStreamDefaultWriterAbort(cx, unwrappedWriter, args.get(0));
+ if (!promise) {
+ return false;
+ }
+ cx->check(promise);
+
+ args.rval().setObject(*promise);
+ return true;
+}
+
+/**
+ * Streams spec, 4.5.4.5. close()
+ */
+static MOZ_MUST_USE bool WritableStreamDefaultWriter_close(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
+ // rejected with a TypeError exception.
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx,
+ UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "close"));
+ if (!unwrappedWriter) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: Let stream be this.[[ownerWritableStream]].
+ // Step 3: If stream is undefined, return a promise rejected with a TypeError
+ // exception.
+ if (!unwrappedWriter->hasStream()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, "write");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ WritableStream* unwrappedStream = UnwrapStreamFromWriter(cx, unwrappedWriter);
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 4: If ! WritableStreamCloseQueuedOrInFlight(stream) is true, return a
+ // promise rejected with a TypeError exception.
+ if (WritableStreamCloseQueuedOrInFlight(unwrappedStream)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_CLOSE_CLOSING_OR_CLOSED);
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 5: Return ! WritableStreamDefaultWriterClose(this).
+ JSObject* promise = WritableStreamDefaultWriterClose(cx, unwrappedWriter);
+ if (!promise) {
+ return false;
+ }
+ cx->check(promise);
+
+ args.rval().setObject(*promise);
+ return true;
+}
+
+/**
+ * Streams spec, 4.5.4.6. releaseLock()
+ */
+static MOZ_MUST_USE bool WritableStreamDefaultWriter_releaseLock(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
+ // rejected with a TypeError exception.
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx,
+ UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "close"));
+ if (!unwrappedWriter) {
+ return false;
+ }
+
+ // Step 2: Let stream be this.[[ownerWritableStream]].
+ // Step 3: If stream is undefined, return.
+ if (!unwrappedWriter->hasStream()) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 4: Assert: stream.[[writer]] is not undefined.
+#ifdef DEBUG
+ {
+ WritableStream* unwrappedStream =
+ UnwrapStreamFromWriter(cx, unwrappedWriter);
+ if (!unwrappedStream) {
+ return false;
+ }
+ MOZ_ASSERT(unwrappedStream->hasWriter());
+ }
+#endif
+
+ // Step 5: Perform ! WritableStreamDefaultWriterRelease(this).
+ if (!WritableStreamDefaultWriterRelease(cx, unwrappedWriter)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+/**
+ * Streams spec, 4.5.4.7. write(chunk)
+ */
+static MOZ_MUST_USE bool WritableStreamDefaultWriter_write(JSContext* cx,
+ unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsWritableStreamDefaultWriter(this) is false, return a promise
+ // rejected with a TypeError exception.
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx,
+ UnwrapAndTypeCheckThis<WritableStreamDefaultWriter>(cx, args, "write"));
+ if (!unwrappedWriter) {
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 2: If this.[[ownerWritableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ if (!unwrappedWriter->hasStream()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAMWRITER_NOT_OWNED, "write");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return this.[[readyPromise]].
+ PromiseObject* promise =
+ WritableStreamDefaultWriterWrite(cx, unwrappedWriter, args.get(0));
+ if (!promise) {
+ return false;
+ }
+ cx->check(promise);
+
+ args.rval().setObject(*promise);
+ return true;
+}
+
+static const JSPropertySpec WritableStreamDefaultWriter_properties[] = {
+ JS_PSG("closed", WritableStreamDefaultWriter_closed, 0),
+ JS_PSG("desiredSize", WritableStreamDefaultWriter_desiredSize, 0),
+ JS_PSG("ready", WritableStreamDefaultWriter_ready, 0), JS_PS_END};
+
+static const JSFunctionSpec WritableStreamDefaultWriter_methods[] = {
+ JS_FN("abort", WritableStreamDefaultWriter_abort, 1, 0),
+ JS_FN("close", WritableStreamDefaultWriter_close, 0, 0),
+ JS_FN("releaseLock", WritableStreamDefaultWriter_releaseLock, 0, 0),
+ JS_FN("write", WritableStreamDefaultWriter_write, 1, 0), JS_FS_END};
+
+JS_STREAMS_CLASS_SPEC(WritableStreamDefaultWriter, 1, SlotCount,
+ ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
diff --git a/js/src/builtin/streams/WritableStreamDefaultWriter.h b/js/src/builtin/streams/WritableStreamDefaultWriter.h
new file mode 100644
index 0000000000..e818cec57b
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamDefaultWriter.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Class WritableStreamDefaultWriter. */
+
+#ifndef builtin_streams_WritableStreamDefaultWriter_h
+#define builtin_streams_WritableStreamDefaultWriter_h
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jstypes.h" // JS_PUBLIC_API
+#include "js/Class.h" // JSClass, js::ClassSpec
+#include "js/Value.h" // JS::{,Object,Undefined}Value
+#include "vm/NativeObject.h" // js::NativeObject
+
+struct JS_PUBLIC_API JSContext;
+class JS_PUBLIC_API JSObject;
+
+namespace js {
+
+class PromiseObject;
+class WritableStream;
+
+class WritableStreamDefaultWriter : public NativeObject {
+ public:
+ /**
+ * Memory layout of Stream Writer instances.
+ *
+ * See https://streams.spec.whatwg.org/#default-writer-internal-slots for
+ * details.
+ */
+ enum Slots {
+ /**
+ * A promise that is resolved when the stream this writes to becomes closed.
+ *
+ * This promise is ordinarily created while this writer is being created; in
+ * this case this promise is not a wrapper and is same-compartment with
+ * this. However, if the writer is closed and then this writer releases its
+ * lock on the stream, this promise will be recreated within whatever realm
+ * is in force when the lock is released:
+ *
+ * var ws = new WritableStream({});
+ * var w = ws.getWriter();
+ * var c = w.closed;
+ * w.close().then(() => {
+ * w.releaseLock(); // changes this slot, and |w.closed|
+ * assertEq(c === w.closed, false);
+ * });
+ *
+ * So this field *may* potentially contain a wrapper around a promise.
+ */
+ Slot_ClosedPromise,
+
+ /**
+ * The stream that this writer writes to. Because writers are created under
+ * |WritableStream.prototype.getWriter| which may not be same-compartment
+ * with the stream, this is potentially a wrapper.
+ */
+ Slot_Stream,
+
+ /**
+ * The promise returned by the |writer.ready| getter property, a promise
+ * signaling that the related stream is accepting writes.
+ *
+ * This value repeatedly changes as the related stream changes back and
+ * forth between being writable and temporarily filled (or, ultimately,
+ * errored or aborted). These changes are invoked by a number of user-
+ * visible functions, so this may be a wrapper around a promise in another
+ * realm.
+ */
+ Slot_ReadyPromise,
+
+ SlotCount,
+ };
+
+ JSObject* closedPromise() const {
+ return &getFixedSlot(Slot_ClosedPromise).toObject();
+ }
+ void setClosedPromise(JSObject* wrappedPromise) {
+ setFixedSlot(Slot_ClosedPromise, JS::ObjectValue(*wrappedPromise));
+ }
+
+ bool hasStream() const { return !getFixedSlot(Slot_Stream).isUndefined(); }
+ void setStream(JSObject* stream) {
+ setFixedSlot(Slot_Stream, JS::ObjectValue(*stream));
+ }
+ void clearStream() { setFixedSlot(Slot_Stream, JS::UndefinedValue()); }
+
+ JSObject* readyPromise() const {
+ return &getFixedSlot(Slot_ReadyPromise).toObject();
+ }
+ void setReadyPromise(JSObject* wrappedPromise) {
+ setFixedSlot(Slot_ReadyPromise, JS::ObjectValue(*wrappedPromise));
+ }
+
+ static bool constructor(JSContext* cx, unsigned argc, JS::Value* vp);
+ static const ClassSpec classSpec_;
+ static const JSClass class_;
+ static const ClassSpec protoClassSpec_;
+ static const JSClass protoClass_;
+};
+
+extern MOZ_MUST_USE WritableStreamDefaultWriter*
+CreateWritableStreamDefaultWriter(JSContext* cx,
+ JS::Handle<WritableStream*> unwrappedStream,
+ JS::Handle<JSObject*> proto = nullptr);
+
+} // namespace js
+
+#endif // builtin_streams_WritableStreamDefaultWriter_h
diff --git a/js/src/builtin/streams/WritableStreamOperations.cpp b/js/src/builtin/streams/WritableStreamOperations.cpp
new file mode 100644
index 0000000000..bc11bf4b39
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamOperations.cpp
@@ -0,0 +1,923 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Writable stream abstract operations. */
+
+#include "builtin/streams/WritableStreamOperations.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include <stdint.h> // uint32_t
+
+#include "jsapi.h" // JS_ReportErrorASCII
+
+#include "builtin/streams/MiscellaneousOperations.h" // js::PromiseRejectedWithPendingError
+#include "builtin/streams/WritableStream.h" // js::WritableStream
+#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStreamDefaultController{,Close}, js::WritableStream::controller
+#include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::WritableStreamControllerErrorSteps
+#include "builtin/streams/WritableStreamWriterOperations.h" // js::WritableStreamDefaultWriterEnsureReadyPromiseRejected
+#include "js/CallArgs.h" // JS::CallArgs{,FromVp}
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Promise.h" // JS::{Reject,Resolve}Promise
+#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
+#include "js/Value.h" // JS::Value, JS::ObjecValue, JS::UndefinedHandleValue
+#include "vm/Compartment.h" // JS::Compartment
+#include "vm/JSContext.h" // JSContext
+#include "vm/List.h" // js::ListObject
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+
+#include "builtin/HandlerFunction-inl.h" // js::NewHandler, js::TargetFromHandler
+#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
+#include "builtin/streams/MiscellaneousOperations-inl.h" // js::ResolveUnwrappedPromiseWithUndefined, js::RejectUnwrappedPromiseWithError
+#include "builtin/streams/WritableStream-inl.h" // js::UnwrapWriterFromStream
+#include "builtin/streams/WritableStreamDefaultWriter-inl.h" // js::WritableStreamDefaultWriter::closedPromise
+#include "vm/Compartment-inl.h" // JS::Compartment::wrap, js::UnwrapAndDowncastObject
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/JSObject-inl.h" // js::NewObjectWithClassProto
+#include "vm/List-inl.h" // js::{AppendTo,StoreNew}ListInFixedSlot
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using js::ExtraFromHandler;
+using js::PromiseObject;
+using js::TargetFromHandler;
+using js::UnwrapAndDowncastObject;
+using js::WritableStream;
+using js::WritableStreamDefaultController;
+using js::WritableStreamRejectCloseAndClosedPromiseIfNeeded;
+
+using JS::CallArgs;
+using JS::CallArgsFromVp;
+using JS::Handle;
+using JS::ObjectValue;
+using JS::RejectPromise;
+using JS::ResolvePromise;
+using JS::Rooted;
+using JS::UndefinedHandleValue;
+using JS::Value;
+
+/*** 4.3. General writable stream abstract operations. **********************/
+
+/**
+ * Streams spec, 4.3.4. InitializeWritableStream ( stream )
+ */
+/* static */ MOZ_MUST_USE
+WritableStream* WritableStream::create(
+ JSContext* cx, void* nsISupportsObject_alreadyAddreffed /* = nullptr */,
+ Handle<JSObject*> proto /* = nullptr */) {
+ cx->check(proto);
+
+ // In the spec, InitializeWritableStream is always passed a newly created
+ // WritableStream object. We instead create it here and return it below.
+ Rooted<WritableStream*> stream(
+ cx, NewObjectWithClassProto<WritableStream>(cx, proto));
+ if (!stream) {
+ return nullptr;
+ }
+
+ stream->setPrivate(nsISupportsObject_alreadyAddreffed);
+
+ stream->initWritableState();
+
+ // Step 1: Set stream.[[state]] to "writable".
+ MOZ_ASSERT(stream->writable());
+
+ // Step 2: Set stream.[[storedError]], stream.[[writer]],
+ // stream.[[writableStreamController]],
+ // stream.[[inFlightWriteRequest]], stream.[[closeRequest]],
+ // stream.[[inFlightCloseRequest]] and stream.[[pendingAbortRequest]]
+ // to undefined.
+ MOZ_ASSERT(stream->storedError().isUndefined());
+ MOZ_ASSERT(!stream->hasWriter());
+ MOZ_ASSERT(!stream->hasController());
+ MOZ_ASSERT(!stream->haveInFlightWriteRequest());
+ MOZ_ASSERT(stream->inFlightWriteRequest().isUndefined());
+ MOZ_ASSERT(stream->closeRequest().isUndefined());
+ MOZ_ASSERT(stream->inFlightCloseRequest().isUndefined());
+ MOZ_ASSERT(!stream->hasPendingAbortRequest());
+
+ // Step 3: Set stream.[[writeRequests]] to a new empty List.
+ if (!StoreNewListInFixedSlot(cx, stream,
+ WritableStream::Slot_WriteRequests)) {
+ return nullptr;
+ }
+
+ // Step 4: Set stream.[[backpressure]] to false.
+ MOZ_ASSERT(!stream->backpressure());
+
+ return stream;
+}
+
+void WritableStream::clearInFlightWriteRequest(JSContext* cx) {
+ MOZ_ASSERT(stateIsInitialized());
+ MOZ_ASSERT(haveInFlightWriteRequest());
+
+ writeRequests()->popFirst(cx);
+ setFlag(HaveInFlightWriteRequest, false);
+
+ MOZ_ASSERT(!haveInFlightWriteRequest());
+ MOZ_ASSERT(inFlightWriteRequest().isUndefined());
+}
+
+/**
+ * Streams spec, 4.3.6.
+ * WritableStreamAbort ( stream, reason )
+ *
+ * Note: The object (a promise) returned by this function is in the current
+ * compartment and does not require special wrapping to be put to use.
+ */
+JSObject* js::WritableStreamAbort(JSContext* cx,
+ Handle<WritableStream*> unwrappedStream,
+ Handle<Value> reason) {
+ cx->check(reason);
+
+ // Step 1: Let state be stream.[[state]].
+ // Step 2: If state is "closed" or "errored", return a promise resolved with
+ // undefined.
+ if (unwrappedStream->closed() || unwrappedStream->errored()) {
+ return PromiseResolvedWithUndefined(cx);
+ }
+
+ // Step 3: If stream.[[pendingAbortRequest]] is not undefined, return
+ // stream.[[pendingAbortRequest]].[[promise]].
+ if (unwrappedStream->hasPendingAbortRequest()) {
+ Rooted<JSObject*> pendingPromise(
+ cx, unwrappedStream->pendingAbortRequestPromise());
+ if (!cx->compartment()->wrap(cx, &pendingPromise)) {
+ return nullptr;
+ }
+ return pendingPromise;
+ }
+
+ // Step 4: Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step 7: Let promise be a new promise (reordered).
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 5: Let wasAlreadyErroring be false.
+ // Step 6: If state is "erroring",
+ // Step 6.a: Set wasAlreadyErroring to true.
+ // Step 6.b: Set reason to undefined.
+ bool wasAlreadyErroring = unwrappedStream->erroring();
+ Handle<Value> pendingReason =
+ wasAlreadyErroring ? UndefinedHandleValue : reason;
+
+ // Step 8: Set stream.[[pendingAbortRequest]] to
+ // Record {[[promise]]: promise, [[reason]]: reason,
+ // [[wasAlreadyErroring]]: wasAlreadyErroring}.
+ {
+ AutoRealm ar(cx, unwrappedStream);
+
+ Rooted<JSObject*> wrappedPromise(cx, promise);
+ Rooted<Value> wrappedPendingReason(cx, pendingReason);
+
+ JS::Compartment* comp = cx->compartment();
+ if (!comp->wrap(cx, &wrappedPromise) ||
+ !comp->wrap(cx, &wrappedPendingReason)) {
+ return nullptr;
+ }
+
+ unwrappedStream->setPendingAbortRequest(
+ wrappedPromise, wrappedPendingReason, wasAlreadyErroring);
+ }
+
+ // Step 9: If wasAlreadyErroring is false, perform
+ // ! WritableStreamStartErroring(stream, reason).
+ if (!wasAlreadyErroring) {
+ if (!WritableStreamStartErroring(cx, unwrappedStream, pendingReason)) {
+ return nullptr;
+ }
+ }
+
+ // Step 10: Return promise.
+ return promise;
+}
+
+/**
+ * Streams spec, 4.3.7.
+ * WritableStreamClose ( stream )
+ *
+ * Note: The object (a promise) returned by this function is in the current
+ * compartment and does not require special wrapping to be put to use.
+ */
+JSObject* js::WritableStreamClose(JSContext* cx,
+ Handle<WritableStream*> unwrappedStream) {
+ // Step 1: Let state be stream.[[state]].
+ // Step 2: If state is "closed" or "errored", return a promise rejected with a
+ // TypeError exception.
+ if (unwrappedStream->closed() || unwrappedStream->errored()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_CLOSED_OR_ERRORED);
+ return PromiseRejectedWithPendingError(cx);
+ }
+
+ // Step 3: Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step 4: Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
+ MOZ_ASSERT(!WritableStreamCloseQueuedOrInFlight(unwrappedStream));
+
+ // Step 5: Let promise be a new promise.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 6: Set stream.[[closeRequest]] to promise.
+ {
+ AutoRealm ar(cx, unwrappedStream);
+ Rooted<JSObject*> wrappedPromise(cx, promise);
+ if (!cx->compartment()->wrap(cx, &wrappedPromise)) {
+ return nullptr;
+ }
+
+ unwrappedStream->setCloseRequest(promise);
+ }
+
+ // Step 7: Let writer be stream.[[writer]].
+ // Step 8: If writer is not undefined, and stream.[[backpressure]] is true,
+ // and state is "writable", resolve writer.[[readyPromise]] with
+ // undefined.
+ if (unwrappedStream->hasWriter() && unwrappedStream->backpressure() &&
+ unwrappedStream->writable()) {
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx, UnwrapWriterFromStream(cx, unwrappedStream));
+ if (!unwrappedWriter) {
+ return nullptr;
+ }
+
+ if (!ResolveUnwrappedPromiseWithUndefined(
+ cx, unwrappedWriter->readyPromise())) {
+ return nullptr;
+ }
+ }
+
+ // Step 9: Perform
+ // ! WritableStreamDefaultControllerClose(
+ // stream.[[writableStreamController]]).
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ if (!WritableStreamDefaultControllerClose(cx, unwrappedController)) {
+ return nullptr;
+ }
+
+ // Step 10: Return promise.
+ return promise;
+}
+
+/*** 4.4. Writable stream abstract operations used by controllers ***********/
+
+/**
+ * Streams spec, 4.4.1.
+ * WritableStreamAddWriteRequest ( stream )
+ */
+MOZ_MUST_USE PromiseObject* js::WritableStreamAddWriteRequest(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream) {
+ // Step 1: Assert: ! IsWritableStreamLocked(stream) is true.
+ MOZ_ASSERT(unwrappedStream->isLocked());
+
+ // Step 2: Assert: stream.[[state]] is "writable".
+ MOZ_ASSERT(unwrappedStream->writable());
+
+ // Step 3: Let promise be a new promise.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 4: Append promise as the last element of stream.[[writeRequests]].
+ if (!AppendToListInFixedSlot(cx, unwrappedStream,
+ WritableStream::Slot_WriteRequests, promise)) {
+ return nullptr;
+ }
+
+ // Step 5: Return promise.
+ return promise;
+}
+
+/**
+ * Streams spec, 4.4.2.
+ * WritableStreamDealWithRejection ( stream, error )
+ */
+MOZ_MUST_USE bool js::WritableStreamDealWithRejection(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream,
+ Handle<Value> error) {
+ cx->check(error);
+
+ // Step 1: Let state be stream.[[state]].
+ // Step 2: If state is "writable",
+ if (unwrappedStream->writable()) {
+ // Step 2a: Perform ! WritableStreamStartErroring(stream, error).
+ // Step 2b: Return.
+ return WritableStreamStartErroring(cx, unwrappedStream, error);
+ }
+
+ // Step 3: Assert: state is "erroring".
+ MOZ_ASSERT(unwrappedStream->erroring());
+
+ // Step 4: Perform ! WritableStreamFinishErroring(stream).
+ return WritableStreamFinishErroring(cx, unwrappedStream);
+}
+
+static bool WritableStreamHasOperationMarkedInFlight(
+ const WritableStream* unwrappedStream);
+
+/**
+ * Streams spec, 4.4.3.
+ * WritableStreamStartErroring ( stream, reason )
+ */
+MOZ_MUST_USE bool js::WritableStreamStartErroring(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream,
+ Handle<Value> reason) {
+ cx->check(reason);
+
+ // Step 1: Assert: stream.[[storedError]] is undefined.
+ MOZ_ASSERT(unwrappedStream->storedError().isUndefined());
+
+ // Step 2: Assert: stream.[[state]] is "writable".
+ MOZ_ASSERT(unwrappedStream->writable());
+
+ // Step 3: Let controller be stream.[[writableStreamController]].
+ // Step 4: Assert: controller is not undefined.
+ MOZ_ASSERT(unwrappedStream->hasController());
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, unwrappedStream->controller());
+
+ // Step 5: Set stream.[[state]] to "erroring".
+ unwrappedStream->setErroring();
+
+ // Step 6: Set stream.[[storedError]] to reason.
+ {
+ AutoRealm ar(cx, unwrappedStream);
+ Rooted<Value> wrappedReason(cx, reason);
+ if (!cx->compartment()->wrap(cx, &wrappedReason)) {
+ return false;
+ }
+ unwrappedStream->setStoredError(wrappedReason);
+ }
+
+ // Step 7: Let writer be stream.[[writer]].
+ // Step 8: If writer is not undefined, perform
+ // ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ // writer, reason).
+ if (unwrappedStream->hasWriter()) {
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx, UnwrapWriterFromStream(cx, unwrappedStream));
+ if (!unwrappedWriter) {
+ return false;
+ }
+
+ if (!WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ cx, unwrappedWriter, reason)) {
+ return false;
+ }
+ }
+
+ // Step 9: If ! WritableStreamHasOperationMarkedInFlight(stream) is false and
+ // controller.[[started]] is true, perform
+ // ! WritableStreamFinishErroring(stream).
+ if (!WritableStreamHasOperationMarkedInFlight(unwrappedStream) &&
+ unwrappedController->started()) {
+ if (!WritableStreamFinishErroring(cx, unwrappedStream)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 4.4.4 WritableStreamFinishErroring ( stream )
+ * Step 13: Upon fulfillment of promise, [...]
+ */
+static bool AbortRequestPromiseFulfilledHandler(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 13.a: Resolve abortRequest.[[promise]] with undefined.
+ Rooted<JSObject*> abortRequestPromise(cx, TargetFromHandler<JSObject>(args));
+ if (!ResolvePromise(cx, abortRequestPromise, UndefinedHandleValue)) {
+ return false;
+ }
+
+ // Step 13.b: Perform
+ // ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapAndDowncastObject<WritableStream>(
+ cx, ExtraFromHandler<JSObject>(args)));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ if (!WritableStreamRejectCloseAndClosedPromiseIfNeeded(cx, unwrappedStream)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return false;
+}
+
+/**
+ * Streams spec, 4.4.4 WritableStreamFinishErroring ( stream )
+ * Step 14: Upon rejection of promise with reason reason, [...]
+ */
+static bool AbortRequestPromiseRejectedHandler(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 14.a: Reject abortRequest.[[promise]] with reason.
+ Rooted<JSObject*> abortRequestPromise(cx, TargetFromHandler<JSObject>(args));
+ if (!RejectPromise(cx, abortRequestPromise, args.get(0))) {
+ return false;
+ }
+
+ // Step 14.b: Perform
+ // ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapAndDowncastObject<WritableStream>(
+ cx, ExtraFromHandler<JSObject>(args)));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ if (!WritableStreamRejectCloseAndClosedPromiseIfNeeded(cx, unwrappedStream)) {
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return false;
+}
+
+/**
+ * Streams spec, 4.4.4.
+ * WritableStreamFinishErroring ( stream )
+ */
+MOZ_MUST_USE bool js::WritableStreamFinishErroring(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream) {
+ // Step 1: Assert: stream.[[state]] is "erroring".
+ MOZ_ASSERT(unwrappedStream->erroring());
+
+ // Step 2: Assert: ! WritableStreamHasOperationMarkedInFlight(stream) is
+ // false.
+ MOZ_ASSERT(!WritableStreamHasOperationMarkedInFlight(unwrappedStream));
+
+ // Step 3: Set stream.[[state]] to "errored".
+ unwrappedStream->setErrored();
+
+ // Step 4: Perform ! stream.[[writableStreamController]].[[ErrorSteps]]().
+ {
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ if (!WritableStreamControllerErrorSteps(cx, unwrappedController)) {
+ return false;
+ }
+ }
+
+ // Step 5: Let storedError be stream.[[storedError]].
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return false;
+ }
+
+ // Step 6: Repeat for each writeRequest that is an element of
+ // stream.[[writeRequests]],
+ {
+ Rooted<ListObject*> unwrappedWriteRequests(
+ cx, unwrappedStream->writeRequests());
+ Rooted<JSObject*> writeRequest(cx);
+ uint32_t len = unwrappedWriteRequests->length();
+ for (uint32_t i = 0; i < len; i++) {
+ // Step 6.a: Reject writeRequest with storedError.
+ writeRequest = &unwrappedWriteRequests->get(i).toObject();
+ if (!RejectUnwrappedPromiseWithError(cx, &writeRequest, storedError)) {
+ return false;
+ }
+ }
+ }
+
+ // Step 7: Set stream.[[writeRequests]] to an empty List.
+ // We optimize this to discard the list entirely. (A brief scan of the
+ // streams spec should verify that [[writeRequests]] is never accessed on a
+ // stream when |stream.[[state]] === "errored"|, set in step 3 above.)
+ unwrappedStream->clearWriteRequests();
+
+ // Step 8: If stream.[[pendingAbortRequest]] is undefined,
+ if (!unwrappedStream->hasPendingAbortRequest()) {
+ // Step 8.a: Perform
+ // ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
+ // Step 8.b: Return.
+ return WritableStreamRejectCloseAndClosedPromiseIfNeeded(cx,
+ unwrappedStream);
+ }
+
+ // Step 9: Let abortRequest be stream.[[pendingAbortRequest]].
+ // Step 10: Set stream.[[pendingAbortRequest]] to undefined.
+ Rooted<Value> abortRequestReason(
+ cx, unwrappedStream->pendingAbortRequestReason());
+ if (!cx->compartment()->wrap(cx, &abortRequestReason)) {
+ return false;
+ }
+ Rooted<JSObject*> abortRequestPromise(
+ cx, unwrappedStream->pendingAbortRequestPromise());
+ bool wasAlreadyErroring =
+ unwrappedStream->pendingAbortRequestWasAlreadyErroring();
+ unwrappedStream->clearPendingAbortRequest();
+
+ // Step 11: If abortRequest.[[wasAlreadyErroring]] is true,
+ if (wasAlreadyErroring) {
+ // Step 11.a: Reject abortRequest.[[promise]] with storedError.
+ if (!RejectUnwrappedPromiseWithError(cx, &abortRequestPromise,
+ storedError)) {
+ return false;
+ }
+
+ // Step 11.b: Perform
+ // ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
+ // Step 11.c: Return.
+ return WritableStreamRejectCloseAndClosedPromiseIfNeeded(cx,
+ unwrappedStream);
+ }
+
+ // Step 12: Let promise be
+ // ! stream.[[writableStreamController]].[[AbortSteps]](
+ // abortRequest.[[reason]]).
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ Rooted<JSObject*> promise(
+ cx, WritableStreamControllerAbortSteps(cx, unwrappedController,
+ abortRequestReason));
+ if (!promise) {
+ return false;
+ }
+ cx->check(promise);
+
+ if (!cx->compartment()->wrap(cx, &abortRequestPromise)) {
+ return false;
+ }
+
+ Rooted<JSObject*> stream(cx, unwrappedStream);
+ if (!cx->compartment()->wrap(cx, &stream)) {
+ return false;
+ }
+
+ // Step 13: Upon fulfillment of promise, [...]
+ // Step 14: Upon rejection of promise with reason reason, [...]
+ Rooted<JSObject*> onFulfilled(
+ cx, NewHandlerWithExtra(cx, AbortRequestPromiseFulfilledHandler,
+ abortRequestPromise, stream));
+ if (!onFulfilled) {
+ return false;
+ }
+ Rooted<JSObject*> onRejected(
+ cx, NewHandlerWithExtra(cx, AbortRequestPromiseRejectedHandler,
+ abortRequestPromise, stream));
+ if (!onRejected) {
+ return false;
+ }
+
+ return JS::AddPromiseReactions(cx, promise, onFulfilled, onRejected);
+}
+
+/**
+ * Streams spec, 4.4.5.
+ * WritableStreamFinishInFlightWrite ( stream )
+ */
+MOZ_MUST_USE bool js::WritableStreamFinishInFlightWrite(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream) {
+ // Step 1: Assert: stream.[[inFlightWriteRequest]] is not undefined.
+ MOZ_ASSERT(unwrappedStream->haveInFlightWriteRequest());
+
+ // Step 2: Resolve stream.[[inFlightWriteRequest]] with undefined.
+ if (!ResolveUnwrappedPromiseWithUndefined(
+ cx, &unwrappedStream->inFlightWriteRequest().toObject())) {
+ return false;
+ }
+
+ // Step 3: Set stream.[[inFlightWriteRequest]] to undefined.
+ unwrappedStream->clearInFlightWriteRequest(cx);
+ MOZ_ASSERT(!unwrappedStream->haveInFlightWriteRequest());
+
+ return true;
+}
+
+/**
+ * Streams spec, 4.4.6.
+ * WritableStreamFinishInFlightWriteWithError ( stream, error )
+ */
+MOZ_MUST_USE bool js::WritableStreamFinishInFlightWriteWithError(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream,
+ Handle<Value> error) {
+ cx->check(error);
+
+ // Step 1: Assert: stream.[[inFlightWriteRequest]] is not undefined.
+ MOZ_ASSERT(unwrappedStream->haveInFlightWriteRequest());
+
+ // Step 2: Reject stream.[[inFlightWriteRequest]] with error.
+ if (!RejectUnwrappedPromiseWithError(
+ cx, &unwrappedStream->inFlightWriteRequest().toObject(), error)) {
+ return false;
+ }
+
+ // Step 3: Set stream.[[inFlightWriteRequest]] to undefined.
+ unwrappedStream->clearInFlightWriteRequest(cx);
+
+ // Step 4: Assert: stream.[[state]] is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step 5: Perform ! WritableStreamDealWithRejection(stream, error).
+ return WritableStreamDealWithRejection(cx, unwrappedStream, error);
+}
+
+/**
+ * Streams spec, 4.4.7.
+ * WritableStreamFinishInFlightClose ( stream )
+ */
+MOZ_MUST_USE bool js::WritableStreamFinishInFlightClose(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream) {
+ // Step 1: Assert: stream.[[inFlightCloseRequest]] is not undefined.
+ MOZ_ASSERT(unwrappedStream->haveInFlightCloseRequest());
+
+ // Step 2: Resolve stream.[[inFlightCloseRequest]] with undefined.
+ if (!ResolveUnwrappedPromiseWithUndefined(
+ cx, &unwrappedStream->inFlightCloseRequest().toObject())) {
+ return false;
+ }
+
+ // Step 3: Set stream.[[inFlightCloseRequest]] to undefined.
+ unwrappedStream->clearInFlightCloseRequest();
+ MOZ_ASSERT(unwrappedStream->inFlightCloseRequest().isUndefined());
+
+ // Step 4: Let state be stream.[[state]].
+ // Step 5: Assert: stream.[[state]] is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step 6: If state is "erroring",
+ if (unwrappedStream->erroring()) {
+ // Step 6.a: Set stream.[[storedError]] to undefined.
+ unwrappedStream->clearStoredError();
+
+ // Step 6.b: If stream.[[pendingAbortRequest]] is not undefined,
+ if (unwrappedStream->hasPendingAbortRequest()) {
+ // Step 6.b.i: Resolve stream.[[pendingAbortRequest]].[[promise]] with
+ // undefined.
+ if (!ResolveUnwrappedPromiseWithUndefined(
+ cx, unwrappedStream->pendingAbortRequestPromise())) {
+ return false;
+ }
+
+ // Step 6.b.ii: Set stream.[[pendingAbortRequest]] to undefined.
+ unwrappedStream->clearPendingAbortRequest();
+ }
+ }
+
+ // Step 7: Set stream.[[state]] to "closed".
+ unwrappedStream->setClosed();
+
+ // Step 8: Let writer be stream.[[writer]].
+ // Step 9: If writer is not undefined, resolve writer.[[closedPromise]] with
+ // undefined.
+ if (unwrappedStream->hasWriter()) {
+ WritableStreamDefaultWriter* unwrappedWriter =
+ UnwrapWriterFromStream(cx, unwrappedStream);
+ if (!unwrappedWriter) {
+ return false;
+ }
+
+ if (!ResolveUnwrappedPromiseWithUndefined(
+ cx, unwrappedWriter->closedPromise())) {
+ return false;
+ }
+ }
+
+ // Step 10: Assert: stream.[[pendingAbortRequest]] is undefined.
+ MOZ_ASSERT(!unwrappedStream->hasPendingAbortRequest());
+
+ // Step 11: Assert: stream.[[storedError]] is undefined.
+ MOZ_ASSERT(unwrappedStream->storedError().isUndefined());
+
+ return true;
+}
+
+/**
+ * Streams spec, 4.4.8.
+ * WritableStreamFinishInFlightCloseWithError ( stream, error )
+ */
+MOZ_MUST_USE bool js::WritableStreamFinishInFlightCloseWithError(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream,
+ Handle<Value> error) {
+ cx->check(error);
+
+ // Step 1: Assert: stream.[[inFlightCloseRequest]] is not undefined.
+ MOZ_ASSERT(unwrappedStream->haveInFlightCloseRequest());
+ MOZ_ASSERT(!unwrappedStream->inFlightCloseRequest().isUndefined());
+
+ // Step 2: Reject stream.[[inFlightCloseRequest]] with error.
+ if (!RejectUnwrappedPromiseWithError(
+ cx, &unwrappedStream->inFlightCloseRequest().toObject(), error)) {
+ return false;
+ }
+
+ // Step 3: Set stream.[[inFlightCloseRequest]] to undefined.
+ unwrappedStream->clearInFlightCloseRequest();
+
+ // Step 4: Assert: stream.[[state]] is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step 5: If stream.[[pendingAbortRequest]] is not undefined,
+ if (unwrappedStream->hasPendingAbortRequest()) {
+ // Step 5.a: Reject stream.[[pendingAbortRequest]].[[promise]] with error.
+ if (!RejectUnwrappedPromiseWithError(
+ cx, unwrappedStream->pendingAbortRequestPromise(), error)) {
+ return false;
+ }
+
+ // Step 5.b: Set stream.[[pendingAbortRequest]] to undefined.
+ unwrappedStream->clearPendingAbortRequest();
+ }
+
+ // Step 6: Perform ! WritableStreamDealWithRejection(stream, error).
+ return WritableStreamDealWithRejection(cx, unwrappedStream, error);
+}
+
+/**
+ * Streams spec, 4.4.9.
+ * WritableStreamCloseQueuedOrInFlight ( stream )
+ */
+bool js::WritableStreamCloseQueuedOrInFlight(
+ const WritableStream* unwrappedStream) {
+ // Step 1: If stream.[[closeRequest]] is undefined and
+ // stream.[[inFlightCloseRequest]] is undefined, return false.
+ // Step 2: Return true.
+ return unwrappedStream->haveCloseRequestOrInFlightCloseRequest();
+}
+
+/**
+ * Streams spec, 4.4.10.
+ * WritableStreamHasOperationMarkedInFlight ( stream )
+ */
+bool WritableStreamHasOperationMarkedInFlight(
+ const WritableStream* unwrappedStream) {
+ // Step 1: If stream.[[inFlightWriteRequest]] is undefined and
+ // controller.[[inFlightCloseRequest]] is undefined, return false.
+ // Step 2: Return true.
+ return unwrappedStream->haveInFlightWriteRequest() ||
+ unwrappedStream->haveInFlightCloseRequest();
+}
+
+/**
+ * Streams spec, 4.4.11.
+ * WritableStreamMarkCloseRequestInFlight ( stream )
+ */
+void js::WritableStreamMarkCloseRequestInFlight(
+ WritableStream* unwrappedStream) {
+ // Step 1: Assert: stream.[[inFlightCloseRequest]] is undefined.
+ MOZ_ASSERT(!unwrappedStream->haveInFlightCloseRequest());
+
+ // Step 2: Assert: stream.[[closeRequest]] is not undefined.
+ MOZ_ASSERT(!unwrappedStream->closeRequest().isUndefined());
+
+ // Step 3: Set stream.[[inFlightCloseRequest]] to stream.[[closeRequest]].
+ // Step 4: Set stream.[[closeRequest]] to undefined.
+ unwrappedStream->convertCloseRequestToInFlightCloseRequest();
+}
+
+/**
+ * Streams spec, 4.4.12.
+ * WritableStreamMarkFirstWriteRequestInFlight ( stream )
+ */
+void js::WritableStreamMarkFirstWriteRequestInFlight(
+ WritableStream* unwrappedStream) {
+ // Step 1: Assert: stream.[[inFlightWriteRequest]] is undefined.
+ MOZ_ASSERT(!unwrappedStream->haveInFlightWriteRequest());
+
+ // Step 2: Assert: stream.[[writeRequests]] is not empty.
+ MOZ_ASSERT(unwrappedStream->writeRequests()->length() > 0);
+
+ // Step 3: Let writeRequest be the first element of stream.[[writeRequests]].
+ // Step 4: Remove writeRequest from stream.[[writeRequests]], shifting all
+ // other elements downward (so that the second becomes the first, and
+ // so on).
+ // Step 5: Set stream.[[inFlightWriteRequest]] to writeRequest.
+ // In our implementation, we model [[inFlightWriteRequest]] as merely the
+ // first element of [[writeRequests]], plus a flag indicating there's an
+ // in-flight request. Set the flag and be done with it.
+ unwrappedStream->setHaveInFlightWriteRequest();
+}
+
+/**
+ * Streams spec, 4.4.13.
+ * WritableStreamRejectCloseAndClosedPromiseIfNeeded ( stream )
+ */
+MOZ_MUST_USE bool js::WritableStreamRejectCloseAndClosedPromiseIfNeeded(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream) {
+ // Step 1: Assert: stream.[[state]] is "errored".
+ MOZ_ASSERT(unwrappedStream->errored());
+
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return false;
+ }
+
+ // Step 2: If stream.[[closeRequest]] is not undefined,
+ if (!unwrappedStream->closeRequest().isUndefined()) {
+ // Step 2.a: Assert: stream.[[inFlightCloseRequest]] is undefined.
+ MOZ_ASSERT(unwrappedStream->inFlightCloseRequest().isUndefined());
+
+ // Step 2.b: Reject stream.[[closeRequest]] with stream.[[storedError]].
+ if (!RejectUnwrappedPromiseWithError(
+ cx, &unwrappedStream->closeRequest().toObject(), storedError)) {
+ return false;
+ }
+
+ // Step 2.c: Set stream.[[closeRequest]] to undefined.
+ unwrappedStream->clearCloseRequest();
+ }
+
+ // Step 3: Let writer be stream.[[writer]].
+ // Step 4: If writer is not undefined,
+ if (unwrappedStream->hasWriter()) {
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx, UnwrapWriterFromStream(cx, unwrappedStream));
+ if (!unwrappedWriter) {
+ return false;
+ }
+
+ // Step 4.a: Reject writer.[[closedPromise]] with stream.[[storedError]].
+ if (!RejectUnwrappedPromiseWithError(cx, unwrappedWriter->closedPromise(),
+ storedError)) {
+ return false;
+ }
+
+ // Step 4.b: Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
+ Rooted<PromiseObject*> unwrappedClosedPromise(
+ cx, UnwrapAndDowncastObject<PromiseObject>(
+ cx, unwrappedWriter->closedPromise()));
+ if (!unwrappedClosedPromise) {
+ return false;
+ }
+
+ js::SetSettledPromiseIsHandled(cx, unwrappedClosedPromise);
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 4.4.14.
+ * WritableStreamUpdateBackpressure ( stream, backpressure )
+ */
+MOZ_MUST_USE bool js::WritableStreamUpdateBackpressure(
+ JSContext* cx, Handle<WritableStream*> unwrappedStream, bool backpressure) {
+ // Step 1: Assert: stream.[[state]] is "writable".
+ MOZ_ASSERT(unwrappedStream->writable());
+
+ // Step 2: Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
+ MOZ_ASSERT(!WritableStreamCloseQueuedOrInFlight(unwrappedStream));
+
+ // Step 3: Let writer be stream.[[writer]].
+ // Step 4: If writer is not undefined and backpressure is not
+ // stream.[[backpressure]],
+ if (unwrappedStream->hasWriter() &&
+ backpressure != unwrappedStream->backpressure()) {
+ Rooted<WritableStreamDefaultWriter*> unwrappedWriter(
+ cx, UnwrapWriterFromStream(cx, unwrappedStream));
+ if (!unwrappedWriter) {
+ return false;
+ }
+
+ // Step 4.a: If backpressure is true, set writer.[[readyPromise]] to a new
+ // promise.
+ if (backpressure) {
+ Rooted<JSObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise) {
+ return false;
+ }
+
+ AutoRealm ar(cx, unwrappedWriter);
+ if (!cx->compartment()->wrap(cx, &promise)) {
+ return false;
+ }
+ unwrappedWriter->setReadyPromise(promise);
+ } else {
+ // Step 4.b: Otherwise,
+ // Step 4.b.i: Assert: backpressure is false. (guaranteed by type)
+ // Step 4.b.ii: Resolve writer.[[readyPromise]] with undefined.
+ if (!ResolveUnwrappedPromiseWithUndefined(
+ cx, unwrappedWriter->readyPromise())) {
+ return false;
+ }
+ }
+ }
+
+ // Step 5: Set stream.[[backpressure]] to backpressure.
+ unwrappedStream->setBackpressure(backpressure);
+
+ return true;
+}
diff --git a/js/src/builtin/streams/WritableStreamOperations.h b/js/src/builtin/streams/WritableStreamOperations.h
new file mode 100644
index 0000000000..149caca401
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamOperations.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Writable stream abstract operations. */
+
+#ifndef builtin_streams_WritableStreamOperations_h
+#define builtin_streams_WritableStreamOperations_h
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jstypes.h" // JS_PUBLIC_API
+#include "js/RootingAPI.h" // JS::Handle
+#include "js/Value.h" // JS::Value
+
+struct JS_PUBLIC_API JSContext;
+
+namespace js {
+
+class PromiseObject;
+class WritableStream;
+
+extern JSObject* WritableStreamAbort(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
+ JS::Handle<JS::Value> reason);
+
+extern JSObject* WritableStreamClose(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
+
+extern MOZ_MUST_USE PromiseObject* WritableStreamAddWriteRequest(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
+
+extern MOZ_MUST_USE bool WritableStreamDealWithRejection(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
+ JS::Handle<JS::Value> error);
+
+extern MOZ_MUST_USE bool WritableStreamStartErroring(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
+ JS::Handle<JS::Value> reason);
+
+extern MOZ_MUST_USE bool WritableStreamFinishErroring(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
+
+extern MOZ_MUST_USE bool WritableStreamFinishInFlightWrite(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
+
+extern MOZ_MUST_USE bool WritableStreamFinishInFlightWriteWithError(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
+ JS::Handle<JS::Value> error);
+
+extern MOZ_MUST_USE bool WritableStreamFinishInFlightClose(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
+
+extern MOZ_MUST_USE bool WritableStreamFinishInFlightCloseWithError(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
+ JS::Handle<JS::Value> error);
+
+extern bool WritableStreamCloseQueuedOrInFlight(
+ const WritableStream* unwrappedStream);
+
+extern void WritableStreamMarkCloseRequestInFlight(
+ WritableStream* unwrappedStream);
+
+extern void WritableStreamMarkFirstWriteRequestInFlight(
+ WritableStream* unwrappedStream);
+
+extern MOZ_MUST_USE bool WritableStreamRejectCloseAndClosedPromiseIfNeeded(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream);
+
+extern MOZ_MUST_USE bool WritableStreamUpdateBackpressure(
+ JSContext* cx, JS::Handle<WritableStream*> unwrappedStream,
+ bool backpressure);
+
+} // namespace js
+
+#endif // builtin_streams_WritableStreamOperations_h
diff --git a/js/src/builtin/streams/WritableStreamWriterOperations.cpp b/js/src/builtin/streams/WritableStreamWriterOperations.cpp
new file mode 100644
index 0000000000..cf8a6d1c84
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamWriterOperations.cpp
@@ -0,0 +1,446 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Writable stream writer abstract operations. */
+
+#include "builtin/streams/WritableStreamWriterOperations.h"
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "jsapi.h" // JS_ReportErrorNumberASCII, JS_ReportErrorASCII
+
+#include "builtin/streams/MiscellaneousOperations.h" // js::PromiseRejectedWithPendingError
+#include "builtin/streams/WritableStream.h" // js::WritableStream
+#include "builtin/streams/WritableStreamDefaultController.h" // js::WritableStream::controller
+#include "builtin/streams/WritableStreamDefaultControllerOperations.h" // js::WritableStreamDefaultController{Close,GetDesiredSize}
+#include "builtin/streams/WritableStreamDefaultWriter.h" // js::WritableStreamDefaultWriter
+#include "builtin/streams/WritableStreamOperations.h" // js::WritableStream{Abort,CloseQueuedOrInFlight}
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/Promise.h" // JS::PromiseState
+#include "js/Value.h" // JS::Value, JS::{Int32,Null}Value
+#include "vm/Compartment.h" // JS::Compartment
+#include "vm/Interpreter.h" // js::GetAndClearException
+#include "vm/JSContext.h" // JSContext
+#include "vm/PromiseObject.h" // js::PromiseObject, js::PromiseResolvedWithUndefined
+
+#include "builtin/Promise-inl.h" // js::SetSettledPromiseIsHandled
+#include "builtin/streams/MiscellaneousOperations-inl.h" // js::ResolveUnwrappedPromiseWithUndefined
+#include "builtin/streams/WritableStream-inl.h" // js::WritableStream::setCloseRequest
+#include "builtin/streams/WritableStreamDefaultWriter-inl.h" // js::UnwrapStreamFromWriter
+#include "vm/Compartment-inl.h" // js::UnwrapAnd{DowncastObject,TypeCheckThis}
+#include "vm/JSContext-inl.h" // JSContext::check
+#include "vm/Realm-inl.h" // js::AutoRealm
+
+using JS::Handle;
+using JS::Int32Value;
+using JS::MutableHandle;
+using JS::NullValue;
+using JS::NumberValue;
+using JS::Rooted;
+using JS::Value;
+
+using js::AutoRealm;
+using js::PromiseObject;
+using js::UnwrapAndDowncastObject;
+using js::WritableStreamDefaultWriter;
+
+/*** 4.6. Writable stream writer abstract operations ************************/
+
+/**
+ * Streams spec, 4.6.2.
+ * WritableStreamDefaultWriterAbort ( writer, reason )
+ */
+JSObject* js::WritableStreamDefaultWriterAbort(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ Handle<Value> reason) {
+ cx->check(reason);
+
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ // Step 2: Assert: stream is not undefined.
+ MOZ_ASSERT(unwrappedWriter->hasStream());
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ // Step 3: Return ! WritableStreamAbort(stream, reason).
+ return WritableStreamAbort(cx, unwrappedStream, reason);
+}
+
+/**
+ * Streams spec, 4.6.3.
+ * WritableStreamDefaultWriterClose ( writer )
+ */
+PromiseObject* js::WritableStreamDefaultWriterClose(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ // Step 2: Assert: stream is not undefined.
+ MOZ_ASSERT(unwrappedWriter->hasStream());
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
+ if (!unwrappedStream) {
+ return PromiseRejectedWithPendingError(cx);
+ }
+
+ // Step 3: Let state be stream.[[state]].
+ // Step 4: If state is "closed" or "errored", return a promise rejected with a
+ // TypeError exception.
+ if (unwrappedStream->closed() || unwrappedStream->errored()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_CLOSED_OR_ERRORED);
+ return PromiseRejectedWithPendingError(cx);
+ }
+
+ // Step 5: Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step 6: Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
+ MOZ_ASSERT(!WritableStreamCloseQueuedOrInFlight(unwrappedStream));
+
+ // Step 7: Let promise be a new promise.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 8: Set stream.[[closeRequest]] to promise.
+ {
+ AutoRealm ar(cx, unwrappedStream);
+ Rooted<JSObject*> closeRequest(cx, promise);
+ if (!cx->compartment()->wrap(cx, &closeRequest)) {
+ return nullptr;
+ }
+
+ unwrappedStream->setCloseRequest(closeRequest);
+ }
+
+ // Step 9: If stream.[[backpressure]] is true and state is "writable", resolve
+ // writer.[[readyPromise]] with undefined.
+ if (unwrappedStream->backpressure() && unwrappedStream->writable()) {
+ if (!ResolveUnwrappedPromiseWithUndefined(
+ cx, unwrappedWriter->readyPromise())) {
+ return nullptr;
+ }
+ }
+
+ // Step 10: Perform
+ // ! WritableStreamDefaultControllerClose(
+ // stream.[[writableStreamController]]).
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, unwrappedStream->controller());
+ if (!WritableStreamDefaultControllerClose(cx, unwrappedController)) {
+ return nullptr;
+ }
+
+ // Step 11: Return promise.
+ return promise;
+}
+
+/**
+ * Streams spec.
+ * WritableStreamDefaultWriterCloseWithErrorPropagation ( writer )
+ */
+PromiseObject* js::WritableStreamDefaultWriterCloseWithErrorPropagation(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ // Step 2: Assert: stream is not undefined.
+ WritableStream* unwrappedStream = UnwrapStreamFromWriter(cx, unwrappedWriter);
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ // Step 3: Let state be stream.[[state]].
+ // Step 4: If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state
+ // is "closed", return a promise resolved with undefined.
+ if (WritableStreamCloseQueuedOrInFlight(unwrappedStream) ||
+ unwrappedStream->closed()) {
+ return PromiseResolvedWithUndefined(cx);
+ }
+
+ // Step 5: If state is "errored", return a promise rejected with
+ // stream.[[storedError]].
+ if (unwrappedStream->errored()) {
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return nullptr;
+ }
+
+ return PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ // Step 6: Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(unwrappedStream->writable() ^ unwrappedStream->erroring());
+
+ // Step 7: Return ! WritableStreamDefaultWriterClose(writer).
+ return WritableStreamDefaultWriterClose(cx, unwrappedWriter);
+}
+
+using GetField = JSObject* (WritableStreamDefaultWriter::*)() const;
+using SetField = void (WritableStreamDefaultWriter::*)(JSObject*);
+
+static bool EnsurePromiseRejected(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ GetField getField, SetField setField, Handle<Value> error) {
+ cx->check(error);
+
+ Rooted<PromiseObject*> unwrappedPromise(
+ cx, UnwrapAndDowncastObject<PromiseObject>(
+ cx, (unwrappedWriter->*getField)()));
+ if (!unwrappedPromise) {
+ return false;
+ }
+
+ // 4.6.{5,6} step 1: If writer.[[<field>]].[[PromiseState]] is "pending",
+ // reject writer.[[<field>]] with error.
+ if (unwrappedPromise->state() == JS::PromiseState::Pending) {
+ if (!RejectUnwrappedPromiseWithError(cx, unwrappedPromise, error)) {
+ return false;
+ }
+ } else {
+ // 4.6.{5,6} step 2: Otherwise, set writer.[[<field>]] to a promise rejected
+ // with error.
+ Rooted<JSObject*> rejectedWithError(
+ cx, PromiseObject::unforgeableReject(cx, error));
+ if (!rejectedWithError) {
+ return false;
+ }
+
+ {
+ AutoRealm ar(cx, unwrappedWriter);
+ if (!cx->compartment()->wrap(cx, &rejectedWithError)) {
+ return false;
+ }
+ (unwrappedWriter->*setField)(rejectedWithError);
+ }
+
+ // Directly-unobservable rejected promises aren't collapsed like resolved
+ // promises, and this promise is created in the current realm, so it's
+ // always an actual Promise.
+ unwrappedPromise = &rejectedWithError->as<PromiseObject>();
+ }
+
+ // 4.6.{5,6} step 3: Set writer.[[<field>]].[[PromiseIsHandled]] to true.
+ js::SetSettledPromiseIsHandled(cx, unwrappedPromise);
+ return true;
+}
+
+/**
+ * Streams spec, 4.6.5.
+ * WritableStreamDefaultWriterEnsureClosedPromiseRejected( writer, error )
+ */
+MOZ_MUST_USE bool js::WritableStreamDefaultWriterEnsureClosedPromiseRejected(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ Handle<Value> error) {
+ return EnsurePromiseRejected(
+ cx, unwrappedWriter, &WritableStreamDefaultWriter::closedPromise,
+ &WritableStreamDefaultWriter::setClosedPromise, error);
+}
+
+/**
+ * Streams spec, 4.6.6.
+ * WritableStreamDefaultWriterEnsureReadyPromiseRejected( writer, error )
+ */
+MOZ_MUST_USE bool js::WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ Handle<Value> error) {
+ return EnsurePromiseRejected(
+ cx, unwrappedWriter, &WritableStreamDefaultWriter::readyPromise,
+ &WritableStreamDefaultWriter::setReadyPromise, error);
+}
+
+/**
+ * Streams spec, 4.6.7.
+ * WritableStreamDefaultWriterGetDesiredSize ( writer )
+ */
+bool js::WritableStreamDefaultWriterGetDesiredSize(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ MutableHandle<Value> size) {
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ const WritableStream* unwrappedStream =
+ UnwrapStreamFromWriter(cx, unwrappedWriter);
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 2: Let state be stream.[[state]].
+ // Step 3: If state is "errored" or "erroring", return null.
+ if (unwrappedStream->errored() || unwrappedStream->erroring()) {
+ size.setNull();
+ }
+ // Step 4: If state is "closed", return 0.
+ else if (unwrappedStream->closed()) {
+ size.setInt32(0);
+ }
+ // Step 5: Return
+ // ! WritableStreamDefaultControllerGetDesiredSize(
+ // stream.[[writableStreamController]]).
+ else {
+ size.setNumber(WritableStreamDefaultControllerGetDesiredSize(
+ unwrappedStream->controller()));
+ }
+
+ return true;
+}
+
+/**
+ * Streams spec, 4.6.8.
+ * WritableStreamDefaultWriterRelease ( writer )
+ */
+bool js::WritableStreamDefaultWriterRelease(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter) {
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ // Step 2: Assert: stream is not undefined.
+ MOZ_ASSERT(unwrappedWriter->hasStream());
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
+ if (!unwrappedStream) {
+ return false;
+ }
+
+ // Step 3: Assert: stream.[[writer]] is writer.
+#ifdef DEBUG
+ {
+ WritableStreamDefaultWriter* unwrappedStreamWriter =
+ UnwrapWriterFromStream(cx, unwrappedStream);
+ if (!unwrappedStreamWriter) {
+ return false;
+ }
+
+ MOZ_ASSERT(unwrappedStreamWriter == unwrappedWriter);
+ }
+#endif
+
+ // Step 4: Let releasedError be a new TypeError.
+ Rooted<Value> releasedError(cx);
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_CANT_RELEASE_ALREADY_CLOSED);
+ if (!cx->isExceptionPending() || !GetAndClearException(cx, &releasedError)) {
+ return false;
+ }
+
+ // Step 5: Perform
+ // ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ // writer, releasedError).
+ if (!WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ cx, unwrappedWriter, releasedError)) {
+ return false;
+ }
+
+ // Step 6: Perform
+ // ! WritableStreamDefaultWriterEnsureClosedPromiseRejected(
+ // writer, releasedError).
+ if (!WritableStreamDefaultWriterEnsureClosedPromiseRejected(
+ cx, unwrappedWriter, releasedError)) {
+ return false;
+ }
+
+ // Step 7: Set stream.[[writer]] to undefined.
+ unwrappedStream->clearWriter();
+
+ // Step 8: Set writer.[[ownerWritableStream]] to undefined.
+ unwrappedWriter->clearStream();
+ return true;
+}
+
+/**
+ * Streams spec, 4.6.9.
+ * WritableStreamDefaultWriterWrite ( writer, chunk )
+ */
+PromiseObject* js::WritableStreamDefaultWriterWrite(
+ JSContext* cx, Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ Handle<Value> chunk) {
+ cx->check(chunk);
+
+ // Step 1: Let stream be writer.[[ownerWritableStream]].
+ // Step 2: Assert: stream is not undefined.
+ MOZ_ASSERT(unwrappedWriter->hasStream());
+ Rooted<WritableStream*> unwrappedStream(
+ cx, UnwrapStreamFromWriter(cx, unwrappedWriter));
+ if (!unwrappedStream) {
+ return nullptr;
+ }
+
+ // Step 3: Let controller be stream.[[writableStreamController]].
+ Rooted<WritableStreamDefaultController*> unwrappedController(
+ cx, unwrappedStream->controller());
+
+ // Step 4: Let chunkSize be
+ // ! WritableStreamDefaultControllerGetChunkSize(controller, chunk).
+ Rooted<Value> chunkSize(cx);
+ if (!WritableStreamDefaultControllerGetChunkSize(cx, unwrappedController,
+ chunk, &chunkSize)) {
+ return nullptr;
+ }
+ cx->check(chunkSize);
+
+ // Step 5: If stream is not equal to writer.[[ownerWritableStream]], return a
+ // promise rejected with a TypeError exception.
+ // (This is just an obscure way of saying "If step 4 caused writer's lock on
+ // stream to be released", or concretely, "If writer.[[ownerWritableStream]]
+ // is [now, newly] undefined".)
+ if (!unwrappedWriter->hasStream()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_RELEASED_DURING_WRITE);
+ return PromiseRejectedWithPendingError(cx);
+ }
+
+ auto RejectWithStoredError =
+ [](JSContext* cx,
+ Handle<WritableStream*> unwrappedStream) -> PromiseObject* {
+ Rooted<Value> storedError(cx, unwrappedStream->storedError());
+ if (!cx->compartment()->wrap(cx, &storedError)) {
+ return nullptr;
+ }
+
+ return PromiseObject::unforgeableReject(cx, storedError);
+ };
+
+ // Step 6: Let state be stream.[[state]].
+ // Step 7: If state is "errored", return a promise rejected with
+ // stream.[[storedError]].
+ if (unwrappedStream->errored()) {
+ return RejectWithStoredError(cx, unwrappedStream);
+ }
+
+ // Step 8: If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state
+ // is "closed", return a promise rejected with a TypeError exception
+ // indicating that the stream is closing or closed.
+ if (WritableStreamCloseQueuedOrInFlight(unwrappedStream) ||
+ unwrappedStream->closed()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_WRITABLESTREAM_WRITE_CLOSING_OR_CLOSED);
+ return PromiseRejectedWithPendingError(cx);
+ }
+
+ // Step 9: If state is "erroring", return a promise rejected with
+ // stream.[[storedError]].
+ if (unwrappedStream->erroring()) {
+ return RejectWithStoredError(cx, unwrappedStream);
+ }
+
+ // Step 10: Assert: state is "writable".
+ MOZ_ASSERT(unwrappedStream->writable());
+
+ // Step 11: Let promise be ! WritableStreamAddWriteRequest(stream).
+ Rooted<PromiseObject*> promise(
+ cx, WritableStreamAddWriteRequest(cx, unwrappedStream));
+ if (!promise) {
+ return nullptr;
+ }
+
+ // Step 12: Perform
+ // ! WritableStreamDefaultControllerWrite(controller, chunk,
+ // chunkSize).
+ if (!WritableStreamDefaultControllerWrite(cx, unwrappedController, chunk,
+ chunkSize)) {
+ return nullptr;
+ }
+
+ // Step 13: Return promise.
+ return promise;
+}
diff --git a/js/src/builtin/streams/WritableStreamWriterOperations.h b/js/src/builtin/streams/WritableStreamWriterOperations.h
new file mode 100644
index 0000000000..241b166354
--- /dev/null
+++ b/js/src/builtin/streams/WritableStreamWriterOperations.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+/* Writable stream writer abstract operations. */
+
+#ifndef builtin_streams_WritableStreamWriterOperations_h
+#define builtin_streams_WritableStreamWriterOperations_h
+
+#include "mozilla/Attributes.h" // MOZ_MUST_USE
+
+#include "js/RootingAPI.h" // JS::{,Mutable}Handle
+#include "js/Value.h" // JS::Value
+
+struct JS_PUBLIC_API JSContext;
+class JS_PUBLIC_API JSObject;
+
+namespace js {
+
+class PromiseObject;
+class WritableStreamDefaultWriter;
+
+extern JSObject* WritableStreamDefaultWriterAbort(
+ JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ JS::Handle<JS::Value> reason);
+
+extern PromiseObject* WritableStreamDefaultWriterClose(
+ JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter);
+
+extern PromiseObject* WritableStreamDefaultWriterCloseWithErrorPropagation(
+ JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultWriterEnsureClosedPromiseRejected(
+ JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ JS::Handle<JS::Value> error);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ JS::Handle<JS::Value> error);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultWriterGetDesiredSize(
+ JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ JS::MutableHandle<JS::Value> size);
+
+extern MOZ_MUST_USE bool WritableStreamDefaultWriterRelease(
+ JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter);
+
+extern PromiseObject* WritableStreamDefaultWriterWrite(
+ JSContext* cx, JS::Handle<WritableStreamDefaultWriter*> unwrappedWriter,
+ JS::Handle<JS::Value> chunk);
+
+} // namespace js
+
+#endif // builtin_streams_WritableStreamWriterOperations_h