/* -*- 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 js_loader_ModuleLoaderBase_h #define js_loader_ModuleLoaderBase_h #include "LoadedScript.h" #include "ScriptLoadRequest.h" #include "ImportMap.h" #include "js/TypeDecls.h" // JS::MutableHandle, JS::Handle, JS::Root #include "js/Modules.h" #include "nsRefPtrHashtable.h" #include "nsCOMArray.h" #include "nsCOMPtr.h" #include "nsILoadInfo.h" // nsSecurityFlags #include "nsINode.h" // nsIURI #include "nsThreadUtils.h" // GetMainThreadSerialEventTarget #include "nsURIHashKey.h" #include "mozilla/CORSMode.h" #include "mozilla/dom/JSExecutionContext.h" #include "mozilla/MaybeOneOf.h" #include "mozilla/MozPromise.h" #include "mozilla/UniquePtr.h" #include "ResolveResult.h" class nsIURI; namespace mozilla { class LazyLogModule; union Utf8Unit; } // namespace mozilla namespace JS { class CompileOptions; template class SourceText; namespace loader { class ModuleLoaderBase; class ModuleLoadRequest; class ModuleScript; /* * [DOMDOC] Shared Classic/Module Script Methods * * The ScriptLoaderInterface defines the shared methods needed by both * ScriptLoaders (loading classic scripts) and ModuleLoaders (loading module * scripts). These include: * * * Error Logging * * Generating the compile options * * Optional: Bytecode Encoding * * ScriptLoaderInterface does not provide any implementations. * It enables the ModuleLoaderBase to reference back to the behavior implemented * by a given ScriptLoader. * * Not all methods will be used by all ModuleLoaders. For example, Bytecode * Encoding does not apply to workers, as we only work with source text there. * Fully virtual methods are implemented by all. * */ class ScriptLoaderInterface : public nsISupports { public: // alias common classes using ScriptFetchOptions = JS::loader::ScriptFetchOptions; using ScriptKind = JS::loader::ScriptKind; using ScriptLoadRequest = JS::loader::ScriptLoadRequest; using ScriptLoadRequestList = JS::loader::ScriptLoadRequestList; using ModuleLoadRequest = JS::loader::ModuleLoadRequest; virtual ~ScriptLoaderInterface() = default; // In some environments, we will need to default to a base URI virtual nsIURI* GetBaseURI() const = 0; virtual void ReportErrorToConsole(ScriptLoadRequest* aRequest, nsresult aResult) const = 0; virtual void ReportWarningToConsole( ScriptLoadRequest* aRequest, const char* aMessageName, const nsTArray& aParams = nsTArray()) const = 0; // Fill in CompileOptions, as well as produce the introducer script for // subsequent calls to UpdateDebuggerMetadata virtual nsresult FillCompileOptionsForRequest( JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions, JS::MutableHandle aIntroductionScript) = 0; virtual void MaybePrepareModuleForBytecodeEncodingBeforeExecute( JSContext* aCx, ModuleLoadRequest* aRequest) {} virtual nsresult MaybePrepareModuleForBytecodeEncodingAfterExecute( ModuleLoadRequest* aRequest, nsresult aRv) { return NS_OK; } virtual void MaybeTriggerBytecodeEncoding() {} }; /* * [DOMDOC] Module Loading * * ModuleLoaderBase provides support for loading module graphs as defined in the * EcmaScript specification. A derived module loader class must be created for a * specific use case (for example loading HTML module scripts). The derived * class provides operations such as fetching of source code and scheduling of * module execution. * * Module loading works in terms of 'requests' which hold data about modules as * they move through the loading process. There may be more than one load * request active for a single module URI, but the module is only loaded * once. This is achieved by tracking all fetching and fetched modules in the * module map. * * The module map is made up of two parts. A module that has been requested but * has not yet loaded is represented by a promise in the mFetchingModules map. A * module which has been loaded is represented by a ModuleScript in the * mFetchedModules map. * * Module loading typically works as follows: * * 1. The client ensures there is an instance of the derived module loader * class for its global or creates one if necessary. * * 2. The client creates a ModuleLoadRequest object for the module to load and * calls the loader's StartModuleLoad() method. This is a top-level request, * i.e. not an import. * * 3. The module loader calls the virtual method CanStartLoad() to check * whether the request should be loaded. * * 4. If the module is not already present in the module map, the loader calls * the virtual method StartFetch() to set up an asynchronous operation to * fetch the module source. * * 5. When the fetch operation is complete, the derived loader calls * OnFetchComplete() passing an error code to indicate success or failure. * * 6. On success, the loader attempts to create a module script by calling the * virtual CompileFetchedModule() method. * * 7. If compilation is successful, the loader creates load requests for any * imported modules if present. If so, the process repeats from step 3. * * 8. When a load request is completed, the virtual OnModuleLoadComplete() * method is called. This is called for the top-level request and import * requests. * * 9. The client calls InstantiateModuleGraph() for the top-level request. This * links the loaded module graph. * * 10. The client calls EvaluateModule() to execute the top-level module. */ class ModuleLoaderBase : public nsISupports { private: using GenericNonExclusivePromise = mozilla::GenericNonExclusivePromise; using GenericPromise = mozilla::GenericPromise; // Module map nsRefPtrHashtable mFetchingModules; nsRefPtrHashtable mFetchedModules; // List of dynamic imports that are currently being loaded. ScriptLoadRequestList mDynamicImportRequests; nsCOMPtr mGlobalObject; // https://html.spec.whatwg.org/multipage/webappapis.html#import-maps-allowed // // Each Window has an import maps allowed boolean, initially true. bool mImportMapsAllowed = true; protected: // Event handler used to process MozPromise actions, used internally to wait // for fetches to finish and for imports to become avilable. nsCOMPtr mEventTarget; RefPtr mLoader; mozilla::UniquePtr mImportMap; virtual ~ModuleLoaderBase(); public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(ModuleLoaderBase) explicit ModuleLoaderBase(ScriptLoaderInterface* aLoader, nsIGlobalObject* aGlobalObject, nsISerialEventTarget* aEventTarget = mozilla::GetMainThreadSerialEventTarget()); // Called to break cycles during shutdown to prevent memory leaks. void Shutdown(); virtual nsIURI* GetBaseURI() const { return mLoader->GetBaseURI(); }; using LoadedScript = JS::loader::LoadedScript; using ScriptFetchOptions = JS::loader::ScriptFetchOptions; using ScriptLoadRequest = JS::loader::ScriptLoadRequest; using ModuleLoadRequest = JS::loader::ModuleLoadRequest; using MaybeSourceText = mozilla::MaybeOneOf, JS::SourceText>; // Methods that must be implemented by an extending class. These are called // internally by ModuleLoaderBase. private: // Create a module load request for a static module import. virtual already_AddRefed CreateStaticImport( nsIURI* aURI, ModuleLoadRequest* aParent) = 0; // Called by HostImportModuleDynamically hook. virtual already_AddRefed CreateDynamicImport( JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript, JS::Handle aReferencingPrivate, JS::Handle aSpecifier, JS::Handle aPromise) = 0; // Check whether we can load a module. May return false with |aRvOut| set to // NS_OK to abort load without returning an error. virtual bool CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) = 0; // Start the process of fetching module source (or bytecode). This is only // called if CanStartLoad returned true. virtual nsresult StartFetch(ModuleLoadRequest* aRequest) = 0; // Create a JS module for a fetched module request. This might compile source // text or decode cached bytecode. virtual nsresult CompileFetchedModule( JSContext* aCx, JS::Handle aGlobal, JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest, JS::MutableHandle aModuleOut) = 0; // Called when a module script has been loaded, including imports. virtual void OnModuleLoadComplete(ModuleLoadRequest* aRequest) = 0; virtual bool IsModuleEvaluationAborted(ModuleLoadRequest* aRequest) { return false; } // Get the error message when resolving failed. The default is to call // nsContentUtils::FormatLoalizedString. But currently // nsContentUtils::FormatLoalizedString cannot be called on a worklet thread, // see bug 1808301. So WorkletModuleLoader will override this function to // get the error message. virtual nsresult GetResolveFailureMessage(ResolveError aError, const nsAString& aSpecifier, nsAString& aResult); // Public API methods. public: ScriptLoaderInterface* GetScriptLoaderInterface() const { return mLoader; } nsIGlobalObject* GetGlobalObject() const { return mGlobalObject; } bool HasPendingDynamicImports() const; void CancelDynamicImport(ModuleLoadRequest* aRequest, nsresult aResult); #ifdef DEBUG bool HasDynamicImport(const ModuleLoadRequest* aRequest) const; #endif // Start a load for a module script URI. Returns immediately if the module is // already being loaded. nsresult StartModuleLoad(ModuleLoadRequest* aRequest); nsresult RestartModuleLoad(ModuleLoadRequest* aRequest); // Notify the module loader when a fetch started by StartFetch() completes. nsresult OnFetchComplete(ModuleLoadRequest* aRequest, nsresult aRv); // Link the module and all its imports. This must occur prior to evaluation. bool InstantiateModuleGraph(ModuleLoadRequest* aRequest); // Executes the module. // Implements https://html.spec.whatwg.org/#run-a-module-script nsresult EvaluateModule(ModuleLoadRequest* aRequest); // Evaluate a module in the given context. Does not push an entry to the // execution stack. nsresult EvaluateModuleInContext(JSContext* aCx, ModuleLoadRequest* aRequest, JS::ModuleErrorBehaviour errorBehaviour); void StartDynamicImport(ModuleLoadRequest* aRequest); void ProcessDynamicImport(ModuleLoadRequest* aRequest); void CancelAndClearDynamicImports(); // Process