summaryrefslogtreecommitdiffstats
path: root/dom/streams/TransformStream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/streams/TransformStream.cpp')
-rw-r--r--dom/streams/TransformStream.cpp706
1 files changed, 706 insertions, 0 deletions
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