/* -*- 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_scriptloader_h__ #define mozilla_dom_workers_scriptloader_h__ #include "js/loader/ScriptLoadRequest.h" #include "js/loader/ModuleLoaderBase.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/Maybe.h" #include "nsIContentPolicy.h" #include "nsStringFwd.h" #include "nsTArrayForwardDeclare.h" class nsIChannel; class nsICookieJarSettings; class nsILoadGroup; class nsIPrincipal; class nsIReferrerInfo; class nsIURI; namespace mozilla { class ErrorResult; namespace dom { class ClientInfo; class Document; struct WorkerLoadInfo; class WorkerPrivate; class SerializedStackHolder; enum WorkerScriptType { WorkerScript, DebuggerScript }; namespace workerinternals { namespace loader { class ScriptExecutorRunnable; class AbruptCancellationRunnable; class CachePromiseHandler; class CacheLoadHandler; class CacheCreator; class NetworkLoadHandler; /* * [DOMDOC] WorkerScriptLoader * * The WorkerScriptLoader is the primary class responsible for loading all * Workers, including: ServiceWorkers, SharedWorkers, RemoteWorkers, and * dedicated Workers. Our implementation also includes a subtype of dedicated * workers: ChromeWorker, which exposes information that isn't normally * accessible on a dedicated worker. See [1] for more information. * * Due to constraints around fetching, this class currently delegates the * "Fetch" portion of its work load to the main thread. Unlike the DOM * ScriptLoader, the WorkerScriptLoader is not persistent and is not reused for * subsequent loads. That means for each iteration of loading (for example, * loading the main script, followed by a load triggered by ImportScripts), we * recreate this class, and handle the case independently. * * The flow of requests across the boundaries looks like this: * * +----------------------------+ * | new WorkerScriptLoader(..) | * +----------------------------+ * | * | Create the loader, along with the ScriptLoadRequests * | call DispatchLoadScripts() * | * ##################################################################### * Enter Main thread * ##################################################################### * | * V * +---------------+ For each request: Is a normal Worker? * | LoadScripts() |--------------------------------------+ * +---------------+ | * | | * | For each request: Is a ServiceWorker? | * | | * V V * +==================+ No script in cache? +====================+ * | CacheLoadHandler |------------------------>| NetworkLoadHandler | * +==================+ +====================+ * : : * : Loaded from Cache : Loaded by Network * : +--------------------+ : * +----------->| OnStreamComplete() |<---------+ * +--------------------+ * | * | A request is ready, is it in post order? * | * | call DispatchPendingProcessRequests() * | This creates ScriptExecutorRunnable * | * ##################################################################### * Enter worker thread * ##################################################################### * | * | * V * +-------------------------+ All Scripts Executed? * | ProcessPendingScripts() |-------------+ * +-------------------------+ | * | * | yes. * | * V * +------------------------+ * | ShutdownScriptLoader() | * +------------------------+ */ class WorkerScriptLoader : public JS::loader::ScriptLoaderInterface, public nsINamed { friend class ScriptExecutorRunnable; friend class AbruptCancellationRunnable; friend class CachePromiseHandler; friend class CacheLoadHandler; friend class CacheCreator; friend class NetworkLoadHandler; RefPtr mWorkerRef; UniquePtr mOriginStack; nsString mOriginStackJSON; nsCOMPtr mSyncLoopTarget; ScriptLoadRequestList mLoadingRequests; ScriptLoadRequestList mLoadedRequests; Maybe mController; WorkerScriptType mWorkerScriptType; ErrorResult& mRv; bool mExecutionAborted = false; bool mMutedErrorFlag = false; // Worker cancellation related Mutex // // Modified on the worker thread. // It is ok to *read* this without a lock on the worker. // Main thread must always acquire a lock. bool mCleanedUp MOZ_GUARDED_BY( mCleanUpLock); // To specify if the cleanUp() has been done. Mutex& CleanUpLock() MOZ_RETURN_CAPABILITY(mCleanUpLock) { return mCleanUpLock; } bool CleanedUp() const MOZ_REQUIRES(mCleanUpLock) { mCleanUpLock.AssertCurrentThreadOwns(); return mCleanedUp; } // Ensure the worker and the main thread won't race to access |mCleanedUp|. // Should be a MutexSingleWriter, but that causes a lot of issues when you // expose the lock via Lock(). Mutex mCleanUpLock; public: NS_DECL_THREADSAFE_ISUPPORTS WorkerScriptLoader(WorkerPrivate* aWorkerPrivate, UniquePtr aOriginStack, nsIEventTarget* aSyncLoopTarget, WorkerScriptType aWorkerScriptType, ErrorResult& aRv); void CancelMainThreadWithBindingAborted( nsTArray&& aContextList); void CreateScriptRequests(const nsTArray& aScriptURLs, const mozilla::Encoding* aDocumentEncoding, bool aIsMainScript); already_AddRefed CreateScriptLoadRequest( const nsString& aScriptURL, const mozilla::Encoding* aDocumentEncoding, bool aIsMainScript); bool DispatchLoadScript(ScriptLoadRequest* aRequest); bool DispatchLoadScripts(); protected: nsIURI* GetBaseURI() const override; nsIURI* GetInitialBaseURI(); void MaybeExecuteFinishedScripts(ScriptLoadRequest* aRequest); void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest); bool StoreCSP(); bool ProcessPendingRequests(JSContext* aCx); bool AllScriptsExecuted() { return mLoadingRequests.isEmpty() && mLoadedRequests.isEmpty(); } nsresult OnStreamComplete(ScriptLoadRequest* aRequest, nsresult aStatus); bool IsDebuggerScript() const { return mWorkerScriptType == DebuggerScript; } void SetController(const Maybe& aDescriptor) { mController = aDescriptor; } Maybe& GetController() { return mController; } bool IsCancelled() { return mCancelMainThread.isSome(); } nsresult GetCancelResult() { return (IsCancelled()) ? mCancelMainThread.ref() : NS_OK; } void CancelMainThread(nsresult aCancelResult, nsTArray* aContextList); nsresult LoadScripts(nsTArray&& aContextList); nsresult LoadScript(ScriptLoadRequest* aRequest); void ShutdownScriptLoader(bool aResult, bool aMutedError); void AbruptShutdown(); private: ~WorkerScriptLoader() = default; NS_IMETHOD GetName(nsACString& aName) override { aName.AssignLiteral("WorkerScriptLoader"); return NS_OK; } void TryShutdown(); void DispatchAbruptShutdown(); nsTArray GetLoadingList(); nsIGlobalObject* GetGlobal(); void LoadingFinished(ScriptLoadRequest* aRequest, nsresult aRv); void DispatchMaybeMoveToLoadedList(ScriptLoadRequest* aRequest); bool EvaluateScript(JSContext* aCx, ScriptLoadRequest* aRequest); nsresult FillCompileOptionsForRequest( JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions, JS::MutableHandle aIntroductionScript) override; void ReportErrorToConsole(ScriptLoadRequest* aRequest, nsresult aResult) const override; // Only used by import maps, crash if we get here. void ReportWarningToConsole( ScriptLoadRequest* aRequest, const char* aMessageName, const nsTArray& aParams = nsTArray()) const override { MOZ_CRASH("Import maps have not been implemented for this context"); } void LogExceptionToConsole(JSContext* aCx, WorkerPrivate* aWorkerPrivate); Maybe mCancelMainThread; }; } // namespace loader nsresult ChannelFromScriptURLMainThread( nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup, nsIURI* aScriptURL, const Maybe& aClientInfo, nsContentPolicyType aContentPolicyType, nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo, nsIChannel** aChannel); nsresult ChannelFromScriptURLWorkerThread(JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL, WorkerLoadInfo& aLoadInfo); void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult, const nsAString& aScriptURL); void LoadMainScript(WorkerPrivate* aWorkerPrivate, UniquePtr aOriginStack, const nsAString& aScriptURL, WorkerScriptType aWorkerScriptType, ErrorResult& aRv, const mozilla::Encoding* aDocumentEncoding); void Load(WorkerPrivate* aWorkerPrivate, UniquePtr aOriginStack, const nsTArray& aScriptURLs, WorkerScriptType aWorkerScriptType, ErrorResult& aRv); } // namespace workerinternals } // namespace dom } // namespace mozilla #endif /* mozilla_dom_workers_scriptloader_h__ */