forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			6168 lines
		
	
	
	
		
			193 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			6168 lines
		
	
	
	
		
			193 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
#include "WorkerPrivate.h"
 | 
						|
 | 
						|
#include <utility>
 | 
						|
 | 
						|
#include "js/CallAndConstruct.h"  // JS_CallFunctionValue
 | 
						|
#include "js/CompilationAndEvaluation.h"
 | 
						|
#include "js/ContextOptions.h"
 | 
						|
#include "js/Exception.h"
 | 
						|
#include "js/friend/ErrorMessages.h"  // JSMSG_OUT_OF_MEMORY
 | 
						|
#include "js/LocaleSensitive.h"
 | 
						|
#include "js/MemoryMetrics.h"
 | 
						|
#include "js/SourceText.h"
 | 
						|
#include "MessageEventRunnable.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/Result.h"
 | 
						|
#include "mozilla/ScopeExit.h"
 | 
						|
#include "mozilla/StaticPrefs_browser.h"
 | 
						|
#include "mozilla/StaticPrefs_dom.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/DocGroup.h"
 | 
						|
#include "mozilla/dom/Document.h"
 | 
						|
#include "mozilla/dom/DOMTypes.h"
 | 
						|
#include "mozilla/dom/Event.h"
 | 
						|
#include "mozilla/dom/Exceptions.h"
 | 
						|
#include "mozilla/dom/FunctionBinding.h"
 | 
						|
#include "mozilla/dom/IndexedDatabaseManager.h"
 | 
						|
#include "mozilla/dom/MessageEvent.h"
 | 
						|
#include "mozilla/dom/MessageEventBinding.h"
 | 
						|
#include "mozilla/dom/MessagePort.h"
 | 
						|
#include "mozilla/dom/MessagePortBinding.h"
 | 
						|
#include "mozilla/dom/nsCSPContext.h"
 | 
						|
#include "mozilla/dom/nsCSPUtils.h"
 | 
						|
#include "mozilla/dom/Performance.h"
 | 
						|
#include "mozilla/dom/PerformanceStorageWorker.h"
 | 
						|
#include "mozilla/dom/PromiseDebugging.h"
 | 
						|
#include "mozilla/dom/ReferrerInfo.h"
 | 
						|
#include "mozilla/dom/RemoteWorkerChild.h"
 | 
						|
#include "mozilla/dom/RemoteWorkerService.h"
 | 
						|
#include "mozilla/dom/RootedDictionary.h"
 | 
						|
#include "mozilla/dom/TimeoutHandler.h"
 | 
						|
#include "mozilla/dom/UseCounterMetrics.h"
 | 
						|
#include "mozilla/dom/WorkerBinding.h"
 | 
						|
#include "mozilla/dom/WorkerScope.h"
 | 
						|
#include "mozilla/dom/WorkerStatus.h"
 | 
						|
#include "mozilla/dom/WebTaskScheduler.h"
 | 
						|
#include "mozilla/dom/JSExecutionManager.h"
 | 
						|
#include "mozilla/dom/WindowContext.h"
 | 
						|
#include "mozilla/extensions/ExtensionBrowser.h"  // extensions::Create{AndDispatchInitWorkerContext,WorkerLoaded,WorkerDestroyed}Runnable
 | 
						|
#include "mozilla/extensions/WebExtensionPolicy.h"
 | 
						|
#include "mozilla/glean/GleanMetrics.h"
 | 
						|
#include "mozilla/ipc/BackgroundChild.h"
 | 
						|
#include "mozilla/ipc/PBackgroundChild.h"
 | 
						|
#include "mozilla/StorageAccess.h"
 | 
						|
#include "mozilla/StoragePrincipalHelper.h"
 | 
						|
#include "mozilla/Telemetry.h"
 | 
						|
#include "mozilla/ThreadEventQueue.h"
 | 
						|
#include "mozilla/ThreadSafety.h"
 | 
						|
#include "mozilla/ThrottledEventQueue.h"
 | 
						|
#include "nsCycleCollector.h"
 | 
						|
#include "nsGlobalWindowInner.h"
 | 
						|
#include "nsIDUtils.h"
 | 
						|
#include "nsNetUtil.h"
 | 
						|
#include "nsIFile.h"
 | 
						|
#include "nsIMemoryReporter.h"
 | 
						|
#include "nsIPermissionManager.h"
 | 
						|
#include "nsIProtocolHandler.h"
 | 
						|
#include "nsIScriptError.h"
 | 
						|
#include "nsIURI.h"
 | 
						|
#include "nsIURL.h"
 | 
						|
#include "nsIUUIDGenerator.h"
 | 
						|
#include "nsPrintfCString.h"
 | 
						|
#include "nsProxyRelease.h"
 | 
						|
#include "nsQueryObject.h"
 | 
						|
#include "nsRFPService.h"
 | 
						|
#include "nsSandboxFlags.h"
 | 
						|
#include "nsThreadUtils.h"
 | 
						|
#include "nsUTF8Utils.h"
 | 
						|
 | 
						|
#include "RuntimeService.h"
 | 
						|
#include "ScriptLoader.h"
 | 
						|
#include "mozilla/dom/ServiceWorkerEvents.h"
 | 
						|
#include "mozilla/dom/ServiceWorkerManager.h"
 | 
						|
#include "mozilla/net/CookieJarSettings.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 "nsContentSecurityManager.h"
 | 
						|
 | 
						|
#include "nsThreadManager.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
 | 
						|
 | 
						|
static mozilla::LazyLogModule sWorkerPrivateLog("WorkerPrivate");
 | 
						|
static mozilla::LazyLogModule sWorkerTimeoutsLog("WorkerTimeouts");
 | 
						|
 | 
						|
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 WorkerRunnable {
 | 
						|
  nsCOMPtr<nsIRunnable> mWrappedRunnable;
 | 
						|
 | 
						|
 public:
 | 
						|
  ExternalRunnableWrapper(WorkerPrivate* aWorkerPrivate,
 | 
						|
                          nsIRunnable* aWrappedRunnable)
 | 
						|
      : WorkerRunnable(aWorkerPrivate, WorkerThread),
 | 
						|
        mWrappedRunnable(aWrappedRunnable) {
 | 
						|
    MOZ_ASSERT(aWorkerPrivate);
 | 
						|
    MOZ_ASSERT(aWrappedRunnable);
 | 
						|
  }
 | 
						|
 | 
						|
  NS_INLINE_DECL_REFCOUNTING_INHERITED(ExternalRunnableWrapper, WorkerRunnable)
 | 
						|
 | 
						|
 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;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
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(aWorkerPrivate, WorkerThread),
 | 
						|
        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!");
 | 
						|
 | 
						|
    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);
 | 
						|
 | 
						|
    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(aWorkerPrivate, WorkerThread),
 | 
						|
        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();
 | 
						|
    }
 | 
						|
    WorkerRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class NotifyRunnable final : public WorkerControlRunnable {
 | 
						|
  WorkerStatus mStatus;
 | 
						|
 | 
						|
 public:
 | 
						|
  NotifyRunnable(WorkerPrivate* aWorkerPrivate, WorkerStatus aStatus)
 | 
						|
      : WorkerControlRunnable(aWorkerPrivate, WorkerThread), 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(aWorkerPrivate, WorkerThread) {}
 | 
						|
 | 
						|
 private:
 | 
						|
  virtual bool WorkerRun(JSContext* aCx,
 | 
						|
                         WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    return aWorkerPrivate->FreezeInternal();
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class ThawRunnable final : public WorkerControlRunnable {
 | 
						|
 public:
 | 
						|
  explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
 | 
						|
      : WorkerControlRunnable(aWorkerPrivate, WorkerThread) {}
 | 
						|
 | 
						|
 private:
 | 
						|
  virtual bool WorkerRun(JSContext* aCx,
 | 
						|
                         WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    return aWorkerPrivate->ThawInternal();
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class PropagateStorageAccessPermissionGrantedRunnable final
 | 
						|
    : public WorkerControlRunnable {
 | 
						|
 public:
 | 
						|
  explicit PropagateStorageAccessPermissionGrantedRunnable(
 | 
						|
      WorkerPrivate* aWorkerPrivate)
 | 
						|
      : WorkerControlRunnable(aWorkerPrivate, WorkerThread) {}
 | 
						|
 | 
						|
 private:
 | 
						|
  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    aWorkerPrivate->PropagateStorageAccessPermissionGrantedInternal();
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class ReportErrorToConsoleRunnable final : public WorkerRunnable {
 | 
						|
  const char* mMessage;
 | 
						|
  const nsTArray<nsString> mParams;
 | 
						|
 | 
						|
 public:
 | 
						|
  // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
 | 
						|
  static void Report(WorkerPrivate* aWorkerPrivate, const char* aMessage,
 | 
						|
                     const nsTArray<nsString>& aParams) {
 | 
						|
    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, aMessage, aParams);
 | 
						|
      runnable->Dispatch();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Log a warning to the console.
 | 
						|
    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
 | 
						|
                                    nullptr, nsContentUtils::eDOM_PROPERTIES,
 | 
						|
                                    aMessage, aParams);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate,
 | 
						|
                               const char* aMessage,
 | 
						|
                               const nsTArray<nsString>& aParams)
 | 
						|
      : WorkerRunnable(aWorkerPrivate, ParentThread),
 | 
						|
        mMessage(aMessage),
 | 
						|
        mParams(aParams.Clone()) {}
 | 
						|
 | 
						|
  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, mMessage, mParams);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class TimerRunnable final : public WorkerRunnable,
 | 
						|
                            public nsITimerCallback,
 | 
						|
                            public nsINamed {
 | 
						|
 public:
 | 
						|
  NS_DECL_ISUPPORTS_INHERITED
 | 
						|
 | 
						|
  explicit TimerRunnable(WorkerPrivate* aWorkerPrivate)
 | 
						|
      : WorkerRunnable(aWorkerPrivate, WorkerThread) {}
 | 
						|
 | 
						|
 private:
 | 
						|
  ~TimerRunnable() = default;
 | 
						|
 | 
						|
  virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    // Silence bad assertions.
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
 | 
						|
                            bool aDispatchResult) override {
 | 
						|
    // Silence bad assertions.
 | 
						|
  }
 | 
						|
 | 
						|
  // MOZ_CAN_RUN_SCRIPT_BOUNDARY until worker runnables are generally
 | 
						|
  // MOZ_CAN_RUN_SCRIPT.
 | 
						|
  MOZ_CAN_RUN_SCRIPT_BOUNDARY
 | 
						|
  virtual bool WorkerRun(JSContext* aCx,
 | 
						|
                         WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    return aWorkerPrivate->RunExpiredTimeouts(aCx);
 | 
						|
  }
 | 
						|
 | 
						|
  NS_IMETHOD
 | 
						|
  Notify(nsITimer* aTimer) override { return Run(); }
 | 
						|
 | 
						|
  NS_IMETHOD
 | 
						|
  GetName(nsACString& aName) override {
 | 
						|
    aName.AssignLiteral("TimerRunnable");
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
NS_IMPL_ISUPPORTS_INHERITED(TimerRunnable, WorkerRunnable, nsITimerCallback,
 | 
						|
                            nsINamed)
 | 
						|
 | 
						|
class DebuggerImmediateRunnable : public WorkerRunnable {
 | 
						|
  RefPtr<dom::Function> mHandler;
 | 
						|
 | 
						|
 public:
 | 
						|
  explicit DebuggerImmediateRunnable(WorkerPrivate* aWorkerPrivate,
 | 
						|
                                     dom::Function& aHandler)
 | 
						|
      : WorkerRunnable(aWorkerPrivate, WorkerThread), 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.
 | 
						|
  }
 | 
						|
 | 
						|
  virtual bool WorkerRun(JSContext* aCx,
 | 
						|
                         WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
 | 
						|
    JS::Rooted<JS::Value> callable(
 | 
						|
        aCx, JS::ObjectOrNullValue(mHandler->CallableOrNull()));
 | 
						|
    JS::HandleValueArray args = JS::HandleValueArray::empty();
 | 
						|
    JS::Rooted<JS::Value> rval(aCx);
 | 
						|
 | 
						|
    // WorkerRunnable::Run will report the exception if it happens.
 | 
						|
    return JS_CallFunctionValue(aCx, global, callable, args, &rval);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
// 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(aWorkerPrivate, WorkerThread),
 | 
						|
        mContextOptions(aContextOptions) {}
 | 
						|
 | 
						|
 private:
 | 
						|
  virtual bool WorkerRun(JSContext* aCx,
 | 
						|
                         WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    aWorkerPrivate->UpdateContextOptionsInternal(aCx, mContextOptions);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class UpdateLanguagesRunnable final : public WorkerRunnable {
 | 
						|
  nsTArray<nsString> mLanguages;
 | 
						|
 | 
						|
 public:
 | 
						|
  UpdateLanguagesRunnable(WorkerPrivate* aWorkerPrivate,
 | 
						|
                          const nsTArray<nsString>& aLanguages)
 | 
						|
      : WorkerRunnable(aWorkerPrivate), mLanguages(aLanguages.Clone()) {}
 | 
						|
 | 
						|
  virtual bool WorkerRun(JSContext* aCx,
 | 
						|
                         WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    aWorkerPrivate->UpdateLanguagesInternal(mLanguages);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class UpdateJSWorkerMemoryParameterRunnable final
 | 
						|
    : public WorkerControlRunnable {
 | 
						|
  Maybe<uint32_t> mValue;
 | 
						|
  JSGCParamKey mKey;
 | 
						|
 | 
						|
 public:
 | 
						|
  UpdateJSWorkerMemoryParameterRunnable(WorkerPrivate* aWorkerPrivate,
 | 
						|
                                        JSGCParamKey aKey,
 | 
						|
                                        Maybe<uint32_t> aValue)
 | 
						|
      : WorkerControlRunnable(aWorkerPrivate, WorkerThread),
 | 
						|
        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(aWorkerPrivate, WorkerThread),
 | 
						|
        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(aWorkerPrivate, WorkerThread), 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(aWorkerPrivate, WorkerThread),
 | 
						|
        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 : public WorkerControlRunnable {
 | 
						|
  bool mCollectChildren;
 | 
						|
 | 
						|
 public:
 | 
						|
  CycleCollectRunnable(WorkerPrivate* aWorkerPrivate, bool aCollectChildren)
 | 
						|
      : WorkerControlRunnable(aWorkerPrivate, WorkerThread),
 | 
						|
        mCollectChildren(aCollectChildren) {}
 | 
						|
 | 
						|
  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    aWorkerPrivate->CycleCollectInternal(mCollectChildren);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class OfflineStatusChangeRunnable : public WorkerRunnable {
 | 
						|
 public:
 | 
						|
  OfflineStatusChangeRunnable(WorkerPrivate* aWorkerPrivate, bool aIsOffline)
 | 
						|
      : WorkerRunnable(aWorkerPrivate), mIsOffline(aIsOffline) {}
 | 
						|
 | 
						|
  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    aWorkerPrivate->OfflineStatusChangeEventInternal(mIsOffline);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  bool mIsOffline;
 | 
						|
};
 | 
						|
 | 
						|
class MemoryPressureRunnable : public WorkerControlRunnable {
 | 
						|
 public:
 | 
						|
  explicit MemoryPressureRunnable(WorkerPrivate* aWorkerPrivate)
 | 
						|
      : WorkerControlRunnable(aWorkerPrivate, WorkerThread) {}
 | 
						|
 | 
						|
  bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
 | 
						|
    aWorkerPrivate->MemoryPressureInternal();
 | 
						|
    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 WorkerDebuggeeRunnable {
 | 
						|
 public:
 | 
						|
  explicit CancelingOnParentRunnable(WorkerPrivate* aWorkerPrivate)
 | 
						|
      : WorkerDebuggeeRunnable(aWorkerPrivate, ParentThread) {}
 | 
						|
 | 
						|
  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 WorkerControlRunnable {
 | 
						|
 public:
 | 
						|
  explicit CancelingWithTimeoutOnParentRunnable(WorkerPrivate* aWorkerPrivate)
 | 
						|
      : WorkerControlRunnable(aWorkerPrivate, ParentThread) {}
 | 
						|
 | 
						|
  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();
 | 
						|
 | 
						|
    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;
 | 
						|
};
 | 
						|
 | 
						|
struct WorkerPrivate::TimeoutInfo {
 | 
						|
  TimeoutInfo()
 | 
						|
      : mId(0),
 | 
						|
        mNestingLevel(0),
 | 
						|
        mReason(Timeout::Reason::eTimeoutOrInterval),
 | 
						|
        mIsInterval(false),
 | 
						|
        mCanceled(false),
 | 
						|
        mOnChromeWorker(false) {
 | 
						|
    MOZ_COUNT_CTOR(mozilla::dom::WorkerPrivate::TimeoutInfo);
 | 
						|
  }
 | 
						|
 | 
						|
  ~TimeoutInfo() { MOZ_COUNT_DTOR(mozilla::dom::WorkerPrivate::TimeoutInfo); }
 | 
						|
 | 
						|
  bool operator==(const TimeoutInfo& aOther) const {
 | 
						|
    return mTargetTime == aOther.mTargetTime;
 | 
						|
  }
 | 
						|
 | 
						|
  bool operator<(const TimeoutInfo& aOther) const {
 | 
						|
    return mTargetTime < aOther.mTargetTime;
 | 
						|
  }
 | 
						|
 | 
						|
  void AccumulateNestingLevel(const uint32_t& aBaseLevel) {
 | 
						|
    if (aBaseLevel < StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup()) {
 | 
						|
      mNestingLevel = aBaseLevel + 1;
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    mNestingLevel = StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup();
 | 
						|
  }
 | 
						|
 | 
						|
  void CalculateTargetTime() {
 | 
						|
    auto target = mInterval;
 | 
						|
    // Don't clamp timeout for chrome workers
 | 
						|
    if (mNestingLevel >=
 | 
						|
            StaticPrefs::dom_clamp_timeout_nesting_level_AtStartup() &&
 | 
						|
        !mOnChromeWorker) {
 | 
						|
      target = TimeDuration::Max(
 | 
						|
          mInterval,
 | 
						|
          TimeDuration::FromMilliseconds(StaticPrefs::dom_min_timeout_value()));
 | 
						|
    }
 | 
						|
    mTargetTime = TimeStamp::Now() + target;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<TimeoutHandler> mHandler;
 | 
						|
  mozilla::TimeStamp mTargetTime;
 | 
						|
  mozilla::TimeDuration mInterval;
 | 
						|
  int32_t mId;
 | 
						|
  uint32_t mNestingLevel;
 | 
						|
  Timeout::Reason mReason;
 | 
						|
  bool mIsInterval;
 | 
						|
  bool mCanceled;
 | 
						|
  bool mOnChromeWorker;
 | 
						|
};
 | 
						|
 | 
						|
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()) {
 | 
						|
    return NS_ERROR_UNEXPECTED;
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
WorkerPrivate::MemoryReporter::CollectReportsRunnable::CollectReportsRunnable(
 | 
						|
    WorkerPrivate* aWorkerPrivate, nsIHandleReportCallback* aHandleReport,
 | 
						|
    nsISupports* aHandlerData, bool aAnonymize, const nsACString& aPath)
 | 
						|
    : MainThreadWorkerControlRunnable(aWorkerPrivate),
 | 
						|
      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();
 | 
						|
  if (mLoadInfo.mWindow) {
 | 
						|
    return mLoadInfo.mWindow;
 | 
						|
  }
 | 
						|
  // if we don't have a document, we should query the document
 | 
						|
  // from the parent in case of a nested worker
 | 
						|
  WorkerPrivate* parent = mParent;
 | 
						|
  while (parent) {
 | 
						|
    if (parent->mLoadInfo.mWindow) {
 | 
						|
      return parent->mLoadInfo.mWindow;
 | 
						|
    }
 | 
						|
    parent = parent->GetParent();
 | 
						|
  }
 | 
						|
  // couldn't query a window, give up and return nullptr
 | 
						|
  return nullptr;
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::SetCsp(nsIContentSecurityPolicy* aCSP) {
 | 
						|
  AssertIsOnMainThread();
 | 
						|
  if (!aCSP) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  aCSP->EnsureEventTarget(mMainThreadEventTarget);
 | 
						|
 | 
						|
  mLoadInfo.mCSP = aCSP;
 | 
						|
  mLoadInfo.mCSPInfo = MakeUnique<CSPInfo>();
 | 
						|
  nsresult rv = CSPToCSPInfo(mLoadInfo.mCSP, mLoadInfo.mCSPInfo.get());
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
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,
 | 
						|
                                           u""_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;
 | 
						|
 | 
						|
  // Set evalAllowed, default value is set in GetAllowsEval
 | 
						|
  bool evalAllowed = false;
 | 
						|
  bool reportEvalViolations = false;
 | 
						|
  rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  mLoadInfo.mEvalAllowed = evalAllowed;
 | 
						|
  mLoadInfo.mReportEvalCSPViolations = reportEvalViolations;
 | 
						|
 | 
						|
  // Set wasmEvalAllowed
 | 
						|
  bool wasmEvalAllowed = false;
 | 
						|
  bool reportWasmEvalViolations = false;
 | 
						|
  rv = csp->GetAllowsWasmEval(&reportWasmEvalViolations, &wasmEvalAllowed);
 | 
						|
  NS_ENSURE_SUCCESS(rv, rv);
 | 
						|
 | 
						|
  // As for nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction,
 | 
						|
  // for MV2 extensions we have to allow wasm by default and report violations
 | 
						|
  // for historical reasons.
 | 
						|
  // TODO bug 1770909: remove this exception.
 | 
						|
  if (!wasmEvalAllowed && addonPolicy && addonPolicy->ManifestVersion() == 2) {
 | 
						|
    wasmEvalAllowed = true;
 | 
						|
    reportWasmEvalViolations = true;
 | 
						|
  }
 | 
						|
 | 
						|
  mLoadInfo.mWasmEvalAllowed = wasmEvalAllowed;
 | 
						|
  mLoadInfo.mReportWasmEvalCSPViolations = reportWasmEvalViolations;
 | 
						|
 | 
						|
  mLoadInfo.mCSPInfo = MakeUnique<CSPInfo>();
 | 
						|
  rv = CSPToCSPInfo(csp, mLoadInfo.mCSPInfo.get());
 | 
						|
  if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
    return rv;
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::StoreCSPOnClient() {
 | 
						|
  auto data = mWorkerThreadAccessible.Access();
 | 
						|
  MOZ_ASSERT(data->mScope);
 | 
						|
  if (mLoadInfo.mCSPInfo) {
 | 
						|
    data->mScope->MutableClientSourceRef().SetCspInfo(*mLoadInfo.mCSPInfo);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
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!
 | 
						|
  MutexAutoLock lock(mMutex);
 | 
						|
  return DispatchLockHeld(std::move(aRunnable), aSyncLoopTarget, lock);
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
  if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Canceling)) {
 | 
						|
    LOGV(("WorkerPrivate::DispatchLockHeld [%p] runnable %p, parent status: %u",
 | 
						|
          this, runnable.get(), (uint8_t)(ParentStatus())));
 | 
						|
    NS_WARNING(
 | 
						|
        "A runnable was posted to a worker that is already shutting "
 | 
						|
        "down!");
 | 
						|
    return NS_ERROR_UNEXPECTED;
 | 
						|
  }
 | 
						|
 | 
						|
  if (runnable->IsDebuggeeRunnable() && !mDebuggerReady) {
 | 
						|
    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()));
 | 
						|
      mPreStartRunnables.AppendElement(runnable);
 | 
						|
      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_NORMAL);
 | 
						|
  } else {
 | 
						|
    // 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))) {
 | 
						|
    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!");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
nsresult WorkerPrivate::DispatchControlRunnable(
 | 
						|
    already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable) {
 | 
						|
  // May be called on any thread!
 | 
						|
  RefPtr<WorkerControlRunnable> runnable(aWorkerControlRunnable);
 | 
						|
  MOZ_ASSERT(runnable);
 | 
						|
 | 
						|
  LOG(WorkerLog(), ("WorkerPrivate::DispatchControlRunnable [%p] runnable %p",
 | 
						|
                    this, runnable.get()));
 | 
						|
 | 
						|
  {
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
    if (mStatus == Dead) {
 | 
						|
      return NS_ERROR_UNEXPECTED;
 | 
						|
    }
 | 
						|
 | 
						|
    // Transfer ownership to the control queue.
 | 
						|
    mControlQueue.Push(runnable.forget().take());
 | 
						|
 | 
						|
    if (JSContext* cx = mJSContext) {
 | 
						|
      MOZ_ASSERT(mThread);
 | 
						|
      JS_RequestInterruptCallback(cx);
 | 
						|
    }
 | 
						|
 | 
						|
    mCondVar.Notify();
 | 
						|
  }
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult WorkerPrivate::DispatchDebuggerRunnable(
 | 
						|
    already_AddRefed<WorkerRunnable> aDebuggerRunnable) {
 | 
						|
  // May be called on any thread!
 | 
						|
 | 
						|
  RefPtr<WorkerRunnable> runnable(aDebuggerRunnable);
 | 
						|
 | 
						|
  MOZ_ASSERT(runnable);
 | 
						|
 | 
						|
  {
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
    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;
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
  }
 | 
						|
 | 
						|
  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;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<NotifyRunnable> runnable = new NotifyRunnable(this, aStatus);
 | 
						|
  return runnable->Dispatch();
 | 
						|
}
 | 
						|
 | 
						|
bool WorkerPrivate::Freeze(const nsPIDOMWindowInner* aWindow) {
 | 
						|
  AssertIsOnParentThread();
 | 
						|
 | 
						|
  mParentFrozen = true;
 | 
						|
 | 
						|
  // 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(true));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
    if (mParentStatus >= Canceling) {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  DisableDebugger();
 | 
						|
 | 
						|
  RefPtr<FreezeRunnable> runnable = new FreezeRunnable(this);
 | 
						|
  return runnable->Dispatch();
 | 
						|
}
 | 
						|
 | 
						|
bool WorkerPrivate::Thaw(const nsPIDOMWindowInner* aWindow) {
 | 
						|
  AssertIsOnParentThread();
 | 
						|
  MOZ_ASSERT(mParentFrozen);
 | 
						|
 | 
						|
  mParentFrozen = false;
 | 
						|
 | 
						|
  // 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.
 | 
						|
    Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(
 | 
						|
        IsParentWindowPaused());
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
    if (mParentStatus >= Canceling) {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  EnableDebugger();
 | 
						|
 | 
						|
  RefPtr<ThawRunnable> runnable = new ThawRunnable(this);
 | 
						|
  return runnable->Dispatch();
 | 
						|
}
 | 
						|
 | 
						|
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) {
 | 
						|
    // Pausing a ThrottledEventQueue is infallible.
 | 
						|
    MOZ_ALWAYS_SUCCEEDS(mMainThreadDebuggeeEventTarget->SetIsPaused(true));
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::ParentWindowResumed() {
 | 
						|
  AssertIsOnMainThread();
 | 
						|
 | 
						|
  MOZ_ASSERT(mParentWindowPaused);
 | 
						|
  mParentWindowPaused = false;
 | 
						|
 | 
						|
  {
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
    if (mParentStatus >= Canceling) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // 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.
 | 
						|
  Unused << mMainThreadDebuggeeEventTarget->SetIsPaused(IsFrozen());
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::PropagateStorageAccessPermissionGranted() {
 | 
						|
  AssertIsOnParentThread();
 | 
						|
 | 
						|
  {
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
    if (mParentStatus >= Canceling) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<PropagateStorageAccessPermissionGrantedRunnable> runnable =
 | 
						|
      new PropagateStorageAccessPermissionGrantedRunnable(this);
 | 
						|
  Unused << NS_WARN_IF(!runnable->Dispatch());
 | 
						|
}
 | 
						|
 | 
						|
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()) {
 | 
						|
    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()) {
 | 
						|
    NS_WARNING("Failed to update worker languages!");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
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()) {
 | 
						|
      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()) {
 | 
						|
    NS_WARNING("Failed to update worker gczeal!");
 | 
						|
  }
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
void WorkerPrivate::SetLowMemoryState(bool aState) {
 | 
						|
  AssertIsOnParentThread();
 | 
						|
 | 
						|
  RefPtr<SetLowMemoryStateRunnable> runnable =
 | 
						|
      new SetLowMemoryStateRunnable(this, aState);
 | 
						|
  if (!runnable->Dispatch()) {
 | 
						|
    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()) {
 | 
						|
    NS_WARNING("Failed to GC worker!");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::CycleCollect() {
 | 
						|
  AssertIsOnParentThread();
 | 
						|
 | 
						|
  RefPtr<CycleCollectRunnable> runnable =
 | 
						|
      new CycleCollectRunnable(this, /* aCollectChildren = */ true);
 | 
						|
  if (!runnable->Dispatch()) {
 | 
						|
    NS_WARNING("Failed to CC worker!");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::OfflineStatusChangeEvent(bool aIsOffline) {
 | 
						|
  AssertIsOnParentThread();
 | 
						|
 | 
						|
  RefPtr<OfflineStatusChangeRunnable> runnable =
 | 
						|
      new OfflineStatusChangeRunnable(this, aIsOffline);
 | 
						|
  if (!runnable->Dispatch()) {
 | 
						|
    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;
 | 
						|
  }
 | 
						|
 | 
						|
  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);
 | 
						|
  Unused << NS_WARN_IF(!runnable->Dispatch());
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
}
 | 
						|
 | 
						|
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),
 | 
						|
      mNextTimeoutId(1),
 | 
						|
      mCurrentTimerNestingLevel(0),
 | 
						|
      mFrozen(false),
 | 
						|
      mTimerRunning(false),
 | 
						|
      mRunningExpiredTimeouts(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)
 | 
						|
    : 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),
 | 
						|
      mJSContext(nullptr),
 | 
						|
      mPRThread(nullptr),
 | 
						|
      mWorkerControlEventTarget(new WorkerEventTarget(
 | 
						|
          this, WorkerEventTarget::Behavior::ControlOnly)),
 | 
						|
      mWorkerHybridEventTarget(
 | 
						|
          new WorkerEventTarget(this, WorkerEventTarget::Behavior::Hybrid)),
 | 
						|
      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),
 | 
						|
      mDebuggerReady(true),
 | 
						|
      mExtensionAPIAllowed(false),
 | 
						|
      mIsInAutomation(false),
 | 
						|
      mId(std::move(aId)),
 | 
						|
      mAgentClusterOpenerPolicy(aAgentClusterOpenerPolicy),
 | 
						|
      mIsPrivilegedAddonGlobal(false),
 | 
						|
      mTopLevelWorkerFinishedRunnableCount(0),
 | 
						|
      mWorkerFinishedRunnableCount(0) {
 | 
						|
  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);
 | 
						|
    }
 | 
						|
 | 
						|
    mIsPrivilegedAddonGlobal = aParent->mIsPrivilegedAddonGlobal;
 | 
						|
  } else {
 | 
						|
    AssertIsOnMainThread();
 | 
						|
 | 
						|
    RuntimeService::GetDefaultJSSettings(mJSSettings);
 | 
						|
 | 
						|
    {
 | 
						|
      JS::RealmOptions& chromeRealmOptions = mJSSettings.chromeRealmOptions;
 | 
						|
      JS::RealmOptions& contentRealmOptions = mJSSettings.contentRealmOptions;
 | 
						|
 | 
						|
      xpc::InitGlobalObjectOptions(
 | 
						|
          chromeRealmOptions, UsesSystemPrincipal(), mIsSecureContext,
 | 
						|
          ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
 | 
						|
          ShouldResistFingerprinting(RFPTarget::JSMathFdlibm),
 | 
						|
          ShouldResistFingerprinting(RFPTarget::JSLocale));
 | 
						|
      xpc::InitGlobalObjectOptions(
 | 
						|
          contentRealmOptions, UsesSystemPrincipal(), mIsSecureContext,
 | 
						|
          ShouldResistFingerprinting(RFPTarget::JSDateTimeUTC),
 | 
						|
          ShouldResistFingerprinting(RFPTarget::JSMathFdlibm),
 | 
						|
          ShouldResistFingerprinting(RFPTarget::JSLocale));
 | 
						|
 | 
						|
      // 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 && mLoadInfo.mWindow->IsSuspended()) {
 | 
						|
      ParentWindowPaused();
 | 
						|
    }
 | 
						|
 | 
						|
    if (mLoadInfo.mWindow && mLoadInfo.mWindow->IsFrozen()) {
 | 
						|
      Freeze(mLoadInfo.mWindow);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  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");
 | 
						|
  if (StaticPrefs::dom_worker_use_medium_high_event_queue()) {
 | 
						|
    mMainThreadEventTarget = ThrottledEventQueue::Create(
 | 
						|
        GetMainThreadSerialEventTarget(), "Worker queue",
 | 
						|
        nsIRunnablePriority::PRIORITY_MEDIUMHIGH);
 | 
						|
  } else {
 | 
						|
    mMainThreadEventTarget = mMainThreadEventTargetForMessaging;
 | 
						|
  }
 | 
						|
  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);
 | 
						|
 | 
						|
  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) {
 | 
						|
  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()};
 | 
						|
  }
 | 
						|
 | 
						|
  // 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) {
 | 
						|
  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();
 | 
						|
  }
 | 
						|
 | 
						|
  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);
 | 
						|
 | 
						|
  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));
 | 
						|
 | 
						|
  // 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);
 | 
						|
 | 
						|
  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->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()) {
 | 
						|
    aRv.Throw(NS_ERROR_UNEXPECTED);
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  return worker.forget();
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
 | 
						|
  if (aReady && mDebuggerRegistered) {
 | 
						|
    // 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.mStorageAccess = aParent->StorageAccess();
 | 
						|
    loadInfo.mUseRegularPrincipal = aParent->UseRegularPrincipal();
 | 
						|
    loadInfo.mUsingStorageAccess = aParent->UsingStorageAccess();
 | 
						|
    loadInfo.mCookieJarSettings = aParent->CookieJarSettings();
 | 
						|
    if (loadInfo.mCookieJarSettings) {
 | 
						|
      loadInfo.mCookieJarSettingsArgs = aParent->CookieJarSettingsArgs();
 | 
						|
    }
 | 
						|
    loadInfo.mOriginAttributes = aParent->GetOriginAttributes();
 | 
						|
    loadInfo.mServiceWorkersTestingInWindow =
 | 
						|
        aParent->ServiceWorkersTestingInWindow();
 | 
						|
    loadInfo.mIsThirdPartyContextToTopWindow =
 | 
						|
        aParent->IsThirdPartyContextToTopWindow();
 | 
						|
    loadInfo.mShouldResistFingerprinting = aParent->ShouldResistFingerprinting(
 | 
						|
        RFPTarget::IsAlwaysEnabledForPrecompute);
 | 
						|
    loadInfo.mOverriddenFingerprintingSettings =
 | 
						|
        aParent->GetOverriddenFingerprintingSettings();
 | 
						|
    loadInfo.mParentController = aParent->GlobalScope()->GetController();
 | 
						|
    loadInfo.mWatchedByDevTools = aParent->IsWatchedByDevTools();
 | 
						|
  } else {
 | 
						|
    AssertIsOnMainThread();
 | 
						|
 | 
						|
    // Make sure that the IndexedDatabaseManager is set up
 | 
						|
    Unused << NS_WARN_IF(!IndexedDatabaseManager::GetOrCreate());
 | 
						|
 | 
						|
    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();
 | 
						|
      loadInfo.mStorageAccess = StorageAllowedForWindow(globalWindow);
 | 
						|
      loadInfo.mUseRegularPrincipal = document->UseRegularPrincipal();
 | 
						|
      loadInfo.mUsingStorageAccess = document->UsingStorageAccess();
 | 
						|
      loadInfo.mShouldResistFingerprinting =
 | 
						|
          document->ShouldResistFingerprinting(
 | 
						|
              RFPTarget::IsAlwaysEnabledForPrecompute);
 | 
						|
      loadInfo.mOverriddenFingerprintingSettings =
 | 
						|
          document->GetOverriddenFingerprintingSettings();
 | 
						|
 | 
						|
      // 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.mIsThirdPartyContextToTopWindow =
 | 
						|
          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(aCx, &fileName)) {
 | 
						|
        // 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 =
 | 
						|
            do_CreateInstance("@mozilla.org/file/local;1", &rv);
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          return rv;
 | 
						|
        }
 | 
						|
 | 
						|
        rv = scriptFile->InitWithPath(NS_ConvertUTF8toUTF16(fileName.get()));
 | 
						|
        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.mIsThirdPartyContextToTopWindow = 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));
 | 
						|
  {
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
    mStatus = Dead;
 | 
						|
  }
 | 
						|
 | 
						|
  // After mStatus is set to Dead there can be no more
 | 
						|
  // WorkerControlRunnables so no need to lock here.
 | 
						|
  if (!mControlQueue.IsEmpty()) {
 | 
						|
    WorkerControlRunnable* runnable = nullptr;
 | 
						|
    while (mControlQueue.Pop(runnable)) {
 | 
						|
      runnable->Cancel();
 | 
						|
      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);
 | 
						|
 | 
						|
  ScheduleDeletion(WorkerPrivate::WorkerRan);
 | 
						|
}
 | 
						|
 | 
						|
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 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);
 | 
						|
 | 
						|
      LOGV(
 | 
						|
          ("WorkerPrivate::DoRunLoop [%p] mStatus %u before getting events"
 | 
						|
           " to run",
 | 
						|
           this, (uint8_t)mStatus));
 | 
						|
      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;
 | 
						|
    }
 | 
						|
 | 
						|
    // 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) {
 | 
						|
        // We are about to destroy worker, report all use counters.
 | 
						|
        ReportUseCounters();
 | 
						|
 | 
						|
        // Flush uncaught rejections immediately, without
 | 
						|
        // waiting for a next tick.
 | 
						|
        PromiseDebugging::FlushUncaughtRejections();
 | 
						|
 | 
						|
        ShutdownGCTimers();
 | 
						|
 | 
						|
        DisableMemoryReporter();
 | 
						|
 | 
						|
        {
 | 
						|
          MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
          mStatus = Dead;
 | 
						|
          mJSContext = 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));
 | 
						|
          WorkerControlRunnable* 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;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Status transitions to Closing/Canceling and there are no SyncLoops,
 | 
						|
    // set global start dying, disconnect EventTargetObjects and
 | 
						|
    // WebTaskScheduler.
 | 
						|
    if (currentStatus >= Closing &&
 | 
						|
        !data->mPerformedShutdownAfterLastContentTaskExecuted) {
 | 
						|
      data->mPerformedShutdownAfterLastContentTaskExecuted.Flip();
 | 
						|
      if (data->mScope) {
 | 
						|
        data->mScope->NoteTerminating();
 | 
						|
        data->mScope->DisconnectGlobalTeardownObservers();
 | 
						|
        if (data->mScope->GetExistingScheduler()) {
 | 
						|
          data->mScope->GetExistingScheduler()->Disconnect();
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (debuggerRunnablesPending || normalRunnablesPending) {
 | 
						|
      // Start the periodic GC timer if it is not already running.
 | 
						|
      SetGCTimerMode(PeriodicTimer);
 | 
						|
    }
 | 
						|
 | 
						|
    if (debuggerRunnablesPending) {
 | 
						|
      WorkerRunnable* runnable = nullptr;
 | 
						|
 | 
						|
      {
 | 
						|
        MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
        mDebuggerQueue.Pop(runnable);
 | 
						|
        debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
 | 
						|
      }
 | 
						|
 | 
						|
      MOZ_ASSERT(runnable);
 | 
						|
      static_cast<nsIRunnable*>(runnable)->Run();
 | 
						|
      runnable->Release();
 | 
						|
 | 
						|
      CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get();
 | 
						|
      ccjs->PerformDebuggerMicroTaskCheckpoint();
 | 
						|
 | 
						|
      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) {
 | 
						|
    Unused << 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,
 | 
						|
                                                         uint32_t aFlags) {
 | 
						|
  nsCOMPtr<nsIRunnable> r = aRunnable;
 | 
						|
  return DispatchToMainThreadForMessaging(r.forget(), aFlags);
 | 
						|
}
 | 
						|
 | 
						|
nsresult WorkerPrivate::DispatchToMainThreadForMessaging(
 | 
						|
    already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) {
 | 
						|
  return mMainThreadEventTargetForMessaging->Dispatch(std::move(aRunnable),
 | 
						|
                                                      aFlags);
 | 
						|
}
 | 
						|
 | 
						|
nsISerialEventTarget* WorkerPrivate::MainThreadEventTarget() {
 | 
						|
  return mMainThreadEventTarget;
 | 
						|
}
 | 
						|
 | 
						|
nsresult WorkerPrivate::DispatchToMainThread(nsIRunnable* aRunnable,
 | 
						|
                                             uint32_t aFlags) {
 | 
						|
  nsCOMPtr<nsIRunnable> r = aRunnable;
 | 
						|
  return DispatchToMainThread(r.forget(), aFlags);
 | 
						|
}
 | 
						|
 | 
						|
nsresult WorkerPrivate::DispatchToMainThread(
 | 
						|
    already_AddRefed<nsIRunnable> aRunnable, uint32_t aFlags) {
 | 
						|
  return mMainThreadEventTarget->Dispatch(std::move(aRunnable), aFlags);
 | 
						|
}
 | 
						|
 | 
						|
nsresult WorkerPrivate::DispatchDebuggeeToMainThread(
 | 
						|
    already_AddRefed<WorkerDebuggeeRunnable> aRunnable, uint32_t aFlags) {
 | 
						|
  return mMainThreadDebuggeeEventTarget->Dispatch(std::move(aRunnable), 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");
 | 
						|
 | 
						|
  auto 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"));
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
  const char* name = nullptr;
 | 
						|
  nsITimer* timer = nullptr;
 | 
						|
 | 
						|
  if (aMode == PeriodicTimer) {
 | 
						|
    delay = PERIODIC_GC_TIMER_DELAY_SEC * 1000;
 | 
						|
    type = nsITimer::TYPE_REPEATING_SLACK;
 | 
						|
    callback = PeriodicGCTimerCallback;
 | 
						|
    name = "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 = "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::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);
 | 
						|
 | 
						|
  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);
 | 
						|
    MOZ_ASSERT(!NS_HasPendingEvents(currentThread));
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  if (WorkerPrivate* parent = GetParent()) {
 | 
						|
    RefPtr<WorkerFinishedRunnable> runnable =
 | 
						|
        new WorkerFinishedRunnable(parent, this);
 | 
						|
    if (!runnable->Dispatch()) {
 | 
						|
      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 (;;) {
 | 
						|
    WorkerControlRunnable* event;
 | 
						|
    if (!mControlQueue.Pop(event)) {
 | 
						|
      break;
 | 
						|
    }
 | 
						|
 | 
						|
    MutexAutoUnlock unlock(mMutex);
 | 
						|
 | 
						|
    MOZ_ASSERT(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<WorkerRunnable>> 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::ClearDebuggerEventQueue() {
 | 
						|
  while (!mDebuggerQueue.IsEmpty()) {
 | 
						|
    WorkerRunnable* runnable = nullptr;
 | 
						|
    mDebuggerQueue.Pop(runnable);
 | 
						|
    // It should be ok to simply release the runnable, without running it.
 | 
						|
    runnable->Release();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool WorkerPrivate::ThawInternal() {
 | 
						|
  auto data = mWorkerThreadAccessible.Access();
 | 
						|
  NS_ASSERTION(data->mFrozen, "Not yet frozen!");
 | 
						|
 | 
						|
  for (uint32_t index = 0; index < data->mChildWorkers.Length(); index++) {
 | 
						|
    data->mChildWorkers[index]->Thaw(nullptr);
 | 
						|
  }
 | 
						|
 | 
						|
  data->mFrozen = false;
 | 
						|
 | 
						|
  // 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;
 | 
						|
}
 | 
						|
 | 
						|
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();
 | 
						|
  for (uint32_t i = 0; i < data->mTimeouts.Length(); ++i) {
 | 
						|
    // TODO(erahm): No idea what's going on here.
 | 
						|
    TimeoutInfo* tmp = data->mTimeouts[i].get();
 | 
						|
    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHandler)
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::UnlinkTimeouts() {
 | 
						|
  auto data = mWorkerThreadAccessible.Access();
 | 
						|
  data->mTimeouts.Clear();
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(!mShutdownTasks.Contains(aTask));
 | 
						|
  mShutdownTasks.AppendElement(aTask);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
nsresult WorkerPrivate::UnregisterShutdownTask(nsITargetShutdownTask* aTask) {
 | 
						|
  NS_ENSURE_ARG(aTask);
 | 
						|
 | 
						|
  MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
  // We've already started running shutdown tasks, so can't unregister them
 | 
						|
  // anymore.
 | 
						|
  if (mShutdownTasksRun) {
 | 
						|
    return NS_ERROR_UNEXPECTED;
 | 
						|
  }
 | 
						|
 | 
						|
  return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED;
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::RunShutdownTasks() {
 | 
						|
  nsTArray<nsCOMPtr<nsITargetShutdownTask>> shutdownTasks;
 | 
						|
 | 
						|
  {
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
    shutdownTasks = std::move(mShutdownTasks);
 | 
						|
    mShutdownTasks.Clear();
 | 
						|
    mShutdownTasksRun = true;
 | 
						|
  }
 | 
						|
 | 
						|
  for (auto& task : shutdownTasks) {
 | 
						|
    task->TargetShutdown();
 | 
						|
  }
 | 
						|
  mWorkerHybridEventTarget->ForgetWorkerPrivate(this);
 | 
						|
}
 | 
						|
 | 
						|
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) {
 | 
						|
  LOGV(("WorkerPrivate::UpdateCCFlag [%p]", this));
 | 
						|
  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: {
 | 
						|
      MOZ_ASSERT(data->mTimeouts.IsEmpty());
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    case CCFlag::IneligibleForTimeout: {
 | 
						|
      MOZ_ASSERT(!data->mTimeouts.IsEmpty());
 | 
						|
      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;
 | 
						|
  };
 | 
						|
 | 
						|
  bool eligibleForCC = data->mChildWorkers.IsEmpty() &&
 | 
						|
                       data->mTimeouts.IsEmpty() &&
 | 
						|
                       !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();
 | 
						|
 | 
						|
  LOG(TimeoutsLog(), ("Worker %p CancelAllTimeouts.\n", this));
 | 
						|
 | 
						|
  if (data->mTimerRunning) {
 | 
						|
    NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Huh?!");
 | 
						|
    NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Huh?!");
 | 
						|
 | 
						|
    if (NS_FAILED(data->mTimer->Cancel())) {
 | 
						|
      NS_WARNING("Failed to cancel timer!");
 | 
						|
    }
 | 
						|
 | 
						|
    for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
 | 
						|
      data->mTimeouts[index]->mCanceled = true;
 | 
						|
    }
 | 
						|
 | 
						|
    // If mRunningExpiredTimeouts, then the fact that they are all canceled now
 | 
						|
    // means that the currently executing RunExpiredTimeouts will deal with
 | 
						|
    // them.  Otherwise, we need to clean them up ourselves.
 | 
						|
    if (!data->mRunningExpiredTimeouts) {
 | 
						|
      data->mTimeouts.Clear();
 | 
						|
      UpdateCCFlag(CCFlag::EligibleForTimeout);
 | 
						|
    }
 | 
						|
 | 
						|
    // Set mTimerRunning false even if mRunningExpiredTimeouts is true, so that
 | 
						|
    // if we get reentered under this same RunExpiredTimeouts call we don't
 | 
						|
    // assert above that !mTimeouts().IsEmpty(), because that's clearly false
 | 
						|
    // now.
 | 
						|
    data->mTimerRunning = false;
 | 
						|
  }
 | 
						|
#ifdef DEBUG
 | 
						|
  else if (!data->mRunningExpiredTimeouts) {
 | 
						|
    NS_ASSERTION(data->mTimeouts.IsEmpty(), "Huh?!");
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  data->mTimer = nullptr;
 | 
						|
  data->mTimerRunnable = nullptr;
 | 
						|
}
 | 
						|
 | 
						|
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();
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::ReportUseCounters() {
 | 
						|
  AssertIsOnWorkerThread();
 | 
						|
 | 
						|
  if (mReportedUseCounters) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  mReportedUseCounters = true;
 | 
						|
 | 
						|
  if (Telemetry::HistogramUseCounterWorkerCount <= 0 || IsChromeWorker()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  const size_t kind = Kind();
 | 
						|
  switch (kind) {
 | 
						|
    case WorkerKindDedicated:
 | 
						|
      Telemetry::Accumulate(Telemetry::DEDICATED_WORKER_DESTROYED, 1);
 | 
						|
      glean::use_counter::dedicated_workers_destroyed.Add();
 | 
						|
      break;
 | 
						|
    case WorkerKindShared:
 | 
						|
      Telemetry::Accumulate(Telemetry::SHARED_WORKER_DESTROYED, 1);
 | 
						|
      glean::use_counter::shared_workers_destroyed.Add();
 | 
						|
      break;
 | 
						|
    case WorkerKindService:
 | 
						|
      Telemetry::Accumulate(Telemetry::SERVICE_WORKER_DESTROYED, 1);
 | 
						|
      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));
 | 
						|
  }
 | 
						|
 | 
						|
  static_assert(
 | 
						|
      static_cast<size_t>(UseCounterWorker::Count) * 3 ==
 | 
						|
          static_cast<size_t>(Telemetry::HistogramUseCounterWorkerCount),
 | 
						|
      "There should be three histograms (dedicated and shared and "
 | 
						|
      "servie) for each worker use counter");
 | 
						|
  const size_t count = static_cast<size_t>(UseCounterWorker::Count);
 | 
						|
  const size_t factor =
 | 
						|
      static_cast<size_t>(Telemetry::HistogramUseCounterWorkerCount) / count;
 | 
						|
  MOZ_ASSERT(factor > kind);
 | 
						|
 | 
						|
  const auto workerKind = Kind();
 | 
						|
  for (size_t c = 0; c < count; ++c) {
 | 
						|
    // Histograms for worker use counters use the same order as the worker kinds
 | 
						|
    // , so we can use the worker kind to index to corresponding histogram.
 | 
						|
    auto id = static_cast<Telemetry::HistogramID>(
 | 
						|
        Telemetry::HistogramFirstUseCounterWorker + c * factor + kind);
 | 
						|
    MOZ_ASSERT(id <= Telemetry::HistogramLastUseCounterWorker);
 | 
						|
 | 
						|
    if (!GetUseCounter(static_cast<UseCounterWorker>(c))) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    if (dumpCounters) {
 | 
						|
      printf_stderr("USE_COUNTER_WORKER: %s - %s\n",
 | 
						|
                    Telemetry::GetHistogramName(id),
 | 
						|
                    workerPathForLogging->get());
 | 
						|
    }
 | 
						|
    Telemetry::Accumulate(id, 1);
 | 
						|
    IncrementWorkerUseCounter(static_cast<UseCounterWorker>(c), workerKind);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
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<MessageEventRunnable> runnable =
 | 
						|
      new MessageEventRunnable(this, WorkerRunnable::ParentThread);
 | 
						|
 | 
						|
  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()) {
 | 
						|
    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);
 | 
						|
 | 
						|
      std::deque<RefPtr<MicroTaskRunnable>>& debuggerMtQueue =
 | 
						|
          ccjscx->GetDebuggerMicroTaskQueue();
 | 
						|
      while (mControlQueue.IsEmpty() &&
 | 
						|
             !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
 | 
						|
             debuggerMtQueue.empty()) {
 | 
						|
        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);
 | 
						|
 | 
						|
      WorkerRunnable* runnable = nullptr;
 | 
						|
 | 
						|
      {
 | 
						|
        MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
        mDebuggerQueue.Pop(runnable);
 | 
						|
      }
 | 
						|
 | 
						|
      MOZ_ASSERT(runnable);
 | 
						|
      static_cast<nsIRunnable*>(runnable)->Run();
 | 
						|
      runnable->Release();
 | 
						|
 | 
						|
      ccjscx->PerformDebuggerMicroTaskCheckpoint();
 | 
						|
 | 
						|
      // 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) {
 | 
						|
  mDebugger->PostMessageToDebugger(aMessage);
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::SetDebuggerImmediate(dom::Function& aHandler,
 | 
						|
                                         ErrorResult& aRv) {
 | 
						|
  AssertIsOnWorkerThread();
 | 
						|
 | 
						|
  RefPtr<DebuggerImmediateRunnable> runnable =
 | 
						|
      new DebuggerImmediateRunnable(this, aHandler);
 | 
						|
  if (!runnable->Dispatch()) {
 | 
						|
    aRv.Throw(NS_ERROR_FAILURE);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::ReportErrorToDebugger(const nsAString& aFilename,
 | 
						|
                                          uint32_t aLineno,
 | 
						|
                                          const nsAString& aMessage) {
 | 
						|
  mDebugger->ReportErrorToDebugger(aFilename, aLineno, aMessage);
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
    if (aStatus >= Canceling) {
 | 
						|
      MutexAutoUnlock unlock(mMutex);
 | 
						|
      if (data->mScope) {
 | 
						|
        if (aStatus == Canceling) {
 | 
						|
          data->mScope->NoteTerminating();
 | 
						|
        } else {
 | 
						|
          data->mScope->NoteShuttingDown();
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    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 = Killing;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (aStatus >= Closing) {
 | 
						|
    CancelAllTimeouts();
 | 
						|
  }
 | 
						|
 | 
						|
  if (aStatus == Closing && GlobalScope()) {
 | 
						|
    GlobalScope()->SetIsNotEligibleForMessaging();
 | 
						|
  }
 | 
						|
 | 
						|
  // Let all our holders know the new status.
 | 
						|
  if (aStatus == Canceling) {
 | 
						|
    NotifyWorkerRefs(aStatus);
 | 
						|
  }
 | 
						|
 | 
						|
  // If the worker script never ran, or failed to compile, we don't need to do
 | 
						|
  // anything else.
 | 
						|
  WorkerGlobalScope* global = GlobalScope();
 | 
						|
  if (!global) {
 | 
						|
    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);
 | 
						|
  if (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);
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    // ReportError is also used for reporting warnings,
 | 
						|
    // so there won't be a pending exception.
 | 
						|
    MOZ_ASSERT(aReport && aReport->isWarning());
 | 
						|
  }
 | 
						|
 | 
						|
  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(const char* aMessage) {
 | 
						|
  nsTArray<nsString> emptyParams;
 | 
						|
  WorkerPrivate::ReportErrorToConsole(aMessage, emptyParams);
 | 
						|
}
 | 
						|
 | 
						|
// static
 | 
						|
void WorkerPrivate::ReportErrorToConsole(const char* aMessage,
 | 
						|
                                         const nsTArray<nsString>& aParams) {
 | 
						|
  WorkerPrivate* wp = nullptr;
 | 
						|
  if (!NS_IsMainThread()) {
 | 
						|
    wp = GetCurrentThreadWorkerPrivate();
 | 
						|
  }
 | 
						|
 | 
						|
  ReportErrorToConsoleRunnable::Report(wp, aMessage, aParams);
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
  // Reasons that doesn't support cancellation will get -1 as their ids.
 | 
						|
  int32_t timerId = -1;
 | 
						|
  if (aReason == Timeout::Reason::eTimeoutOrInterval) {
 | 
						|
    timerId = data->mNextTimeoutId;
 | 
						|
    data->mNextTimeoutId += 1;
 | 
						|
  }
 | 
						|
 | 
						|
  WorkerStatus currentStatus;
 | 
						|
  {
 | 
						|
    MutexAutoLock lock(mMutex);
 | 
						|
    currentStatus = mStatus;
 | 
						|
  }
 | 
						|
 | 
						|
  // If the worker is trying to call setTimeout/setInterval and the parent
 | 
						|
  // thread has initiated the close process then just silently fail.
 | 
						|
  if (currentStatus >= Closing) {
 | 
						|
    return timerId;
 | 
						|
  }
 | 
						|
 | 
						|
  auto newInfo = MakeUnique<TimeoutInfo>();
 | 
						|
  newInfo->mReason = aReason;
 | 
						|
  newInfo->mOnChromeWorker = mIsChromeWorker;
 | 
						|
  newInfo->mIsInterval = aIsInterval;
 | 
						|
  newInfo->mId = timerId;
 | 
						|
  if (newInfo->mReason == Timeout::Reason::eTimeoutOrInterval ||
 | 
						|
      newInfo->mReason == Timeout::Reason::eIdleCallbackTimeout) {
 | 
						|
    newInfo->AccumulateNestingLevel(data->mCurrentTimerNestingLevel);
 | 
						|
  }
 | 
						|
 | 
						|
  if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
 | 
						|
    NS_WARNING("Timeout ids overflowed!");
 | 
						|
    if (aReason == Timeout::Reason::eTimeoutOrInterval) {
 | 
						|
      data->mNextTimeoutId = 1;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  newInfo->mHandler = aHandler;
 | 
						|
 | 
						|
  // See if any of the optional arguments were passed.
 | 
						|
  aTimeout = std::max(0, aTimeout);
 | 
						|
  newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout);
 | 
						|
  newInfo->CalculateTargetTime();
 | 
						|
 | 
						|
  const auto& insertedInfo = data->mTimeouts.InsertElementSorted(
 | 
						|
      std::move(newInfo), GetUniquePtrComparator(data->mTimeouts));
 | 
						|
 | 
						|
  LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%s\n", this,
 | 
						|
                      aTimeout, aIsInterval ? "yes" : "no"));
 | 
						|
 | 
						|
  // If the timeout we just made is set to fire next then we need to update the
 | 
						|
  // timer, unless we're currently running timeouts.
 | 
						|
  if (insertedInfo == data->mTimeouts.Elements() &&
 | 
						|
      !data->mRunningExpiredTimeouts) {
 | 
						|
    if (!data->mTimer) {
 | 
						|
      data->mTimer = NS_NewTimer(GlobalScope()->SerialEventTarget());
 | 
						|
      if (!data->mTimer) {
 | 
						|
        aRv.Throw(NS_ERROR_UNEXPECTED);
 | 
						|
        return 0;
 | 
						|
      }
 | 
						|
 | 
						|
      data->mTimerRunnable = new TimerRunnable(this);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!data->mTimerRunning) {
 | 
						|
      UpdateCCFlag(CCFlag::IneligibleForTimeout);
 | 
						|
      data->mTimerRunning = true;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!RescheduleTimeoutTimer(aCx)) {
 | 
						|
      aRv.Throw(NS_ERROR_FAILURE);
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return timerId;
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::ClearTimeout(int32_t aId, Timeout::Reason aReason) {
 | 
						|
  MOZ_ASSERT(aReason == Timeout::Reason::eTimeoutOrInterval,
 | 
						|
             "This timeout reason doesn't support cancellation.");
 | 
						|
 | 
						|
  auto data = mWorkerThreadAccessible.Access();
 | 
						|
 | 
						|
  if (!data->mTimeouts.IsEmpty()) {
 | 
						|
    NS_ASSERTION(data->mTimerRunning, "Huh?!");
 | 
						|
 | 
						|
    for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
 | 
						|
      const auto& info = data->mTimeouts[index];
 | 
						|
      if (info->mId == aId && info->mReason == aReason) {
 | 
						|
        info->mCanceled = true;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool WorkerPrivate::RunExpiredTimeouts(JSContext* aCx) {
 | 
						|
  auto data = mWorkerThreadAccessible.Access();
 | 
						|
 | 
						|
  // We may be called recursively (e.g. close() inside a timeout) or we could
 | 
						|
  // have been canceled while this event was pending, bail out if there is
 | 
						|
  // nothing to do.
 | 
						|
  if (data->mRunningExpiredTimeouts || !data->mTimerRunning) {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Must have a timer!");
 | 
						|
  NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Should have some work to do!");
 | 
						|
 | 
						|
  bool retval = true;
 | 
						|
 | 
						|
  auto comparator = GetUniquePtrComparator(data->mTimeouts);
 | 
						|
  JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
 | 
						|
 | 
						|
  // We want to make sure to run *something*, even if the timer fired a little
 | 
						|
  // early. Fudge the value of now to at least include the first timeout.
 | 
						|
  const TimeStamp actual_now = TimeStamp::Now();
 | 
						|
  const TimeStamp now = std::max(actual_now, data->mTimeouts[0]->mTargetTime);
 | 
						|
 | 
						|
  if (now != actual_now) {
 | 
						|
    LOG(TimeoutsLog(), ("Worker %p fudged timeout by %f ms.\n", this,
 | 
						|
                        (now - actual_now).ToMilliseconds()));
 | 
						|
#ifdef DEBUG
 | 
						|
    double microseconds = (now - actual_now).ToMicroseconds();
 | 
						|
    uint32_t allowedEarlyFiringMicroseconds;
 | 
						|
    data->mTimer->GetAllowedEarlyFiringMicroseconds(
 | 
						|
        &allowedEarlyFiringMicroseconds);
 | 
						|
    MOZ_ASSERT(microseconds < allowedEarlyFiringMicroseconds);
 | 
						|
#endif
 | 
						|
  }
 | 
						|
 | 
						|
  AutoTArray<TimeoutInfo*, 10> expiredTimeouts;
 | 
						|
  for (uint32_t index = 0; index < data->mTimeouts.Length(); index++) {
 | 
						|
    TimeoutInfo* info = data->mTimeouts[index].get();
 | 
						|
    if (info->mTargetTime > now) {
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    expiredTimeouts.AppendElement(info);
 | 
						|
  }
 | 
						|
 | 
						|
  // Guard against recursion.
 | 
						|
  data->mRunningExpiredTimeouts = true;
 | 
						|
 | 
						|
  MOZ_DIAGNOSTIC_ASSERT(data->mCurrentTimerNestingLevel == 0);
 | 
						|
 | 
						|
  // Run expired timeouts.
 | 
						|
  for (uint32_t index = 0; index < expiredTimeouts.Length(); index++) {
 | 
						|
    TimeoutInfo*& info = expiredTimeouts[index];
 | 
						|
    AutoRestore<uint32_t> nestingLevel(data->mCurrentTimerNestingLevel);
 | 
						|
 | 
						|
    if (info->mCanceled) {
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Set current timer nesting level to current running timer handler's
 | 
						|
    // nesting level
 | 
						|
    data->mCurrentTimerNestingLevel = info->mNestingLevel;
 | 
						|
 | 
						|
    LOG(TimeoutsLog(),
 | 
						|
        ("Worker %p executing timeout with original delay %f ms.\n", this,
 | 
						|
         info->mInterval.ToMilliseconds()));
 | 
						|
 | 
						|
    // Always check JS_IsExceptionPending if something fails, and if
 | 
						|
    // JS_IsExceptionPending returns false (i.e. uncatchable exception) then
 | 
						|
    // break out of the loop.
 | 
						|
 | 
						|
    RefPtr<TimeoutHandler> handler(info->mHandler);
 | 
						|
 | 
						|
    const char* reason;
 | 
						|
    switch (info->mReason) {
 | 
						|
      case Timeout::Reason::eTimeoutOrInterval:
 | 
						|
        if (info->mIsInterval) {
 | 
						|
          reason = "setInterval handler";
 | 
						|
        } else {
 | 
						|
          reason = "setTimeout handler";
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case Timeout::Reason::eDelayedWebTaskTimeout:
 | 
						|
        reason = "delayedWebTask handler";
 | 
						|
        break;
 | 
						|
      default:
 | 
						|
        MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
 | 
						|
        reason = "AbortSignal Timeout";
 | 
						|
    }
 | 
						|
    if (info->mReason == Timeout::Reason::eTimeoutOrInterval ||
 | 
						|
        info->mReason == Timeout::Reason::eDelayedWebTaskTimeout) {
 | 
						|
      RefPtr<WorkerGlobalScope> scope(this->GlobalScope());
 | 
						|
      CallbackDebuggerNotificationGuard guard(
 | 
						|
          scope, info->mIsInterval
 | 
						|
                     ? DebuggerNotificationType::SetIntervalCallback
 | 
						|
                     : DebuggerNotificationType::SetTimeoutCallback);
 | 
						|
 | 
						|
      if (!handler->Call(reason)) {
 | 
						|
        retval = false;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      MOZ_ASSERT(info->mReason == Timeout::Reason::eAbortSignalTimeout);
 | 
						|
      MOZ_ALWAYS_TRUE(handler->Call(reason));
 | 
						|
    }
 | 
						|
 | 
						|
    NS_ASSERTION(data->mRunningExpiredTimeouts, "Someone changed this!");
 | 
						|
  }
 | 
						|
 | 
						|
  // No longer possible to be called recursively.
 | 
						|
  data->mRunningExpiredTimeouts = false;
 | 
						|
 | 
						|
  // Now remove canceled and expired timeouts from the main list.
 | 
						|
  // NB: The timeouts present in expiredTimeouts must have the same order
 | 
						|
  // with respect to each other in mTimeouts.  That is, mTimeouts is just
 | 
						|
  // expiredTimeouts with extra elements inserted.  There may be unexpired
 | 
						|
  // timeouts that have been inserted between the expired timeouts if the
 | 
						|
  // timeout event handler called setTimeout/setInterval.
 | 
						|
  for (uint32_t index = 0, expiredTimeoutIndex = 0,
 | 
						|
                expiredTimeoutLength = expiredTimeouts.Length();
 | 
						|
       index < data->mTimeouts.Length();) {
 | 
						|
    const auto& info = data->mTimeouts[index];
 | 
						|
    if ((expiredTimeoutIndex < expiredTimeoutLength &&
 | 
						|
         info == expiredTimeouts[expiredTimeoutIndex] &&
 | 
						|
         ++expiredTimeoutIndex) ||
 | 
						|
        info->mCanceled) {
 | 
						|
      if (info->mIsInterval && !info->mCanceled) {
 | 
						|
        // Reschedule intervals.
 | 
						|
        // Reschedule a timeout, if needed, increase the nesting level.
 | 
						|
        info->AccumulateNestingLevel(info->mNestingLevel);
 | 
						|
        info->CalculateTargetTime();
 | 
						|
        // Don't resort the list here, we'll do that at the end.
 | 
						|
        ++index;
 | 
						|
      } else {
 | 
						|
        data->mTimeouts.RemoveElement(info);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      // If info did not match the current entry in expiredTimeouts, it
 | 
						|
      // shouldn't be there at all.
 | 
						|
      NS_ASSERTION(!expiredTimeouts.Contains(info),
 | 
						|
                   "Our timeouts are out of order!");
 | 
						|
      ++index;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  data->mTimeouts.Sort(comparator);
 | 
						|
 | 
						|
  // Either signal the parent that we're no longer using timeouts or reschedule
 | 
						|
  // the timer.
 | 
						|
  if (data->mTimeouts.IsEmpty()) {
 | 
						|
    UpdateCCFlag(CCFlag::EligibleForTimeout);
 | 
						|
    data->mTimerRunning = false;
 | 
						|
  } else if (retval && !RescheduleTimeoutTimer(aCx)) {
 | 
						|
    retval = false;
 | 
						|
  }
 | 
						|
 | 
						|
  return retval;
 | 
						|
}
 | 
						|
 | 
						|
bool WorkerPrivate::RescheduleTimeoutTimer(JSContext* aCx) {
 | 
						|
  auto data = mWorkerThreadAccessible.Access();
 | 
						|
  MOZ_ASSERT(!data->mRunningExpiredTimeouts);
 | 
						|
  NS_ASSERTION(!data->mTimeouts.IsEmpty(), "Should have some timeouts!");
 | 
						|
  NS_ASSERTION(data->mTimer && data->mTimerRunnable, "Should have a timer!");
 | 
						|
 | 
						|
  // NB: This is important! The timer may have already fired, e.g. if a timeout
 | 
						|
  // callback itself calls setTimeout for a short duration and then takes longer
 | 
						|
  // than that to finish executing. If that has happened, it's very important
 | 
						|
  // that we don't execute the event that is now pending in our event queue, or
 | 
						|
  // our code in RunExpiredTimeouts to "fudge" the timeout value will unleash an
 | 
						|
  // early timeout when we execute the event we're about to queue.
 | 
						|
  data->mTimer->Cancel();
 | 
						|
 | 
						|
  double delta =
 | 
						|
      (data->mTimeouts[0]->mTargetTime - TimeStamp::Now()).ToMilliseconds();
 | 
						|
  uint32_t delay = delta > 0 ? static_cast<uint32_t>(std::ceil(
 | 
						|
                                   std::min(delta, double(UINT32_MAX))))
 | 
						|
                             : 0;
 | 
						|
 | 
						|
  LOG(TimeoutsLog(),
 | 
						|
      ("Worker %p scheduled timer for %d ms, %zu pending timeouts\n", this,
 | 
						|
       delay, data->mTimeouts.Length()));
 | 
						|
 | 
						|
  nsresult rv = data->mTimer->InitWithCallback(data->mTimerRunnable, delay,
 | 
						|
                                               nsITimer::TYPE_ONE_SHOT);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    JS_ReportErrorASCII(aCx, "Failed to start timer!");
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
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::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;
 | 
						|
}
 | 
						|
 | 
						|
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].forget()));
 | 
						|
    }
 | 
						|
    mPreStartRunnables.Clear();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void WorkerPrivate::ResetWorkerPrivateInWorkerThread() {
 | 
						|
  LOG(WorkerLog(),
 | 
						|
      ("WorkerPrivate::ResetWorkerPrivateInWorkerThread [%p]", this));
 | 
						|
  RefPtr<WorkerThread> doomedThread;
 | 
						|
 | 
						|
  // Release the mutex before doomedThread.
 | 
						|
  MutexAutoLock lock(mMutex);
 | 
						|
 | 
						|
  MOZ_ASSERT(mThread);
 | 
						|
 | 
						|
  mThread->SetWorker(WorkerThreadFriendKey{}, nullptr);
 | 
						|
  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());
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
PerformanceStorage* WorkerPrivate::GetPerformanceStorage() {
 | 
						|
  MOZ_ASSERT(mPerformanceStorage);
 | 
						|
  return mPerformanceStorage;
 | 
						|
}
 | 
						|
 | 
						|
bool WorkerPrivate::ShouldResistFingerprinting(RFPTarget aTarget) const {
 | 
						|
  return mLoadInfo.mShouldResistFingerprinting &&
 | 
						|
         nsRFPService::IsRFPEnabledFor(
 | 
						|
             mLoadInfo.mOriginAttributes.mPrivateBrowsingId >
 | 
						|
                 nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID,
 | 
						|
             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 nsAString& WorkerPrivate::Id() {
 | 
						|
  AssertIsOnMainThread();
 | 
						|
 | 
						|
  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;
 | 
						|
  }
 | 
						|
 | 
						|
  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 {
 | 
						|
  AssertIsOnWorkerThread();
 | 
						|
 | 
						|
  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,
 | 
						|
                                               uint32_t aFlags) {
 | 
						|
  nsCOMPtr<nsIRunnable> event(aRunnable);
 | 
						|
  return Dispatch(event.forget(), aFlags);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
WorkerPrivate::EventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
 | 
						|
                                     uint32_t aFlags) {
 | 
						|
  // May be called on any thread!
 | 
						|
  nsCOMPtr<nsIRunnable> event(aRunnable);
 | 
						|
 | 
						|
  // Workers only support asynchronous dispatch for now.
 | 
						|
  if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
 | 
						|
    return NS_ERROR_UNEXPECTED;
 | 
						|
  }
 | 
						|
 | 
						|
  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;
 | 
						|
}
 | 
						|
 | 
						|
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)
 | 
						|
    : mWorkerPrivate(aWorkerPrivate) {
 | 
						|
  auto data = mWorkerPrivate->mWorkerThreadAccessible.Access();
 | 
						|
  mOldEventLoopGlobal = std::move(data->mCurrentEventLoopGlobal);
 | 
						|
  if (JSObject* global = JS::CurrentGlobalOrNull(aCx)) {
 | 
						|
    data->mCurrentEventLoopGlobal = xpc::NativeGlobal(global);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
WorkerPrivate::AutoPushEventLoopGlobal::~AutoPushEventLoopGlobal() {
 | 
						|
  auto data = mWorkerPrivate->mWorkerThreadAccessible.Access();
 | 
						|
  data->mCurrentEventLoopGlobal = std::move(mOldEventLoopGlobal);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace dom
 | 
						|
}  // namespace mozilla
 |