mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-10-31 16:28:05 +02:00 
			
		
		
		
	 ff5f4bdbb7
			
		
	
	
		ff5f4bdbb7
		
	
	
	
	
		
			
			It's marked as can-run-script because of StartCallback, but Underlying{Source/Sink}AlgorithmsWrapper::StartCallback is empty and thus can't run any scripts.
Differential Revision: https://phabricator.services.mozilla.com/D178356
		
	
			
		
			
				
	
	
		
			811 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			811 lines
		
	
	
	
		
			31 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/WritableStream.h"
 | ||
| 
 | ||
| #include "StreamUtils.h"
 | ||
| #include "js/Array.h"
 | ||
| #include "js/PropertyAndElement.h"
 | ||
| #include "js/TypeDecls.h"
 | ||
| #include "js/Value.h"
 | ||
| #include "mozilla/AlreadyAddRefed.h"
 | ||
| #include "mozilla/Assertions.h"
 | ||
| #include "mozilla/Attributes.h"
 | ||
| #include "mozilla/CycleCollectedJSContext.h"
 | ||
| #include "mozilla/HoldDropJSObjects.h"
 | ||
| #include "mozilla/dom/AbortSignal.h"
 | ||
| #include "mozilla/dom/BindingCallContext.h"
 | ||
| #include "mozilla/dom/QueueWithSizes.h"
 | ||
| #include "mozilla/dom/QueuingStrategyBinding.h"
 | ||
| #include "mozilla/dom/ReadRequest.h"
 | ||
| #include "mozilla/dom/RootedDictionary.h"
 | ||
| #include "mozilla/dom/UnderlyingSinkBinding.h"
 | ||
| #include "mozilla/dom/WritableStreamBinding.h"
 | ||
| #include "mozilla/dom/WritableStreamDefaultController.h"
 | ||
| #include "mozilla/dom/WritableStreamDefaultWriter.h"
 | ||
| #include "nsCOMPtr.h"
 | ||
| 
 | ||
| #include "mozilla/dom/Promise-inl.h"
 | ||
| #include "nsIGlobalObject.h"
 | ||
| #include "nsISupports.h"
 | ||
| 
 | ||
| namespace mozilla::dom {
 | ||
| 
 | ||
| using namespace streams_abstract;
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WITH_JS_MEMBERS(
 | ||
|     WritableStream,
 | ||
|     (mGlobal, mCloseRequest, mController, mInFlightWriteRequest,
 | ||
|      mInFlightCloseRequest, mPendingAbortRequestPromise, mWriter,
 | ||
|      mWriteRequests),
 | ||
|     (mPendingAbortRequestReason, mStoredError))
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTING_ADDREF(WritableStream)
 | ||
| NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(WritableStream,
 | ||
|                                                    LastRelease())
 | ||
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WritableStream)
 | ||
|   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
 | ||
|   NS_INTERFACE_MAP_ENTRY(nsISupports)
 | ||
| NS_INTERFACE_MAP_END
 | ||
| 
 | ||
| WritableStream::WritableStream(nsIGlobalObject* aGlobal,
 | ||
|                                HoldDropJSObjectsCaller aHoldDropCaller)
 | ||
|     : mGlobal(aGlobal), mHoldDropCaller(aHoldDropCaller) {
 | ||
|   if (mHoldDropCaller == HoldDropJSObjectsCaller::Implicit) {
 | ||
|     mozilla::HoldJSObjects(this);
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| WritableStream::WritableStream(const GlobalObject& aGlobal,
 | ||
|                                HoldDropJSObjectsCaller aHoldDropCaller)
 | ||
|     : mGlobal(do_QueryInterface(aGlobal.GetAsSupports())),
 | ||
|       mHoldDropCaller(aHoldDropCaller) {
 | ||
|   if (mHoldDropCaller == HoldDropJSObjectsCaller::Implicit) {
 | ||
|     mozilla::HoldJSObjects(this);
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| WritableStream::~WritableStream() {
 | ||
|   if (mHoldDropCaller == HoldDropJSObjectsCaller::Implicit) {
 | ||
|     mozilla::DropJSObjects(this);
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| JSObject* WritableStream::WrapObject(JSContext* aCx,
 | ||
|                                      JS::Handle<JSObject*> aGivenProto) {
 | ||
|   return WritableStream_Binding::Wrap(aCx, this, aGivenProto);
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-deal-with-rejection
 | ||
| void WritableStream::DealWithRejection(JSContext* aCx,
 | ||
|                                        JS::Handle<JS::Value> aError,
 | ||
|                                        ErrorResult& aRv) {
 | ||
|   // Step 1. Let state be stream.[[state]].
 | ||
|   // Step 2. If state is "writable",
 | ||
|   if (mState == WriterState::Writable) {
 | ||
|     // Step 2.1. Perform ! WritableStreamStartErroring(stream, error).
 | ||
|     StartErroring(aCx, aError, aRv);
 | ||
| 
 | ||
|     // Step 2.2. Return.
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 3. Assert: state is "erroring".
 | ||
|   MOZ_ASSERT(mState == WriterState::Erroring);
 | ||
| 
 | ||
|   // Step 4. Perform ! WritableStreamFinishErroring(stream).
 | ||
|   FinishErroring(aCx, aRv);
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-finish-erroring
 | ||
| void WritableStream::FinishErroring(JSContext* aCx, ErrorResult& aRv) {
 | ||
|   // Step 1. Assert: stream.[[state]] is "erroring".
 | ||
|   MOZ_ASSERT(mState == WriterState::Erroring);
 | ||
| 
 | ||
|   // Step 2. Assert: ! WritableStreamHasOperationMarkedInFlight(stream) is
 | ||
|   // false.
 | ||
|   MOZ_ASSERT(!HasOperationMarkedInFlight());
 | ||
| 
 | ||
|   // Step 3. Set stream.[[state]] to "errored".
 | ||
|   mState = WriterState::Errored;
 | ||
| 
 | ||
|   // Step 4. Perform ! stream.[[controller]].[[ErrorSteps]]().
 | ||
|   Controller()->ErrorSteps();
 | ||
| 
 | ||
|   // Step 5. Let storedError be stream.[[storedError]].
 | ||
|   JS::Rooted<JS::Value> storedError(aCx, mStoredError);
 | ||
| 
 | ||
|   // Step 6. For each writeRequest of stream.[[writeRequests]]:
 | ||
|   for (const RefPtr<Promise>& writeRequest : mWriteRequests) {
 | ||
|     // Step 6.1. Reject writeRequest with storedError.
 | ||
|     writeRequest->MaybeReject(storedError);
 | ||
|   }
 | ||
| 
 | ||
|   // Step 7. Set stream.[[writeRequests]] to an empty list.
 | ||
|   mWriteRequests.Clear();
 | ||
| 
 | ||
|   // Step 8. If stream.[[pendingAbortRequest]] is undefined,
 | ||
|   if (!mPendingAbortRequestPromise) {
 | ||
|     // Step 8.1. Perform !
 | ||
|     // WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
 | ||
|     RejectCloseAndClosedPromiseIfNeeded();
 | ||
| 
 | ||
|     // Step 8.2. Return.
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 9. Let abortRequest be stream.[[pendingAbortRequest]].
 | ||
|   RefPtr<Promise> abortPromise = mPendingAbortRequestPromise;
 | ||
|   JS::Rooted<JS::Value> abortReason(aCx, mPendingAbortRequestReason);
 | ||
|   bool abortWasAlreadyErroring = mPendingAbortRequestWasAlreadyErroring;
 | ||
| 
 | ||
|   // Step 10. Set stream.[[pendingAbortRequest]] to undefined.
 | ||
|   SetPendingAbortRequest(nullptr, JS::UndefinedHandleValue, false);
 | ||
| 
 | ||
|   // Step 11. If abortRequest’s was already erroring is true,
 | ||
|   if (abortWasAlreadyErroring) {
 | ||
|     // Step 11.1. Reject abortRequest’s promise with storedError.
 | ||
|     abortPromise->MaybeReject(storedError);
 | ||
| 
 | ||
|     // Step 11.2. Perform !
 | ||
|     // WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
 | ||
|     RejectCloseAndClosedPromiseIfNeeded();
 | ||
| 
 | ||
|     // Step 11.3. Return.
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 12. Let promise be !
 | ||
|   // stream.[[controller]].[[AbortSteps]](abortRequest’s reason).
 | ||
|   RefPtr<WritableStreamDefaultController> controller = mController;
 | ||
|   RefPtr<Promise> promise = controller->AbortSteps(aCx, abortReason, aRv);
 | ||
|   if (aRv.Failed()) {
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 13 + 14.
 | ||
|   promise->AddCallbacksWithCycleCollectedArgs(
 | ||
|       [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
 | ||
|          Promise* aAbortRequestPromise, WritableStream* aStream) {
 | ||
|         // Step 13. Upon fulfillment of promise,
 | ||
|         // Step 13.1. Resolve abortRequest’s promise with undefined.
 | ||
|         aAbortRequestPromise->MaybeResolveWithUndefined();
 | ||
| 
 | ||
|         // Step 13.2. Perform !
 | ||
|         // WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
 | ||
|         aStream->RejectCloseAndClosedPromiseIfNeeded();
 | ||
|       },
 | ||
|       [](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
 | ||
|          Promise* aAbortRequestPromise, WritableStream* aStream) {
 | ||
|         // Step 14. Upon rejection of promise with reason reason,
 | ||
|         // Step 14.1. Reject abortRequest’s promise with reason.
 | ||
|         aAbortRequestPromise->MaybeReject(aValue);
 | ||
| 
 | ||
|         // Step 14.2. Perform !
 | ||
|         // WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream).
 | ||
|         aStream->RejectCloseAndClosedPromiseIfNeeded();
 | ||
|       },
 | ||
|       RefPtr(abortPromise), RefPtr(this));
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-close
 | ||
| void WritableStream::FinishInFlightClose() {
 | ||
|   // Step 1. Assert: stream.[[inFlightCloseRequest]] is not undefined.
 | ||
|   MOZ_ASSERT(mInFlightCloseRequest);
 | ||
| 
 | ||
|   // Step 2. Resolve stream.[[inFlightCloseRequest]] with undefined.
 | ||
|   mInFlightCloseRequest->MaybeResolveWithUndefined();
 | ||
| 
 | ||
|   // Step 3. Set stream.[[inFlightCloseRequest]] to undefined.
 | ||
|   mInFlightCloseRequest = nullptr;
 | ||
| 
 | ||
|   // Step 4. Let state be stream.[[state]].
 | ||
|   // Step 5. Assert: stream.[[state]] is "writable" or "erroring".
 | ||
|   MOZ_ASSERT(mState == WriterState::Writable ||
 | ||
|              mState == WriterState::Erroring);
 | ||
| 
 | ||
|   // Step 6. If state is "erroring",
 | ||
|   if (mState == WriterState::Erroring) {
 | ||
|     // Step 6.1. Set stream.[[storedError]] to undefined.
 | ||
|     mStoredError.setUndefined();
 | ||
| 
 | ||
|     // Step 6.2. If stream.[[pendingAbortRequest]] is not undefined,
 | ||
|     if (mPendingAbortRequestPromise) {
 | ||
|       // Step 6.2.1. Resolve stream.[[pendingAbortRequest]]'s promise with
 | ||
|       // undefined.
 | ||
|       mPendingAbortRequestPromise->MaybeResolveWithUndefined();
 | ||
| 
 | ||
|       // Step 6.2.2. Set stream.[[pendingAbortRequest]] to undefined.
 | ||
|       SetPendingAbortRequest(nullptr, JS::UndefinedHandleValue, false);
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // Step 7. Set stream.[[state]] to "closed".
 | ||
|   mState = WriterState::Closed;
 | ||
| 
 | ||
|   // Step 8. Let writer be stream.[[writer]].
 | ||
|   // Step 9. If writer is not undefined, resolve writer.[[closedPromise]] with
 | ||
|   // undefined.
 | ||
|   if (mWriter) {
 | ||
|     mWriter->ClosedPromise()->MaybeResolveWithUndefined();
 | ||
|   }
 | ||
| 
 | ||
|   // Step 10. Assert: stream.[[pendingAbortRequest]] is undefined.
 | ||
|   MOZ_ASSERT(!mPendingAbortRequestPromise);
 | ||
|   // Assert: stream.[[storedError]] is undefined.
 | ||
|   MOZ_ASSERT(mStoredError.isUndefined());
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-close-with-error
 | ||
| void WritableStream::FinishInFlightCloseWithError(JSContext* aCx,
 | ||
|                                                   JS::Handle<JS::Value> aError,
 | ||
|                                                   ErrorResult& aRv) {
 | ||
|   // Step 1. Assert: stream.[[inFlightCloseRequest]] is not undefined.
 | ||
|   MOZ_ASSERT(mInFlightCloseRequest);
 | ||
| 
 | ||
|   // Step 2. Reject stream.[[inFlightCloseRequest]] with error.
 | ||
|   mInFlightCloseRequest->MaybeReject(aError);
 | ||
| 
 | ||
|   // Step 3. Set stream.[[inFlightCloseRequest]] to undefined.
 | ||
|   mInFlightCloseRequest = nullptr;
 | ||
| 
 | ||
|   // Step 4. Assert: stream.[[state]] is "writable" or "erroring".
 | ||
|   MOZ_ASSERT(mState == WriterState::Writable ||
 | ||
|              mState == WriterState::Erroring);
 | ||
| 
 | ||
|   // Step 5. If stream.[[pendingAbortRequest]] is not undefined,
 | ||
|   if (mPendingAbortRequestPromise) {
 | ||
|     // Step 5.1. Reject stream.[[pendingAbortRequest]]'s promise with error.
 | ||
|     mPendingAbortRequestPromise->MaybeReject(aError);
 | ||
| 
 | ||
|     // Step 5.2. Set stream.[[pendingAbortRequest]] to undefined.
 | ||
|     SetPendingAbortRequest(nullptr, JS::UndefinedHandleValue, false);
 | ||
|   }
 | ||
| 
 | ||
|   // Step 6. Perform ! WritableStreamDealWithRejection(stream, error).
 | ||
|   DealWithRejection(aCx, aError, aRv);
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-write
 | ||
| void WritableStream::FinishInFlightWrite() {
 | ||
|   // Step 1. Assert: stream.[[inFlightWriteRequest]] is not undefined.
 | ||
|   MOZ_ASSERT(mInFlightWriteRequest);
 | ||
| 
 | ||
|   // Step 2. Resolve stream.[[inFlightWriteRequest]] with undefined.
 | ||
|   mInFlightWriteRequest->MaybeResolveWithUndefined();
 | ||
| 
 | ||
|   // Step 3. Set stream.[[inFlightWriteRequest]] to undefined.
 | ||
|   mInFlightWriteRequest = nullptr;
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-write-with-error
 | ||
| void WritableStream::FinishInFlightWriteWithError(JSContext* aCx,
 | ||
|                                                   JS::Handle<JS::Value> aError,
 | ||
|                                                   ErrorResult& aRv) {
 | ||
|   // Step 1. Assert: stream.[[inFlightWriteRequest]] is not undefined.
 | ||
|   MOZ_ASSERT(mInFlightWriteRequest);
 | ||
| 
 | ||
|   // Step 2. Reject stream.[[inFlightWriteRequest]] with error.
 | ||
|   mInFlightWriteRequest->MaybeReject(aError);
 | ||
| 
 | ||
|   // Step 3. Set stream.[[inFlightWriteRequest]] to undefined.
 | ||
|   mInFlightWriteRequest = nullptr;
 | ||
| 
 | ||
|   // Step 4. Assert: stream.[[state]] is "writable" or "erroring".
 | ||
|   MOZ_ASSERT(mState == WriterState::Writable ||
 | ||
|              mState == WriterState::Erroring);
 | ||
| 
 | ||
|   // Step 5. Perform ! WritableStreamDealWithRejection(stream, error).
 | ||
|   DealWithRejection(aCx, aError, aRv);
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-mark-close-request-in-flight
 | ||
| void WritableStream::MarkCloseRequestInFlight() {
 | ||
|   // Step 1. Assert: stream.[[inFlightCloseRequest]] is undefined.
 | ||
|   MOZ_ASSERT(!mInFlightCloseRequest);
 | ||
| 
 | ||
|   // Step 2. Assert: stream.[[closeRequest]] is not undefined.
 | ||
|   MOZ_ASSERT(mCloseRequest);
 | ||
| 
 | ||
|   // Step 3. Set stream.[[inFlightCloseRequest]] to stream.[[closeRequest]].
 | ||
|   mInFlightCloseRequest = mCloseRequest;
 | ||
| 
 | ||
|   // Step 4. Set stream.[[closeRequest]] to undefined.
 | ||
|   mCloseRequest = nullptr;
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-mark-first-write-request-in-flight
 | ||
| void WritableStream::MarkFirstWriteRequestInFlight() {
 | ||
|   // Step 1. Assert: stream.[[inFlightWriteRequest]] is undefined.
 | ||
|   MOZ_ASSERT(!mInFlightWriteRequest);
 | ||
| 
 | ||
|   // Step 2. Assert: stream.[[writeRequests]] is not empty.
 | ||
|   MOZ_ASSERT(!mWriteRequests.IsEmpty());
 | ||
| 
 | ||
|   // Step 3. Let writeRequest be stream.[[writeRequests]][0].
 | ||
|   RefPtr<Promise> writeRequest = mWriteRequests.ElementAt(0);
 | ||
| 
 | ||
|   // Step 4. Remove writeRequest from stream.[[writeRequests]].
 | ||
|   mWriteRequests.RemoveElementAt(0);
 | ||
| 
 | ||
|   // Step 5. Set stream.[[inFlightWriteRequest]] to writeRequest.
 | ||
|   mInFlightWriteRequest = writeRequest;
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-reject-close-and-closed-promise-if-needed
 | ||
| void WritableStream::RejectCloseAndClosedPromiseIfNeeded() {
 | ||
|   // Step 1. Assert: stream.[[state]] is "errored".
 | ||
|   MOZ_ASSERT(mState == WriterState::Errored);
 | ||
| 
 | ||
|   JS::Rooted<JS::Value> storedError(RootingCx(), mStoredError);
 | ||
|   // Step 2. If stream.[[closeRequest]] is not undefined,
 | ||
|   if (mCloseRequest) {
 | ||
|     // Step 2.1. Assert: stream.[[inFlightCloseRequest]] is undefined.
 | ||
|     MOZ_ASSERT(!mInFlightCloseRequest);
 | ||
| 
 | ||
|     // Step 2.2. Reject stream.[[closeRequest]] with stream.[[storedError]].
 | ||
|     mCloseRequest->MaybeReject(storedError);
 | ||
| 
 | ||
|     // Step 2.3. Set stream.[[closeRequest]] to undefined.
 | ||
|     mCloseRequest = nullptr;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 3. Let writer be stream.[[writer]].
 | ||
|   RefPtr<WritableStreamDefaultWriter> writer = mWriter;
 | ||
| 
 | ||
|   // Step 4. If writer is not undefined,
 | ||
|   if (writer) {
 | ||
|     // Step 4.1. Reject writer.[[closedPromise]] with stream.[[storedError]].
 | ||
|     RefPtr<Promise> closedPromise = writer->ClosedPromise();
 | ||
|     closedPromise->MaybeReject(storedError);
 | ||
| 
 | ||
|     // Step 4.2. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true.
 | ||
|     closedPromise->SetSettledPromiseIsHandled();
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-start-erroring
 | ||
| void WritableStream::StartErroring(JSContext* aCx,
 | ||
|                                    JS::Handle<JS::Value> aReason,
 | ||
|                                    ErrorResult& aRv) {
 | ||
|   // Step 1. Assert: stream.[[storedError]] is undefined.
 | ||
|   MOZ_ASSERT(mStoredError.isUndefined());
 | ||
| 
 | ||
|   // Step 2. Assert: stream.[[state]] is "writable".
 | ||
|   MOZ_ASSERT(mState == WriterState::Writable);
 | ||
| 
 | ||
|   // Step 3. Let controller be stream.[[controller]].
 | ||
|   RefPtr<WritableStreamDefaultController> controller = mController;
 | ||
|   // Step 4. Assert: controller is not undefined.
 | ||
|   MOZ_ASSERT(controller);
 | ||
| 
 | ||
|   // Step 5. Set stream.[[state]] to "erroring".
 | ||
|   mState = WriterState::Erroring;
 | ||
| 
 | ||
|   // Step 6. Set stream.[[storedError]] to reason.
 | ||
|   mStoredError = aReason;
 | ||
| 
 | ||
|   // Step 7. Let writer be stream.[[writer]].
 | ||
|   RefPtr<WritableStreamDefaultWriter> writer = mWriter;
 | ||
|   // Step 8. If writer is not undefined, perform !
 | ||
|   // WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason).
 | ||
|   if (writer) {
 | ||
|     WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, aReason);
 | ||
|   }
 | ||
| 
 | ||
|   // Step 9. If ! WritableStreamHasOperationMarkedInFlight(stream) is false
 | ||
|   // and controller.[[started]] is true,
 | ||
|   // perform !WritableStreamFinishErroring(stream).
 | ||
|   if (!HasOperationMarkedInFlight() && controller->Started()) {
 | ||
|     FinishErroring(aCx, aRv);
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-update-backpressure
 | ||
| void WritableStream::UpdateBackpressure(bool aBackpressure) {
 | ||
|   // Step 1. Assert: stream.[[state]] is "writable".
 | ||
|   MOZ_ASSERT(mState == WriterState::Writable);
 | ||
|   // Step 2. Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
 | ||
|   MOZ_ASSERT(!CloseQueuedOrInFlight());
 | ||
| 
 | ||
|   // Step 3. Let writer be stream.[[writer]].
 | ||
|   RefPtr<WritableStreamDefaultWriter> writer = mWriter;
 | ||
| 
 | ||
|   // Step 4. If writer is not undefined and backpressure is not
 | ||
|   // stream.[[backpressure]],
 | ||
|   if (writer && aBackpressure != mBackpressure) {
 | ||
|     // Step 4.1. If backpressure is true, set writer.[[readyPromise]] to a new
 | ||
|     // promise.
 | ||
|     if (aBackpressure) {
 | ||
|       RefPtr<Promise> promise =
 | ||
|           Promise::CreateInfallible(writer->GetParentObject());
 | ||
|       writer->SetReadyPromise(promise);
 | ||
|     } else {
 | ||
|       // Step 4.2. Otherwise,
 | ||
|       // Step 4.2.1. Assert: backpressure is false.
 | ||
|       // Step 4.2.2. Resolve writer.[[readyPromise]] with undefined.
 | ||
|       writer->ReadyPromise()->MaybeResolveWithUndefined();
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // Step 5. Set stream.[[backpressure]] to backpressure.
 | ||
|   mBackpressure = aBackpressure;
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#ws-constructor
 | ||
| already_AddRefed<WritableStream> WritableStream::Constructor(
 | ||
|     const GlobalObject& aGlobal,
 | ||
|     const Optional<JS::Handle<JSObject*>>& aUnderlyingSink,
 | ||
|     const QueuingStrategy& aStrategy, ErrorResult& aRv) {
 | ||
|   // Step 1. If underlyingSink is missing, set it to null.
 | ||
|   JS::Rooted<JSObject*> underlyingSinkObj(
 | ||
|       aGlobal.Context(),
 | ||
|       aUnderlyingSink.WasPassed() ? aUnderlyingSink.Value() : nullptr);
 | ||
| 
 | ||
|   // Step 2. Let underlyingSinkDict be underlyingSink, converted to
 | ||
|   //         an IDL value of type UnderlyingSink.
 | ||
|   RootedDictionary<UnderlyingSink> underlyingSinkDict(aGlobal.Context());
 | ||
|   if (underlyingSinkObj) {
 | ||
|     JS::Rooted<JS::Value> objValue(aGlobal.Context(),
 | ||
|                                    JS::ObjectValue(*underlyingSinkObj));
 | ||
|     dom::BindingCallContext callCx(aGlobal.Context(),
 | ||
|                                    "WritableStream.constructor");
 | ||
|     aRv.MightThrowJSException();
 | ||
|     if (!underlyingSinkDict.Init(callCx, objValue)) {
 | ||
|       aRv.StealExceptionFromJSContext(aGlobal.Context());
 | ||
|       return nullptr;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // Step 3. If underlyingSinkDict["type"] exists, throw a RangeError exception.
 | ||
|   if (!underlyingSinkDict.mType.isUndefined()) {
 | ||
|     aRv.ThrowRangeError("Implementation preserved member 'type'");
 | ||
|     return nullptr;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 4. Perform ! InitializeWritableStream(this).
 | ||
|   RefPtr<WritableStream> writableStream =
 | ||
|       new WritableStream(aGlobal, HoldDropJSObjectsCaller::Implicit);
 | ||
| 
 | ||
|   // Step 5. Let sizeAlgorithm be ! ExtractSizeAlgorithm(strategy).
 | ||
|   //
 | ||
|   // Implementation Note: The specification demands that if the size doesn't
 | ||
|   // exist, we instead would provide an algorithm that returns 1. Instead, we
 | ||
|   // will teach callers that a missing callback should simply return 1, rather
 | ||
|   // than gin up a fake callback here.
 | ||
|   //
 | ||
|   // This decision may need to be revisited if the default action ever diverges
 | ||
|   // within the specification.
 | ||
|   RefPtr<QueuingStrategySize> sizeAlgorithm =
 | ||
|       aStrategy.mSize.WasPassed() ? &aStrategy.mSize.Value() : nullptr;
 | ||
| 
 | ||
|   // Step 6. Let highWaterMark be ? ExtractHighWaterMark(strategy, 1).
 | ||
|   double highWaterMark = ExtractHighWaterMark(aStrategy, 1, aRv);
 | ||
|   if (aRv.Failed()) {
 | ||
|     return nullptr;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 7. Perform ? SetUpWritableStreamDefaultControllerFromUnderlyingSink(
 | ||
|   // this, underlyingSink, underlyingSinkDict, highWaterMark, sizeAlgorithm).
 | ||
|   SetUpWritableStreamDefaultControllerFromUnderlyingSink(
 | ||
|       aGlobal.Context(), writableStream, underlyingSinkObj, underlyingSinkDict,
 | ||
|       highWaterMark, sizeAlgorithm, aRv);
 | ||
|   if (aRv.Failed()) {
 | ||
|     return nullptr;
 | ||
|   }
 | ||
| 
 | ||
|   return writableStream.forget();
 | ||
| }
 | ||
| 
 | ||
| namespace streams_abstract {
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-abort
 | ||
| already_AddRefed<Promise> WritableStreamAbort(JSContext* aCx,
 | ||
|                                               WritableStream* aStream,
 | ||
|                                               JS::Handle<JS::Value> aReason,
 | ||
|                                               ErrorResult& aRv) {
 | ||
|   // Step 1. If stream.[[state]] is "closed" or "errored", return a promise
 | ||
|   // resolved with undefined.
 | ||
|   if (aStream->State() == WritableStream::WriterState::Closed ||
 | ||
|       aStream->State() == WritableStream::WriterState::Errored) {
 | ||
|     RefPtr<Promise> promise =
 | ||
|         Promise::CreateInfallible(aStream->GetParentObject());
 | ||
|     promise->MaybeResolveWithUndefined();
 | ||
|     return promise.forget();
 | ||
|   }
 | ||
| 
 | ||
|   // Step 2. Signal abort on stream.[[controller]].[[signal]] with reason.
 | ||
|   RefPtr<WritableStreamDefaultController> controller = aStream->Controller();
 | ||
|   controller->Signal()->SignalAbort(aReason);
 | ||
| 
 | ||
|   // Step 3. Let state be stream.[[state]].
 | ||
|   WritableStream::WriterState state = aStream->State();
 | ||
| 
 | ||
|   // Step 4. If state is "closed" or "errored", return a promise resolved with
 | ||
|   // undefined. Note: We re-check the state because signaling abort runs author
 | ||
|   // code and that might have changed the state.
 | ||
|   if (aStream->State() == WritableStream::WriterState::Closed ||
 | ||
|       aStream->State() == WritableStream::WriterState::Errored) {
 | ||
|     RefPtr<Promise> promise =
 | ||
|         Promise::CreateInfallible(aStream->GetParentObject());
 | ||
|     promise->MaybeResolveWithUndefined();
 | ||
|     return promise.forget();
 | ||
|   }
 | ||
| 
 | ||
|   // Step 5. If stream.[[pendingAbortRequest]] is not undefined, return
 | ||
|   // stream.[[pendingAbortRequest]]'s promise.
 | ||
|   if (aStream->GetPendingAbortRequestPromise()) {
 | ||
|     RefPtr<Promise> promise = aStream->GetPendingAbortRequestPromise();
 | ||
|     return promise.forget();
 | ||
|   }
 | ||
| 
 | ||
|   // Step 6. Assert: state is "writable" or "erroring".
 | ||
|   MOZ_ASSERT(state == WritableStream::WriterState::Writable ||
 | ||
|              state == WritableStream::WriterState::Erroring);
 | ||
| 
 | ||
|   // Step 7. Let wasAlreadyErroring be false.
 | ||
|   bool wasAlreadyErroring = false;
 | ||
| 
 | ||
|   // Step 8. If state is "erroring",
 | ||
|   JS::Rooted<JS::Value> reason(aCx, aReason);
 | ||
|   if (state == WritableStream::WriterState::Erroring) {
 | ||
|     // Step 8.1. Set wasAlreadyErroring to true.
 | ||
|     wasAlreadyErroring = true;
 | ||
|     // Step 8.2. Set reason to undefined.
 | ||
|     reason.setUndefined();
 | ||
|   }
 | ||
| 
 | ||
|   // Step 9. Let promise be a new promise.
 | ||
|   RefPtr<Promise> promise =
 | ||
|       Promise::CreateInfallible(aStream->GetParentObject());
 | ||
| 
 | ||
|   // Step 10. Set stream.[[pendingAbortRequest]] to a new pending abort request
 | ||
|   // whose promise is promise, reason is reason, and was already erroring is
 | ||
|   // wasAlreadyErroring.
 | ||
|   aStream->SetPendingAbortRequest(promise, reason, wasAlreadyErroring);
 | ||
| 
 | ||
|   // Step 11. If wasAlreadyErroring is false, perform !
 | ||
|   // WritableStreamStartErroring(stream, reason).
 | ||
|   if (!wasAlreadyErroring) {
 | ||
|     aStream->StartErroring(aCx, reason, aRv);
 | ||
|     if (aRv.Failed()) {
 | ||
|       return nullptr;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // Step 12. Return promise.
 | ||
|   return promise.forget();
 | ||
| }
 | ||
| }  // namespace streams_abstract
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#ws-abort
 | ||
| already_AddRefed<Promise> WritableStream::Abort(JSContext* aCx,
 | ||
|                                                 JS::Handle<JS::Value> aReason,
 | ||
|                                                 ErrorResult& aRv) {
 | ||
|   // Step 1. If ! IsWritableStreamLocked(this) is true, return a promise
 | ||
|   // rejected with a TypeError exception.
 | ||
|   if (Locked()) {
 | ||
|     return Promise::CreateRejectedWithTypeError(
 | ||
|         GetParentObject(), "Canceled Locked Stream"_ns, aRv);
 | ||
|   }
 | ||
| 
 | ||
|   // Step 2. Return ! WritableStreamAbort(this, reason).
 | ||
|   RefPtr<WritableStream> thisRefPtr = this;
 | ||
|   return WritableStreamAbort(aCx, thisRefPtr, aReason, aRv);
 | ||
| }
 | ||
| 
 | ||
| namespace streams_abstract {
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-close
 | ||
| already_AddRefed<Promise> WritableStreamClose(JSContext* aCx,
 | ||
|                                               WritableStream* aStream,
 | ||
|                                               ErrorResult& aRv) {
 | ||
|   // Step 1. Let state be stream.[[state]].
 | ||
|   WritableStream::WriterState state = aStream->State();
 | ||
| 
 | ||
|   // Step 2. If state is "closed" or "errored", return a promise rejected with a
 | ||
|   // TypeError exception.
 | ||
|   if (state == WritableStream::WriterState::Closed ||
 | ||
|       state == WritableStream::WriterState::Errored) {
 | ||
|     return Promise::CreateRejectedWithTypeError(
 | ||
|         aStream->GetParentObject(),
 | ||
|         "Can not close stream after closing or error"_ns, aRv);
 | ||
|   }
 | ||
| 
 | ||
|   // Step 3. Assert: state is "writable" or "erroring".
 | ||
|   MOZ_ASSERT(state == WritableStream::WriterState::Writable ||
 | ||
|              state == WritableStream::WriterState::Erroring);
 | ||
| 
 | ||
|   // Step 4. Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false.
 | ||
|   MOZ_ASSERT(!aStream->CloseQueuedOrInFlight());
 | ||
| 
 | ||
|   // Step 5. Let promise be a new promise.
 | ||
|   RefPtr<Promise> promise =
 | ||
|       Promise::CreateInfallible(aStream->GetParentObject());
 | ||
| 
 | ||
|   // Step 6. Set stream.[[closeRequest]] to promise.
 | ||
|   aStream->SetCloseRequest(promise);
 | ||
| 
 | ||
|   // Step 7. Let writer be stream.[[writer]].
 | ||
|   RefPtr<WritableStreamDefaultWriter> writer = aStream->GetWriter();
 | ||
| 
 | ||
|   // Step 8. If writer is not undefined, and stream.[[backpressure]] is true,
 | ||
|   // and state is "writable", resolve writer.[[readyPromise]] with undefined.
 | ||
|   if (writer && aStream->Backpressure() &&
 | ||
|       state == WritableStream::WriterState::Writable) {
 | ||
|     writer->ReadyPromise()->MaybeResolveWithUndefined();
 | ||
|   }
 | ||
| 
 | ||
|   // Step 9.
 | ||
|   // Perform ! WritableStreamDefaultControllerClose(stream.[[controller]]).
 | ||
|   RefPtr<WritableStreamDefaultController> controller = aStream->Controller();
 | ||
|   WritableStreamDefaultControllerClose(aCx, controller, aRv);
 | ||
|   if (aRv.Failed()) {
 | ||
|     return nullptr;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 10. Return promise.
 | ||
|   return promise.forget();
 | ||
| }
 | ||
| }  // namespace streams_abstract
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#ws-close
 | ||
| already_AddRefed<Promise> WritableStream::Close(JSContext* aCx,
 | ||
|                                                 ErrorResult& aRv) {
 | ||
|   // Step 1. If ! IsWritableStreamLocked(this) is true, return a promise
 | ||
|   // rejected with a TypeError exception.
 | ||
|   if (Locked()) {
 | ||
|     return Promise::CreateRejectedWithTypeError(
 | ||
|         GetParentObject(), "Can not close locked stream"_ns, aRv);
 | ||
|   }
 | ||
| 
 | ||
|   // Step 2. If ! WritableStreamCloseQueuedOrInFlight(this) is true, return a
 | ||
|   // promise rejected with a TypeError exception.
 | ||
|   if (CloseQueuedOrInFlight()) {
 | ||
|     return Promise::CreateRejectedWithTypeError(
 | ||
|         GetParentObject(), "Stream is already closing"_ns, aRv);
 | ||
|   }
 | ||
| 
 | ||
|   // Step 3. Return ! WritableStreamClose(this).
 | ||
|   RefPtr<WritableStream> thisRefPtr = this;
 | ||
|   return WritableStreamClose(aCx, thisRefPtr, aRv);
 | ||
| }
 | ||
| 
 | ||
| namespace streams_abstract {
 | ||
| // https://streams.spec.whatwg.org/#acquire-writable-stream-default-writer
 | ||
| already_AddRefed<WritableStreamDefaultWriter>
 | ||
| AcquireWritableStreamDefaultWriter(WritableStream* aStream, ErrorResult& aRv) {
 | ||
|   // Step 1. Let writer be a new WritableStreamDefaultWriter.
 | ||
|   RefPtr<WritableStreamDefaultWriter> writer =
 | ||
|       new WritableStreamDefaultWriter(aStream->GetParentObject());
 | ||
| 
 | ||
|   // Step 2. Perform ? SetUpWritableStreamDefaultWriter(writer, stream).
 | ||
|   SetUpWritableStreamDefaultWriter(writer, aStream, aRv);
 | ||
|   if (aRv.Failed()) {
 | ||
|     return nullptr;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 3. Return writer.
 | ||
|   return writer.forget();
 | ||
| }
 | ||
| }  // namespace streams_abstract
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#create-writable-stream
 | ||
| already_AddRefed<WritableStream> WritableStream::CreateAbstract(
 | ||
|     JSContext* aCx, nsIGlobalObject* aGlobal,
 | ||
|     UnderlyingSinkAlgorithmsBase* aAlgorithms, double aHighWaterMark,
 | ||
|     QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) {
 | ||
|   // Step 1: Assert: ! IsNonNegativeNumber(highWaterMark) is true.
 | ||
|   MOZ_ASSERT(IsNonNegativeNumber(aHighWaterMark));
 | ||
| 
 | ||
|   // Step 2: Let stream be a new WritableStream.
 | ||
|   // Step 3: Perform ! InitializeWritableStream(stream).
 | ||
|   RefPtr<WritableStream> stream = new WritableStream(
 | ||
|       aGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit);
 | ||
| 
 | ||
|   // Step 4: Let controller be a new WritableStreamDefaultController.
 | ||
|   auto controller =
 | ||
|       MakeRefPtr<WritableStreamDefaultController>(aGlobal, *stream);
 | ||
| 
 | ||
|   // Step 5: Perform ? SetUpWritableStreamDefaultController(stream, controller,
 | ||
|   // startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm,
 | ||
|   // highWaterMark, sizeAlgorithm).
 | ||
|   SetUpWritableStreamDefaultController(aCx, stream, controller, aAlgorithms,
 | ||
|                                        aHighWaterMark, aSizeAlgorithm, aRv);
 | ||
|   if (aRv.Failed()) {
 | ||
|     return nullptr;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 6: Return stream.
 | ||
|   return stream.forget();
 | ||
| }
 | ||
| 
 | ||
| already_AddRefed<WritableStreamDefaultWriter> WritableStream::GetWriter(
 | ||
|     ErrorResult& aRv) {
 | ||
|   return AcquireWritableStreamDefaultWriter(this, aRv);
 | ||
| }
 | ||
| 
 | ||
| namespace streams_abstract {
 | ||
| // https://streams.spec.whatwg.org/#writable-stream-add-write-request
 | ||
| already_AddRefed<Promise> WritableStreamAddWriteRequest(
 | ||
|     WritableStream* aStream) {
 | ||
|   // Step 1. Assert: ! IsWritableStreamLocked(stream) is true.
 | ||
|   MOZ_ASSERT(IsWritableStreamLocked(aStream));
 | ||
| 
 | ||
|   // Step 2. Assert: stream.[[state]] is "writable".
 | ||
|   MOZ_ASSERT(aStream->State() == WritableStream::WriterState::Writable);
 | ||
| 
 | ||
|   // Step 3. Let promise be a new promise.
 | ||
|   RefPtr<Promise> promise =
 | ||
|       Promise::CreateInfallible(aStream->GetParentObject());
 | ||
| 
 | ||
|   // Step 4. Append promise to stream.[[writeRequests]].
 | ||
|   aStream->AppendWriteRequest(promise);
 | ||
| 
 | ||
|   // Step 5. Return promise.
 | ||
|   return promise.forget();
 | ||
| }
 | ||
| }  // namespace streams_abstract
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writablestream-set-up
 | ||
| // _BOUNDARY because `aAlgorithms->StartCallback` (called by
 | ||
| // SetUpWritableStreamDefaultController below) should not be able to run script
 | ||
| // in this case.
 | ||
| MOZ_CAN_RUN_SCRIPT_BOUNDARY void WritableStream::SetUpNative(
 | ||
|     JSContext* aCx, UnderlyingSinkAlgorithmsWrapper& aAlgorithms,
 | ||
|     Maybe<double> aHighWaterMark, QueuingStrategySize* aSizeAlgorithm,
 | ||
|     ErrorResult& aRv) {
 | ||
|   // an optional number highWaterMark (default 1)
 | ||
|   double highWaterMark = aHighWaterMark.valueOr(1);
 | ||
|   // and if given, highWaterMark must be a non-negative, non-NaN number.
 | ||
|   MOZ_ASSERT(IsNonNegativeNumber(highWaterMark));
 | ||
| 
 | ||
|   // Step 1: Let startAlgorithm be an algorithm that returns undefined.
 | ||
|   // Step 2: Let closeAlgorithmWrapper be an algorithm that runs these steps:
 | ||
|   // Step 3: Let abortAlgorithmWrapper be an algorithm that runs these steps:
 | ||
|   // (Covered by UnderlyingSinkAlgorithmsWrapper)
 | ||
| 
 | ||
|   // Step 4: If sizeAlgorithm was not given, then set it to an algorithm that
 | ||
|   // returns 1. (Callers will treat nullptr as such, see
 | ||
|   // WritableStream::Constructor for details)
 | ||
| 
 | ||
|   // Step 5: Perform ! InitializeWritableStream(stream).
 | ||
|   // (Covered by the constructor)
 | ||
| 
 | ||
|   // Step 6: Let controller be a new WritableStreamDefaultController.
 | ||
|   auto controller =
 | ||
|       MakeRefPtr<WritableStreamDefaultController>(GetParentObject(), *this);
 | ||
| 
 | ||
|   // Step 7: Perform ! SetUpWritableStreamDefaultController(stream, controller,
 | ||
|   // startAlgorithm, writeAlgorithm, closeAlgorithmWrapper,
 | ||
|   // abortAlgorithmWrapper, highWaterMark, sizeAlgorithm).
 | ||
|   SetUpWritableStreamDefaultController(aCx, this, controller, &aAlgorithms,
 | ||
|                                        highWaterMark, aSizeAlgorithm, aRv);
 | ||
| }
 | ||
| 
 | ||
| already_AddRefed<WritableStream> WritableStream::CreateNative(
 | ||
|     JSContext* aCx, nsIGlobalObject& aGlobal,
 | ||
|     UnderlyingSinkAlgorithmsWrapper& aAlgorithms, Maybe<double> aHighWaterMark,
 | ||
|     QueuingStrategySize* aSizeAlgorithm, ErrorResult& aRv) {
 | ||
|   RefPtr<WritableStream> stream = new WritableStream(
 | ||
|       &aGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit);
 | ||
|   stream->SetUpNative(aCx, aAlgorithms, aHighWaterMark, aSizeAlgorithm, aRv);
 | ||
|   if (aRv.Failed()) {
 | ||
|     return nullptr;
 | ||
|   }
 | ||
|   return stream.forget();
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#writablestream-error
 | ||
| // To error a WritableStream stream given a JavaScript value e, perform !
 | ||
| // WritableStreamDefaultControllerErrorIfNeeded(stream.[[controller]], e).
 | ||
| void WritableStream::ErrorNative(JSContext* aCx, JS::Handle<JS::Value> aError,
 | ||
|                                  ErrorResult& aRv) {
 | ||
|   // MOZ_KnownLive here instead of MOZ_KNOWN_LIVE at the field, because
 | ||
|   // mController is set outside of the constructor
 | ||
|   WritableStreamDefaultControllerErrorIfNeeded(aCx, MOZ_KnownLive(mController),
 | ||
|                                                aError, aRv);
 | ||
| }
 | ||
| 
 | ||
| }  // namespace mozilla::dom
 |