Backed out changeset 9d8b4c0b99ed (bug 1945683) for causing btime failures. CLOSED...
[gecko.git] / js / loader / ModuleLoaderBase.h
blob1278905c28df226ea547dd2ebfa6913cee915361
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/Attributes.h" // MOZ_RAII
25 #include "mozilla/CORSMode.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() {}
111 class ModuleMapKey : public PLDHashEntryHdr {
112 public:
113 using KeyType = const ModuleMapKey&;
114 using KeyTypePointer = const ModuleMapKey*;
116 ModuleMapKey(const nsIURI* aUri, const ModuleType aModuleType)
117 : mUri(const_cast<nsIURI*>(aUri)), mModuleType(aModuleType) {
118 MOZ_COUNT_CTOR(ModuleMapKey);
119 MOZ_ASSERT(aUri);
121 explicit ModuleMapKey(KeyTypePointer aOther)
122 : mUri(std::move(aOther->mUri)), mModuleType(aOther->mModuleType) {
123 MOZ_COUNT_CTOR(ModuleMapKey);
124 MOZ_ASSERT(mUri);
126 ModuleMapKey(ModuleMapKey&& aOther)
127 : mUri(std::move(aOther.mUri)), mModuleType(aOther.mModuleType) {
128 MOZ_COUNT_CTOR(ModuleMapKey);
129 MOZ_ASSERT(mUri);
131 MOZ_COUNTED_DTOR(ModuleMapKey)
133 bool KeyEquals(KeyTypePointer aKey) const {
134 if (mModuleType != aKey->mModuleType) {
135 return false;
138 bool eq;
139 if (NS_SUCCEEDED(mUri->Equals(aKey->mUri, &eq))) {
140 return eq;
143 return false;
146 static KeyTypePointer KeyToPointer(KeyType key) { return &key; }
148 static PLDHashNumber HashKey(KeyTypePointer aKey) {
149 MOZ_ASSERT(aKey->mUri);
150 nsAutoCString spec;
151 // This is based on `nsURIHashKey`, and it ignores GetSpec() failures, so
152 // do the same here.
153 (void)aKey->mUri->GetSpec(spec);
154 return mozilla::HashGeneric(mozilla::HashString(spec), aKey->mModuleType);
157 enum { ALLOW_MEMMOVE = true };
159 const nsCOMPtr<nsIURI> mUri;
160 const ModuleType mModuleType;
164 * [DOMDOC] Module Loading
166 * ModuleLoaderBase provides support for loading module graphs as defined in the
167 * EcmaScript specification. A derived module loader class must be created for a
168 * specific use case (for example loading HTML module scripts). The derived
169 * class provides operations such as fetching of source code and scheduling of
170 * module execution.
172 * Module loading works in terms of 'requests' which hold data about modules as
173 * they move through the loading process. There may be more than one load
174 * request active for a single module URI, but the module is only loaded
175 * once. This is achieved by tracking all fetching and fetched modules in the
176 * module map.
178 * The module map is made up of two parts. A module that has been requested but
179 * has not finished fetching is represented by an entry in the mFetchingModules
180 * map. A module which has been fetched and compiled is represented by a
181 * ModuleScript in the mFetchedModules map.
183 * Module loading typically works as follows:
185 * 1. The client ensures there is an instance of the derived module loader
186 * class for its global or creates one if necessary.
188 * 2. The client creates a ModuleLoadRequest object for the module to load and
189 * calls the loader's StartModuleLoad() method. This is a top-level request,
190 * i.e. not an import.
192 * 3. The module loader calls the virtual method CanStartLoad() to check
193 * whether the request should be loaded.
195 * 4. If the module is not already present in the module map, the loader calls
196 * the virtual method StartFetch() to set up an asynchronous operation to
197 * fetch the module source.
199 * 5. When the fetch operation is complete, the derived loader calls
200 * OnFetchComplete() passing an error code to indicate success or failure.
202 * 6. On success, the loader attempts to create a module script by calling the
203 * virtual CompileFetchedModule() method.
205 * 7. If compilation is successful, the loader creates load requests for any
206 * imported modules if present. If so, the process repeats from step 3.
208 * 8. When a load request is completed, the virtual OnModuleLoadComplete()
209 * method is called. This is called for the top-level request and import
210 * requests.
212 * 9. The client calls InstantiateModuleGraph() for the top-level request. This
213 * links the loaded module graph.
215 * 10. The client calls EvaluateModule() to execute the top-level module.
217 class ModuleLoaderBase : public nsISupports {
218 public:
219 // Alias common classes.
220 using LoadedScript = JS::loader::LoadedScript;
221 using ScriptFetchOptions = JS::loader::ScriptFetchOptions;
222 using ScriptLoadRequest = JS::loader::ScriptLoadRequest;
223 using ModuleLoadRequest = JS::loader::ModuleLoadRequest;
225 private:
227 * Represents an ongoing load operation for a URI initiated for one request
228 * and which may have other requests waiting for it to complete.
230 * These are tracked in the mFetchingModules map.
232 class LoadingRequest final : public nsISupports {
233 virtual ~LoadingRequest() = default;
235 public:
236 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
237 NS_DECL_CYCLE_COLLECTION_CLASS(LoadingRequest)
239 // The request that initiated the load and which is currently fetching or
240 // being compiled.
241 RefPtr<ModuleLoadRequest> mRequest;
243 // A list of any other requests for the same URI that are waiting for the
244 // initial load to complete. These will be resumed by ResumeWaitingRequests
245 // when that happens.
246 nsTArray<RefPtr<ModuleLoadRequest>> mWaiting;
249 // Module map
250 nsRefPtrHashtable<ModuleMapKey, LoadingRequest> mFetchingModules;
251 nsRefPtrHashtable<ModuleMapKey, ModuleScript> mFetchedModules;
253 // List of dynamic imports that are currently being loaded.
254 ScriptLoadRequestList mDynamicImportRequests;
256 nsCOMPtr<nsIGlobalObject> mGlobalObject;
258 // If non-null, this module loader is overridden by the module loader pointed
259 // by mOverriddenBy.
260 // See ModuleLoaderBase::GetCurrentModuleLoader for more details.
261 RefPtr<ModuleLoaderBase> mOverriddenBy;
263 // https://html.spec.whatwg.org/multipage/webappapis.html#import-maps-allowed
265 // Each Window has an import maps allowed boolean, initially true.
266 bool mImportMapsAllowed = true;
268 protected:
269 RefPtr<ScriptLoaderInterface> mLoader;
271 mozilla::UniquePtr<ImportMap> mImportMap;
273 virtual ~ModuleLoaderBase();
275 #ifdef DEBUG
276 const ScriptLoadRequestList& DynamicImportRequests() const {
277 return mDynamicImportRequests;
279 #endif
281 public:
282 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
283 NS_DECL_CYCLE_COLLECTION_CLASS(ModuleLoaderBase)
284 explicit ModuleLoaderBase(ScriptLoaderInterface* aLoader,
285 nsIGlobalObject* aGlobalObject);
287 // Called to break cycles during shutdown to prevent memory leaks.
288 void Shutdown();
290 virtual nsIURI* GetBaseURI() const { return mLoader->GetBaseURI(); };
292 using MaybeSourceText =
293 mozilla::MaybeOneOf<JS::SourceText<char16_t>, JS::SourceText<Utf8Unit>>;
295 // Methods that must be implemented by an extending class. These are called
296 // internally by ModuleLoaderBase.
298 private:
299 // Create a module load request for a static module import.
300 virtual already_AddRefed<ModuleLoadRequest> CreateStaticImport(
301 nsIURI* aURI, JS::ModuleType aModuleType, ModuleLoadRequest* aParent) = 0;
303 // Called by HostImportModuleDynamically hook.
304 virtual already_AddRefed<ModuleLoadRequest> CreateDynamicImport(
305 JSContext* aCx, nsIURI* aURI, JS::ModuleType aModuleType,
306 LoadedScript* aMaybeActiveScript, JS::Handle<JSString*> aSpecifier,
307 JS::Handle<JSObject*> aPromise) = 0;
309 virtual bool IsDynamicImportSupported() { return true; }
311 // Called when dynamic import started successfully.
312 virtual void OnDynamicImportStarted(ModuleLoadRequest* aRequest) {}
314 // Check whether we can load a module. May return false with |aRvOut| set to
315 // NS_OK to abort load without returning an error.
316 virtual bool CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) = 0;
318 // Start the process of fetching module source (or bytecode). This is only
319 // called if CanStartLoad returned true.
320 virtual nsresult StartFetch(ModuleLoadRequest* aRequest) = 0;
322 // Create a JS module for a fetched module request. This might compile source
323 // text or decode cached bytecode.
324 virtual nsresult CompileFetchedModule(
325 JSContext* aCx, JS::Handle<JSObject*> aGlobal,
326 JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
327 JS::MutableHandle<JSObject*> aModuleOut) = 0;
329 // Called when a module script has been loaded, including imports.
330 virtual void OnModuleLoadComplete(ModuleLoadRequest* aRequest) = 0;
332 virtual bool IsModuleEvaluationAborted(ModuleLoadRequest* aRequest) {
333 return false;
336 // Get the error message when resolving failed. The default is to call
337 // nsContentUtils::FormatLoalizedString. But currently
338 // nsContentUtils::FormatLoalizedString cannot be called on a worklet thread,
339 // see bug 1808301. So WorkletModuleLoader will override this function to
340 // get the error message.
341 virtual nsresult GetResolveFailureMessage(ResolveError aError,
342 const nsAString& aSpecifier,
343 nsAString& aResult);
345 // Public API methods.
347 public:
348 ScriptLoaderInterface* GetScriptLoaderInterface() const { return mLoader; }
350 nsIGlobalObject* GetGlobalObject() const { return mGlobalObject; }
352 bool HasFetchingModules() const;
354 bool HasPendingDynamicImports() const;
355 void CancelDynamicImport(ModuleLoadRequest* aRequest, nsresult aResult);
356 #ifdef DEBUG
357 bool HasDynamicImport(const ModuleLoadRequest* aRequest) const;
358 #endif
360 // Start a load for a module script URI. Returns immediately if the module is
361 // already being loaded.
362 nsresult StartModuleLoad(ModuleLoadRequest* aRequest);
363 nsresult RestartModuleLoad(ModuleLoadRequest* aRequest);
365 // Notify the module loader when a fetch started by StartFetch() completes.
366 nsresult OnFetchComplete(ModuleLoadRequest* aRequest, nsresult aRv);
368 // Link the module and all its imports. This must occur prior to evaluation.
369 bool InstantiateModuleGraph(ModuleLoadRequest* aRequest);
371 // Executes the module.
372 // Implements https://html.spec.whatwg.org/#run-a-module-script
373 nsresult EvaluateModule(ModuleLoadRequest* aRequest);
375 // Evaluate a module in the given context. Does not push an entry to the
376 // execution stack.
377 nsresult EvaluateModuleInContext(JSContext* aCx, ModuleLoadRequest* aRequest,
378 JS::ModuleErrorBehaviour errorBehaviour);
380 nsresult StartDynamicImport(ModuleLoadRequest* aRequest);
381 void ProcessDynamicImport(ModuleLoadRequest* aRequest);
382 void CancelAndClearDynamicImports();
384 // Process <script type="importmap">
385 mozilla::UniquePtr<ImportMap> ParseImportMap(ScriptLoadRequest* aRequest);
387 // Implements
388 // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
389 void RegisterImportMap(mozilla::UniquePtr<ImportMap> aImportMap);
391 bool HasImportMapRegistered() const { return bool(mImportMap); }
393 // Getter for mImportMapsAllowed.
394 bool IsImportMapAllowed() const { return mImportMapsAllowed; }
395 // https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps
396 void DisallowImportMaps() { mImportMapsAllowed = false; }
398 // Returns true if the module for given module key is already fetched.
399 bool IsModuleFetched(const ModuleMapKey& key) const;
401 nsresult GetFetchedModuleURLs(nsTArray<nsCString>& aURLs);
403 // Override the module loader with given loader until ResetOverride is called.
404 // While overridden, ModuleLoaderBase::GetCurrentModuleLoader returns aLoader.
406 // This is used by mozJSModuleLoader to temporarily override the global's
407 // module loader with SyncModuleLoader while importing a module graph
408 // synchronously.
409 void SetOverride(ModuleLoaderBase* aLoader);
411 // Returns true if SetOverride was called.
412 bool IsOverridden();
414 // Returns true if SetOverride was called with aLoader.
415 bool IsOverriddenBy(ModuleLoaderBase* aLoader);
417 void ResetOverride();
419 // Copy fetched modules to `aDest`.
420 // `this` shouldn't have any fetching.
421 // `aDest` shouldn't have any fetching or fetched modules.
423 // This is used when starting sync module load, to replicate the module cache
424 // in the sync module loader pointed by `aDest`.
425 void CopyModulesTo(ModuleLoaderBase* aDest);
427 // Move all fetched modules to `aDest`.
428 // Both `this` and `aDest` shouldn't have any fetching.
430 // This is used when finishing sync module load, to reflect the loaded modules
431 // to the async module loader pointed by `aDest`.
432 void MoveModulesTo(ModuleLoaderBase* aDest);
434 // Internal methods.
436 private:
437 friend class JS::loader::ModuleLoadRequest;
439 static ModuleLoaderBase* GetCurrentModuleLoader(JSContext* aCx);
440 static LoadedScript* GetLoadedScriptOrNull(
441 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate);
443 static void EnsureModuleHooksInitialized();
445 static JSObject* HostResolveImportedModule(
446 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
447 JS::Handle<JSObject*> aModuleRequest);
448 static bool HostPopulateImportMeta(JSContext* aCx,
449 JS::Handle<JS::Value> aReferencingPrivate,
450 JS::Handle<JSObject*> aMetaObject);
451 static bool ImportMetaResolve(JSContext* cx, unsigned argc, Value* vp);
452 static JSString* ImportMetaResolveImpl(
453 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
454 JS::Handle<JSString*> aSpecifier);
455 static bool HostImportModuleDynamically(
456 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
457 JS::Handle<JSObject*> aModuleRequest, JS::Handle<JSObject*> aPromise);
459 ResolveResult ResolveModuleSpecifier(LoadedScript* aScript,
460 const nsAString& aSpecifier);
462 nsresult HandleResolveFailure(JSContext* aCx, LoadedScript* aScript,
463 const nsAString& aSpecifier,
464 ResolveError aError, uint32_t aLineNumber,
465 JS::ColumnNumberOneOrigin aColumnNumber,
466 JS::MutableHandle<JS::Value> aErrorOut);
468 enum class RestartRequest { No, Yes };
469 nsresult StartOrRestartModuleLoad(ModuleLoadRequest* aRequest,
470 RestartRequest aRestart);
472 bool ModuleMapContainsURL(const ModuleMapKey& key) const;
473 bool IsModuleFetching(const ModuleMapKey& key) const;
474 void WaitForModuleFetch(ModuleLoadRequest* aRequest);
475 void SetModuleFetchStarted(ModuleLoadRequest* aRequest);
477 ModuleScript* GetFetchedModule(const ModuleMapKey& moduleMapKey) const;
479 JS::Value FindFirstParseError(ModuleLoadRequest* aRequest);
480 static nsresult InitDebuggerDataForModuleGraph(JSContext* aCx,
481 ModuleLoadRequest* aRequest);
482 nsresult ResolveRequestedModules(
483 ModuleLoadRequest* aRequest,
484 nsTArray<ModuleMapKey>* aRequestedModulesOut);
486 void SetModuleFetchFinishedAndResumeWaitingRequests(
487 ModuleLoadRequest* aRequest, nsresult aResult);
488 void ResumeWaitingRequests(LoadingRequest* aLoadingRequest, bool aSuccess);
489 void ResumeWaitingRequest(ModuleLoadRequest* aRequest, bool aSuccess);
491 void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
493 void StartFetchingModuleAndDependencies(ModuleLoadRequest* aParent,
494 const ModuleMapKey& aRequestedModule);
496 void InstantiateAndEvaluateDynamicImport(ModuleLoadRequest* aRequest);
499 * Shorthand Wrapper for JSAPI FinishDynamicImport function for the reject
500 * case where we do not have `aEvaluationPromise`. As there is no evaluation
501 * Promise, JS::FinishDynamicImport will always reject.
503 * @param aRequest
504 * The module load request for the dynamic module.
505 * @param aResult
506 * The result of running ModuleEvaluate -- If this is successful, then
507 * we can await the associated EvaluationPromise.
509 void FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
510 nsresult aResult);
513 * Wrapper for JSAPI FinishDynamicImport function. Takes an optional argument
514 * `aEvaluationPromise` which, if null, exits early.
516 * This is the Top Level Await version, which works with modules which return
517 * promises.
519 * @param aCX
520 * The JSContext for the module.
521 * @param aRequest
522 * The module load request for the dynamic module.
523 * @param aResult
524 * The result of running ModuleEvaluate -- If this is successful, then
525 * we can await the associated EvaluationPromise.
526 * @param aEvaluationPromise
527 * The evaluation promise returned from evaluating the module. If this
528 * is null, JS::FinishDynamicImport will reject the dynamic import
529 * module promise.
531 static void FinishDynamicImport(JSContext* aCx, ModuleLoadRequest* aRequest,
532 nsresult aResult,
533 JS::Handle<JSObject*> aEvaluationPromise);
535 void RemoveDynamicImport(ModuleLoadRequest* aRequest);
537 nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
539 bool IsFetchingAndHasWaitingRequest(ModuleLoadRequest* aRequest);
541 // The slot stored in ImportMetaResolve function.
542 enum { ModulePrivateSlot = 0, SlotCount };
544 // The number of args in ImportMetaResolve.
545 static const uint32_t ImportMetaResolveNumArgs = 1;
546 // The index of the 'specifier' argument in ImportMetaResolve.
547 static const uint32_t ImportMetaResolveSpecifierArg = 0;
549 public:
550 static mozilla::LazyLogModule gCspPRLog;
551 static mozilla::LazyLogModule gModuleLoaderBaseLog;
554 // Override the target module loader with given module loader while this
555 // instance is on the stack.
556 class MOZ_RAII AutoOverrideModuleLoader {
557 public:
558 AutoOverrideModuleLoader(ModuleLoaderBase* aTarget,
559 ModuleLoaderBase* aLoader);
560 ~AutoOverrideModuleLoader();
562 private:
563 RefPtr<ModuleLoaderBase> mTarget;
566 } // namespace loader
567 } // namespace JS
569 #endif // js_loader_ModuleLoaderBase_h