summaryrefslogtreecommitdiffstats
path: root/dom/streams
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/streams
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/streams')
-rw-r--r--dom/streams/BaseQueuingStrategy.h37
-rw-r--r--dom/streams/ByteLengthQueuingStrategy.cpp100
-rw-r--r--dom/streams/ByteLengthQueuingStrategy.h49
-rw-r--r--dom/streams/ByteStreamHelpers.cpp106
-rw-r--r--dom/streams/ByteStreamHelpers.h40
-rw-r--r--dom/streams/CountQueuingStrategy.cpp103
-rw-r--r--dom/streams/CountQueuingStrategy.h48
-rw-r--r--dom/streams/QueueWithSizes.h140
-rw-r--r--dom/streams/ReadIntoRequest.h48
-rw-r--r--dom/streams/ReadRequest.h42
-rw-r--r--dom/streams/ReadableByteStreamController.cpp2087
-rw-r--r--dom/streams/ReadableByteStreamController.h230
-rw-r--r--dom/streams/ReadableStream.cpp1521
-rw-r--r--dom/streams/ReadableStream.h292
-rw-r--r--dom/streams/ReadableStreamBYOBReader.cpp359
-rw-r--r--dom/streams/ReadableStreamBYOBReader.h91
-rw-r--r--dom/streams/ReadableStreamBYOBRequest.cpp129
-rw-r--r--dom/streams/ReadableStreamBYOBRequest.h68
-rw-r--r--dom/streams/ReadableStreamController.h73
-rw-r--r--dom/streams/ReadableStreamDefaultController.cpp653
-rw-r--r--dom/streams/ReadableStreamDefaultController.h163
-rw-r--r--dom/streams/ReadableStreamDefaultReader.cpp439
-rw-r--r--dom/streams/ReadableStreamDefaultReader.h122
-rw-r--r--dom/streams/ReadableStreamGenericReader.h77
-rw-r--r--dom/streams/ReadableStreamPipeTo.cpp977
-rw-r--r--dom/streams/ReadableStreamPipeTo.h34
-rw-r--r--dom/streams/ReadableStreamTee.cpp1011
-rw-r--r--dom/streams/ReadableStreamTee.h91
-rw-r--r--dom/streams/StreamUtils.cpp34
-rw-r--r--dom/streams/StreamUtils.h57
-rw-r--r--dom/streams/TeeState.cpp87
-rw-r--r--dom/streams/TeeState.h179
-rw-r--r--dom/streams/Transferable.cpp1068
-rw-r--r--dom/streams/TransformStream.cpp706
-rw-r--r--dom/streams/TransformStream.h115
-rw-r--r--dom/streams/TransformStreamDefaultController.cpp238
-rw-r--r--dom/streams/TransformStreamDefaultController.h74
-rw-r--r--dom/streams/TransformerCallbackHelpers.cpp111
-rw-r--r--dom/streams/TransformerCallbackHelpers.h102
-rw-r--r--dom/streams/UnderlyingSinkCallbackHelpers.cpp274
-rw-r--r--dom/streams/UnderlyingSinkCallbackHelpers.h202
-rw-r--r--dom/streams/UnderlyingSourceCallbackHelpers.cpp584
-rw-r--r--dom/streams/UnderlyingSourceCallbackHelpers.h330
-rw-r--r--dom/streams/WritableStream.cpp811
-rw-r--r--dom/streams/WritableStream.h269
-rw-r--r--dom/streams/WritableStreamDefaultController.cpp564
-rw-r--r--dom/streams/WritableStreamDefaultController.h178
-rw-r--r--dom/streams/WritableStreamDefaultWriter.cpp544
-rw-r--r--dom/streams/WritableStreamDefaultWriter.h112
-rw-r--r--dom/streams/crashtests/1764222.html10
-rw-r--r--dom/streams/crashtests/crashtests.list1
-rw-r--r--dom/streams/moz.build72
-rw-r--r--dom/streams/test/xpcshell/bug-1387503-1.js44
-rw-r--r--dom/streams/test/xpcshell/bug-1387503-2.js52
-rw-r--r--dom/streams/test/xpcshell/bug-1503406.js20
-rw-r--r--dom/streams/test/xpcshell/bug-1773237.js11
-rw-r--r--dom/streams/test/xpcshell/dom_stream_prototype_test.js29
-rw-r--r--dom/streams/test/xpcshell/fetch.js35
-rw-r--r--dom/streams/test/xpcshell/head.js45
-rw-r--r--dom/streams/test/xpcshell/large-pipeto.js101
-rw-r--r--dom/streams/test/xpcshell/proper-realm-cancel.js5
-rw-r--r--dom/streams/test/xpcshell/proper-realm-pull.js6
-rw-r--r--dom/streams/test/xpcshell/response.js30
-rw-r--r--dom/streams/test/xpcshell/subclassing.js123
-rw-r--r--dom/streams/test/xpcshell/too-big-array-buffer.js15
-rw-r--r--dom/streams/test/xpcshell/xpcshell.toml32
66 files changed, 16400 insertions, 0 deletions
diff --git a/dom/streams/BaseQueuingStrategy.h b/dom/streams/BaseQueuingStrategy.h
new file mode 100644
index 0000000000..eef7147c08
--- /dev/null
+++ b/dom/streams/BaseQueuingStrategy.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_BaseQueuingStrategy_h
+#define mozilla_dom_BaseQueuingStrategy_h
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla::dom {
+
+class BaseQueuingStrategy : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(BaseQueuingStrategy)
+
+ BaseQueuingStrategy(nsISupports* aGlobal, double aHighWaterMark)
+ : mGlobal(do_QueryInterface(aGlobal)), mHighWaterMark(aHighWaterMark) {}
+
+ nsIGlobalObject* GetParentObject() const;
+
+ double HighWaterMark() const { return mHighWaterMark; }
+
+ protected:
+ virtual ~BaseQueuingStrategy() = default;
+
+ protected:
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ double mHighWaterMark = 0.0;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/ByteLengthQueuingStrategy.cpp b/dom/streams/ByteLengthQueuingStrategy.cpp
new file mode 100644
index 0000000000..858f6d664b
--- /dev/null
+++ b/dom/streams/ByteLengthQueuingStrategy.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ByteLengthQueuingStrategy.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+
+#include "js/TypeDecls.h"
+#include "js/PropertyAndElement.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED(ByteLengthQueuingStrategy,
+ BaseQueuingStrategy)
+NS_IMPL_ADDREF_INHERITED(ByteLengthQueuingStrategy, BaseQueuingStrategy)
+NS_IMPL_RELEASE_INHERITED(ByteLengthQueuingStrategy, BaseQueuingStrategy)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ByteLengthQueuingStrategy)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(BaseQueuingStrategy)
+
+/* static */
+already_AddRefed<ByteLengthQueuingStrategy>
+ByteLengthQueuingStrategy::Constructor(const GlobalObject& aGlobal,
+ const QueuingStrategyInit& aInit) {
+ RefPtr<ByteLengthQueuingStrategy> strategy = new ByteLengthQueuingStrategy(
+ aGlobal.GetAsSupports(), aInit.mHighWaterMark);
+ return strategy.forget();
+}
+
+JSObject* ByteLengthQueuingStrategy::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return ByteLengthQueuingStrategy_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+static bool ByteLengthQueuingStrategySize(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ // https://streams.spec.whatwg.org/#blqs-internal-slots
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: Return ? GetV(chunk, "byteLength").
+ JS::Rooted<JSObject*> chunkObj(cx, JS::ToObject(cx, args.get(0)));
+ if (!chunkObj) {
+ return false;
+ }
+
+ return JS_GetProperty(cx, chunkObj, "byteLength", args.rval());
+}
+
+// https://streams.spec.whatwg.org/#blqs-size
+already_AddRefed<Function> ByteLengthQueuingStrategy::GetSize(
+ ErrorResult& aRv) {
+ // Step 1. Return this's relevant global object's ByteLength queuing strategy
+ // size function.
+ if (RefPtr<Function> fun =
+ mGlobal->GetByteLengthQueuingStrategySizeFunction()) {
+ return fun.forget();
+ }
+
+ // Note: Instead of eagerly allocating a size function for every global object
+ // we do it lazily once in this getter.
+ // After this point the steps refer to:
+ // https://streams.spec.whatwg.org/#byte-length-queuing-strategy-size-function
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobal)) {
+ aRv.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Step 1. Let steps be the following steps, given chunk
+ // Note: See ByteLengthQueuingStrategySize instead.
+
+ // Step 2. Let F be !CreateBuiltinFunction(steps, 1, "size", « »,
+ // globalObject’s relevant Realm).
+ JS::Rooted<JSFunction*> sizeFunction(
+ cx, JS_NewFunction(cx, ByteLengthQueuingStrategySize, 1, 0, "size"));
+ if (!sizeFunction) {
+ aRv.StealExceptionFromJSContext(cx);
+ return nullptr;
+ }
+
+ // Step 3. Set globalObject’s byte length queuing strategy size function to
+ // a Function that represents a reference to F,
+ // with callback context equal to globalObject’s relevant settings object.
+ JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(sizeFunction));
+ JS::Rooted<JSObject*> global(cx, mGlobal->GetGlobalJSObject());
+ RefPtr<Function> function = new Function(cx, funObj, global, mGlobal);
+ mGlobal->SetByteLengthQueuingStrategySizeFunction(function);
+
+ return function.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/ByteLengthQueuingStrategy.h b/dom/streams/ByteLengthQueuingStrategy.h
new file mode 100644
index 0000000000..64fe3e4528
--- /dev/null
+++ b/dom/streams/ByteLengthQueuingStrategy.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ByteLengthQueuingStrategy_h
+#define mozilla_dom_ByteLengthQueuingStrategy_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BaseQueuingStrategy.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla::dom {
+class ByteLengthQueuingStrategy final : public BaseQueuingStrategy,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_INHERITED(
+ ByteLengthQueuingStrategy, BaseQueuingStrategy)
+
+ public:
+ explicit ByteLengthQueuingStrategy(nsISupports* aGlobal,
+ double aHighWaterMark)
+ : BaseQueuingStrategy(aGlobal, aHighWaterMark) {}
+
+ protected:
+ ~ByteLengthQueuingStrategy() override = default;
+
+ public:
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<ByteLengthQueuingStrategy> Constructor(
+ const GlobalObject& aGlobal, const QueuingStrategyInit& aInit);
+
+ already_AddRefed<Function> GetSize(ErrorResult& aRv);
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/ByteStreamHelpers.cpp b/dom/streams/ByteStreamHelpers.cpp
new file mode 100644
index 0000000000..5022bb2c72
--- /dev/null
+++ b/dom/streams/ByteStreamHelpers.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ByteStreamHelpers.h"
+#include "mozilla/dom/ReadableByteStreamController.h"
+#include "js/ArrayBuffer.h"
+#include "js/RootingAPI.h"
+#include "js/experimental/TypedData.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla::dom {
+
+// https://streams.spec.whatwg.org/#transfer-array-buffer
+// As some parts of the specifcation want to use the abrupt completion value,
+// this function may leave a pending exception if it returns nullptr.
+JSObject* TransferArrayBuffer(JSContext* aCx, JS::Handle<JSObject*> aObject) {
+ MOZ_ASSERT(JS::IsArrayBufferObject(aObject));
+
+ // Step 1.
+ MOZ_ASSERT(!JS::IsDetachedArrayBufferObject(aObject));
+
+ // Step 3 (Reordered)
+ size_t bufferLength = JS::GetArrayBufferByteLength(aObject);
+
+ // Step 2 (Reordered)
+ UniquePtr<void, JS::FreePolicy> bufferData{
+ JS::StealArrayBufferContents(aCx, aObject)};
+
+ // Step 4.
+ if (!JS::DetachArrayBuffer(aCx, aObject)) {
+ return nullptr;
+ }
+
+ // Step 5.
+ return JS::NewArrayBufferWithContents(aCx, bufferLength,
+ std::move(bufferData));
+}
+
+// https://streams.spec.whatwg.org/#can-transfer-array-buffer
+bool CanTransferArrayBuffer(JSContext* aCx, JS::Handle<JSObject*> aObject,
+ ErrorResult& aRv) {
+ // Step 1. Assert: Type(O) is Object. (Implicit in types)
+ // Step 2. Assert: O has an [[ArrayBufferData]] internal slot.
+ MOZ_ASSERT(JS::IsArrayBufferObject(aObject));
+
+ // Step 3. If ! IsDetachedBuffer(O) is true, return false.
+ if (JS::IsDetachedArrayBufferObject(aObject)) {
+ return false;
+ }
+
+ // Step 4. If SameValue(O.[[ArrayBufferDetachKey]], undefined) is false,
+ // return false.
+ // Step 5. Return true.
+ // Note: WASM memories are the only buffers that would qualify
+ // as having an [[ArrayBufferDetachKey]] which is not undefined.
+ bool hasDefinedArrayBufferDetachKey = false;
+ if (!JS::HasDefinedArrayBufferDetachKey(aCx, aObject,
+ &hasDefinedArrayBufferDetachKey)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return false;
+ }
+ return !hasDefinedArrayBufferDetachKey;
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-cloneasuint8array
+JSObject* CloneAsUint8Array(JSContext* aCx, JS::Handle<JSObject*> aObject) {
+ // Step 1. Assert: Type(O) is Object. Implicit.
+ // Step 2. Assert: O has an [[ViewedArrayBuffer]] internal slot.
+ MOZ_ASSERT(JS_IsArrayBufferViewObject(aObject));
+
+ // Step 3. Assert: !IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false.
+ bool isShared;
+ JS::Rooted<JSObject*> viewedArrayBuffer(
+ aCx, JS_GetArrayBufferViewBuffer(aCx, aObject, &isShared));
+ if (!viewedArrayBuffer) {
+ return nullptr;
+ }
+ MOZ_ASSERT(!JS::IsDetachedArrayBufferObject(viewedArrayBuffer));
+
+ // Step 4. Let buffer be ?CloneArrayBuffer(O.[[ViewedArrayBuffer]],
+ // O.[[ByteOffset]], O.[[ByteLength]], %ArrayBuffer%).
+ size_t byteOffset = JS_GetTypedArrayByteOffset(aObject);
+ size_t byteLength = JS_GetTypedArrayByteLength(aObject);
+ JS::Rooted<JSObject*> buffer(
+ aCx,
+ JS::ArrayBufferClone(aCx, viewedArrayBuffer, byteOffset, byteLength));
+ if (!buffer) {
+ return nullptr;
+ }
+
+ // Step 5. Let array be ! Construct(%Uint8Array%, « buffer »).
+ JS::Rooted<JSObject*> array(
+ aCx, JS_NewUint8ArrayWithBuffer(aCx, buffer, 0,
+ static_cast<int64_t>(byteLength)));
+ if (!array) {
+ return nullptr;
+ }
+
+ // Step 6. Return array.
+ return array;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/ByteStreamHelpers.h b/dom/streams/ByteStreamHelpers.h
new file mode 100644
index 0000000000..8557c9b16b
--- /dev/null
+++ b/dom/streams/ByteStreamHelpers.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ByteStreamHelpers_h
+#define mozilla_dom_ByteStreamHelpers_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/ErrorResult.h"
+#include "UnderlyingSourceCallbackHelpers.h"
+
+namespace mozilla::dom {
+
+class ReadableStream;
+class BodyStreamHolder;
+
+// https://streams.spec.whatwg.org/#transfer-array-buffer
+//
+// As some parts of the specifcation want to use the abrupt completion value,
+// this function may leave a pending exception if it returns nullptr.
+JSObject* TransferArrayBuffer(JSContext* aCx, JS::Handle<JSObject*> aObject);
+
+bool CanTransferArrayBuffer(JSContext* aCx, JS::Handle<JSObject*> aObject,
+ ErrorResult& aRv);
+
+// If this returns null, it will leave a pending exception on aCx which
+// must be handled by the caller (in the spec this is always the case
+// currently).
+JSObject* CloneAsUint8Array(JSContext* aCx, JS::Handle<JSObject*> aObject);
+
+MOZ_CAN_RUN_SCRIPT void
+SetUpReadableByteStreamControllerFromBodyStreamUnderlyingSource(
+ JSContext* aCx, ReadableStream* aStream,
+ BodyStreamHolder* aUnderlyingSource, ErrorResult& aRv);
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/CountQueuingStrategy.cpp b/dom/streams/CountQueuingStrategy.cpp
new file mode 100644
index 0000000000..72f000e604
--- /dev/null
+++ b/dom/streams/CountQueuingStrategy.cpp
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CountQueuingStrategy.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION(BaseQueuingStrategy, mGlobal)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(BaseQueuingStrategy)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(BaseQueuingStrategy)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BaseQueuingStrategy)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED(CountQueuingStrategy,
+ BaseQueuingStrategy)
+NS_IMPL_ADDREF_INHERITED(CountQueuingStrategy, BaseQueuingStrategy)
+NS_IMPL_RELEASE_INHERITED(CountQueuingStrategy, BaseQueuingStrategy)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CountQueuingStrategy)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(BaseQueuingStrategy)
+
+/* static */
+already_AddRefed<CountQueuingStrategy> CountQueuingStrategy::Constructor(
+ const GlobalObject& aGlobal, const QueuingStrategyInit& aInit) {
+ RefPtr<CountQueuingStrategy> strategy =
+ new CountQueuingStrategy(aGlobal.GetAsSupports(), aInit.mHighWaterMark);
+ return strategy.forget();
+}
+
+nsIGlobalObject* BaseQueuingStrategy::GetParentObject() const {
+ return mGlobal;
+}
+
+JSObject* CountQueuingStrategy::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return CountQueuingStrategy_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://streams.spec.whatwg.org/#count-queuing-strategy-size-function
+static bool CountQueuingStrategySize(JSContext* aCx, unsigned aArgc,
+ JS::Value* aVp) {
+ JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
+
+ // Step 1.1. Return 1.
+ args.rval().setInt32(1);
+ return true;
+}
+
+// https://streams.spec.whatwg.org/#cqs-size
+already_AddRefed<Function> CountQueuingStrategy::GetSize(ErrorResult& aRv) {
+ // Step 1. Return this's relevant global object's count queuing strategy
+ // size function.
+ if (RefPtr<Function> fun = mGlobal->GetCountQueuingStrategySizeFunction()) {
+ return fun.forget();
+ }
+
+ // Note: Instead of eagerly allocating a size function for every global object
+ // we do it lazily once in this getter.
+ // After this point the steps refer to:
+ // https://streams.spec.whatwg.org/#count-queuing-strategy-size-function.
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobal)) {
+ aRv.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Step 1. Let steps be the following steps:
+ // Note: See CountQueuingStrategySize instead.
+
+ // Step 2. Let F be
+ // ! CreateBuiltinFunction(steps, 0, "size", « »,
+ // globalObject’s relevant Realm).
+ JS::Rooted<JSFunction*> sizeFunction(
+ cx, JS_NewFunction(cx, CountQueuingStrategySize, 0, 0, "size"));
+ if (!sizeFunction) {
+ aRv.StealExceptionFromJSContext(cx);
+ return nullptr;
+ }
+
+ // Step 3. Set globalObject’s count queuing strategy size function to
+ // a Function that represents a reference to F,
+ // with callback context equal to globalObject’s relevant settings object.
+ JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(sizeFunction));
+ JS::Rooted<JSObject*> global(cx, mGlobal->GetGlobalJSObject());
+ RefPtr<Function> function = new Function(cx, funObj, global, mGlobal);
+ mGlobal->SetCountQueuingStrategySizeFunction(function);
+
+ return function.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/CountQueuingStrategy.h b/dom/streams/CountQueuingStrategy.h
new file mode 100644
index 0000000000..bbf109efbc
--- /dev/null
+++ b/dom/streams/CountQueuingStrategy.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_CountQueuingStrategy_h
+#define mozilla_dom_CountQueuingStrategy_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BaseQueuingStrategy.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla::dom {
+
+class CountQueuingStrategy final : public BaseQueuingStrategy,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_INHERITED(CountQueuingStrategy,
+ BaseQueuingStrategy)
+
+ public:
+ explicit CountQueuingStrategy(nsISupports* aGlobal, double aHighWaterMark)
+ : BaseQueuingStrategy(aGlobal, aHighWaterMark) {}
+
+ protected:
+ ~CountQueuingStrategy() override = default;
+
+ public:
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<CountQueuingStrategy> Constructor(
+ const GlobalObject& aGlobal, const QueuingStrategyInit& aInit);
+
+ already_AddRefed<Function> GetSize(ErrorResult& aRv);
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_CountQueuingStrategy_h
diff --git a/dom/streams/QueueWithSizes.h b/dom/streams/QueueWithSizes.h
new file mode 100644
index 0000000000..5a5bb49036
--- /dev/null
+++ b/dom/streams/QueueWithSizes.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_QueueWithSizes_h
+#define mozilla_dom_QueueWithSizes_h
+
+#include <cmath>
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+// Note: Consumers of QueueWithSize need to ensure it is traced. See
+// ReadableStreamDefaultController.cpp for an example.
+
+struct ValueWithSize : LinkedListElement<ValueWithSize> {
+ ValueWithSize(JS::Handle<JS::Value> aValue, double aSize)
+ : mValue(aValue), mSize(aSize){};
+
+ JS::Heap<JS::Value> mValue;
+ double mSize = 0.0f;
+};
+
+// This type is a little tricky lifetime wise: Despite the fact that we're
+// talking about linked list of VaueWithSize, what is actually stored in the
+// list are dynamically allocated ValueWithSize*. As a result, these have to be
+// deleted. There are two ways this lifetime is managed:
+//
+// 1. In DequeueValue we pop the first element into a UniquePtr, so that it is
+// correctly cleaned up at destruction time.
+// 2. We use an AutoCleanLinkedList which will delete elements when destroyed
+// or `clear`ed.
+using QueueWithSizes = AutoCleanLinkedList<ValueWithSize>;
+
+// https://streams.spec.whatwg.org/#is-non-negative-number
+inline bool IsNonNegativeNumber(double v) {
+ // Step 1. Implicit.
+ // Step 2.
+ if (std::isnan(v)) {
+ return false;
+ }
+
+ // Step 3.
+ return !(v < 0);
+}
+
+// https://streams.spec.whatwg.org/#enqueue-value-with-size
+template <class QueueContainingClass>
+inline void EnqueueValueWithSize(QueueContainingClass aContainer,
+ JS::Handle<JS::Value> aValue, double aSize,
+ ErrorResult& aRv) {
+ // Step 1. Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots. (Implicit by template instantiation.)
+ // Step 2. If ! IsNonNegativeNumber(size) is false, throw a RangeError
+ // exception.
+ if (!IsNonNegativeNumber(aSize)) {
+ aRv.ThrowRangeError("invalid size");
+ return;
+ }
+
+ // Step 3. If size is +∞, throw a RangeError exception.
+ if (std::isinf(aSize)) {
+ aRv.ThrowRangeError("Infinite queue size");
+ return;
+ }
+
+ // Step 4. Append a new value-with-size with value value and size size to
+ // container.[[queue]].
+ // (See the comment on QueueWithSizes for the lifetime reasoning around this
+ // allocation.)
+ ValueWithSize* valueWithSize = new ValueWithSize(aValue, aSize);
+ aContainer->Queue().insertBack(valueWithSize);
+ // Step 5. Set container.[[queueTotalSize]] to container.[[queueTotalSize]] +
+ // size.
+ aContainer->SetQueueTotalSize(aContainer->QueueTotalSize() + aSize);
+}
+
+// https://streams.spec.whatwg.org/#dequeue-value
+template <class QueueContainingClass>
+inline void DequeueValue(QueueContainingClass aContainer,
+ JS::MutableHandle<JS::Value> aResultValue) {
+ // Step 1. Implicit via template instantiation.
+ // Step 2.
+ MOZ_ASSERT(!aContainer->Queue().isEmpty());
+
+ // Step 3+4
+ // UniquePtr to ensure memory is freed.
+ UniquePtr<ValueWithSize> valueWithSize(aContainer->Queue().popFirst());
+
+ // Step 5.
+ aContainer->SetQueueTotalSize(aContainer->QueueTotalSize() -
+ valueWithSize->mSize);
+
+ // Step 6.
+ if (aContainer->QueueTotalSize() < 0) {
+ aContainer->SetQueueTotalSize(0);
+ }
+
+ // Step 7.
+ aResultValue.set(valueWithSize->mValue);
+}
+
+// https://streams.spec.whatwg.org/#peek-queue-value
+template <class QueueContainingClass>
+inline void PeekQueueValue(QueueContainingClass aContainer,
+ JS::MutableHandle<JS::Value> aResultValue) {
+ // Step 1. Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots.
+ // Step 2. Assert: container.[[queue]] is not empty.
+ MOZ_ASSERT(!aContainer->Queue().isEmpty());
+
+ // Step 3. Let valueWithSize be container.[[queue]][0].
+ ValueWithSize* valueWithSize = aContainer->Queue().getFirst();
+
+ // Step 4. Return valueWithSize’s value.
+ aResultValue.set(valueWithSize->mValue);
+}
+
+// https://streams.spec.whatwg.org/#reset-queue
+template <class QueueContainingClass>
+inline void ResetQueue(QueueContainingClass aContainer) {
+ // Step 1. Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots. (implicit)
+
+ // Step 2. Set container.[[queue]] to a new empty list.
+ aContainer->Queue().clear();
+
+ // Step 3. Set container.[[queueTotalSize]] to 0.
+ aContainer->SetQueueTotalSize(0.0);
+}
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/ReadIntoRequest.h b/dom/streams/ReadIntoRequest.h
new file mode 100644
index 0000000000..9be3d2c027
--- /dev/null
+++ b/dom/streams/ReadIntoRequest.h
@@ -0,0 +1,48 @@
+
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadIntoRequest_h
+#define mozilla_dom_ReadIntoRequest_h
+
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/RefPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+// This list element needs to be cycle collected by owning structures.
+struct ReadIntoRequest : public nsISupports,
+ public LinkedListElement<RefPtr<ReadIntoRequest>> {
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ReadIntoRequest)
+
+ // An algorithm taking a chunk, called when a chunk is available for reading
+ virtual void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) = 0;
+
+ // An algorithm taking a chunk or undefined, called when no chunks are
+ // available because the stream is closed
+ MOZ_CAN_RUN_SCRIPT
+ virtual void CloseSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) = 0;
+
+ // An algorithm taking a JavaScript value, called when no chunks are available
+ // because the stream is errored
+ virtual void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> e,
+ ErrorResult& aRv) = 0;
+
+ protected:
+ virtual ~ReadIntoRequest() = default;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/ReadRequest.h b/dom/streams/ReadRequest.h
new file mode 100644
index 0000000000..8de991b4b9
--- /dev/null
+++ b/dom/streams/ReadRequest.h
@@ -0,0 +1,42 @@
+
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadRequest_h
+#define mozilla_dom_ReadRequest_h
+
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/RefPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+// List Owners must traverse this class.
+struct ReadRequest : public nsISupports,
+ public LinkedListElement<RefPtr<ReadRequest>> {
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ReadRequest)
+
+ // PipeToReadRequest::ChunkSteps can run script, for example.
+ MOZ_CAN_RUN_SCRIPT virtual void ChunkSteps(JSContext* aCx,
+ JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) = 0;
+ MOZ_CAN_RUN_SCRIPT virtual void CloseSteps(JSContext* aCx,
+ ErrorResult& aRv) = 0;
+ virtual void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> e,
+ ErrorResult& aRv) = 0;
+
+ protected:
+ virtual ~ReadRequest() = default;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/ReadableByteStreamController.cpp b/dom/streams/ReadableByteStreamController.cpp
new file mode 100644
index 0000000000..1ce2ae4331
--- /dev/null
+++ b/dom/streams/ReadableByteStreamController.cpp
@@ -0,0 +1,2087 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ReadableByteStreamController.h"
+
+#include "ReadIntoRequest.h"
+#include "js/ArrayBuffer.h"
+#include "js/ErrorReport.h"
+#include "js/Exception.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "js/ValueArray.h"
+#include "js/experimental/TypedData.h"
+#include "js/friend/ErrorMessages.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/ByteStreamHelpers.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/dom/ReadableByteStreamControllerBinding.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamBYOBReader.h"
+#include "mozilla/dom/ReadableStreamBYOBRequest.h"
+#include "mozilla/dom/ReadableStreamController.h"
+#include "mozilla/dom/ReadableStreamDefaultController.h"
+#include "mozilla/dom/ReadableStreamDefaultReader.h"
+#include "mozilla/dom/ReadableStreamGenericReader.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+
+#include <algorithm> // std::min
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-queue-entry
+struct ReadableByteStreamQueueEntry
+ : LinkedListElement<RefPtr<ReadableByteStreamQueueEntry>> {
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(
+ ReadableByteStreamQueueEntry)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(
+ ReadableByteStreamQueueEntry)
+
+ ReadableByteStreamQueueEntry(JS::Handle<JSObject*> aBuffer,
+ size_t aByteOffset, size_t aByteLength)
+ : mBuffer(aBuffer), mByteOffset(aByteOffset), mByteLength(aByteLength) {
+ mozilla::HoldJSObjects(this);
+ }
+
+ JSObject* Buffer() const { return mBuffer; }
+ void SetBuffer(JS::Handle<JSObject*> aBuffer) { mBuffer = aBuffer; }
+
+ size_t ByteOffset() const { return mByteOffset; }
+ void SetByteOffset(size_t aByteOffset) { mByteOffset = aByteOffset; }
+
+ size_t ByteLength() const { return mByteLength; }
+ void SetByteLength(size_t aByteLength) { mByteLength = aByteLength; }
+
+ private:
+ // An ArrayBuffer, which will be a transferred version of the one originally
+ // supplied by the underlying byte source.
+ JS::Heap<JSObject*> mBuffer;
+
+ // A nonnegative integer number giving the byte offset derived from the view
+ // originally supplied by the underlying byte source
+ size_t mByteOffset = 0;
+
+ // A nonnegative integer number giving the byte length derived from the view
+ // originally supplied by the underlying byte source
+ size_t mByteLength = 0;
+
+ ~ReadableByteStreamQueueEntry() { mozilla::DropJSObjects(this); }
+};
+
+NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(ReadableByteStreamQueueEntry, (),
+ (mBuffer));
+
+struct PullIntoDescriptor final
+ : LinkedListElement<RefPtr<PullIntoDescriptor>> {
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PullIntoDescriptor)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PullIntoDescriptor)
+
+ enum Constructor {
+ DataView,
+#define DEFINE_TYPED_CONSTRUCTOR_ENUM_NAMES(ExternalT, NativeT, Name) Name,
+ JS_FOR_EACH_TYPED_ARRAY(DEFINE_TYPED_CONSTRUCTOR_ENUM_NAMES)
+#undef DEFINE_TYPED_CONSTRUCTOR_ENUM_NAMES
+ };
+
+ static Constructor constructorFromScalar(JS::Scalar::Type type) {
+ switch (type) {
+#define REMAP_PULL_INTO_DESCRIPTOR_TYPE(ExternalT, NativeT, Name) \
+ case JS::Scalar::Name: \
+ return Constructor::Name;
+ JS_FOR_EACH_TYPED_ARRAY(REMAP_PULL_INTO_DESCRIPTOR_TYPE)
+#undef REMAP
+
+ case JS::Scalar::Int64:
+ case JS::Scalar::Simd128:
+ case JS::Scalar::MaxTypedArrayViewType:
+ break;
+ }
+ MOZ_CRASH("Unexpected Scalar::Type");
+ }
+
+ PullIntoDescriptor(JS::Handle<JSObject*> aBuffer, uint64_t aBufferByteLength,
+ uint64_t aByteOffset, uint64_t aByteLength,
+ uint64_t aBytesFilled, uint64_t aElementSize,
+ Constructor aViewConstructor, ReaderType aReaderType)
+ : mBuffer(aBuffer),
+ mBufferByteLength(aBufferByteLength),
+ mByteOffset(aByteOffset),
+ mByteLength(aByteLength),
+ mBytesFilled(aBytesFilled),
+ mElementSize(aElementSize),
+ mViewConstructor(aViewConstructor),
+ mReaderType(aReaderType) {
+ mozilla::HoldJSObjects(this);
+ }
+
+ JSObject* Buffer() const { return mBuffer; }
+ void SetBuffer(JS::Handle<JSObject*> aBuffer) { mBuffer = aBuffer; }
+
+ uint64_t BufferByteLength() const { return mBufferByteLength; }
+ void SetBufferByteLength(const uint64_t aBufferByteLength) {
+ mBufferByteLength = aBufferByteLength;
+ }
+
+ uint64_t ByteOffset() const { return mByteOffset; }
+ void SetByteOffset(const uint64_t aByteOffset) { mByteOffset = aByteOffset; }
+
+ uint64_t ByteLength() const { return mByteLength; }
+ void SetByteLength(const uint64_t aByteLength) { mByteLength = aByteLength; }
+
+ uint64_t BytesFilled() const { return mBytesFilled; }
+ void SetBytesFilled(const uint64_t aBytesFilled) {
+ mBytesFilled = aBytesFilled;
+ }
+
+ uint64_t ElementSize() const { return mElementSize; }
+ void SetElementSize(const uint64_t aElementSize) {
+ mElementSize = aElementSize;
+ }
+
+ Constructor ViewConstructor() const { return mViewConstructor; }
+
+ // Note: Named GetReaderType to avoid name conflict with type.
+ ReaderType GetReaderType() const { return mReaderType; }
+ void SetReaderType(const ReaderType aReaderType) {
+ mReaderType = aReaderType;
+ }
+
+ private:
+ JS::Heap<JSObject*> mBuffer;
+ uint64_t mBufferByteLength = 0;
+ uint64_t mByteOffset = 0;
+ uint64_t mByteLength = 0;
+ uint64_t mBytesFilled = 0;
+ uint64_t mElementSize = 0;
+ Constructor mViewConstructor;
+ ReaderType mReaderType;
+
+ ~PullIntoDescriptor() { mozilla::DropJSObjects(this); }
+};
+
+NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(PullIntoDescriptor, (), (mBuffer));
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ReadableByteStreamController)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ReadableByteStreamController,
+ ReadableStreamController)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mByobRequest, mQueue, mPendingPullIntos)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ReadableByteStreamController,
+ ReadableStreamController)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mByobRequest, mQueue, mPendingPullIntos)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ReadableByteStreamController,
+ ReadableStreamController)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(ReadableByteStreamController, ReadableStreamController)
+NS_IMPL_RELEASE_INHERITED(ReadableByteStreamController,
+ ReadableStreamController)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableByteStreamController)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(ReadableStreamController)
+
+ReadableByteStreamController::ReadableByteStreamController(
+ nsIGlobalObject* aGlobal)
+ : ReadableStreamController(aGlobal) {}
+
+ReadableByteStreamController::~ReadableByteStreamController() = default;
+
+void ReadableByteStreamController::ClearQueue() { mQueue.clear(); }
+
+void ReadableByteStreamController::ClearPendingPullIntos() {
+ mPendingPullIntos.clear();
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollergetbyobrequest
+already_AddRefed<ReadableStreamBYOBRequest>
+ReadableByteStreamControllerGetBYOBRequest(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ErrorResult& aRv) {
+ // Step 1.
+ if (!aController->GetByobRequest() &&
+ !aController->PendingPullIntos().isEmpty()) {
+ // Step 1.1:
+ PullIntoDescriptor* firstDescriptor =
+ aController->PendingPullIntos().getFirst();
+
+ // Step 1.2:
+ aRv.MightThrowJSException();
+ JS::Rooted<JSObject*> buffer(aCx, firstDescriptor->Buffer());
+ JS::Rooted<JSObject*> view(
+ aCx, JS_NewUint8ArrayWithBuffer(
+ aCx, buffer,
+ firstDescriptor->ByteOffset() + firstDescriptor->BytesFilled(),
+ int64_t(firstDescriptor->ByteLength() -
+ firstDescriptor->BytesFilled())));
+ if (!view) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 1.3:
+ RefPtr<ReadableStreamBYOBRequest> byobRequest =
+ new ReadableStreamBYOBRequest(aController->GetParentObject());
+
+ // Step 1.4:
+ byobRequest->SetController(aController);
+
+ // Step 1.5:
+ byobRequest->SetView(view);
+
+ // Step 1.6:
+ aController->SetByobRequest(byobRequest);
+ }
+
+ // Step 2.
+ RefPtr<ReadableStreamBYOBRequest> request(aController->GetByobRequest());
+ return request.forget();
+}
+} // namespace streams_abstract
+
+already_AddRefed<ReadableStreamBYOBRequest>
+ReadableByteStreamController::GetByobRequest(JSContext* aCx, ErrorResult& aRv) {
+ return ReadableByteStreamControllerGetBYOBRequest(aCx, this, aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-get-desired-size
+Nullable<double> ReadableByteStreamControllerGetDesiredSize(
+ const ReadableByteStreamController* aController) {
+ // Step 1.
+ ReadableStream::ReaderState state = aController->Stream()->State();
+
+ // Step 2.
+ if (state == ReadableStream::ReaderState::Errored) {
+ return nullptr;
+ }
+
+ // Step 3.
+ if (state == ReadableStream::ReaderState::Closed) {
+ return 0.0;
+ }
+
+ // Step 4.
+ return aController->StrategyHWM() - aController->QueueTotalSize();
+}
+
+Nullable<double> ReadableByteStreamController::GetDesiredSize() const {
+ // Step 1.
+ return ReadableByteStreamControllerGetDesiredSize(this);
+}
+
+JSObject* ReadableByteStreamController::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return ReadableByteStreamController_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-invalidate-byob-request
+static void ReadableByteStreamControllerInvalidateBYOBRequest(
+ ReadableByteStreamController* aController) {
+ // Step 1.
+ if (!aController->GetByobRequest()) {
+ return;
+ }
+
+ // Step 2.
+ aController->GetByobRequest()->SetController(nullptr);
+
+ // Step 3.
+ aController->GetByobRequest()->SetView(nullptr);
+
+ // Step 4.
+ aController->SetByobRequest(nullptr);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-clear-pending-pull-intos
+void ReadableByteStreamControllerClearPendingPullIntos(
+ ReadableByteStreamController* aController) {
+ // Step 1.
+ ReadableByteStreamControllerInvalidateBYOBRequest(aController);
+
+ // Step 2.
+ aController->ClearPendingPullIntos();
+}
+
+// https://streams.spec.whatwg.org/#reset-queue
+void ResetQueue(ReadableByteStreamController* aContainer) {
+ // Step 1. Implied by type.
+ // Step 2.
+ aContainer->ClearQueue();
+
+ // Step 3.
+ aContainer->SetQueueTotalSize(0);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-clear-algorithms
+void ReadableByteStreamControllerClearAlgorithms(
+ ReadableByteStreamController* aController) {
+ // Step 1. Set controller.[[pullAlgorithm]] to undefined.
+ // Step 2. Set controller.[[cancelAlgorithm]] to undefined.
+ aController->ClearAlgorithms();
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-error
+void ReadableByteStreamControllerError(
+ ReadableByteStreamController* aController, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ // Step 1. Let stream be controller.[[stream]].
+ ReadableStream* stream = aController->Stream();
+
+ // Step 2. If stream.[[state]] is not "readable", return.
+ if (stream->State() != ReadableStream::ReaderState::Readable) {
+ return;
+ }
+
+ // Step 3. Perform
+ // !ReadableByteStreamControllerClearPendingPullIntos(controller).
+ ReadableByteStreamControllerClearPendingPullIntos(aController);
+
+ // Step 4. Perform !ResetQueue(controller).
+ ResetQueue(aController);
+
+ // Step 5. Perform !ReadableByteStreamControllerClearAlgorithms(controller).
+ ReadableByteStreamControllerClearAlgorithms(aController);
+
+ // Step 6. Perform !ReadableStreamError(stream, e).
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aController->GetParentObject())) {
+ return;
+ }
+ ReadableStreamError(jsapi.cx(), stream, aValue, aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-close
+void ReadableByteStreamControllerClose(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ErrorResult& aRv) {
+ // Step 1.
+ RefPtr<ReadableStream> stream = aController->Stream();
+
+ // Step 2.
+ if (aController->CloseRequested() ||
+ stream->State() != ReadableStream::ReaderState::Readable) {
+ return;
+ }
+
+ // Step 3.
+ if (aController->QueueTotalSize() > 0) {
+ // Step 3.1
+ aController->SetCloseRequested(true);
+ // Step 3.2
+ return;
+ }
+
+ // Step 4.
+ if (!aController->PendingPullIntos().isEmpty()) {
+ // Step 4.1
+ PullIntoDescriptor* firstPendingPullInto =
+ aController->PendingPullIntos().getFirst();
+ // Step 4.2
+ if (firstPendingPullInto->BytesFilled() > 0) {
+ // Step 4.2.1
+ ErrorResult rv;
+ rv.ThrowTypeError("Leftover Bytes");
+
+ JS::Rooted<JS::Value> exception(aCx);
+ MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &exception));
+
+ // Step 4.2.2
+ ReadableByteStreamControllerError(aController, exception, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ aRv.MightThrowJSException();
+ aRv.ThrowJSException(aCx, exception);
+ return;
+ }
+ }
+
+ // Step 5.
+ ReadableByteStreamControllerClearAlgorithms(aController);
+
+ // Step 6.
+ ReadableStreamClose(aCx, stream, aRv);
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#rbs-controller-close
+void ReadableByteStreamController::Close(JSContext* aCx, ErrorResult& aRv) {
+ // Step 1.
+ if (mCloseRequested) {
+ aRv.ThrowTypeError("Close already requested");
+ return;
+ }
+
+ // Step 2.
+ if (Stream()->State() != ReadableStream::ReaderState::Readable) {
+ aRv.ThrowTypeError("Closing un-readable stream controller");
+ return;
+ }
+
+ // Step 3.
+ ReadableByteStreamControllerClose(aCx, this, aRv);
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-enqueue-chunk-to-queue
+void ReadableByteStreamControllerEnqueueChunkToQueue(
+ ReadableByteStreamController* aController,
+ JS::Handle<JSObject*> aTransferredBuffer, size_t aByteOffset,
+ size_t aByteLength) {
+ // Step 1.
+ RefPtr<ReadableByteStreamQueueEntry> queueEntry =
+ new ReadableByteStreamQueueEntry(aTransferredBuffer, aByteOffset,
+ aByteLength);
+ aController->Queue().insertBack(queueEntry);
+
+ // Step 2.
+ aController->AddToQueueTotalSize(double(aByteLength));
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollerenqueueclonedchunktoqueue
+void ReadableByteStreamControllerEnqueueClonedChunkToQueue(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ JS::Handle<JSObject*> aBuffer, size_t aByteOffset, size_t aByteLength,
+ ErrorResult& aRv) {
+ // Step 1. Let cloneResult be CloneArrayBuffer(buffer, byteOffset, byteLength,
+ // %ArrayBuffer%).
+ aRv.MightThrowJSException();
+ JS::Rooted<JSObject*> cloneResult(
+ aCx, JS::ArrayBufferClone(aCx, aBuffer, aByteOffset, aByteLength));
+
+ // Step 2. If cloneResult is an abrupt completion,
+ if (!cloneResult) {
+ JS::Rooted<JS::Value> exception(aCx);
+ if (!JS_GetPendingException(aCx, &exception)) {
+ // Uncatchable exception; we should mark aRv and return.
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ JS_ClearPendingException(aCx);
+
+ // Step 2.1. Perform ! ReadableByteStreamControllerError(controller,
+ // cloneResult.[[Value]]).
+ ReadableByteStreamControllerError(aController, exception, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 2.2. Return cloneResult.
+ aRv.ThrowJSException(aCx, exception);
+ return;
+ }
+
+ // Step 3. Perform !
+ // ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // cloneResult.[[Value]], 0, byteLength).
+ ReadableByteStreamControllerEnqueueChunkToQueue(aController, cloneResult, 0,
+ aByteLength);
+}
+
+already_AddRefed<PullIntoDescriptor>
+ReadableByteStreamControllerShiftPendingPullInto(
+ ReadableByteStreamController* aController);
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollerenqueuedetachedpullintotoqueue
+void ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv) {
+ // Step 1. Assert: pullIntoDescriptor’s reader type is "none".
+ MOZ_ASSERT(aPullIntoDescriptor->GetReaderType() == ReaderType::None);
+
+ // Step 2. If pullIntoDescriptor’s bytes filled > 0,
+ // perform ? ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller,
+ // pullIntoDescriptor’s buffer, pullIntoDescriptor’s byte offset,
+ // pullIntoDescriptor’s bytes filled).
+ if (aPullIntoDescriptor->BytesFilled() > 0) {
+ JS::Rooted<JSObject*> buffer(aCx, aPullIntoDescriptor->Buffer());
+ ReadableByteStreamControllerEnqueueClonedChunkToQueue(
+ aCx, aController, buffer, aPullIntoDescriptor->ByteOffset(),
+ aPullIntoDescriptor->BytesFilled(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 3. Perform !
+ // ReadableByteStreamControllerShiftPendingPullInto(controller).
+ RefPtr<PullIntoDescriptor> discarded =
+ ReadableByteStreamControllerShiftPendingPullInto(aController);
+ (void)discarded;
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-get-num-read-into-requests
+static size_t ReadableStreamGetNumReadIntoRequests(ReadableStream* aStream) {
+ // Step 1.
+ MOZ_ASSERT(ReadableStreamHasBYOBReader(aStream));
+
+ // Step 2.
+ return aStream->GetReader()->AsBYOB()->ReadIntoRequests().length();
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-should-call-pull
+bool ReadableByteStreamControllerShouldCallPull(
+ ReadableByteStreamController* aController) {
+ // Step 1. Let stream be controller.[[stream]].
+ ReadableStream* stream = aController->Stream();
+
+ // Step 2. If stream.[[state]] is not "readable", return false.
+ if (stream->State() != ReadableStream::ReaderState::Readable) {
+ return false;
+ }
+
+ // Step 3. If controller.[[closeRequested]] is true, return false.
+ if (aController->CloseRequested()) {
+ return false;
+ }
+
+ // Step 4. If controller.[[started]] is false, return false.
+ if (!aController->Started()) {
+ return false;
+ }
+
+ // Step 5. If ! ReadableStreamHasDefaultReader(stream) is true
+ // and ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
+ if (ReadableStreamHasDefaultReader(stream) &&
+ ReadableStreamGetNumReadRequests(stream) > 0) {
+ return true;
+ }
+
+ // Step 6. If ! ReadableStreamHasBYOBReader(stream) is true
+ // and ! ReadableStreamGetNumReadIntoRequests(stream) > 0, return true.
+ if (ReadableStreamHasBYOBReader(stream) &&
+ ReadableStreamGetNumReadIntoRequests(stream) > 0) {
+ return true;
+ }
+
+ // Step 7. Let desiredSize be
+ // ! ReadableByteStreamControllerGetDesiredSize(controller).
+ Nullable<double> desiredSize =
+ ReadableByteStreamControllerGetDesiredSize(aController);
+
+ // Step 8. Assert: desiredSize is not null.
+ MOZ_ASSERT(!desiredSize.IsNull());
+
+ // Step 9. If desiredSize > 0, return true.
+ // Step 10. Return false.
+ return desiredSize.Value() > 0;
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-call-pull-if-needed
+void ReadableByteStreamControllerCallPullIfNeeded(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ErrorResult& aRv) {
+ // Step 1.
+ bool shouldPull = ReadableByteStreamControllerShouldCallPull(aController);
+
+ // Step 2.
+ if (!shouldPull) {
+ return;
+ }
+
+ // Step 3.
+ if (aController->Pulling()) {
+ aController->SetPullAgain(true);
+ return;
+ }
+
+ // Step 4.
+ MOZ_ASSERT(!aController->PullAgain());
+
+ // Step 5.
+ aController->SetPulling(true);
+
+ // Step 6.
+ RefPtr<ReadableStreamController> controller(aController);
+ RefPtr<UnderlyingSourceAlgorithmsBase> algorithms =
+ aController->GetAlgorithms();
+ RefPtr<Promise> pullPromise = algorithms->PullCallback(aCx, *controller, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Steps 7+8
+ pullPromise->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ ReadableByteStreamController* aController)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ // Step 7.1
+ aController->SetPulling(false);
+ // Step 7.2
+ if (aController->PullAgain()) {
+ // Step 7.2.1
+ aController->SetPullAgain(false);
+
+ // Step 7.2.2
+ ReadableByteStreamControllerCallPullIfNeeded(
+ aCx, MOZ_KnownLive(aController), aRv);
+ }
+ },
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ ReadableByteStreamController* aController) {
+ // Step 8.1
+ ReadableByteStreamControllerError(aController, aValue, aRv);
+ },
+ RefPtr(aController));
+}
+
+bool ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv);
+
+JSObject* ReadableByteStreamControllerConvertPullIntoDescriptor(
+ JSContext* aCx, PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv);
+
+// https://streams.spec.whatwg.org/#readable-stream-fulfill-read-into-request
+MOZ_CAN_RUN_SCRIPT
+void ReadableStreamFulfillReadIntoRequest(JSContext* aCx,
+ ReadableStream* aStream,
+ JS::Handle<JS::Value> aChunk,
+ bool done, ErrorResult& aRv) {
+ // Step 1. Assert: !ReadableStreamHasBYOBReader(stream) is true.
+ MOZ_ASSERT(ReadableStreamHasBYOBReader(aStream));
+
+ // Step 2. Let reader be stream.[[reader]].
+ ReadableStreamBYOBReader* reader = aStream->GetReader()->AsBYOB();
+
+ // Step 3. Assert: reader.[[readIntoRequests]] is not empty.
+ MOZ_ASSERT(!reader->ReadIntoRequests().isEmpty());
+
+ // Step 4. Let readIntoRequest be reader.[[readIntoRequests]][0].
+ // Step 5. Remove readIntoRequest from reader.[[readIntoRequests]].
+ RefPtr<ReadIntoRequest> readIntoRequest =
+ reader->ReadIntoRequests().popFirst();
+
+ // Step 6. If done is true, perform readIntoRequest’s close steps, given
+ // chunk.
+ if (done) {
+ readIntoRequest->CloseSteps(aCx, aChunk, aRv);
+ return;
+ }
+
+ // Step 7. Otherwise, perform readIntoRequest’s chunk steps, given chunk.
+ readIntoRequest->ChunkSteps(aCx, aChunk, aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-commit-pull-into-descriptor
+MOZ_CAN_RUN_SCRIPT
+void ReadableByteStreamControllerCommitPullIntoDescriptor(
+ JSContext* aCx, ReadableStream* aStream,
+ PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv) {
+ // Step 1. Assert: stream.[[state]] is not "errored".
+ MOZ_ASSERT(aStream->State() != ReadableStream::ReaderState::Errored);
+
+ // Step 2. Assert: pullIntoDescriptor.reader type is not "none".
+ MOZ_ASSERT(pullIntoDescriptor->GetReaderType() != ReaderType::None);
+
+ // Step 3. Let done be false.
+ bool done = false;
+
+ // Step 4. If stream.[[state]] is "closed",
+ if (aStream->State() == ReadableStream::ReaderState::Closed) {
+ // Step 4.1. Assert: pullIntoDescriptor’s bytes filled is 0.
+ MOZ_ASSERT(pullIntoDescriptor->BytesFilled() == 0);
+
+ // Step 4.2. Set done to true.
+ done = true;
+ }
+
+ // Step 5. Let filledView be !
+ // ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
+ JS::Rooted<JSObject*> filledView(
+ aCx, ReadableByteStreamControllerConvertPullIntoDescriptor(
+ aCx, pullIntoDescriptor, aRv));
+ if (aRv.Failed()) {
+ return;
+ }
+ JS::Rooted<JS::Value> filledViewValue(aCx, JS::ObjectValue(*filledView));
+
+ // Step 6. If pullIntoDescriptor’s reader type is "default",
+ if (pullIntoDescriptor->GetReaderType() == ReaderType::Default) {
+ // Step 6.1. Perform !ReadableStreamFulfillReadRequest(stream, filledView,
+ // done).
+ ReadableStreamFulfillReadRequest(aCx, aStream, filledViewValue, done, aRv);
+ return;
+ }
+
+ // Step 7.1. Assert: pullIntoDescriptor’s reader type is "byob".
+ MOZ_ASSERT(pullIntoDescriptor->GetReaderType() == ReaderType::BYOB);
+
+ // Step 7.2 Perform !ReadableStreamFulfillReadIntoRequest(stream, filledView,
+ // done).
+ ReadableStreamFulfillReadIntoRequest(aCx, aStream, filledViewValue, done,
+ aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-process-pull-into-descriptors-using-queue
+MOZ_CAN_RUN_SCRIPT
+void ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ErrorResult& aRv) {
+ // Step 1. Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!aController->CloseRequested());
+
+ // Step 2. While controller.[[pendingPullIntos]] is not empty,
+ while (!aController->PendingPullIntos().isEmpty()) {
+ // Step 2.1 If controller.[[queueTotalSize]] is 0, return.
+ if (aController->QueueTotalSize() == 0) {
+ return;
+ }
+
+ // Step 2.2. Let pullIntoDescriptor be controller.[[pendingPullIntos]][0].
+ RefPtr<PullIntoDescriptor> pullIntoDescriptor =
+ aController->PendingPullIntos().getFirst();
+
+ // Step 2.3. If
+ // !ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
+ // pullIntoDescriptor) is true,
+ bool ready = ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(
+ aCx, aController, pullIntoDescriptor, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ if (ready) {
+ // Step 2.3.1. Perform
+ // !ReadableByteStreamControllerShiftPendingPullInto(controller).
+ RefPtr<PullIntoDescriptor> discardedPullIntoDescriptor =
+ ReadableByteStreamControllerShiftPendingPullInto(aController);
+
+ // Step 2.3.2. Perform
+ // !ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]],
+ // pullIntoDescriptor).
+ RefPtr<ReadableStream> stream(aController->Stream());
+ ReadableByteStreamControllerCommitPullIntoDescriptor(
+ aCx, stream, pullIntoDescriptor, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT
+void ReadableByteStreamControllerHandleQueueDrain(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ErrorResult& aRv);
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontrollerfillreadrequestfromqueue
+MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerFillReadRequestFromQueue(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ReadRequest* aReadRequest, ErrorResult& aRv) {
+ // Step 1. Assert: controller.[[queueTotalSize]] > 0.
+ MOZ_ASSERT(aController->QueueTotalSize() > 0);
+ // Also assert that the queue has a non-zero length;
+ MOZ_ASSERT(aController->Queue().length() > 0);
+
+ // Step 2. Let entry be controller.[[queue]][0].
+ // Step 3. Remove entry from controller.[[queue]].
+ RefPtr<ReadableByteStreamQueueEntry> entry = aController->Queue().popFirst();
+
+ // Assert that we actually got an entry.
+ MOZ_ASSERT(entry);
+
+ // Step 4. Set controller.[[queueTotalSize]] to controller.[[queueTotalSize]]
+ // − entry’s byte length.
+ aController->SetQueueTotalSize(aController->QueueTotalSize() -
+ double(entry->ByteLength()));
+
+ // Step 5. Perform ! ReadableByteStreamControllerHandleQueueDrain(controller).
+ ReadableByteStreamControllerHandleQueueDrain(aCx, aController, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 6. Let view be ! Construct(%Uint8Array%, « entry’s buffer, entry’s
+ // byte offset, entry’s byte length »).
+ aRv.MightThrowJSException();
+ JS::Rooted<JSObject*> buffer(aCx, entry->Buffer());
+ JS::Rooted<JSObject*> view(
+ aCx, JS_NewUint8ArrayWithBuffer(aCx, buffer, entry->ByteOffset(),
+ int64_t(entry->ByteLength())));
+ if (!view) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ // Step 7. Perform readRequest’s chunk steps, given view.
+ JS::Rooted<JS::Value> viewValue(aCx, JS::ObjectValue(*view));
+ aReadRequest->ChunkSteps(aCx, viewValue, aRv);
+}
+
+MOZ_CAN_RUN_SCRIPT void
+ReadableByteStreamControllerProcessReadRequestsUsingQueue(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ErrorResult& aRv) {
+ // Step 1. Let reader be controller.[[stream]].[[reader]].
+ // Step 2. Assert: reader implements ReadableStreamDefaultReader.
+ RefPtr<ReadableStreamDefaultReader> reader =
+ aController->Stream()->GetDefaultReader();
+
+ // Step 3. While reader.[[readRequests]] is not empty,
+ while (!reader->ReadRequests().isEmpty()) {
+ // Step 3.1. If controller.[[queueTotalSize]] is 0, return.
+ if (aController->QueueTotalSize() == 0) {
+ return;
+ }
+
+ // Step 3.2. Let readRequest be reader.[[readRequests]][0].
+ // Step 3.3. Remove readRequest from reader.[[readRequests]].
+ RefPtr<ReadRequest> readRequest = reader->ReadRequests().popFirst();
+
+ // Step 3.4. Perform !
+ // ReadableByteStreamControllerFillReadRequestFromQueue(controller,
+ // readRequest).
+ ReadableByteStreamControllerFillReadRequestFromQueue(aCx, aController,
+ readRequest, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-enqueue
+void ReadableByteStreamControllerEnqueue(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ JS::Handle<JSObject*> aChunk, ErrorResult& aRv) {
+ aRv.MightThrowJSException();
+
+ // Step 1.
+ RefPtr<ReadableStream> stream = aController->Stream();
+
+ // Step 2.
+ if (aController->CloseRequested() ||
+ stream->State() != ReadableStream::ReaderState::Readable) {
+ return;
+ }
+
+ // Step 3.
+ bool isShared;
+ JS::Rooted<JSObject*> buffer(
+ aCx, JS_GetArrayBufferViewBuffer(aCx, aChunk, &isShared));
+ if (!buffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ // Step 4.
+ size_t byteOffset = JS_GetArrayBufferViewByteOffset(aChunk);
+
+ // Step 5.
+ size_t byteLength = JS_GetArrayBufferViewByteLength(aChunk);
+
+ // Step 6.
+ if (JS::IsDetachedArrayBufferObject(buffer)) {
+ aRv.ThrowTypeError("Detached Array Buffer");
+ return;
+ }
+
+ // Step 7.
+ JS::Rooted<JSObject*> transferredBuffer(aCx,
+ TransferArrayBuffer(aCx, buffer));
+ if (!transferredBuffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ // Step 8.
+ if (!aController->PendingPullIntos().isEmpty()) {
+ // Step 8.1
+ RefPtr<PullIntoDescriptor> firstPendingPullInto =
+ aController->PendingPullIntos().getFirst();
+
+ // Step 8.2
+ JS::Rooted<JSObject*> pendingBuffer(aCx, firstPendingPullInto->Buffer());
+ if (JS::IsDetachedArrayBufferObject(pendingBuffer)) {
+ aRv.ThrowTypeError("Pending PullInto has detached buffer");
+ return;
+ }
+
+ // Step 8.3. Perform !
+ // ReadableByteStreamControllerInvalidateBYOBRequest(controller).
+ ReadableByteStreamControllerInvalidateBYOBRequest(aController);
+
+ // Step 8.4. Set firstPendingPullInto’s buffer to !
+ // TransferArrayBuffer(firstPendingPullInto’s buffer).
+ pendingBuffer = TransferArrayBuffer(aCx, pendingBuffer);
+ if (!pendingBuffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ firstPendingPullInto->SetBuffer(pendingBuffer);
+
+ // Step 8.5. If firstPendingPullInto’s reader type is "none", perform ?
+ // ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller,
+ // firstPendingPullInto).
+ if (firstPendingPullInto->GetReaderType() == ReaderType::None) {
+ ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(
+ aCx, aController, firstPendingPullInto, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ }
+
+ // Step 9. If ! ReadableStreamHasDefaultReader(stream) is true,
+ if (ReadableStreamHasDefaultReader(stream)) {
+ // Step 9.1. Perform !
+ // ReadableByteStreamControllerProcessReadRequestsUsingQueue(controller).
+ ReadableByteStreamControllerProcessReadRequestsUsingQueue(aCx, aController,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 9.2. If ! ReadableStreamGetNumReadRequests(stream) is 0,
+ if (ReadableStreamGetNumReadRequests(stream) == 0) {
+ // Step 9.2.1 Assert: controller.[[pendingPullIntos]] is empty.
+ MOZ_ASSERT(aController->PendingPullIntos().isEmpty());
+
+ // Step 9.2.2. Perform !
+ // ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer, byteOffset, byteLength).
+ ReadableByteStreamControllerEnqueueChunkToQueue(
+ aController, transferredBuffer, byteOffset, byteLength);
+
+ // Step 9.3. Otherwise,
+ } else {
+ // Step 9.3.1 Assert: controller.[[queue]] is empty.
+ MOZ_ASSERT(aController->Queue().isEmpty());
+
+ // Step 9.3.2. If controller.[[pendingPullIntos]] is not empty,
+ if (!aController->PendingPullIntos().isEmpty()) {
+ // Step 9.3.2.1. Assert: controller.[[pendingPullIntos]][0]'s reader
+ // type is "default".
+ MOZ_ASSERT(
+ aController->PendingPullIntos().getFirst()->GetReaderType() ==
+ ReaderType::Default);
+
+ // Step 9.3.2.2. Perform !
+ // ReadableByteStreamControllerShiftPendingPullInto(controller).
+ RefPtr<PullIntoDescriptor> pullIntoDescriptor =
+ ReadableByteStreamControllerShiftPendingPullInto(aController);
+ (void)pullIntoDescriptor;
+ }
+
+ // Step 9.3.3. Let transferredView be ! Construct(%Uint8Array%, «
+ // transferredBuffer, byteOffset, byteLength »).
+ JS::Rooted<JSObject*> transferredView(
+ aCx, JS_NewUint8ArrayWithBuffer(aCx, transferredBuffer, byteOffset,
+ int64_t(byteLength)));
+ if (!transferredView) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ // Step 9.3.4. Perform ! ReadableStreamFulfillReadRequest(stream,
+ // transferredView, false).
+ JS::Rooted<JS::Value> transferredViewValue(
+ aCx, JS::ObjectValue(*transferredView));
+ ReadableStreamFulfillReadRequest(aCx, stream, transferredViewValue, false,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 10. Otherwise, if ! ReadableStreamHasBYOBReader(stream) is true,
+ } else if (ReadableStreamHasBYOBReader(stream)) {
+ // Step 10.1. Perform !
+ // ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer, byteOffset, byteLength).
+ ReadableByteStreamControllerEnqueueChunkToQueue(
+ aController, transferredBuffer, byteOffset, byteLength);
+
+ // Step 10.2 Perform !
+ // ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
+ ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
+ aCx, aController, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 11. Otherwise,
+ } else {
+ // Step 11.1. Assert: ! IsReadableStreamLocked(stream) is false.
+ MOZ_ASSERT(!IsReadableStreamLocked(stream));
+
+ // Step 11.2. Perform !
+ // ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer, byteOffset, byteLength).
+ ReadableByteStreamControllerEnqueueChunkToQueue(
+ aController, transferredBuffer, byteOffset, byteLength);
+ }
+
+ // Step 12. Perform !
+ // ReadableByteStreamControllerCallPullIfNeeded(controller).
+ ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv);
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#rbs-controller-enqueue
+void ReadableByteStreamController::Enqueue(JSContext* aCx,
+ const ArrayBufferView& aChunk,
+ ErrorResult& aRv) {
+ // Step 1.
+ JS::Rooted<JSObject*> chunk(aCx, aChunk.Obj());
+ if (JS_GetArrayBufferViewByteLength(chunk) == 0) {
+ aRv.ThrowTypeError("Zero Length View");
+ return;
+ }
+
+ // Step 2.
+ bool isShared;
+ JS::Rooted<JSObject*> viewedArrayBuffer(
+ aCx, JS_GetArrayBufferViewBuffer(aCx, chunk, &isShared));
+ if (!viewedArrayBuffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ if (JS::GetArrayBufferByteLength(viewedArrayBuffer) == 0) {
+ aRv.ThrowTypeError("Zero Length Buffer");
+ return;
+ }
+
+ // Step 3.
+ if (CloseRequested()) {
+ aRv.ThrowTypeError("close requested");
+ return;
+ }
+
+ // Step 4.
+ if (Stream()->State() != ReadableStream::ReaderState::Readable) {
+ aRv.ThrowTypeError("Not Readable");
+ return;
+ }
+
+ // Step 5.
+ ReadableByteStreamControllerEnqueue(aCx, this, chunk, aRv);
+}
+
+// https://streams.spec.whatwg.org/#rbs-controller-error
+void ReadableByteStreamController::Error(JSContext* aCx,
+ JS::Handle<JS::Value> aErrorValue,
+ ErrorResult& aRv) {
+ // Step 1.
+ ReadableByteStreamControllerError(this, aErrorValue, aRv);
+}
+
+// https://streams.spec.whatwg.org/#rbs-controller-private-cancel
+already_AddRefed<Promise> ReadableByteStreamController::CancelSteps(
+ JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
+ // Step 1.
+ ReadableByteStreamControllerClearPendingPullIntos(this);
+
+ // Step 2.
+ ResetQueue(this);
+
+ // Step 3.
+ Optional<JS::Handle<JS::Value>> reason(aCx, aReason);
+ RefPtr<UnderlyingSourceAlgorithmsBase> algorithms = mAlgorithms;
+ RefPtr<Promise> result = algorithms->CancelCallback(aCx, reason, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ // Step 4.
+ ReadableByteStreamControllerClearAlgorithms(this);
+
+ // Step 5.
+ return result.forget();
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-handle-queue-drain
+void ReadableByteStreamControllerHandleQueueDrain(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ErrorResult& aRv) {
+ // Step 1.
+ MOZ_ASSERT(aController->Stream()->State() ==
+ ReadableStream::ReaderState::Readable);
+
+ // Step 2.
+ if (aController->QueueTotalSize() == 0 && aController->CloseRequested()) {
+ // Step 2.1
+ ReadableByteStreamControllerClearAlgorithms(aController);
+
+ // Step 2.2
+ RefPtr<ReadableStream> stream = aController->Stream();
+ ReadableStreamClose(aCx, stream, aRv);
+ return;
+ }
+
+ // Step 3.1
+ ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv);
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#rbs-controller-private-pull
+void ReadableByteStreamController::PullSteps(JSContext* aCx,
+ ReadRequest* aReadRequest,
+ ErrorResult& aRv) {
+ // Step 1.
+ ReadableStream* stream = Stream();
+
+ // Step 2.
+ MOZ_ASSERT(ReadableStreamHasDefaultReader(stream));
+
+ // Step 3.
+ if (QueueTotalSize() > 0) {
+ // Step 3.1. Assert: ! ReadableStreamGetNumReadRequests ( stream ) is 0.
+ MOZ_ASSERT(ReadableStreamGetNumReadRequests(stream) == 0);
+
+ // Step 3.2. Perform !
+ // ReadableByteStreamControllerFillReadRequestFromQueue(this, readRequest).
+ ReadableByteStreamControllerFillReadRequestFromQueue(aCx, this,
+ aReadRequest, aRv);
+
+ // Step 3.3. Return.
+ return;
+ }
+
+ // Step 4.
+ Maybe<uint64_t> autoAllocateChunkSize = AutoAllocateChunkSize();
+
+ // Step 5.
+ if (autoAllocateChunkSize) {
+ // Step 5.1
+ aRv.MightThrowJSException();
+ JS::Rooted<JSObject*> buffer(
+ aCx, JS::NewArrayBuffer(aCx, *autoAllocateChunkSize));
+ // Step 5.2
+ if (!buffer) {
+ // Step 5.2.1
+ JS::Rooted<JS::Value> bufferError(aCx);
+ if (!JS_GetPendingException(aCx, &bufferError)) {
+ // Uncatchable exception; we should mark aRv and return.
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ // It's not expliclitly stated, but I assume the intention here is that
+ // we perform a normal completion here.
+ JS_ClearPendingException(aCx);
+
+ aReadRequest->ErrorSteps(aCx, bufferError, aRv);
+
+ // Step 5.2.2.
+ return;
+ }
+
+ // Step 5.3
+ RefPtr<PullIntoDescriptor> pullIntoDescriptor = new PullIntoDescriptor(
+ buffer, *autoAllocateChunkSize, 0, *autoAllocateChunkSize, 0, 1,
+ PullIntoDescriptor::Constructor::Uint8, ReaderType::Default);
+
+ // Step 5.4
+ PendingPullIntos().insertBack(pullIntoDescriptor);
+ }
+
+ // Step 6.
+ ReadableStreamAddReadRequest(stream, aReadRequest);
+
+ // Step 7.
+ ReadableByteStreamControllerCallPullIfNeeded(aCx, this, aRv);
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamcontroller-releasesteps
+void ReadableByteStreamController::ReleaseSteps() {
+ // Step 1. If this.[[pendingPullIntos]] is not empty,
+ if (!PendingPullIntos().isEmpty()) {
+ // Step 1.1. Let firstPendingPullInto be this.[[pendingPullIntos]][0].
+ RefPtr<PullIntoDescriptor> firstPendingPullInto =
+ PendingPullIntos().popFirst();
+
+ // Step 1.2. Set firstPendingPullInto’s reader type to "none".
+ firstPendingPullInto->SetReaderType(ReaderType::None);
+
+ // Step 1.3. Set this.[[pendingPullIntos]] to the list «
+ // firstPendingPullInto ».
+ PendingPullIntos().clear();
+ PendingPullIntos().insertBack(firstPendingPullInto);
+ }
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-shift-pending-pull-into
+already_AddRefed<PullIntoDescriptor>
+ReadableByteStreamControllerShiftPendingPullInto(
+ ReadableByteStreamController* aController) {
+ // Step 1.
+ MOZ_ASSERT(!aController->GetByobRequest());
+
+ // Step 2 + 3
+ RefPtr<PullIntoDescriptor> descriptor =
+ aController->PendingPullIntos().popFirst();
+
+ // Step 4.
+ return descriptor.forget();
+}
+
+JSObject* ConstructFromPullIntoConstructor(
+ JSContext* aCx, PullIntoDescriptor::Constructor constructor,
+ JS::Handle<JSObject*> buffer, size_t byteOffset, size_t length) {
+ switch (constructor) {
+ case PullIntoDescriptor::Constructor::DataView:
+ return JS_NewDataView(aCx, buffer, byteOffset, length);
+ break;
+
+#define CONSTRUCT_TYPED_ARRAY_TYPE(ExternalT, NativeT, Name) \
+ case PullIntoDescriptor::Constructor::Name: \
+ return JS_New##Name##ArrayWithBuffer(aCx, buffer, byteOffset, \
+ int64_t(length)); \
+ break;
+
+ JS_FOR_EACH_TYPED_ARRAY(CONSTRUCT_TYPED_ARRAY_TYPE)
+
+#undef CONSTRUCT_TYPED_ARRAY_TYPE
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown PullIntoDescriptor::Constructor");
+ return nullptr;
+ }
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-convert-pull-into-descriptor
+JSObject* ReadableByteStreamControllerConvertPullIntoDescriptor(
+ JSContext* aCx, PullIntoDescriptor* pullIntoDescriptor, ErrorResult& aRv) {
+ // Step 1. Let bytesFilled be pullIntoDescriptor’s bytes filled.
+ uint64_t bytesFilled = pullIntoDescriptor->BytesFilled();
+
+ // Step 2. Let elementSize be pullIntoDescriptor’s element size.
+ uint64_t elementSize = pullIntoDescriptor->ElementSize();
+
+ // Step 3. Assert: bytesFilled ≤ pullIntoDescriptor’s byte length.
+ MOZ_ASSERT(bytesFilled <= pullIntoDescriptor->ByteLength());
+
+ // Step 4. Assert: bytesFilled mod elementSize is 0.
+ MOZ_ASSERT(bytesFilled % elementSize == 0);
+
+ // Step 5. Let buffer be ! TransferArrayBuffer(pullIntoDescriptor’s buffer).
+ aRv.MightThrowJSException();
+ JS::Rooted<JSObject*> srcBuffer(aCx, pullIntoDescriptor->Buffer());
+ JS::Rooted<JSObject*> buffer(aCx, TransferArrayBuffer(aCx, srcBuffer));
+ if (!buffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 6. Return ! Construct(pullIntoDescriptor’s view constructor,
+ // « buffer, pullIntoDescriptor’s byte offset, bytesFilled ÷ elementSize »).
+ JS::Rooted<JSObject*> res(
+ aCx, ConstructFromPullIntoConstructor(
+ aCx, pullIntoDescriptor->ViewConstructor(), buffer,
+ pullIntoDescriptor->ByteOffset(), bytesFilled / elementSize));
+ if (!res) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+ return res;
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-in-closed-state
+MOZ_CAN_RUN_SCRIPT
+static void ReadableByteStreamControllerRespondInClosedState(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ RefPtr<PullIntoDescriptor>& aFirstDescriptor, ErrorResult& aRv) {
+ // Step 1. Assert: firstDescriptor ’s bytes filled is 0.
+ MOZ_ASSERT(aFirstDescriptor->BytesFilled() == 0);
+
+ // Step 2. If firstDescriptor’s reader type is "none",
+ // perform ! ReadableByteStreamControllerShiftPendingPullInto(controller).
+ if (aFirstDescriptor->GetReaderType() == ReaderType::None) {
+ RefPtr<PullIntoDescriptor> discarded =
+ ReadableByteStreamControllerShiftPendingPullInto(aController);
+ (void)discarded;
+ }
+
+ // Step 3. Let stream be controller.[[stream]].
+ RefPtr<ReadableStream> stream = aController->Stream();
+
+ // Step 4. If ! ReadableStreamHasBYOBReader(stream) is true,
+ if (ReadableStreamHasBYOBReader(stream)) {
+ // Step 4.1. While ! ReadableStreamGetNumReadIntoRequests(stream) > 0,
+ while (ReadableStreamGetNumReadIntoRequests(stream) > 0) {
+ // Step 4.1.1. Let pullIntoDescriptor be !
+ // ReadableByteStreamControllerShiftPendingPullInto(controller).
+ RefPtr<PullIntoDescriptor> pullIntoDescriptor =
+ ReadableByteStreamControllerShiftPendingPullInto(aController);
+
+ // Step 4.1.2. Perform !
+ // ReadableByteStreamControllerCommitPullIntoDescriptor(stream,
+ // pullIntoDescriptor).
+ ReadableByteStreamControllerCommitPullIntoDescriptor(
+ aCx, stream, pullIntoDescriptor, aRv);
+ }
+ }
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-fill-head-pull-into-descriptor
+void ReadableByteStreamControllerFillHeadPullIntoDescriptor(
+ ReadableByteStreamController* aController, size_t aSize,
+ PullIntoDescriptor* aPullIntoDescriptor) {
+ // Step 1. Assert: either controller.[[pendingPullIntos]] is empty, or
+ // controller.[[pendingPullIntos]][0] is pullIntoDescriptor.
+ MOZ_ASSERT(aController->PendingPullIntos().isEmpty() ||
+ aController->PendingPullIntos().getFirst() == aPullIntoDescriptor);
+
+ // Step 2. Assert: controller.[[byobRequest]] is null.
+ MOZ_ASSERT(!aController->GetByobRequest());
+
+ // Step 3. Set pullIntoDescriptor’s bytes filled to bytes filled + size.
+ aPullIntoDescriptor->SetBytesFilled(aPullIntoDescriptor->BytesFilled() +
+ aSize);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-in-readable-state
+MOZ_CAN_RUN_SCRIPT
+static void ReadableByteStreamControllerRespondInReadableState(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ uint64_t aBytesWritten, PullIntoDescriptor* aPullIntoDescriptor,
+ ErrorResult& aRv) {
+ // Step 1. Assert: pullIntoDescriptor’s bytes filled + bytesWritten ≤
+ // pullIntoDescriptor’s byte length.
+ MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() + aBytesWritten <=
+ aPullIntoDescriptor->ByteLength());
+
+ // Step 2. Perform
+ // !ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
+ // bytesWritten, pullIntoDescriptor).
+ ReadableByteStreamControllerFillHeadPullIntoDescriptor(
+ aController, aBytesWritten, aPullIntoDescriptor);
+
+ // Step 3. If pullIntoDescriptor’s reader type is "none",
+ if (aPullIntoDescriptor->GetReaderType() == ReaderType::None) {
+ // Step 3.1. Perform ?
+ // ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(controller,
+ // pullIntoDescriptor).
+ ReadableByteStreamControllerEnqueueDetachedPullIntoToQueue(
+ aCx, aController, aPullIntoDescriptor, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 3.2. Perform !
+ // ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
+ ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
+ aCx, aController, aRv);
+
+ // Step 3.3. Return.
+ return;
+ }
+
+ // Step 4. If pullIntoDescriptor’s bytes filled < pullIntoDescriptor’s element
+ // size, return.
+ if (aPullIntoDescriptor->BytesFilled() < aPullIntoDescriptor->ElementSize()) {
+ return;
+ }
+
+ // Step 5. Perform
+ // !ReadableByteStreamControllerShiftPendingPullInto(controller).
+ RefPtr<PullIntoDescriptor> pullIntoDescriptor =
+ ReadableByteStreamControllerShiftPendingPullInto(aController);
+ (void)pullIntoDescriptor;
+
+ // Step 6. Let remainderSize be pullIntoDescriptor’s bytes filled mod
+ // pullIntoDescriptor’s element size.
+ size_t remainderSize =
+ aPullIntoDescriptor->BytesFilled() % aPullIntoDescriptor->ElementSize();
+
+ // Step 7. If remainderSize > 0,
+ if (remainderSize > 0) {
+ // Step 7.1. Let end be pullIntoDescriptor’s byte offset +
+ // pullIntoDescriptor’s bytes filled.
+ size_t end =
+ aPullIntoDescriptor->ByteOffset() + aPullIntoDescriptor->BytesFilled();
+
+ // Step 7.2. Perform ?
+ // ReadableByteStreamControllerEnqueueClonedChunkToQueue(controller,
+ // pullIntoDescriptor’s buffer, end − remainderSize, remainderSize).
+ JS::Rooted<JSObject*> pullIntoBuffer(aCx, aPullIntoDescriptor->Buffer());
+ ReadableByteStreamControllerEnqueueClonedChunkToQueue(
+ aCx, aController, pullIntoBuffer, end - remainderSize, remainderSize,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 8. Set pullIntoDescriptor’s bytes filled to pullIntoDescriptor’s bytes
+ // filled − remainderSize.
+ aPullIntoDescriptor->SetBytesFilled(aPullIntoDescriptor->BytesFilled() -
+ remainderSize);
+
+ // Step 9. Perform
+ // !ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[stream]],
+ // pullIntoDescriptor).
+ RefPtr<ReadableStream> stream(aController->Stream());
+ ReadableByteStreamControllerCommitPullIntoDescriptor(
+ aCx, stream, aPullIntoDescriptor, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 10. Perform
+ // !ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
+ ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(
+ aCx, aController, aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-internal
+void ReadableByteStreamControllerRespondInternal(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ uint64_t aBytesWritten, ErrorResult& aRv) {
+ // Step 1.
+ RefPtr<PullIntoDescriptor> firstDescriptor =
+ aController->PendingPullIntos().getFirst();
+
+ // Step 2.
+ JS::Rooted<JSObject*> buffer(aCx, firstDescriptor->Buffer());
+#ifdef DEBUG
+ bool canTransferBuffer = CanTransferArrayBuffer(aCx, buffer, aRv);
+ MOZ_ASSERT(!aRv.Failed());
+ MOZ_ASSERT(canTransferBuffer);
+#endif
+
+ // Step 3.
+ ReadableByteStreamControllerInvalidateBYOBRequest(aController);
+
+ // Step 4.
+ auto state = aController->Stream()->State();
+
+ // Step 5.
+ if (state == ReadableStream::ReaderState::Closed) {
+ // Step 5.1
+ MOZ_ASSERT(aBytesWritten == 0);
+
+ // Step 5.2
+ ReadableByteStreamControllerRespondInClosedState(aCx, aController,
+ firstDescriptor, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ } else {
+ // Step 6.1
+ MOZ_ASSERT(state == ReadableStream::ReaderState::Readable);
+
+ // Step 6.2.
+ MOZ_ASSERT(aBytesWritten > 0);
+
+ // Step 6.3
+ ReadableByteStreamControllerRespondInReadableState(
+ aCx, aController, aBytesWritten, firstDescriptor, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ // Step 7.
+ ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond
+void ReadableByteStreamControllerRespond(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ uint64_t aBytesWritten, ErrorResult& aRv) {
+ // Step 1.
+ MOZ_ASSERT(!aController->PendingPullIntos().isEmpty());
+
+ // Step 2.
+ PullIntoDescriptor* firstDescriptor =
+ aController->PendingPullIntos().getFirst();
+
+ // Step 3.
+ auto state = aController->Stream()->State();
+
+ // Step 4.
+ if (state == ReadableStream::ReaderState::Closed) {
+ // Step 4.1
+ if (aBytesWritten != 0) {
+ aRv.ThrowTypeError("bytesWritten not zero on closed stream");
+ return;
+ }
+ } else {
+ // Step 5.1
+ MOZ_ASSERT(state == ReadableStream::ReaderState::Readable);
+
+ // Step 5.2
+ if (aBytesWritten == 0) {
+ aRv.ThrowTypeError("bytesWritten 0");
+ return;
+ }
+
+ // Step 5.3
+ if (firstDescriptor->BytesFilled() + aBytesWritten >
+ firstDescriptor->ByteLength()) {
+ aRv.ThrowRangeError("bytesFilled + bytesWritten > byteLength");
+ return;
+ }
+ }
+
+ // Step 6.
+ aRv.MightThrowJSException();
+ JS::Rooted<JSObject*> buffer(aCx, firstDescriptor->Buffer());
+ JS::Rooted<JSObject*> transferredBuffer(aCx,
+ TransferArrayBuffer(aCx, buffer));
+ if (!transferredBuffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ firstDescriptor->SetBuffer(transferredBuffer);
+
+ // Step 7.
+ ReadableByteStreamControllerRespondInternal(aCx, aController, aBytesWritten,
+ aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-respond-with-new-view
+void ReadableByteStreamControllerRespondWithNewView(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ JS::Handle<JSObject*> aView, ErrorResult& aRv) {
+ aRv.MightThrowJSException();
+
+ // Step 1.
+ MOZ_ASSERT(!aController->PendingPullIntos().isEmpty());
+
+ // Step 2.
+ bool isSharedMemory;
+ JS::Rooted<JSObject*> viewedArrayBuffer(
+ aCx, JS_GetArrayBufferViewBuffer(aCx, aView, &isSharedMemory));
+ if (!viewedArrayBuffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ MOZ_ASSERT(!JS::IsDetachedArrayBufferObject(viewedArrayBuffer));
+
+ // Step 3.
+ RefPtr<PullIntoDescriptor> firstDescriptor =
+ aController->PendingPullIntos().getFirst();
+
+ // Step 4.
+ ReadableStream::ReaderState state = aController->Stream()->State();
+
+ // Step 5.
+ if (state == ReadableStream::ReaderState::Closed) {
+ // Step 5.1
+ if (JS_GetArrayBufferViewByteLength(aView) != 0) {
+ aRv.ThrowTypeError("View has non-zero length in closed stream");
+ return;
+ }
+ } else {
+ // Step 6.1
+ MOZ_ASSERT(state == ReadableStream::ReaderState::Readable);
+
+ // Step 6.2
+ if (JS_GetArrayBufferViewByteLength(aView) == 0) {
+ aRv.ThrowTypeError("View has zero length in readable stream");
+ return;
+ }
+ }
+
+ // Step 7.
+ if (firstDescriptor->ByteOffset() + firstDescriptor->BytesFilled() !=
+ JS_GetArrayBufferViewByteOffset(aView)) {
+ aRv.ThrowRangeError("Invalid Offset");
+ return;
+ }
+
+ // Step 8.
+ if (firstDescriptor->BufferByteLength() !=
+ JS::GetArrayBufferByteLength(viewedArrayBuffer)) {
+ aRv.ThrowRangeError("Mismatched buffer byte lengths");
+ return;
+ }
+
+ // Step 9.
+ if (firstDescriptor->BytesFilled() + JS_GetArrayBufferViewByteLength(aView) >
+ firstDescriptor->ByteLength()) {
+ aRv.ThrowRangeError("Too many bytes");
+ return;
+ }
+
+ // Step 10. Let viewByteLength be view.[[ByteLength]].
+ size_t viewByteLength = JS_GetArrayBufferViewByteLength(aView);
+
+ // Step 11. Set firstDescriptor’s buffer to ?
+ // TransferArrayBuffer(view.[[ViewedArrayBuffer]]).
+ JS::Rooted<JSObject*> transferedBuffer(
+ aCx, TransferArrayBuffer(aCx, viewedArrayBuffer));
+ if (!transferedBuffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ firstDescriptor->SetBuffer(transferedBuffer);
+
+ // Step 12. Perform ? ReadableByteStreamControllerRespondInternal(controller,
+ // viewByteLength).
+ ReadableByteStreamControllerRespondInternal(aCx, aController, viewByteLength,
+ aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-fill-pull-into-descriptor-from-queue
+bool ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ PullIntoDescriptor* aPullIntoDescriptor, ErrorResult& aRv) {
+ // Step 1. Let elementSize be pullIntoDescriptor.[[elementSize]].
+ size_t elementSize = aPullIntoDescriptor->ElementSize();
+
+ // Step 2. Let currentAlignedBytes be pullIntoDescriptor’s bytes filled −
+ // (pullIntoDescriptor’s bytes filled mod elementSize).
+ size_t currentAlignedBytes =
+ aPullIntoDescriptor->BytesFilled() -
+ (aPullIntoDescriptor->BytesFilled() % elementSize);
+
+ // Step 3. Let maxBytesToCopy be min(controller.[[queueTotalSize]],
+ // pullIntoDescriptor’s byte length − pullIntoDescriptor’s bytes filled).
+ size_t maxBytesToCopy =
+ std::min(static_cast<size_t>(aController->QueueTotalSize()),
+ static_cast<size_t>((aPullIntoDescriptor->ByteLength() -
+ aPullIntoDescriptor->BytesFilled())));
+
+ // Step 4. Let maxBytesFilled be pullIntoDescriptor’s bytes filled +
+ // maxBytesToCopy.
+ size_t maxBytesFilled = aPullIntoDescriptor->BytesFilled() + maxBytesToCopy;
+
+ // Step 5. Let maxAlignedBytes be maxBytesFilled − (maxBytesFilled mod
+ // elementSize).
+ size_t maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize);
+
+ // Step 6. Let totalBytesToCopyRemaining be maxBytesToCopy.
+ size_t totalBytesToCopyRemaining = maxBytesToCopy;
+
+ // Step 7. Let ready be false.
+ bool ready = false;
+
+ // Step 8. If maxAlignedBytes > currentAlignedBytes,
+ if (maxAlignedBytes > currentAlignedBytes) {
+ // Step 8.1. Set totalBytesToCopyRemaining to maxAlignedBytes −
+ // pullIntoDescriptor’s bytes filled.
+ totalBytesToCopyRemaining =
+ maxAlignedBytes - aPullIntoDescriptor->BytesFilled();
+ // Step 8.2. Set ready to true.
+ ready = true;
+ }
+
+ // Step 9. Let queue be controller.[[queue]].
+ LinkedList<RefPtr<ReadableByteStreamQueueEntry>>& queue =
+ aController->Queue();
+
+ // Step 10. While totalBytesToCopyRemaining > 0,
+ while (totalBytesToCopyRemaining > 0) {
+ // Step 10.1 Let headOfQueue be queue[0].
+ ReadableByteStreamQueueEntry* headOfQueue = queue.getFirst();
+
+ // Step 10.2. Let bytesToCopy be min(totalBytesToCopyRemaining,
+ // headOfQueue’s byte length).
+ size_t bytesToCopy =
+ std::min(totalBytesToCopyRemaining, headOfQueue->ByteLength());
+
+ // Step 10.3. Let destStart be pullIntoDescriptor’s byte offset +
+ // pullIntoDescriptor’s bytes filled.
+ size_t destStart =
+ aPullIntoDescriptor->ByteOffset() + aPullIntoDescriptor->BytesFilled();
+
+ // Step 10.4. Perform !CopyDataBlockBytes(pullIntoDescriptor’s
+ // buffer.[[ArrayBufferData]], destStart, headOfQueue’s
+ // buffer.[[ArrayBufferData]], headOfQueue’s byte offset,
+ // bytesToCopy).
+ JS::Rooted<JSObject*> descriptorBuffer(aCx, aPullIntoDescriptor->Buffer());
+ JS::Rooted<JSObject*> queueBuffer(aCx, headOfQueue->Buffer());
+ if (!JS::ArrayBufferCopyData(aCx, descriptorBuffer, destStart, queueBuffer,
+ headOfQueue->ByteOffset(), bytesToCopy)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return false;
+ }
+
+ // Step 10.5. If headOfQueue’s byte length is bytesToCopy,
+ if (headOfQueue->ByteLength() == bytesToCopy) {
+ // Step 10.5.1. Remove queue[0].
+ queue.popFirst();
+ } else {
+ // Step 10.6. Otherwise,
+
+ // Step 10.6.1 Set headOfQueue’s byte offset to
+ // headOfQueue’s byte offset + bytesToCopy.
+ headOfQueue->SetByteOffset(headOfQueue->ByteOffset() + bytesToCopy);
+ // Step 10.6.2 Set headOfQueue’s byte length to
+ // headOfQueue’s byte length − bytesToCopy.
+ headOfQueue->SetByteLength(headOfQueue->ByteLength() - bytesToCopy);
+ }
+
+ // Step 10.7. Set controller.[[queueTotalSize]] to
+ // controller.[[queueTotalSize]] − bytesToCopy.
+ aController->SetQueueTotalSize(aController->QueueTotalSize() -
+ (double)bytesToCopy);
+
+ // Step 10.8, Perform
+ // !ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
+ // bytesToCopy, pullIntoDescriptor).
+ ReadableByteStreamControllerFillHeadPullIntoDescriptor(
+ aController, bytesToCopy, aPullIntoDescriptor);
+
+ // Step 10.9. Set totalBytesToCopyRemaining to totalBytesToCopyRemaining −
+ // bytesToCopy.
+ totalBytesToCopyRemaining = totalBytesToCopyRemaining - bytesToCopy;
+ }
+
+ // Step 11. If ready is false,
+ if (!ready) {
+ // Step 11.1. Assert: controller.[[queueTotalSize]] is 0.
+ MOZ_ASSERT(aController->QueueTotalSize() == 0);
+
+ // Step 11.2. Assert: pullIntoDescriptor’s bytes filled > 0.
+ MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() > 0);
+
+ // Step 11.3. Assert: pullIntoDescriptor’s bytes filled <
+ // pullIntoDescriptor’s
+ // element size.
+ MOZ_ASSERT(aPullIntoDescriptor->BytesFilled() <
+ aPullIntoDescriptor->ElementSize());
+ }
+
+ // Step 12. Return ready.
+ return ready;
+}
+
+// https://streams.spec.whatwg.org/#readable-byte-stream-controller-pull-into
+void ReadableByteStreamControllerPullInto(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ JS::Handle<JSObject*> aView, ReadIntoRequest* aReadIntoRequest,
+ ErrorResult& aRv) {
+ aRv.MightThrowJSException();
+
+ // Step 1. Let stream be controller.[[stream]].
+ ReadableStream* stream = aController->Stream();
+
+ // Step 2. Let elementSize be 1.
+ size_t elementSize = 1;
+
+ // Step 3. Let ctor be %DataView%.
+ PullIntoDescriptor::Constructor ctor =
+ PullIntoDescriptor::Constructor::DataView;
+
+ // Step 4. If view has a [[TypedArrayName]] internal slot (i.e., it is not a
+ // DataView),
+ if (JS_IsTypedArrayObject(aView)) {
+ // Step 4.1. Set elementSize to the element size specified in the typed
+ // array constructors table for view.[[TypedArrayName]].
+ JS::Scalar::Type type = JS_GetArrayBufferViewType(aView);
+ elementSize = JS::Scalar::byteSize(type);
+
+ // Step 4.2 Set ctor to the constructor specified in the typed array
+ // constructors table for view.[[TypedArrayName]].
+ ctor = PullIntoDescriptor::constructorFromScalar(type);
+ }
+
+ // Step 5. Let byteOffset be view.[[ByteOffset]].
+ size_t byteOffset = JS_GetArrayBufferViewByteOffset(aView);
+
+ // Step 6. Let byteLength be view.[[ByteLength]].
+ size_t byteLength = JS_GetArrayBufferViewByteLength(aView);
+
+ // Step 7. Let bufferResult be
+ // TransferArrayBuffer(view.[[ViewedArrayBuffer]]).
+ bool isShared;
+ JS::Rooted<JSObject*> viewedArrayBuffer(
+ aCx, JS_GetArrayBufferViewBuffer(aCx, aView, &isShared));
+ if (!viewedArrayBuffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ JS::Rooted<JSObject*> bufferResult(
+ aCx, TransferArrayBuffer(aCx, viewedArrayBuffer));
+
+ // Step 8. If bufferResult is an abrupt completion,
+ if (!bufferResult) {
+ JS::Rooted<JS::Value> pendingException(aCx);
+ if (!JS_GetPendingException(aCx, &pendingException)) {
+ // This means an un-catchable exception. Use StealExceptionFromJSContext
+ // to setup aRv properly.
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ // It's not expliclitly stated, but I assume the intention here is that
+ // we perform a normal completion here; we also need to clear the
+ // exception state anyhow to succesfully run ErrorSteps.
+ JS_ClearPendingException(aCx);
+
+ // Step 8.1. Perform readIntoRequest’s error steps, given
+ // bufferResult.[[Value]].
+ aReadIntoRequest->ErrorSteps(aCx, pendingException, aRv);
+
+ // Step 8.2. Return.
+ return;
+ }
+
+ // Step 9. Let buffer be bufferResult.[[Value]].
+ JS::Rooted<JSObject*> buffer(aCx, bufferResult);
+
+ // Step 10. Let pullIntoDescriptor be a new pull-into descriptor with
+ // buffer: buffer,
+ // buffer byte length: buffer.[[ArrayBufferByteLength]],
+ // byte offset: byteOffset,
+ // byte length: byteLength,
+ // bytes filled: 0,
+ // element size: elementSize,
+ // view constructor: ctor,
+ // and reader type: "byob".
+ RefPtr<PullIntoDescriptor> pullIntoDescriptor = new PullIntoDescriptor(
+ buffer, JS::GetArrayBufferByteLength(buffer), byteOffset, byteLength, 0,
+ elementSize, ctor, ReaderType::BYOB);
+
+ // Step 11. If controller.[[pendingPullIntos]] is not empty,
+ if (!aController->PendingPullIntos().isEmpty()) {
+ // Step 11.1. Append pullIntoDescriptor to controller.[[pendingPullIntos]].
+ aController->PendingPullIntos().insertBack(pullIntoDescriptor);
+
+ // Step 11.2. Perform !ReadableStreamAddReadIntoRequest(stream,
+ // readIntoRequest).
+ ReadableStreamAddReadIntoRequest(stream, aReadIntoRequest);
+
+ // Step 11.3. Return.
+ return;
+ }
+
+ // Step 12. If stream.[[state]] is "closed",
+ if (stream->State() == ReadableStream::ReaderState::Closed) {
+ // Step 12.1. Let emptyView be !Construct(ctor, « pullIntoDescriptor’s
+ // buffer, pullIntoDescriptor’s byte offset, 0 »).
+ JS::Rooted<JSObject*> pullIntoBuffer(aCx, pullIntoDescriptor->Buffer());
+ JS::Rooted<JSObject*> emptyView(
+ aCx,
+ ConstructFromPullIntoConstructor(aCx, ctor, pullIntoBuffer,
+ pullIntoDescriptor->ByteOffset(), 0));
+ if (!emptyView) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ // Step 12.2. Perform readIntoRequest’s close steps, given emptyView.
+ JS::Rooted<JS::Value> emptyViewValue(aCx, JS::ObjectValue(*emptyView));
+ aReadIntoRequest->CloseSteps(aCx, emptyViewValue, aRv);
+
+ // Step 12.3. Return.
+ return;
+ }
+
+ // Step 13,. If controller.[[queueTotalSize]] > 0,
+ if (aController->QueueTotalSize() > 0) {
+ // Step 13.1 If
+ // !ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
+ // pullIntoDescriptor) is true,
+ bool ready = ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(
+ aCx, aController, pullIntoDescriptor, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ if (ready) {
+ // Step 13.1.1 Let filledView be
+ // !ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
+ JS::Rooted<JSObject*> filledView(
+ aCx, ReadableByteStreamControllerConvertPullIntoDescriptor(
+ aCx, pullIntoDescriptor, aRv));
+ if (aRv.Failed()) {
+ return;
+ }
+ // Step 13.1.2. Perform
+ // !ReadableByteStreamControllerHandleQueueDrain(controller).
+ ReadableByteStreamControllerHandleQueueDrain(aCx, aController, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ // Step 13.1.3. Perform readIntoRequest’s chunk steps, given filledView.
+ JS::Rooted<JS::Value> filledViewValue(aCx, JS::ObjectValue(*filledView));
+ aReadIntoRequest->ChunkSteps(aCx, filledViewValue, aRv);
+ // Step 13.1.4. Return.
+ return;
+ }
+
+ // Step 13.2 If controller.[[closeRequested]] is true,
+ if (aController->CloseRequested()) {
+ // Step 13.2.1. Let e be a TypeError exception.
+ ErrorResult typeError;
+ typeError.ThrowTypeError("Close Requested True during Pull Into");
+
+ JS::Rooted<JS::Value> e(aCx);
+ MOZ_RELEASE_ASSERT(ToJSValue(aCx, std::move(typeError), &e));
+
+ // Step 13.2.2. Perform !ReadableByteStreamControllerError(controller, e).
+ ReadableByteStreamControllerError(aController, e, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 13.2.3. Perform readIntoRequest’s error steps, given e.
+ aReadIntoRequest->ErrorSteps(aCx, e, aRv);
+
+ // Step 13.2.4. Return.
+ return;
+ }
+ }
+
+ // Step 14. Append pullIntoDescriptor to controller.[[pendingPullIntos]].
+ aController->PendingPullIntos().insertBack(pullIntoDescriptor);
+
+ // Step 15. Perform !ReadableStreamAddReadIntoRequest(stream,
+ // readIntoRequest).
+ ReadableStreamAddReadIntoRequest(stream, aReadIntoRequest);
+
+ // Step 16, Perform
+ // !ReadableByteStreamControllerCallPullIfNeeded(controller).
+ ReadableByteStreamControllerCallPullIfNeeded(aCx, aController, aRv);
+}
+
+// https://streams.spec.whatwg.org/#set-up-readable-byte-stream-controller
+void SetUpReadableByteStreamController(
+ JSContext* aCx, ReadableStream* aStream,
+ ReadableByteStreamController* aController,
+ UnderlyingSourceAlgorithmsBase* aAlgorithms, double aHighWaterMark,
+ Maybe<uint64_t> aAutoAllocateChunkSize, ErrorResult& aRv) {
+ // Step 1. Assert: stream.[[controller]] is undefined.
+ MOZ_ASSERT(!aStream->Controller());
+
+ // Step 2. If autoAllocateChunkSize is not undefined,
+ // Step 2.1. Assert: ! IsInteger(autoAllocateChunkSize) is true. Implicit
+ // Step 2.2. Assert: autoAllocateChunkSize is positive. (Implicit by
+ // type.)
+
+ // Step 3. Set controller.[[stream]] to stream.
+ aController->SetStream(aStream);
+
+ // Step 4. Set controller.[[pullAgain]] and controller.[[pulling]] to false.
+ aController->SetPullAgain(false);
+ aController->SetPulling(false);
+
+ // Step 5. Set controller.[[byobRequest]] to null.
+ aController->SetByobRequest(nullptr);
+
+ // Step 6. Perform !ResetQueue(controller).
+ ResetQueue(aController);
+
+ // Step 7. Set controller.[[closeRequested]] and controller.[[started]] to
+ // false.
+ aController->SetCloseRequested(false);
+ aController->SetStarted(false);
+
+ // Step 8. Set controller.[[strategyHWM]] to highWaterMark.
+ aController->SetStrategyHWM(aHighWaterMark);
+
+ // Step 9. Set controller.[[pullAlgorithm]] to pullAlgorithm.
+ // Step 10. Set controller.[[cancelAlgorithm]] to cancelAlgorithm.
+ aController->SetAlgorithms(*aAlgorithms);
+
+ // Step 11. Set controller.[[autoAllocateChunkSize]] to autoAllocateChunkSize.
+ aController->SetAutoAllocateChunkSize(aAutoAllocateChunkSize);
+
+ // Step 12. Set controller.[[pendingPullIntos]] to a new empty list.
+ aController->PendingPullIntos().clear();
+
+ // Step 13. Set stream.[[controller]] to controller.
+ aStream->SetController(*aController);
+
+ // Step 14. Let startResult be the result of performing startAlgorithm.
+ JS::Rooted<JS::Value> startResult(aCx, JS::UndefinedValue());
+ RefPtr<ReadableStreamController> controller = aController;
+ aAlgorithms->StartCallback(aCx, *controller, &startResult, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Let startPromise be a promise resolved with startResult.
+ RefPtr<Promise> startPromise =
+ Promise::CreateInfallible(aStream->GetParentObject());
+ startPromise->MaybeResolve(startResult);
+
+ // Step 16+17
+ startPromise->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ ReadableByteStreamController* aController)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ MOZ_ASSERT(aController);
+
+ // Step 16.1
+ aController->SetStarted(true);
+
+ // Step 16.2
+ aController->SetPulling(false);
+
+ // Step 16.3
+ aController->SetPullAgain(false);
+
+ // Step 16.4:
+ ReadableByteStreamControllerCallPullIfNeeded(
+ aCx, MOZ_KnownLive(aController), aRv);
+ },
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ ReadableByteStreamController* aController) {
+ // Step 17.1
+ ReadableByteStreamControllerError(aController, aValue, aRv);
+ },
+ RefPtr(aController));
+}
+
+// https://streams.spec.whatwg.org/#set-up-readable-byte-stream-controller-from-underlying-source
+void SetUpReadableByteStreamControllerFromUnderlyingSource(
+ JSContext* aCx, ReadableStream* aStream,
+ JS::Handle<JSObject*> aUnderlyingSource,
+ UnderlyingSource& aUnderlyingSourceDict, double aHighWaterMark,
+ ErrorResult& aRv) {
+ // Step 1. Let controller be a new ReadableByteStreamController.
+ auto controller =
+ MakeRefPtr<ReadableByteStreamController>(aStream->GetParentObject());
+
+ // Step 2 - 7
+ auto algorithms = MakeRefPtr<UnderlyingSourceAlgorithms>(
+ aStream->GetParentObject(), aUnderlyingSource, aUnderlyingSourceDict);
+
+ // Step 8. Let autoAllocateChunkSize be
+ // underlyingSourceDict["autoAllocateChunkSize"], if it exists, or undefined
+ // otherwise.
+ Maybe<uint64_t> autoAllocateChunkSize = mozilla::Nothing();
+ if (aUnderlyingSourceDict.mAutoAllocateChunkSize.WasPassed()) {
+ uint64_t value = aUnderlyingSourceDict.mAutoAllocateChunkSize.Value();
+ // Step 9. If autoAllocateChunkSize is 0, then throw a TypeError
+ // exception.
+ if (value == 0) {
+ aRv.ThrowTypeError("autoAllocateChunkSize can not be zero.");
+ return;
+ }
+ autoAllocateChunkSize = mozilla::Some(value);
+ }
+
+ // Step 10. Perform ? SetUpReadableByteStreamController(stream, controller,
+ // startAlgorithm, pullAlgorithm, cancelAlgorithm, highWaterMark,
+ // autoAllocateChunkSize).
+ SetUpReadableByteStreamController(aCx, aStream, controller, algorithms,
+ aHighWaterMark, autoAllocateChunkSize, aRv);
+}
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
diff --git a/dom/streams/ReadableByteStreamController.h b/dom/streams/ReadableByteStreamController.h
new file mode 100644
index 0000000000..45e03ba3b4
--- /dev/null
+++ b/dom/streams/ReadableByteStreamController.h
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadableByteStreamController_h
+#define mozilla_dom_ReadableByteStreamController_h
+
+#include <cstddef>
+#include "UnderlyingSourceCallbackHelpers.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "mozilla/dom/QueueWithSizes.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadRequest.h"
+#include "mozilla/dom/ReadableStreamBYOBRequest.h"
+#include "mozilla/dom/ReadableStreamController.h"
+#include "mozilla/dom/TypedArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/Nullable.h"
+#include "nsTArray.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+// https://streams.spec.whatwg.org/#pull-into-descriptor-reader-type
+// Indicates what type of readable stream reader initiated this request,
+// or None if the initiating reader was released.
+enum ReaderType { Default, BYOB, None };
+
+struct PullIntoDescriptor;
+struct ReadableByteStreamQueueEntry;
+struct ReadIntoRequest;
+
+class ReadableByteStreamController final : public ReadableStreamController,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ ReadableByteStreamController, ReadableStreamController)
+
+ public:
+ explicit ReadableByteStreamController(nsIGlobalObject* aGlobal);
+
+ protected:
+ ~ReadableByteStreamController() override;
+
+ public:
+ bool IsDefault() override { return false; }
+ bool IsByte() override { return true; }
+ ReadableStreamDefaultController* AsDefault() override { return nullptr; }
+ ReadableByteStreamController* AsByte() override { return this; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<ReadableStreamBYOBRequest> GetByobRequest(JSContext* aCx,
+ ErrorResult& aRv);
+
+ Nullable<double> GetDesiredSize() const;
+
+ MOZ_CAN_RUN_SCRIPT void Close(JSContext* aCx, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void Enqueue(JSContext* aCx, const ArrayBufferView& aChunk,
+ ErrorResult& aRv);
+
+ void Error(JSContext* aCx, JS::Handle<JS::Value> aErrorValue,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CancelSteps(
+ JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) override;
+ MOZ_CAN_RUN_SCRIPT void PullSteps(JSContext* aCx, ReadRequest* aReadRequest,
+ ErrorResult& aRv) override;
+ void ReleaseSteps() override;
+
+ // Internal Slot Accessors
+ Maybe<uint64_t> AutoAllocateChunkSize() { return mAutoAllocateChunkSize; }
+ void SetAutoAllocateChunkSize(Maybe<uint64_t>& aSize) {
+ mAutoAllocateChunkSize = aSize;
+ }
+
+ ReadableStreamBYOBRequest* GetByobRequest() const { return mByobRequest; }
+ void SetByobRequest(ReadableStreamBYOBRequest* aByobRequest) {
+ mByobRequest = aByobRequest;
+ }
+
+ LinkedList<RefPtr<PullIntoDescriptor>>& PendingPullIntos() {
+ return mPendingPullIntos;
+ }
+ void ClearPendingPullIntos();
+
+ double QueueTotalSize() const { return mQueueTotalSize; }
+ void SetQueueTotalSize(double aQueueTotalSize) {
+ mQueueTotalSize = aQueueTotalSize;
+ }
+ void AddToQueueTotalSize(double aLength) { mQueueTotalSize += aLength; }
+
+ double StrategyHWM() const { return mStrategyHWM; }
+ void SetStrategyHWM(double aStrategyHWM) { mStrategyHWM = aStrategyHWM; }
+
+ bool CloseRequested() const { return mCloseRequested; }
+ void SetCloseRequested(bool aCloseRequested) {
+ mCloseRequested = aCloseRequested;
+ }
+
+ LinkedList<RefPtr<ReadableByteStreamQueueEntry>>& Queue() { return mQueue; }
+ void ClearQueue();
+
+ bool Started() const { return mStarted; }
+ void SetStarted(bool aStarted) { mStarted = aStarted; }
+
+ bool Pulling() const { return mPulling; }
+ void SetPulling(bool aPulling) { mPulling = aPulling; }
+
+ bool PullAgain() const { return mPullAgain; }
+ void SetPullAgain(bool aPullAgain) { mPullAgain = aPullAgain; }
+
+ private:
+ // A boolean flag indicating whether the stream has been closed by its
+ // underlying byte source, but still has chunks in its internal queue that
+ // have not yet been read
+ bool mCloseRequested = false;
+
+ // A boolean flag set to true if the stream’s mechanisms requested a call
+ // to the underlying byte source's pull algorithm to pull more data, but the
+ // pull could not yet be done since a previous call is still executing
+ bool mPullAgain = false;
+
+ // A boolean flag indicating whether the underlying byte source has finished
+ // starting
+ bool mStarted = false;
+
+ // A boolean flag set to true while the underlying byte source's pull
+ // algorithm is executing and the returned promise has not yet fulfilled,
+ // used to prevent reentrant calls
+ bool mPulling = false;
+
+ // A positive integer, when the automatic buffer allocation feature is
+ // enabled. In that case, this value specifies the size of buffer to allocate.
+ // It is undefined otherwise.
+ Maybe<uint64_t> mAutoAllocateChunkSize;
+
+ // A ReadableStreamBYOBRequest instance representing the current BYOB pull
+ // request, or null if there are no pending requests
+ RefPtr<ReadableStreamBYOBRequest> mByobRequest;
+
+ // A list of pull-into descriptors
+ LinkedList<RefPtr<PullIntoDescriptor>> mPendingPullIntos;
+
+ // A list of readable byte stream queue entries representing the stream’s
+ // internal queue of chunks
+ //
+ // This is theoretically supposed to be a QueueWithSizes, but it is
+ // mostly not actually manipulated or used like QueueWithSizes, so instead we
+ // use a LinkedList.
+ LinkedList<RefPtr<ReadableByteStreamQueueEntry>> mQueue;
+
+ // The total size, in bytes, of all the chunks stored in [[queue]] (see § 8.1
+ // Queue-with-sizes)
+ double mQueueTotalSize = 0.0;
+
+ // A number supplied to the constructor as part of the stream’s queuing
+ // strategy, indicating the point at which the stream will apply backpressure
+ // to its underlying byte source
+ double mStrategyHWM = 0.0;
+};
+
+namespace streams_abstract {
+
+MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerRespond(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ uint64_t aBytesWritten, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerRespondInternal(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ uint64_t aBytesWritten, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerRespondWithNewView(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ JS::Handle<JSObject*> aView, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerPullInto(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ JS::Handle<JSObject*> aView, ReadIntoRequest* aReadIntoRequest,
+ ErrorResult& aRv);
+
+void ReadableByteStreamControllerError(
+ ReadableByteStreamController* aController, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerEnqueue(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ JS::Handle<JSObject*> aChunk, ErrorResult& aRv);
+
+already_AddRefed<ReadableStreamBYOBRequest>
+ReadableByteStreamControllerGetBYOBRequest(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerClose(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void SetUpReadableByteStreamController(
+ JSContext* aCx, ReadableStream* aStream,
+ ReadableByteStreamController* aController,
+ UnderlyingSourceAlgorithmsBase* aAlgorithms, double aHighWaterMark,
+ Maybe<uint64_t> aAutoAllocateChunkSize, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableByteStreamControllerCallPullIfNeeded(
+ JSContext* aCx, ReadableByteStreamController* aController,
+ ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void SetUpReadableByteStreamControllerFromUnderlyingSource(
+ JSContext* aCx, ReadableStream* aStream,
+ JS::Handle<JSObject*> aUnderlyingSource,
+ UnderlyingSource& aUnderlyingSourceDict, double aHighWaterMark,
+ ErrorResult& aRv);
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/ReadableStream.cpp b/dom/streams/ReadableStream.cpp
new file mode 100644
index 0000000000..cc2266cbfb
--- /dev/null
+++ b/dom/streams/ReadableStream.cpp
@@ -0,0 +1,1521 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ReadableStream.h"
+
+#include "ReadIntoRequest.h"
+#include "ReadableStreamPipeTo.h"
+#include "ReadableStreamTee.h"
+#include "StreamUtils.h"
+#include "TeeState.h"
+#include "js/Array.h"
+#include "js/Exception.h"
+#include "js/PropertyAndElement.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "js/Iterator.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/BindingCallContext.h"
+#include "mozilla/dom/ByteStreamHelpers.h"
+#include "mozilla/dom/QueueWithSizes.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "mozilla/dom/ReadRequest.h"
+#include "mozilla/dom/ReadableByteStreamController.h"
+#include "mozilla/dom/ReadableStreamBYOBReader.h"
+#include "mozilla/dom/ReadableStreamBYOBRequest.h"
+#include "mozilla/dom/ReadableStreamBinding.h"
+#include "mozilla/dom/ReadableStreamController.h"
+#include "mozilla/dom/ReadableStreamDefaultController.h"
+#include "mozilla/dom/ReadableStreamDefaultReader.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/UnderlyingSourceBinding.h"
+#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/WritableStreamDefaultWriter.h"
+#include "nsCOMPtr.h"
+
+#include "mozilla/dom/Promise-inl.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ mozilla::Variant<mozilla::Nothing,
+ RefPtr<mozilla::dom::ReadableStreamDefaultReader>>&
+ aReader,
+ const char* aName, uint32_t aFlags = 0) {
+ if (aReader.is<RefPtr<mozilla::dom::ReadableStreamDefaultReader>>()) {
+ ImplCycleCollectionTraverse(
+ aCallback,
+ aReader.as<RefPtr<mozilla::dom::ReadableStreamDefaultReader>>(), aName,
+ aFlags);
+ }
+}
+
+inline void ImplCycleCollectionUnlink(
+ mozilla::Variant<mozilla::Nothing,
+ RefPtr<mozilla::dom::ReadableStreamDefaultReader>>&
+ aReader) {
+ aReader = AsVariant(mozilla::Nothing());
+}
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(
+ ReadableStream, (mGlobal, mController, mReader), (mStoredError))
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadableStream)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadableStream)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStream)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ReadableStream::ReadableStream(nsIGlobalObject* aGlobal,
+ HoldDropJSObjectsCaller aHoldDropCaller)
+ : mGlobal(aGlobal), mReader(nullptr), mHoldDropCaller(aHoldDropCaller) {
+ if (mHoldDropCaller == HoldDropJSObjectsCaller::Implicit) {
+ mozilla::HoldJSObjects(this);
+ }
+}
+
+ReadableStream::ReadableStream(const GlobalObject& aGlobal,
+ HoldDropJSObjectsCaller aHoldDropCaller)
+ : mGlobal(do_QueryInterface(aGlobal.GetAsSupports())),
+ mReader(nullptr),
+ mHoldDropCaller(aHoldDropCaller) {
+ if (mHoldDropCaller == HoldDropJSObjectsCaller::Implicit) {
+ mozilla::HoldJSObjects(this);
+ }
+}
+
+ReadableStream::~ReadableStream() {
+ if (mHoldDropCaller == HoldDropJSObjectsCaller::Implicit) {
+ mozilla::DropJSObjects(this);
+ }
+}
+
+JSObject* ReadableStream::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ReadableStream_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+ReadableStreamDefaultReader* ReadableStream::GetDefaultReader() {
+ return mReader->AsDefault();
+}
+
+void ReadableStream::SetReader(ReadableStreamGenericReader* aReader) {
+ mReader = aReader;
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#readable-stream-has-byob-reader
+bool ReadableStreamHasBYOBReader(ReadableStream* aStream) {
+ // Step 1. Let reader be stream.[[reader]].
+ ReadableStreamGenericReader* reader = aStream->GetReader();
+
+ // Step 2. If reader is undefined, return false.
+ if (!reader) {
+ return false;
+ }
+
+ // Step 3. If reader implements ReadableStreamBYOBReader, return true.
+ // Step 4. Return false.
+ return reader->IsBYOB();
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-has-default-reader
+bool ReadableStreamHasDefaultReader(ReadableStream* aStream) {
+ // Step 1. Let reader be stream.[[reader]].
+ ReadableStreamGenericReader* reader = aStream->GetReader();
+
+ // Step 2. If reader is undefined, return false.
+ if (!reader) {
+ return false;
+ }
+
+ // Step 3. If reader implements ReadableStreamDefaultReader, return true.
+ // Step 4. Return false.
+ return reader->IsDefault();
+}
+
+} // namespace streams_abstract
+
+// Streams Spec: 4.2.4: https://streams.spec.whatwg.org/#rs-prototype
+/* static */
+already_AddRefed<ReadableStream> ReadableStream::Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<JS::Handle<JSObject*>>& aUnderlyingSource,
+ const QueuingStrategy& aStrategy, ErrorResult& aRv) {
+ // Step 1.
+ JS::Rooted<JSObject*> underlyingSourceObj(
+ aGlobal.Context(),
+ aUnderlyingSource.WasPassed() ? aUnderlyingSource.Value() : nullptr);
+
+ // Step 2.
+ RootedDictionary<UnderlyingSource> underlyingSourceDict(aGlobal.Context());
+ if (underlyingSourceObj) {
+ JS::Rooted<JS::Value> objValue(aGlobal.Context(),
+ JS::ObjectValue(*underlyingSourceObj));
+ dom::BindingCallContext callCx(aGlobal.Context(),
+ "ReadableStream.constructor");
+ aRv.MightThrowJSException();
+ if (!underlyingSourceDict.Init(callCx, objValue)) {
+ aRv.StealExceptionFromJSContext(aGlobal.Context());
+ return nullptr;
+ }
+ }
+
+ // Step 3.
+ RefPtr<ReadableStream> readableStream =
+ new ReadableStream(aGlobal, HoldDropJSObjectsCaller::Implicit);
+
+ // Step 4.
+ if (underlyingSourceDict.mType.WasPassed()) {
+ // Implicit assertion on above check.
+ MOZ_ASSERT(underlyingSourceDict.mType.Value() == ReadableStreamType::Bytes);
+
+ // Step 4.1
+ if (aStrategy.mSize.WasPassed()) {
+ aRv.ThrowRangeError("Implementation preserved member 'size'");
+ return nullptr;
+ }
+
+ // Step 4.2
+ double highWaterMark = ExtractHighWaterMark(aStrategy, 0, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 4.3
+ SetUpReadableByteStreamControllerFromUnderlyingSource(
+ aGlobal.Context(), readableStream, underlyingSourceObj,
+ underlyingSourceDict, highWaterMark, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return readableStream.forget();
+ }
+
+ // Step 5.1 (implicit in above check)
+ // Step 5.2. Extract callback.
+ //
+ // Implementation Note: The specification demands that if the size doesn't
+ // exist, we instead would provide an algorithm that returns 1. Instead, we
+ // will teach callers that a missing callback should simply return 1, rather
+ // than gin up a fake callback here.
+ //
+ // This decision may need to be revisited if the default action ever diverges
+ // within the specification.
+ RefPtr<QueuingStrategySize> sizeAlgorithm =
+ aStrategy.mSize.WasPassed() ? &aStrategy.mSize.Value() : nullptr;
+
+ // Step 5.3
+ double highWaterMark = ExtractHighWaterMark(aStrategy, 1, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 5.4.
+ SetupReadableStreamDefaultControllerFromUnderlyingSource(
+ aGlobal.Context(), readableStream, underlyingSourceObj,
+ underlyingSourceDict, highWaterMark, sizeAlgorithm, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return readableStream.forget();
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-from-iterable
+class ReadableStreamFromAlgorithms final
+ : public UnderlyingSourceAlgorithmsWrapper {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ ReadableStreamFromAlgorithms, UnderlyingSourceAlgorithmsWrapper)
+
+ ReadableStreamFromAlgorithms(nsIGlobalObject* aGlobal,
+ JS::Handle<JSObject*> aIteratorRecord)
+ : mGlobal(aGlobal), mIteratorRecordMaybeCrossRealm(aIteratorRecord) {
+ mozilla::HoldJSObjects(this);
+ };
+
+ // Step 3. Let startAlgorithm be an algorithm that returns undefined.
+ // Note: Provided by UnderlyingSourceAlgorithmsWrapper::StartCallback.
+
+ // Step 4. Let pullAlgorithm be the following steps:
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PullCallbackImpl(
+ JSContext* aCx, ReadableStreamController& aController,
+ ErrorResult& aRv) override {
+ aRv.MightThrowJSException();
+
+ JS::Rooted<JSObject*> iteratorRecord(aCx, mIteratorRecordMaybeCrossRealm);
+ JSAutoRealm ar(aCx, iteratorRecord);
+
+ // Step 1. Let nextResult be IteratorNext(iteratorRecord).
+ JS::Rooted<JS::Value> nextResult(aCx);
+ if (!JS::IteratorNext(aCx, iteratorRecord, &nextResult)) {
+ // Step 2. If nextResult is an abrupt completion, return a promise
+ // rejected with nextResult.[[Value]].
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 3. Let nextPromise be a promise resolved with nextResult.[[Value]].
+ RefPtr<Promise> nextPromise = Promise::CreateInfallible(mGlobal);
+ nextPromise->MaybeResolve(nextResult);
+
+ // Step 4. Return the result of reacting to nextPromise with the following
+ // fulfillment steps, given iterResult:
+ auto result = nextPromise->ThenWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aIterResult, ErrorResult& aRv,
+ const RefPtr<ReadableStreamDefaultController>& aController)
+ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> already_AddRefed<Promise> {
+ aRv.MightThrowJSException();
+
+ // Step 4.1. If Type(iterResult) is not Object, throw a TypeError.
+ if (!aIterResult.isObject()) {
+ aRv.ThrowTypeError("next() returned a non-object value");
+ return nullptr;
+ }
+
+ JS::Rooted<JSObject*> iterResult(aCx, &aIterResult.toObject());
+
+ // Step 4.2. Let done be ? IteratorComplete(iterResult).
+ bool done = false;
+ if (!JS::IteratorComplete(aCx, iterResult, &done)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 4.3. If done is true:
+ if (done) {
+ // Step 4.3.1. Perform !
+ // ReadableStreamDefaultControllerClose(stream.[[controller]]).
+ ReadableStreamDefaultControllerClose(aCx, aController, aRv);
+ } else {
+ // Step 4.4. Otherwise:
+ // Step 4.4.1. Let value be ? IteratorValue(iterResult).
+ JS::Rooted<JS::Value> value(aCx);
+ if (!JS::IteratorValue(aCx, iterResult, &value)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 4.4.2. Perform !
+ // ReadableStreamDefaultControllerEnqueue(stream.[[controller]],
+ // value).
+ ReadableStreamDefaultControllerEnqueue(aCx, aController, value,
+ aRv);
+ }
+
+ return nullptr;
+ },
+ RefPtr(aController.AsDefault()));
+ if (result.isErr()) {
+ aRv.Throw(result.unwrapErr());
+ return nullptr;
+ }
+ return result.unwrap().forget();
+ };
+
+ // Step 5. Let cancelAlgorithm be the following steps, given reason:
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CancelCallbackImpl(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override {
+ aRv.MightThrowJSException();
+
+ JS::Rooted<JSObject*> iteratorRecord(aCx, mIteratorRecordMaybeCrossRealm);
+ JSAutoRealm ar(aCx, iteratorRecord);
+
+ // Step 1. Let iterator be iteratorRecord.[[Iterator]].
+ JS::Rooted<JS::Value> iterator(aCx);
+ if (!JS::GetIteratorRecordIterator(aCx, iteratorRecord, &iterator)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 2. Let returnMethod be GetMethod(iterator, "return").
+ JS::Rooted<JS::Value> returnMethod(aCx);
+ if (!JS::GetReturnMethod(aCx, iterator, &returnMethod)) {
+ // Step 3. If returnMethod is an abrupt completion, return a promise
+ // rejected with returnMethod.[[Value]].
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 4. If returnMethod.[[Value]] is undefined, return a promise resolved
+ // with undefined.
+ if (returnMethod.isUndefined()) {
+ return Promise::CreateResolvedWithUndefined(mGlobal, aRv);
+ }
+
+ // Step 5. Let returnResult be Call(returnMethod.[[Value]], iterator, «
+ // reason »).
+ JS::Rooted<JS::Value> returnResult(aCx);
+ if (!JS::Call(aCx, iterator, returnMethod,
+ JS::HandleValueArray(aReason.Value()), &returnResult)) {
+ // Step 6. If returnResult is an abrupt completion, return a promise
+ // rejected with returnResult.[[Value]].
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 7. Let returnPromise be a promise resolved with
+ // returnResult.[[Value]].
+ RefPtr<Promise> returnPromise = Promise::CreateInfallible(mGlobal);
+ returnPromise->MaybeResolve(returnResult);
+
+ // Step 8. Return the result of reacting to returnPromise with the following
+ // fulfillment steps, given iterResult:
+ auto result = returnPromise->ThenWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aIterResult, ErrorResult& aRv)
+ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> already_AddRefed<Promise> {
+ // Step 8.1. If Type(iterResult) is not Object, throw a TypeError.
+ if (!aIterResult.isObject()) {
+ aRv.ThrowTypeError("return() returned a non-object value");
+ return nullptr;
+ }
+
+ // Step 8.2. Return undefined.
+ return nullptr;
+ });
+ if (result.isErr()) {
+ aRv.Throw(result.unwrapErr());
+ return nullptr;
+ }
+ return result.unwrap().forget();
+ };
+
+ protected:
+ ~ReadableStreamFromAlgorithms() override { mozilla::DropJSObjects(this); };
+
+ private:
+ // Virtually const, but are cycle collected
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ JS::Heap<JSObject*> mIteratorRecordMaybeCrossRealm;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(
+ ReadableStreamFromAlgorithms, UnderlyingSourceAlgorithmsWrapper, (mGlobal),
+ (mIteratorRecordMaybeCrossRealm))
+NS_IMPL_ADDREF_INHERITED(ReadableStreamFromAlgorithms,
+ UnderlyingSourceAlgorithmsWrapper)
+NS_IMPL_RELEASE_INHERITED(ReadableStreamFromAlgorithms,
+ UnderlyingSourceAlgorithmsWrapper)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamFromAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsWrapper)
+
+// https://streams.spec.whatwg.org/#readable-stream-from-iterable
+static already_AddRefed<ReadableStream> MOZ_CAN_RUN_SCRIPT
+ReadableStreamFromIterable(JSContext* aCx, nsIGlobalObject* aGlobal,
+ JS::Handle<JS::Value> aAsyncIterable,
+ ErrorResult& aRv) {
+ aRv.MightThrowJSException();
+
+ // Step 1. Let stream be undefined. (not required)
+ // Step 2. Let iteratorRecord be ? GetIterator(asyncIterable, async).
+ JS::Rooted<JSObject*> iteratorRecord(
+ aCx, JS::GetIteratorObject(aCx, aAsyncIterable, true));
+ if (!iteratorRecord) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Steps 3-5. are in ReadableStreamFromAlgorithms.
+ auto algorithms =
+ MakeRefPtr<ReadableStreamFromAlgorithms>(aGlobal, iteratorRecord);
+
+ // Step 6. Set stream to ! CreateReadableStream(startAlgorithm, pullAlgorithm,
+ // cancelAlgorithm, 0).
+ // Step 7. Return stream.
+ return ReadableStream::CreateAbstract(aCx, aGlobal, algorithms,
+ mozilla::Some(0.0), nullptr, aRv);
+}
+
+/* static */
+already_AddRefed<ReadableStream> ReadableStream::From(
+ const GlobalObject& aGlobal, JS::Handle<JS::Value> aAsyncIterable,
+ ErrorResult& aRv) {
+ // Step 1. Return ? ReadableStreamFromIterable(asyncIterable).
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ return ReadableStreamFromIterable(aGlobal.Context(), global, aAsyncIterable,
+ aRv);
+}
+
+// Dealing with const this ptr is a pain, so just re-implement.
+// https://streams.spec.whatwg.org/#is-readable-stream-locked
+bool ReadableStream::Locked() const {
+ // Step 1 + 2.
+ return mReader;
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#initialize-readable-stream
+static void InitializeReadableStream(ReadableStream* aStream) {
+ // Step 1.
+ aStream->SetState(ReadableStream::ReaderState::Readable);
+
+ // Step 2.
+ aStream->SetReader(nullptr);
+ aStream->SetStoredError(JS::UndefinedHandleValue);
+
+ // Step 3.
+ aStream->SetDisturbed(false);
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#create-readable-stream
+MOZ_CAN_RUN_SCRIPT
+already_AddRefed<ReadableStream> ReadableStream::CreateAbstract(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ UnderlyingSourceAlgorithmsBase* aAlgorithms,
+ mozilla::Maybe<double> aHighWaterMark, QueuingStrategySize* aSizeAlgorithm,
+ ErrorResult& aRv) {
+ // Step 1. If highWaterMark was not passed, set it to 1.
+ double highWaterMark = aHighWaterMark.valueOr(1.0);
+
+ // Step 2. consumers of sizeAlgorithm
+ // handle null algorithms correctly.
+ // Step 3.
+ MOZ_ASSERT(IsNonNegativeNumber(highWaterMark));
+ // Step 4.
+ RefPtr<ReadableStream> stream =
+ new ReadableStream(aGlobal, HoldDropJSObjectsCaller::Implicit);
+
+ // Step 5.
+ InitializeReadableStream(stream);
+
+ // Step 6.
+ RefPtr<ReadableStreamDefaultController> controller =
+ new ReadableStreamDefaultController(aGlobal);
+
+ // Step 7.
+ SetUpReadableStreamDefaultController(aCx, stream, controller, aAlgorithms,
+ highWaterMark, aSizeAlgorithm, aRv);
+
+ // Step 8.
+ return stream.forget();
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#readable-stream-close
+void ReadableStreamClose(JSContext* aCx, ReadableStream* aStream,
+ ErrorResult& aRv) {
+ // Step 1.
+ MOZ_ASSERT(aStream->State() == ReadableStream::ReaderState::Readable);
+
+ // Step 2.
+ aStream->SetState(ReadableStream::ReaderState::Closed);
+
+ // Step 3.
+ ReadableStreamGenericReader* reader = aStream->GetReader();
+
+ // Step 4.
+ if (!reader) {
+ return;
+ }
+
+ // Step 5.
+ reader->ClosedPromise()->MaybeResolveWithUndefined();
+
+ // Step 6.
+ if (reader->IsDefault()) {
+ // Step 6.1. Let readRequests be reader.[[readRequests]].
+ // Move LinkedList out of DefaultReader onto stack to avoid the potential
+ // for concurrent modification, which could invalidate the iterator.
+ //
+ // See https://bugs.chromium.org/p/chromium/issues/detail?id=1045874 as an
+ // example of the kind of issue that could occur.
+ LinkedList<RefPtr<ReadRequest>> readRequests =
+ std::move(reader->AsDefault()->ReadRequests());
+
+ // Step 6.2. Set reader.[[readRequests]] to an empty list.
+ // Note: The std::move already cleared this anyway.
+ reader->AsDefault()->ReadRequests().clear();
+
+ // Step 6.3. For each readRequest of readRequests,
+ // Drain the local list and destroy elements along the way.
+ while (RefPtr<ReadRequest> readRequest = readRequests.popFirst()) {
+ // Step 6.3.1. Perform readRequest’s close steps.
+ readRequest->CloseSteps(aCx, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ }
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-cancel
+already_AddRefed<Promise> ReadableStreamCancel(JSContext* aCx,
+ ReadableStream* aStream,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ // Step 1.
+ aStream->SetDisturbed(true);
+
+ // Step 2.
+ if (aStream->State() == ReadableStream::ReaderState::Closed) {
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(aStream->GetParentObject());
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ // Step 3.
+ if (aStream->State() == ReadableStream::ReaderState::Errored) {
+ JS::Rooted<JS::Value> storedError(aCx, aStream->StoredError());
+ return Promise::CreateRejected(aStream->GetParentObject(), storedError,
+ aRv);
+ }
+
+ // Step 4.
+ ReadableStreamClose(aCx, aStream, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 5.
+ ReadableStreamGenericReader* reader = aStream->GetReader();
+
+ // Step 6.
+ if (reader && reader->IsBYOB()) {
+ // Step 6.1. Let readIntoRequests be reader.[[readIntoRequests]].
+ LinkedList<RefPtr<ReadIntoRequest>> readIntoRequests =
+ std::move(reader->AsBYOB()->ReadIntoRequests());
+
+ // Step 6.2. Set reader.[[readIntoRequests]] to an empty list.
+ // Note: The std::move already cleared this anyway.
+ reader->AsBYOB()->ReadIntoRequests().clear();
+
+ // Step 6.3. For each readIntoRequest of readIntoRequests,
+ while (RefPtr<ReadIntoRequest> readIntoRequest =
+ readIntoRequests.popFirst()) {
+ // Step 6.3.1.Perform readIntoRequest’s close steps, given undefined.
+ readIntoRequest->CloseSteps(aCx, JS::UndefinedHandleValue, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+ }
+
+ // Step 7.
+ RefPtr<ReadableStreamController> controller(aStream->Controller());
+ RefPtr<Promise> sourceCancelPromise =
+ controller->CancelSteps(aCx, aError, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 8.
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(sourceCancelPromise->GetParentObject());
+
+ // ThenWithCycleCollectedArgs will carry promise, keeping it alive until the
+ // callback executes.
+ Result<RefPtr<Promise>, nsresult> returnResult =
+ sourceCancelPromise->ThenWithCycleCollectedArgs(
+ [](JSContext*, JS::Handle<JS::Value>, ErrorResult&,
+ RefPtr<Promise> newPromise) {
+ newPromise->MaybeResolveWithUndefined();
+ return newPromise.forget();
+ },
+ promise);
+
+ if (returnResult.isErr()) {
+ aRv.Throw(returnResult.unwrapErr());
+ return nullptr;
+ }
+
+ return returnResult.unwrap().forget();
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#rs-cancel
+already_AddRefed<Promise> ReadableStream::Cancel(JSContext* aCx,
+ JS::Handle<JS::Value> aReason,
+ ErrorResult& aRv) {
+ // Step 1. If ! IsReadableStreamLocked(this) is true,
+ // return a promise rejected with a TypeError exception.
+ if (Locked()) {
+ aRv.ThrowTypeError("Cannot cancel a stream locked by a reader.");
+ return nullptr;
+ }
+
+ // Step 2. Return ! ReadableStreamCancel(this, reason).
+ RefPtr<ReadableStream> thisRefPtr = this;
+ return ReadableStreamCancel(aCx, thisRefPtr, aReason, aRv);
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#acquire-readable-stream-reader
+already_AddRefed<ReadableStreamDefaultReader>
+AcquireReadableStreamDefaultReader(ReadableStream* aStream, ErrorResult& aRv) {
+ // Step 1.
+ RefPtr<ReadableStreamDefaultReader> reader =
+ new ReadableStreamDefaultReader(aStream->GetParentObject());
+
+ // Step 2.
+ SetUpReadableStreamDefaultReader(reader, aStream, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 3.
+ return reader.forget();
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#rs-get-reader
+void ReadableStream::GetReader(const ReadableStreamGetReaderOptions& aOptions,
+ OwningReadableStreamReader& resultReader,
+ ErrorResult& aRv) {
+ // Step 1. If options["mode"] does not exist,
+ // return ? AcquireReadableStreamDefaultReader(this).
+ if (!aOptions.mMode.WasPassed()) {
+ RefPtr<ReadableStreamDefaultReader> defaultReader =
+ AcquireReadableStreamDefaultReader(this, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ resultReader.SetAsReadableStreamDefaultReader() = defaultReader;
+ return;
+ }
+
+ // Step 2. Assert: options["mode"] is "byob".
+ MOZ_ASSERT(aOptions.mMode.Value() == ReadableStreamReaderMode::Byob);
+
+ // Step 3. Return ? AcquireReadableStreamBYOBReader(this).
+ RefPtr<ReadableStreamBYOBReader> byobReader =
+ AcquireReadableStreamBYOBReader(this, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ resultReader.SetAsReadableStreamBYOBReader() = byobReader;
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#is-readable-stream-locked
+bool IsReadableStreamLocked(ReadableStream* aStream) {
+ // Step 1 + 2.
+ return aStream->Locked();
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#rs-pipe-through
+MOZ_CAN_RUN_SCRIPT already_AddRefed<ReadableStream> ReadableStream::PipeThrough(
+ const ReadableWritablePair& aTransform, const StreamPipeOptions& aOptions,
+ ErrorResult& aRv) {
+ // Step 1: If ! IsReadableStreamLocked(this) is true, throw a TypeError
+ // exception.
+ if (IsReadableStreamLocked(this)) {
+ aRv.ThrowTypeError("Cannot pipe from a locked stream.");
+ return nullptr;
+ }
+
+ // Step 2: If ! IsWritableStreamLocked(transform["writable"]) is true, throw a
+ // TypeError exception.
+ if (IsWritableStreamLocked(aTransform.mWritable)) {
+ aRv.ThrowTypeError("Cannot pipe to a locked stream.");
+ return nullptr;
+ }
+
+ // Step 3: Let signal be options["signal"] if it exists, or undefined
+ // otherwise.
+ RefPtr<AbortSignal> signal =
+ aOptions.mSignal.WasPassed() ? &aOptions.mSignal.Value() : nullptr;
+
+ // Step 4: Let promise be ! ReadableStreamPipeTo(this, transform["writable"],
+ // options["preventClose"], options["preventAbort"], options["preventCancel"],
+ // signal).
+ RefPtr<WritableStream> writable = aTransform.mWritable;
+ RefPtr<Promise> promise = ReadableStreamPipeTo(
+ this, writable, aOptions.mPreventClose, aOptions.mPreventAbort,
+ aOptions.mPreventCancel, signal, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 5: Set promise.[[PromiseIsHandled]] to true.
+ MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
+
+ // Step 6: Return transform["readable"].
+ return do_AddRef(aTransform.mReadable.get());
+};
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#readable-stream-get-num-read-requests
+double ReadableStreamGetNumReadRequests(ReadableStream* aStream) {
+ // Step 1.
+ MOZ_ASSERT(ReadableStreamHasDefaultReader(aStream));
+
+ // Step 2.
+ return double(aStream->GetDefaultReader()->ReadRequests().length());
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-error
+void ReadableStreamError(JSContext* aCx, ReadableStream* aStream,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
+ // Step 1.
+ MOZ_ASSERT(aStream->State() == ReadableStream::ReaderState::Readable);
+
+ // Step 2.
+ aStream->SetState(ReadableStream::ReaderState::Errored);
+
+ // Step 3.
+ aStream->SetStoredError(aValue);
+
+ // Step 4.
+ ReadableStreamGenericReader* reader = aStream->GetReader();
+
+ // Step 5.
+ if (!reader) {
+ return;
+ }
+
+ // Step 6.
+ reader->ClosedPromise()->MaybeReject(aValue);
+
+ // Step 7.
+ reader->ClosedPromise()->SetSettledPromiseIsHandled();
+
+ // Step 8.
+ if (reader->IsDefault()) {
+ // Step 8.1. Perform ! ReadableStreamDefaultReaderErrorReadRequests(reader,
+ // e).
+ RefPtr<ReadableStreamDefaultReader> defaultReader = reader->AsDefault();
+ ReadableStreamDefaultReaderErrorReadRequests(aCx, defaultReader, aValue,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ } else {
+ // Step 9. Otherwise,
+ // Step 9.1. Assert: reader implements ReadableStreamBYOBReader.
+ MOZ_ASSERT(reader->IsBYOB());
+
+ // Step 9.2. Perform ! ReadableStreamBYOBReaderErrorReadIntoRequests(reader,
+ // e).
+ RefPtr<ReadableStreamBYOBReader> byobReader = reader->AsBYOB();
+ ReadableStreamBYOBReaderErrorReadIntoRequests(aCx, byobReader, aValue, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+}
+
+// https://streams.spec.whatwg.org/#rs-default-controller-close
+void ReadableStreamFulfillReadRequest(JSContext* aCx, ReadableStream* aStream,
+ JS::Handle<JS::Value> aChunk, bool aDone,
+ ErrorResult& aRv) {
+ // Step 1.
+ MOZ_ASSERT(ReadableStreamHasDefaultReader(aStream));
+
+ // Step 2.
+ ReadableStreamDefaultReader* reader = aStream->GetDefaultReader();
+
+ // Step 3.
+ MOZ_ASSERT(!reader->ReadRequests().isEmpty());
+
+ // Step 4+5.
+ RefPtr<ReadRequest> readRequest = reader->ReadRequests().popFirst();
+
+ // Step 6.
+ if (aDone) {
+ readRequest->CloseSteps(aCx, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 7.
+ readRequest->ChunkSteps(aCx, aChunk, aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-add-read-request
+void ReadableStreamAddReadRequest(ReadableStream* aStream,
+ ReadRequest* aReadRequest) {
+ // Step 1.
+ MOZ_ASSERT(aStream->GetReader()->IsDefault());
+ // Step 2.
+ MOZ_ASSERT(aStream->State() == ReadableStream::ReaderState::Readable);
+ // Step 3.
+ aStream->GetDefaultReader()->ReadRequests().insertBack(aReadRequest);
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee
+// Step 14, 15
+MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise>
+ReadableStreamDefaultTeeSourceAlgorithms::CancelCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) {
+ // Step 1.
+ mTeeState->SetCanceled(mBranch, true);
+
+ // Step 2.
+ mTeeState->SetReason(mBranch, aReason.Value());
+
+ // Step 3.
+
+ if (mTeeState->Canceled(OtherTeeBranch(mBranch))) {
+ // Step 3.1
+
+ JS::Rooted<JSObject*> compositeReason(aCx, JS::NewArrayObject(aCx, 2));
+ if (!compositeReason) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ JS::Rooted<JS::Value> reason1(aCx, mTeeState->Reason1());
+ if (!JS_SetElement(aCx, compositeReason, 0, reason1)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ JS::Rooted<JS::Value> reason2(aCx, mTeeState->Reason2());
+ if (!JS_SetElement(aCx, compositeReason, 1, reason2)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 3.2
+ JS::Rooted<JS::Value> compositeReasonValue(
+ aCx, JS::ObjectValue(*compositeReason));
+ RefPtr<ReadableStream> stream(mTeeState->GetStream());
+ RefPtr<Promise> cancelResult =
+ ReadableStreamCancel(aCx, stream, compositeReasonValue, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 3.3
+ mTeeState->CancelPromise()->MaybeResolve(cancelResult);
+ }
+
+ // Step 4.
+ return do_AddRef(mTeeState->CancelPromise());
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee
+MOZ_CAN_RUN_SCRIPT
+static void ReadableStreamDefaultTee(JSContext* aCx, ReadableStream* aStream,
+ bool aCloneForBranch2,
+ nsTArray<RefPtr<ReadableStream>>& aResult,
+ ErrorResult& aRv) {
+ // Step 1. Implicit.
+ // Step 2. Implicit.
+
+ // Steps 3-12 are contained in the construction of Tee State.
+ RefPtr<TeeState> teeState = TeeState::Create(aStream, aCloneForBranch2, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 13 - 16
+ auto branch1Algorithms = MakeRefPtr<ReadableStreamDefaultTeeSourceAlgorithms>(
+ teeState, TeeBranch::Branch1);
+ auto branch2Algorithms = MakeRefPtr<ReadableStreamDefaultTeeSourceAlgorithms>(
+ teeState, TeeBranch::Branch2);
+
+ // Step 17.
+ nsCOMPtr<nsIGlobalObject> global(
+ do_AddRef(teeState->GetStream()->GetParentObject()));
+ teeState->SetBranch1(ReadableStream::CreateAbstract(
+ aCx, global, branch1Algorithms, mozilla::Nothing(), nullptr, aRv));
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 18.
+ teeState->SetBranch2(ReadableStream::CreateAbstract(
+ aCx, global, branch2Algorithms, mozilla::Nothing(), nullptr, aRv));
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 19.
+ teeState->GetReader()->ClosedPromise()->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ TeeState* aTeeState) {},
+ [](JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv,
+ TeeState* aTeeState) {
+ // Step 19.1.
+ ReadableStreamDefaultControllerError(
+ aCx, aTeeState->Branch1()->DefaultController(), aReason, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 19.2
+ ReadableStreamDefaultControllerError(
+ aCx, aTeeState->Branch2()->DefaultController(), aReason, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 19.3
+ if (!aTeeState->Canceled1() || !aTeeState->Canceled2()) {
+ aTeeState->CancelPromise()->MaybeResolveWithUndefined();
+ }
+ },
+ RefPtr(teeState));
+
+ // Step 20.
+ aResult.AppendElement(teeState->Branch1());
+ aResult.AppendElement(teeState->Branch2());
+}
+
+// https://streams.spec.whatwg.org/#rs-pipe-to
+already_AddRefed<Promise> ReadableStream::PipeTo(
+ WritableStream& aDestination, const StreamPipeOptions& aOptions,
+ ErrorResult& aRv) {
+ // Step 1. If !IsReadableStreamLocked(this) is true, return a promise rejected
+ // with a TypeError exception.
+ if (IsReadableStreamLocked(this)) {
+ aRv.ThrowTypeError("Cannot pipe from a locked stream.");
+ return nullptr;
+ }
+
+ // Step 2. If !IsWritableStreamLocked(destination) is true, return a promise
+ // rejected with a TypeError exception.
+ if (IsWritableStreamLocked(&aDestination)) {
+ aRv.ThrowTypeError("Cannot pipe to a locked stream.");
+ return nullptr;
+ }
+
+ // Step 3. Let signal be options["signal"] if it exists, or undefined
+ // otherwise.
+ RefPtr<AbortSignal> signal =
+ aOptions.mSignal.WasPassed() ? &aOptions.mSignal.Value() : nullptr;
+
+ // Step 4. Return ! ReadableStreamPipeTo(this, destination,
+ // options["preventClose"], options["preventAbort"], options["preventCancel"],
+ // signal).
+ return ReadableStreamPipeTo(this, &aDestination, aOptions.mPreventClose,
+ aOptions.mPreventAbort, aOptions.mPreventCancel,
+ signal, aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-tee
+MOZ_CAN_RUN_SCRIPT
+static void ReadableStreamTee(JSContext* aCx, ReadableStream* aStream,
+ bool aCloneForBranch2,
+ nsTArray<RefPtr<ReadableStream>>& aResult,
+ ErrorResult& aRv) {
+ // Step 1. Implicit.
+ // Step 2. Implicit.
+ // Step 3.
+ if (aStream->Controller()->IsByte()) {
+ ReadableByteStreamTee(aCx, aStream, aResult, aRv);
+ return;
+ }
+ // Step 4.
+ ReadableStreamDefaultTee(aCx, aStream, aCloneForBranch2, aResult, aRv);
+}
+
+void ReadableStream::Tee(JSContext* aCx,
+ nsTArray<RefPtr<ReadableStream>>& aResult,
+ ErrorResult& aRv) {
+ ReadableStreamTee(aCx, this, false, aResult, aRv);
+}
+
+void ReadableStream::IteratorData::Traverse(
+ nsCycleCollectionTraversalCallback& cb) {
+ ReadableStream::IteratorData* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReader);
+}
+void ReadableStream::IteratorData::Unlink() {
+ ReadableStream::IteratorData* tmp = this;
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mReader);
+}
+
+// https://streams.spec.whatwg.org/#rs-get-iterator
+void ReadableStream::InitAsyncIteratorData(
+ IteratorData& aData, Iterator::IteratorType aType,
+ const ReadableStreamIteratorOptions& aOptions, ErrorResult& aRv) {
+ // Step 1. Let reader be ? AcquireReadableStreamDefaultReader(stream).
+ RefPtr<ReadableStreamDefaultReader> reader =
+ AcquireReadableStreamDefaultReader(this, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 2. Set iterator’s reader to reader.
+ aData.mReader = reader;
+
+ // Step 3. Let preventCancel be args[0]["preventCancel"].
+ // Step 4. Set iterator’s prevent cancel to preventCancel.
+ aData.mPreventCancel = aOptions.mPreventCancel;
+}
+
+// https://streams.spec.whatwg.org/#rs-asynciterator-prototype-next
+// Step 4.
+struct IteratorReadRequest : public ReadRequest {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IteratorReadRequest, ReadRequest)
+
+ RefPtr<Promise> mPromise;
+ RefPtr<ReadableStreamDefaultReader> mReader;
+
+ explicit IteratorReadRequest(Promise* aPromise,
+ ReadableStreamDefaultReader* aReader)
+ : mPromise(aPromise), mReader(aReader) {}
+
+ // chunk steps, given chunk
+ void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) override {
+ // Step 1. Resolve promise with chunk.
+ mPromise->MaybeResolve(aChunk);
+ }
+
+ // close steps
+ void CloseSteps(JSContext* aCx, ErrorResult& aRv) override {
+ // Step 1. Perform ! ReadableStreamDefaultReaderRelease(reader).
+ ReadableStreamDefaultReaderRelease(aCx, mReader, aRv);
+ if (aRv.Failed()) {
+ mPromise->MaybeRejectWithUndefined();
+ return;
+ }
+
+ // Step 2. Resolve promise with end of iteration.
+ iterator_utils::ResolvePromiseForFinished(mPromise);
+ }
+
+ // error steps, given e
+ void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) override {
+ // Step 1. Perform ! ReadableStreamDefaultReaderRelease(reader).
+ ReadableStreamDefaultReaderRelease(aCx, mReader, aRv);
+ if (aRv.Failed()) {
+ mPromise->MaybeRejectWithUndefined();
+ return;
+ }
+
+ // Step 2. Reject promise with e.
+ mPromise->MaybeReject(aError);
+ }
+
+ protected:
+ virtual ~IteratorReadRequest() = default;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(IteratorReadRequest, ReadRequest, mPromise,
+ mReader)
+
+NS_IMPL_ADDREF_INHERITED(IteratorReadRequest, ReadRequest)
+NS_IMPL_RELEASE_INHERITED(IteratorReadRequest, ReadRequest)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IteratorReadRequest)
+NS_INTERFACE_MAP_END_INHERITING(ReadRequest)
+
+// https://streams.spec.whatwg.org/#rs-asynciterator-prototype-next
+already_AddRefed<Promise> ReadableStream::GetNextIterationResult(
+ Iterator* aIterator, ErrorResult& aRv) {
+ // Step 1. Let reader be iterator’s reader.
+ RefPtr<ReadableStreamDefaultReader> reader = aIterator->Data().mReader;
+
+ // Step 2. Assert: reader.[[stream]] is not undefined.
+ MOZ_ASSERT(reader->GetStream());
+
+ // Step 3. Let promise be a new promise.
+ RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject());
+
+ // Step 4. Let readRequest be a new read request with the following items:
+ RefPtr<ReadRequest> request = new IteratorReadRequest(promise, reader);
+
+ // Step 5. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobal)) {
+ aRv.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+
+ ReadableStreamDefaultReaderRead(jsapi.cx(), reader, request, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 6. Return promise.
+ return promise.forget();
+}
+
+// https://streams.spec.whatwg.org/#rs-asynciterator-prototype-return
+already_AddRefed<Promise> ReadableStream::IteratorReturn(
+ JSContext* aCx, Iterator* aIterator, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ // Step 1. Let reader be iterator’s reader.
+ RefPtr<ReadableStreamDefaultReader> reader = aIterator->Data().mReader;
+
+ // Step 2. Assert: reader.[[stream]] is not undefined.
+ MOZ_ASSERT(reader->GetStream());
+
+ // Step 3. Assert: reader.[[readRequests]] is empty, as the async iterator
+ // machinery guarantees that any previous calls to next() have settled before
+ // this is called.
+ MOZ_ASSERT(reader->ReadRequests().isEmpty());
+
+ // Step 4. If iterator’s prevent cancel is false:
+ if (!aIterator->Data().mPreventCancel) {
+ // Step 4.1. Let result be ! ReadableStreamReaderGenericCancel(reader, arg).
+ RefPtr<ReadableStream> stream(reader->GetStream());
+ RefPtr<Promise> result = ReadableStreamCancel(aCx, stream, aValue, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ reader->GetStream(),
+ "We shouldn't have a null stream here (bug 1821169).");
+ if (!reader->GetStream()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Step 4.2. Perform ! ReadableStreamDefaultReaderRelease(reader).
+ ReadableStreamDefaultReaderRelease(aCx, reader, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Step 4.3. Return result.
+ return result.forget();
+ }
+
+ // Step 5. Perform ! ReadableStreamDefaultReaderRelease(reader).
+ ReadableStreamDefaultReaderRelease(aCx, reader, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // Step 6. Return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(GetParentObject(), aRv);
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#readable-stream-add-read-into-request
+void ReadableStreamAddReadIntoRequest(ReadableStream* aStream,
+ ReadIntoRequest* aReadIntoRequest) {
+ // Step 1. Assert: stream.[[reader]] implements ReadableStreamBYOBReader.
+ MOZ_ASSERT(aStream->GetReader()->IsBYOB());
+
+ // Step 2. Assert: stream.[[state]] is "readable" or "closed".
+ MOZ_ASSERT(aStream->State() == ReadableStream::ReaderState::Readable ||
+ aStream->State() == ReadableStream::ReaderState::Closed);
+
+ // Step 3. Append readRequest to stream.[[reader]].[[readIntoRequests]].
+ aStream->GetReader()->AsBYOB()->ReadIntoRequests().insertBack(
+ aReadIntoRequest);
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#abstract-opdef-createreadablebytestream
+already_AddRefed<ReadableStream> ReadableStream::CreateByteAbstract(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ UnderlyingSourceAlgorithmsBase* aAlgorithms, ErrorResult& aRv) {
+ // Step 1. Let stream be a new ReadableStream.
+ RefPtr<ReadableStream> stream =
+ new ReadableStream(aGlobal, HoldDropJSObjectsCaller::Implicit);
+
+ // Step 2. Perform ! InitializeReadableStream(stream).
+ InitializeReadableStream(stream);
+
+ // Step 3. Let controller be a new ReadableByteStreamController.
+ RefPtr<ReadableByteStreamController> controller =
+ new ReadableByteStreamController(aGlobal);
+
+ // Step 4. Perform ? SetUpReadableByteStreamController(stream, controller,
+ // startAlgorithm, pullAlgorithm, cancelAlgorithm, 0, undefined).
+ SetUpReadableByteStreamController(aCx, stream, controller, aAlgorithms, 0,
+ mozilla::Nothing(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Return stream.
+ return stream.forget();
+}
+
+// https://streams.spec.whatwg.org/#readablestream-set-up
+// (except this instead creates a new ReadableStream rather than accepting an
+// existing instance)
+// _BOUNDARY because `aAlgorithms->StartCallback` (called by
+// SetUpReadableStreamDefaultController below) should not be able to run script
+// in this case.
+MOZ_CAN_RUN_SCRIPT_BOUNDARY already_AddRefed<ReadableStream>
+ReadableStream::CreateNative(JSContext* aCx, nsIGlobalObject* aGlobal,
+ UnderlyingSourceAlgorithmsWrapper& aAlgorithms,
+ mozilla::Maybe<double> aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm,
+ ErrorResult& aRv) {
+ // an optional number highWaterMark (default 1)
+ double highWaterMark = aHighWaterMark.valueOr(1);
+ // and if given, highWaterMark must be a non-negative, non-NaN number.
+ MOZ_ASSERT(IsNonNegativeNumber(highWaterMark));
+
+ // Step 1: Let startAlgorithm be an algorithm that returns undefined.
+ // Step 2: Let pullAlgorithmWrapper be an algorithm that runs these steps:
+ // Step 3: Let cancelAlgorithmWrapper be an algorithm that runs these steps:
+ // (Done by UnderlyingSourceAlgorithmsWrapper)
+
+ // Step 4: If sizeAlgorithm was not given, then set it to an algorithm that
+ // returns 1. (Callers will treat nullptr as such, see
+ // ReadableStream::Constructor for details)
+
+ // Step 5: Perform ! InitializeReadableStream(stream).
+ RefPtr<ReadableStream> stream =
+ new ReadableStream(aGlobal, HoldDropJSObjectsCaller::Implicit);
+
+ // Step 6: Let controller be a new ReadableStreamDefaultController.
+ auto controller = MakeRefPtr<ReadableStreamDefaultController>(aGlobal);
+
+ // Step 7: Perform ! SetUpReadableStreamDefaultController(stream, controller,
+ // startAlgorithm, pullAlgorithmWrapper, cancelAlgorithmWrapper,
+ // highWaterMark, sizeAlgorithm).
+ SetUpReadableStreamDefaultController(aCx, stream, controller, &aAlgorithms,
+ highWaterMark, aSizeAlgorithm, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return stream.forget();
+}
+
+// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
+// _BOUNDARY because `aAlgorithms->StartCallback` (called by
+// SetUpReadableByteStreamController below) should not be able to run script in
+// this case.
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void ReadableStream::SetUpByteNative(
+ JSContext* aCx, UnderlyingSourceAlgorithmsWrapper& aAlgorithms,
+ mozilla::Maybe<double> aHighWaterMark, ErrorResult& aRv) {
+ // an optional number highWaterMark (default 0)
+ double highWaterMark = aHighWaterMark.valueOr(0);
+ // and if given, highWaterMark must be a non-negative, non-NaN number.
+ MOZ_ASSERT(IsNonNegativeNumber(highWaterMark));
+
+ // Step 1: Let startAlgorithm be an algorithm that returns undefined.
+ // Step 2: Let pullAlgorithmWrapper be an algorithm that runs these steps:
+ // Step 3: Let cancelAlgorithmWrapper be an algorithm that runs these steps:
+ // (Done by UnderlyingSourceAlgorithmsWrapper)
+
+ // Step 4: Perform ! InitializeReadableStream(stream).
+ // (Covered by constructor)
+
+ // Step 5: Let controller be a new ReadableByteStreamController.
+ auto controller = MakeRefPtr<ReadableByteStreamController>(GetParentObject());
+
+ // Step 6: Perform ! SetUpReadableByteStreamController(stream, controller,
+ // startAlgorithm, pullAlgorithmWrapper, cancelAlgorithmWrapper,
+ // highWaterMark, undefined).
+ SetUpReadableByteStreamController(aCx, this, controller, &aAlgorithms,
+ highWaterMark, Nothing(), aRv);
+}
+
+already_AddRefed<ReadableStream> ReadableStream::CreateByteNative(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ UnderlyingSourceAlgorithmsWrapper& aAlgorithms,
+ mozilla::Maybe<double> aHighWaterMark, ErrorResult& aRv) {
+ RefPtr<ReadableStream> stream =
+ new ReadableStream(aGlobal, HoldDropJSObjectsCaller::Implicit);
+ stream->SetUpByteNative(aCx, aAlgorithms, aHighWaterMark, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return stream.forget();
+}
+
+// https://streams.spec.whatwg.org/#readablestream-close
+void ReadableStream::CloseNative(JSContext* aCx, ErrorResult& aRv) {
+ MOZ_ASSERT_IF(mController->GetAlgorithms(),
+ mController->GetAlgorithms()->IsNative());
+ // Step 1: If stream.[[controller]] implements ReadableByteStreamController,
+ if (mController->IsByte()) {
+ RefPtr<ReadableByteStreamController> controller = mController->AsByte();
+
+ // Step 1.1: Perform !
+ // ReadableByteStreamControllerClose(stream.[[controller]]).
+ ReadableByteStreamControllerClose(aCx, controller, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 1.2: If stream.[[controller]].[[pendingPullIntos]] is not empty,
+ // perform ! ReadableByteStreamControllerRespond(stream.[[controller]], 0).
+ if (!controller->PendingPullIntos().isEmpty()) {
+ ReadableByteStreamControllerRespond(aCx, controller, 0, aRv);
+ }
+ return;
+ }
+
+ // Step 2: Otherwise, perform !
+ // ReadableStreamDefaultControllerClose(stream.[[controller]]).
+ RefPtr<ReadableStreamDefaultController> controller = mController->AsDefault();
+ ReadableStreamDefaultControllerClose(aCx, controller, aRv);
+}
+
+// https://streams.spec.whatwg.org/#readablestream-error
+void ReadableStream::ErrorNative(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ // Step 1: If stream.[[controller]] implements ReadableByteStreamController,
+ // then perform ! ReadableByteStreamControllerError(stream.[[controller]], e).
+ if (mController->IsByte()) {
+ ReadableByteStreamControllerError(mController->AsByte(), aError, aRv);
+ return;
+ }
+ // Step 2: Otherwise, perform !
+ // ReadableStreamDefaultControllerError(stream.[[controller]], e).
+ ReadableStreamDefaultControllerError(aCx, mController->AsDefault(), aError,
+ aRv);
+}
+
+// https://streams.spec.whatwg.org/#readablestream-current-byob-request-view
+static void CurrentBYOBRequestView(JSContext* aCx,
+ ReadableByteStreamController& aController,
+ JS::MutableHandle<JSObject*> aRetVal,
+ ErrorResult& aRv) {
+ // Step 1. Assert: stream.[[controller]] implements
+ // ReadableByteStreamController. (implicit)
+
+ // Step 2: Let byobRequest be !
+ // ReadableByteStreamControllerGetBYOBRequest(stream.[[controller]]).
+ RefPtr<ReadableStreamBYOBRequest> byobRequest =
+ ReadableByteStreamControllerGetBYOBRequest(aCx, &aController, aRv);
+ // Step 3: If byobRequest is null, then return null.
+ if (!byobRequest) {
+ aRetVal.set(nullptr);
+ return;
+ }
+ // Step 4: Return byobRequest.[[view]].
+ byobRequest->GetView(aCx, aRetVal);
+}
+
+static bool HasSameBufferView(JSContext* aCx, JS::Handle<JSObject*> aX,
+ JS::Handle<JSObject*> aY, ErrorResult& aRv) {
+ bool isShared;
+ JS::Rooted<JSObject*> viewedBufferX(
+ aCx, JS_GetArrayBufferViewBuffer(aCx, aX, &isShared));
+ if (!viewedBufferX) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return false;
+ }
+
+ JS::Rooted<JSObject*> viewedBufferY(
+ aCx, JS_GetArrayBufferViewBuffer(aCx, aY, &isShared));
+ if (!viewedBufferY) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return false;
+ }
+
+ return viewedBufferX == viewedBufferY;
+}
+
+// https://streams.spec.whatwg.org/#readablestream-enqueue
+void ReadableStream::EnqueueNative(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(mController->GetAlgorithms()->IsNative());
+
+ // Step 1: If stream.[[controller]] implements
+ // ReadableStreamDefaultController,
+ if (mController->IsDefault()) {
+ // Step 1.1: Perform !
+ // ReadableStreamDefaultControllerEnqueue(stream.[[controller]], chunk).
+ RefPtr<ReadableStreamDefaultController> controller =
+ mController->AsDefault();
+ ReadableStreamDefaultControllerEnqueue(aCx, controller, aChunk, aRv);
+ return;
+ }
+
+ // Step 2.1: Assert: stream.[[controller]] implements
+ // ReadableByteStreamController.
+ MOZ_ASSERT(mController->IsByte());
+ RefPtr<ReadableByteStreamController> controller = mController->AsByte();
+
+ // Step 2.2: Assert: chunk is an ArrayBufferView.
+ MOZ_ASSERT(aChunk.isObject() &&
+ JS_IsArrayBufferViewObject(&aChunk.toObject()));
+ JS::Rooted<JSObject*> chunk(aCx, &aChunk.toObject());
+
+ // Step 3: Let byobView be the current BYOB request view for stream.
+ JS::Rooted<JSObject*> byobView(aCx);
+ CurrentBYOBRequestView(aCx, *controller, &byobView, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 4: If byobView is non-null, and chunk.[[ViewedArrayBuffer]] is
+ // byobView.[[ViewedArrayBuffer]], then:
+ if (byobView && HasSameBufferView(aCx, chunk, byobView, aRv)) {
+ // Step 4.1: Assert: chunk.[[ByteOffset]] is byobView.[[ByteOffset]].
+ MOZ_ASSERT(JS_GetArrayBufferViewByteOffset(chunk) ==
+ JS_GetArrayBufferViewByteOffset(byobView));
+ // Step 4.2: Assert: chunk.[[ByteLength]] ≤ byobView.[[ByteLength]].
+ MOZ_ASSERT(JS_GetArrayBufferViewByteLength(chunk) <=
+ JS_GetArrayBufferViewByteLength(byobView));
+ // Step 4.3: Perform ?
+ // ReadableByteStreamControllerRespond(stream.[[controller]],
+ // chunk.[[ByteLength]]).
+ ReadableByteStreamControllerRespond(
+ aCx, controller, JS_GetArrayBufferViewByteLength(chunk), aRv);
+ return;
+ }
+
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 5: Otherwise, perform ?
+ // ReadableByteStreamControllerEnqueue(stream.[[controller]], chunk).
+ ReadableByteStreamControllerEnqueue(aCx, controller, chunk, aRv);
+}
+
+// https://streams.spec.whatwg.org/#readablestream-current-byob-request-view
+void ReadableStream::GetCurrentBYOBRequestView(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aView, ErrorResult& aRv) {
+ aView.set(nullptr);
+
+ // Step 1: Assert: stream.[[controller]] implements
+ // ReadableByteStreamController.
+ MOZ_ASSERT(mController->IsByte());
+
+ // Step 2: Let byobRequest be !
+ // ReadableByteStreamControllerGetBYOBRequest(stream.[[controller]]).
+ RefPtr<ReadableStreamBYOBRequest> byobRequest =
+ mController->AsByte()->GetByobRequest(aCx, aRv);
+
+ // Step 3: If byobRequest is null, then return null.
+ if (!byobRequest || aRv.Failed()) {
+ return;
+ }
+
+ // Step 4: Return byobRequest.[[view]].
+ byobRequest->GetView(aCx, aView);
+}
+
+// https://streams.spec.whatwg.org/#readablestream-get-a-reader
+// To get a reader for a ReadableStream stream, return ?
+// AcquireReadableStreamDefaultReader(stream). The result will be a
+// ReadableStreamDefaultReader.
+already_AddRefed<mozilla::dom::ReadableStreamDefaultReader>
+ReadableStream::GetReader(ErrorResult& aRv) {
+ return AcquireReadableStreamDefaultReader(this, aRv);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/ReadableStream.h b/dom/streams/ReadableStream.h
new file mode 100644
index 0000000000..ac2d06caf1
--- /dev/null
+++ b/dom/streams/ReadableStream.h
@@ -0,0 +1,292 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadableStream_h
+#define mozilla_dom_ReadableStream_h
+
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/IterableIterator.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "mozilla/dom/ReadableStreamController.h"
+#include "mozilla/dom/ReadableStreamDefaultController.h"
+#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class Promise;
+class ReadableStreamBYOBRequest;
+class ReadableStreamDefaultReader;
+class ReadableStreamGenericReader;
+struct ReadableStreamGetReaderOptions;
+struct ReadableStreamIteratorOptions;
+struct ReadIntoRequest;
+class WritableStream;
+struct ReadableWritablePair;
+struct StreamPipeOptions;
+
+using ReadableStreamReader =
+ ReadableStreamDefaultReaderOrReadableStreamBYOBReader;
+using OwningReadableStreamReader =
+ OwningReadableStreamDefaultReaderOrReadableStreamBYOBReader;
+class NativeUnderlyingSource;
+class BodyStreamHolder;
+class UniqueMessagePortId;
+class MessagePort;
+
+class ReadableStream : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ReadableStream)
+
+ friend class WritableStream;
+
+ protected:
+ virtual ~ReadableStream();
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ // If one extends ReadableStream with another cycle collectable class,
+ // calling HoldJSObjects and DropJSObjects should happen using 'this' of
+ // that extending class. And in that case Explicit should be passed to the
+ // constructor of ReadableStream so that it doesn't make those calls.
+ // See also https://bugzilla.mozilla.org/show_bug.cgi?id=1801214.
+ enum class HoldDropJSObjectsCaller { Implicit, Explicit };
+
+ explicit ReadableStream(const GlobalObject& aGlobal,
+ HoldDropJSObjectsCaller aHoldDropCaller);
+ explicit ReadableStream(nsIGlobalObject* aGlobal,
+ HoldDropJSObjectsCaller aHoldDropCaller);
+
+ public:
+ // Abstract algorithms
+ MOZ_CAN_RUN_SCRIPT static already_AddRefed<ReadableStream> CreateAbstract(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ UnderlyingSourceAlgorithmsBase* aAlgorithms,
+ mozilla::Maybe<double> aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT static already_AddRefed<ReadableStream> CreateByteAbstract(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ UnderlyingSourceAlgorithmsBase* aAlgorithms, ErrorResult& aRv);
+
+ // Slot Getter/Setters:
+ MOZ_KNOWN_LIVE ReadableStreamController* Controller() { return mController; }
+ ReadableStreamDefaultController* DefaultController() {
+ MOZ_ASSERT(mController && mController->IsDefault());
+ return mController->AsDefault();
+ }
+ void SetController(ReadableStreamController& aController) {
+ MOZ_ASSERT(!mController);
+ mController = &aController;
+ }
+
+ bool Disturbed() const { return mDisturbed; }
+ void SetDisturbed(bool aDisturbed) { mDisturbed = aDisturbed; }
+
+ ReadableStreamGenericReader* GetReader() { return mReader; }
+ void SetReader(ReadableStreamGenericReader* aReader);
+
+ ReadableStreamDefaultReader* GetDefaultReader();
+
+ enum class ReaderState { Readable, Closed, Errored };
+
+ ReaderState State() const { return mState; }
+ void SetState(const ReaderState& aState) { mState = aState; }
+
+ JS::Value StoredError() const { return mStoredError; }
+ void SetStoredError(JS::Handle<JS::Value> aStoredError) {
+ mStoredError = aStoredError;
+ }
+
+ nsIInputStream* MaybeGetInputStreamIfUnread() {
+ MOZ_ASSERT(!Disturbed());
+ if (UnderlyingSourceAlgorithmsBase* algorithms =
+ Controller()->GetAlgorithms()) {
+ return algorithms->MaybeGetInputStreamIfUnread();
+ }
+ return nullptr;
+ }
+
+ // [Transferable]
+ // https://html.spec.whatwg.org/multipage/structured-data.html#transfer-steps
+ MOZ_CAN_RUN_SCRIPT bool Transfer(JSContext* aCx,
+ UniqueMessagePortId& aPortId);
+ MOZ_CAN_RUN_SCRIPT static already_AddRefed<ReadableStream>
+ ReceiveTransferImpl(JSContext* aCx, nsIGlobalObject* aGlobal,
+ MessagePort& aPort);
+ // https://html.spec.whatwg.org/multipage/structured-data.html#transfer-receiving-steps
+ MOZ_CAN_RUN_SCRIPT static bool ReceiveTransfer(
+ JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort,
+ JS::MutableHandle<JSObject*> aReturnObject);
+
+ // Public functions to implement other specs
+ // https://streams.spec.whatwg.org/#other-specs-rs
+
+ // https://streams.spec.whatwg.org/#readablestream-set-up
+ static already_AddRefed<ReadableStream> CreateNative(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ UnderlyingSourceAlgorithmsWrapper& aAlgorithms,
+ mozilla::Maybe<double> aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv);
+
+ // https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
+
+ protected:
+ // Sets up the ReadableStream with byte reading support. Intended for
+ // subclasses.
+ void SetUpByteNative(JSContext* aCx,
+ UnderlyingSourceAlgorithmsWrapper& aAlgorithms,
+ mozilla::Maybe<double> aHighWaterMark, ErrorResult& aRv);
+
+ public:
+ // Creates and sets up a ReadableStream with byte reading support. Use
+ // SetUpByteNative for this purpose in subclasses.
+ static already_AddRefed<ReadableStream> CreateByteNative(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ UnderlyingSourceAlgorithmsWrapper& aAlgorithms,
+ mozilla::Maybe<double> aHighWaterMark, ErrorResult& aRv);
+
+ // The following algorithms must only be used on ReadableStream instances
+ // initialized via the above set up or set up with byte reading support
+ // algorithms (not, e.g., on web-developer-created instances):
+
+ // https://streams.spec.whatwg.org/#readablestream-close
+ MOZ_CAN_RUN_SCRIPT void CloseNative(JSContext* aCx, ErrorResult& aRv);
+
+ // https://streams.spec.whatwg.org/#readablestream-error
+ void ErrorNative(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv);
+
+ // https://streams.spec.whatwg.org/#readablestream-enqueue
+ MOZ_CAN_RUN_SCRIPT void EnqueueNative(JSContext* aCx,
+ JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv);
+
+ // https://streams.spec.whatwg.org/#readablestream-current-byob-request-view
+ void GetCurrentBYOBRequestView(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aView,
+ ErrorResult& aRv);
+
+ // The following algorithms can be used on arbitrary ReadableStream instances,
+ // including ones that are created by web developers. They can all fail in
+ // various operation-specific ways, and these failures should be handled by
+ // the calling specification.
+
+ // https://streams.spec.whatwg.org/#readablestream-get-a-reader
+ already_AddRefed<mozilla::dom::ReadableStreamDefaultReader> GetReader(
+ ErrorResult& aRv);
+
+ // IDL layer functions
+
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // IDL methods
+
+ // TODO: Use MOZ_CAN_RUN_SCRIPT when IDL constructors can use it (bug 1749042)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static already_AddRefed<ReadableStream>
+ Constructor(const GlobalObject& aGlobal,
+ const Optional<JS::Handle<JSObject*>>& aUnderlyingSource,
+ const QueuingStrategy& aStrategy, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT static already_AddRefed<ReadableStream> From(
+ const GlobalObject& aGlobal, JS::Handle<JS::Value> asyncIterable,
+ ErrorResult& aRv);
+
+ bool Locked() const;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Cancel(
+ JSContext* cx, JS::Handle<JS::Value> aReason, ErrorResult& aRv);
+
+ void GetReader(const ReadableStreamGetReaderOptions& aOptions,
+ OwningReadableStreamReader& resultReader, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<ReadableStream> PipeThrough(
+ const ReadableWritablePair& aTransform, const StreamPipeOptions& aOptions,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PipeTo(
+ WritableStream& aDestination, const StreamPipeOptions& aOptions,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void Tee(JSContext* aCx,
+ nsTArray<RefPtr<ReadableStream>>& aResult,
+ ErrorResult& aRv);
+
+ struct IteratorData {
+ void Traverse(nsCycleCollectionTraversalCallback& cb);
+ void Unlink();
+
+ RefPtr<ReadableStreamDefaultReader> mReader;
+ bool mPreventCancel;
+ };
+
+ using Iterator = AsyncIterableIterator<ReadableStream>;
+
+ void InitAsyncIteratorData(IteratorData& aData, Iterator::IteratorType aType,
+ const ReadableStreamIteratorOptions& aOptions,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> GetNextIterationResult(
+ Iterator* aIterator, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> IteratorReturn(
+ JSContext* aCx, Iterator* aIterator, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv);
+
+ // Internal Slots:
+ private:
+ RefPtr<ReadableStreamController> mController;
+ bool mDisturbed = false;
+ RefPtr<ReadableStreamGenericReader> mReader;
+ ReaderState mState = ReaderState::Readable;
+ JS::Heap<JS::Value> mStoredError;
+
+ HoldDropJSObjectsCaller mHoldDropCaller;
+};
+
+namespace streams_abstract {
+
+bool IsReadableStreamLocked(ReadableStream* aStream);
+
+double ReadableStreamGetNumReadRequests(ReadableStream* aStream);
+
+void ReadableStreamError(JSContext* aCx, ReadableStream* aStream,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableStreamClose(JSContext* aCx,
+ ReadableStream* aStream,
+ ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableStreamFulfillReadRequest(
+ JSContext* aCx, ReadableStream* aStream, JS::Handle<JS::Value> aChunk,
+ bool done, ErrorResult& aRv);
+
+void ReadableStreamAddReadRequest(ReadableStream* aStream,
+ ReadRequest* aReadRequest);
+void ReadableStreamAddReadIntoRequest(ReadableStream* aStream,
+ ReadIntoRequest* aReadIntoRequest);
+
+MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> ReadableStreamCancel(
+ JSContext* aCx, ReadableStream* aStream, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv);
+
+already_AddRefed<ReadableStreamDefaultReader>
+AcquireReadableStreamDefaultReader(ReadableStream* aStream, ErrorResult& aRv);
+
+bool ReadableStreamHasBYOBReader(ReadableStream* aStream);
+bool ReadableStreamHasDefaultReader(ReadableStream* aStream);
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ReadableStream_h
diff --git a/dom/streams/ReadableStreamBYOBReader.cpp b/dom/streams/ReadableStreamBYOBReader.cpp
new file mode 100644
index 0000000000..f40d98bb93
--- /dev/null
+++ b/dom/streams/ReadableStreamBYOBReader.cpp
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ReadableStreamBYOBReader.h"
+
+#include "ReadIntoRequest.h"
+#include "js/ArrayBuffer.h"
+#include "js/experimental/TypedData.h"
+#include "mozilla/dom/ReadableStreamBYOBReader.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamBYOBReaderBinding.h"
+#include "mozilla/dom/ReadableStreamGenericReader.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsImpl.h"
+
+// Temporary Includes
+#include "mozilla/dom/ReadableByteStreamController.h"
+#include "mozilla/dom/ReadableStreamBYOBRequest.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED(ReadableStreamBYOBReader,
+ ReadableStreamGenericReader,
+ mReadIntoRequests)
+NS_IMPL_ADDREF_INHERITED(ReadableStreamBYOBReader, ReadableStreamGenericReader)
+NS_IMPL_RELEASE_INHERITED(ReadableStreamBYOBReader, ReadableStreamGenericReader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamBYOBReader)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(ReadableStreamGenericReader)
+
+ReadableStreamBYOBReader::ReadableStreamBYOBReader(nsISupports* aGlobal)
+ : ReadableStreamGenericReader(do_QueryInterface(aGlobal)) {}
+
+JSObject* ReadableStreamBYOBReader::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return ReadableStreamBYOBReader_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://streams.spec.whatwg.org/#set-up-readable-stream-byob-reader
+void SetUpReadableStreamBYOBReader(ReadableStreamBYOBReader* reader,
+ ReadableStream& stream, ErrorResult& rv) {
+ // Step 1. If !IsReadableStreamLocked(stream) is true, throw a TypeError
+ // exception.
+ if (IsReadableStreamLocked(&stream)) {
+ rv.ThrowTypeError("Trying to read locked stream");
+ return;
+ }
+
+ // Step 2. If stream.[[controller]] does not implement
+ // ReadableByteStreamController, throw a TypeError exception.
+ if (!stream.Controller()->IsByte()) {
+ rv.ThrowTypeError("Trying to read with incompatible controller");
+ return;
+ }
+
+ // Step 3. Perform ! ReadableStreamReaderGenericInitialize(reader, stream).
+ ReadableStreamReaderGenericInitialize(reader, &stream);
+
+ // Step 4. Set reader.[[readIntoRequests]] to a new empty list.
+ reader->ReadIntoRequests().clear();
+}
+
+// https://streams.spec.whatwg.org/#byob-reader-constructor
+/* static */ already_AddRefed<ReadableStreamBYOBReader>
+ReadableStreamBYOBReader::Constructor(const GlobalObject& global,
+ ReadableStream& stream, ErrorResult& rv) {
+ nsCOMPtr<nsIGlobalObject> globalObject =
+ do_QueryInterface(global.GetAsSupports());
+ RefPtr<ReadableStreamBYOBReader> reader =
+ new ReadableStreamBYOBReader(globalObject);
+
+ // Step 1.
+ SetUpReadableStreamBYOBReader(reader, stream, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ return reader.forget();
+}
+
+struct Read_ReadIntoRequest final : public ReadIntoRequest {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Read_ReadIntoRequest,
+ ReadIntoRequest)
+
+ RefPtr<Promise> mPromise;
+
+ explicit Read_ReadIntoRequest(Promise* aPromise) : mPromise(aPromise) {}
+
+ void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) override {
+ MOZ_ASSERT(aChunk.isObject());
+ // https://streams.spec.whatwg.org/#byob-reader-read Step 6.
+ //
+ // chunk steps, given chunk:
+ // Resolve promise with «[ "value" → chunk, "done" → false ]».
+
+ // We need to wrap this as the chunk could have come from
+ // another compartment.
+ JS::Rooted<JSObject*> chunk(aCx, &aChunk.toObject());
+ if (!JS_WrapObject(aCx, &chunk)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ RootedDictionary<ReadableStreamReadResult> result(aCx);
+ result.mValue = aChunk;
+ result.mDone.Construct(false);
+
+ mPromise->MaybeResolve(result);
+ }
+
+ void CloseSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) override {
+ MOZ_ASSERT(aChunk.isObject() || aChunk.isUndefined());
+ // https://streams.spec.whatwg.org/#byob-reader-read Step 6.
+ //
+ // close steps, given chunk:
+ // Resolve promise with «[ "value" → chunk, "done" → true ]».
+ RootedDictionary<ReadableStreamReadResult> result(aCx);
+ if (aChunk.isObject()) {
+ // We need to wrap this as the chunk could have come from
+ // another compartment.
+ JS::Rooted<JSObject*> chunk(aCx, &aChunk.toObject());
+ if (!JS_WrapObject(aCx, &chunk)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ result.mValue = aChunk;
+ }
+ result.mDone.Construct(true);
+
+ mPromise->MaybeResolve(result);
+ }
+
+ void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> e,
+ ErrorResult& aRv) override {
+ // https://streams.spec.whatwg.org/#byob-reader-read Step 6.
+ //
+ // error steps, given e:
+ // Reject promise with e.
+ mPromise->MaybeReject(e);
+ }
+
+ protected:
+ ~Read_ReadIntoRequest() override = default;
+};
+
+NS_IMPL_CYCLE_COLLECTION(ReadIntoRequest)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadIntoRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadIntoRequest)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadIntoRequest)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(Read_ReadIntoRequest, ReadIntoRequest,
+ mPromise)
+NS_IMPL_ADDREF_INHERITED(Read_ReadIntoRequest, ReadIntoRequest)
+NS_IMPL_RELEASE_INHERITED(Read_ReadIntoRequest, ReadIntoRequest)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Read_ReadIntoRequest)
+NS_INTERFACE_MAP_END_INHERITING(ReadIntoRequest)
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#readable-stream-byob-reader-read
+void ReadableStreamBYOBReaderRead(JSContext* aCx,
+ ReadableStreamBYOBReader* aReader,
+ JS::Handle<JSObject*> aView,
+ ReadIntoRequest* aReadIntoRequest,
+ ErrorResult& aRv) {
+ // Step 1.Let stream be reader.[[stream]].
+ ReadableStream* stream = aReader->GetStream();
+
+ // Step 2. Assert: stream is not undefined.
+ MOZ_ASSERT(stream);
+
+ // Step 3. Set stream.[[disturbed]] to true.
+ stream->SetDisturbed(true);
+
+ // Step 4. If stream.[[state]] is "errored", perform readIntoRequest’s error
+ // steps given stream.[[storedError]].
+ if (stream->State() == ReadableStream::ReaderState::Errored) {
+ JS::Rooted<JS::Value> error(aCx, stream->StoredError());
+
+ aReadIntoRequest->ErrorSteps(aCx, error, aRv);
+ return;
+ }
+
+ // Step 5. Otherwise, perform
+ // !ReadableByteStreamControllerPullInto(stream.[[controller]], view,
+ // readIntoRequest).
+ MOZ_ASSERT(stream->Controller()->IsByte());
+ RefPtr<ReadableByteStreamController> controller(
+ stream->Controller()->AsByte());
+ ReadableByteStreamControllerPullInto(aCx, controller, aView, aReadIntoRequest,
+ aRv);
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#byob-reader-read
+already_AddRefed<Promise> ReadableStreamBYOBReader::Read(
+ const ArrayBufferView& aArray, ErrorResult& aRv) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(GetParentObject())) {
+ aRv.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> view(cx, aArray.Obj());
+
+ // Step 1. If view.[[ByteLength]] is 0, return a promise rejected with a
+ // TypeError exception.
+ if (JS_GetArrayBufferViewByteLength(view) == 0) {
+ // Binding code should convert this thrown value into a rejected promise.
+ aRv.ThrowTypeError("Zero Length View");
+ return nullptr;
+ }
+
+ // Step 2. If view.[[ViewedArrayBuffer]].[[ArrayBufferByteLength]] is 0,
+ // return a promise rejected with a TypeError exception.
+ bool isSharedMemory;
+ JS::Rooted<JSObject*> viewedArrayBuffer(
+ cx, JS_GetArrayBufferViewBuffer(cx, view, &isSharedMemory));
+ if (!viewedArrayBuffer) {
+ aRv.StealExceptionFromJSContext(cx);
+ return nullptr;
+ }
+
+ if (JS::GetArrayBufferByteLength(viewedArrayBuffer) == 0) {
+ aRv.ThrowTypeError("zero length viewed buffer");
+ return nullptr;
+ }
+
+ // Step 3. If ! IsDetachedBuffer(view.[[ViewedArrayBuffer]]) is true, return a
+ // promise rejected with a TypeError exception.
+ if (JS::IsDetachedArrayBufferObject(viewedArrayBuffer)) {
+ aRv.ThrowTypeError("Detached Buffer");
+ return nullptr;
+ }
+
+ // Step 4. If this.[[stream]] is undefined, return a promise rejected with a
+ // TypeError exception.
+ if (!GetStream()) {
+ aRv.ThrowTypeError("Reader has undefined stream");
+ return nullptr;
+ }
+
+ // Step 5.
+ RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject());
+
+ // Step 6. Let readIntoRequest be a new read-into request with the following
+ // items:
+ RefPtr<ReadIntoRequest> readIntoRequest = new Read_ReadIntoRequest(promise);
+
+ // Step 7. Perform ! ReadableStreamBYOBReaderRead(this, view,
+ // readIntoRequest).
+ ReadableStreamBYOBReaderRead(cx, this, view, readIntoRequest, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 8. Return promise.
+ return promise.forget();
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreambyobreadererrorreadintorequests
+void ReadableStreamBYOBReaderErrorReadIntoRequests(
+ JSContext* aCx, ReadableStreamBYOBReader* aReader,
+ JS::Handle<JS::Value> aError, ErrorResult& aRv) {
+ // Step 1. Let readIntoRequests be reader.[[readIntoRequests]].
+ LinkedList<RefPtr<ReadIntoRequest>> readIntoRequests =
+ std::move(aReader->ReadIntoRequests());
+
+ // Step 2. Set reader.[[readIntoRequests]] to a new empty list.
+ // Note: The std::move already cleared this anyway.
+ aReader->ReadIntoRequests().clear();
+
+ // Step 3. For each readIntoRequest of readIntoRequests,
+ while (RefPtr<ReadIntoRequest> readIntoRequest =
+ readIntoRequests.popFirst()) {
+ // Step 3.1. Perform readIntoRequest’s error steps, given e.
+ readIntoRequest->ErrorSteps(aCx, aError, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreambyobreaderrelease
+void ReadableStreamBYOBReaderRelease(JSContext* aCx,
+ ReadableStreamBYOBReader* aReader,
+ ErrorResult& aRv) {
+ // Step 1. Perform ! ReadableStreamReaderGenericRelease(reader).
+ ReadableStreamReaderGenericRelease(aReader, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 2. Let e be a new TypeError exception.
+ ErrorResult rv;
+ rv.ThrowTypeError("Releasing lock");
+ JS::Rooted<JS::Value> error(aCx);
+ MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &error));
+
+ // Step 3. Perform ! ReadableStreamBYOBReaderErrorReadIntoRequests(reader, e).
+ ReadableStreamBYOBReaderErrorReadIntoRequests(aCx, aReader, error, aRv);
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#byob-reader-release-lock
+void ReadableStreamBYOBReader::ReleaseLock(ErrorResult& aRv) {
+ // Step 1. If this.[[stream]] is undefined, return.
+ if (!mStream) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobal)) {
+ return aRv.ThrowUnknownError("Internal error");
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Step 2. Perform ! ReadableStreamBYOBReaderRelease(this).
+ RefPtr<ReadableStreamBYOBReader> thisRefPtr = this;
+ ReadableStreamBYOBReaderRelease(cx, thisRefPtr, aRv);
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#acquire-readable-stream-byob-reader
+already_AddRefed<ReadableStreamBYOBReader> AcquireReadableStreamBYOBReader(
+ ReadableStream* aStream, ErrorResult& aRv) {
+ // Step 1. Let reader be a new ReadableStreamBYOBReader.
+ RefPtr<ReadableStreamBYOBReader> reader =
+ new ReadableStreamBYOBReader(aStream->GetParentObject());
+
+ // Step 2. Perform ? SetUpReadableStreamBYOBReader(reader, stream).
+ SetUpReadableStreamBYOBReader(reader, *aStream, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 3. Return reader.
+ return reader.forget();
+}
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
diff --git a/dom/streams/ReadableStreamBYOBReader.h b/dom/streams/ReadableStreamBYOBReader.h
new file mode 100644
index 0000000000..896f1acbf9
--- /dev/null
+++ b/dom/streams/ReadableStreamBYOBReader.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadableStreamBYOBReader_h
+#define mozilla_dom_ReadableStreamBYOBReader_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ReadableStreamGenericReader.h"
+#include "mozilla/dom/TypedArray.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "mozilla/LinkedList.h"
+
+namespace mozilla::dom {
+
+class Promise;
+struct ReadIntoRequest;
+class ReadableStream;
+
+} // namespace mozilla::dom
+
+namespace mozilla::dom {
+
+class ReadableStreamBYOBReader final : public ReadableStreamGenericReader,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_INHERITED(
+ ReadableStreamBYOBReader, ReadableStreamGenericReader)
+
+ public:
+ explicit ReadableStreamBYOBReader(nsISupports* aGlobal);
+
+ bool IsDefault() override { return false; };
+ bool IsBYOB() override { return true; }
+ ReadableStreamDefaultReader* AsDefault() override {
+ MOZ_CRASH("Should have verified IsDefault first");
+ return nullptr;
+ }
+ ReadableStreamBYOBReader* AsBYOB() override { return this; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<ReadableStreamBYOBReader> Constructor(
+ const GlobalObject& global, ReadableStream& stream, ErrorResult& rv);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Read(
+ const ArrayBufferView& aArray, ErrorResult& rv);
+
+ void ReleaseLock(ErrorResult& rv);
+
+ LinkedList<RefPtr<ReadIntoRequest>>& ReadIntoRequests() {
+ return mReadIntoRequests;
+ }
+
+ private:
+ ~ReadableStreamBYOBReader() override = default;
+
+ LinkedList<RefPtr<ReadIntoRequest>> mReadIntoRequests;
+};
+
+namespace streams_abstract {
+
+already_AddRefed<ReadableStreamBYOBReader> AcquireReadableStreamBYOBReader(
+ ReadableStream* aStream, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableStreamBYOBReaderRead(
+ JSContext* aCx, ReadableStreamBYOBReader* aReader,
+ JS::Handle<JSObject*> aView, ReadIntoRequest* aReadIntoRequest,
+ ErrorResult& aRv);
+
+void ReadableStreamBYOBReaderErrorReadIntoRequests(
+ JSContext* aCx, ReadableStreamBYOBReader* aReader,
+ JS::Handle<JS::Value> aError, ErrorResult& aRv);
+
+void ReadableStreamBYOBReaderRelease(JSContext* aCx,
+ ReadableStreamBYOBReader* aReader,
+ ErrorResult& aRv);
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ReadableStreamBYOBReader_h
diff --git a/dom/streams/ReadableStreamBYOBRequest.cpp b/dom/streams/ReadableStreamBYOBRequest.cpp
new file mode 100644
index 0000000000..655b914830
--- /dev/null
+++ b/dom/streams/ReadableStreamBYOBRequest.cpp
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ReadableStreamBYOBRequest.h"
+
+#include "mozilla/dom/ByteStreamHelpers.h"
+#include "js/ArrayBuffer.h"
+#include "js/TypeDecls.h"
+#include "mozilla/dom/ReadableByteStreamController.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamBYOBRequestBinding.h"
+#include "js/experimental/TypedData.h"
+#include "mozilla/dom/ReadableStreamController.h"
+#include "nsCOMPtr.h"
+#include "nsIGlobalObject.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+ReadableStreamBYOBRequest::ReadableStreamBYOBRequest(nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal) {
+ mozilla::HoldJSObjects(this);
+}
+
+ReadableStreamBYOBRequest::~ReadableStreamBYOBRequest() {
+ mozilla::DropJSObjects(this);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(ReadableStreamBYOBRequest,
+ (mGlobal, mController),
+ (mView))
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadableStreamBYOBRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadableStreamBYOBRequest)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamBYOBRequest)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* ReadableStreamBYOBRequest::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return ReadableStreamBYOBRequest_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://streams.spec.whatwg.org/#rs-byob-request-view
+void ReadableStreamBYOBRequest::GetView(
+ JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const {
+ // Step 1.
+ aRetVal.set(mView);
+}
+
+// https://streams.spec.whatwg.org/#rs-byob-request-respond
+void ReadableStreamBYOBRequest::Respond(JSContext* aCx, uint64_t bytesWritten,
+ ErrorResult& aRv) {
+ // Step 1.
+ if (!mController) {
+ aRv.ThrowTypeError("Undefined Controller");
+ return;
+ }
+
+ // Step 2.
+ bool isSharedMemory;
+ JS::Rooted<JSObject*> view(aCx, mView);
+ JS::Rooted<JSObject*> arrayBuffer(
+ aCx, JS_GetArrayBufferViewBuffer(aCx, view, &isSharedMemory));
+ if (!arrayBuffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ if (JS::IsDetachedArrayBufferObject(arrayBuffer)) {
+ aRv.ThrowTypeError("View of Detached buffer");
+ return;
+ }
+
+ // Step 3.
+ MOZ_ASSERT(JS_GetArrayBufferViewByteLength(view) > 0);
+
+ // Step 4.
+ MOZ_ASSERT(JS::GetArrayBufferByteLength(arrayBuffer) > 0);
+
+ // Step 5.
+ RefPtr<ReadableByteStreamController> controller(mController);
+ ReadableByteStreamControllerRespond(aCx, controller, bytesWritten, aRv);
+}
+
+// https://streams.spec.whatwg.org/#rs-byob-request-respond-with-new-view
+void ReadableStreamBYOBRequest::RespondWithNewView(JSContext* aCx,
+ const ArrayBufferView& view,
+ ErrorResult& aRv) {
+ // Step 1.
+ if (!mController) {
+ aRv.ThrowTypeError("Undefined Controller");
+ return;
+ }
+
+ // Step 2.
+ bool isSharedMemory;
+ JS::Rooted<JSObject*> rootedViewObj(aCx, view.Obj());
+ JS::Rooted<JSObject*> viewedArrayBuffer(
+ aCx, JS_GetArrayBufferViewBuffer(aCx, rootedViewObj, &isSharedMemory));
+ if (!viewedArrayBuffer) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ if (JS::IsDetachedArrayBufferObject(viewedArrayBuffer)) {
+ aRv.ThrowTypeError("View of Detached Array Buffer");
+ return;
+ }
+
+ // Step 3.
+ RefPtr<ReadableByteStreamController> controller(mController);
+ ReadableByteStreamControllerRespondWithNewView(aCx, controller, rootedViewObj,
+ aRv);
+}
+
+void ReadableStreamBYOBRequest::SetController(
+ ReadableByteStreamController* aController) {
+ mController = aController;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/ReadableStreamBYOBRequest.h b/dom/streams/ReadableStreamBYOBRequest.h
new file mode 100644
index 0000000000..fb8ad62979
--- /dev/null
+++ b/dom/streams/ReadableStreamBYOBRequest.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadableStreamBYOBRequest_h
+#define mozilla_dom_ReadableStreamBYOBRequest_h
+
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/TypedArray.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla::dom {
+
+class ReadableByteStreamController;
+
+class ReadableStreamBYOBRequest final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ReadableStreamBYOBRequest)
+
+ public:
+ explicit ReadableStreamBYOBRequest(nsIGlobalObject* aGlobal);
+
+ protected:
+ ~ReadableStreamBYOBRequest();
+
+ public:
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetView(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+ MOZ_CAN_RUN_SCRIPT void Respond(JSContext* aCx, uint64_t bytesWritten,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void RespondWithNewView(JSContext* aCx,
+ const ArrayBufferView& view,
+ ErrorResult& aRv);
+
+ ReadableByteStreamController* Controller() { return mController; }
+ void SetController(ReadableByteStreamController* aController);
+
+ JSObject* View() { return mView; }
+ void SetView(JS::Handle<JSObject*> aView) { mView = aView; }
+
+ private:
+ // Internal Slots
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ RefPtr<ReadableByteStreamController> mController;
+ JS::Heap<JSObject*> mView;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/ReadableStreamController.h b/dom/streams/ReadableStreamController.h
new file mode 100644
index 0000000000..766c933017
--- /dev/null
+++ b/dom/streams/ReadableStreamController.h
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadableStreamController_h
+#define mozilla_dom_ReadableStreamController_h
+
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+#include "UnderlyingSourceCallbackHelpers.h"
+
+namespace mozilla::dom {
+struct ReadRequest;
+class ReadableStream;
+class ReadableStreamDefaultController;
+class ReadableByteStreamController;
+
+class ReadableStreamController : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ReadableStreamController)
+
+ ReadableStreamController(nsIGlobalObject* aGlobal);
+
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+
+ virtual bool IsDefault() = 0;
+ virtual bool IsByte() = 0;
+ virtual ReadableStreamDefaultController* AsDefault() = 0;
+ virtual ReadableByteStreamController* AsByte() = 0;
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual already_AddRefed<Promise> CancelSteps(JSContext* aCx,
+ JS::Handle<JS::Value> aReason,
+ ErrorResult& aRv) = 0;
+ MOZ_CAN_RUN_SCRIPT
+ virtual void PullSteps(JSContext* aCx, ReadRequest* aReadRequest,
+ ErrorResult& aRv) = 0;
+
+ // No JS implementable UnderlyingSource callback exists for this.
+ virtual void ReleaseSteps() = 0;
+
+ UnderlyingSourceAlgorithmsBase* GetAlgorithms() const { return mAlgorithms; }
+ void SetAlgorithms(UnderlyingSourceAlgorithmsBase& aAlgorithms) {
+ mAlgorithms = &aAlgorithms;
+ }
+ void ClearAlgorithms() {
+ MOZ_ASSERT(mAlgorithms);
+ mAlgorithms->ReleaseObjects();
+ mAlgorithms = nullptr;
+ }
+
+ ReadableStream* Stream() const { return mStream; }
+ void SetStream(ReadableStream* aStream);
+
+ protected:
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ // The algorithms for the underlying source
+ RefPtr<UnderlyingSourceAlgorithmsBase> mAlgorithms;
+
+ RefPtr<ReadableStream> mStream;
+
+ virtual ~ReadableStreamController() = default;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/ReadableStreamDefaultController.cpp b/dom/streams/ReadableStreamDefaultController.cpp
new file mode 100644
index 0000000000..7a5ae3a80f
--- /dev/null
+++ b/dom/streams/ReadableStreamDefaultController.cpp
@@ -0,0 +1,653 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ReadableStreamDefaultController.h"
+
+#include "js/Exception.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamController.h"
+#include "mozilla/dom/ReadableStreamDefaultControllerBinding.h"
+#include "mozilla/dom/ReadableStreamDefaultReaderBinding.h"
+#include "mozilla/dom/UnderlyingSourceBinding.h"
+#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+NS_IMPL_CYCLE_COLLECTION(ReadableStreamController, mGlobal, mAlgorithms,
+ mStream)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadableStreamController)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadableStreamController)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamController)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+ReadableStreamController::ReadableStreamController(nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal) {}
+
+void ReadableStreamController::SetStream(ReadableStream* aStream) {
+ mStream = aStream;
+}
+
+// Note: Using the individual macros vs NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE
+// because I need to specify a manual implementation of
+// NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN.
+NS_IMPL_CYCLE_COLLECTION_CLASS(ReadableStreamDefaultController)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ReadableStreamDefaultController,
+ ReadableStreamController)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mStrategySizeAlgorithm)
+ tmp->mQueue.clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
+ ReadableStreamDefaultController, ReadableStreamController)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStrategySizeAlgorithm)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(ReadableStreamDefaultController,
+ ReadableStreamController)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ // Trace the associated queue.
+ for (const auto& queueEntry : tmp->mQueue) {
+ aCallbacks.Trace(&queueEntry->mValue, "mQueue.mValue", aClosure);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(ReadableStreamDefaultController,
+ ReadableStreamController)
+NS_IMPL_RELEASE_INHERITED(ReadableStreamDefaultController,
+ ReadableStreamController)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamDefaultController)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(ReadableStreamController)
+
+ReadableStreamDefaultController::ReadableStreamDefaultController(
+ nsIGlobalObject* aGlobal)
+ : ReadableStreamController(aGlobal) {
+ mozilla::HoldJSObjects(this);
+}
+
+ReadableStreamDefaultController::~ReadableStreamDefaultController() {
+ // MG:XXX: LinkedLists are required to be empty at destruction, but it seems
+ // it is possible to have a controller be destructed while still
+ // having entries in its queue.
+ //
+ // This needs to be verified as not indicating some other issue.
+ mozilla::DropJSObjects(this);
+ mQueue.clear();
+}
+
+JSObject* ReadableStreamDefaultController::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return ReadableStreamDefaultController_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#readable-stream-default-controller-can-close-or-enqueue
+static bool ReadableStreamDefaultControllerCanCloseOrEnqueue(
+ ReadableStreamDefaultController* aController) {
+ // Step 1. Let state be controller.[[stream]].[[state]].
+ ReadableStream::ReaderState state = aController->Stream()->State();
+
+ // Step 2. If controller.[[closeRequested]] is false and state is "readable",
+ // return true.
+ // Step 3. Return false.
+ return !aController->CloseRequested() &&
+ state == ReadableStream::ReaderState::Readable;
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-default-controller-can-close-or-enqueue
+// This is a variant of ReadableStreamDefaultControllerCanCloseOrEnqueue
+// that also throws when the function would return false to improve error
+// messages.
+bool ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow(
+ ReadableStreamDefaultController* aController,
+ CloseOrEnqueue aCloseOrEnqueue, ErrorResult& aRv) {
+ // Step 1. Let state be controller.[[stream]].[[state]].
+ ReadableStream::ReaderState state = aController->Stream()->State();
+
+ nsCString prefix;
+ if (aCloseOrEnqueue == CloseOrEnqueue::Close) {
+ prefix = "Cannot close a stream that "_ns;
+ } else {
+ prefix = "Cannot enqueue into a stream that "_ns;
+ }
+
+ switch (state) {
+ case ReadableStream::ReaderState::Readable:
+ // Step 2. If controller.[[closeRequested]] is false and
+ // state is "readable", return true.
+ // Note: We don't error/check for [[closeRequest]] first, because
+ // [[closedRequest]] is still true even after the state is "closed".
+ // This doesn't cause any spec observable difference.
+ if (!aController->CloseRequested()) {
+ return true;
+ }
+
+ // Step 3. Return false.
+ aRv.ThrowTypeError(prefix + "has already been requested to close."_ns);
+ return false;
+
+ case ReadableStream::ReaderState::Closed:
+ aRv.ThrowTypeError(prefix + "is already closed."_ns);
+ return false;
+
+ case ReadableStream::ReaderState::Errored:
+ aRv.ThrowTypeError(prefix + "has errored."_ns);
+ return false;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown ReaderState");
+ return false;
+ }
+}
+
+Nullable<double> ReadableStreamDefaultControllerGetDesiredSize(
+ ReadableStreamDefaultController* aController) {
+ ReadableStream::ReaderState state = aController->Stream()->State();
+ if (state == ReadableStream::ReaderState::Errored) {
+ return nullptr;
+ }
+
+ if (state == ReadableStream::ReaderState::Closed) {
+ return 0.0;
+ }
+
+ return aController->StrategyHWM() - aController->QueueTotalSize();
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#rs-default-controller-desired-size
+Nullable<double> ReadableStreamDefaultController::GetDesiredSize() {
+ // Step 1.
+ return ReadableStreamDefaultControllerGetDesiredSize(this);
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#readable-stream-default-controller-clear-algorithms
+//
+// Note: nullptr is used to indicate we run the default algorithm at the
+// moment,
+// so the below doesn't quite match the spec, but serves the correct
+// purpose for disconnecting the algorithms from the object graph to allow
+// collection.
+//
+// As far as I know, this isn't currently visible, but we need to keep
+// this in mind. This is a weakness of this current implementation, and
+// I'd prefer to have a better answer here eventually.
+void ReadableStreamDefaultControllerClearAlgorithms(
+ ReadableStreamDefaultController* aController) {
+ // Step 1.
+ // Step 2.
+ aController->ClearAlgorithms();
+
+ // Step 3.
+ aController->setStrategySizeAlgorithm(nullptr);
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-default-controller-close
+void ReadableStreamDefaultControllerClose(
+ JSContext* aCx, ReadableStreamDefaultController* aController,
+ ErrorResult& aRv) {
+ // Step 1.
+ if (!ReadableStreamDefaultControllerCanCloseOrEnqueue(aController)) {
+ return;
+ }
+
+ // Step 2.
+ RefPtr<ReadableStream> stream = aController->Stream();
+
+ // Step 3.
+ aController->SetCloseRequested(true);
+
+ // Step 4.
+ if (aController->Queue().isEmpty()) {
+ // Step 4.1
+ ReadableStreamDefaultControllerClearAlgorithms(aController);
+
+ // Step 4.2
+ ReadableStreamClose(aCx, stream, aRv);
+ }
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#rs-default-controller-close
+void ReadableStreamDefaultController::Close(JSContext* aCx, ErrorResult& aRv) {
+ // Step 1.
+ if (!ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow(
+ this, CloseOrEnqueue::Close, aRv)) {
+ return;
+ }
+
+ // Step 2.
+ ReadableStreamDefaultControllerClose(aCx, this, aRv);
+}
+
+namespace streams_abstract {
+
+MOZ_CAN_RUN_SCRIPT static void ReadableStreamDefaultControllerCallPullIfNeeded(
+ JSContext* aCx, ReadableStreamDefaultController* aController,
+ ErrorResult& aRv);
+
+// https://streams.spec.whatwg.org/#readable-stream-default-controller-enqueue
+void ReadableStreamDefaultControllerEnqueue(
+ JSContext* aCx, ReadableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
+ // Step 1.
+ if (!ReadableStreamDefaultControllerCanCloseOrEnqueue(aController)) {
+ return;
+ }
+
+ // Step 2.
+ RefPtr<ReadableStream> stream = aController->Stream();
+
+ // Step 3.
+ if (IsReadableStreamLocked(stream) &&
+ ReadableStreamGetNumReadRequests(stream) > 0) {
+ ReadableStreamFulfillReadRequest(aCx, stream, aChunk, false, aRv);
+ } else {
+ // Step 4.1
+ Optional<JS::Handle<JS::Value>> optionalChunk(aCx, aChunk);
+
+ // Step 4.3 (Re-ordered);
+ RefPtr<QueuingStrategySize> sizeAlgorithm(
+ aController->StrategySizeAlgorithm());
+
+ // If !sizeAlgorithm, we return 1, which is inlined from
+ // https://streams.spec.whatwg.org/#make-size-algorithm-from-size-function
+ double chunkSize =
+ sizeAlgorithm
+ ? sizeAlgorithm->Call(
+ optionalChunk, aRv,
+ "ReadableStreamDefaultController.[[strategySizeAlgorithm]]",
+ CallbackObject::eRethrowExceptions)
+ : 1.0;
+
+ // If this is an uncatchable exception we can't continue.
+ if (aRv.IsUncatchableException()) {
+ return;
+ }
+
+ // Step 4.2:
+ if (aRv.MaybeSetPendingException(
+ aCx, "ReadableStreamDefaultController.enqueue")) {
+ JS::Rooted<JS::Value> errorValue(aCx);
+
+ JS_GetPendingException(aCx, &errorValue);
+
+ // Step 4.2.1
+
+ ReadableStreamDefaultControllerError(aCx, aController, errorValue, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 4.2.2 Caller must treat aRv as if it were a completion
+ // value
+ aRv.MightThrowJSException();
+ aRv.ThrowJSException(aCx, errorValue);
+ return;
+ }
+
+ // Step 4.4
+ EnqueueValueWithSize(aController, aChunk, chunkSize, aRv);
+
+ // Step 4.5
+ // Note we convert the pending exception to a JS value here, and then
+ // re-throw it because we save this exception and re-expose it elsewhere
+ // and there are tests to ensure the identity of these errors are the same.
+ if (aRv.MaybeSetPendingException(
+ aCx, "ReadableStreamDefaultController.enqueue")) {
+ JS::Rooted<JS::Value> errorValue(aCx);
+
+ if (!JS_GetPendingException(aCx, &errorValue)) {
+ // Uncatchable exception; we should mark aRv and return.
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ JS_ClearPendingException(aCx);
+
+ // Step 4.5.1
+ ReadableStreamDefaultControllerError(aCx, aController, errorValue, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 4.5.2 Caller must treat aRv as if it were a completion
+ // value
+ aRv.MightThrowJSException();
+ aRv.ThrowJSException(aCx, errorValue);
+ return;
+ }
+ }
+
+ // Step 5.
+ ReadableStreamDefaultControllerCallPullIfNeeded(aCx, aController, aRv);
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#rs-default-controller-close
+void ReadableStreamDefaultController::Enqueue(JSContext* aCx,
+ JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) {
+ // Step 1.
+ if (!ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow(
+ this, CloseOrEnqueue::Enqueue, aRv)) {
+ return;
+ }
+
+ // Step 2.
+ ReadableStreamDefaultControllerEnqueue(aCx, this, aChunk, aRv);
+}
+
+void ReadableStreamDefaultController::Error(JSContext* aCx,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ ReadableStreamDefaultControllerError(aCx, this, aError, aRv);
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#readable-stream-default-controller-should-call-pull
+bool ReadableStreamDefaultControllerShouldCallPull(
+ ReadableStreamDefaultController* aController) {
+ // Step 1.
+ ReadableStream* stream = aController->Stream();
+
+ // Step 2.
+ if (!ReadableStreamDefaultControllerCanCloseOrEnqueue(aController)) {
+ return false;
+ }
+
+ // Step 3.
+ if (!aController->Started()) {
+ return false;
+ }
+
+ // Step 4.
+ if (IsReadableStreamLocked(stream) &&
+ ReadableStreamGetNumReadRequests(stream) > 0) {
+ return true;
+ }
+
+ // Step 5.
+ Nullable<double> desiredSize =
+ ReadableStreamDefaultControllerGetDesiredSize(aController);
+
+ // Step 6.
+ MOZ_ASSERT(!desiredSize.IsNull());
+
+ // Step 7 + 8
+ return desiredSize.Value() > 0;
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-default-controller-error
+void ReadableStreamDefaultControllerError(
+ JSContext* aCx, ReadableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
+ // Step 1.
+ ReadableStream* stream = aController->Stream();
+
+ // Step 2.
+ if (stream->State() != ReadableStream::ReaderState::Readable) {
+ return;
+ }
+
+ // Step 3.
+ ResetQueue(aController);
+
+ // Step 4.
+ ReadableStreamDefaultControllerClearAlgorithms(aController);
+
+ // Step 5.
+ ReadableStreamError(aCx, stream, aValue, aRv);
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-default-controller-call-pull-if-needed
+static void ReadableStreamDefaultControllerCallPullIfNeeded(
+ JSContext* aCx, ReadableStreamDefaultController* aController,
+ ErrorResult& aRv) {
+ // Step 1.
+ bool shouldPull = ReadableStreamDefaultControllerShouldCallPull(aController);
+
+ // Step 2.
+ if (!shouldPull) {
+ return;
+ }
+
+ // Step 3.
+ if (aController->Pulling()) {
+ // Step 3.1
+ aController->SetPullAgain(true);
+ // Step 3.2
+ return;
+ }
+
+ // Step 4.
+ MOZ_ASSERT(!aController->PullAgain());
+
+ // Step 5.
+ aController->SetPulling(true);
+
+ // Step 6.
+ RefPtr<UnderlyingSourceAlgorithmsBase> algorithms =
+ aController->GetAlgorithms();
+ RefPtr<Promise> pullPromise =
+ algorithms->PullCallback(aCx, *aController, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 7 + 8:
+ pullPromise->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ ReadableStreamDefaultController* mController)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ // Step 7.1
+ mController->SetPulling(false);
+ // Step 7.2
+ if (mController->PullAgain()) {
+ // Step 7.2.1
+ mController->SetPullAgain(false);
+
+ // Step 7.2.2
+ ErrorResult rv;
+ ReadableStreamDefaultControllerCallPullIfNeeded(
+ aCx, MOZ_KnownLive(mController), aRv);
+ }
+ },
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ ReadableStreamDefaultController* mController) {
+ // Step 8.1
+ ReadableStreamDefaultControllerError(aCx, mController, aValue, aRv);
+ },
+ RefPtr(aController));
+}
+
+// https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller
+void SetUpReadableStreamDefaultController(
+ JSContext* aCx, ReadableStream* aStream,
+ ReadableStreamDefaultController* aController,
+ UnderlyingSourceAlgorithmsBase* aAlgorithms, double aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) {
+ // Step 1.
+ MOZ_ASSERT(!aStream->Controller());
+
+ // Step 2.
+ aController->SetStream(aStream);
+
+ // Step 3.
+ ResetQueue(aController);
+
+ // Step 4.
+ aController->SetStarted(false);
+ aController->SetCloseRequested(false);
+ aController->SetPullAgain(false);
+ aController->SetPulling(false);
+
+ // Step 5.
+ aController->setStrategySizeAlgorithm(aSizeAlgorithm);
+ aController->SetStrategyHWM(aHighWaterMark);
+
+ // Step 6.
+ // Step 7.
+ aController->SetAlgorithms(*aAlgorithms);
+
+ // Step 8.
+ aStream->SetController(*aController);
+
+ // Step 9. Default algorithm returns undefined. See Step 2 of
+ // https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller
+ JS::Rooted<JS::Value> startResult(aCx, JS::UndefinedValue());
+ RefPtr<ReadableStreamDefaultController> controller = aController;
+ aAlgorithms->StartCallback(aCx, *controller, &startResult, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 10.
+ RefPtr<Promise> startPromise =
+ Promise::CreateInfallible(aStream->GetParentObject());
+ startPromise->MaybeResolve(startResult);
+
+ // Step 11 & 12:
+ startPromise->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ ReadableStreamDefaultController* aController)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ MOZ_ASSERT(aController);
+
+ // Step 11.1
+ aController->SetStarted(true);
+
+ // Step 11.2
+ aController->SetPulling(false);
+
+ // Step 11.3
+ aController->SetPullAgain(false);
+
+ // Step 11.4:
+ ReadableStreamDefaultControllerCallPullIfNeeded(
+ aCx, MOZ_KnownLive(aController), aRv);
+ },
+
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ ReadableStreamDefaultController* aController) {
+ // Step 12.1
+ ReadableStreamDefaultControllerError(aCx, aController, aValue, aRv);
+ },
+ RefPtr(aController));
+}
+
+// https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller-from-underlying-source
+void SetupReadableStreamDefaultControllerFromUnderlyingSource(
+ JSContext* aCx, ReadableStream* aStream,
+ JS::Handle<JSObject*> aUnderlyingSource,
+ UnderlyingSource& aUnderlyingSourceDict, double aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) {
+ // Step 1.
+ RefPtr<ReadableStreamDefaultController> controller =
+ new ReadableStreamDefaultController(aStream->GetParentObject());
+
+ // Step 2 - 7
+ RefPtr<UnderlyingSourceAlgorithms> algorithms =
+ new UnderlyingSourceAlgorithms(aStream->GetParentObject(),
+ aUnderlyingSource, aUnderlyingSourceDict);
+
+ // Step 8:
+ SetUpReadableStreamDefaultController(aCx, aStream, controller, algorithms,
+ aHighWaterMark, aSizeAlgorithm, aRv);
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#rs-default-controller-private-cancel
+already_AddRefed<Promise> ReadableStreamDefaultController::CancelSteps(
+ JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
+ // Step 1.
+ ResetQueue(this);
+
+ // Step 2.
+ Optional<JS::Handle<JS::Value>> errorOption(aCx, aReason);
+ RefPtr<UnderlyingSourceAlgorithmsBase> algorithms = mAlgorithms;
+ RefPtr<Promise> result = algorithms->CancelCallback(aCx, errorOption, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 3.
+ ReadableStreamDefaultControllerClearAlgorithms(this);
+
+ // Step 4.
+ return result.forget();
+}
+
+// https://streams.spec.whatwg.org/#rs-default-controller-private-pull
+void ReadableStreamDefaultController::PullSteps(JSContext* aCx,
+ ReadRequest* aReadRequest,
+ ErrorResult& aRv) {
+ // Step 1.
+ RefPtr<ReadableStream> stream = mStream;
+
+ // Step 2.
+ if (!mQueue.isEmpty()) {
+ // Step 2.1
+ JS::Rooted<JS::Value> chunk(aCx);
+ DequeueValue(this, &chunk);
+
+ // Step 2.2
+ if (CloseRequested() && mQueue.isEmpty()) {
+ // Step 2.2.1
+ ReadableStreamDefaultControllerClearAlgorithms(this);
+ // Step 2.2.2
+ ReadableStreamClose(aCx, stream, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ } else {
+ // Step 2.3
+ ReadableStreamDefaultControllerCallPullIfNeeded(aCx, this, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 2.4
+ aReadRequest->ChunkSteps(aCx, chunk, aRv);
+ } else {
+ // Step 3.
+ // Step 3.1
+ ReadableStreamAddReadRequest(stream, aReadRequest);
+ // Step 3.2
+ ReadableStreamDefaultControllerCallPullIfNeeded(aCx, this, aRv);
+ }
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaultcontroller-releasesteps
+void ReadableStreamDefaultController::ReleaseSteps() {
+ // Step 1. Return.
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/ReadableStreamDefaultController.h b/dom/streams/ReadableStreamDefaultController.h
new file mode 100644
index 0000000000..f94a692f15
--- /dev/null
+++ b/dom/streams/ReadableStreamDefaultController.h
@@ -0,0 +1,163 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadableStreamDefaultController_h
+#define mozilla_dom_ReadableStreamDefaultController_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "mozilla/dom/QueueWithSizes.h"
+#include "mozilla/dom/ReadableStreamController.h"
+#include "mozilla/dom/ReadRequest.h"
+#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/Nullable.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+class ReadableStream;
+class ReadableStreamDefaultReader;
+struct UnderlyingSource;
+class ReadableStreamGenericReader;
+
+class ReadableStreamDefaultController final : public ReadableStreamController,
+ public nsWrapperCache {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ ReadableStreamDefaultController, ReadableStreamController)
+
+ public:
+ explicit ReadableStreamDefaultController(nsIGlobalObject* aGlobal);
+
+ protected:
+ ~ReadableStreamDefaultController() override;
+
+ public:
+ bool IsDefault() override { return true; }
+ bool IsByte() override { return false; }
+ ReadableStreamDefaultController* AsDefault() override { return this; }
+ ReadableByteStreamController* AsByte() override { return nullptr; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ Nullable<double> GetDesiredSize();
+
+ MOZ_CAN_RUN_SCRIPT void Close(JSContext* aCx, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT void Enqueue(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv);
+
+ void Error(JSContext* aCx, JS::Handle<JS::Value> aError, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CancelSteps(
+ JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) override;
+ MOZ_CAN_RUN_SCRIPT void PullSteps(JSContext* aCx, ReadRequest* aReadRequest,
+ ErrorResult& aRv) override;
+
+ void ReleaseSteps() override;
+
+ // Internal Slot Accessors
+ bool CloseRequested() const { return mCloseRequested; }
+ void SetCloseRequested(bool aCloseRequested) {
+ mCloseRequested = aCloseRequested;
+ }
+
+ bool PullAgain() const { return mPullAgain; }
+ void SetPullAgain(bool aPullAgain) { mPullAgain = aPullAgain; }
+
+ bool Pulling() const { return mPulling; }
+ void SetPulling(bool aPulling) { mPulling = aPulling; }
+
+ QueueWithSizes& Queue() { return mQueue; }
+
+ double QueueTotalSize() const { return mQueueTotalSize; }
+ void SetQueueTotalSize(double aQueueTotalSize) {
+ mQueueTotalSize = aQueueTotalSize;
+ }
+
+ bool Started() const { return mStarted; }
+ void SetStarted(bool aStarted) { mStarted = aStarted; }
+
+ double StrategyHWM() const { return mStrategyHWM; }
+ void SetStrategyHWM(double aStrategyHWM) { mStrategyHWM = aStrategyHWM; }
+
+ QueuingStrategySize* StrategySizeAlgorithm() const {
+ return mStrategySizeAlgorithm;
+ }
+ void setStrategySizeAlgorithm(QueuingStrategySize* aStrategySizeAlgorithm) {
+ mStrategySizeAlgorithm = aStrategySizeAlgorithm;
+ }
+
+ private:
+ // Internal Slots:
+ bool mCloseRequested = false;
+ bool mPullAgain = false;
+ bool mPulling = false;
+ QueueWithSizes mQueue = {};
+ double mQueueTotalSize = 0.0;
+ bool mStarted = false;
+ double mStrategyHWM = false;
+ RefPtr<QueuingStrategySize> mStrategySizeAlgorithm;
+};
+
+namespace streams_abstract {
+
+MOZ_CAN_RUN_SCRIPT void SetUpReadableStreamDefaultController(
+ JSContext* aCx, ReadableStream* aStream,
+ ReadableStreamDefaultController* aController,
+ UnderlyingSourceAlgorithmsBase* aAlgorithms, double aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void
+SetupReadableStreamDefaultControllerFromUnderlyingSource(
+ JSContext* aCx, ReadableStream* aStream,
+ JS::Handle<JSObject*> aUnderlyingSource,
+ UnderlyingSource& aUnderlyingSourceDict, double aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableStreamDefaultControllerEnqueue(
+ JSContext* aCx, ReadableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aChunk, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableStreamDefaultControllerClose(
+ JSContext* aCx, ReadableStreamDefaultController* aController,
+ ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void ReadableStreamDefaultReaderRead(
+ JSContext* aCx, ReadableStreamGenericReader* reader, ReadRequest* aRequest,
+ ErrorResult& aRv);
+
+void ReadableStreamDefaultControllerError(
+ JSContext* aCx, ReadableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv);
+
+Nullable<double> ReadableStreamDefaultControllerGetDesiredSize(
+ ReadableStreamDefaultController* aController);
+
+enum class CloseOrEnqueue { Close, Enqueue };
+
+bool ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow(
+ ReadableStreamDefaultController* aController,
+ CloseOrEnqueue aCloseOrEnqueue, ErrorResult& aRv);
+
+bool ReadableStreamDefaultControllerShouldCallPull(
+ ReadableStreamDefaultController* aController);
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ReadableStreamDefaultController_h
diff --git a/dom/streams/ReadableStreamDefaultReader.cpp b/dom/streams/ReadableStreamDefaultReader.cpp
new file mode 100644
index 0000000000..ebc86d4778
--- /dev/null
+++ b/dom/streams/ReadableStreamDefaultReader.cpp
@@ -0,0 +1,439 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ReadableStreamDefaultReader.h"
+
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "js/PropertyAndElement.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "jsapi.h"
+#include "mozilla/dom/ReadableStreamDefaultReaderBinding.h"
+#include "mozilla/dom/UnderlyingSourceBinding.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+NS_IMPL_CYCLE_COLLECTION(ReadableStreamGenericReader, mClosedPromise, mStream,
+ mGlobal)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadableStreamGenericReader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadableStreamGenericReader)
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ReadableStreamGenericReader)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamGenericReader)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_INHERITED(ReadableStreamDefaultReader,
+ ReadableStreamGenericReader,
+ mReadRequests)
+NS_IMPL_ADDREF_INHERITED(ReadableStreamDefaultReader,
+ ReadableStreamGenericReader)
+NS_IMPL_RELEASE_INHERITED(ReadableStreamDefaultReader,
+ ReadableStreamGenericReader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamDefaultReader)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(ReadableStreamGenericReader)
+
+ReadableStreamDefaultReader::ReadableStreamDefaultReader(nsISupports* aGlobal)
+ : ReadableStreamGenericReader(do_QueryInterface(aGlobal)) {}
+
+ReadableStreamDefaultReader::~ReadableStreamDefaultReader() {
+ mReadRequests.clear();
+}
+
+JSObject* ReadableStreamDefaultReader::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return ReadableStreamDefaultReader_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#readable-stream-reader-generic-initialize
+bool ReadableStreamReaderGenericInitialize(ReadableStreamGenericReader* aReader,
+ ReadableStream* aStream) {
+ // Step 1.
+ aReader->SetStream(aStream);
+
+ // Step 2.
+ aStream->SetReader(aReader);
+
+ aReader->SetClosedPromise(
+ Promise::CreateInfallible(aReader->GetParentObject()));
+
+ switch (aStream->State()) {
+ // Step 3.
+ case ReadableStream::ReaderState::Readable:
+ // Step 3.1
+ // Promise created above.
+ return true;
+ // Step 4.
+ case ReadableStream::ReaderState::Closed:
+ // Step 4.1.
+ aReader->ClosedPromise()->MaybeResolve(JS::UndefinedHandleValue);
+
+ return true;
+ // Step 5.
+ case ReadableStream::ReaderState::Errored: {
+ // Step 5.1 Implicit
+ // Step 5.2
+ JS::RootingContext* rcx = RootingCx();
+ JS::Rooted<JS::Value> rootedError(rcx, aStream->StoredError());
+ aReader->ClosedPromise()->MaybeReject(rootedError);
+
+ // Step 5.3
+ aReader->ClosedPromise()->SetSettledPromiseIsHandled();
+ return true;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown ReaderState");
+ return false;
+ }
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#default-reader-constructor &&
+// https://streams.spec.whatwg.org/#set-up-readable-stream-default-reader
+/* static */
+already_AddRefed<ReadableStreamDefaultReader>
+ReadableStreamDefaultReader::Constructor(const GlobalObject& aGlobal,
+ ReadableStream& aStream,
+ ErrorResult& aRv) {
+ RefPtr<ReadableStreamDefaultReader> reader =
+ new ReadableStreamDefaultReader(aGlobal.GetAsSupports());
+
+ // https://streams.spec.whatwg.org/#set-up-readable-stream-default-reader
+ // Step 1.
+ if (aStream.Locked()) {
+ aRv.ThrowTypeError(
+ "Cannot create a new reader for a readable stream already locked by "
+ "another reader.");
+ return nullptr;
+ }
+
+ // Step 2.
+ RefPtr<ReadableStream> streamPtr = &aStream;
+ if (!ReadableStreamReaderGenericInitialize(reader, streamPtr)) {
+ return nullptr;
+ }
+
+ // Step 3.
+ reader->mReadRequests.clear();
+
+ return reader.forget();
+}
+
+void Read_ReadRequest::ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) {
+ // https://streams.spec.whatwg.org/#default-reader-read Step 3.
+ // chunk steps, given chunk:
+ // Step 1. Resolve promise with «[ "value" → chunk, "done" → false ]».
+
+ // Value may need to be wrapped if stream and reader are in different
+ // compartments.
+ JS::Rooted<JS::Value> chunk(aCx, aChunk);
+ if (!JS_WrapValue(aCx, &chunk)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ RootedDictionary<ReadableStreamReadResult> result(aCx);
+ result.mValue = chunk;
+ result.mDone.Construct(false);
+
+ // Ensure that the object is created with the current global.
+ JS::Rooted<JS::Value> value(aCx);
+ if (!ToJSValue(aCx, std::move(result), &value)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ mPromise->MaybeResolve(value);
+}
+
+void Read_ReadRequest::CloseSteps(JSContext* aCx, ErrorResult& aRv) {
+ // https://streams.spec.whatwg.org/#default-reader-read Step 3.
+ // close steps:
+ // Step 1. Resolve promise with «[ "value" → undefined, "done" → true ]».
+ RootedDictionary<ReadableStreamReadResult> result(aCx);
+ result.mValue.setUndefined();
+ result.mDone.Construct(true);
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!ToJSValue(aCx, std::move(result), &value)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ mPromise->MaybeResolve(value);
+}
+
+void Read_ReadRequest::ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> e,
+ ErrorResult& aRv) {
+ // https://streams.spec.whatwg.org/#default-reader-read Step 3.
+ // error steps:
+ // Step 1. Reject promise with e.
+ mPromise->MaybeReject(e);
+}
+
+NS_IMPL_CYCLE_COLLECTION(ReadRequest)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(Read_ReadRequest, ReadRequest, mPromise)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ReadRequest)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ReadRequest)
+
+NS_IMPL_ADDREF_INHERITED(Read_ReadRequest, ReadRequest)
+NS_IMPL_RELEASE_INHERITED(Read_ReadRequest, ReadRequest)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadRequest)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Read_ReadRequest)
+NS_INTERFACE_MAP_END_INHERITING(ReadRequest)
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#readable-stream-default-reader-read
+void ReadableStreamDefaultReaderRead(JSContext* aCx,
+ ReadableStreamGenericReader* aReader,
+ ReadRequest* aRequest, ErrorResult& aRv) {
+ // Step 1.
+ ReadableStream* stream = aReader->GetStream();
+
+ // Step 2.
+ MOZ_ASSERT(stream);
+
+ // Step 3.
+ stream->SetDisturbed(true);
+
+ switch (stream->State()) {
+ // Step 4.
+ case ReadableStream::ReaderState::Closed: {
+ aRequest->CloseSteps(aCx, aRv);
+ return;
+ }
+
+ case ReadableStream::ReaderState::Errored: {
+ JS::Rooted<JS::Value> storedError(aCx, stream->StoredError());
+ aRequest->ErrorSteps(aCx, storedError, aRv);
+ return;
+ }
+
+ case ReadableStream::ReaderState::Readable: {
+ RefPtr<ReadableStreamController> controller(stream->Controller());
+ MOZ_ASSERT(controller);
+ controller->PullSteps(aCx, aRequest, aRv);
+ return;
+ }
+ }
+}
+} // namespace streams_abstract
+
+// Return a raw pointer here to avoid refcounting, but make sure it's safe
+// (the object should be kept alive by the callee).
+// https://streams.spec.whatwg.org/#default-reader-read
+already_AddRefed<Promise> ReadableStreamDefaultReader::Read(ErrorResult& aRv) {
+ // Step 1.
+ if (!mStream) {
+ aRv.ThrowTypeError("Reading is not possible after calling releaseLock.");
+ return nullptr;
+ }
+
+ // Step 2.
+ RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject());
+
+ // Step 3.
+ RefPtr<ReadRequest> request = new Read_ReadRequest(promise);
+
+ // Step 4.
+ AutoEntryScript aes(mGlobal, "ReadableStreamDefaultReader::Read");
+ JSContext* cx = aes.cx();
+
+ ReadableStreamDefaultReaderRead(cx, this, request, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 5.
+ return promise.forget();
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#readable-stream-reader-generic-release
+void ReadableStreamReaderGenericRelease(ReadableStreamGenericReader* aReader,
+ ErrorResult& aRv) {
+ // Step 1. Let stream be reader.[[stream]].
+ RefPtr<ReadableStream> stream = aReader->GetStream();
+
+ // Step 2. Assert: stream is not undefined.
+ MOZ_ASSERT(stream);
+
+ // Step 3. Assert: stream.[[reader]] is reader.
+ MOZ_ASSERT(stream->GetReader() == aReader);
+
+ // Step 4. If stream.[[state]] is "readable", reject reader.[[closedPromise]]
+ // with a TypeError exception.
+ if (stream->State() == ReadableStream::ReaderState::Readable) {
+ aReader->ClosedPromise()->MaybeRejectWithTypeError(
+ "Releasing lock on readable stream");
+ } else {
+ // Step 5. Otherwise, set reader.[[closedPromise]] to a promise rejected
+ // with a TypeError exception.
+ RefPtr<Promise> promise = Promise::CreateRejectedWithTypeError(
+ aReader->GetParentObject(), "Lock Released"_ns, aRv);
+ aReader->SetClosedPromise(promise.forget());
+ }
+
+ // Step 6. Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
+ aReader->ClosedPromise()->SetSettledPromiseIsHandled();
+
+ // Step 7. Perform ! stream.[[controller]].[[ReleaseSteps]]().
+ stream->Controller()->ReleaseSteps();
+
+ // Step 8. Set stream.[[reader]] to undefined.
+ stream->SetReader(nullptr);
+
+ // Step 9. Set reader.[[stream]] to undefined.
+ aReader->SetStream(nullptr);
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaultreadererrorreadrequests
+void ReadableStreamDefaultReaderErrorReadRequests(
+ JSContext* aCx, ReadableStreamDefaultReader* aReader,
+ JS::Handle<JS::Value> aError, ErrorResult& aRv) {
+ // Step 1. Let readRequests be reader.[[readRequests]].
+ LinkedList<RefPtr<ReadRequest>> readRequests =
+ std::move(aReader->ReadRequests());
+
+ // Step 2. Set reader.[[readRequests]] to a new empty list.
+ // Note: The std::move already cleared this anyway.
+ aReader->ReadRequests().clear();
+
+ // Step 3. For each readRequest of readRequests,
+ while (RefPtr<ReadRequest> readRequest = readRequests.popFirst()) {
+ // Step 3.1. Perform readRequest’s error steps, given e.
+ readRequest->ErrorSteps(aCx, aError, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaultreaderrelease
+void ReadableStreamDefaultReaderRelease(JSContext* aCx,
+ ReadableStreamDefaultReader* aReader,
+ ErrorResult& aRv) {
+ // Step 1. Perform ! ReadableStreamReaderGenericRelease(reader).
+ ReadableStreamReaderGenericRelease(aReader, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 2. Let e be a new TypeError exception.
+ ErrorResult rv;
+ rv.ThrowTypeError("Releasing lock");
+ JS::Rooted<JS::Value> error(aCx);
+ MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &error));
+
+ // Step 3. Perform ! ReadableStreamDefaultReaderErrorReadRequests(reader, e).
+ ReadableStreamDefaultReaderErrorReadRequests(aCx, aReader, error, aRv);
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#default-reader-release-lock
+void ReadableStreamDefaultReader::ReleaseLock(ErrorResult& aRv) {
+ // Step 1. If this.[[stream]] is undefined, return.
+ if (!mStream) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mGlobal)) {
+ return aRv.ThrowUnknownError("Internal error");
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Step 2. Perform ! ReadableStreamDefaultReaderRelease(this).
+ RefPtr<ReadableStreamDefaultReader> thisRefPtr = this;
+ ReadableStreamDefaultReaderRelease(cx, thisRefPtr, aRv);
+}
+
+// https://streams.spec.whatwg.org/#generic-reader-closed
+already_AddRefed<Promise> ReadableStreamGenericReader::Closed() const {
+ // Step 1.
+ return do_AddRef(mClosedPromise);
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-reader-generic-cancel
+MOZ_CAN_RUN_SCRIPT
+static already_AddRefed<Promise> ReadableStreamGenericReaderCancel(
+ JSContext* aCx, ReadableStreamGenericReader* aReader,
+ JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
+ // Step 1 (Strong ref for below call).
+ RefPtr<ReadableStream> stream = aReader->GetStream();
+
+ // Step 2.
+ MOZ_ASSERT(stream);
+
+ // Step 3.
+ return ReadableStreamCancel(aCx, stream, aReason, aRv);
+}
+
+already_AddRefed<Promise> ReadableStreamGenericReader::Cancel(
+ JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
+ // Step 1. If this.[[stream]] is undefined,
+ // return a promise rejected with a TypeError exception.
+ if (!mStream) {
+ aRv.ThrowTypeError("Canceling is not possible after calling releaseLock.");
+ return nullptr;
+ }
+
+ // Step 2. Return ! ReadableStreamReaderGenericCancel(this, reason).
+ return ReadableStreamGenericReaderCancel(aCx, this, aReason, aRv);
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#set-up-readable-stream-default-reader
+void SetUpReadableStreamDefaultReader(ReadableStreamDefaultReader* aReader,
+ ReadableStream* aStream,
+ ErrorResult& aRv) {
+ // Step 1.
+ if (IsReadableStreamLocked(aStream)) {
+ return aRv.ThrowTypeError(
+ "Cannot get a new reader for a readable stream already locked by "
+ "another reader.");
+ }
+
+ // Step 2.
+ if (!ReadableStreamReaderGenericInitialize(aReader, aStream)) {
+ return;
+ }
+
+ // Step 3.
+ aReader->ReadRequests().clear();
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-a-chunk
+// To read a chunk from a ReadableStreamDefaultReader reader, given a read
+// request readRequest, perform ! ReadableStreamDefaultReaderRead(reader,
+// readRequest).
+void ReadableStreamDefaultReader::ReadChunk(JSContext* aCx,
+ ReadRequest& aRequest,
+ ErrorResult& aRv) {
+ ReadableStreamDefaultReaderRead(aCx, this, &aRequest, aRv);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/ReadableStreamDefaultReader.h b/dom/streams/ReadableStreamDefaultReader.h
new file mode 100644
index 0000000000..d037b98cb7
--- /dev/null
+++ b/dom/streams/ReadableStreamDefaultReader.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadableStreamDefaultReader_h
+#define mozilla_dom_ReadableStreamDefaultReader_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ReadableStreamGenericReader.h"
+#include "mozilla/dom/ReadRequest.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "mozilla/LinkedList.h"
+
+namespace mozilla::dom {
+
+class Promise;
+class ReadableStream;
+
+// https://streams.spec.whatwg.org/#default-reader-read
+struct Read_ReadRequest : public ReadRequest {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Read_ReadRequest, ReadRequest)
+
+ RefPtr<Promise> mPromise;
+
+ explicit Read_ReadRequest(Promise* aPromise) : mPromise(aPromise) {}
+
+ void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) override;
+
+ void CloseSteps(JSContext* aCx, ErrorResult& aRv) override;
+
+ void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> e,
+ ErrorResult& aRv) override;
+
+ protected:
+ ~Read_ReadRequest() override = default;
+};
+
+class ReadableStreamDefaultReader final : public ReadableStreamGenericReader,
+ public nsWrapperCache
+
+{
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_INHERITED(
+ ReadableStreamDefaultReader, ReadableStreamGenericReader)
+
+ public:
+ explicit ReadableStreamDefaultReader(nsISupports* aGlobal);
+
+ protected:
+ ~ReadableStreamDefaultReader() override;
+
+ public:
+ bool IsDefault() override { return true; }
+ bool IsBYOB() override { return false; }
+ ReadableStreamDefaultReader* AsDefault() override { return this; }
+ ReadableStreamBYOBReader* AsBYOB() override {
+ MOZ_CRASH();
+ return nullptr;
+ }
+
+ // Public functions to implement other specs
+ // https://streams.spec.whatwg.org/#other-specs-rs-create
+
+ // The following algorithms can be used on arbitrary ReadableStream instances,
+ // including ones that are created by web developers. They can all fail in
+ // various operation-specific ways, and these failures should be handled by
+ // the calling specification.
+
+ // https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-a-chunk
+ MOZ_CAN_RUN_SCRIPT void ReadChunk(JSContext* aCx, ReadRequest& aRequest,
+ ErrorResult& aRv);
+
+ // IDL layer functions
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // IDL methods
+
+ static already_AddRefed<ReadableStreamDefaultReader> Constructor(
+ const GlobalObject& aGlobal, ReadableStream& stream, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Read(ErrorResult& aRv);
+
+ void ReleaseLock(ErrorResult& aRv);
+
+ LinkedList<RefPtr<ReadRequest>>& ReadRequests() { return mReadRequests; }
+
+ private:
+ LinkedList<RefPtr<ReadRequest>> mReadRequests = {};
+};
+
+namespace streams_abstract {
+
+void SetUpReadableStreamDefaultReader(ReadableStreamDefaultReader* aReader,
+ ReadableStream* aStream,
+ ErrorResult& aRv);
+
+void ReadableStreamDefaultReaderErrorReadRequests(
+ JSContext* aCx, ReadableStreamDefaultReader* aReader,
+ JS::Handle<JS::Value> aError, ErrorResult& aRv);
+
+void ReadableStreamDefaultReaderRelease(JSContext* aCx,
+ ReadableStreamDefaultReader* aReader,
+ ErrorResult& aRv);
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_ReadableStreamDefaultReader_h
diff --git a/dom/streams/ReadableStreamGenericReader.h b/dom/streams/ReadableStreamGenericReader.h
new file mode 100644
index 0000000000..ccc966bdc4
--- /dev/null
+++ b/dom/streams/ReadableStreamGenericReader.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadableStreamGenericReader_h
+#define mozilla_dom_ReadableStreamGenericReader_h
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ReadableStreamDefaultReaderBinding.h"
+#include "nsISupports.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla::dom {
+
+class ReadableStream;
+class ReadableStreamDefaultReader;
+class ReadableStreamBYOBReader;
+
+// Base class for internal slots of readable stream readers
+class ReadableStreamGenericReader : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ReadableStreamGenericReader)
+
+ explicit ReadableStreamGenericReader(nsCOMPtr<nsIGlobalObject> aGlobal)
+ : mGlobal(std::move(aGlobal)) {}
+
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+
+ virtual bool IsDefault() = 0;
+ virtual bool IsBYOB() = 0;
+ virtual ReadableStreamDefaultReader* AsDefault() = 0;
+ virtual ReadableStreamBYOBReader* AsBYOB() = 0;
+
+ Promise* ClosedPromise() { return mClosedPromise; }
+ void SetClosedPromise(already_AddRefed<Promise>&& aClosedPromise) {
+ mClosedPromise = aClosedPromise;
+ }
+
+ ReadableStream* GetStream() { return mStream; }
+ void SetStream(already_AddRefed<ReadableStream>&& aStream) {
+ mStream = aStream;
+ }
+ void SetStream(ReadableStream* aStream) {
+ RefPtr<ReadableStream> stream(aStream);
+ SetStream(stream.forget());
+ }
+
+ // IDL Methods
+ already_AddRefed<Promise> Closed() const;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Cancel(
+ JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv);
+
+ protected:
+ virtual ~ReadableStreamGenericReader() = default;
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ RefPtr<Promise> mClosedPromise;
+ RefPtr<ReadableStream> mStream;
+};
+
+namespace streams_abstract {
+
+bool ReadableStreamReaderGenericInitialize(ReadableStreamGenericReader* aReader,
+ ReadableStream* aStream);
+
+void ReadableStreamReaderGenericRelease(ReadableStreamGenericReader* aReader,
+ ErrorResult& aRv);
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/ReadableStreamPipeTo.cpp b/dom/streams/ReadableStreamPipeTo.cpp
new file mode 100644
index 0000000000..ed5db4f086
--- /dev/null
+++ b/dom/streams/ReadableStreamPipeTo.cpp
@@ -0,0 +1,977 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ReadableStreamPipeTo.h"
+
+#include "mozilla/dom/AbortFollower.h"
+#include "mozilla/dom/AbortSignal.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamDefaultReader.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/WritableStreamDefaultWriter.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/ErrorResult.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+
+#include "js/Exception.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+struct PipeToReadRequest;
+class WriteFinishedPromiseHandler;
+class ShutdownActionFinishedPromiseHandler;
+
+// https://streams.spec.whatwg.org/#readable-stream-pipe-to (Steps 14-15.)
+//
+// This class implements everything that is required to read all chunks from
+// the reader (source) and write them to writer (destination), while
+// following the constraints given in the spec using our implementation-defined
+// behavior.
+//
+// The cycle-collected references look roughly like this:
+// clang-format off
+//
+// Closed promise <-- ReadableStreamDefaultReader <--> ReadableStream
+// | ^ |
+// |(PromiseHandler) |(mReader) |(ReadRequest)
+// | | |
+// |-------------> PipeToPump <-------
+// ^ | |
+// |---------------| | |
+// | | |-------(mLastWrite) -------->
+// |(PromiseHandler) | |< ---- (PromiseHandler) ---- Promise
+// | | ^
+// | |(mWriter) |(mWriteRequests)
+// | v |
+// Closed promise <-- WritableStreamDefaultWriter <--------> WritableStream
+//
+// clang-format on
+class PipeToPump final : public AbortFollower {
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(PipeToPump)
+
+ friend struct PipeToReadRequest;
+ friend class WriteFinishedPromiseHandler;
+ friend class ShutdownActionFinishedPromiseHandler;
+
+ PipeToPump(Promise* aPromise, ReadableStreamDefaultReader* aReader,
+ WritableStreamDefaultWriter* aWriter, bool aPreventClose,
+ bool aPreventAbort, bool aPreventCancel)
+ : mPromise(aPromise),
+ mReader(aReader),
+ mWriter(aWriter),
+ mPreventClose(aPreventClose),
+ mPreventAbort(aPreventAbort),
+ mPreventCancel(aPreventCancel) {}
+
+ MOZ_CAN_RUN_SCRIPT void Start(JSContext* aCx, AbortSignal* aSignal);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void RunAbortAlgorithm() override;
+
+ private:
+ ~PipeToPump() override = default;
+
+ MOZ_CAN_RUN_SCRIPT void PerformAbortAlgorithm(JSContext* aCx,
+ AbortSignalImpl* aSignal);
+
+ MOZ_CAN_RUN_SCRIPT bool SourceOrDestErroredOrClosed(JSContext* aCx);
+
+ using ShutdownAction = already_AddRefed<Promise> (*)(
+ JSContext*, PipeToPump*, JS::Handle<mozilla::Maybe<JS::Value>>,
+ ErrorResult&);
+
+ MOZ_CAN_RUN_SCRIPT void ShutdownWithAction(
+ JSContext* aCx, ShutdownAction aAction,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError);
+ MOZ_CAN_RUN_SCRIPT void ShutdownWithActionAfterFinishedWrite(
+ JSContext* aCx, ShutdownAction aAction,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError);
+
+ MOZ_CAN_RUN_SCRIPT void Shutdown(
+ JSContext* aCx, JS::Handle<mozilla::Maybe<JS::Value>> aError);
+
+ void Finalize(JSContext* aCx, JS::Handle<mozilla::Maybe<JS::Value>> aError);
+
+ MOZ_CAN_RUN_SCRIPT void OnReadFulfilled(JSContext* aCx,
+ JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void OnWriterReady(JSContext* aCx, JS::Handle<JS::Value>);
+ MOZ_CAN_RUN_SCRIPT void Read(JSContext* aCx);
+
+ MOZ_CAN_RUN_SCRIPT void OnSourceClosed(JSContext* aCx, JS::Handle<JS::Value>);
+ MOZ_CAN_RUN_SCRIPT void OnSourceErrored(
+ JSContext* aCx, JS::Handle<JS::Value> aSourceStoredError);
+
+ MOZ_CAN_RUN_SCRIPT void OnDestClosed(JSContext* aCx, JS::Handle<JS::Value>);
+ MOZ_CAN_RUN_SCRIPT void OnDestErrored(JSContext* aCx,
+ JS::Handle<JS::Value> aDestStoredError);
+
+ RefPtr<Promise> mPromise;
+ RefPtr<ReadableStreamDefaultReader> mReader;
+ RefPtr<WritableStreamDefaultWriter> mWriter;
+ RefPtr<Promise> mLastWritePromise;
+ const bool mPreventClose;
+ const bool mPreventAbort;
+ const bool mPreventCancel;
+ bool mShuttingDown = false;
+#ifdef DEBUG
+ bool mReadChunk = false;
+#endif
+};
+
+// This is a helper class for PipeToPump that allows it to attach
+// member functions as promise handlers.
+class PipeToPumpHandler final : public PromiseNativeHandler {
+ virtual ~PipeToPumpHandler() = default;
+
+ using FunPtr = void (PipeToPump::*)(JSContext*, JS::Handle<JS::Value>);
+
+ RefPtr<PipeToPump> mPipeToPump;
+ FunPtr mResolved;
+ FunPtr mRejected;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(PipeToPumpHandler)
+
+ explicit PipeToPumpHandler(PipeToPump* aPipeToPump, FunPtr aResolved,
+ FunPtr aRejected)
+ : mPipeToPump(aPipeToPump), mResolved(aResolved), mRejected(aRejected) {}
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult&) override {
+ if (mResolved) {
+ (mPipeToPump->*mResolved)(aCx, aValue);
+ }
+ }
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aReason,
+ ErrorResult&) override {
+ if (mRejected) {
+ (mPipeToPump->*mRejected)(aCx, aReason);
+ }
+ }
+};
+
+NS_IMPL_CYCLE_COLLECTION(PipeToPumpHandler, mPipeToPump)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PipeToPumpHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PipeToPumpHandler)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PipeToPumpHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+void PipeToPump::RunAbortAlgorithm() {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mReader->GetStream()->GetParentObject())) {
+ NS_WARNING(
+ "Failed to initialize AutoJSAPI in PipeToPump::RunAbortAlgorithm");
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ RefPtr<AbortSignalImpl> signal = Signal();
+ PerformAbortAlgorithm(cx, signal);
+}
+
+void PipeToPump::PerformAbortAlgorithm(JSContext* aCx,
+ AbortSignalImpl* aSignal) {
+ MOZ_ASSERT(aSignal->Aborted());
+
+ // https://streams.spec.whatwg.org/#readable-stream-pipe-to
+ // Step 14.1. Let abortAlgorithm be the following steps:
+ // Note: All the following steps are 14.1.xx
+
+ // Step 1. Let error be signal’s abort reason.
+ JS::Rooted<JS::Value> error(aCx);
+ aSignal->GetReason(aCx, &error);
+
+ auto action = [](JSContext* aCx, PipeToPump* aPipeToPump,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError,
+ ErrorResult& aRv) MOZ_CAN_RUN_SCRIPT {
+ JS::Rooted<JS::Value> error(aCx, *aError);
+
+ // Step 2. Let actions be an empty ordered set.
+ nsTArray<RefPtr<Promise>> actions;
+
+ // Step 3. If preventAbort is false, append the following action to actions:
+ if (!aPipeToPump->mPreventAbort) {
+ RefPtr<WritableStream> dest = aPipeToPump->mWriter->GetStream();
+
+ // Step 3.1. If dest.[[state]] is "writable", return !
+ // WritableStreamAbort(dest, error).
+ if (dest->State() == WritableStream::WriterState::Writable) {
+ RefPtr<Promise> p = WritableStreamAbort(aCx, dest, error, aRv);
+ if (aRv.Failed()) {
+ return already_AddRefed<Promise>();
+ }
+ actions.AppendElement(p);
+ }
+
+ // Step 3.2. Otherwise, return a promise resolved with undefined.
+ // Note: This is basically a no-op.
+ }
+
+ // Step 4. If preventCancel is false, append the following action action to
+ // actions:
+ if (!aPipeToPump->mPreventCancel) {
+ RefPtr<ReadableStream> source = aPipeToPump->mReader->GetStream();
+
+ // Step 4.1. If source.[[state]] is "readable", return !
+ // ReadableStreamCancel(source, error).
+ if (source->State() == ReadableStream::ReaderState::Readable) {
+ RefPtr<Promise> p = ReadableStreamCancel(aCx, source, error, aRv);
+ if (aRv.Failed()) {
+ return already_AddRefed<Promise>();
+ }
+ actions.AppendElement(p);
+ }
+
+ // Step 4.2. Otherwise, return a promise resolved with undefined.
+ // No-op again.
+ }
+
+ // Step 5. .. action consisting of getting a promise to wait for
+ // all of the actions in actions ...
+ return Promise::All(aCx, actions, aRv);
+ };
+
+ // Step 5. Shutdown with an action consisting of getting a promise to wait for
+ // all of the actions in actions, and with error.
+ JS::Rooted<Maybe<JS::Value>> someError(aCx, Some(error.get()));
+ ShutdownWithAction(aCx, action, someError);
+}
+
+bool PipeToPump::SourceOrDestErroredOrClosed(JSContext* aCx) {
+ // (Constraint) Error and close states must be propagated:
+ // the following conditions must be applied in order.
+ RefPtr<ReadableStream> source = mReader->GetStream();
+ RefPtr<WritableStream> dest = mWriter->GetStream();
+
+ // Step 1. Errors must be propagated forward: if source.[[state]] is or
+ // becomes "errored", then
+ if (source->State() == ReadableStream::ReaderState::Errored) {
+ JS::Rooted<JS::Value> storedError(aCx, source->StoredError());
+ OnSourceErrored(aCx, storedError);
+ return true;
+ }
+
+ // Step 2. Errors must be propagated backward: if dest.[[state]] is or becomes
+ // "errored", then
+ if (dest->State() == WritableStream::WriterState::Errored) {
+ JS::Rooted<JS::Value> storedError(aCx, dest->StoredError());
+ OnDestErrored(aCx, storedError);
+ return true;
+ }
+
+ // Step 3. Closing must be propagated forward: if source.[[state]] is or
+ // becomes "closed", then
+ if (source->State() == ReadableStream::ReaderState::Closed) {
+ OnSourceClosed(aCx, JS::UndefinedHandleValue);
+ return true;
+ }
+
+ // Step 4. Closing must be propagated backward:
+ // if ! WritableStreamCloseQueuedOrInFlight(dest) is true
+ // or dest.[[state]] is "closed", then
+ if (dest->CloseQueuedOrInFlight() ||
+ dest->State() == WritableStream::WriterState::Closed) {
+ OnDestClosed(aCx, JS::UndefinedHandleValue);
+ return true;
+ }
+
+ return false;
+}
+
+// https://streams.spec.whatwg.org/#readable-stream-pipe-to
+// Steps 14-15.
+void PipeToPump::Start(JSContext* aCx, AbortSignal* aSignal) {
+ // Step 14. If signal is not undefined,
+ if (aSignal) {
+ // Step 14.1. Let abortAlgorithm be the following steps:
+ // ... This is implemented by RunAbortAlgorithm.
+
+ // Step 14.2. If signal is aborted, perform abortAlgorithm and
+ // return promise.
+ if (aSignal->Aborted()) {
+ PerformAbortAlgorithm(aCx, aSignal);
+ return;
+ }
+
+ // Step 14.3. Add abortAlgorithm to signal.
+ Follow(aSignal);
+ }
+
+ // Step 15. In parallel but not really; see #905, using reader and writer,
+ // read all chunks from source and write them to dest.
+ // Due to the locking provided by the reader and writer,
+ // the exact manner in which this happens is not observable to author code,
+ // and so there is flexibility in how this is done.
+
+ // (Constraint) Error and close states must be propagated
+
+ // Before piping has started, we have to check for source/destination being
+ // errored/closed manually.
+ if (SourceOrDestErroredOrClosed(aCx)) {
+ return;
+ }
+
+ // We use the following two promises to propagate error and close states
+ // during piping.
+ RefPtr<Promise> readerClosed = mReader->ClosedPromise();
+ readerClosed->AppendNativeHandler(new PipeToPumpHandler(
+ this, &PipeToPump::OnSourceClosed, &PipeToPump::OnSourceErrored));
+
+ // Note: Because we control the destination/writer it should never be closed
+ // after we did the initial check above with SourceOrDestErroredOrClosed.
+ RefPtr<Promise> writerClosed = mWriter->ClosedPromise();
+ writerClosed->AppendNativeHandler(new PipeToPumpHandler(
+ this, &PipeToPump::OnDestClosed, &PipeToPump::OnDestErrored));
+
+ Read(aCx);
+}
+
+class WriteFinishedPromiseHandler final : public PromiseNativeHandler {
+ RefPtr<PipeToPump> mPipeToPump;
+ PipeToPump::ShutdownAction mAction;
+ bool mHasError;
+ JS::Heap<JS::Value> mError;
+
+ virtual ~WriteFinishedPromiseHandler() { mozilla::DropJSObjects(this); };
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WriteFinishedPromiseHandler)
+
+ explicit WriteFinishedPromiseHandler(
+ JSContext* aCx, PipeToPump* aPipeToPump,
+ PipeToPump::ShutdownAction aAction,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError)
+ : mPipeToPump(aPipeToPump), mAction(aAction) {
+ mHasError = aError.isSome();
+ if (mHasError) {
+ mError = *aError;
+ }
+ mozilla::HoldJSObjects(this);
+ }
+
+ MOZ_CAN_RUN_SCRIPT void WriteFinished(JSContext* aCx) {
+ RefPtr<PipeToPump> pipeToPump = mPipeToPump; // XXX known-live?
+ JS::Rooted<Maybe<JS::Value>> error(aCx);
+ if (mHasError) {
+ error = Some(mError);
+ }
+ pipeToPump->ShutdownWithActionAfterFinishedWrite(aCx, mAction, error);
+ }
+
+ MOZ_CAN_RUN_SCRIPT void ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult&) override {
+ WriteFinished(aCx);
+ }
+
+ MOZ_CAN_RUN_SCRIPT void RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aReason,
+ ErrorResult&) override {
+ WriteFinished(aCx);
+ }
+};
+
+NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(WriteFinishedPromiseHandler,
+ (mPipeToPump), (mError))
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WriteFinishedPromiseHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WriteFinishedPromiseHandler)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WriteFinishedPromiseHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// https://streams.spec.whatwg.org/#rs-pipeTo-shutdown-with-action
+// Shutdown with an action: if any of the above requirements ask to shutdown
+// with an action action, optionally with an error originalError, then:
+void PipeToPump::ShutdownWithAction(
+ JSContext* aCx, ShutdownAction aAction,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError) {
+ // Step 1. If shuttingDown is true, abort these substeps.
+ if (mShuttingDown) {
+ return;
+ }
+
+ // Step 2. Set shuttingDown to true.
+ mShuttingDown = true;
+
+ // Step 3. If dest.[[state]] is "writable" and !
+ // WritableStreamCloseQueuedOrInFlight(dest) is false,
+ RefPtr<WritableStream> dest = mWriter->GetStream();
+ if (dest->State() == WritableStream::WriterState::Writable &&
+ !dest->CloseQueuedOrInFlight()) {
+ // Step 3.1. If any chunks have been read but not yet written, write them to
+ // dest.
+ // Step 3.2. Wait until every chunk that has been read has been
+ // written (i.e. the corresponding promises have settled).
+ //
+ // Note: Write requests are processed in order, so when the promise
+ // for the last written chunk is settled all previous chunks have been
+ // written as well.
+ if (mLastWritePromise) {
+ mLastWritePromise->AppendNativeHandler(
+ new WriteFinishedPromiseHandler(aCx, this, aAction, aError));
+ return;
+ }
+ }
+
+ // Don't have to wait for last write, immediately continue.
+ ShutdownWithActionAfterFinishedWrite(aCx, aAction, aError);
+}
+
+class ShutdownActionFinishedPromiseHandler final : public PromiseNativeHandler {
+ RefPtr<PipeToPump> mPipeToPump;
+ bool mHasError;
+ JS::Heap<JS::Value> mError;
+
+ virtual ~ShutdownActionFinishedPromiseHandler() {
+ mozilla::DropJSObjects(this);
+ }
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(
+ ShutdownActionFinishedPromiseHandler)
+
+ explicit ShutdownActionFinishedPromiseHandler(
+ JSContext* aCx, PipeToPump* aPipeToPump,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError)
+ : mPipeToPump(aPipeToPump) {
+ mHasError = aError.isSome();
+ if (mHasError) {
+ mError = *aError;
+ }
+ mozilla::HoldJSObjects(this);
+ }
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult&) override {
+ // https://streams.spec.whatwg.org/#rs-pipeTo-shutdown-with-action
+ // Step 5. Upon fulfillment of p, finalize, passing along originalError if
+ // it was given.
+ JS::Rooted<Maybe<JS::Value>> error(aCx);
+ if (mHasError) {
+ error = Some(mError);
+ }
+ mPipeToPump->Finalize(aCx, error);
+ }
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aReason,
+ ErrorResult&) override {
+ // https://streams.spec.whatwg.org/#rs-pipeTo-shutdown-with-action
+ // Step 6. Upon rejection of p with reason newError, finalize with
+ // newError.
+ JS::Rooted<Maybe<JS::Value>> error(aCx, Some(aReason));
+ mPipeToPump->Finalize(aCx, error);
+ }
+};
+
+NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(ShutdownActionFinishedPromiseHandler,
+ (mPipeToPump), (mError))
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ShutdownActionFinishedPromiseHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ShutdownActionFinishedPromiseHandler)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ShutdownActionFinishedPromiseHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// https://streams.spec.whatwg.org/#rs-pipeTo-shutdown-with-action
+// Continuation after Step 3. triggered a promise resolution.
+void PipeToPump::ShutdownWithActionAfterFinishedWrite(
+ JSContext* aCx, ShutdownAction aAction,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError) {
+ if (!aAction) {
+ // Used to implement shutdown without action. Finalize immediately.
+ Finalize(aCx, aError);
+ return;
+ }
+
+ // Step 4. Let p be the result of performing action.
+ RefPtr<PipeToPump> thisRefPtr = this;
+ ErrorResult rv;
+ RefPtr<Promise> p = aAction(aCx, thisRefPtr, aError, rv);
+
+ // Error while calling actions above, continue immediately with finalization.
+ if (rv.MaybeSetPendingException(aCx)) {
+ JS::Rooted<Maybe<JS::Value>> someError(aCx);
+
+ JS::Rooted<JS::Value> error(aCx);
+ if (JS_GetPendingException(aCx, &error)) {
+ someError = Some(error.get());
+ }
+
+ JS_ClearPendingException(aCx);
+
+ Finalize(aCx, someError);
+ return;
+ }
+
+ // Steps 5-6.
+ p->AppendNativeHandler(
+ new ShutdownActionFinishedPromiseHandler(aCx, this, aError));
+}
+
+// https://streams.spec.whatwg.org/#rs-pipeTo-shutdown
+// Shutdown: if any of the above requirements or steps ask to shutdown,
+// optionally with an error error, then:
+void PipeToPump::Shutdown(JSContext* aCx,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError) {
+ // Note: We implement "shutdown" in terms of "shutdown with action".
+ // We can observe that when passing along an action that always succeeds
+ // shutdown with action and shutdown have the same behavior, when
+ // Ignoring the potential micro task for the promise that we skip anyway.
+ ShutdownWithAction(aCx, nullptr, aError);
+}
+
+// https://streams.spec.whatwg.org/#rs-pipeTo-finalize
+// Finalize: both forms of shutdown will eventually ask to finalize,
+// optionally with an error error, which means to perform the following steps:
+void PipeToPump::Finalize(JSContext* aCx,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError) {
+ IgnoredErrorResult rv;
+ // Step 1. Perform ! WritableStreamDefaultWriterRelease(writer).
+ WritableStreamDefaultWriterRelease(aCx, mWriter);
+
+ // Step 2. If reader implements ReadableStreamBYOBReader,
+ // perform ! ReadableStreamBYOBReaderRelease(reader).
+ // Note: We always use a default reader.
+ MOZ_ASSERT(!mReader->IsBYOB());
+
+ // Step 3. Otherwise, perform ! ReadableStreamDefaultReaderRelease(reader).
+ ReadableStreamDefaultReaderRelease(aCx, mReader, rv);
+ NS_WARNING_ASSERTION(!rv.Failed(),
+ "ReadableStreamReaderGenericRelease should not fail.");
+
+ // Step 3. If signal is not undefined, remove abortAlgorithm from signal.
+ if (IsFollowing()) {
+ Unfollow();
+ }
+
+ // Step 4. If error was given, reject promise with error.
+ if (aError.isSome()) {
+ JS::Rooted<JS::Value> error(aCx, *aError);
+ mPromise->MaybeReject(error);
+ } else {
+ // Step 5. Otherwise, resolve promise with undefined.
+ mPromise->MaybeResolveWithUndefined();
+ }
+
+ // Remove all references.
+ mPromise = nullptr;
+ mReader = nullptr;
+ mWriter = nullptr;
+ mLastWritePromise = nullptr;
+ Unfollow();
+}
+
+void PipeToPump::OnReadFulfilled(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) {
+ // (Constraint) 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).
+ //
+ // TODO: Implement the eventual resolution from
+ // https://github.com/whatwg/streams/issues/1207
+ if (mShuttingDown) {
+ return;
+ }
+
+ // Write asynchronously. Roughly this is like:
+ // `Promise.resolve().then(() => stream.write(chunk));`
+ // XXX: The spec currently does not require asynchronicity, but this still
+ // matches other engines' behavior. See
+ // https://github.com/whatwg/streams/issues/1243.
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(xpc::CurrentNativeGlobal(aCx));
+ promise->MaybeResolveWithUndefined();
+ auto result = promise->ThenWithCycleCollectedArgsJS(
+ [](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv,
+ const RefPtr<PipeToPump>& aSelf,
+ const RefPtr<WritableStreamDefaultWriter>& aWriter,
+ JS::Handle<JS::Value> aChunk)
+ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> already_AddRefed<Promise> {
+ RefPtr<Promise> promise =
+ WritableStreamDefaultWriterWrite(aCx, aWriter, aChunk, aRv);
+
+ // Last read has finished, so it's time to start reading again.
+ aSelf->Read(aCx);
+
+ return promise.forget();
+ },
+ std::make_tuple(RefPtr{this}, mWriter), std::make_tuple(aChunk));
+ if (result.isErr()) {
+ mLastWritePromise = nullptr;
+ return;
+ }
+ mLastWritePromise = result.unwrap();
+
+ mLastWritePromise->AppendNativeHandler(
+ new PipeToPumpHandler(this, nullptr, &PipeToPump::OnDestErrored));
+}
+
+void PipeToPump::OnWriterReady(JSContext* aCx, JS::Handle<JS::Value>) {
+ // Writer is ready again (i.e. backpressure was resolved), so read.
+ Read(aCx);
+}
+
+struct PipeToReadRequest : public ReadRequest {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PipeToReadRequest, ReadRequest)
+
+ RefPtr<PipeToPump> mPipeToPump;
+
+ explicit PipeToReadRequest(PipeToPump* aPipeToPump)
+ : mPipeToPump(aPipeToPump) {}
+
+ MOZ_CAN_RUN_SCRIPT void ChunkSteps(JSContext* aCx,
+ JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) override {
+ RefPtr<PipeToPump> pipeToPump = mPipeToPump; // XXX known live?
+ pipeToPump->OnReadFulfilled(aCx, aChunk, aRv);
+ }
+
+ // The reader's closed promise handlers will already call OnSourceClosed/
+ // OnSourceErrored, so these steps can just be ignored.
+ void CloseSteps(JSContext* aCx, ErrorResult& aRv) override {}
+ void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) override {}
+
+ protected:
+ virtual ~PipeToReadRequest() = default;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PipeToReadRequest, ReadRequest, mPipeToPump)
+
+NS_IMPL_ADDREF_INHERITED(PipeToReadRequest, ReadRequest)
+NS_IMPL_RELEASE_INHERITED(PipeToReadRequest, ReadRequest)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PipeToReadRequest)
+NS_INTERFACE_MAP_END_INHERITING(ReadRequest)
+
+void PipeToPump::Read(JSContext* aCx) {
+#ifdef DEBUG
+ mReadChunk = true;
+#endif
+
+ // (Constraint) Shutdown must stop activity:
+ // If shuttingDown becomes true, the user agent must not initiate
+ // further reads from reader
+ if (mShuttingDown) {
+ return;
+ }
+
+ // (Constraint) Backpressure must be enforced:
+ // While WritableStreamDefaultWriterGetDesiredSize(writer) is ≤ 0 or is null,
+ // the user agent must not read from reader.
+ Nullable<double> desiredSize =
+ WritableStreamDefaultWriterGetDesiredSize(mWriter);
+ if (desiredSize.IsNull()) {
+ // This means the writer has errored. This is going to be handled
+ // by the writer closed promise.
+ return;
+ }
+
+ if (desiredSize.Value() <= 0) {
+ // Wait for the writer to become ready before reading more data from
+ // the reader. We don't care about rejections here, because those are
+ // already handled by the writer closed promise.
+ RefPtr<Promise> readyPromise = mWriter->Ready();
+ readyPromise->AppendNativeHandler(
+ new PipeToPumpHandler(this, &PipeToPump::OnWriterReady, nullptr));
+ return;
+ }
+
+ RefPtr<ReadableStreamDefaultReader> reader = mReader;
+ RefPtr<ReadRequest> request = new PipeToReadRequest(this);
+ ErrorResult rv;
+ ReadableStreamDefaultReaderRead(aCx, reader, request, rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ // XXX It's actually not quite obvious what we should do here.
+ // We've got an error during reading, so on the surface it seems logical
+ // to invoke `OnSourceErrored`. However in certain cases the required
+ // condition > source.[[state]] is or becomes "errored" < won't actually
+ // happen i.e. when `WritableStreamDefaultWriterWrite` called from
+ // `OnReadFulfilled` (via PipeToReadRequest::ChunkSteps) fails in
+ // a synchronous fashion.
+ JS::Rooted<JS::Value> error(aCx);
+ JS::Rooted<Maybe<JS::Value>> someError(aCx);
+
+ // The error was moved to the JSContext by MaybeSetPendingException.
+ if (JS_GetPendingException(aCx, &error)) {
+ someError = Some(error.get());
+ }
+
+ JS_ClearPendingException(aCx);
+
+ Shutdown(aCx, someError);
+ }
+}
+
+// Step 3. Closing must be propagated forward: if source.[[state]] is or
+// becomes "closed", then
+void PipeToPump::OnSourceClosed(JSContext* aCx, JS::Handle<JS::Value>) {
+ // Step 3.1. If preventClose is false, shutdown with an action of
+ // ! WritableStreamDefaultWriterCloseWithErrorPropagation(writer).
+ if (!mPreventClose) {
+ ShutdownWithAction(
+ aCx,
+ [](JSContext* aCx, PipeToPump* aPipeToPump,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError, ErrorResult& aRv)
+ MOZ_CAN_RUN_SCRIPT {
+ RefPtr<WritableStreamDefaultWriter> writer = aPipeToPump->mWriter;
+ return WritableStreamDefaultWriterCloseWithErrorPropagation(
+ aCx, writer, aRv);
+ },
+ JS::NothingHandleValue);
+ } else {
+ // Step 3.2 Otherwise, shutdown.
+ Shutdown(aCx, JS::NothingHandleValue);
+ }
+}
+
+// Step 1. Errors must be propagated forward: if source.[[state]] is or
+// becomes "errored", then
+void PipeToPump::OnSourceErrored(JSContext* aCx,
+ JS::Handle<JS::Value> aSourceStoredError) {
+ // 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.
+ //
+ // TODO: Implement the eventual resolution from
+ // https://github.com/whatwg/streams/issues/1207
+
+ // Step 1.1 If preventAbort is false, shutdown with an action of
+ // ! WritableStreamAbort(dest, source.[[storedError]])
+ // and with source.[[storedError]].
+ JS::Rooted<Maybe<JS::Value>> error(aCx, Some(aSourceStoredError));
+ if (!mPreventAbort) {
+ ShutdownWithAction(
+ aCx,
+ [](JSContext* aCx, PipeToPump* aPipeToPump,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError, ErrorResult& aRv)
+ MOZ_CAN_RUN_SCRIPT {
+ JS::Rooted<JS::Value> error(aCx, *aError);
+ RefPtr<WritableStream> dest = aPipeToPump->mWriter->GetStream();
+ return WritableStreamAbort(aCx, dest, error, aRv);
+ },
+ error);
+ } else {
+ // Step 1.1. Otherwise, shutdown with source.[[storedError]].
+ Shutdown(aCx, error);
+ }
+}
+
+// Step 4. Closing must be propagated backward:
+// if ! WritableStreamCloseQueuedOrInFlight(dest) is true
+// or dest.[[state]] is "closed", then
+void PipeToPump::OnDestClosed(JSContext* aCx, JS::Handle<JS::Value>) {
+ // Step 4.1. Assert: no chunks have been read or written.
+ // Note: No reading automatically implies no writing.
+ // In a perfect world OnDestClosed would only be called before we start
+ // piping, because afterwards the writer has an exclusive lock on the stream.
+ // In reality the closed promise can still be resolved after we release
+ // the lock on the writer in Finalize.
+ if (mShuttingDown) {
+ return;
+ }
+ MOZ_ASSERT(!mReadChunk);
+
+ // Step 4.2. Let destClosed be a new TypeError.
+ JS::Rooted<Maybe<JS::Value>> destClosed(aCx, Nothing());
+ {
+ ErrorResult rv;
+ rv.ThrowTypeError("Cannot pipe to closed stream");
+ JS::Rooted<JS::Value> error(aCx);
+ bool ok = ToJSValue(aCx, std::move(rv), &error);
+ MOZ_RELEASE_ASSERT(ok, "must be ok");
+ destClosed = Some(error.get());
+ }
+
+ // Step 4.3. If preventCancel is false, shutdown with an action of
+ // ! ReadableStreamCancel(source, destClosed) and with destClosed.
+ if (!mPreventCancel) {
+ ShutdownWithAction(
+ aCx,
+ [](JSContext* aCx, PipeToPump* aPipeToPump,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError, ErrorResult& aRv)
+ MOZ_CAN_RUN_SCRIPT {
+ JS::Rooted<JS::Value> error(aCx, *aError);
+ RefPtr<ReadableStream> dest = aPipeToPump->mReader->GetStream();
+ return ReadableStreamCancel(aCx, dest, error, aRv);
+ },
+ destClosed);
+ } else {
+ // Step 4.4. Otherwise, shutdown with destClosed.
+ Shutdown(aCx, destClosed);
+ }
+}
+
+// Step 2. Errors must be propagated backward: if dest.[[state]] is or becomes
+// "errored", then
+void PipeToPump::OnDestErrored(JSContext* aCx,
+ JS::Handle<JS::Value> aDestStoredError) {
+ // Step 2.1. If preventCancel is false, shutdown with an action of
+ // ! ReadableStreamCancel(source, dest.[[storedError]])
+ // and with dest.[[storedError]].
+ JS::Rooted<Maybe<JS::Value>> error(aCx, Some(aDestStoredError));
+ if (!mPreventCancel) {
+ ShutdownWithAction(
+ aCx,
+ [](JSContext* aCx, PipeToPump* aPipeToPump,
+ JS::Handle<mozilla::Maybe<JS::Value>> aError, ErrorResult& aRv)
+ MOZ_CAN_RUN_SCRIPT {
+ JS::Rooted<JS::Value> error(aCx, *aError);
+ RefPtr<ReadableStream> dest = aPipeToPump->mReader->GetStream();
+ return ReadableStreamCancel(aCx, dest, error, aRv);
+ },
+ error);
+ } else {
+ // Step 2.1. Otherwise, shutdown with dest.[[storedError]].
+ Shutdown(aCx, error);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PipeToPump)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PipeToPump)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PipeToPump)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PipeToPump)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PipeToPump)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWriter)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastWritePromise)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PipeToPump)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mReader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWriter)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastWritePromise)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#readable-stream-pipe-to
+already_AddRefed<Promise> ReadableStreamPipeTo(
+ ReadableStream* aSource, WritableStream* aDest, bool aPreventClose,
+ bool aPreventAbort, bool aPreventCancel, AbortSignal* aSignal,
+ mozilla::ErrorResult& aRv) {
+ // Step 1. Assert: source implements ReadableStream. (Implicit)
+ // Step 2. Assert: dest implements WritableStream. (Implicit)
+ // Step 3. Assert: preventClose, preventAbort, and preventCancel are all
+ // booleans (Implicit)
+ // Step 4. If signal was not given, let signal be
+ // undefined. (Implicit)
+ // Step 5. Assert: either signal is undefined, or signal
+ // implements AbortSignal. (Implicit)
+ // Step 6. Assert: !IsReadableStreamLocked(source) is false.
+ MOZ_ASSERT(!IsReadableStreamLocked(aSource));
+
+ // Step 7. Assert: !IsWritableStreamLocked(dest) is false.
+ MOZ_ASSERT(!IsWritableStreamLocked(aDest));
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aSource->GetParentObject())) {
+ aRv.ThrowUnknownError("Internal error");
+ return nullptr;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Step 8. If source.[[controller]] implements ReadableByteStreamController,
+ // let reader be either !AcquireReadableStreamBYOBReader(source) or
+ // !AcquireReadableStreamDefaultReader(source), at the user agent’s
+ // discretion.
+ // Step 9. Otherwise, let reader be
+ // !AcquireReadableStreamDefaultReader(source).
+
+ // Note: In the interests of simplicity, we choose here to always acquire
+ // a default reader.
+ RefPtr<ReadableStreamDefaultReader> reader =
+ AcquireReadableStreamDefaultReader(aSource, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 10. Let writer be ! AcquireWritableStreamDefaultWriter(dest).
+ RefPtr<WritableStreamDefaultWriter> writer =
+ AcquireWritableStreamDefaultWriter(aDest, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 11. Set source.[[disturbed]] to true.
+ aSource->SetDisturbed(true);
+
+ // Step 12. Let shuttingDown be false.
+ // Note: PipeToPump ensures this by construction.
+
+ // Step 13. Let promise be a new promise.
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(aSource->GetParentObject());
+
+ // Steps 14-15.
+ RefPtr<PipeToPump> pump = new PipeToPump(
+ promise, reader, writer, aPreventClose, aPreventAbort, aPreventCancel);
+ pump->Start(cx, aSignal);
+
+ // Step 16. Return promise.
+ return promise.forget();
+}
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
diff --git a/dom/streams/ReadableStreamPipeTo.h b/dom/streams/ReadableStreamPipeTo.h
new file mode 100644
index 0000000000..6c92cb8c32
--- /dev/null
+++ b/dom/streams/ReadableStreamPipeTo.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadableStreamPipeTo_h
+#define mozilla_dom_ReadableStreamPipeTo_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/AlreadyAddRefed.h"
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+class AbortSignal;
+class Promise;
+class ReadableStream;
+class WritableStream;
+
+namespace streams_abstract {
+MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> ReadableStreamPipeTo(
+ ReadableStream* aSource, WritableStream* aDest, bool aPreventClose,
+ bool aPreventAbort, bool aPreventCancel, AbortSignal* aSignal,
+ ErrorResult& aRv);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_ReadableStreamPipeTo_h
diff --git a/dom/streams/ReadableStreamTee.cpp b/dom/streams/ReadableStreamTee.cpp
new file mode 100644
index 0000000000..9bb30d8859
--- /dev/null
+++ b/dom/streams/ReadableStreamTee.cpp
@@ -0,0 +1,1011 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ReadableStreamTee.h"
+
+#include "ReadIntoRequest.h"
+#include "TeeState.h"
+#include "js/Exception.h"
+#include "js/TypeDecls.h"
+#include "js/experimental/TypedData.h"
+#include "mozilla/dom/ByteStreamHelpers.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamBYOBReader.h"
+#include "mozilla/dom/ReadableStreamDefaultController.h"
+#include "mozilla/dom/ReadableStreamGenericReader.h"
+#include "mozilla/dom/ReadableStreamDefaultReader.h"
+#include "mozilla/dom/ReadableByteStreamController.h"
+#include "mozilla/dom/UnderlyingSourceBinding.h"
+#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/CycleCollectedJSContext.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ReadableStreamDefaultTeeSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase, mTeeState)
+NS_IMPL_ADDREF_INHERITED(ReadableStreamDefaultTeeSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(ReadableStreamDefaultTeeSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+ ReadableStreamDefaultTeeSourceAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsBase)
+
+already_AddRefed<Promise>
+ReadableStreamDefaultTeeSourceAlgorithms::PullCallback(
+ JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = aController.GetParentObject();
+ mTeeState->PullCallback(aCx, global, aRv);
+ if (!aRv.Failed()) {
+ return Promise::CreateResolvedWithUndefined(global, aRv);
+ }
+ return nullptr;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(ReadableStreamDefaultTeeReadRequest)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
+ ReadableStreamDefaultTeeReadRequest, ReadRequest)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mTeeState)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
+ ReadableStreamDefaultTeeReadRequest, ReadRequest)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTeeState)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(ReadableStreamDefaultTeeReadRequest, ReadRequest)
+NS_IMPL_RELEASE_INHERITED(ReadableStreamDefaultTeeReadRequest, ReadRequest)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReadableStreamDefaultTeeReadRequest)
+NS_INTERFACE_MAP_END_INHERITING(ReadRequest)
+
+void ReadableStreamDefaultTeeReadRequest::ChunkSteps(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
+ // Step 1.
+ class ReadableStreamDefaultTeeReadRequestChunkSteps
+ : public MicroTaskRunnable {
+ // Virtually const, but is cycle collected
+ MOZ_KNOWN_LIVE RefPtr<TeeState> mTeeState;
+ JS::PersistentRooted<JS::Value> mChunk;
+
+ public:
+ ReadableStreamDefaultTeeReadRequestChunkSteps(JSContext* aCx,
+ TeeState* aTeeState,
+ JS::Handle<JS::Value> aChunk)
+ : mTeeState(aTeeState), mChunk(aCx, aChunk) {}
+
+ MOZ_CAN_RUN_SCRIPT
+ void Run(AutoSlowOperation& aAso) override {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mTeeState->GetStream()->GetParentObject()))) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ // Step Numbering below is relative to Chunk steps Microtask:
+ //
+ // Step 1.
+ mTeeState->SetReadAgain(false);
+
+ // Step 2.
+ JS::Rooted<JS::Value> chunk1(cx, mChunk);
+ JS::Rooted<JS::Value> chunk2(cx, mChunk);
+
+ // Step 3. Skipped until we implement cloneForBranch2 path.
+ MOZ_RELEASE_ASSERT(!mTeeState->CloneForBranch2());
+
+ // Step 4.
+ if (!mTeeState->Canceled1()) {
+ IgnoredErrorResult rv;
+ // Since we controlled the creation of the two stream branches, we know
+ // they both have default controllers.
+ RefPtr<ReadableStreamDefaultController> controller(
+ mTeeState->Branch1()->DefaultController());
+ ReadableStreamDefaultControllerEnqueue(cx, controller, chunk1, rv);
+ (void)NS_WARN_IF(rv.Failed());
+ }
+
+ // Step 5.
+ if (!mTeeState->Canceled2()) {
+ IgnoredErrorResult rv;
+ RefPtr<ReadableStreamDefaultController> controller(
+ mTeeState->Branch2()->DefaultController());
+ ReadableStreamDefaultControllerEnqueue(cx, controller, chunk2, rv);
+ (void)NS_WARN_IF(rv.Failed());
+ }
+
+ // Step 6.
+ mTeeState->SetReading(false);
+
+ // Step 7. If |readAgain| is true, perform |pullAlgorithm|.
+ if (mTeeState->ReadAgain()) {
+ IgnoredErrorResult rv;
+ nsCOMPtr<nsIGlobalObject> global(
+ mTeeState->GetStream()->GetParentObject());
+ mTeeState->PullCallback(cx, global, rv);
+ (void)NS_WARN_IF(rv.Failed());
+ }
+ }
+
+ bool Suppressed() override {
+ nsIGlobalObject* global = mTeeState->GetStream()->GetParentObject();
+ return global && global->IsInSyncOperation();
+ }
+ };
+
+ RefPtr<ReadableStreamDefaultTeeReadRequestChunkSteps> task =
+ MakeRefPtr<ReadableStreamDefaultTeeReadRequestChunkSteps>(aCx, mTeeState,
+ aChunk);
+ CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget());
+}
+
+void ReadableStreamDefaultTeeReadRequest::CloseSteps(JSContext* aCx,
+ ErrorResult& aRv) {
+ // Step Numbering below is relative to 'close steps' of
+ // https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee
+ //
+ // Step 1.
+ mTeeState->SetReading(false);
+
+ // Step 2.
+ if (!mTeeState->Canceled1()) {
+ RefPtr<ReadableStreamDefaultController> controller(
+ mTeeState->Branch1()->DefaultController());
+ ReadableStreamDefaultControllerClose(aCx, controller, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 3.
+ if (!mTeeState->Canceled2()) {
+ RefPtr<ReadableStreamDefaultController> controller(
+ mTeeState->Branch2()->DefaultController());
+ ReadableStreamDefaultControllerClose(aCx, controller, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 4.
+ if (!mTeeState->Canceled1() || !mTeeState->Canceled2()) {
+ mTeeState->CancelPromise()->MaybeResolveWithUndefined();
+ }
+}
+
+void ReadableStreamDefaultTeeReadRequest::ErrorSteps(
+ JSContext* aCx, JS::Handle<JS::Value> aError, ErrorResult& aRv) {
+ mTeeState->SetReading(false);
+}
+
+MOZ_CAN_RUN_SCRIPT void PullWithDefaultReader(JSContext* aCx,
+ TeeState* aTeeState,
+ ErrorResult& aRv);
+MOZ_CAN_RUN_SCRIPT void PullWithBYOBReader(JSContext* aCx, TeeState* aTeeState,
+ JS::Handle<JSObject*> aView,
+ TeeBranch aForBranch,
+ ErrorResult& aRv);
+
+// Algorithm described in
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee, Steps
+// 17 and Steps 18, genericized across branch numbers:
+//
+// Note: As specified this algorithm always returns a promise resolved with
+// undefined, however as some places immediately discard said promise, we
+// provide this version which doesn't return a promise.
+//
+// NativeByteStreamTeePullAlgorithm, which implements
+// UnderlyingSourcePullCallbackHelper is the version which provies the return
+// promise.
+MOZ_CAN_RUN_SCRIPT void ByteStreamTeePullAlgorithm(JSContext* aCx,
+ TeeBranch aForBranch,
+ TeeState* aTeeState,
+ ErrorResult& aRv) {
+ // Step {17,18}.1: If reading is true,
+ if (aTeeState->Reading()) {
+ // Step {17,18}.1.1: Set readAgainForBranch{1,2} to true.
+ aTeeState->SetReadAgainForBranch(aForBranch, true);
+
+ // Step {17,18}.1.1: Return a promise resolved with undefined.
+ return;
+ }
+
+ // Step {17,18}.2: Set reading to true.
+ aTeeState->SetReading(true);
+
+ // Step {17,18}.3: Let byobRequest be
+ // !ReadableByteStreamControllerGetBYOBRequest(branch{1,2}.[[controller]]).
+ RefPtr<ReadableStreamBYOBRequest> byobRequest =
+ ReadableByteStreamControllerGetBYOBRequest(
+ aCx, aTeeState->Branch(aForBranch)->Controller()->AsByte(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step {17,18}.4: If byobRequest is null, perform pullWithDefaultReader.
+ if (!byobRequest) {
+ PullWithDefaultReader(aCx, aTeeState, aRv);
+ } else {
+ // Step {17,18}.5: Otherwise, perform pullWithBYOBReader, given
+ // byobRequest.[[view]] and {false, true}.
+ JS::Rooted<JSObject*> view(aCx, byobRequest->View());
+ PullWithBYOBReader(aCx, aTeeState, view, aForBranch, aRv);
+ }
+
+ // Step {17,18}.6: Return a promise resolved with undefined.
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
+class ByteStreamTeeSourceAlgorithms final
+ : public UnderlyingSourceAlgorithmsBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ByteStreamTeeSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+
+ ByteStreamTeeSourceAlgorithms(TeeState* aTeeState, TeeBranch aBranch)
+ : mTeeState(aTeeState), mBranch(aBranch) {}
+
+ MOZ_CAN_RUN_SCRIPT void StartCallback(JSContext* aCx,
+ ReadableStreamController& aController,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv) override {
+ // Step 21: Let startAlgorithm be an algorithm that returns undefined.
+ aRetVal.setUndefined();
+ }
+
+ // Step 17, 18
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PullCallback(
+ JSContext* aCx, ReadableStreamController& aController,
+ ErrorResult& aRv) override {
+ // Step 1 - 5
+ ByteStreamTeePullAlgorithm(aCx, mBranch, MOZ_KnownLive(mTeeState), aRv);
+
+ // Step 6: Return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(
+ mTeeState->GetStream()->GetParentObject(), aRv);
+ }
+
+ // Step 19, 20
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CancelCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override {
+ // Step 1.
+ mTeeState->SetCanceled(mBranch, true);
+
+ // Step 2.
+ mTeeState->SetReason(mBranch, aReason.Value());
+
+ // Step 3.
+ if (mTeeState->Canceled(otherStream())) {
+ // Step 3.1
+ JS::Rooted<JSObject*> compositeReason(aCx, JS::NewArrayObject(aCx, 2));
+ if (!compositeReason) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ JS::Rooted<JS::Value> reason1(aCx, mTeeState->Reason1());
+ if (!JS_SetElement(aCx, compositeReason, 0, reason1)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ JS::Rooted<JS::Value> reason2(aCx, mTeeState->Reason2());
+ if (!JS_SetElement(aCx, compositeReason, 1, reason2)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+
+ // Step 3.2
+ JS::Rooted<JS::Value> compositeReasonValue(
+ aCx, JS::ObjectValue(*compositeReason));
+ RefPtr<ReadableStream> stream(mTeeState->GetStream());
+ RefPtr<Promise> cancelResult =
+ ReadableStreamCancel(aCx, stream, compositeReasonValue, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 3.3
+ mTeeState->CancelPromise()->MaybeResolve(cancelResult);
+ }
+
+ // Step 4.
+ return do_AddRef(mTeeState->CancelPromise());
+ };
+
+ protected:
+ ~ByteStreamTeeSourceAlgorithms() override = default;
+
+ private:
+ TeeBranch otherStream() { return OtherTeeBranch(mBranch); }
+
+ RefPtr<TeeState> mTeeState;
+ TeeBranch mBranch;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ByteStreamTeeSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase, mTeeState)
+NS_IMPL_ADDREF_INHERITED(ByteStreamTeeSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(ByteStreamTeeSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ByteStreamTeeSourceAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsBase)
+
+struct PullWithDefaultReaderReadRequest final : public ReadRequest {
+ RefPtr<TeeState> mTeeState;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PullWithDefaultReaderReadRequest,
+ ReadRequest)
+
+ explicit PullWithDefaultReaderReadRequest(TeeState* aTeeState)
+ : mTeeState(aTeeState) {}
+
+ void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) override {
+ // https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
+ // Step 15.2.1
+ class PullWithDefaultReaderChunkStepMicrotask : public MicroTaskRunnable {
+ RefPtr<TeeState> mTeeState;
+ JS::PersistentRooted<JSObject*> mChunk;
+
+ public:
+ PullWithDefaultReaderChunkStepMicrotask(JSContext* aCx,
+ TeeState* aTeeState,
+ JS::Handle<JSObject*> aChunk)
+ : mTeeState(aTeeState), mChunk(aCx, aChunk) {}
+
+ MOZ_CAN_RUN_SCRIPT
+ void Run(AutoSlowOperation& aAso) override {
+ // Step Numbering in this function is relative to the Queue a microtask
+ // of the Chunk steps of 15.2.1 of
+ // https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(
+ !jsapi.Init(mTeeState->GetStream()->GetParentObject()))) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Step 1. Set readAgainForBranch1 to false.
+ mTeeState->SetReadAgainForBranch1(false);
+
+ // Step 2. Set readAgainForBranch2 to false.
+ mTeeState->SetReadAgainForBranch2(false);
+
+ // Step 3. Let chunk1 and chunk2 be chunk.
+ JS::Rooted<JSObject*> chunk1(cx, mChunk);
+ JS::Rooted<JSObject*> chunk2(cx, mChunk);
+
+ // Step 4. If canceled1 is false and canceled2 is false,
+ ErrorResult rv;
+ if (!mTeeState->Canceled1() && !mTeeState->Canceled2()) {
+ // Step 4.1. Let cloneResult be CloneAsUint8Array(chunk).
+ JS::Rooted<JSObject*> cloneResult(cx, CloneAsUint8Array(cx, mChunk));
+
+ // Step 4.2. If cloneResult is an abrupt completion,
+ if (!cloneResult) {
+ // Step 4.2.1 Perform
+ // !ReadableByteStreamControllerError(branch1.[[controller]],
+ // cloneResult.[[Value]]).
+ JS::Rooted<JS::Value> exceptionValue(cx);
+ if (!JS_GetPendingException(cx, &exceptionValue)) {
+ // Uncatchable exception, simply return.
+ return;
+ }
+ JS_ClearPendingException(cx);
+
+ ErrorResult rv;
+ ReadableByteStreamControllerError(
+ mTeeState->Branch1()->Controller()->AsByte(), exceptionValue,
+ rv);
+ if (rv.MaybeSetPendingException(
+ cx, "Error during ReadableByteStreamControllerError")) {
+ return;
+ }
+
+ // Step 4.2.2. Perform !
+ // ReadableByteStreamControllerError(branch2.[[controller]],
+ // cloneResult.[[Value]]).
+ ReadableByteStreamControllerError(
+ mTeeState->Branch2()->Controller()->AsByte(), exceptionValue,
+ rv);
+ if (rv.MaybeSetPendingException(
+ cx, "Error during ReadableByteStreamControllerError")) {
+ return;
+ }
+
+ // Step 4.2.3. Resolve cancelPromise with !
+ // ReadableStreamCancel(stream, cloneResult.[[Value]]).
+ RefPtr<ReadableStream> stream(mTeeState->GetStream());
+ RefPtr<Promise> promise =
+ ReadableStreamCancel(cx, stream, exceptionValue, rv);
+ if (rv.MaybeSetPendingException(
+ cx, "Error during ReadableByteStreamControllerError")) {
+ return;
+ }
+ mTeeState->CancelPromise()->MaybeResolve(promise);
+
+ // Step 4.2.4. Return.
+ return;
+ }
+
+ // Step 4.3. Otherwise, set chunk2 to cloneResult.[[Value]].
+ chunk2 = cloneResult;
+ }
+
+ // Step 5. If canceled1 is false,
+ // perform ! ReadableByteStreamControllerEnqueue(branch1.[[controller]],
+ // chunk1).
+ if (!mTeeState->Canceled1()) {
+ ErrorResult rv;
+ RefPtr<ReadableByteStreamController> controller(
+ mTeeState->Branch1()->Controller()->AsByte());
+ ReadableByteStreamControllerEnqueue(cx, controller, chunk1, rv);
+ if (rv.MaybeSetPendingException(
+ cx, "Error during ReadableByteStreamControllerEnqueue")) {
+ return;
+ }
+ }
+
+ // Step 6. If canceled2 is false,
+ // perform ! ReadableByteStreamControllerEnqueue(branch2.[[controller]],
+ // chunk2).
+ if (!mTeeState->Canceled2()) {
+ ErrorResult rv;
+ RefPtr<ReadableByteStreamController> controller(
+ mTeeState->Branch2()->Controller()->AsByte());
+ ReadableByteStreamControllerEnqueue(cx, controller, chunk2, rv);
+ if (rv.MaybeSetPendingException(
+ cx, "Error during ReadableByteStreamControllerEnqueue")) {
+ return;
+ }
+ }
+
+ // Step 7. Set reading to false.
+ mTeeState->SetReading(false);
+
+ // Step 8. If readAgainForBranch1 is true, perform pull1Algorithm.
+ if (mTeeState->ReadAgainForBranch1()) {
+ ByteStreamTeePullAlgorithm(cx, TeeBranch::Branch1,
+ MOZ_KnownLive(mTeeState), rv);
+ } else if (mTeeState->ReadAgainForBranch2()) {
+ // Step 9. Otherwise, if readAgainForBranch2 is true, perform
+ // pull2Algorithm.
+ ByteStreamTeePullAlgorithm(cx, TeeBranch::Branch2,
+ MOZ_KnownLive(mTeeState), rv);
+ }
+ }
+
+ bool Suppressed() override {
+ nsIGlobalObject* global = mTeeState->GetStream()->GetParentObject();
+ return global && global->IsInSyncOperation();
+ }
+ };
+
+ MOZ_ASSERT(aChunk.isObjectOrNull());
+ MOZ_ASSERT(aChunk.toObjectOrNull() != nullptr);
+ JS::Rooted<JSObject*> chunk(aCx, &aChunk.toObject());
+ RefPtr<PullWithDefaultReaderChunkStepMicrotask> task =
+ MakeRefPtr<PullWithDefaultReaderChunkStepMicrotask>(aCx, mTeeState,
+ chunk);
+ CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget());
+ }
+
+ MOZ_CAN_RUN_SCRIPT void CloseSteps(JSContext* aCx,
+ ErrorResult& aRv) override {
+ // Step numbering below is relative to Step 15.2. 'close steps' of
+ // https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
+
+ // Step 1. Set reading to false.
+ mTeeState->SetReading(false);
+
+ // Step 2. If canceled1 is false, perform !
+ // ReadableByteStreamControllerClose(branch1.[[controller]]).
+ RefPtr<ReadableByteStreamController> branch1Controller =
+ mTeeState->Branch1()->Controller()->AsByte();
+ if (!mTeeState->Canceled1()) {
+ ReadableByteStreamControllerClose(aCx, branch1Controller, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 3. If canceled2 is false, perform !
+ // ReadableByteStreamControllerClose(branch2.[[controller]]).
+ RefPtr<ReadableByteStreamController> branch2Controller =
+ mTeeState->Branch2()->Controller()->AsByte();
+ if (!mTeeState->Canceled2()) {
+ ReadableByteStreamControllerClose(aCx, branch2Controller, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 4. If branch1.[[controller]].[[pendingPullIntos]] is not empty,
+ // perform ! ReadableByteStreamControllerRespond(branch1.[[controller]], 0).
+ if (!branch1Controller->PendingPullIntos().isEmpty()) {
+ ReadableByteStreamControllerRespond(aCx, branch1Controller, 0, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 5. If branch2.[[controller]].[[pendingPullIntos]] is not empty,
+ // perform ! ReadableByteStreamControllerRespond(branch2.[[controller]], 0).
+ if (!branch2Controller->PendingPullIntos().isEmpty()) {
+ ReadableByteStreamControllerRespond(aCx, branch2Controller, 0, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 6. If canceled1 is false or canceled2 is false, resolve
+ // cancelPromise with undefined.
+ if (!mTeeState->Canceled1() || !mTeeState->Canceled2()) {
+ mTeeState->CancelPromise()->MaybeResolveWithUndefined();
+ }
+ }
+
+ void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) override {
+ mTeeState->SetReading(false);
+ }
+
+ protected:
+ ~PullWithDefaultReaderReadRequest() override = default;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PullWithDefaultReaderReadRequest,
+ ReadRequest, mTeeState)
+NS_IMPL_ADDREF_INHERITED(PullWithDefaultReaderReadRequest, ReadRequest)
+NS_IMPL_RELEASE_INHERITED(PullWithDefaultReaderReadRequest, ReadRequest)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PullWithDefaultReaderReadRequest)
+NS_INTERFACE_MAP_END_INHERITING(ReadRequest)
+
+void ForwardReaderError(TeeState* aTeeState,
+ ReadableStreamGenericReader* aThisReader);
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee:
+// Step 15.
+void PullWithDefaultReader(JSContext* aCx, TeeState* aTeeState,
+ ErrorResult& aRv) {
+ RefPtr<ReadableStreamGenericReader> reader = aTeeState->GetReader();
+
+ // Step 15.1. If reader implements ReadableStreamBYOBReader,
+ if (reader->IsBYOB()) {
+ // Step 15.1.1. Assert: reader.[[readIntoRequests]] is empty.
+ MOZ_ASSERT(reader->AsBYOB()->ReadIntoRequests().length() == 0);
+
+ // Step 15.1.2. Perform ! ReadableStreamBYOBReaderRelease(reader).
+ ReadableStreamBYOBReaderRelease(aCx, reader->AsBYOB(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 15.1.3. Set reader to ! AcquireReadableStreamDefaultReader(stream).
+ reader = AcquireReadableStreamDefaultReader(aTeeState->GetStream(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ aTeeState->SetReader(reader);
+
+ // Step 16.1.4. Perform forwardReaderError, given reader.
+ ForwardReaderError(aTeeState, reader);
+ }
+
+ // Step 15.2
+ RefPtr<ReadRequest> readRequest =
+ new PullWithDefaultReaderReadRequest(aTeeState);
+
+ // Step 15.3
+ ReadableStreamDefaultReaderRead(aCx, reader, readRequest, aRv);
+}
+
+class PullWithBYOBReader_ReadIntoRequest final : public ReadIntoRequest {
+ RefPtr<TeeState> mTeeState;
+ const TeeBranch mForBranch;
+ ~PullWithBYOBReader_ReadIntoRequest() override = default;
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PullWithBYOBReader_ReadIntoRequest,
+ ReadIntoRequest)
+
+ explicit PullWithBYOBReader_ReadIntoRequest(TeeState* aTeeState,
+ TeeBranch aForBranch)
+ : mTeeState(aTeeState), mForBranch(aForBranch) {}
+
+ void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) override {
+ // https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
+ // Step 16.4 chunk steps, Step 1.
+ class PullWithBYOBReaderChunkMicrotask : public MicroTaskRunnable {
+ RefPtr<TeeState> mTeeState;
+ JS::PersistentRooted<JSObject*> mChunk;
+ const TeeBranch mForBranch;
+
+ public:
+ PullWithBYOBReaderChunkMicrotask(JSContext* aCx, TeeState* aTeeState,
+ JS::Handle<JSObject*> aChunk,
+ TeeBranch aForBranch)
+ : mTeeState(aTeeState), mChunk(aCx, aChunk), mForBranch(aForBranch) {}
+
+ MOZ_CAN_RUN_SCRIPT
+ void Run(AutoSlowOperation& aAso) override {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(
+ !jsapi.Init(mTeeState->GetStream()->GetParentObject()))) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ ErrorResult rv;
+ // https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
+ //
+ // Step Numbering below is relative to Chunk steps Microtask at
+ // Step 16.4 chunk steps, Step 1.
+
+ // Step 1.
+ mTeeState->SetReadAgainForBranch1(false);
+
+ // Step 2.
+ mTeeState->SetReadAgainForBranch2(false);
+
+ // Step 3.
+ bool byobCanceled = mTeeState->Canceled(mForBranch);
+ // Step 4.
+ bool otherCanceled = mTeeState->Canceled(OtherTeeBranch(mForBranch));
+
+ // Rather than store byobBranch / otherBranch, we re-derive the pointers
+ // below, as borrowed from steps 16.2/16.3
+ ReadableStream* byobBranch = mTeeState->Branch(mForBranch);
+ ReadableStream* otherBranch =
+ mTeeState->Branch(OtherTeeBranch(mForBranch));
+
+ // Step 5.
+ if (!otherCanceled) {
+ // Step 5.1 (using the name clonedChunk because we don't want to name
+ // the completion record explicitly)
+ JS::Rooted<JSObject*> clonedChunk(cx, CloneAsUint8Array(cx, mChunk));
+
+ // Step 5.2. If cloneResult is an abrupt completion,
+ if (!clonedChunk) {
+ JS::Rooted<JS::Value> exception(cx);
+ if (!JS_GetPendingException(cx, &exception)) {
+ // Uncatchable exception. Return with pending
+ // exception still on context.
+ return;
+ }
+
+ // It's not expliclitly stated, but I assume the intention here is
+ // that we perform a normal completion here, so we clear the
+ // exception.
+ JS_ClearPendingException(cx);
+
+ // Step 5.2.1
+
+ ReadableByteStreamControllerError(
+ byobBranch->Controller()->AsByte(), exception, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return;
+ }
+
+ // Step 5.2.2.
+ ReadableByteStreamControllerError(
+ otherBranch->Controller()->AsByte(), exception, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return;
+ }
+
+ // Step 5.2.3.
+ RefPtr<ReadableStream> stream = mTeeState->GetStream();
+ RefPtr<Promise> cancelPromise =
+ ReadableStreamCancel(cx, stream, exception, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return;
+ }
+ mTeeState->CancelPromise()->MaybeResolve(cancelPromise);
+
+ // Step 5.2.4.
+ return;
+ }
+
+ // Step 5.3 (implicitly handled above by name selection)
+ // Step 5.4.
+ if (!byobCanceled) {
+ RefPtr<ReadableByteStreamController> controller(
+ byobBranch->Controller()->AsByte());
+ ReadableByteStreamControllerRespondWithNewView(cx, controller,
+ mChunk, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return;
+ }
+ }
+
+ // Step 5.4.
+ RefPtr<ReadableByteStreamController> otherController =
+ otherBranch->Controller()->AsByte();
+ ReadableByteStreamControllerEnqueue(cx, otherController, clonedChunk,
+ rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return;
+ }
+ // Step 6.
+ } else if (!byobCanceled) {
+ RefPtr<ReadableByteStreamController> byobController =
+ byobBranch->Controller()->AsByte();
+ ReadableByteStreamControllerRespondWithNewView(cx, byobController,
+ mChunk, rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return;
+ }
+ }
+
+ // Step 7.
+ mTeeState->SetReading(false);
+
+ // Step 8.
+ if (mTeeState->ReadAgainForBranch1()) {
+ ByteStreamTeePullAlgorithm(cx, TeeBranch::Branch1,
+ MOZ_KnownLive(mTeeState), rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return;
+ }
+ } else if (mTeeState->ReadAgainForBranch2()) {
+ ByteStreamTeePullAlgorithm(cx, TeeBranch::Branch2,
+ MOZ_KnownLive(mTeeState), rv);
+ if (rv.MaybeSetPendingException(cx)) {
+ return;
+ }
+ }
+ }
+
+ bool Suppressed() override {
+ nsIGlobalObject* global = mTeeState->GetStream()->GetParentObject();
+ return global && global->IsInSyncOperation();
+ }
+ };
+
+ MOZ_ASSERT(aChunk.isObjectOrNull());
+ MOZ_ASSERT(aChunk.toObjectOrNull());
+ JS::Rooted<JSObject*> chunk(aCx, aChunk.toObjectOrNull());
+ RefPtr<PullWithBYOBReaderChunkMicrotask> task =
+ MakeRefPtr<PullWithBYOBReaderChunkMicrotask>(aCx, mTeeState, chunk,
+ mForBranch);
+ CycleCollectedJSContext::Get()->DispatchToMicroTask(task.forget());
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void CloseSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) override {
+ // Step 1.
+ mTeeState->SetReading(false);
+
+ // Step 2.
+ bool byobCanceled = mTeeState->Canceled(mForBranch);
+
+ // Step 3.
+ bool otherCanceled = mTeeState->Canceled(OtherTeeBranch(mForBranch));
+
+ // Rather than store byobBranch / otherBranch, we re-derive the pointers
+ // below, as borrowed from steps 16.2/16.3
+ ReadableStream* byobBranch = mTeeState->Branch(mForBranch);
+ ReadableStream* otherBranch = mTeeState->Branch(OtherTeeBranch(mForBranch));
+
+ // Step 4.
+ if (!byobCanceled) {
+ RefPtr<ReadableByteStreamController> controller =
+ byobBranch->Controller()->AsByte();
+ ReadableByteStreamControllerClose(aCx, controller, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ // Step 5.
+ if (!otherCanceled) {
+ RefPtr<ReadableByteStreamController> controller =
+ otherBranch->Controller()->AsByte();
+ ReadableByteStreamControllerClose(aCx, controller, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 6.
+ if (!aChunk.isUndefined()) {
+ MOZ_ASSERT(aChunk.isObject());
+ MOZ_ASSERT(aChunk.toObjectOrNull());
+
+ JS::Rooted<JSObject*> chunkObject(aCx, &aChunk.toObject());
+ MOZ_ASSERT(JS_IsArrayBufferViewObject(chunkObject));
+ // Step 6.1.
+ MOZ_ASSERT(JS_GetArrayBufferViewByteLength(chunkObject) == 0);
+
+ // Step 6.2.
+ if (!byobCanceled) {
+ RefPtr<ReadableByteStreamController> byobController(
+ byobBranch->Controller()->AsByte());
+ ReadableByteStreamControllerRespondWithNewView(aCx, byobController,
+ chunkObject, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ // Step 6.3
+ if (!otherCanceled &&
+ !otherBranch->Controller()->AsByte()->PendingPullIntos().isEmpty()) {
+ RefPtr<ReadableByteStreamController> otherController(
+ otherBranch->Controller()->AsByte());
+ ReadableByteStreamControllerRespond(aCx, otherController, 0, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+ }
+
+ // Step 7.
+ if (!byobCanceled || !otherCanceled) {
+ mTeeState->CancelPromise()->MaybeResolveWithUndefined();
+ }
+ }
+
+ void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> e,
+ ErrorResult& aRv) override {
+ // Step 1.
+ mTeeState->SetReading(false);
+ }
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PullWithBYOBReader_ReadIntoRequest,
+ ReadIntoRequest, mTeeState)
+NS_IMPL_ADDREF_INHERITED(PullWithBYOBReader_ReadIntoRequest, ReadIntoRequest)
+NS_IMPL_RELEASE_INHERITED(PullWithBYOBReader_ReadIntoRequest, ReadIntoRequest)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PullWithBYOBReader_ReadIntoRequest)
+NS_INTERFACE_MAP_END_INHERITING(ReadIntoRequest)
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
+// Step 16.
+void PullWithBYOBReader(JSContext* aCx, TeeState* aTeeState,
+ JS::Handle<JSObject*> aView, TeeBranch aForBranch,
+ ErrorResult& aRv) {
+ // Step 16.1
+ if (aTeeState->GetReader()->IsDefault()) {
+ // Step 16.1.1
+ MOZ_ASSERT(aTeeState->GetDefaultReader()->ReadRequests().isEmpty());
+
+ // Step 16.1.2. Perform ! ReadableStreamDefaultReaderRelease(reader).
+ ReadableStreamDefaultReaderRelease(aCx, aTeeState->GetDefaultReader(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 16.1.3. Set reader to !AcquireReadableStreamBYOBReader(stream).
+ RefPtr<ReadableStreamBYOBReader> reader =
+ AcquireReadableStreamBYOBReader(aTeeState->GetStream(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ aTeeState->SetReader(reader);
+
+ // Step 16.1.4. Perform forwardReaderError, given reader.
+ ForwardReaderError(aTeeState, reader);
+ }
+
+ // Step 16.2. Unused in this function, moved to consumers.
+ // Step 16.3. Unused in this function, moved to consumers.
+
+ // Step 16.4.
+ RefPtr<ReadIntoRequest> readIntoRequest =
+ new PullWithBYOBReader_ReadIntoRequest(aTeeState, aForBranch);
+
+ // Step 16.5.
+ RefPtr<ReadableStreamBYOBReader> byobReader =
+ aTeeState->GetReader()->AsBYOB();
+ ReadableStreamBYOBReaderRead(aCx, byobReader, aView, readIntoRequest, aRv);
+}
+
+// See https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
+// Step 14.
+void ForwardReaderError(TeeState* aTeeState,
+ ReadableStreamGenericReader* aThisReader) {
+ aThisReader->ClosedPromise()->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ TeeState* aTeeState, ReadableStreamGenericReader* aThisReader) {},
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ TeeState* aTeeState, ReadableStreamGenericReader* aReader) {
+ // Step 14.1.1
+ if (aTeeState->GetReader() != aReader) {
+ return;
+ }
+
+ ErrorResult rv;
+ // Step 14.1.2: Perform
+ // !ReadableByteStreamControllerError(branch1.[[controller]], r).
+ MOZ_ASSERT(aTeeState->Branch1()->Controller()->IsByte());
+ ReadableByteStreamControllerError(
+ aTeeState->Branch1()->Controller()->AsByte(), aValue, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 14.1.3: Perform
+ // !ReadableByteStreamControllerError(branch2.[[controller]], r).
+ MOZ_ASSERT(aTeeState->Branch2()->Controller()->IsByte());
+ ReadableByteStreamControllerError(
+ aTeeState->Branch2()->Controller()->AsByte(), aValue, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 14.1.4: If canceled1 is false or canceled2 is false, resolve
+ // cancelPromise with undefined.
+ if (!aTeeState->Canceled1() || !aTeeState->Canceled2()) {
+ aTeeState->CancelPromise()->MaybeResolveWithUndefined();
+ }
+ },
+ RefPtr(aTeeState), RefPtr(aThisReader));
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
+void ReadableByteStreamTee(JSContext* aCx, ReadableStream* aStream,
+ nsTArray<RefPtr<ReadableStream>>& aResult,
+ ErrorResult& aRv) {
+ // Step 1. Implicit
+ // Step 2.
+ MOZ_ASSERT(aStream->Controller()->IsByte());
+
+ // Step 3-13 captured as part of TeeState allocation
+ RefPtr<TeeState> teeState = TeeState::Create(aStream, false, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 14: See ForwardReaderError
+ // Step 15. See PullWithDefaultReader
+ // Step 16. See PullWithBYOBReader
+ // Step 17,18. See {Native,}ByteStreamTeePullAlgorithm
+ // Step 19,20. See ReadableByteStreamTeeCancelAlgorithm
+ // Step 21. Elided because consumers know how to handle nullptr correctly.
+ // Step 22.
+ nsCOMPtr<nsIGlobalObject> global = aStream->GetParentObject();
+ auto branch1Algorithms =
+ MakeRefPtr<ByteStreamTeeSourceAlgorithms>(teeState, TeeBranch::Branch1);
+ teeState->SetBranch1(
+ ReadableStream::CreateByteAbstract(aCx, global, branch1Algorithms, aRv));
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 23.
+ auto branch2Algorithms =
+ MakeRefPtr<ByteStreamTeeSourceAlgorithms>(teeState, TeeBranch::Branch2);
+ teeState->SetBranch2(
+ ReadableStream::CreateByteAbstract(aCx, global, branch2Algorithms, aRv));
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 24.
+ ForwardReaderError(teeState, teeState->GetReader());
+
+ // Step 25.
+ aResult.AppendElement(teeState->Branch1());
+ aResult.AppendElement(teeState->Branch2());
+}
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
diff --git a/dom/streams/ReadableStreamTee.h b/dom/streams/ReadableStreamTee.h
new file mode 100644
index 0000000000..855fcf4286
--- /dev/null
+++ b/dom/streams/ReadableStreamTee.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_ReadableStreamTee_h
+#define mozilla_dom_ReadableStreamTee_h
+
+#include "mozilla/dom/ReadRequest.h"
+#include "mozilla/dom/ReadableStreamController.h"
+#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+struct TeeState;
+enum class TeeBranch : bool;
+class ReadableStream;
+
+// Implementation of the Pull algorithm steps for ReadableStreamDefaultTee,
+// from
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee
+// Step 12.
+class ReadableStreamDefaultTeeSourceAlgorithms final
+ : public UnderlyingSourceAlgorithmsBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
+ ReadableStreamDefaultTeeSourceAlgorithms, UnderlyingSourceAlgorithmsBase)
+
+ ReadableStreamDefaultTeeSourceAlgorithms(TeeState* aTeeState,
+ TeeBranch aBranch)
+ : mTeeState(aTeeState), mBranch(aBranch) {}
+
+ MOZ_CAN_RUN_SCRIPT void StartCallback(JSContext* aCx,
+ ReadableStreamController& aController,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv) override {
+ aRetVal.setUndefined();
+ }
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PullCallback(
+ JSContext* aCx, ReadableStreamController& aController,
+ ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CancelCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override;
+
+ protected:
+ ~ReadableStreamDefaultTeeSourceAlgorithms() override = default;
+
+ private:
+ // Virtually const, but is cycle collected
+ MOZ_KNOWN_LIVE RefPtr<TeeState> mTeeState;
+ TeeBranch mBranch;
+};
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee
+// Step 12.3
+struct ReadableStreamDefaultTeeReadRequest final : public ReadRequest {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ReadableStreamDefaultTeeReadRequest,
+ ReadRequest)
+
+ RefPtr<TeeState> mTeeState;
+
+ explicit ReadableStreamDefaultTeeReadRequest(TeeState* aTeeState)
+ : mTeeState(aTeeState) {}
+
+ void ChunkSteps(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT void CloseSteps(JSContext* aCx, ErrorResult& aRv) override;
+
+ void ErrorSteps(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) override;
+
+ protected:
+ ~ReadableStreamDefaultTeeReadRequest() override = default;
+};
+
+namespace streams_abstract {
+MOZ_CAN_RUN_SCRIPT void ReadableByteStreamTee(
+ JSContext* aCx, ReadableStream* aStream,
+ nsTArray<RefPtr<ReadableStream>>& aResult, ErrorResult& aRv);
+}
+
+} // namespace mozilla::dom
+#endif
diff --git a/dom/streams/StreamUtils.cpp b/dom/streams/StreamUtils.cpp
new file mode 100644
index 0000000000..15ad80867d
--- /dev/null
+++ b/dom/streams/StreamUtils.cpp
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StreamUtils.h"
+
+#include <cmath>
+#include "mozilla/dom/QueuingStrategyBinding.h"
+
+namespace mozilla::dom {
+
+// Streams Spec: 7.4
+// https://streams.spec.whatwg.org/#validate-and-normalize-high-water-mark
+double ExtractHighWaterMark(const QueuingStrategy& aStrategy,
+ double aDefaultHWM, mozilla::ErrorResult& aRv) {
+ // Step 1.
+ if (!aStrategy.mHighWaterMark.WasPassed()) {
+ return aDefaultHWM;
+ }
+
+ // Step 2.
+ double highWaterMark = aStrategy.mHighWaterMark.Value();
+
+ // Step 3.
+ if (std::isnan(highWaterMark) || highWaterMark < 0) {
+ aRv.ThrowRangeError("Invalid highWaterMark");
+ return 0.0;
+ }
+
+ // Step 4.
+ return highWaterMark;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/StreamUtils.h b/dom/streams/StreamUtils.h
new file mode 100644
index 0000000000..f0b09c63b8
--- /dev/null
+++ b/dom/streams/StreamUtils.h
@@ -0,0 +1,57 @@
+/* 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 mozilla_dom_StreamUtils_h
+#define mozilla_dom_StreamUtils_h
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Promise.h"
+
+class nsIGlobalObject;
+
+namespace mozilla::dom {
+
+struct QueuingStrategy;
+
+double ExtractHighWaterMark(const QueuingStrategy& aStrategy,
+ double aDefaultHWM, ErrorResult& aRv);
+
+// Promisification algorithm, shared among:
+// Step 2 and 3 of https://streams.spec.whatwg.org/#readablestream-set-up
+// Step 2 and 3 of
+// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
+// Step 2 and 3 of https://streams.spec.whatwg.org/#writablestream-set-up
+// Step 5 and 6 of https://streams.spec.whatwg.org/#transformstream-set-up
+template <typename T>
+MOZ_CAN_RUN_SCRIPT static already_AddRefed<Promise> PromisifyAlgorithm(
+ nsIGlobalObject* aGlobal, T aFunc, mozilla::ErrorResult& aRv) {
+ // Step 1. Let result be the result of running (algorithm). If this throws an
+ // exception e, return a promise rejected with e.
+ RefPtr<Promise> result;
+ if constexpr (!std::is_same<decltype(aFunc(aRv)), void>::value) {
+ result = aFunc(aRv);
+ } else {
+ aFunc(aRv);
+ }
+
+ if (aRv.IsUncatchableException()) {
+ return nullptr;
+ }
+
+ if (aRv.Failed()) {
+ return Promise::CreateRejectedWithErrorResult(aGlobal, aRv);
+ }
+
+ // Step 2. If result is a Promise, then return result.
+ if (result) {
+ return result.forget();
+ }
+
+ // Step 3. Return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(aGlobal, aRv);
+}
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_StreamUtils_h
diff --git a/dom/streams/TeeState.cpp b/dom/streams/TeeState.cpp
new file mode 100644
index 0000000000..8600ea1205
--- /dev/null
+++ b/dom/streams/TeeState.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TeeState.h"
+
+#include "ReadableStreamTee.h"
+#include "js/Value.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(TeeState,
+ (mStream, mReader, mBranch1, mBranch2,
+ mCancelPromise),
+ (mReason1, mReason2))
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TeeState)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TeeState)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TeeState)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TeeState::TeeState(ReadableStream* aStream, bool aCloneForBranch2)
+ : mStream(aStream),
+ mReason1(JS::NullValue()),
+ mReason2(JS::NullValue()),
+ mCloneForBranch2(aCloneForBranch2) {
+ mozilla::HoldJSObjects(this);
+ MOZ_RELEASE_ASSERT(!aCloneForBranch2,
+ "cloneForBranch2 path is not implemented.");
+}
+
+already_AddRefed<TeeState> TeeState::Create(ReadableStream* aStream,
+ bool aCloneForBranch2,
+ ErrorResult& aRv) {
+ RefPtr<TeeState> teeState = new TeeState(aStream, aCloneForBranch2);
+
+ RefPtr<ReadableStreamDefaultReader> reader =
+ AcquireReadableStreamDefaultReader(teeState->GetStream(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ teeState->SetReader(reader);
+
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(teeState->GetStream()->GetParentObject());
+ teeState->SetCancelPromise(promise);
+
+ return teeState.forget();
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee
+// Pull Algorithm Steps:
+void TeeState::PullCallback(JSContext* aCx, nsIGlobalObject* aGlobal,
+ ErrorResult& aRv) {
+ // Step 13.1: If reading is true,
+ if (Reading()) {
+ // Step 13.1.1: Set readAgain to true.
+ SetReadAgain(true);
+
+ // Step 13.1.2: Return a promise resolved with undefined.
+ // (The caller will create it if necessary)
+ return;
+ }
+
+ // Step 13.2: Set reading to true.
+ SetReading(true);
+
+ // Step 13.3: Let readRequest be a read request with the following items:
+ RefPtr<ReadRequest> readRequest =
+ new ReadableStreamDefaultTeeReadRequest(this);
+
+ // Step 13.4: Perform ! ReadableStreamDefaultReaderRead(reader, readRequest).
+ RefPtr<ReadableStreamGenericReader> reader = GetReader();
+ ReadableStreamDefaultReaderRead(aCx, reader, readRequest, aRv);
+
+ // Step 13.5: Return a promise resolved with undefined.
+ // (The caller will create it if necessary)
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/TeeState.h b/dom/streams/TeeState.h
new file mode 100644
index 0000000000..9a688be662
--- /dev/null
+++ b/dom/streams/TeeState.h
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_TeeState_h
+#define mozilla_dom_TeeState_h
+
+#include "mozilla/HoldDropJSObjects.h"
+#include "nsISupports.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamDefaultReader.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla::dom {
+
+class ReadableStreamDefaultTeeSourceAlgorithms;
+
+enum class TeeBranch : bool {
+ Branch1,
+ Branch2,
+};
+
+inline TeeBranch OtherTeeBranch(TeeBranch aBranch) {
+ if (aBranch == TeeBranch::Branch1) {
+ return TeeBranch::Branch2;
+ }
+ return TeeBranch::Branch1;
+}
+
+// A closure capturing the free variables in the ReadableStreamTee family of
+// algorithms.
+// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee
+// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
+struct TeeState : public nsISupports {
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TeeState)
+
+ static already_AddRefed<TeeState> Create(ReadableStream* aStream,
+ bool aCloneForBranch2,
+ ErrorResult& aRv);
+
+ ReadableStream* GetStream() const { return mStream; }
+ void SetStream(ReadableStream* aStream) { mStream = aStream; }
+
+ ReadableStreamGenericReader* GetReader() const { return mReader; }
+ void SetReader(ReadableStreamGenericReader* aReader) { mReader = aReader; }
+
+ ReadableStreamDefaultReader* GetDefaultReader() const {
+ return mReader->AsDefault();
+ }
+
+ bool ReadAgain() const { return mReadAgain; }
+ void SetReadAgain(bool aReadAgain) { mReadAgain = aReadAgain; }
+
+ // ReadableByteStreamTee uses ReadAgainForBranch{1,2};
+ bool ReadAgainForBranch1() const { return ReadAgain(); }
+ void SetReadAgainForBranch1(bool aReadAgainForBranch1) {
+ SetReadAgain(aReadAgainForBranch1);
+ }
+
+ bool ReadAgainForBranch2() const { return mReadAgainForBranch2; }
+ void SetReadAgainForBranch2(bool aReadAgainForBranch2) {
+ mReadAgainForBranch2 = aReadAgainForBranch2;
+ }
+
+ bool Reading() const { return mReading; }
+ void SetReading(bool aReading) { mReading = aReading; }
+
+ bool Canceled1() const { return mCanceled1; }
+ void SetCanceled1(bool aCanceled1) { mCanceled1 = aCanceled1; }
+
+ bool Canceled2() const { return mCanceled2; }
+ void SetCanceled2(bool aCanceled2) { mCanceled2 = aCanceled2; }
+
+ void SetCanceled(TeeBranch aBranch, bool aCanceled) {
+ aBranch == TeeBranch::Branch1 ? SetCanceled1(aCanceled)
+ : SetCanceled2(aCanceled);
+ }
+ bool Canceled(TeeBranch aBranch) {
+ return aBranch == TeeBranch::Branch1 ? Canceled1() : Canceled2();
+ }
+
+ JS::Value Reason1() const { return mReason1; }
+ void SetReason1(JS::Handle<JS::Value> aReason1) { mReason1 = aReason1; }
+
+ JS::Value Reason2() const { return mReason2; }
+ void SetReason2(JS::Handle<JS::Value> aReason2) { mReason2 = aReason2; }
+
+ void SetReason(TeeBranch aBranch, JS::Handle<JS::Value> aReason) {
+ aBranch == TeeBranch::Branch1 ? SetReason1(aReason) : SetReason2(aReason);
+ }
+
+ ReadableStream* Branch1() const { return mBranch1; }
+ void SetBranch1(already_AddRefed<ReadableStream> aBranch1) {
+ mBranch1 = aBranch1;
+ }
+
+ ReadableStream* Branch2() const { return mBranch2; }
+ void SetBranch2(already_AddRefed<ReadableStream> aBranch2) {
+ mBranch2 = aBranch2;
+ }
+
+ Promise* CancelPromise() const { return mCancelPromise; }
+ void SetCancelPromise(Promise* aCancelPromise) {
+ mCancelPromise = aCancelPromise;
+ }
+
+ bool CloneForBranch2() const { return mCloneForBranch2; }
+ void setCloneForBranch2(bool aCloneForBranch2) {
+ mCloneForBranch2 = aCloneForBranch2;
+ }
+
+ // Some code is better served by using an enum into various internal slots to
+ // avoid duplication: Here we provide alternative accessors for that case.
+ ReadableStream* Branch(TeeBranch aBranch) const {
+ return aBranch == TeeBranch::Branch1 ? Branch1() : Branch2();
+ }
+
+ void SetReadAgainForBranch(TeeBranch aBranch, bool aValue) {
+ if (aBranch == TeeBranch::Branch1) {
+ SetReadAgainForBranch1(aValue);
+ return;
+ }
+ SetReadAgainForBranch2(aValue);
+ }
+
+ MOZ_CAN_RUN_SCRIPT void PullCallback(JSContext* aCx, nsIGlobalObject* aGlobal,
+ ErrorResult& aRv);
+
+ private:
+ TeeState(ReadableStream* aStream, bool aCloneForBranch2);
+
+ // Implicit:
+ RefPtr<ReadableStream> mStream;
+
+ // Step 3. (Step Numbering is based on ReadableStreamDefaultTee)
+ RefPtr<ReadableStreamGenericReader> mReader;
+
+ // Step 4.
+ bool mReading = false;
+
+ // Step 5.
+ // (Aliased to readAgainForBranch1, for the purpose of ReadableByteStreamTee)
+ bool mReadAgain = false;
+
+ // ReadableByteStreamTee
+ bool mReadAgainForBranch2 = false;
+
+ // Step 6.
+ bool mCanceled1 = false;
+
+ // Step 7.
+ bool mCanceled2 = false;
+
+ // Step 8.
+ JS::Heap<JS::Value> mReason1;
+
+ // Step 9.
+ JS::Heap<JS::Value> mReason2;
+
+ // Step 10.
+ RefPtr<ReadableStream> mBranch1;
+
+ // Step 11.
+ RefPtr<ReadableStream> mBranch2;
+
+ // Step 12.
+ RefPtr<Promise> mCancelPromise;
+
+ // Implicit:
+ bool mCloneForBranch2 = false;
+
+ virtual ~TeeState() { mozilla::DropJSObjects(this); }
+};
+
+} // namespace mozilla::dom
+#endif
diff --git a/dom/streams/Transferable.cpp b/dom/streams/Transferable.cpp
new file mode 100644
index 0000000000..f3e9504d86
--- /dev/null
+++ b/dom/streams/Transferable.cpp
@@ -0,0 +1,1068 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ErrorList.h"
+#include "ReadableStreamPipeTo.h"
+#include "js/RootingAPI.h"
+#include "js/String.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDOMEventListener.h"
+#include "nsIGlobalObject.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/MessageEvent.h"
+#include "mozilla/dom/MessageChannel.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/TransformStream.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+static void PackAndPostMessage(JSContext* aCx, MessagePort* aPort,
+ const nsAString& aType,
+ JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
+ JS::Rooted<JSObject*> obj(aCx,
+ JS_NewObjectWithGivenProto(aCx, nullptr, nullptr));
+ if (!obj) {
+ // XXX: Should we crash here and there? See also bug 1762233.
+ JS_ClearPendingException(aCx);
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ JS::Rooted<JS::Value> type(aCx);
+ if (!xpc::NonVoidStringToJsval(aCx, aType, &type)) {
+ JS_ClearPendingException(aCx);
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ if (!JS_DefineProperty(aCx, obj, "type", type, JSPROP_ENUMERATE)) {
+ JS_ClearPendingException(aCx);
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ JS::Rooted<JS::Value> value(aCx, aValue);
+ if (!JS_WrapValue(aCx, &value)) {
+ JS_ClearPendingException(aCx);
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ if (!JS_DefineProperty(aCx, obj, "value", value, JSPROP_ENUMERATE)) {
+ JS_ClearPendingException(aCx);
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ Sequence<JSObject*> transferables; // none in this case
+ JS::Rooted<JS::Value> objValue(aCx, JS::ObjectValue(*obj));
+ aPort->PostMessage(aCx, objValue, transferables, aRv);
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-crossrealmtransformsenderror
+static void CrossRealmTransformSendError(JSContext* aCx, MessagePort* aPort,
+ JS::Handle<JS::Value> aError) {
+ // Step 1: Perform PackAndPostMessage(port, "error", error), discarding the
+ // result.
+ PackAndPostMessage(aCx, aPort, u"error"_ns, aError, IgnoreErrors());
+}
+
+class SetUpTransformWritableMessageEventListener final
+ : public nsIDOMEventListener {
+ public:
+ SetUpTransformWritableMessageEventListener(
+ WritableStreamDefaultController* aController, Promise* aPromise)
+ : mController(aController), mBackpressurePromise(aPromise) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(SetUpTransformWritableMessageEventListener)
+
+ // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
+ // The handler steps of Step 4.
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mController->GetParentObject())) {
+ return NS_OK;
+ }
+ JSContext* cx = jsapi.cx();
+ MessageEvent* messageEvent = aEvent->AsMessageEvent();
+ if (NS_WARN_IF(!messageEvent || !messageEvent->IsTrusted())) {
+ return NS_OK;
+ }
+
+ // Step 1: Let data be the data of the message.
+ JS::Rooted<JS::Value> dataValue(cx);
+ IgnoredErrorResult rv;
+ messageEvent->GetData(cx, &dataValue, rv);
+ if (rv.Failed()) {
+ return NS_OK;
+ }
+
+ // Step 2: Assert: Type(data) is Object.
+ // (But we check in runtime instead to avoid potential malicious events from
+ // a compromised process. Same below.)
+ if (NS_WARN_IF(!dataValue.isObject())) {
+ return NS_OK;
+ }
+ JS::Rooted<JSObject*> data(cx, &dataValue.toObject());
+
+ // Step 3: Let type be ! Get(data, "type").
+ JS::Rooted<JS::Value> type(cx);
+ if (!JS_GetProperty(cx, data, "type", &type)) {
+ // XXX: See bug 1762233
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+
+ // Step 4: Let value be ! Get(data, "value").
+ JS::Rooted<JS::Value> value(cx);
+ if (!JS_GetProperty(cx, data, "value", &value)) {
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+
+ // Step 5: Assert: Type(type) is String.
+ if (NS_WARN_IF(!type.isString())) {
+ return NS_OK;
+ }
+
+ // Step 6: If type is "pull",
+ bool equals = false;
+ if (!JS_StringEqualsLiteral(cx, type.toString(), "pull", &equals)) {
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+ if (equals) {
+ // Step 6.1: If backpressurePromise is not undefined,
+ MaybeResolveAndClearBackpressurePromise();
+ return NS_OK; // implicit
+ }
+
+ // Step 7: If type is "error",
+ if (!JS_StringEqualsLiteral(cx, type.toString(), "error", &equals)) {
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+ if (equals) {
+ // Step 7.1: Perform !
+ // WritableStreamDefaultControllerErrorIfNeeded(controller, value).
+ WritableStreamDefaultControllerErrorIfNeeded(cx, mController, value, rv);
+ if (rv.Failed()) {
+ return NS_OK;
+ }
+
+ // Step 7.2: If backpressurePromise is not undefined,
+ MaybeResolveAndClearBackpressurePromise();
+ return NS_OK; // implicit
+ }
+
+ // Logically it should be unreachable here, but we should expect random
+ // malicious messages.
+ NS_WARNING("Got an unexpected type other than pull/error.");
+ return NS_OK;
+ }
+
+ void MaybeResolveAndClearBackpressurePromise() {
+ if (mBackpressurePromise) {
+ mBackpressurePromise->MaybeResolveWithUndefined();
+ mBackpressurePromise = nullptr;
+ }
+ }
+
+ // Note: This promise field is shared with the sink algorithms.
+ Promise* BackpressurePromise() { return mBackpressurePromise; }
+
+ void CreateBackpressurePromise() {
+ mBackpressurePromise =
+ Promise::CreateInfallible(mController->GetParentObject());
+ }
+
+ private:
+ ~SetUpTransformWritableMessageEventListener() = default;
+
+ // mController never changes before CC
+ // TODO: MOZ_IMMUTABLE_OUTSIDE_CC
+ MOZ_KNOWN_LIVE RefPtr<WritableStreamDefaultController> mController;
+ RefPtr<Promise> mBackpressurePromise;
+};
+
+NS_IMPL_CYCLE_COLLECTION(SetUpTransformWritableMessageEventListener,
+ mController, mBackpressurePromise)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformWritableMessageEventListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SetUpTransformWritableMessageEventListener)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+ SetUpTransformWritableMessageEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+NS_INTERFACE_MAP_END
+
+class SetUpTransformWritableMessageErrorEventListener final
+ : public nsIDOMEventListener {
+ public:
+ SetUpTransformWritableMessageErrorEventListener(
+ WritableStreamDefaultController* aController, MessagePort* aPort)
+ : mController(aController), mPort(aPort) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(
+ SetUpTransformWritableMessageErrorEventListener)
+
+ // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
+ // The handler steps of Step 5.
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
+ auto cleanupPort =
+ MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
+
+ if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) {
+ return NS_OK;
+ }
+
+ // Step 1: Let error be a new "DataCloneError" DOMException.
+ RefPtr<DOMException> exception =
+ DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mPort->GetParentObject())) {
+ return NS_OK;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> error(cx);
+ if (!ToJSValue(cx, *exception, &error)) {
+ return NS_OK;
+ }
+
+ // Step 2: Perform ! CrossRealmTransformSendError(port, error).
+ CrossRealmTransformSendError(cx, mPort, error);
+
+ // Step 3: Perform
+ // ! WritableStreamDefaultControllerErrorIfNeeded(controller, error).
+ WritableStreamDefaultControllerErrorIfNeeded(cx, mController, error,
+ IgnoreErrors());
+
+ // Step 4: Disentangle port.
+ // (Close() does it)
+ mPort->Close();
+ cleanupPort.release();
+
+ return NS_OK;
+ }
+
+ private:
+ ~SetUpTransformWritableMessageErrorEventListener() = default;
+
+ // mController never changes before CC
+ // TODO: MOZ_IMMUTABLE_OUTSIDE_CC
+ MOZ_KNOWN_LIVE RefPtr<WritableStreamDefaultController> mController;
+ RefPtr<MessagePort> mPort;
+};
+
+NS_IMPL_CYCLE_COLLECTION(SetUpTransformWritableMessageErrorEventListener,
+ mController, mPort)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformWritableMessageErrorEventListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(
+ SetUpTransformWritableMessageErrorEventListener)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+ SetUpTransformWritableMessageErrorEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+NS_INTERFACE_MAP_END
+
+// https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessagehandlingerror
+static bool PackAndPostMessageHandlingError(
+ JSContext* aCx, mozilla::dom::MessagePort* aPort, const nsAString& aType,
+ JS::Handle<JS::Value> aValue, JS::MutableHandle<JS::Value> aError) {
+ // Step 1: Let result be PackAndPostMessage(port, type, value).
+ ErrorResult rv;
+ PackAndPostMessage(aCx, aPort, aType, aValue, rv);
+
+ // Step 2: If result is an abrupt completion,
+ if (rv.Failed()) {
+ // Step 2.2: Perform ! CrossRealmTransformSendError(port, result.[[Value]]).
+ MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), aError));
+ CrossRealmTransformSendError(aCx, aPort, aError);
+ return false;
+ }
+
+ // Step 3: Return result as a completion record.
+ return true;
+}
+
+// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
+class CrossRealmWritableUnderlyingSinkAlgorithms final
+ : public UnderlyingSinkAlgorithmsBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
+ CrossRealmWritableUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
+
+ CrossRealmWritableUnderlyingSinkAlgorithms(
+ SetUpTransformWritableMessageEventListener* aListener, MessagePort* aPort)
+ : mListener(aListener), mPort(aPort) {}
+
+ void StartCallback(JSContext* aCx,
+ WritableStreamDefaultController& aController,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv) override {
+ // Step 7. Let startAlgorithm be an algorithm that returns undefined.
+ aRetVal.setUndefined();
+ }
+
+ already_AddRefed<Promise> WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aRv) override {
+ // Step 1: If backpressurePromise is undefined, set backpressurePromise to a
+ // promise resolved with undefined.
+ // Note: This promise field is shared with the message event listener.
+ if (!mListener->BackpressurePromise()) {
+ mListener->CreateBackpressurePromise();
+ mListener->BackpressurePromise()->MaybeResolveWithUndefined();
+ }
+
+ // Step 2: Return the result of reacting to backpressurePromise with the
+ // following fulfillment steps:
+ auto result =
+ mListener->BackpressurePromise()->ThenWithCycleCollectedArgsJS(
+ [](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv,
+ SetUpTransformWritableMessageEventListener* aListener,
+ MessagePort* aPort,
+ JS::Handle<JS::Value> aChunk) -> already_AddRefed<Promise> {
+ // Step 2.1: Set backpressurePromise to a new promise.
+ aListener->CreateBackpressurePromise();
+
+ // Step 2.2: Let result be PackAndPostMessageHandlingError(port,
+ // "chunk", chunk).
+ JS::Rooted<JS::Value> error(aCx);
+ bool result = PackAndPostMessageHandlingError(
+ aCx, aPort, u"chunk"_ns, aChunk, &error);
+
+ // Step 2.3: If result is an abrupt completion,
+ if (!result) {
+ // Step 2.3.1: Disentangle port.
+ // (Close() does it)
+ aPort->Close();
+
+ // Step 2.3.2: Return a promise rejected with result.[[Value]].
+ return Promise::CreateRejected(aPort->GetParentObject(), error,
+ aRv);
+ }
+
+ // Step 2.4: Otherwise, return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(
+ aPort->GetParentObject(), aRv);
+ },
+ std::make_tuple(mListener, mPort), std::make_tuple(aChunk));
+ if (result.isErr()) {
+ aRv.Throw(result.unwrapErr());
+ return nullptr;
+ }
+ return result.unwrap().forget();
+ }
+
+ already_AddRefed<Promise> CloseCallback(JSContext* aCx,
+ ErrorResult& aRv) override {
+ // Step 1: Perform ! PackAndPostMessage(port, "close", undefined).
+ PackAndPostMessage(aCx, mPort, u"close"_ns, JS::UndefinedHandleValue, aRv);
+ // (We'll check the result after step 2)
+
+ // Step 2: Disentangle port.
+ // (Close() will do this)
+ mPort->Close();
+
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 3: Return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
+ }
+
+ already_AddRefed<Promise> AbortCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override {
+ // Step 1: Let result be PackAndPostMessageHandlingError(port, "error",
+ // reason).
+ JS::Rooted<JS::Value> error(aCx);
+ bool result = PackAndPostMessageHandlingError(
+ aCx, mPort, u"error"_ns,
+ aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue,
+ &error);
+
+ // Step 2: Disentangle port.
+ // (Close() will do this)
+ mPort->Close();
+
+ // Step 3: If result is an abrupt completion, return a promise rejected with
+ // result.[[Value]].
+ if (!result) {
+ return Promise::CreateRejected(mPort->GetParentObject(), error, aRv);
+ }
+
+ // Step 4: Otherwise, return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
+ }
+
+ protected:
+ ~CrossRealmWritableUnderlyingSinkAlgorithms() override = default;
+
+ private:
+ RefPtr<SetUpTransformWritableMessageEventListener> mListener;
+ RefPtr<MessagePort> mPort;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms,
+ UnderlyingSinkAlgorithmsBase, mListener,
+ mPort)
+NS_IMPL_ADDREF_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms,
+ UnderlyingSinkAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms,
+ UnderlyingSinkAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+ CrossRealmWritableUnderlyingSinkAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase)
+
+// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
+MOZ_CAN_RUN_SCRIPT static void SetUpCrossRealmTransformWritable(
+ WritableStream* aWritable, MessagePort* aPort, ErrorResult& aRv) {
+ // (This is only needed for step 12, but let's do this early to fail early
+ // enough)
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aWritable->GetParentObject())) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Step 1: Perform ! InitializeWritableStream(stream).
+ // (Done by the constructor)
+
+ // Step 2: Let controller be a new WritableStreamDefaultController.
+ auto controller = MakeRefPtr<WritableStreamDefaultController>(
+ aWritable->GetParentObject(), *aWritable);
+
+ // Step 3: Let backpressurePromise be a new promise.
+ RefPtr<Promise> backpressurePromise =
+ Promise::CreateInfallible(aWritable->GetParentObject());
+
+ // Step 4: Add a handler for port’s message event with the following steps:
+ auto listener = MakeRefPtr<SetUpTransformWritableMessageEventListener>(
+ controller, backpressurePromise);
+ aPort->AddEventListener(u"message"_ns, listener, false);
+
+ // Step 5: Add a handler for port’s messageerror event with the following
+ // steps:
+ auto errorListener =
+ MakeRefPtr<SetUpTransformWritableMessageErrorEventListener>(controller,
+ aPort);
+ aPort->AddEventListener(u"messageerror"_ns, errorListener, false);
+
+ // Step 6: Enable port’s port message queue.
+ // (Start() does it)
+ aPort->Start();
+
+ // Step 7 - 10:
+ auto algorithms =
+ MakeRefPtr<CrossRealmWritableUnderlyingSinkAlgorithms>(listener, aPort);
+
+ // Step 11: Let sizeAlgorithm be an algorithm that returns 1.
+ // (nullptr should serve this purpose. See also WritableStream::Constructor)
+
+ // Step 12: Perform ! SetUpWritableStreamDefaultController(stream, controller,
+ // startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, 1,
+ // sizeAlgorithm).
+ SetUpWritableStreamDefaultController(cx, aWritable, controller, algorithms, 1,
+ /* aSizeAlgorithm */ nullptr, aRv);
+}
+
+class SetUpTransformReadableMessageEventListener final
+ : public nsIDOMEventListener {
+ public:
+ SetUpTransformReadableMessageEventListener(
+ ReadableStreamDefaultController* aController, MessagePort* aPort)
+ : mController(aController), mPort(aPort) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(SetUpTransformReadableMessageEventListener)
+
+ // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
+ // The handler steps of Step 3.
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
+ auto cleanupPort =
+ MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mPort->GetParentObject())) {
+ return NS_OK;
+ }
+ JSContext* cx = jsapi.cx();
+ MessageEvent* messageEvent = aEvent->AsMessageEvent();
+ if (NS_WARN_IF(!messageEvent || !messageEvent->IsTrusted())) {
+ return NS_OK;
+ }
+
+ // Step 1: Let data be the data of the message.
+ JS::Rooted<JS::Value> dataValue(cx);
+ IgnoredErrorResult rv;
+ messageEvent->GetData(cx, &dataValue, rv);
+ if (rv.Failed()) {
+ return NS_OK;
+ }
+
+ // Step 2: Assert: Type(data) is Object.
+ // (But we check in runtime instead to avoid potential malicious events from
+ // a compromised process. Same below.)
+ if (NS_WARN_IF(!dataValue.isObject())) {
+ return NS_OK;
+ }
+ JS::Rooted<JSObject*> data(cx, JS::ToObject(cx, dataValue));
+
+ // Step 3: Let type be ! Get(data, "type").
+ JS::Rooted<JS::Value> type(cx);
+ if (!JS_GetProperty(cx, data, "type", &type)) {
+ // XXX: See bug 1762233
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+
+ // Step 4: Let value be ! Get(data, "value").
+ JS::Rooted<JS::Value> value(cx);
+ if (!JS_GetProperty(cx, data, "value", &value)) {
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+
+ // Step 5: Assert: Type(type) is String.
+ if (NS_WARN_IF(!type.isString())) {
+ return NS_OK;
+ }
+
+ // Step 6: If type is "chunk",
+ bool equals = false;
+ if (!JS_StringEqualsLiteral(cx, type.toString(), "chunk", &equals)) {
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+ if (equals) {
+ // Step 6.1: Perform ! ReadableStreamDefaultControllerEnqueue(controller,
+ // value).
+ ReadableStreamDefaultControllerEnqueue(cx, mController, value,
+ IgnoreErrors());
+ cleanupPort.release();
+ return NS_OK; // implicit
+ }
+
+ // Step 7: Otherwise, if type is "close",
+ if (!JS_StringEqualsLiteral(cx, type.toString(), "close", &equals)) {
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+ if (equals) {
+ // Step 7.1: Perform ! ReadableStreamDefaultControllerClose(controller).
+ ReadableStreamDefaultControllerClose(cx, mController, IgnoreErrors());
+ // Step 7.2: Disentangle port.
+ // (Close() does it)
+ mPort->Close();
+ cleanupPort.release();
+ return NS_OK; // implicit
+ }
+
+ // Step 8: Otherwise, if type is "error",
+ if (!JS_StringEqualsLiteral(cx, type.toString(), "error", &equals)) {
+ JS_ClearPendingException(cx);
+ return NS_OK;
+ }
+ if (equals) {
+ // Step 8.1: Perform ! ReadableStreamDefaultControllerError(controller,
+ // value).
+ ReadableStreamDefaultControllerError(cx, mController, value,
+ IgnoreErrors());
+
+ // Step 8.2: Disentangle port.
+ // (Close() does it)
+ mPort->Close();
+ cleanupPort.release();
+ return NS_OK; // implicit
+ }
+
+ // Logically it should be unreachable here, but we should expect random
+ // malicious messages.
+ NS_WARNING("Got an unexpected type other than chunk/close/error.");
+ return NS_OK;
+ }
+
+ private:
+ ~SetUpTransformReadableMessageEventListener() = default;
+
+ // mController never changes before CC
+ // TODO: MOZ_IMMUTABLE_OUTSIDE_CC
+ MOZ_KNOWN_LIVE RefPtr<ReadableStreamDefaultController> mController;
+ RefPtr<MessagePort> mPort;
+};
+
+NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageEventListener,
+ mController, mPort)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformReadableMessageEventListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(SetUpTransformReadableMessageEventListener)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+ SetUpTransformReadableMessageEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+NS_INTERFACE_MAP_END
+
+class SetUpTransformReadableMessageErrorEventListener final
+ : public nsIDOMEventListener {
+ public:
+ SetUpTransformReadableMessageErrorEventListener(
+ ReadableStreamDefaultController* aController, MessagePort* aPort)
+ : mController(aController), mPort(aPort) {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(
+ SetUpTransformReadableMessageErrorEventListener)
+
+ // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
+ // The handler steps of Step 4.
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
+ auto cleanupPort =
+ MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
+
+ if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) {
+ return NS_OK;
+ }
+
+ // Step 1: Let error be a new "DataCloneError" DOMException.
+ RefPtr<DOMException> exception =
+ DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR);
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mPort->GetParentObject())) {
+ return NS_OK;
+ }
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> error(cx);
+ if (!ToJSValue(cx, *exception, &error)) {
+ return NS_OK;
+ }
+
+ // Step 2: Perform ! CrossRealmTransformSendError(port, error).
+ CrossRealmTransformSendError(cx, mPort, error);
+
+ // Step 3: Perform ! ReadableStreamDefaultControllerError(controller,
+ // error).
+ ReadableStreamDefaultControllerError(cx, mController, error,
+ IgnoreErrors());
+
+ // Step 4: Disentangle port.
+ // (Close() does it)
+ mPort->Close();
+ cleanupPort.release();
+
+ return NS_OK;
+ }
+
+ private:
+ ~SetUpTransformReadableMessageErrorEventListener() = default;
+
+ RefPtr<ReadableStreamDefaultController> mController;
+ RefPtr<MessagePort> mPort;
+};
+
+NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageErrorEventListener,
+ mController, mPort)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformReadableMessageErrorEventListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(
+ SetUpTransformReadableMessageErrorEventListener)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+ SetUpTransformReadableMessageErrorEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+NS_INTERFACE_MAP_END
+
+// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
+class CrossRealmReadableUnderlyingSourceAlgorithms final
+ : public UnderlyingSourceAlgorithmsBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
+ CrossRealmReadableUnderlyingSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+
+ explicit CrossRealmReadableUnderlyingSourceAlgorithms(MessagePort* aPort)
+ : mPort(aPort) {}
+
+ void StartCallback(JSContext* aCx, ReadableStreamController& aController,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv) override {
+ // Step 6. Let startAlgorithm be an algorithm that returns undefined.
+ aRetVal.setUndefined();
+ }
+
+ already_AddRefed<Promise> PullCallback(JSContext* aCx,
+ ReadableStreamController& aController,
+ ErrorResult& aRv) override {
+ // Step 7: Let pullAlgorithm be the following steps:
+
+ // Step 7.1: Perform ! PackAndPostMessage(port, "pull", undefined).
+ PackAndPostMessage(aCx, mPort, u"pull"_ns, JS::UndefinedHandleValue, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 7.2: Return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
+ }
+
+ already_AddRefed<Promise> CancelCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override {
+ // Step 8: Let cancelAlgorithm be the following steps, taking a reason
+ // argument:
+
+ // Step 8.1: Let result be PackAndPostMessageHandlingError(port, "error",
+ // reason).
+ JS::Rooted<JS::Value> error(aCx);
+ bool result = PackAndPostMessageHandlingError(
+ aCx, mPort, u"error"_ns,
+ aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue,
+ &error);
+
+ // Step 8.2: Disentangle port.
+ // (Close() does it)
+ mPort->Close();
+
+ // Step 8.3: If result is an abrupt completion, return a promise rejected
+ // with result.[[Value]].
+ if (!result) {
+ return Promise::CreateRejected(mPort->GetParentObject(), error, aRv);
+ }
+
+ // Step 8.4: Otherwise, return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
+ }
+
+ protected:
+ ~CrossRealmReadableUnderlyingSourceAlgorithms() override = default;
+
+ private:
+ RefPtr<MessagePort> mPort;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase, mPort)
+NS_IMPL_ADDREF_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+ CrossRealmReadableUnderlyingSourceAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsBase)
+
+// https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
+MOZ_CAN_RUN_SCRIPT static void SetUpCrossRealmTransformReadable(
+ ReadableStream* aReadable, MessagePort* aPort, ErrorResult& aRv) {
+ // (This is only needed for step 10, but let's do this early to fail early
+ // enough)
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(aReadable->GetParentObject())) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+
+ // Step 1: Perform ! InitializeReadableStream(stream).
+ // (This is implicitly done by the constructor)
+
+ // Step 2: Let controller be a new ReadableStreamDefaultController.
+ auto controller =
+ MakeRefPtr<ReadableStreamDefaultController>(aReadable->GetParentObject());
+
+ // Step 3: Add a handler for port’s message event with the following steps:
+ auto listener =
+ MakeRefPtr<SetUpTransformReadableMessageEventListener>(controller, aPort);
+ aPort->AddEventListener(u"message"_ns, listener, false);
+
+ // Step 4: Add a handler for port’s messageerror event with the following
+ // steps:
+ auto errorListener =
+ MakeRefPtr<SetUpTransformReadableMessageErrorEventListener>(controller,
+ aPort);
+ aPort->AddEventListener(u"messageerror"_ns, errorListener, false);
+
+ // Step 5: Enable port’s port message queue.
+ // (Start() does it)
+ aPort->Start();
+
+ // Step 6-8:
+ auto algorithms =
+ MakeRefPtr<CrossRealmReadableUnderlyingSourceAlgorithms>(aPort);
+
+ // Step 9: Let sizeAlgorithm be an algorithm that returns 1.
+ // (nullptr should serve this purpose. See also ReadableStream::Constructor)
+
+ // Step 10: Perform ! SetUpReadableStreamDefaultController(stream, controller,
+ // startAlgorithm, pullAlgorithm, cancelAlgorithm, 0, sizeAlgorithm).
+ SetUpReadableStreamDefaultController(cx, aReadable, controller, algorithms, 0,
+ /* aSizeAlgorithm */ nullptr, aRv);
+}
+
+// https://streams.spec.whatwg.org/#ref-for-transfer-steps
+bool ReadableStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId) {
+ // Step 1: If ! IsReadableStreamLocked(value) is true, throw a
+ // "DataCloneError" DOMException.
+ // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
+ // check here as the state might have changed in case this ReadableStream is
+ // created by a TransferStream and being transferred together with the
+ // parent.)
+ if (IsReadableStreamLocked(this)) {
+ return false;
+ }
+
+ // Step 2: Let port1 be a new MessagePort in the current Realm.
+ // Step 3: Let port2 be a new MessagePort in the current Realm.
+ // Step 4: Entangle port1 and port2.
+ // (The MessageChannel constructor does exactly that.)
+ // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messagechannel
+ ErrorResult rv;
+ RefPtr<dom::MessageChannel> channel =
+ dom::MessageChannel::Constructor(mGlobal, rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+
+ // Step 5: Let writable be a new WritableStream in the current Realm.
+ RefPtr<WritableStream> writable = new WritableStream(
+ mGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit);
+
+ // Step 6: Perform ! SetUpCrossRealmTransformWritable(writable, port1).
+ // MOZ_KnownLive because Port1 never changes before CC
+ SetUpCrossRealmTransformWritable(writable, MOZ_KnownLive(channel->Port1()),
+ rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+
+ // Step 7: Let promise be ! ReadableStreamPipeTo(value, writable, false,
+ // false, false).
+ RefPtr<Promise> promise =
+ ReadableStreamPipeTo(this, writable, false, false, false, nullptr, rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+
+ // Step 8: Set promise.[[PromiseIsHandled]] to true.
+ MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
+
+ // Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
+ // « port2 »).
+ channel->Port2()->CloneAndDisentangle(aPortId);
+
+ return true;
+}
+
+// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps
+MOZ_CAN_RUN_SCRIPT already_AddRefed<ReadableStream>
+ReadableStream::ReceiveTransferImpl(JSContext* aCx, nsIGlobalObject* aGlobal,
+ MessagePort& aPort) {
+ // Step 1: Let deserializedRecord be
+ // ! StructuredDeserializeWithTransfer(dataHolder.[[port]], the current
+ // Realm).
+ // Step 2: Let port be deserializedRecord.[[Deserialized]].
+
+ // Step 3: Perform ! SetUpCrossRealmTransformReadable(value, port).
+ RefPtr<ReadableStream> readable =
+ new ReadableStream(aGlobal, HoldDropJSObjectsCaller::Implicit);
+ ErrorResult rv;
+ SetUpCrossRealmTransformReadable(readable, &aPort, rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ return nullptr;
+ }
+ return readable.forget();
+}
+
+bool ReadableStream::ReceiveTransfer(
+ JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort,
+ JS::MutableHandle<JSObject*> aReturnObject) {
+ RefPtr<ReadableStream> readable =
+ ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort);
+ if (!readable) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, readable, &value)) {
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+ aReturnObject.set(&value.toObject());
+
+ return true;
+}
+
+// https://streams.spec.whatwg.org/#ref-for-transfer-steps①
+bool WritableStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId) {
+ // Step 1: If ! IsWritableStreamLocked(value) is true, throw a
+ // "DataCloneError" DOMException.
+ // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
+ // check here as the state might have changed in case this WritableStream is
+ // created by a TransferStream and being transferred together with the
+ // parent.)
+ if (IsWritableStreamLocked(this)) {
+ return false;
+ }
+
+ // Step 2: Let port1 be a new MessagePort in the current Realm.
+ // Step 3: Let port2 be a new MessagePort in the current Realm.
+ // Step 4: Entangle port1 and port2.
+ // (The MessageChannel constructor does exactly that.)
+ // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messagechannel
+ ErrorResult rv;
+ RefPtr<dom::MessageChannel> channel =
+ dom::MessageChannel::Constructor(mGlobal, rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+
+ // Step 5: Let readable be a new ReadableStream in the current Realm.
+ RefPtr<ReadableStream> readable = new ReadableStream(
+ mGlobal, ReadableStream::HoldDropJSObjectsCaller::Implicit);
+
+ // Step 6: Perform ! SetUpCrossRealmTransformReadable(readable, port1).
+ // MOZ_KnownLive because Port1 never changes before CC
+ SetUpCrossRealmTransformReadable(readable, MOZ_KnownLive(channel->Port1()),
+ rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+
+ // Step 7: Let promise be ! ReadableStreamPipeTo(readable, value, false,
+ // false, false).
+ RefPtr<Promise> promise =
+ ReadableStreamPipeTo(readable, this, false, false, false, nullptr, rv);
+ if (rv.Failed()) {
+ return false;
+ }
+
+ // Step 8: Set promise.[[PromiseIsHandled]] to true.
+ MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
+
+ // Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
+ // « port2 »).
+ channel->Port2()->CloneAndDisentangle(aPortId);
+
+ return true;
+}
+
+// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps①
+MOZ_CAN_RUN_SCRIPT already_AddRefed<WritableStream>
+WritableStream::ReceiveTransferImpl(JSContext* aCx, nsIGlobalObject* aGlobal,
+ MessagePort& aPort) {
+ // Step 1: Let deserializedRecord be !
+ // StructuredDeserializeWithTransfer(dataHolder.[[port]], the current Realm).
+ // Step 2: Let port be a deserializedRecord.[[Deserialized]].
+
+ // Step 3: Perform ! SetUpCrossRealmTransformWritable(value, port).
+ RefPtr<WritableStream> writable = new WritableStream(
+ aGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit);
+ ErrorResult rv;
+ SetUpCrossRealmTransformWritable(writable, &aPort, rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ return nullptr;
+ }
+ return writable.forget();
+}
+
+// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps①
+bool WritableStream::ReceiveTransfer(
+ JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort,
+ JS::MutableHandle<JSObject*> aReturnObject) {
+ RefPtr<WritableStream> writable =
+ WritableStream::ReceiveTransferImpl(aCx, aGlobal, aPort);
+ if (!writable) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, writable, &value)) {
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+ aReturnObject.set(&value.toObject());
+
+ return true;
+}
+
+// https://streams.spec.whatwg.org/#ref-for-transfer-steps②
+bool TransformStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId1,
+ UniqueMessagePortId& aPortId2) {
+ // Step 1: Let readable be value.[[readable]].
+ // Step 2: Let writable be value.[[writable]].
+ // Step 3: If ! IsReadableStreamLocked(readable) is true, throw a
+ // "DataCloneError" DOMException.
+ // Step 4: If ! IsWritableStreamLocked(writable) is true, throw a
+ // "DataCloneError" DOMException.
+ // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
+ // check here as the state might have changed by
+ // Readable/WritableStream::Transfer in case the stream members of this
+ // TransformStream are being transferred together.)
+ if (IsReadableStreamLocked(mReadable) || IsWritableStreamLocked(mWritable)) {
+ return false;
+ }
+
+ // Step 5: Set dataHolder.[[readable]] to !
+ // StructuredSerializeWithTransfer(readable, « readable »).
+ // TODO: Mark mReadable as MOZ_KNOWN_LIVE again (bug 1769854)
+ if (!MOZ_KnownLive(mReadable)->Transfer(aCx, aPortId1)) {
+ return false;
+ }
+
+ // Step 6: Set dataHolder.[[writable]] to !
+ // StructuredSerializeWithTransfer(writable, « writable »).
+ // TODO: Mark mReadable as MOZ_KNOWN_LIVE again (bug 1769854)
+ return MOZ_KnownLive(mWritable)->Transfer(aCx, aPortId2);
+}
+
+// https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps②
+bool TransformStream::ReceiveTransfer(
+ JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort1,
+ MessagePort& aPort2, JS::MutableHandle<JSObject*> aReturnObject) {
+ // Step 1: Let readableRecord be !
+ // StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current
+ // Realm).
+ RefPtr<ReadableStream> readable =
+ ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort1);
+ if (!readable) {
+ return false;
+ }
+
+ // Step 2: Let writableRecord be !
+ // StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current
+ // Realm).
+ RefPtr<WritableStream> writable =
+ WritableStream::ReceiveTransferImpl(aCx, aGlobal, aPort2);
+ if (!writable) {
+ return false;
+ }
+
+ // Step 3: Set value.[[readable]] to readableRecord.[[Deserialized]].
+ // Step 4: Set value.[[writable]] to writableRecord.[[Deserialized]].
+ // Step 5: Set value.[[backpressure]], value.[[backpressureChangePromise]],
+ // and value.[[controller]] to undefined.
+ RefPtr<TransformStream> stream =
+ new TransformStream(aGlobal, readable, writable);
+ JS::Rooted<JS::Value> value(aCx);
+ if (!GetOrCreateDOMReflector(aCx, stream, &value)) {
+ JS_ClearPendingException(aCx);
+ return false;
+ }
+ aReturnObject.set(&value.toObject());
+
+ return true;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/TransformStream.cpp b/dom/streams/TransformStream.cpp
new file mode 100644
index 0000000000..e517ecda89
--- /dev/null
+++ b/dom/streams/TransformStream.cpp
@@ -0,0 +1,706 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TransformStream.h"
+
+#include "StreamUtils.h"
+#include "TransformerCallbackHelpers.h"
+#include "UnderlyingSourceCallbackHelpers.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/TransformStreamBinding.h"
+#include "mozilla/dom/TransformerBinding.h"
+#include "nsWrapperCache.h"
+
+// XXX: GCC somehow does not allow attributes before lambda return types, while
+// clang requires so. See also bug 1627007.
+#ifdef __clang__
+# define MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA MOZ_CAN_RUN_SCRIPT_BOUNDARY
+#else
+# define MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA
+#endif
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransformStream, mGlobal,
+ mBackpressureChangePromise, mController,
+ mReadable, mWritable)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TransformStream)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TransformStream)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformStream)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// https://streams.spec.whatwg.org/#transformstream-set-up
+// (except this instead creates a new TransformStream rather than accepting an
+// existing instance)
+already_AddRefed<TransformStream> TransformStream::CreateGeneric(
+ const GlobalObject& aGlobal, TransformerAlgorithmsWrapper& aAlgorithms,
+ ErrorResult& aRv) {
+ // Step 1. Let writableHighWaterMark be 1.
+ double writableHighWaterMark = 1;
+
+ // Step 2. Let writableSizeAlgorithm be an algorithm that returns 1.
+ // Note: Callers should recognize nullptr as a callback that returns 1. See
+ // also WritableStream::Constructor for this design decision.
+ RefPtr<QueuingStrategySize> writableSizeAlgorithm;
+
+ // Step 3. Let readableHighWaterMark be 0.
+ double readableHighWaterMark = 0;
+
+ // Step 4. Let readableSizeAlgorithm be an algorithm that returns 1.
+ // Note: Callers should recognize nullptr as a callback that returns 1. See
+ // also ReadableStream::Constructor for this design decision.
+ RefPtr<QueuingStrategySize> readableSizeAlgorithm;
+
+ // Step 5. Let transformAlgorithmWrapper be an algorithm that runs these steps
+ // given a value chunk:
+ // Step 6. Let flushAlgorithmWrapper be an algorithm that runs these steps:
+ // (Done by TransformerAlgorithmsWrapper)
+
+ // Step 7. Let startPromise be a promise resolved with undefined.
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<Promise> startPromise =
+ Promise::CreateResolvedWithUndefined(global, aRv);
+ if (!startPromise) {
+ return nullptr;
+ }
+
+ // Step 8. Perform ! InitializeTransformStream(stream, startPromise,
+ // writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark,
+ // readableSizeAlgorithm).
+ RefPtr<TransformStream> stream =
+ new TransformStream(global, nullptr, nullptr);
+ stream->Initialize(aGlobal.Context(), startPromise, writableHighWaterMark,
+ writableSizeAlgorithm, readableHighWaterMark,
+ readableSizeAlgorithm, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 9. Let controller be a new TransformStreamDefaultController.
+ auto controller = MakeRefPtr<TransformStreamDefaultController>(global);
+
+ // Step 10. Perform ! SetUpTransformStreamDefaultController(stream,
+ // controller, transformAlgorithmWrapper, flushAlgorithmWrapper).
+ SetUpTransformStreamDefaultController(aGlobal.Context(), *stream, *controller,
+ aAlgorithms);
+
+ return stream.forget();
+}
+
+TransformStream::TransformStream(nsIGlobalObject* aGlobal) : mGlobal(aGlobal) {
+ mozilla::HoldJSObjects(this);
+}
+
+TransformStream::TransformStream(nsIGlobalObject* aGlobal,
+ ReadableStream* aReadable,
+ WritableStream* aWritable)
+ : mGlobal(aGlobal), mReadable(aReadable), mWritable(aWritable) {
+ mozilla::HoldJSObjects(this);
+}
+
+TransformStream::~TransformStream() { mozilla::DropJSObjects(this); }
+
+JSObject* TransformStream::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return TransformStream_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#transform-stream-error-writable-and-unblock-write
+void TransformStreamErrorWritableAndUnblockWrite(JSContext* aCx,
+ TransformStream* aStream,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ // Step 1: Perform !
+ // TransformStreamDefaultControllerClearAlgorithms(stream.[[controller]]).
+ aStream->Controller()->SetAlgorithms(nullptr);
+
+ // Step 2: Perform !
+ // WritableStreamDefaultControllerErrorIfNeeded(stream.[[writable]].[[controller]],
+ // e).
+ // TODO: Remove MOZ_KnownLive (bug 1761577)
+ WritableStreamDefaultControllerErrorIfNeeded(
+ aCx, MOZ_KnownLive(aStream->Writable()->Controller()), aError, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 3: If stream.[[backpressure]] is true, perform !
+ // TransformStreamSetBackpressure(stream, false).
+ if (aStream->Backpressure()) {
+ aStream->SetBackpressure(false);
+ }
+}
+
+// https://streams.spec.whatwg.org/#transform-stream-error
+void TransformStreamError(JSContext* aCx, TransformStream* aStream,
+ JS::Handle<JS::Value> aError, ErrorResult& aRv) {
+ // Step 1: Perform !
+ // ReadableStreamDefaultControllerError(stream.[[readable]].[[controller]],
+ // e).
+ ReadableStreamDefaultControllerError(
+ aCx, aStream->Readable()->Controller()->AsDefault(), aError, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 2: Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, e).
+ TransformStreamErrorWritableAndUnblockWrite(aCx, aStream, aError, aRv);
+}
+
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#transform-stream-default-controller-perform-transform
+MOZ_CAN_RUN_SCRIPT static already_AddRefed<Promise>
+TransformStreamDefaultControllerPerformTransform(
+ JSContext* aCx, TransformStreamDefaultController* aController,
+ JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
+ // Step 1: Let transformPromise be the result of performing
+ // controller.[[transformAlgorithm]], passing chunk.
+ RefPtr<TransformerAlgorithmsBase> algorithms = aController->Algorithms();
+ RefPtr<Promise> transformPromise =
+ algorithms->TransformCallback(aCx, aChunk, *aController, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 2: Return the result of reacting to transformPromise with the
+ // following rejection steps given the argument r:
+ auto result = transformPromise->CatchWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aError, ErrorResult& aRv,
+ const RefPtr<TransformStreamDefaultController>& aController)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA -> already_AddRefed<Promise> {
+ // Step 2.1: Perform ! TransformStreamError(controller.[[stream]],
+ // r).
+ // TODO: Remove MOZ_KnownLive (bug 1761577)
+ TransformStreamError(aCx, MOZ_KnownLive(aController->Stream()),
+ aError, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 2.2: Throw r.
+ JS::Rooted<JS::Value> r(aCx, aError);
+ aRv.MightThrowJSException();
+ aRv.ThrowJSException(aCx, r);
+ return nullptr;
+ },
+ RefPtr(aController));
+ if (result.isErr()) {
+ aRv.Throw(result.unwrapErr());
+ return nullptr;
+ }
+ return result.unwrap().forget();
+}
+
+// https://streams.spec.whatwg.org/#initialize-transform-stream
+class TransformStreamUnderlyingSinkAlgorithms final
+ : public UnderlyingSinkAlgorithmsBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
+ TransformStreamUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
+
+ TransformStreamUnderlyingSinkAlgorithms(Promise* aStartPromise,
+ TransformStream* aStream)
+ : mStartPromise(aStartPromise), mStream(aStream) {}
+
+ void StartCallback(JSContext* aCx,
+ WritableStreamDefaultController& aController,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv) override {
+ // Step 1. Let startAlgorithm be an algorithm that returns startPromise.
+ // (Same as TransformStreamUnderlyingSourceAlgorithms::StartCallback)
+ aRetVal.setObject(*mStartPromise->PromiseObj());
+ }
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aRv) override {
+ // Step 2. Let writeAlgorithm be the following steps, taking a chunk
+ // argument:
+ // Step 2.1. Return ! TransformStreamDefaultSinkWriteAlgorithm(stream,
+ // chunk).
+
+ // Inlining TransformStreamDefaultSinkWriteAlgorithm here:
+ // https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm
+
+ // Step 1: Assert: stream.[[writable]].[[state]] is "writable".
+ MOZ_ASSERT(mStream->Writable()->State() ==
+ WritableStream::WriterState::Writable);
+
+ // Step 2: Let controller be stream.[[controller]].
+ RefPtr<TransformStreamDefaultController> controller = mStream->Controller();
+
+ // Step 3: If stream.[[backpressure]] is true,
+ if (mStream->Backpressure()) {
+ // Step 3.1: Let backpressureChangePromise be
+ // stream.[[backpressureChangePromise]].
+ RefPtr<Promise> backpressureChangePromise =
+ mStream->BackpressureChangePromise();
+
+ // Step 3.2: Assert: backpressureChangePromise is not undefined.
+ MOZ_ASSERT(backpressureChangePromise);
+
+ // Step 3.3: Return the result of reacting to backpressureChangePromise
+ // with the following fulfillment steps:
+ auto result = backpressureChangePromise->ThenWithCycleCollectedArgsJS(
+ [](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv,
+ const RefPtr<TransformStream>& aStream,
+ const RefPtr<TransformStreamDefaultController>& aController,
+ JS::Handle<JS::Value> aChunk)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA -> already_AddRefed<Promise> {
+ // Step 1: Let writable be stream.[[writable]].
+ RefPtr<WritableStream> writable = aStream->Writable();
+
+ // Step 2: Let state be writable.[[state]].
+ WritableStream::WriterState state = writable->State();
+
+ // Step 3: If state is "erroring", throw
+ // writable.[[storedError]].
+ if (state == WritableStream::WriterState::Erroring) {
+ JS::Rooted<JS::Value> storedError(aCx,
+ writable->StoredError());
+ aRv.MightThrowJSException();
+ aRv.ThrowJSException(aCx, storedError);
+ return nullptr;
+ }
+
+ // Step 4: Assert: state is "writable".
+ MOZ_ASSERT(state == WritableStream::WriterState::Writable);
+
+ // Step 5: Return !
+ // TransformStreamDefaultControllerPerformTransform(controller,
+ // chunk).
+ return TransformStreamDefaultControllerPerformTransform(
+ aCx, aController, aChunk, aRv);
+ },
+ std::make_tuple(mStream, controller), std::make_tuple(aChunk));
+
+ if (result.isErr()) {
+ aRv.Throw(result.unwrapErr());
+ return nullptr;
+ }
+ return result.unwrap().forget();
+ }
+
+ // Step 4: Return !
+ // TransformStreamDefaultControllerPerformTransform(controller, chunk).
+ return TransformStreamDefaultControllerPerformTransform(aCx, controller,
+ aChunk, aRv);
+ }
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> AbortCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override {
+ // Step 3. Let abortAlgorithm be the following steps, taking a reason
+ // argument:
+ // Step 3.1. Return ! TransformStreamDefaultSinkAbortAlgorithm(stream,
+ // reason).
+
+ // Inlining TransformStreamDefaultSinkAbortAlgorithm here:
+ // https://streams.spec.whatwg.org/#transform-stream-default-sink-abort-algorithm
+
+ // Step 1:Perform ! TransformStreamError(stream, reason).
+ TransformStreamError(
+ aCx, mStream,
+ aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 2: Return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(),
+ aRv);
+ }
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CloseCallback(
+ JSContext* aCx, ErrorResult& aRv) override {
+ // Step 4. Let closeAlgorithm be the following steps:
+ // Step 4.1. Return ! TransformStreamDefaultSinkCloseAlgorithm(stream).
+
+ // Inlining TransformStreamDefaultSinkCloseAlgorithm here:
+ // https://streams.spec.whatwg.org/#transform-stream-default-sink-close-algorithm
+
+ // Step 1: Let readable be stream.[[readable]].
+ RefPtr<ReadableStream> readable = mStream->Readable();
+
+ // Step 2: Let controller be stream.[[controller]].
+ RefPtr<TransformStreamDefaultController> controller = mStream->Controller();
+
+ // Step 3: Let flushPromise be the result of performing
+ // controller.[[flushAlgorithm]].
+ RefPtr<TransformerAlgorithmsBase> algorithms = controller->Algorithms();
+ RefPtr<Promise> flushPromise =
+ algorithms->FlushCallback(aCx, *controller, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 4: Perform !
+ // TransformStreamDefaultControllerClearAlgorithms(controller).
+ controller->SetAlgorithms(nullptr);
+
+ // Step 5: Return the result of reacting to flushPromise:
+ Result<RefPtr<Promise>, nsresult> result =
+ flushPromise->ThenCatchWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ const RefPtr<ReadableStream>& aReadable,
+ const RefPtr<TransformStream>& aStream)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA
+ -> already_AddRefed<Promise> {
+ // Step 5.1: If flushPromise was fulfilled, then:
+
+ // Step 5.1.1: If readable.[[state]] is "errored", throw
+ // readable.[[storedError]].
+ if (aReadable->State() ==
+ ReadableStream::ReaderState::Errored) {
+ JS::Rooted<JS::Value> storedError(aCx,
+ aReadable->StoredError());
+ aRv.MightThrowJSException();
+ aRv.ThrowJSException(aCx, storedError);
+ return nullptr;
+ }
+
+ // Step 5.1.2: Perform !
+ // ReadableStreamDefaultControllerClose(readable.[[controller]]).
+ ReadableStreamDefaultControllerClose(
+ aCx, MOZ_KnownLive(aReadable->Controller()->AsDefault()),
+ aRv);
+ return nullptr;
+ },
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ const RefPtr<ReadableStream>& aReadable,
+ const RefPtr<TransformStream>& aStream)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA
+ -> already_AddRefed<Promise> {
+ // Step 5.2: If flushPromise was rejected with reason r, then:
+
+ // Step 5.2.1: Perform ! TransformStreamError(stream, r).
+ TransformStreamError(aCx, aStream, aValue, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 5.2.2: Throw readable.[[storedError]].
+ JS::Rooted<JS::Value> storedError(aCx,
+ aReadable->StoredError());
+ aRv.MightThrowJSException();
+ aRv.ThrowJSException(aCx, storedError);
+ return nullptr;
+ },
+ readable, mStream);
+
+ if (result.isErr()) {
+ aRv.Throw(result.unwrapErr());
+ return nullptr;
+ }
+ return result.unwrap().forget();
+ }
+
+ protected:
+ ~TransformStreamUnderlyingSinkAlgorithms() override = default;
+
+ private:
+ RefPtr<Promise> mStartPromise;
+ // MOZ_KNOWN_LIVE because it won't be reassigned
+ MOZ_KNOWN_LIVE RefPtr<TransformStream> mStream;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(TransformStreamUnderlyingSinkAlgorithms,
+ UnderlyingSinkAlgorithmsBase, mStartPromise,
+ mStream)
+NS_IMPL_ADDREF_INHERITED(TransformStreamUnderlyingSinkAlgorithms,
+ UnderlyingSinkAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(TransformStreamUnderlyingSinkAlgorithms,
+ UnderlyingSinkAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformStreamUnderlyingSinkAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase)
+
+// https://streams.spec.whatwg.org/#initialize-transform-stream
+class TransformStreamUnderlyingSourceAlgorithms final
+ : public UnderlyingSourceAlgorithmsBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
+ TransformStreamUnderlyingSourceAlgorithms, UnderlyingSourceAlgorithmsBase)
+
+ TransformStreamUnderlyingSourceAlgorithms(Promise* aStartPromise,
+ TransformStream* aStream)
+ : mStartPromise(aStartPromise), mStream(aStream) {}
+
+ void StartCallback(JSContext* aCx, ReadableStreamController& aController,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv) override {
+ // Step 1. Let startAlgorithm be an algorithm that returns startPromise.
+ // (Same as TransformStreamUnderlyingSinkAlgorithms::StartCallback)
+ aRetVal.setObject(*mStartPromise->PromiseObj());
+ }
+
+ already_AddRefed<Promise> PullCallback(JSContext* aCx,
+ ReadableStreamController& aController,
+ ErrorResult& aRv) override {
+ // Step 6. Let pullAlgorithm be the following steps:
+ // Step 6.1. Return ! TransformStreamDefaultSourcePullAlgorithm(stream).
+
+ // Inlining TransformStreamDefaultSourcePullAlgorithm here:
+ // https://streams.spec.whatwg.org/#transform-stream-default-source-pull-algorithm
+
+ // Step 1: Assert: stream.[[backpressure]] is true.
+ MOZ_ASSERT(mStream->Backpressure());
+
+ // Step 2: Assert: stream.[[backpressureChangePromise]] is not undefined.
+ MOZ_ASSERT(mStream->BackpressureChangePromise());
+
+ // Step 3: Perform ! TransformStreamSetBackpressure(stream, false).
+ mStream->SetBackpressure(false);
+
+ // Step 4: Return stream.[[backpressureChangePromise]].
+ return do_AddRef(mStream->BackpressureChangePromise());
+ }
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CancelCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override {
+ // Step 7. Let cancelAlgorithm be the following steps, taking a reason
+ // argument:
+ // Step 7.1. Perform ! TransformStreamErrorWritableAndUnblockWrite(stream,
+ // reason).
+ TransformStreamErrorWritableAndUnblockWrite(
+ aCx, mStream,
+ aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 7.2. Return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mStream->GetParentObject(),
+ aRv);
+ }
+
+ protected:
+ ~TransformStreamUnderlyingSourceAlgorithms() override = default;
+
+ private:
+ RefPtr<Promise> mStartPromise;
+ // MOZ_KNOWNLIVE because it will never be reassigned
+ MOZ_KNOWN_LIVE RefPtr<TransformStream> mStream;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(TransformStreamUnderlyingSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase,
+ mStartPromise, mStream)
+NS_IMPL_ADDREF_INHERITED(TransformStreamUnderlyingSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(TransformStreamUnderlyingSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
+ TransformStreamUnderlyingSourceAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsBase)
+
+// https://streams.spec.whatwg.org/#transform-stream-set-backpressure
+void TransformStream::SetBackpressure(bool aBackpressure) {
+ // Step 1. Assert: stream.[[backpressure]] is not backpressure.
+ MOZ_ASSERT(Backpressure() != aBackpressure);
+
+ // Step 2. If stream.[[backpressureChangePromise]] is not undefined, resolve
+ // stream.[[backpressureChangePromise]] with undefined.
+ if (Promise* promise = BackpressureChangePromise()) {
+ promise->MaybeResolveWithUndefined();
+ }
+
+ // Step 3. Set stream.[[backpressureChangePromise]] to a new promise.
+ RefPtr<Promise> promise = Promise::CreateInfallible(GetParentObject());
+ mBackpressureChangePromise = promise;
+
+ // Step 4. Set stream.[[backpressure]] to backpressure.
+ mBackpressure = aBackpressure;
+}
+
+// https://streams.spec.whatwg.org/#initialize-transform-stream
+void TransformStream::Initialize(JSContext* aCx, Promise* aStartPromise,
+ double aWritableHighWaterMark,
+ QueuingStrategySize* aWritableSizeAlgorithm,
+ double aReadableHighWaterMark,
+ QueuingStrategySize* aReadableSizeAlgorithm,
+ ErrorResult& aRv) {
+ // Step 1 - 4
+ auto sinkAlgorithms =
+ MakeRefPtr<TransformStreamUnderlyingSinkAlgorithms>(aStartPromise, this);
+
+ // Step 5. Set stream.[[writable]] to ! CreateWritableStream(startAlgorithm,
+ // writeAlgorithm, closeAlgorithm, abortAlgorithm, writableHighWaterMark,
+ // writableSizeAlgorithm).
+ mWritable = WritableStream::CreateAbstract(
+ aCx, MOZ_KnownLive(mGlobal), sinkAlgorithms, aWritableHighWaterMark,
+ aWritableSizeAlgorithm, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 6 - 7
+ auto sourceAlgorithms = MakeRefPtr<TransformStreamUnderlyingSourceAlgorithms>(
+ aStartPromise, this);
+
+ // Step 8. Set stream.[[readable]] to ! CreateReadableStream(startAlgorithm,
+ // pullAlgorithm, cancelAlgorithm, readableHighWaterMark,
+ // readableSizeAlgorithm).
+ mReadable = ReadableStream::CreateAbstract(
+ aCx, MOZ_KnownLive(mGlobal), sourceAlgorithms,
+ Some(aReadableHighWaterMark), aReadableSizeAlgorithm, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 9. Set stream.[[backpressure]] and
+ // stream.[[backpressureChangePromise]] to undefined.
+ // Note(krosylight): The spec allows setting [[backpressure]] as undefined,
+ // but I don't see why it should be. Since the spec also allows strict boolean
+ // type, and this is only to not trigger assertion inside the setter, we just
+ // set it as false.
+ mBackpressure = false;
+ mBackpressureChangePromise = nullptr;
+
+ // Step 10. Perform ! TransformStreamSetBackpressure(stream, true).
+ SetBackpressure(true);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 11. Set stream.[[controller]] to undefined.
+ mController = nullptr;
+}
+
+// https://streams.spec.whatwg.org/#ts-constructor
+already_AddRefed<TransformStream> TransformStream::Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<JS::Handle<JSObject*>>& aTransformer,
+ const QueuingStrategy& aWritableStrategy,
+ const QueuingStrategy& aReadableStrategy, ErrorResult& aRv) {
+ // Step 1. If transformer is missing, set it to null.
+ JS::Rooted<JSObject*> transformerObj(
+ aGlobal.Context(),
+ aTransformer.WasPassed() ? aTransformer.Value() : nullptr);
+
+ // Step 2. Let transformerDict be transformer, converted to an IDL value of
+ // type Transformer.
+ RootedDictionary<Transformer> transformerDict(aGlobal.Context());
+ if (transformerObj) {
+ JS::Rooted<JS::Value> objValue(aGlobal.Context(),
+ JS::ObjectValue(*transformerObj));
+ dom::BindingCallContext callCx(aGlobal.Context(),
+ "TransformStream.constructor");
+ aRv.MightThrowJSException();
+ if (!transformerDict.Init(callCx, objValue)) {
+ aRv.StealExceptionFromJSContext(aGlobal.Context());
+ return nullptr;
+ }
+ }
+
+ // Step 3. If transformerDict["readableType"] exists, throw a RangeError
+ // exception.
+ if (!transformerDict.mReadableType.isUndefined()) {
+ aRv.ThrowRangeError(
+ "`readableType` is unsupported and preserved for future use");
+ return nullptr;
+ }
+
+ // Step 4. If transformerDict["writableType"] exists, throw a RangeError
+ // exception.
+ if (!transformerDict.mWritableType.isUndefined()) {
+ aRv.ThrowRangeError(
+ "`writableType` is unsupported and preserved for future use");
+ return nullptr;
+ }
+
+ // Step 5. Let readableHighWaterMark be ?
+ // ExtractHighWaterMark(readableStrategy, 0).
+ double readableHighWaterMark =
+ ExtractHighWaterMark(aReadableStrategy, 0, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 6. Let readableSizeAlgorithm be !
+ // ExtractSizeAlgorithm(readableStrategy).
+ // Note: Callers should recognize nullptr as a callback that returns 1. See
+ // also ReadableStream::Constructor for this design decision.
+ RefPtr<QueuingStrategySize> readableSizeAlgorithm =
+ aReadableStrategy.mSize.WasPassed() ? &aReadableStrategy.mSize.Value()
+ : nullptr;
+
+ // Step 7. Let writableHighWaterMark be ?
+ // ExtractHighWaterMark(writableStrategy, 1).
+ double writableHighWaterMark =
+ ExtractHighWaterMark(aWritableStrategy, 1, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 8. Let writableSizeAlgorithm be !
+ // ExtractSizeAlgorithm(writableStrategy).
+ // Note: Callers should recognize nullptr as a callback that returns 1. See
+ // also WritableStream::Constructor for this design decision.
+ RefPtr<QueuingStrategySize> writableSizeAlgorithm =
+ aWritableStrategy.mSize.WasPassed() ? &aWritableStrategy.mSize.Value()
+ : nullptr;
+
+ // Step 9. Let startPromise be a new promise.
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<Promise> startPromise = Promise::CreateInfallible(global);
+
+ // Step 10. Perform ! InitializeTransformStream(this, startPromise,
+ // writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark,
+ // readableSizeAlgorithm).
+ RefPtr<TransformStream> transformStream = new TransformStream(global);
+ transformStream->Initialize(
+ aGlobal.Context(), startPromise, writableHighWaterMark,
+ writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 11. Perform ?
+ // SetUpTransformStreamDefaultControllerFromTransformer(this, transformer,
+ // transformerDict).
+ SetUpTransformStreamDefaultControllerFromTransformer(
+ aGlobal.Context(), *transformStream, transformerObj, transformerDict);
+
+ // Step 12. If transformerDict["start"] exists, then resolve startPromise with
+ // the result of invoking transformerDict["start"] with argument list «
+ // this.[[controller]] » and callback this value transformer.
+ if (transformerDict.mStart.WasPassed()) {
+ RefPtr<TransformerStartCallback> callback = transformerDict.mStart.Value();
+ RefPtr<TransformStreamDefaultController> controller =
+ transformStream->Controller();
+ JS::Rooted<JS::Value> retVal(aGlobal.Context());
+ callback->Call(transformerObj, *controller, &retVal, aRv,
+ "Transformer.start", CallbackFunction::eRethrowExceptions);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ startPromise->MaybeResolve(retVal);
+ } else {
+ // Step 13. Otherwise, resolve startPromise with undefined.
+ startPromise->MaybeResolveWithUndefined();
+ }
+
+ return transformStream.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/TransformStream.h b/dom/streams/TransformStream.h
new file mode 100644
index 0000000000..ed9ec6f812
--- /dev/null
+++ b/dom/streams/TransformStream.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 DOM_STREAMS_TRANSFORMSTREAM_H_
+#define DOM_STREAMS_TRANSFORMSTREAM_H_
+
+#include "TransformStreamDefaultController.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+
+#include "mozilla/dom/TransformerBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class WritableStream;
+class ReadableStream;
+class UniqueMessagePortId;
+class MessagePort;
+class TransformerAlgorithmsWrapper;
+
+class TransformStream final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(TransformStream)
+
+ // https://streams.spec.whatwg.org/#transformstream-set-up
+ // Intended to be used by interfaces using GenericTransformStream mixin.
+ MOZ_CAN_RUN_SCRIPT static already_AddRefed<TransformStream> CreateGeneric(
+ const GlobalObject& aGlobal, TransformerAlgorithmsWrapper& aAlgorithms,
+ ErrorResult& aRv);
+
+ // Internal slot accessors
+ bool Backpressure() const { return mBackpressure; }
+ Promise* BackpressureChangePromise() { return mBackpressureChangePromise; }
+ void SetBackpressure(bool aBackpressure);
+ MOZ_KNOWN_LIVE TransformStreamDefaultController* Controller() {
+ return mController;
+ }
+ void SetController(TransformStreamDefaultController& aController) {
+ MOZ_ASSERT(!mController);
+ mController = &aController;
+ }
+
+ // [Transferable]
+ // https://html.spec.whatwg.org/multipage/structured-data.html#transfer-steps
+ MOZ_CAN_RUN_SCRIPT bool Transfer(JSContext* aCx,
+ UniqueMessagePortId& aPortId1,
+ UniqueMessagePortId& aPortId2);
+ // https://html.spec.whatwg.org/multipage/structured-data.html#transfer-receiving-steps
+ static MOZ_CAN_RUN_SCRIPT bool ReceiveTransfer(
+ JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort1,
+ MessagePort& aPort2, JS::MutableHandle<JSObject*> aReturnObject);
+
+ protected:
+ TransformStream(nsIGlobalObject* aGlobal, ReadableStream* aReadable,
+ WritableStream* aWritable);
+ explicit TransformStream(nsIGlobalObject* aGlobal);
+
+ ~TransformStream();
+
+ MOZ_CAN_RUN_SCRIPT void Initialize(
+ JSContext* aCx, Promise* aStartPromise, double aWritableHighWaterMark,
+ QueuingStrategySize* aWritableSizeAlgorithm,
+ double aReadableHighWaterMark,
+ QueuingStrategySize* aReadableSizeAlgorithm, ErrorResult& aRv);
+
+ public:
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL methods
+ // TODO: Mark as MOZ_CAN_RUN_SCRIPT when IDL constructors can be (bug 1749042)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static already_AddRefed<TransformStream>
+ Constructor(const GlobalObject& aGlobal,
+ const Optional<JS::Handle<JSObject*>>& aTransformer,
+ const QueuingStrategy& aWritableStrategy,
+ const QueuingStrategy& aReadableStrategy, ErrorResult& aRv);
+
+ ReadableStream* Readable() const { return mReadable; }
+ WritableStream* Writable() const { return mWritable; }
+
+ private:
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ // Internal slots
+ // MOZ_KNOWN_LIVE for slots that will never be reassigned
+ bool mBackpressure = false;
+ RefPtr<Promise> mBackpressureChangePromise;
+ RefPtr<TransformStreamDefaultController> mController;
+ RefPtr<ReadableStream> mReadable;
+ RefPtr<WritableStream> mWritable;
+};
+
+namespace streams_abstract {
+
+MOZ_CAN_RUN_SCRIPT void TransformStreamErrorWritableAndUnblockWrite(
+ JSContext* aCx, TransformStream* aStream, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void TransformStreamError(JSContext* aCx,
+ TransformStream* aStream,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv);
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif // DOM_STREAMS_TRANSFORMSTREAM_H_
diff --git a/dom/streams/TransformStreamDefaultController.cpp b/dom/streams/TransformStreamDefaultController.cpp
new file mode 100644
index 0000000000..bbcb7f94f2
--- /dev/null
+++ b/dom/streams/TransformStreamDefaultController.cpp
@@ -0,0 +1,238 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/TransformStreamDefaultController.h"
+
+#include "TransformerCallbackHelpers.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamDefaultController.h"
+#include "mozilla/dom/TransformStream.h"
+#include "mozilla/dom/TransformStreamDefaultControllerBinding.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TransformStreamDefaultController, mGlobal,
+ mStream, mTransformerAlgorithms)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TransformStreamDefaultController)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TransformStreamDefaultController)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformStreamDefaultController)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+TransformStream* TransformStreamDefaultController::Stream() { return mStream; }
+
+void TransformStreamDefaultController::SetStream(TransformStream& aStream) {
+ MOZ_ASSERT(!mStream);
+ mStream = &aStream;
+}
+
+TransformerAlgorithmsBase* TransformStreamDefaultController::Algorithms() {
+ return mTransformerAlgorithms;
+}
+
+void TransformStreamDefaultController::SetAlgorithms(
+ TransformerAlgorithmsBase* aTransformerAlgorithms) {
+ mTransformerAlgorithms = aTransformerAlgorithms;
+}
+
+TransformStreamDefaultController::TransformStreamDefaultController(
+ nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal) {
+ mozilla::HoldJSObjects(this);
+}
+
+TransformStreamDefaultController::~TransformStreamDefaultController() {
+ mozilla::DropJSObjects(this);
+}
+
+JSObject* TransformStreamDefaultController::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return TransformStreamDefaultController_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://streams.spec.whatwg.org/#ts-default-controller-desired-size
+Nullable<double> TransformStreamDefaultController::GetDesiredSize() const {
+ // Step 1. Let readableController be
+ // this.[[stream]].[[readable]].[[controller]].
+ RefPtr<ReadableStreamDefaultController> readableController =
+ mStream->Readable()->Controller()->AsDefault();
+
+ // Step 2. Return !
+ // ReadableStreamDefaultControllerGetDesiredSize(readableController).
+ return ReadableStreamDefaultControllerGetDesiredSize(readableController);
+}
+
+// https://streams.spec.whatwg.org/#rs-default-controller-has-backpressure
+// Looks like a readable stream thing but the spec explicitly says this is for
+// TransformStream.
+static bool ReadableStreamDefaultControllerHasBackpressure(
+ ReadableStreamDefaultController* aController) {
+ // Step 1: If ! ReadableStreamDefaultControllerShouldCallPull(controller) is
+ // true, return false.
+ // Step 2: Otherwise, return true.
+ return !ReadableStreamDefaultControllerShouldCallPull(aController);
+}
+
+void TransformStreamDefaultController::Enqueue(JSContext* aCx,
+ JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv) {
+ // Step 1: Perform ? TransformStreamDefaultControllerEnqueue(this, chunk).
+
+ // Inlining TransformStreamDefaultControllerEnqueue here.
+ // https://streams.spec.whatwg.org/#transform-stream-default-controller-enqueue
+
+ // Step 1: Let stream be controller.[[stream]].
+ RefPtr<TransformStream> stream = mStream;
+
+ // Step 2: Let readableController be stream.[[readable]].[[controller]].
+ RefPtr<ReadableStreamDefaultController> readableController =
+ stream->Readable()->Controller()->AsDefault();
+
+ // Step 3: If !
+ // ReadableStreamDefaultControllerCanCloseOrEnqueue(readableController) is
+ // false, throw a TypeError exception.
+ if (!ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow(
+ readableController, CloseOrEnqueue::Enqueue, aRv)) {
+ return;
+ }
+
+ // Step 4: Let enqueueResult be
+ // ReadableStreamDefaultControllerEnqueue(readableController, chunk).
+ ErrorResult rv;
+ ReadableStreamDefaultControllerEnqueue(aCx, readableController, aChunk, rv);
+
+ // Step 5: If enqueueResult is an abrupt completion,
+ if (rv.MaybeSetPendingException(aCx)) {
+ JS::Rooted<JS::Value> error(aCx);
+ if (!JS_GetPendingException(aCx, &error)) {
+ // Uncatchable exception; we should mark aRv and return.
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+ JS_ClearPendingException(aCx);
+
+ // Step 5.1: Perform ! TransformStreamErrorWritableAndUnblockWrite(stream,
+ // enqueueResult.[[Value]]).
+ TransformStreamErrorWritableAndUnblockWrite(aCx, stream, error, aRv);
+
+ // Step 5.2: Throw stream.[[readable]].[[storedError]].
+ JS::Rooted<JS::Value> storedError(aCx, stream->Readable()->StoredError());
+ aRv.MightThrowJSException();
+ aRv.ThrowJSException(aCx, storedError);
+ return;
+ }
+
+ // Step 6: Let backpressure be !
+ // ReadableStreamDefaultControllerHasBackpressure(readableController).
+ bool backpressure =
+ ReadableStreamDefaultControllerHasBackpressure(readableController);
+
+ // Step 7: If backpressure is not stream.[[backpressure]],
+ if (backpressure != stream->Backpressure()) {
+ // Step 7.1: Assert: backpressure is true.
+ MOZ_ASSERT(backpressure);
+
+ // Step 7.2: Perform ! TransformStreamSetBackpressure(true).
+ stream->SetBackpressure(true);
+ }
+}
+
+// https://streams.spec.whatwg.org/#ts-default-controller-error
+void TransformStreamDefaultController::Error(JSContext* aCx,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ // Step 1: Perform ? TransformStreamDefaultControllerError(this, e).
+
+ // Inlining TransformStreamDefaultControllerError here.
+ // https://streams.spec.whatwg.org/#transform-stream-default-controller-error
+
+ // Perform ! TransformStreamError(controller.[[stream]], e).
+ // mStream is set in initialization step and only modified in cycle
+ // collection.
+ // TODO: Move mStream initialization to a method/constructor and make it
+ // MOZ_KNOWN_LIVE again. (See bug 1769854)
+ TransformStreamError(aCx, MOZ_KnownLive(mStream), aError, aRv);
+}
+
+// https://streams.spec.whatwg.org/#ts-default-controller-terminate
+
+void TransformStreamDefaultController::Terminate(JSContext* aCx,
+ ErrorResult& aRv) {
+ // Step 1: Perform ? TransformStreamDefaultControllerTerminate(this).
+
+ // Inlining TransformStreamDefaultControllerTerminate here.
+ // https://streams.spec.whatwg.org/#transform-stream-default-controller-terminate
+
+ // Step 1: Let stream be controller.[[stream]].
+ RefPtr<TransformStream> stream = mStream;
+
+ // Step 2: Let readableController be stream.[[readable]].[[controller]].
+ RefPtr<ReadableStreamDefaultController> readableController =
+ stream->Readable()->Controller()->AsDefault();
+
+ // Step 3: Perform ! ReadableStreamDefaultControllerClose(readableController).
+ ReadableStreamDefaultControllerClose(aCx, readableController, aRv);
+
+ // Step 4: Let error be a TypeError exception indicating that the stream has
+ // been terminated.
+ ErrorResult rv;
+ rv.ThrowTypeError("Terminating the stream");
+ JS::Rooted<JS::Value> error(aCx);
+ MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &error));
+
+ // Step 5: Perform ! TransformStreamErrorWritableAndUnblockWrite(stream,
+ // error).
+ TransformStreamErrorWritableAndUnblockWrite(aCx, stream, error, aRv);
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller
+void SetUpTransformStreamDefaultController(
+ JSContext* aCx, TransformStream& aStream,
+ TransformStreamDefaultController& aController,
+ TransformerAlgorithmsBase& aTransformerAlgorithms) {
+ // Step 1. Assert: stream implements TransformStream.
+ // Step 2. Assert: stream.[[controller]] is undefined.
+ MOZ_ASSERT(!aStream.Controller());
+
+ // Step 3. Set controller.[[stream]] to stream.
+ aController.SetStream(aStream);
+
+ // Step 4. Set stream.[[controller]] to controller.
+ aStream.SetController(aController);
+
+ // Step 5. Set controller.[[transformAlgorithm]] to transformAlgorithm.
+ // Step 6. Set controller.[[flushAlgorithm]] to flushAlgorithm.
+ aController.SetAlgorithms(&aTransformerAlgorithms);
+}
+
+// https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer
+void SetUpTransformStreamDefaultControllerFromTransformer(
+ JSContext* aCx, TransformStream& aStream,
+ JS::Handle<JSObject*> aTransformer, Transformer& aTransformerDict) {
+ // Step 1. Let controller be a new TransformStreamDefaultController.
+ auto controller =
+ MakeRefPtr<TransformStreamDefaultController>(aStream.GetParentObject());
+
+ // Step 2 - 5:
+ auto algorithms = MakeRefPtr<TransformerAlgorithms>(
+ aStream.GetParentObject(), aTransformer, aTransformerDict);
+
+ // Step 6: Perform ! SetUpTransformStreamDefaultController(stream, controller,
+ // transformAlgorithm, flushAlgorithm).
+ SetUpTransformStreamDefaultController(aCx, aStream, *controller, *algorithms);
+}
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
diff --git a/dom/streams/TransformStreamDefaultController.h b/dom/streams/TransformStreamDefaultController.h
new file mode 100644
index 0000000000..71b92f69fe
--- /dev/null
+++ b/dom/streams/TransformStreamDefaultController.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 DOM_STREAMS_TRANSFORMSTREAMDEFAULTCONTROLLER_H_
+#define DOM_STREAMS_TRANSFORMSTREAMDEFAULTCONTROLLER_H_
+
+#include "js/TypeDecls.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+
+#include "mozilla/dom/TransformerBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class TransformStream;
+class TransformerAlgorithmsBase;
+
+class TransformStreamDefaultController final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(TransformStreamDefaultController)
+
+ MOZ_KNOWN_LIVE TransformStream* Stream();
+ void SetStream(TransformStream& aStream);
+ TransformerAlgorithmsBase* Algorithms();
+ void SetAlgorithms(TransformerAlgorithmsBase* aTransformerAlgorithms);
+
+ explicit TransformStreamDefaultController(nsIGlobalObject* aGlobal);
+
+ protected:
+ ~TransformStreamDefaultController();
+
+ public:
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ Nullable<double> GetDesiredSize() const;
+
+ MOZ_CAN_RUN_SCRIPT void Enqueue(JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void Error(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT void Terminate(JSContext* aCx, ErrorResult& aRv);
+
+ private:
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ // Internal slots
+ RefPtr<TransformStream> mStream;
+ RefPtr<TransformerAlgorithmsBase> mTransformerAlgorithms;
+};
+
+namespace streams_abstract {
+void SetUpTransformStreamDefaultController(
+ JSContext* aCx, TransformStream& aStream,
+ TransformStreamDefaultController& aController,
+ TransformerAlgorithmsBase& aTransformerAlgorithms);
+
+void SetUpTransformStreamDefaultControllerFromTransformer(
+ JSContext* aCx, TransformStream& aStream,
+ JS::Handle<JSObject*> aTransformer, Transformer& aTransformerDict);
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif // DOM_STREAMS_TRANSFORMSTREAMDEFAULTCONTROLLER_H_
diff --git a/dom/streams/TransformerCallbackHelpers.cpp b/dom/streams/TransformerCallbackHelpers.cpp
new file mode 100644
index 0000000000..606c24adda
--- /dev/null
+++ b/dom/streams/TransformerCallbackHelpers.cpp
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TransformerCallbackHelpers.h"
+
+#include "StreamUtils.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/TransformStreamDefaultController.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION(TransformerAlgorithmsBase)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(TransformerAlgorithmsBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(TransformerAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformerAlgorithmsBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(
+ TransformerAlgorithms, TransformerAlgorithmsBase,
+ (mGlobal, mTransformCallback, mFlushCallback), (mTransformer))
+NS_IMPL_ADDREF_INHERITED(TransformerAlgorithms, TransformerAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(TransformerAlgorithms, TransformerAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformerAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(TransformerAlgorithmsBase)
+
+// https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer
+already_AddRefed<Promise> TransformerAlgorithms::TransformCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ TransformStreamDefaultController& aController, ErrorResult& aRv) {
+ if (!mTransformCallback) {
+ // Step 2.1. Let result be
+ // TransformStreamDefaultControllerEnqueue(controller, chunk).
+ aController.Enqueue(aCx, aChunk, aRv);
+
+ // Step 2.2. If result is an abrupt completion, return a promise rejected
+ // with result.[[Value]].
+ if (aRv.MaybeSetPendingException(aCx)) {
+ JS::Rooted<JS::Value> error(aCx);
+ if (!JS_GetPendingException(aCx, &error)) {
+ // Uncatchable exception; we should mark aRv and return.
+ aRv.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+ JS_ClearPendingException(aCx);
+
+ return Promise::CreateRejected(aController.GetParentObject(), error, aRv);
+ }
+
+ // Step 2.3. Otherwise, return a promise resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(aController.GetParentObject(),
+ aRv);
+ }
+ // Step 4. If transformerDict["transform"] exists, set transformAlgorithm to
+ // an algorithm which takes an argument chunk and returns the result of
+ // invoking transformerDict["transform"] with argument list « chunk,
+ // controller » and callback this value transformer.
+ JS::Rooted<JSObject*> thisObj(aCx, mTransformer);
+ return MOZ_KnownLive(mTransformCallback)
+ ->Call(thisObj, aChunk, aController, aRv,
+ "TransformStreamDefaultController.[[transformAlgorithm]]",
+ CallbackObject::eRethrowExceptions);
+}
+
+// https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer
+already_AddRefed<Promise> TransformerAlgorithms::FlushCallback(
+ JSContext* aCx, TransformStreamDefaultController& aController,
+ ErrorResult& aRv) {
+ if (!mFlushCallback) {
+ // Step 3. Let flushAlgorithm be an algorithm which returns a promise
+ // resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(aController.GetParentObject(),
+ aRv);
+ }
+ // Step 5. If transformerDict["flush"] exists, set flushAlgorithm to an
+ // algorithm which returns the result of invoking transformerDict["flush"]
+ // with argument list « controller » and callback this value transformer.
+ JS::Rooted<JSObject*> thisObj(aCx, mTransformer);
+ return MOZ_KnownLive(mFlushCallback)
+ ->Call(thisObj, aController, aRv,
+ "TransformStreamDefaultController.[[flushAlgorithm]]",
+ CallbackObject::eRethrowExceptions);
+}
+
+already_AddRefed<Promise> TransformerAlgorithmsWrapper::TransformCallback(
+ JSContext*, JS::Handle<JS::Value> aChunk,
+ TransformStreamDefaultController& aController, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = aController.GetParentObject();
+ return PromisifyAlgorithm(
+ global,
+ [this, &aChunk, &aController](ErrorResult& aRv)
+ MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ return TransformCallbackImpl(aChunk, aController, aRv);
+ },
+ aRv);
+}
+
+already_AddRefed<Promise> TransformerAlgorithmsWrapper::FlushCallback(
+ JSContext*, TransformStreamDefaultController& aController,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = aController.GetParentObject();
+ return PromisifyAlgorithm(
+ global,
+ [this, &aController](ErrorResult& aRv) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ return FlushCallbackImpl(aController, aRv);
+ },
+ aRv);
+}
diff --git a/dom/streams/TransformerCallbackHelpers.h b/dom/streams/TransformerCallbackHelpers.h
new file mode 100644
index 0000000000..8b12a31b0f
--- /dev/null
+++ b/dom/streams/TransformerCallbackHelpers.h
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 DOM_STREAMS_TRANSFORMERCALLBACKHELPERS_H_
+#define DOM_STREAMS_TRANSFORMERCALLBACKHELPERS_H_
+
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/TransformerBinding.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla::dom {
+
+class Promise;
+
+class TransformerAlgorithmsBase : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(TransformerAlgorithmsBase)
+
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> TransformCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ TransformStreamDefaultController& aController, ErrorResult& aRv) = 0;
+
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> FlushCallback(
+ JSContext* aCx, TransformStreamDefaultController& aController,
+ ErrorResult& aRv) = 0;
+
+ protected:
+ virtual ~TransformerAlgorithmsBase() = default;
+};
+
+// https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer
+class TransformerAlgorithms final : public TransformerAlgorithmsBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ TransformerAlgorithms, TransformerAlgorithmsBase)
+
+ TransformerAlgorithms(nsIGlobalObject* aGlobal,
+ JS::Handle<JSObject*> aTransformer,
+ Transformer& aTransformerDict)
+ : mGlobal(aGlobal), mTransformer(aTransformer) {
+ // Step 4. (Step 2 is implicitly done through the initialization of
+ // mTransformCallback to null)
+ if (aTransformerDict.mTransform.WasPassed()) {
+ mTransformCallback = aTransformerDict.mTransform.Value();
+ }
+
+ // Step 5. (Step 3 is implicitly done through the initialization of
+ // mTransformCallback to null)
+ if (aTransformerDict.mFlush.WasPassed()) {
+ mFlushCallback = aTransformerDict.mFlush.Value();
+ }
+
+ mozilla::HoldJSObjects(this);
+ };
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> TransformCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ TransformStreamDefaultController& aController, ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> FlushCallback(
+ JSContext* aCx, TransformStreamDefaultController& aController,
+ ErrorResult& aRv) override;
+
+ protected:
+ ~TransformerAlgorithms() { mozilla::DropJSObjects(this); }
+
+ private:
+ // Virtually const, but are cycle collected
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ JS::Heap<JSObject*> mTransformer;
+ MOZ_KNOWN_LIVE RefPtr<TransformerTransformCallback> mTransformCallback;
+ MOZ_KNOWN_LIVE RefPtr<TransformerFlushCallback> mFlushCallback;
+};
+
+// https://streams.spec.whatwg.org/#transformstream-set-up
+class TransformerAlgorithmsWrapper : public TransformerAlgorithmsBase {
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> TransformCallback(
+ JSContext*, JS::Handle<JS::Value> aChunk,
+ TransformStreamDefaultController& aController, ErrorResult& aRv) final;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> FlushCallback(
+ JSContext*, TransformStreamDefaultController& aController,
+ ErrorResult& aRv) final;
+
+ MOZ_CAN_RUN_SCRIPT virtual void TransformCallbackImpl(
+ JS::Handle<JS::Value> aChunk,
+ TransformStreamDefaultController& aController, ErrorResult& aRv) = 0;
+
+ MOZ_CAN_RUN_SCRIPT virtual void FlushCallbackImpl(
+ TransformStreamDefaultController& aController, ErrorResult& aRv) {
+ // flushAlgorithm is optional, do nothing by default
+ }
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_STREAMS_TRANSFORMERCALLBACKHELPERS_H_
diff --git a/dom/streams/UnderlyingSinkCallbackHelpers.cpp b/dom/streams/UnderlyingSinkCallbackHelpers.cpp
new file mode 100644
index 0000000000..91562a2db3
--- /dev/null
+++ b/dom/streams/UnderlyingSinkCallbackHelpers.cpp
@@ -0,0 +1,274 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/UnderlyingSinkCallbackHelpers.h"
+#include "StreamUtils.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/WebTransportError.h"
+#include "nsHttp.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION(UnderlyingSinkAlgorithmsBase)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(UnderlyingSinkAlgorithmsBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(UnderlyingSinkAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithmsBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(
+ UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase,
+ (mGlobal, mStartCallback, mWriteCallback, mCloseCallback, mAbortCallback),
+ (mUnderlyingSink))
+NS_IMPL_ADDREF_INHERITED(UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(UnderlyingSinkAlgorithms,
+ UnderlyingSinkAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSinkAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase)
+
+// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
+void UnderlyingSinkAlgorithms::StartCallback(
+ JSContext* aCx, WritableStreamDefaultController& aController,
+ JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) {
+ if (!mStartCallback) {
+ // Step 2: Let startAlgorithm be an algorithm that returns undefined.
+ aRetVal.setUndefined();
+ return;
+ }
+
+ // Step 6: If underlyingSinkDict["start"] exists, then set startAlgorithm to
+ // an algorithm which returns the result of invoking
+ // underlyingSinkDict["start"] with argument list « controller » and callback
+ // this value underlyingSink.
+ JS::Rooted<JSObject*> thisObj(aCx, mUnderlyingSink);
+ return mStartCallback->Call(thisObj, aController, aRetVal, aRv,
+ "UnderlyingSink.start",
+ CallbackFunction::eRethrowExceptions);
+}
+
+// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
+already_AddRefed<Promise> UnderlyingSinkAlgorithms::WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aRv) {
+ if (!mWriteCallback) {
+ // Step 3: Let writeAlgorithm be an algorithm that returns a promise
+ // resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mGlobal, aRv);
+ }
+
+ // Step 7: If underlyingSinkDict["write"] exists, then set writeAlgorithm to
+ // an algorithm which takes an argument chunk and returns the result of
+ // invoking underlyingSinkDict["write"] with argument list « chunk, controller
+ // » and callback this value underlyingSink.
+ JS::Rooted<JSObject*> thisObj(aCx, mUnderlyingSink);
+ RefPtr<Promise> promise = mWriteCallback->Call(
+ thisObj, aChunk, aController, aRv, "UnderlyingSink.write",
+ CallbackFunction::eRethrowExceptions);
+ return promise.forget();
+}
+
+// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
+already_AddRefed<Promise> UnderlyingSinkAlgorithms::CloseCallback(
+ JSContext* aCx, ErrorResult& aRv) {
+ if (!mCloseCallback) {
+ // Step 4: Let closeAlgorithm be an algorithm that returns a promise
+ // resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mGlobal, aRv);
+ }
+
+ // Step 8: If underlyingSinkDict["close"] exists, then set closeAlgorithm to
+ // an algorithm which returns the result of invoking
+ // underlyingSinkDict["close"] with argument list «» and callback this value
+ // underlyingSink.
+ JS::Rooted<JSObject*> thisObj(aCx, mUnderlyingSink);
+ RefPtr<Promise> promise =
+ mCloseCallback->Call(thisObj, aRv, "UnderlyingSink.close",
+ CallbackFunction::eRethrowExceptions);
+ return promise.forget();
+}
+
+// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
+already_AddRefed<Promise> UnderlyingSinkAlgorithms::AbortCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) {
+ if (!mAbortCallback) {
+ // Step 5: Let abortAlgorithm be an algorithm that returns a promise
+ // resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mGlobal, aRv);
+ }
+
+ // Step 9: Let abortAlgorithm be an algorithm that returns a promise resolved
+ // with undefined.
+ JS::Rooted<JSObject*> thisObj(aCx, mUnderlyingSink);
+ RefPtr<Promise> promise =
+ mAbortCallback->Call(thisObj, aReason, aRv, "UnderlyingSink.abort",
+ CallbackFunction::eRethrowExceptions);
+
+ return promise.forget();
+}
+
+// https://streams.spec.whatwg.org/#writable-set-up
+// Step 2.1: Let closeAlgorithmWrapper be an algorithm that runs these steps:
+already_AddRefed<Promise> UnderlyingSinkAlgorithmsWrapper::CloseCallback(
+ JSContext* aCx, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
+ return PromisifyAlgorithm(
+ global, [&](ErrorResult& aRv) { return CloseCallbackImpl(aCx, aRv); },
+ aRv);
+}
+
+// https://streams.spec.whatwg.org/#writable-set-up
+// Step 3.1: Let abortAlgorithmWrapper be an algorithm that runs these steps:
+already_AddRefed<Promise> UnderlyingSinkAlgorithmsWrapper::AbortCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
+ return PromisifyAlgorithm(
+ global,
+ [&](ErrorResult& aRv) { return AbortCallbackImpl(aCx, aReason, aRv); },
+ aRv);
+}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(WritableStreamToOutput,
+ UnderlyingSinkAlgorithmsBase,
+ nsIOutputStreamCallback)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(WritableStreamToOutput,
+ UnderlyingSinkAlgorithmsBase, mParent,
+ mOutput, mPromise)
+
+NS_IMETHODIMP
+WritableStreamToOutput::OnOutputStreamReady(nsIAsyncOutputStream* aStream) {
+ if (!mData) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(mPromise);
+ uint32_t written = 0;
+ nsresult rv = mOutput->Write(
+ reinterpret_cast<const char*>(mData->Elements() + mWritten),
+ mData->Length() - mWritten, &written);
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ mPromise->MaybeRejectWithAbortError("Error writing to stream"_ns);
+ ClearData();
+ // XXX should we add mErrored and fail future calls immediately?
+ // I presume new calls to Write() will fail though, too
+ return rv;
+ }
+ if (NS_SUCCEEDED(rv)) {
+ mWritten += written;
+ MOZ_ASSERT(mWritten <= mData->Length());
+ if (mWritten >= mData->Length()) {
+ mPromise->MaybeResolveWithUndefined();
+ ClearData();
+ return NS_OK;
+ }
+ // more to write
+ }
+ // wrote partial or nothing
+ // Wait for space
+ nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget();
+ rv = mOutput->AsyncWait(this, 0, 0, target);
+ if (NS_FAILED(rv)) {
+ mPromise->MaybeRejectWithUnknownError("error waiting to write data");
+ ClearData();
+ // XXX should we add mErrored and fail future calls immediately?
+ // New calls to Write() will fail, note
+ // See step 5.2 of
+ // https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write.
+ return rv;
+ }
+ return NS_OK;
+}
+
+already_AddRefed<Promise> WritableStreamToOutput::WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aError) {
+ ArrayBufferViewOrArrayBuffer data;
+ if (!data.Init(aCx, aChunk)) {
+ aError.StealExceptionFromJSContext(aCx);
+ return nullptr;
+ }
+ // buffer/bufferView
+ MOZ_ASSERT(data.IsArrayBuffer() || data.IsArrayBufferView());
+
+ RefPtr<Promise> promise = Promise::Create(mParent, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+
+ // Try to write first, and only enqueue data if we were already blocked
+ // or the write didn't write it all. This avoids allocations and copies
+ // in common cases.
+ MOZ_ASSERT(!mPromise);
+ MOZ_ASSERT(mWritten == 0);
+ uint32_t written = 0;
+ ProcessTypedArraysFixed(data, [&](const Span<uint8_t>& aData) {
+ Span<uint8_t> dataSpan = aData;
+ nsresult rv = mOutput->Write(mozilla::AsChars(dataSpan).Elements(),
+ dataSpan.Length(), &written);
+ if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) {
+ promise->MaybeRejectWithAbortError("error writing data");
+ return;
+ }
+ if (NS_SUCCEEDED(rv)) {
+ if (written == dataSpan.Length()) {
+ promise->MaybeResolveWithUndefined();
+ return;
+ }
+ dataSpan = dataSpan.From(written);
+ }
+
+ auto buffer = Buffer<uint8_t>::CopyFrom(dataSpan);
+ if (buffer.isNothing()) {
+ promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ mData = std::move(buffer);
+ });
+
+ if (promise->State() != Promise::PromiseState::Pending) {
+ return promise.forget();
+ }
+
+ mPromise = promise;
+
+ nsCOMPtr<nsIEventTarget> target = mozilla::GetCurrentSerialEventTarget();
+ nsresult rv = mOutput->AsyncWait(this, 0, 0, target);
+ if (NS_FAILED(rv)) {
+ ClearData();
+ promise->MaybeRejectWithUnknownError("error waiting to write data");
+ }
+ return promise.forget();
+}
+
+already_AddRefed<Promise> WritableStreamToOutput::AbortCallbackImpl(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) {
+ // https://streams.spec.whatwg.org/#writablestream-set-up
+ // Step 3. Let abortAlgorithmWrapper be an algorithm that runs these steps:
+
+ if (aReason.WasPassed() && aReason.Value().isObject()) {
+ JS::Rooted<JSObject*> obj(aCx, &aReason.Value().toObject());
+ RefPtr<WebTransportError> error;
+ UnwrapObject<prototypes::id::WebTransportError, WebTransportError>(
+ obj, error, nullptr);
+ if (error) {
+ mOutput->CloseWithStatus(net::GetNSResultFromWebTransportError(
+ error->GetStreamErrorCode().Value()));
+ return nullptr;
+ }
+ }
+
+ // XXX The close or rather a dedicated abort should be async. For now we have
+ // to always fall back to the Step 3.3 below.
+ // XXX how do we know this stream is used by webtransport?
+ mOutput->CloseWithStatus(NS_ERROR_WEBTRANSPORT_CODE_BASE);
+
+ // Step 3.3. Return a promise resolved with undefined.
+ // Wrapper handles this
+ return nullptr;
+}
+
+void WritableStreamToOutput::ReleaseObjects() { mOutput->Close(); }
diff --git a/dom/streams/UnderlyingSinkCallbackHelpers.h b/dom/streams/UnderlyingSinkCallbackHelpers.h
new file mode 100644
index 0000000000..c99c8709ce
--- /dev/null
+++ b/dom/streams/UnderlyingSinkCallbackHelpers.h
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_UnderlyingSinkCallbackHelpers_h
+#define mozilla_dom_UnderlyingSinkCallbackHelpers_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Buffer.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/UnderlyingSinkBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsISupportsImpl.h"
+#include "nsIAsyncOutputStream.h"
+
+/*
+ * See the comment in UnderlyingSourceCallbackHelpers.h!
+ *
+ * A native implementation of these callbacks is however currently not required.
+ */
+namespace mozilla::dom {
+
+class WritableStreamDefaultController;
+
+class UnderlyingSinkAlgorithmsBase : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(UnderlyingSinkAlgorithmsBase)
+
+ MOZ_CAN_RUN_SCRIPT virtual void StartCallback(
+ JSContext* aCx, WritableStreamDefaultController& aController,
+ JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) = 0;
+
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aRv) = 0;
+
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> CloseCallback(
+ JSContext* aCx, ErrorResult& aRv) = 0;
+
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> AbortCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) = 0;
+
+ // Implement this when you need to release underlying resources immediately
+ // from closed/errored(aborted) streams, without waiting for GC.
+ virtual void ReleaseObjects() {}
+
+ protected:
+ virtual ~UnderlyingSinkAlgorithmsBase() = default;
+};
+
+// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
+class UnderlyingSinkAlgorithms final : public UnderlyingSinkAlgorithmsBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ UnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
+
+ UnderlyingSinkAlgorithms(nsIGlobalObject* aGlobal,
+ JS::Handle<JSObject*> aUnderlyingSink,
+ UnderlyingSink& aUnderlyingSinkDict)
+ : mGlobal(aGlobal), mUnderlyingSink(aUnderlyingSink) {
+ // Step 6. (implicit Step 2.)
+ if (aUnderlyingSinkDict.mStart.WasPassed()) {
+ mStartCallback = aUnderlyingSinkDict.mStart.Value();
+ }
+
+ // Step 7. (implicit Step 3.)
+ if (aUnderlyingSinkDict.mWrite.WasPassed()) {
+ mWriteCallback = aUnderlyingSinkDict.mWrite.Value();
+ }
+
+ // Step 8. (implicit Step 4.)
+ if (aUnderlyingSinkDict.mClose.WasPassed()) {
+ mCloseCallback = aUnderlyingSinkDict.mClose.Value();
+ }
+
+ // Step 9. (implicit Step 5.)
+ if (aUnderlyingSinkDict.mAbort.WasPassed()) {
+ mAbortCallback = aUnderlyingSinkDict.mAbort.Value();
+ }
+
+ mozilla::HoldJSObjects(this);
+ };
+
+ MOZ_CAN_RUN_SCRIPT void StartCallback(
+ JSContext* aCx, WritableStreamDefaultController& aController,
+ JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CloseCallback(
+ JSContext* aCx, ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> AbortCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override;
+
+ protected:
+ ~UnderlyingSinkAlgorithms() override { mozilla::DropJSObjects(this); }
+
+ private:
+ // Virtually const, but are cycle collected
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ JS::Heap<JSObject*> mUnderlyingSink;
+ MOZ_KNOWN_LIVE RefPtr<UnderlyingSinkStartCallback> mStartCallback;
+ MOZ_KNOWN_LIVE RefPtr<UnderlyingSinkWriteCallback> mWriteCallback;
+ MOZ_KNOWN_LIVE RefPtr<UnderlyingSinkCloseCallback> mCloseCallback;
+ MOZ_KNOWN_LIVE RefPtr<UnderlyingSinkAbortCallback> mAbortCallback;
+};
+
+// https://streams.spec.whatwg.org/#writablestream-set-up
+// Wrappers defined by the "Set up" methods in the spec.
+// (closeAlgorithmWrapper, abortAlgorithmWrapper)
+// This helps you just return nullptr when 1) the algorithm is synchronous, or
+// 2) an error occurred, as this wrapper will return a resolved or rejected
+// promise respectively.
+// Note that StartCallback is only for JS consumers to access the
+// controller, and thus is no-op here since native consumers can call
+// `ErrorNative()` etc. without direct controller access.
+class UnderlyingSinkAlgorithmsWrapper : public UnderlyingSinkAlgorithmsBase {
+ public:
+ void StartCallback(JSContext* aCx,
+ WritableStreamDefaultController& aController,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv) final {
+ // Step 1: Let startAlgorithm be an algorithm that returns undefined.
+ aRetVal.setUndefined();
+ }
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CloseCallback(
+ JSContext* aCx, ErrorResult& aRv) final;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> AbortCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) final;
+
+ virtual already_AddRefed<Promise> CloseCallbackImpl(JSContext* aCx,
+ ErrorResult& aRv) {
+ // (closeAlgorithm is optional, give null by default)
+ return nullptr;
+ }
+
+ virtual already_AddRefed<Promise> AbortCallbackImpl(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) {
+ // (abortAlgorithm is optional, give null by default)
+ return nullptr;
+ }
+};
+
+class WritableStreamToOutput final : public UnderlyingSinkAlgorithmsWrapper,
+ public nsIOutputStreamCallback {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WritableStreamToOutput,
+ UnderlyingSinkAlgorithmsBase)
+
+ WritableStreamToOutput(nsIGlobalObject* aParent,
+ nsIAsyncOutputStream* aOutput)
+ : mWritten(0), mParent(aParent), mOutput(aOutput) {}
+
+ // Streams algorithms
+
+ already_AddRefed<Promise> WriteCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk,
+ WritableStreamDefaultController& aController, ErrorResult& aRv) override;
+
+ // No CloseCallbackImpl() since ReleaseObjects() will call Close()
+
+ already_AddRefed<Promise> AbortCallbackImpl(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override;
+
+ void ReleaseObjects() override;
+
+ private:
+ ~WritableStreamToOutput() override = default;
+
+ void ClearData() {
+ mData = Nothing();
+ mPromise = nullptr;
+ mWritten = 0;
+ }
+
+ uint32_t mWritten;
+ nsCOMPtr<nsIGlobalObject> mParent;
+ nsCOMPtr<nsIAsyncOutputStream> mOutput;
+ RefPtr<Promise> mPromise; // Resolved when entirely written
+ Maybe<Buffer<uint8_t>> mData;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/UnderlyingSourceCallbackHelpers.cpp b/dom/streams/UnderlyingSourceCallbackHelpers.cpp
new file mode 100644
index 0000000000..8435050214
--- /dev/null
+++ b/dom/streams/UnderlyingSourceCallbackHelpers.cpp
@@ -0,0 +1,584 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StreamUtils.h"
+#include "mozilla/dom/ReadableStream.h"
+#include "mozilla/dom/ReadableStreamDefaultController.h"
+#include "mozilla/dom/ReadableByteStreamController.h"
+#include "mozilla/dom/UnderlyingSourceCallbackHelpers.h"
+#include "mozilla/dom/UnderlyingSourceBinding.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "js/experimental/TypedData.h"
+#include "nsStreamUtils.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+// UnderlyingSourceAlgorithmsBase
+NS_IMPL_CYCLE_COLLECTION(UnderlyingSourceAlgorithmsBase)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(UnderlyingSourceAlgorithmsBase)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(UnderlyingSourceAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSourceAlgorithmsBase)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS(
+ UnderlyingSourceAlgorithms, UnderlyingSourceAlgorithmsBase,
+ (mGlobal, mStartCallback, mPullCallback, mCancelCallback),
+ (mUnderlyingSource))
+NS_IMPL_ADDREF_INHERITED(UnderlyingSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+NS_IMPL_RELEASE_INHERITED(UnderlyingSourceAlgorithms,
+ UnderlyingSourceAlgorithmsBase)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(UnderlyingSourceAlgorithms)
+NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsBase)
+
+// https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller-from-underlying-source
+void UnderlyingSourceAlgorithms::StartCallback(
+ JSContext* aCx, ReadableStreamController& aController,
+ JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) {
+ if (!mStartCallback) {
+ // Step 2: Let startAlgorithm be an algorithm that returns undefined.
+ aRetVal.setUndefined();
+ return;
+ }
+
+ // Step 5: If underlyingSourceDict["start"] exists, then set startAlgorithm to
+ // an algorithm which returns the result of invoking
+ // underlyingSourceDict["start"] with argument list « controller » and
+ // callback this value underlyingSource.
+ JS::Rooted<JSObject*> thisObj(aCx, mUnderlyingSource);
+ ReadableStreamDefaultControllerOrReadableByteStreamController controller;
+ if (aController.IsDefault()) {
+ controller.SetAsReadableStreamDefaultController() = aController.AsDefault();
+ } else {
+ controller.SetAsReadableByteStreamController() = aController.AsByte();
+ }
+
+ return mStartCallback->Call(thisObj, controller, aRetVal, aRv,
+ "UnderlyingSource.start",
+ CallbackFunction::eRethrowExceptions);
+}
+
+// https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller-from-underlying-source
+already_AddRefed<Promise> UnderlyingSourceAlgorithms::PullCallback(
+ JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) {
+ JS::Rooted<JSObject*> thisObj(aCx, mUnderlyingSource);
+ if (!mPullCallback) {
+ // Step 3: Let pullAlgorithm be an algorithm that returns a promise resolved
+ // with undefined.
+ return Promise::CreateResolvedWithUndefined(mGlobal, aRv);
+ }
+
+ // Step 6: If underlyingSourceDict["pull"] exists, then set pullAlgorithm to
+ // an algorithm which returns the result of invoking
+ // underlyingSourceDict["pull"] with argument list « controller » and callback
+ // this value underlyingSource.
+ ReadableStreamDefaultControllerOrReadableByteStreamController controller;
+ if (aController.IsDefault()) {
+ controller.SetAsReadableStreamDefaultController() = aController.AsDefault();
+ } else {
+ controller.SetAsReadableByteStreamController() = aController.AsByte();
+ }
+
+ RefPtr<Promise> promise =
+ mPullCallback->Call(thisObj, controller, aRv, "UnderlyingSource.pull",
+ CallbackFunction::eRethrowExceptions);
+
+ return promise.forget();
+}
+
+// https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller-from-underlying-source
+already_AddRefed<Promise> UnderlyingSourceAlgorithms::CancelCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) {
+ if (!mCancelCallback) {
+ // Step 4: Let cancelAlgorithm be an algorithm that returns a promise
+ // resolved with undefined.
+ return Promise::CreateResolvedWithUndefined(mGlobal, aRv);
+ }
+
+ // Step 7: If underlyingSourceDict["cancel"] exists, then set cancelAlgorithm
+ // to an algorithm which takes an argument reason and returns the result of
+ // invoking underlyingSourceDict["cancel"] with argument list « reason » and
+ // callback this value underlyingSource.
+ JS::Rooted<JSObject*> thisObj(aCx, mUnderlyingSource);
+ RefPtr<Promise> promise =
+ mCancelCallback->Call(thisObj, aReason, aRv, "UnderlyingSource.cancel",
+ CallbackFunction::eRethrowExceptions);
+
+ return promise.forget();
+}
+
+// Shared between:
+// https://streams.spec.whatwg.org/#readablestream-set-up
+// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
+// Step 1: Let startAlgorithm be an algorithm that returns undefined.
+void UnderlyingSourceAlgorithmsWrapper::StartCallback(
+ JSContext*, ReadableStreamController&, JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult&) {
+ aRetVal.setUndefined();
+}
+
+// Shared between:
+// https://streams.spec.whatwg.org/#readablestream-set-up
+// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
+// Step 2: Let pullAlgorithmWrapper be an algorithm that runs these steps:
+already_AddRefed<Promise> UnderlyingSourceAlgorithmsWrapper::PullCallback(
+ JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = aController.GetParentObject();
+ return PromisifyAlgorithm(
+ global,
+ [&](ErrorResult& aRv) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ return PullCallbackImpl(aCx, aController, aRv);
+ },
+ aRv);
+}
+
+// Shared between:
+// https://streams.spec.whatwg.org/#readablestream-set-up
+// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
+// Step 3: Let cancelAlgorithmWrapper be an algorithm that runs these steps:
+already_AddRefed<Promise> UnderlyingSourceAlgorithmsWrapper::CancelCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
+ return PromisifyAlgorithm(
+ global,
+ [&](ErrorResult& aRv) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ return CancelCallbackImpl(aCx, aReason, aRv);
+ },
+ aRv);
+}
+
+NS_IMPL_ISUPPORTS(InputStreamHolder, nsIInputStreamCallback)
+
+InputStreamHolder::InputStreamHolder(nsIGlobalObject* aGlobal,
+ InputToReadableStreamAlgorithms* aCallback,
+ nsIAsyncInputStream* aInput)
+ : GlobalTeardownObserver(aGlobal), mCallback(aCallback), mInput(aInput) {}
+
+void InputStreamHolder::Init(JSContext* aCx) {
+ if (!NS_IsMainThread()) {
+ // We're in a worker
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ MOZ_ASSERT(workerPrivate);
+
+ workerPrivate->AssertIsOnWorkerThread();
+
+ // Note, this will create a ref-cycle between the holder and the stream.
+ // The cycle is broken when the stream is closed or the worker begins
+ // shutting down.
+ mWorkerRef = StrongWorkerRef::Create(workerPrivate, "InputStreamHolder",
+ [self = RefPtr{this}]() {});
+ if (NS_WARN_IF(!mWorkerRef)) {
+ return;
+ }
+ }
+}
+
+InputStreamHolder::~InputStreamHolder() = default;
+
+void InputStreamHolder::DisconnectFromOwner() {
+ Shutdown();
+ GlobalTeardownObserver::DisconnectFromOwner();
+}
+
+void InputStreamHolder::Shutdown() {
+ if (mInput) {
+ mInput->Close();
+ }
+ // NOTE(krosylight): Dropping mAsyncWaitAlgorithms here means letting cycle
+ // collection happen on the underlying source, which can cause a dangling
+ // read promise that never resolves. Doing so shouldn't be a problem at
+ // shutdown phase.
+ // Note that this is currently primarily for Fetch which does not explicitly
+ // close its streams at shutdown. (i.e. to prevent memory leak for cases e.g
+ // WPT /fetch/api/basic/stream-response.any.html)
+ mAsyncWaitAlgorithms = nullptr;
+ // If we have an AsyncWait running, we'll get a callback and clear
+ // the mAsyncWaitWorkerRef
+ mWorkerRef = nullptr;
+}
+
+nsresult InputStreamHolder::AsyncWait(uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ nsresult rv = mInput->AsyncWait(this, aFlags, aRequestedCount, aEventTarget);
+ if (NS_SUCCEEDED(rv)) {
+ mAsyncWaitWorkerRef = mWorkerRef;
+ mAsyncWaitAlgorithms = mCallback;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP InputStreamHolder::OnInputStreamReady(
+ nsIAsyncInputStream* aStream) {
+ mAsyncWaitWorkerRef = nullptr;
+ mAsyncWaitAlgorithms = nullptr;
+ // We may get called back after ::Shutdown()
+ if (mCallback) {
+ return mCallback->OnInputStreamReady(aStream);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(InputToReadableStreamAlgorithms,
+ UnderlyingSourceAlgorithmsWrapper,
+ nsIInputStreamCallback)
+NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(InputToReadableStreamAlgorithms,
+ UnderlyingSourceAlgorithmsWrapper,
+ mPullPromise, mStream)
+
+InputToReadableStreamAlgorithms::InputToReadableStreamAlgorithms(
+ JSContext* aCx, nsIAsyncInputStream* aInput, ReadableStream* aStream)
+ : mOwningEventTarget(GetCurrentSerialEventTarget()),
+ mInput(new InputStreamHolder(aStream->GetParentObject(), this, aInput)),
+ mStream(aStream) {
+ mInput->Init(aCx);
+}
+
+already_AddRefed<Promise> InputToReadableStreamAlgorithms::PullCallbackImpl(
+ JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) {
+ MOZ_ASSERT(aController.IsByte());
+ ReadableStream* stream = aController.Stream();
+ MOZ_ASSERT(stream);
+
+ MOZ_DIAGNOSTIC_ASSERT(stream->Disturbed());
+
+ MOZ_DIAGNOSTIC_ASSERT(!IsClosed());
+ MOZ_ASSERT(!mPullPromise);
+ mPullPromise = Promise::CreateInfallible(aController.GetParentObject());
+
+ MOZ_DIAGNOSTIC_ASSERT(mInput);
+
+ nsresult rv = mInput->AsyncWait(0, 0, mOwningEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ErrorPropagation(aCx, stream, rv);
+ return nullptr;
+ }
+
+ // All good.
+ return do_AddRef(mPullPromise);
+}
+
+// _BOUNDARY because OnInputStreamReady doesn't have [can-run-script]
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+InputToReadableStreamAlgorithms::OnInputStreamReady(
+ nsIAsyncInputStream* aStream) {
+ MOZ_DIAGNOSTIC_ASSERT(aStream);
+
+ // Already closed. We have nothing else to do here.
+ if (IsClosed()) {
+ return NS_OK;
+ }
+
+ AutoEntryScript aes(mStream->GetParentObject(),
+ "InputToReadableStream data available");
+
+ MOZ_DIAGNOSTIC_ASSERT(mInput);
+
+ JSContext* cx = aes.cx();
+
+ uint64_t size = 0;
+ nsresult rv = mInput->Available(&size);
+ MOZ_ASSERT_IF(NS_SUCCEEDED(rv), size > 0);
+
+ // No warning for stream closed.
+ if (rv == NS_BASE_STREAM_CLOSED || NS_WARN_IF(NS_FAILED(rv))) {
+ ErrorPropagation(cx, mStream, rv);
+ return NS_OK;
+ }
+
+ // Not having a promise means we are pinged by stream closure
+ // (WAIT_CLOSURE_ONLY below), but here we still have more data to read. Let's
+ // wait for the next read request in that case.
+ if (!mPullPromise) {
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mPullPromise->State() ==
+ Promise::PromiseState::Pending);
+
+ ErrorResult errorResult;
+ PullFromInputStream(cx, size, errorResult);
+ errorResult.WouldReportJSException();
+ if (errorResult.Failed()) {
+ ErrorPropagation(cx, mStream, errorResult.StealNSResult());
+ return NS_OK;
+ }
+
+ // PullFromInputStream can fulfill read request, which can trigger read
+ // request chunk steps, which again may execute JS. But it should be still
+ // safe from cycle collection as the caller nsIAsyncInputStream should hold
+ // the reference of `this`.
+ //
+ // That said, it's generally good to be cautious as there's no guarantee that
+ // the interface is implemented in the safest way.
+ MOZ_DIAGNOSTIC_ASSERT(mPullPromise);
+ if (mPullPromise) {
+ mPullPromise->MaybeResolveWithUndefined();
+ mPullPromise = nullptr;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mInput);
+ if (mInput) {
+ // Subscribe WAIT_CLOSURE_ONLY so that OnInputStreamReady can be called when
+ // mInput is closed.
+ rv = mInput->AsyncWait(nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0,
+ mOwningEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ErrorPropagation(cx, mStream, errorResult.StealNSResult());
+ return NS_OK;
+ }
+ }
+
+ return NS_OK;
+}
+
+void InputToReadableStreamAlgorithms::WriteIntoReadRequestBuffer(
+ JSContext* aCx, ReadableStream* aStream, JS::Handle<JSObject*> aBuffer,
+ uint32_t aLength, uint32_t* aByteWritten, ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(aBuffer);
+ MOZ_DIAGNOSTIC_ASSERT(aByteWritten);
+ MOZ_DIAGNOSTIC_ASSERT(mInput);
+ MOZ_DIAGNOSTIC_ASSERT(!IsClosed());
+ MOZ_DIAGNOSTIC_ASSERT(mPullPromise->State() ==
+ Promise::PromiseState::Pending);
+
+ uint32_t written;
+ nsresult rv;
+ void* buffer;
+ {
+ // Bug 1754513: Hazard suppression.
+ //
+ // Because mInput->Read is detected as possibly GCing by the
+ // current state of our static hazard analysis, we need to do the
+ // suppression here. This can be removed with future improvements
+ // to the static analysis.
+ JS::AutoSuppressGCAnalysis suppress;
+ JS::AutoCheckCannotGC noGC;
+ bool isSharedMemory;
+
+ buffer = JS_GetArrayBufferViewData(aBuffer, &isSharedMemory, noGC);
+ MOZ_ASSERT(!isSharedMemory);
+
+ rv = mInput->Read(static_cast<char*>(buffer), aLength, &written);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ }
+
+ *aByteWritten = written;
+
+ if (written == 0) {
+ // If bytesWritten is zero, then the stream has been closed; return rather
+ // than enqueueing a chunk filled with zeros.
+ aRv.Throw(NS_BASE_STREAM_CLOSED);
+ return;
+ }
+
+ // All good.
+}
+
+// https://streams.spec.whatwg.org/#readablestream-pull-from-bytes
+// This is a ReadableStream algorithm but will probably be used solely in
+// InputToReadableStreamAlgorithms.
+void InputToReadableStreamAlgorithms::PullFromInputStream(JSContext* aCx,
+ uint64_t aAvailable,
+ ErrorResult& aRv) {
+ // Step 1. Assert: stream.[[controller]] implements
+ // ReadableByteStreamController.
+ MOZ_ASSERT(mStream->Controller()->IsByte());
+
+ // Step 2. Let available be bytes’s length. (aAvailable)
+ // Step 3. Let desiredSize be available.
+ uint64_t desiredSize = aAvailable;
+
+ // Step 4. If stream’s current BYOB request view is non-null, then set
+ // desiredSize to stream’s current BYOB request view's byte length.
+ JS::Rooted<JSObject*> byobView(aCx);
+ mStream->GetCurrentBYOBRequestView(aCx, &byobView, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ if (byobView) {
+ desiredSize = JS_GetArrayBufferViewByteLength(byobView);
+ }
+
+ // Step 5. Let pullSize be the smaller value of available and desiredSize.
+ //
+ // To avoid OOMing up on huge amounts of available data on a 32 bit system,
+ // as well as potentially overflowing nsIInputStream's Read method's
+ // parameter, let's limit our maximum chunk size to 256MB.
+ //
+ // (Note that nsIInputStream uses uint64_t for Available and uint32_t for
+ // Read.)
+ uint64_t pullSize = std::min(static_cast<uint64_t>(256 * 1024 * 1024),
+ std::min(aAvailable, desiredSize));
+
+ // Step 6. Let pulled be the first pullSize bytes of bytes.
+ // Step 7. Remove the first pullSize bytes from bytes.
+ //
+ // We do this in step 8 and 9, as we don't have a direct access to the data
+ // but need to let nsIInputStream to write into the view.
+
+ // Step 8. If stream’s current BYOB request view is non-null, then:
+ if (byobView) {
+ // Step 8.1. Write pulled into stream’s current BYOB request view.
+ uint32_t bytesWritten = 0;
+ WriteIntoReadRequestBuffer(aCx, mStream, byobView, pullSize, &bytesWritten,
+ aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 8.2. Perform ?
+ // ReadableByteStreamControllerRespond(stream.[[controller]], pullSize).
+ //
+ // But we do not use pullSize but use byteWritten here, since nsIInputStream
+ // does not guarantee to read as much as it told in Available().
+ MOZ_DIAGNOSTIC_ASSERT(pullSize == bytesWritten);
+ ReadableByteStreamControllerRespond(
+ aCx, MOZ_KnownLive(mStream->Controller()->AsByte()), bytesWritten, aRv);
+ }
+ // Step 9. Otherwise,
+ else {
+ // Step 9.1. Set view to the result of creating a Uint8Array from pulled in
+ // stream’s relevant Realm.
+ UniquePtr<uint8_t[], JS::FreePolicy> buffer(
+ static_cast<uint8_t*>(JS_malloc(aCx, pullSize)));
+ if (!buffer) {
+ aRv.ThrowTypeError("Out of memory");
+ return;
+ }
+
+ uint32_t bytesWritten = 0;
+ nsresult rv = mInput->Read((char*)buffer.get(), pullSize, &bytesWritten);
+ if (!bytesWritten) {
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(pullSize == bytesWritten);
+ JS::Rooted<JSObject*> view(aCx, nsJSUtils::MoveBufferAsUint8Array(
+ aCx, bytesWritten, std::move(buffer)));
+ if (!view) {
+ JS_ClearPendingException(aCx);
+ aRv.ThrowTypeError("Out of memory");
+ return;
+ }
+
+ // Step 9.2. Perform ?
+ // ReadableByteStreamControllerEnqueue(stream.[[controller]], view).
+ ReadableByteStreamControllerEnqueue(
+ aCx, MOZ_KnownLive(mStream->Controller()->AsByte()), view, aRv);
+ }
+}
+
+void InputToReadableStreamAlgorithms::CloseAndReleaseObjects(
+ JSContext* aCx, ReadableStream* aStream) {
+ MOZ_DIAGNOSTIC_ASSERT(!IsClosed());
+
+ ReleaseObjects();
+
+ if (aStream->State() == ReadableStream::ReaderState::Readable) {
+ IgnoredErrorResult rv;
+ aStream->CloseNative(aCx, rv);
+ NS_WARNING_ASSERTION(!rv.Failed(), "Failed to Close Stream");
+ }
+}
+
+void InputToReadableStreamAlgorithms::ReleaseObjects() {
+ if (mInput) {
+ mInput->CloseWithStatus(NS_BASE_STREAM_CLOSED);
+ mInput->Shutdown();
+ mInput = nullptr;
+ }
+
+ // It's okay to leave a potentially unsettled promise as-is as this is only
+ // used to prevent reentrant to PullCallback. CloseNative() or ErrorNative()
+ // will settle the read requests for us.
+ mPullPromise = nullptr;
+}
+
+nsIInputStream* InputToReadableStreamAlgorithms::MaybeGetInputStreamIfUnread() {
+ MOZ_ASSERT(!mStream->Disturbed(),
+ "Should be only called on non-disturbed streams");
+ return mInput->GetInputStream();
+}
+
+void InputToReadableStreamAlgorithms::ErrorPropagation(JSContext* aCx,
+ ReadableStream* aStream,
+ nsresult aError) {
+ // Nothing to do.
+ if (IsClosed()) {
+ return;
+ }
+
+ // Let's close the stream.
+ if (aError == NS_BASE_STREAM_CLOSED) {
+ CloseAndReleaseObjects(aCx, aStream);
+ return;
+ }
+
+ // Let's use a generic error.
+ ErrorResult rv;
+ // XXXbz can we come up with a better error message here to tell the
+ // consumer what went wrong?
+ rv.ThrowTypeError("Error in input stream");
+
+ JS::Rooted<JS::Value> errorValue(aCx);
+ bool ok = ToJSValue(aCx, std::move(rv), &errorValue);
+ MOZ_RELEASE_ASSERT(ok, "ToJSValue never fails for ErrorResult");
+
+ {
+ // This will be ignored if it's already errored.
+ IgnoredErrorResult rv;
+ aStream->ErrorNative(aCx, errorValue, rv);
+ NS_WARNING_ASSERTION(!rv.Failed(), "Failed to error InputToReadableStream");
+ }
+
+ MOZ_ASSERT(IsClosed());
+}
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(
+ NonAsyncInputToReadableStreamAlgorithms, UnderlyingSourceAlgorithmsWrapper)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(NonAsyncInputToReadableStreamAlgorithms,
+ UnderlyingSourceAlgorithmsWrapper,
+ mAsyncAlgorithms)
+
+already_AddRefed<Promise>
+NonAsyncInputToReadableStreamAlgorithms::PullCallbackImpl(
+ JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) {
+ if (!mAsyncAlgorithms) {
+ nsCOMPtr<nsIAsyncInputStream> asyncStream;
+
+ // NS_MakeAsyncNonBlockingInputStream may immediately start a stream read
+ // via nsInputStreamTransport::OpenInputStream, which is why this should be
+ // called on a pull callback instead of in the constructor.
+ nsresult rv = NS_MakeAsyncNonBlockingInputStream(
+ mInput.forget(), getter_AddRefs(asyncStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ mAsyncAlgorithms = MakeRefPtr<InputToReadableStreamAlgorithms>(
+ aCx, asyncStream, aController.Stream());
+ }
+
+ MOZ_ASSERT(!mInput);
+ return mAsyncAlgorithms->PullCallbackImpl(aCx, aController, aRv);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/UnderlyingSourceCallbackHelpers.h b/dom/streams/UnderlyingSourceCallbackHelpers.h
new file mode 100644
index 0000000000..e35e1dc59f
--- /dev/null
+++ b/dom/streams/UnderlyingSourceCallbackHelpers.h
@@ -0,0 +1,330 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_UnderlyingSourceCallbackHelpers_h
+#define mozilla_dom_UnderlyingSourceCallbackHelpers_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/UnderlyingSourceBinding.h"
+#include "mozilla/WeakPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsISupports.h"
+#include "nsISupportsImpl.h"
+
+/* Since the streams specification has native descriptions of some callbacks
+ * (i.e. described in prose, rather than provided by user code), we need to be
+ * able to pass around native callbacks. To handle this, we define polymorphic
+ * classes That cover the difference between native callback and user-provided.
+ *
+ * The Streams specification wants us to invoke these callbacks, run through
+ * WebIDL as if they were methods. So we have to preserve the underlying object
+ * to use as the This value on invocation.
+ */
+enum class nsresult : uint32_t;
+
+namespace mozilla::dom {
+
+class StrongWorkerRef;
+class BodyStreamHolder;
+class ReadableStreamController;
+class ReadableStream;
+
+class UnderlyingSourceAlgorithmsBase : public nsISupports {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(UnderlyingSourceAlgorithmsBase)
+
+ MOZ_CAN_RUN_SCRIPT virtual void StartCallback(
+ JSContext* aCx, ReadableStreamController& aController,
+ JS::MutableHandle<JS::Value> aRetVal, ErrorResult& aRv) = 0;
+
+ // A promise-returning algorithm that pulls data from the underlying byte
+ // source
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> PullCallback(
+ JSContext* aCx, ReadableStreamController& aController,
+ ErrorResult& aRv) = 0;
+
+ // A promise-returning algorithm, taking one argument (the cancel reason),
+ // which communicates a requested cancelation to the underlying byte source
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> CancelCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) = 0;
+
+ // Implement this when you need to release underlying resources immediately
+ // from closed(canceled)/errored streams, without waiting for GC.
+ virtual void ReleaseObjects() {}
+
+ // Can be used to read chunks directly via nsIInputStream to skip JS-related
+ // overhead, if this readable stream is a wrapper of a native stream.
+ // Currently used by Fetch helper functions e.g. new Response(stream).text()
+ virtual nsIInputStream* MaybeGetInputStreamIfUnread() { return nullptr; }
+
+ // https://streams.spec.whatwg.org/#other-specs-rs-create
+ // By "native" we mean "instances initialized via the above set up or set up
+ // with byte reading support algorithms (not, e.g., on web-developer-created
+ // instances)"
+ virtual bool IsNative() { return true; }
+
+ protected:
+ virtual ~UnderlyingSourceAlgorithmsBase() = default;
+};
+
+class UnderlyingSourceAlgorithms final : public UnderlyingSourceAlgorithmsBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ UnderlyingSourceAlgorithms, UnderlyingSourceAlgorithmsBase)
+
+ UnderlyingSourceAlgorithms(nsIGlobalObject* aGlobal,
+ JS::Handle<JSObject*> aUnderlyingSource,
+ UnderlyingSource& aUnderlyingSourceDict)
+ : mGlobal(aGlobal), mUnderlyingSource(aUnderlyingSource) {
+ // Step 6. (implicit Step 2.)
+ if (aUnderlyingSourceDict.mStart.WasPassed()) {
+ mStartCallback = aUnderlyingSourceDict.mStart.Value();
+ }
+
+ // Step 7. (implicit Step 3.)
+ if (aUnderlyingSourceDict.mPull.WasPassed()) {
+ mPullCallback = aUnderlyingSourceDict.mPull.Value();
+ }
+
+ // Step 8. (implicit Step 4.)
+ if (aUnderlyingSourceDict.mCancel.WasPassed()) {
+ mCancelCallback = aUnderlyingSourceDict.mCancel.Value();
+ }
+
+ mozilla::HoldJSObjects(this);
+ };
+
+ MOZ_CAN_RUN_SCRIPT void StartCallback(JSContext* aCx,
+ ReadableStreamController& aController,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PullCallback(
+ JSContext* aCx, ReadableStreamController& aController,
+ ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CancelCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) override;
+
+ bool IsNative() override { return false; }
+
+ protected:
+ ~UnderlyingSourceAlgorithms() override { mozilla::DropJSObjects(this); };
+
+ private:
+ // Virtually const, but are cycle collected
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ JS::Heap<JSObject*> mUnderlyingSource;
+ MOZ_KNOWN_LIVE RefPtr<UnderlyingSourceStartCallback> mStartCallback;
+ MOZ_KNOWN_LIVE RefPtr<UnderlyingSourcePullCallback> mPullCallback;
+ MOZ_KNOWN_LIVE RefPtr<UnderlyingSourceCancelCallback> mCancelCallback;
+};
+
+// https://streams.spec.whatwg.org/#readablestream-set-up
+// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
+// Wrappers defined by the "Set up" methods in the spec. This helps you just
+// return nullptr when an error occurred as this wrapper converts it to a
+// rejected promise.
+// Note that StartCallback is only for JS consumers to access
+// the controller, and thus is no-op here since native consumers can call
+// `EnqueueNative()` etc. without direct controller access.
+class UnderlyingSourceAlgorithmsWrapper
+ : public UnderlyingSourceAlgorithmsBase {
+ void StartCallback(JSContext*, ReadableStreamController&,
+ JS::MutableHandle<JS::Value> aRetVal, ErrorResult&) final;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PullCallback(
+ JSContext* aCx, ReadableStreamController& aController,
+ ErrorResult& aRv) final;
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CancelCallback(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) final;
+
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> PullCallbackImpl(
+ JSContext* aCx, ReadableStreamController& aController, ErrorResult& aRv) {
+ // pullAlgorithm is optional, return null by default
+ return nullptr;
+ }
+
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> CancelCallbackImpl(
+ JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
+ ErrorResult& aRv) {
+ // cancelAlgorithm is optional, return null by default
+ return nullptr;
+ }
+};
+
+class InputToReadableStreamAlgorithms;
+
+// This class exists to isolate InputToReadableStreamAlgorithms from the
+// nsIAsyncInputStream. If we call AsyncWait(this,...), it holds a
+// reference to 'this' which can't be cc'd, and we can leak the stream,
+// causing a Worker to assert with globalScopeAlive. By isolating
+// ourselves from the inputstream, we can safely be CC'd if needed and
+// will inform the inputstream to shut down.
+class InputStreamHolder final : public nsIInputStreamCallback,
+ public GlobalTeardownObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+
+ InputStreamHolder(nsIGlobalObject* aGlobal,
+ InputToReadableStreamAlgorithms* aCallback,
+ nsIAsyncInputStream* aInput);
+
+ void Init(JSContext* aCx);
+
+ void DisconnectFromOwner() override;
+
+ // Used by global teardown
+ void Shutdown();
+
+ // These just proxy the calls to the nsIAsyncInputStream
+ nsresult AsyncWait(uint32_t aFlags, uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget);
+ nsresult Available(uint64_t* aSize) { return mInput->Available(aSize); }
+ nsresult Read(char* aBuffer, uint32_t aLength, uint32_t* aWritten) {
+ return mInput->Read(aBuffer, aLength, aWritten);
+ }
+ nsresult CloseWithStatus(nsresult aStatus) {
+ return mInput->CloseWithStatus(aStatus);
+ }
+
+ nsIAsyncInputStream* GetInputStream() { return mInput; }
+
+ private:
+ ~InputStreamHolder();
+
+ // WeakPtr to avoid cycles
+ WeakPtr<InputToReadableStreamAlgorithms> mCallback;
+ // To ensure the worker sticks around
+ RefPtr<StrongWorkerRef> mAsyncWaitWorkerRef;
+ RefPtr<StrongWorkerRef> mWorkerRef;
+ nsCOMPtr<nsIAsyncInputStream> mInput;
+
+ // To ensure the underlying source sticks around during an ongoing read
+ // operation. mAlgorithms is not cycle collected on purpose, and this holder
+ // is responsible to keep the underlying source algorithms until
+ // nsIAsyncInputStream responds.
+ //
+ // This is done because otherwise the whole stream objects may be cycle
+ // collected, including the promises created from read(), as our JS engine may
+ // throw unsettled promises away for optimization. See bug 1849860.
+ RefPtr<InputToReadableStreamAlgorithms> mAsyncWaitAlgorithms;
+};
+
+// Using this class means you are also passing the lifetime control of your
+// nsIAsyncInputStream, as it will be closed when this class tears down.
+class InputToReadableStreamAlgorithms final
+ : public UnderlyingSourceAlgorithmsWrapper,
+ public nsIInputStreamCallback,
+ public SupportsWeakPtr {
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InputToReadableStreamAlgorithms,
+ UnderlyingSourceAlgorithmsWrapper)
+
+ InputToReadableStreamAlgorithms(JSContext* aCx, nsIAsyncInputStream* aInput,
+ ReadableStream* aStream);
+
+ // Streams algorithms
+
+ already_AddRefed<Promise> PullCallbackImpl(
+ JSContext* aCx, ReadableStreamController& aController,
+ ErrorResult& aRv) override;
+
+ void ReleaseObjects() override;
+
+ nsIInputStream* MaybeGetInputStreamIfUnread() override;
+
+ private:
+ ~InputToReadableStreamAlgorithms() {
+ if (mInput) {
+ mInput->Shutdown();
+ }
+ }
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void CloseAndReleaseObjects(
+ JSContext* aCx, ReadableStream* aStream);
+
+ void WriteIntoReadRequestBuffer(JSContext* aCx, ReadableStream* aStream,
+ JS::Handle<JSObject*> aBuffer,
+ uint32_t aLength, uint32_t* aByteWritten,
+ ErrorResult& aRv);
+
+ // https://streams.spec.whatwg.org/#readablestream-pull-from-bytes
+ // (Uses InputStreamHolder for the "byte sequence" in the spec)
+ MOZ_CAN_RUN_SCRIPT void PullFromInputStream(JSContext* aCx,
+ uint64_t aAvailable,
+ ErrorResult& aRv);
+
+ void ErrorPropagation(JSContext* aCx, ReadableStream* aStream,
+ nsresult aError);
+
+ // Common methods
+
+ bool IsClosed() { return !mInput; }
+
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+
+ // This promise is created by PullCallback and resolved when
+ // OnInputStreamReady succeeds. No need to try hard to settle it though, see
+ // also ReleaseObjects() for the reason.
+ RefPtr<Promise> mPullPromise;
+
+ RefPtr<InputStreamHolder> mInput;
+
+ // mStream never changes after construction and before CC
+ MOZ_KNOWN_LIVE RefPtr<ReadableStream> mStream;
+};
+
+class NonAsyncInputToReadableStreamAlgorithms
+ : public UnderlyingSourceAlgorithmsWrapper {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
+ NonAsyncInputToReadableStreamAlgorithms,
+ UnderlyingSourceAlgorithmsWrapper)
+
+ explicit NonAsyncInputToReadableStreamAlgorithms(nsIInputStream& aInput)
+ : mInput(&aInput) {}
+
+ already_AddRefed<Promise> PullCallbackImpl(
+ JSContext* aCx, ReadableStreamController& aController,
+ ErrorResult& aRv) override;
+
+ void ReleaseObjects() override {
+ if (RefPtr<InputToReadableStreamAlgorithms> algorithms =
+ mAsyncAlgorithms.forget()) {
+ algorithms->ReleaseObjects();
+ }
+ if (nsCOMPtr<nsIInputStream> input = mInput.forget()) {
+ input->Close();
+ }
+ }
+
+ nsIInputStream* MaybeGetInputStreamIfUnread() override {
+ MOZ_ASSERT(mInput, "Should be only called on non-disturbed streams");
+ return mInput;
+ }
+
+ private:
+ ~NonAsyncInputToReadableStreamAlgorithms() = default;
+
+ nsCOMPtr<nsIInputStream> mInput;
+ RefPtr<InputToReadableStreamAlgorithms> mAsyncAlgorithms;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/streams/WritableStream.cpp b/dom/streams/WritableStream.cpp
new file mode 100644
index 0000000000..8dab9a564e
--- /dev/null
+++ b/dom/streams/WritableStream.cpp
@@ -0,0 +1,811 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/WritableStream.h"
+
+#include "StreamUtils.h"
+#include "js/Array.h"
+#include "js/PropertyAndElement.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/AbortSignal.h"
+#include "mozilla/dom/BindingCallContext.h"
+#include "mozilla/dom/QueueWithSizes.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "mozilla/dom/ReadRequest.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/UnderlyingSinkBinding.h"
+#include "mozilla/dom/WritableStreamBinding.h"
+#include "mozilla/dom/WritableStreamDefaultController.h"
+#include "mozilla/dom/WritableStreamDefaultWriter.h"
+#include "nsCOMPtr.h"
+
+#include "mozilla/dom/Promise-inl.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(
+ WritableStream,
+ (mGlobal, mCloseRequest, mController, mInFlightWriteRequest,
+ mInFlightCloseRequest, mPendingAbortRequestPromise, mWriter,
+ mWriteRequests),
+ (mPendingAbortRequestReason, mStoredError))
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WritableStream)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(WritableStream,
+ LastRelease())
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStream)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+WritableStream::WritableStream(nsIGlobalObject* aGlobal,
+ HoldDropJSObjectsCaller aHoldDropCaller)
+ : mGlobal(aGlobal), mHoldDropCaller(aHoldDropCaller) {
+ if (mHoldDropCaller == HoldDropJSObjectsCaller::Implicit) {
+ mozilla::HoldJSObjects(this);
+ }
+}
+
+WritableStream::WritableStream(const GlobalObject& aGlobal,
+ HoldDropJSObjectsCaller aHoldDropCaller)
+ : mGlobal(do_QueryInterface(aGlobal.GetAsSupports())),
+ mHoldDropCaller(aHoldDropCaller) {
+ if (mHoldDropCaller == HoldDropJSObjectsCaller::Implicit) {
+ mozilla::HoldJSObjects(this);
+ }
+}
+
+WritableStream::~WritableStream() {
+ if (mHoldDropCaller == HoldDropJSObjectsCaller::Implicit) {
+ mozilla::DropJSObjects(this);
+ }
+}
+
+JSObject* WritableStream::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return WritableStream_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-deal-with-rejection
+void WritableStream::DealWithRejection(JSContext* aCx,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ // Step 1. Let state be stream.[[state]].
+ // Step 2. If state is "writable",
+ if (mState == WriterState::Writable) {
+ // Step 2.1. Perform ! WritableStreamStartErroring(stream, error).
+ StartErroring(aCx, aError, aRv);
+
+ // Step 2.2. Return.
+ return;
+ }
+
+ // Step 3. Assert: state is "erroring".
+ MOZ_ASSERT(mState == WriterState::Erroring);
+
+ // Step 4. Perform ! WritableStreamFinishErroring(stream).
+ FinishErroring(aCx, aRv);
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-finish-erroring
+void WritableStream::FinishErroring(JSContext* aCx, ErrorResult& aRv) {
+ // Step 1. Assert: stream.[[state]] is "erroring".
+ MOZ_ASSERT(mState == WriterState::Erroring);
+
+ // Step 2. Assert: ! WritableStreamHasOperationMarkedInFlight(stream) is
+ // false.
+ MOZ_ASSERT(!HasOperationMarkedInFlight());
+
+ // Step 3. Set stream.[[state]] to "errored".
+ mState = WriterState::Errored;
+
+ // Step 4. Perform ! stream.[[controller]].[[ErrorSteps]]().
+ Controller()->ErrorSteps();
+
+ // Step 5. Let storedError be stream.[[storedError]].
+ JS::Rooted<JS::Value> storedError(aCx, mStoredError);
+
+ // Step 6. For each writeRequest of stream.[[writeRequests]]:
+ for (const RefPtr<Promise>& writeRequest : mWriteRequests) {
+ // Step 6.1. Reject writeRequest with storedError.
+ writeRequest->MaybeReject(storedError);
+ }
+
+ // Step 7. Set stream.[[writeRequests]] to an empty list.
+ mWriteRequests.Clear();
+
+ // Step 8. If stream.[[pendingAbortRequest]] is undefined,
+ if (!mPendingAbortRequestPromise) {
+ // Step 8.1. Perform !
+ // WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
+ RejectCloseAndClosedPromiseIfNeeded();
+
+ // Step 8.2. Return.
+ return;
+ }
+
+ // Step 9. Let abortRequest be stream.[[pendingAbortRequest]].
+ RefPtr<Promise> abortPromise = mPendingAbortRequestPromise;
+ JS::Rooted<JS::Value> abortReason(aCx, mPendingAbortRequestReason);
+ bool abortWasAlreadyErroring = mPendingAbortRequestWasAlreadyErroring;
+
+ // Step 10. Set stream.[[pendingAbortRequest]] to undefined.
+ SetPendingAbortRequest(nullptr, JS::UndefinedHandleValue, false);
+
+ // Step 11. If abortRequest’s was already erroring is true,
+ if (abortWasAlreadyErroring) {
+ // Step 11.1. Reject abortRequest’s promise with storedError.
+ abortPromise->MaybeReject(storedError);
+
+ // Step 11.2. Perform !
+ // WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
+ RejectCloseAndClosedPromiseIfNeeded();
+
+ // Step 11.3. Return.
+ return;
+ }
+
+ // Step 12. Let promise be !
+ // stream.[[controller]].[[AbortSteps]](abortRequest’s reason).
+ RefPtr<WritableStreamDefaultController> controller = mController;
+ RefPtr<Promise> promise = controller->AbortSteps(aCx, abortReason, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 13 + 14.
+ promise->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ Promise* aAbortRequestPromise, WritableStream* aStream) {
+ // Step 13. Upon fulfillment of promise,
+ // Step 13.1. Resolve abortRequest’s promise with undefined.
+ aAbortRequestPromise->MaybeResolveWithUndefined();
+
+ // Step 13.2. Perform !
+ // WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
+ aStream->RejectCloseAndClosedPromiseIfNeeded();
+ },
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ Promise* aAbortRequestPromise, WritableStream* aStream) {
+ // Step 14. Upon rejection of promise with reason reason,
+ // Step 14.1. Reject abortRequest’s promise with reason.
+ aAbortRequestPromise->MaybeReject(aValue);
+
+ // Step 14.2. Perform !
+ // WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
+ aStream->RejectCloseAndClosedPromiseIfNeeded();
+ },
+ RefPtr(abortPromise), RefPtr(this));
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-close
+void WritableStream::FinishInFlightClose() {
+ // Step 1. Assert: stream.[[inFlightCloseRequest]] is not undefined.
+ MOZ_ASSERT(mInFlightCloseRequest);
+
+ // Step 2. Resolve stream.[[inFlightCloseRequest]] with undefined.
+ mInFlightCloseRequest->MaybeResolveWithUndefined();
+
+ // Step 3. Set stream.[[inFlightCloseRequest]] to undefined.
+ mInFlightCloseRequest = nullptr;
+
+ // Step 4. Let state be stream.[[state]].
+ // Step 5. Assert: stream.[[state]] is "writable" or "erroring".
+ MOZ_ASSERT(mState == WriterState::Writable ||
+ mState == WriterState::Erroring);
+
+ // Step 6. If state is "erroring",
+ if (mState == WriterState::Erroring) {
+ // Step 6.1. Set stream.[[storedError]] to undefined.
+ mStoredError.setUndefined();
+
+ // Step 6.2. If stream.[[pendingAbortRequest]] is not undefined,
+ if (mPendingAbortRequestPromise) {
+ // Step 6.2.1. Resolve stream.[[pendingAbortRequest]]'s promise with
+ // undefined.
+ mPendingAbortRequestPromise->MaybeResolveWithUndefined();
+
+ // Step 6.2.2. Set stream.[[pendingAbortRequest]] to undefined.
+ SetPendingAbortRequest(nullptr, JS::UndefinedHandleValue, false);
+ }
+ }
+
+ // Step 7. Set stream.[[state]] to "closed".
+ mState = WriterState::Closed;
+
+ // Step 8. Let writer be stream.[[writer]].
+ // Step 9. If writer is not undefined, resolve writer.[[closedPromise]] with
+ // undefined.
+ if (mWriter) {
+ mWriter->ClosedPromise()->MaybeResolveWithUndefined();
+ }
+
+ // Step 10. Assert: stream.[[pendingAbortRequest]] is undefined.
+ MOZ_ASSERT(!mPendingAbortRequestPromise);
+ // Assert: stream.[[storedError]] is undefined.
+ MOZ_ASSERT(mStoredError.isUndefined());
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-close-with-error
+void WritableStream::FinishInFlightCloseWithError(JSContext* aCx,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ // Step 1. Assert: stream.[[inFlightCloseRequest]] is not undefined.
+ MOZ_ASSERT(mInFlightCloseRequest);
+
+ // Step 2. Reject stream.[[inFlightCloseRequest]] with error.
+ mInFlightCloseRequest->MaybeReject(aError);
+
+ // Step 3. Set stream.[[inFlightCloseRequest]] to undefined.
+ mInFlightCloseRequest = nullptr;
+
+ // Step 4. Assert: stream.[[state]] is "writable" or "erroring".
+ MOZ_ASSERT(mState == WriterState::Writable ||
+ mState == WriterState::Erroring);
+
+ // Step 5. If stream.[[pendingAbortRequest]] is not undefined,
+ if (mPendingAbortRequestPromise) {
+ // Step 5.1. Reject stream.[[pendingAbortRequest]]'s promise with error.
+ mPendingAbortRequestPromise->MaybeReject(aError);
+
+ // Step 5.2. Set stream.[[pendingAbortRequest]] to undefined.
+ SetPendingAbortRequest(nullptr, JS::UndefinedHandleValue, false);
+ }
+
+ // Step 6. Perform ! WritableStreamDealWithRejection(stream, error).
+ DealWithRejection(aCx, aError, aRv);
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-write
+void WritableStream::FinishInFlightWrite() {
+ // Step 1. Assert: stream.[[inFlightWriteRequest]] is not undefined.
+ MOZ_ASSERT(mInFlightWriteRequest);
+
+ // Step 2. Resolve stream.[[inFlightWriteRequest]] with undefined.
+ mInFlightWriteRequest->MaybeResolveWithUndefined();
+
+ // Step 3. Set stream.[[inFlightWriteRequest]] to undefined.
+ mInFlightWriteRequest = nullptr;
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-write-with-error
+void WritableStream::FinishInFlightWriteWithError(JSContext* aCx,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ // Step 1. Assert: stream.[[inFlightWriteRequest]] is not undefined.
+ MOZ_ASSERT(mInFlightWriteRequest);
+
+ // Step 2. Reject stream.[[inFlightWriteRequest]] with error.
+ mInFlightWriteRequest->MaybeReject(aError);
+
+ // Step 3. Set stream.[[inFlightWriteRequest]] to undefined.
+ mInFlightWriteRequest = nullptr;
+
+ // Step 4. Assert: stream.[[state]] is "writable" or "erroring".
+ MOZ_ASSERT(mState == WriterState::Writable ||
+ mState == WriterState::Erroring);
+
+ // Step 5. Perform ! WritableStreamDealWithRejection(stream, error).
+ DealWithRejection(aCx, aError, aRv);
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-mark-close-request-in-flight
+void WritableStream::MarkCloseRequestInFlight() {
+ // Step 1. Assert: stream.[[inFlightCloseRequest]] is undefined.
+ MOZ_ASSERT(!mInFlightCloseRequest);
+
+ // Step 2. Assert: stream.[[closeRequest]] is not undefined.
+ MOZ_ASSERT(mCloseRequest);
+
+ // Step 3. Set stream.[[inFlightCloseRequest]] to stream.[[closeRequest]].
+ mInFlightCloseRequest = mCloseRequest;
+
+ // Step 4. Set stream.[[closeRequest]] to undefined.
+ mCloseRequest = nullptr;
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-mark-first-write-request-in-flight
+void WritableStream::MarkFirstWriteRequestInFlight() {
+ // Step 1. Assert: stream.[[inFlightWriteRequest]] is undefined.
+ MOZ_ASSERT(!mInFlightWriteRequest);
+
+ // Step 2. Assert: stream.[[writeRequests]] is not empty.
+ MOZ_ASSERT(!mWriteRequests.IsEmpty());
+
+ // Step 3. Let writeRequest be stream.[[writeRequests]][0].
+ RefPtr<Promise> writeRequest = mWriteRequests.ElementAt(0);
+
+ // Step 4. Remove writeRequest from stream.[[writeRequests]].
+ mWriteRequests.RemoveElementAt(0);
+
+ // Step 5. Set stream.[[inFlightWriteRequest]] to writeRequest.
+ mInFlightWriteRequest = writeRequest;
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-reject-close-and-closed-promise-if-needed
+void WritableStream::RejectCloseAndClosedPromiseIfNeeded() {
+ // Step 1. Assert: stream.[[state]] is "errored".
+ MOZ_ASSERT(mState == WriterState::Errored);
+
+ JS::Rooted<JS::Value> storedError(RootingCx(), mStoredError);
+ // Step 2. If stream.[[closeRequest]] is not undefined,
+ if (mCloseRequest) {
+ // Step 2.1. Assert: stream.[[inFlightCloseRequest]] is undefined.
+ MOZ_ASSERT(!mInFlightCloseRequest);
+
+ // Step 2.2. Reject stream.[[closeRequest]] with stream.[[storedError]].
+ mCloseRequest->MaybeReject(storedError);
+
+ // Step 2.3. Set stream.[[closeRequest]] to undefined.
+ mCloseRequest = nullptr;
+ }
+
+ // Step 3. Let writer be stream.[[writer]].
+ RefPtr<WritableStreamDefaultWriter> writer = mWriter;
+
+ // Step 4. If writer is not undefined,
+ if (writer) {
+ // Step 4.1. Reject writer.[[closedPromise]] with stream.[[storedError]].
+ RefPtr<Promise> closedPromise = writer->ClosedPromise();
+ closedPromise->MaybeReject(storedError);
+
+ // Step 4.2. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
+ closedPromise->SetSettledPromiseIsHandled();
+ }
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-start-erroring
+void WritableStream::StartErroring(JSContext* aCx,
+ JS::Handle<JS::Value> aReason,
+ ErrorResult& aRv) {
+ // Step 1. Assert: stream.[[storedError]] is undefined.
+ MOZ_ASSERT(mStoredError.isUndefined());
+
+ // Step 2. Assert: stream.[[state]] is "writable".
+ MOZ_ASSERT(mState == WriterState::Writable);
+
+ // Step 3. Let controller be stream.[[controller]].
+ RefPtr<WritableStreamDefaultController> controller = mController;
+ // Step 4. Assert: controller is not undefined.
+ MOZ_ASSERT(controller);
+
+ // Step 5. Set stream.[[state]] to "erroring".
+ mState = WriterState::Erroring;
+
+ // Step 6. Set stream.[[storedError]] to reason.
+ mStoredError = aReason;
+
+ // Step 7. Let writer be stream.[[writer]].
+ RefPtr<WritableStreamDefaultWriter> writer = mWriter;
+ // Step 8. If writer is not undefined, perform !
+ // WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason).
+ if (writer) {
+ WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, aReason);
+ }
+
+ // Step 9. If ! WritableStreamHasOperationMarkedInFlight(stream) is false
+ // and controller.[[started]] is true,
+ // perform !WritableStreamFinishErroring(stream).
+ if (!HasOperationMarkedInFlight() && controller->Started()) {
+ FinishErroring(aCx, aRv);
+ }
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-update-backpressure
+void WritableStream::UpdateBackpressure(bool aBackpressure) {
+ // Step 1. Assert: stream.[[state]] is "writable".
+ MOZ_ASSERT(mState == WriterState::Writable);
+ // Step 2. Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
+ MOZ_ASSERT(!CloseQueuedOrInFlight());
+
+ // Step 3. Let writer be stream.[[writer]].
+ RefPtr<WritableStreamDefaultWriter> writer = mWriter;
+
+ // Step 4. If writer is not undefined and backpressure is not
+ // stream.[[backpressure]],
+ if (writer && aBackpressure != mBackpressure) {
+ // Step 4.1. If backpressure is true, set writer.[[readyPromise]] to a new
+ // promise.
+ if (aBackpressure) {
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(writer->GetParentObject());
+ writer->SetReadyPromise(promise);
+ } else {
+ // Step 4.2. Otherwise,
+ // Step 4.2.1. Assert: backpressure is false.
+ // Step 4.2.2. Resolve writer.[[readyPromise]] with undefined.
+ writer->ReadyPromise()->MaybeResolveWithUndefined();
+ }
+ }
+
+ // Step 5. Set stream.[[backpressure]] to backpressure.
+ mBackpressure = aBackpressure;
+}
+
+// https://streams.spec.whatwg.org/#ws-constructor
+already_AddRefed<WritableStream> WritableStream::Constructor(
+ const GlobalObject& aGlobal,
+ const Optional<JS::Handle<JSObject*>>& aUnderlyingSink,
+ const QueuingStrategy& aStrategy, ErrorResult& aRv) {
+ // Step 1. If underlyingSink is missing, set it to null.
+ JS::Rooted<JSObject*> underlyingSinkObj(
+ aGlobal.Context(),
+ aUnderlyingSink.WasPassed() ? aUnderlyingSink.Value() : nullptr);
+
+ // Step 2. Let underlyingSinkDict be underlyingSink, converted to
+ // an IDL value of type UnderlyingSink.
+ RootedDictionary<UnderlyingSink> underlyingSinkDict(aGlobal.Context());
+ if (underlyingSinkObj) {
+ JS::Rooted<JS::Value> objValue(aGlobal.Context(),
+ JS::ObjectValue(*underlyingSinkObj));
+ dom::BindingCallContext callCx(aGlobal.Context(),
+ "WritableStream.constructor");
+ aRv.MightThrowJSException();
+ if (!underlyingSinkDict.Init(callCx, objValue)) {
+ aRv.StealExceptionFromJSContext(aGlobal.Context());
+ return nullptr;
+ }
+ }
+
+ // Step 3. If underlyingSinkDict["type"] exists, throw a RangeError exception.
+ if (!underlyingSinkDict.mType.isUndefined()) {
+ aRv.ThrowRangeError("Implementation preserved member 'type'");
+ return nullptr;
+ }
+
+ // Step 4. Perform ! InitializeWritableStream(this).
+ RefPtr<WritableStream> writableStream =
+ new WritableStream(aGlobal, HoldDropJSObjectsCaller::Implicit);
+
+ // Step 5. Let sizeAlgorithm be ! ExtractSizeAlgorithm(strategy).
+ //
+ // Implementation Note: The specification demands that if the size doesn't
+ // exist, we instead would provide an algorithm that returns 1. Instead, we
+ // will teach callers that a missing callback should simply return 1, rather
+ // than gin up a fake callback here.
+ //
+ // This decision may need to be revisited if the default action ever diverges
+ // within the specification.
+ RefPtr<QueuingStrategySize> sizeAlgorithm =
+ aStrategy.mSize.WasPassed() ? &aStrategy.mSize.Value() : nullptr;
+
+ // Step 6. Let highWaterMark be ? ExtractHighWaterMark(strategy, 1).
+ double highWaterMark = ExtractHighWaterMark(aStrategy, 1, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 7. Perform ? SetUpWritableStreamDefaultControllerFromUnderlyingSink(
+ // this, underlyingSink, underlyingSinkDict, highWaterMark, sizeAlgorithm).
+ SetUpWritableStreamDefaultControllerFromUnderlyingSink(
+ aGlobal.Context(), writableStream, underlyingSinkObj, underlyingSinkDict,
+ highWaterMark, sizeAlgorithm, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return writableStream.forget();
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#writable-stream-abort
+already_AddRefed<Promise> WritableStreamAbort(JSContext* aCx,
+ WritableStream* aStream,
+ JS::Handle<JS::Value> aReason,
+ ErrorResult& aRv) {
+ // Step 1. If stream.[[state]] is "closed" or "errored", return a promise
+ // resolved with undefined.
+ if (aStream->State() == WritableStream::WriterState::Closed ||
+ aStream->State() == WritableStream::WriterState::Errored) {
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(aStream->GetParentObject());
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ // Step 2. Signal abort on stream.[[controller]].[[signal]] with reason.
+ RefPtr<WritableStreamDefaultController> controller = aStream->Controller();
+ controller->Signal()->SignalAbort(aReason);
+
+ // Step 3. Let state be stream.[[state]].
+ WritableStream::WriterState state = aStream->State();
+
+ // Step 4. If state is "closed" or "errored", return a promise resolved with
+ // undefined. Note: We re-check the state because signaling abort runs author
+ // code and that might have changed the state.
+ if (aStream->State() == WritableStream::WriterState::Closed ||
+ aStream->State() == WritableStream::WriterState::Errored) {
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(aStream->GetParentObject());
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+ }
+
+ // Step 5. If stream.[[pendingAbortRequest]] is not undefined, return
+ // stream.[[pendingAbortRequest]]'s promise.
+ if (aStream->GetPendingAbortRequestPromise()) {
+ RefPtr<Promise> promise = aStream->GetPendingAbortRequestPromise();
+ return promise.forget();
+ }
+
+ // Step 6. Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(state == WritableStream::WriterState::Writable ||
+ state == WritableStream::WriterState::Erroring);
+
+ // Step 7. Let wasAlreadyErroring be false.
+ bool wasAlreadyErroring = false;
+
+ // Step 8. If state is "erroring",
+ JS::Rooted<JS::Value> reason(aCx, aReason);
+ if (state == WritableStream::WriterState::Erroring) {
+ // Step 8.1. Set wasAlreadyErroring to true.
+ wasAlreadyErroring = true;
+ // Step 8.2. Set reason to undefined.
+ reason.setUndefined();
+ }
+
+ // Step 9. Let promise be a new promise.
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(aStream->GetParentObject());
+
+ // Step 10. Set stream.[[pendingAbortRequest]] to a new pending abort request
+ // whose promise is promise, reason is reason, and was already erroring is
+ // wasAlreadyErroring.
+ aStream->SetPendingAbortRequest(promise, reason, wasAlreadyErroring);
+
+ // Step 11. If wasAlreadyErroring is false, perform !
+ // WritableStreamStartErroring(stream, reason).
+ if (!wasAlreadyErroring) {
+ aStream->StartErroring(aCx, reason, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ // Step 12. Return promise.
+ return promise.forget();
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#ws-abort
+already_AddRefed<Promise> WritableStream::Abort(JSContext* aCx,
+ JS::Handle<JS::Value> aReason,
+ ErrorResult& aRv) {
+ // Step 1. If ! IsWritableStreamLocked(this) is true, return a promise
+ // rejected with a TypeError exception.
+ if (Locked()) {
+ return Promise::CreateRejectedWithTypeError(
+ GetParentObject(), "Canceled Locked Stream"_ns, aRv);
+ }
+
+ // Step 2. Return ! WritableStreamAbort(this, reason).
+ RefPtr<WritableStream> thisRefPtr = this;
+ return WritableStreamAbort(aCx, thisRefPtr, aReason, aRv);
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#writable-stream-close
+already_AddRefed<Promise> WritableStreamClose(JSContext* aCx,
+ WritableStream* aStream,
+ ErrorResult& aRv) {
+ // Step 1. Let state be stream.[[state]].
+ WritableStream::WriterState state = aStream->State();
+
+ // Step 2. If state is "closed" or "errored", return a promise rejected with a
+ // TypeError exception.
+ if (state == WritableStream::WriterState::Closed ||
+ state == WritableStream::WriterState::Errored) {
+ return Promise::CreateRejectedWithTypeError(
+ aStream->GetParentObject(),
+ "Can not close stream after closing or error"_ns, aRv);
+ }
+
+ // Step 3. Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(state == WritableStream::WriterState::Writable ||
+ state == WritableStream::WriterState::Erroring);
+
+ // Step 4. Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
+ MOZ_ASSERT(!aStream->CloseQueuedOrInFlight());
+
+ // Step 5. Let promise be a new promise.
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(aStream->GetParentObject());
+
+ // Step 6. Set stream.[[closeRequest]] to promise.
+ aStream->SetCloseRequest(promise);
+
+ // Step 7. Let writer be stream.[[writer]].
+ RefPtr<WritableStreamDefaultWriter> writer = aStream->GetWriter();
+
+ // Step 8. If writer is not undefined, and stream.[[backpressure]] is true,
+ // and state is "writable", resolve writer.[[readyPromise]] with undefined.
+ if (writer && aStream->Backpressure() &&
+ state == WritableStream::WriterState::Writable) {
+ writer->ReadyPromise()->MaybeResolveWithUndefined();
+ }
+
+ // Step 9.
+ // Perform ! WritableStreamDefaultControllerClose(stream.[[controller]]).
+ RefPtr<WritableStreamDefaultController> controller = aStream->Controller();
+ WritableStreamDefaultControllerClose(aCx, controller, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 10. Return promise.
+ return promise.forget();
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#ws-close
+already_AddRefed<Promise> WritableStream::Close(JSContext* aCx,
+ ErrorResult& aRv) {
+ // Step 1. If ! IsWritableStreamLocked(this) is true, return a promise
+ // rejected with a TypeError exception.
+ if (Locked()) {
+ return Promise::CreateRejectedWithTypeError(
+ GetParentObject(), "Can not close locked stream"_ns, aRv);
+ }
+
+ // Step 2. If ! WritableStreamCloseQueuedOrInFlight(this) is true, return a
+ // promise rejected with a TypeError exception.
+ if (CloseQueuedOrInFlight()) {
+ return Promise::CreateRejectedWithTypeError(
+ GetParentObject(), "Stream is already closing"_ns, aRv);
+ }
+
+ // Step 3. Return ! WritableStreamClose(this).
+ RefPtr<WritableStream> thisRefPtr = this;
+ return WritableStreamClose(aCx, thisRefPtr, aRv);
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#acquire-writable-stream-default-writer
+already_AddRefed<WritableStreamDefaultWriter>
+AcquireWritableStreamDefaultWriter(WritableStream* aStream, ErrorResult& aRv) {
+ // Step 1. Let writer be a new WritableStreamDefaultWriter.
+ RefPtr<WritableStreamDefaultWriter> writer =
+ new WritableStreamDefaultWriter(aStream->GetParentObject());
+
+ // Step 2. Perform ? SetUpWritableStreamDefaultWriter(writer, stream).
+ SetUpWritableStreamDefaultWriter(writer, aStream, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 3. Return writer.
+ return writer.forget();
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#create-writable-stream
+already_AddRefed<WritableStream> WritableStream::CreateAbstract(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) {
+ // Step 1: Assert: ! IsNonNegativeNumber(highWaterMark) is true.
+ MOZ_ASSERT(IsNonNegativeNumber(aHighWaterMark));
+
+ // Step 2: Let stream be a new WritableStream.
+ // Step 3: Perform ! InitializeWritableStream(stream).
+ RefPtr<WritableStream> stream = new WritableStream(
+ aGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit);
+
+ // Step 4: Let controller be a new WritableStreamDefaultController.
+ auto controller =
+ MakeRefPtr<WritableStreamDefaultController>(aGlobal, *stream);
+
+ // Step 5: Perform ? SetUpWritableStreamDefaultController(stream, controller,
+ // startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm,
+ // highWaterMark, sizeAlgorithm).
+ SetUpWritableStreamDefaultController(aCx, stream, controller, aAlgorithms,
+ aHighWaterMark, aSizeAlgorithm, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 6: Return stream.
+ return stream.forget();
+}
+
+already_AddRefed<WritableStreamDefaultWriter> WritableStream::GetWriter(
+ ErrorResult& aRv) {
+ return AcquireWritableStreamDefaultWriter(this, aRv);
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#writable-stream-add-write-request
+already_AddRefed<Promise> WritableStreamAddWriteRequest(
+ WritableStream* aStream) {
+ // Step 1. Assert: ! IsWritableStreamLocked(stream) is true.
+ MOZ_ASSERT(IsWritableStreamLocked(aStream));
+
+ // Step 2. Assert: stream.[[state]] is "writable".
+ MOZ_ASSERT(aStream->State() == WritableStream::WriterState::Writable);
+
+ // Step 3. Let promise be a new promise.
+ RefPtr<Promise> promise =
+ Promise::CreateInfallible(aStream->GetParentObject());
+
+ // Step 4. Append promise to stream.[[writeRequests]].
+ aStream->AppendWriteRequest(promise);
+
+ // Step 5. Return promise.
+ return promise.forget();
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#writablestream-set-up
+// _BOUNDARY because `aAlgorithms->StartCallback` (called by
+// SetUpWritableStreamDefaultController below) should not be able to run script
+// in this case.
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void WritableStream::SetUpNative(
+ JSContext* aCx, UnderlyingSinkAlgorithmsWrapper& aAlgorithms,
+ Maybe<double> aHighWaterMark, QueuingStrategySize* aSizeAlgorithm,
+ ErrorResult& aRv) {
+ // an optional number highWaterMark (default 1)
+ double highWaterMark = aHighWaterMark.valueOr(1);
+ // and if given, highWaterMark must be a non-negative, non-NaN number.
+ MOZ_ASSERT(IsNonNegativeNumber(highWaterMark));
+
+ // Step 1: Let startAlgorithm be an algorithm that returns undefined.
+ // Step 2: Let closeAlgorithmWrapper be an algorithm that runs these steps:
+ // Step 3: Let abortAlgorithmWrapper be an algorithm that runs these steps:
+ // (Covered by UnderlyingSinkAlgorithmsWrapper)
+
+ // Step 4: If sizeAlgorithm was not given, then set it to an algorithm that
+ // returns 1. (Callers will treat nullptr as such, see
+ // WritableStream::Constructor for details)
+
+ // Step 5: Perform ! InitializeWritableStream(stream).
+ // (Covered by the constructor)
+
+ // Step 6: Let controller be a new WritableStreamDefaultController.
+ auto controller =
+ MakeRefPtr<WritableStreamDefaultController>(GetParentObject(), *this);
+
+ // Step 7: Perform ! SetUpWritableStreamDefaultController(stream, controller,
+ // startAlgorithm, writeAlgorithm, closeAlgorithmWrapper,
+ // abortAlgorithmWrapper, highWaterMark, sizeAlgorithm).
+ SetUpWritableStreamDefaultController(aCx, this, controller, &aAlgorithms,
+ highWaterMark, aSizeAlgorithm, aRv);
+}
+
+already_AddRefed<WritableStream> WritableStream::CreateNative(
+ JSContext* aCx, nsIGlobalObject& aGlobal,
+ UnderlyingSinkAlgorithmsWrapper& aAlgorithms, Maybe<double> aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) {
+ RefPtr<WritableStream> stream = new WritableStream(
+ &aGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit);
+ stream->SetUpNative(aCx, aAlgorithms, aHighWaterMark, aSizeAlgorithm, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return stream.forget();
+}
+
+// https://streams.spec.whatwg.org/#writablestream-error
+// To error a WritableStream stream given a JavaScript value e, perform !
+// WritableStreamDefaultControllerErrorIfNeeded(stream.[[controller]], e).
+void WritableStream::ErrorNative(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ // MOZ_KnownLive here instead of MOZ_KNOWN_LIVE at the field, because
+ // mController is set outside of the constructor
+ WritableStreamDefaultControllerErrorIfNeeded(aCx, MOZ_KnownLive(mController),
+ aError, aRv);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/streams/WritableStream.h b/dom/streams/WritableStream.h
new file mode 100644
index 0000000000..8b380020fe
--- /dev/null
+++ b/dom/streams/WritableStream.h
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_WritableStream_h
+#define mozilla_dom_WritableStream_h
+
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "mozilla/dom/WritableStreamDefaultController.h"
+#include "mozilla/dom/WritableStreamDefaultWriter.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class Promise;
+class WritableStreamDefaultController;
+class WritableStreamDefaultWriter;
+class UnderlyingSinkAlgorithmsBase;
+class UniqueMessagePortId;
+class MessagePort;
+
+class WritableStream : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WritableStream)
+
+ friend class ReadableStream;
+
+ protected:
+ virtual ~WritableStream();
+
+ virtual void LastRelease() {}
+
+ // If one extends WritableStream with another cycle collectable class,
+ // calling HoldJSObjects and DropJSObjects should happen using 'this' of
+ // that extending class. And in that case Explicit should be passed to the
+ // constructor of WriteableStream so that it doesn't make those calls.
+ // See also https://bugzilla.mozilla.org/show_bug.cgi?id=1801214.
+ enum class HoldDropJSObjectsCaller { Implicit, Explicit };
+
+ explicit WritableStream(const GlobalObject& aGlobal,
+ HoldDropJSObjectsCaller aHoldDropCaller);
+ explicit WritableStream(nsIGlobalObject* aGlobal,
+ HoldDropJSObjectsCaller aHoldDropCaller);
+
+ public:
+ // Slot Getter/Setters:
+ bool Backpressure() const { return mBackpressure; }
+ void SetBackpressure(bool aBackpressure) { mBackpressure = aBackpressure; }
+
+ Promise* GetCloseRequest() { return mCloseRequest; }
+ void SetCloseRequest(Promise* aRequest) { mCloseRequest = aRequest; }
+
+ MOZ_KNOWN_LIVE WritableStreamDefaultController* Controller() {
+ return mController;
+ }
+ void SetController(WritableStreamDefaultController& aController) {
+ MOZ_ASSERT(!mController);
+ mController = &aController;
+ }
+
+ Promise* GetInFlightWriteRequest() const { return mInFlightWriteRequest; }
+
+ Promise* GetPendingAbortRequestPromise() const {
+ return mPendingAbortRequestPromise;
+ }
+
+ void SetPendingAbortRequest(Promise* aPromise, JS::Handle<JS::Value> aReason,
+ bool aWasAlreadyErroring) {
+ mPendingAbortRequestPromise = aPromise;
+ mPendingAbortRequestReason = aReason;
+ mPendingAbortRequestWasAlreadyErroring = aWasAlreadyErroring;
+ }
+
+ WritableStreamDefaultWriter* GetWriter() const { return mWriter; }
+ void SetWriter(WritableStreamDefaultWriter* aWriter) { mWriter = aWriter; }
+
+ enum class WriterState { Writable, Closed, Erroring, Errored };
+
+ WriterState State() const { return mState; }
+ void SetState(const WriterState& aState) { mState = aState; }
+
+ JS::Value StoredError() const { return mStoredError; }
+ void SetStoredError(JS::Handle<JS::Value> aStoredError) {
+ mStoredError = aStoredError;
+ }
+
+ void AppendWriteRequest(RefPtr<Promise>& aRequest) {
+ mWriteRequests.AppendElement(aRequest);
+ }
+
+ // CreateWritableStream
+ MOZ_CAN_RUN_SCRIPT static already_AddRefed<WritableStream> CreateAbstract(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv);
+
+ // WritableStreamCloseQueuedOrInFlight
+ bool CloseQueuedOrInFlight() const {
+ return mCloseRequest || mInFlightCloseRequest;
+ }
+
+ // WritableStreamDealWithRejection
+ MOZ_CAN_RUN_SCRIPT void DealWithRejection(JSContext* aCx,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv);
+
+ // WritableStreamFinishErroring
+ MOZ_CAN_RUN_SCRIPT void FinishErroring(JSContext* aCx, ErrorResult& aRv);
+
+ // WritableStreamFinishInFlightClose
+ void FinishInFlightClose();
+
+ // WritableStreamFinishInFlightCloseWithError
+ MOZ_CAN_RUN_SCRIPT void FinishInFlightCloseWithError(
+ JSContext* aCx, JS::Handle<JS::Value> aError, ErrorResult& aRv);
+
+ // WritableStreamFinishInFlightWrite
+ void FinishInFlightWrite();
+
+ // WritableStreamFinishInFlightWriteWithError
+ MOZ_CAN_RUN_SCRIPT void FinishInFlightWriteWithError(
+ JSContext* aCX, JS::Handle<JS::Value> aError, ErrorResult& aR);
+
+ // WritableStreamHasOperationMarkedInFlight
+ bool HasOperationMarkedInFlight() const {
+ return mInFlightWriteRequest || mInFlightCloseRequest;
+ }
+
+ // WritableStreamMarkCloseRequestInFlight
+ void MarkCloseRequestInFlight();
+
+ // WritableStreamMarkFirstWriteRequestInFlight
+ void MarkFirstWriteRequestInFlight();
+
+ // WritableStreamRejectCloseAndClosedPromiseIfNeeded
+ void RejectCloseAndClosedPromiseIfNeeded();
+
+ // WritableStreamStartErroring
+ MOZ_CAN_RUN_SCRIPT void StartErroring(JSContext* aCx,
+ JS::Handle<JS::Value> aReason,
+ ErrorResult& aRv);
+
+ // WritableStreamUpdateBackpressure
+ void UpdateBackpressure(bool aBackpressure);
+
+ // [Transferable]
+ // https://html.spec.whatwg.org/multipage/structured-data.html#transfer-steps
+ MOZ_CAN_RUN_SCRIPT bool Transfer(JSContext* aCx,
+ UniqueMessagePortId& aPortId);
+ // https://html.spec.whatwg.org/multipage/structured-data.html#transfer-receiving-steps
+ MOZ_CAN_RUN_SCRIPT static already_AddRefed<WritableStream>
+ ReceiveTransferImpl(JSContext* aCx, nsIGlobalObject* aGlobal,
+ MessagePort& aPort);
+ MOZ_CAN_RUN_SCRIPT static bool ReceiveTransfer(
+ JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort,
+ JS::MutableHandle<JSObject*> aReturnObject);
+
+ // Public functions to implement other specs
+ // https://streams.spec.whatwg.org/#other-specs-ws
+
+ // https://streams.spec.whatwg.org/#writablestream-set-up
+ protected:
+ // Sets up the WritableStream. Intended for subclasses.
+ void SetUpNative(JSContext* aCx, UnderlyingSinkAlgorithmsWrapper& aAlgorithms,
+ Maybe<double> aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv);
+
+ public:
+ // Creates and sets up a WritableStream. Use SetUpNative for this purpose in
+ // subclasses.
+ static already_AddRefed<WritableStream> CreateNative(
+ JSContext* aCx, nsIGlobalObject& aGlobal,
+ UnderlyingSinkAlgorithmsWrapper& aAlgorithms,
+ Maybe<double> aHighWaterMark, QueuingStrategySize* aSizeAlgorithm,
+ ErrorResult& aRv);
+
+ // The following definitions must only be used on WritableStream instances
+ // initialized via the above set up algorithm:
+
+ // https://streams.spec.whatwg.org/#writablestream-error
+ MOZ_CAN_RUN_SCRIPT void ErrorNative(JSContext* aCx,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv);
+
+ // IDL layer functions
+
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // IDL methods
+
+ // TODO: Use MOZ_CAN_RUN_SCRIPT when IDL constructors can use it (bug 1749042)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY static already_AddRefed<WritableStream>
+ Constructor(const GlobalObject& aGlobal,
+ const Optional<JS::Handle<JSObject*>>& aUnderlyingSink,
+ const QueuingStrategy& aStrategy, ErrorResult& aRv);
+
+ bool Locked() const { return !!mWriter; }
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Abort(
+ JSContext* cx, JS::Handle<JS::Value> aReason, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Close(JSContext* aCx,
+ ErrorResult& aRv);
+
+ already_AddRefed<WritableStreamDefaultWriter> GetWriter(ErrorResult& aRv);
+
+ protected:
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ // Internal Slots:
+ private:
+ bool mBackpressure = false;
+ RefPtr<Promise> mCloseRequest;
+ RefPtr<WritableStreamDefaultController> mController;
+ RefPtr<Promise> mInFlightWriteRequest;
+ RefPtr<Promise> mInFlightCloseRequest;
+
+ // We inline all members of [[pendingAbortRequest]] in this class.
+ // The absence (i.e. undefined) of the [[pendingAbortRequest]]
+ // is indicated by mPendingAbortRequestPromise = nullptr.
+ RefPtr<Promise> mPendingAbortRequestPromise;
+ JS::Heap<JS::Value> mPendingAbortRequestReason;
+ bool mPendingAbortRequestWasAlreadyErroring = false;
+
+ WriterState mState = WriterState::Writable;
+ JS::Heap<JS::Value> mStoredError;
+ RefPtr<WritableStreamDefaultWriter> mWriter;
+ nsTArray<RefPtr<Promise>> mWriteRequests;
+
+ HoldDropJSObjectsCaller mHoldDropCaller;
+};
+
+namespace streams_abstract {
+
+inline bool IsWritableStreamLocked(WritableStream* aStream) {
+ return aStream->Locked();
+}
+
+MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> WritableStreamAbort(
+ JSContext* aCx, WritableStream* aStream, JS::Handle<JS::Value> aReason,
+ ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> WritableStreamClose(
+ JSContext* aCx, WritableStream* aStream, ErrorResult& aRv);
+
+already_AddRefed<Promise> WritableStreamAddWriteRequest(
+ WritableStream* aStream);
+
+already_AddRefed<WritableStreamDefaultWriter>
+AcquireWritableStreamDefaultWriter(WritableStream* aStream, ErrorResult& aRv);
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WritableStream_h
diff --git a/dom/streams/WritableStreamDefaultController.cpp b/dom/streams/WritableStreamDefaultController.cpp
new file mode 100644
index 0000000000..c1ee4bd8ea
--- /dev/null
+++ b/dom/streams/WritableStreamDefaultController.cpp
@@ -0,0 +1,564 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "js/Exception.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/AbortSignal.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Promise-inl.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/WritableStreamDefaultController.h"
+#include "mozilla/dom/WritableStreamDefaultControllerBinding.h"
+#include "mozilla/dom/UnderlyingSinkBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDebug.h"
+#include "nsISupports.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+// Note: Using the individual macros vs NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE
+// because I need to specificy a manual implementation of
+// NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN.
+NS_IMPL_CYCLE_COLLECTION_CLASS(WritableStreamDefaultController)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WritableStreamDefaultController)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal, mSignal, mStrategySizeAlgorithm,
+ mAlgorithms, mStream)
+ tmp->mQueue.clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WritableStreamDefaultController)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal, mSignal, mStrategySizeAlgorithm,
+ mAlgorithms, mStream)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WritableStreamDefaultController)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ // Trace the associated queue.
+ for (const auto& queueEntry : tmp->mQueue) {
+ aCallbacks.Trace(&queueEntry->mValue, "mQueue.mValue", aClosure);
+ }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WritableStreamDefaultController)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WritableStreamDefaultController)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStreamDefaultController)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+WritableStreamDefaultController::WritableStreamDefaultController(
+ nsISupports* aGlobal, WritableStream& aStream)
+ : mGlobal(do_QueryInterface(aGlobal)), mStream(&aStream) {
+ mozilla::HoldJSObjects(this);
+}
+
+WritableStreamDefaultController::~WritableStreamDefaultController() {
+ // MG:XXX: LinkedLists are required to be empty at destruction, but it seems
+ // it is possible to have a controller be destructed while still
+ // having entries in its queue.
+ //
+ // This needs to be verified as not indicating some other issue.
+ mQueue.clear();
+ mozilla::DropJSObjects(this);
+}
+
+JSObject* WritableStreamDefaultController::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return WritableStreamDefaultController_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// https://streams.spec.whatwg.org/#ws-default-controller-error
+void WritableStreamDefaultController::Error(JSContext* aCx,
+ JS::Handle<JS::Value> aError,
+ ErrorResult& aRv) {
+ // Step 1. Let state be this.[[stream]].[[state]].
+ // Step 2. If state is not "writable", return.
+ if (mStream->State() != WritableStream::WriterState::Writable) {
+ return;
+ }
+ // Step 3. Perform ! WritableStreamDefaultControllerError(this, e).
+ RefPtr<WritableStreamDefaultController> thisRefPtr = this;
+ WritableStreamDefaultControllerError(aCx, thisRefPtr, aError, aRv);
+}
+
+// https://streams.spec.whatwg.org/#ws-default-controller-private-abort
+already_AddRefed<Promise> WritableStreamDefaultController::AbortSteps(
+ JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
+ // Step 1. Let result be the result of performing this.[[abortAlgorithm]],
+ // passing reason.
+ RefPtr<UnderlyingSinkAlgorithmsBase> algorithms = mAlgorithms;
+ Optional<JS::Handle<JS::Value>> optionalReason(aCx, aReason);
+ RefPtr<Promise> abortPromise =
+ algorithms->AbortCallback(aCx, optionalReason, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 2. Perform ! WritableStreamDefaultControllerClearAlgorithms(this).
+ ClearAlgorithms();
+
+ // Step 3. Return result.
+ return abortPromise.forget();
+}
+
+// https://streams.spec.whatwg.org/#ws-default-controller-private-error
+void WritableStreamDefaultController::ErrorSteps() {
+ // Step 1. Perform ! ResetQueue(this).
+ ResetQueue(this);
+}
+
+void WritableStreamDefaultController::SetSignal(AbortSignal* aSignal) {
+ MOZ_ASSERT(aSignal);
+ mSignal = aSignal;
+}
+
+namespace streams_abstract {
+
+MOZ_CAN_RUN_SCRIPT static void
+WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ ErrorResult& aRv);
+
+// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller
+void SetUpWritableStreamDefaultController(
+ JSContext* aCx, WritableStream* aStream,
+ WritableStreamDefaultController* aController,
+ UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) {
+ // Step 1. Assert: stream implements WritableStream.
+ // Step 2. Assert: stream.[[controller]] is undefined.
+ MOZ_ASSERT(!aStream->Controller());
+
+ // Step 3. Set controller.[[stream]] to stream.
+ // Note: Already set in
+ // SetUpWritableStreamDefaultControllerFromUnderlyingSink.
+ MOZ_ASSERT(aController->Stream() == aStream);
+
+ // Step 4. Set stream.[[controller]] to controller.
+ aStream->SetController(*aController);
+
+ // Step 5. Perform ! ResetQueue(controller).
+ ResetQueue(aController);
+
+ // Step 6. Set controller.[[signal]] to a new AbortSignal.
+ RefPtr<AbortSignal> signal = new AbortSignal(aController->GetParentObject(),
+ false, JS::UndefinedHandleValue);
+ aController->SetSignal(signal);
+
+ // Step 7. Set controller.[[started]] to false.
+ aController->SetStarted(false);
+
+ // Step 8. Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm.
+ aController->SetStrategySizeAlgorithm(aSizeAlgorithm);
+
+ // Step 9. Set controller.[[strategyHWM]] to highWaterMark.
+ aController->SetStrategyHWM(aHighWaterMark);
+
+ // Step 10. Set controller.[[writeAlgorithm]] to writeAlgorithm.
+ // Step 11. Set controller.[[closeAlgorithm]] to closeAlgorithm.
+ // Step 12. Set controller.[[abortAlgorithm]] to abortAlgorithm.
+ aController->SetAlgorithms(*aAlgorithms);
+
+ // Step 13. Let backpressure be !
+ // WritableStreamDefaultControllerGetBackpressure(controller).
+ bool backpressure = aController->GetBackpressure();
+
+ // Step 14. Perform ! WritableStreamUpdateBackpressure(stream, backpressure).
+ aStream->UpdateBackpressure(backpressure);
+
+ // Step 15. Let startResult be the result of performing startAlgorithm. (This
+ // may throw an exception.)
+ JS::Rooted<JS::Value> startResult(aCx, JS::UndefinedValue());
+ RefPtr<WritableStreamDefaultController> controller(aController);
+ aAlgorithms->StartCallback(aCx, *controller, &startResult, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 16. Let startPromise be a promise resolved with startResult.
+ RefPtr<Promise> startPromise =
+ Promise::CreateInfallible(aStream->GetParentObject());
+ startPromise->MaybeResolve(startResult);
+
+ // Step 17/18.
+ startPromise->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ WritableStreamDefaultController* aController)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ // Step 17. Upon fulfillment of startPromise,
+ // Step 17.1. Assert: stream.[[state]] is "writable" or "erroring".
+ MOZ_ASSERT(aController->Stream()->State() ==
+ WritableStream::WriterState::Writable ||
+ aController->Stream()->State() ==
+ WritableStream::WriterState::Erroring);
+ // Step 17.2. Set controller.[[started]] to true.
+ aController->SetStarted(true);
+ // Step 17.3 Perform
+ // !WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
+ WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ aCx, MOZ_KnownLive(aController), aRv);
+ },
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ WritableStreamDefaultController* aController)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ RefPtr<WritableStream> stream = aController->Stream();
+ // Step 18. Upon rejection of startPromise with reason r,
+ // Step 18.1. Assert: stream.[[state]] is "writable" or "erroring".
+ MOZ_ASSERT(
+ stream->State() == WritableStream::WriterState::Writable ||
+ stream->State() == WritableStream::WriterState::Erroring);
+ // Step 18.2. Set controller.[[started]] to true.
+ aController->SetStarted(true);
+ // Step 18.3. Perform ! WritableStreamDealWithRejection(stream, r).
+ stream->DealWithRejection(aCx, aValue, aRv);
+ },
+ RefPtr(aController));
+}
+
+// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink
+void SetUpWritableStreamDefaultControllerFromUnderlyingSink(
+ JSContext* aCx, WritableStream* aStream,
+ JS::Handle<JSObject*> aUnderlyingSink, UnderlyingSink& aUnderlyingSinkDict,
+ double aHighWaterMark, QueuingStrategySize* aSizeAlgorithm,
+ ErrorResult& aRv) {
+ // Step 1.
+ RefPtr<WritableStreamDefaultController> controller =
+ new WritableStreamDefaultController(aStream->GetParentObject(), *aStream);
+
+ // Step 2 - 9.
+ auto algorithms = MakeRefPtr<UnderlyingSinkAlgorithms>(
+ aStream->GetParentObject(), aUnderlyingSink, aUnderlyingSinkDict);
+
+ // Step 10.
+ SetUpWritableStreamDefaultController(aCx, aStream, controller, algorithms,
+ aHighWaterMark, aSizeAlgorithm, aRv);
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close
+MOZ_CAN_RUN_SCRIPT static void WritableStreamDefaultControllerProcessClose(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ ErrorResult& aRv) {
+ // Step 1. Let stream be controller.[[stream]].
+ RefPtr<WritableStream> stream = aController->Stream();
+
+ // Step 2. Perform ! WritableStreamMarkCloseRequestInFlight(stream).
+ stream->MarkCloseRequestInFlight();
+
+ // Step 3. Perform ! DequeueValue(controller).
+ JS::Rooted<JS::Value> value(aCx);
+ DequeueValue(aController, &value);
+
+ // Step 4. Assert: controller.[[queue]] is empty.
+ MOZ_ASSERT(aController->Queue().isEmpty());
+
+ // Step 5. Let sinkClosePromise be the result of performing
+ // controller.[[closeAlgorithm]].
+ RefPtr<UnderlyingSinkAlgorithmsBase> algorithms =
+ aController->GetAlgorithms();
+ RefPtr<Promise> sinkClosePromise = algorithms->CloseCallback(aCx, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 6. Perform !
+ // WritableStreamDefaultControllerClearAlgorithms(controller).
+ aController->ClearAlgorithms();
+
+ // Step 7 + 8.
+ sinkClosePromise->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ WritableStreamDefaultController* aController) {
+ RefPtr<WritableStream> stream = aController->Stream();
+ // Step 7. Upon fulfillment of sinkClosePromise,
+ // Step 7.1. Perform ! WritableStreamFinishInFlightClose(stream).
+ stream->FinishInFlightClose();
+ },
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ WritableStreamDefaultController* aController)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ RefPtr<WritableStream> stream = aController->Stream();
+ // Step 8. Upon rejection of sinkClosePromise with reason reason,
+ // Step 8.1. Perform
+ // ! WritableStreamFinishInFlightCloseWithError(stream, reason).
+ stream->FinishInFlightCloseWithError(aCx, aValue, aRv);
+ },
+ RefPtr(aController));
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write
+MOZ_CAN_RUN_SCRIPT static void WritableStreamDefaultControllerProcessWrite(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
+ // Step 1. Let stream be controller.[[stream]].
+ RefPtr<WritableStream> stream = aController->Stream();
+
+ // Step 2. Perform ! WritableStreamMarkFirstWriteRequestInFlight(stream).
+ stream->MarkFirstWriteRequestInFlight();
+
+ // Step 3. Let sinkWritePromise be the result of performing
+ // controller.[[writeAlgorithm]], passing in chunk.
+ RefPtr<UnderlyingSinkAlgorithmsBase> algorithms =
+ aController->GetAlgorithms();
+ RefPtr<Promise> sinkWritePromise =
+ algorithms->WriteCallback(aCx, aChunk, *aController, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Step 4 + 5:
+ sinkWritePromise->AddCallbacksWithCycleCollectedArgs(
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ WritableStreamDefaultController* aController)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ RefPtr<WritableStream> stream = aController->Stream();
+
+ // Step 4.1. Perform ! WritableStreamFinishInFlightWrite(stream).
+ stream->FinishInFlightWrite();
+
+ // Step 4.2. Let state be stream.[[state]].
+ WritableStream::WriterState state = stream->State();
+
+ // Step 4.3. Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(state == WritableStream::WriterState::Writable ||
+ state == WritableStream::WriterState::Erroring);
+
+ // Step 4.4. Perform ! DequeueValue(controller).
+ JS::Rooted<JS::Value> value(aCx);
+ DequeueValue(aController, &value);
+
+ // Step 4.5. If ! WritableStreamCloseQueuedOrInFlight(stream) is
+ // false and state is "writable",
+ if (!stream->CloseQueuedOrInFlight() &&
+ state == WritableStream::WriterState::Writable) {
+ // Step 4.5.1. Let backpressure be !
+ // WritableStreamDefaultControllerGetBackpressure(controller).
+ bool backpressure = aController->GetBackpressure();
+ // Step 4.5.2. Perform ! WritableStreamUpdateBackpressure(stream,
+ // backpressure).
+ stream->UpdateBackpressure(backpressure);
+ }
+
+ // Step 4.6. Perform !
+ // WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
+ WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ aCx, MOZ_KnownLive(aController), aRv);
+ },
+ [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
+ WritableStreamDefaultController* aController)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ RefPtr<WritableStream> stream = aController->Stream();
+
+ // Step 5.1. If stream.[[state]] is "writable", perform !
+ // WritableStreamDefaultControllerClearAlgorithms(controller).
+ if (stream->State() == WritableStream::WriterState::Writable) {
+ aController->ClearAlgorithms();
+ }
+
+ // Step 5.2. Perform !
+ // WritableStreamFinishInFlightWriteWithError(stream, reason)
+ stream->FinishInFlightWriteWithError(aCx, aValue, aRv);
+ },
+ RefPtr(aController));
+}
+
+// We use a JS::MagicValue to represent the close sentinel required by the spec.
+// Normal JavaScript code can not generate magic values, so we can use this
+// as a special value. However care has to be taken to not leak the magic value
+// to other code.
+constexpr JSWhyMagic CLOSE_SENTINEL = JS_GENERIC_MAGIC;
+
+// https://streams.spec.whatwg.org/#writable-stream-default-controller-advance-queue-if-needed
+static void WritableStreamDefaultControllerAdvanceQueueIfNeeded(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ ErrorResult& aRv) {
+ // Step 1. Let stream be controller.[[stream]].
+ RefPtr<WritableStream> stream = aController->Stream();
+
+ // Step 2. If controller.[[started]] is false, return.
+ if (!aController->Started()) {
+ return;
+ }
+
+ // Step 3. If stream.[[inFlightWriteRequest]] is not undefined, return.
+ if (stream->GetInFlightWriteRequest()) {
+ return;
+ }
+
+ // Step 4. Let state be stream.[[state]].
+ WritableStream::WriterState state = stream->State();
+
+ // Step 5. Assert: state is not "closed" or "errored".
+ MOZ_ASSERT(state != WritableStream::WriterState::Closed &&
+ state != WritableStream::WriterState::Errored);
+
+ // Step 6. If state is "erroring",
+ if (state == WritableStream::WriterState::Erroring) {
+ // Step 6.1. Perform ! WritableStreamFinishErroring(stream).
+ stream->FinishErroring(aCx, aRv);
+
+ // Step 6.2. Return.
+ return;
+ }
+
+ // Step 7. If controller.[[queue]] is empty, return.
+ if (aController->Queue().isEmpty()) {
+ return;
+ }
+
+ // Step 8. Let value be ! PeekQueueValue(controller).
+ JS::Rooted<JS::Value> value(aCx);
+ PeekQueueValue(aController, &value);
+
+ // Step 9. If value is the close sentinel, perform !
+ // WritableStreamDefaultControllerProcessClose(controller).
+ if (value.isMagic(CLOSE_SENTINEL)) {
+ WritableStreamDefaultControllerProcessClose(aCx, aController, aRv);
+ return;
+ }
+
+ // Step 10. Otherwise, perform !
+ // WritableStreamDefaultControllerProcessWrite(controller, value).
+ WritableStreamDefaultControllerProcessWrite(aCx, aController, value, aRv);
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-controller-close
+void WritableStreamDefaultControllerClose(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ ErrorResult& aRv) {
+ // Step 1. Perform ! EnqueueValueWithSize(controller, close sentinel, 0).
+ JS::Rooted<JS::Value> aCloseSentinel(aCx, JS::MagicValue(CLOSE_SENTINEL));
+ EnqueueValueWithSize(aController, aCloseSentinel, 0, aRv);
+ MOZ_ASSERT(!aRv.Failed());
+
+ // Step 2. Perform !
+ // WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
+ WritableStreamDefaultControllerAdvanceQueueIfNeeded(aCx, aController, aRv);
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-controller-write
+void WritableStreamDefaultControllerWrite(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aChunk, double chunkSize, ErrorResult& aRv) {
+ // Step 1. Let enqueueResult be EnqueueValueWithSize(controller, chunk,
+ // chunkSize).
+ IgnoredErrorResult rv;
+ EnqueueValueWithSize(aController, aChunk, chunkSize, rv);
+
+ // Step 2. If enqueueResult is an abrupt completion,
+ if (rv.MaybeSetPendingException(aCx,
+ "WritableStreamDefaultController.write")) {
+ JS::Rooted<JS::Value> error(aCx);
+ JS_GetPendingException(aCx, &error);
+ JS_ClearPendingException(aCx);
+
+ // Step 2.1. Perform !
+ // WritableStreamDefaultControllerErrorIfNeeded(controller,
+ // enqueueResult.[[Value]]).
+ WritableStreamDefaultControllerErrorIfNeeded(aCx, aController, error, aRv);
+
+ // Step 2.2. Return.
+ return;
+ }
+
+ // Step 3. Let stream be controller.[[stream]].
+ RefPtr<WritableStream> stream = aController->Stream();
+
+ // Step 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
+ // stream.[[state]] is "writable",
+ if (!stream->CloseQueuedOrInFlight() &&
+ stream->State() == WritableStream::WriterState::Writable) {
+ // Step 4.1. Let backpressure be
+ // !WritableStreamDefaultControllerGetBackpressure(controller).
+ bool backpressure = aController->GetBackpressure();
+
+ // Step 4.2. Perform ! WritableStreamUpdateBackpressure(stream,
+ // backpressure).
+ stream->UpdateBackpressure(backpressure);
+ }
+
+ // Step 5. Perform
+ // ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller).
+ WritableStreamDefaultControllerAdvanceQueueIfNeeded(aCx, aController, aRv);
+}
+
+void WritableStreamDefaultControllerError(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aError, ErrorResult& aRv) {
+ // Step 1. Let stream be controller.[[stream]].
+ RefPtr<WritableStream> stream = aController->Stream();
+
+ // Step 2. Assert: stream.[[state]] is "writable".
+ MOZ_ASSERT(stream->State() == WritableStream::WriterState::Writable);
+
+ // Step 3. Perform
+ // ! WritableStreamDefaultControllerClearAlgorithms(controller).
+ aController->ClearAlgorithms();
+
+ // Step 4.Perform ! WritableStreamStartErroring(stream, error).
+ stream->StartErroring(aCx, aError, aRv);
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-controller-error-if-needed
+void WritableStreamDefaultControllerErrorIfNeeded(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aError, ErrorResult& aRv) {
+ // Step 1. If controller.[[stream]].[[state]] is "writable", perform
+ // !WritableStreamDefaultControllerError(controller, error).
+ if (aController->Stream()->State() == WritableStream::WriterState::Writable) {
+ WritableStreamDefaultControllerError(aCx, aController, aError, aRv);
+ }
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-controller-get-chunk-size
+double WritableStreamDefaultControllerGetChunkSize(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
+ // Step 1. Let returnValue be the result of performing
+ // controller.[[strategySizeAlgorithm]], passing in chunk, and interpreting
+ // the result as a completion record.
+ RefPtr<QueuingStrategySize> sizeAlgorithm(
+ aController->StrategySizeAlgorithm());
+
+ // If !sizeAlgorithm, we return 1, which is inlined from
+ // https://streams.spec.whatwg.org/#make-size-algorithm-from-size-function
+ Optional<JS::Handle<JS::Value>> optionalChunk(aCx, aChunk);
+
+ double chunkSize =
+ sizeAlgorithm
+ ? sizeAlgorithm->Call(
+ optionalChunk, aRv,
+ "WritableStreamDefaultController.[[strategySizeAlgorithm]]",
+ CallbackObject::eRethrowExceptions)
+ : 1.0;
+
+ // Step 2. If returnValue is an abrupt completion,
+ if (aRv.MaybeSetPendingException(
+ aCx, "WritableStreamDefaultController.[[strategySizeAlgorithm]]")) {
+ JS::Rooted<JS::Value> error(aCx);
+ JS_GetPendingException(aCx, &error);
+ JS_ClearPendingException(aCx);
+
+ // Step 2.1. Perform !
+ // WritableStreamDefaultControllerErrorIfNeeded(controller,
+ // returnValue.[[Value]]).
+ WritableStreamDefaultControllerErrorIfNeeded(aCx, aController, error, aRv);
+
+ // Step 2.2. Return 1.
+ return 1.0;
+ }
+
+ // Step 3. Return returnValue.[[Value]].
+ return chunkSize;
+}
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
diff --git a/dom/streams/WritableStreamDefaultController.h b/dom/streams/WritableStreamDefaultController.h
new file mode 100644
index 0000000000..d41014594c
--- /dev/null
+++ b/dom/streams/WritableStreamDefaultController.h
@@ -0,0 +1,178 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_WritableStreamDefaultController_h
+#define mozilla_dom_WritableStreamDefaultController_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+#include "mozilla/dom/QueueWithSizes.h"
+#include "mozilla/dom/ReadRequest.h"
+#include "mozilla/dom/UnderlyingSinkCallbackHelpers.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "mozilla/dom/Nullable.h"
+#include "nsTArray.h"
+#include "nsISupports.h"
+
+namespace mozilla::dom {
+
+class AbortSignal;
+class WritableStream;
+struct UnderlyingSink;
+
+class WritableStreamDefaultController final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WritableStreamDefaultController)
+
+ explicit WritableStreamDefaultController(nsISupports* aGlobal,
+ WritableStream& aStream);
+
+ protected:
+ ~WritableStreamDefaultController();
+
+ public:
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL methods/properties
+
+ AbortSignal* Signal() { return mSignal; }
+
+ MOZ_CAN_RUN_SCRIPT void Error(JSContext* aCx, JS::Handle<JS::Value> aError,
+ ErrorResult& aRv);
+
+ // [[AbortSteps]]
+ MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> AbortSteps(
+ JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv);
+
+ // [[ErrorSteps]]
+ virtual void ErrorSteps();
+
+ // Internal Slot Accessors
+
+ QueueWithSizes& Queue() { return mQueue; }
+
+ double QueueTotalSize() const { return mQueueTotalSize; }
+ void SetQueueTotalSize(double aQueueTotalSize) {
+ mQueueTotalSize = aQueueTotalSize;
+ }
+
+ void SetSignal(AbortSignal* aSignal);
+
+ bool Started() const { return mStarted; }
+ void SetStarted(bool aStarted) { mStarted = aStarted; }
+
+ double StrategyHWM() const { return mStrategyHWM; }
+ void SetStrategyHWM(double aStrategyHWM) { mStrategyHWM = aStrategyHWM; }
+
+ QueuingStrategySize* StrategySizeAlgorithm() const {
+ return mStrategySizeAlgorithm;
+ }
+ void SetStrategySizeAlgorithm(QueuingStrategySize* aStrategySizeAlgorithm) {
+ mStrategySizeAlgorithm = aStrategySizeAlgorithm;
+ }
+
+ UnderlyingSinkAlgorithmsBase* GetAlgorithms() { return mAlgorithms; }
+ void SetAlgorithms(UnderlyingSinkAlgorithmsBase& aAlgorithms) {
+ mAlgorithms = &aAlgorithms;
+ }
+
+ WritableStream* Stream() { return mStream; }
+
+ // WritableStreamDefaultControllerGetBackpressure
+ // https://streams.spec.whatwg.org/#writable-stream-default-controller-get-backpressure
+ bool GetBackpressure() const {
+ // Step 1. Let desiredSize be !
+ // WritableStreamDefaultControllerGetDesiredSize(controller).
+ double desiredSize = GetDesiredSize();
+ // Step 2. Return true if desiredSize ≤ 0, or false otherwise.
+ return desiredSize <= 0;
+ }
+
+ // WritableStreamDefaultControllerGetDesiredSize
+ // https://streams.spec.whatwg.org/#writable-stream-default-controller-get-desired-size
+ double GetDesiredSize() const { return mStrategyHWM - mQueueTotalSize; }
+
+ // WritableStreamDefaultControllerClearAlgorithms
+ // https://streams.spec.whatwg.org/#writable-stream-default-controller-clear-algorithms
+ void ClearAlgorithms() {
+ // Step 1. Set controller.[[writeAlgorithm]] to undefined.
+ // Step 2. Set controller.[[closeAlgorithm]] to undefined.
+ // Step 3. Set controller.[[abortAlgorithm]] to undefined.
+ // (As written in the spec, this can happen multiple time. Try running
+ // wpt/streams/transform-streams/errors.any.js for example.)
+ if (RefPtr<UnderlyingSinkAlgorithmsBase> algorithms =
+ mAlgorithms.forget()) {
+ algorithms->ReleaseObjects();
+ }
+
+ // Step 4. Set controller.[[strategySizeAlgorithm]] to undefined.
+ mStrategySizeAlgorithm = nullptr;
+ }
+
+ private:
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+
+ // Internal Slots
+ QueueWithSizes mQueue = {};
+ double mQueueTotalSize = 0.0;
+ RefPtr<AbortSignal> mSignal;
+ bool mStarted = false;
+ double mStrategyHWM = 0.0;
+
+ RefPtr<QueuingStrategySize> mStrategySizeAlgorithm;
+ RefPtr<UnderlyingSinkAlgorithmsBase> mAlgorithms;
+ RefPtr<WritableStream> mStream;
+};
+
+namespace streams_abstract {
+
+MOZ_CAN_RUN_SCRIPT void SetUpWritableStreamDefaultController(
+ JSContext* aCx, WritableStream* aStream,
+ WritableStreamDefaultController* aController,
+ UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark,
+ QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void SetUpWritableStreamDefaultControllerFromUnderlyingSink(
+ JSContext* aCx, WritableStream* aStream,
+ JS::Handle<JSObject*> aUnderlyingSink, UnderlyingSink& aUnderlyingSinkDict,
+ double aHighWaterMark, QueuingStrategySize* aSizeAlgorithm,
+ ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void WritableStreamDefaultControllerClose(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void WritableStreamDefaultControllerWrite(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aChunk, double chunkSize, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void WritableStreamDefaultControllerError(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aError, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT void WritableStreamDefaultControllerErrorIfNeeded(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aError, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT double WritableStreamDefaultControllerGetChunkSize(
+ JSContext* aCx, WritableStreamDefaultController* aController,
+ JS::Handle<JS::Value> aChunk, ErrorResult& aRv);
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WritableStreamDefaultController_h
diff --git a/dom/streams/WritableStreamDefaultWriter.cpp b/dom/streams/WritableStreamDefaultWriter.cpp
new file mode 100644
index 0000000000..589eada7e2
--- /dev/null
+++ b/dom/streams/WritableStreamDefaultWriter.cpp
@@ -0,0 +1,544 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/WritableStreamDefaultWriter.h"
+#include "js/Array.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/WritableStream.h"
+#include "mozilla/dom/WritableStreamDefaultWriterBinding.h"
+#include "nsCOMPtr.h"
+
+#include "mozilla/dom/Promise-inl.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+
+namespace mozilla::dom {
+
+using namespace streams_abstract;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WritableStreamDefaultWriter)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WritableStreamDefaultWriter)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal, mStream, mReadyPromise,
+ mClosedPromise)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WritableStreamDefaultWriter)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal, mStream, mReadyPromise,
+ mClosedPromise)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WritableStreamDefaultWriter)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WritableStreamDefaultWriter)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WritableStreamDefaultWriter)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStreamDefaultWriter)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+WritableStreamDefaultWriter::WritableStreamDefaultWriter(
+ nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal) {
+ mozilla::HoldJSObjects(this);
+}
+
+WritableStreamDefaultWriter::~WritableStreamDefaultWriter() {
+ mozilla::DropJSObjects(this);
+}
+
+void WritableStreamDefaultWriter::SetReadyPromise(Promise* aPromise) {
+ MOZ_ASSERT(aPromise);
+ mReadyPromise = aPromise;
+}
+
+void WritableStreamDefaultWriter::SetClosedPromise(Promise* aPromise) {
+ MOZ_ASSERT(aPromise);
+ mClosedPromise = aPromise;
+}
+
+JSObject* WritableStreamDefaultWriter::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return WritableStreamDefaultWriter_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */
+already_AddRefed<WritableStreamDefaultWriter>
+WritableStreamDefaultWriter::Constructor(const GlobalObject& aGlobal,
+ WritableStream& aStream,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+ RefPtr<WritableStreamDefaultWriter> writer =
+ new WritableStreamDefaultWriter(global);
+ SetUpWritableStreamDefaultWriter(writer, &aStream, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return writer.forget();
+}
+
+already_AddRefed<Promise> WritableStreamDefaultWriter::Closed() {
+ RefPtr<Promise> closedPromise = mClosedPromise;
+ return closedPromise.forget();
+}
+
+already_AddRefed<Promise> WritableStreamDefaultWriter::Ready() {
+ RefPtr<Promise> readyPromise = mReadyPromise;
+ return readyPromise.forget();
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#writable-stream-default-writer-get-desired-size
+Nullable<double> WritableStreamDefaultWriterGetDesiredSize(
+ WritableStreamDefaultWriter* aWriter) {
+ // Step 1. Let stream be writer.[[stream]].
+ RefPtr<WritableStream> stream = aWriter->GetStream();
+
+ // Step 2. Let state be stream.[[state]].
+ WritableStream::WriterState state = stream->State();
+
+ // Step 3. If state is "errored" or "erroring", return null.
+ if (state == WritableStream::WriterState::Errored ||
+ state == WritableStream::WriterState::Erroring) {
+ return nullptr;
+ }
+
+ // Step 4. If state is "closed", return 0.
+ if (state == WritableStream::WriterState::Closed) {
+ return 0.0;
+ }
+
+ // Step 5. Return
+ // ! WritableStreamDefaultControllerGetDesiredSize(stream.[[controller]]).
+ return stream->Controller()->GetDesiredSize();
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#default-writer-desired-size
+Nullable<double> WritableStreamDefaultWriter::GetDesiredSize(ErrorResult& aRv) {
+ // Step 1. If this.[[stream]] is undefined, throw a TypeError exception.
+ if (!mStream) {
+ aRv.ThrowTypeError("Missing stream");
+ return nullptr;
+ }
+
+ // Step 2. Return ! WritableStreamDefaultWriterGetDesiredSize(this).
+ RefPtr<WritableStreamDefaultWriter> thisRefPtr = this;
+ return WritableStreamDefaultWriterGetDesiredSize(thisRefPtr);
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-writer-abort
+MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> WritableStreamDefaultWriterAbort(
+ JSContext* aCx, WritableStreamDefaultWriter* aWriter,
+ JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
+ // Step 1. Let stream be writer.[[stream]].
+ RefPtr<WritableStream> stream = aWriter->GetStream();
+
+ // Step 2. Assert: stream is not undefined.
+ MOZ_ASSERT(stream);
+
+ // Step 3. Return ! WritableStreamAbort(stream, reason).
+ return WritableStreamAbort(aCx, stream, aReason, aRv);
+}
+
+// https://streams.spec.whatwg.org/#default-writer-abort
+already_AddRefed<Promise> WritableStreamDefaultWriter::Abort(
+ JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv) {
+ // Step 1. If this.[[stream]] is undefined, return a promise rejected with a
+ // TypeError exception.
+ if (!mStream) {
+ aRv.ThrowTypeError("Missing stream");
+ return nullptr;
+ }
+
+ // Step 2. Return ! WritableStreamDefaultWriterAbort(this, reason).
+ RefPtr<WritableStreamDefaultWriter> thisRefPtr = this;
+ return WritableStreamDefaultWriterAbort(aCx, thisRefPtr, aReason, aRv);
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-writer-close
+MOZ_CAN_RUN_SCRIPT static already_AddRefed<Promise>
+WritableStreamDefaultWriterClose(JSContext* aCx,
+ WritableStreamDefaultWriter* aWriter,
+ ErrorResult& aRv) {
+ // Step 1. Let stream be writer.[[stream]].
+ RefPtr<WritableStream> stream = aWriter->GetStream();
+
+ // Step 2. Assert: stream is not undefined.
+ MOZ_ASSERT(stream);
+
+ // Step 3. Return ! WritableStreamClose(stream).
+ return WritableStreamClose(aCx, stream, aRv);
+}
+
+// https://streams.spec.whatwg.org/#default-writer-close
+already_AddRefed<Promise> WritableStreamDefaultWriter::Close(JSContext* aCx,
+ ErrorResult& aRv) {
+ // Step 1. Let stream be this.[[stream]].
+ RefPtr<WritableStream> stream = mStream;
+
+ // Step 2. If stream is undefined, return a promise rejected with a TypeError
+ // exception.
+ if (!stream) {
+ aRv.ThrowTypeError("Missing stream");
+ return nullptr;
+ }
+
+ // Step 3. If ! WritableStreamCloseQueuedOrInFlight(stream) is true,
+ // return a promise rejected with a TypeError exception.
+ if (stream->CloseQueuedOrInFlight()) {
+ aRv.ThrowTypeError("Stream is closing");
+ return nullptr;
+ }
+
+ // Step 3. Return ! WritableStreamDefaultWriterClose(this).
+ RefPtr<WritableStreamDefaultWriter> thisRefPtr = this;
+ return WritableStreamDefaultWriterClose(aCx, thisRefPtr, aRv);
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#writable-stream-default-writer-release
+void WritableStreamDefaultWriterRelease(JSContext* aCx,
+ WritableStreamDefaultWriter* aWriter) {
+ // Step 1. Let stream be writer.[[stream]].
+ RefPtr<WritableStream> stream = aWriter->GetStream();
+
+ // Step 2. Assert: stream is not undefined.
+ MOZ_ASSERT(stream);
+
+ // Step 3. Assert: stream.[[writer]] is writer.
+ MOZ_ASSERT(stream->GetWriter() == aWriter);
+
+ // Step 4. Let releasedError be a new TypeError.
+ JS::Rooted<JS::Value> releasedError(RootingCx(), JS::UndefinedValue());
+ {
+ ErrorResult rv;
+ rv.ThrowTypeError("Releasing lock");
+ bool ok = ToJSValue(aCx, std::move(rv), &releasedError);
+ MOZ_RELEASE_ASSERT(ok, "must be ok");
+ }
+
+ // Step 5. Perform !
+ // WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer,
+ // releasedError).
+ WritableStreamDefaultWriterEnsureReadyPromiseRejected(aWriter, releasedError);
+
+ // Step 6. Perform !
+ // WritableStreamDefaultWriterEnsureClosedPromiseRejected(writer,
+ // releasedError).
+ WritableStreamDefaultWriterEnsureClosedPromiseRejected(aWriter,
+ releasedError);
+
+ // Step 7. Set stream.[[writer]] to undefined.
+ stream->SetWriter(nullptr);
+
+ // Step 8. Set writer.[[stream]] to undefined.
+ aWriter->SetStream(nullptr);
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#default-writer-release-lock
+void WritableStreamDefaultWriter::ReleaseLock(JSContext* aCx) {
+ // Step 1. Let stream be this.[[stream]].
+ RefPtr<WritableStream> stream = mStream;
+
+ // Step 2. If stream is undefined, return.
+ if (!stream) {
+ return;
+ }
+
+ // Step 3. Assert: stream.[[writer]] is not undefined.
+ MOZ_ASSERT(stream->GetWriter());
+
+ // Step 4. Perform ! WritableStreamDefaultWriterRelease(this).
+ RefPtr<WritableStreamDefaultWriter> thisRefPtr = this;
+ return WritableStreamDefaultWriterRelease(aCx, thisRefPtr);
+}
+
+namespace streams_abstract {
+// https://streams.spec.whatwg.org/#writable-stream-default-writer-write
+already_AddRefed<Promise> WritableStreamDefaultWriterWrite(
+ JSContext* aCx, WritableStreamDefaultWriter* aWriter,
+ JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
+ // Step 1. Let stream be writer.[[stream]].
+ RefPtr<WritableStream> stream = aWriter->GetStream();
+
+ // Step 2. Assert: stream is not undefined.
+ MOZ_ASSERT(stream);
+
+ // Step 3. Let controller be stream.[[controller]].
+ RefPtr<WritableStreamDefaultController> controller = stream->Controller();
+
+ // Step 4. Let chunkSize be !
+ // WritableStreamDefaultControllerGetChunkSize(controller, chunk).
+ double chunkSize =
+ WritableStreamDefaultControllerGetChunkSize(aCx, controller, aChunk, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 5. If stream is not equal to writer.[[stream]], return a promise
+ // rejected with a TypeError exception.
+ if (stream != aWriter->GetStream()) {
+ aRv.ThrowTypeError(
+ "Can not write on WritableStream owned by another writer.");
+ return nullptr;
+ }
+
+ // Step 6. Let state be stream.[[state]].
+ WritableStream::WriterState state = stream->State();
+
+ // Step 7. If state is "errored", return a promise rejected with
+ // stream.[[storedError]].
+ if (state == WritableStream::WriterState::Errored) {
+ JS::Rooted<JS::Value> error(aCx, stream->StoredError());
+ return Promise::CreateRejected(aWriter->GetParentObject(), error, aRv);
+ }
+
+ // 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 (stream->CloseQueuedOrInFlight() ||
+ state == WritableStream::WriterState::Closed) {
+ return Promise::CreateRejectedWithTypeError(
+ aWriter->GetParentObject(), "Stream is closed or closing"_ns, aRv);
+ }
+
+ // Step 9. If state is "erroring", return a promise rejected with
+ // stream.[[storedError]].
+ if (state == WritableStream::WriterState::Erroring) {
+ JS::Rooted<JS::Value> error(aCx, stream->StoredError());
+ return Promise::CreateRejected(aWriter->GetParentObject(), error, aRv);
+ }
+
+ // Step 10. Assert: state is "writable".
+ MOZ_ASSERT(state == WritableStream::WriterState::Writable);
+
+ // Step 11. Let promise be ! WritableStreamAddWriteRequest(stream).
+ RefPtr<Promise> promise = WritableStreamAddWriteRequest(stream);
+
+ // Step 12. Perform ! WritableStreamDefaultControllerWrite(controller, chunk,
+ // chunkSize).
+ WritableStreamDefaultControllerWrite(aCx, controller, aChunk, chunkSize, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // Step 13. Return promise.
+ return promise.forget();
+}
+} // namespace streams_abstract
+
+// https://streams.spec.whatwg.org/#default-writer-write
+already_AddRefed<Promise> WritableStreamDefaultWriter::Write(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk, ErrorResult& aRv) {
+ // Step 1. If this.[[stream]] is undefined, return a promise rejected with a
+ // TypeError exception.
+ if (!mStream) {
+ aRv.ThrowTypeError("Missing stream");
+ return nullptr;
+ }
+
+ // Step 2. Return ! WritableStreamDefaultWriterWrite(this, chunk).
+ return WritableStreamDefaultWriterWrite(aCx, this, aChunk, aRv);
+}
+
+namespace streams_abstract {
+
+// https://streams.spec.whatwg.org/#set-up-writable-stream-default-writer
+void SetUpWritableStreamDefaultWriter(WritableStreamDefaultWriter* aWriter,
+ WritableStream* aStream,
+ ErrorResult& aRv) {
+ // Step 1. If ! IsWritableStreamLocked(stream) is true, throw a TypeError
+ // exception.
+ if (IsWritableStreamLocked(aStream)) {
+ aRv.ThrowTypeError("WritableStream is already locked!");
+ return;
+ }
+
+ // Step 2. Set writer.[[stream]] to stream.
+ aWriter->SetStream(aStream);
+
+ // Step 3. Set stream.[[writer]] to writer.
+ aStream->SetWriter(aWriter);
+
+ // Step 4. Let state be stream.[[state]].
+ WritableStream::WriterState state = aStream->State();
+
+ // Step 5. If state is "writable",
+ if (state == WritableStream::WriterState::Writable) {
+ RefPtr<Promise> readyPromise =
+ Promise::CreateInfallible(aWriter->GetParentObject());
+
+ // Step 5.1 If ! WritableStreamCloseQueuedOrInFlight(stream) is false and
+ // stream.[[backpressure]] is true, set writer.[[readyPromise]] to a new
+ // promise.
+ if (!aStream->CloseQueuedOrInFlight() && aStream->Backpressure()) {
+ aWriter->SetReadyPromise(readyPromise);
+ } else {
+ // Step 5.2. Otherwise, set writer.[[readyPromise]] to a promise resolved
+ // with undefined.
+ readyPromise->MaybeResolveWithUndefined();
+ aWriter->SetReadyPromise(readyPromise);
+ }
+
+ // Step 5.3. Set writer.[[closedPromise]] to a new promise.
+ RefPtr<Promise> closedPromise =
+ Promise::CreateInfallible(aWriter->GetParentObject());
+ aWriter->SetClosedPromise(closedPromise);
+ } else if (state == WritableStream::WriterState::Erroring) {
+ // Step 6. Otherwise, if state is "erroring",
+
+ // Step 6.1. Set writer.[[readyPromise]] to a promise rejected with
+ // stream.[[storedError]].
+ JS::Rooted<JS::Value> storedError(RootingCx(), aStream->StoredError());
+ RefPtr<Promise> readyPromise =
+ Promise::CreateInfallible(aWriter->GetParentObject());
+ readyPromise->MaybeReject(storedError);
+ aWriter->SetReadyPromise(readyPromise);
+
+ // Step 6.2. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true.
+ readyPromise->SetSettledPromiseIsHandled();
+
+ // Step 6.3. Set writer.[[closedPromise]] to a new promise.
+ RefPtr<Promise> closedPromise =
+ Promise::CreateInfallible(aWriter->GetParentObject());
+ aWriter->SetClosedPromise(closedPromise);
+ } else if (state == WritableStream::WriterState::Closed) {
+ // Step 7. Otherwise, if state is "closed",
+ // Step 7.1. Set writer.[[readyPromise]] to a promise resolved with
+ // undefined.
+ RefPtr<Promise> readyPromise =
+ Promise::CreateResolvedWithUndefined(aWriter->GetParentObject(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ aWriter->SetReadyPromise(readyPromise);
+
+ // Step 7.2. Set writer.[[closedPromise]] to a promise resolved with
+ // undefined.
+ RefPtr<Promise> closedPromise =
+ Promise::CreateResolvedWithUndefined(aWriter->GetParentObject(), aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ aWriter->SetClosedPromise(closedPromise);
+ } else {
+ // Step 8. Otherwise,
+ // Step 8.1 Assert: state is "errored".
+ MOZ_ASSERT(state == WritableStream::WriterState::Errored);
+
+ // Step 8.2. Step Let storedError be stream.[[storedError]].
+ JS::Rooted<JS::Value> storedError(RootingCx(), aStream->StoredError());
+
+ // Step 8.3. Set writer.[[readyPromise]] to a promise rejected with
+ // storedError.
+ RefPtr<Promise> readyPromise =
+ Promise::CreateInfallible(aWriter->GetParentObject());
+ readyPromise->MaybeReject(storedError);
+ aWriter->SetReadyPromise(readyPromise);
+
+ // Step 8.4. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true.
+ readyPromise->SetSettledPromiseIsHandled();
+
+ // Step 8.5. Set writer.[[closedPromise]] to a promise rejected with
+ // storedError.
+ RefPtr<Promise> closedPromise =
+ Promise::CreateInfallible(aWriter->GetParentObject());
+ closedPromise->MaybeReject(storedError);
+ aWriter->SetClosedPromise(closedPromise);
+
+ // Step 8.6 Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
+ closedPromise->SetSettledPromiseIsHandled();
+ }
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-closed-promise-rejected
+void WritableStreamDefaultWriterEnsureClosedPromiseRejected(
+ WritableStreamDefaultWriter* aWriter, JS::Handle<JS::Value> aError) {
+ RefPtr<Promise> closedPromise = aWriter->ClosedPromise();
+ // Step 1. If writer.[[closedPromise]].[[PromiseState]] is "pending", reject
+ // writer.[[closedPromise]] with error.
+ if (closedPromise->State() == Promise::PromiseState::Pending) {
+ closedPromise->MaybeReject(aError);
+ } else {
+ // Step 2. Otherwise, set writer.[[closedPromise]] to a promise rejected
+ // with error.
+ closedPromise = Promise::CreateInfallible(aWriter->GetParentObject());
+ closedPromise->MaybeReject(aError);
+ aWriter->SetClosedPromise(closedPromise);
+ }
+
+ // Step 3. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
+ closedPromise->SetSettledPromiseIsHandled();
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-ready-promise-rejected
+void WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ WritableStreamDefaultWriter* aWriter, JS::Handle<JS::Value> aError) {
+ RefPtr<Promise> readyPromise = aWriter->ReadyPromise();
+ // Step 1. If writer.[[readyPromise]].[[PromiseState]] is "pending", reject
+ // writer.[[readyPromise]] with error.
+ if (readyPromise->State() == Promise::PromiseState::Pending) {
+ readyPromise->MaybeReject(aError);
+ } else {
+ // Step 2. Otherwise, set writer.[[readyPromise]] to a promise rejected with
+ // error.
+ readyPromise = Promise::CreateInfallible(aWriter->GetParentObject());
+ readyPromise->MaybeReject(aError);
+ aWriter->SetReadyPromise(readyPromise);
+ }
+
+ // Step 3. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true.
+ readyPromise->SetSettledPromiseIsHandled();
+}
+
+// https://streams.spec.whatwg.org/#writable-stream-default-writer-close-with-error-propagation
+already_AddRefed<Promise> WritableStreamDefaultWriterCloseWithErrorPropagation(
+ JSContext* aCx, WritableStreamDefaultWriter* aWriter, ErrorResult& aRv) {
+ // Step 1. Let stream be writer.[[stream]].
+ RefPtr<WritableStream> stream = aWriter->GetStream();
+
+ // Step 2. Assert: stream is not undefined.
+ MOZ_ASSERT(stream);
+
+ // Step 3. Let state be stream.[[state]].
+ WritableStream::WriterState state = stream->State();
+
+ // Step 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is true
+ // or state is "closed", return a promise resolved with undefined.
+ if (stream->CloseQueuedOrInFlight() ||
+ state == WritableStream::WriterState::Closed) {
+ return Promise::CreateResolvedWithUndefined(aWriter->GetParentObject(),
+ aRv);
+ }
+
+ // Step 5. If state is "errored",
+ // return a promise rejected with stream.[[storedError]].
+ if (state == WritableStream::WriterState::Errored) {
+ JS::Rooted<JS::Value> error(aCx, stream->StoredError());
+ return Promise::CreateRejected(aWriter->GetParentObject(), error, aRv);
+ }
+
+ // Step 6. Assert: state is "writable" or "erroring".
+ MOZ_ASSERT(state == WritableStream::WriterState::Writable ||
+ state == WritableStream::WriterState::Erroring);
+
+ // Step 7. Return ! WritableStreamDefaultWriterClose(writer).
+ return WritableStreamDefaultWriterClose(aCx, aWriter, aRv);
+}
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
diff --git a/dom/streams/WritableStreamDefaultWriter.h b/dom/streams/WritableStreamDefaultWriter.h
new file mode 100644
index 0000000000..487a504cd1
--- /dev/null
+++ b/dom/streams/WritableStreamDefaultWriter.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_WritableStreamDefaultWriter_h
+#define mozilla_dom_WritableStreamDefaultWriter_h
+
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/QueuingStrategyBinding.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class Promise;
+class WritableStream;
+
+class WritableStreamDefaultWriter final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WritableStreamDefaultWriter)
+
+ protected:
+ ~WritableStreamDefaultWriter();
+
+ public:
+ explicit WritableStreamDefaultWriter(nsIGlobalObject* aGlobal);
+
+ // Slot Getter/Setters:
+ public:
+ WritableStream* GetStream() const { return mStream; }
+ void SetStream(WritableStream* aStream) { mStream = aStream; }
+
+ Promise* ReadyPromise() const { return mReadyPromise; }
+ void SetReadyPromise(Promise* aPromise);
+
+ Promise* ClosedPromise() const { return mClosedPromise; }
+ void SetClosedPromise(Promise* aPromise);
+
+ public:
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // IDL Methods
+ static already_AddRefed<WritableStreamDefaultWriter> Constructor(
+ const GlobalObject& aGlobal, WritableStream& aStream, ErrorResult& aRv);
+
+ already_AddRefed<Promise> Closed();
+ already_AddRefed<Promise> Ready();
+
+ Nullable<double> GetDesiredSize(ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Abort(
+ JSContext* aCx, JS::Handle<JS::Value> aReason, ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Close(JSContext* aCx,
+ ErrorResult& aRv);
+
+ void ReleaseLock(JSContext* aCx);
+
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Write(
+ JSContext* aCx, JS::Handle<JS::Value> aChunk, ErrorResult& aRv);
+
+ // Internal Slots:
+ private:
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ RefPtr<WritableStream> mStream;
+ RefPtr<Promise> mReadyPromise;
+ RefPtr<Promise> mClosedPromise;
+};
+
+namespace streams_abstract {
+
+void SetUpWritableStreamDefaultWriter(WritableStreamDefaultWriter* aWriter,
+ WritableStream* aStream,
+ ErrorResult& aRv);
+
+void WritableStreamDefaultWriterEnsureClosedPromiseRejected(
+ WritableStreamDefaultWriter* aWriter, JS::Handle<JS::Value> aError);
+
+void WritableStreamDefaultWriterEnsureReadyPromiseRejected(
+ WritableStreamDefaultWriter* aWriter, JS::Handle<JS::Value> aError);
+
+Nullable<double> WritableStreamDefaultWriterGetDesiredSize(
+ WritableStreamDefaultWriter* aWriter);
+
+void WritableStreamDefaultWriterRelease(JSContext* aCx,
+ WritableStreamDefaultWriter* aWriter);
+
+MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> WritableStreamDefaultWriterWrite(
+ JSContext* aCx, WritableStreamDefaultWriter* aWriter,
+ JS::Handle<JS::Value> aChunk, ErrorResult& aRv);
+
+MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise>
+WritableStreamDefaultWriterCloseWithErrorPropagation(
+ JSContext* aCx, WritableStreamDefaultWriter* aWriter, ErrorResult& aRv);
+
+} // namespace streams_abstract
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_WritableStreamDefaultWriter_h
diff --git a/dom/streams/crashtests/1764222.html b/dom/streams/crashtests/1764222.html
new file mode 100644
index 0000000000..a48a1168a1
--- /dev/null
+++ b/dom/streams/crashtests/1764222.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+ // This can't be in WPT because of bug 1766711.
+ let a = new AbortController()
+ a.abort()
+ let b = new File(['ó ¬—a'], 'a', {})
+ let c = new WritableStream()
+ b.stream().tee()[0].pipeTo(c, { 'signal': a.signal })
+</script>
diff --git a/dom/streams/crashtests/crashtests.list b/dom/streams/crashtests/crashtests.list
new file mode 100644
index 0000000000..c19e7107dc
--- /dev/null
+++ b/dom/streams/crashtests/crashtests.list
@@ -0,0 +1 @@
+load 1764222.html
diff --git a/dom/streams/moz.build b/dom/streams/moz.build
new file mode 100644
index 0000000000..454a32b789
--- /dev/null
+++ b/dom/streams/moz.build
@@ -0,0 +1,72 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Streams")
+
+EXPORTS.mozilla.dom += [
+ "BaseQueuingStrategy.h",
+ "ByteLengthQueuingStrategy.h",
+ "ByteStreamHelpers.h",
+ "CountQueuingStrategy.h",
+ "QueueWithSizes.h",
+ "ReadableByteStreamController.h",
+ "ReadableStream.h",
+ "ReadableStreamBYOBReader.h",
+ "ReadableStreamBYOBRequest.h",
+ "ReadableStreamController.h",
+ "ReadableStreamDefaultController.h",
+ "ReadableStreamDefaultReader.h",
+ "ReadableStreamGenericReader.h",
+ "ReadRequest.h",
+ "TransformerCallbackHelpers.h",
+ "TransformStream.h",
+ "TransformStreamDefaultController.h",
+ "UnderlyingSinkCallbackHelpers.h",
+ "UnderlyingSourceCallbackHelpers.h",
+ "WritableStream.h",
+ "WritableStreamDefaultController.h",
+ "WritableStreamDefaultWriter.h",
+]
+
+UNIFIED_SOURCES += [
+ "ByteLengthQueuingStrategy.cpp",
+ "ByteStreamHelpers.cpp",
+ "CountQueuingStrategy.cpp",
+ "ReadableByteStreamController.cpp",
+ "ReadableStream.cpp",
+ "ReadableStreamBYOBReader.cpp",
+ "ReadableStreamBYOBRequest.cpp",
+ "ReadableStreamDefaultController.cpp",
+ "ReadableStreamDefaultReader.cpp",
+ "ReadableStreamPipeTo.cpp",
+ "ReadableStreamTee.cpp",
+ "StreamUtils.cpp",
+ "TeeState.cpp",
+ "Transferable.cpp",
+ "TransformerCallbackHelpers.cpp",
+ "TransformStream.cpp",
+ "TransformStreamDefaultController.cpp",
+ "UnderlyingSinkCallbackHelpers.cpp",
+ "UnderlyingSourceCallbackHelpers.cpp",
+ "WritableStream.cpp",
+ "WritableStreamDefaultController.cpp",
+ "WritableStreamDefaultWriter.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild") # to import MessagePort.h
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/dom/ipc",
+]
+
+# MOCHITEST_MANIFESTS += ["tests/mochitest.ini"]
+# ROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"]
+
+XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell/xpcshell.toml"]
diff --git a/dom/streams/test/xpcshell/bug-1387503-1.js b/dom/streams/test/xpcshell/bug-1387503-1.js
new file mode 100644
index 0000000000..777f614c44
--- /dev/null
+++ b/dom/streams/test/xpcshell/bug-1387503-1.js
@@ -0,0 +1,44 @@
+// Test uncatchable error when a stream source's pull() method is called.
+let readerCreated = false;
+let fnFinished = false;
+let g;
+
+add_task(async function test() {
+ // Make `debugger;` raise an uncatchable error.
+ g = newGlobal({ newCompartment: true });
+ g.parent = this;
+ g.hit = false;
+ g.eval(
+ ` new Debugger(parent).onDebuggerStatement = _frame => (hit = true, null);`
+ );
+
+ // Create a stream whose pull() method raises an uncatchable error,
+ // and try reading from it.
+
+ async function fn() {
+ try {
+ let stream = new ReadableStream({
+ start(controller) {},
+ pull(controller) {
+ // eslint-disable-next-line no-debugger
+ debugger;
+ },
+ });
+
+ let reader = stream.getReader();
+ let p = reader.read();
+ readerCreated = true;
+ await p;
+ } finally {
+ fnFinished = true;
+ }
+ }
+
+ fn();
+});
+
+add_task(() => {
+ equal(readerCreated, true);
+ equal(g.hit, true);
+ equal(fnFinished, false);
+});
diff --git a/dom/streams/test/xpcshell/bug-1387503-2.js b/dom/streams/test/xpcshell/bug-1387503-2.js
new file mode 100644
index 0000000000..1678b46649
--- /dev/null
+++ b/dom/streams/test/xpcshell/bug-1387503-2.js
@@ -0,0 +1,52 @@
+// Test uncatchable error when a stream's queuing strategy's size() method is called.
+/* global newGlobal */
+
+let fnFinished = false;
+let g;
+add_task(async function test() {
+ // Make `debugger;` raise an uncatchable exception.
+ g = newGlobal();
+ g.parent = this;
+ g.hit = false;
+ g.info = info;
+ g.eval(`
+ var dbg = new Debugger(parent);
+ dbg.onDebuggerStatement = (_frame, exc) => {hit = true; info("hit"); return null};
+`);
+
+ async function fn() {
+ // Await once to postpone the uncatchable error until we're running inside
+ // a reaction job. We don't want the rest of the test to be terminated.
+ // (`drainJobQueue` catches uncatchable errors!)
+ await 1;
+
+ try {
+ // Create a stream with a strategy whose .size() method raises an
+ // uncatchable exception, and have it call that method.
+ new ReadableStream(
+ {
+ start(controller) {
+ controller.enqueue("FIRST POST"); // this calls .size()
+ },
+ },
+ {
+ size() {
+ // eslint-disable-next-line no-debugger
+ debugger;
+ },
+ }
+ );
+ } finally {
+ fnFinished = true;
+ }
+ }
+
+ fn()
+ .then(() => info("Resolved"))
+ .catch(() => info("Rejected"));
+});
+
+add_task(() => {
+ equal(g.hit, true, "We hit G");
+ equal(fnFinished, false, "We didn't hit the finally block");
+});
diff --git a/dom/streams/test/xpcshell/bug-1503406.js b/dom/streams/test/xpcshell/bug-1503406.js
new file mode 100644
index 0000000000..8e45291faf
--- /dev/null
+++ b/dom/streams/test/xpcshell/bug-1503406.js
@@ -0,0 +1,20 @@
+let read;
+let reader;
+
+add_task(async function test() {
+ let g = newGlobal({ wantGlobalProperties: ["ReadableStream"] });
+ reader = g.eval(`
+ let stream = new ReadableStream({
+ start(controller) {
+ controller.enqueue([]);
+ },
+ });
+ let [b1, b2] = stream.tee();
+ b1.getReader();
+`);
+ read = new ReadableStream({}).getReader().read;
+});
+
+add_task(async function test2() {
+ read.call(reader);
+});
diff --git a/dom/streams/test/xpcshell/bug-1773237.js b/dom/streams/test/xpcshell/bug-1773237.js
new file mode 100644
index 0000000000..0d0107fe85
--- /dev/null
+++ b/dom/streams/test/xpcshell/bug-1773237.js
@@ -0,0 +1,11 @@
+// This test fails if there is an unhandled promise rejection
+var stream = new ReadableStream({
+ pull() {
+ return Promise.reject("foobar");
+ },
+});
+var response = new Response(stream);
+var text = response.text().then(
+ () => {},
+ e => {}
+);
diff --git a/dom/streams/test/xpcshell/dom_stream_prototype_test.js b/dom/streams/test/xpcshell/dom_stream_prototype_test.js
new file mode 100644
index 0000000000..b127368318
--- /dev/null
+++ b/dom/streams/test/xpcshell/dom_stream_prototype_test.js
@@ -0,0 +1,29 @@
+"use strict";
+
+var log = [];
+const stream = new ReadableStream({
+ start(controller) {
+ log.push("started");
+ },
+ pull(controller) {
+ log.push("pulled");
+ controller.enqueue("hi from pull");
+ },
+ cancel() {
+ log.push("cancelled");
+ },
+});
+
+print(log); // Currently prints "started"!
+
+add_task(async function helper() {
+ var reader = stream.getReader();
+ var readPromise = reader.read();
+ readPromise.then(x => print(`Printing promise result ${x}, log ${log}`));
+ print(log);
+
+ var x = await readPromise;
+ print(`Promise result ${x} ${x.value}`);
+ Assert.equal(x.value, "hi from pull");
+ Assert.ok(true);
+});
diff --git a/dom/streams/test/xpcshell/fetch.js b/dom/streams/test/xpcshell/fetch.js
new file mode 100644
index 0000000000..af414735f0
--- /dev/null
+++ b/dom/streams/test/xpcshell/fetch.js
@@ -0,0 +1,35 @@
+"use strict";
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+
+AddonTestUtils.init(this);
+AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "42",
+ "42"
+);
+
+add_task(async function helper() {
+ do_get_profile();
+
+ // The SearchService is also needed in order to construct the initial state,
+ // which means that the AddonManager needs to be available.
+ await AddonTestUtils.promiseStartupManager();
+
+ // The example.com domain will be used to host the dynamic layout JSON and
+ // the top stories JSON.
+ let server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] });
+ server.registerDirectory("/", do_get_cwd());
+
+ Assert.equal(true, fetch instanceof Function);
+ var k = await fetch("http://example.com/");
+ console.log(k);
+ console.log(k.body);
+ var r = k.body.getReader();
+ console.log(r);
+ var v = await r.read();
+ console.log(v);
+});
diff --git a/dom/streams/test/xpcshell/head.js b/dom/streams/test/xpcshell/head.js
new file mode 100644
index 0000000000..510cabe757
--- /dev/null
+++ b/dom/streams/test/xpcshell/head.js
@@ -0,0 +1,45 @@
+"use strict";
+
+const { addDebuggerToGlobal } = ChromeUtils.importESModule(
+ "resource://gre/modules/jsdebugger.sys.mjs"
+);
+
+const SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"].createInstance(
+ Ci.nsIPrincipal
+);
+
+function addTestingFunctionsToGlobal(global) {
+ global.eval(
+ `
+ const testingFunctions = Cu.getJSTestingFunctions();
+ for (let k in testingFunctions) {
+
+ this[k] = testingFunctions[k];
+ }
+ `
+ );
+ if (!global.print) {
+ global.print = info;
+ }
+ if (!global.newGlobal) {
+ global.newGlobal = newGlobal;
+ }
+ if (!global.Debugger) {
+ addDebuggerToGlobal(global);
+ }
+}
+
+addTestingFunctionsToGlobal(this);
+
+/* Create a new global, with all the JS shell testing functions. Similar to the
+ * newGlobal function exposed to JS shells, and useful for porting JS shell
+ * tests to xpcshell tests.
+ */
+function newGlobal(args) {
+ const global = new Cu.Sandbox(SYSTEM_PRINCIPAL, {
+ freshCompartment: true,
+ ...args,
+ });
+ addTestingFunctionsToGlobal(global);
+ return global;
+}
diff --git a/dom/streams/test/xpcshell/large-pipeto.js b/dom/streams/test/xpcshell/large-pipeto.js
new file mode 100644
index 0000000000..420adfea20
--- /dev/null
+++ b/dom/streams/test/xpcshell/large-pipeto.js
@@ -0,0 +1,101 @@
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+// Stamp an array buffer with a pattern; verified by verify_chunk below.
+function init(array) {
+ for (var i = 0; i < array.length; i++) {
+ array[i] = i % 256;
+ }
+}
+
+// The construction of the file below ends up with 12 instances
+// of the array buffer -- we want this to be larger than 2**32 bytes
+// to exercise potential truncation of nsIInputStream::Read's count
+// parameter.
+const ABLENGTH = (2 ** 32 + 8) / 12;
+
+// Get a large file (bigger than 2GB!)
+function get_file() {
+ const array = new ArrayBuffer(ABLENGTH);
+ const buff = new Uint8Array(array);
+
+ // Stamp with pattern.
+ init(buff);
+
+ const blob = new Blob([buff, buff], {});
+ return new File([blob, blob, blob, blob, blob, blob], {});
+}
+
+// Verify that the chunks the stream recieve correspond to the initialization
+function verify_chunk(chunk, verification_state) {
+ for (var j = 0; j < chunk.length; j++) {
+ // If we don't match the fill pattern.
+ if (chunk[j] != verification_state.expected) {
+ // we ran out of array buffer; so we should be looking at the first byte of the array again.
+ if (
+ verification_state.total_index % ABLENGTH != 0 ||
+ chunk[j] != 0 /* ASSUME THAT THE INITIALIZATION OF THE BUFFER IS ZERO */
+ ) {
+ throw new Error(
+ `Mismatch: chunk[${j}] (${chunk[j]}) != ${verification_state.expected} (total_index ${verification_state.total_index})`
+ );
+ }
+ // Reset the fill expectation to 1 for next round.
+ verification_state.expected = 1;
+ } else {
+ // We are inside regular fill section
+ verification_state.expected = (verification_state.expected + 1) % 256;
+ }
+ verification_state.total_index++;
+ }
+}
+
+// Pipe To Testing: Win32 can't handle the file size created in this test and OOMs.
+add_task(
+ {
+ skip_if: () => AppConstants.platform == "win" && !Services.appinfo.is64Bit,
+ },
+ async () => {
+ var chunk_verification_state = {
+ expected: 0,
+ total_index: 0,
+ };
+
+ const file = get_file();
+
+ await file.stream().pipeTo(
+ new WritableStream({
+ write(chunk) {
+ verify_chunk(chunk, chunk_verification_state);
+ },
+ })
+ );
+ }
+);
+
+// Do the same test as above, but this time don't use pipeTo.
+add_task(
+ {
+ skip_if: () => AppConstants.platform == "win" && !Services.appinfo.is64Bit,
+ },
+ async () => {
+ var file = get_file();
+
+ var chunk_verification_state = {
+ expected: 0,
+ total_index: 0,
+ };
+
+ var streamReader = file.stream().getReader();
+
+ while (true) {
+ var res = await streamReader.read();
+ if (res.done) {
+ break;
+ }
+ var chunk = res.value;
+ verify_chunk(chunk, chunk_verification_state);
+ }
+ }
+);
diff --git a/dom/streams/test/xpcshell/proper-realm-cancel.js b/dom/streams/test/xpcshell/proper-realm-cancel.js
new file mode 100644
index 0000000000..1de6db3172
--- /dev/null
+++ b/dom/streams/test/xpcshell/proper-realm-cancel.js
@@ -0,0 +1,5 @@
+// This test passes if we don't have a CCW assertion.
+var g = newGlobal();
+var ccwCancelMethod = new g.Function("return 17;");
+
+new ReadableStream({ cancel: ccwCancelMethod }).cancel("bye");
diff --git a/dom/streams/test/xpcshell/proper-realm-pull.js b/dom/streams/test/xpcshell/proper-realm-pull.js
new file mode 100644
index 0000000000..9d42d9a65d
--- /dev/null
+++ b/dom/streams/test/xpcshell/proper-realm-pull.js
@@ -0,0 +1,6 @@
+// This test passes if we don't have a CCW assertion.
+
+var g = newGlobal({ newCompartment: true });
+var ccwPullMethod = new g.Function("return 17;");
+
+new ReadableStream({ pull: ccwPullMethod });
diff --git a/dom/streams/test/xpcshell/response.js b/dom/streams/test/xpcshell/response.js
new file mode 100644
index 0000000000..ead1be527f
--- /dev/null
+++ b/dom/streams/test/xpcshell/response.js
@@ -0,0 +1,30 @@
+"use strict";
+
+add_task(async function (test) {
+ return new Response(new Blob([], { type: "text/plain" })).body.cancel();
+});
+
+add_task(function (test) {
+ var response = new Response(
+ new Blob(["This is data"], { type: "text/plain" })
+ );
+ var reader = response.body.getReader();
+ reader.read();
+ return reader.cancel();
+});
+
+add_task(function (test) {
+ var response = new Response(new Blob(["T"], { type: "text/plain" }));
+ var reader = response.body.getReader();
+
+ var closedPromise = reader.closed.then(function () {
+ return reader.cancel();
+ });
+ reader.read().then(function readMore({ done, value }) {
+ if (!done) {
+ return reader.read().then(readMore);
+ }
+ return undefined;
+ });
+ return closedPromise;
+});
diff --git a/dom/streams/test/xpcshell/subclassing.js b/dom/streams/test/xpcshell/subclassing.js
new file mode 100644
index 0000000000..b2b86cf353
--- /dev/null
+++ b/dom/streams/test/xpcshell/subclassing.js
@@ -0,0 +1,123 @@
+// Adapted from js/src/tests/non262/ReadableStream/subclassing.js to suit requirements of xpcshell-testing.
+
+function assertEq(a, b) {
+ Assert.equal(a, b);
+}
+function assertThrowsInstanceOf(fun, err) {
+ var regexp = new RegExp(err.name);
+ print(regexp);
+ Assert.throws(fun, regexp);
+}
+
+// Spot-check subclassing of stream constructors.
+
+// ReadableStream can be subclassed.
+class PartyStreamer extends ReadableStream {}
+
+let started = false;
+add_task(function subclass_helper() {
+ // The base class constructor is called.
+ let stream = new PartyStreamer({
+ // (The ReadableStream constructor calls this start method.)
+ start(c) {
+ started = true;
+ },
+ });
+
+ assertEq(started, true);
+
+ // The instance's prototype chain is correct.
+ assertEq(stream.__proto__, PartyStreamer.prototype);
+ assertEq(stream.__proto__.__proto__, ReadableStream.prototype);
+ assertEq(stream.__proto__.__proto__.__proto__, Object.prototype);
+ assertEq(stream.__proto__.__proto__.__proto__.__proto__, null);
+ assertEq(stream instanceof ReadableStream, true);
+
+ // Non-generic methods can be called on the resulting stream.
+ stream.getReader();
+ assertEq(stream.locked, true);
+});
+
+add_task(function strategy_helper() {
+ // CountQueuingStrategy can be subclassed.
+ class PixelStrategy extends CountQueuingStrategy {}
+ assertEq(
+ new PixelStrategy({ highWaterMark: 4 }).__proto__,
+ PixelStrategy.prototype
+ );
+
+ // The base class constructor is called.
+ assertThrowsInstanceOf(() => new PixelStrategy(), TypeError);
+ assertEq(new PixelStrategy({ highWaterMark: -1 }).highWaterMark, -1);
+
+ // // VerySmartStrategy can be subclassed.
+ // class VerySmartStrategy extends ByteLengthQueuingStrategy {
+ // size(chunk) {
+ // return super.size(chunk) * 8;
+ // }
+ // }
+ // let vss = new VerySmartStrategy({ highWaterMark: 12 });
+ // assertEq(vss.size(new ArrayBuffer(8)), 64);
+ // assertEq(vss.__proto__, VerySmartStrategy.prototype);
+});
+
+// Even ReadableStreamDefaultReader can be subclassed.
+add_task(async function readerTest() {
+ const ReadableStreamDefaultReader = new ReadableStream().getReader()
+ .constructor;
+ class MindReader extends ReadableStreamDefaultReader {
+ async read() {
+ let foretold = { value: "death", done: false };
+ let actual = await super.read();
+ actual = foretold; // ZOMG I WAS RIGHT, EXACTLY AS FORETOLD they should call me a righter
+ return actual;
+ }
+ }
+
+ let stream = new ReadableStream({
+ start(c) {
+ c.enqueue("one");
+ c.enqueue("two");
+ },
+ pull(c) {
+ c.close();
+ },
+ });
+ let reader = new MindReader(stream);
+ let result = await reader.read();
+ assertEq(result.value, "death");
+ reader.releaseLock();
+
+ reader = stream.getReader();
+ result = await reader.read();
+ assertEq(result.done, false);
+ assertEq(result.value, "two");
+ result = await reader.read();
+ assertEq(result.done, true);
+ assertEq(result.value, undefined);
+});
+
+add_task(function default_controller() {
+ // Even ReadableStreamDefaultController, which can't be constructed,
+ // can be subclassed.
+ let ReadableStreamDefaultController;
+ new ReadableStream({
+ start(c) {
+ ReadableStreamDefaultController = c.constructor;
+ },
+ });
+ class MasterController extends ReadableStreamDefaultController {
+ constructor() {
+ // don't call super, it'll just throw
+ return Object.create(MasterController.prototype);
+ }
+ }
+ let c = new MasterController();
+
+ // The prototype chain is per spec.
+ assertEq(c instanceof ReadableStreamDefaultController, true);
+
+ // But the instance does not have the internal slots of a
+ // ReadableStreamDefaultController, so the non-generic methods can't be used.
+ assertThrowsInstanceOf(() => c.enqueue("horse"), TypeError);
+});
diff --git a/dom/streams/test/xpcshell/too-big-array-buffer.js b/dom/streams/test/xpcshell/too-big-array-buffer.js
new file mode 100644
index 0000000000..b80c36e813
--- /dev/null
+++ b/dom/streams/test/xpcshell/too-big-array-buffer.js
@@ -0,0 +1,15 @@
+add_task(async function helper() {
+ // Note: this test assumes the largest possible ArrayBuffer is
+ // smaller than 10GB -- if that changes, this test will fail.
+ let rs = new ReadableStream({
+ type: "bytes",
+ autoAllocateChunkSize: 10 * 1024 * 1024 * 1024,
+ });
+ let reader = rs.getReader();
+ try {
+ await reader.read();
+ Assert.equal(true, false, "Shouldn't succeed at reading");
+ } catch (e) {
+ Assert.equal(e instanceof RangeError, true, "Should throw RangeError");
+ }
+});
diff --git a/dom/streams/test/xpcshell/xpcshell.toml b/dom/streams/test/xpcshell/xpcshell.toml
new file mode 100644
index 0000000000..3fe0232d17
--- /dev/null
+++ b/dom/streams/test/xpcshell/xpcshell.toml
@@ -0,0 +1,32 @@
+[DEFAULT]
+head = "head.js"
+skip-if = ["os == 'android'"]
+support-files = ""
+
+["bug-1387503-1.js"]
+prefs = ["security.allow_parent_unrestricted_js_loads=true"]
+
+["bug-1503406.js"]
+
+["bug-1773237.js"]
+
+["dom_stream_prototype_test.js"]
+
+["fetch.js"]
+
+["large-pipeto.js"]
+skip-if = [
+ "os == 'win'",
+ "tsan", # Causes claim expired errors; see Bug 1770170.
+]
+run-sequentially = "very high failure rate in parallel"
+
+["proper-realm-cancel.js"]
+
+["proper-realm-pull.js"]
+
+["response.js"]
+
+["subclassing.js"]
+
+["too-big-array-buffer.js"]