diff options
Diffstat (limited to 'dom/streams/TransformStream.cpp')
-rw-r--r-- | dom/streams/TransformStream.cpp | 706 |
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 |