Merge mozilla-central to autoland. a=merge CLOSED TREE
[gecko.git] / js / loader / ModuleLoaderBase.h
blob29de70d2f2a27c09ba7a5fb0c7a219c9d550a517
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/dom/JSExecutionContext.h"
27 #include "mozilla/MaybeOneOf.h"
28 #include "mozilla/UniquePtr.h"
29 #include "ResolveResult.h"
31 class nsIURI;
33 namespace mozilla {
35 class LazyLogModule;
36 union Utf8Unit;
38 } // namespace mozilla
40 namespace JS {
42 class CompileOptions;
44 template <typename UnitT>
45 class SourceText;
47 namespace loader {
49 class ModuleLoaderBase;
50 class ModuleLoadRequest;
51 class ModuleScript;
54 * [DOMDOC] Shared Classic/Module Script Methods
56 * The ScriptLoaderInterface defines the shared methods needed by both
57 * ScriptLoaders (loading classic scripts) and ModuleLoaders (loading module
58 * scripts). These include:
60 * * Error Logging
61 * * Generating the compile options
62 * * Optional: Bytecode Encoding
64 * ScriptLoaderInterface does not provide any implementations.
65 * It enables the ModuleLoaderBase to reference back to the behavior implemented
66 * by a given ScriptLoader.
68 * Not all methods will be used by all ModuleLoaders. For example, Bytecode
69 * Encoding does not apply to workers, as we only work with source text there.
70 * Fully virtual methods are implemented by all.
74 class ScriptLoaderInterface : public nsISupports {
75 public:
76 // alias common classes
77 using ScriptFetchOptions = JS::loader::ScriptFetchOptions;
78 using ScriptKind = JS::loader::ScriptKind;
79 using ScriptLoadRequest = JS::loader::ScriptLoadRequest;
80 using ScriptLoadRequestList = JS::loader::ScriptLoadRequestList;
81 using ModuleLoadRequest = JS::loader::ModuleLoadRequest;
83 virtual ~ScriptLoaderInterface() = default;
85 // In some environments, we will need to default to a base URI
86 virtual nsIURI* GetBaseURI() const = 0;
88 virtual void ReportErrorToConsole(ScriptLoadRequest* aRequest,
89 nsresult aResult) const = 0;
91 virtual void ReportWarningToConsole(
92 ScriptLoadRequest* aRequest, const char* aMessageName,
93 const nsTArray<nsString>& aParams = nsTArray<nsString>()) const = 0;
95 // Fill in CompileOptions, as well as produce the introducer script for
96 // subsequent calls to UpdateDebuggerMetadata
97 virtual nsresult FillCompileOptionsForRequest(
98 JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
99 JS::MutableHandle<JSScript*> aIntroductionScript) = 0;
101 virtual void MaybePrepareModuleForBytecodeEncodingBeforeExecute(
102 JSContext* aCx, ModuleLoadRequest* aRequest) {}
104 virtual nsresult MaybePrepareModuleForBytecodeEncodingAfterExecute(
105 ModuleLoadRequest* aRequest, nsresult aRv) {
106 return NS_OK;
109 virtual void MaybeTriggerBytecodeEncoding() {}
113 * [DOMDOC] Module Loading
115 * ModuleLoaderBase provides support for loading module graphs as defined in the
116 * EcmaScript specification. A derived module loader class must be created for a
117 * specific use case (for example loading HTML module scripts). The derived
118 * class provides operations such as fetching of source code and scheduling of
119 * module execution.
121 * Module loading works in terms of 'requests' which hold data about modules as
122 * they move through the loading process. There may be more than one load
123 * request active for a single module URI, but the module is only loaded
124 * once. This is achieved by tracking all fetching and fetched modules in the
125 * module map.
127 * The module map is made up of two parts. A module that has been requested but
128 * has not finished fetching is represented by an entry in the mFetchingModules
129 * map. A module which has been fetched and compiled is represented by a
130 * ModuleScript in the mFetchedModules map.
132 * Module loading typically works as follows:
134 * 1. The client ensures there is an instance of the derived module loader
135 * class for its global or creates one if necessary.
137 * 2. The client creates a ModuleLoadRequest object for the module to load and
138 * calls the loader's StartModuleLoad() method. This is a top-level request,
139 * i.e. not an import.
141 * 3. The module loader calls the virtual method CanStartLoad() to check
142 * whether the request should be loaded.
144 * 4. If the module is not already present in the module map, the loader calls
145 * the virtual method StartFetch() to set up an asynchronous operation to
146 * fetch the module source.
148 * 5. When the fetch operation is complete, the derived loader calls
149 * OnFetchComplete() passing an error code to indicate success or failure.
151 * 6. On success, the loader attempts to create a module script by calling the
152 * virtual CompileFetchedModule() method.
154 * 7. If compilation is successful, the loader creates load requests for any
155 * imported modules if present. If so, the process repeats from step 3.
157 * 8. When a load request is completed, the virtual OnModuleLoadComplete()
158 * method is called. This is called for the top-level request and import
159 * requests.
161 * 9. The client calls InstantiateModuleGraph() for the top-level request. This
162 * links the loaded module graph.
164 * 10. The client calls EvaluateModule() to execute the top-level module.
166 class ModuleLoaderBase : public nsISupports {
168 * Represents an ongoing load operation for a URI initiated for one request
169 * and which may have other requests waiting for it to complete.
171 * These are tracked in the mFetchingModules map.
173 class LoadingRequest final : public nsISupports {
174 virtual ~LoadingRequest() = default;
176 public:
177 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
178 NS_DECL_CYCLE_COLLECTION_CLASS(LoadingRequest)
180 // The request that initiated the load and which is currently fetching or
181 // being compiled.
182 RefPtr<ModuleLoadRequest> mRequest;
184 // A list of any other requests for the same URI that are waiting for the
185 // initial load to complete. These will be resumed by ResumeWaitingRequests
186 // when that happens.
187 nsTArray<RefPtr<ModuleLoadRequest>> mWaiting;
190 // Module map
191 nsRefPtrHashtable<nsURIHashKey, LoadingRequest> mFetchingModules;
192 nsRefPtrHashtable<nsURIHashKey, ModuleScript> mFetchedModules;
194 // List of dynamic imports that are currently being loaded.
195 ScriptLoadRequestList mDynamicImportRequests;
197 nsCOMPtr<nsIGlobalObject> mGlobalObject;
199 // If non-null, this module loader is overridden by the module loader pointed
200 // by mOverriddenBy.
201 // See ModuleLoaderBase::GetCurrentModuleLoader for more details.
202 RefPtr<ModuleLoaderBase> mOverriddenBy;
204 // https://html.spec.whatwg.org/multipage/webappapis.html#import-maps-allowed
206 // Each Window has an import maps allowed boolean, initially true.
207 bool mImportMapsAllowed = true;
209 protected:
210 RefPtr<ScriptLoaderInterface> mLoader;
212 mozilla::UniquePtr<ImportMap> mImportMap;
214 virtual ~ModuleLoaderBase();
216 #ifdef DEBUG
217 const ScriptLoadRequestList& DynamicImportRequests() const {
218 return mDynamicImportRequests;
220 #endif
222 public:
223 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
224 NS_DECL_CYCLE_COLLECTION_CLASS(ModuleLoaderBase)
225 explicit ModuleLoaderBase(ScriptLoaderInterface* aLoader,
226 nsIGlobalObject* aGlobalObject);
228 // Called to break cycles during shutdown to prevent memory leaks.
229 void Shutdown();
231 virtual nsIURI* GetBaseURI() const { return mLoader->GetBaseURI(); };
233 using LoadedScript = JS::loader::LoadedScript;
234 using ScriptFetchOptions = JS::loader::ScriptFetchOptions;
235 using ScriptLoadRequest = JS::loader::ScriptLoadRequest;
236 using ModuleLoadRequest = JS::loader::ModuleLoadRequest;
238 using MaybeSourceText =
239 mozilla::MaybeOneOf<JS::SourceText<char16_t>, JS::SourceText<Utf8Unit>>;
241 // Methods that must be implemented by an extending class. These are called
242 // internally by ModuleLoaderBase.
244 private:
245 // Create a module load request for a static module import.
246 virtual already_AddRefed<ModuleLoadRequest> CreateStaticImport(
247 nsIURI* aURI, ModuleLoadRequest* aParent) = 0;
249 // Called by HostImportModuleDynamically hook.
250 virtual already_AddRefed<ModuleLoadRequest> CreateDynamicImport(
251 JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
252 JS::Handle<JSString*> aSpecifier, JS::Handle<JSObject*> aPromise) = 0;
254 virtual bool IsDynamicImportSupported() { return true; }
256 // Called when dynamic import started successfully.
257 virtual void OnDynamicImportStarted(ModuleLoadRequest* aRequest) {}
259 // Check whether we can load a module. May return false with |aRvOut| set to
260 // NS_OK to abort load without returning an error.
261 virtual bool CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) = 0;
263 // Start the process of fetching module source (or bytecode). This is only
264 // called if CanStartLoad returned true.
265 virtual nsresult StartFetch(ModuleLoadRequest* aRequest) = 0;
267 // Create a JS module for a fetched module request. This might compile source
268 // text or decode cached bytecode.
269 virtual nsresult CompileFetchedModule(
270 JSContext* aCx, JS::Handle<JSObject*> aGlobal,
271 JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
272 JS::MutableHandle<JSObject*> aModuleOut) = 0;
274 // Called when a module script has been loaded, including imports.
275 virtual void OnModuleLoadComplete(ModuleLoadRequest* aRequest) = 0;
277 virtual bool IsModuleEvaluationAborted(ModuleLoadRequest* aRequest) {
278 return false;
281 // Get the error message when resolving failed. The default is to call
282 // nsContentUtils::FormatLoalizedString. But currently
283 // nsContentUtils::FormatLoalizedString cannot be called on a worklet thread,
284 // see bug 1808301. So WorkletModuleLoader will override this function to
285 // get the error message.
286 virtual nsresult GetResolveFailureMessage(ResolveError aError,
287 const nsAString& aSpecifier,
288 nsAString& aResult);
290 // Public API methods.
292 public:
293 ScriptLoaderInterface* GetScriptLoaderInterface() const { return mLoader; }
295 nsIGlobalObject* GetGlobalObject() const { return mGlobalObject; }
297 bool HasFetchingModules() const;
299 bool HasPendingDynamicImports() const;
300 void CancelDynamicImport(ModuleLoadRequest* aRequest, nsresult aResult);
301 #ifdef DEBUG
302 bool HasDynamicImport(const ModuleLoadRequest* aRequest) const;
303 #endif
305 // Start a load for a module script URI. Returns immediately if the module is
306 // already being loaded.
307 nsresult StartModuleLoad(ModuleLoadRequest* aRequest);
308 nsresult RestartModuleLoad(ModuleLoadRequest* aRequest);
310 // Notify the module loader when a fetch started by StartFetch() completes.
311 nsresult OnFetchComplete(ModuleLoadRequest* aRequest, nsresult aRv);
313 // Link the module and all its imports. This must occur prior to evaluation.
314 bool InstantiateModuleGraph(ModuleLoadRequest* aRequest);
316 // Executes the module.
317 // Implements https://html.spec.whatwg.org/#run-a-module-script
318 nsresult EvaluateModule(ModuleLoadRequest* aRequest);
320 // Evaluate a module in the given context. Does not push an entry to the
321 // execution stack.
322 nsresult EvaluateModuleInContext(JSContext* aCx, ModuleLoadRequest* aRequest,
323 JS::ModuleErrorBehaviour errorBehaviour);
325 nsresult StartDynamicImport(ModuleLoadRequest* aRequest);
326 void ProcessDynamicImport(ModuleLoadRequest* aRequest);
327 void CancelAndClearDynamicImports();
329 // Process <script type="importmap">
330 mozilla::UniquePtr<ImportMap> ParseImportMap(ScriptLoadRequest* aRequest);
332 // Implements
333 // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map
334 void RegisterImportMap(mozilla::UniquePtr<ImportMap> aImportMap);
336 bool HasImportMapRegistered() const { return bool(mImportMap); }
338 // Getter for mImportMapsAllowed.
339 bool IsImportMapAllowed() const { return mImportMapsAllowed; }
340 // https://html.spec.whatwg.org/multipage/webappapis.html#disallow-further-import-maps
341 void DisallowImportMaps() { mImportMapsAllowed = false; }
343 // Returns true if the module for given URL is already fetched.
344 bool IsModuleFetched(nsIURI* aURL) const;
346 nsresult GetFetchedModuleURLs(nsTArray<nsCString>& aURLs);
348 // Removed a fetched module from the module map. Asserts that the module is
349 // unlinked. Extreme care should be taken when calling this method.
350 bool RemoveFetchedModule(nsIURI* aURL);
352 // Override the module loader with given loader until ResetOverride is called.
353 // While overridden, ModuleLoaderBase::GetCurrentModuleLoader returns aLoader.
355 // This is used by mozJSModuleLoader to temporarily override the global's
356 // module loader with SyncModuleLoader while importing a module graph
357 // synchronously.
358 void SetOverride(ModuleLoaderBase* aLoader);
360 // Returns true if SetOverride was called.
361 bool IsOverridden();
363 // Returns true if SetOverride was called with aLoader.
364 bool IsOverriddenBy(ModuleLoaderBase* aLoader);
366 void ResetOverride();
368 // Copy fetched modules to `aDest`.
369 // `this` shouldn't have any fetching.
370 // `aDest` shouldn't have any fetching or fetched modules.
372 // This is used when starting sync module load, to replicate the module cache
373 // in the sync module loader pointed by `aDest`.
374 void CopyModulesTo(ModuleLoaderBase* aDest);
376 // Move all fetched modules to `aDest`.
377 // Both `this` and `aDest` shouldn't have any fetching.
379 // This is used when finishing sync module load, to reflect the loaded modules
380 // to the async module loader pointed by `aDest`.
381 void MoveModulesTo(ModuleLoaderBase* aDest);
383 // Internal methods.
385 private:
386 friend class JS::loader::ModuleLoadRequest;
388 static ModuleLoaderBase* GetCurrentModuleLoader(JSContext* aCx);
389 static LoadedScript* GetLoadedScriptOrNull(
390 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate);
392 static void EnsureModuleHooksInitialized();
394 static JSObject* HostResolveImportedModule(
395 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
396 JS::Handle<JSObject*> aModuleRequest);
397 static bool HostPopulateImportMeta(JSContext* aCx,
398 JS::Handle<JS::Value> aReferencingPrivate,
399 JS::Handle<JSObject*> aMetaObject);
400 static bool ImportMetaResolve(JSContext* cx, unsigned argc, Value* vp);
401 static JSString* ImportMetaResolveImpl(
402 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
403 JS::Handle<JSString*> aSpecifier);
404 static bool HostImportModuleDynamically(
405 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate,
406 JS::Handle<JSObject*> aModuleRequest, JS::Handle<JSObject*> aPromise);
408 ResolveResult ResolveModuleSpecifier(LoadedScript* aScript,
409 const nsAString& aSpecifier);
411 nsresult HandleResolveFailure(JSContext* aCx, LoadedScript* aScript,
412 const nsAString& aSpecifier,
413 ResolveError aError, uint32_t aLineNumber,
414 JS::ColumnNumberOneOrigin aColumnNumber,
415 JS::MutableHandle<JS::Value> aErrorOut);
417 enum class RestartRequest { No, Yes };
418 nsresult StartOrRestartModuleLoad(ModuleLoadRequest* aRequest,
419 RestartRequest aRestart);
421 bool ModuleMapContainsURL(nsIURI* aURL) const;
422 bool IsModuleFetching(nsIURI* aURL) const;
423 void WaitForModuleFetch(ModuleLoadRequest* aRequest);
424 void SetModuleFetchStarted(ModuleLoadRequest* aRequest);
426 ModuleScript* GetFetchedModule(nsIURI* aURL) const;
428 JS::Value FindFirstParseError(ModuleLoadRequest* aRequest);
429 static nsresult InitDebuggerDataForModuleGraph(JSContext* aCx,
430 ModuleLoadRequest* aRequest);
431 nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
432 nsCOMArray<nsIURI>* aUrlsOut);
434 void SetModuleFetchFinishedAndResumeWaitingRequests(
435 ModuleLoadRequest* aRequest, nsresult aResult);
436 void ResumeWaitingRequests(LoadingRequest* aLoadingRequest, bool aSuccess);
437 void ResumeWaitingRequest(ModuleLoadRequest* aRequest, bool aSuccess);
439 void StartFetchingModuleDependencies(ModuleLoadRequest* aRequest);
441 void StartFetchingModuleAndDependencies(ModuleLoadRequest* aParent,
442 nsIURI* aURI);
444 void InstantiateAndEvaluateDynamicImport(ModuleLoadRequest* aRequest);
447 * Shorthand Wrapper for JSAPI FinishDynamicImport function for the reject
448 * case where we do not have `aEvaluationPromise`. As there is no evaluation
449 * Promise, JS::FinishDynamicImport will always reject.
451 * @param aRequest
452 * The module load request for the dynamic module.
453 * @param aResult
454 * The result of running ModuleEvaluate -- If this is successful, then
455 * we can await the associated EvaluationPromise.
457 void FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
458 nsresult aResult);
461 * Wrapper for JSAPI FinishDynamicImport function. Takes an optional argument
462 * `aEvaluationPromise` which, if null, exits early.
464 * This is the Top Level Await version, which works with modules which return
465 * promises.
467 * @param aCX
468 * The JSContext for the module.
469 * @param aRequest
470 * The module load request for the dynamic module.
471 * @param aResult
472 * The result of running ModuleEvaluate -- If this is successful, then
473 * we can await the associated EvaluationPromise.
474 * @param aEvaluationPromise
475 * The evaluation promise returned from evaluating the module. If this
476 * is null, JS::FinishDynamicImport will reject the dynamic import
477 * module promise.
479 static void FinishDynamicImport(JSContext* aCx, ModuleLoadRequest* aRequest,
480 nsresult aResult,
481 JS::Handle<JSObject*> aEvaluationPromise);
483 void RemoveDynamicImport(ModuleLoadRequest* aRequest);
485 nsresult CreateModuleScript(ModuleLoadRequest* aRequest);
487 // The slot stored in ImportMetaResolve function.
488 enum { ModulePrivateSlot = 0, SlotCount };
490 // The number of args in ImportMetaResolve.
491 static const uint32_t ImportMetaResolveNumArgs = 1;
492 // The index of the 'specifier' argument in ImportMetaResolve.
493 static const uint32_t ImportMetaResolveSpecifierArg = 0;
495 public:
496 static mozilla::LazyLogModule gCspPRLog;
497 static mozilla::LazyLogModule gModuleLoaderBaseLog;
500 // Override the target module loader with given module loader while this
501 // instance is on the stack.
502 class MOZ_RAII AutoOverrideModuleLoader {
503 public:
504 AutoOverrideModuleLoader(ModuleLoaderBase* aTarget,
505 ModuleLoaderBase* aLoader);
506 ~AutoOverrideModuleLoader();
508 private:
509 RefPtr<ModuleLoaderBase> mTarget;
512 } // namespace loader
513 } // namespace JS
515 #endif // js_loader_ModuleLoaderBase_h