/* -*- 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 aValue, ErrorResult& aRv) { JS::Rooted 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 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 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 transferables; // none in this case JS::Rooted 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 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 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 data(cx, &dataValue.toObject()); // Step 3: Let type be ! Get(data, "type"). JS::Rooted 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 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 mController; RefPtr 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(mPort)]() { port->Close(); }); if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) { return NS_OK; } // Step 1: Let error be a new "DataCloneError" DOMException. RefPtr 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 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 mController; RefPtr 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 aValue, JS::MutableHandle 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 aRetVal, ErrorResult& aRv) override { // Step 7. Let startAlgorithm be an algorithm that returns undefined. aRetVal.setUndefined(); } already_AddRefed WriteCallback( JSContext* aCx, JS::Handle 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, ErrorResult& aRv, SetUpTransformWritableMessageEventListener* aListener, MessagePort* aPort, JS::Handle aChunk) -> already_AddRefed { // Step 2.1: Set backpressurePromise to a new promise. aListener->CreateBackpressurePromise(); // Step 2.2: Let result be PackAndPostMessageHandlingError(port, // "chunk", chunk). JS::Rooted 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 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 AbortCallback( JSContext* aCx, const Optional>& aReason, ErrorResult& aRv) override { // Step 1: Let result be PackAndPostMessageHandlingError(port, "error", // reason). JS::Rooted 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 mListener; RefPtr 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( aWritable->GetParentObject(), *aWritable); // Step 3: Let backpressurePromise be a new promise. RefPtr backpressurePromise = Promise::CreateInfallible(aWritable->GetParentObject()); // Step 4: Add a handler for port’s message event with the following steps: auto listener = MakeRefPtr( 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(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(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(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 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 data(cx, JS::ToObject(cx, dataValue)); // Step 3: Let type be ! Get(data, "type"). JS::Rooted 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 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 mController; RefPtr 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(mPort)]() { port->Close(); }); if (NS_WARN_IF(!aEvent->AsMessageEvent() || !aEvent->IsTrusted())) { return NS_OK; } // Step 1: Let error be a new "DataCloneError" DOMException. RefPtr 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 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 mController; RefPtr 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 aRetVal, ErrorResult& aRv) override { // Step 6. Let startAlgorithm be an algorithm that returns undefined. aRetVal.setUndefined(); } already_AddRefed 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 CancelCallback( JSContext* aCx, const Optional>& 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 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 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(aReadable->GetParentObject()); // Step 3: Add a handler for port’s message event with the following steps: auto listener = MakeRefPtr(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(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(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 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 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 = 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::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 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 aReturnObject) { RefPtr readable = ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort); if (!readable) { return false; } JS::Rooted 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 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 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 = 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::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 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 aReturnObject) { RefPtr writable = WritableStream::ReceiveTransferImpl(aCx, aGlobal, aPort); if (!writable) { return false; } JS::Rooted 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 aReturnObject) { // Step 1: Let readableRecord be ! // StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current // Realm). RefPtr readable = ReadableStream::ReceiveTransferImpl(aCx, aGlobal, aPort1); if (!readable) { return false; } // Step 2: Let writableRecord be ! // StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current // Realm). RefPtr 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 stream = new TransformStream(aGlobal, readable, writable); JS::Rooted value(aCx); if (!GetOrCreateDOMReflector(aCx, stream, &value)) { JS_ClearPendingException(aCx); return false; } aReturnObject.set(&value.toObject()); return true; } } // namespace mozilla::dom