Bug 1769170 [wpt PR 34049] - Update wpt metadata, a=testonly
[gecko.git] / js / loader / ModuleLoaderBase.cpp
blobe89d599f106e4f483af924363bc230ecaeef2e7d
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"
32 using mozilla::Err;
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(
42 "ModuleLoaderBase");
44 #undef LOG
45 #define LOG(args) \
46 MOZ_LOG(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug, \
47 args)
49 #define LOG_ENABLED() \
50 MOZ_LOG_TEST(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug)
51 // ModuleLoaderBase
52 //////////////////////////////////////////////////////////////
54 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase)
55 NS_INTERFACE_MAP_END
57 NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase, mFetchedModules,
58 mDynamicImportRequests, mGlobalObject, mEventTarget,
59 mLoader)
61 NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase)
62 NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase)
64 // static
65 void ModuleLoaderBase::EnsureModuleHooksInitialized() {
66 AutoJSAPI jsapi;
67 jsapi.Init();
68 JSRuntime* rt = JS_GetRuntime(jsapi.cx());
69 if (JS::GetModuleResolveHook(rt)) {
70 return;
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",
81 (void*)nullptr);
84 // static
85 void ModuleLoaderBase::DynamicImportPrefChangedCallback(const char* aPrefName,
86 void* aClosure) {
87 bool enabled = Preferences::GetBool(aPrefName);
88 JS::ModuleDynamicImportHook hook =
89 enabled ? HostImportModuleDynamically : nullptr;
91 AutoJSAPI jsapi;
92 jsapi.Init();
93 JSRuntime* rt = JS_GetRuntime(jsapi.cx());
94 JS::SetModuleDynamicImportHook(rt, hook);
97 // 8.1.3.8.1 HostResolveImportedModule(referencingModule, moduleRequest)
98 /**
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.
110 // static
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
118 // hazard
119 RefPtr<LoadedScript> script(
120 GetLoadedScriptOrNull(aCx, aReferencingPrivate));
122 JS::Rooted<JSString*> specifierString(
123 aCx, JS::GetModuleRequestSpecifier(aCx, aModuleRequest));
124 if (!specifierString) {
125 return nullptr;
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)) {
132 return nullptr;
135 RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx);
136 if (!loader) {
137 return nullptr;
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());
156 return module;
159 // static
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);
169 nsAutoCString url;
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()));
174 if (!urlString) {
175 JS_ReportOutOfMemory(aCx);
176 return false;
179 return JS_DefineProperty(aCx, aMetaObject, "url", urlString,
180 JSPROP_ENUMERATE);
183 // static
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) {
192 return false;
195 // Attempt to resolve the module specifier.
196 nsAutoJSString specifier;
197 if (!specifier.init(aCx, specifierString)) {
198 return false;
201 RefPtr<ModuleLoaderBase> loader = GetCurrentModuleLoader(aCx);
202 if (!loader) {
203 return false;
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);
211 if (NS_FAILED(rv)) {
212 JS_ReportOutOfMemory(aCx);
213 return false;
216 JS_SetPendingException(aCx, error);
217 return false;
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);
225 if (!request) {
226 JS_ReportErrorASCII(aCx, "Dynamic import not supported in this context");
227 return false;
230 loader->StartDynamicImport(request);
231 return true;
234 bool ModuleLoaderBase::HostGetSupportedImportAssertions(
235 JSContext* aCx, JS::ImportAssertionVector& aValues) {
236 MOZ_ASSERT(aValues.empty());
238 if (!aValues.reserve(1)) {
239 JS_ReportOutOfMemory(aCx);
240 return false;
243 aValues.infallibleAppend(JS::ImportAssertion::Type);
245 return true;
248 // static
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));
255 if (!object) {
256 return nullptr;
259 nsIGlobalObject* global = xpc::NativeGlobal(object);
260 if (!global) {
261 return nullptr;
264 ModuleLoaderBase* loader = global->GetModuleLoader(aCx);
265 if (!loader) {
266 return nullptr;
269 MOZ_ASSERT(loader->mGlobalObject == global);
271 reportError.release();
272 return loader;
275 // static
276 LoadedScript* ModuleLoaderBase::GetLoadedScriptOrNull(
277 JSContext* aCx, JS::Handle<JS::Value> aReferencingPrivate) {
278 if (aReferencingPrivate.isUndefined()) {
279 return nullptr;
282 auto* script = static_cast<LoadedScript*>(aReferencingPrivate.toPrivate());
283 if (script->IsEventScript()) {
284 return nullptr;
287 MOZ_ASSERT_IF(
288 script->IsModuleScript(),
289 JS::GetModulePrivate(script->AsModuleScript()->ModuleRecord()) ==
290 aReferencingPrivate);
292 return script;
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
311 // "fetching" map.
312 MOZ_ASSERT_IF(aRestart == RestartRequest::Yes,
313 IsModuleFetching(aRequest->mURI));
315 // Check with the derived class whether we should load this module.
316 nsresult rv = NS_OK;
317 if (!CanStartLoad(aRequest, &rv)) {
318 return 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);
331 return NS_OK;
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());
343 return NS_OK;
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();
362 nsAutoCString spec;
363 nsresult rv = uri->GetSpec(spec);
364 NS_ENSURE_SUCCESS(rv, rv);
366 aURLs.AppendElement(spec);
369 return NS_OK;
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);
392 LOG(
393 ("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == "
394 "%u)",
395 aRequest, aRequest->mModuleScript.get(), unsigned(aResult)));
397 RefPtr<mozilla::GenericNonExclusivePromise::Private> promise;
398 if (!mFetchingModules.Remove(aRequest->mURI, getter_AddRefs(promise))) {
399 LOG(
400 ("ScriptLoadRequest (%p): Key not found in mFetchingModules, "
401 "assuming we have an inline module or have finished fetching already",
402 aRequest));
403 return;
406 RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
407 MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);
409 mFetchedModules.InsertOrUpdate(aRequest->mURI, RefPtr{moduleScript});
411 if (promise) {
412 if (moduleScript) {
413 LOG(("ScriptLoadRequest (%p): resolving %p", aRequest, promise.get()));
414 promise->Resolve(true, __func__);
415 } else {
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)) {
428 if (!entry.Data()) {
429 entry.Data() = new mozilla::GenericNonExclusivePromise::Private(__func__);
431 return entry.Data();
434 RefPtr<ModuleScript> ms;
435 MOZ_ALWAYS_TRUE(mFetchedModules.Get(aURL, getter_AddRefs(ms)));
436 if (!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 {
445 if (LOG_ENABLED()) {
446 nsAutoCString url;
447 aURL->GetAsciiSpec(url);
448 LOG(("GetFetchedModule %s", url.get()));
451 bool found;
452 ModuleScript* ms = mFetchedModules.GetWeak(aURL, &found);
453 MOZ_ASSERT(found);
454 return ms;
457 nsresult ModuleLoaderBase::OnFetchComplete(ModuleLoadRequest* aRequest,
458 nsresult aRv) {
459 MOZ_ASSERT(aRequest->mLoader == this);
460 MOZ_ASSERT(!aRequest->mModuleScript);
462 nsresult rv = aRv;
463 if (NS_SUCCEEDED(rv)) {
464 rv = CreateModuleScript(aRequest);
466 aRequest->ClearScriptSource();
468 if (NS_FAILED(rv)) {
469 aRequest->LoadFailed();
470 return rv;
474 MOZ_ASSERT(NS_SUCCEEDED(rv) == bool(aRequest->mModuleScript));
475 SetModuleFetchFinishedAndResumeWaitingRequests(aRequest, rv);
477 if (aRequest->mModuleScript && !aRequest->mModuleScript->HasParseError()) {
478 StartFetchingModuleDependencies(aRequest);
481 return NS_OK;
484 nsresult ModuleLoaderBase::CreateModuleScript(ModuleLoadRequest* aRequest) {
485 MOZ_ASSERT(!aRequest->mModuleScript);
486 MOZ_ASSERT(aRequest->mBaseURL);
488 LOG(("ScriptLoadRequest (%p): Create module script", aRequest));
490 AutoJSAPI jsapi;
491 if (!jsapi.Init(mGlobalObject)) {
492 return NS_ERROR_FAILURE;
495 nsresult rv;
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));
512 if (module) {
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,
518 nullptr)) {
519 return NS_ERROR_OUT_OF_MEMORY;
523 RefPtr<ModuleScript> moduleScript =
524 new ModuleScript(aRequest->mFetchOptions, aRequest->mBaseURL);
525 aRequest->mModuleScript = moduleScript;
527 if (!module) {
528 LOG(("ScriptLoadRequest (%p): compilation failed (%d)", aRequest,
529 unsigned(rv)));
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();
540 return NS_OK;
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);
548 if (NS_FAILED(rv)) {
549 aRequest->ModuleErrored();
550 return NS_OK;
554 LOG(("ScriptLoadRequest (%p): module script == %p", aRequest,
555 aRequest->mModuleScript.get()));
557 return rv;
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);
565 if (aScript) {
566 nsAutoCString url;
567 aScript->BaseURL()->GetAsciiSpec(url);
568 filename = JS_NewStringCopyZ(aCx, url.get());
569 } else {
570 filename = JS_NewStringCopyZ(aCx, "(unknown)");
573 if (!filename) {
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()));
587 if (!string) {
588 return NS_ERROR_OUT_OF_MEMORY;
591 if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber,
592 aColumnNumber, nullptr, string, JS::NothingHandleValue,
593 aErrorOut)) {
594 return NS_ERROR_OUT_OF_MEMORY;
597 return NS_OK;
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,
611 aSpecifier);
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;
638 if (aScript) {
639 baseURL = aScript->BaseURL();
640 } else {
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;
656 AutoJSAPI jsapi;
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);
667 uint32_t length;
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));
680 MOZ_ASSERT(str);
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
688 // and requested.
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,
695 &columnNumber);
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();
707 if (aUrlsOut) {
708 aUrlsOut->AppendElement(uri.forget());
712 return NS_OK;
715 void ModuleLoaderBase::StartFetchingModuleDependencies(
716 ModuleLoadRequest* aRequest) {
717 LOG(("ScriptLoadRequest (%p): Start fetching module dependencies", aRequest));
719 if (aRequest->IsCanceled()) {
720 return;
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);
734 if (NS_FAILED(rv)) {
735 aRequest->mModuleScript = nullptr;
736 aRequest->ModuleErrored();
737 return;
740 // Remove already visited URLs from the list. Put unvisited URLs into the
741 // visited set.
742 int32_t i = 0;
743 while (i < urls.Count()) {
744 nsIURI* url = urls[i];
745 if (visitedSet->Contains(url)) {
746 urls.RemoveObjectAt(i);
747 } else {
748 visitedSet->PutEntry(url);
749 i++;
753 if (urls.Count() == 0) {
754 // There are no descendants to load so this request is ready.
755 aRequest->DependenciesLoaded();
756 return;
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,
778 nsIURI* aURI) {
779 MOZ_ASSERT(aURI);
781 RefPtr<ModuleLoadRequest> childRequest = CreateStaticImport(aURI, aParent);
783 aParent->mImports.AppendElement(childRequest);
785 if (LOG_ENABLED()) {
786 nsAutoCString url1;
787 aParent->mURI->GetAsciiSpec(url1);
789 nsAutoCString url2;
790 aURI->GetAsciiSpec(url2);
792 LOG(("ScriptLoadRequest (%p): Start fetching dependency %p", aParent,
793 childRequest.get()));
794 LOG(("StartFetchingModuleAndDependencies \"%s\" -> \"%s\"", url1.get(),
795 url2.get()));
798 RefPtr<mozilla::GenericPromise> ready = childRequest->mReady.Ensure(__func__);
800 nsresult rv = StartModuleLoad(childRequest);
801 if (NS_FAILED(rv)) {
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__);
808 return ready;
811 return ready;
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);
822 if (NS_FAILED(rv)) {
823 mLoader->ReportErrorToConsole(aRequest, rv);
824 FinishDynamicImportAndReject(aRequest, rv);
828 void ModuleLoaderBase::FinishDynamicImportAndReject(ModuleLoadRequest* aRequest,
829 nsresult aResult) {
830 AutoJSAPI jsapi;
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),
878 mLoader(aLoader) {
879 MOZ_ASSERT(mGlobalObject);
880 MOZ_ASSERT(mEventTarget);
881 MOZ_ASSERT(mLoader);
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;
897 mLoader = nullptr;
900 bool ModuleLoaderBase::HasPendingDynamicImports() const {
901 return !mDynamicImportRequests.isEmpty();
904 void ModuleLoaderBase::CancelDynamicImport(ModuleLoadRequest* aRequest,
905 nsresult aResult) {
906 MOZ_ASSERT(aRequest->mLoader == this);
908 RefPtr<ScriptLoadRequest> req = mDynamicImportRequests.Steal(aRequest);
909 aRequest->Cancel();
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);
921 #ifdef DEBUG
922 bool ModuleLoaderBase::HasDynamicImport(
923 const ModuleLoadRequest* aRequest) const {
924 MOZ_ASSERT(aRequest->mLoader == this);
925 return mDynamicImportRequests.Contains(
926 const_cast<ModuleLoadRequest*>(aRequest));
928 #endif
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()) {
943 return error;
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));
968 return true;
971 MOZ_ASSERT(moduleScript->ModuleRecord());
973 AutoJSAPI jsapi;
974 if (NS_WARN_IF(!jsapi.Init(moduleScript->ModuleRecord()))) {
975 return false;
978 JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
979 if (!xpc::Scriptability::Get(module).Allowed()) {
980 return true;
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)) {
988 return false;
990 MOZ_ASSERT(!exception.isUndefined());
991 moduleScript->SetErrorToRethrow(exception);
994 return true;
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()) {
1008 return NS_OK;
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());
1017 MOZ_ASSERT(module);
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();
1024 return NS_OK;
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);
1088 return NS_OK;
1091 JS::Rooted<JSObject*> module(aCx, moduleScript->ModuleRecord());
1092 MOZ_ASSERT(module);
1094 if (!xpc::Scriptability::Get(module).Allowed()) {
1095 return NS_OK;
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));
1116 if (!ok) {
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);
1132 } else {
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,
1136 errorBehaviour)) {
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,
1144 NS_OK);
1146 mLoader->MaybeTriggerBytecodeEncoding();
1148 return rv;
1151 void ModuleLoaderBase::CancelAndClearDynamicImports() {
1152 for (ScriptLoadRequest* req = mDynamicImportRequests.getFirst(); req;
1153 req = req->getNext()) {
1154 req->Cancel();
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) {
1165 AutoJSAPI jsapi;
1166 if (!jsapi.Init(GetGlobalObject())) {
1167 return nullptr;
1170 MOZ_ASSERT(aRequest->IsTextSource());
1171 MaybeSourceText maybeSource;
1172 nsresult rv = aRequest->GetScriptSource(jsapi.cx(), &maybeSource);
1173 if (NS_FAILED(rv)) {
1174 return nullptr;
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:
1185 // "importmap"
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
1192 // ~AutoJSAPI.
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);
1207 #undef LOG
1208 #undef LOG_ENABLED
1210 } // namespace JS::loader