summaryrefslogtreecommitdiffstats
path: root/dom/messagechannel/MessagePort.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/messagechannel/MessagePort.cpp')
-rw-r--r--dom/messagechannel/MessagePort.cpp870
1 files changed, 870 insertions, 0 deletions
diff --git a/dom/messagechannel/MessagePort.cpp b/dom/messagechannel/MessagePort.cpp
new file mode 100644
index 0000000000..07cf42bd1f
--- /dev/null
+++ b/dom/messagechannel/MessagePort.cpp
@@ -0,0 +1,870 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "MessagePort.h"
+
+#include "MessageEvent.h"
+#include "MessagePortChild.h"
+#include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MessageChannel.h"
+#include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/MessagePortBinding.h"
+#include "mozilla/dom/MessagePortChild.h"
+#include "mozilla/dom/PMessagePort.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/dom/RefMessageBodyService.h"
+#include "mozilla/dom/SharedMessageBody.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/MessagePortTimelineMarker.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/TimelineMarker.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsPresContext.h"
+
+#ifdef XP_WIN
+# undef PostMessage
+#endif
+
+namespace mozilla::dom {
+
+void UniqueMessagePortId::ForceClose() {
+ if (!mIdentifier.neutered()) {
+ MessagePort::ForceClose(mIdentifier);
+ mIdentifier.neutered() = true;
+ }
+}
+
+class PostMessageRunnable final : public CancelableRunnable {
+ friend class MessagePort;
+
+ public:
+ PostMessageRunnable(MessagePort* aPort, SharedMessageBody* aData)
+ : CancelableRunnable("dom::PostMessageRunnable"),
+ mPort(aPort),
+ mData(aData) {
+ MOZ_ASSERT(aPort);
+ MOZ_ASSERT(aData);
+ }
+
+ NS_IMETHOD
+ Run() override {
+ NS_ASSERT_OWNINGTHREAD(Runnable);
+
+ // The port can be cycle collected while this runnable is pending in
+ // the event queue.
+ if (!mPort) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mPort->mPostMessageRunnable == this);
+
+ DispatchMessage();
+
+ // We must check if we were waiting for this message in order to shutdown
+ // the port.
+ mPort->UpdateMustKeepAlive();
+
+ mPort->mPostMessageRunnable = nullptr;
+ mPort->Dispatch();
+
+ return NS_OK;
+ }
+
+ nsresult Cancel() override {
+ NS_ASSERT_OWNINGTHREAD(Runnable);
+
+ mPort = nullptr;
+ mData = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ void DispatchMessage() const {
+ NS_ASSERT_OWNINGTHREAD(Runnable);
+
+ if (NS_FAILED(mPort->CheckCurrentGlobalCorrectness())) {
+ return;
+ }
+
+ nsCOMPtr<nsIGlobalObject> globalObject = mPort->GetParentObject();
+
+ AutoJSAPI jsapi;
+ if (!globalObject || !jsapi.Init(globalObject)) {
+ NS_WARNING("Failed to initialize AutoJSAPI object.");
+ return;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ IgnoredErrorResult rv;
+ JS::Rooted<JS::Value> value(cx);
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ bool isTimelineRecording = !TimelineConsumers::IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<MessagePortTimelineMarker>(
+ ProfileTimelineMessagePortOperationType::DeserializeData,
+ MarkerTracingType::START);
+ }
+
+ mData->Read(cx, &value, mPort->mRefMessageBodyService,
+ SharedMessageBody::ReadMethod::StealRefMessageBody, rv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<MessagePortTimelineMarker>(
+ ProfileTimelineMessagePortOperationType::DeserializeData,
+ MarkerTracingType::END);
+ TimelineConsumers::AddMarkerForAllObservedDocShells(start);
+ TimelineConsumers::AddMarkerForAllObservedDocShells(end);
+ }
+
+ if (NS_WARN_IF(rv.Failed())) {
+ JS_ClearPendingException(cx);
+ mPort->DispatchError();
+ return;
+ }
+
+ // Create the event
+ nsCOMPtr<mozilla::dom::EventTarget> eventTarget =
+ do_QueryInterface(mPort->GetOwner());
+ RefPtr<MessageEvent> event =
+ new MessageEvent(eventTarget, nullptr, nullptr);
+
+ Sequence<OwningNonNull<MessagePort>> ports;
+ if (!mData->TakeTransferredPortsAsSequence(ports)) {
+ mPort->DispatchError();
+ return;
+ }
+
+ event->InitMessageEvent(nullptr, u"message"_ns, CanBubble::eNo,
+ Cancelable::eNo, value, u""_ns, u""_ns, nullptr,
+ ports);
+ event->SetTrusted(true);
+
+ mPort->DispatchEvent(*event);
+ }
+
+ private:
+ ~PostMessageRunnable() = default;
+
+ RefPtr<MessagePort> mPort;
+ RefPtr<SharedMessageBody> mData;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MessagePort)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MessagePort,
+ DOMEventTargetHelper)
+ if (tmp->mPostMessageRunnable) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPostMessageRunnable->mPort);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagesForTheOtherPort);
+
+ tmp->CloseForced();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MessagePort,
+ DOMEventTargetHelper)
+ if (tmp->mPostMessageRunnable) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPostMessageRunnable->mPort);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnshippedEntangledPort);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MessagePort)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(MessagePort, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MessagePort, DOMEventTargetHelper)
+
+MessagePort::MessagePort(nsIGlobalObject* aGlobal, State aState)
+ : DOMEventTargetHelper(aGlobal),
+ mRefMessageBodyService(RefMessageBodyService::GetOrCreate()),
+ mState(aState),
+ mMessageQueueEnabled(false),
+ mIsKeptAlive(false),
+ mHasBeenTransferredOrClosed(false) {
+ MOZ_ASSERT(aGlobal);
+
+ mIdentifier = MakeUnique<MessagePortIdentifier>();
+ mIdentifier->neutered() = true;
+ mIdentifier->sequenceId() = 0;
+}
+
+MessagePort::~MessagePort() {
+ CloseForced();
+ MOZ_ASSERT(!mWorkerRef);
+}
+
+/* static */
+already_AddRefed<MessagePort> MessagePort::Create(nsIGlobalObject* aGlobal,
+ const nsID& aUUID,
+ const nsID& aDestinationUUID,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aGlobal);
+
+ RefPtr<MessagePort> mp = new MessagePort(aGlobal, eStateUnshippedEntangled);
+ mp->Initialize(aUUID, aDestinationUUID, 1 /* 0 is an invalid sequence ID */,
+ false /* Neutered */, aRv);
+ return mp.forget();
+}
+
+/* static */
+already_AddRefed<MessagePort> MessagePort::Create(
+ nsIGlobalObject* aGlobal, UniqueMessagePortId& aIdentifier,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aGlobal);
+
+ RefPtr<MessagePort> mp = new MessagePort(aGlobal, eStateEntangling);
+ mp->Initialize(aIdentifier.uuid(), aIdentifier.destinationUuid(),
+ aIdentifier.sequenceId(), aIdentifier.neutered(), aRv);
+ aIdentifier.neutered() = true;
+ return mp.forget();
+}
+
+void MessagePort::UnshippedEntangle(MessagePort* aEntangledPort) {
+ MOZ_DIAGNOSTIC_ASSERT(aEntangledPort);
+ MOZ_DIAGNOSTIC_ASSERT(!mUnshippedEntangledPort);
+
+ mUnshippedEntangledPort = aEntangledPort;
+}
+
+void MessagePort::Initialize(const nsID& aUUID, const nsID& aDestinationUUID,
+ uint32_t aSequenceID, bool aNeutered,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(mIdentifier);
+ mIdentifier->uuid() = aUUID;
+ mIdentifier->destinationUuid() = aDestinationUUID;
+ mIdentifier->sequenceId() = aSequenceID;
+
+ if (aNeutered) {
+ // If this port is neutered we don't want to keep it alive artificially nor
+ // we want to add listeners or WorkerRefs.
+ mState = eStateDisentangled;
+ return;
+ }
+
+ if (mState == eStateEntangling) {
+ if (!ConnectToPBackground()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+ } else {
+ MOZ_ASSERT(mState == eStateUnshippedEntangled);
+ }
+
+ // The port has to keep itself alive until it's entangled.
+ UpdateMustKeepAlive();
+
+ if (WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate()) {
+ RefPtr<MessagePort> self = this;
+
+ // When the callback is executed, we cannot process messages anymore because
+ // we cannot dispatch new runnables. Let's force a Close().
+ RefPtr<StrongWorkerRef> strongWorkerRef = StrongWorkerRef::Create(
+ workerPrivate, "MessagePort", [self]() { self->CloseForced(); });
+ if (NS_WARN_IF(!strongWorkerRef)) {
+ // The worker is shutting down.
+ mState = eStateDisentangled;
+ UpdateMustKeepAlive();
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ MOZ_ASSERT(!mWorkerRef);
+ mWorkerRef = std::move(strongWorkerRef);
+ }
+}
+
+JSObject* MessagePort::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MessagePort_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void MessagePort::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Sequence<JSObject*>& aTransferable,
+ ErrorResult& aRv) {
+ // We *must* clone the data here, or the JS::Value could be modified
+ // by script
+
+ // Here we want to check if the transerable object list contains
+ // this port.
+ for (uint32_t i = 0; i < aTransferable.Length(); ++i) {
+ JS::Rooted<JSObject*> object(aCx, aTransferable[i]);
+ if (!object) {
+ continue;
+ }
+
+ MessagePort* port = nullptr;
+ nsresult rv = UNWRAP_OBJECT(MessagePort, &object, port);
+ if (NS_SUCCEEDED(rv) && port == this) {
+ aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+ }
+
+ JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+
+ aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
+ &transferable);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ Maybe<nsID> agentClusterId;
+ nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+ if (global) {
+ agentClusterId = global->GetAgentClusterId();
+ }
+
+ RefPtr<SharedMessageBody> data = new SharedMessageBody(
+ StructuredCloneHolder::TransferringSupported, agentClusterId);
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ bool isTimelineRecording = !TimelineConsumers::IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<MessagePortTimelineMarker>(
+ ProfileTimelineMessagePortOperationType::SerializeData,
+ MarkerTracingType::START);
+ }
+
+ data->Write(aCx, aMessage, transferable, mIdentifier->uuid(),
+ mRefMessageBodyService, aRv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<MessagePortTimelineMarker>(
+ ProfileTimelineMessagePortOperationType::SerializeData,
+ MarkerTracingType::END);
+ TimelineConsumers::AddMarkerForAllObservedDocShells(start);
+ TimelineConsumers::AddMarkerForAllObservedDocShells(end);
+ }
+
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ // This message has to be ignored.
+ if (mState > eStateEntangled) {
+ return;
+ }
+
+ // If we are unshipped we are connected to the other port on the same thread.
+ if (mState == eStateUnshippedEntangled) {
+ MOZ_DIAGNOSTIC_ASSERT(mUnshippedEntangledPort);
+ mUnshippedEntangledPort->mMessages.AppendElement(data);
+ mUnshippedEntangledPort->Dispatch();
+ return;
+ }
+
+ // Not entangled yet, but already closed/disentangled.
+ if (mState == eStateEntanglingForDisentangle ||
+ mState == eStateEntanglingForClose) {
+ return;
+ }
+
+ RemoveDocFromBFCache();
+
+ // Not entangled yet.
+ if (mState == eStateEntangling) {
+ mMessagesForTheOtherPort.AppendElement(data);
+ return;
+ }
+
+ MOZ_ASSERT(mActor);
+ MOZ_ASSERT(mMessagesForTheOtherPort.IsEmpty());
+
+ AutoTArray<RefPtr<SharedMessageBody>, 1> array;
+ array.AppendElement(data);
+
+ AutoTArray<MessageData, 1> messages;
+ // note: `messages` will borrow the underlying buffer, but this is okay
+ // because reverse destruction order means `messages` will be destroyed prior
+ // to `array`/`data`.
+ SharedMessageBody::FromSharedToMessagesChild(mActor->Manager(), array,
+ messages);
+ mActor->SendPostMessages(messages);
+}
+
+void MessagePort::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const StructuredSerializeOptions& aOptions,
+ ErrorResult& aRv) {
+ PostMessage(aCx, aMessage, aOptions.mTransfer, aRv);
+}
+
+void MessagePort::Start() {
+ if (mMessageQueueEnabled) {
+ return;
+ }
+
+ mMessageQueueEnabled = true;
+ Dispatch();
+}
+
+void MessagePort::Dispatch() {
+ if (!mMessageQueueEnabled || mMessages.IsEmpty() || mPostMessageRunnable) {
+ return;
+ }
+
+ switch (mState) {
+ case eStateUnshippedEntangled:
+ // Everything is fine here. We have messages because the other
+ // port populates our queue directly.
+ break;
+
+ case eStateEntangling:
+ // Everything is fine here as well. We have messages because the other
+ // port populated our queue directly when we were in the
+ // eStateUnshippedEntangled state.
+ break;
+
+ case eStateEntanglingForDisentangle:
+ // Here we don't want to ship messages because these messages must be
+ // delivered by the cloned version of this one. They will be sent in the
+ // SendDisentangle().
+ return;
+
+ case eStateEntanglingForClose:
+ // We still want to deliver messages if we are closing. These messages
+ // are here from the previous eStateUnshippedEntangled state.
+ break;
+
+ case eStateEntangled:
+ // This port is up and running.
+ break;
+
+ case eStateDisentangling:
+ // If we are in the process to disentangle the port, we cannot dispatch
+ // messages. They will be sent to the cloned version of this port via
+ // SendDisentangle();
+ return;
+
+ case eStateDisentangled:
+ MOZ_CRASH("This cannot happen.");
+ // It cannot happen because Disentangle should take off all the pending
+ // messages.
+ break;
+
+ case eStateDisentangledForClose:
+ // If we are here is because the port has been closed. We can still
+ // process the pending messages.
+ break;
+ }
+
+ RefPtr<SharedMessageBody> data = mMessages.ElementAt(0);
+ mMessages.RemoveElementAt(0);
+
+ mPostMessageRunnable = new PostMessageRunnable(this, data);
+
+ nsCOMPtr<nsIGlobalObject> global = GetOwnerGlobal();
+ if (NS_IsMainThread() && global) {
+ MOZ_ALWAYS_SUCCEEDS(
+ global->Dispatch(TaskCategory::Other, do_AddRef(mPostMessageRunnable)));
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mPostMessageRunnable));
+}
+
+void MessagePort::Close() {
+ mHasBeenTransferredOrClosed = true;
+ CloseInternal(true /* aSoftly */);
+}
+
+void MessagePort::CloseForced() { CloseInternal(false /* aSoftly */); }
+
+void MessagePort::CloseInternal(bool aSoftly) {
+ // If we have some messages to send but we don't want a 'soft' close, we have
+ // to flush them now.
+ if (!aSoftly) {
+ mMessages.Clear();
+ }
+
+ // Let's inform the RefMessageBodyService that any our shared messages are
+ // now invalid.
+ mRefMessageBodyService->ForgetPort(mIdentifier->uuid());
+
+ if (mState == eStateUnshippedEntangled) {
+ MOZ_DIAGNOSTIC_ASSERT(mUnshippedEntangledPort);
+
+ // This avoids loops.
+ RefPtr<MessagePort> port = std::move(mUnshippedEntangledPort);
+
+ mState = eStateDisentangledForClose;
+ port->CloseInternal(aSoftly);
+
+ UpdateMustKeepAlive();
+ return;
+ }
+
+ // Not entangled yet, we have to wait.
+ if (mState == eStateEntangling) {
+ mState = eStateEntanglingForClose;
+ return;
+ }
+
+ // Not entangled but already cloned or closed
+ if (mState == eStateEntanglingForDisentangle ||
+ mState == eStateEntanglingForClose) {
+ return;
+ }
+
+ // Maybe we were already closing the port but softly. In this case we call
+ // UpdateMustKeepAlive() to consider the empty pending message queue.
+ if (mState == eStateDisentangledForClose && !aSoftly) {
+ UpdateMustKeepAlive();
+ return;
+ }
+
+ if (mState > eStateEntangled) {
+ return;
+ }
+
+ // We don't care about stopping the sending of messages because from now all
+ // the incoming messages will be ignored.
+ mState = eStateDisentangledForClose;
+
+ MOZ_ASSERT(mActor);
+
+ mActor->SendClose();
+ mActor->SetPort(nullptr);
+ mActor = nullptr;
+
+ UpdateMustKeepAlive();
+}
+
+EventHandlerNonNull* MessagePort::GetOnmessage() {
+ return GetEventHandler(nsGkAtoms::onmessage);
+}
+
+void MessagePort::SetOnmessage(EventHandlerNonNull* aCallback) {
+ SetEventHandler(nsGkAtoms::onmessage, aCallback);
+
+ // When using onmessage, the call to start() is implied.
+ Start();
+}
+
+// This method is called when the PMessagePortChild actor is entangled to
+// another actor. It receives a list of messages to be dispatch. It can be that
+// we were waiting for this entangling step in order to disentangle the port or
+// to close it.
+void MessagePort::Entangled(nsTArray<MessageData>& aMessages) {
+ MOZ_ASSERT(mState == eStateEntangling ||
+ mState == eStateEntanglingForDisentangle ||
+ mState == eStateEntanglingForClose);
+
+ State oldState = mState;
+ mState = eStateEntangled;
+
+ // If we have pending messages, these have to be sent.
+ if (!mMessagesForTheOtherPort.IsEmpty()) {
+ {
+ nsTArray<MessageData> messages;
+ SharedMessageBody::FromSharedToMessagesChild(
+ mActor->Manager(), mMessagesForTheOtherPort, messages);
+ mActor->SendPostMessages(messages);
+ }
+ // Because `messages` borrow the underlying JSStructuredCloneData buffers,
+ // only clear after `messages` have gone out of scope.
+ mMessagesForTheOtherPort.Clear();
+ }
+
+ // We must convert the messages into SharedMessageBodys to avoid leaks.
+ FallibleTArray<RefPtr<SharedMessageBody>> data;
+ if (NS_WARN_IF(
+ !SharedMessageBody::FromMessagesToSharedChild(aMessages, data))) {
+ DispatchError();
+ return;
+ }
+
+ // If the next step is to close the port, we do it ignoring the received
+ // messages.
+ if (oldState == eStateEntanglingForClose) {
+ CloseForced();
+ return;
+ }
+
+ mMessages.AppendElements(data);
+
+ // We were waiting for the entangling callback in order to disentangle this
+ // port immediately after.
+ if (oldState == eStateEntanglingForDisentangle) {
+ StartDisentangling();
+ return;
+ }
+
+ Dispatch();
+}
+
+void MessagePort::StartDisentangling() {
+ MOZ_ASSERT(mActor);
+ MOZ_ASSERT(mState == eStateEntangled);
+
+ mState = eStateDisentangling;
+
+ // Sending this message we communicate to the parent actor that we don't want
+ // to receive any new messages. It is possible that a message has been
+ // already sent but not received yet. So we have to collect all of them and
+ // we send them in the SendDispatch() request.
+ mActor->SendStopSendingData();
+}
+
+void MessagePort::MessagesReceived(nsTArray<MessageData>& aMessages) {
+ MOZ_ASSERT(mState == eStateEntangled || mState == eStateDisentangling ||
+ // This last step can happen only if Close() has been called
+ // manually. At this point SendClose() is sent but we can still
+ // receive something until the Closing request is processed.
+ mState == eStateDisentangledForClose);
+ MOZ_ASSERT(mMessagesForTheOtherPort.IsEmpty());
+
+ RemoveDocFromBFCache();
+
+ FallibleTArray<RefPtr<SharedMessageBody>> data;
+ if (NS_WARN_IF(
+ !SharedMessageBody::FromMessagesToSharedChild(aMessages, data))) {
+ DispatchError();
+ return;
+ }
+
+ mMessages.AppendElements(data);
+
+ if (mState == eStateEntangled) {
+ Dispatch();
+ }
+}
+
+void MessagePort::StopSendingDataConfirmed() {
+ MOZ_ASSERT(mState == eStateDisentangling);
+ MOZ_ASSERT(mActor);
+
+ Disentangle();
+}
+
+void MessagePort::Disentangle() {
+ MOZ_ASSERT(mState == eStateDisentangling);
+ MOZ_ASSERT(mActor);
+
+ mState = eStateDisentangled;
+
+ {
+ nsTArray<MessageData> messages;
+ SharedMessageBody::FromSharedToMessagesChild(mActor->Manager(), mMessages,
+ messages);
+ mActor->SendDisentangle(messages);
+ }
+
+ // Let's inform the RefMessageBodyService that any our shared messages are
+ // now invalid.
+ mRefMessageBodyService->ForgetPort(mIdentifier->uuid());
+
+ // Only clear mMessages after the MessageData instances have gone out of scope
+ // because they borrow mMessages' underlying JSStructuredCloneDatas.
+ mMessages.Clear();
+
+ mActor->SetPort(nullptr);
+ mActor = nullptr;
+
+ UpdateMustKeepAlive();
+}
+
+void MessagePort::CloneAndDisentangle(UniqueMessagePortId& aIdentifier) {
+ MOZ_ASSERT(mIdentifier);
+ MOZ_ASSERT(!mHasBeenTransferredOrClosed);
+
+ mHasBeenTransferredOrClosed = true;
+
+ // We can clone a port that has already been transfered. In this case, on the
+ // otherside will have a neutered port. Here we set neutered to true so that
+ // we are safe in case a early return.
+ aIdentifier.neutered() = true;
+
+ if (mState > eStateEntangled) {
+ return;
+ }
+
+ // We already have a 'next step'. We have to consider this port as already
+ // cloned/closed/disentangled.
+ if (mState == eStateEntanglingForDisentangle ||
+ mState == eStateEntanglingForClose) {
+ return;
+ }
+
+ aIdentifier.uuid() = mIdentifier->uuid();
+ aIdentifier.destinationUuid() = mIdentifier->destinationUuid();
+ aIdentifier.sequenceId() = mIdentifier->sequenceId() + 1;
+ aIdentifier.neutered() = false;
+
+ // We have to entangle first.
+ if (mState == eStateUnshippedEntangled) {
+ MOZ_ASSERT(mUnshippedEntangledPort);
+ MOZ_ASSERT(mMessagesForTheOtherPort.IsEmpty());
+
+ RefPtr<MessagePort> port = std::move(mUnshippedEntangledPort);
+
+ // Disconnect the entangled port and connect it to PBackground.
+ if (!port->ConnectToPBackground()) {
+ // We are probably shutting down. We cannot proceed.
+ mState = eStateDisentangled;
+ UpdateMustKeepAlive();
+ return;
+ }
+
+ // In this case, we don't need to be connected to the PBackground service.
+ if (mMessages.IsEmpty()) {
+ aIdentifier.sequenceId() = mIdentifier->sequenceId();
+
+ mState = eStateDisentangled;
+ UpdateMustKeepAlive();
+ return;
+ }
+
+ // Register this component to PBackground.
+ if (!ConnectToPBackground()) {
+ // We are probably shutting down. We cannot proceed.
+ return;
+ }
+
+ mState = eStateEntanglingForDisentangle;
+ return;
+ }
+
+ // Not entangled yet, we have to wait.
+ if (mState == eStateEntangling) {
+ mState = eStateEntanglingForDisentangle;
+ return;
+ }
+
+ MOZ_ASSERT(mState == eStateEntangled);
+ StartDisentangling();
+}
+
+void MessagePort::Closed() {
+ if (mState >= eStateDisentangled) {
+ return;
+ }
+
+ mState = eStateDisentangledForClose;
+
+ if (mActor) {
+ mActor->SetPort(nullptr);
+ mActor = nullptr;
+ }
+
+ UpdateMustKeepAlive();
+}
+
+bool MessagePort::ConnectToPBackground() {
+ RefPtr<MessagePort> self = this;
+ auto raii = MakeScopeExit([self] {
+ self->mState = eStateDisentangled;
+ self->UpdateMustKeepAlive();
+ });
+
+ mozilla::ipc::PBackgroundChild* actorChild =
+ mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actorChild)) {
+ return false;
+ }
+
+ PMessagePortChild* actor = actorChild->SendPMessagePortConstructor(
+ mIdentifier->uuid(), mIdentifier->destinationUuid(),
+ mIdentifier->sequenceId());
+ if (NS_WARN_IF(!actor)) {
+ return false;
+ }
+
+ mActor = static_cast<MessagePortChild*>(actor);
+ MOZ_ASSERT(mActor);
+
+ mActor->SetPort(this);
+ mState = eStateEntangling;
+
+ raii.release();
+ return true;
+}
+
+void MessagePort::UpdateMustKeepAlive() {
+ if (mState >= eStateDisentangled && mMessages.IsEmpty() && mIsKeptAlive) {
+ mIsKeptAlive = false;
+
+ // The DTOR of this WorkerRef will release the worker for us.
+ mWorkerRef = nullptr;
+
+ Release();
+ return;
+ }
+
+ if (mState < eStateDisentangled && !mIsKeptAlive) {
+ mIsKeptAlive = true;
+ AddRef();
+ }
+}
+
+void MessagePort::DisconnectFromOwner() {
+ CloseForced();
+ DOMEventTargetHelper::DisconnectFromOwner();
+}
+
+void MessagePort::RemoveDocFromBFCache() {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ if (nsPIDOMWindowInner* window = GetOwner()) {
+ window->RemoveFromBFCacheSync();
+ }
+}
+
+/* static */
+void MessagePort::ForceClose(const MessagePortIdentifier& aIdentifier) {
+ mozilla::ipc::PBackgroundChild* actorChild =
+ mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actorChild)) {
+ MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+ }
+
+ Unused << actorChild->SendMessagePortForceClose(aIdentifier.uuid(),
+ aIdentifier.destinationUuid(),
+ aIdentifier.sequenceId());
+}
+
+void MessagePort::DispatchError() {
+ nsCOMPtr<nsIGlobalObject> globalObject = GetParentObject();
+
+ AutoJSAPI jsapi;
+ if (!globalObject || !jsapi.Init(globalObject)) {
+ NS_WARNING("Failed to initialize AutoJSAPI object.");
+ return;
+ }
+
+ RootedDictionary<MessageEventInit> init(jsapi.cx());
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ RefPtr<Event> event =
+ MessageEvent::Constructor(this, u"messageerror"_ns, init);
+ event->SetTrusted(true);
+
+ DispatchEvent(*event);
+}
+
+} // namespace mozilla::dom