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 #include "GeckoProfiler.h"
8 #include "LoadedScript.h"
9 #include "ModuleLoadRequest.h"
10 #include "ScriptLoadRequest.h"
11 #include "mozilla/dom/ScriptSettings.h" // AutoJSAPI
12 #include "mozilla/dom/ScriptTrace.h"
14 #include "js/Array.h" // JS::GetArrayLength
15 #include "js/CompilationAndEvaluation.h"
16 #include "js/ContextOptions.h" // JS::ContextOptionsRef
17 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
18 #include "js/Modules.h" // JS::FinishDynamicModuleImport, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{DynamicImport,Metadata}Hook
19 #include "js/OffThreadScriptCompilation.h"
20 #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetElement
21 #include "js/SourceText.h"
22 #include "mozilla/BasePrincipal.h"
23 #include "mozilla/dom/AutoEntryScript.h"
24 #include "mozilla/dom/ScriptLoadContext.h"
25 #include "mozilla/CycleCollectedJSContext.h" // nsAutoMicroTask
26 #include "mozilla/Preferences.h"
27 #include "nsContentUtils.h"
28 #include "nsICacheInfoChannel.h" // nsICacheInfoChannel
29 #include "nsNetUtil.h" // NS_NewURI
30 #include "xpcpublic.h"
33 using mozilla::Preferences
;
34 using mozilla::UniquePtr
;
35 using mozilla::WrapNotNull
;
36 using mozilla::dom::AutoJSAPI
;
38 namespace JS::loader
{
40 mozilla::LazyLogModule
ModuleLoaderBase::gCspPRLog("CSP");
41 mozilla::LazyLogModule
ModuleLoaderBase::gModuleLoaderBaseLog(
46 MOZ_LOG(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug, \
49 #define LOG_ENABLED() \
50 MOZ_LOG_TEST(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug)
52 //////////////////////////////////////////////////////////////
54 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase
)
57 NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase
, mFetchedModules
,
58 mDynamicImportRequests
, mGlobalObject
, mEventTarget
,
61 NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase
)
62 NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase
)
65 void ModuleLoaderBase::EnsureModuleHooksInitialized() {
68 JSRuntime
* rt
= JS_GetRuntime(jsapi
.cx());
69 if (JS::GetModuleResolveHook(rt
)) {
73 JS::SetModuleResolveHook(rt
, HostResolveImportedModule
);
74 JS::SetModuleMetadataHook(rt
, HostPopulateImportMeta
);
75 JS::SetScriptPrivateReferenceHooks(rt
, HostAddRefTopLevelScript
,
76 HostReleaseTopLevelScript
);
77 JS::SetSupportedAssertionsHook(rt
, HostGetSupportedImportAssertions
);
79 Preferences::RegisterCallbackAndCall(DynamicImportPrefChangedCallback
,
80 "javascript.options.dynamicImport",
85 void ModuleLoaderBase::DynamicImportPrefChangedCallback(const char* aPrefName
,
87 bool enabled
= Preferences::GetBool(aPrefName
);
88 JS::ModuleDynamicImportHook hook
=
89 enabled
? HostImportModuleDynamically
: nullptr;
93 JSRuntime
* rt
= JS_GetRuntime(jsapi
.cx());
94 JS::SetModuleDynamicImportHook(rt
, hook
);
97 // 8.1.3.8.1 HostResolveImportedModule(referencingModule, moduleRequest)
99 * Implement the HostResolveImportedModule abstract operation.
101 * Resolve a module specifier string and look this up in the module
102 * map, returning the result. This is only called for previously
103 * loaded modules and always succeeds.
105 * @param aReferencingPrivate A JS::Value which is either undefined
106 * or contains a LoadedScript private pointer.
107 * @param aModuleRequest A module request object.
108 * @returns module This is set to the module found.
111 JSObject
* ModuleLoaderBase::HostResolveImportedModule(
112 JSContext
* aCx
, JS::Handle
<JS::Value
> aReferencingPrivate
,
113 JS::Handle
<JSObject
*> aModuleRequest
) {
114 JS::Rooted
<JSObject
*> module(aCx
);
117 // LoadedScript should only live in this block, otherwise it will be a GC
119 RefPtr
<LoadedScript
> script(
120 GetLoadedScriptOrNull(aCx
, aReferencingPrivate
));
122 JS::Rooted
<JSString
*> specifierString(
123 aCx
, JS::GetModuleRequestSpecifier(aCx
, aModuleRequest
));
124 if (!specifierString
) {
128 // Let url be the result of resolving a module specifier given referencing
129 // module script and specifier.
130 nsAutoJSString string
;
131 if (!string
.init(aCx
, specifierString
)) {
135 RefPtr
<ModuleLoaderBase
> loader
= GetCurrentModuleLoader(aCx
);
140 auto result
= loader
->ResolveModuleSpecifier(script
, string
);
141 // This cannot fail because resolving a module specifier must have been
142 // previously successful with these same two arguments.
143 MOZ_ASSERT(result
.isOk());
144 nsCOMPtr
<nsIURI
> uri
= result
.unwrap();
145 MOZ_ASSERT(uri
, "Failed to resolve previously-resolved module specifier");
147 // Let resolved module script be moduleMap[url]. (This entry must exist for
148 // us to have gotten to this point.)
149 ModuleScript
* ms
= loader
->GetFetchedModule(uri
);
150 MOZ_ASSERT(ms
, "Resolved module not found in module map");
151 MOZ_ASSERT(!ms
->HasParseError());
152 MOZ_ASSERT(ms
->ModuleRecord());
154 module
.set(ms
->ModuleRecord());
160 bool ModuleLoaderBase::HostPopulateImportMeta(
161 JSContext
* aCx
, JS::Handle
<JS::Value
> aReferencingPrivate
,
162 JS::Handle
<JSObject
*> aMetaObject
) {
163 RefPtr
<ModuleScript
> script
=
164 static_cast<ModuleScript
*>(aReferencingPrivate
.toPrivate());
165 MOZ_ASSERT(script
->IsModuleScript());
166 MOZ_ASSERT(JS::GetModulePrivate(script
->ModuleRecord()) ==
167 aReferencingPrivate
);
170 MOZ_DIAGNOSTIC_ASSERT(script
->BaseURL());
171 MOZ_ALWAYS_SUCCEEDS(script
->BaseURL()->GetAsciiSpec(url
));
173 JS::Rooted
<JSString
*> urlString(aCx
, JS_NewStringCopyZ(aCx
, url
.get()));
175 JS_ReportOutOfMemory(aCx
);
179 return JS_DefineProperty(aCx
, aMetaObject
, "url", urlString
,
184 bool ModuleLoaderBase::HostImportModuleDynamically(
185 JSContext
* aCx
, JS::Handle
<JS::Value
> aReferencingPrivate
,
186 JS::Handle
<JSObject
*> aModuleRequest
, JS::Handle
<JSObject
*> aPromise
) {
187 RefPtr
<LoadedScript
> script(GetLoadedScriptOrNull(aCx
, aReferencingPrivate
));
189 JS::Rooted
<JSString
*> specifierString(
190 aCx
, JS::GetModuleRequestSpecifier(aCx
, aModuleRequest
));
191 if (!specifierString
) {
195 // Attempt to resolve the module specifier.
196 nsAutoJSString specifier
;
197 if (!specifier
.init(aCx
, specifierString
)) {
201 RefPtr
<ModuleLoaderBase
> loader
= GetCurrentModuleLoader(aCx
);
206 auto result
= loader
->ResolveModuleSpecifier(script
, specifier
);
207 if (result
.isErr()) {
208 JS::Rooted
<JS::Value
> error(aCx
);
209 nsresult rv
= HandleResolveFailure(aCx
, script
, specifier
,
210 result
.unwrapErr(), 0, 0, &error
);
212 JS_ReportOutOfMemory(aCx
);
216 JS_SetPendingException(aCx
, error
);
220 // Create a new top-level load request.
221 nsCOMPtr
<nsIURI
> uri
= result
.unwrap();
222 RefPtr
<ModuleLoadRequest
> request
= loader
->CreateDynamicImport(
223 aCx
, uri
, script
, aReferencingPrivate
, specifierString
, aPromise
);
226 JS_ReportErrorASCII(aCx
, "Dynamic import not supported in this context");
230 loader
->StartDynamicImport(request
);
234 bool ModuleLoaderBase::HostGetSupportedImportAssertions(
235 JSContext
* aCx
, JS::ImportAssertionVector
& aValues
) {
236 MOZ_ASSERT(aValues
.empty());
238 if (!aValues
.reserve(1)) {
239 JS_ReportOutOfMemory(aCx
);
243 aValues
.infallibleAppend(JS::ImportAssertion::Type
);
249 ModuleLoaderBase
* ModuleLoaderBase::GetCurrentModuleLoader(JSContext
* aCx
) {
250 auto reportError
= mozilla::MakeScopeExit([aCx
]() {
251 JS_ReportErrorASCII(aCx
, "No ScriptLoader found for the current context");
254 JS::Rooted
<JSObject
*> object(aCx
, JS::CurrentGlobalOrNull(aCx
));
259 nsIGlobalObject
* global
= xpc::NativeGlobal(object
);
264 ModuleLoaderBase
* loader
= global
->GetModuleLoader(aCx
);
269 MOZ_ASSERT(loader
->mGlobalObject
== global
);
271 reportError
.release();
276 LoadedScript
* ModuleLoaderBase::GetLoadedScriptOrNull(
277 JSContext
* aCx
, JS::Handle
<JS::Value
> aReferencingPrivate
) {
278 if (aReferencingPrivate
.isUndefined()) {
282 auto* script
= static_cast<LoadedScript
*>(aReferencingPrivate
.toPrivate());
283 if (script
->IsEventScript()) {
288 script
->IsModuleScript(),
289 JS::GetModulePrivate(script
->AsModuleScript()->ModuleRecord()) ==
290 aReferencingPrivate
);
295 nsresult
ModuleLoaderBase::StartModuleLoad(ModuleLoadRequest
* aRequest
) {
296 return StartOrRestartModuleLoad(aRequest
, RestartRequest::No
);
299 nsresult
ModuleLoaderBase::RestartModuleLoad(ModuleLoadRequest
* aRequest
) {
300 return StartOrRestartModuleLoad(aRequest
, RestartRequest::Yes
);
303 nsresult
ModuleLoaderBase::StartOrRestartModuleLoad(ModuleLoadRequest
* aRequest
,
304 RestartRequest aRestart
) {
305 MOZ_ASSERT(aRequest
->mLoader
== this);
306 MOZ_ASSERT(aRequest
->IsFetching());
308 aRequest
->SetUnknownDataType();
310 // If we're restarting the request, the module should already be in the
312 MOZ_ASSERT_IF(aRestart
== RestartRequest::Yes
,
313 IsModuleFetching(aRequest
->mURI
));
315 // Check with the derived class whether we should load this module.
317 if (!CanStartLoad(aRequest
, &rv
)) {
321 // Check whether the module has been fetched or is currently being fetched,
322 // and if so wait for it rather than starting a new fetch.
323 ModuleLoadRequest
* request
= aRequest
->AsModuleRequest();
325 if (aRestart
== RestartRequest::No
&& ModuleMapContainsURL(request
->mURI
)) {
326 LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest
));
327 WaitForModuleFetch(request
->mURI
)
328 ->Then(mEventTarget
, __func__
, request
,
329 &ModuleLoadRequest::ModuleLoaded
,
330 &ModuleLoadRequest::LoadFailed
);
334 rv
= StartFetch(aRequest
);
335 NS_ENSURE_SUCCESS(rv
, rv
);
337 // We successfully started fetching a module so put its URL in the module
338 // map and mark it as fetching.
339 if (aRestart
== RestartRequest::No
) {
340 SetModuleFetchStarted(aRequest
->AsModuleRequest());
346 bool ModuleLoaderBase::ModuleMapContainsURL(nsIURI
* aURL
) const {
347 return IsModuleFetching(aURL
) || IsModuleFetched(aURL
);
350 bool ModuleLoaderBase::IsModuleFetching(nsIURI
* aURL
) const {
351 return mFetchingModules
.Contains(aURL
);
354 bool ModuleLoaderBase::IsModuleFetched(nsIURI
* aURL
) const {
355 return mFetchedModules
.Contains(aURL
);
358 nsresult
ModuleLoaderBase::GetFetchedModuleURLs(nsTArray
<nsCString
>& aURLs
) {
359 for (const auto& entry
: mFetchedModules
) {
360 nsIURI
* uri
= entry
.GetData()->BaseURL();
363 nsresult rv
= uri
->GetSpec(spec
);
364 NS_ENSURE_SUCCESS(rv
, rv
);
366 aURLs
.AppendElement(spec
);
372 void ModuleLoaderBase::SetModuleFetchStarted(ModuleLoadRequest
* aRequest
) {
373 // Update the module map to indicate that a module is currently being fetched.
375 MOZ_ASSERT(aRequest
->IsFetching());
376 MOZ_ASSERT(!ModuleMapContainsURL(aRequest
->mURI
));
378 mFetchingModules
.InsertOrUpdate(
379 aRequest
->mURI
, RefPtr
<mozilla::GenericNonExclusivePromise::Private
>{});
382 void ModuleLoaderBase::SetModuleFetchFinishedAndResumeWaitingRequests(
383 ModuleLoadRequest
* aRequest
, nsresult aResult
) {
384 // Update module map with the result of fetching a single module script.
386 // If any requests for the same URL are waiting on this one to complete, they
387 // will have ModuleLoaded or LoadFailed on them when the promise is
388 // resolved/rejected. This is set up in StartLoad.
390 MOZ_ASSERT(aRequest
->mLoader
== this);
393 ("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == "
395 aRequest
, aRequest
->mModuleScript
.get(), unsigned(aResult
)));
397 RefPtr
<mozilla::GenericNonExclusivePromise::Private
> promise
;
398 if (!mFetchingModules
.Remove(aRequest
->mURI
, getter_AddRefs(promise
))) {
400 ("ScriptLoadRequest (%p): Key not found in mFetchingModules, "
401 "assuming we have an inline module or have finished fetching already",
406 RefPtr
<ModuleScript
> moduleScript(aRequest
->mModuleScript
);
407 MOZ_ASSERT(NS_FAILED(aResult
) == !moduleScript
);
409 mFetchedModules
.InsertOrUpdate(aRequest
->mURI
, RefPtr
{moduleScript
});
413 LOG(("ScriptLoadRequest (%p): resolving %p", aRequest
, promise
.get()));
414 promise
->Resolve(true, __func__
);
416 LOG(("ScriptLoadRequest (%p): rejecting %p", aRequest
, promise
.get()));
417 promise
->Reject(aResult
, __func__
);
422 RefPtr
<mozilla::GenericNonExclusivePromise
>
423 ModuleLoaderBase::WaitForModuleFetch(nsIURI
* aURL
) {
424 MOZ_ASSERT(ModuleMapContainsURL(aURL
));
426 nsURIHashKey
key(aURL
);
427 if (auto entry
= mFetchingModules
.Lookup(aURL
)) {
429 entry
.Data() = new mozilla::GenericNonExclusivePromise::Private(__func__
);
434 RefPtr
<ModuleScript
> ms
;
435 MOZ_ALWAYS_TRUE(mFetchedModules
.Get(aURL
, getter_AddRefs(ms
)));
437 return mozilla::GenericNonExclusivePromise::CreateAndReject(
438 NS_ERROR_FAILURE
, __func__
);
441 return mozilla::GenericNonExclusivePromise::CreateAndResolve(true, __func__
);
444 ModuleScript
* ModuleLoaderBase::GetFetchedModule(nsIURI
* aURL
) const {
447 aURL
->GetAsciiSpec(url
);
448 LOG(("GetFetchedModule %s", url
.get()));
452 ModuleScript
* ms
= mFetchedModules
.GetWeak(aURL
, &found
);
457 nsresult
ModuleLoaderBase::OnFetchComplete(ModuleLoadRequest
* aRequest
,
459 MOZ_ASSERT(aRequest
->mLoader
== this);
460 MOZ_ASSERT(!aRequest
->mModuleScript
);
463 if (NS_SUCCEEDED(rv
)) {
464 rv
= CreateModuleScript(aRequest
);
466 aRequest
->ClearScriptSource();
469 aRequest
->LoadFailed();
474 MOZ_ASSERT(NS_SUCCEEDED(rv
) == bool(aRequest
->mModuleScript
));
475 SetModuleFetchFinishedAndResumeWaitingRequests(aRequest
, rv
);
477 if (aRequest
->mModuleScript
&& !aRequest
->mModuleScript
->HasParseError()) {
478 StartFetchingModuleDependencies(aRequest
);
484 nsresult
ModuleLoaderBase::CreateModuleScript(ModuleLoadRequest
* aRequest
) {
485 MOZ_ASSERT(!aRequest
->mModuleScript
);
486 MOZ_ASSERT(aRequest
->mBaseURL
);
488 LOG(("ScriptLoadRequest (%p): Create module script", aRequest
));
491 if (!jsapi
.Init(mGlobalObject
)) {
492 return NS_ERROR_FAILURE
;
497 JSContext
* cx
= jsapi
.cx();
498 JS::Rooted
<JSObject
*> module(cx
);
500 JS::CompileOptions
options(cx
);
501 JS::RootedScript
introductionScript(cx
);
502 rv
= mLoader
->FillCompileOptionsForRequest(cx
, aRequest
, &options
,
503 &introductionScript
);
505 if (NS_SUCCEEDED(rv
)) {
506 JS::Rooted
<JSObject
*> global(cx
, mGlobalObject
->GetGlobalJSObject());
507 rv
= CompileFetchedModule(cx
, global
, options
, aRequest
, &module
);
510 MOZ_ASSERT(NS_SUCCEEDED(rv
) == (module
!= nullptr));
513 JS::RootedValue
privateValue(cx
);
514 JS::RootedScript
moduleScript(cx
, JS::GetModuleScript(module
));
515 JS::InstantiateOptions
instantiateOptions(options
);
516 if (!JS::UpdateDebugMetadata(cx
, moduleScript
, instantiateOptions
,
517 privateValue
, nullptr, introductionScript
,
519 return NS_ERROR_OUT_OF_MEMORY
;
523 RefPtr
<ModuleScript
> moduleScript
=
524 new ModuleScript(aRequest
->mFetchOptions
, aRequest
->mBaseURL
);
525 aRequest
->mModuleScript
= moduleScript
;
528 LOG(("ScriptLoadRequest (%p): compilation failed (%d)", aRequest
,
531 MOZ_ASSERT(jsapi
.HasException());
532 JS::Rooted
<JS::Value
> error(cx
);
533 if (!jsapi
.StealException(&error
)) {
534 aRequest
->mModuleScript
= nullptr;
535 return NS_ERROR_FAILURE
;
538 moduleScript
->SetParseError(error
);
539 aRequest
->ModuleErrored();
543 moduleScript
->SetModuleRecord(module
);
545 // Validate requested modules and treat failure to resolve module specifiers
546 // the same as a parse error.
547 rv
= ResolveRequestedModules(aRequest
, nullptr);
549 aRequest
->ModuleErrored();
554 LOG(("ScriptLoadRequest (%p): module script == %p", aRequest
,
555 aRequest
->mModuleScript
.get()));
560 nsresult
ModuleLoaderBase::HandleResolveFailure(
561 JSContext
* aCx
, LoadedScript
* aScript
, const nsAString
& aSpecifier
,
562 ResolveError aError
, uint32_t aLineNumber
, uint32_t aColumnNumber
,
563 JS::MutableHandle
<JS::Value
> aErrorOut
) {
564 JS::Rooted
<JSString
*> filename(aCx
);
567 aScript
->BaseURL()->GetAsciiSpec(url
);
568 filename
= JS_NewStringCopyZ(aCx
, url
.get());
570 filename
= JS_NewStringCopyZ(aCx
, "(unknown)");
574 return NS_ERROR_OUT_OF_MEMORY
;
577 AutoTArray
<nsString
, 1> errorParams
;
578 errorParams
.AppendElement(aSpecifier
);
580 nsAutoString errorText
;
581 nsresult rv
= nsContentUtils::FormatLocalizedString(
582 nsContentUtils::eDOM_PROPERTIES
, ResolveErrorInfo::GetString(aError
),
583 errorParams
, errorText
);
584 NS_ENSURE_SUCCESS(rv
, rv
);
586 JS::Rooted
<JSString
*> string(aCx
, JS_NewUCStringCopyZ(aCx
, errorText
.get()));
588 return NS_ERROR_OUT_OF_MEMORY
;
591 if (!JS::CreateError(aCx
, JSEXN_TYPEERR
, nullptr, filename
, aLineNumber
,
592 aColumnNumber
, nullptr, string
, JS::NothingHandleValue
,
594 return NS_ERROR_OUT_OF_MEMORY
;
600 ResolveResult
ModuleLoaderBase::ResolveModuleSpecifier(
601 LoadedScript
* aScript
, const nsAString
& aSpecifier
) {
602 bool importMapsEnabled
= Preferences::GetBool("dom.importMaps.enabled");
603 // If import map is enabled, forward to the updated 'Resolve a module
604 // specifier' algorithm defined in Import maps spec.
606 // Once import map is enabled by default,
607 // ModuleLoaderBase::ResolveModuleSpecifier should be replaced by
608 // ImportMap::ResolveModuleSpecifier.
609 if (importMapsEnabled
) {
610 return ImportMap::ResolveModuleSpecifier(mImportMap
.get(), mLoader
, aScript
,
614 // The following module specifiers are allowed by the spec:
615 // - a valid absolute URL
616 // - a valid relative URL that starts with "/", "./" or "../"
618 // Bareword module specifiers are handled in Import maps.
620 nsCOMPtr
<nsIURI
> uri
;
621 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), aSpecifier
);
622 if (NS_SUCCEEDED(rv
)) {
623 return WrapNotNull(uri
);
626 if (rv
!= NS_ERROR_MALFORMED_URI
) {
627 return Err(ResolveError::ModuleResolveFailure
);
630 if (!StringBeginsWith(aSpecifier
, u
"/"_ns
) &&
631 !StringBeginsWith(aSpecifier
, u
"./"_ns
) &&
632 !StringBeginsWith(aSpecifier
, u
"../"_ns
)) {
633 return Err(ResolveError::ModuleResolveFailure
);
636 // Get the document's base URL if we don't have a referencing script here.
637 nsCOMPtr
<nsIURI
> baseURL
;
639 baseURL
= aScript
->BaseURL();
641 baseURL
= mLoader
->GetBaseURI();
644 rv
= NS_NewURI(getter_AddRefs(uri
), aSpecifier
, nullptr, baseURL
);
645 if (NS_SUCCEEDED(rv
)) {
646 return WrapNotNull(uri
);
649 return Err(ResolveError::ModuleResolveFailure
);
652 nsresult
ModuleLoaderBase::ResolveRequestedModules(
653 ModuleLoadRequest
* aRequest
, nsCOMArray
<nsIURI
>* aUrlsOut
) {
654 ModuleScript
* ms
= aRequest
->mModuleScript
;
657 if (!jsapi
.Init(ms
->ModuleRecord())) {
658 return NS_ERROR_FAILURE
;
661 JSContext
* cx
= jsapi
.cx();
662 JS::Rooted
<JSObject
*> moduleRecord(cx
, ms
->ModuleRecord());
663 JS::Rooted
<JSObject
*> requestedModules(cx
);
664 requestedModules
= JS::GetRequestedModules(cx
, moduleRecord
);
665 MOZ_ASSERT(requestedModules
);
668 if (!JS::GetArrayLength(cx
, requestedModules
, &length
)) {
669 return NS_ERROR_FAILURE
;
672 JS::Rooted
<JS::Value
> requestedModule(cx
);
673 for (uint32_t i
= 0; i
< length
; i
++) {
674 if (!JS_GetElement(cx
, requestedModules
, i
, &requestedModule
)) {
675 return NS_ERROR_FAILURE
;
678 JS::Rooted
<JSString
*> str(
679 cx
, JS::GetRequestedModuleSpecifier(cx
, requestedModule
));
682 nsAutoJSString specifier
;
683 if (!specifier
.init(cx
, str
)) {
684 return NS_ERROR_FAILURE
;
687 // Let url be the result of resolving a module specifier given module script
689 ModuleLoaderBase
* loader
= aRequest
->mLoader
;
690 auto result
= loader
->ResolveModuleSpecifier(ms
, specifier
);
691 if (result
.isErr()) {
692 uint32_t lineNumber
= 0;
693 uint32_t columnNumber
= 0;
694 JS::GetRequestedModuleSourcePos(cx
, requestedModule
, &lineNumber
,
697 JS::Rooted
<JS::Value
> error(cx
);
698 nsresult rv
= HandleResolveFailure(cx
, ms
, specifier
, result
.unwrapErr(),
699 lineNumber
, columnNumber
, &error
);
700 NS_ENSURE_SUCCESS(rv
, rv
);
702 ms
->SetParseError(error
);
703 return NS_ERROR_FAILURE
;
706 nsCOMPtr
<nsIURI
> uri
= result
.unwrap();
708 aUrlsOut
->AppendElement(uri
.forget());
715 void ModuleLoaderBase::StartFetchingModuleDependencies(
716 ModuleLoadRequest
* aRequest
) {
717 LOG(("ScriptLoadRequest (%p): Start fetching module dependencies", aRequest
));
719 if (aRequest
->IsCanceled()) {
723 MOZ_ASSERT(aRequest
->mModuleScript
);
724 MOZ_ASSERT(!aRequest
->mModuleScript
->HasParseError());
725 MOZ_ASSERT(!aRequest
->IsReadyToRun());
727 auto visitedSet
= aRequest
->mVisitedSet
;
728 MOZ_ASSERT(visitedSet
->Contains(aRequest
->mURI
));
730 aRequest
->mState
= ModuleLoadRequest::State::LoadingImports
;
732 nsCOMArray
<nsIURI
> urls
;
733 nsresult rv
= ResolveRequestedModules(aRequest
, &urls
);
735 aRequest
->mModuleScript
= nullptr;
736 aRequest
->ModuleErrored();
740 // Remove already visited URLs from the list. Put unvisited URLs into the
743 while (i
< urls
.Count()) {
744 nsIURI
* url
= urls
[i
];
745 if (visitedSet
->Contains(url
)) {
746 urls
.RemoveObjectAt(i
);
748 visitedSet
->PutEntry(url
);
753 if (urls
.Count() == 0) {
754 // There are no descendants to load so this request is ready.
755 aRequest
->DependenciesLoaded();
759 // For each url in urls, fetch a module script graph given url, module
760 // script's CORS setting, and module script's settings object.
761 nsTArray
<RefPtr
<mozilla::GenericPromise
>> importsReady
;
762 for (auto* url
: urls
) {
763 RefPtr
<mozilla::GenericPromise
> childReady
=
764 StartFetchingModuleAndDependencies(aRequest
, url
);
765 importsReady
.AppendElement(childReady
);
768 // Wait for all imports to become ready.
769 RefPtr
<mozilla::GenericPromise::AllPromiseType
> allReady
=
770 mozilla::GenericPromise::All(mEventTarget
, importsReady
);
771 allReady
->Then(mEventTarget
, __func__
, aRequest
,
772 &ModuleLoadRequest::DependenciesLoaded
,
773 &ModuleLoadRequest::ModuleErrored
);
776 RefPtr
<mozilla::GenericPromise
>
777 ModuleLoaderBase::StartFetchingModuleAndDependencies(ModuleLoadRequest
* aParent
,
781 RefPtr
<ModuleLoadRequest
> childRequest
= CreateStaticImport(aURI
, aParent
);
783 aParent
->mImports
.AppendElement(childRequest
);
787 aParent
->mURI
->GetAsciiSpec(url1
);
790 aURI
->GetAsciiSpec(url2
);
792 LOG(("ScriptLoadRequest (%p): Start fetching dependency %p", aParent
,
793 childRequest
.get()));
794 LOG(("StartFetchingModuleAndDependencies \"%s\" -> \"%s\"", url1
.get(),
798 RefPtr
<mozilla::GenericPromise
> ready
= childRequest
->mReady
.Ensure(__func__
);
800 nsresult rv
= StartModuleLoad(childRequest
);
802 MOZ_ASSERT(!childRequest
->mModuleScript
);
803 LOG(("ScriptLoadRequest (%p): rejecting %p", aParent
,
804 &childRequest
->mReady
));
806 mLoader
->ReportErrorToConsole(childRequest
, rv
);
807 childRequest
->mReady
.Reject(rv
, __func__
);
814 void ModuleLoaderBase::StartDynamicImport(ModuleLoadRequest
* aRequest
) {
815 MOZ_ASSERT(aRequest
->mLoader
== this);
817 LOG(("ScriptLoadRequest (%p): Start dynamic import", aRequest
));
819 mDynamicImportRequests
.AppendElement(aRequest
);
821 nsresult rv
= StartModuleLoad(aRequest
);
823 mLoader
->ReportErrorToConsole(aRequest
, rv
);
824 FinishDynamicImportAndReject(aRequest
, rv
);
828 void ModuleLoaderBase::FinishDynamicImportAndReject(ModuleLoadRequest
* aRequest
,
831 MOZ_ASSERT(NS_FAILED(aResult
));
832 MOZ_ALWAYS_TRUE(jsapi
.Init(aRequest
->mDynamicPromise
));
833 FinishDynamicImport(jsapi
.cx(), aRequest
, aResult
, nullptr);
836 void ModuleLoaderBase::FinishDynamicImport(
837 JSContext
* aCx
, ModuleLoadRequest
* aRequest
, nsresult aResult
,
838 JS::Handle
<JSObject
*> aEvaluationPromise
) {
839 // If aResult is a failed result, we don't have an EvaluationPromise. If it
840 // succeeded, evaluationPromise may still be null, but in this case it will
841 // be handled by rejecting the dynamic module import promise in the JSAPI.
842 MOZ_ASSERT_IF(NS_FAILED(aResult
), !aEvaluationPromise
);
843 LOG(("ScriptLoadRequest (%p): Finish dynamic import %x %d", aRequest
,
844 unsigned(aResult
), JS_IsExceptionPending(aCx
)));
846 // Complete the dynamic import, report failures indicated by aResult or as a
847 // pending exception on the context.
849 if (NS_FAILED(aResult
) &&
850 aResult
!= NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE
) {
851 MOZ_ASSERT(!JS_IsExceptionPending(aCx
));
852 JS_ReportErrorNumberUC(aCx
, js::GetErrorMessage
, nullptr,
853 JSMSG_DYNAMIC_IMPORT_FAILED
);
856 JS::Rooted
<JS::Value
> referencingScript(aCx
,
857 aRequest
->mDynamicReferencingPrivate
);
858 JS::Rooted
<JSString
*> specifier(aCx
, aRequest
->mDynamicSpecifier
);
859 JS::Rooted
<JSObject
*> promise(aCx
, aRequest
->mDynamicPromise
);
861 JS::Rooted
<JSObject
*> moduleRequest(aCx
,
862 JS::CreateModuleRequest(aCx
, specifier
));
864 JS::FinishDynamicModuleImport(aCx
, aEvaluationPromise
, referencingScript
,
865 moduleRequest
, promise
);
867 // FinishDynamicModuleImport clears any pending exception.
868 MOZ_ASSERT(!JS_IsExceptionPending(aCx
));
870 aRequest
->ClearDynamicImport();
873 ModuleLoaderBase::ModuleLoaderBase(ScriptLoaderInterface
* aLoader
,
874 nsIGlobalObject
* aGlobalObject
,
875 nsISerialEventTarget
* aEventTarget
)
876 : mGlobalObject(aGlobalObject
),
877 mEventTarget(aEventTarget
),
879 MOZ_ASSERT(mGlobalObject
);
880 MOZ_ASSERT(mEventTarget
);
883 EnsureModuleHooksInitialized();
886 ModuleLoaderBase::~ModuleLoaderBase() {
887 mDynamicImportRequests
.CancelRequestsAndClear();
889 LOG(("ModuleLoaderBase::~ModuleLoaderBase %p", this));
892 void ModuleLoaderBase::Shutdown() {
893 MOZ_ASSERT(mFetchingModules
.IsEmpty());
894 mFetchedModules
.Clear();
895 mGlobalObject
= nullptr;
896 mEventTarget
= nullptr;
900 bool ModuleLoaderBase::HasPendingDynamicImports() const {
901 return !mDynamicImportRequests
.isEmpty();
904 void ModuleLoaderBase::CancelDynamicImport(ModuleLoadRequest
* aRequest
,
906 MOZ_ASSERT(aRequest
->mLoader
== this);
908 RefPtr
<ScriptLoadRequest
> req
= mDynamicImportRequests
.Steal(aRequest
);
910 // FinishDynamicImport must happen exactly once for each dynamic import
911 // request. If the load is aborted we do it when we remove the request
912 // from mDynamicImportRequests.
913 FinishDynamicImportAndReject(aRequest
, aResult
);
916 void ModuleLoaderBase::RemoveDynamicImport(ModuleLoadRequest
* aRequest
) {
917 MOZ_ASSERT(aRequest
->IsDynamicImport());
918 mDynamicImportRequests
.Remove(aRequest
);
922 bool ModuleLoaderBase::HasDynamicImport(
923 const ModuleLoadRequest
* aRequest
) const {
924 MOZ_ASSERT(aRequest
->mLoader
== this);
925 return mDynamicImportRequests
.Contains(
926 const_cast<ModuleLoadRequest
*>(aRequest
));
930 JS::Value
ModuleLoaderBase::FindFirstParseError(ModuleLoadRequest
* aRequest
) {
931 MOZ_ASSERT(aRequest
);
933 ModuleScript
* moduleScript
= aRequest
->mModuleScript
;
934 MOZ_ASSERT(moduleScript
);
936 if (moduleScript
->HasParseError()) {
937 return moduleScript
->ParseError();
940 for (ModuleLoadRequest
* childRequest
: aRequest
->mImports
) {
941 JS::Value error
= FindFirstParseError(childRequest
);
942 if (!error
.isUndefined()) {
947 return JS::UndefinedValue();
950 bool ModuleLoaderBase::InstantiateModuleGraph(ModuleLoadRequest
* aRequest
) {
951 // Instantiate a top-level module and record any error.
953 MOZ_ASSERT(aRequest
);
954 MOZ_ASSERT(aRequest
->mLoader
== this);
955 MOZ_ASSERT(aRequest
->IsTopLevel());
957 LOG(("ScriptLoadRequest (%p): Instantiate module graph", aRequest
));
959 AUTO_PROFILER_LABEL("ModuleLoaderBase::InstantiateModuleGraph", JS
);
961 ModuleScript
* moduleScript
= aRequest
->mModuleScript
;
962 MOZ_ASSERT(moduleScript
);
964 JS::Value parseError
= FindFirstParseError(aRequest
);
965 if (!parseError
.isUndefined()) {
966 moduleScript
->SetErrorToRethrow(parseError
);
967 LOG(("ScriptLoadRequest (%p): found parse error", aRequest
));
971 MOZ_ASSERT(moduleScript
->ModuleRecord());
974 if (NS_WARN_IF(!jsapi
.Init(moduleScript
->ModuleRecord()))) {
978 JS::Rooted
<JSObject
*> module(jsapi
.cx(), moduleScript
->ModuleRecord());
979 if (!xpc::Scriptability::Get(module
).Allowed()) {
983 if (!JS::ModuleInstantiate(jsapi
.cx(), module
)) {
984 LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest
));
985 MOZ_ASSERT(jsapi
.HasException());
986 JS::RootedValue
exception(jsapi
.cx());
987 if (!jsapi
.StealException(&exception
)) {
990 MOZ_ASSERT(!exception
.isUndefined());
991 moduleScript
->SetErrorToRethrow(exception
);
997 nsresult
ModuleLoaderBase::InitDebuggerDataForModuleGraph(
998 JSContext
* aCx
, ModuleLoadRequest
* aRequest
) {
999 // JS scripts can be associated with a DOM element for use by the debugger,
1000 // but preloading can cause scripts to be compiled before DOM script element
1001 // nodes have been created. This method ensures that this association takes
1002 // place before the first time a module script is run.
1004 MOZ_ASSERT(aRequest
);
1006 ModuleScript
* moduleScript
= aRequest
->mModuleScript
;
1007 if (moduleScript
->DebuggerDataInitialized()) {
1011 for (ModuleLoadRequest
* childRequest
: aRequest
->mImports
) {
1012 nsresult rv
= InitDebuggerDataForModuleGraph(aCx
, childRequest
);
1013 NS_ENSURE_SUCCESS(rv
, rv
);
1016 JS::Rooted
<JSObject
*> module(aCx
, moduleScript
->ModuleRecord());
1019 // The script is now ready to be exposed to the debugger.
1020 JS::Rooted
<JSScript
*> script(aCx
, JS::GetModuleScript(module
));
1021 JS::ExposeScriptToDebugger(aCx
, script
);
1023 moduleScript
->SetDebuggerDataInitialized();
1027 void ModuleLoaderBase::ProcessDynamicImport(ModuleLoadRequest
* aRequest
) {
1028 MOZ_ASSERT(aRequest
->mLoader
== this);
1030 if (aRequest
->mModuleScript
) {
1031 if (!InstantiateModuleGraph(aRequest
)) {
1032 aRequest
->mModuleScript
= nullptr;
1036 nsresult rv
= NS_ERROR_FAILURE
;
1037 if (aRequest
->mModuleScript
) {
1038 rv
= EvaluateModule(aRequest
);
1041 if (NS_FAILED(rv
)) {
1042 FinishDynamicImportAndReject(aRequest
, rv
);
1046 nsresult
ModuleLoaderBase::EvaluateModule(ModuleLoadRequest
* aRequest
) {
1047 MOZ_ASSERT(aRequest
->mLoader
== this);
1049 mozilla::nsAutoMicroTask mt
;
1050 mozilla::dom::AutoEntryScript
aes(mGlobalObject
, "EvaluateModule", true);
1052 return EvaluateModuleInContext(aes
.cx(), aRequest
,
1053 JS::ReportModuleErrorsAsync
);
1056 nsresult
ModuleLoaderBase::EvaluateModuleInContext(
1057 JSContext
* aCx
, ModuleLoadRequest
* aRequest
,
1058 JS::ModuleErrorBehaviour errorBehaviour
) {
1059 MOZ_ASSERT(aRequest
->mLoader
== this);
1061 AUTO_PROFILER_LABEL("ModuleLoaderBase::EvaluateModule", JS
);
1063 nsAutoCString profilerLabelString
;
1064 if (aRequest
->HasScriptLoadContext()) {
1065 aRequest
->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString
);
1068 LOG(("ScriptLoadRequest (%p): Evaluate Module", aRequest
));
1069 AUTO_PROFILER_MARKER_TEXT("ModuleEvaluation", JS
,
1070 MarkerInnerWindowIdFromJSContext(aCx
),
1071 profilerLabelString
);
1073 ModuleLoadRequest
* request
= aRequest
->AsModuleRequest();
1074 MOZ_ASSERT(request
->mModuleScript
);
1075 MOZ_ASSERT_IF(request
->HasScriptLoadContext(),
1076 !request
->GetScriptLoadContext()->mOffThreadToken
);
1078 ModuleScript
* moduleScript
= request
->mModuleScript
;
1079 if (moduleScript
->HasErrorToRethrow()) {
1080 LOG(("ScriptLoadRequest (%p): module has error to rethrow", aRequest
));
1081 JS::Rooted
<JS::Value
> error(aCx
, moduleScript
->ErrorToRethrow());
1082 JS_SetPendingException(aCx
, error
);
1083 // For a dynamic import, the promise is rejected. Otherwise an error
1084 // is either reported by AutoEntryScript.
1085 if (request
->IsDynamicImport()) {
1086 FinishDynamicImport(aCx
, request
, NS_OK
, nullptr);
1091 JS::Rooted
<JSObject
*> module(aCx
, moduleScript
->ModuleRecord());
1094 if (!xpc::Scriptability::Get(module
).Allowed()) {
1098 nsresult rv
= InitDebuggerDataForModuleGraph(aCx
, request
);
1099 NS_ENSURE_SUCCESS(rv
, rv
);
1101 if (request
->HasScriptLoadContext()) {
1102 TRACE_FOR_TEST(aRequest
->GetScriptLoadContext()->GetScriptElement(),
1103 "scriptloader_evaluate_module");
1106 JS::Rooted
<JS::Value
> rval(aCx
);
1108 mLoader
->MaybePrepareModuleForBytecodeEncodingBeforeExecute(aCx
, request
);
1110 bool ok
= JS::ModuleEvaluate(aCx
, module
, &rval
);
1112 // ModuleEvaluate will usually set a pending exception if it returns false,
1113 // unless the user cancels execution.
1114 MOZ_ASSERT_IF(ok
, !JS_IsExceptionPending(aCx
));
1117 LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest
));
1118 // For a dynamic import, the promise is rejected. Otherwise an error is
1119 // reported by AutoEntryScript.
1122 // ModuleEvaluate returns a promise unless the user cancels the execution in
1123 // which case rval will be undefined. We should treat it as a failed
1124 // evaluation, and reject appropriately.
1125 JS::Rooted
<JSObject
*> evaluationPromise(aCx
);
1126 if (rval
.isObject()) {
1127 evaluationPromise
.set(&rval
.toObject());
1130 if (request
->IsDynamicImport()) {
1131 FinishDynamicImport(aCx
, request
, NS_OK
, evaluationPromise
);
1133 // If this is not a dynamic import, and if the promise is rejected,
1134 // the value is unwrapped from the promise value.
1135 if (!JS::ThrowOnModuleEvaluationFailure(aCx
, evaluationPromise
,
1137 LOG(("ScriptLoadRequest (%p): evaluation failed on throw", aRequest
));
1138 // For a dynamic import, the promise is rejected. Otherwise an error is
1139 // reported by AutoEntryScript.
1143 rv
= mLoader
->MaybePrepareModuleForBytecodeEncodingAfterExecute(request
,
1146 mLoader
->MaybeTriggerBytecodeEncoding();
1151 void ModuleLoaderBase::CancelAndClearDynamicImports() {
1152 for (ScriptLoadRequest
* req
= mDynamicImportRequests
.getFirst(); req
;
1153 req
= req
->getNext()) {
1155 // FinishDynamicImport must happen exactly once for each dynamic import
1156 // request. If the load is aborted we do it when we remove the request
1157 // from mDynamicImportRequests.
1158 FinishDynamicImportAndReject(req
->AsModuleRequest(), NS_ERROR_ABORT
);
1160 mDynamicImportRequests
.CancelRequestsAndClear();
1163 UniquePtr
<ImportMap
> ModuleLoaderBase::ParseImportMap(
1164 ScriptLoadRequest
* aRequest
) {
1166 if (!jsapi
.Init(GetGlobalObject())) {
1170 MOZ_ASSERT(aRequest
->IsTextSource());
1171 MaybeSourceText maybeSource
;
1172 nsresult rv
= aRequest
->GetScriptSource(jsapi
.cx(), &maybeSource
);
1173 if (NS_FAILED(rv
)) {
1177 JS::SourceText
<char16_t
>& text
= maybeSource
.ref
<SourceText
<char16_t
>>();
1178 ReportWarningHelper warning
{mLoader
, aRequest
};
1180 // https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
1181 // https://wicg.github.io/import-maps/#integration-prepare-a-script
1182 // Insert the following case to prepare a script step 25.2:
1183 // (Impl Note: the latest html spec is step 27.2)
1184 // Switch on the script's type:
1186 // Step 1. Let import map parse result be the result of create an import map
1187 // parse result, given source text, base URL and settings object.
1189 // Impl note: According to the spec, ImportMap::ParseString will throw a
1190 // TypeError if there's any invalid key/value in the text. After the parsing
1191 // is done, we should report the error if there's any, this is done in
1194 // See https://wicg.github.io/import-maps/#register-an-import-map, step 7.
1195 return ImportMap::ParseString(jsapi
.cx(), text
, aRequest
->mBaseURL
, warning
);
1198 void ModuleLoaderBase::RegisterImportMap(UniquePtr
<ImportMap
> aImportMap
) {
1199 // Check for aImportMap is done in ScriptLoader.
1200 MOZ_ASSERT(aImportMap
);
1202 // Step 8. Set element’s node document's import map to import map parse
1203 // result’s import map.
1204 mImportMap
= std::move(aImportMap
);
1210 } // namespace JS::loader