Bug 1864587 - [angle] Vendor mozilla/angle/firefox-123. r=gfx-reviewers,aosmond
[gecko.git] / js / loader / ModuleLoaderBase.h
blob6341de3030b0e49fd88df19bbbc56699e8eb45d2
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef js_loader_ModuleLoaderBase_h
8 #define js_loader_ModuleLoaderBase_h
10 #include "LoadedScript.h"
11 #include "ScriptLoadRequest.h"
13 #include "ImportMap.h"
14 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
15 #include "js/TypeDecls.h" // JS::MutableHandle, JS::Handle, JS::Root
16 #include "js/Modules.h"
17 #include "nsRefPtrHashtable.h"
18 #include "nsCOMArray.h"
19 #include "nsCOMPtr.h"
20 #include "nsILoadInfo.h" // nsSecurityFlags
21 #include "nsINode.h" // nsIURI
22 #include "nsThreadUtils.h" // GetMainThreadSerialEventTarget
23 #include "nsURIHashKey.h"
24 #include "mozilla/CORSMode.h"
25 #include "mozilla/dom/JSExecutionContext.h"
26 #include "mozilla/MaybeOneOf.h"
27 #include "mozilla/UniquePtr.h"
28 #include "ResolveResult.h"
30 class nsIURI;
32 namespace mozilla {
34 class LazyLogModule;
35 union Utf8Unit;
37 } // namespace mozilla
39 namespace JS {
41 class CompileOptions;
43 template <typename UnitT>
44 class SourceText;
46 namespace loader {
48 class ModuleLoaderBase;
49 class ModuleLoadRequest;
50 class ModuleScript;
53 * [DOMDOC] Shared Classic/Module Script Methods
55 * The ScriptLoaderInterface defines the shared methods needed by both
56 * ScriptLoaders (loading classic scripts) and ModuleLoaders (loading module
57 * scripts). These include:
59 * * Error Logging
60 * * Generating the compile options
61 * * Optional: Bytecode Encoding
63 * ScriptLoaderInterface does not provide any implementations.
64 * It enables the ModuleLoaderBase to reference back to the behavior implemented
65 * by a given ScriptLoader.
67 * Not all methods will be used by all ModuleLoaders. For example, Bytecode
68 * Encoding does not apply to workers, as we only work with source text there.
69 * Fully virtual methods are implemented by all.
73 class ScriptLoaderInterface : public nsISupports {
74 public:
75 // alias common classes
76 using ScriptFetchOptions = JS::loader::ScriptFetchOptions;
77 using ScriptKind = JS::loader::ScriptKind;
78 using ScriptLoadRequest = JS::loader::ScriptLoadRequest;
79 using ScriptLoadRequestList = JS::loader::ScriptLoadRequestList;
80 using ModuleLoadRequest = JS::loader::ModuleLoadRequest;
82 virtual ~ScriptLoaderInterface() = default;
84 // In some environments, we will need to default to a base URI
85 virtual nsIURI* GetBaseURI() const = 0;
87 virtual void ReportErrorToConsole(ScriptLoadRequest* aRequest,
88 nsresult aResult) const = 0;
90 virtual void ReportWarningToConsole(
91 ScriptLoadRequest* aRequest, const char* aMessageName,
92 const nsTArray<nsString>& aParams = nsTArray<nsString>()) const = 0;
94 // Fill in CompileOptions, as well as produce the introducer script for
95 // subsequent calls to UpdateDebuggerMetadata
96 virtual nsresult FillCompileOptionsForRequest(
97 JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
98 JS::MutableHandle<JSScript*> aIntroductionScript) = 0;
100 virtual void MaybePrepareModuleForBytecodeEncodingBeforeExecute(
101 JSContext* aCx, ModuleLoadRequest* aRequest) {}
103 virtual nsresult MaybePrepareModuleForBytecodeEncodingAfterExecute(
104 ModuleLoadRequest* aRequest, nsresult aRv) {
105 return NS_OK;
108 virtual void MaybeTriggerBytecodeEncoding() {}
112 * [DOMDOC] Module Loading
114 * ModuleLoaderBase provides support for loading module graphs as defined in the
115 * EcmaScript specification. A derived module loader class must be created for a
116 * specific use case (for example loading HTML module scripts). The derived
117 * class provides operations such as fetching of source code and scheduling of
118 * module execution.
120 * Module loading works in terms of 'requests' which hold data about modules as
121 * they move through the loading process. There may be more than one load
122 * request active for a single module URI, but the module is only loaded
123 * once. This is achieved by tracking all fetching and fetched modules in the
124 * module map.
126 * The module map is made up of two parts. A module that has been requested but
127 * has not finished fetching is represented by an entry in the mFetchingModules
128 * map. A module which has been fetched and compiled is represented by a
129 * ModuleScript in the mFetchedModules map.
131 * Module loading typically works as follows:
133 * 1. The client ensures there is an instance of the derived module loader
134 * class for its global or creates one if necessary.
136 * 2. The client creates a ModuleLoadRequest object for the module to load and
137 * calls the loader's StartModuleLoad() method. This is a top-level request,
138 * i.e. not an import.
140 * 3. The module loader calls the virtual method CanStartLoad() to check
141 * whether the request should be loaded.
143 * 4. If the module is not already present in the module map, the loader calls
144 * the virtual method StartFetch() to set up an asynchronous operation to
145 * fetch the module source.
147 * 5. When the fetch operation is complete, the derived loader calls
148 * OnFetchComplete() passing an error code to indicate success or failure.
150 * 6. On success, the loader attempts to create a module script by calling the
151 * virtual CompileFetchedModule() method.
153 * 7. If compilation is successful, the loader creates load requests for any
154 * imported modules if present. If so, the process repeats from step 3.
156 * 8. When a load request is completed, the virtual OnModuleLoadComplete()
157 * method is called. This is called for the top-level request and import
158 * requests.
160 * 9. The client calls InstantiateModuleGraph() for the top-level request. This
161 * links the loaded module graph.
163 * 10. The client calls EvaluateModule() to execute the top-level module.
165 class ModuleLoaderBase : public nsISupports {
167 * The set of requests that are waiting for an ongoing fetch to complete.
169 class WaitingRequests final : public nsISupports {
170 virtual ~WaitingRequests() = default;
172 public:
173 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
174 NS_DECL_CYCLE_COLLECTION_CLASS(WaitingRequests)
176 nsTArray<RefPtr<ModuleLoadRequest>> mWaiting;
179 // Module map
180 nsRefPtrHashtable<nsURIHashKey, WaitingRequests> mFetchingModules;
181 nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
183 // List of dynamic imports that are currently being loaded.
184 ScriptLoadRequestList mDynamicImportRequests;
186 nsCOMPtr<nsIGlobalObject> mGlobalObject;
188 // https://html.spec.whatwg.org/multipage/webappapis.html#import-maps-allowed
190 // Each Window has an import maps allowed boolean, initially true.
191 bool mImportMapsAllowed = true;
193 protected:
194 // Event handler used to dispatch runnables, used internally to wait for
195 // fetches to finish and for imports to become avilable.
196 nsCOMPtr<nsISerialEventTarget> mEventTarget;
197 RefPtr<ScriptLoaderInterface> mLoader;
199 mozilla::UniquePtr<ImportMap> mImportMap;
201 virtual ~ModuleLoaderBase();
203 public:
204 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
205 NS_DECL_CYCLE_COLLECTION_CLASS(ModuleLoaderBase)
206 explicit ModuleLoaderBase(ScriptLoaderInterface* aLoader,
207 nsIGlobalObject* aGlobalObject,
208 nsISerialEventTarget* aEventTarget =
209 mozilla::GetMainThreadSerialEventTarget());
211 // Called to break cycles during shutdown to prevent memory leaks.
212 void Shutdown();
214 virtual nsIURI* GetBaseURI() const { return mLoader->GetBaseURI(); };
216 using LoadedScript = JS::loader::LoadedScript;
217 using ScriptFetchOptions = JS::loader::ScriptFetchOptions;
218 using ScriptLoadRequest = JS::loader::ScriptLoadRequest;
219 using ModuleLoadRequest = JS::loader::ModuleLoadRequest;
221 using MaybeSourceText =
222 mozilla::MaybeOneOf<JS::SourceText<char16_t>, JS::SourceText<Utf8Unit>>;
224 // Methods that must be implemented by an extending class. These are called
225 // internally by ModuleLoaderBase.
227 private:
228 // Create a module load request for a static module import.
229 virtual already_AddRefed<ModuleLoadRequest> CreateStaticImport(
230 nsIURI* aURI, ModuleLoadRequest* aParent) = 0;
232 // Called by HostImportModuleDynamically hook.
233 virtual already_AddRefed<ModuleLoadRequest> CreateDynamicImport(
234 JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
235 JS::Handle<JSString*> aSpecifier, JS::Handle<JSObject*> aPromise) = 0;
237 // Check whether we can load a module. May return false with |aRvOut| set to
238 // NS_OK to abort load without returning an error.
239 virtual bool CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) = 0;
241 // Start the process of fetching module source (or bytecode). This is only
242 // called if CanStartLoad returned true.
243 virtual nsresult StartFetch(ModuleLoadRequest* aRequest) = 0;
245 // Create a JS module for a fetched module request. This might compile source
246 // text or decode cached bytecode.
247 virtual nsresult CompileFetchedModule(
248 JSContext* aCx, JS::Handle<JSObject*> aGlobal,
249 JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
250 JS::MutableHandle<JSObject*> aModuleOut) = 0;
252 // Called when a module script has been loaded, including imports.
253 virtual void OnModuleLoadComplete(ModuleLoadRequest* aRequest) = 0;
255 virtual bool IsModuleEvaluationAborted(ModuleLoadRequest* aRequest) {
256 return false;
259 // Get the error message when resolving failed. The default is to call
260 // nsContentUtils::FormatLoalizedString. But currently
261 // nsContentUtils::FormatLoalizedString cannot be called on a worklet thread,
262 // see bug 1808301. So WorkletModuleLoader will override this function to
263 // get the error message.
264 virtual nsresult GetResolveFailureMessage(ResolveError aError,
265 const nsAString& aSpecifier,
266 nsAString& aResult);
268 // Public API methods.
270 public:
271 ScriptLoaderInterface* GetScriptLoaderInterface() const { return mLoader; }
273 nsIGlobalObject* GetGlobalObject() const { return mGlobalObject; }
275 bool HasPendingDynamicImports() const;
276 void CancelDynamicImport(ModuleLoadRequest* aRequest, nsresult aResult);
277 #ifdef DEBUG
278 bool HasDynamicImport(const ModuleLoadRequest* aRequest) const;
279 #endif
281 // Start a load for a module script URI. Returns immediately if the module is
282 // already being loaded.
283 nsresult StartModuleLoad(ModuleLoadRequest* aRequest);
284 nsresult RestartModuleLoad(ModuleLoadRequest* aRequest);
286 // Notify the module loader when a fetch started by StartFetch() completes.
287 nsresult OnFetchComplete(ModuleLoadRequest* aRequest, nsresult aRv);
289 // Link the module and all its imports. This must occur prior to evaluation.
290 bool InstantiateModuleGraph(ModuleLoadRequest* aRequest);
292 // Executes the module.
293 // Implements https://html.spec.whatwg.org/#run-a-module-script
294 nsresult EvaluateModule(ModuleLoadRequest* aRequest);
296 // Evaluate a module in the given context. Does not push an entry to the
297 // execution stack.
298 nsresult EvaluateModuleInContext(JSContext* aCx, ModuleLoadRequest* aRequest,
299 JS::ModuleErrorBehaviour errorBehaviour);
301 void StartDynamicImport(ModuleLoadRequest* aRequest);
302 void ProcessDynamicImport(ModuleLoadRequest* aRequest);
303 void CancelAndClearDynamicImports();
305 // Process <script type="importmap">
306 mozilla::UniquePtr<ImportMap> ParseImportMap(ScriptLoadRequest* aRequest);
308 // Implements
309 // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
310 void RegisterImportMap(mozilla::UniquePtr<ImportMap> aImportMap);
312 bool HasImportMapRegistered() const { return bool(mImportMap); }
314 // Getter for mImportMapsAllowed.
315 bool IsImportMapAllowed() const { return mImportMapsAllowed; }
316 // https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps
317 void DisallowImportMaps() { mImportMapsAllowed = false; }
319 // Returns true if the module for given URL is already fetched.
320 bool IsModuleFetched(nsIURI* aURL) const;
322 nsresult GetFetchedModuleURLs(nsTArray<nsCString>& aURLs);
324 // Removed a fetched module from the module map. Asserts that the module is
325 // unlinked. Extreme care should be taken when calling this method.
326 bool RemoveFetchedModule(nsIURI* aURL);
328 // Internal methods.
330 private:
331 friend class JS::loader::ModuleLoadRequest;
333 static ModuleLoaderBase* GetCurrentModuleLoader(JSContext* aCx);
334 static LoadedScript* GetLoadedScriptOrNull(
335 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate);
337 static void EnsureModuleHooksInitialized();
339 static JSObject* HostResolveImportedModule(
340 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
341 JS::Handle<JSObject*> aModuleRequest);
342 static bool HostPopulateImportMeta(JSContext* aCx,
343 JS::Handle<JS::Value> aReferencingPrivate,
344 JS::Handle<JSObject*> aMetaObject);
345 static bool ImportMetaResolve(JSContext* cx, unsigned argc, Value* vp);
346 static JSString* ImportMetaResolveImpl(
347 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
348 JS::Handle<JSString*> aSpecifier);
349 static bool HostImportModuleDynamically(
350 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
351 JS::Handle<JSObject*> aModuleRequest, JS::Handle<JSObject*> aPromise);
352 static bool HostGetSupportedImportAssertions(
353 JSContext* aCx, JS::ImportAssertionVector& aValues);
355 ResolveResult ResolveModuleSpecifier(LoadedScript* aScript,
356 const nsAString& aSpecifier);
358 nsresult HandleResolveFailure(JSContext* aCx, LoadedScript* aScript,
359 const nsAString& aSpecifier,
360 ResolveError aError, uint32_t aLineNumber,
361 JS::ColumnNumberOneOrigin aColumnNumber,
362 JS::MutableHandle<JS::Value> aErrorOut);
364 enum class RestartRequest { No, Yes };
365 nsresult StartOrRestartModuleLoad(ModuleLoadRequest* aRequest,
366 RestartRequest aRestart);
368 bool ModuleMapContainsURL(nsIURI* aURL) const;
369 bool IsModuleFetching(nsIURI* aURL) const;
370 void WaitForModuleFetch(ModuleLoadRequest* aRequest);
371 void SetModuleFetchStarted(ModuleLoadRequest* aRequest);
373 ModuleScript* GetFetchedModule(nsIURI* aURL) const;
375 JS::Value FindFirstParseError(ModuleLoadRequest* aRequest);
376 static nsresult InitDebuggerDataForModuleGraph(JSContext* aCx,
377 ModuleLoadRequest* aRequest);
378 nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
379 nsCOMArray<nsIURI>* aUrlsOut);
381 void SetModuleFetchFinishedAndResumeWaitingRequests(
382 ModuleLoadRequest* aRequest, nsresult aResult);
383 void ResumeWaitingRequests(WaitingRequests* aWaitingRequests, bool aSuccess);
384 void ResumeWaitingRequest(ModuleLoadRequest* aRequest, bool aSuccess);
386 void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
388 void StartFetchingModuleAndDependencies(ModuleLoadRequest* aParent,
389 nsIURI* aURI);
391 void InstantiateAndEvaluateDynamicImport(ModuleLoadRequest* aRequest);
394 * Shorthand Wrapper for JSAPI FinishDynamicImport function for the reject
395 * case where we do not have `aEvaluationPromise`. As there is no evaluation
396 * Promise, JS::FinishDynamicImport will always reject.
398 * @param aRequest
399 * The module load request for the dynamic module.
400 * @param aResult
401 * The result of running ModuleEvaluate -- If this is successful, then
402 * we can await the associated EvaluationPromise.
404 void FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
405 nsresult aResult);
408 * Wrapper for JSAPI FinishDynamicImport function. Takes an optional argument
409 * `aEvaluationPromise` which, if null, exits early.
411 * This is the Top Level Await version, which works with modules which return
412 * promises.
414 * @param aCX
415 * The JSContext for the module.
416 * @param aRequest
417 * The module load request for the dynamic module.
418 * @param aResult
419 * The result of running ModuleEvaluate -- If this is successful, then
420 * we can await the associated EvaluationPromise.
421 * @param aEvaluationPromise
422 * The evaluation promise returned from evaluating the module. If this
423 * is null, JS::FinishDynamicImport will reject the dynamic import
424 * module promise.
426 static void FinishDynamicImport(JSContext* aCx, ModuleLoadRequest* aRequest,
427 nsresult aResult,
428 JS::Handle<JSObject*> aEvaluationPromise);
430 void RemoveDynamicImport(ModuleLoadRequest* aRequest);
432 nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
434 // The slot stored in ImportMetaResolve function.
435 enum { ModulePrivateSlot = 0, SlotCount };
437 // The number of args in ImportMetaResolve.
438 static const uint32_t ImportMetaResolveNumArgs = 1;
439 // The index of the 'specifier' argument in ImportMetaResolve.
440 static const uint32_t ImportMetaResolveSpecifierArg = 0;
442 public:
443 static mozilla::LazyLogModule gCspPRLog;
444 static mozilla::LazyLogModule gModuleLoaderBaseLog;
447 } // namespace loader
448 } // namespace JS
450 #endif // js_loader_ModuleLoaderBase_h