summaryrefslogtreecommitdiffstats
path: root/dom/workers/WorkerPrivate.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/workers/WorkerPrivate.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/workers/WorkerPrivate.cpp')
-rw-r--r--dom/workers/WorkerPrivate.cpp6003
1 files changed, 6003 insertions, 0 deletions
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
new file mode 100644
index 0000000000..fc349c1642
--- /dev/null
+++ b/dom/workers/WorkerPrivate.cpp
@@ -0,0 +1,6003 @@
+/* -*- 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/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/WorkerBinding.h"
+#include "mozilla/dom/WorkerScope.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/StorageAccess.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ThreadEventQueue.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/WorkerTimelineMarker.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 "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
+#define LOG(log, _args) MOZ_LOG(log, LogLevel::Debug, _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, WorkerThreadUnchangedBusyCount),
+ 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 {
+ // We need to check first if cancel is called twice
+ nsresult rv = WorkerRunnable::Cancel();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDiscardableRunnable> doomed =
+ do_QueryInterface(mWrappedRunnable);
+ MOZ_ASSERT(doomed); // We checked this earlier!
+ 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, WorkerThreadUnchangedBusyCount),
+ 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 ModifyBusyCountRunnable final : public WorkerControlRunnable {
+ bool mIncrease;
+
+ public:
+ ModifyBusyCountRunnable(WorkerPrivate* aWorkerPrivate, bool aIncrease)
+ : WorkerControlRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount),
+ mIncrease(aIncrease) {}
+
+ private:
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ return aWorkerPrivate->ModifyBusyCount(mIncrease);
+ }
+
+ virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override {
+ if (mIncrease) {
+ WorkerControlRunnable::PostRun(aCx, aWorkerPrivate, aRunResult);
+ return;
+ }
+ // Don't do anything here as it's possible that aWorkerPrivate has been
+ // deleted.
+ }
+};
+
+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, WorkerThreadModifyBusyCount),
+ 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, WorkerThreadUnchangedBusyCount),
+ mStatus(aStatus) {
+ MOZ_ASSERT(aStatus == Closing || aStatus == Canceling ||
+ aStatus == Killing);
+ }
+
+ private:
+ virtual bool PreDispatch(WorkerPrivate* aWorkerPrivate) override {
+ aWorkerPrivate->AssertIsOnParentThread();
+ return aWorkerPrivate->ModifyBusyCount(true);
+ }
+
+ virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
+ bool aDispatchResult) override {
+ aWorkerPrivate->AssertIsOnParentThread();
+ if (!aDispatchResult) {
+ // We couldn't dispatch to the worker, which means it's already dead.
+ // Undo the busy count modification.
+ aWorkerPrivate->ModifyBusyCount(false);
+ }
+ }
+
+ virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+ bool aRunResult) override {
+ aWorkerPrivate->ModifyBusyCountFromWorker(false);
+ }
+
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ return aWorkerPrivate->NotifyInternal(mStatus);
+ }
+};
+
+class FreezeRunnable final : public WorkerControlRunnable {
+ public:
+ explicit FreezeRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ private:
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ return aWorkerPrivate->FreezeInternal();
+ }
+};
+
+class ThawRunnable final : public WorkerControlRunnable {
+ public:
+ explicit ThawRunnable(WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ private:
+ virtual bool WorkerRun(JSContext* aCx,
+ WorkerPrivate* aWorkerPrivate) override {
+ return aWorkerPrivate->ThawInternal();
+ }
+};
+
+class PropagateStorageAccessPermissionGrantedRunnable final
+ : public WorkerControlRunnable {
+ public:
+ explicit PropagateStorageAccessPermissionGrantedRunnable(
+ WorkerPrivate* aWorkerPrivate)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount) {}
+
+ 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, ParentThreadUnchangedBusyCount),
+ 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, WorkerThreadUnchangedBusyCount) {}
+
+ 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, WorkerThreadUnchangedBusyCount),
+ 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, WorkerThreadUnchangedBusyCount),
+ 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, WorkerThreadUnchangedBusyCount),
+ 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, WorkerThreadUnchangedBusyCount),
+ 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, WorkerThreadUnchangedBusyCount),
+ 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, WorkerThreadUnchangedBusyCount),
+ 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, WorkerThreadUnchangedBusyCount),
+ 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, WorkerThreadUnchangedBusyCount) {}
+
+ 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, ParentThreadUnchangedBusyCount) {
+ }
+
+ 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, ParentThreadUnchangedBusyCount) {}
+
+ 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 {
+ 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 our busy count is zero 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 (!mBusyCount && !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);
+
+ MOZ_ASSERT_IF(aSyncLoopTarget, mThread);
+
+ if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Running)) {
+ 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) {
+ 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) {
+ 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.
+ 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);
+
+ {
+ 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);
+
+ RefPtr<WorkerRunnable> workerRunnable =
+ WorkerRunnable::FromRunnable(runnable);
+ if (workerRunnable) {
+ return workerRunnable.forget();
+ }
+
+ nsCOMPtr<nsIDiscardableRunnable> maybe = do_QueryInterface(runnable);
+ if (!maybe) {
+ MOZ_CRASH(
+ "All runnables destined for a worker thread must be "
+ "nsIDiscardableRunnable!");
+ }
+
+ workerRunnable = new ExternalRunnableWrapper(this, runnable);
+ return workerRunnable.forget();
+}
+
+bool WorkerPrivate::Start() {
+ // May be called on any thread!
+ {
+ 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::ModifyBusyCount(bool aIncrease) {
+ AssertIsOnParentThread();
+
+ MOZ_ASSERT(aIncrease || mBusyCount, "Mismatched busy count mods!");
+
+ if (aIncrease) {
+ mBusyCount++;
+ return true;
+ }
+
+ if (--mBusyCount == 0) {
+ bool shouldCancel;
+ {
+ MutexAutoLock lock(mMutex);
+ shouldCancel = mParentStatus == Canceling;
+ }
+
+ if (shouldCancel && !Cancel()) {
+ return false;
+ }
+ }
+
+ 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::GetUTFOrigin(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);
+}
+
+#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),
+ 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),
+ mBusyCount(0),
+ mCreationTimeStamp(TimeStamp::Now()),
+ mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC),
+ mReportedUseCounters(false),
+ mAgentClusterId(aAgentClusterId),
+ mWorkerThreadAccessible(aParent),
+ mPostSyncLoopOperations(0),
+ mParentWindowPaused(false),
+ mCancelAllPendingRunnables(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) {
+ 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;
+
+ JS::RealmBehaviors& chromeRealmBehaviors = chromeRealmOptions.behaviors();
+ JS::RealmBehaviors& contentRealmBehaviors =
+ contentRealmOptions.behaviors();
+
+ bool usesSystemPrincipal = UsesSystemPrincipal();
+
+ // Make timing imprecise in unprivileged code to blunt Spectre timing
+ // attacks.
+ bool clampAndJitterTime = !usesSystemPrincipal;
+ chromeRealmBehaviors.setClampAndJitterTime(clampAndJitterTime)
+ .setShouldResistFingerprinting(false);
+ contentRealmBehaviors.setClampAndJitterTime(clampAndJitterTime)
+ .setShouldResistFingerprinting(mLoadInfo.mShouldResistFingerprinting);
+
+ JS::RealmCreationOptions& chromeCreationOptions =
+ chromeRealmOptions.creationOptions();
+ JS::RealmCreationOptions& contentCreationOptions =
+ contentRealmOptions.creationOptions();
+
+ // Expose uneval and toSource functions only if this is privileged code.
+ bool toSourceEnabled = usesSystemPrincipal;
+ chromeCreationOptions.setToSourceEnabled(toSourceEnabled);
+ contentCreationOptions.setToSourceEnabled(toSourceEnabled);
+
+ if (mIsSecureContext) {
+ chromeCreationOptions.setSecureContext(true);
+ contentCreationOptions.setSecureContext(true);
+ }
+
+ // 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();
+ chromeCreationOptions.setDefineSharedArrayBufferConstructor(
+ defineSharedArrayBufferConstructor);
+ contentCreationOptions.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.mHasStorageAccessPermissionGranted =
+ aParent->HasStorageAccessPermissionGranted();
+ 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->GlobalScope()->ShouldResistFingerprinting(
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+ 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.mHasStorageAccessPermissionGranted =
+ document->HasStorageAccessPermissionGranted();
+ loadInfo.mShouldResistFingerprinting =
+ document->ShouldResistFingerprinting(
+ RFPTarget::IsAlwaysEnabledForPrecompute);
+
+ // This is an hack to deny the storage-access-permission for workers of
+ // sub-iframes.
+ if (loadInfo.mHasStorageAccessPermissionGranted &&
+ StorageAllowedForDocument(document) != StorageAccess::eAllow) {
+ loadInfo.mHasStorageAccessPermissionGranted = 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.mHasStorageAccessPermissionGranted = 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.mHasStorageAccessPermissionGranted
+ ? 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() {
+ {
+ 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();
+ }
+ }
+
+ NotifyWorkerRefs(Killing);
+
+ ScheduleDeletion(WorkerPrivate::WorkerRan);
+}
+
+void WorkerPrivate::UnrootGlobalScopes() {
+ 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) {
+ 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();
+
+ for (;;) {
+ WorkerStatus currentStatus;
+ bool debuggerRunnablesPending = false;
+ bool normalRunnablesPending = false;
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ // 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 (mControlQueue.IsEmpty() &&
+ !(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
+ !(normalRunnablesPending = NS_HasPendingEvents(thread)) &&
+ !(mStatus != Running && !HasActiveWorkerRefs())) {
+ // 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());
+
+ WaitForWorkerEvents();
+ }
+
+ auto result = ProcessAllControlRunnablesLocked();
+ if (result != ProcessAllControlRunnablesResult::Nothing) {
+ // NB: There's no JS on the stack here, so Abort vs MayContinue is
+ // irrelevant
+
+ // The state of the world may have changed, recheck it.
+ normalRunnablesPending = NS_HasPendingEvents(thread);
+ // The debugger queue doesn't get cleared, so we can ignore that.
+ }
+
+ currentStatus = mStatus;
+ }
+
+ // if all holders are done then we can kill this thread.
+ if (currentStatus != Running && !HasActiveWorkerRefs()) {
+ // 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()) {
+ 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;
+ }
+ }
+
+ 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();
+ MOZ_ASSERT(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);
+ }
+ }
+
+ 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);
+
+ ClearMainEventQueue(aRanOrNot);
+#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::ClearMainEventQueue(WorkerRanOrNot aRanOrNot) {
+ AssertIsOnWorkerThread();
+
+ MOZ_ASSERT((mPostSyncLoopOperations & ePendingEventQueueClearing)
+ ? (mSyncLoopStack.Length() == 1)
+ : mSyncLoopStack.IsEmpty());
+ MOZ_ASSERT(!mCancelAllPendingRunnables);
+
+ mCancelAllPendingRunnables = true;
+ WorkerGlobalScope* globalScope = GlobalScope();
+ if (globalScope) {
+ // It's appropriate to disconnect event targets at the point that it's no
+ // longer possible for new tasks to be dispatched at the global, and this is
+ // that point.
+ globalScope->DisconnectGlobalTeardownObservers();
+
+ globalScope->WorkerPrivateSaysForbidScript();
+ }
+
+ if (WorkerNeverRan == aRanOrNot) {
+ nsTArray<RefPtr<WorkerRunnable>> prestart;
+ {
+ MutexAutoLock lock(mMutex);
+ mPreStartRunnables.SwapElements(prestart);
+ }
+ for (uint32_t count = prestart.Length(), index = 0; index < count;
+ index++) {
+ RefPtr<WorkerRunnable> runnable = std::move(prestart[index]);
+ static_cast<nsIRunnable*>(runnable.get())->Run();
+ }
+ } else {
+ nsIThread* currentThread = NS_GetCurrentThread();
+ MOZ_ASSERT(currentThread);
+
+ NS_ProcessPendingEvents(currentThread);
+ }
+
+ if (globalScope) {
+ globalScope->WorkerPrivateSaysAllowScript();
+ }
+ MOZ_ASSERT(mCancelAllPendingRunnables);
+ mCancelAllPendingRunnables = false;
+}
+
+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.mHasStorageAccessPermissionGranted = 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::ModifyBusyCountFromWorker(bool aIncrease) {
+ AssertIsOnWorkerThread();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ // If we're in shutdown then the busy count is no longer being considered so
+ // just return now.
+ if (mStatus >= Killing) {
+ return true;
+ }
+ }
+
+ RefPtr<ModifyBusyCountRunnable> runnable =
+ new ModifyBusyCountRunnable(this, aIncrease);
+ return runnable->Dispatch();
+}
+
+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);
+
+ return data->mChildWorkers.Length() == 1 ? ModifyBusyCountFromWorker(true)
+ : 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() && !ModifyBusyCountFromWorker(false)) {
+ NS_WARNING("Failed to modify busy count!");
+ }
+}
+
+bool WorkerPrivate::AddWorkerRef(WorkerRef* aWorkerRef,
+ WorkerStatus aFailStatus) {
+ MOZ_ASSERT(aWorkerRef);
+ auto data = mWorkerThreadAccessible.Access();
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ 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()) {
+ if (!data->mNumWorkerRefsPreventingShutdownStart &&
+ !ModifyBusyCountFromWorker(true)) {
+ return false;
+ }
+ data->mNumWorkerRefsPreventingShutdownStart += 1;
+ }
+
+ data->mWorkerRefs.AppendElement(aWorkerRef);
+ return true;
+}
+
+void WorkerPrivate::RemoveWorkerRef(WorkerRef* aWorkerRef) {
+ MOZ_ASSERT(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 &&
+ !ModifyBusyCountFromWorker(false)) {
+ NS_WARNING("Failed to modify busy count!");
+ }
+ }
+}
+
+void WorkerPrivate::NotifyWorkerRefs(WorkerStatus aStatus) {
+ auto data = mWorkerThreadAccessible.Access();
+
+ NS_ASSERTION(aStatus > Closing, "Bad status!");
+
+ for (auto* workerRef : data->mWorkerRefs.ForwardRange()) {
+ 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::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();
+ ModifyBusyCountFromWorker(false);
+ }
+
+ // 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!");
+
+ 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();
+ 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);
+
+ 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 & ePendingEventQueueClearing)) {
+ ClearMainEventQueue(WorkerRan);
+ }
+
+ if ((mPostSyncLoopOperations & eDispatchCancelingRunnable)) {
+ 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.
+ 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.
+ 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);
+ break;
+ case WorkerKindShared:
+ Telemetry::Accumulate(Telemetry::SHARED_WORKER_DESTROYED, 1);
+ break;
+ case WorkerKindService:
+ Telemetry::Accumulate(Telemetry::SERVICE_WORKER_DESTROYED, 1);
+ 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);
+
+ 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);
+ }
+}
+
+void WorkerPrivate::StopSyncLoop(nsIEventTarget* aSyncLoopTarget,
+ nsresult aResult) {
+ AssertIsOnWorkerThread();
+ AssertValidSyncLoop(aSyncLoopTarget);
+
+ MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
+
+ 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;
+ }
+
+ MOZ_ASSERT(!SameCOMIdentity(loopInfo->mEventTarget, aSyncLoopTarget));
+ }
+
+ MOZ_CRASH("Unknown sync loop!");
+}
+
+#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) {
+ 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::ParentThreadUnchangedBusyCount);
+
+ UniquePtr<AbstractTimelineMarker> start;
+ UniquePtr<AbstractTimelineMarker> end;
+ bool isTimelineRecording = !TimelineConsumers::IsEmpty();
+
+ if (isTimelineRecording) {
+ start = MakeUnique<WorkerTimelineMarker>(
+ NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::START);
+ }
+
+ 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 (isTimelineRecording) {
+ end = MakeUnique<WorkerTimelineMarker>(
+ NS_IsMainThread()
+ ? ProfileTimelineWorkerOperationType::SerializeDataOnMainThread
+ : ProfileTimelineWorkerOperationType::SerializeDataOffMainThread,
+ MarkerTracingType::END);
+ TimelineConsumers::AddMarkerForAllObservedDocShells(start);
+ TimelineConsumers::AddMarkerForAllObservedDocShells(end);
+ }
+
+ 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.
+ WorkerStatus previousStatus;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (mStatus >= aStatus) {
+ return true;
+ }
+
+ MOZ_ASSERT_IF(aStatus == Killing, mStatus == Canceling);
+
+ if (aStatus >= Canceling) {
+ MutexAutoUnlock unlock(mMutex);
+ if (data->mScope) {
+ if (aStatus == Canceling) {
+ data->mScope->NoteTerminating();
+ } else {
+ data->mScope->NoteShuttingDown();
+ }
+ }
+ }
+
+ previousStatus = mStatus;
+ mStatus = aStatus;
+
+ // Mark parent status as closing immediately to avoid new events being
+ // dispatched after we clear the queue below.
+ if (aStatus == Closing) {
+ Close();
+ }
+ }
+
+ MOZ_ASSERT(previousStatus != Pending);
+
+ if (aStatus >= Closing) {
+ CancelAllTimeouts();
+ }
+
+ // Let all our holders know the new status.
+ if (aStatus > Closing) {
+ NotifyWorkerRefs(aStatus);
+ }
+
+ // If this is the first time our status has changed then we need to clear the
+ // main event queue.
+ if (previousStatus == Running) {
+ // NB: If we're in a sync loop, we can't clear the queue immediately,
+ // because this is the wrong queue. So we have to defer it until later.
+ if (!mSyncLoopStack.IsEmpty()) {
+ mPostSyncLoopOperations |= ePendingEventQueueClearing;
+ } else {
+ ClearMainEventQueue(WorkerRan);
+ }
+ }
+
+ // If the worker script never ran, or failed to compile, we don't need to do
+ // anything else.
+ WorkerGlobalScope* global = GlobalScope();
+ if (!global) {
+ return true;
+ }
+
+ if (WebTaskScheduler* scheduler = global->GetExistingScheduler()) {
+ scheduler->Disconnect();
+ }
+
+ // 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()) {
+ mPostSyncLoopOperations |= eDispatchCancelingRunnable;
+ } else {
+ DispatchCancelingRunnable();
+ }
+ return true;
+ }
+
+ MOZ_ASSERT(aStatus == Canceling || aStatus == Killing);
+
+ // 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()->EventTargetFor(TaskCategory::Timer));
+ if (!data->mTimer) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return 0;
+ }
+
+ data->mTimerRunnable = new TimerRunnable(this);
+ }
+
+ if (!data->mTimerRunning) {
+ if (!ModifyBusyCountFromWorker(true)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return 0;
+ }
+ 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()) {
+ if (!ModifyBusyCountFromWorker(false)) {
+ retval = false;
+ }
+ 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();
+
+ auto errorCleanup = MakeScopeExit([&] { mCancelingTimer = nullptr; });
+
+ MOZ_ASSERT(!mCancelingTimer);
+
+ 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) {
+ 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() {
+ 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;
+ }
+
+ bool rfp = mLoadInfo.mShouldResistFingerprinting;
+
+ if (IsSharedWorker()) {
+ data->mScope = new SharedWorkerGlobalScope(this, CreateClientSource(),
+ WorkerName(), rfp);
+ } else if (IsServiceWorker()) {
+ data->mScope = new ServiceWorkerGlobalScope(
+ this, CreateClientSource(), GetServiceWorkerRegistrationDescriptor(),
+ rfp);
+ } else {
+ data->mScope = new DedicatedWorkerGlobalScope(this, CreateClientSource(),
+ WorkerName(), rfp);
+ }
+
+ 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;
+ }
+
+ 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());
+
+ bool rfp = false; // The debugger for a worker can exempt RFP; it is not
+ // client-exposed
+ data->mDebuggerScope =
+ new WorkerDebuggerGlobalScope(this, std::move(clientSource), rfp);
+
+ 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;
+}
+
+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