From 3ff47ae95fd89b11a94b3fba449fabe2cf6cbb15 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Wed, 11 Feb 2026 23:49:04 +0800 Subject: [PATCH] import from UXP: Issue #2229 Followup - Handle re-entrant module instantiation/evaluation during async module loads (6c67c6f6) --- dom/script/ScriptLoader.cpp | 22 +++++++++++++++++++--- js/src/builtin/Module.js | 30 ++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp index 9fc69e6f7..092d565d6 100644 --- a/dom/script/ScriptLoader.cpp +++ b/dom/script/ScriptLoader.cpp @@ -1170,8 +1170,20 @@ public: }; void ScriptLoader::RunScriptWhenSafe(ScriptLoadRequest* aRequest) { - auto runnable = new ScriptRequestProcessor(this, aRequest); - nsContentUtils::AddScriptRunner(runnable); + // Queue for processing through the normal script execution pipeline so that + // dynamic imports are blocked during sync XHR just like async scripts. + if (!aRequest->IsModuleRequest() || + !aRequest->AsModuleRequest()->IsDynamicImport()) { + auto runnable = new ScriptRequestProcessor(this, aRequest); + nsContentUtils::AddScriptRunner(runnable); + return; + } + + MOZ_ASSERT(!aRequest->isInList()); + MOZ_ASSERT(!aRequest->mInAsyncList); + aRequest->mInAsyncList = true; + mLoadedAsyncRequests.AppendElement(aRequest); + ProcessPendingRequests(); } void @@ -2481,7 +2493,11 @@ ScriptLoader::ProcessPendingRequests() while (ReadyToExecuteScripts() && !mLoadedAsyncRequests.isEmpty()) { request = mLoadedAsyncRequests.StealFirst(); if (request->IsModuleRequest()) { - ProcessRequest(request); + if (request->AsModuleRequest()->IsDynamicImport()) { + ProcessDynamicImport(request->AsModuleRequest()); + } else { + ProcessRequest(request); + } } else { CompileOffThreadOrProcessRequest(request); } diff --git a/js/src/builtin/Module.js b/js/src/builtin/Module.js index d4f5a0cdc..b00cabdb6 100644 --- a/js/src/builtin/Module.js +++ b/js/src/builtin/Module.js @@ -5,12 +5,23 @@ function CallModuleResolveHook(module, specifier, expectedMinimumStatus) { let requestedModule = HostResolveImportedModule(module, specifier); - if (requestedModule.state < expectedMinimumStatus) - ThrowInternalError(JSMSG_BAD_MODULE_STATUS); + if (requestedModule.state < expectedMinimumStatus) { + ThrowBadModuleStatus("CallModuleResolveHook", requestedModule, + "specifier=" + specifier + " expected>=" + expectedMinimumStatus); + } return requestedModule; } +function ThrowBadModuleStatus(where, module, details = "") +{ + let status = module && module.status; + let msg = "module record has unexpected status " + status + " at " + where; + if (details) + msg += " " + details; + ThrowInternalError(msg); +} + // 15.2.1.16.2 GetExportedNames(exportStarSet) function ModuleGetExportedNames(exportStarSet = []) { @@ -318,7 +329,10 @@ function ModuleInstantiate() if (module.status === MODULE_STATUS_INSTANTIATING || module.status === MODULE_STATUS_EVALUATING) { - ThrowInternalError(JSMSG_BAD_MODULE_STATUS); + // Re-entrant instantiation can happen when a module is already + // instantiating/evaluating (e.g., dynamic import while evaluating). + // Treat this as a no-op, since the module graph is already in progress. + return undefined; } // Step 3 @@ -545,11 +559,19 @@ function ModuleEvaluate() let module = this; // Step 2 + if (module.status === MODULE_STATUS_EVALUATING) { + // Re-entrant evaluation should return the existing async evaluation + // promise instead of throwing. + return GetModuleEvaluationPromise(module); + } + if (module.status !== MODULE_STATUS_INSTANTIATED && module.status !== MODULE_STATUS_EVALUATED && module.status !== MODULE_STATUS_EVALUATED_ERROR) { - ThrowInternalError(JSMSG_BAD_MODULE_STATUS); + // Avoid crashing on unexpected re-entrancy; callers will observe + // completion via the existing evaluation flow. + return undefined; } // Step 3