summaryrefslogtreecommitdiffstats
path: root/js/loader/ModuleLoaderBase.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/loader/ModuleLoaderBase.h
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/loader/ModuleLoaderBase.h')
-rw-r--r--js/loader/ModuleLoaderBase.h503
1 files changed, 503 insertions, 0 deletions
diff --git a/js/loader/ModuleLoaderBase.h b/js/loader/ModuleLoaderBase.h
new file mode 100644
index 0000000000..2c2c385a30
--- /dev/null
+++ b/js/loader/ModuleLoaderBase.h
@@ -0,0 +1,503 @@
+/* -*- 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/ColumnNumber.h" // JS::ColumnNumberOneOrigin
+#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/Attributes.h" // MOZ_RAII
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/JSExecutionContext.h"
+#include "mozilla/MaybeOneOf.h"
+#include "mozilla/UniquePtr.h"
+#include "ResolveResult.h"
+
+class nsIURI;
+
+namespace mozilla {
+
+class LazyLogModule;
+union Utf8Unit;
+
+} // namespace mozilla
+
+namespace JS {
+
+class CompileOptions;
+
+template <typename UnitT>
+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<nsString>& aParams = nsTArray<nsString>()) 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<JSScript*> 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 finished fetching is represented by an entry in the mFetchingModules
+ * map. A module which has been fetched and compiled 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 {
+ /*
+ * The set of requests that are waiting for an ongoing fetch to complete.
+ */
+ class WaitingRequests final : public nsISupports {
+ virtual ~WaitingRequests() = default;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(WaitingRequests)
+
+ nsTArray<RefPtr<ModuleLoadRequest>> mWaiting;
+ };
+
+ // Module map
+ nsRefPtrHashtable<nsURIHashKey, WaitingRequests> mFetchingModules;
+ nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
+
+ // List of dynamic imports that are currently being loaded.
+ ScriptLoadRequestList mDynamicImportRequests;
+
+ nsCOMPtr<nsIGlobalObject> mGlobalObject;
+
+ // If non-null, this module loader is overridden by the module loader pointed
+ // by mOverriddenBy.
+ // See ModuleLoaderBase::GetCurrentModuleLoader for more details.
+ RefPtr<ModuleLoaderBase> mOverriddenBy;
+
+ // 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:
+ RefPtr<ScriptLoaderInterface> mLoader;
+
+ mozilla::UniquePtr<ImportMap> mImportMap;
+
+ virtual ~ModuleLoaderBase();
+
+#ifdef DEBUG
+ const ScriptLoadRequestList& DynamicImportRequests() const {
+ return mDynamicImportRequests;
+ }
+#endif
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(ModuleLoaderBase)
+ explicit ModuleLoaderBase(ScriptLoaderInterface* aLoader,
+ nsIGlobalObject* aGlobalObject);
+
+ // 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<char16_t>, JS::SourceText<Utf8Unit>>;
+
+ // 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<ModuleLoadRequest> CreateStaticImport(
+ nsIURI* aURI, ModuleLoadRequest* aParent) = 0;
+
+ // Called by HostImportModuleDynamically hook.
+ virtual already_AddRefed<ModuleLoadRequest> CreateDynamicImport(
+ JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
+ JS::Handle<JSString*> aSpecifier, JS::Handle<JSObject*> aPromise) = 0;
+
+ // Called when dynamic import started successfully.
+ virtual void OnDynamicImportStarted(ModuleLoadRequest* aRequest) {}
+
+ // 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<JSObject*> aGlobal,
+ JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
+ JS::MutableHandle<JSObject*> 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 HasFetchingModules() const;
+
+ 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);
+
+ nsresult StartDynamicImport(ModuleLoadRequest* aRequest);
+ void ProcessDynamicImport(ModuleLoadRequest* aRequest);
+ void CancelAndClearDynamicImports();
+
+ // Process <script type="importmap">
+ mozilla::UniquePtr<ImportMap> ParseImportMap(ScriptLoadRequest* aRequest);
+
+ // Implements
+ // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
+ void RegisterImportMap(mozilla::UniquePtr<ImportMap> aImportMap);
+
+ bool HasImportMapRegistered() const { return bool(mImportMap); }
+
+ // Getter for mImportMapsAllowed.
+ bool IsImportMapAllowed() const { return mImportMapsAllowed; }
+ // https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps
+ void DisallowImportMaps() { mImportMapsAllowed = false; }
+
+ // Returns true if the module for given URL is already fetched.
+ bool IsModuleFetched(nsIURI* aURL) const;
+
+ nsresult GetFetchedModuleURLs(nsTArray<nsCString>& aURLs);
+
+ // Removed a fetched module from the module map. Asserts that the module is
+ // unlinked. Extreme care should be taken when calling this method.
+ bool RemoveFetchedModule(nsIURI* aURL);
+
+ // Override the module loader with given loader until ResetOverride is called.
+ // While overridden, ModuleLoaderBase::GetCurrentModuleLoader returns aLoader.
+ //
+ // This is used by mozJSModuleLoader to temporarily override the global's
+ // module loader with SyncModuleLoader while importing a module graph
+ // synchronously.
+ void SetOverride(ModuleLoaderBase* aLoader);
+
+ // Returns true if SetOverride was called.
+ bool IsOverridden();
+
+ // Returns true if SetOverride was called with aLoader.
+ bool IsOverriddenBy(ModuleLoaderBase* aLoader);
+
+ void ResetOverride();
+
+ // Copy fetched modules to `aDest`.
+ // `this` shouldn't have any fetching.
+ // `aDest` shouldn't have any fetching or fetched modules.
+ //
+ // This is used when starting sync module load, to replicate the module cache
+ // in the sync module loader pointed by `aDest`.
+ void CopyModulesTo(ModuleLoaderBase* aDest);
+
+ // Move all fetched modules to `aDest`.
+ // Both `this` and `aDest` shouldn't have any fetching.
+ //
+ // This is used when finishing sync module load, to reflect the loaded modules
+ // to the async module loader pointed by `aDest`.
+ void MoveModulesTo(ModuleLoaderBase* aDest);
+
+ // Internal methods.
+
+ private:
+ friend class JS::loader::ModuleLoadRequest;
+
+ static ModuleLoaderBase* GetCurrentModuleLoader(JSContext* aCx);
+ static LoadedScript* GetLoadedScriptOrNull(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate);
+
+ static void EnsureModuleHooksInitialized();
+
+ static JSObject* HostResolveImportedModule(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSObject*> aModuleRequest);
+ static bool HostPopulateImportMeta(JSContext* aCx,
+ JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSObject*> aMetaObject);
+ static bool ImportMetaResolve(JSContext* cx, unsigned argc, Value* vp);
+ static JSString* ImportMetaResolveImpl(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSString*> aSpecifier);
+ static bool HostImportModuleDynamically(
+ JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
+ JS::Handle<JSObject*> aModuleRequest, JS::Handle<JSObject*> aPromise);
+
+ ResolveResult ResolveModuleSpecifier(LoadedScript* aScript,
+ const nsAString& aSpecifier);
+
+ nsresult HandleResolveFailure(JSContext* aCx, LoadedScript* aScript,
+ const nsAString& aSpecifier,
+ ResolveError aError, uint32_t aLineNumber,
+ JS::ColumnNumberOneOrigin aColumnNumber,
+ JS::MutableHandle<JS::Value> aErrorOut);
+
+ enum class RestartRequest { No, Yes };
+ nsresult StartOrRestartModuleLoad(ModuleLoadRequest* aRequest,
+ RestartRequest aRestart);
+
+ bool ModuleMapContainsURL(nsIURI* aURL) const;
+ bool IsModuleFetching(nsIURI* aURL) const;
+ void WaitForModuleFetch(ModuleLoadRequest* aRequest);
+ void SetModuleFetchStarted(ModuleLoadRequest* aRequest);
+
+ ModuleScript* GetFetchedModule(nsIURI* aURL) const;
+
+ JS::Value FindFirstParseError(ModuleLoadRequest* aRequest);
+ static nsresult InitDebuggerDataForModuleGraph(JSContext* aCx,
+ ModuleLoadRequest* aRequest);
+ nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
+ nsCOMArray<nsIURI>* aUrlsOut);
+
+ void SetModuleFetchFinishedAndResumeWaitingRequests(
+ ModuleLoadRequest* aRequest, nsresult aResult);
+ void ResumeWaitingRequests(WaitingRequests* aWaitingRequests, bool aSuccess);
+ void ResumeWaitingRequest(ModuleLoadRequest* aRequest, bool aSuccess);
+
+ void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
+
+ void StartFetchingModuleAndDependencies(ModuleLoadRequest* aParent,
+ nsIURI* aURI);
+
+ void InstantiateAndEvaluateDynamicImport(ModuleLoadRequest* aRequest);
+
+ /**
+ * Shorthand Wrapper for JSAPI FinishDynamicImport function for the reject
+ * case where we do not have `aEvaluationPromise`. As there is no evaluation
+ * Promise, JS::FinishDynamicImport will always reject.
+ *
+ * @param aRequest
+ * The module load request for the dynamic module.
+ * @param aResult
+ * The result of running ModuleEvaluate -- If this is successful, then
+ * we can await the associated EvaluationPromise.
+ */
+ void FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
+ nsresult aResult);
+
+ /**
+ * Wrapper for JSAPI FinishDynamicImport function. Takes an optional argument
+ * `aEvaluationPromise` which, if null, exits early.
+ *
+ * This is the Top Level Await version, which works with modules which return
+ * promises.
+ *
+ * @param aCX
+ * The JSContext for the module.
+ * @param aRequest
+ * The module load request for the dynamic module.
+ * @param aResult
+ * The result of running ModuleEvaluate -- If this is successful, then
+ * we can await the associated EvaluationPromise.
+ * @param aEvaluationPromise
+ * The evaluation promise returned from evaluating the module. If this
+ * is null, JS::FinishDynamicImport will reject the dynamic import
+ * module promise.
+ */
+ static void FinishDynamicImport(JSContext* aCx, ModuleLoadRequest* aRequest,
+ nsresult aResult,
+ JS::Handle<JSObject*> aEvaluationPromise);
+
+ void RemoveDynamicImport(ModuleLoadRequest* aRequest);
+
+ nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
+
+ // The slot stored in ImportMetaResolve function.
+ enum { ModulePrivateSlot = 0, SlotCount };
+
+ // The number of args in ImportMetaResolve.
+ static const uint32_t ImportMetaResolveNumArgs = 1;
+ // The index of the 'specifier' argument in ImportMetaResolve.
+ static const uint32_t ImportMetaResolveSpecifierArg = 0;
+
+ public:
+ static mozilla::LazyLogModule gCspPRLog;
+ static mozilla::LazyLogModule gModuleLoaderBaseLog;
+};
+
+// Override the target module loader with given module loader while this
+// instance is on the stack.
+class MOZ_RAII AutoOverrideModuleLoader {
+ public:
+ AutoOverrideModuleLoader(ModuleLoaderBase* aTarget,
+ ModuleLoaderBase* aLoader);
+ ~AutoOverrideModuleLoader();
+
+ private:
+ RefPtr<ModuleLoaderBase> mTarget;
+};
+
+} // namespace loader
+} // namespace JS
+
+#endif // js_loader_ModuleLoaderBase_h