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

#include <utility>

#include "MessageEventRunnable.h"
#include "RuntimeService.h"
#include "ScriptLoader.h"
#include "WorkerCSPEventListener.h"
#include "WorkerDebugger.h"
#include "WorkerDebuggerManager.h"
#include "WorkerError.h"
#include "WorkerEventTarget.h"
#include "WorkerNavigator.h"
#include "WorkerRef.h"
#include "WorkerRunnable.h"
#include "WorkerThread.h"
#include "js/CallAndConstruct.h"  // JS_CallFunctionValue
#include "js/CompilationAndEvaluation.h"
#include "js/ContextOptions.h"
#include "js/Exception.h"
#include "js/LocaleSensitive.h"
#include "js/MemoryMetrics.h"
#include "js/SourceText.h"
#include "js/friend/ErrorMessages.h"  // JSMSG_OUT_OF_MEMORY
#include "js/friend/MicroTask.h"
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/ExtensionPolicyService.h"
#include "mozilla/Mutex.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/Result.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_javascript.h"
#include "mozilla/StorageAccess.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "mozilla/ThreadEventQueue.h"
#include "mozilla/ThreadSafety.h"
#include "mozilla/ThrottledEventQueue.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/CallbackDebuggerNotification.h"
#include "mozilla/dom/ClientManager.h"
#include "mozilla/dom/ClientState.h"
#include "mozilla/dom/Console.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/FeaturePolicyUtils.h"
#include "mozilla/dom/FunctionBinding.h"
#include "mozilla/dom/IndexedDatabaseManager.h"
#include "mozilla/dom/JSExecutionManager.h"
#include "mozilla/dom/MessageEvent.h"
#include "mozilla/dom/MessageEventBinding.h"
#include "mozilla/dom/MessagePort.h"
#include "mozilla/dom/MessagePortBinding.h"
#include "mozilla/dom/Navigator.h"
#include "mozilla/dom/PRemoteWorkerDebuggerParent.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PerformanceStorageWorker.h"
#include "mozilla/dom/PolicyContainer.h"
#include "mozilla/dom/PromiseDebugging.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/dom/RemoteWorkerChild.h"
#include "mozilla/dom/RemoteWorkerDebuggerChild.h"
#include "mozilla/dom/RemoteWorkerNonLifeCycleOpControllerChild.h"
#include "mozilla/dom/RemoteWorkerService.h"
#include "mozilla/dom/ReportDeliver.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/ServiceWorkerEvents.h"
#include "mozilla/dom/ServiceWorkerManager.h"
#include "mozilla/dom/SimpleGlobalObject.h"
#include "mozilla/dom/TimeoutHandler.h"
#include "mozilla/dom/TimeoutManager.h"
#include "mozilla/dom/UseCounterMetrics.h"
#include "mozilla/dom/WebTaskScheduler.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WorkerBinding.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/dom/WorkerStatus.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/extensions/ExtensionBrowser.h"  // extensions::Create{AndDispatchInitWorkerContext,WorkerLoaded,WorkerDestroyed}Runnable
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/glean/DomUseCounterMetrics.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/net/CookieJarSettings.h"
#include "nsContentSecurityManager.h"
#include "nsCycleCollector.h"
#include "nsGlobalWindowInner.h"
#include "nsIDUtils.h"
#include "nsIEventTarget.h"
#include "nsIFile.h"
#include "nsIHttpChannel.h"
#include "nsIMemoryReporter.h"
#include "nsIPermissionManager.h"
#include "nsIProtocolHandler.h"
#include "nsIScriptError.h"
#include "nsIURI.h"
#include "nsIURL.h"
#include "nsIUUIDGenerator.h"
#include "nsNetUtil.h"
#include "nsPresContext.h"
#include "nsPrintfCString.h"
#include "nsProxyRelease.h"
#include "nsQueryObject.h"
#include "nsRFPService.h"
#include "nsSandboxFlags.h"
#include "nsThreadManager.h"
#include "nsThreadUtils.h"
#include "nsUTF8Utils.h"

#ifdef XP_WIN
#  undef PostMessage
#endif

// JS_MaybeGC will run once every second during normal execution.
#define PERIODIC_GC_TIMER_DELAY_SEC 1

// A shrinking GC will run five seconds after the last event is processed.
#define IDLE_GC_TIMER_DELAY_SEC 5

// Arbitrary short grace period for the currently running task to finish.
// There isn't an advantage for us to immediately interrupt JS in the middle of
// execution that might yield soon, especially when there is so much async
// variability in the data flow prior to us deciding to trigger the interrupt.
#define DEBUGGER_RUNNABLE_INTERRUPT_AFTER_MS 250

static mozilla::LazyLogModule sWorkerPrivateLog("WorkerPrivate");
static mozilla::LazyLogModule sWorkerTimeoutsLog("WorkerTimeouts");
static mozilla::LazyLogModule gFingerprinterDetection("FingerprinterDetection");

mozilla::LogModule* WorkerLog() { return sWorkerPrivateLog; }

mozilla::LogModule* TimeoutsLog() { return sWorkerTimeoutsLog; }

#ifdef LOG
#  undef LOG
#endif
#ifdef LOGV
#  undef LOGV
#endif
#define LOG(log, _args) MOZ_LOG(log, LogLevel::Debug, _args);
#define LOGV(args) MOZ_LOG(sWorkerPrivateLog, LogLevel::Verbose, args);

namespace mozilla {

using namespace ipc;

namespace dom {

using namespace workerinternals;

MOZ_DEFINE_MALLOC_SIZE_OF(JsWorkerMallocSizeOf)

namespace {

#ifdef DEBUG

const nsIID kDEBUGWorkerEventTargetIID = {
    0xccaba3fa,
    0x5be2,
    0x4de2,
    {0xba, 0x87, 0x3b, 0x3b, 0x5b, 0x1d, 0x5, 0xfb}};

#endif

template <class T>
class UniquePtrComparator {
  using A = UniquePtr<T>;
  using B = T*;

 public:
  bool Equals(const A& a, const A& b) const {
    return (a && b) ? (*a == *b) : (!a && !b);
  }
  bool LessThan(const A& a, const A& b) const {
    return (a && b) ? (*a < *b) : !!b;
  }
};

template <class T>
inline UniquePtrComparator<T> GetUniquePtrComparator(
    const nsTArray<UniquePtr<T>>&) {
  return UniquePtrComparator<T>();
}

// This class is used to wrap any runnables that the worker receives via the
// nsIEventTarget::Dispatch() method (either from NS_DispatchToCurrentThread or
// from the worker's EventTarget).
class ExternalRunnableWrapper final : public WorkerThreadRunnable {
  nsCOMPtr<nsIRunnable> mWrappedRunnable;

 public:
  ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate,
                          nsIRunnable* aWrappedRunnable)
      : WorkerThreadRunnable("ExternalRunnableWrapper"),
        mWrappedRunnable(aWrappedRunnable) {
    MOZ_ASSERT(aWorkerPrivate);
    MOZ_ASSERT(aWrappedRunnable);
  }

  NS_INLINE_DECL_REFCOUNTING_INHERITED(ExternalRunnableWrapper,
                                       WorkerThreadRunnable)

 private:
  ~ExternalRunnableWrapper() = default;

  virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
    // Silence bad assertions.
    return true;
  }

  virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
                            bool aDispatchResult) override {
    // Silence bad assertions.
  }

  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    nsresult rv = mWrappedRunnable->Run();
    mWrappedRunnable = nullptr;
    if (NS_FAILED(rv)) {
      if (!JS_IsExceptionPending(aCx)) {
        Throw(aCx, rv);
      }
      return false;
    }
    return true;
  }

  nsresult Cancel() override {
    nsCOMPtr<nsIDiscardableRunnable> doomed =
        do_QueryInterface(mWrappedRunnable);
    if (doomed) {
      doomed->OnDiscard();
    }
    mWrappedRunnable = nullptr;
    return NS_OK;
  }

#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
  NS_IMETHOD GetName(nsACString& aName) override {
    aName.AssignLiteral("ExternalRunnableWrapper(");
    if (nsCOMPtr<nsINamed> named = do_QueryInterface(mWrappedRunnable)) {
      nsAutoCString containedName;
      named->GetName(containedName);
      aName.Append(containedName);
    } else {
      aName.AppendLiteral("?");
    }
    aName.AppendLiteral(")");
    return NS_OK;
  }
#endif
};

struct WindowAction {
  nsPIDOMWindowInner* mWindow;
  bool mDefaultAction;

  MOZ_IMPLICIT WindowAction(nsPIDOMWindowInner* aWindow)
      : mWindow(aWindow), mDefaultAction(true) {}

  bool operator==(const WindowAction& aOther) const {
    return mWindow == aOther.mWindow;
  }
};

class WorkerFinishedRunnable final : public WorkerControlRunnable {
  WorkerPrivate* mFinishedWorker;

 public:
  WorkerFinishedRunnable(WorkerPrivate* aWorkerPrivate,
                         WorkerPrivate* aFinishedWorker)
      : WorkerControlRunnable("WorkerFinishedRunnable"),
        mFinishedWorker(aFinishedWorker) {
    aFinishedWorker->IncreaseWorkerFinishedRunnableCount();
  }

 private:
  virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
    // Silence bad assertions.
    return true;
  }

  virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
                            bool aDispatchResult) override {
    // Silence bad assertions.
  }

  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    // This may block on the main thread.
    AutoYieldJSThreadExecution yield;

    mFinishedWorker->DecreaseWorkerFinishedRunnableCount();

    if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
      NS_WARNING("Failed to dispatch, going to leak!");
    }

    RuntimeService* runtime = RuntimeService::GetService();
    NS_ASSERTION(runtime, "This should never be null!");

    // Remote workers tear down their debugger on the worker thread during
    // shutdown; only the local debugger needs unregistering here.
    if (!mFinishedWorker->UseRemoteDebugger()) {
      mFinishedWorker->DisableDebugger();
    }

    runtime->UnregisterWorker(*mFinishedWorker);

    mFinishedWorker->ClearSelfAndParentEventTargetRef();
    return true;
  }
};

class TopLevelWorkerFinishedRunnable final : public Runnable {
  WorkerPrivate* mFinishedWorker;

 public:
  explicit TopLevelWorkerFinishedRunnable(WorkerPrivate* aFinishedWorker)
      : mozilla::Runnable("TopLevelWorkerFinishedRunnable"),
        mFinishedWorker(aFinishedWorker) {
    aFinishedWorker->AssertIsOnWorkerThread();
    aFinishedWorker->IncreaseTopLevelWorkerFinishedRunnableCount();
  }

  NS_INLINE_DECL_REFCOUNTING_INHERITED(TopLevelWorkerFinishedRunnable, Runnable)

 private:
  ~TopLevelWorkerFinishedRunnable() = default;

  NS_IMETHOD
  Run() override {
    AssertIsOnMainThread();

    mFinishedWorker->DecreaseTopLevelWorkerFinishedRunnableCount();

    RuntimeService* runtime = RuntimeService::GetService();
    MOZ_ASSERT(runtime);

    // Remote workers tear down their debugger on the worker thread during
    // shutdown; only the local debugger needs unregistering here.
    if (!mFinishedWorker->UseRemoteDebugger()) {
      mFinishedWorker->DisableDebugger();
    }

    runtime->UnregisterWorker(*mFinishedWorker);

    if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
      NS_WARNING("Failed to dispatch, going to leak!");
    }

    mFinishedWorker->ClearSelfAndParentEventTargetRef();
    return NS_OK;
  }
};

class CompileScriptRunnable final : public WorkerDebuggeeRunnable {
  nsString mScriptURL;
  const mozilla::Encoding* mDocumentEncoding;
  UniquePtr<SerializedStackHolder> mOriginStack;

 public:
  explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate,
                                 UniquePtr<SerializedStackHolder> aOriginStack,
                                 const nsAString& aScriptURL,
                                 const mozilla::Encoding* aDocumentEncoding)
      : WorkerDebuggeeRunnable("CompileScriptRunnable"),
        mScriptURL(aScriptURL),
        mDocumentEncoding(aDocumentEncoding),
        mOriginStack(aOriginStack.release()) {}

 private:
  // We can't implement PreRun effectively, because at the point when that would
  // run we have not yet done our load so don't know things like our final
  // principal and whatnot.

  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->AssertIsOnWorkerThread();

    WorkerGlobalScope* globalScope =
        aWorkerPrivate->GetOrCreateGlobalScope(aCx);
    if (NS_WARN_IF(!globalScope)) {
      return false;
    }

    if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) {
      return false;
    }

    ErrorResult rv;
    workerinternals::LoadMainScript(aWorkerPrivate, std::move(mOriginStack),
                                    mScriptURL, WorkerScript, rv,
                                    mDocumentEncoding);

    if (aWorkerPrivate->ExtensionAPIAllowed()) {
      MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
      RefPtr<Runnable> extWorkerRunnable =
          extensions::CreateWorkerLoadedRunnable(
              aWorkerPrivate->ServiceWorkerID(), aWorkerPrivate->GetBaseURI());
      // Dispatch as a low priority runnable.
      if (NS_FAILED(aWorkerPrivate->DispatchToMainThreadForMessaging(
              extWorkerRunnable.forget()))) {
        NS_WARNING(
            "Failed to dispatch runnable to notify extensions worker loaded");
      }
    }

    rv.WouldReportJSException();
    // Explicitly ignore NS_BINDING_ABORTED on rv.  Or more precisely, still
    // return false and don't SetWorkerScriptExecutedSuccessfully() in that
    // case, but don't throw anything on aCx.  The idea is to not dispatch error
    // events if our load is canceled with that error code.
    if (rv.ErrorCodeIs(NS_BINDING_ABORTED)) {
      rv.SuppressException();
      return false;
    }

    // Make sure to propagate exceptions from rv onto aCx, so that they will get
    // reported after we return.  We want to propagate just JS exceptions,
    // because all the other errors are handled when the script is loaded.
    // See: https://dom.spec.whatwg.org/#concept-event-fire
    if (rv.Failed() && !rv.IsJSException()) {
      WorkerErrorReport::CreateAndDispatchGenericErrorRunnableToParent(
          aWorkerPrivate);
      rv.SuppressException();
      return false;
    }

    // This is a little dumb, but aCx is in the null realm here because we
    // set it up that way in our Run(), since we had not created the global at
    // that point yet.  So we need to enter the realm of our global,
    // because setting a pending exception on aCx involves wrapping into its
    // current compartment.  Luckily we have a global now.
    JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject());
    if (rv.MaybeSetPendingException(aCx)) {
      // In the event of an uncaught exception, the worker should still keep
      // running (return true) but should not be marked as having executed
      // successfully (which will cause ServiceWorker installation to fail).
      // In previous error handling cases in this method, we return false (to
      // trigger CloseInternal) because the global is not in an operable
      // state at all.
      //
      // For ServiceWorkers, this would correspond to the "Run Service Worker"
      // algorithm returning an "abrupt completion" and _not_ failure.
      //
      // For DedicatedWorkers and SharedWorkers, this would correspond to the
      // "run a worker" algorithm disregarding the return value of "run the
      // classic script"/"run the module script" in step 24:
      //
      // "If script is a classic script, then run the classic script script.
      // Otherwise, it is a module script; run the module script script."
      return true;
    }

    aWorkerPrivate->SetWorkerScriptExecutedSuccessfully();
    return true;
  }

  void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
               bool aRunResult) override {
    if (!aRunResult) {
      aWorkerPrivate->CloseInternal();
    }
    WorkerThreadRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
  }
};

class NotifyRunnable final : public WorkerControlRunnable {
  WorkerStatus mStatus;

 public:
  NotifyRunnable(WorkerPrivate* aWorkerPrivate, WorkerStatus aStatus)
      : WorkerControlRunnable("NotifyRunnable"), mStatus(aStatus) {
    MOZ_ASSERT(aStatus == Closing || aStatus == Canceling ||
               aStatus == Killing);
  }

 private:
  virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->AssertIsOnParentThread();
    return true;
  }

  virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
                            bool aDispatchResult) override {
    aWorkerPrivate->AssertIsOnParentThread();
  }

  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    return aWorkerPrivate->NotifyInternal(mStatus);
  }

  virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
                       bool aRunResult) override {}
};

class FreezeRunnable final : public WorkerControlRunnable {
 public:
  explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate)
      : WorkerControlRunnable("FreezeRunnable") {}

 private:
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    return aWorkerPrivate->FreezeInternal();
  }
};

class ThawRunnable final : public WorkerControlRunnable {
 public:
  explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
      : WorkerControlRunnable("ThawRunnable") {}

 private:
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    return aWorkerPrivate->ThawInternal();
  }
};

class ChangeBackgroundStateRunnable final : public WorkerControlRunnable {
 public:
  ChangeBackgroundStateRunnable() = delete;
  explicit ChangeBackgroundStateRunnable(WorkerPrivate* aWorkerPrivate) =
      delete;
  ChangeBackgroundStateRunnable(WorkerPrivate* aWorkerPrivate,
                                bool aIsBackground)
      : WorkerControlRunnable("ChangeBackgroundStateRunnable"),
        mIsBackground(aIsBackground) {}

 private:
  bool mIsBackground = false;
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    return aWorkerPrivate->ChangeBackgroundStateInternal(mIsBackground);
  }
};

class ChangePlaybackStateRunnable final : public WorkerControlRunnable {
 public:
  ChangePlaybackStateRunnable() = delete;
  explicit ChangePlaybackStateRunnable(WorkerPrivate* aWorkerPrivate) = delete;
  ChangePlaybackStateRunnable(WorkerPrivate* aWorkerPrivate,
                              bool aIsPlayingAudio)
      : WorkerControlRunnable("ChangePlaybackStateRunnable"),
        mIsPlayingAudio(aIsPlayingAudio) {}

 private:
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    return aWorkerPrivate->ChangePlaybackStateInternal(mIsPlayingAudio);
  }
  bool mIsPlayingAudio = false;
};

class ChangeActivePeerConnectionsRunnable final : public WorkerControlRunnable {
 public:
  ChangeActivePeerConnectionsRunnable() = delete;
  explicit ChangeActivePeerConnectionsRunnable(WorkerPrivate* aWorkerPrivate) =
      delete;
  ChangeActivePeerConnectionsRunnable(WorkerPrivate* aWorkerPrivate,
                                      bool aHasPeerConnections)
      : WorkerControlRunnable("ChangeActivePeerConnectionsRunnable"),
        mConnections(aHasPeerConnections) {}

 private:
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    return aWorkerPrivate->ChangePeerConnectionsInternal(mConnections);
  }
  bool mConnections = false;
};

class PropagateStorageAccessPermissionGrantedRunnable final
    : public WorkerControlRunnable {
 public:
  explicit PropagateStorageAccessPermissionGrantedRunnable(
      WorkerPrivate* aWorkerPrivate)
      : WorkerControlRunnable(
            "PropagateStorageAccessPermissionGrantedRunnable") {}

 private:
  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->PropagateStorageAccessPermissionGrantedInternal();
    return true;
  }
};

class ReportErrorToConsoleRunnable final : public WorkerParentThreadRunnable {
 public:
  // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
  static void Report(WorkerPrivate* aWorkerPrivate, uint32_t aErrorFlags,
                     const nsCString& aCategory, PropertiesFile aFile,
                     const nsCString& aMessageName,
                     const nsTArray<nsString>& aParams,
                     const mozilla::SourceLocation& aLocation) {
    if (aWorkerPrivate) {
      aWorkerPrivate->AssertIsOnWorkerThread();
    } else {
      AssertIsOnMainThread();
    }

    // Now fire a runnable to do the same on the parent's thread if we can.
    if (aWorkerPrivate) {
      RefPtr<ReportErrorToConsoleRunnable> runnable =
          new ReportErrorToConsoleRunnable(aWorkerPrivate, aErrorFlags,
                                           aCategory, aFile, aMessageName,
                                           aParams, aLocation);
      runnable->Dispatch(aWorkerPrivate);
      return;
    }

    // Log a warning to the console.
    nsContentUtils::ReportToConsole(aErrorFlags, aCategory, nullptr, aFile,
                                    aMessageName.get(), aParams, aLocation);
  }

 private:
  ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate,
                               uint32_t aErrorFlags, const nsCString& aCategory,
                               PropertiesFile aFile,
                               const nsCString& aMessageName,
                               const nsTArray<nsString>& aParams,
                               const mozilla::SourceLocation& aLocation)
      : WorkerParentThreadRunnable("ReportErrorToConsoleRunnable"),
        mErrorFlags(aErrorFlags),
        mCategory(aCategory),
        mFile(aFile),
        mMessageName(aMessageName),
        mParams(aParams.Clone()),
        mLocation(aLocation) {}

  virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
                            bool aDispatchResult) override {
    aWorkerPrivate->AssertIsOnWorkerThread();

    // Dispatch may fail if the worker was canceled, no need to report that as
    // an error, so don't call base class PostDispatch.
  }

  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    WorkerPrivate* parent = aWorkerPrivate->GetParent();
    MOZ_ASSERT_IF(!parent, NS_IsMainThread());
    Report(parent, mErrorFlags, mCategory, mFile, mMessageName, mParams,
           mLocation);
    return true;
  }

  const uint32_t mErrorFlags;
  const nsCString mCategory;
  const PropertiesFile mFile;
  const nsCString mMessageName;
  const nsTArray<nsString> mParams;
  const mozilla::SourceLocation mLocation;
};

class DebuggerImmediateRunnable final : public WorkerThreadRunnable {
  RefPtr<dom::Function> mHandler;

 public:
  explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate,
                                     dom::Function& aHandler)
      : WorkerThreadRunnable("DebuggerImmediateRunnable"),
        mHandler(&aHandler) {}

 private:
  virtual bool IsDebuggerRunnable() const override { return true; }

  virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
    // Silence bad assertions.
    return true;
  }

  virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
                            bool aDispatchResult) override {
    // Silence bad assertions.
  }

  // Make as MOZ_CAN_RUN_SCRIPT_BOUNDARY for calling mHandler->Call();
  // Since WorkerRunnable::WorkerRun has not to be MOZ_CAN_RUN_SCRIPT, but
  // DebuggerImmediateRunnable is a special case that must to call the function
  // defined in the debugger script.
  MOZ_CAN_RUN_SCRIPT_BOUNDARY
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    JS::Rooted<JS::Value> rval(aCx);
    IgnoredErrorResult rv;
    MOZ_KnownLive(mHandler)->Call({}, &rval, rv);

    return !rv.Failed();
  }
};

// GetJSContext() is safe on the worker thread
void PeriodicGCTimerCallback(nsITimer* aTimer,
                             void* aClosure) MOZ_NO_THREAD_SAFETY_ANALYSIS {
  auto* workerPrivate = static_cast<WorkerPrivate*>(aClosure);
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
  workerPrivate->AssertIsOnWorkerThread();
  workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
                                        false /* shrinking */,
                                        false /* collect children */);
  LOG(WorkerLog(), ("Worker %p run periodic GC\n", workerPrivate));
}

void IdleGCTimerCallback(nsITimer* aTimer,
                         void* aClosure) MOZ_NO_THREAD_SAFETY_ANALYSIS {
  auto* workerPrivate = static_cast<WorkerPrivate*>(aClosure);
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
  workerPrivate->AssertIsOnWorkerThread();
  workerPrivate->GarbageCollectInternal(workerPrivate->GetJSContext(),
                                        true /* shrinking */,
                                        false /* collect children */);
  LOG(WorkerLog(), ("Worker %p run idle GC\n", workerPrivate));

  // After running idle GC we can cancel the current timers.
  workerPrivate->CancelGCTimers();
}

class UpdateContextOptionsRunnable final : public WorkerControlRunnable {
  JS::ContextOptions mContextOptions;

 public:
  UpdateContextOptionsRunnable(WorkerPrivate* aWorkerPrivate,
                               const JS::ContextOptions& aContextOptions)
      : WorkerControlRunnable("UpdateContextOptionsRunnable"),
        mContextOptions(aContextOptions) {}

 private:
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->UpdateContextOptionsInternal(aCx, mContextOptions);
    return true;
  }
};

class UpdateTimezoneOverrideRunnable final : public WorkerThreadRunnable {
  nsString mTimezone;

 public:
  UpdateTimezoneOverrideRunnable(WorkerPrivate* aWorkerPrivate,
                                 const nsAString& aTimezone)
      : WorkerThreadRunnable("UpdateTimezoneOverrideRunnable"),
        mTimezone(aTimezone) {}

  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->UpdateTimezoneOverrideInternal(aCx, mTimezone);
    return true;
  }
};

class UpdateLanguagesRunnable final : public WorkerThreadRunnable {
  nsTArray<nsString> mLanguages;

 public:
  UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate,
                          const nsTArray<nsString>& aLanguages)
      : WorkerThreadRunnable("UpdateLanguagesRunnable"),
        mLanguages(aLanguages.Clone()) {}

  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->UpdateLanguagesInternal(mLanguages);
    return true;
  }
};

class UpdateLanguageOverrideRunnable final : public WorkerThreadRunnable {
  nsCString mLanguageOverride;
  CopyableTArray<nsString> mResolvedLanguages;

 public:
  UpdateLanguageOverrideRunnable(WorkerPrivate* aWorkerPrivate,
                                 const nsACString& aLanguageOverride,
                                 const nsTArray<nsString>& aResolvedLanguages)
      : WorkerThreadRunnable("UpdateLanguageOverrideRunnable"),
        mLanguageOverride(aLanguageOverride),
        mResolvedLanguages(aResolvedLanguages) {}

  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->UpdateLanguageOverrideInternal(mLanguageOverride,
                                                   mResolvedLanguages);
    return true;
  }
};

class UpdateJSWorkerMemoryParameterRunnable final
    : public WorkerControlRunnable {
  Maybe<uint32_t> mValue;
  JSGCParamKey mKey;

 public:
  UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate,
                                        JSGCParamKey aKey,
                                        Maybe<uint32_t> aValue)
      : WorkerControlRunnable("UpdateJSWorkerMemoryParameterRunnable"),
        mValue(aValue),
        mKey(aKey) {}

 private:
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->UpdateJSWorkerMemoryParameterInternal(aCx, mKey, mValue);
    return true;
  }
};

#ifdef JS_GC_ZEAL
class UpdateGCZealRunnable final : public WorkerControlRunnable {
  uint8_t mGCZeal;
  uint32_t mFrequency;

 public:
  UpdateGCZealRunnable(WorkerPrivate* aWorkerPrivate, uint8_t aGCZeal,
                       uint32_t aFrequency)
      : WorkerControlRunnable("UpdateGCZealRunnable"),
        mGCZeal(aGCZeal),
        mFrequency(aFrequency) {}

 private:
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->UpdateGCZealInternal(aCx, mGCZeal, mFrequency);
    return true;
  }
};
#endif

class SetLowMemoryStateRunnable final : public WorkerControlRunnable {
  bool mState;

 public:
  SetLowMemoryStateRunnable(WorkerPrivate* aWorkerPrivate, bool aState)
      : WorkerControlRunnable("SetLowMemoryStateRunnable"), mState(aState) {}

 private:
  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->SetLowMemoryStateInternal(aCx, mState);
    return true;
  }
};

class GarbageCollectRunnable final : public WorkerControlRunnable {
  bool mShrinking;
  bool mCollectChildren;

 public:
  GarbageCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aShrinking,
                         bool aCollectChildren)
      : WorkerControlRunnable("GarbageCollectRunnable"),
        mShrinking(aShrinking),
        mCollectChildren(aCollectChildren) {}

 private:
  virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
    // Silence bad assertions, this can be dispatched from either the main
    // thread or the timer thread..
    return true;
  }

  virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
                            bool aDispatchResult) override {
    // Silence bad assertions, this can be dispatched from either the main
    // thread or the timer thread..
  }

  virtual bool WorkerRun(JSContext* aCx,
                         WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->GarbageCollectInternal(aCx, mShrinking, mCollectChildren);
    if (mShrinking) {
      // Either we've run the idle GC or explicit GC call from the parent,
      // we can cancel the current timers.
      aWorkerPrivate->CancelGCTimers();
    }
    return true;
  }
};

class CycleCollectRunnable final : public WorkerControlRunnable {
  bool mCollectChildren;

 public:
  CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren)
      : WorkerControlRunnable("CycleCollectRunnable"),
        mCollectChildren(aCollectChildren) {}

  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->CycleCollectInternal(mCollectChildren);
    return true;
  }
};

class OfflineStatusChangeRunnable final : public WorkerThreadRunnable {
 public:
  OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline)
      : WorkerThreadRunnable("OfflineStatusChangeRunnable"),
        mIsOffline(aIsOffline) {}

  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->OfflineStatusChangeEventInternal(mIsOffline);
    return true;
  }

 private:
  bool mIsOffline;
};

class MemoryPressureRunnable final : public WorkerControlRunnable {
 public:
  explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
      : WorkerControlRunnable("MemoryPressureRunnable") {}

  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->MemoryPressureInternal();
    return true;
  }
};

class DisableRemoteDebuggerRunnable final : public WorkerControlRunnable {
 public:
  explicit DisableRemoteDebuggerRunnable(WorkerPrivate* aWorkerPrivate)
      : WorkerControlRunnable("DisableRemoteDebuggerRunnable") {}

  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->DisableRemoteDebuggerOnWorkerThread();
    return true;
  }
};

#ifdef DEBUG
static bool StartsWithExplicit(nsACString& s) {
  return StringBeginsWith(s, "explicit/"_ns);
}
#endif

PRThread* PRThreadFromThread(nsIThread* aThread) {
  MOZ_ASSERT(aThread);

  PRThread* result;
  MOZ_ALWAYS_SUCCEEDS(aThread->GetPRThread(&result));
  MOZ_ASSERT(result);

  return result;
}

// A runnable to cancel the worker from the parent thread when self.close() is
// called. This runnable is executed on the parent process in order to cancel
// the current runnable. It uses a normal WorkerDebuggeeRunnable in order to be
// sure that all the pending WorkerDebuggeeRunnables are executed before this.
class CancelingOnParentRunnable final : public WorkerParentDebuggeeRunnable {
 public:
  explicit CancelingOnParentRunnable(WorkerPrivate* aWorkerPrivate)
      : WorkerParentDebuggeeRunnable("CancelingOnParentRunnable") {}

  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->Cancel();
    return true;
  }
};

// A runnable to cancel the worker from the parent process.
class CancelingWithTimeoutOnParentRunnable final
    : public WorkerParentControlRunnable {
 public:
  explicit CancelingWithTimeoutOnParentRunnable(WorkerPrivate* aWorkerPrivate)
      : WorkerParentControlRunnable("CancelingWithTimeoutOnParentRunnable") {}

  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
    aWorkerPrivate->AssertIsOnParentThread();
    aWorkerPrivate->StartCancelingTimer();
    return true;
  }
};

class CancelingTimerCallback final : public nsITimerCallback {
 public:
  NS_DECL_ISUPPORTS

  explicit CancelingTimerCallback(WorkerPrivate* aWorkerPrivate)
      : mWorkerPrivate(aWorkerPrivate) {}

  NS_IMETHOD
  Notify(nsITimer* aTimer) override {
    mWorkerPrivate->AssertIsOnParentThread();
    mWorkerPrivate->Cancel();
    return NS_OK;
  }

 private:
  ~CancelingTimerCallback() = default;

  // Raw pointer here is OK because the timer is canceled during the shutdown
  // steps.
  WorkerPrivate* mWorkerPrivate;
};

NS_IMPL_ISUPPORTS(CancelingTimerCallback, nsITimerCallback)

// This runnable starts the canceling of a worker after a self.close().
class CancelingRunnable final : public Runnable {
 public:
  CancelingRunnable() : Runnable("CancelingRunnable") {}

  NS_IMETHOD
  Run() override {
    LOG(WorkerLog(), ("CancelingRunnable::Run [%p]", this));
    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
    MOZ_ASSERT(workerPrivate);
    workerPrivate->AssertIsOnWorkerThread();

    // Now we can cancel the this worker from the parent process.
    RefPtr<CancelingOnParentRunnable> r =
        new CancelingOnParentRunnable(workerPrivate);
    r->Dispatch(workerPrivate);

    return NS_OK;
  }
};

} /* anonymous namespace */

nsString ComputeWorkerPrivateId() {
  nsID uuid = nsID::GenerateUUID();
  return NSID_TrimBracketsUTF16(uuid);
}

class WorkerPrivate::EventTarget final : public nsISerialEventTarget {
  // This mutex protects mWorkerPrivate and must be acquired *before* the
  // WorkerPrivate's mutex whenever they must both be held.
  mozilla::Mutex mMutex;
  WorkerPrivate* mWorkerPrivate MOZ_GUARDED_BY(mMutex);
  nsCOMPtr<nsIEventTarget> mNestedEventTarget MOZ_GUARDED_BY(mMutex);
  bool mDisabled MOZ_GUARDED_BY(mMutex);
  bool mShutdown MOZ_GUARDED_BY(mMutex);

 public:
  EventTarget(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aNestedEventTarget)
      : mMutex("WorkerPrivate::EventTarget::mMutex"),
        mWorkerPrivate(aWorkerPrivate),
        mNestedEventTarget(aNestedEventTarget),
        mDisabled(false),
        mShutdown(false) {
    MOZ_ASSERT(aWorkerPrivate);
    MOZ_ASSERT(aNestedEventTarget);
  }

  void Disable() {
    {
      MutexAutoLock lock(mMutex);

      // Note, Disable() can be called more than once safely.
      mDisabled = true;
    }
  }

  void Shutdown() {
    nsCOMPtr<nsIEventTarget> nestedEventTarget;
    {
      MutexAutoLock lock(mMutex);

      mWorkerPrivate = nullptr;
      mNestedEventTarget.swap(nestedEventTarget);
      MOZ_ASSERT(mDisabled);
      mShutdown = true;
    }
  }

  RefPtr<nsIEventTarget> GetNestedEventTarget() {
    RefPtr<nsIEventTarget> nestedEventTarget = nullptr;
    {
      MutexAutoLock lock(mMutex);
      if (mWorkerPrivate) {
        mWorkerPrivate->AssertIsOnWorkerThread();
        nestedEventTarget = mNestedEventTarget.get();
      }
    }
    return nestedEventTarget;
  }

  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIEVENTTARGET_FULL

 private:
  ~EventTarget() = default;
};

class WorkerJSContextStats final : public JS::RuntimeStats {
  const nsCString mRtPath;

 public:
  explicit WorkerJSContextStats(const nsACString& aRtPath)
      : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath) {}

  ~WorkerJSContextStats() {
    for (JS::ZoneStats& stats : zoneStatsVector) {
      delete static_cast<xpc::ZoneStatsExtras*>(stats.extra);
    }

    for (JS::RealmStats& stats : realmStatsVector) {
      delete static_cast<xpc::RealmStatsExtras*>(stats.extra);
    }
  }

  const nsCString& Path() const { return mRtPath; }

  virtual void initExtraZoneStats(JS::Zone* aZone, JS::ZoneStats* aZoneStats,
                                  const JS::AutoRequireNoGC& nogc) override {
    MOZ_ASSERT(!aZoneStats->extra);

    // ReportJSRuntimeExplicitTreeStats expects that
    // aZoneStats->extra is a xpc::ZoneStatsExtras pointer.
    xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras;
    extras->pathPrefix = mRtPath;
    extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void*)aZone);

    MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix));

    aZoneStats->extra = extras;
  }

  virtual void initExtraRealmStats(JS::Realm* aRealm,
                                   JS::RealmStats* aRealmStats,
                                   const JS::AutoRequireNoGC& nogc) override {
    MOZ_ASSERT(!aRealmStats->extra);

    // ReportJSRuntimeExplicitTreeStats expects that
    // aRealmStats->extra is a xpc::RealmStatsExtras pointer.
    xpc::RealmStatsExtras* extras = new xpc::RealmStatsExtras;

    // This is the |jsPathPrefix|.  Each worker has exactly one realm.
    extras->jsPathPrefix.Assign(mRtPath);
    extras->jsPathPrefix +=
        nsPrintfCString("zone(0x%p)/", (void*)js::GetRealmZone(aRealm));
    extras->jsPathPrefix += "realm(web-worker)/"_ns;

    // This should never be used when reporting with workers (hence the "?!").
    extras->domPathPrefix.AssignLiteral("explicit/workers/?!/");

    MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix));
    MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix));

    extras->location = nullptr;

    aRealmStats->extra = extras;
  }
};

class WorkerPrivate::MemoryReporter final : public nsIMemoryReporter {
  NS_DECL_THREADSAFE_ISUPPORTS

  friend class WorkerPrivate;

  SharedMutex mMutex;
  WorkerPrivate* mWorkerPrivate;

 public:
  explicit MemoryReporter(WorkerPrivate* aWorkerPrivate)
      : mMutex(aWorkerPrivate->mMutex), mWorkerPrivate(aWorkerPrivate) {
    aWorkerPrivate->AssertIsOnWorkerThread();
  }

  NS_IMETHOD
  CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
                 bool aAnonymize) override;

 private:
  class FinishCollectRunnable;

  class CollectReportsRunnable final : public MainThreadWorkerControlRunnable {
    RefPtr<FinishCollectRunnable> mFinishCollectRunnable;
    const bool mAnonymize;

   public:
    CollectReportsRunnable(WorkerPrivate* aWorkerPrivate,
                           nsIHandleReportCallback* aHandleReport,
                           nsISupports* aHandlerData, bool aAnonymize,
                           const nsACString& aPath);

   private:
    bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override;

    ~CollectReportsRunnable() {
      if (NS_IsMainThread()) {
        mFinishCollectRunnable->Run();
        return;
      }

      WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
      MOZ_ASSERT(workerPrivate);
      MOZ_ALWAYS_SUCCEEDS(workerPrivate->DispatchToMainThreadForMessaging(
          mFinishCollectRunnable.forget()));
    }
  };

  class FinishCollectRunnable final : public Runnable {
    nsCOMPtr<nsIHandleReportCallback> mHandleReport;
    nsCOMPtr<nsISupports> mHandlerData;
    size_t mPerformanceUserEntries;
    size_t mPerformanceResourceEntries;
    const bool mAnonymize;
    bool mSuccess;

   public:
    WorkerJSContextStats mCxStats;

    explicit FinishCollectRunnable(nsIHandleReportCallback* aHandleReport,
                                   nsISupports* aHandlerData, bool aAnonymize,
                                   const nsACString& aPath);

    NS_IMETHOD Run() override;

    void SetPerformanceSizes(size_t userEntries, size_t resourceEntries) {
      mPerformanceUserEntries = userEntries;
      mPerformanceResourceEntries = resourceEntries;
    }

    void SetSuccess(bool success) { mSuccess = success; }

    FinishCollectRunnable(const FinishCollectRunnable&) = delete;
    FinishCollectRunnable& operator=(const FinishCollectRunnable&) = delete;
    FinishCollectRunnable& operator=(const FinishCollectRunnable&&) = delete;

   private:
    ~FinishCollectRunnable() {
      // mHandleReport and mHandlerData are released on the main thread.
      AssertIsOnMainThread();
    }
  };

  ~MemoryReporter() = default;

  void Disable() {
    // Called from WorkerPrivate::DisableMemoryReporter.
    mMutex.AssertCurrentThreadOwns();

    NS_ASSERTION(mWorkerPrivate, "Disabled more than once!");
    mWorkerPrivate = nullptr;
  }
};

NS_IMPL_ISUPPORTS(WorkerPrivate::MemoryReporter, nsIMemoryReporter)

NS_IMETHODIMP
WorkerPrivate::MemoryReporter::CollectReports(
    nsIHandleReportCallback* aHandleReport, nsISupports* aData,
    bool aAnonymize) {
  AssertIsOnMainThread();

  RefPtr<CollectReportsRunnable> runnable;

  {
    MutexAutoLock lock(mMutex);

    if (!mWorkerPrivate) {
      // This will effectively report 0 memory.
      nsCOMPtr<nsIMemoryReporterManager> manager =
          do_GetService("@mozilla.org/memory-reporter-manager;1");
      if (manager) {
        manager->EndReport();
      }
      return NS_OK;
    }

    nsAutoCString path;
    path.AppendLiteral("explicit/workers/workers(");
    if (aAnonymize && !mWorkerPrivate->Domain().IsEmpty()) {
      path.AppendLiteral("<anonymized-domain>)/worker(<anonymized-url>");
    } else {
      nsAutoCString escapedDomain(mWorkerPrivate->Domain());
      if (escapedDomain.IsEmpty()) {
        escapedDomain += "chrome";
      } else {
        escapedDomain.ReplaceChar('/', '\\');
      }
      path.Append(escapedDomain);
      path.AppendLiteral(")/worker(");
      NS_ConvertUTF16toUTF8 escapedURL(mWorkerPrivate->ScriptURL());
      escapedURL.ReplaceChar('/', '\\');
      path.Append(escapedURL);
    }
    path.AppendPrintf(", 0x%p)/", static_cast<void*>(mWorkerPrivate));

    runnable = new CollectReportsRunnable(mWorkerPrivate, aHandleReport, aData,
                                          aAnonymize, path);
  }

  if (!runnable->Dispatch(mWorkerPrivate)) {
    return NS_ERROR_UNEXPECTED;
  }

  return NS_OK;
}

WorkerPrivate::MemoryReporter::CollectReportsRunnable::CollectReportsRunnable(
    WorkerPrivate* aWorkerPrivate, nsIHandleReportCallback* aHandleReport,
    nsISupports* aHandlerData, bool aAnonymize, const nsACString& aPath)
    : MainThreadWorkerControlRunnable("CollectReportsRunnable"),
      mFinishCollectRunnable(new FinishCollectRunnable(
          aHandleReport, aHandlerData, aAnonymize, aPath)),
      mAnonymize(aAnonymize) {}

bool WorkerPrivate::MemoryReporter::CollectReportsRunnable::WorkerRun(
    JSContext* aCx, WorkerPrivate* aWorkerPrivate) {
  aWorkerPrivate->AssertIsOnWorkerThread();

  RefPtr<WorkerGlobalScope> scope = aWorkerPrivate->GlobalScope();
  RefPtr<Performance> performance =
      scope ? scope->GetPerformanceIfExists() : nullptr;
  if (performance) {
    size_t userEntries = performance->SizeOfUserEntries(JsWorkerMallocSizeOf);
    size_t resourceEntries =
        performance->SizeOfResourceEntries(JsWorkerMallocSizeOf);
    mFinishCollectRunnable->SetPerformanceSizes(userEntries, resourceEntries);
  }

  mFinishCollectRunnable->SetSuccess(aWorkerPrivate->CollectRuntimeStats(
      &mFinishCollectRunnable->mCxStats, mAnonymize));

  return true;
}

WorkerPrivate::MemoryReporter::FinishCollectRunnable::FinishCollectRunnable(
    nsIHandleReportCallback* aHandleReport, nsISupports* aHandlerData,
    bool aAnonymize, const nsACString& aPath)
    : mozilla::Runnable(
          "dom::WorkerPrivate::MemoryReporter::FinishCollectRunnable"),
      mHandleReport(aHandleReport),
      mHandlerData(aHandlerData),
      mPerformanceUserEntries(0),
      mPerformanceResourceEntries(0),
      mAnonymize(aAnonymize),
      mSuccess(false),
      mCxStats(aPath) {}

NS_IMETHODIMP
WorkerPrivate::MemoryReporter::FinishCollectRunnable::Run() {
  AssertIsOnMainThread();

  nsCOMPtr<nsIMemoryReporterManager> manager =
      do_GetService("@mozilla.org/memory-reporter-manager;1");

  if (!manager) return NS_OK;

  if (mSuccess) {
    xpc::ReportJSRuntimeExplicitTreeStats(
        mCxStats, mCxStats.Path(), mHandleReport, mHandlerData, mAnonymize);

    if (mPerformanceUserEntries) {
      nsCString path = mCxStats.Path();
      path.AppendLiteral("dom/performance/user-entries");
      mHandleReport->Callback(""_ns, path, nsIMemoryReporter::KIND_HEAP,
                              nsIMemoryReporter::UNITS_BYTES,
                              static_cast<int64_t>(mPerformanceUserEntries),
                              "Memory used for performance user entries."_ns,
                              mHandlerData);
    }

    if (mPerformanceResourceEntries) {
      nsCString path = mCxStats.Path();
      path.AppendLiteral("dom/performance/resource-entries");
      mHandleReport->Callback(
          ""_ns, path, nsIMemoryReporter::KIND_HEAP,
          nsIMemoryReporter::UNITS_BYTES,
          static_cast<int64_t>(mPerformanceResourceEntries),
          "Memory used for performance resource entries."_ns, mHandlerData);
    }
  }

  manager->EndReport();

  return NS_OK;
}

WorkerPrivate::SyncLoopInfo::SyncLoopInfo(EventTarget* aEventTarget)
    : mEventTarget(aEventTarget),
      mResult(NS_ERROR_FAILURE),
      mCompleted(false)
#ifdef DEBUG
      ,
      mHasRun(false)
#endif
{
}

Document* WorkerPrivate::GetDocument() const {
  AssertIsOnMainThread();
  if (nsPIDOMWindowInner* window = GetAncestorWindow()) {
    return window->GetExtantDoc();
  }
  // couldn't query a document, give up and return nullptr
  return nullptr;
}

nsPIDOMWindowInner* WorkerPrivate::GetAncestorWindow() const {
  AssertIsOnMainThread();

  // We should query the window from the top level worker in case of a nested
  // worker, as only the top level one can have a window.
  WorkerPrivate* top = GetTopLevelWorker();
  return top->GetWindow();
}

class EvictFromBFCacheRunnable final : public WorkerProxyToMainThreadRunnable {
 public:
  void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
    MOZ_ASSERT(aWorkerPrivate);
    AssertIsOnMainThread();
    if (nsCOMPtr<nsPIDOMWindowInner> win =
            aWorkerPrivate->GetAncestorWindow()) {
      win->RemoveFromBFCacheSync();
    }
  }

  void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
    MOZ_ASSERT(aWorkerPrivate);
    aWorkerPrivate->AssertIsOnWorkerThread();
  }
};

void WorkerPrivate::EvictFromBFCache() {
  AssertIsOnWorkerThread();
  RefPtr<EvictFromBFCacheRunnable> runnable = new EvictFromBFCacheRunnable();
  runnable->Dispatch(this);
}

nsresult WorkerPrivate::SetCsp(nsIContentSecurityPolicy* aCSP) {
  AssertIsOnMainThread();
  if (!aCSP) {
    return NS_OK;
  }
  aCSP->EnsureEventTarget(mMainThreadEventTarget);

  mLoadInfo.mCSP = aCSP;
  auto ctx = OffThreadCSPContext::CreateFromCSP(aCSP);
  if (NS_WARN_IF(ctx.isErr())) {
    return ctx.unwrapErr();
  }
  mLoadInfo.mCSPContext = ctx.unwrap();
  return NS_OK;
}

nsresult WorkerPrivate::SetCSPFromHeaderValues(
    const nsACString& aCSPHeaderValue,
    const nsACString& aCSPReportOnlyHeaderValue) {
  AssertIsOnMainThread();
  MOZ_DIAGNOSTIC_ASSERT(!mLoadInfo.mCSP);

  NS_ConvertASCIItoUTF16 cspHeaderValue(aCSPHeaderValue);
  NS_ConvertASCIItoUTF16 cspROHeaderValue(aCSPReportOnlyHeaderValue);

  nsresult rv;
  nsCOMPtr<nsIContentSecurityPolicy> csp = new nsCSPContext();

  // First, we try to query the URI from the Principal, but
  // in case selfURI remains empty (e.g in case the Principal
  // is a SystemPrincipal) then we fall back and use the
  // base URI as selfURI for CSP.
  nsCOMPtr<nsIURI> selfURI;
  // Its not recommended to use the BasePrincipal to get the URI
  // but in this case we need to make an exception
  auto* basePrin = BasePrincipal::Cast(mLoadInfo.mPrincipal);
  if (basePrin) {
    basePrin->GetURI(getter_AddRefs(selfURI));
  }
  if (!selfURI) {
    selfURI = mLoadInfo.mBaseURI;
  }
  MOZ_ASSERT(selfURI, "need a self URI for CSP");

  rv = csp->SetRequestContextWithPrincipal(mLoadInfo.mPrincipal, selfURI, ""_ns,
                                           0);
  NS_ENSURE_SUCCESS(rv, rv);

  csp->EnsureEventTarget(mMainThreadEventTarget);

  // If there's a CSP header, apply it.
  if (!cspHeaderValue.IsEmpty()) {
    rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  // If there's a report-only CSP header, apply it.
  if (!cspROHeaderValue.IsEmpty()) {
    rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  RefPtr<extensions::WebExtensionPolicy> addonPolicy;

  if (basePrin) {
    addonPolicy = basePrin->AddonPolicy();
  }

  // For extension workers there aren't any csp header values,
  // instead it will inherit the Extension CSP.
  if (addonPolicy) {
    csp->AppendPolicy(addonPolicy->BaseCSP(), false, false);
    csp->AppendPolicy(addonPolicy->ExtensionPageCSP(), false, false);
  }

  mLoadInfo.mCSP = csp;

  auto ctx = OffThreadCSPContext::CreateFromCSP(csp);
  if (NS_WARN_IF(ctx.isErr())) {
    return ctx.unwrapErr();
  }
  mLoadInfo.mCSPContext = ctx.unwrap();
  return NS_OK;
}

bool WorkerPrivate::IsFrozenForWorkerThread() const {
  auto data = mWorkerThreadAccessible.Access();
  return data->mFrozen;
}

bool WorkerPrivate::IsFrozen() const {
  AssertIsOnParentThread();
  return mParentFrozen;
}

void WorkerPrivate::StorePolicyContainerArgsOnClient() {
  auto data = mWorkerThreadAccessible.Access();
  MOZ_ASSERT(data->mScope);
  auto& clientSource = data->mScope->MutableClientSourceRef();

  mozilla::ipc::PolicyContainerArgs policyContainerArgs;

  if (mLoadInfo.mCSPContext) {
    policyContainerArgs.csp() = Some(mLoadInfo.mCSPContext->CSPInfo());
  }

  policyContainerArgs.ipAddressSpace() =
      static_cast<nsILoadInfo::IPAddressSpace>(mLoadInfo.mIPAddressSpace);
  // TODO(Bug 2017654): Check if integrity policy args need to be propagated
  // to workers as well. Currently not set explicitly here.

  clientSource.SetPolicyContainerArgs(policyContainerArgs);
}

void WorkerPrivate::UpdateReferrerInfoFromHeader(
    const nsACString& aReferrerPolicyHeaderValue) {
  NS_ConvertUTF8toUTF16 headerValue(aReferrerPolicyHeaderValue);

  if (headerValue.IsEmpty()) {
    return;
  }

  ReferrerPolicy policy =
      ReferrerInfo::ReferrerPolicyFromHeaderString(headerValue);
  if (policy == ReferrerPolicy::_empty) {
    return;
  }

  nsCOMPtr<nsIReferrerInfo> referrerInfo =
      static_cast<ReferrerInfo*>(GetReferrerInfo())->CloneWithNewPolicy(policy);
  SetReferrerInfo(referrerInfo);
}

void WorkerPrivate::Traverse(nsCycleCollectionTraversalCallback& aCb) {
  AssertIsOnParentThread();

  // The WorkerPrivate::mParentEventTargetRef has a reference to the exposed
  // Worker object, which is really held by the worker thread.  We traverse this
  // reference if and only if all main thread event queues are empty, no
  // shutdown tasks, no StrongWorkerRefs, no child workers, no timeouts, no
  // blocking background actors, and we have not released the main thread
  // reference.  We do not unlink it. This allows the CC to break cycles
  // involving the Worker and begin shutting it down (which does happen in
  // unlink) but ensures that the WorkerPrivate won't be deleted before we're
  // done shutting down the thread.
  if (IsEligibleForCC() && !mMainThreadObjectsForgotten) {
    nsCycleCollectionTraversalCallback& cb = aCb;
    WorkerPrivate* tmp = this;
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentEventTargetRef);
  }
}

nsresult WorkerPrivate::Dispatch(already_AddRefed<WorkerRunnable> aRunnable,
                                 nsIEventTarget* aSyncLoopTarget) {
  // May be called on any thread!
  RefPtr<WorkerRunnable> runnable(aRunnable);

  LOGV(("WorkerPrivate::Dispatch [%p] runnable %p", this, runnable.get()));
  if (!aSyncLoopTarget) {
    // Dispatch control runnable
    if (runnable->IsControlRunnable()) {
      return DispatchControlRunnable(runnable.forget());
    }

    // Dispatch debugger runnable
    if (runnable->IsDebuggerRunnable()) {
      return DispatchDebuggerRunnable(runnable.forget());
    }
  }
  MutexAutoLock lock(mMutex);
  return DispatchLockHeld(runnable.forget(), aSyncLoopTarget, lock);
}

nsresult WorkerPrivate::DispatchToParent(
    already_AddRefed<WorkerRunnable> aRunnable) {
  RefPtr<WorkerRunnable> runnable(aRunnable);

  LOGV(("WorkerPrivate::DispatchToParent [%p] runnable %p", this,
        runnable.get()));

  WorkerPrivate* parent = GetParent();
  // Dispatch to parent worker
  if (parent) {
    if (runnable->IsControlRunnable()) {
      return parent->DispatchControlRunnable(runnable.forget());
    }
    return parent->Dispatch(runnable.forget());
  }

  // Dispatch to main thread
  if (runnable->IsDebuggeeRunnable()) {
    RefPtr<WorkerParentDebuggeeRunnable> debuggeeRunnable =
        runnable.forget().downcast<WorkerParentDebuggeeRunnable>();
    return DispatchDebuggeeToMainThread(debuggeeRunnable.forget(),
                                        NS_DISPATCH_FALLIBLE);
  }
  return DispatchToMainThread(runnable.forget(), NS_DISPATCH_FALLIBLE);
}

nsresult WorkerPrivate::DispatchLockHeld(
    already_AddRefed<WorkerRunnable> aRunnable, nsIEventTarget* aSyncLoopTarget,
    const MutexAutoLock& aProofOfLock) {
  // May be called on any thread!
  RefPtr<WorkerRunnable> runnable(aRunnable);
  LOGV(("WorkerPrivate::DispatchLockHeld [%p] runnable: %p", this,
        runnable.get()));

  MOZ_ASSERT_IF(aSyncLoopTarget, mThread);

  // Dispatch normal worker runnable
  if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Canceling)) {
    NS_WARNING(
        "A runnable was posted to a worker that is already shutting "
        "down!");
    return NS_ERROR_UNEXPECTED;
  }

  // Postpone the debuggee runnable dispatching while remote debugger
  // registration
  if (runnable->IsDebuggeeRunnable() && !mDebuggerReady &&
      !mRemoteDebuggerReady &&
      (!mRemoteDebuggerRegistered && XRE_IsParentProcess())) {
    MOZ_RELEASE_ASSERT(!aSyncLoopTarget);
    mDelayedDebuggeeRunnables.AppendElement(runnable);
    return NS_OK;
  }

  if (!mThread) {
    if (ParentStatus() == Pending || mStatus == Pending) {
      LOGV(
          ("WorkerPrivate::DispatchLockHeld [%p] runnable %p is queued in "
           "mPreStartRunnables",
           this, runnable.get()));
      RefPtr<WorkerThreadRunnable> workerThreadRunnable =
          static_cast<WorkerThreadRunnable*>(runnable.get());
      PROFILER_MARKER("WorkerPrivate::DispatchLockHeld", OTHER,
                      {MarkerStack::MaybeCapture(
                          profiler_feature_active(ProfilerFeature::Flows))},
                      FlowMarker, Flow::FromPointer(runnable.get()));
      mPreStartRunnables.AppendElement(workerThreadRunnable);
      return NS_OK;
    }

    NS_WARNING(
        "Using a worker event target after the thread has already"
        "been released!");
    return NS_ERROR_UNEXPECTED;
  }

  nsresult rv;
  if (aSyncLoopTarget) {
    LOGV(
        ("WorkerPrivate::DispatchLockHeld [%p] runnable %p dispatch to a "
         "SyncLoop(%p)",
         this, runnable.get(), aSyncLoopTarget));
    rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_FALLIBLE);
  } else {
    // If mStatus is Pending, the WorkerPrivate initialization still can fail.
    // Append this WorkerThreadRunnable to WorkerPrivate::mPreStartRunnables,
    // such that this WorkerThreadRunnable can get the correct value of
    // mCleanPreStartDispatching in WorkerPrivate::RunLoopNeverRan().
    if (mStatus == Pending) {
      LOGV(
          ("WorkerPrivate::DispatchLockHeld [%p] runnable %p is append in "
           "mPreStartRunnables",
           this, runnable.get()));
      RefPtr<WorkerThreadRunnable> workerThreadRunnable =
          static_cast<WorkerThreadRunnable*>(runnable.get());
      PROFILER_MARKER("WorkerPrivate::DispatchLockHeld", OTHER,
                      {MarkerStack::MaybeCapture(
                          profiler_feature_active(ProfilerFeature::Flows))},
                      FlowMarker, Flow::FromPointer(runnable.get()));
      mPreStartRunnables.AppendElement(workerThreadRunnable);
    }

    // WorkerDebuggeeRunnables don't need any special treatment here. True,
    // they should not be delivered to a frozen worker. But frozen workers
    // aren't drawing from the thread's main event queue anyway, only from
    // mControlQueue.
    LOGV(
        ("WorkerPrivate::DispatchLockHeld [%p] runnable %p dispatch to the "
         "main event queue",
         this, runnable.get()));
    rv = mThread->DispatchAnyThread(WorkerThreadFriendKey(), runnable.forget());
  }

  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOGV(("WorkerPrivate::Dispatch Failed [%p]", this));
    return rv;
  }

  mCondVar.Notify();
  return NS_OK;
}

void WorkerPrivate::EnableDebugger() {
  AssertIsOnParentThread();

  if (NS_FAILED(RegisterWorkerDebugger(this))) {
    NS_WARNING("Failed to register worker debugger!");
    return;
  }
}

void WorkerPrivate::DisableDebugger() {
  AssertIsOnParentThread();

  // RegisterDebuggerMainThreadRunnable might be dispatched but not executed.
  // Wait for its execution before unregistraion.
  if (!NS_IsMainThread()) {
    WaitForIsDebuggerRegistered(true);
  }

  if (NS_FAILED(UnregisterWorkerDebugger(this))) {
    NS_WARNING("Failed to unregister worker debugger!");
  }
}

void WorkerPrivate::BindRemoteWorkerDebuggerChild() {
  AssertIsOnWorkerThread();
  MOZ_ASSERT_DEBUG_OR_FUZZING(!mRemoteDebugger);

  if (!UseRemoteDebugger()) {
    return;
  }

  RefPtr<RemoteWorkerDebuggerChild> debugger =
      MakeRefPtr<RemoteWorkerDebuggerChild>(this);
  // Deliver PRemoteWorkerDebugger messages on the worker's debugger event
  // target so the Recv handlers run as debugger runnables. The debugger pause
  // loop (EnterDebuggerEventLoop) only pumps the control and debugger queues,
  // so binding on the default (normal) target would stall the debugger protocol
  // while the worker is paused, breaking interactive debugging.
  mDebuggerChildEp.Bind(debugger, mWorkerDebuggerEventTarget);
  {
    MutexAutoLock lock(mMutex);
    MOZ_ASSERT_DEBUG_OR_FUZZING(!mRemoteDebugger);
    mRemoteDebugger = std::move(debugger);
    mDebuggerBindingCondVar.Notify();
  }
}

void WorkerPrivate::CreateRemoteDebuggerEndpoints() {
  AssertIsOnParentThread();

  if (!UseRemoteDebugger()) {
    return;
  }

  MutexAutoLock lock(mMutex);
  MOZ_ASSERT_DEBUG_OR_FUZZING(!mRemoteDebugger &&
                              !mDebuggerParentEp.IsValid() &&
                              !mDebuggerChildEp.IsValid());

  (void)NS_WARN_IF(NS_FAILED(PRemoteWorkerDebugger::CreateEndpoints(
      &mDebuggerParentEp, &mDebuggerChildEp)));
}

void WorkerPrivate::SetIsRemoteDebuggerRegistered(const bool& aRegistered) {
  AssertIsOnWorkerThread();

  if (aRegistered) {
    MutexAutoLock lock(mMutex);
    MOZ_ASSERT(mRemoteDebuggerRegistered != aRegistered);

    mRemoteDebuggerRegistered = aRegistered;
    bool debuggerRegistered = mDebuggerRegistered && mRemoteDebuggerRegistered;
    if (mRemoteDebuggerReady && mDebuggerReady && debuggerRegistered) {
      LOGV(
          ("WorkerPrivate::SetIsRemoteDebuggerRegistered [%p] dispatching "
           "the delayed debuggee runnables",
           this));
      // Dispatch all the delayed runnables without releasing the lock, to
      // ensure that the order in which debuggee runnables execute is the same
      // as the order in which they were originally dispatched.
      auto pending = std::move(mDelayedDebuggeeRunnables);
      for (uint32_t i = 0; i < pending.Length(); i++) {
        RefPtr<WorkerRunnable> runnable = std::move(pending[i]);
        (void)NS_WARN_IF(
            NS_FAILED(DispatchLockHeld(runnable.forget(), nullptr, lock)));
      }
      MOZ_RELEASE_ASSERT(mDelayedDebuggeeRunnables.IsEmpty());
    }
    mDebuggerBindingCondVar.Notify();
    return;
  }

  RefPtr<RemoteWorkerDebuggerChild> unregisteredDebugger;
  {
    MutexAutoLock lock(mMutex);
    // Can not call RemoteWorkerDebuggerChild::Close() with lock. It causes
    // deadlock between mMutex and MessageChannel::mMonitor.
    unregisteredDebugger = std::move(mRemoteDebugger);
    // Force to set as unregistered, mRemoteDebuggerRegistered could be false
    // here since Worker quickly shutdown or initialization fails in
    // WorkerThreadPrimaryRunnable::Run().
    mRemoteDebuggerRegistered = aRegistered;
  }
  if (unregisteredDebugger) {
    unregisteredDebugger->Close();
    unregisteredDebugger = nullptr;
  }
  {
    MutexAutoLock lock(mMutex);
    mDebuggerBindingCondVar.Notify();
  }
}

void WorkerPrivate::SetIsRemoteDebuggerReady(const bool& aReady) {
  AssertIsOnWorkerThread();
  MutexAutoLock lock(mMutex);

  if (mRemoteDebuggerReady == aReady) {
    return;
  }

  bool debuggerRegistered = mDebuggerRegistered && mRemoteDebuggerRegistered;

  if (!aReady && debuggerRegistered) {
    // The debugger can only be marked as not ready during registration.
    return;
  }

  mRemoteDebuggerReady = aReady;

  if (mRemoteDebuggerReady && mDebuggerReady && debuggerRegistered) {
    LOGV(
        ("WorkerPrivate::SetIsRemoteDebuggerReady [%p] dispatching "
         "the delayed debuggee runnables",
         this));
    // Dispatch all the delayed runnables without releasing the lock, to ensure
    // that the order in which debuggee runnables execute is the same as the
    // order in which they were originally dispatched.
    auto pending = std::move(mDelayedDebuggeeRunnables);
    for (uint32_t i = 0; i < pending.Length(); i++) {
      RefPtr<WorkerRunnable> runnable = std::move(pending[i]);
      (void)NS_WARN_IF(
          NS_FAILED(DispatchLockHeld(runnable.forget(), nullptr, lock)));
    }
    MOZ_RELEASE_ASSERT(mDelayedDebuggeeRunnables.IsEmpty());
  }
}

void WorkerPrivate::SetIsQueued(const bool& aQueued) {
  AssertIsOnParentThread();
  mIsQueued = aQueued;
}

bool WorkerPrivate::IsQueued() const {
  AssertIsOnParentThread();
  return mIsQueued;
}

void WorkerPrivate::EnableRemoteDebugger() {
  AssertIsOnParentThread();

  if (!UseRemoteDebugger()) {
    return;
  }

  // Wait for RemoteWorkerDebuggerChild binding done in the worker thread.
  mozilla::ipc::Endpoint<PRemoteWorkerDebuggerParent> parentEp;
  {
    MutexAutoLock lock(mMutex);
    if (!mRemoteDebugger) {
      mDebuggerBindingCondVar.Wait();
    }
    // If Worker Thread never run the event loop, i.e. JSContext initilaization
    // fails, directly return for the cases. Because mRemoteDebugger is only
    // created after initialization successfully, but mDebuggerBindingCondVar
    // can get notified if the initialization fails.
    if (!mRemoteDebugger) {
      return;
    }
    parentEp = std::move(mDebuggerParentEp);
  }

  // Resolve the (possibly relative) script URL to an absolute URL so the
  // debugger reports it correctly. DevTools resolves the local WorkerDebugger
  // URL against the owning window, which isn't available for a remote worker,
  // so we resolve here instead. The script loader already resolves URLs on the
  // worker thread (WorkerScriptLoader::CreateScriptLoadRequest), so NS_NewURI
  // is safe here too. A top-level worker registers on the main thread and
  // resolves against its own base URI; a nested worker registers on its parent
  // worker thread before its own base URI is set, so it resolves against the
  // parent's already-resolved script URI (the base for the nested script).
  nsString scriptURL(mScriptURL);
  nsCOMPtr<nsIURI> baseURI;
  if (NS_IsMainThread()) {
    baseURI = GetBaseURI();
  } else if (WorkerPrivate* parent = GetParent()) {
    baseURI = parent->GetResolvedScriptURI();
  }
  if (baseURI) {
    nsCOMPtr<nsIURI> scriptURI;
    if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(scriptURI),
                               NS_ConvertUTF16toUTF8(mScriptURL), nullptr,
                               baseURI))) {
      nsAutoCString spec;
      if (NS_SUCCEEDED(scriptURI->GetSpec(spec))) {
        CopyUTF8toUTF16(spec, scriptURL);
      }
    }
  }

  // Call IPC for RemoteWorkerDebuggerParent binding and registration.
  RemoteWorkerDebuggerInfo info(
      mIsChromeWorker, mWorkerKind, scriptURL, WindowID(),
      WrapNotNull(GetPrincipal()), IsServiceWorker() ? ServiceWorkerID() : 0,
      Id(), mWorkerName,
      GetParent() ? nsAutoString(GetParent()->Id()) : EmptyString());

  MOZ_ASSERT_DEBUG_OR_FUZZING(parentEp.IsValid());
  RemoteWorkerService::RegisterRemoteDebugger(std::move(info),
                                              std::move(parentEp));
  // Wait for register done
  {
    MutexAutoLock lock(mMutex);
    if (!mRemoteDebuggerRegistered) {
      mDebuggerBindingCondVar.Wait();
    }
    // Warning the case if the Worker shutdown before remote debugger
    // registration down.
    (void)NS_WARN_IF(!mRemoteDebuggerRegistered);
  }
}

void WorkerPrivate::DisableRemoteDebugger() {
  AssertIsOnParentThread();

  if (!UseRemoteDebugger()) {
    return;
  }

  RefPtr<DisableRemoteDebuggerRunnable> r =
      new DisableRemoteDebuggerRunnable(this);

  if (r->Dispatch(this)) {
    MutexAutoLock lock(mMutex);
    if (mRemoteDebuggerRegistered) {
      mDebuggerBindingCondVar.Wait();
    }
  }
}

void WorkerPrivate::DisableRemoteDebuggerOnWorkerThread(
    const bool& aForShutdown) {
  AssertIsOnWorkerThread();

  if (!UseRemoteDebugger()) {
    return;
  }
  RefPtr<RemoteWorkerDebuggerChild> remoteDebugger;
  {
    MutexAutoLock lock(mMutex);
    remoteDebugger = mRemoteDebugger;
  }
  if (remoteDebugger) {
    remoteDebugger->SendUnregister();
  }

  // Now notify the parent thread if it is blocked by waiting
  // RemoteWorkerDebugger registration or unregsiteration.
  if (aForShutdown) {
    SetIsRemoteDebuggerRegistered(false);
  }
}

nsresult WorkerPrivate::DispatchControlRunnable(
    already_AddRefed<WorkerRunnable> aWorkerRunnable) {
  // May be called on any thread!
  RefPtr<WorkerRunnable> runnable(aWorkerRunnable);
  MOZ_ASSERT_DEBUG_OR_FUZZING(runnable && runnable->IsControlRunnable());

  LOG(WorkerLog(), ("WorkerPrivate::DispatchControlRunnable [%p] runnable %p",
                    this, runnable.get()));

  JSContext* cx = nullptr;
  {
    MutexAutoLock lock(mMutex);

    if (mStatus == Dead) {
      return NS_ERROR_UNEXPECTED;
    }

    // Unfortunately we can not distinguish if we are on WorkerThread or not.
    // mThread is set in WorkerPrivate::SetWorkerPrivateInWorkerThread(), but
    // ControlRunnable can be dispatching before setting mThread.
    MOZ_ASSERT(mDispatchingControlRunnables < UINT32_MAX);
    mDispatchingControlRunnables++;

    // Transfer ownership to the control queue.
    mControlQueue.Push(runnable.forget().take());
    cx = mJSContext;
    MOZ_ASSERT_IF(cx, mThread);
  }

  if (cx) {
    JS_RequestInterruptCallback(cx);
  }

  {
    MutexAutoLock lock(mMutex);
    if (!--mDispatchingControlRunnables) {
      mCondVar.Notify();
    }
  }

  return NS_OK;
}

void DebuggerInterruptTimerCallback(nsITimer* aTimer, void* aClosure)
    MOZ_NO_THREAD_SAFETY_ANALYSIS {
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
  MOZ_DIAGNOSTIC_ASSERT(workerPrivate);
  workerPrivate->DebuggerInterruptRequest();
}

nsresult WorkerPrivate::DispatchDebuggerRunnable(
    already_AddRefed<WorkerRunnable> aDebuggerRunnable) {
  // May be called on any thread!

  RefPtr<WorkerRunnable> runnable(aDebuggerRunnable);

  MOZ_ASSERT(runnable);

  // Any timer we swap out below must have its ref dropped with mMutex unheld:
  // nsTimerImpl::CancelImpl takes the timer's own lock, and releasing under
  // mMutex inverts against the TimerThread lock ordering. Declared before the
  // lock so it is destroyed after mMutex is released.
  nsCOMPtr<nsITimer> oldTimer;
  MutexAutoLock lock(mMutex);
  if (!mDebuggerInterruptTimer) {
    // There is no timer, so we need to create one.  For locking discipline
    // purposes we can't manipulate the timer while our mutex is held so
    // drop the mutex while we build and configure the timer.  This function
    // may be called concurrently on more than one thread (e.g. the parent's
    // intra-process RemoteWorkerDebugger sends on the main thread while the
    // worker thread dispatches debugger runnables), so a racing caller may
    // have installed a timer while we had the mutex dropped; swap ours in and
    // release whatever was there with the mutex unheld (see oldTimer above).
    nsCOMPtr<nsITimer> timer;
    {
      MutexAutoUnlock unlock(mMutex);
      timer = NS_NewTimer();
      MOZ_ALWAYS_SUCCEEDS(timer->SetTarget(mWorkerControlEventTarget));

      // Whenever an event is scheduled on the WorkerControlEventTarget an
      // interrupt is automatically requested which causes us to yield JS
      // execution and the next JS execution in the queue to execute. This
      // allows for simple code reuse of the existing interrupt callback code
      // used for control events.
      MOZ_ALWAYS_SUCCEEDS(timer->InitWithNamedFuncCallback(
          DebuggerInterruptTimerCallback, nullptr,
          DEBUGGER_RUNNABLE_INTERRUPT_AFTER_MS, nsITimer::TYPE_ONE_SHOT,
          "dom:DebuggerInterruptTimer"_ns));
    }

    // okay, we have our mutex back now, put the timer in place.
    mDebuggerInterruptTimer.swap(timer);
    oldTimer.swap(timer);
  }

  if (mStatus == Dead) {
    NS_WARNING(
        "A debugger runnable was posted to a worker that is already "
        "shutting down!");
    return NS_ERROR_UNEXPECTED;
  }

  // Transfer ownership to the debugger queue.
  mDebuggerQueue.Push(runnable.forget().take());

  mCondVar.Notify();

  return NS_OK;
}

void WorkerPrivate::DebuggerInterruptRequest() {
  AssertIsOnWorkerThread();

  auto data = mWorkerThreadAccessible.Access();
  data->mDebuggerInterruptRequested = true;
}

already_AddRefed<WorkerRunnable> WorkerPrivate::MaybeWrapAsWorkerRunnable(
    already_AddRefed<nsIRunnable> aRunnable) {
  // May be called on any thread!

  nsCOMPtr<nsIRunnable> runnable(aRunnable);
  MOZ_ASSERT(runnable);

  LOGV(("WorkerPrivate::MaybeWrapAsWorkerRunnable [%p] runnable: %p", this,
        runnable.get()));

  RefPtr<WorkerRunnable> workerRunnable =
      WorkerRunnable::FromRunnable(runnable);
  if (workerRunnable) {
    return workerRunnable.forget();
  }

  workerRunnable = new ExternalRunnableWrapper(this, runnable);
  return workerRunnable.forget();
}

bool WorkerPrivate::Start() {
  // May be called on any thread!
  LOG(WorkerLog(), ("WorkerPrivate::Start [%p]", this));
  {
    MutexAutoLock lock(mMutex);
    NS_ASSERTION(mParentStatus != Running, "How can this be?!");

    if (mParentStatus == Pending) {
      mParentStatus = Running;
      return true;
    }
  }

  return false;
}

// aCx is null when called from the finalizer
bool WorkerPrivate::Notify(WorkerStatus aStatus) {
  AssertIsOnParentThread();
  // This method is only called for Canceling or later.
  MOZ_DIAGNOSTIC_ASSERT(aStatus >= Canceling);

  bool pending;
  {
    MutexAutoLock lock(mMutex);

    if (mParentStatus >= aStatus) {
      return true;
    }

    pending = mParentStatus == Pending;
    mParentStatus = aStatus;
  }

  if (mCancellationCallback) {
    mCancellationCallback(!pending);
    mCancellationCallback = nullptr;
  }

  mParentRef->DropWorkerPrivate();

  if (pending) {
#ifdef DEBUG
    {
      // Fake a thread here just so that our assertions don't go off for no
      // reason.
      nsIThread* currentThread = NS_GetCurrentThread();
      MOZ_ASSERT(currentThread);

      MOZ_ASSERT(!mPRThread);
      mPRThread = PRThreadFromThread(currentThread);
      MOZ_ASSERT(mPRThread);
    }
#endif

    // Worker never got a chance to run, go ahead and delete it.
    ScheduleDeletion(WorkerPrivate::WorkerNeverRan);
    return true;
  }

  // No Canceling timeout is needed.
  if (mCancelingTimer) {
    mCancelingTimer->Cancel();
    mCancelingTimer = nullptr;
  }

  // The NotifyRunnable kicks off a series of events that need the
  // CancelingOnParentRunnable to be executed always.
  // Note that we already advanced mParentStatus above and we check that
  // status in all other (asynchronous) call sites of SetIsPaused.
  if (!mParent) {
    MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(false));
  }

  RefPtr<NotifyRunnable> runnable = new NotifyRunnable(this, aStatus);
  return runnable->Dispatch(this);
}

bool WorkerPrivate::Freeze(const nsPIDOMWindowInner* aWindow) {
  AssertIsOnParentThread();

  mParentFrozen = true;

  bool isCanceling = false;
  {
    MutexAutoLock lock(mMutex);

    isCanceling = mParentStatus >= Canceling;
  }

  // WorkerDebuggeeRunnables sent from a worker to content must not be
  // delivered while the worker is frozen.
  //
  // Since a top-level worker and all its children share the same
  // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the
  // top-level worker.
  if (aWindow) {
    // This is called from WorkerPrivate construction, and We may not have
    // allocated mMainThreadDebuggeeEventTarget yet.
    if (mMainThreadDebuggeeEventTarget) {
      // Pausing a ThrottledEventQueue is infallible.
      MOZ_ALWAYS_SUCCEEDS(
          mMainThreadDebuggeeEventTarget->SetIsPaused(!isCanceling));
    }
  }

  if (isCanceling) {
    return true;
  }

  DisableRemoteDebugger();

  if (!UseRemoteDebugger()) {
    DisableDebugger();
  }

  RefPtr<FreezeRunnable> runnable = new FreezeRunnable(this);
  return runnable->Dispatch(this);
}

bool WorkerPrivate::Thaw(const nsPIDOMWindowInner* aWindow) {
  AssertIsOnParentThread();
  MOZ_ASSERT(mParentFrozen);

  mParentFrozen = false;

  {
    bool isCanceling = false;

    {
      MutexAutoLock lock(mMutex);

      isCanceling = mParentStatus >= Canceling;
    }

    // Delivery of WorkerDebuggeeRunnables to the window may resume.
    //
    // Since a top-level worker and all its children share the same
    // mMainThreadDebuggeeEventTarget, it's sufficient to do this only in the
    // top-level worker.
    if (aWindow) {
      // Since the worker is no longer frozen, only a paused parent window
      // should require the queue to remain paused.
      //
      // This can only fail if the ThrottledEventQueue cannot dispatch its
      // executor to the main thread, in which case the main thread was never
      // going to draw runnables from it anyway, so the failure doesn't matter.
      (void)mMainThreadDebuggeeEventTarget->SetIsPaused(
          IsParentWindowPaused() && !isCanceling);
    }

    if (isCanceling) {
      return true;
    }
  }

  // Create remote debugger endpoints here for child binding in ThawRunnable.
  CreateRemoteDebuggerEndpoints();
  RefPtr<ThawRunnable> runnable = new ThawRunnable(this);
  bool rv = runnable->Dispatch(this);
  EnableRemoteDebugger();

  if (!UseRemoteDebugger()) {
    EnableDebugger();
  }

  return rv;
}

void WorkerPrivate::ParentWindowPaused() {
  AssertIsOnMainThread();
  MOZ_ASSERT(!mParentWindowPaused);
  mParentWindowPaused = true;

  // This is called from WorkerPrivate construction, and we may not have
  // allocated mMainThreadDebuggeeEventTarget yet.
  if (mMainThreadDebuggeeEventTarget) {
    bool isCanceling = false;

    {
      MutexAutoLock lock(mMutex);

      isCanceling = mParentStatus >= Canceling;
    }

    // If we are already canceling we might wait for CancelingOnParentRunnable
    // to be executed, so do not pause.
    MOZ_ALWAYS_SUCCEEDS(
        mMainThreadDebuggeeEventTarget->SetIsPaused(!isCanceling));
  }
}

void WorkerPrivate::ParentWindowResumed() {
  AssertIsOnMainThread();

  MOZ_ASSERT(mParentWindowPaused);
  mParentWindowPaused = false;

  bool isCanceling = false;
  {
    MutexAutoLock lock(mMutex);

    isCanceling = mParentStatus >= Canceling;
  }

  // Since the window is no longer paused, the queue should only remain paused
  // if the worker is frozen.
  //
  // This can only fail if the ThrottledEventQueue cannot dispatch its executor
  // to the main thread, in which case the main thread was never going to draw
  // runnables from it anyway, so the failure doesn't matter.
  (void)mMainThreadDebuggeeEventTarget->SetIsPaused(IsFrozen() && !isCanceling);
}

void WorkerPrivate::PropagateStorageAccessPermissionGranted() {
  AssertIsOnParentThread();

  {
    MutexAutoLock lock(mMutex);

    if (mParentStatus >= Canceling) {
      return;
    }
  }

  RefPtr<PropagateStorageAccessPermissionGrantedRunnable> runnable =
      new PropagateStorageAccessPermissionGrantedRunnable(this);
  (void)NS_WARN_IF(!runnable->Dispatch(this));
}

bool WorkerPrivate::Close() {
  mMutex.AssertCurrentThreadOwns();
  if (mParentStatus < Closing) {
    mParentStatus = Closing;
  }

  return true;
}

bool WorkerPrivate::ProxyReleaseMainThreadObjects() {
  AssertIsOnParentThread();
  MOZ_ASSERT(!mMainThreadObjectsForgotten);

  nsCOMPtr<nsILoadGroup> loadGroupToCancel;
  // If we're not overriden, then do nothing here.  Let the load group get
  // handled in ForgetMainThreadObjects().
  if (mLoadInfo.mInterfaceRequestor) {
    mLoadInfo.mLoadGroup.swap(loadGroupToCancel);
  }

  bool result = mLoadInfo.ProxyReleaseMainThreadObjects(
      this, std::move(loadGroupToCancel));

  mMainThreadObjectsForgotten = true;

  return result;
}

void WorkerPrivate::UpdateContextOptions(
    const JS::ContextOptions& aContextOptions) {
  AssertIsOnParentThread();

  {
    MutexAutoLock lock(mMutex);
    mJSSettings.contextOptions = aContextOptions;
  }

  RefPtr<UpdateContextOptionsRunnable> runnable =
      new UpdateContextOptionsRunnable(this, aContextOptions);
  if (!runnable->Dispatch(this)) {
    NS_WARNING("Failed to update worker context options!");
  }
}

void WorkerPrivate::UpdateLanguages(const nsTArray<nsString>& aLanguages) {
  AssertIsOnParentThread();

  RefPtr<UpdateLanguagesRunnable> runnable =
      new UpdateLanguagesRunnable(this, aLanguages);
  if (!runnable->Dispatch(this)) {
    NS_WARNING("Failed to update worker languages!");
  }
}

void WorkerPrivate::UpdateLanguageOverride(
    const nsACString& aLanguageOverride,
    const nsTArray<nsString>& aResolvedLanguages) {
  AssertIsOnParentThread();

  RefPtr<UpdateLanguageOverrideRunnable> runnable =
      new UpdateLanguageOverrideRunnable(this, aLanguageOverride,
                                         aResolvedLanguages);
  if (!runnable->Dispatch(this)) {
    NS_WARNING("Failed to update worker language override!");
  }
}

void WorkerPrivate::UpdateTimezoneOverride(const nsAString& aTimezone) {
  AssertIsOnParentThread();

  RefPtr<UpdateTimezoneOverrideRunnable> runnable =
      new UpdateTimezoneOverrideRunnable(this, aTimezone);
  if (!runnable->Dispatch(this)) {
    NS_WARNING("Failed to update worker timezone override!");
  }
}

void WorkerPrivate::UpdateJSWorkerMemoryParameter(JSGCParamKey aKey,
                                                  Maybe<uint32_t> aValue) {
  AssertIsOnParentThread();

  bool changed = false;

  {
    MutexAutoLock lock(mMutex);
    changed = mJSSettings.ApplyGCSetting(aKey, aValue);
  }

  if (changed) {
    RefPtr<UpdateJSWorkerMemoryParameterRunnable> runnable =
        new UpdateJSWorkerMemoryParameterRunnable(this, aKey, aValue);
    if (!runnable->Dispatch(this)) {
      NS_WARNING("Failed to update memory parameter!");
    }
  }
}

#ifdef JS_GC_ZEAL
void WorkerPrivate::UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency) {
  AssertIsOnParentThread();

  {
    MutexAutoLock lock(mMutex);
    mJSSettings.gcZeal = aGCZeal;
    mJSSettings.gcZealFrequency = aFrequency;
  }

  RefPtr<UpdateGCZealRunnable> runnable =
      new UpdateGCZealRunnable(this, aGCZeal, aFrequency);
  if (!runnable->Dispatch(this)) {
    NS_WARNING("Failed to update worker gczeal!");
  }
}
#endif

void WorkerPrivate::SetLowMemoryState(bool aState) {
  AssertIsOnParentThread();

  RefPtr<SetLowMemoryStateRunnable> runnable =
      new SetLowMemoryStateRunnable(this, aState);
  if (!runnable->Dispatch(this)) {
    NS_WARNING("Failed to set low memory state!");
  }
}

void WorkerPrivate::GarbageCollect(bool aShrinking) {
  AssertIsOnParentThread();

  RefPtr<GarbageCollectRunnable> runnable = new GarbageCollectRunnable(
      this, aShrinking, /* aCollectChildren = */ true);
  if (!runnable->Dispatch(this)) {
    NS_WARNING("Failed to GC worker!");
  }
}

void WorkerPrivate::CycleCollect() {
  AssertIsOnParentThread();

  RefPtr<CycleCollectRunnable> runnable =
      new CycleCollectRunnable(this, /* aCollectChildren = */ true);
  if (!runnable->Dispatch(this)) {
    NS_WARNING("Failed to CC worker!");
  }
}

void WorkerPrivate::OfflineStatusChangeEvent(bool aIsOffline) {
  AssertIsOnParentThread();

  RefPtr<OfflineStatusChangeRunnable> runnable =
      new OfflineStatusChangeRunnable(this, aIsOffline);
  if (!runnable->Dispatch(this)) {
    NS_WARNING("Failed to dispatch offline status change event!");
  }
}

void WorkerPrivate::OfflineStatusChangeEventInternal(bool aIsOffline) {
  auto data = mWorkerThreadAccessible.Access();

  // The worker is already in this state. No need to dispatch an event.
  if (data->mOnLine == !aIsOffline) {
    return;
  }

  if (ShouldResistFingerprinting(RFPTarget::NetworkConnection)) {
    // We always report the worker as online if resistFingerprinting is
    // enabled, regardless of the actual network status.
    return;
  }

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); ++index) {
    data->mChildWorkers[index]->OfflineStatusChangeEvent(aIsOffline);
  }

  data->mOnLine = !aIsOffline;
  WorkerGlobalScope* globalScope = GlobalScope();
  RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
  if (nav) {
    nav->SetOnLine(data->mOnLine);
  }

  nsString eventType;
  if (aIsOffline) {
    eventType.AssignLiteral("offline");
  } else {
    eventType.AssignLiteral("online");
  }

  RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);

  event->InitEvent(eventType, false, false);
  event->SetTrusted(true);

  globalScope->DispatchEvent(*event);
}

void WorkerPrivate::MemoryPressure() {
  AssertIsOnParentThread();

  RefPtr<MemoryPressureRunnable> runnable = new MemoryPressureRunnable(this);
  (void)NS_WARN_IF(!runnable->Dispatch(this));
}

RefPtr<WorkerPrivate::JSMemoryUsagePromise> WorkerPrivate::GetJSMemoryUsage() {
  AssertIsOnMainThread();

  {
    MutexAutoLock lock(mMutex);
    // If we have started shutting down the worker, do not dispatch a runnable
    // to measure its memory.
    if (ParentStatus() > Running) {
      return nullptr;
    }
  }

  return InvokeAsync(ControlEventTarget(), __func__, []() {
    WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
    MOZ_ASSERT(wp);
    wp->AssertIsOnWorkerThread();
    MutexAutoLock lock(wp->mMutex);
    return JSMemoryUsagePromise::CreateAndResolve(
        js::GetGCHeapUsage(wp->mJSContext), __func__);
  });
}

void WorkerPrivate::WorkerScriptLoaded() {
  AssertIsOnMainThread();

  if (IsSharedWorker() || IsServiceWorker()) {
    // No longer need to hold references to the window or document we came from.
    mLoadInfo.mWindow = nullptr;
    mLoadInfo.mScriptContext = nullptr;
  }
}

void WorkerPrivate::SetBaseURI(nsIURI* aBaseURI) {
  AssertIsOnMainThread();

  if (!mLoadInfo.mBaseURI) {
    NS_ASSERTION(GetParent(), "Shouldn't happen without a parent!");
    mLoadInfo.mResolvedScriptURI = aBaseURI;
  }

  mLoadInfo.mBaseURI = aBaseURI;

  if (NS_FAILED(aBaseURI->GetSpec(mLocationInfo.mHref))) {
    mLocationInfo.mHref.Truncate();
  }

  mLocationInfo.mHostname.Truncate();
  nsContentUtils::GetHostOrIPv6WithBrackets(aBaseURI, mLocationInfo.mHostname);

  nsCOMPtr<nsIURL> url(do_QueryInterface(aBaseURI));
  if (!url || NS_FAILED(url->GetFilePath(mLocationInfo.mPathname))) {
    mLocationInfo.mPathname.Truncate();
  }

  nsCString temp;

  if (url && NS_SUCCEEDED(url->GetQuery(temp)) && !temp.IsEmpty()) {
    mLocationInfo.mSearch.Assign('?');
    mLocationInfo.mSearch.Append(temp);
  }

  if (NS_SUCCEEDED(aBaseURI->GetRef(temp)) && !temp.IsEmpty()) {
    if (mLocationInfo.mHash.IsEmpty()) {
      mLocationInfo.mHash.Assign('#');
      mLocationInfo.mHash.Append(temp);
    }
  }

  if (NS_SUCCEEDED(aBaseURI->GetScheme(mLocationInfo.mProtocol))) {
    mLocationInfo.mProtocol.Append(':');
  } else {
    mLocationInfo.mProtocol.Truncate();
  }

  int32_t port;
  if (NS_SUCCEEDED(aBaseURI->GetPort(&port)) && port != -1) {
    mLocationInfo.mPort.AppendInt(port);

    nsAutoCString host(mLocationInfo.mHostname);
    host.Append(':');
    host.Append(mLocationInfo.mPort);

    mLocationInfo.mHost.Assign(host);
  } else {
    mLocationInfo.mHost.Assign(mLocationInfo.mHostname);
  }

  nsContentUtils::GetWebExposedOriginSerialization(aBaseURI,
                                                   mLocationInfo.mOrigin);
}

nsresult WorkerPrivate::SetPrincipalsAndCSPOnMainThread(
    nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
    nsILoadGroup* aLoadGroup, nsIContentSecurityPolicy* aCsp) {
  return mLoadInfo.SetPrincipalsAndCSPOnMainThread(
      aPrincipal, aPartitionedPrincipal, aLoadGroup, aCsp);
}

nsresult WorkerPrivate::SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel) {
  return mLoadInfo.SetPrincipalsAndCSPFromChannel(aChannel);
}

bool WorkerPrivate::FinalChannelPrincipalIsValid(nsIChannel* aChannel) {
  return mLoadInfo.FinalChannelPrincipalIsValid(aChannel);
}

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
bool WorkerPrivate::PrincipalURIMatchesScriptURL() {
  return mLoadInfo.PrincipalURIMatchesScriptURL();
}
#endif

void WorkerPrivate::UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup) {
  AssertIsOnMainThread();

  // The load group should have been overriden at init time.
  mLoadInfo.mInterfaceRequestor->MaybeAddBrowserChild(aBaseLoadGroup);
}

void WorkerPrivate::UpdateIsOnContentBlockingAllowList(
    bool aOnContentBlockingAllowList) {
  AssertIsOnWorkerThread();
  MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());

  RefPtr<StrongWorkerRef> strongRef = StrongWorkerRef::Create(
      this, "WorkerPrivate::UpdateIsOnContentBlockingAllowList");
  if (!strongRef) {
    return;
  }
  RefPtr<ThreadSafeWorkerRef> ref = new ThreadSafeWorkerRef(strongRef);
  DispatchToMainThread(NS_NewRunnableFunction(
      "WorkerPrivate::UpdateIsOnContentBlockingAllowList",
      [ref = std::move(ref), aOnContentBlockingAllowList] {
        ref->Private()
            ->mLoadInfo.mCookieJarSettingsArgs.isOnContentBlockingAllowList() =
            aOnContentBlockingAllowList;

        nsCOMPtr<nsICookieJarSettings> workerCookieJarSettings;
        net::CookieJarSettings::Deserialize(
            ref->Private()->mLoadInfo.mCookieJarSettingsArgs,
            getter_AddRefs(workerCookieJarSettings));
        bool shouldResistFingerprinting =
            nsContentUtils::ShouldResistFingerprinting_dangerous(
                ref->Private()->mLoadInfo.mPrincipal,
                "Service Workers exist outside a Document or Channel; as a "
                "property of the domain (and origin attributes). We don't have "
                "a "
                "CookieJarSettings to perform the *nested check*, but we can "
                "rely "
                "on the FPI/dFPI partition key check. The WorkerPrivate's "
                "ShouldResistFingerprinting function for the ServiceWorker "
                "depends "
                "on this boolean and will also consider an explicit RFPTarget.",
                RFPTarget::IsAlwaysEnabledForPrecompute) &&
            !nsContentUtils::ETPSaysShouldNotResistFingerprinting(
                workerCookieJarSettings, false);

        ref->Private()
            ->mLoadInfo.mCookieJarSettingsArgs.shouldResistFingerprinting() =
            shouldResistFingerprinting;
        ref->Private()->mLoadInfo.mShouldResistFingerprinting =
            shouldResistFingerprinting;
      }));

  /* From:
    https://searchfox.org/mozilla-central/rev/964b8aa226c68bbf83c9ffc38984804734bb0de2/js/public/RealmOptions.h#316-318
    > RealmCreationOptions specify fundamental realm characteristics that must
    be specified when the realm is created, that can't be changed after the
    realm is created.
  */
  /*
  nsCString locale;
  if (aEnabled) {
    locale = nsRFPService::GetSpoofedJSLocale();
  }

  MutexAutoLock lock(mMutex);
  mJSSettings.chromeRealmOptions.creationOptions().setForceUTC(aEnabled);
  mJSSettings.chromeRealmOptions.creationOptions().setAlwaysUseFdlibm(aEnabled);
  if (aEnabled) {
    mJSSettings.chromeRealmOptions.creationOptions().setLocaleCopyZ(
        locale.get());
  }

  mJSSettings.contentRealmOptions.creationOptions().setForceUTC(aEnabled);
  mJSSettings.contentRealmOptions.creationOptions().setAlwaysUseFdlibm(
      aEnabled);
  if (aEnabled) {
    mJSSettings.contentRealmOptions.creationOptions().setLocaleCopyZ(
        locale.get());
  }
  */
}

bool WorkerPrivate::IsOnParentThread() const {
  if (GetParent()) {
    return GetParent()->IsOnWorkerThread();
  }
  return NS_IsMainThread();
}

#ifdef DEBUG

void WorkerPrivate::AssertIsOnParentThread() const {
  if (GetParent()) {
    GetParent()->AssertIsOnWorkerThread();
  } else {
    AssertIsOnMainThread();
  }
}

void WorkerPrivate::AssertInnerWindowIsCorrect() const {
  AssertIsOnParentThread();

  // Only care about top level workers from windows.
  if (mParent || !mLoadInfo.mWindow) {
    return;
  }

  AssertIsOnMainThread();

  nsPIDOMWindowOuter* outer = mLoadInfo.mWindow->GetOuterWindow();
  NS_ASSERTION(outer && outer->GetCurrentInnerWindow() == mLoadInfo.mWindow,
               "Inner window no longer correct!");
}

#endif

#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
bool WorkerPrivate::PrincipalIsValid() const {
  return mLoadInfo.PrincipalIsValid();
}
#endif

WorkerPrivate::WorkerThreadAccessible::WorkerThreadAccessible(
    WorkerPrivate* const aParent)
    : mNumWorkerRefsPreventingShutdownStart(0),
      mDebuggerEventLoopLevel(0),
      mNonblockingCCBackgroundActorCount(0),
      mErrorHandlerRecursionCount(0),
      mFrozen(false),
      mDebuggerInterruptRequested(false),
      mPeriodicGCTimerRunning(false),
      mIdleGCTimerRunning(false),
      mOnLine(aParent ? aParent->OnLine() : !NS_IsOffline()),
      mJSThreadExecutionGranted(false),
      mCCCollectedAnything(false) {}

namespace {

bool IsNewWorkerSecureContext(const WorkerPrivate* const aParent,
                              const WorkerKind aWorkerKind,
                              const WorkerLoadInfo& aLoadInfo) {
  if (aParent) {
    return aParent->IsSecureContext();
  }

  // Our secure context state depends on the kind of worker we have.

  if (aLoadInfo.mPrincipal && aLoadInfo.mPrincipal->IsSystemPrincipal()) {
    return true;
  }

  if (aWorkerKind == WorkerKindService) {
    return true;
  }

  if (aLoadInfo.mSecureContext != WorkerLoadInfo::eNotSet) {
    return aLoadInfo.mSecureContext == WorkerLoadInfo::eSecureContext;
  }

  MOZ_ASSERT_UNREACHABLE(
      "non-chrome worker that is not a service worker "
      "that has no parent and no associated window");

  return false;
}

}  // namespace

WorkerPrivate::WorkerPrivate(
    WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker,
    WorkerKind aWorkerKind, RequestCredentials aRequestCredentials,
    enum WorkerType aWorkerType, const nsAString& aWorkerName,
    const nsACString& aServiceWorkerScope, WorkerLoadInfo& aLoadInfo,
    nsString&& aId, const nsID& aAgentClusterId,
    const nsILoadInfo::CrossOriginOpenerPolicy aAgentClusterOpenerPolicy,
    CancellationCallback&& aCancellationCallback,
    TerminationCallback&& aTerminationCallback,
    mozilla::ipc::Endpoint<PRemoteWorkerNonLifeCycleOpControllerChild>&&
        aChildEp)
    : mMutex("WorkerPrivate Mutex"),
      mCondVar(mMutex, "WorkerPrivate CondVar"),
      mParent(aParent),
      mScriptURL(aScriptURL),
      mWorkerName(aWorkerName),
      mCredentialsMode(aRequestCredentials),
      mWorkerType(aWorkerType),  // If the worker runs as a script or a module
      mWorkerKind(aWorkerKind),
      mCancellationCallback(std::move(aCancellationCallback)),
      mTerminationCallback(std::move(aTerminationCallback)),
      mLoadInfo(std::move(aLoadInfo)),
      mDebugger(nullptr),
      mDispatchingControlRunnables(0),
      mJSContext(nullptr),
      mPRThread(nullptr),
      mWorkerControlEventTarget(new WorkerEventTarget(
          this, WorkerEventTarget::Behavior::ControlOnly)),
      mWorkerHybridEventTarget(
          new WorkerEventTarget(this, WorkerEventTarget::Behavior::Hybrid)),
      mChildEp(std::move(aChildEp)),
      mRemoteDebuggerRegistered(false),
      mRemoteDebuggerReady(true),
      mIsQueued(false),
      // Route the worker through the RemoteWorkerDebugger, including top-level
      // and nested parent-process workers (nested parent workers register via
      // branch 1 of RemoteWorkerService::RegisterRemoteDebugger, using the
      // parent's intra-process mDebuggerManagerChild).
      //
      // A content-process worker always has a RemoteWorkerService (it backs
      // every remote worker), but a parent-process worker can be constructed
      // before the service is initialized -- or in a process that never
      // initializes it, such as xpcshell -- in which case
      // RegisterRemoteDebugger would dereference the null singleton and crash.
      // Only route a parent-process worker to the remote debugger once the
      // service is available; otherwise it uses the local WorkerDebugger (also
      // the pref-off rollback path). The decision is latched here and read
      // again in EnableRemoteDebugger on the same (synchronous) construction
      // path, so the service cannot disappear in between.
      mUseRemoteDebugger(
          StaticPrefs::dom_worker_remoteDebugger_enabled() &&
          (!XRE_IsParentProcess() || RemoteWorkerService::IsInitialized())),
      mDebuggerBindingCondVar(mMutex,
                              "WorkerPrivate RemoteDebuggerBindingCondVar"),
      mWorkerDebuggerEventTarget(new WorkerEventTarget(
          this, WorkerEventTarget::Behavior::DebuggerOnly)),
      mParentStatus(Pending),
      mStatus(Pending),
      mCreationTimeStamp(TimeStamp::Now()),
      mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC),
      mReportedUseCounters(false),
      mAgentClusterId(aAgentClusterId),
      mWorkerThreadAccessible(aParent),
      mPostSyncLoopOperations(0),
      mParentWindowPaused(false),
      mWorkerScriptExecutedSuccessfully(false),
      mFetchHandlerWasAdded(false),
      mMainThreadObjectsForgotten(false),
      mIsChromeWorker(aIsChromeWorker),
      mParentFrozen(false),
      mIsSecureContext(
          IsNewWorkerSecureContext(mParent, mWorkerKind, mLoadInfo)),
      mDebuggerRegistered(false),
      mIsInBackground(false),
      mDebuggerReady(true),
      mExtensionAPIAllowed(false),
      mIsInAutomation(false),
      mId(std::move(aId)),
      mAgentClusterOpenerPolicy(aAgentClusterOpenerPolicy),
      mIsPrivilegedAddonGlobal(false),
      mTopLevelWorkerFinishedRunnableCount(0),
      mWorkerFinishedRunnableCount(0),
      mFontVisibility(ComputeFontVisibility()) {
  LOG(WorkerLog(), ("WorkerPrivate::WorkerPrivate [%p]", this));
  MOZ_ASSERT_IF(!IsDedicatedWorker(), NS_IsMainThread());

  if (aParent) {
    aParent->AssertIsOnWorkerThread();

    // Note that this copies our parent's secure context state into mJSSettings.
    aParent->CopyJSSettings(mJSSettings);

    MOZ_ASSERT_IF(mIsChromeWorker, mIsSecureContext);

    mIsInAutomation = aParent->IsInAutomation();

    MOZ_ASSERT(IsDedicatedWorker());

    if (aParent->mParentFrozen) {
      Freeze(nullptr);
    }

    if (aParent->IsRunningInBackground()) {
      mIsInBackground = true;
    }
    if (aParent->IsPlayingAudio()) {
      mIsPlayingAudio = true;
    }

    if (aParent->HasActivePeerConnections()) {
      mHasActivePeerConnections = true;
    }

    mIsPrivilegedAddonGlobal = aParent->mIsPrivilegedAddonGlobal;
  } else {
    AssertIsOnMainThread();

    RuntimeService::GetDefaultJSSettings(mJSSettings);

    {
      JS::RealmOptions& chromeRealmOptions = mJSSettings.chromeRealmOptions;
      JS::RealmOptions& contentRealmOptions = mJSSettings.contentRealmOptions;

      const nsCString& languageOverride = mLoadInfo.mLanguageOverrideLocale;
      const nsAString& timezoneOverride = mLoadInfo.mTimezoneOverride;

      xpc::InitGlobalObjectOptions(
          chromeRealmOptions, UsesSystemPrincipal(), mIsSecureContext,
          ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
          ShouldResistFingerprinting(RFPTarget::JSMathFdlibm),
          ShouldResistFingerprinting(RFPTarget::JSLocale), languageOverride,
          timezoneOverride);
      xpc::InitGlobalObjectOptions(
          contentRealmOptions, UsesSystemPrincipal(), mIsSecureContext,
          ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
          ShouldResistFingerprinting(RFPTarget::JSMathFdlibm),
          ShouldResistFingerprinting(RFPTarget::JSLocale), languageOverride,
          timezoneOverride);
      // Check if it's a privileged addon executing in order to allow access
      // to SharedArrayBuffer
      if (mLoadInfo.mPrincipal) {
        if (auto* policy =
                BasePrincipal::Cast(mLoadInfo.mPrincipal)->AddonPolicy()) {
          if (policy->IsPrivileged() &&
              ExtensionPolicyService::GetSingleton().IsExtensionProcess()) {
            // Privileged extensions are allowed to use SharedArrayBuffer in
            // their extension process, but never in content scripts in
            // content processes.
            mIsPrivilegedAddonGlobal = true;
          }

          if (StaticPrefs::
                  extensions_backgroundServiceWorker_enabled_AtStartup() &&
              mWorkerKind == WorkerKindService &&
              policy->IsManifestBackgroundWorker(mScriptURL)) {
            // Only allows ExtensionAPI for extension service workers
            // that are declared in the extension manifest json as
            // the background service worker.
            mExtensionAPIAllowed = true;
          }
        }
      }

      // The SharedArrayBuffer global constructor property should not be present
      // in a fresh global object when shared memory objects aren't allowed
      // (because COOP/COEP support isn't enabled, or because COOP/COEP don't
      // act to isolate this worker to a separate process).
      const bool defineSharedArrayBufferConstructor = IsSharedMemoryAllowed();
      chromeRealmOptions.creationOptions()
          .setDefineSharedArrayBufferConstructor(
              defineSharedArrayBufferConstructor);
      contentRealmOptions.creationOptions()
          .setDefineSharedArrayBufferConstructor(
              defineSharedArrayBufferConstructor);
    }

    mIsInAutomation = xpc::IsInAutomation();

    // Our parent can get suspended after it initiates the async creation
    // of a new worker thread.  In this case suspend the new worker as well.
    if (mLoadInfo.mWindow &&
        nsGlobalWindowInner::Cast(mLoadInfo.mWindow)->IsSuspended()) {
      ParentWindowPaused();
    }

    if (mLoadInfo.mWindow &&
        nsGlobalWindowInner::Cast(mLoadInfo.mWindow)->IsFrozen()) {
      Freeze(mLoadInfo.mWindow);
    }

    if (mLoadInfo.mWindow && mLoadInfo.mWindow->GetOuterWindow() &&
        mLoadInfo.mWindow->GetOuterWindow()->IsBackground()) {
      mIsInBackground = true;
    }

    if (mLoadInfo.mWindow &&
        nsGlobalWindowInner::Cast(mLoadInfo.mWindow)->IsPlayingAudio()) {
      SetIsPlayingAudio(true);
    }

    if (mLoadInfo.mWindow && nsGlobalWindowInner::Cast(mLoadInfo.mWindow)
                                 ->HasActivePeerConnections()) {
      SetActivePeerConnections(true);
    }
  }

  nsCOMPtr<nsISerialEventTarget> target;

  // A child worker just inherits the parent workers ThrottledEventQueue
  // and main thread target for now.  This is mainly due to the restriction
  // that ThrottledEventQueue can only be created on the main thread at the
  // moment.
  if (aParent) {
    mMainThreadEventTargetForMessaging =
        aParent->mMainThreadEventTargetForMessaging;
    mMainThreadEventTarget = aParent->mMainThreadEventTarget;
    mMainThreadDebuggeeEventTarget = aParent->mMainThreadDebuggeeEventTarget;
    return;
  }

  MOZ_ASSERT(NS_IsMainThread());
  target = GetWindow()
               ? GetWindow()->GetBrowsingContextGroup()->GetWorkerEventQueue()
               : nullptr;

  if (!target) {
    target = GetMainThreadSerialEventTarget();
    MOZ_DIAGNOSTIC_ASSERT(target);
  }

  // Throttle events to the main thread using a ThrottledEventQueue specific to
  // this tree of worker threads.
  mMainThreadEventTargetForMessaging =
      ThrottledEventQueue::Create(target, "Worker queue for messaging");
  mMainThreadEventTarget = ThrottledEventQueue::Create(
      GetMainThreadSerialEventTarget(), "Worker queue",
      nsIRunnablePriority::PRIORITY_MEDIUMHIGH);
  mMainThreadDebuggeeEventTarget =
      ThrottledEventQueue::Create(target, "Worker debuggee queue");
  if (IsParentWindowPaused() || IsFrozen()) {
    MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(true));
  }
}

WorkerPrivate::~WorkerPrivate() {
  MOZ_DIAGNOSTIC_ASSERT(mTopLevelWorkerFinishedRunnableCount == 0);
  MOZ_DIAGNOSTIC_ASSERT(mWorkerFinishedRunnableCount == 0);
  MOZ_DIAGNOSTIC_ASSERT(mPendingJSAsyncTasks.empty());

  mWorkerDebuggerEventTarget->ForgetWorkerPrivate(this);

  mWorkerControlEventTarget->ForgetWorkerPrivate(this);

  // We force the hybrid event target to forget the thread when we
  // enter the Killing state, but we do it again here to be safe.
  // Its possible that we may be created and destroyed without progressing
  // to Killing via some obscure code path.
  mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
}

WorkerPrivate::AgentClusterIdAndCoop
WorkerPrivate::ComputeAgentClusterIdAndCoop(WorkerPrivate* aParent,
                                            WorkerKind aWorkerKind,
                                            WorkerLoadInfo* aLoadInfo,
                                            bool aIsChromeWorker) {
  nsILoadInfo::CrossOriginOpenerPolicy agentClusterCoop =
      nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;

  if (aParent) {
    MOZ_ASSERT(aWorkerKind == WorkerKind::WorkerKindDedicated);

    return {aParent->AgentClusterId(), aParent->mAgentClusterOpenerPolicy};
  }

  AssertIsOnMainThread();

  if (aWorkerKind == WorkerKind::WorkerKindService ||
      aWorkerKind == WorkerKind::WorkerKindShared) {
    return {aLoadInfo->mAgentClusterId, agentClusterCoop};
  }

  if (aLoadInfo->mWindow) {
    Document* doc = aLoadInfo->mWindow->GetExtantDoc();
    MOZ_DIAGNOSTIC_ASSERT(doc);
    RefPtr<DocGroup> docGroup = doc->GetDocGroup();

    nsID agentClusterId =
        docGroup ? docGroup->AgentClusterId() : nsID::GenerateUUID();

    BrowsingContext* bc = aLoadInfo->mWindow->GetBrowsingContext();
    MOZ_DIAGNOSTIC_ASSERT(bc);
    return {agentClusterId, bc->Top()->GetOpenerPolicy()};
  }

  // Chrome workers share an AgentCluster with the XPC module global. This
  // allows things like shared memory and WASM modules to be transferred between
  // chrome workers and system JS.
  // Also set COOP+COEP flags to allow access to shared memory.
  if (aIsChromeWorker) {
    if (nsIGlobalObject* systemGlobal =
            xpc::NativeGlobal(xpc::PrivilegedJunkScope())) {
      nsID agentClusterId = systemGlobal->GetAgentClusterId().valueOrFrom(
          [] { return nsID::GenerateUUID(); });
      return {
          agentClusterId,
          nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP};
    }
  }

  // If the window object was failed to be set into the WorkerLoadInfo, we
  // make the worker into another agent cluster group instead of failures.
  return {nsID::GenerateUUID(), agentClusterCoop};
}

// static
already_AddRefed<WorkerPrivate> WorkerPrivate::Constructor(
    JSContext* aCx, const nsAString& aScriptURL, bool aIsChromeWorker,
    WorkerKind aWorkerKind, RequestCredentials aRequestCredentials,
    enum WorkerType aWorkerType, const nsAString& aWorkerName,
    const nsACString& aServiceWorkerScope, WorkerLoadInfo* aLoadInfo,
    ErrorResult& aRv, nsString aId,
    CancellationCallback&& aCancellationCallback,
    TerminationCallback&& aTerminationCallback,
    mozilla::ipc::Endpoint<PRemoteWorkerNonLifeCycleOpControllerChild>&&
        aChildEp) {
  WorkerPrivate* parent =
      NS_IsMainThread() ? nullptr : GetCurrentThreadWorkerPrivate();

  // If this is a sub-worker, we need to keep the parent worker alive until this
  // one is registered.
  RefPtr<StrongWorkerRef> workerRef;
  if (parent) {
    parent->AssertIsOnWorkerThread();

    workerRef = StrongWorkerRef::Create(parent, "WorkerPrivate::Constructor");
    if (NS_WARN_IF(!workerRef)) {
      aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
      return nullptr;
    }
  } else {
    AssertIsOnMainThread();

    // Ensure the process is untrusted before it runs any workers.
    ContentChild::MaybeBecomeUntrusted();
  }

  Maybe<WorkerLoadInfo> stackLoadInfo;
  if (!aLoadInfo) {
    stackLoadInfo.emplace();

    nsresult rv = GetLoadInfo(
        aCx, nullptr, parent, aScriptURL, aWorkerType, aRequestCredentials,
        aIsChromeWorker, InheritLoadGroup, aWorkerKind, stackLoadInfo.ptr());
    aRv.MightThrowJSException();
    if (NS_FAILED(rv)) {
      workerinternals::ReportLoadError(aRv, rv, aScriptURL);
      return nullptr;
    }

    aLoadInfo = stackLoadInfo.ptr();
  }

  // NB: This has to be done before creating the WorkerPrivate, because it will
  // attempt to use static variables that are initialized in the RuntimeService
  // constructor.
  RuntimeService* runtimeService;

  if (!parent) {
    runtimeService = RuntimeService::GetOrCreateService();
    if (!runtimeService) {
      aRv.Throw(NS_ERROR_FAILURE);
      return nullptr;
    }
  } else {
    runtimeService = RuntimeService::GetService();
  }

  MOZ_ASSERT(runtimeService);

  // Don't create a worker with the shutting down RuntimeService.
  if (runtimeService->IsShuttingDown()) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  AgentClusterIdAndCoop idAndCoop = ComputeAgentClusterIdAndCoop(
      parent, aWorkerKind, aLoadInfo, aIsChromeWorker);

  RefPtr<WorkerPrivate> worker = new WorkerPrivate(
      parent, aScriptURL, aIsChromeWorker, aWorkerKind, aRequestCredentials,
      aWorkerType, aWorkerName, aServiceWorkerScope, *aLoadInfo, std::move(aId),
      idAndCoop.mId, idAndCoop.mCoop, std::move(aCancellationCallback),
      std::move(aTerminationCallback), std::move(aChildEp));

  // Gecko contexts always have an explicitly-set default locale (set by
  // XPJSRuntime::Initialize for the main thread, set by
  // WorkerThreadPrimaryRunnable::Run for workers just before running worker
  // code), so this is never SpiderMonkey's builtin default locale.
  JS::UniqueChars defaultLocale = JS_GetDefaultLocale(aCx);
  if (NS_WARN_IF(!defaultLocale)) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  worker->mDefaultLocale = std::move(defaultLocale);

  // Create the remote debugger endpoints here so the child endpoint can be
  // bound in WorkerThreadPrimaryRunnable. Self-gates on UseRemoteDebugger().
  worker->CreateRemoteDebuggerEndpoints();

  if (!runtimeService->RegisterWorker(*worker)) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  // From this point on (worker thread has been started) we
  // must keep ourself alive. We can now only be cleared by
  // ClearSelfAndParentEventTargetRef().
  worker->mSelfRef = worker;
  worker->mParentRef = MakeRefPtr<WorkerParentRef>(worker);

  // The remote and local debugger mechanisms are mutually exclusive. Enable the
  // remote worker debugger once the worker is really scheduled; otherwise
  // register the local nsIWorkerDebugger.
  if (worker->UseRemoteDebugger()) {
    if (!worker->mIsQueued) {
      worker->EnableRemoteDebugger();
    }
  } else {
    worker->EnableDebugger();
  }

  MOZ_DIAGNOSTIC_ASSERT(worker->PrincipalIsValid());

  UniquePtr<SerializedStackHolder> stack;
  if (worker->IsWatchedByDevTools()) {
    stack = GetCurrentStackForNetMonitor(aCx);
  }

  // This should be non-null for dedicated workers and null for Shared and
  // Service workers. All Encoding values are static and will live as long
  // as the process and the convention is to therefore use raw pointers.
  const mozilla::Encoding* aDocumentEncoding =
      NS_IsMainThread() && !worker->GetParent() && worker->GetDocument()
          ? worker->GetDocument()->GetDocumentCharacterSet().get()
          : nullptr;

  RefPtr<CompileScriptRunnable> compiler = new CompileScriptRunnable(
      worker, std::move(stack), aScriptURL, aDocumentEncoding);
  if (!compiler->Dispatch(worker)) {
    aRv.Throw(NS_ERROR_UNEXPECTED);
    return nullptr;
  }

  return worker.forget();
}

// Mark worker private as running in the background tab
// for further throttling
void WorkerPrivate::SetIsRunningInBackground() {
  AssertIsOnParentThread();

  RefPtr<ChangeBackgroundStateRunnable> runnable =
      new ChangeBackgroundStateRunnable(this, true);
  runnable->Dispatch(this);

  LOG(WorkerLog(), ("SetIsRunningInBackground [%p]", this));
}

void WorkerPrivate::SetIsRunningInForeground() {
  AssertIsOnParentThread();

  RefPtr<ChangeBackgroundStateRunnable> runnable =
      new ChangeBackgroundStateRunnable(this, false);
  runnable->Dispatch(this);

  LOG(WorkerLog(), ("SetIsRunningInForeground [%p]", this));
}

void WorkerPrivate::SetIsPlayingAudio(bool aIsPlayingAudio) {
  AssertIsOnParentThread();

  RefPtr<ChangePlaybackStateRunnable> runnable =
      new ChangePlaybackStateRunnable(this, aIsPlayingAudio);
  runnable->Dispatch(this);

  AUTO_PROFILER_MARKER_UNTYPED("WorkerPrivate::SetIsPlayingAudio", DOM, {});
  LOG(WorkerLog(), ("SetIsPlayingAudio [%p]", this));
}

void WorkerPrivate::SetActivePeerConnections(bool aHasPeerConnections) {
  AssertIsOnParentThread();

  RefPtr<ChangeActivePeerConnectionsRunnable> runnable =
      new ChangeActivePeerConnectionsRunnable(this, aHasPeerConnections);
  runnable->Dispatch(this);

  AUTO_PROFILER_MARKER_UNTYPED("WorkerPrivate::SetActivePeerConnections", DOM,
                               {});
  LOG(WorkerLog(), ("SetActivePeerConnections [%p]", this));
}

nsresult WorkerPrivate::SetIsDebuggerReady(bool aReady) {
  AssertIsOnMainThread();
  MutexAutoLock lock(mMutex);

  if (mDebuggerReady == aReady) {
    return NS_OK;
  }

  if (!aReady && mDebuggerRegistered) {
    // The debugger can only be marked as not ready during registration.
    return NS_ERROR_FAILURE;
  }

  mDebuggerReady = aReady;

  bool debuggerRegistered = mDebuggerRegistered && (mRemoteDebuggerRegistered ||
                                                    XRE_IsParentProcess());

  if (aReady && debuggerRegistered) {
    // Dispatch all the delayed runnables without releasing the lock, to ensure
    // that the order in which debuggee runnables execute is the same as the
    // order in which they were originally dispatched.
    auto pending = std::move(mDelayedDebuggeeRunnables);
    for (uint32_t i = 0; i < pending.Length(); i++) {
      RefPtr<WorkerRunnable> runnable = std::move(pending[i]);
      nsresult rv = DispatchLockHeld(runnable.forget(), nullptr, lock);
      NS_ENSURE_SUCCESS(rv, rv);
    }
    MOZ_RELEASE_ASSERT(mDelayedDebuggeeRunnables.IsEmpty());
  }

  return NS_OK;
}

// static
nsresult WorkerPrivate::GetLoadInfo(
    JSContext* aCx, nsPIDOMWindowInner* aWindow, WorkerPrivate* aParent,
    const nsAString& aScriptURL, const enum WorkerType& aWorkerType,
    const RequestCredentials& aCredentials, bool aIsChromeWorker,
    LoadGroupBehavior aLoadGroupBehavior, WorkerKind aWorkerKind,
    WorkerLoadInfo* aLoadInfo) {
  using namespace mozilla::dom::workerinternals;

  MOZ_ASSERT(aCx);
  MOZ_ASSERT_IF(NS_IsMainThread(),
                aCx == nsContentUtils::GetCurrentJSContext());

  if (aWindow) {
    AssertIsOnMainThread();
  }

  WorkerLoadInfo loadInfo;
  nsresult rv;

  if (aParent) {
    aParent->AssertIsOnWorkerThread();

    // If the parent is going away give up now.
    WorkerStatus parentStatus;
    {
      MutexAutoLock lock(aParent->mMutex);
      parentStatus = aParent->mStatus;
    }

    if (parentStatus > Running) {
      return NS_ERROR_FAILURE;
    }

    // Passing a pointer to our stack loadInfo is safe here because this
    // method uses a sync runnable to get the channel from the main thread.
    rv = ChannelFromScriptURLWorkerThread(aCx, aParent, aScriptURL, aWorkerType,
                                          aCredentials, loadInfo);
    if (NS_FAILED(rv)) {
      MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
      return rv;
    }

    // Now that we've spun the loop there's no guarantee that our parent is
    // still alive.  We may have received control messages initiating shutdown.
    {
      MutexAutoLock lock(aParent->mMutex);
      parentStatus = aParent->mStatus;
    }

    if (parentStatus > Running) {
      MOZ_ALWAYS_TRUE(loadInfo.ProxyReleaseMainThreadObjects(aParent));
      return NS_ERROR_FAILURE;
    }

    loadInfo.mTrials = aParent->Trials();
    loadInfo.mDomain = aParent->Domain();
    loadInfo.mFromWindow = aParent->IsFromWindow();
    loadInfo.mWindowID = aParent->WindowID();
    loadInfo.mAssociatedBrowsingContextID =
        aParent->AssociatedBrowsingContextID();
    loadInfo.mLanguageOverrideLocale = aParent->GetLanguageOverrideLocale();
    loadInfo.mLanguageOverride = aParent->GetLanguageOverride().Clone();
    loadInfo.mStorageAccess = aParent->StorageAccess();
    loadInfo.mUseRegularPrincipal = aParent->UseRegularPrincipal();
    loadInfo.mUsingStorageAccess = aParent->UsingStorageAccess();
    loadInfo.mSerialAllowed = aParent->SerialAllowed();
    loadInfo.mCookieJarSettings = aParent->CookieJarSettings();
    if (loadInfo.mCookieJarSettings) {
      loadInfo.mCookieJarSettingsArgs = aParent->CookieJarSettingsArgs();
    }
    loadInfo.mOriginAttributes = aParent->GetOriginAttributes();
    loadInfo.mServiceWorkersTestingInWindow =
        aParent->ServiceWorkersTestingInWindow();
    loadInfo.mIsThirdPartyContext = aParent->IsThirdPartyContext();
    loadInfo.mShouldResistFingerprinting = aParent->ShouldResistFingerprinting(
        RFPTarget::IsAlwaysEnabledForPrecompute);
    loadInfo.mOverriddenFingerprintingSettings =
        aParent->GetOverriddenFingerprintingSettings();
    loadInfo.mParentController = aParent->GlobalScope()->GetController();
    loadInfo.mWatchedByDevTools = aParent->IsWatchedByDevTools();
    loadInfo.mIsOn3PCBExceptionList = aParent->IsOn3PCBExceptionList();
    loadInfo.mIPAddressSpace = aParent->mLoadInfo.mIPAddressSpace;
    loadInfo.mTimezoneOverride = aParent->TimezoneOverride();
  } else {
    AssertIsOnMainThread();

    // Make sure that the IndexedDatabaseManager is set up
    IndexedDatabaseManager* idm = IndexedDatabaseManager::GetOrCreate();
    if (idm) {
      (void)NS_WARN_IF(NS_FAILED(idm->EnsureLocale()));
    } else {
      NS_WARNING("Failed to get IndexedDatabaseManager!");
    }

    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
    MOZ_ASSERT(ssm);

    bool isChrome = nsContentUtils::IsSystemCaller(aCx);

    // First check to make sure the caller has permission to make a privileged
    // worker if they called the ChromeWorker/ChromeSharedWorker constructor.
    if (aIsChromeWorker && !isChrome) {
      return NS_ERROR_DOM_SECURITY_ERR;
    }

    // Chrome callers (whether creating a ChromeWorker or Worker) always get the
    // system principal here as they're allowed to load anything. The script
    // loader will refuse to run any script that does not also have the system
    // principal.
    if (isChrome) {
      rv = ssm->GetSystemPrincipal(getter_AddRefs(loadInfo.mLoadingPrincipal));
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // See if we're being called from a window.
    nsCOMPtr<nsPIDOMWindowInner> globalWindow = aWindow;
    if (!globalWindow) {
      globalWindow = xpc::CurrentWindowOrNull(aCx);
    }

    nsCOMPtr<Document> document;
    Maybe<ClientInfo> clientInfo;

    if (globalWindow) {
      // Only use the current inner window, and only use it if the caller can
      // access it.
      if (nsPIDOMWindowOuter* outerWindow = globalWindow->GetOuterWindow()) {
        loadInfo.mWindow = outerWindow->GetCurrentInnerWindow();
      }

      loadInfo.mTrials =
          OriginTrials::FromWindow(nsGlobalWindowInner::Cast(loadInfo.mWindow));

      BrowsingContext* browsingContext = globalWindow->GetBrowsingContext();

      // TODO: fix this for SharedWorkers with multiple documents (bug
      // 1177935)
      loadInfo.mServiceWorkersTestingInWindow =
          browsingContext &&
          browsingContext->Top()->ServiceWorkersTestingEnabled();

      if (!loadInfo.mWindow ||
          (globalWindow != loadInfo.mWindow &&
           !nsContentUtils::CanCallerAccess(loadInfo.mWindow))) {
        return NS_ERROR_DOM_SECURITY_ERR;
      }

      nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(loadInfo.mWindow);
      MOZ_ASSERT(sgo);

      loadInfo.mScriptContext = sgo->GetContext();
      NS_ENSURE_TRUE(loadInfo.mScriptContext, NS_ERROR_FAILURE);

      // If we're called from a window then we can dig out the principal and URI
      // from the document.
      document = loadInfo.mWindow->GetExtantDoc();
      NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);

      loadInfo.mBaseURI = document->GetDocBaseURI();
      loadInfo.mLoadGroup = document->GetDocumentLoadGroup();
      NS_ENSURE_TRUE(loadInfo.mLoadGroup, NS_ERROR_FAILURE);

      clientInfo = globalWindow->GetClientInfo();

      // Use the document's NodePrincipal as loading principal if we're not
      // being called from chrome.
      if (!loadInfo.mLoadingPrincipal) {
        loadInfo.mLoadingPrincipal = document->NodePrincipal();
        NS_ENSURE_TRUE(loadInfo.mLoadingPrincipal, NS_ERROR_FAILURE);

        // We use the document's base domain to limit the number of workers
        // each domain can create. For sandboxed documents, we use the domain
        // of their first non-sandboxed document, walking up until we find
        // one. If we can't find one, we fall back to using the GUID of the
        // null principal as the base domain.
        if (document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
          nsCOMPtr<Document> tmpDoc = document;
          do {
            tmpDoc = tmpDoc->GetInProcessParentDocument();
          } while (tmpDoc && tmpDoc->GetSandboxFlags() & SANDBOXED_ORIGIN);

          if (tmpDoc) {
            // There was an unsandboxed ancestor, yay!
            nsCOMPtr<nsIPrincipal> tmpPrincipal = tmpDoc->NodePrincipal();
            rv = tmpPrincipal->GetBaseDomain(loadInfo.mDomain);
            NS_ENSURE_SUCCESS(rv, rv);
          } else {
            // No unsandboxed ancestor, use our GUID.
            rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain);
            NS_ENSURE_SUCCESS(rv, rv);
          }
        } else {
          // Document creating the worker is not sandboxed.
          rv = loadInfo.mLoadingPrincipal->GetBaseDomain(loadInfo.mDomain);
          NS_ENSURE_SUCCESS(rv, rv);
        }
      }

      NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
                                                  loadInfo.mLoadingPrincipal),
                     NS_ERROR_FAILURE);

      nsCOMPtr<nsIPermissionManager> permMgr =
          do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
      NS_ENSURE_SUCCESS(rv, rv);

      uint32_t perm;
      rv = permMgr->TestPermissionFromPrincipal(loadInfo.mLoadingPrincipal,
                                                "systemXHR"_ns, &perm);
      NS_ENSURE_SUCCESS(rv, rv);

      loadInfo.mXHRParamsAllowed = perm == nsIPermissionManager::ALLOW_ACTION;

      loadInfo.mWatchedByDevTools =
          browsingContext && browsingContext->WatchedByDevTools();

      loadInfo.mReferrerInfo =
          ReferrerInfo::CreateForFetch(loadInfo.mLoadingPrincipal, document);
      loadInfo.mFromWindow = true;
      loadInfo.mWindowID = globalWindow->WindowID();
      loadInfo.mAssociatedBrowsingContextID =
          globalWindow->GetBrowsingContext()->Id();
      const nsCString& languageOverride =
          globalWindow->GetBrowsingContext()->Top()->GetLanguageOverride();
      if (!languageOverride.IsEmpty()) {
        loadInfo.mLanguageOverrideLocale = languageOverride;
        Navigator::GetAcceptLanguages(loadInfo.mLanguageOverride,
                                      &languageOverride);
      }

      // Inherit the document's IP address space for Local Network Access
      // checks in workers.
      if (document->GetPolicyContainer()) {
        loadInfo.mIPAddressSpace = static_cast<uint16_t>(
            PolicyContainer::Cast(document->GetPolicyContainer())
                ->GetIPAddressSpace());
      }
      loadInfo.mStorageAccess = StorageAllowedForWindow(globalWindow);
      loadInfo.mUseRegularPrincipal = document->UseRegularPrincipal();
      loadInfo.mUsingStorageAccess = document->UsingStorageAccess();
      loadInfo.mSerialAllowed =
          FeaturePolicyUtils::IsFeatureAllowed(document, u"serial"_ns);
      loadInfo.mShouldResistFingerprinting =
          document->ShouldResistFingerprinting(
              RFPTarget::IsAlwaysEnabledForPrecompute);
      loadInfo.mOverriddenFingerprintingSettings =
          document->GetOverriddenFingerprintingSettings();
      loadInfo.mIsOn3PCBExceptionList = document->IsOn3PCBExceptionList();
      loadInfo.mTimezoneOverride =
          browsingContext->Top()->GetTimezoneOverride();

      // This is an hack to deny the storage-access-permission for workers of
      // sub-iframes.
      if (loadInfo.mUsingStorageAccess &&
          StorageAllowedForDocument(document) != StorageAccess::eAllow) {
        loadInfo.mUsingStorageAccess = false;
      }
      loadInfo.mIsThirdPartyContext =
          AntiTrackingUtils::IsThirdPartyWindow(globalWindow, nullptr);
      loadInfo.mCookieJarSettings = document->CookieJarSettings();
      if (loadInfo.mCookieJarSettings) {
        auto* cookieJarSettings =
            net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings);
        cookieJarSettings->Serialize(loadInfo.mCookieJarSettingsArgs);
      }
      StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(
          document, loadInfo.mOriginAttributes);
      loadInfo.mParentController = globalWindow->GetController();
      loadInfo.mSecureContext = loadInfo.mWindow->IsSecureContext()
                                    ? WorkerLoadInfo::eSecureContext
                                    : WorkerLoadInfo::eInsecureContext;
    } else {
      // Not a window
      MOZ_ASSERT(isChrome);

      // We're being created outside of a window. Need to figure out the script
      // that is creating us in order for us to use relative URIs later on.
      JS::AutoFilename fileName;
      if (JS::DescribeScriptedCaller(&fileName, aCx)) {
        // In most cases, fileName is URI. In a few other cases
        // (e.g. xpcshell), fileName is a file path. Ideally, we would
        // prefer testing whether fileName parses as an URI and fallback
        // to file path in case of error, but Windows file paths have
        // the interesting property that they can be parsed as bogus
        // URIs (e.g. C:/Windows/Tmp is interpreted as scheme "C",
        // hostname "Windows", path "Tmp"), which defeats this algorithm.
        // Therefore, we adopt the opposite convention.
        nsCOMPtr<nsIFile> scriptFile;
        rv = NS_NewNativeLocalFile(nsDependentCString(fileName.get()),
                                   getter_AddRefs(scriptFile));
        if (NS_SUCCEEDED(rv)) {
          rv = NS_NewFileURI(getter_AddRefs(loadInfo.mBaseURI), scriptFile);
        }
        if (NS_FAILED(rv)) {
          // As expected, fileName is not a path, so proceed with
          // a uri.
          rv = NS_NewURI(getter_AddRefs(loadInfo.mBaseURI), fileName.get());
        }
        if (NS_FAILED(rv)) {
          return rv;
        }
      }
      loadInfo.mXHRParamsAllowed = true;
      loadInfo.mFromWindow = false;
      loadInfo.mWindowID = UINT64_MAX;
      loadInfo.mStorageAccess = StorageAccess::eAllow;
      loadInfo.mUseRegularPrincipal = true;
      loadInfo.mUsingStorageAccess = false;
      loadInfo.mCookieJarSettings =
          mozilla::net::CookieJarSettings::Create(loadInfo.mLoadingPrincipal);
      loadInfo.mShouldResistFingerprinting =
          nsContentUtils::ShouldResistFingerprinting_dangerous(
              loadInfo.mLoadingPrincipal,
              "Unusual situation - we have no document or CookieJarSettings",
              RFPTarget::IsAlwaysEnabledForPrecompute);
      MOZ_ASSERT(loadInfo.mCookieJarSettings);
      auto* cookieJarSettings =
          net::CookieJarSettings::Cast(loadInfo.mCookieJarSettings);
      cookieJarSettings->Serialize(loadInfo.mCookieJarSettingsArgs);

      loadInfo.mOriginAttributes = OriginAttributes();
      loadInfo.mIsThirdPartyContext = false;
      loadInfo.mIsOn3PCBExceptionList = false;
    }

    MOZ_ASSERT(loadInfo.mLoadingPrincipal);
    MOZ_ASSERT(isChrome || !loadInfo.mDomain.IsEmpty());

    if (!loadInfo.mLoadGroup || aLoadGroupBehavior == OverrideLoadGroup) {
      OverrideLoadInfoLoadGroup(loadInfo, loadInfo.mLoadingPrincipal);
    }
    MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadInfo.mLoadGroup,
                                            loadInfo.mLoadingPrincipal));

    // Top level workers' main script use the document charset for the script
    // uri encoding.
    nsCOMPtr<nsIURI> url;
    rv = nsContentUtils::NewURIWithDocumentCharset(
        getter_AddRefs(url), aScriptURL, document, loadInfo.mBaseURI);
    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);

    rv = ChannelFromScriptURLMainThread(
        loadInfo.mLoadingPrincipal, document, loadInfo.mLoadGroup, url,
        aWorkerType, aCredentials, clientInfo, ContentPolicyType(aWorkerKind),
        loadInfo.mCookieJarSettings, loadInfo.mReferrerInfo,
        getter_AddRefs(loadInfo.mChannel));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = NS_GetFinalChannelURI(loadInfo.mChannel,
                               getter_AddRefs(loadInfo.mResolvedScriptURI));
    NS_ENSURE_SUCCESS(rv, rv);

    // We need the correct hasStoragePermission flag for the channel here since
    // we will do a content blocking check later when we set the storage
    // principal for the worker. The channel here won't be opened when we do the
    // check later, so the hasStoragePermission flag is incorrect. To address
    // this, We copy the hasStoragePermission flag from the document if there is
    // a window. The worker is created as the same origin of the window. So, the
    // worker is supposed to have the same storage permission as the window as
    // well as the hasStoragePermission flag.
    nsCOMPtr<nsILoadInfo> channelLoadInfo = loadInfo.mChannel->LoadInfo();
    rv = channelLoadInfo->SetStoragePermission(
        loadInfo.mUsingStorageAccess ? nsILoadInfo::HasStoragePermission
                                     : nsILoadInfo::NoStoragePermission);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = loadInfo.SetPrincipalsAndCSPFromChannel(loadInfo.mChannel);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  MOZ_DIAGNOSTIC_ASSERT(loadInfo.mLoadingPrincipal);
  MOZ_DIAGNOSTIC_ASSERT(loadInfo.PrincipalIsValid());

  *aLoadInfo = std::move(loadInfo);
  return NS_OK;
}

// static
void WorkerPrivate::OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo,
                                              nsIPrincipal* aPrincipal) {
  MOZ_ASSERT(!aLoadInfo.mInterfaceRequestor);
  MOZ_ASSERT(aLoadInfo.mLoadingPrincipal == aPrincipal);

  aLoadInfo.mInterfaceRequestor =
      new WorkerLoadInfo::InterfaceRequestor(aPrincipal, aLoadInfo.mLoadGroup);
  aLoadInfo.mInterfaceRequestor->MaybeAddBrowserChild(aLoadInfo.mLoadGroup);

  // NOTE: this defaults the load context to:
  //  - private browsing = false
  //  - content = true
  //  - use remote tabs = false
  nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);

  nsresult rv =
      loadGroup->SetNotificationCallbacks(aLoadInfo.mInterfaceRequestor);
  MOZ_ALWAYS_SUCCEEDS(rv);

  aLoadInfo.mLoadGroup = std::move(loadGroup);

  MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadInfo.mLoadGroup, aPrincipal));
}

void WorkerPrivate::RunLoopNeverRan() {
  LOG(WorkerLog(), ("WorkerPrivate::RunLoopNeverRan [%p]", this));
  // RunLoopNeverRan is only called in WorkerThreadPrimaryRunnable::Run() to
  // handle cases
  //   1. Fail to get BackgroundChild for the worker thread or
  //   2. Fail to initialize the worker's JS context
  // However, mPreStartRunnables had already dispatched in
  // WorkerThread::SetWorkerPrivateInWorkerThread() where beforing above jobs
  // start. So we need to clean up these dispatched runnables for the worker
  // thread.

  auto data = mWorkerThreadAccessible.Access();
  RefPtr<WorkerThread> thread;
  {
    MutexAutoLock lock(mMutex);

    if (!mPreStartRunnables.IsEmpty()) {
      for (const RefPtr<WorkerThreadRunnable>& runnable : mPreStartRunnables) {
        runnable->mCleanPreStartDispatching = true;
        PROFILER_MARKER("WorkerPrivate::RunLoopNeverRan", OTHER, {},
                        TerminatingFlowMarker,
                        Flow::FromPointer(runnable.get()));
      }
      mPreStartRunnables.Clear();
    }

    // Switch State to Dead
    mStatus = Dead;
    thread = mThread;
  }

  // Clear the dispatched mPreStartRunnables.
  if (thread && NS_HasPendingEvents(thread)) {
    NS_ProcessPendingEvents(nullptr);
  }

  // After mStatus is set to Dead there can be no more
  // WorkerControlRunnables so no need to lock here.
  if (!mControlQueue.IsEmpty()) {
    WorkerRunnable* runnable = nullptr;
    while (mControlQueue.Pop(runnable)) {
      runnable->Release();
    }
  }

  // There should be no StrongWorkerRefs, child Workers, and Timeouts, but
  // WeakWorkerRefs could. WorkerThreadPrimaryRunnable could have created a
  // PerformanceStorageWorker which holds a WeakWorkerRef.
  // Notify WeakWorkerRefs with Dead status.
  NotifyWorkerRefs(Dead);
}

void WorkerPrivate::UnrootGlobalScopes() {
  LOG(WorkerLog(), ("WorkerPrivate::UnrootGlobalScopes [%p]", this));
  auto data = mWorkerThreadAccessible.Access();

  RefPtr<WorkerDebuggerGlobalScope> debugScope = data->mDebuggerScope.forget();
  if (debugScope) {
    MOZ_ASSERT(debugScope->mWorkerPrivate == this);
  }
  RefPtr<WorkerGlobalScope> scope = data->mScope.forget();
  if (scope) {
    MOZ_ASSERT(scope->mWorkerPrivate == this);
  }
}

void WorkerPrivate::DoRunLoop(JSContext* aCx) {
  LOG(WorkerLog(), ("WorkerPrivate::DoRunLoop [%p]", this));
  auto data = mWorkerThreadAccessible.Access();
  MOZ_RELEASE_ASSERT(!GetExecutionManager());

  RefPtr<WorkerThread> thread;
  {
    MutexAutoLock lock(mMutex);
    mJSContext = aCx;
    // mThread is set before we enter, and is never changed during DoRunLoop.
    // copy to local so we don't trigger mutex analysis
    MOZ_ASSERT(mThread);
    thread = mThread;

    MOZ_ASSERT(mStatus == Pending);
    mStatus = Running;

    // Now, start to run the event loop, mPreStartRunnables can be cleared,
    // since when get here, Worker initialization has done successfully.
    mPreStartRunnables.Clear();
  }

  // Create IPC between the content process worker thread and the parent
  // process background thread for non-life cycle related operations of
  // SharedWorker/ServiceWorker
  if (mChildEp.IsValid()) {
    mRemoteWorkerNonLifeCycleOpController =
        RemoteWorkerNonLifeCycleOpControllerChild::Create();
    MOZ_ASSERT_DEBUG_OR_FUZZING(mRemoteWorkerNonLifeCycleOpController);
    mChildEp.Bind(mRemoteWorkerNonLifeCycleOpController);
  }

  // Now that we've done that, we can go ahead and set up our AutoJSAPI.  We
  // can't before this point, because it can't find the right JSContext before
  // then, since it gets it from our mJSContext.
  AutoJSAPI jsapi;
  jsapi.Init();
  MOZ_ASSERT(jsapi.cx() == aCx);

  EnableMemoryReporter();

  InitializeGCTimers();

  bool checkFinalGCCC =
      StaticPrefs::dom_workers_GCCC_on_potentially_last_event();

  bool debuggerRunnablesPending = false;
  bool normalRunnablesPending = false;
  auto noRunnablesPendingAndKeepAlive =
      [&debuggerRunnablesPending, &normalRunnablesPending, &thread, this]()
          MOZ_REQUIRES(mMutex) {
            // We want to keep both pending flags always updated while looping.
            debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
            normalRunnablesPending = NS_HasPendingEvents(thread);

            bool anyRunnablesPending = !mControlQueue.IsEmpty() ||
                                       debuggerRunnablesPending ||
                                       normalRunnablesPending;
            bool keepWorkerAlive = mStatus == Running || HasActiveWorkerRefs();

            return (!anyRunnablesPending && keepWorkerAlive);
          };

  for (;;) {
    WorkerStatus currentStatus;

    if (checkFinalGCCC) {
      // If we get here after the last event ran but someone holds a WorkerRef
      // and there is no other logic to release that WorkerRef than lazily
      // through GC/CC, we might block forever on the next WaitForWorkerEvents.
      // Every object holding a WorkerRef should really have a straight,
      // deterministic line from the WorkerRef's callback being invoked to the
      // WorkerRef being released which is supported by strong-references that
      // can't form a cycle.
      bool mayNeedFinalGCCC = false;
      {
        MutexAutoLock lock(mMutex);

        currentStatus = mStatus;
        mayNeedFinalGCCC =
            (mStatus >= Canceling && HasActiveWorkerRefs() &&
             !debuggerRunnablesPending && !normalRunnablesPending &&
             data->mPerformedShutdownAfterLastContentTaskExecuted);
      }
      if (mayNeedFinalGCCC) {
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
        // WorkerRef::ReleaseWorker will check this flag via
        // AssertIsNotPotentiallyLastGCCCRunning
        data->mIsPotentiallyLastGCCCRunning = true;
#endif
        // GarbageCollectInternal will trigger both GC and CC
        GarbageCollectInternal(aCx, true /* aShrinking */,
                               true /* aCollectChildren */);
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
        data->mIsPotentiallyLastGCCCRunning = false;
#endif
      }
    }

    {
      MutexAutoLock lock(mMutex);
      if (checkFinalGCCC && currentStatus != mStatus) {
        // Something moved our status while we were supposed to check for a
        // potentially needed GC/CC. Just check again.
        continue;
      }

      // Wait for a runnable to arrive that we can execute, or for it to be okay
      // to shutdown this worker once all holders have been removed.
      // Holders may be removed from inside normal runnables,  but we don't
      // check for that after processing normal runnables, so we need to let
      // control flow to the shutdown logic without blocking.
      while (noRunnablesPendingAndKeepAlive()) {
        // We pop out to this loop when there are no pending events.
        // If we don't reset these, we may not re-enter ProcessNextEvent()
        // until we have events to process, and it may seem like we have
        // an event running for a very long time.
        thread->SetRunningEventDelay(TimeDuration(), TimeStamp());

        mWorkerLoopIsIdle = true;

        WaitForWorkerEvents();

        mWorkerLoopIsIdle = false;
      }

      auto result = ProcessAllControlRunnablesLocked();
      if (result != ProcessAllControlRunnablesResult::Nothing) {
        // Update all saved runnable flags for side effect for the
        // loop check about transitioning to Killing below.
        (void)noRunnablesPendingAndKeepAlive();
      }

      currentStatus = mStatus;
    }

    // Status transitions to Closing/Canceling and there are no SyncLoops,
    // set global start dying, disconnect EventTargetObjects and
    // WebTaskScheduler.
    // The Worker might switch to the "Killing" immediately then directly exits
    // DoRunLoop(). Before exiting the DoRunLoop(), explicitly disconnecting the
    // WorkerGlobalScope's EventTargetObject here would help to fail runnable
    // dispatching when the Worker is in the status changing.
    if (currentStatus >= Closing &&
        !data->mPerformedShutdownAfterLastContentTaskExecuted) {
      data->mPerformedShutdownAfterLastContentTaskExecuted.Flip();
      if (data->mScope) {
        data->mScope->NoteTerminating();
        data->mScope->DisconnectGlobalTeardownObservers();
        if (WebTaskScheduler* scheduler =
                data->mScope->GetExistingScheduler()) {
          scheduler->Disconnect();
        }
      }
    }

    // Transition from Canceling to Killing and exit this loop when:
    //  * All (non-weak) WorkerRefs have been released.
    //  * There are no runnables pending. This is intended to let same-thread
    //    dispatches as part of cleanup be able to run to completion, but any
    //    logic that still wants async things to happen should be holding a
    //    StrongWorkerRef.
    if (currentStatus != Running && !HasActiveWorkerRefs() &&
        !normalRunnablesPending && !debuggerRunnablesPending) {
      // Now we are ready to kill the worker thread.
      if (currentStatus == Canceling) {
        NotifyInternal(Killing);

#ifdef DEBUG
        {
          MutexAutoLock lock(mMutex);
          currentStatus = mStatus;
        }
        MOZ_ASSERT(currentStatus == Killing);
#else
        currentStatus = Killing;
#endif
      }

      // If we're supposed to die then we should exit the loop.
      if (currentStatus == Killing) {
        // Unfortunately WorkerRunnable/WorkerControlRunnable could be
        // dispatched between the duration after processing ControlRunnables and
        // before transistioning the Worker to "Killing." Generally, these
        // runnables should be dispatched and executed with a StrongWorkerRef,
        // such that the runnable's execution can be protected, and the
        // corresponding resource of Worker are ensured to be alived in
        // "Canceling" state. However, the existence of the StrongWorkerRef is
        // not guaranteed, it could be released unexpectedly or even not held by
        // the dispatcher. So once the Worker is in "Killing" state with pending
        // runnables, these runnables should be cleaned first, then continuing
        // the killing steps. The Worker has been in "Killing" already,
        // runnables dispatching, syncLoop and WorkerRefs creating are
        // forbidden, so the "continue" would only focus on the dispatched
        // runnables.
        {
          MutexAutoLock lock(mMutex);
          if (NS_HasPendingEvents(thread) || !mDebuggerQueue.IsEmpty()) {
            continue;
          }
        }

        // Status transition to "Killing" and no pending runnables, shutdown
        // the StorageManager.
        if (data->mScope) {
          data->mScope->NoteShuttingDown();
        }
        if (mRemoteWorkerNonLifeCycleOpController) {
          mRemoteWorkerNonLifeCycleOpController->TransistionStateToKilled();
          mRemoteWorkerNonLifeCycleOpController = nullptr;
        }

        // We are about to destroy worker, report all use counters.
        ReportUseCounters();

        // Flush uncaught rejections immediately, without
        // waiting for a next tick.
        PromiseDebugging::FlushUncaughtRejections();

        DisableRemoteDebuggerOnWorkerThread(true /*aForShutdown*/);

        ShutdownGCTimers();

        DisableMemoryReporter();

        // Move the timer out with the mutex held but only drop the ref
        // when the mutex is not held.
        nsCOMPtr<nsITimer> timer;
        {
          MutexAutoLock lock(mMutex);
          mStatus = Dead;
          // Wait for the dispatching ControlRunnables complete.
          while (mDispatchingControlRunnables) {
            mCondVar.Wait();
          }
          mJSContext = nullptr;
          mDebuggerInterruptTimer.swap(timer);
        }
        timer = nullptr;

        // After mStatus is set to Dead there can be no more
        // WorkerControlRunnables so no need to lock here.
        if (!mControlQueue.IsEmpty()) {
          LOG(WorkerLog(),
              ("WorkerPrivate::DoRunLoop [%p] dropping control runnables in "
               "Dead status",
               this));
          WorkerRunnable* runnable = nullptr;
          while (mControlQueue.Pop(runnable)) {
            runnable->Cancel();
            runnable->Release();
          }
        }

        // We do not need the timeouts any more, they have been canceled
        // by NotifyInternal(Killing) above if they were active.
        UnlinkTimeouts();

        return;
      }
    }

    if (debuggerRunnablesPending || normalRunnablesPending) {
      // Start the periodic GC timer if it is not already running.
      SetGCTimerMode(PeriodicTimer);
    }

    if (debuggerRunnablesPending) {
      ProcessSingleDebuggerRunnable();

      {
        MutexAutoLock lock(mMutex);
        debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
      }

      if (debuggerRunnablesPending) {
        WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
        // If the worker was canceled before ever creating its content global
        // then mCancelBeforeWorkerScopeConstructed could have been flipped and
        // all of the WorkerDebuggerRunnables canceled, so the debugger global
        // would never have been created.
        if (globalScope) {
          // Now *might* be a good time to GC. Let the JS engine make the
          // decision.
          JSAutoRealm ar(aCx, globalScope->GetGlobalJSObject());
          JS_MaybeGC(aCx);
        }
      }
    } else if (normalRunnablesPending) {
      // Process a single runnable from the main queue.
      NS_ProcessNextEvent(thread, false);

      normalRunnablesPending = NS_HasPendingEvents(thread);
      if (normalRunnablesPending && GlobalScope()) {
        // Now *might* be a good time to GC. Let the JS engine make the
        // decision.
        JSAutoRealm ar(aCx, GlobalScope()->GetGlobalJSObject());
        JS_MaybeGC(aCx);
      }
    }

    // Checking the background actors if needed, any runnable execution could
    // release background actors which blocks GC/CC on
    // WorkerPrivate::mParentEventTargetRef.
    if (currentStatus < Canceling) {
      UpdateCCFlag(CCFlag::CheckBackgroundActors);
    }

    if (!debuggerRunnablesPending && !normalRunnablesPending) {
      // Both the debugger event queue and the normal event queue has been
      // exhausted, cancel the periodic GC timer and schedule the idle GC timer.
      SetGCTimerMode(IdleTimer);
    }

    // If the worker thread is spamming the main thread faster than it can
    // process the work, then pause the worker thread until the main thread
    // catches up.
    size_t queuedEvents = mMainThreadEventTargetForMessaging->Length() +
                          mMainThreadDebuggeeEventTarget->Length();
    if (queuedEvents > 5000) {
      // Note, postMessage uses mMainThreadDebuggeeEventTarget!
      mMainThreadDebuggeeEventTarget->AwaitIdle();
    }
  }

  MOZ_CRASH("Shouldn't get here!");
}

namespace {
/**
 * If there is a current CycleCollectedJSContext, return its recursion depth,
 * otherwise return 1.
 *
 * In the edge case where a worker is starting up so late that PBackground is
 * already shutting down, the cycle collected context will never be created,
 * but we will need to drain the event loop in ClearMainEventQueue.  This will
 * result in a normal NS_ProcessPendingEvents invocation which will call
 * WorkerPrivate::OnProcessNextEvent and WorkerPrivate::AfterProcessNextEvent
 * which want to handle the need to process control runnables and perform a
 * sanity check assertion, respectively.
 *
 * We claim a depth of 1 when there's no CCJS because this most corresponds to
 * reality, but this doesn't meant that other code might want to drain various
 * runnable queues as part of this cleanup.
 */
uint32_t GetEffectiveEventLoopRecursionDepth() {
  auto* ccjs = CycleCollectedJSContext::Get();
  if (ccjs) {
    return ccjs->RecursionDepth();
  }

  return 1;
}

}  // namespace

void WorkerPrivate::OnProcessNextEvent() {
  AssertIsOnWorkerThread();

  uint32_t recursionDepth = GetEffectiveEventLoopRecursionDepth();
  MOZ_ASSERT(recursionDepth);

  // Normally we process control runnables in DoRunLoop or RunCurrentSyncLoop.
  // However, it's possible that non-worker C++ could spin its own nested event
  // loop, and in that case we must ensure that we continue to process control
  // runnables here.
  if (recursionDepth > 1 && mSyncLoopStack.Length() < recursionDepth - 1) {
    (void)ProcessAllControlRunnables();
    // There's no running JS, and no state to revalidate, so we can ignore the
    // return value.
  }
}

void WorkerPrivate::AfterProcessNextEvent() {
  AssertIsOnWorkerThread();
  MOZ_ASSERT(GetEffectiveEventLoopRecursionDepth());
}

nsISerialEventTarget* WorkerPrivate::MainThreadEventTargetForMessaging() {
  return mMainThreadEventTargetForMessaging;
}

nsresult WorkerPrivate::DispatchToMainThreadForMessaging(
    nsIRunnable* aRunnable, nsIEventTarget::DispatchFlags aFlags) {
  nsCOMPtr<nsIRunnable> r = aRunnable;
  return DispatchToMainThreadForMessaging(r.forget(), aFlags);
}

nsresult WorkerPrivate::DispatchToMainThreadForMessaging(
    already_AddRefed<nsIRunnable> aRunnable,
    nsIEventTarget::DispatchFlags aFlags) {
  return mMainThreadEventTargetForMessaging->Dispatch(std::move(aRunnable),
                                                      aFlags);
}

nsISerialEventTarget* WorkerPrivate::MainThreadEventTarget() {
  return mMainThreadEventTarget;
}

nsresult WorkerPrivate::DispatchToMainThread(
    nsIRunnable* aRunnable, nsIEventTarget::DispatchFlags aFlags) {
  nsCOMPtr<nsIRunnable> r = aRunnable;
  return DispatchToMainThread(r.forget(), aFlags);
}

nsresult WorkerPrivate::DispatchToMainThread(
    already_AddRefed<nsIRunnable> aRunnable,
    nsIEventTarget::DispatchFlags aFlags) {
  return mMainThreadEventTarget->Dispatch(std::move(aRunnable), aFlags);
}

nsresult WorkerPrivate::DispatchDebuggeeToMainThread(
    already_AddRefed<WorkerRunnable> aRunnable,
    nsIEventTarget::DispatchFlags aFlags) {
  RefPtr<WorkerRunnable> debuggeeRunnable = std::move(aRunnable);
  MOZ_ASSERT_DEBUG_OR_FUZZING(debuggeeRunnable->IsDebuggeeRunnable());
  return mMainThreadDebuggeeEventTarget->Dispatch(debuggeeRunnable.forget(),
                                                  aFlags);
}

nsISerialEventTarget* WorkerPrivate::ControlEventTarget() {
  return mWorkerControlEventTarget;
}

nsISerialEventTarget* WorkerPrivate::HybridEventTarget() {
  return mWorkerHybridEventTarget;
}

ClientType WorkerPrivate::GetClientType() const {
  switch (Kind()) {
    case WorkerKindDedicated:
      return ClientType::Worker;
    case WorkerKindShared:
      return ClientType::Sharedworker;
    case WorkerKindService:
      return ClientType::Serviceworker;
    default:
      MOZ_CRASH("unknown worker type!");
  }
}

UniquePtr<ClientSource> WorkerPrivate::CreateClientSource() {
  auto data = mWorkerThreadAccessible.Access();
  MOZ_ASSERT(!data->mScope, "Client should be created before the global");

  UniquePtr<ClientSource> clientSource;
  if (IsServiceWorker()) {
    clientSource = ClientManager::CreateSourceFromInfo(
        GetSourceInfo(), mWorkerHybridEventTarget);
  } else {
    clientSource = ClientManager::CreateSource(
        GetClientType(), mWorkerHybridEventTarget,
        StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker(
            this)
            ? GetPartitionedPrincipalInfo()
            : GetPrincipalInfo());
  }
  MOZ_DIAGNOSTIC_ASSERT(clientSource);

  clientSource->SetAgentClusterId(mAgentClusterId);

  if (data->mFrozen) {
    clientSource->Freeze();
  }

  // Shortly after the client is reserved we will try loading the main script
  // for the worker.  This may get intercepted by the ServiceWorkerManager
  // which will then try to create a ClientHandle.  Its actually possible for
  // the main thread to create this ClientHandle before our IPC message creating
  // the ClientSource completes.  To avoid this race we synchronously ping our
  // parent Client actor here.  This ensure the worker ClientSource is created
  // in the parent before the main thread might try reaching it with a
  // ClientHandle.
  //
  // An alternative solution would have been to handle the out-of-order
  // operations on the parent side.  We could have created a small window where
  // we allow ClientHandle objects to exist without a ClientSource.  We would
  // then time out these handles if they stayed orphaned for too long.  This
  // approach would be much more complex, but also avoid this extra bit of
  // latency when starting workers.
  //
  // Note, we only have to do this for workers that can be controlled by a
  // service worker.  So avoid the sync overhead here if we are starting a
  // service worker or a chrome worker.
  if (Kind() != WorkerKindService && !IsChromeWorker()) {
    clientSource->WorkerSyncPing(this);
  }

  return clientSource;
}

bool WorkerPrivate::EnsureCSPEventListener() {
  if (!mCSPEventListener) {
    mCSPEventListener = WorkerCSPEventListener::Create(this);
    if (NS_WARN_IF(!mCSPEventListener)) {
      return false;
    }
  }
  return true;
}

nsICSPEventListener* WorkerPrivate::CSPEventListener() const {
  MOZ_ASSERT(mCSPEventListener);
  return mCSPEventListener;
}

void WorkerPrivate::EnsurePerformanceStorage() {
  AssertIsOnWorkerThread();

  if (!mPerformanceStorage) {
    mPerformanceStorage = PerformanceStorageWorker::Create(this);
  }
}

bool WorkerPrivate::GetExecutionGranted() const {
  auto data = mWorkerThreadAccessible.Access();
  return data->mJSThreadExecutionGranted;
}

void WorkerPrivate::SetExecutionGranted(bool aGranted) {
  auto data = mWorkerThreadAccessible.Access();
  data->mJSThreadExecutionGranted = aGranted;
}

void WorkerPrivate::ScheduleTimeSliceExpiration(uint32_t aDelay) {
  auto data = mWorkerThreadAccessible.Access();

  if (!data->mTSTimer) {
    data->mTSTimer = NS_NewTimer();
    MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->SetTarget(mWorkerControlEventTarget));
  }

  // Whenever an event is scheduled on the WorkerControlEventTarget an
  // interrupt is automatically requested which causes us to yield JS execution
  // and the next JS execution in the queue to execute.
  // This allows for simple code reuse of the existing interrupt callback code
  // used for control events.
  MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->InitWithNamedFuncCallback(
      [](nsITimer* Timer, void* aClosure) { return; }, nullptr, aDelay,
      nsITimer::TYPE_ONE_SHOT, "TimeSliceExpirationTimer"_ns));
}

void WorkerPrivate::CancelTimeSliceExpiration() {
  auto data = mWorkerThreadAccessible.Access();
  MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->Cancel());
}

JSExecutionManager* WorkerPrivate::GetExecutionManager() const {
  auto data = mWorkerThreadAccessible.Access();
  return data->mExecutionManager.get();
}

void WorkerPrivate::SetExecutionManager(JSExecutionManager* aManager) {
  auto data = mWorkerThreadAccessible.Access();
  data->mExecutionManager = aManager;
}

void WorkerPrivate::ExecutionReady() {
  auto data = mWorkerThreadAccessible.Access();
  {
    MutexAutoLock lock(mMutex);
    if (mStatus >= Canceling) {
      return;
    }
  }

  data->mScope->MutableClientSourceRef().WorkerExecutionReady(this);

  if (ExtensionAPIAllowed()) {
    extensions::CreateAndDispatchInitWorkerContextRunnable();
  }
}

void WorkerPrivate::InitializeGCTimers() {
  auto data = mWorkerThreadAccessible.Access();

  // We need timers for GC. The basic plan is to run a non-shrinking GC
  // periodically (PERIODIC_GC_TIMER_DELAY_SEC) while the worker is running.
  // Once the worker goes idle we set a short (IDLE_GC_TIMER_DELAY_SEC) timer to
  // run a shrinking GC.
  data->mPeriodicGCTimer = NS_NewTimer();
  data->mIdleGCTimer = NS_NewTimer();

  data->mPeriodicGCTimerRunning = false;
  data->mIdleGCTimerRunning = false;
}

void WorkerPrivate::SetGCTimerMode(GCTimerMode aMode) {
  auto data = mWorkerThreadAccessible.Access();

  if (!data->mPeriodicGCTimer || !data->mIdleGCTimer) {
    // GC timers have been cleared already.
    return;
  }

  if (aMode == NoTimer) {
    MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
    data->mPeriodicGCTimerRunning = false;
    MOZ_ALWAYS_SUCCEEDS(data->mIdleGCTimer->Cancel());
    data->mIdleGCTimerRunning = false;
    return;
  }

  WorkerStatus status;
  {
    MutexAutoLock lock(mMutex);
    status = mStatus;
  }

  if (status >= Killing) {
    ShutdownGCTimers();
    return;
  }

  // If the idle timer is running, don't cancel it when the periodic timer
  // is scheduled since we do want shrinking GC to be called occasionally.
  if (aMode == PeriodicTimer && data->mPeriodicGCTimerRunning) {
    return;
  }

  if (aMode == IdleTimer) {
    if (!data->mPeriodicGCTimerRunning) {
      // Since running idle GC cancels both GC timers, after that we want
      // first at least periodic GC timer getting activated, since that tells
      // us that there have been some non-control tasks to process. Otherwise
      // idle GC timer would keep running all the time.
      return;
    }

    // Cancel the periodic timer now, since the event loop is (in the common
    // case) empty now.
    MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
    data->mPeriodicGCTimerRunning = false;

    if (data->mIdleGCTimerRunning) {
      return;
    }
  }

  MOZ_ASSERT(aMode == PeriodicTimer || aMode == IdleTimer);

  uint32_t delay = 0;
  int16_t type = nsITimer::TYPE_ONE_SHOT;
  nsTimerCallbackFunc callback = nullptr;
  nsCString name;
  nsITimer* timer = nullptr;

  if (aMode == PeriodicTimer) {
    delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000;
    type = nsITimer::TYPE_REPEATING_SLACK;
    callback = PeriodicGCTimerCallback;
    name.AssignLiteral("dom::PeriodicGCTimerCallback");
    timer = data->mPeriodicGCTimer;
    data->mPeriodicGCTimerRunning = true;
    LOG(WorkerLog(), ("Worker %p scheduled periodic GC timer\n", this));
  } else {
    delay = IDLE_GC_TIMER_DELAY_SEC * 1000;
    type = nsITimer::TYPE_ONE_SHOT;
    callback = IdleGCTimerCallback;
    name.AssignLiteral("dom::IdleGCTimerCallback");
    timer = data->mIdleGCTimer;
    data->mIdleGCTimerRunning = true;
    LOG(WorkerLog(), ("Worker %p scheduled idle GC timer\n", this));
  }

  MOZ_ALWAYS_SUCCEEDS(timer->SetTarget(mWorkerControlEventTarget));
  MOZ_ALWAYS_SUCCEEDS(
      timer->InitWithNamedFuncCallback(callback, this, delay, type, name));
}

void WorkerPrivate::InitializeGlobalReportingEndpoints() {
  if (mLoadInfo.mReportingEndpointsHeader.IsEmpty() ||
      mLoadInfo.mReportingEndpointsHeader.IsVoid()) {
    return;
  }

  MOZ_ASSERT(GlobalScope());
  // We *must* convert to nsIGlobalObject* first, so that when something wants
  // to report, it uses the right key

  ReportDeliver::WorkerInitializeReportingEndpoints(
      reinterpret_cast<uintptr_t>(static_cast<nsIGlobalObject*>(GlobalScope())),
      mLoadInfo.mBaseURI, mLoadInfo.mReportingEndpointsHeader,
      ShouldResistFingerprinting(RFPTarget::NavigatorUserAgent),
      CookieJarSettings());
}

void WorkerPrivate::SetReportingEndpointsHeader(const nsACString& aHeader) {
  MOZ_ASSERT(mLoadInfo.mReportingEndpointsHeader.IsEmpty(),
             "Headers set multiple times.");
  mLoadInfo.mReportingEndpointsHeader = aHeader;
}

void WorkerPrivate::ShutdownGCTimers() {
  auto data = mWorkerThreadAccessible.Access();

  MOZ_ASSERT(!data->mPeriodicGCTimer == !data->mIdleGCTimer);

  if (!data->mPeriodicGCTimer && !data->mIdleGCTimer) {
    return;
  }

  // Always make sure the timers are canceled.
  MOZ_ALWAYS_SUCCEEDS(data->mPeriodicGCTimer->Cancel());
  MOZ_ALWAYS_SUCCEEDS(data->mIdleGCTimer->Cancel());

  LOG(WorkerLog(), ("Worker %p killed the GC timers\n", this));

  data->mPeriodicGCTimer = nullptr;
  data->mIdleGCTimer = nullptr;
  data->mPeriodicGCTimerRunning = false;
  data->mIdleGCTimerRunning = false;
}

bool WorkerPrivate::InterruptCallback(JSContext* aCx) {
  auto data = mWorkerThreadAccessible.Access();

  AutoYieldJSThreadExecution yield;

  // If we are here it's because a WorkerControlRunnable has been dispatched.
  // The runnable could be processed here or it could have already been
  // processed by a sync event loop.
  // The most important thing this method must do, is to decide if the JS
  // execution should continue or not. If the runnable returns an error or if
  // the worker status is >= Canceling, we should stop the JS execution.

  MOZ_ASSERT(!JS_IsExceptionPending(aCx));

  bool mayContinue = true;
  bool scheduledIdleGC = false;

  for (;;) {
    // Run all control events now.
    auto result = ProcessAllControlRunnables();
    if (result == ProcessAllControlRunnablesResult::Abort) {
      mayContinue = false;
    }

    bool mayFreeze = data->mFrozen;

    {
      MutexAutoLock lock(mMutex);

      if (mayFreeze) {
        mayFreeze = mStatus <= Running;
      }

      if (mStatus >= Canceling) {
        mayContinue = false;
      }
    }

    if (!mayContinue || !mayFreeze) {
      break;
    }

    // Cancel the periodic GC timer here before freezing. The idle GC timer
    // will clean everything up once it runs.
    if (!scheduledIdleGC) {
      SetGCTimerMode(IdleTimer);
      scheduledIdleGC = true;
    }

    while ((mayContinue = MayContinueRunning())) {
      MutexAutoLock lock(mMutex);
      if (!mControlQueue.IsEmpty()) {
        break;
      }

      WaitForWorkerEvents();
    }
  }

  if (!mayContinue) {
    // We want only uncatchable exceptions here.
    NS_ASSERTION(!JS_IsExceptionPending(aCx),
                 "Should not have an exception set here!");
    return false;
  }

  // Make sure the periodic timer gets turned back on here.
  SetGCTimerMode(PeriodicTimer);

  if (data->mDebuggerInterruptRequested) {
    bool debuggerRunnablesPending = false;
    {
      MutexAutoLock lock(mMutex);
      debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
    }
    if (debuggerRunnablesPending) {
      // Prevents interrupting the debugger's own logic unless it has called
      // back into content
      WorkerGlobalScope* globalScope = GlobalScope();
      if (globalScope) {
        JSObject* global = JS::CurrentGlobalOrNull(aCx);
        if (global && global == globalScope->GetGlobalJSObject()) {
          while (debuggerRunnablesPending) {
            ProcessSingleDebuggerRunnable();
            {
              MutexAutoLock lock(mMutex);
              debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
            }
          }
        }
      }
    }
    data->mDebuggerInterruptRequested = false;
  }

  return true;
}

void WorkerPrivate::CloseInternal() {
  AssertIsOnWorkerThread();
  NotifyInternal(Closing);
}

bool WorkerPrivate::IsOnCurrentThread() {
  // May be called on any thread!

  MOZ_ASSERT(mPRThread);
  return PR_GetCurrentThread() == mPRThread;
}

void WorkerPrivate::ScheduleDeletion(WorkerRanOrNot aRanOrNot) {
  AssertIsOnWorkerThread();
  {
    // mWorkerThreadAccessible's accessor must be destructed before
    // the scheduled Runnable gets to run.
    auto data = mWorkerThreadAccessible.Access();
    MOZ_ASSERT(data->mChildWorkers.IsEmpty());

    MOZ_RELEASE_ASSERT(!data->mDeletionScheduled);
    data->mDeletionScheduled.Flip();
  }
  MOZ_ASSERT(mSyncLoopStack.IsEmpty());
  MOZ_ASSERT(mPostSyncLoopOperations == 0);

  // If Worker is never ran, clear the mPreStartRunnables. To let the resource
  // hold by the pre-submmited runnables.
  if (WorkerNeverRan == aRanOrNot) {
    ClearPreStartRunnables();
  }

#ifdef DEBUG
  if (WorkerRan == aRanOrNot) {
    nsIThread* currentThread = NS_GetCurrentThread();
    MOZ_ASSERT(currentThread);
    // On the worker thread WorkerRunnable will refuse to run if not nested
    // on top of a WorkerThreadPrimaryRunnable.
    (void)NS_WARN_IF(NS_HasPendingEvents(currentThread));
  }
#endif

  // Force to set mRemoteDebuggerRegistered as false and notify if the Worker is
  // waiting for the registration done.
  SetIsRemoteDebuggerRegistered(false);

  if (WorkerPrivate* parent = GetParent()) {
    RefPtr<WorkerFinishedRunnable> runnable =
        new WorkerFinishedRunnable(parent, this);
    if (!runnable->Dispatch(parent)) {
      NS_WARNING("Failed to dispatch runnable!");
    }
  } else {
    if (ExtensionAPIAllowed()) {
      MOZ_ASSERT(IsServiceWorker());
      RefPtr<Runnable> extWorkerRunnable =
          extensions::CreateWorkerDestroyedRunnable(ServiceWorkerID(),
                                                    GetBaseURI());
      // Dispatch as a low priority runnable.
      if (NS_FAILED(
              DispatchToMainThreadForMessaging(extWorkerRunnable.forget()))) {
        NS_WARNING(
            "Failed to dispatch runnable to notify extensions worker "
            "destroyed");
      }
    }

    // Note, this uses the lower priority DispatchToMainThreadForMessaging for
    // dispatching TopLevelWorkerFinishedRunnable to the main thread so that
    // other relevant runnables are guaranteed to run before it.
    RefPtr<TopLevelWorkerFinishedRunnable> runnable =
        new TopLevelWorkerFinishedRunnable(this);
    if (NS_FAILED(DispatchToMainThreadForMessaging(runnable.forget()))) {
      NS_WARNING("Failed to dispatch runnable!");
    }

    // NOTE: Calling any WorkerPrivate methods (or accessing member data) after
    // this point is unsafe (the TopLevelWorkerFinishedRunnable just dispatched
    // may be able to call ClearSelfAndParentEventTargetRef on this
    // WorkerPrivate instance and by the time we get here the WorkerPrivate
    // instance destructor may have been already called).
  }
}

bool WorkerPrivate::CollectRuntimeStats(
    JS::RuntimeStats* aRtStats, bool aAnonymize) MOZ_NO_THREAD_SAFETY_ANALYSIS {
  // We don't have a lock to access mJSContext, but it's safe to access on this
  // thread.
  AssertIsOnWorkerThread();
  NS_ASSERTION(aRtStats, "Null RuntimeStats!");
  // We don't really own it, but it's safe to access on this thread
  NS_ASSERTION(mJSContext, "This must never be null!");

  return JS::CollectRuntimeStats(mJSContext, aRtStats, nullptr, aAnonymize);
}

void WorkerPrivate::EnableMemoryReporter() {
  auto data = mWorkerThreadAccessible.Access();
  MOZ_ASSERT(!data->mMemoryReporter);

  // No need to lock here since the main thread can't race until we've
  // successfully registered the reporter.
  data->mMemoryReporter = new MemoryReporter(this);

  if (NS_FAILED(RegisterWeakAsyncMemoryReporter(data->mMemoryReporter))) {
    NS_WARNING("Failed to register memory reporter!");
    // No need to lock here since a failed registration means our memory
    // reporter can't start running. Just clean up.
    data->mMemoryReporter = nullptr;
  }
}

void WorkerPrivate::DisableMemoryReporter() {
  auto data = mWorkerThreadAccessible.Access();

  RefPtr<MemoryReporter> memoryReporter;
  {
    // Mutex protectes MemoryReporter::mWorkerPrivate which is cleared by
    // MemoryReporter::Disable() below.
    MutexAutoLock lock(mMutex);

    // There is nothing to do here if the memory reporter was never successfully
    // registered.
    if (!data->mMemoryReporter) {
      return;
    }

    // We don't need this set any longer. Swap it out so that we can unregister
    // below.
    data->mMemoryReporter.swap(memoryReporter);

    // Next disable the memory reporter so that the main thread stops trying to
    // signal us.
    memoryReporter->Disable();
  }

  // Finally unregister the memory reporter.
  if (NS_FAILED(UnregisterWeakMemoryReporter(memoryReporter))) {
    NS_WARNING("Failed to unregister memory reporter!");
  }
}

void WorkerPrivate::WaitForWorkerEvents() {
  AUTO_PROFILER_LABEL("WorkerPrivate::WaitForWorkerEvents", IDLE);

  AssertIsOnWorkerThread();
  mMutex.AssertCurrentThreadOwns();

  // Wait for a worker event.
  mCondVar.Wait();
}

WorkerPrivate::ProcessAllControlRunnablesResult
WorkerPrivate::ProcessAllControlRunnablesLocked() {
  AssertIsOnWorkerThread();
  mMutex.AssertCurrentThreadOwns();

  AutoYieldJSThreadExecution yield;

  auto result = ProcessAllControlRunnablesResult::Nothing;

  for (;;) {
    WorkerRunnable* event;
    if (!mControlQueue.Pop(event)) {
      break;
    }

    MutexAutoUnlock unlock(mMutex);

    {
      MOZ_ASSERT(event);
      AUTO_PROFILE_FOLLOWING_RUNNABLE(event);
      if (NS_FAILED(static_cast<nsIRunnable*>(event)->Run())) {
        result = ProcessAllControlRunnablesResult::Abort;
      }
    }

    if (result == ProcessAllControlRunnablesResult::Nothing) {
      // We ran at least one thing.
      result = ProcessAllControlRunnablesResult::MayContinue;
    }
    event->Release();
  }

  return result;
}

void WorkerPrivate::ShutdownModuleLoader() {
  AssertIsOnWorkerThread();

  WorkerGlobalScope* globalScope = GlobalScope();
  if (globalScope) {
    if (globalScope->GetModuleLoader(nullptr)) {
      globalScope->GetModuleLoader(nullptr)->Shutdown();
    }
  }
  WorkerDebuggerGlobalScope* debugGlobalScope = DebuggerGlobalScope();
  if (debugGlobalScope) {
    if (debugGlobalScope->GetModuleLoader(nullptr)) {
      debugGlobalScope->GetModuleLoader(nullptr)->Shutdown();
    }
  }
}

void WorkerPrivate::ClearPreStartRunnables() {
  nsTArray<RefPtr<WorkerThreadRunnable>> prestart;
  {
    MutexAutoLock lock(mMutex);
    mPreStartRunnables.SwapElements(prestart);
  }
  for (uint32_t count = prestart.Length(), index = 0; index < count; index++) {
    LOG(WorkerLog(), ("WorkerPrivate::ClearPreStartRunnable [%p]", this));
    RefPtr<WorkerRunnable> runnable = std::move(prestart[index]);
    runnable->Cancel();
  }
}

void WorkerPrivate::ProcessSingleDebuggerRunnable() {
  WorkerRunnable* runnable = nullptr;

  // Move the timer out with the mutex held but only drop the ref
  // when the mutex is not held.
  nsCOMPtr<nsITimer> timer;
  {
    MutexAutoLock lock(mMutex);

    mDebuggerQueue.Pop(runnable);

    mDebuggerInterruptTimer.swap(timer);
  }
  timer = nullptr;

  {
    MOZ_ASSERT(runnable);
    AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable);
    static_cast<nsIRunnable*>(runnable)->Run();
  }
  runnable->Release();

  CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
  ccjs->PerformDebuggerMicroTaskCheckpoint();
}

void WorkerPrivate::ClearDebuggerEventQueue() {
  bool debuggerRunnablesPending = false;
  {
    MutexAutoLock lock(mMutex);
    debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
  }
  while (debuggerRunnablesPending) {
    WorkerRunnable* runnable = nullptr;
    {
      MutexAutoLock lock(mMutex);
      mDebuggerQueue.Pop(runnable);
      debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
    }
    // It should be ok to simply release the runnable, without running it.
    runnable->Release();

    // Move the timer out with the mutex held but only drop the ref
    // when the mutex is not held.
    nsCOMPtr<nsITimer> timer;
    {
      MutexAutoLock lock(mMutex);
      mDebuggerInterruptTimer.swap(timer);
    }
    timer = nullptr;
  }
}

bool WorkerPrivate::FreezeInternal() {
  auto data = mWorkerThreadAccessible.Access();
  NS_ASSERTION(!data->mFrozen, "Already frozen!");

  AutoYieldJSThreadExecution yield;

  // The worker can freeze even if it failed to run (and doesn't have a global).
  if (data->mScope) {
    data->mScope->MutableClientSourceRef().Freeze();
  }

  data->mFrozen = true;

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->Freeze(nullptr);
  }
  auto* timeoutManager =
      data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
  if (timeoutManager) {
    timeoutManager->Suspend();
    timeoutManager->Freeze();
  }

  return true;
}

bool WorkerPrivate::HasActiveWorkerRefs() {
  auto data = mWorkerThreadAccessible.Access();
  auto* timeoutManager =
      data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
  return !data->mChildWorkers.IsEmpty() ||
         (timeoutManager && timeoutManager->HasTimeouts()) ||
         !data->mWorkerRefs.IsEmpty();
}

bool WorkerPrivate::ThawInternal() {
  auto data = mWorkerThreadAccessible.Access();
  NS_ASSERTION(data->mFrozen, "Not yet frozen!");

  BindRemoteWorkerDebuggerChild();

  data->mFrozen = false;

  auto* timeoutManager =
      data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
  if (timeoutManager) {
    timeoutManager->Thaw();
    timeoutManager->Resume();
  }

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->Thaw(nullptr);
  }

  // The worker can thaw even if it failed to run (and doesn't have a global).
  if (data->mScope) {
    data->mScope->MutableClientSourceRef().Thaw();
  }

  return true;
}

bool WorkerPrivate::ChangeBackgroundStateInternal(bool aIsBackground) {
  AssertIsOnWorkerThread();
  mIsInBackground = aIsBackground;
  auto data = mWorkerThreadAccessible.Access();
  if (StaticPrefs::dom_workers_throttling_enabled_AtStartup()) {
    auto* timeoutManager =
        data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
    if (timeoutManager) {
      timeoutManager->UpdateBackgroundState();
    }
  }

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    if (aIsBackground) {
      data->mChildWorkers[index]->SetIsRunningInBackground();
    } else {
      data->mChildWorkers[index]->SetIsRunningInForeground();
    }
  }
  return true;
}

bool WorkerPrivate::ChangePlaybackStateInternal(bool aIsPlayingAudio) {
  AssertIsOnWorkerThread();
  mIsPlayingAudio = aIsPlayingAudio;
  auto data = mWorkerThreadAccessible.Access();

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->SetIsPlayingAudio(aIsPlayingAudio);
  }
  return true;
}

bool WorkerPrivate::ChangePeerConnectionsInternal(bool aHasPeerConnections) {
  AssertIsOnWorkerThread();
  mHasActivePeerConnections = aHasPeerConnections;
  auto data = mWorkerThreadAccessible.Access();

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->SetActivePeerConnections(aHasPeerConnections);
  }
  return true;
}

void WorkerPrivate::PropagateStorageAccessPermissionGrantedInternal() {
  auto data = mWorkerThreadAccessible.Access();

  mLoadInfo.mUseRegularPrincipal = true;
  mLoadInfo.mUsingStorageAccess = true;

  WorkerGlobalScope* globalScope = GlobalScope();
  if (globalScope) {
    globalScope->StorageAccessPermissionGranted();
  }

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->PropagateStorageAccessPermissionGranted();
  }
}

void WorkerPrivate::TraverseTimeouts(nsCycleCollectionTraversalCallback& cb) {
  auto data = mWorkerThreadAccessible.Access();
  auto* timeoutManager =
      data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
  if (timeoutManager) {
    timeoutManager->ForEachUnorderedTimeout([&cb](Timeout* timeout) {
      cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(Timeout));
    });
  }
}

void WorkerPrivate::UnlinkTimeouts() {
  auto data = mWorkerThreadAccessible.Access();
  auto* timeoutManager =
      data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
  if (timeoutManager) {
    timeoutManager->ClearAllTimeouts();
    if (!timeoutManager->HasTimeouts()) {
      UpdateCCFlag(CCFlag::EligibleForTimeout);
    }
  }
}

bool WorkerPrivate::AddChildWorker(WorkerPrivate& aChildWorker) {
  auto data = mWorkerThreadAccessible.Access();

#ifdef DEBUG
  {
    WorkerStatus currentStatus;
    {
      MutexAutoLock lock(mMutex);
      currentStatus = mStatus;
    }

    MOZ_ASSERT(currentStatus == Running);
  }
#endif

  NS_ASSERTION(!data->mChildWorkers.Contains(&aChildWorker),
               "Already know about this one!");
  data->mChildWorkers.AppendElement(&aChildWorker);

  if (data->mChildWorkers.Length() == 1) {
    UpdateCCFlag(CCFlag::IneligibleForChildWorker);
  }

  return true;
}

void WorkerPrivate::RemoveChildWorker(WorkerPrivate& aChildWorker) {
  auto data = mWorkerThreadAccessible.Access();

  NS_ASSERTION(data->mChildWorkers.Contains(&aChildWorker),
               "Didn't know about this one!");
  data->mChildWorkers.RemoveElement(&aChildWorker);

  if (data->mChildWorkers.IsEmpty()) {
    UpdateCCFlag(CCFlag::EligibleForChildWorker);
  }
}

bool WorkerPrivate::AddWorkerRef(WorkerRef* aWorkerRef,
                                 WorkerStatus aFailStatus) {
  MOZ_ASSERT(aWorkerRef);
  auto data = mWorkerThreadAccessible.Access();

  {
    MutexAutoLock lock(mMutex);

    LOG(WorkerLog(),
        ("WorkerPrivate::AddWorkerRef [%p] mStatus: %u, aFailStatus: (%u)",
         this, static_cast<uint8_t>(mStatus),
         static_cast<uint8_t>(aFailStatus)));

    if (mStatus >= aFailStatus) {
      return false;
    }

    // We shouldn't create strong references to workers before their main loop
    // begins running. Strong references must be disposed of on the worker
    // thread, so strong references from other threads use a control runnable
    // for that purpose. If the worker fails to reach the main loop stage then
    // no control runnables get run and it would be impossible to get rid of the
    // reference properly.
    MOZ_DIAGNOSTIC_ASSERT_IF(aWorkerRef->IsPreventingShutdown(),
                             mStatus >= WorkerStatus::Running);
  }

  MOZ_ASSERT(!data->mWorkerRefs.Contains(aWorkerRef),
             "Already know about this one!");

  if (aWorkerRef->IsPreventingShutdown()) {
    data->mNumWorkerRefsPreventingShutdownStart += 1;
    if (data->mNumWorkerRefsPreventingShutdownStart == 1) {
      UpdateCCFlag(CCFlag::IneligibleForWorkerRef);
    }
  }

  data->mWorkerRefs.AppendElement(aWorkerRef);
  return true;
}

void WorkerPrivate::RemoveWorkerRef(WorkerRef* aWorkerRef) {
  MOZ_ASSERT(aWorkerRef);
  LOG(WorkerLog(),
      ("WorkerPrivate::RemoveWorkerRef [%p] aWorkerRef: %p", this, aWorkerRef));
  auto data = mWorkerThreadAccessible.Access();

  MOZ_ASSERT(data->mWorkerRefs.Contains(aWorkerRef),
             "Didn't know about this one!");
  data->mWorkerRefs.RemoveElement(aWorkerRef);

  if (aWorkerRef->IsPreventingShutdown()) {
    data->mNumWorkerRefsPreventingShutdownStart -= 1;
    if (!data->mNumWorkerRefsPreventingShutdownStart) {
      UpdateCCFlag(CCFlag::EligibleForWorkerRef);
    }
  }
}

void WorkerPrivate::NotifyWorkerRefs(WorkerStatus aStatus) {
  auto data = mWorkerThreadAccessible.Access();

  NS_ASSERTION(aStatus > Closing, "Bad status!");

  LOG(WorkerLog(), ("WorkerPrivate::NotifyWorkerRefs [%p] aStatus: %u", this,
                    static_cast<uint8_t>(aStatus)));

  for (auto* workerRef : data->mWorkerRefs.ForwardRange()) {
    LOG(WorkerLog(), ("WorkerPrivate::NotifyWorkerRefs [%p] WorkerRefs(%s %p)",
                      this, workerRef->mName, workerRef));
    workerRef->Notify();
  }

  AutoTArray<CheckedUnsafePtr<WorkerPrivate>, 10> children;
  children.AppendElements(data->mChildWorkers);

  for (uint32_t index = 0; index < children.Length(); index++) {
    if (!children[index]->Notify(aStatus)) {
      NS_WARNING("Failed to notify child worker!");
    }
  }
}

nsresult WorkerPrivate::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
  NS_ENSURE_ARG(aTask);

  MutexAutoLock lock(mMutex);
  // If we've already started running shutdown tasks, don't allow registering
  // new ones.
  if (mShutdownTasksRun) {
    return NS_ERROR_UNEXPECTED;
  }
  return mShutdownTasks.AddTask(aTask);
}

nsresult WorkerPrivate::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
  NS_ENSURE_ARG(aTask);

  MutexAutoLock lock(mMutex);
  return mShutdownTasks.RemoveTask(aTask);
}

nsresult WorkerPrivate::RegisterDebuggerShutdownTask(
    nsITargetShutdownTask* aTask) {
  NS_ENSURE_ARG(aTask);

  MutexAutoLock lock(mMutex);
  if (mShutdownTasksRun) {
    return NS_ERROR_UNEXPECTED;
  }
  return mDebuggerShutdownTasks.AddTask(aTask);
}

nsresult WorkerPrivate::UnregisterDebuggerShutdownTask(
    nsITargetShutdownTask* aTask) {
  NS_ENSURE_ARG(aTask);

  MutexAutoLock lock(mMutex);
  return mDebuggerShutdownTasks.RemoveTask(aTask);
}

void WorkerPrivate::JSAsyncTaskStarted(JS::Dispatchable* aDispatchable) {
  RefPtr<StrongWorkerRef> ref = StrongWorkerRef::Create(this, "JSAsyncTask");
  MOZ_ASSERT_DEBUG_OR_FUZZING(ref);
  if (NS_WARN_IF(!ref)) {
    return;
  }
  MOZ_ALWAYS_TRUE(mPendingJSAsyncTasks.putNew(aDispatchable, std::move(ref)));
}

void WorkerPrivate::JSAsyncTaskFinished(JS::Dispatchable* aDispatchable) {
  mPendingJSAsyncTasks.remove(aDispatchable);
}

void WorkerPrivate::RunShutdownTasks() {
  TargetShutdownTaskSet::TasksArray shutdownTasks;
  TargetShutdownTaskSet::TasksArray debuggerShutdownTasks;

  {
    MutexAutoLock lock(mMutex);
    mShutdownTasksRun = true;
    shutdownTasks = mShutdownTasks.Extract();
    debuggerShutdownTasks = mDebuggerShutdownTasks.Extract();
  }

  for (const auto& task : shutdownTasks) {
    task->TargetShutdown();
  }
  // Run the debugger channel's shutdown task(s) too; they are only tracked
  // separately to avoid blocking CC eligibility (see
  // RegisterDebuggerShutdownTask).
  for (const auto& task : debuggerShutdownTasks) {
    task->TargetShutdown();
  }
  mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
}

RefPtr<WorkerParentRef> WorkerPrivate::GetWorkerParentRef() const {
  RefPtr<WorkerParentRef> ref(mParentRef);
  return ref;
}

void WorkerPrivate::AdjustNonblockingCCBackgroundActorCount(int32_t aCount) {
  AssertIsOnWorkerThread();
  auto data = mWorkerThreadAccessible.Access();
  LOGV(("WorkerPrivate::AdjustNonblockingCCBackgroundActors [%p] (%d/%u)", this,
        aCount, data->mNonblockingCCBackgroundActorCount));

#ifdef DEBUG
  if (aCount < 0) {
    MOZ_ASSERT(data->mNonblockingCCBackgroundActorCount >=
               (uint32_t)abs(aCount));
  }
#endif

  data->mNonblockingCCBackgroundActorCount += aCount;
}

void WorkerPrivate::UpdateCCFlag(const CCFlag aFlag) {
  AssertIsOnWorkerThread();

  auto data = mWorkerThreadAccessible.Access();

#ifdef DEBUG
  switch (aFlag) {
    case CCFlag::EligibleForWorkerRef: {
      MOZ_ASSERT(!data->mNumWorkerRefsPreventingShutdownStart);
      break;
    }
    case CCFlag::IneligibleForWorkerRef: {
      MOZ_ASSERT(data->mNumWorkerRefsPreventingShutdownStart);
      break;
    }
    case CCFlag::EligibleForChildWorker: {
      MOZ_ASSERT(data->mChildWorkers.IsEmpty());
      break;
    }
    case CCFlag::IneligibleForChildWorker: {
      MOZ_ASSERT(!data->mChildWorkers.IsEmpty());
      break;
    }
    case CCFlag::EligibleForTimeout: {
      auto* timeoutManager =
          data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
      MOZ_ASSERT(timeoutManager && !timeoutManager->HasTimeouts());
      break;
    }
    case CCFlag::IneligibleForTimeout: {
      auto* timeoutManager =
          data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
      MOZ_ASSERT(!timeoutManager || timeoutManager->HasTimeouts());
      break;
    }
    case CCFlag::CheckBackgroundActors: {
      break;
    }
  }
#endif

  {
    MutexAutoLock lock(mMutex);
    if (mStatus > Canceling) {
      mCCFlagSaysEligible = true;
      return;
    }
  }
  auto HasBackgroundActors = [nonblockingActorCount =
                                  data->mNonblockingCCBackgroundActorCount]() {
    RefPtr<PBackgroundChild> backgroundChild =
        BackgroundChild::GetForCurrentThread();
    MOZ_ASSERT(backgroundChild);
    auto totalCount = backgroundChild->AllManagedActorsCount();
    LOGV(("WorkerPrivate::UpdateCCFlag HasBackgroundActors: %s(%u/%u)",
          totalCount > nonblockingActorCount ? "true" : "false", totalCount,
          nonblockingActorCount));

    return totalCount > nonblockingActorCount;
  };

  auto* timeoutManager =
      data->mScope ? data->mScope->GetTimeoutManager() : nullptr;

  bool noTimeouts{true};
  if (timeoutManager) {
    noTimeouts = !timeoutManager->HasTimeouts();
  }

  bool eligibleForCC = data->mChildWorkers.IsEmpty() && noTimeouts &&
                       !data->mNumWorkerRefsPreventingShutdownStart;

  // Only checking BackgroundActors when no strong WorkerRef, ChildWorker, and
  // Timeout since the checking is expensive.
  if (eligibleForCC) {
    eligibleForCC = !HasBackgroundActors();
  }

  {
    MutexAutoLock lock(mMutex);
    mCCFlagSaysEligible = eligibleForCC;
  }
}

bool WorkerPrivate::IsEligibleForCC() {
  LOGV(("WorkerPrivate::IsEligibleForCC [%p]", this));
  MutexAutoLock lock(mMutex);
  if (mStatus > Canceling) {
    return true;
  }

  bool hasShutdownTasks = !mShutdownTasks.IsEmpty();
  bool hasPendingEvents = false;
  if (mThread) {
    hasPendingEvents =
        NS_SUCCEEDED(mThread->HasPendingEvents(&hasPendingEvents)) &&
        hasPendingEvents;
  }

  LOGV(("mMainThreadEventTarget: %s",
        mMainThreadEventTarget->IsEmpty() ? "empty" : "non-empty"));
  LOGV(("mMainThreadEventTargetForMessaging: %s",
        mMainThreadEventTargetForMessaging->IsEmpty() ? "empty" : "non-empty"));
  LOGV(("mMainThreadDebuggerEventTarget: %s",
        mMainThreadDebuggeeEventTarget->IsEmpty() ? "empty" : "non-empty"));
  LOGV(("mCCFlagSaysEligible: %s", mCCFlagSaysEligible ? "true" : "false"));
  LOGV(("hasShutdownTasks: %s", hasShutdownTasks ? "true" : "false"));
  LOGV(("hasPendingEvents: %s", hasPendingEvents ? "true" : "false"));

  return mMainThreadEventTarget->IsEmpty() &&
         mMainThreadEventTargetForMessaging->IsEmpty() &&
         mMainThreadDebuggeeEventTarget->IsEmpty() && mCCFlagSaysEligible &&
         !hasShutdownTasks && !hasPendingEvents && mWorkerLoopIsIdle;
}

void WorkerPrivate::CancelAllTimeouts() {
  auto data = mWorkerThreadAccessible.Access();

  auto* timeoutManager =
      data->mScope ? data->mScope->GetTimeoutManager() : nullptr;
  if (timeoutManager) {
    timeoutManager->ClearAllTimeouts();
    if (!timeoutManager->HasTimeouts()) {
      UpdateCCFlag(CCFlag::EligibleForTimeout);
    }
  }

  LOG(TimeoutsLog(), ("Worker %p CancelAllTimeouts.\n", this));
}

already_AddRefed<nsISerialEventTarget> WorkerPrivate::CreateNewSyncLoop(
    WorkerStatus aFailStatus) {
  AssertIsOnWorkerThread();
  MOZ_ASSERT(
      aFailStatus >= Canceling,
      "Sync loops can be created when the worker is in Running/Closing state!");

  LOG(WorkerLog(), ("WorkerPrivate::CreateNewSyncLoop [%p] failstatus: %u",
                    this, static_cast<uint8_t>(aFailStatus)));

  ThreadEventQueue* queue = nullptr;
  {
    MutexAutoLock lock(mMutex);

    if (mStatus >= aFailStatus) {
      return nullptr;
    }
    queue = static_cast<ThreadEventQueue*>(mThread->EventQueue());
  }

  nsCOMPtr<nsISerialEventTarget> nestedEventTarget = queue->PushEventQueue();
  MOZ_ASSERT(nestedEventTarget);

  RefPtr<EventTarget> workerEventTarget =
      new EventTarget(this, nestedEventTarget);

  {
    // Modifications must be protected by mMutex in DEBUG builds, see comment
    // about mSyncLoopStack in WorkerPrivate.h.
#ifdef DEBUG
    MutexAutoLock lock(mMutex);
#endif

    mSyncLoopStack.AppendElement(new SyncLoopInfo(workerEventTarget));
  }

  return workerEventTarget.forget();
}

nsresult WorkerPrivate::RunCurrentSyncLoop() {
  AssertIsOnWorkerThread();
  LOG(WorkerLog(), ("WorkerPrivate::RunCurrentSyncLoop [%p]", this));
  RefPtr<WorkerThread> thread;
  JSContext* cx = GetJSContext();
  MOZ_ASSERT(cx);
  // mThread is set before we enter, and is never changed during
  // RunCurrentSyncLoop.
  {
    MutexAutoLock lock(mMutex);
    // Copy to local so we don't trigger mutex analysis lower down
    // mThread is set before we enter, and is never changed during
    // RunCurrentSyncLoop copy to local so we don't trigger mutex analysis
    thread = mThread;
  }

  AutoPushEventLoopGlobal eventLoopGlobal(this, cx);

  // This should not change between now and the time we finish running this sync
  // loop.
  uint32_t currentLoopIndex = mSyncLoopStack.Length() - 1;

  SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex].get();

  AutoYieldJSThreadExecution yield;

  MOZ_ASSERT(loopInfo);
  MOZ_ASSERT(!loopInfo->mHasRun);
  MOZ_ASSERT(!loopInfo->mCompleted);

#ifdef DEBUG
  loopInfo->mHasRun = true;
#endif

  {
    while (!loopInfo->mCompleted) {
      bool normalRunnablesPending = false;

      // Don't block with the periodic GC timer running.
      if (!NS_HasPendingEvents(thread)) {
        SetGCTimerMode(IdleTimer);
      }

      // Wait for something to do.
      {
        MutexAutoLock lock(mMutex);

        for (;;) {
          while (mControlQueue.IsEmpty() && !normalRunnablesPending &&
                 !(normalRunnablesPending = NS_HasPendingEvents(thread))) {
            WaitForWorkerEvents();
          }

          auto result = ProcessAllControlRunnablesLocked();
          if (result != ProcessAllControlRunnablesResult::Nothing) {
            // The state of the world may have changed. Recheck it if we need to
            // continue.
            normalRunnablesPending =
                result == ProcessAllControlRunnablesResult::MayContinue &&
                NS_HasPendingEvents(thread);

            // NB: If we processed a NotifyRunnable, we might have run
            // non-control runnables, one of which may have shut down the
            // sync loop.
            if (loopInfo->mCompleted) {
              break;
            }
          }

          // If we *didn't* run any control runnables, this should be unchanged.
          MOZ_ASSERT(!loopInfo->mCompleted);

          if (normalRunnablesPending) {
            break;
          }
        }
      }

      if (normalRunnablesPending) {
        // Make sure the periodic timer is running before we continue.
        SetGCTimerMode(PeriodicTimer);

        MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(thread, false));

        // Now *might* be a good time to GC. Let the JS engine make the
        // decision.
        if (GetCurrentEventLoopGlobal()) {
          // If GetCurrentEventLoopGlobal() is non-null, our JSContext is in a
          // Realm, so it's safe to try to GC.
          MOZ_ASSERT(JS::CurrentGlobalOrNull(cx));
          JS_MaybeGC(cx);
        }
      }
    }
  }

  // Make sure that the stack didn't change underneath us.
  MOZ_ASSERT(mSyncLoopStack[currentLoopIndex].get() == loopInfo);

  return DestroySyncLoop(currentLoopIndex);
}

nsresult WorkerPrivate::DestroySyncLoop(uint32_t aLoopIndex) {
  MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
  MOZ_ASSERT(mSyncLoopStack.Length() - 1 == aLoopIndex);

  LOG(WorkerLog(),
      ("WorkerPrivate::DestroySyncLoop [%p] aLoopIndex: %u", this, aLoopIndex));

  AutoYieldJSThreadExecution yield;

  // We're about to delete the loop, stash its event target and result.
  const auto& loopInfo = mSyncLoopStack[aLoopIndex];

  nsresult result = loopInfo->mResult;

  {
    RefPtr<nsIEventTarget> nestedEventTarget(
        loopInfo->mEventTarget->GetNestedEventTarget());
    MOZ_ASSERT(nestedEventTarget);

    loopInfo->mEventTarget->Shutdown();

    {
      MutexAutoLock lock(mMutex);
      static_cast<ThreadEventQueue*>(mThread->EventQueue())
          ->PopEventQueue(nestedEventTarget);
    }
  }

  // Are we making a 1 -> 0 transition here?
  if (mSyncLoopStack.Length() == 1) {
    if ((mPostSyncLoopOperations & eDispatchCancelingRunnable)) {
      LOG(WorkerLog(),
          ("WorkerPrivate::DestroySyncLoop [%p] Dispatching CancelingRunnables",
           this));
      DispatchCancelingRunnable();
    }

    mPostSyncLoopOperations = 0;
  }

  {
    // Modifications must be protected by mMutex in DEBUG builds, see comment
    // about mSyncLoopStack in WorkerPrivate.h.
#ifdef DEBUG
    MutexAutoLock lock(mMutex);
#endif

    // This will delete |loopInfo|!
    mSyncLoopStack.RemoveElementAt(aLoopIndex);
  }

  return result;
}

void WorkerPrivate::DispatchCancelingRunnable() {
  // Here we use a normal runnable to know when the current JS chunk of code
  // is finished. We cannot use a WorkerRunnable because they are not
  // accepted any more by the worker, and we do not want to use a
  // WorkerControlRunnable because they are immediately executed.

  LOG(WorkerLog(), ("WorkerPrivate::DispatchCancelingRunnable [%p]", this));
  RefPtr<CancelingRunnable> r = new CancelingRunnable();
  {
    MutexAutoLock lock(mMutex);
    mThread->nsThread::Dispatch(r.forget(), NS_DISPATCH_NORMAL);
  }

  // At the same time, we want to be sure that we interrupt infinite loops.
  // The following runnable starts a timer that cancel the worker, from the
  // parent thread, after CANCELING_TIMEOUT millseconds.
  LOG(WorkerLog(), ("WorkerPrivate::DispatchCancelingRunnable [%p] Setup a "
                    "timeout canceling",
                    this));
  RefPtr<CancelingWithTimeoutOnParentRunnable> rr =
      new CancelingWithTimeoutOnParentRunnable(this);
  rr->Dispatch(this);
}

void WorkerPrivate::ReportUseCounters() {
  AssertIsOnWorkerThread();

  if (mReportedUseCounters) {
    return;
  }
  mReportedUseCounters = true;

  if (IsChromeWorker()) {
    return;
  }

  const size_t kind = Kind();
  switch (kind) {
    case WorkerKindDedicated:
      glean::use_counter::dedicated_workers_destroyed.Add();
      break;
    case WorkerKindShared:
      glean::use_counter::shared_workers_destroyed.Add();
      break;
    case WorkerKindService:
      glean::use_counter::service_workers_destroyed.Add();
      break;
    default:
      MOZ_ASSERT(false, "Unknown worker kind");
      return;
  }

  Maybe<nsCString> workerPathForLogging;
  const bool dumpCounters = StaticPrefs::dom_use_counters_dump_worker();
  if (dumpCounters) {
    nsAutoCString path(Domain());
    path.AppendLiteral("(");
    NS_ConvertUTF16toUTF8 script(ScriptURL());
    path.Append(script);
    path.AppendPrintf(", 0x%p)", this);
    workerPathForLogging.emplace(std::move(path));
  }

  const size_t count = static_cast<size_t>(UseCounterWorker::Count);

  const auto workerKind = Kind();
  for (size_t c = 0; c < count; ++c) {
    if (!GetUseCounter(static_cast<UseCounterWorker>(c))) {
      continue;
    }
    const char* metricName =
        IncrementWorkerUseCounter(static_cast<UseCounterWorker>(c), workerKind);
    if (dumpCounters) {
      printf_stderr("USE_COUNTER_WORKER: %s - %s\n", metricName,
                    workerPathForLogging->get());
    }
  }
}

void WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget,
                                 nsresult aResult) {
  AssertValidSyncLoop(aSyncLoopTarget);

  if (!MaybeStopSyncLoop(aSyncLoopTarget, aResult)) {
    // TODO: I wonder if we should really ever crash here given the assert.
    MOZ_CRASH("Unknown sync loop!");
  }
}

bool WorkerPrivate::MaybeStopSyncLoop(nsIEventTarget* aSyncLoopTarget,
                                      nsresult aResult) {
  AssertIsOnWorkerThread();

  for (uint32_t index = mSyncLoopStack.Length(); index > 0; index--) {
    const auto& loopInfo = mSyncLoopStack[index - 1];
    MOZ_ASSERT(loopInfo);
    MOZ_ASSERT(loopInfo->mEventTarget);

    if (loopInfo->mEventTarget == aSyncLoopTarget) {
      // Can't assert |loop->mHasRun| here because dispatch failures can cause
      // us to bail out early.
      MOZ_ASSERT(!loopInfo->mCompleted);

      loopInfo->mResult = aResult;
      loopInfo->mCompleted = true;

      loopInfo->mEventTarget->Disable();

      return true;
    }

    MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
  }

  return false;
}

#ifdef DEBUG
void WorkerPrivate::AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget) {
  MOZ_ASSERT(aSyncLoopTarget);

  EventTarget* workerTarget;
  nsresult rv = aSyncLoopTarget->QueryInterface(
      kDEBUGWorkerEventTargetIID, reinterpret_cast<void**>(&workerTarget));
  MOZ_ASSERT(NS_SUCCEEDED(rv));
  MOZ_ASSERT(workerTarget);

  bool valid = false;

  {
    MutexAutoLock lock(mMutex);

    for (uint32_t index = 0; index < mSyncLoopStack.Length(); index++) {
      const auto& loopInfo = mSyncLoopStack[index];
      MOZ_ASSERT(loopInfo);
      MOZ_ASSERT(loopInfo->mEventTarget);

      if (loopInfo->mEventTarget == aSyncLoopTarget) {
        valid = true;
        break;
      }

      MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
    }
  }

  MOZ_ASSERT(valid);
}
#endif

void WorkerPrivate::PostMessageToParent(
    JSContext* aCx, JS::Handle<JS::Value> aMessage,
    const Sequence<JSObject*>& aTransferable, ErrorResult& aRv) {
  LOG(WorkerLog(), ("WorkerPrivate::PostMessageToParent [%p]", this));
  AssertIsOnWorkerThread();
  MOZ_DIAGNOSTIC_ASSERT(IsDedicatedWorker());

  JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());

  aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransferable,
                                                          &transferable);
  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  RefPtr<MessageEventToParentRunnable> runnable =
      new MessageEventToParentRunnable(this);

  JS::CloneDataPolicy clonePolicy;

  // Parent and dedicated workers are always part of the same cluster.
  clonePolicy.allowIntraClusterClonableSharedObjects();

  if (IsSharedMemoryAllowed()) {
    clonePolicy.allowSharedMemoryObjects();
  }

  runnable->Write(aCx, aMessage, transferable, clonePolicy, aRv);

  if (NS_WARN_IF(aRv.Failed())) {
    return;
  }

  if (!runnable->Dispatch(this)) {
    aRv = NS_ERROR_FAILURE;
  }
}

void WorkerPrivate::EnterDebuggerEventLoop() {
  auto data = mWorkerThreadAccessible.Access();

  JSContext* cx = GetJSContext();
  MOZ_ASSERT(cx);

  AutoPushEventLoopGlobal eventLoopGlobal(this, cx);
  AutoYieldJSThreadExecution yield;

  CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();

  uint32_t currentEventLoopLevel = ++data->mDebuggerEventLoopLevel;

  while (currentEventLoopLevel <= data->mDebuggerEventLoopLevel) {
    bool debuggerRunnablesPending = false;

    {
      MutexAutoLock lock(mMutex);

      debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
    }

    // Don't block with the periodic GC timer running.
    if (!debuggerRunnablesPending) {
      SetGCTimerMode(IdleTimer);
    }

    // Wait for something to do
    {
      MutexAutoLock lock(mMutex);

      while (mControlQueue.IsEmpty() &&
             !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
             !JS::HasDebuggerMicroTasks(cx)) {
        WaitForWorkerEvents();
      }

      ProcessAllControlRunnablesLocked();

      // XXXkhuey should we abort JS on the stack here if we got Abort above?
    }
    ccjscx->PerformDebuggerMicroTaskCheckpoint();
    if (debuggerRunnablesPending) {
      // Start the periodic GC timer if it is not already running.
      SetGCTimerMode(PeriodicTimer);

      ProcessSingleDebuggerRunnable();

      // Now *might* be a good time to GC. Let the JS engine make the decision.
      if (GetCurrentEventLoopGlobal()) {
        // If GetCurrentEventLoopGlobal() is non-null, our JSContext is in a
        // Realm, so it's safe to try to GC.
        MOZ_ASSERT(JS::CurrentGlobalOrNull(cx));
        JS_MaybeGC(cx);
      }
    }
  }
}

void WorkerPrivate::LeaveDebuggerEventLoop() {
  auto data = mWorkerThreadAccessible.Access();

  // TODO: Why lock the mutex if we're accessing data accessible to one thread
  // only?
  MutexAutoLock lock(mMutex);

  if (data->mDebuggerEventLoopLevel > 0) {
    --data->mDebuggerEventLoopLevel;
  }
}

void WorkerPrivate::PostMessageToDebugger(const nsAString& aMessage) {
  AssertIsOnWorkerThread();

  // The local and remote debuggers are mutually exclusive; mDebugger is null
  // when this worker is exposed through the parent-process
  // RemoteWorkerDebugger. When the local debugger is present the remote one is
  // absent, so return rather than falling through and taking the mutex for
  // nothing.
  if (mDebugger) {
    mDebugger->PostMessageToDebugger(aMessage);
    return;
  }
  RefPtr<RemoteWorkerDebuggerChild> remoteDebugger;
  {
    MutexAutoLock lock(mMutex);
    if (!mRemoteDebugger) {
      return;
    }
    remoteDebugger = mRemoteDebugger;
  }
  MOZ_ASSERT_DEBUG_OR_FUZZING(remoteDebugger);
  (void)remoteDebugger->SendPostMessageToDebugger(nsAutoString(aMessage));
}

void WorkerPrivate::SetDebuggerImmediate(dom::Function& aHandler,
                                         ErrorResult& aRv) {
  AssertIsOnWorkerThread();

  RefPtr<DebuggerImmediateRunnable> runnable =
      new DebuggerImmediateRunnable(this, aHandler);
  if (!runnable->Dispatch(this)) {
    aRv.Throw(NS_ERROR_FAILURE);
  }
}

void WorkerPrivate::ReportErrorToDebugger(const nsACString& aFilename,
                                          uint32_t aLineno,
                                          const nsAString& aMessage) {
  AssertIsOnWorkerThread();
  // mDebugger is null for workers exposed through the remote debugger, and the
  // two are mutually exclusive, so return rather than falling through (see
  // PostMessageToDebugger).
  if (mDebugger) {
    mDebugger->ReportErrorToDebugger(aFilename, aLineno, aMessage);
    return;
  }
  RefPtr<RemoteWorkerDebuggerChild> remoteDebugger;
  {
    MutexAutoLock lock(mMutex);
    if (!mRemoteDebugger) {
      return;
    }
    remoteDebugger = mRemoteDebugger;
  }
  MOZ_ASSERT_DEBUG_OR_FUZZING(remoteDebugger);
  (void)remoteDebugger->SendReportErrorToDebugger(RemoteWorkerDebuggerErrorInfo(
      nsAutoCString(aFilename), aLineno, nsAutoString(aMessage)));
}

void WorkerPrivate::UpdateWindowIDToDebugger(const uint64_t& aWindowID,
                                             const bool& aIsAdd) {
  AssertIsOnWorkerThread();
  // only need to update the remote debugger since local debugger grab the
  // windowIDs information from RemoteWorkerChild directly.

  RefPtr<RemoteWorkerDebuggerChild> remoteDebugger;
  {
    MutexAutoLock lock(mMutex);
    if (!mRemoteDebugger) {
      return;
    }
    remoteDebugger = mRemoteDebugger;
  }
  MOZ_ASSERT_DEBUG_OR_FUZZING(remoteDebugger);
  if (aIsAdd) {
    (void)remoteDebugger->SendAddWindowID(aWindowID);
  } else {
    (void)remoteDebugger->SendRemoveWindowID(aWindowID);
  }
}

bool WorkerPrivate::NotifyInternal(WorkerStatus aStatus) {
  auto data = mWorkerThreadAccessible.Access();

  // Yield execution while notifying out-of-module WorkerRefs and cancelling
  // runnables.
  AutoYieldJSThreadExecution yield;

  NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!");

  RefPtr<EventTarget> eventTarget;

  // Save the old status and set the new status.
  {
    MutexAutoLock lock(mMutex);

    LOG(WorkerLog(),
        ("WorkerPrivate::NotifyInternal [%p] mStatus: %u, aStatus: %u", this,
         static_cast<uint8_t>(mStatus), static_cast<uint8_t>(aStatus)));

    if (mStatus >= aStatus) {
      return true;
    }

    MOZ_ASSERT_IF(aStatus == Killing,
                  mStatus == Canceling && mParentStatus == Canceling);

    mStatus = aStatus;

    // Mark parent status as closing immediately to avoid new events being
    // dispatched after we clear the queue below.
    if (aStatus == Closing) {
      Close();
    }

    // Synchronize the mParentStatus with mStatus, such that event dispatching
    // will fail in proper after WorkerPrivate gets into Killing status.
    if (aStatus >= Killing) {
      mParentStatus = aStatus;
    }
  }

  // Status transistion to "Canceling", mark the scope as dying.
  if (aStatus == Canceling && data->mScope) {
    data->mScope->NoteTerminating();
  }

  if (aStatus >= Closing) {
    CancelAllTimeouts();

    JSContext* cx = GetJSContext();
    if (cx) {
      // This will invoke the JS async task finished callback for cancellable
      // JS tasks, which will invoke JSAsyncTaskFinished and remove from
      // mPendingJSAsyncTasks.
      //
      // There may still be outstanding JS tasks for things that couldn't be
      // cancelled. These must either finish normally, or be blocked on
      // through a call to JS::ShutdownAsyncTasks. Cycle collector shutdown
      // will call this during worker shutdown.
      JS::CancelAsyncTasks(cx);
    }
  }

  if (aStatus == Closing && data->mScope) {
    data->mScope->SetIsNotEligibleForMessaging();
  }

  // Let all our holders know the new status.
  if (aStatus == Canceling) {
    NotifyWorkerRefs(aStatus);
  }

  if (aStatus == Canceling && mRemoteWorkerNonLifeCycleOpController) {
    mRemoteWorkerNonLifeCycleOpController->TransistionStateToCanceled();
  }

  // If the worker script never ran, or failed to compile, we don't need to do
  // anything else.
  if (!data->mScope) {
    if (aStatus == Canceling) {
      MOZ_ASSERT(!data->mCancelBeforeWorkerScopeConstructed);
      data->mCancelBeforeWorkerScopeConstructed.Flip();
    }
    return true;
  }

  // Don't abort the script now, but we dispatch a runnable to do it when the
  // current JS frame is executed.
  if (aStatus == Closing) {
    if (!mSyncLoopStack.IsEmpty()) {
      LOG(WorkerLog(), ("WorkerPrivate::NotifyInternal [%p] request to "
                        "dispatch canceling runnables...",
                        this));
      mPostSyncLoopOperations |= eDispatchCancelingRunnable;
    } else {
      DispatchCancelingRunnable();
    }
    return true;
  }

  MOZ_ASSERT(aStatus == Canceling || aStatus == Killing);

  LOG(WorkerLog(), ("WorkerPrivate::NotifyInternal [%p] abort script", this));

  // Always abort the script.
  return false;
}

void WorkerPrivate::ReportError(JSContext* aCx,
                                JS::ConstUTF8CharsZ aToStringResult,
                                JSErrorReport* aReport) {
  auto data = mWorkerThreadAccessible.Access();

  if (!MayContinueRunning() || data->mErrorHandlerRecursionCount == 2) {
    return;
  }

  NS_ASSERTION(data->mErrorHandlerRecursionCount == 0 ||
                   data->mErrorHandlerRecursionCount == 1,
               "Bad recursion logic!");

  UniquePtr<WorkerErrorReport> report = MakeUnique<WorkerErrorReport>();
  if (aReport) {
    report->AssignErrorReport(aReport);
  }

  JS::ExceptionStack exnStack(aCx);
  // NOTE: This function is used both for errors and warnings, and warnings
  //       can be reported while there's a pending exception.
  //       Warnings are always reported with non-null JSErrorReport.
  if (!aReport || !aReport->isWarning()) {
    MOZ_ASSERT(JS_IsExceptionPending(aCx));
    if (!JS::StealPendingExceptionStack(aCx, &exnStack)) {
      JS_ClearPendingException(aCx);
      return;
    }

    JS::Rooted<JSObject*> stack(aCx), stackGlobal(aCx);
    xpc::FindExceptionStackForConsoleReport(
        nullptr, exnStack.exception(), exnStack.stack(), &stack, &stackGlobal);

    if (stack) {
      JSAutoRealm ar(aCx, stackGlobal);
      report->SerializeWorkerStack(aCx, this, stack);
    }
  }

  if (report->mMessage.IsEmpty() && aToStringResult) {
    nsDependentCString toStringResult(aToStringResult.c_str());
    if (!AppendUTF8toUTF16(toStringResult, report->mMessage,
                           mozilla::fallible)) {
      // Try again, with only a 1 KB string. Do this infallibly this time.
      // If the user doesn't have 1 KB to spare we're done anyways.
      size_t index = std::min<size_t>(1024, toStringResult.Length());

      // Drop the last code point that may be cropped.
      index = RewindToPriorUTF8Codepoint(toStringResult.BeginReading(), index);

      nsDependentCString truncatedToStringResult(aToStringResult.c_str(),
                                                 index);
      AppendUTF8toUTF16(truncatedToStringResult, report->mMessage);
    }
  }

  data->mErrorHandlerRecursionCount++;

  // Don't want to run the scope's error handler if this is a recursive error or
  // if we ran out of memory.
  bool fireAtScope = data->mErrorHandlerRecursionCount == 1 &&
                     report->mErrorNumber != JSMSG_OUT_OF_MEMORY &&
                     JS::CurrentGlobalOrNull(aCx);

  WorkerErrorReport::ReportError(aCx, this, fireAtScope, nullptr,
                                 std::move(report), 0, exnStack.exception());

  data->mErrorHandlerRecursionCount--;
}

// static
void WorkerPrivate::ReportErrorToConsole(
    uint32_t aErrorFlags, const nsCString& aCategory, PropertiesFile aFile,
    const nsCString& aMessageName, const nsTArray<nsString>& aParams,
    const mozilla::SourceLocation& aLocation) {
  WorkerPrivate* wp = nullptr;
  if (!NS_IsMainThread()) {
    wp = GetCurrentThreadWorkerPrivate();
  }

  ReportErrorToConsoleRunnable::Report(wp, aErrorFlags, aCategory, aFile,
                                       aMessageName, aParams, aLocation);
}

int32_t WorkerPrivate::SetTimeout(JSContext* aCx, TimeoutHandler* aHandler,
                                  int32_t aTimeout, bool aIsInterval,
                                  Timeout::Reason aReason, ErrorResult& aRv) {
  auto data = mWorkerThreadAccessible.Access();
  MOZ_ASSERT(aHandler);

  WorkerGlobalScope* globalScope = GlobalScope();
  MOZ_DIAGNOSTIC_ASSERT(globalScope);
  auto* timeoutManager = globalScope->GetTimeoutManager();
  MOZ_DIAGNOSTIC_ASSERT(timeoutManager);
  int32_t timerId = -1;
  WorkerStatus status;
  {
    MutexAutoLock lock(mMutex);
    status = mStatus;
  }
  // If the worker is trying to call setTimeout/setInterval and the
  // worker itself which has initiated the close process.
  if (status >= Closing) {
    return timeoutManager->GetTimeoutId(aReason);
  }
  bool hadTimeouts = timeoutManager->HasTimeouts();
  nsresult rv = timeoutManager->SetTimeout(aHandler, aTimeout, aIsInterval,
                                           aReason, &timerId);
  if (NS_FAILED(rv)) {
    aRv.Throw(NS_ERROR_FAILURE);
    return timerId;
  }
  if (!hadTimeouts) {
    UpdateCCFlag(CCFlag::IneligibleForTimeout);
  }
  return timerId;
}

void WorkerPrivate::ClearTimeout(int32_t aId, Timeout::Reason aReason) {
  MOZ_ASSERT(aReason == Timeout::Reason::eTimeoutOrInterval,
             "This timeout reason doesn't support cancellation.");
  WorkerGlobalScope* globalScope = GlobalScope();
  MOZ_DIAGNOSTIC_ASSERT(globalScope);
  auto* timeoutManager = globalScope->GetTimeoutManager();
  MOZ_DIAGNOSTIC_ASSERT(timeoutManager);
  timeoutManager->ClearTimeout(aId, aReason);
  if (!timeoutManager->HasTimeouts()) {
    UpdateCCFlag(CCFlag::EligibleForTimeout);
  }
}

void WorkerPrivate::StartCancelingTimer() {
  AssertIsOnParentThread();

  // return if mCancelingTimer has already existed.
  if (mCancelingTimer) {
    return;
  }

  auto errorCleanup = MakeScopeExit([&] { mCancelingTimer = nullptr; });

  if (WorkerPrivate* parent = GetParent()) {
    mCancelingTimer = NS_NewTimer(parent->ControlEventTarget());
  } else {
    mCancelingTimer = NS_NewTimer();
  }

  if (NS_WARN_IF(!mCancelingTimer)) {
    return;
  }

  // This is not needed if we are already in an advanced shutdown state.
  {
    MutexAutoLock lock(mMutex);
    if (ParentStatus() >= Canceling) {
      return;
    }
  }

  uint32_t cancelingTimeoutMillis =
      StaticPrefs::dom_worker_canceling_timeoutMilliseconds();

  RefPtr<CancelingTimerCallback> callback = new CancelingTimerCallback(this);
  nsresult rv = mCancelingTimer->InitWithCallback(
      callback, cancelingTimeoutMillis, nsITimer::TYPE_ONE_SHOT);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return;
  }

  errorCleanup.release();
}

void WorkerPrivate::UpdateContextOptionsInternal(
    JSContext* aCx, const JS::ContextOptions& aContextOptions) {
  auto data = mWorkerThreadAccessible.Access();

  JS::ContextOptionsRef(aCx) = aContextOptions;

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->UpdateContextOptions(aContextOptions);
  }
}

void WorkerPrivate::UpdateLanguageOverrideInternal(
    const nsCString& aLanguageOverride,
    const nsTArray<nsString>& aResolvedLanguages) {
  mLoadInfo.mLanguageOverrideLocale = aLanguageOverride;
  if (aLanguageOverride.IsEmpty()) {
    mLoadInfo.mLanguageOverride.Clear();
  } else {
    mLoadInfo.mLanguageOverride = aResolvedLanguages.Clone();
  }

  WorkerGlobalScope* globalScope = GlobalScope();
  if (globalScope) {
    JSObject* global = globalScope->GetGlobalJSObject();
    if (global) {
      JS::Realm* realm = JS::GetObjectRealmOrNull(global);
      if (realm) {
        if (aLanguageOverride.IsEmpty()) {
          JS::SetRealmLocaleOverride(realm, nullptr);
        } else {
          JS::SetRealmLocaleOverride(realm, aLanguageOverride.get());
        }
      }
    }

    if (RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator()) {
      nav->SetLanguages(aResolvedLanguages);
    }
  }

  auto data = mWorkerThreadAccessible.Access();
  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->UpdateLanguageOverride(aLanguageOverride,
                                                       aResolvedLanguages);
  }
}

void WorkerPrivate::UpdateTimezoneOverrideInternal(JSContext* aCx,
                                                   const nsAString& aTimezone) {
  auto data = mWorkerThreadAccessible.Access();

  WorkerGlobalScope* globalScope = GlobalScope();
  if (globalScope) {
    JSObject* global = globalScope->GetGlobalJSObject();
    if (global) {
      JS::Realm* realm = JS::GetObjectRealmOrNull(global);
      if (realm) {
        if (aTimezone.IsEmpty()) {
          JS::SetRealmTimezoneOverride(realm, nullptr);
        } else {
          JS::SetRealmTimezoneOverride(realm,
                                       NS_ConvertUTF16toUTF8(aTimezone).get());
        }
      }
    }
  }

  mLoadInfo.mTimezoneOverride = aTimezone;

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->UpdateTimezoneOverride(aTimezone);
  }
}

void WorkerPrivate::UpdateLanguagesInternal(
    const nsTArray<nsString>& aLanguages) {
  WorkerGlobalScope* globalScope = GlobalScope();
  RefPtr<WorkerNavigator> nav = globalScope->GetExistingNavigator();
  if (nav) {
    nav->SetLanguages(aLanguages);
  }

  auto data = mWorkerThreadAccessible.Access();
  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->UpdateLanguages(aLanguages);
  }

  RefPtr<Event> event = NS_NewDOMEvent(globalScope, nullptr, nullptr);

  event->InitEvent(u"languagechange"_ns, false, false);
  event->SetTrusted(true);

  globalScope->DispatchEvent(*event);
}

void WorkerPrivate::UpdateJSWorkerMemoryParameterInternal(
    JSContext* aCx, JSGCParamKey aKey, Maybe<uint32_t> aValue) {
  auto data = mWorkerThreadAccessible.Access();

  if (aValue) {
    JS_SetGCParameter(aCx, aKey, *aValue);
  } else {
    JS_ResetGCParameter(aCx, aKey);
  }

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->UpdateJSWorkerMemoryParameter(aKey, aValue);
  }
}

#ifdef JS_GC_ZEAL
void WorkerPrivate::UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal,
                                         uint32_t aFrequency) {
  auto data = mWorkerThreadAccessible.Access();

  JS::SetGCZeal(aCx, aGCZeal, aFrequency);

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->UpdateGCZeal(aGCZeal, aFrequency);
  }
}
#endif

void WorkerPrivate::SetLowMemoryStateInternal(JSContext* aCx, bool aState) {
  auto data = mWorkerThreadAccessible.Access();

  JS::SetLowMemoryState(aCx, aState);

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->SetLowMemoryState(aState);
  }
}

void WorkerPrivate::SetCCCollectedAnything(bool collectedAnything) {
  mWorkerThreadAccessible.Access()->mCCCollectedAnything = collectedAnything;
}

uint32_t WorkerPrivate::GetCurrentTimerNestingLevel() const {
  auto data = mWorkerThreadAccessible.Access();
  return data->mScope
             ? data->mScope->GetTimeoutManager()->GetNestingLevelForWorker()
             : 0;
}

bool WorkerPrivate::isLastCCCollectedAnything() {
  return mWorkerThreadAccessible.Access()->mCCCollectedAnything;
}

void WorkerPrivate::GarbageCollectInternal(JSContext* aCx, bool aShrinking,
                                           bool aCollectChildren) {
  // Perform GC followed by CC (the CC is triggered by
  // WorkerJSRuntime::CustomGCCallback at the end of the collection).

  auto data = mWorkerThreadAccessible.Access();

  if (!GlobalScope()) {
    // We haven't compiled anything yet. Just bail out.
    return;
  }

  if (aShrinking || aCollectChildren) {
    JS::PrepareForFullGC(aCx);

    if (aShrinking && mSyncLoopStack.IsEmpty()) {
      JS::NonIncrementalGC(aCx, JS::GCOptions::Shrink,
                           JS::GCReason::DOM_WORKER);

      // Check whether the CC collected anything and if so GC again. This is
      // necessary to collect all garbage.
      if (data->mCCCollectedAnything) {
        JS::NonIncrementalGC(aCx, JS::GCOptions::Normal,
                             JS::GCReason::DOM_WORKER);
      }

      if (!aCollectChildren) {
        LOG(WorkerLog(), ("Worker %p collected idle garbage\n", this));
      }
    } else {
      JS::NonIncrementalGC(aCx, JS::GCOptions::Normal,
                           JS::GCReason::DOM_WORKER);
      LOG(WorkerLog(), ("Worker %p collected garbage\n", this));
    }
  } else {
    JS_MaybeGC(aCx);
    LOG(WorkerLog(), ("Worker %p collected periodic garbage\n", this));
  }

  if (aCollectChildren) {
    for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
      data->mChildWorkers[index]->GarbageCollect(aShrinking);
    }
  }
}

void WorkerPrivate::CycleCollectInternal(bool aCollectChildren) {
  auto data = mWorkerThreadAccessible.Access();

  nsCycleCollector_collect(CCReason::WORKER, nullptr);

  if (aCollectChildren) {
    for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
      data->mChildWorkers[index]->CycleCollect();
    }
  }
}

void WorkerPrivate::MemoryPressureInternal() {
  auto data = mWorkerThreadAccessible.Access();

  if (data->mScope) {
    RefPtr<Console> console = data->mScope->GetConsoleIfExists();
    if (console) {
      console->ClearStorage();
    }

    RefPtr<Performance> performance = data->mScope->GetPerformanceIfExists();
    if (performance) {
      performance->MemoryPressure();
    }

    data->mScope->RemoveReportRecords();
  }

  if (data->mDebuggerScope) {
    RefPtr<Console> console = data->mDebuggerScope->GetConsoleIfExists();
    if (console) {
      console->ClearStorage();
    }
  }

  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
    data->mChildWorkers[index]->MemoryPressure();
  }
}

void WorkerPrivate::SetThread(WorkerThread* aThread) {
  if (aThread) {
#ifdef DEBUG
    {
      bool isOnCurrentThread;
      MOZ_ASSERT(NS_SUCCEEDED(aThread->IsOnCurrentThread(&isOnCurrentThread)));
      MOZ_ASSERT(!isOnCurrentThread);
    }
#endif

    MOZ_ASSERT(!mPRThread);
    mPRThread = PRThreadFromThread(aThread);
    MOZ_ASSERT(mPRThread);

    mWorkerThreadAccessible.Transfer(mPRThread);
  } else {
    MOZ_ASSERT(mPRThread);
  }
}

void WorkerPrivate::SetWorkerPrivateInWorkerThread(
    WorkerThread* const aThread) {
  LOG(WorkerLog(),
      ("WorkerPrivate::SetWorkerPrivateInWorkerThread [%p]", this));
  MutexAutoLock lock(mMutex);

  MOZ_ASSERT(!mThread);
  MOZ_ASSERT(mStatus == Pending);

  mThread = aThread;
  mThread->SetWorker(WorkerThreadFriendKey{}, this);

  if (!mPreStartRunnables.IsEmpty()) {
    for (uint32_t index = 0; index < mPreStartRunnables.Length(); index++) {
      MOZ_ALWAYS_SUCCEEDS(mThread->DispatchAnyThread(
          WorkerThreadFriendKey{}, mPreStartRunnables[index]));
    }
    // Don't clear mPreStartRunnables here, it will be cleared in the beginning
    // of WorkerPrivate::DoRunLoop() or when in WorkerPrivate::RunLoopNeverRan()
  }
}

void WorkerPrivate::ResetWorkerPrivateInWorkerThread() {
  LOG(WorkerLog(),
      ("WorkerPrivate::ResetWorkerPrivateInWorkerThread [%p]", this));
  RefPtr<WorkerThread> doomedThread;

  // Release the mutex before doomedThread.
  MutexAutoLock lock(mMutex);
  MOZ_ASSERT(mStatus == Dead);

  MOZ_ASSERT(mThread);

  mThread->ClearEventQueueAndWorker(WorkerThreadFriendKey{});
  mThread.swap(doomedThread);
}

void WorkerPrivate::BeginCTypesCall() {
  AssertIsOnWorkerThread();
  auto data = mWorkerThreadAccessible.Access();

  // Don't try to GC while we're blocked in a ctypes call.
  SetGCTimerMode(NoTimer);

  data->mYieldJSThreadExecution.EmplaceBack();
}

void WorkerPrivate::EndCTypesCall() {
  AssertIsOnWorkerThread();
  auto data = mWorkerThreadAccessible.Access();

  data->mYieldJSThreadExecution.RemoveLastElement();

  // Make sure the periodic timer is running before we start running JS again.
  SetGCTimerMode(PeriodicTimer);
}

void WorkerPrivate::BeginCTypesCallback() {
  AssertIsOnWorkerThread();

  // Make sure the periodic timer is running before we start running JS again.
  SetGCTimerMode(PeriodicTimer);

  // Re-requesting execution is not needed since the JSRuntime code calling
  // this will do an AutoEntryScript.
}

void WorkerPrivate::EndCTypesCallback() {
  AssertIsOnWorkerThread();

  // Don't try to GC while we're blocked in a ctypes call.
  SetGCTimerMode(NoTimer);
}

bool WorkerPrivate::ConnectMessagePort(JSContext* aCx,
                                       UniqueMessagePortId& aIdentifier) {
  AssertIsOnWorkerThread();

  WorkerGlobalScope* globalScope = GlobalScope();

  JS::Rooted<JSObject*> jsGlobal(aCx, globalScope->GetWrapper());
  MOZ_ASSERT(jsGlobal);

  // This UniqueMessagePortId is used to create a new port, still connected
  // with the other one, but in the worker thread.
  ErrorResult rv;
  RefPtr<MessagePort> port = MessagePort::Create(globalScope, aIdentifier, rv);
  if (NS_WARN_IF(rv.Failed())) {
    rv.SuppressException();
    return false;
  }

  GlobalObject globalObject(aCx, jsGlobal);
  if (globalObject.Failed()) {
    return false;
  }

  RootedDictionary<MessageEventInit> init(aCx);
  init.mData = JS_GetEmptyStringValue(aCx);
  init.mBubbles = false;
  init.mCancelable = false;
  init.mSource.SetValue().SetAsMessagePort() = port;
  if (!init.mPorts.AppendElement(port.forget(), fallible)) {
    return false;
  }

  RefPtr<MessageEvent> event =
      MessageEvent::Constructor(globalObject, u"connect"_ns, init);

  event->SetTrusted(true);

  globalScope->DispatchEvent(*event);

  return true;
}

WorkerGlobalScope* WorkerPrivate::GetOrCreateGlobalScope(JSContext* aCx) {
  auto data = mWorkerThreadAccessible.Access();

  if (data->mScope) {
    return data->mScope;
  }

  if (IsSharedWorker()) {
    data->mScope =
        new SharedWorkerGlobalScope(this, CreateClientSource(), WorkerName());
  } else if (IsServiceWorker()) {
    data->mScope = new ServiceWorkerGlobalScope(
        this, CreateClientSource(), GetServiceWorkerRegistrationDescriptor());
  } else {
    data->mScope = new DedicatedWorkerGlobalScope(this, CreateClientSource(),
                                                  WorkerName());
  }

  JS::Rooted<JSObject*> global(aCx);
  NS_ENSURE_TRUE(data->mScope->WrapGlobalObject(aCx, &global), nullptr);

  JSAutoRealm ar(aCx, global);

  if (!RegisterBindings(aCx, global)) {
    data->mScope = nullptr;
    return nullptr;
  }

  // Worker has already in "Canceling", let the WorkerGlobalScope start dying.
  if (data->mCancelBeforeWorkerScopeConstructed) {
    data->mScope->NoteTerminating();
    data->mScope->DisconnectGlobalTeardownObservers();
  }

  JS_FireOnNewGlobalObject(aCx, global);

  return data->mScope;
}

WorkerDebuggerGlobalScope* WorkerPrivate::CreateDebuggerGlobalScope(
    JSContext* aCx) {
  auto data = mWorkerThreadAccessible.Access();
  MOZ_ASSERT(!data->mDebuggerScope);

  // The debugger global gets a dummy client, not the "real" client used by the
  // debugee worker.
  auto clientSource = ClientManager::CreateSource(
      GetClientType(), HybridEventTarget(), NullPrincipalInfo());

  data->mDebuggerScope =
      new WorkerDebuggerGlobalScope(this, std::move(clientSource));

  JS::Rooted<JSObject*> global(aCx);
  NS_ENSURE_TRUE(data->mDebuggerScope->WrapGlobalObject(aCx, &global), nullptr);

  JSAutoRealm ar(aCx, global);

  if (!RegisterDebuggerBindings(aCx, global)) {
    data->mDebuggerScope = nullptr;
    return nullptr;
  }

  JS_FireOnNewGlobalObject(aCx, global);

  return data->mDebuggerScope;
}

bool WorkerPrivate::IsOnWorkerThread() const {
  // We can't use mThread because it must be protected by mMutex and sometimes
  // this method is called when mMutex is already locked. This method should
  // always work.
  MOZ_ASSERT(mPRThread,
             "AssertIsOnWorkerThread() called before a thread was assigned!");

  return mPRThread == PR_GetCurrentThread();
}

#ifdef DEBUG
void WorkerPrivate::AssertIsOnWorkerThread() const {
  MOZ_ASSERT(IsOnWorkerThread());
}
#endif  // DEBUG

void WorkerPrivate::DumpCrashInformation(nsACString& aString) {
  auto data = mWorkerThreadAccessible.Access();

  aString.Append("IsChromeWorker(");
  if (IsChromeWorker()) {
    aString.Append(NS_ConvertUTF16toUTF8(ScriptURL()));
  } else {
    aString.Append("false");
  }
  aString.Append(")");
  for (const auto* workerRef : data->mWorkerRefs.NonObservingRange()) {
    if (workerRef->IsPreventingShutdown()) {
      aString.Append("|");
      aString.Append(workerRef->Name());
      const nsCString status = GET_WORKERREF_DEBUG_STATUS(workerRef);
      if (!status.IsEmpty()) {
        aString.Append("[");
        aString.Append(status);
        aString.Append("]");
      }
    }
  }
}

PerformanceStorage* WorkerPrivate::GetPerformanceStorage() {
  MOZ_ASSERT(mPerformanceStorage);
  return mPerformanceStorage;
}

bool WorkerPrivate::ShouldResistFingerprinting(RFPTarget aTarget) const {
  return mLoadInfo.mShouldResistFingerprinting &&
         nsRFPService::IsRFPEnabledFor(
             mLoadInfo.mOriginAttributes.IsPrivateBrowsing(), aTarget,
             mLoadInfo.mOverriddenFingerprintingSettings);
}

void WorkerPrivate::SetRemoteWorkerController(RemoteWorkerChild* aController) {
  AssertIsOnMainThread();
  MOZ_ASSERT(aController);
  MOZ_ASSERT(!mRemoteWorkerController);

  mRemoteWorkerController = aController;
}

RemoteWorkerChild* WorkerPrivate::GetRemoteWorkerController() {
  AssertIsOnMainThread();
  MOZ_ASSERT(mRemoteWorkerController);
  return mRemoteWorkerController;
}

RefPtr<GenericPromise> WorkerPrivate::SetServiceWorkerSkipWaitingFlag() {
  AssertIsOnWorkerThread();
  MOZ_ASSERT(IsServiceWorker());

  RefPtr<RemoteWorkerChild> rwc = mRemoteWorkerController;

  if (!rwc) {
    return GenericPromise::CreateAndReject(NS_ERROR_DOM_ABORT_ERR, __func__);
  }

  RefPtr<GenericPromise> promise =
      rwc->MaybeSendSetServiceWorkerSkipWaitingFlag();

  return promise;
}

const nsString& WorkerPrivate::Id() {
  if (mId.IsEmpty()) {
    mId = ComputeWorkerPrivateId();
  }

  MOZ_ASSERT(!mId.IsEmpty());

  return mId;
}

bool WorkerPrivate::IsSharedMemoryAllowed() const {
  if (StaticPrefs::
          dom_postMessage_sharedArrayBuffer_bypassCOOP_COEP_insecure_enabled()) {
    return true;
  }

  // Allow privileged addons to access shared memory.
  if (mIsPrivilegedAddonGlobal) {
    return true;
  }

  return CrossOriginIsolated();
}

bool WorkerPrivate::CrossOriginIsolated() const {
  if (!StaticPrefs::
          dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup()) {
    return false;
  }

  return mAgentClusterOpenerPolicy ==
         nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
}

nsILoadInfo::CrossOriginEmbedderPolicy WorkerPrivate::GetEmbedderPolicy()
    const {
  if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
    return nsILoadInfo::EMBEDDER_POLICY_NULL;
  }

  return mEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
}

Result<Ok, nsresult> WorkerPrivate::SetEmbedderPolicy(
    nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(mEmbedderPolicy.isNothing());

  if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
    return Ok();
  }

  // https://html.spec.whatwg.org/multipage/browsers.html#check-a-global-object's-embedder-policy
  // If ownerPolicy's value is not compatible with cross-origin isolation or
  // policy's value is compatible with cross-origin isolation, then return true.
  EnsureOwnerEmbedderPolicy();
  nsILoadInfo::CrossOriginEmbedderPolicy ownerPolicy =
      mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
  if (nsContentSecurityManager::IsCompatibleWithCrossOriginIsolation(
          ownerPolicy) &&
      !nsContentSecurityManager::IsCompatibleWithCrossOriginIsolation(
          aPolicy)) {
    return Err(NS_ERROR_BLOCKED_BY_POLICY);
  }

  mEmbedderPolicy.emplace(aPolicy);

  return Ok();
}

void WorkerPrivate::InheritOwnerEmbedderPolicyOrNull(nsIRequest* aRequest) {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(aRequest);

  EnsureOwnerEmbedderPolicy();

  if (mOwnerEmbedderPolicy.isSome()) {
    nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
    MOZ_ASSERT(channel);

    nsCOMPtr<nsIURI> scriptURI;
    MOZ_ALWAYS_SUCCEEDS(channel->GetURI(getter_AddRefs(scriptURI)));

    bool isLocalScriptURI = false;
    MOZ_ALWAYS_SUCCEEDS(NS_URIChainHasFlags(
        scriptURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
        &isLocalScriptURI));

    MOZ_RELEASE_ASSERT(isLocalScriptURI);
  }

  mEmbedderPolicy.emplace(
      mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL));
}

bool WorkerPrivate::MatchEmbedderPolicy(
    nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) const {
  MOZ_ASSERT(NS_IsMainThread());

  if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
    return true;
  }

  return mEmbedderPolicy.value() == aPolicy;
}

nsILoadInfo::CrossOriginEmbedderPolicy WorkerPrivate::GetOwnerEmbedderPolicy()
    const {
  if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
    return nsILoadInfo::EMBEDDER_POLICY_NULL;
  }

  return mOwnerEmbedderPolicy.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL);
}

void WorkerPrivate::EnsureOwnerEmbedderPolicy() {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(mOwnerEmbedderPolicy.isNothing());

  if (GetParent()) {
    mOwnerEmbedderPolicy.emplace(GetParent()->GetEmbedderPolicy());
  } else if (GetWindow() && GetWindow()->GetWindowContext()) {
    mOwnerEmbedderPolicy.emplace(
        GetWindow()->GetWindowContext()->GetEmbedderPolicy());
  }
}

nsIPrincipal* WorkerPrivate::GetEffectiveStoragePrincipal() const {
  if (mLoadInfo.mUseRegularPrincipal) {
    return mLoadInfo.mPrincipal;
  }

  return mLoadInfo.mPartitionedPrincipal;
}

const mozilla::ipc::PrincipalInfo&
WorkerPrivate::GetEffectiveStoragePrincipalInfo() const {
  AssertIsOnWorkerThread();

  if (mLoadInfo.mUseRegularPrincipal) {
    return *mLoadInfo.mPrincipalInfo;
  }

  return *mLoadInfo.mPartitionedPrincipalInfo;
}

NS_IMPL_ADDREF(WorkerPrivate::EventTarget)
NS_IMPL_RELEASE(WorkerPrivate::EventTarget)

NS_INTERFACE_MAP_BEGIN(WorkerPrivate::EventTarget)
  NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget)
  NS_INTERFACE_MAP_ENTRY(nsIEventTarget)
  NS_INTERFACE_MAP_ENTRY(nsISupports)
#ifdef DEBUG
  // kDEBUGWorkerEventTargetIID is special in that it does not AddRef its
  // result.
  if (aIID.Equals(kDEBUGWorkerEventTargetIID)) {
    *aInstancePtr = this;
    return NS_OK;
  } else
#endif
NS_INTERFACE_MAP_END

NS_IMETHODIMP
WorkerPrivate::EventTarget::DispatchFromScript(nsIRunnable* aRunnable,
                                               DispatchFlags aFlags) {
  return Dispatch(do_AddRef(aRunnable), aFlags);
}

NS_IMETHODIMP
WorkerPrivate::EventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
                                     DispatchFlags aFlags) {
  // May be called on any thread!

  // NOTE: This nsIEventTarget implementation never leaks aRunnable, even if
  // NS_DISPATCH_FALLIBLE is not set.
  nsCOMPtr<nsIRunnable> event(aRunnable);

  RefPtr<WorkerRunnable> workerRunnable;

  MutexAutoLock lock(mMutex);

  if (mDisabled) {
    NS_WARNING(
        "A runnable was posted to a worker that is already shutting "
        "down!");
    return NS_ERROR_UNEXPECTED;
  }

  MOZ_ASSERT(mWorkerPrivate);
  MOZ_ASSERT(mNestedEventTarget);

  if (event) {
    workerRunnable = mWorkerPrivate->MaybeWrapAsWorkerRunnable(event.forget());
  }

  nsresult rv =
      mWorkerPrivate->Dispatch(workerRunnable.forget(), mNestedEventTarget);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

NS_IMETHODIMP
WorkerPrivate::EventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>,
                                            uint32_t)

{
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
WorkerPrivate::EventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

NS_IMETHODIMP
WorkerPrivate::EventTarget::UnregisterShutdownTask(
    nsITargetShutdownTask* aTask) {
  return NS_ERROR_NOT_IMPLEMENTED;
}

nsIEventTarget::FeatureFlags WorkerPrivate::EventTarget::GetFeatures() {
  return SUPPORTS_BASE;
}

NS_IMETHODIMP
WorkerPrivate::EventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) {
  // May be called on any thread!

  MOZ_ASSERT(aIsOnCurrentThread);

  MutexAutoLock lock(mMutex);

  if (mShutdown) {
    NS_WARNING(
        "A worker's event target was used after the worker has shutdown!");
    return NS_ERROR_UNEXPECTED;
  }

  MOZ_ASSERT(mNestedEventTarget);

  *aIsOnCurrentThread = mNestedEventTarget->IsOnCurrentThread();
  return NS_OK;
}

NS_IMETHODIMP_(bool)
WorkerPrivate::EventTarget::IsOnCurrentThreadInfallible() {
  // May be called on any thread!

  MutexAutoLock lock(mMutex);

  if (mShutdown) {
    NS_WARNING(
        "A worker's event target was used after the worker has shutdown!");
    return false;
  }

  MOZ_ASSERT(mNestedEventTarget);

  return mNestedEventTarget->IsOnCurrentThread();
}

WorkerPrivate::AutoPushEventLoopGlobal::AutoPushEventLoopGlobal(
    WorkerPrivate* aWorkerPrivate, JSContext* aCx) {
  auto data = aWorkerPrivate->mWorkerThreadAccessible.Access();
  mOldEventLoopGlobal = std::move(data->mCurrentEventLoopGlobal);
  if (JSObject* global = JS::CurrentGlobalOrNull(aCx)) {
    data->mCurrentEventLoopGlobal = xpc::NativeGlobal(global);
  }
#ifdef DEBUG
  mNewEventLoopGlobal = data->mCurrentEventLoopGlobal;
#endif
}

WorkerPrivate::AutoPushEventLoopGlobal::~AutoPushEventLoopGlobal() {
  WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
  // We are popping out the event loop global, WorkerPrivate is supposed to be
  // alive and in a valid status(Running or Canceling)
  MOZ_ASSERT(workerPrivate);
  auto data = workerPrivate->mWorkerThreadAccessible.Access();
#ifdef DEBUG
  // Saved event loop global should be matched.
  MOZ_ASSERT(data->mCurrentEventLoopGlobal == mNewEventLoopGlobal);
  mNewEventLoopGlobal = nullptr;
#endif
  data->mCurrentEventLoopGlobal = std::move(mOldEventLoopGlobal);
}

// FontVisibilityProvider implementation
FontVisibility WorkerPrivate::GetFontVisibility() const {
  return mFontVisibility;
}

void WorkerPrivate::ReportBlockedFontFamily(const nsCString& aMsg) const {
  MOZ_LOG(gFingerprinterDetection, mozilla::LogLevel::Info, ("%s", aMsg.get()));
  nsContentUtils::ReportToConsoleByWindowID(NS_ConvertUTF8toUTF16(aMsg),
                                            nsIScriptError::warningFlag,
                                            "Security"_ns, WindowID());
}

bool WorkerPrivate::IsChrome() const { return IsChromeWorker(); }

bool WorkerPrivate::IsPrivateBrowsing() const {
  return mLoadInfo.mOriginAttributes.IsPrivateBrowsing();
}

nsICookieJarSettings* WorkerPrivate::GetCookieJarSettings() const {
  return CookieJarSettings();
}

Maybe<FontVisibility> WorkerPrivate::MaybeInheritFontVisibility() const {
  if (mParent) {
    // If we have a parent, we inherit the parent's font visibility.
    return Some(mParent->GetFontVisibility());
  }

  dom::Document* doc = GetDocument();
  if (!doc) {
    return Nothing();
  }

  nsPresContext* presContext = doc->GetPresContext();
  NS_ENSURE_TRUE(presContext, Nothing());

  return Some(presContext->GetFontVisibility());
}

void WorkerPrivate::UserFontSetUpdated(gfxUserFontEntry*) {}

// -----------------------------------------------------------------------------
// AutoSyncLoopHolder

AutoSyncLoopHolder::AutoSyncLoopHolder(WorkerPrivate* aWorkerPrivate,
                                       WorkerStatus aFailStatus,
                                       const char* const aName)
    : mTarget(aWorkerPrivate->CreateNewSyncLoop(aFailStatus)),
      mIndex(aWorkerPrivate->mSyncLoopStack.Length() - 1) {
  aWorkerPrivate->AssertIsOnWorkerThread();
  LOGV(
      ("AutoSyncLoopHolder::AutoSyncLoopHolder [%p] creator: %s", this, aName));
  if (aFailStatus < Canceling) {
    mWorkerRef = StrongWorkerRef::Create(aWorkerPrivate, aName, [aName]() {
      // Do nothing with the shutdown callback here since we need to wait for
      // the underlying SyncLoop to complete by itself.
      LOGV(
          ("AutoSyncLoopHolder::AutoSyncLoopHolder Worker starts to shutdown "
           "with a AutoSyncLoopHolder(%s).",
           aName));
    });
  } else {
    LOGV(
        ("AutoSyncLoopHolder::AutoSyncLoopHolder [%p] Create "
         "AutoSyncLoopHolder(%s) while Worker is shutting down",
         this, aName));
    mWorkerRef = StrongWorkerRef::CreateForcibly(aWorkerPrivate, aName);
  }
  // mWorkerRef can be nullptr here.
}

AutoSyncLoopHolder::~AutoSyncLoopHolder() {
  if (mWorkerRef && mTarget) {
    mWorkerRef->Private()->AssertIsOnWorkerThread();
    mWorkerRef->Private()->StopSyncLoop(mTarget, NS_ERROR_FAILURE);
    mWorkerRef->Private()->DestroySyncLoop(mIndex);
  }
}

nsresult AutoSyncLoopHolder::Run() {
  if (mWorkerRef) {
    WorkerPrivate* workerPrivate = mWorkerRef->Private();
    MOZ_ASSERT(workerPrivate);

    workerPrivate->AssertIsOnWorkerThread();

    nsresult rv = workerPrivate->RunCurrentSyncLoop();

    // The sync loop is done, sync loop has already destroyed in the end of
    // WorkerPrivate::RunCurrentSyncLoop(). So, release mWorkerRef here to
    // avoid destroying sync loop again in the ~AutoSyncLoopHolder();
    mWorkerRef = nullptr;

    return rv;
  }
  return NS_OK;
}

nsISerialEventTarget* AutoSyncLoopHolder::GetSerialEventTarget() const {
  // This can be null if CreateNewSyncLoop() fails.
  return mTarget;
}

// -----------------------------------------------------------------------------
// WorkerParentRef
WorkerParentRef::WorkerParentRef(RefPtr<WorkerPrivate>& aWorkerPrivate)
    : mWorkerPrivate(aWorkerPrivate) {
  LOGV(("WorkerParentRef::WorkerParentRef [%p] aWorkerPrivate %p", this,
        aWorkerPrivate.get()));
  MOZ_ASSERT(mWorkerPrivate);
  mWorkerPrivate->AssertIsOnParentThread();
}

const RefPtr<WorkerPrivate>& WorkerParentRef::Private() const {
  if (mWorkerPrivate) {
    mWorkerPrivate->AssertIsOnParentThread();
  }
  return mWorkerPrivate;
}

void WorkerParentRef::DropWorkerPrivate() {
  LOGV(("WorkerParentRef::DropWorkerPrivate [%p]", this));
  if (mWorkerPrivate) {
    mWorkerPrivate->AssertIsOnParentThread();
    mWorkerPrivate = nullptr;
  }
}

WorkerParentRef::~WorkerParentRef() = default;

}  // namespace dom
}  // namespace mozilla
