/* -*- 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::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 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 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 global = do_QueryInterface(aGlobal.GetAsSupports()); RefPtr startPromise = Promise::CreateResolvedWithUndefined(global, aRv); if (!startPromise) { return nullptr; } // Step 8. Perform ! InitializeTransformStream(stream, startPromise, // writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, // readableSizeAlgorithm). RefPtr 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(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 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 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 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 TransformStreamDefaultControllerPerformTransform( JSContext* aCx, TransformStreamDefaultController* aController, JS::Handle aChunk, ErrorResult& aRv) { // Step 1: Let transformPromise be the result of performing // controller.[[transformAlgorithm]], passing chunk. RefPtr algorithms = aController->Algorithms(); RefPtr 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 aError, ErrorResult& aRv, const RefPtr& aController) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA -> already_AddRefed { // 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 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 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 WriteCallback( JSContext* aCx, JS::Handle 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 controller = mStream->Controller(); // Step 3: If stream.[[backpressure]] is true, if (mStream->Backpressure()) { // Step 3.1: Let backpressureChangePromise be // stream.[[backpressureChangePromise]]. RefPtr 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, ErrorResult& aRv, const RefPtr& aStream, const RefPtr& aController, JS::Handle aChunk) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA -> already_AddRefed { // Step 1: Let writable be stream.[[writable]]. RefPtr 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 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 AbortCallback( JSContext* aCx, const Optional>& 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 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 readable = mStream->Readable(); // Step 2: Let controller be stream.[[controller]]. RefPtr controller = mStream->Controller(); // Step 3: Let flushPromise be the result of performing // controller.[[flushAlgorithm]]. RefPtr algorithms = controller->Algorithms(); RefPtr 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, nsresult> result = flushPromise->ThenCatchWithCycleCollectedArgs( [](JSContext* aCx, JS::Handle aValue, ErrorResult& aRv, const RefPtr& aReadable, const RefPtr& aStream) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA -> already_AddRefed { // 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 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 aValue, ErrorResult& aRv, const RefPtr& aReadable, const RefPtr& aStream) MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA -> already_AddRefed { // 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 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 mStartPromise; // MOZ_KNOWN_LIVE because it won't be reassigned MOZ_KNOWN_LIVE RefPtr 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 aRetVal, ErrorResult& aRv) override { // Step 1. Let startAlgorithm be an algorithm that returns startPromise. // (Same as TransformStreamUnderlyingSinkAlgorithms::StartCallback) aRetVal.setObject(*mStartPromise->PromiseObj()); } already_AddRefed 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 CancelCallback( JSContext* aCx, const Optional>& 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 mStartPromise; // MOZ_KNOWNLIVE because it will never be reassigned MOZ_KNOWN_LIVE RefPtr 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::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(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( 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::Constructor( const GlobalObject& aGlobal, const Optional>& aTransformer, const QueuingStrategy& aWritableStrategy, const QueuingStrategy& aReadableStrategy, ErrorResult& aRv) { // Step 1. If transformer is missing, set it to null. JS::Rooted transformerObj( aGlobal.Context(), aTransformer.WasPassed() ? aTransformer.Value() : nullptr); // Step 2. Let transformerDict be transformer, converted to an IDL value of // type Transformer. RootedDictionary transformerDict(aGlobal.Context()); if (transformerObj) { JS::Rooted 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 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 writableSizeAlgorithm = aWritableStrategy.mSize.WasPassed() ? &aWritableStrategy.mSize.Value() : nullptr; // Step 9. Let startPromise be a new promise. nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports()); RefPtr startPromise = Promise::CreateInfallible(global); // Step 10. Perform ! InitializeTransformStream(this, startPromise, // writableHighWaterMark, writableSizeAlgorithm, readableHighWaterMark, // readableSizeAlgorithm). RefPtr 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 callback = transformerDict.mStart.Value(); RefPtr controller = transformStream->Controller(); JS::Rooted 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