diff options
Diffstat (limited to '')
-rw-r--r-- | dom/workers/WorkerPrivate.h | 1625 |
1 files changed, 1625 insertions, 0 deletions
diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h new file mode 100644 index 0000000000..84b4f6d89e --- /dev/null +++ b/dom/workers/WorkerPrivate.h @@ -0,0 +1,1625 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_workers_workerprivate_h__ +#define mozilla_dom_workers_workerprivate_h__ + +#include <bitset> +#include "MainThreadUtils.h" +#include "ScriptLoader.h" +#include "js/ContextOptions.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/CondVar.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" +#include "mozilla/OriginTrials.h" +#include "mozilla/PerformanceCounter.h" +#include "mozilla/RelativeTimeline.h" +#include "mozilla/Result.h" +#include "mozilla/StorageAccess.h" +#include "mozilla/ThreadBound.h" +#include "mozilla/ThreadSafeWeakPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UseCounter.h" +#include "mozilla/dom/ClientSource.h" +#include "mozilla/dom/FlippedOnce.h" +#include "mozilla/dom/Timeout.h" +#include "mozilla/dom/quota/CheckedUnsafePtr.h" +#include "mozilla/dom/Worker.h" +#include "mozilla/dom/WorkerBinding.h" +#include "mozilla/dom/WorkerCommon.h" +#include "mozilla/dom/WorkerLoadInfo.h" +#include "mozilla/dom/WorkerStatus.h" +#include "mozilla/dom/workerinternals/JSSettings.h" +#include "mozilla/dom/workerinternals/Queue.h" +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/net/NeckoChannelParams.h" +#include "mozilla/StaticPrefs_extensions.h" +#include "nsContentUtils.h" +#include "nsIChannel.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIEventTarget.h" +#include "nsILoadInfo.h" +#include "nsTObserverArray.h" + +class nsIThreadInternal; + +namespace JS { +struct RuntimeStats; +} + +namespace mozilla { +class ThrottledEventQueue; +namespace dom { + +class RemoteWorkerChild; + +// If you change this, the corresponding list in nsIWorkerDebugger.idl needs +// to be updated too. And histograms enum for worker use counters uses the same +// order of worker kind. Please also update dom/base/usecounters.py. +enum WorkerKind { WorkerKindDedicated, WorkerKindShared, WorkerKindService }; + +class ClientInfo; +class ClientSource; +class Function; +class JSExecutionManager; +class MessagePort; +class UniqueMessagePortId; +class PerformanceStorage; +class TimeoutHandler; +class WorkerControlRunnable; +class WorkerCSPEventListener; +class WorkerDebugger; +class WorkerDebuggerGlobalScope; +class WorkerErrorReport; +class WorkerEventTarget; +class WorkerGlobalScope; +class WorkerRef; +class WorkerRunnable; +class WorkerDebuggeeRunnable; +class WorkerThread; + +// SharedMutex is a small wrapper around an (internal) reference-counted Mutex +// object. It exists to avoid changing a lot of code to use Mutex* instead of +// Mutex&. +class MOZ_CAPABILITY("mutex") SharedMutex { + using Mutex = mozilla::Mutex; + + class MOZ_CAPABILITY("mutex") RefCountedMutex final : public Mutex { + public: + explicit RefCountedMutex(const char* aName) : Mutex(aName) {} + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMutex) + + private: + ~RefCountedMutex() = default; + }; + + const RefPtr<RefCountedMutex> mMutex; + + public: + explicit SharedMutex(const char* aName) + : mMutex(new RefCountedMutex(aName)) {} + + SharedMutex(const SharedMutex& aOther) = default; + + operator Mutex&() MOZ_RETURN_CAPABILITY(this) { return *mMutex; } + + operator const Mutex&() const MOZ_RETURN_CAPABILITY(this) { return *mMutex; } + + // We need these to make thread-safety analysis work + void Lock() MOZ_CAPABILITY_ACQUIRE() { mMutex->Lock(); } + void Unlock() MOZ_CAPABILITY_RELEASE() { mMutex->Unlock(); } + + // We can assert we own 'this', but we can't assert we hold mMutex + void AssertCurrentThreadOwns() const + MOZ_ASSERT_CAPABILITY(this) MOZ_NO_THREAD_SAFETY_ANALYSIS { + mMutex->AssertCurrentThreadOwns(); + } +}; + +nsString ComputeWorkerPrivateId(); + +class WorkerPrivate final + : public RelativeTimeline, + public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> { + public: + // Callback invoked on the parent thread when the worker's cancellation is + // about to be requested. This covers both calls to + // WorkerPrivate::Cancel() by the owner as well as self-initiated cancellation + // due to top-level script evaluation failing or close() being invoked on the + // global scope for Dedicated and Shared workers, but not Service Workers as + // they do not expose a close() method. + // + // ### Parent-Initiated Cancellation + // + // When WorkerPrivate::Cancel is invoked on the parent thread (by the binding + // exposed Worker::Terminate), this callback is invoked synchronously inside + // that call. + // + // ### Worker Self-Cancellation + // + // When a worker initiates self-cancellation, the worker's notification to the + // parent thread is a non-blocking, async mechanism triggered by + // `WorkerPrivate::DispatchCancelingRunnable`. + // + // Self-cancellation races a normally scheduled runnable against a timer that + // is scheduled against the parent. The 2 paths initiated by + // DispatchCancelingRunnable are: + // + // 1. A CancelingRunnable is dispatched at the worker's normal event target to + // wait for the event loop to be clear of runnables. When the + // CancelingRunnable runs it will dispatch a CancelingOnParentRunnable to + // its parent which is a normal, non-control WorkerDebuggeeRunnable to + // ensure that any postMessages to the parent or similar events get a + // chance to be processed prior to cancellation. The timer scheduled in + // the next bullet will not be canceled unless + // + // 2. A CancelingWithTimeoutOnParentRunnable control runnable is dispatched + // to the parent to schedule a timer which will (also) fire on the parent + // thread. This handles the case where the worker does not yield + // control-flow, and so the normal runnable scheduled above does not get to + // run in a timely fashion. Because this is a control runnable, if the + // parent is a worker then the runnable will be processed with urgency. + // However, if the worker is top-level, then the control-like throttled + // WorkerPrivate::mMainThreadEventTarget will end up getting used which is + // nsIRunnablePriority::PRIORITY_MEDIUMHIGH and distinct from the + // mMainThreadDebuggeeEventTarget which most runnables (like postMessage) + // use. + // + // The timer will explicitly use the control event target if the parent is + // a worker and the implicit event target (via `NS_NewTimer()`) otherwise. + // The callback is CancelingTimerCallback which just calls + // WorkerPrivate::Cancel. + using CancellationCallback = std::function<void(bool aEverRan)>; + + // Callback invoked on the parent just prior to dropping the worker thread's + // strong reference that keeps the WorkerPrivate alive while the worker thread + // is running. This does not provide a guarantee that the underlying thread + // has fully shutdown, just that the worker logic has fully shutdown. + // + // ### Details + // + // The last thing the worker thread's WorkerThreadPrimaryRunnable does before + // initiating the shutdown of the underlying thread is call ScheduleDeletion. + // ScheduleDeletion dispatches a runnable to the parent to notify it that the + // worker has completed its work and will never touch the WorkerPrivate again + // and that the strong self-reference can be dropped. + // + // For parents that are themselves workers, this will be done by + // WorkerFinishedRunnable which is a WorkerControlRunnable, ensuring that this + // is processed in a timely fashion. For main-thread parents, + // TopLevelWorkerFinishedRunnable will be used and sent via + // mMainThreadEventTargetForMessaging which is a weird ThrottledEventQueue + // which does not provide any ordering guarantees relative to + // mMainThreadDebuggeeEventTarget, so if you want those, you need to enhance + // things. + using TerminationCallback = std::function<void(void)>; + + struct LocationInfo { + nsCString mHref; + nsCString mProtocol; + nsCString mHost; + nsCString mHostname; + nsCString mPort; + nsCString mPathname; + nsCString mSearch; + nsCString mHash; + nsString mOrigin; + }; + + NS_INLINE_DECL_REFCOUNTING(WorkerPrivate) + + static already_AddRefed<WorkerPrivate> Constructor( + JSContext* aCx, const nsAString& aScriptURL, bool aIsChromeWorker, + WorkerKind aWorkerKind, RequestCredentials aRequestCredentials, + const WorkerType aWorkerType, const nsAString& aWorkerName, + const nsACString& aServiceWorkerScope, WorkerLoadInfo* aLoadInfo, + ErrorResult& aRv, nsString aId = u""_ns, + CancellationCallback&& aCancellationCallback = {}, + TerminationCallback&& aTerminationCallback = {}); + + enum LoadGroupBehavior { InheritLoadGroup, OverrideLoadGroup }; + + static nsresult 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); + + void Traverse(nsCycleCollectionTraversalCallback& aCb); + + void ClearSelfAndParentEventTargetRef() { + AssertIsOnParentThread(); + MOZ_ASSERT(mSelfRef); + + if (mTerminationCallback) { + mTerminationCallback(); + mTerminationCallback = nullptr; + } + + mParentEventTargetRef = nullptr; + mSelfRef = nullptr; + } + + // May be called on any thread... + bool Start(); + + // Called on the parent thread. + bool Notify(WorkerStatus aStatus); + + bool Cancel() { return Notify(Canceling); } + + bool Close() MOZ_REQUIRES(mMutex); + + // The passed principal must be the Worker principal in case of a + // ServiceWorker and the loading principal for any other type. + static void OverrideLoadInfoLoadGroup(WorkerLoadInfo& aLoadInfo, + nsIPrincipal* aPrincipal); + + bool IsDebuggerRegistered() MOZ_NO_THREAD_SAFETY_ANALYSIS { + AssertIsOnMainThread(); + + // No need to lock here since this is only ever modified by the same thread. + return mDebuggerRegistered; // would give a thread-safety warning + } + + bool ExtensionAPIAllowed() { + return ( + StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup() && + mExtensionAPIAllowed); + } + + void SetIsDebuggerRegistered(bool aDebuggerRegistered) { + AssertIsOnMainThread(); + + MutexAutoLock lock(mMutex); + + MOZ_ASSERT(mDebuggerRegistered != aDebuggerRegistered); + mDebuggerRegistered = aDebuggerRegistered; + + mCondVar.Notify(); + } + + void WaitForIsDebuggerRegistered(bool aDebuggerRegistered) { + AssertIsOnParentThread(); + + // Yield so that the main thread won't be blocked. + AutoYieldJSThreadExecution yield; + + MOZ_ASSERT(!NS_IsMainThread()); + + MutexAutoLock lock(mMutex); + + while (mDebuggerRegistered != aDebuggerRegistered) { + mCondVar.Wait(); + } + } + + nsresult SetIsDebuggerReady(bool aReady); + + WorkerDebugger* Debugger() const { + AssertIsOnMainThread(); + + MOZ_ASSERT(mDebugger); + return mDebugger; + } + + const OriginTrials& Trials() const { return mLoadInfo.mTrials; } + + void SetDebugger(WorkerDebugger* aDebugger) { + AssertIsOnMainThread(); + + MOZ_ASSERT(mDebugger != aDebugger); + mDebugger = aDebugger; + } + + JS::UniqueChars AdoptDefaultLocale() { + MOZ_ASSERT(mDefaultLocale, + "the default locale must have been successfully set for anyone " + "to be trying to adopt it"); + return std::move(mDefaultLocale); + } + + /** + * Invoked by WorkerThreadPrimaryRunnable::Run if it already called + * SetWorkerPrivateInWorkerThread but has to bail out on initialization before + * calling DoRunLoop because PBackground failed to initialize or something + * like that. Note that there's currently no point earlier than this that + * failure can be reported. + * + * When this happens, the worker will need to be deleted, plus the call to + * SetWorkerPrivateInWorkerThread will have scheduled all the + * mPreStartRunnables which need to be cleaned up after, as well as any + * scheduled control runnables. We're somewhat punting on debugger runnables + * for now, which may leak, but the intent is to moot this whole scenario via + * shutdown blockers, so we don't want the extra complexity right now. + */ + void RunLoopNeverRan(); + + MOZ_CAN_RUN_SCRIPT + void DoRunLoop(JSContext* aCx); + + void UnrootGlobalScopes(); + + bool InterruptCallback(JSContext* aCx); + + bool IsOnCurrentThread(); + + void CloseInternal(); + + bool FreezeInternal(); + + bool ThawInternal(); + + void PropagateStorageAccessPermissionGrantedInternal(); + + void TraverseTimeouts(nsCycleCollectionTraversalCallback& aCallback); + + void UnlinkTimeouts(); + + bool ModifyBusyCountFromWorker(bool aIncrease); + + bool AddChildWorker(WorkerPrivate& aChildWorker); + + void RemoveChildWorker(WorkerPrivate& aChildWorker); + + void PostMessageToParent(JSContext* aCx, JS::Handle<JS::Value> aMessage, + const Sequence<JSObject*>& aTransferable, + ErrorResult& aRv); + + void PostMessageToParentMessagePort(JSContext* aCx, + JS::Handle<JS::Value> aMessage, + const Sequence<JSObject*>& aTransferable, + ErrorResult& aRv); + + MOZ_CAN_RUN_SCRIPT void EnterDebuggerEventLoop(); + + void LeaveDebuggerEventLoop(); + + void PostMessageToDebugger(const nsAString& aMessage); + + void SetDebuggerImmediate(Function& aHandler, ErrorResult& aRv); + + void ReportErrorToDebugger(const nsAString& aFilename, uint32_t aLineno, + const nsAString& aMessage); + + bool NotifyInternal(WorkerStatus aStatus); + + void ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult, + JSErrorReport* aReport); + + static void ReportErrorToConsole(const char* aMessage); + + static void ReportErrorToConsole(const char* aMessage, + const nsTArray<nsString>& aParams); + + int32_t SetTimeout(JSContext* aCx, TimeoutHandler* aHandler, int32_t aTimeout, + bool aIsInterval, Timeout::Reason aReason, + ErrorResult& aRv); + + void ClearTimeout(int32_t aId, Timeout::Reason aReason); + + MOZ_CAN_RUN_SCRIPT bool RunExpiredTimeouts(JSContext* aCx); + + bool RescheduleTimeoutTimer(JSContext* aCx); + + void UpdateContextOptionsInternal(JSContext* aCx, + const JS::ContextOptions& aContextOptions); + + void UpdateLanguagesInternal(const nsTArray<nsString>& aLanguages); + + void UpdateJSWorkerMemoryParameterInternal(JSContext* aCx, JSGCParamKey key, + Maybe<uint32_t> aValue); + + enum WorkerRanOrNot { WorkerNeverRan = 0, WorkerRan }; + + void ScheduleDeletion(WorkerRanOrNot aRanOrNot); + + bool CollectRuntimeStats(JS::RuntimeStats* aRtStats, bool aAnonymize); + +#ifdef JS_GC_ZEAL + void UpdateGCZealInternal(JSContext* aCx, uint8_t aGCZeal, + uint32_t aFrequency); +#endif + + void SetLowMemoryStateInternal(JSContext* aCx, bool aState); + + void GarbageCollectInternal(JSContext* aCx, bool aShrinking, + bool aCollectChildren); + + void CycleCollectInternal(bool aCollectChildren); + + void OfflineStatusChangeEventInternal(bool aIsOffline); + + void MemoryPressureInternal(); + + typedef MozPromise<uint64_t, nsresult, true> JSMemoryUsagePromise; + RefPtr<JSMemoryUsagePromise> GetJSMemoryUsage(); + + void SetFetchHandlerWasAdded() { + MOZ_ASSERT(IsServiceWorker()); + AssertIsOnWorkerThread(); + mFetchHandlerWasAdded = true; + } + + bool FetchHandlerWasAdded() const { + MOZ_ASSERT(IsServiceWorker()); + AssertIsOnWorkerThread(); + return mFetchHandlerWasAdded; + } + + JSContext* GetJSContext() const MOZ_NO_THREAD_SAFETY_ANALYSIS { + // mJSContext is only modified on the worker thread, so workerthread code + // can safely read it without a lock + AssertIsOnWorkerThread(); + return mJSContext; + } + + WorkerGlobalScope* GlobalScope() const { + auto data = mWorkerThreadAccessible.Access(); + return data->mScope; + } + + WorkerDebuggerGlobalScope* DebuggerGlobalScope() const { + auto data = mWorkerThreadAccessible.Access(); + return data->mDebuggerScope; + } + + // Get the global associated with the current nested event loop. Will return + // null if we're not in a nested event loop or that nested event loop does not + // have an associated global. + nsIGlobalObject* GetCurrentEventLoopGlobal() const { + auto data = mWorkerThreadAccessible.Access(); + return data->mCurrentEventLoopGlobal; + } + + nsICSPEventListener* CSPEventListener() const; + + void SetThread(WorkerThread* aThread); + + void SetWorkerPrivateInWorkerThread(WorkerThread* aThread); + + void ResetWorkerPrivateInWorkerThread(); + + bool IsOnWorkerThread() const; + + void AssertIsOnWorkerThread() const +#ifdef DEBUG + ; +#else + { + } +#endif + + // This may block! + void BeginCTypesCall(); + + // This may block! + void EndCTypesCall(); + + void BeginCTypesCallback(); + + void EndCTypesCallback(); + + bool ConnectMessagePort(JSContext* aCx, UniqueMessagePortId& aIdentifier); + + WorkerGlobalScope* GetOrCreateGlobalScope(JSContext* aCx); + + WorkerDebuggerGlobalScope* CreateDebuggerGlobalScope(JSContext* aCx); + + bool RegisterBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal); + + bool RegisterDebuggerBindings(JSContext* aCx, JS::Handle<JSObject*> aGlobal); + + bool OnLine() const { + auto data = mWorkerThreadAccessible.Access(); + return data->mOnLine; + } + + void StopSyncLoop(nsIEventTarget* aSyncLoopTarget, nsresult aResult); + + bool AllPendingRunnablesShouldBeCanceled() const { + return mCancelAllPendingRunnables; + } + + void ShutdownModuleLoader(); + + void ClearMainEventQueue(WorkerRanOrNot aRanOrNot); + + void ClearDebuggerEventQueue(); + + void OnProcessNextEvent(); + + void AfterProcessNextEvent(); + + void AssertValidSyncLoop(nsIEventTarget* aSyncLoopTarget) +#ifdef DEBUG + ; +#else + { + } +#endif + + void SetWorkerScriptExecutedSuccessfully() { + AssertIsOnWorkerThread(); + // Should only be called once! + MOZ_ASSERT(!mWorkerScriptExecutedSuccessfully); + mWorkerScriptExecutedSuccessfully = true; + } + + // Only valid after CompileScriptRunnable has finished running! + bool WorkerScriptExecutedSuccessfully() const { + AssertIsOnWorkerThread(); + return mWorkerScriptExecutedSuccessfully; + } + + // Get the event target to use when dispatching to the main thread + // from this Worker thread. This may be the main thread itself or + // a ThrottledEventQueue to the main thread. + nsISerialEventTarget* MainThreadEventTargetForMessaging(); + + nsresult DispatchToMainThreadForMessaging( + nsIRunnable* aRunnable, uint32_t aFlags = NS_DISPATCH_NORMAL); + + nsresult DispatchToMainThreadForMessaging( + already_AddRefed<nsIRunnable> aRunnable, + uint32_t aFlags = NS_DISPATCH_NORMAL); + + nsISerialEventTarget* MainThreadEventTarget(); + + nsresult DispatchToMainThread(nsIRunnable* aRunnable, + uint32_t aFlags = NS_DISPATCH_NORMAL); + + nsresult DispatchToMainThread(already_AddRefed<nsIRunnable> aRunnable, + uint32_t aFlags = NS_DISPATCH_NORMAL); + + nsresult DispatchDebuggeeToMainThread( + already_AddRefed<WorkerDebuggeeRunnable> aRunnable, + uint32_t aFlags = NS_DISPATCH_NORMAL); + + // Get an event target that will dispatch runnables as control runnables on + // the worker thread. Implement nsICancelableRunnable if you wish to take + // action on cancelation. + nsISerialEventTarget* ControlEventTarget(); + + // Get an event target that will attempt to dispatch a normal WorkerRunnable, + // but if that fails will then fall back to a control runnable. + nsISerialEventTarget* HybridEventTarget(); + + void DumpCrashInformation(nsACString& aString); + + ClientType GetClientType() const; + + bool EnsureCSPEventListener(); + + void EnsurePerformanceStorage(); + + bool GetExecutionGranted() const; + void SetExecutionGranted(bool aGranted); + + void ScheduleTimeSliceExpiration(uint32_t aDelay); + void CancelTimeSliceExpiration(); + + JSExecutionManager* GetExecutionManager() const; + void SetExecutionManager(JSExecutionManager* aManager); + + void ExecutionReady(); + + PerformanceStorage* GetPerformanceStorage(); + + PerformanceCounter& MutablePerformanceCounterRef() const { + return *mPerformanceCounter; + } + + const PerformanceCounter& PerformanceCounterRef() const { + return MutablePerformanceCounterRef(); + } + + bool IsAcceptingEvents() { + AssertIsOnParentThread(); + + MutexAutoLock lock(mMutex); + return mParentStatus < Canceling; + } + + WorkerStatus ParentStatusProtected() { + AssertIsOnParentThread(); + MutexAutoLock lock(mMutex); + return mParentStatus; + } + + WorkerStatus ParentStatus() const MOZ_REQUIRES(mMutex) { + mMutex.AssertCurrentThreadOwns(); + return mParentStatus; + } + + Worker* ParentEventTargetRef() const { + MOZ_DIAGNOSTIC_ASSERT(mParentEventTargetRef); + return mParentEventTargetRef; + } + + void SetParentEventTargetRef(Worker* aParentEventTargetRef) { + MOZ_DIAGNOSTIC_ASSERT(aParentEventTargetRef); + MOZ_DIAGNOSTIC_ASSERT(!mParentEventTargetRef); + mParentEventTargetRef = aParentEventTargetRef; + } + + bool ModifyBusyCount(bool aIncrease); + + // This method is used by RuntimeService to know what is going wrong the + // shutting down. + uint32_t BusyCount() { return mBusyCount; } + + // Check whether this worker is a secure context. For use from the parent + // thread only; the canonical "is secure context" boolean is stored on the + // compartment of the worker global. The only reason we don't + // AssertIsOnParentThread() here is so we can assert that this value matches + // the one on the compartment, which has to be done from the worker thread. + bool IsSecureContext() const { return mIsSecureContext; } + + // Check whether we're running in automation. + bool IsInAutomation() const { return mIsInAutomation; } + + bool IsPrivilegedAddonGlobal() const { return mIsPrivilegedAddonGlobal; } + + TimeStamp CreationTimeStamp() const { return mCreationTimeStamp; } + + DOMHighResTimeStamp CreationTime() const { return mCreationTimeHighRes; } + + DOMHighResTimeStamp TimeStampToDOMHighRes(const TimeStamp& aTimeStamp) const { + MOZ_ASSERT(!aTimeStamp.IsNull()); + TimeDuration duration = aTimeStamp - mCreationTimeStamp; + return duration.ToMilliseconds(); + } + + LocationInfo& GetLocationInfo() { return mLocationInfo; } + + void CopyJSSettings(workerinternals::JSSettings& aSettings) { + mozilla::MutexAutoLock lock(mMutex); + aSettings = mJSSettings; + } + + void CopyJSRealmOptions(JS::RealmOptions& aOptions) { + mozilla::MutexAutoLock lock(mMutex); + aOptions = IsChromeWorker() ? mJSSettings.chromeRealmOptions + : mJSSettings.contentRealmOptions; + } + + // The ability to be a chrome worker is orthogonal to the type of + // worker [Dedicated|Shared|Service]. + bool IsChromeWorker() const { return mIsChromeWorker; } + + // TODO: Invariants require that the parent worker out-live any child + // worker, so WorkerPrivate* should be safe in the moment of calling. + // We would like to have stronger type-system annotated/enforced handling. + WorkerPrivate* GetParent() const { return mParent; } + + bool IsFrozen() const { + AssertIsOnParentThread(); + return mParentFrozen; + } + + bool IsParentWindowPaused() const { + AssertIsOnParentThread(); + return mParentWindowPaused; + } + + // When we debug a worker, we want to disconnect the window and the worker + // communication. This happens calling this method. + // Note: this method doesn't suspend the worker! Use Freeze/Thaw instead. + void ParentWindowPaused(); + + void ParentWindowResumed(); + + const nsString& ScriptURL() const { return mScriptURL; } + + const nsString& WorkerName() const { return mWorkerName; } + RequestCredentials WorkerCredentials() const { return mCredentialsMode; } + enum WorkerType WorkerType() const { return mWorkerType; } + + WorkerKind Kind() const { return mWorkerKind; } + + bool IsDedicatedWorker() const { return mWorkerKind == WorkerKindDedicated; } + + bool IsSharedWorker() const { return mWorkerKind == WorkerKindShared; } + + bool IsServiceWorker() const { return mWorkerKind == WorkerKindService; } + + nsContentPolicyType ContentPolicyType() const { + return ContentPolicyType(mWorkerKind); + } + + static nsContentPolicyType ContentPolicyType(WorkerKind aWorkerKind) { + switch (aWorkerKind) { + case WorkerKindDedicated: + return nsIContentPolicy::TYPE_INTERNAL_WORKER; + case WorkerKindShared: + return nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER; + case WorkerKindService: + return nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER; + default: + MOZ_ASSERT_UNREACHABLE("Invalid worker type"); + return nsIContentPolicy::TYPE_INVALID; + } + } + + nsIScriptContext* GetScriptContext() const { + AssertIsOnMainThread(); + return mLoadInfo.mScriptContext; + } + + const nsCString& Domain() const { return mLoadInfo.mDomain; } + + bool IsFromWindow() const { return mLoadInfo.mFromWindow; } + + nsLoadFlags GetLoadFlags() const { return mLoadInfo.mLoadFlags; } + + uint64_t WindowID() const { return mLoadInfo.mWindowID; } + + uint64_t AssociatedBrowsingContextID() const { + return mLoadInfo.mAssociatedBrowsingContextID; + } + + uint64_t ServiceWorkerID() const { return GetServiceWorkerDescriptor().Id(); } + + const nsCString& ServiceWorkerScope() const { + return GetServiceWorkerDescriptor().Scope(); + } + + // This value should never change after the script load completes. Before + // then, it may only be called on the main thread. + nsIURI* GetBaseURI() const { return mLoadInfo.mBaseURI; } + + void SetBaseURI(nsIURI* aBaseURI); + + nsIURI* GetResolvedScriptURI() const { return mLoadInfo.mResolvedScriptURI; } + + const nsString& ServiceWorkerCacheName() const { + MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker()); + AssertIsOnMainThread(); + return mLoadInfo.mServiceWorkerCacheName; + } + + const ServiceWorkerDescriptor& GetServiceWorkerDescriptor() const { + MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker()); + MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mServiceWorkerDescriptor.isSome()); + return mLoadInfo.mServiceWorkerDescriptor.ref(); + } + + const ServiceWorkerRegistrationDescriptor& + GetServiceWorkerRegistrationDescriptor() const { + MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker()); + MOZ_DIAGNOSTIC_ASSERT( + mLoadInfo.mServiceWorkerRegistrationDescriptor.isSome()); + return mLoadInfo.mServiceWorkerRegistrationDescriptor.ref(); + } + + void UpdateServiceWorkerState(ServiceWorkerState aState) { + MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker()); + MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mServiceWorkerDescriptor.isSome()); + return mLoadInfo.mServiceWorkerDescriptor.ref().SetState(aState); + } + + const Maybe<ServiceWorkerDescriptor>& GetParentController() const { + return mLoadInfo.mParentController; + } + + const ChannelInfo& GetChannelInfo() const { return mLoadInfo.mChannelInfo; } + + void SetChannelInfo(const ChannelInfo& aChannelInfo) { + AssertIsOnMainThread(); + MOZ_ASSERT(!mLoadInfo.mChannelInfo.IsInitialized()); + MOZ_ASSERT(aChannelInfo.IsInitialized()); + mLoadInfo.mChannelInfo = aChannelInfo; + } + + void InitChannelInfo(nsIChannel* aChannel) { + mLoadInfo.mChannelInfo.InitFromChannel(aChannel); + } + + void InitChannelInfo(const ChannelInfo& aChannelInfo) { + mLoadInfo.mChannelInfo = aChannelInfo; + } + + nsIPrincipal* GetPrincipal() const { return mLoadInfo.mPrincipal; } + + nsIPrincipal* GetLoadingPrincipal() const { + return mLoadInfo.mLoadingPrincipal; + } + + nsIPrincipal* GetPartitionedPrincipal() const { + return mLoadInfo.mPartitionedPrincipal; + } + + nsIPrincipal* GetEffectiveStoragePrincipal() const; + + nsILoadGroup* GetLoadGroup() const { + AssertIsOnMainThread(); + return mLoadInfo.mLoadGroup; + } + + bool UsesSystemPrincipal() const { + return GetPrincipal()->IsSystemPrincipal(); + } + bool UsesAddonOrExpandedAddonPrincipal() const { + return GetPrincipal()->GetIsAddonOrExpandedAddonPrincipal(); + } + + const mozilla::ipc::PrincipalInfo& GetPrincipalInfo() const { + return *mLoadInfo.mPrincipalInfo; + } + + const mozilla::ipc::PrincipalInfo& GetPartitionedPrincipalInfo() const { + return *mLoadInfo.mPartitionedPrincipalInfo; + } + + const mozilla::ipc::PrincipalInfo& GetEffectiveStoragePrincipalInfo() const; + + already_AddRefed<nsIChannel> ForgetWorkerChannel() { + AssertIsOnMainThread(); + return mLoadInfo.mChannel.forget(); + } + + nsPIDOMWindowInner* GetWindow() const { + AssertIsOnMainThread(); + return mLoadInfo.mWindow; + } + + nsPIDOMWindowInner* GetAncestorWindow() const; + + void EvictFromBFCache(); + + nsIContentSecurityPolicy* GetCsp() const { + AssertIsOnMainThread(); + return mLoadInfo.mCSP; + } + + void SetCsp(nsIContentSecurityPolicy* aCSP); + + nsresult SetCSPFromHeaderValues(const nsACString& aCSPHeaderValue, + const nsACString& aCSPReportOnlyHeaderValue); + + void StoreCSPOnClient(); + + const mozilla::ipc::CSPInfo& GetCSPInfo() const { + return *mLoadInfo.mCSPInfo; + } + + void UpdateReferrerInfoFromHeader( + const nsACString& aReferrerPolicyHeaderValue); + + nsIReferrerInfo* GetReferrerInfo() const { return mLoadInfo.mReferrerInfo; } + + ReferrerPolicy GetReferrerPolicy() const { + return mLoadInfo.mReferrerInfo->ReferrerPolicy(); + } + + void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) { + mLoadInfo.mReferrerInfo = aReferrerInfo; + } + + bool IsEvalAllowed() const { return mLoadInfo.mEvalAllowed; } + + void SetEvalAllowed(bool aAllowed) { mLoadInfo.mEvalAllowed = aAllowed; } + + bool GetReportEvalCSPViolations() const { + return mLoadInfo.mReportEvalCSPViolations; + } + + void SetReportEvalCSPViolations(bool aReport) { + mLoadInfo.mReportEvalCSPViolations = aReport; + } + + bool IsWasmEvalAllowed() const { return mLoadInfo.mWasmEvalAllowed; } + + void SetWasmEvalAllowed(bool aAllowed) { + mLoadInfo.mWasmEvalAllowed = aAllowed; + } + + bool GetReportWasmEvalCSPViolations() const { + return mLoadInfo.mReportWasmEvalCSPViolations; + } + + void SetReportWasmEvalCSPViolations(bool aReport) { + mLoadInfo.mReportWasmEvalCSPViolations = aReport; + } + + bool XHRParamsAllowed() const { return mLoadInfo.mXHRParamsAllowed; } + + void SetXHRParamsAllowed(bool aAllowed) { + mLoadInfo.mXHRParamsAllowed = aAllowed; + } + + mozilla::StorageAccess StorageAccess() const { + AssertIsOnWorkerThread(); + if (mLoadInfo.mHasStorageAccessPermissionGranted) { + return mozilla::StorageAccess::eAllow; + } + + return mLoadInfo.mStorageAccess; + } + + bool UseRegularPrincipal() const { + AssertIsOnWorkerThread(); + return mLoadInfo.mUseRegularPrincipal; + } + + bool HasStorageAccessPermissionGranted() const { + AssertIsOnWorkerThread(); + return mLoadInfo.mHasStorageAccessPermissionGranted; + } + + nsICookieJarSettings* CookieJarSettings() const { + // Any thread. + MOZ_ASSERT(mLoadInfo.mCookieJarSettings); + return mLoadInfo.mCookieJarSettings; + } + + const net::CookieJarSettingsArgs& CookieJarSettingsArgs() const { + MOZ_ASSERT(mLoadInfo.mCookieJarSettings); + return mLoadInfo.mCookieJarSettingsArgs; + } + + const OriginAttributes& GetOriginAttributes() const { + return mLoadInfo.mOriginAttributes; + } + + // Determine if the SW testing per-window flag is set by devtools + bool ServiceWorkersTestingInWindow() const { + return mLoadInfo.mServiceWorkersTestingInWindow; + } + + // Determine if the worker was created under a third-party context. + bool IsThirdPartyContextToTopWindow() const { + return mLoadInfo.mIsThirdPartyContextToTopWindow; + } + + bool IsWatchedByDevTools() const { return mLoadInfo.mWatchedByDevTools; } + + RemoteWorkerChild* GetRemoteWorkerController(); + + void SetRemoteWorkerController(RemoteWorkerChild* aController); + + RefPtr<GenericPromise> SetServiceWorkerSkipWaitingFlag(); + + // We can assume that an nsPIDOMWindow will be available for Freeze, Thaw + // as these are only used for globals going in and out of the bfcache. + bool Freeze(const nsPIDOMWindowInner* aWindow); + + bool Thaw(const nsPIDOMWindowInner* aWindow); + + void PropagateStorageAccessPermissionGranted(); + + void EnableDebugger(); + + void DisableDebugger(); + + already_AddRefed<WorkerRunnable> MaybeWrapAsWorkerRunnable( + already_AddRefed<nsIRunnable> aRunnable); + + bool ProxyReleaseMainThreadObjects(); + + void SetLowMemoryState(bool aState); + + void GarbageCollect(bool aShrinking); + + void CycleCollect(); + + nsresult SetPrincipalsAndCSPOnMainThread(nsIPrincipal* aPrincipal, + nsIPrincipal* aPartitionedPrincipal, + nsILoadGroup* aLoadGroup, + nsIContentSecurityPolicy* aCsp); + + nsresult SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel); + + bool FinalChannelPrincipalIsValid(nsIChannel* aChannel); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + bool PrincipalURIMatchesScriptURL(); +#endif + + void UpdateOverridenLoadGroup(nsILoadGroup* aBaseLoadGroup); + + void WorkerScriptLoaded(); + + Document* GetDocument() const; + + void MemoryPressure(); + + void UpdateContextOptions(const JS::ContextOptions& aContextOptions); + + void UpdateLanguages(const nsTArray<nsString>& aLanguages); + + void UpdateJSWorkerMemoryParameter(JSGCParamKey key, Maybe<uint32_t> value); + +#ifdef JS_GC_ZEAL + void UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency); +#endif + + void OfflineStatusChangeEvent(bool aIsOffline); + + nsresult Dispatch(already_AddRefed<WorkerRunnable> aRunnable, + nsIEventTarget* aSyncLoopTarget = nullptr); + + nsresult DispatchControlRunnable( + already_AddRefed<WorkerControlRunnable> aWorkerControlRunnable); + + nsresult DispatchDebuggerRunnable( + already_AddRefed<WorkerRunnable> aDebuggerRunnable); + +#ifdef DEBUG + void AssertIsOnParentThread() const; + + void AssertInnerWindowIsCorrect() const; +#else + void AssertIsOnParentThread() const {} + + void AssertInnerWindowIsCorrect() const {} +#endif + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + bool PrincipalIsValid() const; +#endif + + void StartCancelingTimer(); + + const nsAString& Id(); + + const nsID& AgentClusterId() const { return mAgentClusterId; } + + bool IsSharedMemoryAllowed() const; + + // https://whatpr.org/html/4734/structured-data.html#cross-origin-isolated + bool CrossOriginIsolated() const; + + void SetUseCounter(UseCounterWorker aUseCounter) { + MOZ_ASSERT(!mReportedUseCounters); + MOZ_ASSERT(aUseCounter > UseCounterWorker::Unknown); + AssertIsOnWorkerThread(); + mUseCounters[static_cast<size_t>(aUseCounter)] = true; + } + + /** + * COEP Methods + * + * If browser.tabs.remote.useCrossOriginEmbedderPolicy=false, these methods + * will, depending on the return type, return a value that will avoid + * assertion failures or a value that won't block loads. + */ + nsILoadInfo::CrossOriginEmbedderPolicy GetEmbedderPolicy() const; + + // Fails if a policy has already been set or if `aPolicy` violates the owner's + // policy, if an owner exists. + mozilla::Result<Ok, nsresult> SetEmbedderPolicy( + nsILoadInfo::CrossOriginEmbedderPolicy aPolicy); + + // `aRequest` is the request loading the worker and must be QI-able to + // `nsIChannel*`. It's used to verify that the worker can indeed inherit its + // owner's COEP (when an owner exists). + // + // TODO: remove `aRequest`; currently, it's required because instances may not + // always know its final, resolved script URL or have access internally to + // `aRequest`. + void InheritOwnerEmbedderPolicyOrNull(nsIRequest* aRequest); + + // Requires a policy to already have been set. + bool MatchEmbedderPolicy( + nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) const; + + nsILoadInfo::CrossOriginEmbedderPolicy GetOwnerEmbedderPolicy() const; + + void SetCCCollectedAnything(bool collectedAnything); + bool isLastCCCollectedAnything(); + + uint32_t GetCurrentTimerNestingLevel() const { + auto data = mWorkerThreadAccessible.Access(); + return data->mCurrentTimerNestingLevel; + } + + void IncreaseTopLevelWorkerFinishedRunnableCount() { + ++mTopLevelWorkerFinishedRunnableCount; + } + void DecreaseTopLevelWorkerFinishedRunnableCount() { + --mTopLevelWorkerFinishedRunnableCount; + } + void IncreaseWorkerFinishedRunnableCount() { ++mWorkerFinishedRunnableCount; } + void DecreaseWorkerFinishedRunnableCount() { --mWorkerFinishedRunnableCount; } + + void RunShutdownTasks(); + + private: + 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); + + ~WorkerPrivate(); + + struct AgentClusterIdAndCoop { + nsID mId; + nsILoadInfo::CrossOriginOpenerPolicy mCoop; + }; + + static AgentClusterIdAndCoop ComputeAgentClusterIdAndCoop( + WorkerPrivate* aParent, WorkerKind aWorkerKind, + WorkerLoadInfo* aLoadInfo); + + bool MayContinueRunning() { + AssertIsOnWorkerThread(); + + WorkerStatus status; + { + MutexAutoLock lock(mMutex); + status = mStatus; + } + + if (status < Canceling) { + return true; + } + + return false; + } + + void CancelAllTimeouts(); + + enum class ProcessAllControlRunnablesResult { + // We did not process anything. + Nothing, + // We did process something, states may have changed, but we can keep + // executing script. + MayContinue, + // We did process something, and should not continue executing script. + Abort + }; + + ProcessAllControlRunnablesResult ProcessAllControlRunnables() { + MutexAutoLock lock(mMutex); + return ProcessAllControlRunnablesLocked(); + } + + ProcessAllControlRunnablesResult ProcessAllControlRunnablesLocked() + MOZ_REQUIRES(mMutex); + + void EnableMemoryReporter(); + + void DisableMemoryReporter(); + + void WaitForWorkerEvents() MOZ_REQUIRES(mMutex); + + // If the worker shutdown status is equal or greater then aFailStatus, this + // operation will fail and nullptr will be returned. See WorkerStatus.h for + // more information about the correct value to use. + already_AddRefed<nsISerialEventTarget> CreateNewSyncLoop( + WorkerStatus aFailStatus); + + nsresult RunCurrentSyncLoop(); + + nsresult DestroySyncLoop(uint32_t aLoopIndex); + + void InitializeGCTimers(); + + enum GCTimerMode { PeriodicTimer = 0, IdleTimer, NoTimer }; + + void SetGCTimerMode(GCTimerMode aMode); + + public: + void CancelGCTimers() { SetGCTimerMode(NoTimer); } + + private: + void ShutdownGCTimers(); + + friend class WorkerRef; + + bool AddWorkerRef(WorkerRef* aWorkerRefer, WorkerStatus aFailStatus); + + void RemoveWorkerRef(WorkerRef* aWorkerRef); + + void NotifyWorkerRefs(WorkerStatus aStatus); + + bool HasActiveWorkerRefs() { + auto data = mWorkerThreadAccessible.Access(); + return !(data->mChildWorkers.IsEmpty() && data->mTimeouts.IsEmpty() && + data->mWorkerRefs.IsEmpty()); + } + + friend class WorkerEventTarget; + + nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask); + + nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask); + + // Internal logic to dispatch a runnable. This is separate from Dispatch() + // to allow runnables to be atomically dispatched in bulk. + nsresult DispatchLockHeld(already_AddRefed<WorkerRunnable> aRunnable, + nsIEventTarget* aSyncLoopTarget, + const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(mMutex); + + // This method dispatches a simple runnable that starts the shutdown procedure + // after a self.close(). This method is called after a ClearMainEventQueue() + // to be sure that the canceling runnable is the only one in the queue. We + // need this async operation to be sure that all the current JS code is + // executed. + void DispatchCancelingRunnable(); + + bool GetUseCounter(UseCounterWorker aUseCounter) { + MOZ_ASSERT(aUseCounter > UseCounterWorker::Unknown); + AssertIsOnWorkerThread(); + return mUseCounters[static_cast<size_t>(aUseCounter)]; + } + + void ReportUseCounters(); + + UniquePtr<ClientSource> CreateClientSource(); + + // This method is called when corresponding script loader processes the COEP + // header for the worker. + // This method should be called only once in the main thread. + // After this method is called the COEP value owner(window/parent worker) is + // cached in mOwnerEmbedderPolicy such that it can be accessed in other + // threads, i.e. WorkerThread. + void EnsureOwnerEmbedderPolicy(); + + class EventTarget; + friend class EventTarget; + friend class AutoSyncLoopHolder; + + struct TimeoutInfo; + + class MemoryReporter; + friend class MemoryReporter; + + friend class mozilla::dom::WorkerThread; + + SharedMutex mMutex; + mozilla::CondVar mCondVar MOZ_GUARDED_BY(mMutex); + + // We cannot make this CheckedUnsafePtr<WorkerPrivate> as this would violate + // our static assert + MOZ_NON_OWNING_REF WorkerPrivate* const mParent; + + const nsString mScriptURL; + + // This is the worker name for shared workers and dedicated workers. + const nsString mWorkerName; + const RequestCredentials mCredentialsMode; + enum WorkerType mWorkerType; + + const WorkerKind mWorkerKind; + + // The worker is owned by its thread, which is represented here. This is set + // in Constructor() and emptied by WorkerFinishedRunnable, and conditionally + // traversed by the cycle collector if the busy count is zero. + // + // There are 4 ways a worker can be terminated: + // 1. GC/CC - When the worker is in idle state (busycount == 0), it allows to + // traverse the 'hidden' mParentEventTargetRef pointer. This is the exposed + // Worker webidl object. Doing this, CC will be able to detect a cycle and + // Unlink is called. In Unlink, Worker calls Cancel(). + // 2. Worker::Cancel() is called - the shutdown procedure starts immediately. + // 3. WorkerScope::Close() is called - Similar to point 2. + // 4. xpcom-shutdown notification - We call Kill(). + RefPtr<Worker> mParentEventTargetRef; + RefPtr<WorkerPrivate> mSelfRef; + + CancellationCallback mCancellationCallback; + + // The termination callback is passed into the constructor on the parent + // thread and invoked by `ClearSelfAndParentEventTargetRef` just before it + // drops its self-ref. + TerminationCallback mTerminationCallback; + + // The lifetime of these objects within LoadInfo is managed explicitly; + // they do not need to be cycle collected. + WorkerLoadInfo mLoadInfo; + LocationInfo mLocationInfo; + + // Protected by mMutex. + workerinternals::JSSettings mJSSettings MOZ_GUARDED_BY(mMutex); + + WorkerDebugger* mDebugger; + + workerinternals::Queue<WorkerControlRunnable*, 4> mControlQueue; + workerinternals::Queue<WorkerRunnable*, 4> mDebuggerQueue; + + // Touched on multiple threads, protected with mMutex. Only modified on the + // worker thread + JSContext* mJSContext MOZ_GUARDED_BY(mMutex); + // mThread is only modified on the Worker thread, before calling DoRunLoop + RefPtr<WorkerThread> mThread MOZ_GUARDED_BY(mMutex); + // mPRThread is only modified on another thread in ScheduleWorker(), and is + // constant for the duration of DoRunLoop. Static mutex analysis doesn't help + // here + PRThread* mPRThread; + + // Accessed from main thread + RefPtr<ThrottledEventQueue> mMainThreadEventTargetForMessaging; + RefPtr<ThrottledEventQueue> mMainThreadEventTarget; + + // Accessed from worker thread and destructing thread + RefPtr<WorkerEventTarget> mWorkerControlEventTarget; + RefPtr<WorkerEventTarget> mWorkerHybridEventTarget; + + // A pauseable queue for WorkerDebuggeeRunnables directed at the main thread. + // See WorkerDebuggeeRunnable for details. + RefPtr<ThrottledEventQueue> mMainThreadDebuggeeEventTarget; + + struct SyncLoopInfo { + explicit SyncLoopInfo(EventTarget* aEventTarget); + + RefPtr<EventTarget> mEventTarget; + nsresult mResult; + bool mCompleted; +#ifdef DEBUG + bool mHasRun; +#endif + }; + + // This is only modified on the worker thread, but in DEBUG builds + // AssertValidSyncLoop function iterates it on other threads. Therefore + // modifications are done with mMutex held *only* in DEBUG builds. + nsTArray<UniquePtr<SyncLoopInfo>> mSyncLoopStack; + + nsCOMPtr<nsITimer> mCancelingTimer; + + // fired on the main thread if the worker script fails to load + nsCOMPtr<nsIRunnable> mLoadFailedRunnable; + + RefPtr<PerformanceStorage> mPerformanceStorage; + + RefPtr<WorkerCSPEventListener> mCSPEventListener; + + // Protected by mMutex. + nsTArray<RefPtr<WorkerRunnable>> mPreStartRunnables MOZ_GUARDED_BY(mMutex); + + // Only touched on the parent thread. Used for both SharedWorker and + // ServiceWorker RemoteWorkers. + RefPtr<RemoteWorkerChild> mRemoteWorkerController; + + JS::UniqueChars mDefaultLocale; // nulled during worker JSContext init + TimeStamp mKillTime; + WorkerStatus mParentStatus MOZ_GUARDED_BY(mMutex); + WorkerStatus mStatus MOZ_GUARDED_BY(mMutex); + + // This is touched on parent thread only, but it can be read on a different + // thread before crashing because hanging. + Atomic<uint64_t> mBusyCount; + + TimeStamp mCreationTimeStamp; + DOMHighResTimeStamp mCreationTimeHighRes; + + // Flags for use counters used directly by this worker. + static_assert(sizeof(UseCounterWorker) <= sizeof(size_t), + "UseCounterWorker is too big"); + static_assert(UseCounterWorker::Count >= static_cast<UseCounterWorker>(0), + "Should be non-negative value and safe to cast to unsigned"); + std::bitset<static_cast<size_t>(UseCounterWorker::Count)> mUseCounters; + bool mReportedUseCounters; + + // This is created while creating the WorkerPrivate, so it's safe to be + // touched on any thread. + const nsID mAgentClusterId; + + // Things touched on worker thread only. + struct WorkerThreadAccessible { + explicit WorkerThreadAccessible(WorkerPrivate* aParent); + + RefPtr<WorkerGlobalScope> mScope; + RefPtr<WorkerDebuggerGlobalScope> mDebuggerScope; + // We cannot make this CheckedUnsafePtr<WorkerPrivate> as this would violate + // our static assert + nsTArray<WorkerPrivate*> mChildWorkers; + nsTObserverArray<WorkerRef*> mWorkerRefs; + nsTArray<UniquePtr<TimeoutInfo>> mTimeouts; + + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsITimerCallback> mTimerRunnable; + + nsCOMPtr<nsITimer> mPeriodicGCTimer; + nsCOMPtr<nsITimer> mIdleGCTimer; + + RefPtr<MemoryReporter> mMemoryReporter; + + // While running a nested event loop, whether a sync loop or a debugger + // event loop we want to keep track of which global is running it, if any, + // so runnables that run off that event loop can get at that information. In + // practice this only matters for various worker debugger runnables running + // against sandboxes, because all other runnables know which globals they + // belong to already. We could also address this by threading the relevant + // global through the chains of runnables involved, but we'd need to thread + // it through some runnables that run on the main thread, and that would + // require some care to make sure things get released on the correct thread, + // which we'd rather avoid. This member is only accessed on the worker + // thread. + nsCOMPtr<nsIGlobalObject> mCurrentEventLoopGlobal; + + // Timer that triggers an interrupt on expiration of the current time slice + nsCOMPtr<nsITimer> mTSTimer; + + // Execution manager used to regulate execution for this worker. + RefPtr<JSExecutionManager> mExecutionManager; + + // Used to relinguish clearance for CTypes Callbacks. + nsTArray<AutoYieldJSThreadExecution> mYieldJSThreadExecution; + + uint32_t mNumWorkerRefsPreventingShutdownStart; + uint32_t mDebuggerEventLoopLevel; + + uint32_t mErrorHandlerRecursionCount; + int32_t mNextTimeoutId; + + // Tracks the current setTimeout/setInterval nesting level. + // When there isn't a TimeoutHandler on the stack, this will be 0. + // Whenever setTimeout/setInterval are called, a new TimeoutInfo will be + // created with a nesting level one more than the current nesting level, + // saturating at the kClampTimeoutNestingLevel. + // + // When RunExpiredTimeouts is run, it sets this value to the + // TimeoutInfo::mNestingLevel for the duration of + // the WorkerScriptTimeoutHandler::Call which will explicitly trigger a + // microtask checkpoint so that any immediately-resolved promises will + // still see the nesting level. + uint32_t mCurrentTimerNestingLevel; + + bool mFrozen; + bool mTimerRunning; + bool mRunningExpiredTimeouts; + bool mPeriodicGCTimerRunning; + bool mIdleGCTimerRunning; + bool mOnLine; + bool mJSThreadExecutionGranted; + bool mCCCollectedAnything; + FlippedOnce<false> mDeletionScheduled; + }; + ThreadBound<WorkerThreadAccessible> mWorkerThreadAccessible; + + class MOZ_RAII AutoPushEventLoopGlobal { + public: + AutoPushEventLoopGlobal(WorkerPrivate* aWorkerPrivate, JSContext* aCx); + ~AutoPushEventLoopGlobal(); + + private: + // We cannot make this CheckedUnsafePtr<WorkerPrivate> as this would violate + // our static assert + MOZ_NON_OWNING_REF WorkerPrivate* mWorkerPrivate; + nsCOMPtr<nsIGlobalObject> mOldEventLoopGlobal; + }; + friend class AutoPushEventLoopGlobal; + + uint32_t mPostSyncLoopOperations; + + // List of operations to do at the end of the last sync event loop. + enum { + ePendingEventQueueClearing = 0x01, + eDispatchCancelingRunnable = 0x02, + }; + + bool mParentWindowPaused; + + bool mCancelAllPendingRunnables; + bool mWorkerScriptExecutedSuccessfully; + bool mFetchHandlerWasAdded; + bool mMainThreadObjectsForgotten; + bool mIsChromeWorker; + bool mParentFrozen; + + // mIsSecureContext is set once in our constructor; after that it can be read + // from various threads. + // + // It's a bit unfortunate that we have to have an out-of-band boolean for + // this, but we need access to this state from the parent thread, and we can't + // use our global object's secure state there. + const bool mIsSecureContext; + + bool mDebuggerRegistered MOZ_GUARDED_BY(mMutex); + + // During registration, this worker may be marked as not being ready to + // execute debuggee runnables or content. + // + // Protected by mMutex. + bool mDebuggerReady; + nsTArray<RefPtr<WorkerRunnable>> mDelayedDebuggeeRunnables; + + // Whether this worker should have access to the WebExtension API bindings + // (currently only the Extension Background ServiceWorker declared in the + // extension manifest is allowed to access any WebExtension API bindings). + // This default to false, and it is eventually set to true by + // RemoteWorkerChild::ExecWorkerOnMainThread if the needed conditions + // are met. + bool mExtensionAPIAllowed; + + // mIsInAutomation is true when we're running in test automation. + // We expose some extra testing functions in that case. + bool mIsInAutomation; + + const RefPtr<PerformanceCounter> mPerformanceCounter = + MakeRefPtr<PerformanceCounter>(nsPrintfCString( + "Worker:%s", NS_ConvertUTF16toUTF8(mWorkerName).get())); + + nsString mId; + + // This is used to check if it's allowed to share the memory across the agent + // cluster. + const nsILoadInfo::CrossOriginOpenerPolicy mAgentClusterOpenerPolicy; + + // Member variable of this class rather than the worker global scope because + // it's received on the main thread, but the global scope is thread-bound + // to the worker thread, so storing the value in the global scope would + // involve sacrificing the thread-bound-ness or using a WorkerRunnable, and + // there isn't a strong reason to store it on the global scope other than + // better consistency with the COEP spec. + Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> mEmbedderPolicy; + Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> mOwnerEmbedderPolicy; + + /* Privileged add-on flag extracted from the AddonPolicy on the nsIPrincipal + * on the main thread when constructing a top-level worker. The flag is + * propagated to nested workers. The flag is only allowed to take effect in + * extension processes and is forbidden in content scripts in content + * processes. The flag may be read on either the parent/owner thread as well + * as on the worker thread itself. When bug 1443925 is fixed allowing + * nsIPrincipal to be used OMT, it may be possible to remove this flag. */ + bool mIsPrivilegedAddonGlobal; + + Atomic<uint32_t> mTopLevelWorkerFinishedRunnableCount; + Atomic<uint32_t> mWorkerFinishedRunnableCount; + + nsTArray<nsCOMPtr<nsITargetShutdownTask>> mShutdownTasks + MOZ_GUARDED_BY(mMutex); + bool mShutdownTasksRun MOZ_GUARDED_BY(mMutex) = false; +}; + +class AutoSyncLoopHolder { + CheckedUnsafePtr<WorkerPrivate> mWorkerPrivate; + nsCOMPtr<nsISerialEventTarget> mTarget; + uint32_t mIndex; + + public: + // See CreateNewSyncLoop() for more information about the correct value to use + // for aFailStatus. + AutoSyncLoopHolder(WorkerPrivate* aWorkerPrivate, WorkerStatus aFailStatus) + : mWorkerPrivate(aWorkerPrivate), + mTarget(aWorkerPrivate->CreateNewSyncLoop(aFailStatus)), + mIndex(aWorkerPrivate->mSyncLoopStack.Length() - 1) { + aWorkerPrivate->AssertIsOnWorkerThread(); + } + + ~AutoSyncLoopHolder() { + if (mWorkerPrivate && mTarget) { + mWorkerPrivate->AssertIsOnWorkerThread(); + mWorkerPrivate->StopSyncLoop(mTarget, NS_ERROR_FAILURE); + mWorkerPrivate->DestroySyncLoop(mIndex); + } + } + + nsresult Run() { + CheckedUnsafePtr<WorkerPrivate> workerPrivate = mWorkerPrivate; + mWorkerPrivate = nullptr; + + workerPrivate->AssertIsOnWorkerThread(); + + return workerPrivate->RunCurrentSyncLoop(); + } + + nsISerialEventTarget* GetSerialEventTarget() const { + // This can be null if CreateNewSyncLoop() fails. + return mTarget; + } +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_workers_workerprivate_h__ */ |