diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 44ee4e71c739..5f86eb0a11eb 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -646,6 +646,10 @@ DOMInterfaces = { 'headerFile': 'TransceiverImpl.h' }, +'TransformStreamDefaultController': { + 'implicitJSContext': ['terminate'], +}, + 'Plugin': { 'headerFile' : 'nsPluginArray.h', 'nativeType': 'nsPluginElement', diff --git a/dom/streams/ReadableStreamDefaultController.cpp b/dom/streams/ReadableStreamDefaultController.cpp index 34a32c0503b3..0ac930fa907f 100644 --- a/dom/streams/ReadableStreamDefaultController.cpp +++ b/dom/streams/ReadableStreamDefaultController.cpp @@ -106,13 +106,11 @@ static bool ReadableStreamDefaultControllerCanCloseOrEnqueue( state == ReadableStream::ReaderState::Readable; } -enum class CloseOrEnqueue { Close, Enqueue }; - // https://streams.spec.whatwg.org/#readable-stream-default-controller-can-close-or-enqueue // This is a variant of ReadableStreamDefaultControllerCanCloseOrEnqueue // that also throws when the function would return false to improve error // messages. -static bool ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow( +bool ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow( ReadableStreamDefaultController* aController, CloseOrEnqueue aCloseOrEnqueue, ErrorResult& aRv) { // Step 1. Let state be controller.[[stream]].[[state]]. @@ -120,9 +118,9 @@ static bool ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow( nsCString prefix; if (aCloseOrEnqueue == CloseOrEnqueue::Close) { - prefix = "Cannot close a readable stream that "_ns; + prefix = "Cannot close a stream that "_ns; } else { - prefix = "Cannot enqueue into a readable stream that "_ns; + prefix = "Cannot enqueue into a stream that "_ns; } switch (state) { @@ -348,7 +346,7 @@ void ReadableStreamDefaultController::Error(JSContext* aCx, } // https://streams.spec.whatwg.org/#readable-stream-default-controller-should-call-pull -static bool ReadableStreamDefaultControllerShouldCallPull( +bool ReadableStreamDefaultControllerShouldCallPull( ReadableStreamDefaultController* aController) { // Step 1. ReadableStream* stream = aController->GetStream(); diff --git a/dom/streams/ReadableStreamDefaultController.h b/dom/streams/ReadableStreamDefaultController.h index e76365f5fdb5..043507c2307d 100644 --- a/dom/streams/ReadableStreamDefaultController.h +++ b/dom/streams/ReadableStreamDefaultController.h @@ -159,6 +159,15 @@ void ReadableStreamDefaultControllerClearAlgorithms( Nullable ReadableStreamDefaultControllerGetDesiredSize( ReadableStreamDefaultController* aController); +enum class CloseOrEnqueue { Close, Enqueue }; + +bool ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow( + ReadableStreamDefaultController* aController, + CloseOrEnqueue aCloseOrEnqueue, ErrorResult& aRv); + +bool ReadableStreamDefaultControllerShouldCallPull( + ReadableStreamDefaultController* aController); + } // namespace mozilla::dom #endif // mozilla_dom_ReadableStreamDefaultController_h diff --git a/dom/streams/TransformStream.cpp b/dom/streams/TransformStream.cpp index 7270f63a7c3f..21ff0603dde8 100644 --- a/dom/streams/TransformStream.cpp +++ b/dom/streams/TransformStream.cpp @@ -40,6 +40,48 @@ JSObject* TransformStream::WrapObject(JSContext* aCx, return TransformStream_Binding::Wrap(aCx, this, aGivenProto); } +// https://streams.spec.whatwg.org/#transform-stream-error-writable-and-unblock-write +void TransformStreamErrorWritableAndUnblockWrite(JSContext* aCx, + TransformStream* aStream, + JS::HandleValue 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()) { + TransformStreamSetBackpressure(aStream, false, aRv); + } +} + +// https://streams.spec.whatwg.org/#transform-stream-error +void TransformStreamError(JSContext* aCx, TransformStream* aStream, + JS::HandleValue 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); +} + // https://streams.spec.whatwg.org/#initialize-transform-stream class TransformStreamUnderlyingSinkAlgorithms final : public UnderlyingSinkAlgorithmsBase { @@ -178,9 +220,8 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION( NS_INTERFACE_MAP_END_INHERITING(TransformStreamUnderlyingSourceAlgorithms) // https://streams.spec.whatwg.org/#transform-stream-set-backpressure -static void TransformStreamSetBackpressure(TransformStream* aStream, - bool aBackpressure, - ErrorResult& aRv) { +void TransformStreamSetBackpressure(TransformStream* aStream, + bool aBackpressure, ErrorResult& aRv) { // Step 1. Assert: stream.[[backpressure]] is not backpressure. MOZ_ASSERT(aStream->Backpressure() != aBackpressure); @@ -238,6 +279,11 @@ void TransformStream::Initialize(JSContext* aCx, Promise* aStartPromise, // 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). diff --git a/dom/streams/TransformStream.h b/dom/streams/TransformStream.h index 2c9d7f4f0d58..4c565209278d 100644 --- a/dom/streams/TransformStream.h +++ b/dom/streams/TransformStream.h @@ -36,12 +36,15 @@ class TransformStream final : public nsISupports, public nsWrapperCache { void SetBackpressureChangePromise(Promise* aPromise) { mBackpressureChangePromise = aPromise; } - TransformStreamDefaultController* Controller() { return mController; } - void SetController(TransformStreamDefaultController* aController) { - mController = aController; + MOZ_KNOWN_LIVE TransformStreamDefaultController* Controller() { + return mController; } - ReadableStream* Readable() { return mReadable; } - WritableStream* Writable() { return mWritable; } + void SetController(TransformStreamDefaultController& aController) { + MOZ_ASSERT(!mController); + mController = &aController; + } + MOZ_KNOWN_LIVE ReadableStream* Readable() { return mReadable; } + MOZ_KNOWN_LIVE WritableStream* Writable() { return mWritable; } protected: ~TransformStream(); @@ -73,13 +76,26 @@ class TransformStream final : public nsISupports, public nsWrapperCache { nsCOMPtr mGlobal; // Internal slots + // MOZ_KNOWN_LIVE for slots that will never be reassigned bool mBackpressure = false; RefPtr mBackpressureChangePromise; - RefPtr mController; - RefPtr mReadable; - RefPtr mWritable; + MOZ_KNOWN_LIVE RefPtr mController; + MOZ_KNOWN_LIVE RefPtr mReadable; + MOZ_KNOWN_LIVE RefPtr mWritable; }; +MOZ_CAN_RUN_SCRIPT void TransformStreamErrorWritableAndUnblockWrite( + JSContext* aCx, TransformStream* aStream, JS::HandleValue aError, + ErrorResult& aRv); + +MOZ_CAN_RUN_SCRIPT void TransformStreamError(JSContext* aCx, + TransformStream* aStream, + JS::HandleValue aError, + ErrorResult& aRv); + +void TransformStreamSetBackpressure(TransformStream* aStream, + bool aBackpressure, ErrorResult& aRv); + } // namespace mozilla::dom #endif // DOM_STREAMS_TRANSFORMSTREAM_H_ diff --git a/dom/streams/TransformStreamDefaultController.cpp b/dom/streams/TransformStreamDefaultController.cpp index 7270e335d06e..ef6b4577c3dd 100644 --- a/dom/streams/TransformStreamDefaultController.cpp +++ b/dom/streams/TransformStreamDefaultController.cpp @@ -26,8 +26,9 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TransformStreamDefaultController) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -void TransformStreamDefaultController::SetStream(TransformStream* aStream) { - mStream = aStream; +void TransformStreamDefaultController::SetStream(TransformStream& aStream) { + MOZ_ASSERT(!mStream); + mStream = &aStream; } void TransformStreamDefaultController::SetAlgorithms( @@ -62,20 +63,123 @@ Nullable TransformStreamDefaultController::GetDesiredSize() const { return ReadableStreamDefaultControllerGetDesiredSize(readableController); } +// https://streams.spec.whatwg.org/#rs-default-controller-has-backpressure +// Looks like a readable stream thing but the spec explicitly says this is for +// TransformStream. +static bool ReadableStreamDefaultControllerHasBackpressure( + ReadableStreamDefaultController* aController) { + // Step 1: If ! ReadableStreamDefaultControllerShouldCallPull(controller) is + // true, return false. + // Step 2: Otherwise, return true. + return !ReadableStreamDefaultControllerShouldCallPull(aController); +} + void TransformStreamDefaultController::Enqueue(JSContext* aCx, JS::Handle aChunk, ErrorResult& aRv) { - aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + // Step 1: Perform ? TransformStreamDefaultControllerEnqueue(this, chunk). + + // Inlining TransformStreamDefaultControllerEnqueue here. + // https://streams.spec.whatwg.org/#transform-stream-default-controller-enqueue + + // Step 1: Let stream be controller.[[stream]]. + RefPtr stream = mStream; + + // Step 2: Let readableController be stream.[[readable]].[[controller]]. + RefPtr readableController = + stream->Readable()->Controller()->AsDefault(); + + // Step 3: If ! + // ReadableStreamDefaultControllerCanCloseOrEnqueue(readableController) is + // false, throw a TypeError exception. + if (!ReadableStreamDefaultControllerCanCloseOrEnqueueAndThrow( + readableController, CloseOrEnqueue::Enqueue, aRv)) { + return; + } + + // Step 4: Let enqueueResult be + // ReadableStreamDefaultControllerEnqueue(readableController, chunk). + ErrorResult rv; + ReadableStreamDefaultControllerEnqueue(aCx, readableController, aChunk, rv); + + // Step 5: If enqueueResult is an abrupt completion, + if (rv.MaybeSetPendingException(aCx)) { + JS::Rooted error(aCx); + if (!JS_GetPendingException(aCx, &error)) { + // Uncatchable exception; we should mark aRv and return. + aRv.StealExceptionFromJSContext(aCx); + return; + } + JS_ClearPendingException(aCx); + + // Step 5.1: Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, + // enqueueResult.[[Value]]). + TransformStreamErrorWritableAndUnblockWrite(aCx, stream, error, aRv); + + // Step 5.2: Throw stream.[[readable]].[[storedError]]. + JS::RootedValue storedError(aCx, stream->Readable()->StoredError()); + aRv.MightThrowJSException(); + aRv.ThrowJSException(aCx, storedError); + return; + } + + // Step 6: Let backpressure be ! + // ReadableStreamDefaultControllerHasBackpressure(readableController). + bool backpressure = + ReadableStreamDefaultControllerHasBackpressure(readableController); + + // Step 7: If backpressure is not stream.[[backpressure]], + if (backpressure != stream->Backpressure()) { + // Step 7.1: Assert: backpressure is true. + MOZ_ASSERT(backpressure); + + // Step 7.2: Perform ! TransformStreamSetBackpressure(stream, true). + TransformStreamSetBackpressure(stream, true, aRv); + } } +// https://streams.spec.whatwg.org/#ts-default-controller-error void TransformStreamDefaultController::Error(JSContext* aCx, JS::Handle aError, ErrorResult& aRv) { - aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + // Step 1: Perform ? TransformStreamDefaultControllerError(this, e). + + // Inlining TransformStreamDefaultControllerError here. + // https://streams.spec.whatwg.org/#transform-stream-default-controller-error + + // Perform ! TransformStreamError(controller.[[stream]], e). + TransformStreamError(aCx, mStream, aError, aRv); } -void TransformStreamDefaultController::Terminate(ErrorResult& aRv) { - aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); +// https://streams.spec.whatwg.org/#ts-default-controller-terminate + +void TransformStreamDefaultController::Terminate(JSContext* aCx, + ErrorResult& aRv) { + // Step 1: Perform ? TransformStreamDefaultControllerTerminate(this). + + // Inlining TransformStreamDefaultControllerTerminate here. + // https://streams.spec.whatwg.org/#transform-stream-default-controller-terminate + + // Step 1: Let stream be controller.[[stream]]. + RefPtr stream = mStream; + + // Step 2: Let readableController be stream.[[readable]].[[controller]]. + RefPtr readableController = + stream->Readable()->Controller()->AsDefault(); + + // Step 3: Perform ! ReadableStreamDefaultControllerClose(readableController). + ReadableStreamDefaultControllerClose(aCx, readableController, aRv); + + // Step 4: Let error be a TypeError exception indicating that the stream has + // been terminated. + ErrorResult rv; + rv.ThrowTypeError("Terminating the stream"); + JS::Rooted error(aCx); + MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), &error)); + + // Step 5: Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, + // error). + TransformStreamErrorWritableAndUnblockWrite(aCx, stream, error, aRv); } // https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller @@ -88,10 +192,10 @@ void SetUpTransformStreamDefaultController( MOZ_ASSERT(!aStream.Controller()); // Step 3. Set controller.[[stream]] to stream. - aController.SetStream(&aStream); + aController.SetStream(aStream); // Step 4. Set stream.[[controller]] to controller. - aStream.SetController(&aController); + aStream.SetController(aController); // Step 5. Set controller.[[transformAlgorithm]] to transformAlgorithm. // Step 6. Set controller.[[flushAlgorithm]] to flushAlgorithm. diff --git a/dom/streams/TransformStreamDefaultController.h b/dom/streams/TransformStreamDefaultController.h index 07016d549c13..701a735b9d66 100644 --- a/dom/streams/TransformStreamDefaultController.h +++ b/dom/streams/TransformStreamDefaultController.h @@ -31,7 +31,7 @@ class TransformStreamDefaultController final : public nsISupports, NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TransformStreamDefaultController) - void SetStream(TransformStream* aStream); + void SetStream(TransformStream& aStream); void SetAlgorithms(TransformerAlgorithms* aTransformerAlgorithms); explicit TransformStreamDefaultController(nsIGlobalObject* aGlobal); @@ -46,15 +46,17 @@ class TransformStreamDefaultController final : public nsISupports, Nullable GetDesiredSize() const; - void Enqueue(JSContext* aCx, JS::Handle aChunk, ErrorResult& aRv); - void Error(JSContext* aCx, JS::Handle aError, ErrorResult& aRv); - void Terminate(ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT void Enqueue(JSContext* aCx, JS::Handle aChunk, + ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT void Error(JSContext* aCx, JS::Handle aError, + ErrorResult& aRv); + MOZ_CAN_RUN_SCRIPT void Terminate(JSContext* aCx, ErrorResult& aRv); private: nsCOMPtr mGlobal; // Internal slots - RefPtr mStream; + MOZ_KNOWN_LIVE RefPtr mStream; RefPtr mTransformerAlgorithms; }; diff --git a/dom/streams/WritableStream.h b/dom/streams/WritableStream.h index 4700a819f595..ce19326fdf58 100644 --- a/dom/streams/WritableStream.h +++ b/dom/streams/WritableStream.h @@ -52,10 +52,12 @@ class WritableStream : public nsISupports, public nsWrapperCache { Promise* GetCloseRequest() { return mCloseRequest; } void SetCloseRequest(Promise* aRequest) { mCloseRequest = aRequest; } - WritableStreamDefaultController* Controller() { return mController; } - void SetController(WritableStreamDefaultController* aController) { - MOZ_ASSERT(aController); - mController = aController; + MOZ_KNOWN_LIVE WritableStreamDefaultController* Controller() { + return mController; + } + void SetController(WritableStreamDefaultController& aController) { + MOZ_ASSERT(!mController); + mController = &aController; } Promise* GetInFlightWriteRequest() const { return mInFlightWriteRequest; } @@ -162,7 +164,7 @@ class WritableStream : public nsISupports, public nsWrapperCache { private: bool mBackpressure = false; RefPtr mCloseRequest; - RefPtr mController; + MOZ_KNOWN_LIVE RefPtr mController; RefPtr mInFlightWriteRequest; RefPtr mInFlightCloseRequest; diff --git a/dom/streams/WritableStreamDefaultController.cpp b/dom/streams/WritableStreamDefaultController.cpp index 8df09749c53b..38008a9b9c35 100644 --- a/dom/streams/WritableStreamDefaultController.cpp +++ b/dom/streams/WritableStreamDefaultController.cpp @@ -138,7 +138,7 @@ void SetUpWritableStreamDefaultController( MOZ_ASSERT(aController->Stream() == aStream); // Step 4. Set stream.[[controller]] to controller. - aStream->SetController(aController); + aStream->SetController(*aController); // Step 5. Perform ! ResetQueue(controller). ResetQueue(aController); diff --git a/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini b/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini index c4152600e888..16abd902dbd2 100644 --- a/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/errors.any.js.ini @@ -11,12 +11,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] - expected: FAIL - - [when strategy.size calls controller.error() then throws, the constructor should throw the first error] - expected: FAIL - [cancelling the readable side should error the writable] expected: FAIL @@ -53,9 +47,6 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL - [errored TransformStream should not enqueue new chunks] - expected: FAIL - [errors.any.serviceworker.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -70,12 +61,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] - expected: FAIL - - [when strategy.size calls controller.error() then throws, the constructor should throw the first error] - expected: FAIL - [cancelling the readable side should error the writable] expected: FAIL @@ -112,9 +97,6 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL - [errored TransformStream should not enqueue new chunks] - expected: FAIL - [errors.any.worker.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -129,12 +111,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] - expected: FAIL - - [when strategy.size calls controller.error() then throws, the constructor should throw the first error] - expected: FAIL - [cancelling the readable side should error the writable] expected: FAIL @@ -171,9 +147,6 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL - [errored TransformStream should not enqueue new chunks] - expected: FAIL - [errors.any.html] [TransformStream errors thrown in transform put the writable and readable in an errored state] @@ -188,12 +161,6 @@ [when controller.error is followed by a rejection, the error reason should come from controller.error] expected: FAIL - [when strategy.size throws inside start(), the constructor should throw the same error] - expected: FAIL - - [when strategy.size calls controller.error() then throws, the constructor should throw the first error] - expected: FAIL - [cancelling the readable side should error the writable] expected: FAIL @@ -229,6 +196,3 @@ [the readable should be errored with the reason passed to the writable abort() method] expected: FAIL - - [errored TransformStream should not enqueue new chunks] - expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/general.any.js.ini b/testing/web-platform/meta/streams/transform-streams/general.any.js.ini index 0b163c08e560..255b45933b45 100644 --- a/testing/web-platform/meta/streams/transform-streams/general.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/general.any.js.ini @@ -53,12 +53,6 @@ [Subclassing TransformStream should work] expected: FAIL - [enqueue() should throw after controller.terminate()] - expected: FAIL - - [controller.terminate() should do nothing the second time it is called] - expected: FAIL - [general.any.html] [TransformStream writable starts in the writable state] @@ -115,12 +109,6 @@ [Subclassing TransformStream should work] expected: FAIL - [enqueue() should throw after controller.terminate()] - expected: FAIL - - [controller.terminate() should do nothing the second time it is called] - expected: FAIL - [general.any.worker.html] [TransformStream writable starts in the writable state] @@ -177,12 +165,6 @@ [Subclassing TransformStream should work] expected: FAIL - [enqueue() should throw after controller.terminate()] - expected: FAIL - - [controller.terminate() should do nothing the second time it is called] - expected: FAIL - [general.any.serviceworker.html] [TransformStream writable starts in the writable state] @@ -238,9 +220,3 @@ [Subclassing TransformStream should work] expected: FAIL - - [enqueue() should throw after controller.terminate()] - expected: FAIL - - [controller.terminate() should do nothing the second time it is called] - expected: FAIL diff --git a/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini b/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini index 7536f29dea2b..cf1b68ddbf43 100644 --- a/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini +++ b/testing/web-platform/meta/streams/transform-streams/terminate.any.js.ini @@ -14,9 +14,6 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL - [controller.enqueue() should throw after controller.terminate()] - expected: FAIL - [terminate.any.worker.html] [controller.terminate() should error pipeTo()] @@ -34,9 +31,6 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL - [controller.enqueue() should throw after controller.terminate()] - expected: FAIL - [terminate.any.serviceworker.html] [controller.terminate() should error pipeTo()] @@ -54,9 +48,6 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL - [controller.enqueue() should throw after controller.terminate()] - expected: FAIL - [terminate.any.html] [controller.terminate() should error pipeTo()] @@ -73,6 +64,3 @@ [controller.terminate() inside flush() should not prevent writer.close() from succeeding] expected: FAIL - - [controller.enqueue() should throw after controller.terminate()] - expected: FAIL