forked from mirrors/gecko-dev
		
	 0638b18b45
			
		
	
	
		0638b18b45
		
	
	
	
	
		
			
			Status checks are done in batch before any transfer, but transferring streams can change the status of parent/child of TransformStream and cause an assertion failure. Differential Revision: https://phabricator.services.mozilla.com/D170897
		
			
				
	
	
		
			1068 lines
		
	
	
	
		
			38 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1068 lines
		
	
	
	
		
			38 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 "ErrorList.h"
 | ||
| #include "ReadableStreamPipeTo.h"
 | ||
| #include "js/RootingAPI.h"
 | ||
| #include "js/String.h"
 | ||
| #include "js/TypeDecls.h"
 | ||
| #include "js/Value.h"
 | ||
| #include "mozilla/AlreadyAddRefed.h"
 | ||
| #include "mozilla/dom/DOMExceptionBinding.h"
 | ||
| #include "nsCycleCollectionParticipant.h"
 | ||
| #include "nsIDOMEventListener.h"
 | ||
| #include "nsIGlobalObject.h"
 | ||
| #include "mozilla/ErrorResult.h"
 | ||
| #include "mozilla/ResultVariant.h"
 | ||
| #include "mozilla/dom/DOMException.h"
 | ||
| #include "mozilla/dom/MessageEvent.h"
 | ||
| #include "mozilla/dom/MessageChannel.h"
 | ||
| #include "mozilla/dom/MessagePort.h"
 | ||
| #include "mozilla/dom/Promise.h"
 | ||
| #include "mozilla/dom/Promise-inl.h"
 | ||
| #include "mozilla/dom/ReadableStream.h"
 | ||
| #include "mozilla/dom/WritableStream.h"
 | ||
| #include "mozilla/dom/TransformStream.h"
 | ||
| #include "nsISupportsImpl.h"
 | ||
| 
 | ||
| namespace mozilla::dom {
 | ||
| 
 | ||
| using namespace streams_abstract;
 | ||
| 
 | ||
| static void PackAndPostMessage(JSContext* aCx, MessagePort* aPort,
 | ||
|                                const nsAString& aType,
 | ||
|                                JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
 | ||
|   JS::Rooted<JSObject*> obj(aCx,
 | ||
|                             JS_NewObjectWithGivenProto(aCx, nullptr, nullptr));
 | ||
|   if (!obj) {
 | ||
|     // XXX: Should we crash here and there? See also bug 1762233.
 | ||
|     JS_ClearPendingException(aCx);
 | ||
|     aRv.Throw(NS_ERROR_UNEXPECTED);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   JS::Rooted<JS::Value> type(aCx);
 | ||
|   if (!xpc::NonVoidStringToJsval(aCx, aType, &type)) {
 | ||
|     JS_ClearPendingException(aCx);
 | ||
|     aRv.Throw(NS_ERROR_UNEXPECTED);
 | ||
|     return;
 | ||
|   }
 | ||
|   if (!JS_DefineProperty(aCx, obj, "type", type, JSPROP_ENUMERATE)) {
 | ||
|     JS_ClearPendingException(aCx);
 | ||
|     aRv.Throw(NS_ERROR_UNEXPECTED);
 | ||
|     return;
 | ||
|   }
 | ||
|   JS::Rooted<JS::Value> value(aCx, aValue);
 | ||
|   if (!JS_WrapValue(aCx, &value)) {
 | ||
|     JS_ClearPendingException(aCx);
 | ||
|     aRv.Throw(NS_ERROR_UNEXPECTED);
 | ||
|     return;
 | ||
|   }
 | ||
|   if (!JS_DefineProperty(aCx, obj, "value", value, JSPROP_ENUMERATE)) {
 | ||
|     JS_ClearPendingException(aCx);
 | ||
|     aRv.Throw(NS_ERROR_UNEXPECTED);
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   Sequence<JSObject*> transferables;  // none in this case
 | ||
|   JS::Rooted<JS::Value> objValue(aCx, JS::ObjectValue(*obj));
 | ||
|   aPort->PostMessage(aCx, objValue, transferables, aRv);
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#abstract-opdef-crossrealmtransformsenderror
 | ||
| static void CrossRealmTransformSendError(JSContext* aCx, MessagePort* aPort,
 | ||
|                                          JS::Handle<JS::Value> aError) {
 | ||
|   // Step 1: Perform PackAndPostMessage(port, "error", error), discarding the
 | ||
|   // result.
 | ||
|   PackAndPostMessage(aCx, aPort, u"error"_ns, aError, IgnoreErrors());
 | ||
| }
 | ||
| 
 | ||
| class SetUpTransformWritableMessageEventListener final
 | ||
|     : public nsIDOMEventListener {
 | ||
|  public:
 | ||
|   SetUpTransformWritableMessageEventListener(
 | ||
|       WritableStreamDefaultController* aController, Promise* aPromise)
 | ||
|       : mController(aController), mBackpressurePromise(aPromise) {}
 | ||
| 
 | ||
|   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | ||
|   NS_DECL_CYCLE_COLLECTION_CLASS(SetUpTransformWritableMessageEventListener)
 | ||
| 
 | ||
|   // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
 | ||
|   // The handler steps of Step 4.
 | ||
|   MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
 | ||
|     AutoJSAPI jsapi;
 | ||
|     if (!jsapi.Init(mController->GetParentObject())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     JSContext* cx = jsapi.cx();
 | ||
|     MessageEvent* messageEvent = aEvent->AsMessageEvent();
 | ||
|     if (NS_WARN_IF(!messageEvent || !messageEvent->IsTrusted())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 1: Let data be the data of the message.
 | ||
|     JS::Rooted<JS::Value> dataValue(cx);
 | ||
|     IgnoredErrorResult rv;
 | ||
|     messageEvent->GetData(cx, &dataValue, rv);
 | ||
|     if (rv.Failed()) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 2: Assert: Type(data) is Object.
 | ||
|     // (But we check in runtime instead to avoid potential malicious events from
 | ||
|     // a compromised process. Same below.)
 | ||
|     if (NS_WARN_IF(!dataValue.isObject())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     JS::Rooted<JSObject*> data(cx, &dataValue.toObject());
 | ||
| 
 | ||
|     // Step 3: Let type be ! Get(data, "type").
 | ||
|     JS::Rooted<JS::Value> type(cx);
 | ||
|     if (!JS_GetProperty(cx, data, "type", &type)) {
 | ||
|       // XXX: See bug 1762233
 | ||
|       JS_ClearPendingException(cx);
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 4: Let value be ! Get(data, "value").
 | ||
|     JS::Rooted<JS::Value> value(cx);
 | ||
|     if (!JS_GetProperty(cx, data, "value", &value)) {
 | ||
|       JS_ClearPendingException(cx);
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 5: Assert: Type(type) is String.
 | ||
|     if (NS_WARN_IF(!type.isString())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 6: If type is "pull",
 | ||
|     bool equals = false;
 | ||
|     if (!JS_StringEqualsLiteral(cx, type.toString(), "pull", &equals)) {
 | ||
|       JS_ClearPendingException(cx);
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     if (equals) {
 | ||
|       // Step 6.1: If backpressurePromise is not undefined,
 | ||
|       MaybeResolveAndClearBackpressurePromise();
 | ||
|       return NS_OK;  // implicit
 | ||
|     }
 | ||
| 
 | ||
|     // Step 7: If type is "error",
 | ||
|     if (!JS_StringEqualsLiteral(cx, type.toString(), "error", &equals)) {
 | ||
|       JS_ClearPendingException(cx);
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     if (equals) {
 | ||
|       // Step 7.1: Perform !
 | ||
|       // WritableStreamDefaultControllerErrorIfNeeded(controller, value).
 | ||
|       WritableStreamDefaultControllerErrorIfNeeded(cx, mController, value, rv);
 | ||
|       if (rv.Failed()) {
 | ||
|         return NS_OK;
 | ||
|       }
 | ||
| 
 | ||
|       // Step 7.2: If backpressurePromise is not undefined,
 | ||
|       MaybeResolveAndClearBackpressurePromise();
 | ||
|       return NS_OK;  // implicit
 | ||
|     }
 | ||
| 
 | ||
|     // Logically it should be unreachable here, but we should expect random
 | ||
|     // malicious messages.
 | ||
|     NS_WARNING("Got an unexpected type other than pull/error.");
 | ||
|     return NS_OK;
 | ||
|   }
 | ||
| 
 | ||
|   void MaybeResolveAndClearBackpressurePromise() {
 | ||
|     if (mBackpressurePromise) {
 | ||
|       mBackpressurePromise->MaybeResolveWithUndefined();
 | ||
|       mBackpressurePromise = nullptr;
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   // Note: This promise field is shared with the sink algorithms.
 | ||
|   Promise* BackpressurePromise() { return mBackpressurePromise; }
 | ||
| 
 | ||
|   void CreateBackpressurePromise() {
 | ||
|     mBackpressurePromise =
 | ||
|         Promise::CreateInfallible(mController->GetParentObject());
 | ||
|   }
 | ||
| 
 | ||
|  private:
 | ||
|   ~SetUpTransformWritableMessageEventListener() = default;
 | ||
| 
 | ||
|   // mController never changes before CC
 | ||
|   // TODO: MOZ_IMMUTABLE_OUTSIDE_CC
 | ||
|   MOZ_KNOWN_LIVE RefPtr<WritableStreamDefaultController> mController;
 | ||
|   RefPtr<Promise> mBackpressurePromise;
 | ||
| };
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION(SetUpTransformWritableMessageEventListener,
 | ||
|                          mController, mBackpressurePromise)
 | ||
| NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformWritableMessageEventListener)
 | ||
| NS_IMPL_CYCLE_COLLECTING_RELEASE(SetUpTransformWritableMessageEventListener)
 | ||
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 | ||
|     SetUpTransformWritableMessageEventListener)
 | ||
|   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
 | ||
| NS_INTERFACE_MAP_END
 | ||
| 
 | ||
| class SetUpTransformWritableMessageErrorEventListener final
 | ||
|     : public nsIDOMEventListener {
 | ||
|  public:
 | ||
|   SetUpTransformWritableMessageErrorEventListener(
 | ||
|       WritableStreamDefaultController* aController, MessagePort* aPort)
 | ||
|       : mController(aController), mPort(aPort) {}
 | ||
| 
 | ||
|   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | ||
|   NS_DECL_CYCLE_COLLECTION_CLASS(
 | ||
|       SetUpTransformWritableMessageErrorEventListener)
 | ||
| 
 | ||
|   // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
 | ||
|   // The handler steps of Step 5.
 | ||
|   MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
 | ||
|     auto cleanupPort =
 | ||
|         MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
 | ||
| 
 | ||
|     if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 1: Let error be a new "DataCloneError" DOMException.
 | ||
|     RefPtr<DOMException> exception =
 | ||
|         DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR);
 | ||
| 
 | ||
|     AutoJSAPI jsapi;
 | ||
|     if (!jsapi.Init(mPort->GetParentObject())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     JSContext* cx = jsapi.cx();
 | ||
|     JS::Rooted<JS::Value> error(cx);
 | ||
|     if (!ToJSValue(cx, *exception, &error)) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 2: Perform ! CrossRealmTransformSendError(port, error).
 | ||
|     CrossRealmTransformSendError(cx, mPort, error);
 | ||
| 
 | ||
|     // Step 3: Perform
 | ||
|     // ! WritableStreamDefaultControllerErrorIfNeeded(controller, error).
 | ||
|     WritableStreamDefaultControllerErrorIfNeeded(cx, mController, error,
 | ||
|                                                  IgnoreErrors());
 | ||
| 
 | ||
|     // Step 4: Disentangle port.
 | ||
|     // (Close() does it)
 | ||
|     mPort->Close();
 | ||
|     cleanupPort.release();
 | ||
| 
 | ||
|     return NS_OK;
 | ||
|   }
 | ||
| 
 | ||
|  private:
 | ||
|   ~SetUpTransformWritableMessageErrorEventListener() = default;
 | ||
| 
 | ||
|   // mController never changes before CC
 | ||
|   // TODO: MOZ_IMMUTABLE_OUTSIDE_CC
 | ||
|   MOZ_KNOWN_LIVE RefPtr<WritableStreamDefaultController> mController;
 | ||
|   RefPtr<MessagePort> mPort;
 | ||
| };
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION(SetUpTransformWritableMessageErrorEventListener,
 | ||
|                          mController, mPort)
 | ||
| NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformWritableMessageErrorEventListener)
 | ||
| NS_IMPL_CYCLE_COLLECTING_RELEASE(
 | ||
|     SetUpTransformWritableMessageErrorEventListener)
 | ||
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 | ||
|     SetUpTransformWritableMessageErrorEventListener)
 | ||
|   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
 | ||
| NS_INTERFACE_MAP_END
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#abstract-opdef-packandpostmessagehandlingerror
 | ||
| static bool PackAndPostMessageHandlingError(
 | ||
|     JSContext* aCx, mozilla::dom::MessagePort* aPort, const nsAString& aType,
 | ||
|     JS::Handle<JS::Value> aValue, JS::MutableHandle<JS::Value> aError) {
 | ||
|   // Step 1: Let result be PackAndPostMessage(port, type, value).
 | ||
|   ErrorResult rv;
 | ||
|   PackAndPostMessage(aCx, aPort, aType, aValue, rv);
 | ||
| 
 | ||
|   // Step 2: If result is an abrupt completion,
 | ||
|   if (rv.Failed()) {
 | ||
|     // Step 2.2: Perform ! CrossRealmTransformSendError(port, result.[[Value]]).
 | ||
|     MOZ_ALWAYS_TRUE(ToJSValue(aCx, std::move(rv), aError));
 | ||
|     CrossRealmTransformSendError(aCx, aPort, aError);
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 3: Return result as a completion record.
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
 | ||
| class CrossRealmWritableUnderlyingSinkAlgorithms final
 | ||
|     : public UnderlyingSinkAlgorithmsBase {
 | ||
|  public:
 | ||
|   NS_DECL_ISUPPORTS_INHERITED
 | ||
|   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
 | ||
|       CrossRealmWritableUnderlyingSinkAlgorithms, UnderlyingSinkAlgorithmsBase)
 | ||
| 
 | ||
|   CrossRealmWritableUnderlyingSinkAlgorithms(
 | ||
|       SetUpTransformWritableMessageEventListener* aListener, MessagePort* aPort)
 | ||
|       : mListener(aListener), mPort(aPort) {}
 | ||
| 
 | ||
|   void StartCallback(JSContext* aCx,
 | ||
|                      WritableStreamDefaultController& aController,
 | ||
|                      JS::MutableHandle<JS::Value> aRetVal,
 | ||
|                      ErrorResult& aRv) override {
 | ||
|     // Step 7. Let startAlgorithm be an algorithm that returns undefined.
 | ||
|     aRetVal.setUndefined();
 | ||
|   }
 | ||
| 
 | ||
|   already_AddRefed<Promise> WriteCallback(
 | ||
|       JSContext* aCx, JS::Handle<JS::Value> aChunk,
 | ||
|       WritableStreamDefaultController& aController, ErrorResult& aRv) override {
 | ||
|     // Step 1: If backpressurePromise is undefined, set backpressurePromise to a
 | ||
|     // promise resolved with undefined.
 | ||
|     // Note: This promise field is shared with the message event listener.
 | ||
|     if (!mListener->BackpressurePromise()) {
 | ||
|       mListener->CreateBackpressurePromise();
 | ||
|       mListener->BackpressurePromise()->MaybeResolveWithUndefined();
 | ||
|     }
 | ||
| 
 | ||
|     // Step 2: Return the result of reacting to backpressurePromise with the
 | ||
|     // following fulfillment steps:
 | ||
|     auto result =
 | ||
|         mListener->BackpressurePromise()->ThenWithCycleCollectedArgsJS(
 | ||
|             [](JSContext* aCx, JS::Handle<JS::Value>, ErrorResult& aRv,
 | ||
|                SetUpTransformWritableMessageEventListener* aListener,
 | ||
|                MessagePort* aPort,
 | ||
|                JS::Handle<JS::Value> aChunk) -> already_AddRefed<Promise> {
 | ||
|               // Step 2.1: Set backpressurePromise to a new promise.
 | ||
|               aListener->CreateBackpressurePromise();
 | ||
| 
 | ||
|               // Step 2.2: Let result be PackAndPostMessageHandlingError(port,
 | ||
|               // "chunk", chunk).
 | ||
|               JS::Rooted<JS::Value> error(aCx);
 | ||
|               bool result = PackAndPostMessageHandlingError(
 | ||
|                   aCx, aPort, u"chunk"_ns, aChunk, &error);
 | ||
| 
 | ||
|               // Step 2.3: If result is an abrupt completion,
 | ||
|               if (!result) {
 | ||
|                 // Step 2.3.1: Disentangle port.
 | ||
|                 // (Close() does it)
 | ||
|                 aPort->Close();
 | ||
| 
 | ||
|                 // Step 2.3.2: Return a promise rejected with result.[[Value]].
 | ||
|                 return Promise::CreateRejected(aPort->GetParentObject(), error,
 | ||
|                                                aRv);
 | ||
|               }
 | ||
| 
 | ||
|               // Step 2.4: Otherwise, return a promise resolved with undefined.
 | ||
|               return Promise::CreateResolvedWithUndefined(
 | ||
|                   aPort->GetParentObject(), aRv);
 | ||
|             },
 | ||
|             std::make_tuple(mListener, mPort), std::make_tuple(aChunk));
 | ||
|     if (result.isErr()) {
 | ||
|       aRv.Throw(result.unwrapErr());
 | ||
|       return nullptr;
 | ||
|     }
 | ||
|     return result.unwrap().forget();
 | ||
|   }
 | ||
| 
 | ||
|   already_AddRefed<Promise> CloseCallback(JSContext* aCx,
 | ||
|                                           ErrorResult& aRv) override {
 | ||
|     // Step 1: Perform ! PackAndPostMessage(port, "close", undefined).
 | ||
|     PackAndPostMessage(aCx, mPort, u"close"_ns, JS::UndefinedHandleValue, aRv);
 | ||
|     // (We'll check the result after step 2)
 | ||
| 
 | ||
|     // Step 2: Disentangle port.
 | ||
|     // (Close() will do this)
 | ||
|     mPort->Close();
 | ||
| 
 | ||
|     if (aRv.Failed()) {
 | ||
|       return nullptr;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 3: Return a promise resolved with undefined.
 | ||
|     return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
 | ||
|   }
 | ||
| 
 | ||
|   already_AddRefed<Promise> AbortCallback(
 | ||
|       JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
 | ||
|       ErrorResult& aRv) override {
 | ||
|     // Step 1: Let result be PackAndPostMessageHandlingError(port, "error",
 | ||
|     // reason).
 | ||
|     JS::Rooted<JS::Value> error(aCx);
 | ||
|     bool result = PackAndPostMessageHandlingError(
 | ||
|         aCx, mPort, u"error"_ns,
 | ||
|         aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue,
 | ||
|         &error);
 | ||
| 
 | ||
|     // Step 2: Disentangle port.
 | ||
|     // (Close() will do this)
 | ||
|     mPort->Close();
 | ||
| 
 | ||
|     // Step 3: If result is an abrupt completion, return a promise rejected with
 | ||
|     // result.[[Value]].
 | ||
|     if (!result) {
 | ||
|       return Promise::CreateRejected(mPort->GetParentObject(), error, aRv);
 | ||
|     }
 | ||
| 
 | ||
|     // Step 4: Otherwise, return a promise resolved with undefined.
 | ||
|     return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
 | ||
|   }
 | ||
| 
 | ||
|  protected:
 | ||
|   ~CrossRealmWritableUnderlyingSinkAlgorithms() override = default;
 | ||
| 
 | ||
|  private:
 | ||
|   RefPtr<SetUpTransformWritableMessageEventListener> mListener;
 | ||
|   RefPtr<MessagePort> mPort;
 | ||
| };
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms,
 | ||
|                                    UnderlyingSinkAlgorithmsBase, mListener,
 | ||
|                                    mPort)
 | ||
| NS_IMPL_ADDREF_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms,
 | ||
|                          UnderlyingSinkAlgorithmsBase)
 | ||
| NS_IMPL_RELEASE_INHERITED(CrossRealmWritableUnderlyingSinkAlgorithms,
 | ||
|                           UnderlyingSinkAlgorithmsBase)
 | ||
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 | ||
|     CrossRealmWritableUnderlyingSinkAlgorithms)
 | ||
| NS_INTERFACE_MAP_END_INHERITING(UnderlyingSinkAlgorithmsBase)
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable
 | ||
| MOZ_CAN_RUN_SCRIPT static void SetUpCrossRealmTransformWritable(
 | ||
|     WritableStream* aWritable, MessagePort* aPort, ErrorResult& aRv) {
 | ||
|   // (This is only needed for step 12, but let's do this early to fail early
 | ||
|   // enough)
 | ||
|   AutoJSAPI jsapi;
 | ||
|   if (!jsapi.Init(aWritable->GetParentObject())) {
 | ||
|     return;
 | ||
|   }
 | ||
|   JSContext* cx = jsapi.cx();
 | ||
| 
 | ||
|   // Step 1: Perform ! InitializeWritableStream(stream).
 | ||
|   // (Done by the constructor)
 | ||
| 
 | ||
|   // Step 2: Let controller be a new WritableStreamDefaultController.
 | ||
|   auto controller = MakeRefPtr<WritableStreamDefaultController>(
 | ||
|       aWritable->GetParentObject(), *aWritable);
 | ||
| 
 | ||
|   // Step 3: Let backpressurePromise be a new promise.
 | ||
|   RefPtr<Promise> backpressurePromise =
 | ||
|       Promise::CreateInfallible(aWritable->GetParentObject());
 | ||
| 
 | ||
|   // Step 4: Add a handler for port’s message event with the following steps:
 | ||
|   auto listener = MakeRefPtr<SetUpTransformWritableMessageEventListener>(
 | ||
|       controller, backpressurePromise);
 | ||
|   aPort->AddEventListener(u"message"_ns, listener, false);
 | ||
| 
 | ||
|   // Step 5: Add a handler for port’s messageerror event with the following
 | ||
|   // steps:
 | ||
|   auto errorListener =
 | ||
|       MakeRefPtr<SetUpTransformWritableMessageErrorEventListener>(controller,
 | ||
|                                                                   aPort);
 | ||
|   aPort->AddEventListener(u"messageerror"_ns, errorListener, false);
 | ||
| 
 | ||
|   // Step 6: Enable port’s port message queue.
 | ||
|   // (Start() does it)
 | ||
|   aPort->Start();
 | ||
| 
 | ||
|   // Step 7 - 10:
 | ||
|   auto algorithms =
 | ||
|       MakeRefPtr<CrossRealmWritableUnderlyingSinkAlgorithms>(listener, aPort);
 | ||
| 
 | ||
|   // Step 11: Let sizeAlgorithm be an algorithm that returns 1.
 | ||
|   // (nullptr should serve this purpose. See also WritableStream::Constructor)
 | ||
| 
 | ||
|   // Step 12: Perform ! SetUpWritableStreamDefaultController(stream, controller,
 | ||
|   // startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, 1,
 | ||
|   // sizeAlgorithm).
 | ||
|   SetUpWritableStreamDefaultController(cx, aWritable, controller, algorithms, 1,
 | ||
|                                        /* aSizeAlgorithm */ nullptr, aRv);
 | ||
| }
 | ||
| 
 | ||
| class SetUpTransformReadableMessageEventListener final
 | ||
|     : public nsIDOMEventListener {
 | ||
|  public:
 | ||
|   SetUpTransformReadableMessageEventListener(
 | ||
|       ReadableStreamDefaultController* aController, MessagePort* aPort)
 | ||
|       : mController(aController), mPort(aPort) {}
 | ||
| 
 | ||
|   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | ||
|   NS_DECL_CYCLE_COLLECTION_CLASS(SetUpTransformReadableMessageEventListener)
 | ||
| 
 | ||
|   // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
 | ||
|   // The handler steps of Step 3.
 | ||
|   MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
 | ||
|     auto cleanupPort =
 | ||
|         MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
 | ||
| 
 | ||
|     AutoJSAPI jsapi;
 | ||
|     if (!jsapi.Init(mPort->GetParentObject())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     JSContext* cx = jsapi.cx();
 | ||
|     MessageEvent* messageEvent = aEvent->AsMessageEvent();
 | ||
|     if (NS_WARN_IF(!messageEvent || !messageEvent->IsTrusted())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 1: Let data be the data of the message.
 | ||
|     JS::Rooted<JS::Value> dataValue(cx);
 | ||
|     IgnoredErrorResult rv;
 | ||
|     messageEvent->GetData(cx, &dataValue, rv);
 | ||
|     if (rv.Failed()) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 2: Assert: Type(data) is Object.
 | ||
|     // (But we check in runtime instead to avoid potential malicious events from
 | ||
|     // a compromised process. Same below.)
 | ||
|     if (NS_WARN_IF(!dataValue.isObject())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     JS::Rooted<JSObject*> data(cx, JS::ToObject(cx, dataValue));
 | ||
| 
 | ||
|     // Step 3: Let type be ! Get(data, "type").
 | ||
|     JS::Rooted<JS::Value> type(cx);
 | ||
|     if (!JS_GetProperty(cx, data, "type", &type)) {
 | ||
|       // XXX: See bug 1762233
 | ||
|       JS_ClearPendingException(cx);
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 4: Let value be ! Get(data, "value").
 | ||
|     JS::Rooted<JS::Value> value(cx);
 | ||
|     if (!JS_GetProperty(cx, data, "value", &value)) {
 | ||
|       JS_ClearPendingException(cx);
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 5: Assert: Type(type) is String.
 | ||
|     if (NS_WARN_IF(!type.isString())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 6: If type is "chunk",
 | ||
|     bool equals = false;
 | ||
|     if (!JS_StringEqualsLiteral(cx, type.toString(), "chunk", &equals)) {
 | ||
|       JS_ClearPendingException(cx);
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     if (equals) {
 | ||
|       // Step 6.1: Perform ! ReadableStreamDefaultControllerEnqueue(controller,
 | ||
|       // value).
 | ||
|       ReadableStreamDefaultControllerEnqueue(cx, mController, value,
 | ||
|                                              IgnoreErrors());
 | ||
|       cleanupPort.release();
 | ||
|       return NS_OK;  // implicit
 | ||
|     }
 | ||
| 
 | ||
|     // Step 7: Otherwise, if type is "close",
 | ||
|     if (!JS_StringEqualsLiteral(cx, type.toString(), "close", &equals)) {
 | ||
|       JS_ClearPendingException(cx);
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     if (equals) {
 | ||
|       // Step 7.1: Perform ! ReadableStreamDefaultControllerClose(controller).
 | ||
|       ReadableStreamDefaultControllerClose(cx, mController, IgnoreErrors());
 | ||
|       // Step 7.2: Disentangle port.
 | ||
|       // (Close() does it)
 | ||
|       mPort->Close();
 | ||
|       cleanupPort.release();
 | ||
|       return NS_OK;  // implicit
 | ||
|     }
 | ||
| 
 | ||
|     // Step 8: Otherwise, if type is "error",
 | ||
|     if (!JS_StringEqualsLiteral(cx, type.toString(), "error", &equals)) {
 | ||
|       JS_ClearPendingException(cx);
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     if (equals) {
 | ||
|       // Step 8.1: Perform ! ReadableStreamDefaultControllerError(controller,
 | ||
|       // value).
 | ||
|       ReadableStreamDefaultControllerError(cx, mController, value,
 | ||
|                                            IgnoreErrors());
 | ||
| 
 | ||
|       // Step 8.2: Disentangle port.
 | ||
|       // (Close() does it)
 | ||
|       mPort->Close();
 | ||
|       cleanupPort.release();
 | ||
|       return NS_OK;  // implicit
 | ||
|     }
 | ||
| 
 | ||
|     // Logically it should be unreachable here, but we should expect random
 | ||
|     // malicious messages.
 | ||
|     NS_WARNING("Got an unexpected type other than chunk/close/error.");
 | ||
|     return NS_OK;
 | ||
|   }
 | ||
| 
 | ||
|  private:
 | ||
|   ~SetUpTransformReadableMessageEventListener() = default;
 | ||
| 
 | ||
|   // mController never changes before CC
 | ||
|   // TODO: MOZ_IMMUTABLE_OUTSIDE_CC
 | ||
|   MOZ_KNOWN_LIVE RefPtr<ReadableStreamDefaultController> mController;
 | ||
|   RefPtr<MessagePort> mPort;
 | ||
| };
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageEventListener,
 | ||
|                          mController, mPort)
 | ||
| NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformReadableMessageEventListener)
 | ||
| NS_IMPL_CYCLE_COLLECTING_RELEASE(SetUpTransformReadableMessageEventListener)
 | ||
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 | ||
|     SetUpTransformReadableMessageEventListener)
 | ||
|   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
 | ||
| NS_INTERFACE_MAP_END
 | ||
| 
 | ||
| class SetUpTransformReadableMessageErrorEventListener final
 | ||
|     : public nsIDOMEventListener {
 | ||
|  public:
 | ||
|   SetUpTransformReadableMessageErrorEventListener(
 | ||
|       ReadableStreamDefaultController* aController, MessagePort* aPort)
 | ||
|       : mController(aController), mPort(aPort) {}
 | ||
| 
 | ||
|   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | ||
|   NS_DECL_CYCLE_COLLECTION_CLASS(
 | ||
|       SetUpTransformReadableMessageErrorEventListener)
 | ||
| 
 | ||
|   // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
 | ||
|   // The handler steps of Step 4.
 | ||
|   MOZ_CAN_RUN_SCRIPT NS_IMETHOD HandleEvent(Event* aEvent) override {
 | ||
|     auto cleanupPort =
 | ||
|         MakeScopeExit([port = RefPtr<MessagePort>(mPort)]() { port->Close(); });
 | ||
| 
 | ||
|     if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 1: Let error be a new "DataCloneError" DOMException.
 | ||
|     RefPtr<DOMException> exception =
 | ||
|         DOMException::Create(NS_ERROR_DOM_DATA_CLONE_ERR);
 | ||
| 
 | ||
|     AutoJSAPI jsapi;
 | ||
|     if (!jsapi.Init(mPort->GetParentObject())) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
|     JSContext* cx = jsapi.cx();
 | ||
|     JS::Rooted<JS::Value> error(cx);
 | ||
|     if (!ToJSValue(cx, *exception, &error)) {
 | ||
|       return NS_OK;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 2: Perform ! CrossRealmTransformSendError(port, error).
 | ||
|     CrossRealmTransformSendError(cx, mPort, error);
 | ||
| 
 | ||
|     // Step 3: Perform ! ReadableStreamDefaultControllerError(controller,
 | ||
|     // error).
 | ||
|     ReadableStreamDefaultControllerError(cx, mController, error,
 | ||
|                                          IgnoreErrors());
 | ||
| 
 | ||
|     // Step 4: Disentangle port.
 | ||
|     // (Close() does it)
 | ||
|     mPort->Close();
 | ||
|     cleanupPort.release();
 | ||
| 
 | ||
|     return NS_OK;
 | ||
|   }
 | ||
| 
 | ||
|  private:
 | ||
|   ~SetUpTransformReadableMessageErrorEventListener() = default;
 | ||
| 
 | ||
|   RefPtr<ReadableStreamDefaultController> mController;
 | ||
|   RefPtr<MessagePort> mPort;
 | ||
| };
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION(SetUpTransformReadableMessageErrorEventListener,
 | ||
|                          mController, mPort)
 | ||
| NS_IMPL_CYCLE_COLLECTING_ADDREF(SetUpTransformReadableMessageErrorEventListener)
 | ||
| NS_IMPL_CYCLE_COLLECTING_RELEASE(
 | ||
|     SetUpTransformReadableMessageErrorEventListener)
 | ||
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 | ||
|     SetUpTransformReadableMessageErrorEventListener)
 | ||
|   NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
 | ||
| NS_INTERFACE_MAP_END
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
 | ||
| class CrossRealmReadableUnderlyingSourceAlgorithms final
 | ||
|     : public UnderlyingSourceAlgorithmsBase {
 | ||
|  public:
 | ||
|   NS_DECL_ISUPPORTS_INHERITED
 | ||
|   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
 | ||
|       CrossRealmReadableUnderlyingSourceAlgorithms,
 | ||
|       UnderlyingSourceAlgorithmsBase)
 | ||
| 
 | ||
|   explicit CrossRealmReadableUnderlyingSourceAlgorithms(MessagePort* aPort)
 | ||
|       : mPort(aPort) {}
 | ||
| 
 | ||
|   void StartCallback(JSContext* aCx, ReadableStreamController& aController,
 | ||
|                      JS::MutableHandle<JS::Value> aRetVal,
 | ||
|                      ErrorResult& aRv) override {
 | ||
|     // Step 6. Let startAlgorithm be an algorithm that returns undefined.
 | ||
|     aRetVal.setUndefined();
 | ||
|   }
 | ||
| 
 | ||
|   already_AddRefed<Promise> PullCallback(JSContext* aCx,
 | ||
|                                          ReadableStreamController& aController,
 | ||
|                                          ErrorResult& aRv) override {
 | ||
|     // Step 7: Let pullAlgorithm be the following steps:
 | ||
| 
 | ||
|     // Step 7.1: Perform ! PackAndPostMessage(port, "pull", undefined).
 | ||
|     PackAndPostMessage(aCx, mPort, u"pull"_ns, JS::UndefinedHandleValue, aRv);
 | ||
|     if (aRv.Failed()) {
 | ||
|       return nullptr;
 | ||
|     }
 | ||
| 
 | ||
|     // Step 7.2: Return a promise resolved with undefined.
 | ||
|     return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
 | ||
|   }
 | ||
| 
 | ||
|   already_AddRefed<Promise> CancelCallback(
 | ||
|       JSContext* aCx, const Optional<JS::Handle<JS::Value>>& aReason,
 | ||
|       ErrorResult& aRv) override {
 | ||
|     // Step 8: Let cancelAlgorithm be the following steps, taking a reason
 | ||
|     // argument:
 | ||
| 
 | ||
|     // Step 8.1: Let result be PackAndPostMessageHandlingError(port, "error",
 | ||
|     // reason).
 | ||
|     JS::Rooted<JS::Value> error(aCx);
 | ||
|     bool result = PackAndPostMessageHandlingError(
 | ||
|         aCx, mPort, u"error"_ns,
 | ||
|         aReason.WasPassed() ? aReason.Value() : JS::UndefinedHandleValue,
 | ||
|         &error);
 | ||
| 
 | ||
|     // Step 8.2: Disentangle port.
 | ||
|     // (Close() does it)
 | ||
|     mPort->Close();
 | ||
| 
 | ||
|     // Step 8.3: If result is an abrupt completion, return a promise rejected
 | ||
|     // with result.[[Value]].
 | ||
|     if (!result) {
 | ||
|       return Promise::CreateRejected(mPort->GetParentObject(), error, aRv);
 | ||
|     }
 | ||
| 
 | ||
|     // Step 8.4: Otherwise, return a promise resolved with undefined.
 | ||
|     return Promise::CreateResolvedWithUndefined(mPort->GetParentObject(), aRv);
 | ||
|   }
 | ||
| 
 | ||
|  protected:
 | ||
|   ~CrossRealmReadableUnderlyingSourceAlgorithms() override = default;
 | ||
| 
 | ||
|  private:
 | ||
|   RefPtr<MessagePort> mPort;
 | ||
| };
 | ||
| 
 | ||
| NS_IMPL_CYCLE_COLLECTION_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms,
 | ||
|                                    UnderlyingSourceAlgorithmsBase, mPort)
 | ||
| NS_IMPL_ADDREF_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms,
 | ||
|                          UnderlyingSourceAlgorithmsBase)
 | ||
| NS_IMPL_RELEASE_INHERITED(CrossRealmReadableUnderlyingSourceAlgorithms,
 | ||
|                           UnderlyingSourceAlgorithmsBase)
 | ||
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 | ||
|     CrossRealmReadableUnderlyingSourceAlgorithms)
 | ||
| NS_INTERFACE_MAP_END_INHERITING(UnderlyingSourceAlgorithmsBase)
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable
 | ||
| MOZ_CAN_RUN_SCRIPT static void SetUpCrossRealmTransformReadable(
 | ||
|     ReadableStream* aReadable, MessagePort* aPort, ErrorResult& aRv) {
 | ||
|   // (This is only needed for step 10, but let's do this early to fail early
 | ||
|   // enough)
 | ||
|   AutoJSAPI jsapi;
 | ||
|   if (!jsapi.Init(aReadable->GetParentObject())) {
 | ||
|     return;
 | ||
|   }
 | ||
|   JSContext* cx = jsapi.cx();
 | ||
| 
 | ||
|   // Step 1: Perform ! InitializeReadableStream(stream).
 | ||
|   // (This is implicitly done by the constructor)
 | ||
| 
 | ||
|   // Step 2: Let controller be a new ReadableStreamDefaultController.
 | ||
|   auto controller =
 | ||
|       MakeRefPtr<ReadableStreamDefaultController>(aReadable->GetParentObject());
 | ||
| 
 | ||
|   // Step 3: Add a handler for port’s message event with the following steps:
 | ||
|   auto listener =
 | ||
|       MakeRefPtr<SetUpTransformReadableMessageEventListener>(controller, aPort);
 | ||
|   aPort->AddEventListener(u"message"_ns, listener, false);
 | ||
| 
 | ||
|   // Step 4: Add a handler for port’s messageerror event with the following
 | ||
|   // steps:
 | ||
|   auto errorListener =
 | ||
|       MakeRefPtr<SetUpTransformReadableMessageErrorEventListener>(controller,
 | ||
|                                                                   aPort);
 | ||
|   aPort->AddEventListener(u"messageerror"_ns, errorListener, false);
 | ||
| 
 | ||
|   // Step 5: Enable port’s port message queue.
 | ||
|   // (Start() does it)
 | ||
|   aPort->Start();
 | ||
| 
 | ||
|   // Step 6-8:
 | ||
|   auto algorithms =
 | ||
|       MakeRefPtr<CrossRealmReadableUnderlyingSourceAlgorithms>(aPort);
 | ||
| 
 | ||
|   // Step 9: Let sizeAlgorithm be an algorithm that returns 1.
 | ||
|   // (nullptr should serve this purpose. See also ReadableStream::Constructor)
 | ||
| 
 | ||
|   // Step 10: Perform ! SetUpReadableStreamDefaultController(stream, controller,
 | ||
|   // startAlgorithm, pullAlgorithm, cancelAlgorithm, 0, sizeAlgorithm).
 | ||
|   SetUpReadableStreamDefaultController(cx, aReadable, controller, algorithms, 0,
 | ||
|                                        /* aSizeAlgorithm */ nullptr, aRv);
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#ref-for-transfer-steps
 | ||
| bool ReadableStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId) {
 | ||
|   // Step 1: If ! IsReadableStreamLocked(value) is true, throw a
 | ||
|   // "DataCloneError" DOMException.
 | ||
|   // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
 | ||
|   // check here as the state might have changed in case this ReadableStream is
 | ||
|   // created by a TransferStream and being transferred together with the
 | ||
|   // parent.)
 | ||
|   if (IsReadableStreamLocked(this)) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 2: Let port1 be a new MessagePort in the current Realm.
 | ||
|   // Step 3: Let port2 be a new MessagePort in the current Realm.
 | ||
|   // Step 4: Entangle port1 and port2.
 | ||
|   // (The MessageChannel constructor does exactly that.)
 | ||
|   // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messagechannel
 | ||
|   ErrorResult rv;
 | ||
|   RefPtr<dom::MessageChannel> channel =
 | ||
|       dom::MessageChannel::Constructor(mGlobal, rv);
 | ||
|   if (rv.MaybeSetPendingException(aCx)) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 5: Let writable be a new WritableStream in the current Realm.
 | ||
|   RefPtr<WritableStream> writable = new WritableStream(
 | ||
|       mGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit);
 | ||
| 
 | ||
|   // Step 6: Perform ! SetUpCrossRealmTransformWritable(writable, port1).
 | ||
|   // MOZ_KnownLive because Port1 never changes before CC
 | ||
|   SetUpCrossRealmTransformWritable(writable, MOZ_KnownLive(channel->Port1()),
 | ||
|                                    rv);
 | ||
|   if (rv.MaybeSetPendingException(aCx)) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 7: Let promise be ! ReadableStreamPipeTo(value, writable, false,
 | ||
|   // false, false).
 | ||
|   RefPtr<Promise> promise =
 | ||
|       ReadableStreamPipeTo(this, writable, false, false, false, nullptr, rv);
 | ||
|   if (rv.MaybeSetPendingException(aCx)) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 8: Set promise.[[PromiseIsHandled]] to true.
 | ||
|   MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
 | ||
| 
 | ||
|   // Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
 | ||
|   // « port2 »).
 | ||
|   channel->Port2()->CloneAndDisentangle(aPortId);
 | ||
| 
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps
 | ||
| MOZ_CAN_RUN_SCRIPT already_AddRefed<ReadableStream>
 | ||
| ReadableStream::ReceiveTransferImpl(JSContext* aCx, nsIGlobalObject* aGlobal,
 | ||
|                                     MessagePort& aPort) {
 | ||
|   // Step 1: Let deserializedRecord be
 | ||
|   // ! StructuredDeserializeWithTransfer(dataHolder.[[port]], the current
 | ||
|   // Realm).
 | ||
|   // Step 2: Let port be deserializedRecord.[[Deserialized]].
 | ||
| 
 | ||
|   // Step 3: Perform ! SetUpCrossRealmTransformReadable(value, port).
 | ||
|   RefPtr<ReadableStream> readable =
 | ||
|       new ReadableStream(aGlobal, HoldDropJSObjectsCaller::Implicit);
 | ||
|   ErrorResult rv;
 | ||
|   SetUpCrossRealmTransformReadable(readable, &aPort, rv);
 | ||
|   if (rv.MaybeSetPendingException(aCx)) {
 | ||
|     return nullptr;
 | ||
|   }
 | ||
|   return readable.forget();
 | ||
| }
 | ||
| 
 | ||
| bool ReadableStream::ReceiveTransfer(
 | ||
|     JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort,
 | ||
|     JS::MutableHandle<JSObject*> aReturnObject) {
 | ||
|   RefPtr<ReadableStream> readable =
 | ||
|       ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort);
 | ||
|   if (!readable) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   JS::Rooted<JS::Value> value(aCx);
 | ||
|   if (!GetOrCreateDOMReflector(aCx, readable, &value)) {
 | ||
|     JS_ClearPendingException(aCx);
 | ||
|     return false;
 | ||
|   }
 | ||
|   aReturnObject.set(&value.toObject());
 | ||
| 
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#ref-for-transfer-steps①
 | ||
| bool WritableStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId) {
 | ||
|   // Step 1: If ! IsWritableStreamLocked(value) is true, throw a
 | ||
|   // "DataCloneError" DOMException.
 | ||
|   // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
 | ||
|   // check here as the state might have changed in case this WritableStream is
 | ||
|   // created by a TransferStream and being transferred together with the
 | ||
|   // parent.)
 | ||
|   if (IsWritableStreamLocked(this)) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 2: Let port1 be a new MessagePort in the current Realm.
 | ||
|   // Step 3: Let port2 be a new MessagePort in the current Realm.
 | ||
|   // Step 4: Entangle port1 and port2.
 | ||
|   // (The MessageChannel constructor does exactly that.)
 | ||
|   // https://html.spec.whatwg.org/multipage/web-messaging.html#dom-messagechannel
 | ||
|   ErrorResult rv;
 | ||
|   RefPtr<dom::MessageChannel> channel =
 | ||
|       dom::MessageChannel::Constructor(mGlobal, rv);
 | ||
|   if (rv.MaybeSetPendingException(aCx)) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 5: Let readable be a new ReadableStream in the current Realm.
 | ||
|   RefPtr<ReadableStream> readable = new ReadableStream(
 | ||
|       mGlobal, ReadableStream::HoldDropJSObjectsCaller::Implicit);
 | ||
| 
 | ||
|   // Step 6: Perform ! SetUpCrossRealmTransformReadable(readable, port1).
 | ||
|   // MOZ_KnownLive because Port1 never changes before CC
 | ||
|   SetUpCrossRealmTransformReadable(readable, MOZ_KnownLive(channel->Port1()),
 | ||
|                                    rv);
 | ||
|   if (rv.MaybeSetPendingException(aCx)) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 7: Let promise be ! ReadableStreamPipeTo(readable, value, false,
 | ||
|   // false, false).
 | ||
|   RefPtr<Promise> promise =
 | ||
|       ReadableStreamPipeTo(readable, this, false, false, false, nullptr, rv);
 | ||
|   if (rv.Failed()) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 8: Set promise.[[PromiseIsHandled]] to true.
 | ||
|   MOZ_ALWAYS_TRUE(promise->SetAnyPromiseIsHandled());
 | ||
| 
 | ||
|   // Step 9: Set dataHolder.[[port]] to ! StructuredSerializeWithTransfer(port2,
 | ||
|   // « port2 »).
 | ||
|   channel->Port2()->CloneAndDisentangle(aPortId);
 | ||
| 
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps①
 | ||
| MOZ_CAN_RUN_SCRIPT already_AddRefed<WritableStream>
 | ||
| WritableStream::ReceiveTransferImpl(JSContext* aCx, nsIGlobalObject* aGlobal,
 | ||
|                                     MessagePort& aPort) {
 | ||
|   // Step 1: Let deserializedRecord be !
 | ||
|   // StructuredDeserializeWithTransfer(dataHolder.[[port]], the current Realm).
 | ||
|   // Step 2: Let port be a deserializedRecord.[[Deserialized]].
 | ||
| 
 | ||
|   // Step 3: Perform ! SetUpCrossRealmTransformWritable(value, port).
 | ||
|   RefPtr<WritableStream> writable = new WritableStream(
 | ||
|       aGlobal, WritableStream::HoldDropJSObjectsCaller::Implicit);
 | ||
|   ErrorResult rv;
 | ||
|   SetUpCrossRealmTransformWritable(writable, &aPort, rv);
 | ||
|   if (rv.MaybeSetPendingException(aCx)) {
 | ||
|     return nullptr;
 | ||
|   }
 | ||
|   return writable.forget();
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps①
 | ||
| bool WritableStream::ReceiveTransfer(
 | ||
|     JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort,
 | ||
|     JS::MutableHandle<JSObject*> aReturnObject) {
 | ||
|   RefPtr<WritableStream> writable =
 | ||
|       WritableStream::ReceiveTransferImpl(aCx, aGlobal, aPort);
 | ||
|   if (!writable) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   JS::Rooted<JS::Value> value(aCx);
 | ||
|   if (!GetOrCreateDOMReflector(aCx, writable, &value)) {
 | ||
|     JS_ClearPendingException(aCx);
 | ||
|     return false;
 | ||
|   }
 | ||
|   aReturnObject.set(&value.toObject());
 | ||
| 
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#ref-for-transfer-steps②
 | ||
| bool TransformStream::Transfer(JSContext* aCx, UniqueMessagePortId& aPortId1,
 | ||
|                                UniqueMessagePortId& aPortId2) {
 | ||
|   // Step 1: Let readable be value.[[readable]].
 | ||
|   // Step 2: Let writable be value.[[writable]].
 | ||
|   // Step 3: If ! IsReadableStreamLocked(readable) is true, throw a
 | ||
|   // "DataCloneError" DOMException.
 | ||
|   // Step 4: If ! IsWritableStreamLocked(writable) is true, throw a
 | ||
|   // "DataCloneError" DOMException.
 | ||
|   // (Implemented in StructuredCloneHolder::CustomCanTransferHandler, but double
 | ||
|   // check here as the state might have changed by
 | ||
|   // Readable/WritableStream::Transfer in case the stream members of this
 | ||
|   // TransformStream are being transferred together.)
 | ||
|   if (IsReadableStreamLocked(mReadable) || IsWritableStreamLocked(mWritable)) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 5: Set dataHolder.[[readable]] to !
 | ||
|   // StructuredSerializeWithTransfer(readable, « readable »).
 | ||
|   // TODO: Mark mReadable as MOZ_KNOWN_LIVE again (bug 1769854)
 | ||
|   if (!MOZ_KnownLive(mReadable)->Transfer(aCx, aPortId1)) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 6: Set dataHolder.[[writable]] to !
 | ||
|   // StructuredSerializeWithTransfer(writable, « writable »).
 | ||
|   // TODO: Mark mReadable as MOZ_KNOWN_LIVE again (bug 1769854)
 | ||
|   return MOZ_KnownLive(mWritable)->Transfer(aCx, aPortId2);
 | ||
| }
 | ||
| 
 | ||
| // https://streams.spec.whatwg.org/#ref-for-transfer-receiving-steps②
 | ||
| bool TransformStream::ReceiveTransfer(
 | ||
|     JSContext* aCx, nsIGlobalObject* aGlobal, MessagePort& aPort1,
 | ||
|     MessagePort& aPort2, JS::MutableHandle<JSObject*> aReturnObject) {
 | ||
|   // Step 1: Let readableRecord be !
 | ||
|   // StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current
 | ||
|   // Realm).
 | ||
|   RefPtr<ReadableStream> readable =
 | ||
|       ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort1);
 | ||
|   if (!readable) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 2: Let writableRecord be !
 | ||
|   // StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current
 | ||
|   // Realm).
 | ||
|   RefPtr<WritableStream> writable =
 | ||
|       WritableStream::ReceiveTransferImpl(aCx, aGlobal, aPort2);
 | ||
|   if (!writable) {
 | ||
|     return false;
 | ||
|   }
 | ||
| 
 | ||
|   // Step 3: Set value.[[readable]] to readableRecord.[[Deserialized]].
 | ||
|   // Step 4: Set value.[[writable]] to writableRecord.[[Deserialized]].
 | ||
|   // Step 5: Set value.[[backpressure]], value.[[backpressureChangePromise]],
 | ||
|   // and value.[[controller]] to undefined.
 | ||
|   RefPtr<TransformStream> stream =
 | ||
|       new TransformStream(aGlobal, readable, writable);
 | ||
|   JS::Rooted<JS::Value> value(aCx);
 | ||
|   if (!GetOrCreateDOMReflector(aCx, stream, &value)) {
 | ||
|     JS_ClearPendingException(aCx);
 | ||
|     return false;
 | ||
|   }
 | ||
|   aReturnObject.set(&value.toObject());
 | ||
| 
 | ||
|   return true;
 | ||
| }
 | ||
| 
 | ||
| }  // namespace mozilla::dom
 |