698 lines
28 KiB
C++
698 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"
|
|
|
|
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, ReadableStreamControllerBase& 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, ReadableStreamControllerBase& 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
|