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/TypeDecls.h" // JS::MutableHandle, JS::Handle, JS::Root
15 #include "js/Modules.h"
16 #include "nsRefPtrHashtable.h"
17 #include "nsCOMArray.h"
19 #include "nsILoadInfo.h" // nsSecurityFlags
20 #include "nsINode.h" // nsIURI
21 #include "nsThreadUtils.h" // GetMainThreadSerialEventTarget
22 #include "nsURIHashKey.h"
23 #include "mozilla/CORSMode.h"
24 #include "mozilla/dom/JSExecutionContext.h"
25 #include "mozilla/MaybeOneOf.h"
26 #include "mozilla/MozPromise.h"
27 #include "mozilla/UniquePtr.h"
28 #include "ResolveResult.h"
37 } // namespace mozilla
43 template <typename UnitT
>
48 class ModuleLoaderBase
;
49 class ModuleLoadRequest
;
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:
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
{
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
) {
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
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
126 * The module map is made up of two parts. A module that has been requested but
127 * has not yet loaded is represented by a promise in the mFetchingModules map. A
128 * module which has been loaded is represented by a ModuleScript in the
129 * 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
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 using GenericNonExclusivePromise
= mozilla::GenericNonExclusivePromise
;
168 using GenericPromise
= mozilla::GenericPromise
;
171 nsRefPtrHashtable
<nsURIHashKey
, GenericNonExclusivePromise::Private
>
173 nsRefPtrHashtable
<nsURIHashKey
, ModuleScript
> mFetchedModules
;
175 // List of dynamic imports that are currently being loaded.
176 ScriptLoadRequestList mDynamicImportRequests
;
178 nsCOMPtr
<nsIGlobalObject
> mGlobalObject
;
180 // https://html.spec.whatwg.org/multipage/webappapis.html#import-maps-allowed
182 // Each Window has an import maps allowed boolean, initially true.
183 bool mImportMapsAllowed
= true;
186 // Event handler used to process MozPromise actions, used internally to wait
187 // for fetches to finish and for imports to become avilable.
188 nsCOMPtr
<nsISerialEventTarget
> mEventTarget
;
189 RefPtr
<ScriptLoaderInterface
> mLoader
;
191 mozilla::UniquePtr
<ImportMap
> mImportMap
;
193 virtual ~ModuleLoaderBase();
196 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
197 NS_DECL_CYCLE_COLLECTION_CLASS(ModuleLoaderBase
)
198 explicit ModuleLoaderBase(ScriptLoaderInterface
* aLoader
,
199 nsIGlobalObject
* aGlobalObject
,
200 nsISerialEventTarget
* aEventTarget
=
201 mozilla::GetMainThreadSerialEventTarget());
203 // Called to break cycles during shutdown to prevent memory leaks.
206 virtual nsIURI
* GetBaseURI() const { return mLoader
->GetBaseURI(); };
208 using LoadedScript
= JS::loader::LoadedScript
;
209 using ScriptFetchOptions
= JS::loader::ScriptFetchOptions
;
210 using ScriptLoadRequest
= JS::loader::ScriptLoadRequest
;
211 using ModuleLoadRequest
= JS::loader::ModuleLoadRequest
;
213 using MaybeSourceText
=
214 mozilla::MaybeOneOf
<JS::SourceText
<char16_t
>, JS::SourceText
<Utf8Unit
>>;
216 // Methods that must be implemented by an extending class. These are called
217 // internally by ModuleLoaderBase.
220 // Create a module load request for a static module import.
221 virtual already_AddRefed
<ModuleLoadRequest
> CreateStaticImport(
222 nsIURI
* aURI
, ModuleLoadRequest
* aParent
) = 0;
224 // Called by HostImportModuleDynamically hook.
225 virtual already_AddRefed
<ModuleLoadRequest
> CreateDynamicImport(
226 JSContext
* aCx
, nsIURI
* aURI
, LoadedScript
* aMaybeActiveScript
,
227 JS::Handle
<JS::Value
> aReferencingPrivate
,
228 JS::Handle
<JSString
*> aSpecifier
, JS::Handle
<JSObject
*> aPromise
) = 0;
230 // Check whether we can load a module. May return false with |aRvOut| set to
231 // NS_OK to abort load without returning an error.
232 virtual bool CanStartLoad(ModuleLoadRequest
* aRequest
, nsresult
* aRvOut
) = 0;
234 // Start the process of fetching module source (or bytecode). This is only
235 // called if CanStartLoad returned true.
236 virtual nsresult
StartFetch(ModuleLoadRequest
* aRequest
) = 0;
238 // Create a JS module for a fetched module request. This might compile source
239 // text or decode cached bytecode.
240 virtual nsresult
CompileFetchedModule(
241 JSContext
* aCx
, JS::Handle
<JSObject
*> aGlobal
,
242 JS::CompileOptions
& aOptions
, ModuleLoadRequest
* aRequest
,
243 JS::MutableHandle
<JSObject
*> aModuleOut
) = 0;
245 // Called when a module script has been loaded, including imports.
246 virtual void OnModuleLoadComplete(ModuleLoadRequest
* aRequest
) = 0;
248 virtual bool IsModuleEvaluationAborted(ModuleLoadRequest
* aRequest
) {
252 // Get the error message when resolving failed. The default is to call
253 // nsContentUtils::FormatLoalizedString. But currently
254 // nsContentUtils::FormatLoalizedString cannot be called on a worklet thread,
255 // see bug 1808301. So WorkletModuleLoader will override this function to
256 // get the error message.
257 virtual nsresult
GetResolveFailureMessage(ResolveError aError
,
258 const nsAString
& aSpecifier
,
261 // Public API methods.
264 ScriptLoaderInterface
* GetScriptLoaderInterface() const { return mLoader
; }
266 nsIGlobalObject
* GetGlobalObject() const { return mGlobalObject
; }
268 bool HasPendingDynamicImports() const;
269 void CancelDynamicImport(ModuleLoadRequest
* aRequest
, nsresult aResult
);
271 bool HasDynamicImport(const ModuleLoadRequest
* aRequest
) const;
274 // Start a load for a module script URI. Returns immediately if the module is
275 // already being loaded.
276 nsresult
StartModuleLoad(ModuleLoadRequest
* aRequest
);
277 nsresult
RestartModuleLoad(ModuleLoadRequest
* aRequest
);
279 // Notify the module loader when a fetch started by StartFetch() completes.
280 nsresult
OnFetchComplete(ModuleLoadRequest
* aRequest
, nsresult aRv
);
282 // Link the module and all its imports. This must occur prior to evaluation.
283 bool InstantiateModuleGraph(ModuleLoadRequest
* aRequest
);
285 // Executes the module.
286 // Implements https://html.spec.whatwg.org/#run-a-module-script
287 nsresult
EvaluateModule(ModuleLoadRequest
* aRequest
);
289 // Evaluate a module in the given context. Does not push an entry to the
291 nsresult
EvaluateModuleInContext(JSContext
* aCx
, ModuleLoadRequest
* aRequest
,
292 JS::ModuleErrorBehaviour errorBehaviour
);
294 void StartDynamicImport(ModuleLoadRequest
* aRequest
);
295 void ProcessDynamicImport(ModuleLoadRequest
* aRequest
);
296 void CancelAndClearDynamicImports();
298 // Process <script type="importmap">
299 mozilla::UniquePtr
<ImportMap
> ParseImportMap(ScriptLoadRequest
* aRequest
);
302 // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
303 void RegisterImportMap(mozilla::UniquePtr
<ImportMap
> aImportMap
);
305 bool HasImportMapRegistered() const { return bool(mImportMap
); }
307 // Getter for mImportMapsAllowed.
308 bool IsImportMapAllowed() const { return mImportMapsAllowed
; }
309 // https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps
310 void DisallowImportMaps() { mImportMapsAllowed
= false; }
312 // Returns true if the module for given URL is already fetched.
313 bool IsModuleFetched(nsIURI
* aURL
) const;
315 nsresult
GetFetchedModuleURLs(nsTArray
<nsCString
>& aURLs
);
320 friend class JS::loader::ModuleLoadRequest
;
322 static ModuleLoaderBase
* GetCurrentModuleLoader(JSContext
* aCx
);
323 static LoadedScript
* GetLoadedScriptOrNull(
324 JSContext
* aCx
, JS::Handle
<JS::Value
> aReferencingPrivate
);
326 static void EnsureModuleHooksInitialized();
328 static JSObject
* HostResolveImportedModule(
329 JSContext
* aCx
, JS::Handle
<JS::Value
> aReferencingPrivate
,
330 JS::Handle
<JSObject
*> aModuleRequest
);
331 static bool HostPopulateImportMeta(JSContext
* aCx
,
332 JS::Handle
<JS::Value
> aReferencingPrivate
,
333 JS::Handle
<JSObject
*> aMetaObject
);
334 static bool ImportMetaResolve(JSContext
* cx
, unsigned argc
, Value
* vp
);
335 static JSString
* ImportMetaResolveImpl(
336 JSContext
* aCx
, JS::Handle
<JS::Value
> aReferencingPrivate
,
337 JS::Handle
<JSString
*> aSpecifier
);
338 static bool HostImportModuleDynamically(
339 JSContext
* aCx
, JS::Handle
<JS::Value
> aReferencingPrivate
,
340 JS::Handle
<JSObject
*> aModuleRequest
, JS::Handle
<JSObject
*> aPromise
);
341 static bool HostGetSupportedImportAssertions(
342 JSContext
* aCx
, JS::ImportAssertionVector
& aValues
);
344 ResolveResult
ResolveModuleSpecifier(LoadedScript
* aScript
,
345 const nsAString
& aSpecifier
);
347 nsresult
HandleResolveFailure(JSContext
* aCx
, LoadedScript
* aScript
,
348 const nsAString
& aSpecifier
,
349 ResolveError aError
, uint32_t aLineNumber
,
350 uint32_t aColumnNumber
,
351 JS::MutableHandle
<JS::Value
> aErrorOut
);
353 enum class RestartRequest
{ No
, Yes
};
354 nsresult
StartOrRestartModuleLoad(ModuleLoadRequest
* aRequest
,
355 RestartRequest aRestart
);
357 bool ModuleMapContainsURL(nsIURI
* aURL
) const;
358 bool IsModuleFetching(nsIURI
* aURL
) const;
359 RefPtr
<GenericNonExclusivePromise
> WaitForModuleFetch(nsIURI
* aURL
);
360 void SetModuleFetchStarted(ModuleLoadRequest
* aRequest
);
362 ModuleScript
* GetFetchedModule(nsIURI
* aURL
) const;
364 JS::Value
FindFirstParseError(ModuleLoadRequest
* aRequest
);
365 static nsresult
InitDebuggerDataForModuleGraph(JSContext
* aCx
,
366 ModuleLoadRequest
* aRequest
);
367 nsresult
ResolveRequestedModules(ModuleLoadRequest
* aRequest
,
368 nsCOMArray
<nsIURI
>* aUrlsOut
);
370 void SetModuleFetchFinishedAndResumeWaitingRequests(
371 ModuleLoadRequest
* aRequest
, nsresult aResult
);
373 void StartFetchingModuleDependencies(ModuleLoadRequest
* aRequest
);
375 RefPtr
<GenericPromise
> StartFetchingModuleAndDependencies(
376 ModuleLoadRequest
* aParent
, nsIURI
* aURI
);
379 * Shorthand Wrapper for JSAPI FinishDynamicImport function for the reject
380 * case where we do not have `aEvaluationPromise`. As there is no evaluation
381 * Promise, JS::FinishDynamicImport will always reject.
384 * The module load request for the dynamic module.
386 * The result of running ModuleEvaluate -- If this is successful, then
387 * we can await the associated EvaluationPromise.
389 void FinishDynamicImportAndReject(ModuleLoadRequest
* aRequest
,
393 * Wrapper for JSAPI FinishDynamicImport function. Takes an optional argument
394 * `aEvaluationPromise` which, if null, exits early.
396 * This is the Top Level Await version, which works with modules which return
400 * The JSContext for the module.
402 * The module load request for the dynamic module.
404 * The result of running ModuleEvaluate -- If this is successful, then
405 * we can await the associated EvaluationPromise.
406 * @param aEvaluationPromise
407 * The evaluation promise returned from evaluating the module. If this
408 * is null, JS::FinishDynamicImport will reject the dynamic import
411 static void FinishDynamicImport(JSContext
* aCx
, ModuleLoadRequest
* aRequest
,
413 JS::Handle
<JSObject
*> aEvaluationPromise
);
415 void RemoveDynamicImport(ModuleLoadRequest
* aRequest
);
417 nsresult
CreateModuleScript(ModuleLoadRequest
* aRequest
);
419 // The slot stored in ImportMetaResolve function.
420 enum { ModulePrivateSlot
= 0, SlotCount
};
422 // The number of args in ImportMetaResolve.
423 static const uint32_t ImportMetaResolveNumArgs
= 1;
424 // The index of the 'specifier' argument in ImportMetaResolve.
425 static const uint32_t ImportMetaResolveSpecifierArg
= 0;
428 static mozilla::LazyLogModule gCspPRLog
;
429 static mozilla::LazyLogModule gModuleLoaderBaseLog
;
432 } // namespace loader
435 #endif // js_loader_ModuleLoaderBase_h