/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GeckoProfiler.h" #include "LoadedScript.h" #include "ModuleLoadRequest.h" #include "ScriptLoadRequest.h" #include "mozilla/dom/ScriptSettings.h" // AutoJSAPI #include "mozilla/dom/ScriptTrace.h" #include "js/Array.h" // JS::GetArrayLength #include "js/CompilationAndEvaluation.h" #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin #include "js/ContextOptions.h" // JS::ContextOptionsRef #include "js/ErrorReport.h" // JSErrorBase #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/Modules.h" // JS::FinishLoadingImportedModule, JS::{G,S}etModuleResolveHook, JS::Get{ModulePrivate,ModuleScript,RequestedModule{s,Specifier,SourcePos}}, JS::SetModule{Load,Metadata}Hook #include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetElement #include "js/SourceText.h" #include "mozilla/Assertions.h" // MOZ_ASSERT #include "mozilla/BasePrincipal.h" #include "mozilla/dom/AutoEntryScript.h" #include "mozilla/dom/ScriptLoadContext.h" #include "mozilla/CycleCollectedJSContext.h" // nsAutoMicroTask #include "mozilla/Preferences.h" #include "mozilla/RefPtr.h" // mozilla::StaticRefPtr #include "mozilla/StaticPrefs_dom.h" #include "nsContentUtils.h" #include "nsICacheInfoChannel.h" // nsICacheInfoChannel #include "nsNetUtil.h" // NS_NewURI #include "xpcpublic.h" using mozilla::AutoSlowOperation; using mozilla::CycleCollectedJSContext; using mozilla::Err; using mozilla::MicroTaskRunnable; using mozilla::Preferences; using mozilla::UniquePtr; using mozilla::WrapNotNull; using mozilla::dom::AutoJSAPI; using mozilla::dom::ReferrerPolicy; namespace JS::loader { mozilla::LazyLogModule ModuleLoaderBase::gCspPRLog("CSP"); mozilla::LazyLogModule ModuleLoaderBase::gModuleLoaderBaseLog( "ModuleLoaderBase"); #undef LOG #define LOG(args) \ MOZ_LOG(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug, \ args) #define LOG_ENABLED() \ MOZ_LOG_TEST(ModuleLoaderBase::gModuleLoaderBaseLog, mozilla::LogLevel::Debug) ////////////////////////////////////////////////////////////// // ModuleLoaderBase::LoadingRequest ////////////////////////////////////////////////////////////// NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase::LoadingRequest) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase::LoadingRequest, mRequest, mWaiting) NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase::LoadingRequest) NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase::LoadingRequest) ////////////////////////////////////////////////////////////// // ModuleLoaderBase ////////////////////////////////////////////////////////////// NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ModuleLoaderBase) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTION(ModuleLoaderBase, mFetchingModules, mFetchedModules, mDynamicImportRequests, mGlobalObject, mOverriddenBy, mLoader) NS_IMPL_CYCLE_COLLECTING_ADDREF(ModuleLoaderBase) NS_IMPL_CYCLE_COLLECTING_RELEASE(ModuleLoaderBase) // static void ModuleLoaderBase::EnsureModuleHooksInitialized() { AutoJSAPI jsapi; jsapi.Init(); JSRuntime* rt = JS_GetRuntime(jsapi.cx()); if (GetModuleLoadHook(rt)) { return; } SetModuleLoadHook(rt, HostLoadImportedModule); SetModuleMetadataHook(rt, HostPopulateImportMeta); SetScriptPrivateReferenceHooks(rt, HostAddRefTopLevelScript, HostReleaseTopLevelScript); } static bool CreateBadModuleTypeError(JSContext* aCx, LoadedScript* aScript, nsIURI* aURI, MutableHandle aErrorOut) { Rooted filename(aCx); if (aScript) { nsAutoCString url; aScript->BaseURL()->GetAsciiSpec(url); filename = JS_NewStringCopyZ(aCx, url.get()); } else { filename = JS_NewStringCopyZ(aCx, "(unknown)"); } if (!filename) { return false; } MOZ_ASSERT(aURI); nsAutoCString url; aURI->GetSpec(url); Rooted uri(aCx, JS_NewStringCopyZ(aCx, url.get())); if (!uri) { return false; } Rooted msg(aCx, JS_NewStringCopyZ(aCx, ": invalid module type")); if (!msg) { return false; } Rooted errMsg(aCx, JS_ConcatStrings(aCx, uri, msg)); if (!errMsg) { return false; } return CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, 0, ColumnNumberOneOrigin(), nullptr, errMsg, NothingHandleValue, aErrorOut); } // https://html.spec.whatwg.org/#hostloadimportedmodule // static bool ModuleLoaderBase::HostLoadImportedModule( JSContext* aCx, Handle aReferrer, Handle aModuleRequest, Handle aHostDefined, Handle aPayload, uint32_t aLineNumber, JS::ColumnNumberOneOrigin aColumnNumber) { Rooted object(aCx); if (aPayload.isObject()) { object = &aPayload.toObject(); } bool isDynamicImport = object && IsPromiseObject(object); Rooted specifierString( aCx, GetModuleRequestSpecifier(aCx, aModuleRequest)); if (!specifierString) { JS_ReportOutOfMemory(aCx); return false; } // Let url be the result of resolving a module specifier given referencing // module script and specifier. nsAutoJSString string; if (!string.init(aCx, specifierString)) { JS_ReportOutOfMemory(aCx); return false; } RefPtr loader = GetCurrentModuleLoader(aCx); if (!loader) { return false; } if (isDynamicImport && !loader->IsDynamicImportSupported()) { JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_DYNAMIC_IMPORT_NOT_SUPPORTED); return false; } { // LoadedScript should only live in this block, otherwise it will be a GC // hazard RefPtr script(GetLoadedScriptOrNull(aReferrer)); // Step 8. Let url be the result of resolving a module specifier given // referencingScript and moduleRequest.[[Specifier]], catching any // exceptions. If they throw an exception, let resolutionError be the // thrown exception. auto result = loader->ResolveModuleSpecifier(script, string); // Step 9. If the previous step threw an exception, then: if (result.isErr()) { Rooted error(aCx); nsresult rv = loader->HandleResolveFailure(aCx, script, string, result.unwrapErr(), aLineNumber, aColumnNumber, &error); if (NS_FAILED(rv)) { JS_ReportOutOfMemory(aCx); return false; } // Step 2. Perform FinishLoadingImportedModule(referrer, moduleRequest, // payload, ThrowCompletion(resolutionError)). FinishLoadingImportedModuleFailed(aCx, aPayload, error); // Step 3. Return. return true; } MOZ_ASSERT(result.isOk()); nsCOMPtr uri = result.unwrap(); MOZ_ASSERT(uri, "Failed to resolve module specifier"); ModuleType moduleType = GetModuleRequestType(aCx, aModuleRequest); if (!loader->IsModuleTypeAllowed(moduleType)) { LOG(("ModuleLoaderBase::HostLoadImportedModule uri %s, bad module type", uri->GetSpecOrDefault().get())); Rooted error(aCx); if (!CreateBadModuleTypeError(aCx, script, uri, &error)) { JS_ReportOutOfMemory(aCx); return false; } JS_SetPendingException(aCx, error); return false; } RefPtr options = nullptr; ReferrerPolicy referrerPolicy; nsIURI* fetchReferrer = nullptr; if (script) { options = script->GetFetchOptions(); referrerPolicy = script->ReferrerPolicy(); fetchReferrer = script->BaseURL(); } else { options = loader->CreateDefaultScriptFetchOptions(); referrerPolicy = ReferrerPolicy::_empty; fetchReferrer = loader->GetClientReferrerURI(); } mozilla::dom::SRIMetadata sriMetadata; loader->GetImportMapSRI( uri, fetchReferrer, loader->GetScriptLoaderInterface()->GetConsoleReportCollector(), &sriMetadata); RefPtr request = loader->CreateRequest( aCx, uri, aModuleRequest, aHostDefined, aPayload, isDynamicImport, options, referrerPolicy, fetchReferrer, sriMetadata); if (!request) { MOZ_ASSERT(isDynamicImport); nsAutoCString url; uri->GetSpec(url); JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_DYNAMIC_IMPORT_FAILED, url.get()); return false; } LOG( ("ModuleLoaderBase::HostLoadImportedModule loader (%p) uri %s referrer " "(%p) request (%p)", loader.get(), uri->GetSpecOrDefault().get(), aReferrer.get(), request.get())); request->SetImport(aReferrer, aModuleRequest, aPayload); if (isDynamicImport) { loader->AppendDynamicImport(request); } nsresult rv = loader->StartModuleLoad(request); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_ASSERT(!request->mModuleScript); loader->GetScriptLoaderInterface()->ReportErrorToConsole(request, rv); if (isDynamicImport) { loader->RemoveDynamicImport(request); nsAutoCString url; uri->GetSpec(url); JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_DYNAMIC_IMPORT_FAILED, url.get()); } else { loader->OnFetchFailed(request); return true; } return false; } if (isDynamicImport) { loader->OnDynamicImportStarted(request); } } return true; } // static bool ModuleLoaderBase::FinishLoadingImportedModule( JSContext* aCx, ModuleLoadRequest* aRequest) { // The request should been removed from mDynamicImportRequests. MOZ_ASSERT_IF(aRequest->IsDynamicImport(), !aRequest->mLoader->HasDynamicImport(aRequest)); Rooted module(aCx); { ModuleScript* moduleScript = aRequest->mModuleScript; MOZ_ASSERT(moduleScript); MOZ_ASSERT(moduleScript->ModuleRecord()); module.set(moduleScript->ModuleRecord()); } MOZ_ASSERT(module); Rooted referrer(aCx, aRequest->mReferrerScript); Rooted moduleReqObj(aCx, aRequest->mModuleRequestObj); Rooted statePrivate(aCx, aRequest->mPayload); Rooted payload(aCx, aRequest->mPayload); LOG(("ScriptLoadRequest (%p): FinishLoadingImportedModule module (%p)", aRequest, module.get())); bool usePromise = aRequest->HasScriptLoadContext(); MOZ_ALWAYS_TRUE(JS::FinishLoadingImportedModule(aCx, referrer, moduleReqObj, payload, module, usePromise)); MOZ_ASSERT(!JS_IsExceptionPending(aCx)); aRequest->ClearImport(); return true; } // static bool ModuleLoaderBase::ImportMetaResolve(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedValue modulePrivate( cx, js::GetFunctionNativeReserved( &args.callee(), static_cast(ImportMetaSlots::ModulePrivateSlot))); // https://html.spec.whatwg.org/#hostgetimportmetaproperties // Step 4.1. Set specifier to ? ToString(specifier). // // https://tc39.es/ecma262/#sec-tostring RootedValue v(cx, args.get(ImportMetaResolveSpecifierArg)); RootedString specifier(cx, ToString(cx, v)); if (!specifier) { return false; } // Step 4.2, 4.3 are implemented in ImportMetaResolveImpl. RootedString url(cx, ImportMetaResolveImpl(cx, modulePrivate, specifier)); if (!url) { return false; } // Step 4.4. Return the serialization of url. args.rval().setString(url); return true; } // static JSString* ModuleLoaderBase::ImportMetaResolveImpl( JSContext* aCx, Handle aReferencingPrivate, Handle aSpecifier) { RootedString urlString(aCx); { // ModuleScript should only live in this block, otherwise it will be a GC // hazard RefPtr script = static_cast(aReferencingPrivate.toPrivate()); MOZ_ASSERT(script->IsModuleScript()); MOZ_ASSERT(GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate); RefPtr loader = GetCurrentModuleLoader(aCx); if (!loader) { return nullptr; } nsAutoJSString specifier; if (!specifier.init(aCx, aSpecifier)) { return nullptr; } auto result = loader->ResolveModuleSpecifier(script, specifier); if (result.isErr()) { Rooted error(aCx); nsresult rv = loader->HandleResolveFailure( aCx, script, specifier, result.unwrapErr(), 0, ColumnNumberOneOrigin(), &error); if (NS_FAILED(rv)) { JS_ReportOutOfMemory(aCx); return nullptr; } JS_SetPendingException(aCx, error); return nullptr; } nsCOMPtr uri = result.unwrap(); nsAutoCString url; MOZ_ALWAYS_SUCCEEDS(uri->GetAsciiSpec(url)); urlString.set(JS_NewStringCopyZ(aCx, url.get())); } return urlString; } // static bool ModuleLoaderBase::HostPopulateImportMeta(JSContext* aCx, Handle aReferencingPrivate, Handle aMetaObject) { RefPtr script = static_cast(aReferencingPrivate.toPrivate()); MOZ_ASSERT(script->IsModuleScript()); MOZ_ASSERT(GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate); nsAutoCString url; MOZ_DIAGNOSTIC_ASSERT(script->BaseURL()); MOZ_ALWAYS_SUCCEEDS(script->BaseURL()->GetAsciiSpec(url)); Rooted urlString(aCx, JS_NewStringCopyZ(aCx, url.get())); if (!urlString) { JS_ReportOutOfMemory(aCx); return false; } // https://html.spec.whatwg.org/#import-meta-url if (!JS_DefineProperty(aCx, aMetaObject, "url", urlString, JSPROP_ENUMERATE)) { return false; } // https://html.spec.whatwg.org/#import-meta-resolve // Define 'resolve' function on the import.meta object. JSFunction* resolveFunc = js::DefineFunctionWithReserved( aCx, aMetaObject, "resolve", ImportMetaResolve, ImportMetaResolveNumArgs, JSPROP_ENUMERATE); if (!resolveFunc) { return false; } // Store the 'active script' of the meta object into the function slot. // https://html.spec.whatwg.org/#active-script RootedObject resolveFuncObj(aCx, JS_GetFunctionObject(resolveFunc)); js::SetFunctionNativeReserved( resolveFuncObj, static_cast(ImportMetaSlots::ModulePrivateSlot), aReferencingPrivate); return true; } AutoOverrideModuleLoader::AutoOverrideModuleLoader(ModuleLoaderBase* aTarget, ModuleLoaderBase* aLoader) : mTarget(aTarget) { mTarget->SetOverride(aLoader); } AutoOverrideModuleLoader::~AutoOverrideModuleLoader() { mTarget->ResetOverride(); } void ModuleLoaderBase::SetOverride(ModuleLoaderBase* aLoader) { MOZ_ASSERT(!mOverriddenBy); MOZ_ASSERT(!aLoader->mOverriddenBy); MOZ_ASSERT(mGlobalObject == aLoader->mGlobalObject); mOverriddenBy = aLoader; } bool ModuleLoaderBase::IsOverridden() { return !!mOverriddenBy; } bool ModuleLoaderBase::IsOverriddenBy(ModuleLoaderBase* aLoader) { return mOverriddenBy == aLoader; } void ModuleLoaderBase::ResetOverride() { MOZ_ASSERT(mOverriddenBy); mOverriddenBy = nullptr; } // static ModuleLoaderBase* ModuleLoaderBase::GetCurrentModuleLoader(JSContext* aCx) { auto reportError = mozilla::MakeScopeExit([aCx]() { JS_ReportErrorASCII(aCx, "No ScriptLoader found for the current context"); }); Rooted object(aCx, CurrentGlobalOrNull(aCx)); if (!object) { return nullptr; } nsIGlobalObject* global = xpc::NativeGlobal(object); if (!global) { return nullptr; } ModuleLoaderBase* loader = global->GetModuleLoader(aCx); if (!loader) { return nullptr; } MOZ_ASSERT(loader->mGlobalObject == global); reportError.release(); if (loader->mOverriddenBy) { MOZ_ASSERT(loader->mOverriddenBy->mGlobalObject == global); return loader->mOverriddenBy; } return loader; } // static LoadedScript* ModuleLoaderBase::GetLoadedScriptOrNull( Handle aReferrer) { if (!aReferrer) { return nullptr; } Value value = GetScriptPrivate(aReferrer); if (value.isUndefined()) { return nullptr; } return static_cast(value.toPrivate()); } nsresult ModuleLoaderBase::StartModuleLoad(ModuleLoadRequest* aRequest) { return StartOrRestartModuleLoad(aRequest, RestartRequest::No); } nsresult ModuleLoaderBase::RestartModuleLoad(ModuleLoadRequest* aRequest) { return StartOrRestartModuleLoad(aRequest, RestartRequest::Yes); } nsresult ModuleLoaderBase::StartOrRestartModuleLoad(ModuleLoadRequest* aRequest, RestartRequest aRestart) { MOZ_ASSERT(aRequest->mLoader == this); MOZ_ASSERT(aRequest->IsFetching()); // NOTE: The LoadedScript::mDataType field used by the IsStencil call can be // modified asynchronously after the StartFetch call. // In order to avoid the race condition, cache the value here. bool isCachedStencil = aRequest->IsCachedStencil(); MOZ_ASSERT_IF(isCachedStencil, aRestart == RestartRequest::No); if (!isCachedStencil) { aRequest->SetUnknownDataType(); } if (LOG_ENABLED()) { nsAutoCString url; aRequest->URI()->GetAsciiSpec(url); LOG(("ScriptLoadRequest (%p): Start module load %s", aRequest, url.get())); } // If we're restarting the request, the module should already be in the // "fetching" map. MOZ_ASSERT_IF( aRestart == RestartRequest::Yes, IsModuleFetching(ModuleMapKey(aRequest->URI(), aRequest->mModuleType))); // Check with the derived class whether we should load this module. nsresult rv = NS_OK; if (!CanStartLoad(aRequest, &rv)) { return rv; } // Check whether the module has been fetched or is currently being fetched, // and if so wait for it rather than starting a new fetch. if (aRestart == RestartRequest::No && ModuleMapContainsURL( ModuleMapKey(aRequest->URI(), aRequest->mModuleType))) { LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest)); WaitForModuleFetch(aRequest); return NS_OK; } rv = StartFetch(aRequest); NS_ENSURE_SUCCESS(rv, rv); if (isCachedStencil) { MOZ_ASSERT( IsModuleFetched(ModuleMapKey(aRequest->URI(), aRequest->mModuleType))); return NS_OK; } // We successfully started fetching a module so put its URL in the module // map and mark it as fetching. if (aRestart == RestartRequest::No) { SetModuleFetchStarted(aRequest); } return NS_OK; } bool ModuleLoaderBase::ModuleMapContainsURL(const ModuleMapKey& key) const { return IsModuleFetching(key) || IsModuleFetched(key); } bool ModuleLoaderBase::IsModuleFetching(const ModuleMapKey& key) const { return mFetchingModules.Contains(key); } bool ModuleLoaderBase::IsModuleFetched(const ModuleMapKey& key) const { return mFetchedModules.Contains(key); } nsresult ModuleLoaderBase::GetFetchedModuleURLs(nsTArray& aURLs) { for (const auto& entry : mFetchedModules) { nsIURI* uri = entry.GetData()->BaseURL(); nsAutoCString spec; nsresult rv = uri->GetSpec(spec); NS_ENSURE_SUCCESS(rv, rv); aURLs.AppendElement(spec); } return NS_OK; } void ModuleLoaderBase::SetModuleFetchStarted(ModuleLoadRequest* aRequest) { // Update the module map to indicate that a module is currently being fetched. ModuleMapKey moduleMapKey(aRequest->URI(), aRequest->mModuleType); MOZ_ASSERT(aRequest->IsFetching()); MOZ_ASSERT(!ModuleMapContainsURL(moduleMapKey)); RefPtr loadingRequest = new LoadingRequest(); loadingRequest->mRequest = aRequest; mFetchingModules.InsertOrUpdate(moduleMapKey, loadingRequest); } already_AddRefed ModuleLoaderBase::SetModuleFetchFinishedAndGetWaitingRequests( ModuleLoadRequest* aRequest, nsresult aResult) { // Update module map with the result of fetching a single module script. // // If any requests for the same URL are waiting on this one to complete, call // ModuleLoaded or LoadFailed to resume or fail them as appropriate. MOZ_ASSERT(aRequest->mLoader == this); LOG( ("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == " "%u)", aRequest, aRequest->mModuleScript.get(), unsigned(aResult))); ModuleMapKey moduleMapKey(aRequest->URI(), aRequest->mModuleType); auto entry = mFetchingModules.Lookup(moduleMapKey); if (!entry) { LOG( ("ScriptLoadRequest (%p): Key not found in mFetchingModules, " "assuming we have an inline module or have finished fetching already", aRequest)); return nullptr; } // It's possible for a request to be cancelled and removed from the fetching // modules map and a new request started for the same URI and added to the // map. In this case we don't want the first cancelled request to complete the // later request (which will cause it to fail) so we ignore it. RefPtr loadingRequest = entry.Data(); if (loadingRequest->mRequest != aRequest) { MOZ_ASSERT(aRequest->IsCanceled()); LOG( ("ScriptLoadRequest (%p): Ignoring completion of cancelled request " "that was removed from the map", aRequest)); return nullptr; } MOZ_ALWAYS_TRUE(mFetchingModules.Remove(moduleMapKey)); RefPtr moduleScript(aRequest->mModuleScript); MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript); mFetchedModules.InsertOrUpdate(moduleMapKey, RefPtr{moduleScript}); return loadingRequest.forget(); } void ModuleLoaderBase::ResumeWaitingRequests(LoadingRequest* aLoadingRequest, bool aSuccess) { for (ModuleLoadRequest* request : aLoadingRequest->mWaiting) { ResumeWaitingRequest(request, aSuccess); } } void ModuleLoaderBase::ResumeWaitingRequest(ModuleLoadRequest* aRequest, bool aSuccess) { if (aSuccess) { aRequest->ModuleLoaded(); } if (!aRequest->IsErrored()) { OnFetchSucceeded(aRequest); } else { OnFetchFailed(aRequest); } } void ModuleLoaderBase::WaitForModuleFetch(ModuleLoadRequest* aRequest) { ModuleMapKey moduleMapKey(aRequest->URI(), aRequest->mModuleType); MOZ_ASSERT(ModuleMapContainsURL(moduleMapKey)); if (auto entry = mFetchingModules.Lookup(moduleMapKey)) { RefPtr loadingRequest = entry.Data(); loadingRequest->mWaiting.AppendElement(aRequest); return; } RefPtr ms; MOZ_ALWAYS_TRUE(mFetchedModules.Get(moduleMapKey, getter_AddRefs(ms))); ResumeWaitingRequest(aRequest, bool(ms)); } ModuleScript* ModuleLoaderBase::GetFetchedModule( const ModuleMapKey& moduleMapKey) const { if (LOG_ENABLED()) { nsAutoCString url; moduleMapKey.mUri->GetAsciiSpec(url); LOG(("GetFetchedModule %s", url.get())); } bool found; ModuleScript* ms = mFetchedModules.GetWeak(moduleMapKey, &found); MOZ_ASSERT(found); return ms; } nsresult ModuleLoaderBase::OnFetchComplete(ModuleLoadRequest* aRequest, nsresult aRv) { LOG(("ScriptLoadRequest (%p): OnFetchComplete result %x", aRequest, (unsigned)aRv)); MOZ_ASSERT(aRequest->mLoader == this); MOZ_ASSERT(!aRequest->mModuleScript); nsresult rv = aRv; if (NS_SUCCEEDED(rv)) { rv = CreateModuleScript(aRequest); #if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) // If a module script was created, it should either have a module record // object or a parse error. if (ModuleScript* ms = aRequest->mModuleScript) { MOZ_DIAGNOSTIC_ASSERT(bool(ms->ModuleRecord()) != ms->HasParseError()); } #endif if (aRequest->IsTextSource()) { aRequest->ClearScriptText(); } if (NS_FAILED(rv)) { aRequest->LoadFailed(); return rv; } } RefPtr waitingRequests = SetModuleFetchFinishedAndGetWaitingRequests(aRequest, rv); MOZ_ASSERT_IF(waitingRequests, waitingRequests->mRequest == aRequest); bool success = bool(aRequest->mModuleScript); MOZ_ASSERT(NS_SUCCEEDED(rv) == success); // TODO: https://github.com/whatwg/html/issues/11755 // Should we check if the module script has an error to rethrow as well? // // https://html.spec.whatwg.org/#hostloadimportedmodule:fetch-a-single-imported-module-script // Step 14. onSingleFetchComplete: step 2, and step 3 // return ThrowCompletion if moduleScript is null or its parse error is not // null: // Step 4. Otherwise, set completion to NormalCompletion(moduleScript's // record). if (!aRequest->IsErrored()) { OnFetchSucceeded(aRequest); } else { OnFetchFailed(aRequest); } if (!waitingRequests) { return NS_OK; } ResumeWaitingRequests(waitingRequests, success); return NS_OK; } void ModuleLoaderBase::OnFetchSucceeded(ModuleLoadRequest* aRequest) { // TODO, Bug 1990416: Align the loading descendants behavior of dynamic import // According to the spec, dynamic import should trigger fetching of // descendant modules in the JS engine. However, when the dynamic import’s // module graph is loaded, ScriptLoaders move the request to a Loaded event // queue and invoke ProcessDynamicImport. To account for this behavior, // we perform the fetching of submodules in the host layer instead. if (aRequest->IsTopLevel() || aRequest->IsDynamicImport()) { StartFetchingModuleDependencies(aRequest); } else { MOZ_ASSERT(!aRequest->IsDynamicImport()); AutoJSAPI jsapi; if (!jsapi.Init(mGlobalObject)) { return; } JSContext* cx = jsapi.cx(); FinishLoadingImportedModule(cx, aRequest); aRequest->SetReady(); aRequest->LoadFinished(); } } void ModuleLoaderBase::OnFetchFailed(ModuleLoadRequest* aRequest) { MOZ_ASSERT(aRequest->IsErrored()); AutoJSAPI jsapi; if (!jsapi.Init(mGlobalObject)) { return; } JSContext* cx = jsapi.cx(); if (aRequest->IsTopLevel()) { // https://html.spec.whatwg.org/#fetch-the-descendants-of-and-link-a-module-script // Step 2. If record is null, then: // Step 2.1. Set moduleScript's error to rethrow to moduleScript's parse // error. if (aRequest->mModuleScript && !aRequest->mModuleScript->ModuleRecord()) { MOZ_ASSERT(aRequest->mModuleScript->HasParseError()); Value parseError = aRequest->mModuleScript->ParseError(); LOG(("ScriptLoadRequest (%p): found parse error", aRequest)); aRequest->mModuleScript->SetErrorToRethrow(parseError); } DispatchModuleErrored(aRequest); return; } // The remaining case is static/dynamic import. MOZ_ASSERT(aRequest->IsStaticImport() || aRequest->IsDynamicImport()); MOZ_ASSERT(!aRequest->mPayload.isUndefined()); Rooted payload(cx, aRequest->mPayload); // https://html.spec.whatwg.org/#hostloadimportedmodule // // Step 14.2. If moduleScript is null, then set completion to Completion // Record { [[Type]]: throw, [[Value]]: a new TypeError, // [[Target]]: empty }. if (!aRequest->mModuleScript) { LOG( ("ScriptLoadRequest (%p): FinishLoadingImportedModule: module script " "is null", aRequest)); // Impl note: // For dynamic import, the TypeError will be pass to the rejected handler of // the LoadRequestedModules from the dynamic import. // // For static import/top-level import, the TypeError will be ignore in the // rejected handler of LoadRequestedModules (the state.[[ErrorToRethrow]] // isn't set). So we don't actually create a TypeError for the these two // cases. if (aRequest->GetRootModule()->IsDynamicImport()) { nsAutoCString url; aRequest->URI()->GetSpec(url); JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_DYNAMIC_IMPORT_FAILED, url.get()); FinishLoadingImportedModuleFailedWithPendingException(cx, payload); } else { Rooted error(cx, UndefinedValue()); FinishLoadingImportedModuleFailed(cx, payload, error); } aRequest->ModuleErrored(); aRequest->ClearImport(); return; } // Step 14.3. Otherwise, if moduleScript's parse error is not null, then: // 1. Let parseError be moduleScript's parse error. // 2. Set completion to Completion Record { [[Type]]: throw, // [[Value]]: parseError, [[Target]]: empty }. // 3. If loadState is not undefined and loadState.[[ErrorToRethrow]] // is null, set loadState.[[ErrorToRethrow]] to parseError. MOZ_ASSERT(aRequest->mModuleScript->HasParseError()); Rooted parseError(cx, aRequest->mModuleScript->ParseError()); LOG( ("ScriptLoadRequest (%p): FinishLoadingImportedModule: found parse " "error", aRequest)); // Step 14.5. Perform FinishLoadingImportedModule(referrer, moduleRequest, // payload, completion). FinishLoadingImportedModuleFailed(cx, payload, parseError); aRequest->ModuleErrored(); aRequest->ClearImport(); } void ModuleLoaderBase::Cancel(ModuleLoadRequest* aRequest) { if (aRequest->IsFinished()) { return; } aRequest->ScriptLoadRequest::Cancel(); aRequest->mModuleScript = nullptr; if (aRequest->mPayload.isUndefined()) { return; } AutoJSAPI jsapi; if (!jsapi.Init(mGlobalObject)) { return; } JSContext* cx = jsapi.cx(); Rooted payload(cx, aRequest->mPayload); Rooted error(cx, UndefinedValue()); LOG( ("ScriptLoadRequest (%p): Canceled, calling " "FinishLoadingImportedModuleFailed", aRequest)); FinishLoadingImportedModuleFailed(cx, payload, error); aRequest->ModuleErrored(); aRequest->ClearImport(); } class ModuleErroredRunnable : public MicroTaskRunnable { public: explicit ModuleErroredRunnable(ModuleLoadRequest* aRequest) : mRequest(aRequest) {} virtual void Run(AutoSlowOperation& aAso) override { mRequest->ModuleErrored(); } private: RefPtr mRequest; }; void ModuleLoaderBase::DispatchModuleErrored(ModuleLoadRequest* aRequest) { if (aRequest->HasScriptLoadContext() && aRequest->GetScriptLoadContext()->mIsInline) { // https://html.spec.whatwg.org/#prepare-the-script-element // Step 32. If el does not have a src content attribute: // 2. "module".3.1 // Queue an element task on the networking task source given el to // perform the following steps: CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); RefPtr runnable = new ModuleErroredRunnable(aRequest); context->DispatchToMicroTask(runnable.forget()); } else { aRequest->ModuleErrored(); } } nsresult ModuleLoaderBase::CreateModuleScript(ModuleLoadRequest* aRequest) { MOZ_ASSERT(!aRequest->mModuleScript); MOZ_ASSERT(aRequest->BaseURL()); LOG(("ScriptLoadRequest (%p): Create module script", aRequest)); AutoJSAPI jsapi; if (!jsapi.Init(mGlobalObject)) { return NS_ERROR_FAILURE; } nsresult rv; { JSContext* cx = jsapi.cx(); Rooted module(cx); CompileOptions options(cx); RootedScript introductionScript(cx); rv = mLoader->FillCompileOptionsForRequest(cx, aRequest, &options, &introductionScript); if (NS_SUCCEEDED(rv)) { Rooted global(cx, mGlobalObject->GetGlobalJSObject()); rv = CompileFetchedModule(cx, global, options, aRequest, &module); } MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr)); if (module) { RootedScript moduleScript(cx, GetModuleScript(module)); if (moduleScript) { RootedValue privateValue(cx); InstantiateOptions instantiateOptions(options); if (!UpdateDebugMetadata(cx, moduleScript, instantiateOptions, privateValue, nullptr, introductionScript, nullptr)) { return NS_ERROR_OUT_OF_MEMORY; } } } MOZ_ASSERT(aRequest->mLoadedScript->IsModuleScript()); RefPtr moduleScript = aRequest->mLoadedScript->AsModuleScript(); // Update the module script's referrer policy to reflect any changes made // to the ModuleLoadRequest during HTTP response parsing. if (moduleScript->ReferrerPolicy() != aRequest->ReferrerPolicy()) { moduleScript->UpdateReferrerPolicy(aRequest->ReferrerPolicy()); } aRequest->mModuleScript = moduleScript; moduleScript->SetForPreload(aRequest->mLoadContext->IsPreload()); moduleScript->SetHadImportMap(HasImportMapRegistered()); if (!module) { LOG(("ScriptLoadRequest (%p): compilation failed (%d)", aRequest, unsigned(rv))); Rooted error(cx); if (!jsapi.HasException() || !jsapi.StealException(&error) || error.isUndefined()) { aRequest->mModuleScript = nullptr; return NS_ERROR_FAILURE; } moduleScript->SetParseError(error); return NS_OK; } moduleScript->SetModuleRecord(module); } LOG(("ScriptLoadRequest (%p): module script == %p", aRequest, aRequest->mModuleScript.get())); return rv; } nsresult ModuleLoaderBase::GetResolveFailureMessage(ResolveError aError, const nsAString& aSpecifier, nsAString& aResult) { AutoTArray errorParams; errorParams.AppendElement(aSpecifier); nsresult rv = nsContentUtils::FormatLocalizedString( nsContentUtils::eDOM_PROPERTIES, ResolveErrorInfo::GetString(aError), errorParams, aResult); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult ModuleLoaderBase::HandleResolveFailure( JSContext* aCx, LoadedScript* aScript, const nsAString& aSpecifier, ResolveError aError, uint32_t aLineNumber, ColumnNumberOneOrigin aColumnNumber, MutableHandle aErrorOut) { Rooted filename(aCx); if (aScript) { nsAutoCString url; aScript->BaseURL()->GetAsciiSpec(url); filename = JS_NewStringCopyZ(aCx, url.get()); } else { filename = JS_NewStringCopyZ(aCx, "(unknown)"); } if (!filename) { return NS_ERROR_OUT_OF_MEMORY; } nsAutoString errorText; nsresult rv = GetResolveFailureMessage(aError, aSpecifier, errorText); NS_ENSURE_SUCCESS(rv, rv); Rooted string(aCx, JS_NewUCStringCopyZ(aCx, errorText.get())); if (!string) { return NS_ERROR_OUT_OF_MEMORY; } if (!CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber, aColumnNumber, nullptr, string, NothingHandleValue, aErrorOut)) { return NS_ERROR_OUT_OF_MEMORY; } return NS_OK; } ResolveResult ModuleLoaderBase::ResolveModuleSpecifier( LoadedScript* aScript, const nsAString& aSpecifier) { // Import Maps are not supported on workers/worklets. // See https://github.com/WICG/import-maps/issues/2 MOZ_ASSERT_IF(!NS_IsMainThread(), mImportMap == nullptr); // Forward to the updated 'Resolve a module specifier' algorithm defined in // the Import Maps spec. return ImportMap::ResolveModuleSpecifier(mImportMap.get(), mLoader, aScript, aSpecifier); } void ModuleLoaderBase::StartFetchingModuleDependencies( ModuleLoadRequest* aRequest) { if (aRequest->IsCanceled()) { return; } MOZ_ASSERT(aRequest->mModuleScript); MOZ_ASSERT(!aRequest->mModuleScript->HasParseError()); ModuleScript* moduleScript = aRequest->mModuleScript; MOZ_ASSERT(moduleScript->ModuleRecord()); MOZ_ASSERT(aRequest->IsFetching() || aRequest->IsCompiling()); MOZ_ASSERT(aRequest->IsTopLevel() || aRequest->IsDynamicImport()); AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) { return; } JSContext* cx = jsapi.cx(); Rooted module(cx, moduleScript->ModuleRecord()); LOG( ("ScriptLoadRequest (%p): module record (%p) Start fetching module " "dependencies", aRequest, module.get())); // Wrap the request into a JS::Value, and AddRef() it. // The Release() will be called in the resolved/rejected handlers. Rooted hostDefinedVal(cx, PrivateValue(aRequest)); aRequest->AddRef(); bool result = false; // PromiseJobRunnable::Call() is not executed if the global is being // destroyed. As a result, the promise returned by LoadRequestedModules may // neither resolve nor reject. To ensure module loading completes reliably in // chrome pages, we use the synchronous variant of LoadRequestedModules. bool isSync = aRequest->URI()->SchemeIs("chrome") || aRequest->URI()->SchemeIs("resource"); // TODO: Bug1973660: Use Promise version of LoadRequestedModules on Workers. if (aRequest->HasScriptLoadContext() && !isSync) { Rooted onResolved( cx, js::NewFunctionWithReserved(cx, OnLoadRequestedModulesResolved, OnLoadRequestedModulesResolvedNumArgs, 0, "resolved")); if (!onResolved) { JS_ReportOutOfMemory(cx); return; } RootedFunction onRejected( cx, js::NewFunctionWithReserved(cx, OnLoadRequestedModulesRejected, OnLoadRequestedModulesRejectedNumArgs, 0, "rejected")); if (!onRejected) { JS_ReportOutOfMemory(cx); return; } RootedObject resolveFuncObj(cx, JS_GetFunctionObject(onResolved)); js::SetFunctionNativeReserved(resolveFuncObj, LoadReactionHostDefinedSlot, hostDefinedVal); RootedObject rejectFuncObj(cx, JS_GetFunctionObject(onRejected)); js::SetFunctionNativeReserved(rejectFuncObj, LoadReactionHostDefinedSlot, hostDefinedVal); Rooted loadPromise(cx); result = LoadRequestedModules(cx, module, hostDefinedVal, &loadPromise); AddPromiseReactions(cx, loadPromise, resolveFuncObj, rejectFuncObj); } else { result = LoadRequestedModules(cx, module, hostDefinedVal, OnLoadRequestedModulesResolved, OnLoadRequestedModulesRejected); } if (!result) { LOG(("ScriptLoadRequest (%p): LoadRequestedModules failed", aRequest)); OnLoadRequestedModulesRejected(cx, aRequest, UndefinedHandleValue); } } // static bool ModuleLoaderBase::OnLoadRequestedModulesResolved(JSContext* aCx, unsigned aArgc, Value* aVp) { CallArgs args = CallArgsFromVp(aArgc, aVp); Rooted hostDefined(aCx); hostDefined = js::GetFunctionNativeReserved(&args.callee(), LoadReactionHostDefinedSlot); return OnLoadRequestedModulesResolved(aCx, hostDefined); } // static bool ModuleLoaderBase::OnLoadRequestedModulesResolved( JSContext* aCx, Handle aHostDefined) { auto* request = static_cast(aHostDefined.toPrivate()); MOZ_ASSERT(request); return OnLoadRequestedModulesResolved(request); } // static bool ModuleLoaderBase::OnLoadRequestedModulesResolved( ModuleLoadRequest* aRequest) { LOG(("ScriptLoadRequest (%p): LoadRequestedModules resolved", aRequest)); if (!aRequest->IsCanceled()) { aRequest->SetReady(); aRequest->LoadFinished(); } // Decrease the reference 'AddRef'ed when converting the hostDefined. aRequest->Release(); return true; } // static bool ModuleLoaderBase::OnLoadRequestedModulesRejected(JSContext* aCx, unsigned aArgc, Value* aVp) { CallArgs args = CallArgsFromVp(aArgc, aVp); Rooted error(aCx, args.get(OnLoadRequestedModulesRejectedErrorArg)); Rooted hostDefined(aCx); hostDefined = js::GetFunctionNativeReserved(&args.callee(), LoadReactionHostDefinedSlot); return OnLoadRequestedModulesRejected(aCx, hostDefined, error); } // static bool ModuleLoaderBase::OnLoadRequestedModulesRejected( JSContext* aCx, Handle aHostDefined, Handle aError) { auto* request = static_cast(aHostDefined.toPrivate()); MOZ_ASSERT(request); return OnLoadRequestedModulesRejected(aCx, request, aError); } // static bool ModuleLoaderBase::OnLoadRequestedModulesRejected( JSContext* aCx, ModuleLoadRequest* aRequest, Handle error) { ModuleScript* moduleScript = aRequest->mModuleScript; if (aRequest->IsCanceled()) { aRequest->Release(); return true; } // TODO, Bug 1990416: Align the loading descendants behavior of dynamic import if (aRequest->IsDynamicImport()) { LOG( ("ScriptLoadRequest (%p): LoadRequestedModules rejected for dynamic " "import", aRequest)); // https://tc39.es/ecma262/#sec-ContinueDynamicImport // Step 4.a. Perform ! Call(promiseCapability.[[Reject]], undefined, // « reason »). Rooted payload(aCx, aRequest->mPayload); if (!error.isUndefined()) { FinishLoadingImportedModuleFailed(aCx, payload, error); } else { nsAutoCString url; aRequest->URI()->GetSpec(url); JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_DYNAMIC_IMPORT_FAILED, url.get()); FinishLoadingImportedModuleFailedWithPendingException(aCx, payload); } aRequest->SetErroredLoadingImports(); } else if (moduleScript && !error.isUndefined()) { LOG( ("ScriptLoadRequest (%p): LoadRequestedModules rejected: set error to " "rethrow", aRequest)); // https://html.spec.whatwg.org/#fetch-the-descendants-of-and-link-a-module-script // Step 7. Upon rejection of loadingPromise, run the following // steps: // Step 7.1. If state.[[ErrorToRethrow]] is not null, set moduleScript's // error to rethrow to state.[[ErrorToRethrow]] and run // onComplete given moduleScript. moduleScript->SetErrorToRethrow(error); } else { LOG( ("ScriptLoadRequest (%p): LoadRequestedModules rejected: set module " "script to null", aRequest)); // Step 7.2. Otherwise, run onComplete given null. aRequest->mModuleScript = nullptr; } aRequest->ModuleErrored(); // Decrease the reference 'AddRef'ed when converting the hostDefined. aRequest->Release(); return true; } bool ModuleLoaderBase::GetImportMapSRI( nsIURI* aURI, nsIURI* aSourceURI, nsIConsoleReportCollector* aReporter, mozilla::dom::SRIMetadata* aMetadataOut) { MOZ_ASSERT(aMetadataOut->IsEmpty()); MOZ_ASSERT(aURI); if (!HasImportMapRegistered()) { return false; } mozilla::Maybe entry = ImportMap::LookupIntegrity(mImportMap.get(), aURI); if (entry.isNothing()) { return false; } mozilla::dom::SRICheck::IntegrityMetadata( *entry, aSourceURI->GetSpecOrDefault(), aReporter, aMetadataOut); return true; } void ModuleLoaderBase::AppendDynamicImport(ModuleLoadRequest* aRequest) { MOZ_ASSERT(aRequest->mLoader == this); mDynamicImportRequests.AppendElement(aRequest); } ModuleLoaderBase::ModuleLoaderBase(ScriptLoaderInterface* aLoader, nsIGlobalObject* aGlobalObject) : mGlobalObject(aGlobalObject), mLoader(aLoader) { MOZ_ASSERT(mGlobalObject); MOZ_ASSERT(mLoader); EnsureModuleHooksInitialized(); } ModuleLoaderBase::~ModuleLoaderBase() { mDynamicImportRequests.CancelRequestsAndClear(); LOG(("ModuleLoaderBase::~ModuleLoaderBase %p", this)); } void ModuleLoaderBase::CancelFetchingModules() { for (const auto& entry : mFetchingModules) { RefPtr loadingRequest = entry.GetData(); loadingRequest->mRequest->Cancel(); for (const auto& request : loadingRequest->mWaiting) { request->Cancel(); } } // We don't clear mFetchingModules here, as the fetching requests might arrive // after the global is still shutting down. } void ModuleLoaderBase::Shutdown() { CancelAndClearDynamicImports(); for (const auto& entry : mFetchingModules) { RefPtr loadingRequest(entry.GetData()); if (loadingRequest) { ResumeWaitingRequests(loadingRequest, false); } } for (const auto& entry : mFetchedModules) { if (entry.GetData()) { entry.GetData()->Shutdown(); } } mFetchingModules.Clear(); mFetchedModules.Clear(); mGlobalObject = nullptr; mLoader = nullptr; } bool ModuleLoaderBase::HasFetchingModules() const { return !mFetchingModules.IsEmpty(); } bool ModuleLoaderBase::HasPendingDynamicImports() const { return !mDynamicImportRequests.isEmpty(); } void ModuleLoaderBase::CancelDynamicImport(ModuleLoadRequest* aRequest, nsresult aResult) { // aRequest may have already been unlinked by CC. MOZ_ASSERT(aRequest->mLoader == this || !aRequest->mLoader); RefPtr req = mDynamicImportRequests.Steal(aRequest); if (!aRequest->IsCanceled()) { // If the ClearDynamicImport() has been called, then it should have been // removed from mDynamicImportRequests as well. MOZ_ASSERT(!aRequest->mPayload.isUndefined()); aRequest->Cancel(); } } void ModuleLoaderBase::RemoveDynamicImport(ModuleLoadRequest* aRequest) { MOZ_ASSERT(aRequest->IsDynamicImport()); mDynamicImportRequests.Remove(aRequest); } #ifdef DEBUG bool ModuleLoaderBase::HasDynamicImport( const ModuleLoadRequest* aRequest) const { MOZ_ASSERT(aRequest->mLoader == this); return mDynamicImportRequests.Contains( const_cast(aRequest)); } #endif bool ModuleLoaderBase::InstantiateModuleGraph(ModuleLoadRequest* aRequest) { // Instantiate a top-level module and record any error. MOZ_ASSERT(aRequest); MOZ_ASSERT(aRequest->mLoader == this); MOZ_ASSERT(aRequest->IsTopLevel()); LOG(("ScriptLoadRequest (%p): Instantiate module graph", aRequest)); AUTO_PROFILER_LABEL("ModuleLoaderBase::InstantiateModuleGraph", JS); ModuleScript* moduleScript = aRequest->mModuleScript; MOZ_ASSERT(moduleScript); MOZ_ASSERT(!moduleScript->HasParseError()); MOZ_ASSERT(moduleScript->ModuleRecord()); AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) { return false; } JSContext* cx = jsapi.cx(); Rooted module(cx, moduleScript->ModuleRecord()); if (!xpc::Scriptability::AllowedIfExists(module)) { return true; } if (!ModuleLink(jsapi.cx(), module)) { LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest)); MOZ_ASSERT(jsapi.HasException()); RootedValue exception(jsapi.cx()); if (!jsapi.StealException(&exception)) { return false; } MOZ_ASSERT(!exception.isUndefined()); moduleScript->SetErrorToRethrow(exception); } return true; } void ModuleLoaderBase::ProcessDynamicImport(ModuleLoadRequest* aRequest) { AutoJSAPI jsapi; if (!jsapi.Init(GetGlobalObject())) { return; } JSContext* cx = jsapi.cx(); MOZ_ASSERT(aRequest->IsDynamicImport()); if (aRequest->IsErrored()) { LOG(("ScriptLoadRequest (%p): ProcessDynamicImport, request has an error", aRequest)); // The error is already processed in OnLoadRequestedModulesRejected. return; } LOG(("ScriptLoadRequest (%p): ProcessDynamicImport", aRequest)); FinishLoadingImportedModule(cx, aRequest); (void)mLoader->MaybePrepareModuleForDiskCacheAfterExecute(aRequest, NS_OK); mLoader->MaybeUpdateDiskCache(); } nsresult ModuleLoaderBase::EvaluateModule(ModuleLoadRequest* aRequest) { MOZ_ASSERT(aRequest->mLoader == this); mozilla::nsAutoMicroTask mt; mozilla::dom::AutoEntryScript aes(mGlobalObject, "EvaluateModule", NS_IsMainThread()); // We would like to handle any evaluation errors synchronously for service // workers immediately such that if we are performing service worker // registration we can fail it right away rather than having it fail later on // an event loop turn which might be too late and the registration process // would have succeeded by then. return EvaluateModuleInContext( aes.cx(), aRequest, IsForServiceWorker() ? ThrowModuleErrorsSync : ReportModuleErrorsAsync); } nsresult ModuleLoaderBase::EvaluateModuleInContext( JSContext* aCx, ModuleLoadRequest* aRequest, ModuleErrorBehaviour errorBehaviour) { MOZ_ASSERT(aRequest->mLoader == this); MOZ_ASSERT_IF(!mGlobalObject->GetModuleLoader(aCx)->IsOverridden(), mGlobalObject->GetModuleLoader(aCx) == this); MOZ_ASSERT_IF(mGlobalObject->GetModuleLoader(aCx)->IsOverridden(), mGlobalObject->GetModuleLoader(aCx)->IsOverriddenBy(this)); MOZ_ASSERT(!aRequest->IsDynamicImport()); AUTO_PROFILER_LABEL("ModuleLoaderBase::EvaluateModule", JS); nsAutoCString profilerLabelString; if (aRequest->HasScriptLoadContext()) { aRequest->GetScriptLoadContext()->GetProfilerLabel(profilerLabelString); } LOG(("ScriptLoadRequest (%p): Evaluate Module", aRequest)); AUTO_PROFILER_MARKER_TEXT("ModuleEvaluation", JS, MarkerInnerWindowIdFromJSContext(aCx), profilerLabelString); MOZ_ASSERT(aRequest->mModuleScript); MOZ_ASSERT_IF(aRequest->HasScriptLoadContext(), !aRequest->GetScriptLoadContext()->mCompileOrDecodeTask); ModuleScript* moduleScript = aRequest->mModuleScript; if (moduleScript->HasErrorToRethrow()) { LOG(("ScriptLoadRequest (%p): module has error to rethrow", aRequest)); Rooted error(aCx, moduleScript->ErrorToRethrow()); JS_SetPendingException(aCx, error); return NS_OK; } Rooted module(aCx, moduleScript->ModuleRecord()); MOZ_ASSERT(module); MOZ_ASSERT(CurrentGlobalOrNull(aCx) == GetNonCCWObjectGlobal(module)); if (!xpc::Scriptability::AllowedIfExists(module)) { return NS_OK; } if (aRequest->HasScriptLoadContext()) { TRACE_FOR_TEST(aRequest, "evaluate:module"); } Rooted rval(aCx); bool ok = ModuleEvaluate(aCx, module, &rval); // ModuleEvaluate will usually set a pending exception if it returns false, // unless the user cancels execution. MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aCx)); nsresult rv = NS_OK; if (!ok || IsModuleEvaluationAborted(aRequest)) { LOG(("ScriptLoadRequest (%p): evaluation failed", aRequest)); // For a dynamic import, the promise is rejected. Otherwise an error is // reported by AutoEntryScript. rv = NS_ERROR_ABORT; } // ModuleEvaluate returns a promise unless the user cancels the execution in // which case rval will be undefined. We should treat it as a failed // evaluation, and reject appropriately. Rooted evaluationPromise(aCx); if (rval.isObject()) { evaluationPromise.set(&rval.toObject()); } // If the promise is rejected, the value is unwrapped from the promise value. if (!ThrowOnModuleEvaluationFailure(aCx, evaluationPromise, errorBehaviour)) { LOG(("ScriptLoadRequest (%p): evaluation failed on throw", aRequest)); if (IsForServiceWorker()) { // If this module eval is being done for service workers, then we would // like to throw/return right from here, such that if we are in // registration phase, we can fail it synchronously right away. return NS_ERROR_ABORT; } } rv = mLoader->MaybePrepareModuleForDiskCacheAfterExecute(aRequest, NS_OK); mLoader->MaybeUpdateDiskCache(); return rv; } void ModuleLoaderBase::CancelAndClearDynamicImports() { while (ScriptLoadRequest* req = mDynamicImportRequests.getFirst()) { // This also removes the request from the list. CancelDynamicImport(req->AsModuleRequest(), NS_ERROR_ABORT); } } UniquePtr ModuleLoaderBase::ParseImportMap( ScriptLoadRequest* aRequest) { AutoJSAPI jsapi; if (!jsapi.Init(GetGlobalObject())) { return nullptr; } MOZ_ASSERT(aRequest->IsTextSource()); MaybeSourceText maybeSource; nsresult rv = aRequest->GetScriptSource(jsapi.cx(), &maybeSource, aRequest->mLoadContext.get()); if (NS_FAILED(rv)) { return nullptr; } SourceText& text = maybeSource.ref>(); ReportWarningHelper warning{mLoader, aRequest}; // https://html.spec.whatwg.org/multipage/webappapis.html#create-an-import-map-parse-result // Step 2. Parse an import map string given input and baseURL, catching any // exceptions. If this threw an exception, then set result's error to rethrow // to that exception. Otherwise, set result's import map to the return value. // // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map // Step 1. If result's error to rethrow is not null, then report the exception // given by result's error to rethrow and return. // // Impl note: We didn't implement 'Import map parse result' from the spec, // https://html.spec.whatwg.org/multipage/webappapis.html#import-map-parse-result // As the struct has another item called 'error to rethow' to store the // exception thrown during parsing import-maps, and report that exception // while registering an import map. Currently only inline import-maps are // supported, therefore parsing and registering import-maps will be executed // consecutively. To simplify the implementation, we didn't create the 'error // to rethow' item and report the exception immediately(done in ~AutoJSAPI). return ImportMap::ParseString(jsapi.cx(), text, aRequest->BaseURL(), warning); } void ModuleLoaderBase::RegisterImportMap(UniquePtr aImportMap) { // Check for aImportMap is done in ScriptLoader. MOZ_ASSERT(aImportMap); // https://html.spec.whatwg.org/multipage/webappapis.html#register-an-import-map // The step 1(report the exception if there's an error) is done in // ParseImportMap. // // Step 2. Assert: global's import map is an empty import map. // Impl note: The default import map from the spec is an empty import map, but // from the implementation it defaults to nullptr, so we check if the global's // import map is null here. // // Also see // https://html.spec.whatwg.org/multipage/webappapis.html#empty-import-map MOZ_ASSERT(!mImportMap); // Step 3. Set global's import map to result's import map. mImportMap = std::move(aImportMap); // Any import resolution has been invalidated by the addition of the import // map. If speculative preloading is currently fetching any modules then // cancel their requests and remove them from the map. // // The cancelled requests will still complete later so we have to check this // in SetModuleFetchFinishedAndGetWaitingRequests. for (const auto& entry : mFetchingModules) { LoadingRequest* loadingRequest = entry.GetData(); MOZ_DIAGNOSTIC_ASSERT(loadingRequest->mRequest->mLoadContext->IsPreload()); loadingRequest->mRequest->Cancel(); for (const auto& request : loadingRequest->mWaiting) { MOZ_DIAGNOSTIC_ASSERT(request->mLoadContext->IsPreload()); request->Cancel(); } } mFetchingModules.Clear(); // If speculative preloading has added modules to the module map, remove // them. for (const auto& entry : mFetchedModules) { ModuleScript* script = entry.GetData(); if (script) { MOZ_DIAGNOSTIC_ASSERT( script->ForPreload(), "Non-preload module loads should block import maps"); MOZ_DIAGNOSTIC_ASSERT(!script->HadImportMap(), "Only one import map can be registered"); #if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) if (JSObject* module = script->ModuleRecord()) { MOZ_DIAGNOSTIC_ASSERT(!ModuleIsLinked(module)); } #endif script->Shutdown(); } } mFetchedModules.Clear(); } void ModuleLoaderBase::CopyModulesTo(ModuleLoaderBase* aDest) { MOZ_ASSERT(aDest->mFetchingModules.IsEmpty()); MOZ_ASSERT(aDest->mFetchedModules.IsEmpty()); MOZ_ASSERT(mFetchingModules.IsEmpty()); for (const auto& entry : mFetchedModules) { RefPtr moduleScript = entry.GetData(); if (!moduleScript) { continue; } aDest->mFetchedModules.InsertOrUpdate(entry, moduleScript); } } void ModuleLoaderBase::MoveModulesTo(ModuleLoaderBase* aDest) { MOZ_ASSERT(mFetchingModules.IsEmpty()); MOZ_ASSERT(aDest->mFetchingModules.IsEmpty()); for (const auto& entry : mFetchedModules) { RefPtr moduleScript = entry.GetData(); if (!moduleScript) { continue; } #ifdef DEBUG if (auto existingEntry = aDest->mFetchedModules.Lookup(entry)) { MOZ_ASSERT(moduleScript == existingEntry.Data()); } #endif aDest->mFetchedModules.InsertOrUpdate(entry, moduleScript); } mFetchedModules.Clear(); } #undef LOG #undef LOG_ENABLED } // namespace JS::loader