From f15d0e57f37115f0e453f84e3113b58f9e3f6a03 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 4 Mar 2026 11:12:43 +0100 Subject: [PATCH 1/6] =?UTF-8?q?=20=201.=20LibraryInitializerTests.cs=20?= =?UTF-8?q?=E2=80=94=20Made=20AbortStartupOnError=20Mono-only;=20added=20A?= =?UTF-8?q?bortStartupOnError=5FCoreCLR=20that=20throws=20in=20onRuntimeRe?= =?UTF-8?q?ady=20(later=20lifecycle=20hook)=20and=20checks=20for=20the=20a?= =?UTF-8?q?bort=20message=20with=20script=20name=20=20=20=202.=20WasmBasic?= =?UTF-8?q?TestApp.lib.module.js=20=E2=80=94=20Added=20throwErrorOnReady?= =?UTF-8?q?=20param=20to=20trigger=20error=20in=20onRuntimeReady=20(for=20?= =?UTF-8?q?CoreCLR=20test)=20=20=20=203.=20run.ts=20=E2=80=94=20Wrapped=20?= =?UTF-8?q?library=20initializer=20calls=20with=20try/catch=20that=20inclu?= =?UTF-8?q?des=20the=20script=20name=20in=20the=20error=20message;=20extra?= =?UTF-8?q?cted=20shared=20invokeLibraryInitializers=20helper=20=20=20=204?= =?UTF-8?q?.=20BuildTestBase.cs=20=E2=80=94=20Added=20IsCoreClrRuntime=20h?= =?UTF-8?q?elper=20property=20to=20support=20test=20separation.=20=20=20?= =?UTF-8?q?=205.=20BuildWasmAppsJobsListCoreCLR.txt=20=E2=80=94=20Added=20?= =?UTF-8?q?LibraryInitializerTests=20to=20CoreCLR=20CI=20job=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BuildWasmAppsJobsListCoreCLR.txt | 3 ++- .../wasm/Wasm.Build.Tests/BuildTestBase.cs | 1 + .../LibraryInitializerTests.cs | 23 ++++++++++++++++++- .../wwwroot/WasmBasicTestApp.lib.module.js | 4 ++++ .../libs/Common/JavaScript/loader/run.ts | 23 +++++++++++++------ 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt b/eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt index 1d66c27562b7bf..4f567f5cba1082 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsListCoreCLR.txt @@ -10,4 +10,5 @@ Wasm.Build.Tests.SatelliteLoadingTests Wasm.Build.Tests.WasmBuildAppTest Wasm.Build.Tests.WasmRunOutOfAppBundleTests Wasm.Build.Tests.WasmTemplateTests -Wasm.Build.Tests.MaxParallelDownloadsTests \ No newline at end of file +Wasm.Build.Tests.MaxParallelDownloadsTests +Wasm.Build.Tests.LibraryInitializerTests diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index c6393044f3846e..880cc2ee21a6a0 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -64,6 +64,7 @@ public abstract class BuildTestBase : IClassFixture !s_buildEnv.IsWorkload; public static bool IsWorkloadWithMultiThreadingForDefaultFramework => s_buildEnv.IsWorkloadWithMultiThreadingForDefaultFramework; public static bool IsMonoRuntime => s_buildEnv.IsMonoRuntime; + public static bool IsCoreClrRuntime => !s_buildEnv.IsMonoRuntime; public static bool UseWebcil => s_buildEnv.UseWebcil; public static string GetNuGetConfigPath() => Path.Combine(BuildEnvironment.TestDataPath, "nuget.config"); diff --git a/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs index f6cfb18468a370..89629684d6caef 100644 --- a/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs @@ -40,7 +40,10 @@ public async Task LoadLibraryInitializer() [GeneratedRegex("MONO_WASM: Failed to invoke 'onRuntimeConfigLoaded' on library initializer '../WasmBasicTestApp.[a-z0-9]+.lib.module.js': Error: Error thrown from library initializer")] private static partial Regex AbortStartupOnErrorRegex { get; } - [Fact, TestCategory("bundler-friendly")] + [GeneratedRegex("Aborting startup, reason: Error: Failed to invoke 'onRuntimeReady' on library initializer '.*': Error: Error thrown from library initializer")] + private static partial Regex AbortStartupOnErrorCoreClrRegex { get; } + + [ConditionalFact(typeof(BuildTestBase), nameof(IsMonoRuntime)), TestCategory("bundler-friendly")] public async Task AbortStartupOnError() { Configuration config = Configuration.Debug; @@ -55,4 +58,22 @@ public async Task AbortStartupOnError() RunResult result = await RunForPublishWithWebServer(options); Assert.True(result.ConsoleOutput.Any(m => AbortStartupOnErrorRegex.IsMatch(m)), "The library initializer test didn't emit expected error message"); } + + [ConditionalFact(typeof(BuildTestBase), nameof(IsCoreClrRuntime)), TestCategory("bundler-friendly")] + public async Task AbortStartupOnError_CoreCLR() + { + Configuration config = Configuration.Debug; + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LibraryInitializerTests_AbortStartupOnError_CoreCLR"); + PublishProject(info, config); + + BrowserRunOptions options = new( + config, + TestScenario: "LibraryInitializerTest", + BrowserQueryString: new NameValueCollection { {"throwErrorOnReady", "true" } }, + ExpectedExitCode: 1); + RunResult result = await RunForPublishWithWebServer(options); + Assert.True( + result.ConsoleOutput.Any(m => AbortStartupOnErrorCoreClrRegex.IsMatch(m)), + $"The library initializer test didn't emit expected error message.\nConsole output:\n{string.Join("\n", result.ConsoleOutput)}"); + } } diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/WasmBasicTestApp.lib.module.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/WasmBasicTestApp.lib.module.js index 200f17f6abaebf..f0a78e2efb3185 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/WasmBasicTestApp.lib.module.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/WasmBasicTestApp.lib.module.js @@ -12,6 +12,10 @@ export function onRuntimeConfigLoaded(config) { } export async function onRuntimeReady({ getAssemblyExports, getConfig }) { + if (params.get("throwErrorOnReady") === "true") { + throw new Error("Error thrown from library initializer"); + } + const testCase = params.get("test"); if (testCase == "LibraryInitializerTest") { const config = getConfig(); diff --git a/src/native/libs/Common/JavaScript/loader/run.ts b/src/native/libs/Common/JavaScript/loader/run.ts index 45c0ffb1c5aa1f..d0a9d3a3dd3c08 100644 --- a/src/native/libs/Common/JavaScript/loader/run.ts +++ b/src/native/libs/Common/JavaScript/loader/run.ts @@ -14,6 +14,17 @@ import { validateEngineFeatures } from "./bootstrap"; const runMainPromiseController = createPromiseCompletionSource(); +async function invokeLibraryInitializers(modules: JsModuleExports[], resources: any[], methodName: string): Promise { + for (let i = 0; i < modules.length; i++) { + try { + await (modules[i] as any)[methodName]?.(loaderConfig); + } catch (err) { + const name = (resources[i] as any).name || "unknown"; + throw new Error(`Failed to invoke '${methodName}' on library initializer '${name}': ${err}`); + } + } +} + // WASM-TODO: downloadOnly - Blazor render mode auto pre-download. Really no start. // WASM-TODO: debugLevel @@ -31,10 +42,9 @@ export async function createRuntime(downloadOnly: boolean): Promise { } validateLoaderConfig(); - const modulesAfterConfigLoaded = await Promise.all((loaderConfig.resources.modulesAfterConfigLoaded || []).map(loadJSModule)); - for (const afterConfigLoadedModule of modulesAfterConfigLoaded) { - await afterConfigLoadedModule.onRuntimeConfigLoaded?.(loaderConfig); - } + const afterConfigLoadedResources = loaderConfig.resources.modulesAfterConfigLoaded || []; + const modulesAfterConfigLoaded = await Promise.all(afterConfigLoadedResources.map(loadJSModule)); + await invokeLibraryInitializers(modulesAfterConfigLoaded, afterConfigLoadedResources, "onRuntimeConfigLoaded"); // after onConfigLoaded hooks, polyfills can be initialized await initPolyfills(); @@ -98,10 +108,9 @@ export async function createRuntime(downloadOnly: boolean): Promise { if (typeof Module.onDotnetReady === "function") { await Module.onDotnetReady(); } + const afterRuntimeReadyResources = loaderConfig.resources.modulesAfterRuntimeReady || []; const modulesAfterRuntimeReady = await modulesAfterRuntimeReadyPromise; - for (const afterRuntimeReadyModule of modulesAfterRuntimeReady) { - await afterRuntimeReadyModule.onRuntimeReady?.(loaderConfig); - } + await invokeLibraryInitializers(modulesAfterRuntimeReady, afterRuntimeReadyResources, "onRuntimeReady"); runtimeState.creatingRuntime = false; } catch (err) { exit(1, err); From 2cee23931158787466f0098ecf41917ba24b6683 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 4 Mar 2026 16:04:37 +0100 Subject: [PATCH 2/6] =?UTF-8?q?1.=20run.ts:=20invokeLibraryInitializers=20?= =?UTF-8?q?now=20takes=20an=20args=20parameter=20=E2=80=94=20passes=20load?= =?UTF-8?q?erConfig=20for=20onRuntimeConfigLoaded=20and=20dotnetApi=20for?= =?UTF-8?q?=20onRuntimeReady=20(which=20has=20getAssemblyExports/getConfig?= =?UTF-8?q?,=20matching=20what=20Mono=20does)=202.=20exit.ts:=20Restored?= =?UTF-8?q?=20runtimeState.creatingRuntime=20check=20(needed=20because=20d?= =?UTF-8?q?otnetReady=20is=20already=20true=20when=20onRuntimeReady=20thro?= =?UTF-8?q?ws)=203.=20run.ts:=20Call=20onRuntimeReady=20on=20all=20library?= =?UTF-8?q?=20initializers=20(both=20modulesAfterConfigLoaded=20and=20modu?= =?UTF-8?q?lesAfterRuntimeReady),=20since=20the=20SDK=20only=20lists=20lib?= =?UTF-8?q?=20modules=20in=20modulesAfterConfigLoaded=204.=20run.ts:=20Wra?= =?UTF-8?q?p=20library=20initializer=20errors=20with=20script=20name=20in?= =?UTF-8?q?=20a=20shared=20invokeLibraryInitializers=20helper=205.=20run.t?= =?UTF-8?q?s:=20Skip=20onDotnetReady/onRuntimeReady=20hooks=20when=20downl?= =?UTF-8?q?oadOnly=20is=20true=20(runtime=20is=20not=20initialized,=20so?= =?UTF-8?q?=20dotnetApi=20would=20throw)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../libs/Common/JavaScript/loader/exit.ts | 2 +- .../libs/Common/JavaScript/loader/run.ts | 24 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/native/libs/Common/JavaScript/loader/exit.ts b/src/native/libs/Common/JavaScript/loader/exit.ts index 09c2c9b7ef63b7..84659a277fd528 100644 --- a/src/native/libs/Common/JavaScript/loader/exit.ts +++ b/src/native/libs/Common/JavaScript/loader/exit.ts @@ -130,7 +130,7 @@ export function exit(exitCode: number, reason: any): void { } } } - if (!runtimeState.dotnetReady) { + if (runtimeState.creatingRuntime) { dotnetLogger.info(`Aborting startup, reason: ${reason}`); dotnetLoaderExports.abortStartup(reason); } diff --git a/src/native/libs/Common/JavaScript/loader/run.ts b/src/native/libs/Common/JavaScript/loader/run.ts index d0a9d3a3dd3c08..87444765a5b9b3 100644 --- a/src/native/libs/Common/JavaScript/loader/run.ts +++ b/src/native/libs/Common/JavaScript/loader/run.ts @@ -3,7 +3,7 @@ import type { JsModuleExports, EmscriptenModuleInternal } from "./types"; -import { dotnetAssert, dotnetInternals, dotnetBrowserHostExports, Module } from "./cross-module"; +import { dotnetAssert, dotnetInternals, dotnetBrowserHostExports, dotnetApi, Module } from "./cross-module"; import { exit, runtimeState } from "./exit"; import { createPromiseCompletionSource } from "./promise-completion-source"; import { getIcuResourceName } from "./icu"; @@ -14,10 +14,10 @@ import { validateEngineFeatures } from "./bootstrap"; const runMainPromiseController = createPromiseCompletionSource(); -async function invokeLibraryInitializers(modules: JsModuleExports[], resources: any[], methodName: string): Promise { +async function invokeLibraryInitializers(modules: JsModuleExports[], resources: any[], methodName: string, args: any): Promise { for (let i = 0; i < modules.length; i++) { try { - await (modules[i] as any)[methodName]?.(loaderConfig); + await (modules[i] as any)[methodName]?.(args); } catch (err) { const name = (resources[i] as any).name || "unknown"; throw new Error(`Failed to invoke '${methodName}' on library initializer '${name}': ${err}`); @@ -44,7 +44,7 @@ export async function createRuntime(downloadOnly: boolean): Promise { const afterConfigLoadedResources = loaderConfig.resources.modulesAfterConfigLoaded || []; const modulesAfterConfigLoaded = await Promise.all(afterConfigLoadedResources.map(loadJSModule)); - await invokeLibraryInitializers(modulesAfterConfigLoaded, afterConfigLoadedResources, "onRuntimeConfigLoaded"); + await invokeLibraryInitializers(modulesAfterConfigLoaded, afterConfigLoadedResources, "onRuntimeConfigLoaded", loaderConfig); // after onConfigLoaded hooks, polyfills can be initialized await initPolyfills(); @@ -76,7 +76,8 @@ export async function createRuntime(downloadOnly: boolean): Promise { const isDebuggingSupported = loaderConfig.debugLevel != 0; const corePDBsPromise = isDebuggingSupported ? Promise.all((loaderConfig.resources.corePdb || []).map(fetchPdb)) : Promise.resolve([]); const pdbsPromise = isDebuggingSupported ? Promise.all((loaderConfig.resources.pdb || []).map(fetchPdb)) : Promise.resolve([]); - const modulesAfterRuntimeReadyPromise = Promise.all((loaderConfig.resources.modulesAfterRuntimeReady || []).map(loadJSModule)); + const afterRuntimeReadyResources = loaderConfig.resources.modulesAfterRuntimeReady || []; + const modulesAfterRuntimeReadyPromise = Promise.all(afterRuntimeReadyResources.map(loadJSModule)); const nativeModule = await nativeModulePromise; const modulePromise = nativeModule.dotnetInitializeModule(dotnetInternals); @@ -105,12 +106,15 @@ export async function createRuntime(downloadOnly: boolean): Promise { verifyAllAssetsDownloaded(); - if (typeof Module.onDotnetReady === "function") { - await Module.onDotnetReady(); + if (!downloadOnly) { + if (typeof Module.onDotnetReady === "function") { + await Module.onDotnetReady(); + } + const modulesAfterRuntimeReady = await modulesAfterRuntimeReadyPromise; + const allRuntimeReadyModules = [...modulesAfterConfigLoaded, ...modulesAfterRuntimeReady]; + const allRuntimeReadyResources = [...afterConfigLoadedResources, ...afterRuntimeReadyResources]; + await invokeLibraryInitializers(allRuntimeReadyModules, allRuntimeReadyResources, "onRuntimeReady", dotnetApi); } - const afterRuntimeReadyResources = loaderConfig.resources.modulesAfterRuntimeReady || []; - const modulesAfterRuntimeReady = await modulesAfterRuntimeReadyPromise; - await invokeLibraryInitializers(modulesAfterRuntimeReady, afterRuntimeReadyResources, "onRuntimeReady"); runtimeState.creatingRuntime = false; } catch (err) { exit(1, err); From d5b1a39b36ce2f2e42cdbe19d0f7723a4e0f27e8 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Wed, 4 Mar 2026 17:26:57 +0100 Subject: [PATCH 3/6] Feedback. --- src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs | 2 +- .../wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs | 2 ++ .../wasm/Wasm.Build.Tests/LibraryInitializerTests.cs | 2 +- src/native/libs/Common/JavaScript/loader/run.ts | 9 +++++---- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs index 880cc2ee21a6a0..4e993991471be5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BuildTestBase.cs @@ -64,7 +64,7 @@ public abstract class BuildTestBase : IClassFixture !s_buildEnv.IsWorkload; public static bool IsWorkloadWithMultiThreadingForDefaultFramework => s_buildEnv.IsWorkloadWithMultiThreadingForDefaultFramework; public static bool IsMonoRuntime => s_buildEnv.IsMonoRuntime; - public static bool IsCoreClrRuntime => !s_buildEnv.IsMonoRuntime; + public static bool IsCoreClrRuntime => s_buildEnv.IsCoreClrRuntime; public static bool UseWebcil => s_buildEnv.UseWebcil; public static string GetNuGetConfigPath() => Path.Combine(BuildEnvironment.TestDataPath, "nuget.config"); diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs index dd190f9037cf12..f04557aa2f90ef 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs @@ -28,6 +28,7 @@ public class BuildEnvironment public bool UseWebcil { get; init; } public bool IsWorkloadWithMultiThreadingForDefaultFramework { get; init; } public bool IsMonoRuntime { get; init; } + public bool IsCoreClrRuntime { get; init; } public bool IsRunningOnCI => EnvironmentVariables.IsRunningOnCI; public static readonly string RelativeTestAssetsPath = @"..\testassets\"; @@ -114,6 +115,7 @@ public BuildEnvironment() UseWebcil = EnvironmentVariables.UseWebcil && EnvironmentVariables.RuntimeFlavor != "CoreCLR"; // TODO-WASM: CoreCLR support for Webcil https://github.com/dotnet/runtime/issues/120248 IsMonoRuntime = EnvironmentVariables.RuntimeFlavor == "Mono"; + IsCoreClrRuntime = EnvironmentVariables.RuntimeFlavor == "CoreCLR"; if (EnvironmentVariables.BuiltNuGetsPath is null || !Directory.Exists(EnvironmentVariables.BuiltNuGetsPath)) throw new Exception($"Cannot find 'BUILT_NUGETS_PATH={EnvironmentVariables.BuiltNuGetsPath}'"); diff --git a/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs index 89629684d6caef..7869651aa309d2 100644 --- a/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs @@ -40,7 +40,7 @@ public async Task LoadLibraryInitializer() [GeneratedRegex("MONO_WASM: Failed to invoke 'onRuntimeConfigLoaded' on library initializer '../WasmBasicTestApp.[a-z0-9]+.lib.module.js': Error: Error thrown from library initializer")] private static partial Regex AbortStartupOnErrorRegex { get; } - [GeneratedRegex("Aborting startup, reason: Error: Failed to invoke 'onRuntimeReady' on library initializer '.*': Error: Error thrown from library initializer")] + [GeneratedRegex("Aborting startup, reason: Error: Failed to invoke 'onRuntimeReady' on library initializer '(\\.\\./)?WasmBasicTestApp(\\.[a-z0-9]+)?\\.lib\\.module\\.js': Error thrown from library initializer")] private static partial Regex AbortStartupOnErrorCoreClrRegex { get; } [ConditionalFact(typeof(BuildTestBase), nameof(IsMonoRuntime)), TestCategory("bundler-friendly")] diff --git a/src/native/libs/Common/JavaScript/loader/run.ts b/src/native/libs/Common/JavaScript/loader/run.ts index 87444765a5b9b3..0d853f915d0f07 100644 --- a/src/native/libs/Common/JavaScript/loader/run.ts +++ b/src/native/libs/Common/JavaScript/loader/run.ts @@ -14,13 +14,14 @@ import { validateEngineFeatures } from "./bootstrap"; const runMainPromiseController = createPromiseCompletionSource(); -async function invokeLibraryInitializers(modules: JsModuleExports[], resources: any[], methodName: string, args: any): Promise { +async function callLibraryInitializers(modules: JsModuleExports[], resources: any[], methodName: string, args: any): Promise { for (let i = 0; i < modules.length; i++) { try { await (modules[i] as any)[methodName]?.(args); } catch (err) { const name = (resources[i] as any).name || "unknown"; - throw new Error(`Failed to invoke '${methodName}' on library initializer '${name}': ${err}`); + const message = err instanceof Error ? err.message : String(err); + throw new Error(`Failed to invoke '${methodName}' on library initializer '${name}': ${message}`, { cause: err }); } } } @@ -44,7 +45,7 @@ export async function createRuntime(downloadOnly: boolean): Promise { const afterConfigLoadedResources = loaderConfig.resources.modulesAfterConfigLoaded || []; const modulesAfterConfigLoaded = await Promise.all(afterConfigLoadedResources.map(loadJSModule)); - await invokeLibraryInitializers(modulesAfterConfigLoaded, afterConfigLoadedResources, "onRuntimeConfigLoaded", loaderConfig); + await callLibraryInitializers(modulesAfterConfigLoaded, afterConfigLoadedResources, "onRuntimeConfigLoaded", loaderConfig); // after onConfigLoaded hooks, polyfills can be initialized await initPolyfills(); @@ -113,7 +114,7 @@ export async function createRuntime(downloadOnly: boolean): Promise { const modulesAfterRuntimeReady = await modulesAfterRuntimeReadyPromise; const allRuntimeReadyModules = [...modulesAfterConfigLoaded, ...modulesAfterRuntimeReady]; const allRuntimeReadyResources = [...afterConfigLoadedResources, ...afterRuntimeReadyResources]; - await invokeLibraryInitializers(allRuntimeReadyModules, allRuntimeReadyResources, "onRuntimeReady", dotnetApi); + await callLibraryInitializers(allRuntimeReadyModules, allRuntimeReadyResources, "onRuntimeReady", dotnetApi); } runtimeState.creatingRuntime = false; } catch (err) { From a85941c0cd6c3e23e32f24ee709f0d22965c92ab Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 6 Mar 2026 16:03:49 +0100 Subject: [PATCH 4/6] Parallel library initializers --- src/native/libs/Common/JavaScript/loader/run.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/native/libs/Common/JavaScript/loader/run.ts b/src/native/libs/Common/JavaScript/loader/run.ts index 0d853f915d0f07..83e42b1576236b 100644 --- a/src/native/libs/Common/JavaScript/loader/run.ts +++ b/src/native/libs/Common/JavaScript/loader/run.ts @@ -15,15 +15,15 @@ import { validateEngineFeatures } from "./bootstrap"; const runMainPromiseController = createPromiseCompletionSource(); async function callLibraryInitializers(modules: JsModuleExports[], resources: any[], methodName: string, args: any): Promise { - for (let i = 0; i < modules.length; i++) { + await Promise.all(modules.map(async (module, i) => { try { - await (modules[i] as any)[methodName]?.(args); + await (module as any)[methodName]?.(args); } catch (err) { const name = (resources[i] as any).name || "unknown"; const message = err instanceof Error ? err.message : String(err); throw new Error(`Failed to invoke '${methodName}' on library initializer '${name}': ${message}`, { cause: err }); } - } + })); } // WASM-TODO: downloadOnly - Blazor render mode auto pre-download. Really no start. From f8ce05e616b5b44f3d550085447edd67e347f313 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 6 Mar 2026 16:04:18 +0100 Subject: [PATCH 5/6] Unified RuntimeFlavor checks --- src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs | 2 +- .../wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs index f04557aa2f90ef..dcb7014120803e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs @@ -143,7 +143,7 @@ public BuildEnvironment() EnvVars["WasmFingerprintAssets"] = "false"; } - if (EnvironmentVariables.RuntimeFlavor == "CoreCLR") + if (IsCoreClrRuntime) { EnvVars["WasmTestSupport"] = "true"; EnvVars["WasmTestExitOnUnhandledError"] = "true"; diff --git a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs index 820e4464de9eab..5b7a00a8eaf346 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Templates/WasmTemplateTestsBase.cs @@ -124,7 +124,7 @@ protected ProjectInfo CopyTestAsset( """; } - if (EnvironmentVariables.RuntimeFlavor == "CoreCLR") + if (s_buildEnv.IsCoreClrRuntime) { // TODO-WASM: https://github.com/dotnet/sdk/issues/51213 string versionSuffix = s_buildEnv.IsRunningOnCI ? "ci" : "dev"; From 1189b57da86830c505b09c65089a469c843d73bd Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 6 Mar 2026 16:04:30 +0100 Subject: [PATCH 6/6] Merge AbortStartupOnError tests --- .../LibraryInitializerTests.cs | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs index 7869651aa309d2..2ef6e921a754a2 100644 --- a/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/LibraryInitializerTests.cs @@ -43,37 +43,25 @@ public async Task LoadLibraryInitializer() [GeneratedRegex("Aborting startup, reason: Error: Failed to invoke 'onRuntimeReady' on library initializer '(\\.\\./)?WasmBasicTestApp(\\.[a-z0-9]+)?\\.lib\\.module\\.js': Error thrown from library initializer")] private static partial Regex AbortStartupOnErrorCoreClrRegex { get; } - [ConditionalFact(typeof(BuildTestBase), nameof(IsMonoRuntime)), TestCategory("bundler-friendly")] + [Fact, TestCategory("bundler-friendly")] public async Task AbortStartupOnError() { - Configuration config = Configuration.Debug; - ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LibraryInitializerTests_AbortStartupOnError"); - PublishProject(info, config); + bool isCoreClr = IsCoreClrRuntime; + string queryParam = isCoreClr ? "throwErrorOnReady" : "throwError"; + Regex expectedRegex = isCoreClr ? AbortStartupOnErrorCoreClrRegex : AbortStartupOnErrorRegex; - BrowserRunOptions options = new( - config, - TestScenario: "LibraryInitializerTest", - BrowserQueryString: new NameValueCollection { {"throwError", "true" } }, - ExpectedExitCode: 1); - RunResult result = await RunForPublishWithWebServer(options); - Assert.True(result.ConsoleOutput.Any(m => AbortStartupOnErrorRegex.IsMatch(m)), "The library initializer test didn't emit expected error message"); - } - - [ConditionalFact(typeof(BuildTestBase), nameof(IsCoreClrRuntime)), TestCategory("bundler-friendly")] - public async Task AbortStartupOnError_CoreCLR() - { Configuration config = Configuration.Debug; - ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LibraryInitializerTests_AbortStartupOnError_CoreCLR"); + ProjectInfo info = CopyTestAsset(config, false, TestAsset.WasmBasicTestApp, "LibraryInitializerTests_AbortStartupOnError"); PublishProject(info, config); BrowserRunOptions options = new( config, TestScenario: "LibraryInitializerTest", - BrowserQueryString: new NameValueCollection { {"throwErrorOnReady", "true" } }, + BrowserQueryString: new NameValueCollection { { queryParam, "true" } }, ExpectedExitCode: 1); RunResult result = await RunForPublishWithWebServer(options); Assert.True( - result.ConsoleOutput.Any(m => AbortStartupOnErrorCoreClrRegex.IsMatch(m)), + result.ConsoleOutput.Any(m => expectedRegex.IsMatch(m)), $"The library initializer test didn't emit expected error message.\nConsole output:\n{string.Join("\n", result.ConsoleOutput)}"); } }