/* -*- 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 #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/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/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 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> { public: 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 Constructor( JSContext* aCx, const nsAString& aScriptURL, bool aIsChromeWorker, WorkerKind aWorkerKind, const nsAString& aWorkerName, const nsACString& aServiceWorkerScope, WorkerLoadInfo* aLoadInfo, ErrorResult& aRv, nsString aId = u""_ns); enum LoadGroupBehavior { InheritLoadGroup, OverrideLoadGroup }; static nsresult GetLoadInfo(JSContext* aCx, nsPIDOMWindowInner* aWindow, WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker, LoadGroupBehavior aLoadGroupBehavior, WorkerKind aWorkerKind, WorkerLoadInfo* aLoadInfo); void Traverse(nsCycleCollectionTraversalCallback& aCb); void ClearSelfAndParentEventTargetRef() { AssertIsOnParentThread(); MOZ_ASSERT(mSelfRef); 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 aMessage, const Sequence& aTransferable, ErrorResult& aRv); void PostMessageToParentMessagePort(JSContext* aCx, JS::Handle aMessage, const Sequence& 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& 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& aLanguages); void UpdateJSWorkerMemoryParameterInternal(JSContext* aCx, JSGCParamKey key, Maybe 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 JSMemoryUsagePromise; RefPtr 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 aGlobal); bool RegisterDebuggerBindings(JSContext* aCx, JS::Handle aGlobal); bool OnLine() const { auto data = mWorkerThreadAccessible.Access(); return data->mOnLine; } void StopSyncLoop(nsIEventTarget* aSyncLoopTarget, nsresult aResult); bool AllPendingRunnablesShouldBeCanceled() const { return mCancelAllPendingRunnables; } 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. nsIEventTarget* MainThreadEventTargetForMessaging(); nsresult DispatchToMainThreadForMessaging( nsIRunnable* aRunnable, uint32_t aFlags = NS_DISPATCH_NORMAL); nsresult DispatchToMainThreadForMessaging( already_AddRefed aRunnable, uint32_t aFlags = NS_DISPATCH_NORMAL); nsIEventTarget* MainThreadEventTarget(); nsresult DispatchToMainThread(nsIRunnable* aRunnable, uint32_t aFlags = NS_DISPATCH_NORMAL); nsresult DispatchToMainThread(already_AddRefed aRunnable, uint32_t aFlags = NS_DISPATCH_NORMAL); nsresult DispatchDebuggeeToMainThread( already_AddRefed 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; } 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 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& 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 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 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); void SetRemoteWorkerControllerWeakRef( ThreadSafeWeakPtr aWeakRef); ThreadSafeWeakPtr GetRemoteWorkerControllerWeakRef(); RefPtr 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 MaybeWrapAsWorkerRunnable( already_AddRefed 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& aLanguages); void UpdateJSWorkerMemoryParameter(JSGCParamKey key, Maybe value); #ifdef JS_GC_ZEAL void UpdateGCZeal(uint8_t aGCZeal, uint32_t aFrequency); #endif void OfflineStatusChangeEvent(bool aIsOffline); nsresult Dispatch(already_AddRefed aRunnable, nsIEventTarget* aSyncLoopTarget = nullptr); nsresult DispatchControlRunnable( already_AddRefed aWorkerControlRunnable); nsresult DispatchDebuggerRunnable( already_AddRefed 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(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 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); 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, const nsAString& aWorkerName, const nsACString& aServiceWorkerScope, WorkerLoadInfo& aLoadInfo, nsString&& aId, const nsID& aAgentClusterId, const nsILoadInfo::CrossOriginOpenerPolicy aAgentClusterOpenerPolicy); ~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 CreateNewSyncLoop( WorkerStatus aFailStatus); nsresult RunCurrentSyncLoop(); nsresult DestroySyncLoop(uint32_t aLoopIndex); void InitializeGCTimers(); enum GCTimerMode { PeriodicTimer = 0, IdleTimer, NoTimer }; void SetGCTimerMode(GCTimerMode aMode); 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; bool RegisterShutdownTask(nsITargetShutdownTask* aTask); bool 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 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(aUseCounter)]; } void ReportUseCounters(); UniquePtr 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 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 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 mParentEventTargetRef; RefPtr mSelfRef; // 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 mControlQueue; workerinternals::Queue 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 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 mMainThreadEventTargetForMessaging; RefPtr mMainThreadEventTarget; // Accessed from worker thread and destructing thread RefPtr mWorkerControlEventTarget; RefPtr mWorkerHybridEventTarget; // A pauseable queue for WorkerDebuggeeRunnables directed at the main thread. // See WorkerDebuggeeRunnable for details. RefPtr mMainThreadDebuggeeEventTarget; struct SyncLoopInfo { explicit SyncLoopInfo(EventTarget* aEventTarget); RefPtr 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> mSyncLoopStack; nsCOMPtr mCancelingTimer; // fired on the main thread if the worker script fails to load nsCOMPtr mLoadFailedRunnable; RefPtr mPerformanceStorage; RefPtr mCSPEventListener; // Protected by mMutex. nsTArray> mPreStartRunnables MOZ_GUARDED_BY(mMutex); // Only touched on the parent thread. This is set only if IsSharedWorker(). RefPtr mRemoteWorkerController; // This is set only if IsServiceWorker(). ThreadSafeWeakPtr mRemoteWorkerControllerWeakRef; 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 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(0), "Should be non-negative value and safe to cast to unsigned"); std::bitset(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 mScope; RefPtr mDebuggerScope; // We cannot make this CheckedUnsafePtr as this would violate // our static assert nsTArray mChildWorkers; nsTObserverArray mWorkerRefs; nsTArray> mTimeouts; nsCOMPtr mTimer; nsCOMPtr mTimerRunnable; nsCOMPtr mGCTimer; RefPtr 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 mCurrentEventLoopGlobal; // Timer that triggers an interrupt on expiration of the current time slice nsCOMPtr mTSTimer; // Execution manager used to regulate execution for this worker. RefPtr mExecutionManager; // Used to relinguish clearance for CTypes Callbacks. nsTArray 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 mDeletionScheduled; }; ThreadBound mWorkerThreadAccessible; class MOZ_RAII AutoPushEventLoopGlobal { public: AutoPushEventLoopGlobal(WorkerPrivate* aWorkerPrivate, JSContext* aCx); ~AutoPushEventLoopGlobal(); private: // We cannot make this CheckedUnsafePtr as this would violate // our static assert MOZ_NON_OWNING_REF WorkerPrivate* mWorkerPrivate; nsCOMPtr 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> 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 mPerformanceCounter = MakeRefPtr(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 mEmbedderPolicy; Maybe 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 mTopLevelWorkerFinishedRunnableCount; Atomic mWorkerFinishedRunnableCount; nsTArray> mShutdownTasks MOZ_GUARDED_BY(mMutex); bool mRunShutdownTasksStarted MOZ_GUARDED_BY(mMutex) = false; bool mRunShutdownTasksFinished MOZ_GUARDED_BY(mMutex) = false; }; class AutoSyncLoopHolder { CheckedUnsafePtr mWorkerPrivate; nsCOMPtr 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 = 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__ */