/* -*- 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/friend/StackLimits.h" // js::AutoCheckRecursionLimit #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::CycleCollectedJSContext; using mozilla::Err; using mozilla::Preferences; using mozilla::UniquePtr; using mozilla::WrapNotNull; using mozilla::dom::AutoJSAPI; 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 (JS::GetModuleLoadHook(rt)) { return; } JS::SetModuleLoadHook(rt, HostLoadImportedModule); JS::SetModuleMetadataHook(rt, HostPopulateImportMeta); JS::SetScriptPrivateReferenceHooks(rt, HostAddRefTopLevelScript, HostReleaseTopLevelScript); } static bool ModuleTypeAllowed(JS::ModuleType aModuleType) { return aModuleType != JS::ModuleType::Unknown; } static bool CreateBadModuleTypeError(JSContext* aCx, LoadedScript* aScript, nsIURI* aURI, JS::MutableHandle aErrorOut) { JS::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); JS::Rooted uri(aCx, JS_NewStringCopyZ(aCx, url.get())); if (!uri) { return false; } JS::Rooted msg(aCx, JS_NewStringCopyZ(aCx, ": invalid module type")); if (!msg) { return false; } JS::Rooted errMsg(aCx, JS_ConcatStrings(aCx, uri, msg)); if (!errMsg) { return false; } return JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, 0, JS::ColumnNumberOneOrigin(), nullptr, errMsg, JS::NothingHandleValue, aErrorOut); } // https://html.spec.whatwg.org/#hostloadimportedmodule // static bool ModuleLoaderBase::HostLoadImportedModule( JSContext* aCx, JS::Handle aReferrer, JS::Handle aReferencingPrivate, JS::Handle aModuleRequest, JS::Handle aStatePrivate, JS::Handle aPromise) { // https://tc39.es/ecma262/#sec-HostLoadImportedModule // // The host environment must perform FinishLoadingImportedModule(referrer, // specifier, payload, result), where result is either a normal completion // containing the loaded Module Record or a throw completion, either // synchronously or asynchronously. auto finishLoading = mozilla::MakeScopeExit([aCx, aStatePrivate, aPromise]() { JS::FinishLoadingImportedModuleFailed(aCx, aStatePrivate, aPromise, UndefinedHandleValue); }); JS::Rooted specifierString( aCx, JS::GetModuleRequestSpecifier(aCx, aModuleRequest)); if (!specifierString) { 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)) { return false; } { // LoadedScript should only live in this block, otherwise it will be a GC // hazard RefPtr script( GetLoadedScriptOrNull(aCx, aReferencingPrivate)); RefPtr loader = GetCurrentModuleLoader(aCx); if (!loader) { return false; } if (aPromise && !loader->IsDynamicImportSupported()) { finishLoading.release(); JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_DYNAMIC_IMPORT_NOT_SUPPORTED); JS::FinishLoadingImportedModuleFailedWithPendingException(aCx, aPromise); return true; } // Step 7. Disallow further import maps given settingsObject. // Impl note: Disallow import maps is done in ModuleLoader::StartFetch // 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()) { // Step 9.1. Let completion be Completion Record { [[Type]]: throw, // [[Value]]: resolutionError, [[Target]]: empty }. JS::Rooted error(aCx); nsresult rv = loader->HandleResolveFailure(aCx, script, string, result.unwrapErr(), 0, JS::ColumnNumberOneOrigin(), &error); if (NS_FAILED(rv)) { JS_ReportOutOfMemory(aCx); return false; } // Step 9.2. Perform FinishLoadingImportedModule(referrer, moduleRequest, // payload, completion). finishLoading.release(); JS::FinishLoadingImportedModuleFailed(aCx, aStatePrivate, aPromise, error); // Step 9.3. Return. return true; } MOZ_ASSERT(result.isOk()); nsCOMPtr uri = result.unwrap(); MOZ_ASSERT(uri, "Failed to resolve module specifier"); LOG( ("ModuleLoaderBase::HostLoadImportedModule loader (%p) uri %s referrer " "(%p)", loader.get(), uri->GetSpecOrDefault().get(), aReferrer.get())); JS::ModuleType moduleType = JS::GetModuleRequestType(aCx, aModuleRequest); if (!ModuleTypeAllowed(moduleType)) { LOG(("ModuleLoaderBase::HostLoadImportedModule uri %s, bad module type", uri->GetSpecOrDefault().get())); JS::Rooted error(aCx); if (!CreateBadModuleTypeError(aCx, script, uri, &error)) { JS_ReportOutOfMemory(aCx); return false; } JS::FinishLoadingImportedModuleFailed(aCx, aStatePrivate, aPromise, error); return true; } // TODO: Bug 1968895 : Unify the fetching for static/dynamic import if (aPromise) { // This is a dynamic import. RefPtr request = loader->CreateDynamicImport( aCx, uri, script, aModuleRequest, aPromise); if (!request) { finishLoading.release(); // Throws TypeError if CreateDynamicImport returns nullptr. JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_DYNAMIC_IMPORT_NOT_SUPPORTED); JS::FinishLoadingImportedModuleFailedWithPendingException(aCx, aPromise); return true; } nsresult rv = loader->StartDynamicImport(request); if (NS_SUCCEEDED(rv)) { loader->OnDynamicImportStarted(request); } else { finishLoading.release(); nsAutoCString url; uri->GetSpec(url); JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_DYNAMIC_IMPORT_FAILED, url.get()); JS::FinishLoadingImportedModuleFailedWithPendingException(aCx, aPromise); return true; } } else { finishLoading.release(); loader->StartFetchingModuleAndDependencies( aCx, ModuleMapKey(uri, moduleType), aReferrer, aReferencingPrivate, aModuleRequest, aStatePrivate); } } finishLoading.release(); return true; } // static bool ModuleLoaderBase::FinishLoadingImportedModule( JSContext* aCx, ModuleLoadRequest* aRequest) { // The request should been removed from mDynamicImportRequests. MOZ_ASSERT_IF(aRequest->mDynamicPromise, !aRequest->mLoader->HasDynamicImport(aRequest)); JS::Rooted module(aCx); { ModuleScript* moduleScript = aRequest->mModuleScript; MOZ_ASSERT(moduleScript); MOZ_ASSERT(moduleScript->ModuleRecord()); module.set(moduleScript->ModuleRecord()); } MOZ_ASSERT(module); JS::Rooted referrer(aCx, aRequest->mReferrerObj); JS::Rooted referencingPrivate(aCx, aRequest->mReferencingPrivate); JS::Rooted moduleReqObj(aCx, aRequest->mModuleRequestObj); JS::Rooted statePrivate(aCx, aRequest->mStatePrivate); if (!statePrivate.isUndefined()) { LOG(("ScriptLoadRequest (%p): FinishLoadingImportedModule module (%p)", aRequest, module.get())); JS::FinishLoadingImportedModule(aCx, referrer, referencingPrivate, moduleReqObj, statePrivate, module); aRequest->mReferrerObj = nullptr; aRequest->mReferencingPrivate.setUndefined(); aRequest->mModuleRequestObj = nullptr; aRequest->mStatePrivate.setUndefined(); } else { JS::Rooted promise(aCx, aRequest->mDynamicPromise); JS::Rooted hostDefined(aCx, JS::PrivateValue(aRequest)); JS::FinishLoadingImportedModule(aCx, referrer, referencingPrivate, moduleReqObj, promise, module, hostDefined); MOZ_ASSERT(!JS_IsExceptionPending(aCx)); aRequest->ClearDynamicImport(); } 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, JS::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, JS::Handle aReferencingPrivate, JS::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(JS::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()) { JS::Rooted error(aCx); nsresult rv = loader->HandleResolveFailure( aCx, script, specifier, result.unwrapErr(), 0, JS::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, JS::Handle aReferencingPrivate, JS::Handle aMetaObject) { RefPtr script = static_cast(aReferencingPrivate.toPrivate()); MOZ_ASSERT(script->IsModuleScript()); MOZ_ASSERT(JS::GetModulePrivate(script->ModuleRecord()) == aReferencingPrivate); nsAutoCString url; MOZ_DIAGNOSTIC_ASSERT(script->BaseURL()); MOZ_ALWAYS_SUCCEEDS(script->BaseURL()->GetAsciiSpec(url)); JS::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"); }); JS::Rooted object(aCx, JS::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( JSContext* aCx, JS::Handle aReferencingPrivate) { if (aReferencingPrivate.isUndefined()) { return nullptr; } auto* script = static_cast(aReferencingPrivate.toPrivate()); MOZ_ASSERT_IF( script->IsModuleScript(), JS::GetModulePrivate(script->AsModuleScript()->ModuleRecord()) == aReferencingPrivate); return script; } JS::Value PrivateFromLoadedScript(LoadedScript* aScript) { if (!aScript) { return JS::UndefinedValue(); } return JS::PrivateValue(aScript); } 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() || aRequest->IsPendingFetchingError()); // 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 isStencil = aRequest->IsStencil(); MOZ_ASSERT_IF(isStencil, aRestart == RestartRequest::No); if (!isStencil) { aRequest->SetUnknownDataType(); } if (LOG_ENABLED()) { nsAutoCString url; aRequest->mURI->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->mURI, 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->mURI, aRequest->mModuleType))) { LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest)); WaitForModuleFetch(aRequest); return NS_OK; } rv = StartFetch(aRequest); NS_ENSURE_SUCCESS(rv, rv); if (isStencil) { MOZ_ASSERT( IsModuleFetched(ModuleMapKey(aRequest->mURI, 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->mURI, aRequest->mModuleType); MOZ_ASSERT(aRequest->IsFetching() || aRequest->IsPendingFetchingError()); 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->mURI, 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(); } else { aRequest->LoadFailed(); } if (!aRequest->IsErrored()) { OnFetchSucceeded(aRequest); } else { OnFetchFailed(aRequest); } } void ModuleLoaderBase::WaitForModuleFetch(ModuleLoadRequest* aRequest) { ModuleMapKey moduleMapKey(aRequest->mURI, 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 aRequest->ClearScriptSource(); 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); if (!aRequest->IsErrored()) { OnFetchSucceeded(aRequest); } else { OnFetchFailed(aRequest); } if (!waitingRequests) { return NS_OK; } ResumeWaitingRequests(waitingRequests, success); return NS_OK; } void ModuleLoaderBase::OnFetchSucceeded(ModuleLoadRequest* aRequest) { if (aRequest->IsTopLevel()) { 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()); // For dynamic import, the error handling is done in ProcessDynamicImport if (aRequest->IsDynamicImport()) { return; } 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()); JS::Value parseError = aRequest->mModuleScript->ParseError(); LOG(("ScriptLoadRequest (%p): found parse error", aRequest)); aRequest->mModuleScript->SetErrorToRethrow(parseError); } } else { // The remaining case is static import. AutoJSAPI jsapi; if (!jsapi.Init(mGlobalObject)) { return; } JSContext* cx = jsapi.cx(); MOZ_ASSERT(!aRequest->mStatePrivate.isUndefined()); JS::Rooted statePrivate(cx, aRequest->mStatePrivate); JS::Rooted error(cx); // 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 }. // // Impl note: // When moduleScript is null, the ScriptLoader will call onerror handler. // So we don't actually create a TypeError for this. if (!aRequest->mModuleScript) { error = UndefinedValue(); } else { // 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()); error = aRequest->mModuleScript->ParseError(); } LOG(("ScriptLoadRequest (%p): FinishLoadingImportedModuleFailed", aRequest)); // Step 14.5. Perform FinishLoadingImportedModule(referrer, moduleRequest, // payload, completion). JS::FinishLoadingImportedModuleFailed(cx, statePrivate, nullptr, error); aRequest->mReferrerObj = nullptr; aRequest->mReferencingPrivate.setUndefined(); aRequest->mModuleRequestObj = nullptr; aRequest->mStatePrivate.setUndefined(); } } nsresult ModuleLoaderBase::CreateModuleScript(ModuleLoadRequest* aRequest) { MOZ_ASSERT(!aRequest->mModuleScript); MOZ_ASSERT(aRequest->mBaseURL); LOG(("ScriptLoadRequest (%p): Create module script", aRequest)); AutoJSAPI jsapi; if (!jsapi.Init(mGlobalObject)) { return NS_ERROR_FAILURE; } nsresult rv; { JSContext* cx = jsapi.cx(); JS::Rooted module(cx); JS::CompileOptions options(cx); JS::RootedScript introductionScript(cx); rv = mLoader->FillCompileOptionsForRequest(cx, aRequest, &options, &introductionScript); if (NS_SUCCEEDED(rv)) { JS::Rooted global(cx, mGlobalObject->GetGlobalJSObject()); rv = CompileFetchedModule(cx, global, options, aRequest, &module); } MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr)); if (module) { JS::RootedScript moduleScript(cx, JS::GetModuleScript(module)); if (moduleScript) { JS::RootedValue privateValue(cx); JS::InstantiateOptions instantiateOptions(options); if (!JS::UpdateDebugMetadata(cx, moduleScript, instantiateOptions, privateValue, nullptr, introductionScript, nullptr)) { return NS_ERROR_OUT_OF_MEMORY; } } } MOZ_ASSERT(aRequest->mLoadedScript->IsModuleScript()); MOZ_ASSERT(aRequest->mFetchOptions->IsCompatible( aRequest->mLoadedScript->GetFetchOptions())); #ifdef DEBUG { bool equals = false; aRequest->mURI->Equals(aRequest->mLoadedScript->GetURI(), &equals); MOZ_ASSERT(equals); } #endif if (!aRequest->mLoadedScript->BaseURL()) { // If this script is not cached, the BaseURL should be copied from // request to script for later use. aRequest->mLoadedScript->SetBaseURL(aRequest->mBaseURL); } else { // If this script is cached, the BaseURL should match, which is // checked when looking for the cache. #ifdef DEBUG bool equals = false; aRequest->mBaseURL->Equals(aRequest->mLoadedScript->BaseURL(), &equals); MOZ_ASSERT(equals); #endif } 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))); JS::Rooted error(cx); if (!jsapi.HasException() || !jsapi.StealException(&error) || error.isUndefined()) { aRequest->mModuleScript = nullptr; return NS_ERROR_FAILURE; } moduleScript->SetParseError(error); aRequest->ModuleErrored(); return NS_OK; } moduleScript->SetModuleRecord(module); // TODO: Bug 1968885: Remove ModuleLoaderBase::ResolveRequestedModules // // Validate requested modules and treat failure to resolve module specifiers // the same as a parse error. rv = ResolveRequestedModules(aRequest, nullptr); if (NS_FAILED(rv)) { if (!aRequest->IsErrored()) { aRequest->mModuleScript = nullptr; return rv; } aRequest->ModuleErrored(); return NS_OK; } } 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, JS::ColumnNumberOneOrigin aColumnNumber, JS::MutableHandle aErrorOut) { JS::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); JS::Rooted string(aCx, JS_NewUCStringCopyZ(aCx, errorText.get())); if (!string) { return NS_ERROR_OUT_OF_MEMORY; } if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber, aColumnNumber, nullptr, string, JS::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); } nsresult ModuleLoaderBase::ResolveRequestedModules( ModuleLoadRequest* aRequest, nsTArray* aRequestedModulesOut) { MOZ_ASSERT_IF(aRequestedModulesOut, aRequestedModulesOut->IsEmpty()); ModuleScript* ms = aRequest->mModuleScript; AutoJSAPI jsapi; if (!jsapi.Init(mGlobalObject)) { return NS_ERROR_FAILURE; } JSContext* cx = jsapi.cx(); JS::Rooted moduleRecord(cx, ms->ModuleRecord()); if (!JS::IsCyclicModule(moduleRecord)) { return NS_OK; } uint32_t length = JS::GetRequestedModulesCount(cx, moduleRecord); for (uint32_t i = 0; i < length; i++) { JS::Rooted str( cx, JS::GetRequestedModuleSpecifier(cx, moduleRecord, i)); if (!str) { JS::Rooted pendingException(cx); if (!JS_GetPendingException(cx, &pendingException)) { return NS_ERROR_FAILURE; } ms->SetParseError(pendingException); JS_ClearPendingException(cx); return NS_ERROR_FAILURE; } nsAutoJSString specifier; if (!specifier.init(cx, str)) { return NS_ERROR_FAILURE; } // Let url be the result of resolving a module specifier given module script // and requested. ModuleLoaderBase* loader = aRequest->mLoader; auto result = loader->ResolveModuleSpecifier(ms, specifier); if (result.isErr()) { uint32_t lineNumber = 0; JS::ColumnNumberOneOrigin columnNumber; JS::GetRequestedModuleSourcePos(cx, moduleRecord, i, &lineNumber, &columnNumber); JS::Rooted error(cx); nsresult rv = loader->HandleResolveFailure(cx, ms, specifier, result.unwrapErr(), lineNumber, columnNumber, &error); NS_ENSURE_SUCCESS(rv, rv); ms->SetParseError(error); return NS_ERROR_FAILURE; } nsCOMPtr uri = result.unwrap(); if (aRequestedModulesOut) { // Let moduleType be the result of running the module type from module // request steps given moduleRequest. JS::ModuleType moduleType = JS::GetRequestedModuleType(cx, moduleRecord, i); aRequestedModulesOut->AppendElement(ModuleMapKey(uri, moduleType)); } } return NS_OK; } static bool OnLoadRequestedModulesResolvedImpl(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 OnLoadRequestedModulesRejectedImpl(ModuleLoadRequest* aRequest, Handle error) { LOG(("ScriptLoadRequest (%p): LoadRequestedModules rejected", aRequest)); ModuleScript* moduleScript = aRequest->mModuleScript; // 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. if (moduleScript && !error.isUndefined()) { moduleScript->SetErrorToRethrow(error); } else { // 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; } 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()); AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) { return; } JSContext* cx = jsapi.cx(); JS::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. JS::Rooted hostDefinedVal(cx, JS::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->mURI->SchemeIs("chrome") || aRequest->mURI->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, static_cast(OnLoadRequestedModulesSlot::HostDefinedSlot), hostDefinedVal); RootedObject rejectFuncObj(cx, JS_GetFunctionObject(onRejected)); js::SetFunctionNativeReserved( rejectFuncObj, static_cast(OnLoadRequestedModulesSlot::HostDefinedSlot), hostDefinedVal); JS::Rooted loadPromise(cx); result = JS::LoadRequestedModules(cx, module, hostDefinedVal, &loadPromise); JS::AddPromiseReactions(cx, loadPromise, resolveFuncObj, rejectFuncObj); } else { result = JS::LoadRequestedModules( cx, module, hostDefinedVal, [](JSContext* cx, JS::Handle requestVal) { ModuleLoadRequest* request = static_cast(requestVal.toPrivate()); MOZ_ASSERT(request); return OnLoadRequestedModulesResolvedImpl(request); }, [](JSContext* cx, JS::Handle requestVal, Handle error) { ModuleLoadRequest* request = static_cast(requestVal.toPrivate()); MOZ_ASSERT(request); return OnLoadRequestedModulesRejectedImpl(request, error); }); } if (!result) { LOG(("ScriptLoadRequest (%p): LoadRequestedModules failed", aRequest)); OnLoadRequestedModulesRejectedImpl(aRequest, UndefinedHandleValue); } } // static bool ModuleLoaderBase::OnLoadRequestedModulesResolved(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); ModuleLoadRequest* request = static_cast( js::GetFunctionNativeReserved( &args.callee(), static_cast(OnLoadRequestedModulesSlot::HostDefinedSlot)) .toPrivate()); MOZ_ASSERT(request); return OnLoadRequestedModulesResolvedImpl(request); } // static bool ModuleLoaderBase::OnLoadRequestedModulesRejected(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); Rooted error(cx, args.get(OnLoadRequestedModulesRejectedErrorArg)); ModuleLoadRequest* request = static_cast( js::GetFunctionNativeReserved( &args.callee(), static_cast(OnLoadRequestedModulesSlot::HostDefinedSlot)) .toPrivate()); MOZ_ASSERT(request); return OnLoadRequestedModulesRejectedImpl(request, error); } 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::StartFetchingModuleAndDependencies( JSContext* aCx, const ModuleMapKey& aRequestedModule, JS::Handle aReferrer, JS::Handle aReferencingPrivate, JS::Handle aModuleRequest, JS::Handle aStatePrivate) { MOZ_ASSERT(aReferrer); JS::Rooted referrerPrivate(aCx, JS::GetModulePrivate(aReferrer)); RefPtr referrer = GetLoadedScriptOrNull(aCx, referrerPrivate); // Check import map for integrity information mozilla::dom::SRIMetadata sriMetadata; GetImportMapSRI(aRequestedModule.mUri, referrer->GetURI(), mLoader->GetConsoleReportCollector(), &sriMetadata); JS::Rooted hostDefinedVal(aCx); JS::GetLoadingModuleHostDefinedValue(aCx, aStatePrivate, &hostDefinedVal); ModuleLoadRequest* root = static_cast(hostDefinedVal.toPrivate()); MOZ_ASSERT(root); LoadContextBase* loadContext = root->mLoadContext; RefPtr childRequest = CreateStaticImport( aRequestedModule.mUri, aRequestedModule.mModuleType, referrer->AsModuleScript(), sriMetadata, loadContext, this); LOG(("ScriptLoadRequest (%p): start fetch dependencies: root (%p)", childRequest.get(), root)); MOZ_ASSERT(!childRequest->mWaitingParentRequest); childRequest->mWaitingParentRequest = aParent; childRequest->mReferrerObj = aReferrer; childRequest->mReferencingPrivate = aReferencingPrivate; childRequest->mModuleRequestObj = aModuleRequest; childRequest->mStatePrivate = aStatePrivate; // To prevent mStatePrivate from GCed. mozilla::HoldJSObjects(childRequest.get()); nsresult rv = StartModuleLoad(childRequest); if (NS_FAILED(rv)) { MOZ_ASSERT(!childRequest->mModuleScript); mLoader->ReportErrorToConsole(childRequest, rv); childRequest->LoadFailed(); OnFetchFailed(childRequest); } } void ModuleLoadRequest::ChildLoadComplete(bool aSuccess) { RefPtr parent = mWaitingParentRequest; MOZ_ASSERT(parent); MOZ_ASSERT(parent->mAwaitingImports != 0); mWaitingParentRequest = nullptr; parent->mAwaitingImports--; if (parent->IsFinished()) { MOZ_ASSERT_IF(!aSuccess, parent->IsErrored()); return; } AutoJSAPI jsapi; if (!jsapi.Init(mLoader->GetGlobalObject())) { return; } js::AutoCheckRecursionLimit recursion(jsapi.cx()); if (!recursion.check(jsapi.cx())) { // Call LoadFinished on root module directly, as LoadFailed might trigger // another recursive call to sub-modules. mRootModule->SetReady(); mRootModule->LoadFinished(); return; } if (!aSuccess) { parent->ModuleErrored(); } else if (parent->mAwaitingImports == 0) { parent->DependenciesLoaded(); } } nsresult ModuleLoaderBase::StartDynamicImport(ModuleLoadRequest* aRequest) { MOZ_ASSERT(aRequest->mLoader == this); LOG(("ScriptLoadRequest (%p): Start dynamic import", aRequest)); mDynamicImportRequests.AppendElement(aRequest); nsresult rv = StartModuleLoad(aRequest); if (NS_FAILED(rv)) { mLoader->ReportErrorToConsole(aRequest, rv); RemoveDynamicImport(aRequest); FinishDynamicImportAndReject(aRequest, rv); } return rv; } void ModuleLoaderBase::FinishDynamicImportAndReject(ModuleLoadRequest* aRequest, nsresult aResult) { AutoJSAPI jsapi; MOZ_ASSERT(NS_FAILED(aResult)); if (!jsapi.Init(mGlobalObject)) { return; } FinishDynamicImport(jsapi.cx(), aRequest, aResult, nullptr); } /* static */ void ModuleLoaderBase::FinishDynamicImport( JSContext* aCx, ModuleLoadRequest* aRequest, nsresult aResult, JS::Handle aEvaluationPromise) { LOG(("ScriptLoadRequest (%p): Finish dynamic import %x %d", aRequest, unsigned(aResult), JS_IsExceptionPending(aCx))); MOZ_ASSERT_IF(NS_SUCCEEDED(aResult), GetCurrentModuleLoader(aCx) == aRequest->mLoader); // For failure case, aRequest may have already been unlinked by CC. MOZ_ASSERT_IF( NS_FAILED(aResult), GetCurrentModuleLoader(aCx) == aRequest->mLoader || !aRequest->mLoader); // If aResult is a failed result, we don't have an EvaluationPromise. If it // succeeded, evaluationPromise may still be null, but in this case it will // be handled by rejecting the dynamic module import promise in the JSAPI. MOZ_ASSERT_IF(NS_FAILED(aResult), !aEvaluationPromise); // The request should been removed from mDynamicImportRequests. MOZ_ASSERT(!aRequest->mLoader->HasDynamicImport(aRequest)); // Complete the dynamic import, report failures indicated by aResult or as a // pending exception on the context. if (!aRequest->mDynamicPromise) { // Import has already been completed. return; } if (NS_FAILED(aResult) && aResult != NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) { MOZ_ASSERT(!JS_IsExceptionPending(aCx)); nsAutoCString url; aRequest->mURI->GetSpec(url); JS_ReportErrorNumberASCII(aCx, js::GetErrorMessage, nullptr, JSMSG_DYNAMIC_IMPORT_FAILED, url.get()); } JS::Rooted referencingScript( aCx, PrivateFromLoadedScript(aRequest->mDynamicReferencingScript)); JS::Rooted specifier(aCx, aRequest->mDynamicSpecifier); JS::Rooted promise(aCx, aRequest->mDynamicPromise); JS::Rooted moduleRequest( aCx, JS::CreateModuleRequest(aCx, specifier, aRequest->mModuleType)); JS::FinishDynamicModuleImport(aCx, aEvaluationPromise, referencingScript, moduleRequest, promise); // FinishDynamicModuleImport clears any pending exception. MOZ_ASSERT(!JS_IsExceptionPending(aCx)); aRequest->ClearDynamicImport(); } 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::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(); } // TODO: Bug 1968890 : Update error handling for dynamic import 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 mDynamicPromise has been cleared, then it should be remove from // mDynamicImportRequests as well. MOZ_ASSERT(aRequest->mDynamicPromise); aRequest->Cancel(); // FinishDynamicImport must happen exactly once for each dynamic import // request. If the load is aborted we do it when we remove the request // from mDynamicImportRequests. FinishDynamicImportAndReject(aRequest, aResult); } } 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 JS::Value ModuleLoaderBase::FindFirstParseError(JSContext* aCx, ModuleLoadRequest* aRequest) { MOZ_ASSERT(aRequest); js::AutoCheckRecursionLimit recursion(aCx); if (!recursion.check(aCx)) { return JS::UndefinedValue(); } ModuleScript* moduleScript = aRequest->mModuleScript; MOZ_ASSERT(moduleScript); if (moduleScript->HasParseError()) { return moduleScript->ParseError(); } for (ModuleLoadRequest* childRequest : aRequest->mImports) { MOZ_DIAGNOSTIC_ASSERT(moduleScript->HadImportMap() == childRequest->mModuleScript->HadImportMap()); JS::Value error = FindFirstParseError(aCx, childRequest); if (!error.isUndefined()) { return error; } } return JS::UndefinedValue(); } 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); AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.Init(mGlobalObject))) { return false; } JSContext* cx = jsapi.cx(); JS::Value parseError = FindFirstParseError(cx, aRequest); if (!parseError.isUndefined()) { moduleScript->SetErrorToRethrow(parseError); LOG(("ScriptLoadRequest (%p): found parse error", aRequest)); return true; } MOZ_ASSERT(moduleScript->ModuleRecord()); JS::Rooted module(cx, moduleScript->ModuleRecord()); if (!xpc::Scriptability::AllowedIfExists(module)) { return true; } if (!JS::ModuleLink(jsapi.cx(), module)) { LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest)); MOZ_ASSERT(jsapi.HasException()); JS::RootedValue exception(jsapi.cx()); if (!jsapi.StealException(&exception)) { return false; } MOZ_ASSERT(!exception.isUndefined()); moduleScript->SetErrorToRethrow(exception); } return true; } void ModuleLoaderBase::ProcessDynamicImport(ModuleLoadRequest* aRequest) { if (!aRequest->mModuleScript) { FinishDynamicImportAndReject(aRequest, NS_ERROR_FAILURE); return; } InstantiateAndEvaluateDynamicImport(aRequest); } void ModuleLoaderBase::InstantiateAndEvaluateDynamicImport( ModuleLoadRequest* aRequest) { MOZ_ASSERT(aRequest->mModuleScript); if (!InstantiateModuleGraph(aRequest)) { aRequest->mModuleScript = nullptr; } nsresult rv = NS_ERROR_FAILURE; if (aRequest->mModuleScript) { rv = EvaluateModule(aRequest); } if (NS_FAILED(rv)) { FinishDynamicImportAndReject(aRequest, rv); } } nsresult ModuleLoaderBase::EvaluateModule(ModuleLoadRequest* aRequest) { MOZ_ASSERT(aRequest->mLoader == this); mozilla::nsAutoMicroTask mt; mozilla::dom::AutoEntryScript aes(mGlobalObject, "EvaluateModule", NS_IsMainThread()); return EvaluateModuleInContext(aes.cx(), aRequest, JS::ReportModuleErrorsAsync); } nsresult ModuleLoaderBase::EvaluateModuleInContext( JSContext* aCx, ModuleLoadRequest* aRequest, JS::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)); JS::Rooted error(aCx, moduleScript->ErrorToRethrow()); JS_SetPendingException(aCx, error); // For a dynamic import, the promise is rejected. Otherwise an error // is either reported by AutoEntryScript. if (aRequest->IsDynamicImport()) { FinishDynamicImport(aCx, aRequest, NS_OK, nullptr); } return NS_OK; } JS::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, "scriptloader_evaluate_module"); } JS::Rooted rval(aCx); // TODO: Bug 1973321: Prepare Bytecode encoding for dynamic import mLoader->MaybePrepareModuleForBytecodeEncodingBeforeExecute(aCx, aRequest); bool ok = JS::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. JS::Rooted evaluationPromise(aCx); if (rval.isObject()) { evaluationPromise.set(&rval.toObject()); } // If the promise is rejected, the value is unwrapped from the promise value. if (!JS::ThrowOnModuleEvaluationFailure(aCx, evaluationPromise, errorBehaviour)) { LOG(("ScriptLoadRequest (%p): evaluation failed on throw", aRequest)); } rv = mLoader->MaybePrepareModuleForBytecodeEncodingAfterExecute(aRequest, NS_OK); mLoader->MaybeTriggerBytecodeEncoding(); 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; } JS::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->mBaseURL, 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(!JS::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(); } bool ModuleLoaderBase::IsFetchingAndHasWaitingRequest( ModuleLoadRequest* aRequest) { auto entry = mFetchingModules.Lookup( ModuleMapKey(aRequest->mURI, aRequest->mModuleType)); if (!entry) { return false; } RefPtr loadingRequest = entry.Data(); return !loadingRequest->mWaiting.IsEmpty(); } #undef LOG #undef LOG_ENABLED } // namespace JS::loader