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.cpp1011
1 files changed, 1011 insertions, 0 deletions
diff --git a/dom/messagechannel/MessagePort.cpp b/dom/messagechannel/MessagePort.cpp
new file mode 100644
index 000000000..56204da99
--- /dev/null
+++ b/dom/messagechannel/MessagePort.cpp
@@ -0,0 +1,1011 @@
+/* -*- 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/StructuredCloneTags.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/MessagePortTimelineMarker.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/TimelineMarker.h"
+#include "mozilla/Unused.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "nsPresContext.h"
+#include "ScriptSettings.h"
+#include "SharedMessagePortMessage.h"
+
+#include "nsIBFCacheEntry.h"
+#include "nsIDocument.h"
+#include "nsIDOMFileList.h"
+#include "nsIPresShell.h"
+#include "nsISupportsPrimitives.h"
+#include "nsServiceManagerUtils.h"
+
+#ifdef XP_WIN
+#undef PostMessage
+#endif
+
+using namespace mozilla::dom::workers;
+
+namespace mozilla {
+namespace dom {
+
+class PostMessageRunnable final : public CancelableRunnable
+{
+ friend class MessagePort;
+
+public:
+ PostMessageRunnable(MessagePort* aPort, SharedMessagePortMessage* aData)
+ : 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);
+
+ nsresult rv = 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 rv;
+ }
+
+ nsresult
+ Cancel() override
+ {
+ NS_ASSERT_OWNINGTHREAD(Runnable);
+
+ mPort = nullptr;
+ mData = nullptr;
+ return NS_OK;
+ }
+
+private:
+ nsresult
+ DispatchMessage() const
+ {
+ NS_ASSERT_OWNINGTHREAD(Runnable);
+
+ nsCOMPtr<nsIGlobalObject> globalObject = mPort->GetParentObject();
+
+ AutoJSAPI jsapi;
+ if (!globalObject || !jsapi.Init(globalObject)) {
+ NS_WARNING("Failed to initialize AutoJSAPI object.");
+ return NS_ERROR_FAILURE;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ ErrorResult rv;
+ JS::Rooted<JS::Value> value(cx);
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<MessagePortTimelineMarker>(
+ ProfileTimelineMessagePortOperationType::DeserializeData,
+ MarkerTracingType::START);
+ }
+
+ mData->Read(mPort->GetParentObject(), cx, &value, rv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<MessagePortTimelineMarker>(
+ ProfileTimelineMessagePortOperationType::DeserializeData,
+ MarkerTracingType::END);
+ timelines->AddMarkerForAllObservedDocShells(start);
+ timelines->AddMarkerForAllObservedDocShells(end);
+ }
+
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ // 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)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ Nullable<WindowProxyOrMessagePort> source;
+ source.SetValue().SetAsMessagePort() = mPort;
+
+ event->InitMessageEvent(nullptr, NS_LITERAL_STRING("message"),
+ false /* non-bubbling */,
+ false /* cancelable */, value, EmptyString(),
+ EmptyString(), source, ports);
+ event->SetTrusted(true);
+
+ bool dummy;
+ mPort->DispatchEvent(static_cast<dom::Event*>(event.get()), &dummy);
+
+ return NS_OK;
+ }
+
+private:
+ ~PostMessageRunnable()
+ {}
+
+ RefPtr<MessagePort> mPort;
+ RefPtr<SharedMessagePortMessage> 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(mMessages);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessagesForTheOtherPort);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnshippedEntangledPort);
+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_INHERITED(MessagePort)
+ NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(MessagePort, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(MessagePort, DOMEventTargetHelper)
+
+namespace {
+
+class MessagePortWorkerHolder final : public workers::WorkerHolder
+{
+ MessagePort* mPort;
+
+public:
+ explicit MessagePortWorkerHolder(MessagePort* aPort)
+ : mPort(aPort)
+ {
+ MOZ_ASSERT(aPort);
+ MOZ_COUNT_CTOR(MessagePortWorkerHolder);
+ }
+
+ virtual bool Notify(workers::Status aStatus) override
+ {
+ if (aStatus > Running) {
+ // We cannot process messages anymore because we cannot dispatch new
+ // runnables. Let's force a Close().
+ mPort->CloseForced();
+ }
+
+ return true;
+ }
+
+private:
+ ~MessagePortWorkerHolder()
+ {
+ MOZ_COUNT_DTOR(MessagePortWorkerHolder);
+ }
+};
+
+class ForceCloseHelper final : public nsIIPCBackgroundChildCreateCallback
+{
+public:
+ NS_DECL_ISUPPORTS
+
+ static void ForceClose(const MessagePortIdentifier& aIdentifier)
+ {
+ PBackgroundChild* actor =
+ mozilla::ipc::BackgroundChild::GetForCurrentThread();
+ if (actor) {
+ Unused << actor->SendMessagePortForceClose(aIdentifier.uuid(),
+ aIdentifier.destinationUuid(),
+ aIdentifier.sequenceId());
+ return;
+ }
+
+ RefPtr<ForceCloseHelper> helper = new ForceCloseHelper(aIdentifier);
+ if (NS_WARN_IF(!mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(helper))) {
+ MOZ_CRASH();
+ }
+ }
+
+private:
+ explicit ForceCloseHelper(const MessagePortIdentifier& aIdentifier)
+ : mIdentifier(aIdentifier)
+ {}
+
+ ~ForceCloseHelper() {}
+
+ void ActorFailed() override
+ {
+ MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+ }
+
+ void ActorCreated(mozilla::ipc::PBackgroundChild* aActor) override
+ {
+ ForceClose(mIdentifier);
+ }
+
+ const MessagePortIdentifier mIdentifier;
+};
+
+NS_IMPL_ISUPPORTS(ForceCloseHelper, nsIIPCBackgroundChildCreateCallback)
+
+} // namespace
+
+MessagePort::MessagePort(nsIGlobalObject* aGlobal)
+ : DOMEventTargetHelper(aGlobal)
+ , mInnerID(0)
+ , mMessageQueueEnabled(false)
+ , mIsKeptAlive(false)
+{
+ MOZ_ASSERT(aGlobal);
+
+ mIdentifier = new MessagePortIdentifier();
+ mIdentifier->neutered() = true;
+ mIdentifier->sequenceId() = 0;
+}
+
+MessagePort::~MessagePort()
+{
+ CloseForced();
+ MOZ_ASSERT(!mWorkerHolder);
+}
+
+/* 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);
+ mp->Initialize(aUUID, aDestinationUUID, 1 /* 0 is an invalid sequence ID */,
+ false /* Neutered */, eStateUnshippedEntangled, aRv);
+ return mp.forget();
+}
+
+/* static */ already_AddRefed<MessagePort>
+MessagePort::Create(nsIGlobalObject* aGlobal,
+ const MessagePortIdentifier& aIdentifier,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aGlobal);
+
+ RefPtr<MessagePort> mp = new MessagePort(aGlobal);
+ mp->Initialize(aIdentifier.uuid(), aIdentifier.destinationUuid(),
+ aIdentifier.sequenceId(), aIdentifier.neutered(),
+ eStateEntangling, aRv);
+ return mp.forget();
+}
+
+void
+MessagePort::UnshippedEntangle(MessagePort* aEntangledPort)
+{
+ MOZ_ASSERT(aEntangledPort);
+ MOZ_ASSERT(!mUnshippedEntangledPort);
+
+ mUnshippedEntangledPort = aEntangledPort;
+}
+
+void
+MessagePort::Initialize(const nsID& aUUID,
+ const nsID& aDestinationUUID,
+ uint32_t aSequenceID, bool mNeutered,
+ State aState, ErrorResult& aRv)
+{
+ MOZ_ASSERT(mIdentifier);
+ mIdentifier->uuid() = aUUID;
+ mIdentifier->destinationUuid() = aDestinationUUID;
+ mIdentifier->sequenceId() = aSequenceID;
+
+ mState = aState;
+
+ if (mNeutered) {
+ // If this port is neutered we don't want to keep it alive artificially nor
+ // we want to add listeners or workerWorkerHolders.
+ mState = eStateDisentangled;
+ return;
+ }
+
+ if (mState == eStateEntangling) {
+ ConnectToPBackground();
+ } else {
+ MOZ_ASSERT(mState == eStateUnshippedEntangled);
+ }
+
+ // The port has to keep itself alive until it's entangled.
+ UpdateMustKeepAlive();
+
+ if (!NS_IsMainThread()) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(workerPrivate);
+ MOZ_ASSERT(!mWorkerHolder);
+
+ nsAutoPtr<WorkerHolder> workerHolder(new MessagePortWorkerHolder(this));
+ if (NS_WARN_IF(!workerHolder->HoldWorker(workerPrivate, Closing))) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mWorkerHolder = Move(workerHolder);
+ } else if (GetOwner()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(GetOwner()->IsInnerWindow());
+ mInnerID = GetOwner()->WindowID();
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "inner-window-destroyed", false);
+ }
+ }
+}
+
+JSObject*
+MessagePort::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return MessagePortBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+MessagePort::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const Optional<Sequence<JS::Value>>& aTransferable,
+ ErrorResult& aRv)
+{
+ // We *must* clone the data here, or the JS::Value could be modified
+ // by script
+
+ JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
+ if (aTransferable.WasPassed()) {
+ const Sequence<JS::Value>& realTransferable = aTransferable.Value();
+
+ // Here we want to check if the transerable object list contains
+ // this port. No other checks are done.
+ for (const JS::Value& value : realTransferable) {
+ if (!value.isObject()) {
+ continue;
+ }
+
+ JS::Rooted<JSObject*> object(aCx, &value.toObject());
+
+ MessagePort* port = nullptr;
+ nsresult rv = UNWRAP_OBJECT(MessagePort, &object, port);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (port == this) {
+ aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
+ return;
+ }
+ }
+
+ // The input sequence only comes from the generated bindings code, which
+ // ensures it is rooted.
+ JS::HandleValueArray elements =
+ JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(),
+ realTransferable.Elements());
+
+ JSObject* array =
+ JS_NewArrayObject(aCx, elements);
+ if (!array) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ transferable.setObject(*array);
+ }
+
+ RefPtr<SharedMessagePortMessage> data = new SharedMessagePortMessage();
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
+ bool isTimelineRecording = timelines && !timelines->IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<MessagePortTimelineMarker>(
+ ProfileTimelineMessagePortOperationType::SerializeData,
+ MarkerTracingType::START);
+ }
+
+ data->Write(aCx, aMessage, transferable,
+ JS::CloneDataPolicy().denySharedArrayBuffer(), aRv);
+
+ if (isTimelineRecording) {
+ end = MakeUnique<MessagePortTimelineMarker>(
+ ProfileTimelineMessagePortOperationType::SerializeData,
+ MarkerTracingType::END);
+ timelines->AddMarkerForAllObservedDocShells(start);
+ timelines->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_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<SharedMessagePortMessage>, 1> array;
+ array.AppendElement(data);
+
+ AutoTArray<MessagePortMessage, 1> messages;
+ SharedMessagePortMessage::FromSharedToMessagesChild(mActor, array, messages);
+ mActor->SendPostMessages(messages);
+}
+
+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<SharedMessagePortMessage> data = mMessages.ElementAt(0);
+ mMessages.RemoveElementAt(0);
+
+ mPostMessageRunnable = new PostMessageRunnable(this, data);
+
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mPostMessageRunnable));
+}
+
+void
+MessagePort::Close()
+{
+ 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();
+ }
+
+ if (mState == eStateUnshippedEntangled) {
+ MOZ_ASSERT(mUnshippedEntangledPort);
+
+ // This avoids loops.
+ RefPtr<MessagePort> port = Move(mUnshippedEntangledPort);
+ MOZ_ASSERT(mUnshippedEntangledPort == nullptr);
+
+ 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()
+{
+ if (NS_IsMainThread()) {
+ return GetEventHandler(nsGkAtoms::onmessage, EmptyString());
+ }
+ return GetEventHandler(nullptr, NS_LITERAL_STRING("message"));
+}
+
+void
+MessagePort::SetOnmessage(EventHandlerNonNull* aCallback)
+{
+ if (NS_IsMainThread()) {
+ SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback);
+ } else {
+ SetEventHandler(nullptr, NS_LITERAL_STRING("message"), 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<MessagePortMessage>& 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<MessagePortMessage> messages;
+ SharedMessagePortMessage::FromSharedToMessagesChild(mActor,
+ mMessagesForTheOtherPort,
+ messages);
+ mMessagesForTheOtherPort.Clear();
+ mActor->SendPostMessages(messages);
+ }
+
+ // We must convert the messages into SharedMessagePortMessages to avoid leaks.
+ FallibleTArray<RefPtr<SharedMessagePortMessage>> data;
+ if (NS_WARN_IF(!SharedMessagePortMessage::FromMessagesToSharedChild(aMessages,
+ data))) {
+ // OOM, we cannot continue.
+ 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<MessagePortMessage>& 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<SharedMessagePortMessage>> data;
+ if (NS_WARN_IF(!SharedMessagePortMessage::FromMessagesToSharedChild(aMessages,
+ data))) {
+ // OOM, We cannot continue.
+ 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<MessagePortMessage> messages;
+ SharedMessagePortMessage::FromSharedToMessagesChild(mActor, mMessages,
+ messages);
+ mMessages.Clear();
+ mActor->SendDisentangle(messages);
+
+ mActor->SetPort(nullptr);
+ mActor = nullptr;
+
+ UpdateMustKeepAlive();
+}
+
+void
+MessagePort::CloneAndDisentangle(MessagePortIdentifier& aIdentifier)
+{
+ MOZ_ASSERT(mIdentifier);
+
+ // 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());
+
+ // Disconnect the entangled port and connect it to PBackground.
+ mUnshippedEntangledPort->ConnectToPBackground();
+ mUnshippedEntangledPort = nullptr;
+
+ // 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.
+ ConnectToPBackground();
+
+ 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();
+}
+
+void
+MessagePort::ConnectToPBackground()
+{
+ mState = eStateEntangling;
+
+ PBackgroundChild* actor =
+ mozilla::ipc::BackgroundChild::GetForCurrentThread();
+ if (actor) {
+ ActorCreated(actor);
+ } else {
+ if (NS_WARN_IF(
+ !mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(this))) {
+ MOZ_CRASH();
+ }
+ }
+}
+
+void
+MessagePort::ActorFailed()
+{
+ MOZ_CRASH("Failed to create a PBackgroundChild actor!");
+}
+
+void
+MessagePort::ActorCreated(mozilla::ipc::PBackgroundChild* aActor)
+{
+ MOZ_ASSERT(aActor);
+ MOZ_ASSERT(!mActor);
+ MOZ_ASSERT(mIdentifier);
+ MOZ_ASSERT(mState == eStateEntangling ||
+ mState == eStateEntanglingForDisentangle ||
+ mState == eStateEntanglingForClose);
+
+ PMessagePortChild* actor =
+ aActor->SendPMessagePortConstructor(mIdentifier->uuid(),
+ mIdentifier->destinationUuid(),
+ mIdentifier->sequenceId());
+
+ mActor = static_cast<MessagePortChild*>(actor);
+ MOZ_ASSERT(mActor);
+
+ mActor->SetPort(this);
+}
+
+void
+MessagePort::UpdateMustKeepAlive()
+{
+ if (mState >= eStateDisentangled &&
+ mMessages.IsEmpty() &&
+ mIsKeptAlive) {
+ mIsKeptAlive = false;
+
+ // The DTOR of this WorkerHolder will release the worker for us.
+ mWorkerHolder = nullptr;
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIObserverService> obs =
+ do_GetService("@mozilla.org/observer-service;1");
+ if (obs) {
+ obs->RemoveObserver(this, "inner-window-destroyed");
+ }
+ }
+
+ Release();
+ return;
+ }
+
+ if (mState < eStateDisentangled && !mIsKeptAlive) {
+ mIsKeptAlive = true;
+ AddRef();
+ }
+}
+
+NS_IMETHODIMP
+MessagePort::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (strcmp(aTopic, "inner-window-destroyed")) {
+ return NS_OK;
+ }
+
+ // If the window id destroyed we have to release the reference that we are
+ // keeping.
+ if (!mIsKeptAlive) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (innerID == mInnerID) {
+ CloseForced();
+ }
+
+ return NS_OK;
+}
+
+void
+MessagePort::RemoveDocFromBFCache()
+{
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ nsPIDOMWindowInner* window = GetOwner();
+ if (!window) {
+ return;
+ }
+
+ nsIDocument* doc = window->GetExtantDoc();
+ if (!doc) {
+ return;
+ }
+
+ nsCOMPtr<nsIBFCacheEntry> bfCacheEntry = doc->GetBFCacheEntry();
+ if (!bfCacheEntry) {
+ return;
+ }
+
+ bfCacheEntry->RemoveFromBFCacheSync();
+}
+
+/* static */ void
+MessagePort::ForceClose(const MessagePortIdentifier& aIdentifier)
+{
+ ForceCloseHelper::ForceClose(aIdentifier);
+}
+
+} // namespace dom
+} // namespace mozilla