forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			706 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			706 lines
		
	
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- 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
 |