diff options
Diffstat (limited to 'dom/messagechannel/MessagePort.cpp')
-rw-r--r-- | dom/messagechannel/MessagePort.cpp | 1011 |
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 |