fune/dom/messagechannel/MessagePort.cpp
Nika Layzell 8f236832ff Bug 1758115 - Part 2: Streamline locking, initialization and shutdown for TimelineConsumers, r=smaug
The current implementation of TimelineConsumers contains some unnecessary
complexity due to how it is initialized as a singleton, and the need for it to
be initialized and used in a threadsafe way. This patch attempts to simplify it
by making all members static, and removing the need to explicitly observe
shutdown for cleanup.

In addition, this approach avoids the risk of the type being accessed from
off-main-thread during initialization or shutdown.

Depends on D150442

Differential Revision: https://phabricator.services.mozilla.com/D150443
2022-06-29 15:01:51 +00:00

870 lines
25 KiB
C++

/* -*- 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