dom/messagechannel/MessagePort.cpp
author Nicolas B. Pierron <nicolas.b.pierron@nbp.name>
Wed, 24 Oct 2018 15:07:53 +0200
changeset 443125 1bd322732a6e67a0caab2e0bf26ff0b20f01fc1a
parent 428232 608abb3e16abd90c287340c47d882d5734b50184
child 443380 2b6babc63893f7b578bd8012d0ef5d437b36ee3e
permissions -rw-r--r--
Bug 1502106 - Add a configure option to disable assume_unreachable spew. r=iain

/* -*- 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/ScriptSettings.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/ipc/BackgroundChild.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"
#include "SharedMessagePortMessage.h"

#include "nsIBFCacheEntry.h"
#include "nsIDocument.h"
#include "nsIPresShell.h"
#include "nsISupportsPrimitives.h"
#include "nsServiceManagerUtils.h"

#ifdef XP_WIN
#undef PostMessage
#endif

namespace mozilla {
namespace dom {

class PostMessageRunnable final : public CancelableRunnable
{
  friend class MessagePort;

public:
  PostMessageRunnable(MessagePort* aPort, SharedMessagePortMessage* 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);

    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(cx, &value, rv);

    if (isTimelineRecording) {
      end = MakeUnique<MessagePortTimelineMarker>(
        ProfileTimelineMessagePortOperationType::DeserializeData,
        MarkerTracingType::END);
      timelines->AddMarkerForAllObservedDocShells(start);
      timelines->AddMarkerForAllObservedDocShells(end);
    }

    if (NS_WARN_IF(rv.Failed())) {
      mPort->DispatchError();
      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)) {
      mPort->DispatchError();
      return NS_ERROR_OUT_OF_MEMORY;
    }

    event->InitMessageEvent(nullptr, NS_LITERAL_STRING("message"),
                            CanBubble::eNo, Cancelable::eNo, value, EmptyString(),
                            EmptyString(), nullptr, ports);
    event->SetTrusted(true);

    mPort->DispatchEvent(*event);

    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(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)
  , mState(aState)
  , mMessageQueueEnabled(false)
  , mIsKeptAlive(false)
  , mHasBeenTransferredOrClosed(false)
{
  MOZ_ASSERT(aGlobal);

  mIdentifier = new 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,
                    const MessagePortIdentifier& aIdentifier,
                    ErrorResult& aRv)
{
  MOZ_ASSERT(aGlobal);

  RefPtr<MessagePort> mp = new MessagePort(aGlobal, eStateEntangling);
  mp->Initialize(aIdentifier.uuid(), aIdentifier.destinationUuid(),
                 aIdentifier.sequenceId(), aIdentifier.neutered(), aRv);
  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 (!NS_IsMainThread()) {
    RefPtr<MessagePort> self = this;

    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    MOZ_ASSERT(workerPrivate);

    // 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;
  }

  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, 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_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<SharedMessagePortMessage>, 1> array;
  array.AppendElement(data);

  AutoTArray<ClonedMessageData, 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`.
  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);

  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();
  }

  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<ClonedMessageData>& 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<ClonedMessageData> messages;
      SharedMessagePortMessage::FromSharedToMessagesChild(mActor,
                                                          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 SharedMessagePortMessages to avoid leaks.
  FallibleTArray<RefPtr<SharedMessagePortMessage>> data;
  if (NS_WARN_IF(!SharedMessagePortMessage::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<ClonedMessageData>& 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))) {
    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<ClonedMessageData> messages;
    SharedMessagePortMessage::FromSharedToMessagesChild(mActor, mMessages,
                                                        messages);
    mActor->SendDisentangle(messages);
  }
  // Only clear mMessages after the ClonedMessageData instances have gone out of
  // scope because they borrow mMessages' underlying JSStructuredCloneDatas.
  mMessages.Clear();

  mActor->SetPort(nullptr);
  mActor = nullptr;

  UpdateMustKeepAlive();
}

void
MessagePort::CloneAndDisentangle(MessagePortIdentifier& 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;
  }

  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)
{
  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, NS_LITERAL_STRING("messageerror"), init);
  event->SetTrusted(true);

  DispatchEvent(*event);
}

} // namespace dom
} // namespace mozilla