/* -*- 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 "RemoteWorkerChild.h"

#include <utility>

#include "MainThreadUtils.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsIConsoleReportCollector.h"
#include "nsIInterfaceRequestor.h"
#include "nsIPrincipal.h"
#include "nsNetUtil.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"

#include "RemoteWorkerService.h"
#include "mozilla/ArrayAlgorithm.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/Services.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/FetchEventOpProxyChild.h"
#include "mozilla/dom/IndexedDatabaseManager.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/RemoteWorkerManager.h"  // RemoteWorkerManager::IsRemoteTypeAllowed
#include "mozilla/dom/RemoteWorkerTypes.h"
#include "mozilla/dom/ServiceWorkerDescriptor.h"
#include "mozilla/dom/ServiceWorkerInterceptController.h"
#include "mozilla/dom/ServiceWorkerOp.h"
#include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h"
#include "mozilla/dom/ServiceWorkerShutdownState.h"
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/dom/workerinternals/ScriptLoader.h"
#include "mozilla/dom/WorkerError.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "mozilla/net/CookieJarSettings.h"
#include "mozilla/PermissionManager.h"

mozilla::LazyLogModule gRemoteWorkerChildLog("RemoteWorkerChild");

#ifdef LOG
#  undef LOG
#endif
#define LOG(fmt) MOZ_LOG(gRemoteWorkerChildLog, mozilla::LogLevel::Verbose, fmt)

namespace mozilla {

using namespace ipc;

namespace dom {

using workerinternals::ChannelFromScriptURLMainThread;

namespace {

class SharedWorkerInterfaceRequestor final : public nsIInterfaceRequestor {
 public:
  NS_DECL_ISUPPORTS

  SharedWorkerInterfaceRequestor() {
    // This check must match the code nsDocShell::Create.
    if (XRE_IsParentProcess()) {
      mSWController = new ServiceWorkerInterceptController();
    }
  }

  NS_IMETHOD
  GetInterface(const nsIID& aIID, void** aSink) override {
    MOZ_ASSERT(NS_IsMainThread());

    if (mSWController &&
        aIID.Equals(NS_GET_IID(nsINetworkInterceptController))) {
      // If asked for the network intercept controller, ask the outer requestor,
      // which could be the docshell.
      RefPtr<ServiceWorkerInterceptController> swController = mSWController;
      swController.forget(aSink);
      return NS_OK;
    }

    return NS_NOINTERFACE;
  }

 private:
  ~SharedWorkerInterfaceRequestor() = default;

  RefPtr<ServiceWorkerInterceptController> mSWController;
};

NS_IMPL_ADDREF(SharedWorkerInterfaceRequestor)
NS_IMPL_RELEASE(SharedWorkerInterfaceRequestor)
NS_IMPL_QUERY_INTERFACE(SharedWorkerInterfaceRequestor, nsIInterfaceRequestor)

// Normal runnable because AddPortIdentifier() is going to exec JS code.
class MessagePortIdentifierRunnable final : public WorkerRunnable {
 public:
  MessagePortIdentifierRunnable(WorkerPrivate* aWorkerPrivate,
                                RemoteWorkerChild* aActor,
                                const MessagePortIdentifier& aPortIdentifier)
      : WorkerRunnable(aWorkerPrivate),
        mActor(aActor),
        mPortIdentifier(aPortIdentifier) {}

 private:
  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
    if (aWorkerPrivate->GlobalScope()->IsDying()) {
      mPortIdentifier.ForceClose();
      return true;
    }
    mActor->AddPortIdentifier(aCx, aWorkerPrivate, mPortIdentifier);
    return true;
  }

  RefPtr<RemoteWorkerChild> mActor;
  UniqueMessagePortId mPortIdentifier;
};

}  // anonymous namespace

RemoteWorkerChild::RemoteWorkerChild(const RemoteWorkerData& aData)
    : mState(VariantType<Pending>(), "RemoteWorkerChild::mState"),
      mServiceKeepAlive(RemoteWorkerService::MaybeGetKeepAlive()),
      mIsServiceWorker(aData.serviceWorkerData().type() ==
                       OptionalServiceWorkerData::TServiceWorkerData) {
  MOZ_ASSERT(RemoteWorkerService::Thread()->IsOnCurrentThread());
}

RemoteWorkerChild::~RemoteWorkerChild() {
#ifdef DEBUG
  auto lock = mState.Lock();
  MOZ_ASSERT(lock->is<Killed>());
#endif
}

void RemoteWorkerChild::ActorDestroy(ActorDestroyReason) {
  auto launcherData = mLauncherData.Access();

  Unused << NS_WARN_IF(!launcherData->mTerminationPromise.IsEmpty());
  launcherData->mTerminationPromise.RejectIfExists(NS_ERROR_DOM_ABORT_ERR,
                                                   __func__);

  auto lock = mState.Lock();

  // If the worker hasn't shutdown or begun shutdown, we need to ensure it gets
  // canceled.
  if (NS_WARN_IF(!lock->is<Killed>() && !lock->is<Canceled>())) {
    // In terms of strong references to this RemoteWorkerChild, at this moment:
    // - IPC is holding a strong reference that will be dropped in the near
    //   future after this method returns.
    // - If the worker has been started by ExecWorkerOnMainThread, then
    //   WorkerPrivate::mRemoteWorkerController is a strong reference to us.
    //   If the worker has not been started, ExecWorker's runnable lambda will
    //   have a strong reference that will cover the call to
    //   ExecWorkerOnMainThread.
    //   - The WorkerPrivate cancellation and termination callbacks will also
    //     hold strong references, but those callbacks will not outlive the
    //     WorkerPrivate and are not exposed to callers like
    //     mRemoteWorkerController is.
    //
    // Note that this call to RequestWorkerCancellation can still race worker
    // cancellation, in which case the strong reference obtained by
    // NewRunnableMethod can end up being the last strong reference.
    // (RequestWorkerCancellation handles the case that the Worker is already
    // canceled if this happens.)
    RefPtr<nsIRunnable> runnable =
        NewRunnableMethod("RequestWorkerCancellation", this,
                          &RemoteWorkerChild::RequestWorkerCancellation);
    MOZ_ALWAYS_SUCCEEDS(
        SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget()));
  }
}

void RemoteWorkerChild::ExecWorker(const RemoteWorkerData& aData) {
#ifdef DEBUG
  MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread());
  auto launcherData = mLauncherData.Access();
  MOZ_ASSERT(CanSend());
#endif

  RefPtr<RemoteWorkerChild> self = this;

  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
      __func__, [self = std::move(self), data = aData]() mutable {
        nsresult rv = self->ExecWorkerOnMainThread(std::move(data));

        // Creation failure will already have been reported via the method
        // above internally using ScopeExit.
        Unused << NS_WARN_IF(NS_FAILED(rv));
      });

  MOZ_ALWAYS_SUCCEEDS(
      SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
}

nsresult RemoteWorkerChild::ExecWorkerOnMainThread(RemoteWorkerData&& aData) {
  MOZ_ASSERT(NS_IsMainThread());

  // Ensure that the IndexedDatabaseManager is initialized so that if any
  // workers do any IndexedDB calls that all of IDB's prefs/etc. are
  // initialized.
  Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());

  auto scopeExit =
      MakeScopeExit([&] { ExceptionalErrorTransitionDuringExecWorker(); });

  // Verify the the RemoteWorker should be really allowed to run in this
  // process, and fail if it shouldn't (This shouldn't normally happen,
  // unless the RemoteWorkerData has been tempered in the process it was
  // sent from).
  if (!RemoteWorkerManager::IsRemoteTypeAllowed(aData)) {
    return NS_ERROR_UNEXPECTED;
  }

  auto principalOrErr = PrincipalInfoToPrincipal(aData.principalInfo());
  if (NS_WARN_IF(principalOrErr.isErr())) {
    return principalOrErr.unwrapErr();
  }

  nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();

  auto loadingPrincipalOrErr =
      PrincipalInfoToPrincipal(aData.loadingPrincipalInfo());
  if (NS_WARN_IF(loadingPrincipalOrErr.isErr())) {
    return loadingPrincipalOrErr.unwrapErr();
  }

  auto partitionedPrincipalOrErr =
      PrincipalInfoToPrincipal(aData.partitionedPrincipalInfo());
  if (NS_WARN_IF(partitionedPrincipalOrErr.isErr())) {
    return partitionedPrincipalOrErr.unwrapErr();
  }

  WorkerLoadInfo info;
  info.mBaseURI = DeserializeURI(aData.baseScriptURL());
  info.mResolvedScriptURI = DeserializeURI(aData.resolvedScriptURL());

  info.mPrincipalInfo = MakeUnique<PrincipalInfo>(aData.principalInfo());
  info.mPartitionedPrincipalInfo =
      MakeUnique<PrincipalInfo>(aData.partitionedPrincipalInfo());

  info.mReferrerInfo = aData.referrerInfo();
  info.mDomain = aData.domain();
  info.mTrials = aData.originTrials();
  info.mPrincipal = principal;
  info.mPartitionedPrincipal = partitionedPrincipalOrErr.unwrap();
  info.mLoadingPrincipal = loadingPrincipalOrErr.unwrap();
  info.mStorageAccess = aData.storageAccess();
  info.mUseRegularPrincipal = aData.useRegularPrincipal();
  info.mHasStorageAccessPermissionGranted =
      aData.hasStorageAccessPermissionGranted();
  info.mIsThirdPartyContextToTopWindow = aData.isThirdPartyContextToTopWindow();
  info.mOriginAttributes =
      BasePrincipal::Cast(principal)->OriginAttributesRef();
  info.mShouldResistFingerprinting = aData.shouldResistFingerprinting();
  net::CookieJarSettings::Deserialize(aData.cookieJarSettings(),
                                      getter_AddRefs(info.mCookieJarSettings));
  info.mCookieJarSettingsArgs = aData.cookieJarSettings();

  // Default CSP permissions for now.  These will be overrided if necessary
  // based on the script CSP headers during load in ScriptLoader.
  info.mEvalAllowed = true;
  info.mReportEvalCSPViolations = false;
  info.mWasmEvalAllowed = true;
  info.mReportWasmEvalCSPViolations = false;
  info.mSecureContext = aData.isSecureContext()
                            ? WorkerLoadInfo::eSecureContext
                            : WorkerLoadInfo::eInsecureContext;

  WorkerPrivate::OverrideLoadInfoLoadGroup(info, info.mLoadingPrincipal);

  RefPtr<SharedWorkerInterfaceRequestor> requestor =
      new SharedWorkerInterfaceRequestor();
  info.mInterfaceRequestor->SetOuterRequestor(requestor);

  Maybe<ClientInfo> clientInfo;
  if (aData.clientInfo().isSome()) {
    clientInfo.emplace(ClientInfo(aData.clientInfo().ref()));
  }

  nsresult rv = NS_OK;

  if (clientInfo.isSome()) {
    Maybe<mozilla::ipc::CSPInfo> cspInfo = clientInfo.ref().GetCspInfo();
    if (cspInfo.isSome()) {
      info.mCSP = CSPInfoToCSP(cspInfo.ref(), nullptr);
      info.mCSPInfo = MakeUnique<CSPInfo>();
      rv = CSPToCSPInfo(info.mCSP, info.mCSPInfo.get());
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }

  rv = info.SetPrincipalsAndCSPOnMainThread(
      info.mPrincipal, info.mPartitionedPrincipal, info.mLoadGroup, info.mCSP);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsString workerPrivateId;

  if (mIsServiceWorker) {
    ServiceWorkerData& data = aData.serviceWorkerData().get_ServiceWorkerData();

    MOZ_ASSERT(!data.id().IsEmpty());
    workerPrivateId = std::move(data.id());

    info.mServiceWorkerCacheName = data.cacheName();
    info.mServiceWorkerDescriptor.emplace(data.descriptor());
    info.mServiceWorkerRegistrationDescriptor.emplace(
        data.registrationDescriptor());
    info.mLoadFlags = static_cast<nsLoadFlags>(data.loadFlags());
  } else {
    // Top level workers' main script use the document charset for the script
    // uri encoding.
    rv = ChannelFromScriptURLMainThread(
        info.mLoadingPrincipal, nullptr /* parent document */, info.mLoadGroup,
        info.mResolvedScriptURI, aData.type(), aData.credentials(), clientInfo,
        nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER, info.mCookieJarSettings,
        info.mReferrerInfo, getter_AddRefs(info.mChannel));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  info.mAgentClusterId = aData.agentClusterId();

  AutoJSAPI jsapi;
  jsapi.Init();

  ErrorResult error;
  RefPtr<RemoteWorkerChild> self = this;
  RefPtr<WorkerPrivate> workerPrivate = WorkerPrivate::Constructor(
      jsapi.cx(), aData.originalScriptURL(), false,
      mIsServiceWorker ? WorkerKindService : WorkerKindShared,
      aData.credentials(), aData.type(), aData.name(), VoidCString(), &info,
      error, std::move(workerPrivateId),
      [self](bool aEverRan) {
        self->OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled();
      },
      // This will be invoked here on the main thread when the worker is already
      // fully shutdown.  This replaces a prior approach where we would
      // begin to transition when the worker thread would reach the Canceling
      // state.  This lambda ensures that we not only wait for the Killing state
      // to be reached but that the global shutdown has already occurred.
      [self]() { self->TransitionStateFromCanceledToKilled(); });

  if (NS_WARN_IF(error.Failed())) {
    MOZ_ASSERT(!workerPrivate);

    rv = error.StealNSResult();
    return rv;
  }

  workerPrivate->SetRemoteWorkerController(this);

  // This wants to run as a normal task sequentially after the top level script
  // evaluation, so the hybrid target is the correct choice between hybrid and
  // `ControlEventTarget`.
  nsCOMPtr<nsISerialEventTarget> workerTarget =
      workerPrivate->HybridEventTarget();

  nsCOMPtr<nsIRunnable> runnable = NewCancelableRunnableMethod(
      "InitialzeOnWorker", this, &RemoteWorkerChild::InitializeOnWorker);

  {
    MOZ_ASSERT(workerPrivate);
    auto lock = mState.Lock();
    // We MUST be pending here, so direct access is ok.
    lock->as<Pending>().mWorkerPrivate = std::move(workerPrivate);
  }

  if (mIsServiceWorker) {
    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
        __func__, [workerTarget,
                   initializeWorkerRunnable = std::move(runnable)]() mutable {
          Unused << NS_WARN_IF(NS_FAILED(
              workerTarget->Dispatch(initializeWorkerRunnable.forget())));
        });

    RefPtr<PermissionManager> permissionManager =
        PermissionManager::GetInstance();
    if (!permissionManager) {
      return NS_ERROR_FAILURE;
    }
    permissionManager->WhenPermissionsAvailable(principal, r);
  } else {
    if (NS_WARN_IF(NS_FAILED(workerTarget->Dispatch(runnable.forget())))) {
      rv = NS_ERROR_FAILURE;
      return rv;
    }
  }

  scopeExit.release();

  return NS_OK;
}

void RemoteWorkerChild::RequestWorkerCancellation() {
  MOZ_ASSERT(NS_IsMainThread());

  LOG(("RequestWorkerCancellation[this=%p]", this));

  // We want to ensure that we've requested the worker be canceled.  So if the
  // worker is running, cancel it.  We can't do this with the lock held,
  // however, because our lambdas will want to manipulate the state.
  RefPtr<WorkerPrivate> cancelWith;
  {
    auto lock = mState.Lock();
    if (lock->is<Pending>()) {
      cancelWith = lock->as<Pending>().mWorkerPrivate;
    } else if (lock->is<Running>()) {
      cancelWith = lock->as<Running>().mWorkerPrivate;
    }
  }

  if (cancelWith) {
    cancelWith->Cancel();
  }
}

// This method will be invoked on the worker after the top-level
// CompileScriptRunnable task has succeeded and as long as the worker has not
// been closed/canceled.  There are edge-cases related to cancellation, but we
// have our caller ensure that we are only called as long as the worker's state
// is Running.
//
// (https://bugzilla.mozilla.org/show_bug.cgi?id=1800659 will eliminate
// cancellation, and the documentation around that bug / in design documents
// helps provide more context about this.)
void RemoteWorkerChild::InitializeOnWorker() {
  nsCOMPtr<nsIRunnable> r =
      NewRunnableMethod("TransitionStateToRunning", this,
                        &RemoteWorkerChild::TransitionStateToRunning);
  MOZ_ALWAYS_SUCCEEDS(
      SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
}

RefPtr<GenericNonExclusivePromise> RemoteWorkerChild::GetTerminationPromise() {
  auto launcherData = mLauncherData.Access();
  return launcherData->mTerminationPromise.Ensure(__func__);
}

void RemoteWorkerChild::CreationSucceededOnAnyThread() {
  CreationSucceededOrFailedOnAnyThread(true);
}

void RemoteWorkerChild::CreationFailedOnAnyThread() {
  CreationSucceededOrFailedOnAnyThread(false);
}

void RemoteWorkerChild::CreationSucceededOrFailedOnAnyThread(
    bool aDidCreationSucceed) {
#ifdef DEBUG
  {
    auto lock = mState.Lock();
    MOZ_ASSERT_IF(aDidCreationSucceed, lock->is<Running>());
    MOZ_ASSERT_IF(!aDidCreationSucceed, lock->is<Killed>());
  }
#endif

  RefPtr<RemoteWorkerChild> self = this;

  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
      __func__,
      [self = std::move(self), didCreationSucceed = aDidCreationSucceed] {
        auto launcherData = self->mLauncherData.Access();

        if (!self->CanSend() || launcherData->mDidSendCreated) {
          return;
        }

        Unused << self->SendCreated(didCreationSucceed);
        launcherData->mDidSendCreated = true;
      });

  GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}

void RemoteWorkerChild::CloseWorkerOnMainThread() {
  AssertIsOnMainThread();

  LOG(("CloseWorkerOnMainThread[this=%p]", this));

  // We can't hold the state lock while calling WorkerPrivate::Cancel because
  // the lambda callback will want to touch the state, so save off the
  // WorkerPrivate so we can cancel it (if we need to cancel it).
  RefPtr<WorkerPrivate> cancelWith;
  {
    auto lock = mState.Lock();

    if (lock->is<Pending>()) {
      cancelWith = lock->as<Pending>().mWorkerPrivate;
      // There should be no way for this code to run before we
      // ExecWorkerOnMainThread runs, which means that either it should have
      // set a WorkerPrivate on Pending, or its error handling should already
      // have transitioned us to Canceled and Killing in that order.  (It's
      // also possible that it assigned a WorkerPrivate and subsequently we
      // transitioned to Running, which would put us in the next branch.)
      MOZ_DIAGNOSTIC_ASSERT(cancelWith);
    } else if (lock->is<Running>()) {
      cancelWith = lock->as<Running>().mWorkerPrivate;
    }
  }

  // It's very okay for us to not have a WorkerPrivate here if we've already
  // canceled the worker or if errors happened.
  if (cancelWith) {
    cancelWith->Cancel();
  }
}

/**
 * Error reporting method
 */
void RemoteWorkerChild::ErrorPropagation(const ErrorValue& aValue) {
  MOZ_ASSERT(GetActorEventTarget()->IsOnCurrentThread());

  if (!CanSend()) {
    return;
  }

  Unused << SendError(aValue);
}

void RemoteWorkerChild::ErrorPropagationDispatch(nsresult aError) {
  MOZ_ASSERT(NS_FAILED(aError));

  RefPtr<RemoteWorkerChild> self = this;
  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
      "RemoteWorkerChild::ErrorPropagationDispatch",
      [self = std::move(self), aError]() { self->ErrorPropagation(aError); });

  GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}

void RemoteWorkerChild::ErrorPropagationOnMainThread(
    const WorkerErrorReport* aReport, bool aIsErrorEvent) {
  AssertIsOnMainThread();

  ErrorValue value;
  if (aIsErrorEvent) {
    ErrorData data(
        aReport->mIsWarning, aReport->mLineNumber, aReport->mColumnNumber,
        aReport->mMessage, aReport->mFilename, aReport->mLine,
        TransformIntoNewArray(aReport->mNotes, [](const WorkerErrorNote& note) {
          return ErrorDataNote(note.mLineNumber, note.mColumnNumber,
                               note.mMessage, note.mFilename);
        }));
    value = data;
  } else {
    value = void_t();
  }

  RefPtr<RemoteWorkerChild> self = this;
  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
      "RemoteWorkerChild::ErrorPropagationOnMainThread",
      [self = std::move(self), value]() { self->ErrorPropagation(value); });

  GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}

void RemoteWorkerChild::NotifyLock(bool aCreated) {
  nsCOMPtr<nsIRunnable> r =
      NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] {
        if (!self->CanSend()) {
          return;
        }

        Unused << self->SendNotifyLock(aCreated);
      });

  GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}

void RemoteWorkerChild::NotifyWebTransport(bool aCreated) {
  nsCOMPtr<nsIRunnable> r =
      NS_NewRunnableFunction(__func__, [self = RefPtr(this), aCreated] {
        if (!self->CanSend()) {
          return;
        }

        Unused << self->SendNotifyWebTransport(aCreated);
      });

  GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}

void RemoteWorkerChild::FlushReportsOnMainThread(
    nsIConsoleReportCollector* aReporter) {
  AssertIsOnMainThread();

  bool reportErrorToBrowserConsole = true;

  // Flush the reports.
  for (uint32_t i = 0, len = mWindowIDs.Length(); i < len; ++i) {
    aReporter->FlushReportsToConsole(
        mWindowIDs[i], nsIConsoleReportCollector::ReportAction::Save);
    reportErrorToBrowserConsole = false;
  }

  // Finally report to browser console if there is no any window.
  if (reportErrorToBrowserConsole) {
    aReporter->FlushReportsToConsole(0);
    return;
  }

  aReporter->ClearConsoleReports();
}

/**
 * Worker state transition methods
 */
RemoteWorkerChild::WorkerPrivateAccessibleState::
    ~WorkerPrivateAccessibleState() {
  // We should now only be performing state transitions on the main thread, so
  // we should assert we're only releasing on the main thread.
  MOZ_ASSERT(!mWorkerPrivate || NS_IsMainThread());
  // mWorkerPrivate can be safely released on the main thread.
  if (!mWorkerPrivate || NS_IsMainThread()) {
    return;
  }

  // But as a backstop, do proxy the release to the main thread.
  NS_ReleaseOnMainThread(
      "RemoteWorkerChild::WorkerPrivateAccessibleState::mWorkerPrivate",
      mWorkerPrivate.forget());
}

void RemoteWorkerChild::
    OnWorkerCancellationTransitionStateFromPendingOrRunningToCanceled() {
  auto lock = mState.Lock();

  LOG(("TransitionStateFromPendingOrRunningToCanceled[this=%p]", this));

  if (lock->is<Pending>()) {
    TransitionStateFromPendingToCanceled(lock.ref());
  } else if (lock->is<Running>()) {
    *lock = VariantType<Canceled>();
  } else {
    MOZ_ASSERT(false, "State should have been Pending or Running");
  }
}

void RemoteWorkerChild::TransitionStateFromPendingToCanceled(State& aState) {
  AssertIsOnMainThread();
  MOZ_ASSERT(aState.is<Pending>());
  LOG(("TransitionStateFromPendingToCanceled[this=%p]", this));

  CancelAllPendingOps(aState);

  aState = VariantType<Canceled>();
}

void RemoteWorkerChild::TransitionStateFromCanceledToKilled() {
  AssertIsOnMainThread();

  LOG(("TransitionStateFromCanceledToKilled[this=%p]", this));

  auto lock = mState.Lock();
  MOZ_ASSERT(lock->is<Canceled>());

  *lock = VariantType<Killed>();

  RefPtr<RemoteWorkerChild> self = this;
  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [self]() {
    auto launcherData = self->mLauncherData.Access();

    // (We maintain the historical ordering of resolving this promise prior to
    // calling SendClose, however the previous code used 2 separate dispatches
    // to this thread for the resolve and SendClose, and there inherently
    // would be a race between the runnables resulting from the resolved
    // promise and the promise containing the call to SendClose.  Now it's
    // entirely clear that our call to SendClose will effectively run before
    // any of the resolved promises are able to do anything.)
    launcherData->mTerminationPromise.ResolveIfExists(true, __func__);

    if (self->CanSend()) {
      Unused << self->SendClose();
    }
  });

  GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
}

void RemoteWorkerChild::TransitionStateToRunning() {
  AssertIsOnMainThread();

  LOG(("TransitionStateToRunning[this=%p]", this));

  nsTArray<RefPtr<Op>> pendingOps;

  {
    auto lock = mState.Lock();

    // Because this is an async notification sent from the worker to the main
    // thread, it's very possible that we've already decided on the main thread
    // to transition to the Canceled state, in which case there is nothing for
    // us to do here.
    if (!lock->is<Pending>()) {
      LOG(("State is already not pending in TransitionStateToRunning[this=%p]!",
           this));
      return;
    }

    RefPtr<WorkerPrivate> workerPrivate =
        std::move(lock->as<Pending>().mWorkerPrivate);
    pendingOps = std::move(lock->as<Pending>().mPendingOps);

    // Move the worker private into place to avoid gratuitous ref churn; prior
    // comments here suggest the Variant can't accept a move.
    *lock = VariantType<Running>();
    lock->as<Running>().mWorkerPrivate = std::move(workerPrivate);
  }

  CreationSucceededOnAnyThread();

  RefPtr<RemoteWorkerChild> self = this;
  for (auto& op : pendingOps) {
    op->StartOnMainThread(self);
  }
}

void RemoteWorkerChild::ExceptionalErrorTransitionDuringExecWorker() {
  AssertIsOnMainThread();

  LOG(("ExceptionalErrorTransitionDuringExecWorker[this=%p]", this));

  // This method is called synchronously by ExecWorkerOnMainThread in the event
  // of any error.  Because we only transition to Running on the main thread
  // as the result of a notification from the worker, we know our state will be
  // Pending, but mWorkerPrivate may or may not be null, as we may not have
  // gotten to spawning the worker.
  //
  // In the event the worker exists, we need to Cancel() it.  We must do this
  // without the lock held because our call to Cancel() will invoke the
  // cancellation callback we created which will call TransitionStateToCanceled,
  // and we can't be holding the lock when that happens.

  RefPtr<WorkerPrivate> cancelWith;

  {
    auto lock = mState.Lock();

    MOZ_ASSERT(lock->is<Pending>());
    if (lock->is<Pending>()) {
      cancelWith = lock->as<Pending>().mWorkerPrivate;
      if (!cancelWith) {
        // The worker wasn't actually created, so we should synthetically
        // transition to canceled and onward.  Since we have the lock,
        // perform the transition now for clarity, but we'll handle the rest of
        // this case after dropping the lock.
        TransitionStateFromPendingToCanceled(lock.ref());
      }
    }
  }

  if (cancelWith) {
    cancelWith->Cancel();
  } else {
    TransitionStateFromCanceledToKilled();
    CreationFailedOnAnyThread();
  }
}

/**
 * Operation execution classes/methods
 */
class RemoteWorkerChild::SharedWorkerOp : public RemoteWorkerChild::Op {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWorkerOp, override)

  explicit SharedWorkerOp(RemoteWorkerOp&& aOp) : mOp(std::move(aOp)) {}

  bool MaybeStart(RemoteWorkerChild* aOwner,
                  RemoteWorkerChild::State& aState) override {
    MOZ_ASSERT(!mStarted);
    MOZ_ASSERT(aOwner);
    // Thread: We are on the Worker Launcher thread.

    // Return false, indicating we should queue this op if our current state is
    // pending and this isn't a termination op (which should skip the line).
    if (aState.is<Pending>() && !IsTerminationOp()) {
      return false;
    }

    // If the worker is already shutting down (which should be unexpected
    // because we should be told new operations after a termination op), just
    // return true to indicate the op should be discarded.
    if (aState.is<Canceled>() || aState.is<Killed>()) {
#ifdef DEBUG
      mStarted = true;
#endif
      if (mOp.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) {
        MessagePort::ForceClose(
            mOp.get_RemoteWorkerPortIdentifierOp().portIdentifier());
      }
      return true;
    }

    MOZ_ASSERT(aState.is<Running>() || IsTerminationOp());

    RefPtr<SharedWorkerOp> self = this;
    RefPtr<RemoteWorkerChild> owner = aOwner;

    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
        __func__, [self = std::move(self), owner = std::move(owner)]() mutable {
          {
            auto lock = owner->mState.Lock();

            if (NS_WARN_IF(lock->is<Canceled>() || lock->is<Killed>())) {
              self->Cancel();
              // Worker has already canceled, force close the MessagePort.
              if (self->mOp.type() ==
                  RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) {
                MessagePort::ForceClose(
                    self->mOp.get_RemoteWorkerPortIdentifierOp()
                        .portIdentifier());
              }
              return;
            }
          }

          self->StartOnMainThread(owner);
        });

    MOZ_ALWAYS_SUCCEEDS(
        SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));

#ifdef DEBUG
    mStarted = true;
#endif

    return true;
  }

  void StartOnMainThread(RefPtr<RemoteWorkerChild>& aOwner) final {
    using Running = RemoteWorkerChild::Running;

    AssertIsOnMainThread();

    if (IsTerminationOp()) {
      aOwner->CloseWorkerOnMainThread();
      return;
    }

    auto lock = aOwner->mState.Lock();
    MOZ_ASSERT(lock->is<Running>());
    if (!lock->is<Running>()) {
      aOwner->ErrorPropagationDispatch(NS_ERROR_DOM_INVALID_STATE_ERR);
      return;
    }

    RefPtr<WorkerPrivate> workerPrivate = lock->as<Running>().mWorkerPrivate;

    MOZ_ASSERT(workerPrivate);

    if (mOp.type() == RemoteWorkerOp::TRemoteWorkerSuspendOp) {
      workerPrivate->ParentWindowPaused();
    } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerResumeOp) {
      workerPrivate->ParentWindowResumed();
    } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerFreezeOp) {
      workerPrivate->Freeze(nullptr);
    } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerThawOp) {
      workerPrivate->Thaw(nullptr);
    } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerPortIdentifierOp) {
      RefPtr<MessagePortIdentifierRunnable> r =
          new MessagePortIdentifierRunnable(
              workerPrivate, aOwner,
              mOp.get_RemoteWorkerPortIdentifierOp().portIdentifier());
      if (NS_WARN_IF(!r->Dispatch())) {
        aOwner->ErrorPropagationDispatch(NS_ERROR_FAILURE);
      }
    } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerAddWindowIDOp) {
      aOwner->mWindowIDs.AppendElement(
          mOp.get_RemoteWorkerAddWindowIDOp().windowID());
    } else if (mOp.type() == RemoteWorkerOp::TRemoteWorkerRemoveWindowIDOp) {
      aOwner->mWindowIDs.RemoveElement(
          mOp.get_RemoteWorkerRemoveWindowIDOp().windowID());
    } else {
      MOZ_CRASH("Unknown RemoteWorkerOp type!");
    }
  }

  void Cancel() override {
#ifdef DEBUG
    mStarted = true;
#endif
  }

 private:
  ~SharedWorkerOp() { MOZ_ASSERT(mStarted); }

  bool IsTerminationOp() const {
    return mOp.type() == RemoteWorkerOp::TRemoteWorkerTerminateOp;
  }

  RemoteWorkerOp mOp;

#ifdef DEBUG
  bool mStarted = false;
#endif
};

void RemoteWorkerChild::AddPortIdentifier(
    JSContext* aCx, WorkerPrivate* aWorkerPrivate,
    UniqueMessagePortId& aPortIdentifier) {
  if (NS_WARN_IF(!aWorkerPrivate->ConnectMessagePort(aCx, aPortIdentifier))) {
    ErrorPropagationDispatch(NS_ERROR_FAILURE);
  }
}

void RemoteWorkerChild::CancelAllPendingOps(State& aState) {
  MOZ_ASSERT(aState.is<Pending>());

  auto pendingOps = std::move(aState.as<Pending>().mPendingOps);

  for (auto& op : pendingOps) {
    op->Cancel();
  }
}

void RemoteWorkerChild::MaybeStartOp(RefPtr<Op>&& aOp) {
  MOZ_ASSERT(aOp);

  auto lock = mState.Lock();

  if (!aOp->MaybeStart(this, lock.ref())) {
    // Maybestart returns false only if we are <Pending>.
    lock->as<Pending>().mPendingOps.AppendElement(std::move(aOp));
  }
}

IPCResult RemoteWorkerChild::RecvExecOp(RemoteWorkerOp&& aOp) {
  MOZ_ASSERT(!mIsServiceWorker);

  MaybeStartOp(new SharedWorkerOp(std::move(aOp)));

  return IPC_OK();
}

IPCResult RemoteWorkerChild::RecvExecServiceWorkerOp(
    ServiceWorkerOpArgs&& aArgs, ExecServiceWorkerOpResolver&& aResolve) {
  MOZ_ASSERT(mIsServiceWorker);
  MOZ_ASSERT(
      aArgs.type() !=
          ServiceWorkerOpArgs::TParentToChildServiceWorkerFetchEventOpArgs,
      "FetchEvent operations should be sent via PFetchEventOp(Proxy) actors!");

  MaybeReportServiceWorkerShutdownProgress(aArgs);

  MaybeStartOp(ServiceWorkerOp::Create(std::move(aArgs), std::move(aResolve)));

  return IPC_OK();
}

RefPtr<GenericPromise>
RemoteWorkerChild::MaybeSendSetServiceWorkerSkipWaitingFlag() {
  RefPtr<GenericPromise::Private> promise =
      new GenericPromise::Private(__func__);

  RefPtr<RemoteWorkerChild> self = this;

  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(__func__, [self = std::move(
                                                                  self),
                                                              promise] {
    if (!self->CanSend()) {
      promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
      return;
    }

    self->SendSetServiceWorkerSkipWaitingFlag()->Then(
        GetCurrentSerialEventTarget(), __func__,
        [promise](
            const SetServiceWorkerSkipWaitingFlagPromise::ResolveOrRejectValue&
                aResult) {
          if (NS_WARN_IF(aResult.IsReject())) {
            promise->Reject(NS_ERROR_DOM_ABORT_ERR, __func__);
            return;
          }

          promise->Resolve(aResult.ResolveValue(), __func__);
        });
  });

  GetActorEventTarget()->Dispatch(r.forget(), NS_DISPATCH_NORMAL);

  return promise;
}

/**
 * PFetchEventOpProxy methods
 */
already_AddRefed<PFetchEventOpProxyChild>
RemoteWorkerChild::AllocPFetchEventOpProxyChild(
    const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) {
  return RefPtr{new FetchEventOpProxyChild()}.forget();
}

IPCResult RemoteWorkerChild::RecvPFetchEventOpProxyConstructor(
    PFetchEventOpProxyChild* aActor,
    const ParentToChildServiceWorkerFetchEventOpArgs& aArgs) {
  MOZ_ASSERT(aActor);

  (static_cast<FetchEventOpProxyChild*>(aActor))->Initialize(aArgs);

  return IPC_OK();
}

}  // namespace dom
}  // namespace mozilla
