From b0f29990a87b51a30030738b62e6d70b6cb481df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 8 Jun 2023 17:11:37 +0200 Subject: [PATCH 01/57] Import functions from blazor --- .../JavaScript/Interop/JavaScriptExports.cs | 22 ++++++++ src/mono/wasm/runtime/exports-internal.ts | 5 ++ src/mono/wasm/runtime/lazyLoading.ts | 50 +++++++++++++++++++ src/mono/wasm/runtime/satelliteAssemblies.ts | 26 ++++++++++ 4 files changed, 103 insertions(+) create mode 100644 src/mono/wasm/runtime/lazyLoading.ts create mode 100644 src/mono/wasm/runtime/satelliteAssemblies.ts diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index 9a5ec0d21f24fa..baf004c776a204 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -91,6 +91,28 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer) } } + public static async Task LoadAssembly(List loadedAssemblies) + { + using var files = await LazyAssemblyLoaderInterop.LoadLazyAssembly(assemblyToLoad); + + var dllBytes = files.GetPropertyAsByteArray("dll")!; + var pdbBytes = files.GetPropertyAsByteArray("pdb"); + Assembly loadedAssembly = pdbBytes == null + ? AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes)) + : AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes)); + + loadedAssemblies.Add(loadedAssembly); + _loadedAssemblyCache!.Add(assemblyToLoad); + + } + + public static void LoadSatelliteAssembly(JSObject wrapper) + { + var dllBytes = wrapper.GetPropertyAsByteArray("dll")!; + using var stream = new MemoryStream(dllBytes); + AssemblyLoadContext.Default.LoadFromStream(stream); + wrapper.Dispose(); + } // The JS layer invokes this method when the JS wrapper for a JS owned object // has been collected by the JS garbage collector diff --git a/src/mono/wasm/runtime/exports-internal.ts b/src/mono/wasm/runtime/exports-internal.ts index 66540624866639..a1d3bc930648ae 100644 --- a/src/mono/wasm/runtime/exports-internal.ts +++ b/src/mono/wasm/runtime/exports-internal.ts @@ -13,6 +13,8 @@ import { mono_wasm_get_loaded_files } from "./assets"; import { jiterpreter_dump_stats } from "./jiterpreter"; import { getOptions, applyOptions } from "./jiterpreter-support"; import { mono_wasm_gc_lock, mono_wasm_gc_unlock } from "./gc-lock"; +import { loadLazyAssembly } from "./lazyLoading"; +import { loadSatelliteAssemblies } from "./satelliteAssemblies"; export function export_internal(): any { return { @@ -82,6 +84,9 @@ export function export_internal(): any { // Blazor GC Lock support mono_wasm_gc_lock, mono_wasm_gc_unlock, + + loadLazyAssembly, + loadSatelliteAssemblies }; } diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts new file mode 100644 index 00000000000000..d8bf11a2f96d04 --- /dev/null +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -0,0 +1,50 @@ +/* eslint-disable no-prototype-builtins */ + +import { INTERNAL } from "./globals"; +import { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; +import { hasDebuggingEnabled } from "./loader/blazor/_Polyfill"; + +export async function loadLazyAssembly(assemblyNameToLoad: string): Promise { + const resourceLoader: WebAssemblyResourceLoader = INTERNAL.resourceLoader; + const resources = resourceLoader.bootConfig.resources; + const lazyAssemblies = resources.lazyAssembly; + if (!lazyAssemblies) { + throw new Error("No assemblies have been marked as lazy-loadable. Use the 'BlazorWebAssemblyLazyLoad' item group in your project file to enable lazy loading an assembly."); + } + + const assemblyMarkedAsLazy = lazyAssemblies.hasOwnProperty(assemblyNameToLoad); + if (!assemblyMarkedAsLazy) { + throw new Error(`${assemblyNameToLoad} must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.`); + } + const dllNameToLoad = assemblyNameToLoad; + const pdbNameToLoad = changeExtension(assemblyNameToLoad, ".pdb"); + const shouldLoadPdb = hasDebuggingEnabled(resourceLoader.bootConfig) && resources.pdb && lazyAssemblies.hasOwnProperty(pdbNameToLoad); + + const dllBytesPromise = resourceLoader.loadResource(dllNameToLoad, `_framework/${dllNameToLoad}`, lazyAssemblies[dllNameToLoad], "assembly").response.then(response => response.arrayBuffer()); + + // TODO MF: call interop + + if (shouldLoadPdb) { + const pdbBytesPromise = await resourceLoader.loadResource(pdbNameToLoad, `_framework/${pdbNameToLoad}`, lazyAssemblies[pdbNameToLoad], "pdb").response.then(response => response.arrayBuffer()); + const [dllBytes, pdbBytes] = await Promise.all([dllBytesPromise, pdbBytesPromise]); + return { + dll: new Uint8Array(dllBytes), + pdb: new Uint8Array(pdbBytes), + }; + } else { + const dllBytes = await dllBytesPromise; + return { + dll: new Uint8Array(dllBytes), + pdb: null, + }; + } +} + +function changeExtension(filename: string, newExtensionWithLeadingDot: string) { + const lastDotIndex = filename.lastIndexOf("."); + if (lastDotIndex < 0) { + throw new Error(`No extension to replace in '${filename}'`); + } + + return filename.substr(0, lastDotIndex) + newExtensionWithLeadingDot; +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts new file mode 100644 index 00000000000000..3ec15c06f0957b --- /dev/null +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -0,0 +1,26 @@ +/* eslint-disable no-prototype-builtins */ + +import { INTERNAL } from "./globals"; +import { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; +import { LoadingResource } from "./types"; + +export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise { + const resourceLoader: WebAssemblyResourceLoader = INTERNAL.resourceLoader; + const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources; + if (!satelliteResources) { + return; + } + + await Promise.all(culturesToLoad! + .filter(culture => satelliteResources.hasOwnProperty(culture)) + .map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => `_framework/${fileName}`, "assembly")) + .reduce((previous, next) => previous.concat(next), new Array()) + .map(async resource => { + const response = await resource.response; + const bytes = await response.arrayBuffer(); + const wrapper = { dll: new Uint8Array(bytes) }; + + // TODO MF: call interop + loader(wrapper); + })); +} \ No newline at end of file From 7dd3d714cd5afdb6a9d8c80141b80d808519f522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 21 Jun 2023 17:18:59 +0200 Subject: [PATCH 02/57] Satellite assembly callback --- .../JavaScript/Interop/JavaScriptExports.cs | 1 + src/mono/wasm/runtime/managed-exports.ts | 15 ++++++++++++++- src/mono/wasm/runtime/marshal-to-cs.ts | 6 +++--- src/mono/wasm/runtime/satelliteAssemblies.ts | 5 ++--- src/mono/wasm/runtime/types/internal.ts | 2 ++ 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index ba3fb3801a5f69..e6aaa294e9d2b0 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks; diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index 6bd65eb2ec0254..5b2cc35fd96f92 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -8,7 +8,7 @@ import cwraps from "./cwraps"; import { runtimeHelpers, Module } from "./globals"; import { alloc_stack_frame, get_arg, get_arg_gc_handle, set_arg_type, set_gc_handle } from "./marshal"; import { invoke_method_and_handle_exception } from "./invoke-cs"; -import { marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs"; +import { marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs, marshal_js_object_to_cs } from "./marshal-to-cs"; import { marshal_int32_to_js, marshal_string_to_js, marshal_task_to_js } from "./marshal-to-js"; export function init_managed_exports(): void { @@ -37,6 +37,8 @@ export function init_managed_exports(): void { mono_assert(call_delegate_method, "Can't find CallDelegate method"); const get_managed_stack_trace_method = get_method("GetManagedStackTrace"); mono_assert(get_managed_stack_trace_method, "Can't find GetManagedStackTrace method"); + const load_satellite_assembly_method = get_method("LoadSatelliteAssembly"); + mono_assert(load_satellite_assembly_method, "Can't find LoadSatelliteAssembly method"); runtimeHelpers.javaScriptExports.call_entry_point = async (entry_point: MonoMethod, program_args?: string[]): Promise => { const sp = Module.stackSave(); @@ -62,6 +64,17 @@ export function init_managed_exports(): void { Module.stackRestore(sp); } }; + runtimeHelpers.javaScriptExports.load_satellite_assembly = (assembly: { dll: Uint8Array }): void => { + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(3); + const arg1 = get_arg(args, 2); + marshal_js_object_to_cs(arg1, assembly); + invoke_method_and_handle_exception(load_satellite_assembly_method, args); + } finally { + Module.stackRestore(sp); + } + }; runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle = (gc_handle: GCHandle) => { mono_assert(gc_handle, "Must be valid gc_handle"); const sp = Module.stackSave(); diff --git a/src/mono/wasm/runtime/marshal-to-cs.ts b/src/mono/wasm/runtime/marshal-to-cs.ts index 9f18be87510275..e9c48edea654f6 100644 --- a/src/mono/wasm/runtime/marshal-to-cs.ts +++ b/src/mono/wasm/runtime/marshal-to-cs.ts @@ -42,7 +42,7 @@ export function initialize_marshalers_to_cs(): void { js_to_cs_marshalers.set(MarshalerType.String, _marshal_string_to_cs); js_to_cs_marshalers.set(MarshalerType.Exception, marshal_exception_to_cs); js_to_cs_marshalers.set(MarshalerType.JSException, marshal_exception_to_cs); - js_to_cs_marshalers.set(MarshalerType.JSObject, _marshal_js_object_to_cs); + js_to_cs_marshalers.set(MarshalerType.JSObject, marshal_js_object_to_cs); js_to_cs_marshalers.set(MarshalerType.Object, _marshal_cs_object_to_cs); js_to_cs_marshalers.set(MarshalerType.Task, _marshal_task_to_cs); js_to_cs_marshalers.set(MarshalerType.Action, _marshal_function_to_cs); @@ -352,7 +352,7 @@ export function marshal_exception_to_cs(arg: JSMarshalerArgument, value: any): v } } -function _marshal_js_object_to_cs(arg: JSMarshalerArgument, value: any): void { +export function marshal_js_object_to_cs(arg: JSMarshalerArgument, value: any): void { if (value === undefined || value === null) { set_arg_type(arg, MarshalerType.None); } @@ -491,7 +491,7 @@ export function marshal_array_to_cs_impl(arg: JSMarshalerArgument, value: Array< _zero_region(buffer_ptr, buffer_length); for (let index = 0; index < length; index++) { const element_arg = get_arg(buffer_ptr, index); - _marshal_js_object_to_cs(element_arg, value[index]); + marshal_js_object_to_cs(element_arg, value[index]); } } else if (element_type == MarshalerType.Byte) { diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index 3ec15c06f0957b..70c8b340725658 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -1,6 +1,6 @@ /* eslint-disable no-prototype-builtins */ -import { INTERNAL } from "./globals"; +import { INTERNAL, runtimeHelpers } from "./globals"; import { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; import { LoadingResource } from "./types"; @@ -20,7 +20,6 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise const bytes = await response.arrayBuffer(); const wrapper = { dll: new Uint8Array(bytes) }; - // TODO MF: call interop - loader(wrapper); + runtimeHelpers.javaScriptExports.load_satellite_assembly(wrapper); })); } \ No newline at end of file diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 992fea25a25705..69a36334e95941 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -318,6 +318,8 @@ export interface JavaScriptExports { // the marshaled signature is: string GetManagedStackTrace(GCHandle exception) get_managed_stack_trace(exception_gc_handle: GCHandle): string | null + + load_satellite_assembly(assembly: { dll: Uint8Array }): void; } export type MarshalerToJs = (arg: JSMarshalerArgument, element_type?: MarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) => any; From 1c4081c88159c6d376cf96c3a99d87d0bf9caabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 21 Jun 2023 17:53:58 +0200 Subject: [PATCH 03/57] Lazy loading callback --- ....Runtime.InteropServices.JavaScript.csproj | 1 + .../JavaScript/Interop/JavaScriptExports.cs | 22 +++++++++---------- src/mono/wasm/runtime/lazyLoading.ts | 22 ++++++++++++++----- src/mono/wasm/runtime/managed-exports.ts | 13 +++++++++++ src/mono/wasm/runtime/types/internal.ts | 1 + 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj index 43251dd4d5952a..d16e23465d11e0 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj @@ -84,6 +84,7 @@ + diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index e6aaa294e9d2b0..b232aaffe5e94b 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.IO; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.Loader; using System.Threading.Tasks; using System.Diagnostics.CodeAnalysis; @@ -93,21 +95,19 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer) } } - public static async Task LoadAssembly(List loadedAssemblies) + [RequiresUnreferencedCode("Types and members the loaded assemblies depend on might be removed")] + public static void LazyLoadAssembly(JSObject wrapper) { - using var files = await LazyAssemblyLoaderInterop.LoadLazyAssembly(assemblyToLoad); - - var dllBytes = files.GetPropertyAsByteArray("dll")!; - var pdbBytes = files.GetPropertyAsByteArray("pdb"); - Assembly loadedAssembly = pdbBytes == null - ? AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes)) - : AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes)); - - loadedAssemblies.Add(loadedAssembly); - _loadedAssemblyCache!.Add(assemblyToLoad); + var dllBytes = wrapper.GetPropertyAsByteArray("dll")!; + var pdbBytes = wrapper.GetPropertyAsByteArray("pdb"); + if (pdbBytes == null) + AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes)); + else + AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes)); } + [RequiresUnreferencedCode("Types and members the loaded assemblies depend on might be removed")] public static void LoadSatelliteAssembly(JSObject wrapper) { var dllBytes = wrapper.GetPropertyAsByteArray("dll")!; diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index d8bf11a2f96d04..cd60d3ba8f187e 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -1,10 +1,11 @@ /* eslint-disable no-prototype-builtins */ -import { INTERNAL } from "./globals"; +import { mono_wasm_get_loaded_files } from "./assets"; +import { INTERNAL, runtimeHelpers } from "./globals"; import { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; import { hasDebuggingEnabled } from "./loader/blazor/_Polyfill"; -export async function loadLazyAssembly(assemblyNameToLoad: string): Promise { +export async function loadLazyAssembly(assemblyNameToLoad: string): Promise { const resourceLoader: WebAssemblyResourceLoader = INTERNAL.resourceLoader; const resources = resourceLoader.bootConfig.resources; const lazyAssemblies = resources.lazyAssembly; @@ -16,28 +17,37 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise response.arrayBuffer()); - // TODO MF: call interop - + let assembly = null; if (shouldLoadPdb) { const pdbBytesPromise = await resourceLoader.loadResource(pdbNameToLoad, `_framework/${pdbNameToLoad}`, lazyAssemblies[pdbNameToLoad], "pdb").response.then(response => response.arrayBuffer()); const [dllBytes, pdbBytes] = await Promise.all([dllBytesPromise, pdbBytesPromise]); - return { + + assembly = { dll: new Uint8Array(dllBytes), pdb: new Uint8Array(pdbBytes), }; } else { const dllBytes = await dllBytesPromise; - return { + assembly = { dll: new Uint8Array(dllBytes), pdb: null, }; } + + runtimeHelpers.javaScriptExports.lazy_load_assembly(assembly); + return true; } function changeExtension(filename: string, newExtensionWithLeadingDot: string) { diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index 5b2cc35fd96f92..669ddc53898b32 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -39,6 +39,8 @@ export function init_managed_exports(): void { mono_assert(get_managed_stack_trace_method, "Can't find GetManagedStackTrace method"); const load_satellite_assembly_method = get_method("LoadSatelliteAssembly"); mono_assert(load_satellite_assembly_method, "Can't find LoadSatelliteAssembly method"); + const lazy_load_assembly_method = get_method("LazyLoadAssembly"); + mono_assert(lazy_load_assembly_method, "Can't find LazyLoadAssembly method"); runtimeHelpers.javaScriptExports.call_entry_point = async (entry_point: MonoMethod, program_args?: string[]): Promise => { const sp = Module.stackSave(); @@ -75,6 +77,17 @@ export function init_managed_exports(): void { Module.stackRestore(sp); } }; + runtimeHelpers.javaScriptExports.lazy_load_assembly = (assembly: { dll: Uint8Array, pdb: Uint8Array | null }): void => { + const sp = Module.stackSave(); + try { + const args = alloc_stack_frame(3); + const arg1 = get_arg(args, 2); + marshal_js_object_to_cs(arg1, assembly); + invoke_method_and_handle_exception(lazy_load_assembly_method, args); + } finally { + Module.stackRestore(sp); + } + }; runtimeHelpers.javaScriptExports.release_js_owned_object_by_gc_handle = (gc_handle: GCHandle) => { mono_assert(gc_handle, "Must be valid gc_handle"); const sp = Module.stackSave(); diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 69a36334e95941..ff39f25b0cd271 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -320,6 +320,7 @@ export interface JavaScriptExports { get_managed_stack_trace(exception_gc_handle: GCHandle): string | null load_satellite_assembly(assembly: { dll: Uint8Array }): void; + lazy_load_assembly(assembly: { dll: Uint8Array, pdb: Uint8Array | null }): void; } export type MarshalerToJs = (arg: JSMarshalerArgument, element_type?: MarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) => any; From e3f88872f057e96f62bcacfa4eb3b7819d5aa832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 22 Jun 2023 10:08:41 +0200 Subject: [PATCH 04/57] Fix imports. Use locateFile --- src/mono/wasm/runtime/dotnet.d.ts | 58 +++++++++---------- src/mono/wasm/runtime/lazyLoading.ts | 11 ++-- .../runtime/loader/blazor/_Integration.ts | 3 +- src/mono/wasm/runtime/loader/globals.ts | 3 + src/mono/wasm/runtime/satelliteAssemblies.ts | 4 +- src/mono/wasm/runtime/types/internal.ts | 4 ++ 6 files changed, 44 insertions(+), 39 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 7e055b7d85a8dd..1841d7a54e33d0 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -275,35 +275,6 @@ type ModuleAPI = { }; type CreateDotnetRuntimeType = (moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)) => Promise; -interface IDisposable { - dispose(): void; - get isDisposed(): boolean; -} -interface IMemoryView extends IDisposable { - /** - * copies elements from provided source to the wasm memory. - * target has to have the elements of the same type as the underlying C# array. - * same as TypedArray.set() - */ - set(source: TypedArray, targetOffset?: number): void; - /** - * copies elements from wasm memory to provided target. - * target has to have the elements of the same type as the underlying C# array. - */ - copyTo(target: TypedArray, sourceOffset?: number): void; - /** - * same as TypedArray.slice() - */ - slice(start?: number, end?: number): TypedArray; - get length(): number; - get byteLength(): number; -} - -declare function mono_exit(exit_code: number, reason?: any): void; - -declare const dotnet: DotnetHostBuilder; -declare const exit: typeof mono_exit; - interface BootJsonData { readonly entryAssembly: string; readonly resources: ResourceGroups; @@ -350,6 +321,35 @@ declare enum ICUDataMode { Hybrid = 4 } +interface IDisposable { + dispose(): void; + get isDisposed(): boolean; +} +interface IMemoryView extends IDisposable { + /** + * copies elements from provided source to the wasm memory. + * target has to have the elements of the same type as the underlying C# array. + * same as TypedArray.set() + */ + set(source: TypedArray, targetOffset?: number): void; + /** + * copies elements from wasm memory to provided target. + * target has to have the elements of the same type as the underlying C# array. + */ + copyTo(target: TypedArray, sourceOffset?: number): void; + /** + * same as TypedArray.slice() + */ + slice(start?: number, end?: number): TypedArray; + get length(): number; + get byteLength(): number; +} + +declare function mono_exit(exit_code: number, reason?: any): void; + +declare const dotnet: DotnetHostBuilder; +declare const exit: typeof mono_exit; + declare global { function getDotnetRuntime(runtimeId: number): RuntimeAPI | undefined; } diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index cd60d3ba8f187e..91cf40c1e77985 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -2,8 +2,7 @@ import { mono_wasm_get_loaded_files } from "./assets"; import { INTERNAL, runtimeHelpers } from "./globals"; -import { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; -import { hasDebuggingEnabled } from "./loader/blazor/_Polyfill"; +import type { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; export async function loadLazyAssembly(assemblyNameToLoad: string): Promise { const resourceLoader: WebAssemblyResourceLoader = INTERNAL.resourceLoader; @@ -25,13 +24,13 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise response.arrayBuffer()); + const dllBytesPromise = resourceLoader.loadResource(dllNameToLoad, runtimeHelpers.locateFile(dllNameToLoad), lazyAssemblies[dllNameToLoad], "assembly").response.then(response => response.arrayBuffer()); let assembly = null; if (shouldLoadPdb) { - const pdbBytesPromise = await resourceLoader.loadResource(pdbNameToLoad, `_framework/${pdbNameToLoad}`, lazyAssemblies[pdbNameToLoad], "pdb").response.then(response => response.arrayBuffer()); + const pdbBytesPromise = await resourceLoader.loadResource(pdbNameToLoad, runtimeHelpers.locateFile(pdbNameToLoad), lazyAssemblies[pdbNameToLoad], "pdb").response.then(response => response.arrayBuffer()); const [dllBytes, pdbBytes] = await Promise.all([dllBytesPromise, pdbBytesPromise]); assembly = { @@ -56,5 +55,5 @@ function changeExtension(filename: string, newExtensionWithLeadingDot: string) { throw new Error(`No extension to replace in '${filename}'`); } - return filename.substr(0, lastDotIndex) + newExtensionWithLeadingDot; + return filename.substring(0, lastDotIndex) + newExtensionWithLeadingDot; } \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 61b2565b736a7b..abb40e89c9ef2b 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -203,8 +203,7 @@ function getICUResourceName(bootConfig: BootJsonData, culture: string | undefine } } - if (bootConfig.icuDataMode === ICUDataMode.Hybrid) - { + if (bootConfig.icuDataMode === ICUDataMode.Hybrid) { const reducedICUResourceName = "icudt_hybrid.dat"; return reducedICUResourceName; } diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index 6cebb54eaa428a..15e492a97369a0 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -7,6 +7,7 @@ import { abort_startup, mono_exit } from "./exit"; import { assertIsControllablePromise, createPromiseController, getPromiseController } from "./promise-controller"; import { mono_download_assets, resolve_asset_path } from "./assets"; import { setup_proxy_console } from "./logging"; +import { hasDebuggingEnabled } from "./blazor/_Polyfill"; export const ENVIRONMENT_IS_NODE = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string"; export const ENVIRONMENT_IS_WEB = typeof window == "object"; @@ -55,6 +56,8 @@ export function setLoaderGlobals( javaScriptExports: {} as any, config: globalObjects.module.config, diagnosticTracing: false, + hasDebuggingEnabled: hasDebuggingEnabled, + locateFile: loaderHelpers.locateFile }); Object.assign(loaderHelpers, { config: globalObjects.module.config, diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index 70c8b340725658..ddaeda9b94c4bb 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -1,7 +1,7 @@ /* eslint-disable no-prototype-builtins */ import { INTERNAL, runtimeHelpers } from "./globals"; -import { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; +import type { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; import { LoadingResource } from "./types"; export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise { @@ -13,7 +13,7 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise await Promise.all(culturesToLoad! .filter(culture => satelliteResources.hasOwnProperty(culture)) - .map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => `_framework/${fileName}`, "assembly")) + .map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => runtimeHelpers.locateFile(fileName), "assembly")) .reduce((previous, next) => previous.concat(next), new Array()) .map(async resource => { const response = await resource.response; diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index ff39f25b0cd271..54c73c26222cbb 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadingResource, MonoConfig, ResourceRequest, RuntimeAPI, WebAssemblyStartOptions } from "."; +import type { BootJsonData } from "./blazor"; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; export type GCHandle = { @@ -179,6 +180,9 @@ export type RuntimeHelpers = { instantiate_asset: (asset: AssetEntry, url: string, bytes: Uint8Array) => void, instantiate_symbols_asset: (pendingAsset: AssetEntryInternal) => Promise, jiterpreter_dump_stats?: (x: boolean) => string, + + hasDebuggingEnabled(bootConfig: BootJsonData): boolean, + locateFile: (path: string, prefix?: string) => string, } export type AOTProfilerOptions = { From e8aa02c3c9c46354a855da07b5db2dfda26f32f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 22 Jun 2023 13:55:57 +0200 Subject: [PATCH 05/57] Interop for lazy loading and satellite --- .../JavaScript/Interop/JavaScriptExports.cs | 44 ++++++++++++------- .../Interop/JavaScriptImports.Generated.cs | 5 +++ .../JavaScript/JSHostImplementation.cs | 21 ++++++++- src/mono/wasm/runtime/globals.ts | 2 + src/mono/wasm/runtime/lazyLoading.ts | 21 +++++---- src/mono/wasm/runtime/loader/globals.ts | 6 +-- src/mono/wasm/runtime/managed-exports.ts | 23 ++++++---- src/mono/wasm/runtime/marshal-to-cs.ts | 6 +-- src/mono/wasm/runtime/satelliteAssemblies.ts | 4 +- src/mono/wasm/runtime/types/internal.ts | 8 +++- 10 files changed, 92 insertions(+), 48 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index b232aaffe5e94b..a448e14b6b5ce3 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -5,7 +5,6 @@ using System.IO; using System.Reflection; using System.Runtime.CompilerServices; -using System.Runtime.Loader; using System.Threading.Tasks; using System.Diagnostics.CodeAnalysis; @@ -95,25 +94,40 @@ public static void CallEntrypoint(JSMarshalerArgument* arguments_buffer) } } - [RequiresUnreferencedCode("Types and members the loaded assemblies depend on might be removed")] - public static void LazyLoadAssembly(JSObject wrapper) + public static void LoadLazyAssembly(JSMarshalerArgument* arguments_buffer) { - var dllBytes = wrapper.GetPropertyAsByteArray("dll")!; - var pdbBytes = wrapper.GetPropertyAsByteArray("pdb"); + ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; + ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; + ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; + try + { + arg_1.ToManaged(out byte[]? dllBytes); + arg_2.ToManaged(out byte[]? pdbBytes); - if (pdbBytes == null) - AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes)); - else - AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes)); + if (dllBytes != null) + JSHostImplementation.LoadLazyAssembly(dllBytes, pdbBytes); + } + catch (Exception ex) + { + arg_exc.ToJS(ex); + } } - [RequiresUnreferencedCode("Types and members the loaded assemblies depend on might be removed")] - public static void LoadSatelliteAssembly(JSObject wrapper) + public static void LoadSatelliteAssembly(JSMarshalerArgument* arguments_buffer) { - var dllBytes = wrapper.GetPropertyAsByteArray("dll")!; - using var stream = new MemoryStream(dllBytes); - AssemblyLoadContext.Default.LoadFromStream(stream); - wrapper.Dispose(); + ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; + ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; + try + { + arg_1.ToManaged(out byte[]? dllBytes); + + if (dllBytes != null) + JSHostImplementation.LoadSatelliteAssembly(dllBytes); + } + catch (Exception ex) + { + arg_exc.ToJS(ex); + } } // The JS layer invokes this method when the JS wrapper for a JS owned object diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs index 4a7b92ddb19493..589ea75add2fb9 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs @@ -44,5 +44,10 @@ internal static unsafe partial class JavaScriptImports public static partial JSObject GetDotnetInstance(); [JSImport("INTERNAL.dynamic_import")] public static partial Task DynamicImport(string moduleName, string moduleUrl); + +#if DEBUG + [JSImport("globalThis.console.log")] + public static partial void Log([JSMarshalAs] string message); +#endif } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs index e844a5d6ac0df9..73c75c15aaeb69 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs @@ -1,12 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Threading.Tasks; -using System.Reflection; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.Loader; using System.Threading; +using System.Threading.Tasks; namespace System.Runtime.InteropServices.JavaScript { @@ -198,6 +200,21 @@ public static JSObject CreateCSOwnedProxy(nint jsHandle) return res; } + [Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")] + public static void LoadLazyAssembly(byte[] dllBytes, byte[]? pdbBytes) + { + if (pdbBytes == null) + AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes)); + else + AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes)); + } + + [Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")] + public static void LoadSatelliteAssembly(byte[] dllBytes) + { + AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes)); + } + #if FEATURE_WASM_THREADS public static void InstallWebWorkerInterop(bool installJSSynchronizationContext) { diff --git a/src/mono/wasm/runtime/globals.ts b/src/mono/wasm/runtime/globals.ts index 22fef7474b2ae3..315cb6695da3a1 100644 --- a/src/mono/wasm/runtime/globals.ts +++ b/src/mono/wasm/runtime/globals.ts @@ -59,6 +59,8 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) { beforeOnRuntimeInitialized: createPromiseController(), afterOnRuntimeInitialized: createPromiseController(), afterPostRun: createPromiseController(), + hasDebuggingEnabled: loaderHelpers.hasDebuggingEnabled, + locateFile: loaderHelpers.locateFile, }); Object.assign(globalObjects.module.config!, {}) as any; diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index 91cf40c1e77985..eed438ea64ab33 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -18,7 +18,7 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise f.includes(assemblyNameToLoad))) { return false; } @@ -28,24 +28,23 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise response.arrayBuffer()); - let assembly = null; + let dll = null; + let pdb = null; if (shouldLoadPdb) { const pdbBytesPromise = await resourceLoader.loadResource(pdbNameToLoad, runtimeHelpers.locateFile(pdbNameToLoad), lazyAssemblies[pdbNameToLoad], "pdb").response.then(response => response.arrayBuffer()); const [dllBytes, pdbBytes] = await Promise.all([dllBytesPromise, pdbBytesPromise]); - assembly = { - dll: new Uint8Array(dllBytes), - pdb: new Uint8Array(pdbBytes), - }; + dll = new Uint8Array(dllBytes); + pdb = new Uint8Array(pdbBytes); } else { const dllBytes = await dllBytesPromise; - assembly = { - dll: new Uint8Array(dllBytes), - pdb: null, - }; + dll = new Uint8Array(dllBytes); + pdb = null; } - runtimeHelpers.javaScriptExports.lazy_load_assembly(assembly); + // TODO MF: Push to loaderHelpers.loadedFiles + + runtimeHelpers.javaScriptExports.load_lazy_assembly(dll, pdb); return true; } diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index 15e492a97369a0..a0b9344c9b4eb3 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -55,9 +55,7 @@ export function setLoaderGlobals( mono_wasm_bindings_is_ready: false, javaScriptExports: {} as any, config: globalObjects.module.config, - diagnosticTracing: false, - hasDebuggingEnabled: hasDebuggingEnabled, - locateFile: loaderHelpers.locateFile + diagnosticTracing: false }); Object.assign(loaderHelpers, { config: globalObjects.module.config, @@ -87,5 +85,7 @@ export function setLoaderGlobals( resolve_asset_path, setup_proxy_console, + hasDebuggingEnabled, + } as Partial); } diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index 669ddc53898b32..e23edddfa19f3e 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -8,7 +8,7 @@ import cwraps from "./cwraps"; import { runtimeHelpers, Module } from "./globals"; import { alloc_stack_frame, get_arg, get_arg_gc_handle, set_arg_type, set_gc_handle } from "./marshal"; import { invoke_method_and_handle_exception } from "./invoke-cs"; -import { marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs, marshal_js_object_to_cs } from "./marshal-to-cs"; +import { marshal_array_to_cs, marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs"; import { marshal_int32_to_js, marshal_string_to_js, marshal_task_to_js } from "./marshal-to-js"; export function init_managed_exports(): void { @@ -39,8 +39,8 @@ export function init_managed_exports(): void { mono_assert(get_managed_stack_trace_method, "Can't find GetManagedStackTrace method"); const load_satellite_assembly_method = get_method("LoadSatelliteAssembly"); mono_assert(load_satellite_assembly_method, "Can't find LoadSatelliteAssembly method"); - const lazy_load_assembly_method = get_method("LazyLoadAssembly"); - mono_assert(lazy_load_assembly_method, "Can't find LazyLoadAssembly method"); + const load_lazy_assembly_method = get_method("LoadLazyAssembly"); + mono_assert(load_lazy_assembly_method, "Can't find LoadLazyAssembly method"); runtimeHelpers.javaScriptExports.call_entry_point = async (entry_point: MonoMethod, program_args?: string[]): Promise => { const sp = Module.stackSave(); @@ -66,24 +66,29 @@ export function init_managed_exports(): void { Module.stackRestore(sp); } }; - runtimeHelpers.javaScriptExports.load_satellite_assembly = (assembly: { dll: Uint8Array }): void => { + runtimeHelpers.javaScriptExports.load_satellite_assembly = (dll: Uint8Array): void => { const sp = Module.stackSave(); try { const args = alloc_stack_frame(3); const arg1 = get_arg(args, 2); - marshal_js_object_to_cs(arg1, assembly); + set_arg_type(arg1, MarshalerType.Array); + marshal_array_to_cs(arg1, dll, MarshalerType.Byte); invoke_method_and_handle_exception(load_satellite_assembly_method, args); } finally { Module.stackRestore(sp); } }; - runtimeHelpers.javaScriptExports.lazy_load_assembly = (assembly: { dll: Uint8Array, pdb: Uint8Array | null }): void => { + runtimeHelpers.javaScriptExports.load_lazy_assembly = (dll: Uint8Array, pdb: Uint8Array | null): void => { const sp = Module.stackSave(); try { - const args = alloc_stack_frame(3); + const args = alloc_stack_frame(4); const arg1 = get_arg(args, 2); - marshal_js_object_to_cs(arg1, assembly); - invoke_method_and_handle_exception(lazy_load_assembly_method, args); + const arg2 = get_arg(args, 3); + set_arg_type(arg1, MarshalerType.Array); + set_arg_type(arg2, MarshalerType.Array); + marshal_array_to_cs(arg1, dll, MarshalerType.Byte); + marshal_array_to_cs(arg2, pdb, MarshalerType.Byte); + invoke_method_and_handle_exception(load_lazy_assembly_method, args); } finally { Module.stackRestore(sp); } diff --git a/src/mono/wasm/runtime/marshal-to-cs.ts b/src/mono/wasm/runtime/marshal-to-cs.ts index e9c48edea654f6..daaa3e54cbfa6a 100644 --- a/src/mono/wasm/runtime/marshal-to-cs.ts +++ b/src/mono/wasm/runtime/marshal-to-cs.ts @@ -24,7 +24,7 @@ import { addUnsettledPromise, settleUnsettledPromise } from "./pthreads/shared/e export function initialize_marshalers_to_cs(): void { if (js_to_cs_marshalers.size == 0) { - js_to_cs_marshalers.set(MarshalerType.Array, _marshal_array_to_cs); + js_to_cs_marshalers.set(MarshalerType.Array, marshal_array_to_cs); js_to_cs_marshalers.set(MarshalerType.Span, _marshal_span_to_cs); js_to_cs_marshalers.set(MarshalerType.ArraySegment, _marshal_array_segment_to_cs); js_to_cs_marshalers.set(MarshalerType.Boolean, _marshal_bool_to_cs); @@ -453,12 +453,12 @@ function _marshal_cs_object_to_cs(arg: JSMarshalerArgument, value: any): void { } } -function _marshal_array_to_cs(arg: JSMarshalerArgument, value: Array | TypedArray, element_type?: MarshalerType): void { +export function marshal_array_to_cs(arg: JSMarshalerArgument, value: Array | TypedArray | undefined | null, element_type?: MarshalerType): void { mono_assert(!!element_type, "Expected valid element_type parameter"); marshal_array_to_cs_impl(arg, value, element_type); } -export function marshal_array_to_cs_impl(arg: JSMarshalerArgument, value: Array | TypedArray | undefined, element_type: MarshalerType): void { +export function marshal_array_to_cs_impl(arg: JSMarshalerArgument, value: Array | TypedArray | undefined | null, element_type: MarshalerType): void { if (value === null || value === undefined) { set_arg_type(arg, MarshalerType.None); } diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index ddaeda9b94c4bb..db0f8086c0c4aa 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -18,8 +18,6 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise .map(async resource => { const response = await resource.response; const bytes = await response.arrayBuffer(); - const wrapper = { dll: new Uint8Array(bytes) }; - - runtimeHelpers.javaScriptExports.load_satellite_assembly(wrapper); + runtimeHelpers.javaScriptExports.load_satellite_assembly(new Uint8Array(bytes)); })); } \ No newline at end of file diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 54c73c26222cbb..2978e6758a69b8 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -133,6 +133,8 @@ export type LoaderHelpers = { err(message: string): void; getApplicationEnvironment?: (bootConfigResponse: Response) => string | null; + hasDebuggingEnabled(bootConfig: BootJsonData): boolean, + isChromium: boolean, isFirefox: boolean, } @@ -323,8 +325,10 @@ export interface JavaScriptExports { // the marshaled signature is: string GetManagedStackTrace(GCHandle exception) get_managed_stack_trace(exception_gc_handle: GCHandle): string | null - load_satellite_assembly(assembly: { dll: Uint8Array }): void; - lazy_load_assembly(assembly: { dll: Uint8Array, pdb: Uint8Array | null }): void; + load_satellite_assembly(dll: Uint8Array): void; + + // the marshaled signature is: void LoadLazyAssembly(byte[] dll, byte[] pdb) + load_lazy_assembly(dll: Uint8Array, pdb: Uint8Array | null): void; } export type MarshalerToJs = (arg: JSMarshalerArgument, element_type?: MarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs) => any; From 54b2c90e60db37454e73b18233c49be55b0239ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 23 Jun 2023 12:19:21 +0200 Subject: [PATCH 06/57] Dissolve startupOptions --- src/mono/wasm/runtime/dotnet.d.ts | 16 ++++++++++++++++ .../wasm/runtime/loader/blazor/BootConfig.ts | 4 +--- .../loader/blazor/WebAssemblyResourceLoader.ts | 12 ++++++------ .../wasm/runtime/loader/blazor/_Integration.ts | 16 ++++++++-------- src/mono/wasm/runtime/loader/config.ts | 9 +++------ src/mono/wasm/runtime/loader/run.ts | 4 +++- src/mono/wasm/runtime/startup.ts | 2 +- src/mono/wasm/runtime/types/index.ts | 18 ++++++++++++++++++ src/mono/wasm/runtime/types/internal.ts | 5 ++--- 9 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 1841d7a54e33d0..c247167af9f13f 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -153,7 +153,22 @@ type MonoConfig = { * query string to be used for asset loading */ assetUniqueQuery?: string; + /** + * Gets the application culture. This is a name specified in the BCP 47 format. See https://tools.ietf.org/html/bcp47 + */ + applicationCulture?: string; + /** + * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched + * from a custom source, such as an external CDN. + * @param type The type of the resource to be loaded. + * @param name The name of the resource to be loaded. + * @param defaultUri The URI from which the framework would fetch the resource by default. The URI may be relative or absolute. + * @param integrity The integrity string representing the expected content in the response. + * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. + */ + loadBootResource?: LoadBootResourceCallback; }; +type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; interface ResourceRequest { name: string; behavior: AssetBehaviours; @@ -274,6 +289,7 @@ type ModuleAPI = { exit: (code: number, reason?: any) => void; }; type CreateDotnetRuntimeType = (moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)) => Promise; +type WebAssemblyBootResourceType = "assembly" | "pdb" | "dotnetjs" | "dotnetwasm" | "globalization" | "manifest" | "configuration"; interface BootJsonData { readonly entryAssembly: string; diff --git a/src/mono/wasm/runtime/loader/blazor/BootConfig.ts b/src/mono/wasm/runtime/loader/blazor/BootConfig.ts index 05defc18901352..272b699c7e43ec 100644 --- a/src/mono/wasm/runtime/loader/blazor/BootConfig.ts +++ b/src/mono/wasm/runtime/loader/blazor/BootConfig.ts @@ -2,11 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { BootJsonData } from "../../types/blazor"; -import type { WebAssemblyBootResourceType } from "../../types"; +import type { LoadBootResourceCallback } from "../../types"; import { loaderHelpers } from "../globals"; -export type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; - export class BootConfigResult { private constructor(public bootConfig: BootJsonData, public applicationEnvironment: string) { } diff --git a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts index 0c3208e7851741..8dc80a4c30c583 100644 --- a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts +++ b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { WebAssemblyBootResourceType, WebAssemblyStartOptions } from "../../types"; +import type { LoadBootResourceCallback, WebAssemblyBootResourceType } from "../../types"; import type { BootJsonData, ResourceList } from "../../types/blazor"; import { toAbsoluteUri } from "./_Polyfill"; const networkFetchCacheMode = "no-cache"; @@ -15,12 +15,12 @@ export class WebAssemblyResourceLoader { private cacheLoads: { [name: string]: LoadLogEntry } = {}; - static async initAsync(bootConfig: BootJsonData, startOptions: Partial): Promise { + static async initAsync(bootConfig: BootJsonData, loadBootResource?: LoadBootResourceCallback): Promise { const cache = await getCacheToUseIfEnabled(bootConfig); - return new WebAssemblyResourceLoader(bootConfig, cache, startOptions); + return new WebAssemblyResourceLoader(bootConfig, cache, loadBootResource); } - constructor(readonly bootConfig: BootJsonData, readonly cacheIfUsed: Cache | null, readonly startOptions: Partial) { + constructor(readonly bootConfig: BootJsonData, readonly cacheIfUsed: Cache | null, readonly loadBootResource?: LoadBootResourceCallback) { } loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[] { @@ -123,8 +123,8 @@ export class WebAssemblyResourceLoader { private loadResourceWithoutCaching(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): Promise { // Allow developers to override how the resource is loaded - if (this.startOptions.loadBootResource) { - const customLoadResult = this.startOptions.loadBootResource(resourceType, name, url, contentHash); + if (this.loadBootResource) { + const customLoadResult = this.loadBootResource(resourceType, name, url, contentHash); if (customLoadResult instanceof Promise) { // They are supplying an entire custom response, so just use that return customLoadResult; diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index abb40e89c9ef2b..531fea4a414c5d 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { DotnetModuleInternal, MonoConfigInternal } from "../../types/internal"; -import type { AssetBehaviours, AssetEntry, LoadingResource, WebAssemblyBootResourceType, WebAssemblyStartOptions } from "../../types"; +import type { AssetBehaviours, AssetEntry, LoadBootResourceCallback, LoadingResource, WebAssemblyBootResourceType } from "../../types"; import type { BootJsonData } from "../../types/blazor"; import { INTERNAL, loaderHelpers } from "../globals"; @@ -14,13 +14,13 @@ import { ICUDataMode } from "../../types/blazor"; let resourceLoader: WebAssemblyResourceLoader; export async function loadBootConfig(config: MonoConfigInternal, module: DotnetModuleInternal) { - const bootConfigPromise = BootConfigResult.initAsync(config.startupOptions?.loadBootResource, config.applicationEnvironment); + const bootConfigPromise = BootConfigResult.initAsync(config.loadBootResource, config.applicationEnvironment); const bootConfigResult: BootConfigResult = await bootConfigPromise; - await initializeBootConfig(bootConfigResult, module, config.startupOptions); + await initializeBootConfig(bootConfigResult, module, config.loadBootResource); } -export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, startupOptions?: Partial) { - INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, startupOptions ?? {}); +export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, loadBootResource?: LoadBootResourceCallback) { + INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, loadBootResource); mapBootConfigToMonoConfig(loaderHelpers.config, bootConfigResult.applicationEnvironment); setupModuleForBlazor(module); } @@ -130,7 +130,7 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl assets.push(asset); } } - const applicationCulture = resourceLoader.startOptions.applicationCulture || (navigator.languages && navigator.languages[0]); + const applicationCulture = moduleConfig.applicationCulture || (navigator.languages && navigator.languages[0]); const icuDataResourceName = getICUResourceName(resourceLoader.bootConfig, applicationCulture); let hasIcuData = false; for (const name in resources.runtime) { @@ -178,9 +178,9 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = resourceLoader.bootConfig.modifiableAssemblies; } - if (resourceLoader.startOptions.applicationCulture) { + if (moduleConfig.applicationCulture) { // If a culture is specified via start options use that to initialize the Emscripten \ .NET culture. - environmentVariables["LANG"] = `${resourceLoader.startOptions.applicationCulture}.UTF-8`; + environmentVariables["LANG"] = `${moduleConfig.applicationCulture}.UTF-8`; } if (resourceLoader.bootConfig.startupMemoryCache !== undefined) { diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 695911efe018be..82a162bf238dca 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -18,9 +18,6 @@ export function deep_merge_config(target: MonoConfigInternal, source: MonoConfig if (providedConfig.environmentVariables) { providedConfig.environmentVariables = { ...(target.environmentVariables || {}), ...(providedConfig.environmentVariables || {}) }; } - if (providedConfig.startupOptions) { - providedConfig.startupOptions = { ...(target.startupOptions || {}), ...(providedConfig.startupOptions || {}) }; - } if (providedConfig.runtimeOptions) { providedConfig.runtimeOptions = [...(target.runtimeOptions || []), ...(providedConfig.runtimeOptions || [])]; } @@ -82,9 +79,9 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi } mono_log_debug("mono_wasm_load_config"); try { - loaderHelpers.config.applicationEnvironment = loaderHelpers.config.applicationEnvironment ?? loaderHelpers.config.startupOptions?.environment ?? "Production"; + loaderHelpers.config.applicationEnvironment = loaderHelpers.config.applicationEnvironment ?? "Production"; - if (loaderHelpers.config.startupOptions && loaderHelpers.config.startupOptions.loadBootResource) { + if (loaderHelpers.config.loadBootResource) { // If we have custom loadBootResource await loadBootConfig(loaderHelpers.config, module); } else { @@ -94,7 +91,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi const loadedAnyConfig: any = (await configResponse.json()) || {}; if (loadedAnyConfig.resources) { // If we found boot config schema - await initializeBootConfig(BootConfigResult.fromFetchResponse(configResponse, loadedAnyConfig as BootJsonData, loaderHelpers.config.applicationEnvironment), module, loaderHelpers.config.startupOptions); + await initializeBootConfig(BootConfigResult.fromFetchResponse(configResponse, loadedAnyConfig as BootJsonData, loaderHelpers.config.applicationEnvironment), module, loaderHelpers.config.loadBootResource); } else { // Otherwise we found mono config schema const loadedConfig = loadedAnyConfig as MonoConfigInternal; diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index a147f9fed19d5c..74d2b3f578f74a 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -277,7 +277,9 @@ export class HostBuilder implements DotnetHostBuilder { withStartupOptions(startupOptions: Partial): DotnetHostBuilder { deep_merge_config(monoConfig, { - startupOptions + applicationEnvironment: startupOptions.environment, + applicationCulture: startupOptions.applicationCulture, + loadBootResource: startupOptions.loadBootResource, }); return this; } diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 7faea31a50e530..728fe763c8d809 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -262,7 +262,7 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) { if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready(); - if (runtimeHelpers.config.startupOptions && INTERNAL.resourceLoader) { + if (INTERNAL.resourceLoader) { if (INTERNAL.resourceLoader.bootConfig.debugBuild && INTERNAL.resourceLoader.bootConfig.cacheBootResources) { INTERNAL.resourceLoader.logToConsole(); } diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 1d0a72158ebe3a..247c40252805f4 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -87,8 +87,26 @@ export type MonoConfig = { * query string to be used for asset loading */ assetUniqueQuery?: string, + + /** + * Gets the application culture. This is a name specified in the BCP 47 format. See https://tools.ietf.org/html/bcp47 + */ + applicationCulture?: string, + + /** + * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched + * from a custom source, such as an external CDN. + * @param type The type of the resource to be loaded. + * @param name The name of the resource to be loaded. + * @param defaultUri The URI from which the framework would fetch the resource by default. The URI may be relative or absolute. + * @param integrity The integrity string representing the expected content in the response. + * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. + */ + loadBootResource?: LoadBootResourceCallback; }; +export type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; + export interface ResourceRequest { name: string, // the name of the asset, including extension. behavior: AssetBehaviours, // determines how the asset will be handled once loaded diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 2978e6758a69b8..81f957c603f4b2 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadingResource, MonoConfig, ResourceRequest, RuntimeAPI, WebAssemblyStartOptions } from "."; +import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadingResource, MonoConfig, ResourceRequest, RuntimeAPI } from "."; import type { BootJsonData } from "./blazor"; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; @@ -76,8 +76,7 @@ export type MonoConfigInternal = MonoConfig & { logExitCode?: boolean forwardConsoleLogsToWS?: boolean, asyncFlushOnExit?: boolean - exitAfterSnapshot?: number, - startupOptions?: Partial + exitAfterSnapshot?: number }; export type RunArguments = { From 12ecc3d5333cd9d8b5aee98862576d5726e50c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 28 Jun 2023 14:13:52 +0200 Subject: [PATCH 07/57] WBT test for lazy loading --- .../Wasm.Build.Tests/WasmLazyLoadingTests.cs | 82 +++++++++++++++++++ .../testassets/WasmLazyLoading/Program.cs | 18 ++++ .../Properties/AssemblyInfo.cs | 4 + .../WasmLazyLoading/WasmLazyLoading.csproj | 12 +++ .../runtimeconfig.template.json | 11 +++ .../WasmLazyLoading/wwwroot/index.html | 17 ++++ .../WasmLazyLoading/wwwroot/main.js | 22 +++++ 7 files changed, 166 insertions(+) create mode 100644 src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs create mode 100644 src/mono/wasm/testassets/WasmLazyLoading/Program.cs create mode 100644 src/mono/wasm/testassets/WasmLazyLoading/Properties/AssemblyInfo.cs create mode 100644 src/mono/wasm/testassets/WasmLazyLoading/WasmLazyLoading.csproj create mode 100644 src/mono/wasm/testassets/WasmLazyLoading/runtimeconfig.template.json create mode 100644 src/mono/wasm/testassets/WasmLazyLoading/wwwroot/index.html create mode 100644 src/mono/wasm/testassets/WasmLazyLoading/wwwroot/main.js diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs new file mode 100644 index 00000000000000..f349e726b3d508 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Playwright; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + +#nullable enable + +namespace Wasm.Build.Tests; + +public class WasmLazyLoadingTests : BuildTestBase +{ + public WasmLazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Fact] + public async Task LazyLoadAssembly() + { + string id = $"WasmLazyLoading_{Path.GetRandomFileName()}"; + InitBlazorWasmProjectDir(id); + + string logPath = Path.Combine(s_buildEnv.LogRootPath, id); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "WasmLazyLoading"), Path.Combine(_projectDir!)); + + string projectFile = Path.Combine(_projectDir!, "WasmLazyLoading.csproj"); + AddItemsPropertiesToProject(projectFile); + + string publishLogPath = Path.Combine(logPath, $"{id}.binlog"); + CommandResult result = new DotNetCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(_projectDir!) + .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) + .ExecuteWithCapturedOutput("publish", $"-bl:{publishLogPath}", $"-p:Configuration=Debug"); + + result.EnsureSuccessful(); + + + string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files"; + string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir("Debug", forPublish: true), "..")); + + using var runCommand = new RunCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(workingDirectory); + + var tcs = new TaskCompletionSource(); + + bool hasExpectedMessage = false; + Regex exitRegex = new Regex("WASM EXIT (?[0-9]+)$"); + + await using var runner = new BrowserRunner(_testOutput); + var page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage); + + + void OnConsoleMessage(IConsoleMessage msg) + { + if (EnvironmentVariables.ShowBuildOutput) + Console.WriteLine($"[{msg.Type}] {msg.Text}"); + + _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); + + if (msg.Text.Contains("FirstName")) + hasExpectedMessage = true; + + if (exitRegex.Match(msg.Text).Success) + tcs.SetResult(true); + } + + TimeSpan timeout = TimeSpan.FromMinutes(2); + await Task.WhenAny(tcs.Task, Task.Delay(timeout)); + if (!tcs.Task.IsCompleted || !tcs.Task.Result) + throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); + + Assert.True(hasExpectedMessage, "The lazy loading application didn't emitted expected message"); + } +} diff --git a/src/mono/wasm/testassets/WasmLazyLoading/Program.cs b/src/mono/wasm/testassets/WasmLazyLoading/Program.cs new file mode 100644 index 00000000000000..08d02be69d1b43 --- /dev/null +++ b/src/mono/wasm/testassets/WasmLazyLoading/Program.cs @@ -0,0 +1,18 @@ +using System; +using System.Text.Json; +using System.Runtime.InteropServices.JavaScript; + +Console.WriteLine("Hello, Browser!"); + +public partial class MyClass +{ + [JSExport] + internal static string GetJson() + { + var text = JsonSerializer.Serialize(new Person("John", "Doe")); + Console.WriteLine(text); + return text; + } +} + +public record Person(string FirstName, string LastName); \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmLazyLoading/Properties/AssemblyInfo.cs b/src/mono/wasm/testassets/WasmLazyLoading/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000000..9ad9b578f20649 --- /dev/null +++ b/src/mono/wasm/testassets/WasmLazyLoading/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +[assembly:System.Runtime.Versioning.SupportedOSPlatform("browser")] diff --git a/src/mono/wasm/testassets/WasmLazyLoading/WasmLazyLoading.csproj b/src/mono/wasm/testassets/WasmLazyLoading/WasmLazyLoading.csproj new file mode 100644 index 00000000000000..fb563ea6b52dea --- /dev/null +++ b/src/mono/wasm/testassets/WasmLazyLoading/WasmLazyLoading.csproj @@ -0,0 +1,12 @@ + + + net8.0 + browser-wasm + Exe + true + + + + + + diff --git a/src/mono/wasm/testassets/WasmLazyLoading/runtimeconfig.template.json b/src/mono/wasm/testassets/WasmLazyLoading/runtimeconfig.template.json new file mode 100644 index 00000000000000..8f0557352c6ed3 --- /dev/null +++ b/src/mono/wasm/testassets/WasmLazyLoading/runtimeconfig.template.json @@ -0,0 +1,11 @@ +{ + "wasmHostProperties": { + "perHostConfig": [ + { + "name": "browser", + "html-path": "index.html", + "Host": "browser" + } + ] + } +} diff --git a/src/mono/wasm/testassets/WasmLazyLoading/wwwroot/index.html b/src/mono/wasm/testassets/WasmLazyLoading/wwwroot/index.html new file mode 100644 index 00000000000000..997c0d3047c08b --- /dev/null +++ b/src/mono/wasm/testassets/WasmLazyLoading/wwwroot/index.html @@ -0,0 +1,17 @@ + + + + + + + WasmLazyLoading + + + + + + + + + + diff --git a/src/mono/wasm/testassets/WasmLazyLoading/wwwroot/main.js b/src/mono/wasm/testassets/WasmLazyLoading/wwwroot/main.js new file mode 100644 index 00000000000000..f0871d98bb1253 --- /dev/null +++ b/src/mono/wasm/testassets/WasmLazyLoading/wwwroot/main.js @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { dotnet, exit } from './_framework/dotnet.js' + +const { getAssemblyExports, getConfig, INTERNAL } = await dotnet + .withElementOnExit() + .withExitCodeLogging() + .withExitOnUnhandledError() + .create(); + +const config = getConfig(); +const exports = await getAssemblyExports(config.mainAssemblyName); + +try { + await INTERNAL.loadLazyAssembly("System.Text.Json.wasm"); + const json = await exports.MyClass.GetJson(); + console.log(json); + exit(0); +} catch (e) { + exit(1, e); +} From b44c7558217c494755a452bf81b0053652d7c638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 28 Jun 2023 14:57:38 +0200 Subject: [PATCH 08/57] WBT for satellite loading. --- .../Wasm.Build.Tests/WasmLazyLoadingTests.cs | 5 - .../WasmSatelliteLoadingTests.cs | 85 ++++++++++++ .../WasmSatelliteLoading/Program.cs | 21 +++ .../Properties/AssemblyInfo.cs | 4 + .../WasmSatelliteLoading.csproj | 8 ++ .../runtimeconfig.template.json | 11 ++ .../WasmSatelliteLoading/words.es-ES.resx | 127 ++++++++++++++++++ .../WasmSatelliteLoading/words.resx | 127 ++++++++++++++++++ .../WasmSatelliteLoading/wwwroot/index.html | 17 +++ .../WasmSatelliteLoading/wwwroot/main.js | 17 +++ 10 files changed, 417 insertions(+), 5 deletions(-) create mode 100644 src/mono/wasm/Wasm.Build.Tests/WasmSatelliteLoadingTests.cs create mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/Program.cs create mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/Properties/AssemblyInfo.cs create mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/WasmSatelliteLoading.csproj create mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/runtimeconfig.template.json create mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/words.es-ES.resx create mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/words.resx create mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/index.html create mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/main.js diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs index f349e726b3d508..197ee8f9f1e86d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs @@ -31,9 +31,6 @@ public async Task LazyLoadAssembly() string logPath = Path.Combine(s_buildEnv.LogRootPath, id); Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "WasmLazyLoading"), Path.Combine(_projectDir!)); - string projectFile = Path.Combine(_projectDir!, "WasmLazyLoading.csproj"); - AddItemsPropertiesToProject(projectFile); - string publishLogPath = Path.Combine(logPath, $"{id}.binlog"); CommandResult result = new DotNetCommand(s_buildEnv, _testOutput) .WithWorkingDirectory(_projectDir!) @@ -42,7 +39,6 @@ public async Task LazyLoadAssembly() result.EnsureSuccessful(); - string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files"; string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir("Debug", forPublish: true), "..")); @@ -57,7 +53,6 @@ public async Task LazyLoadAssembly() await using var runner = new BrowserRunner(_testOutput); var page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage); - void OnConsoleMessage(IConsoleMessage msg) { if (EnvironmentVariables.ShowBuildOutput) diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSatelliteLoadingTests.cs new file mode 100644 index 00000000000000..8231faac55e7a3 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/WasmSatelliteLoadingTests.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.Playwright; +using Xunit.Abstractions; +using Xunit; + +#nullable enable + +namespace Wasm.Build.Tests; + +public class WasmSatelliteLoadingTests : BuildTestBase +{ + public WasmSatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Fact] + public async Task LoadSatelliteAssembly() + { + string id = $"WasmSatelliteLoading_{Path.GetRandomFileName()}"; + InitBlazorWasmProjectDir(id); + + string logPath = Path.Combine(s_buildEnv.LogRootPath, id); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "WasmSatelliteLoading"), Path.Combine(_projectDir!)); + + string publishLogPath = Path.Combine(logPath, $"{id}.binlog"); + CommandResult result = new DotNetCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(_projectDir!) + .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) + .ExecuteWithCapturedOutput("publish", $"-bl:{publishLogPath}", $"-p:Configuration=Debug"); + + result.EnsureSuccessful(); + + string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files"; + string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir("Debug", forPublish: true), "..")); + + using var runCommand = new RunCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(workingDirectory); + + var tcs = new TaskCompletionSource(); + + List testOutput = new(); + Regex exitRegex = new("WASM EXIT (?[0-9]+)$"); + + await using var runner = new BrowserRunner(_testOutput); + var page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage); + + void OnConsoleMessage(IConsoleMessage msg) + { + if (EnvironmentVariables.ShowBuildOutput) + Console.WriteLine($"[{msg.Type}] {msg.Text}"); + + _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); + + const string testOutputPrefix = "TestOutput -> "; + if (msg.Text.StartsWith(testOutputPrefix)) + testOutput.Add(msg.Text.Substring(testOutputPrefix.Length)); + + if (exitRegex.Match(msg.Text).Success) + tcs.SetResult(true); + } + + TimeSpan timeout = TimeSpan.FromMinutes(2); + await Task.WhenAny(tcs.Task, Task.Delay(timeout)); + if (!tcs.Task.IsCompleted || !tcs.Task.Result) + throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); + + Assert.Collection( + testOutput, + m => Assert.Equal("default: hello", m), + m => Assert.Equal("es-ES without satellite: hello", m), + m => Assert.Equal("default: hello", m), + m => Assert.Equal("es-ES with satellite: hola", m) + ); + } +} diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/Program.cs b/src/mono/wasm/testassets/WasmSatelliteLoading/Program.cs new file mode 100644 index 00000000000000..1a590cfe0b04ae --- /dev/null +++ b/src/mono/wasm/testassets/WasmSatelliteLoading/Program.cs @@ -0,0 +1,21 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using System.Resources; +using System.Runtime.InteropServices.JavaScript; + +var rm = new ResourceManager("WasmSatelliteLoading.words", typeof(Program).Assembly); +Console.WriteLine("TestOutput -> default: " + rm.GetString("hello", CultureInfo.CurrentCulture)); +Console.WriteLine("TestOutput -> es-ES without satellite: " + rm.GetString("hello", new CultureInfo("es-ES"))); + +await Interop.LoadSatelliteAssemblies(new[] { "es-ES" }); + +rm = new ResourceManager("WasmSatelliteLoading.words", typeof(Program).Assembly); +Console.WriteLine("TestOutput -> default: " + rm.GetString("hello", CultureInfo.CurrentCulture)); +Console.WriteLine("TestOutput -> es-ES with satellite: " + rm.GetString("hello", new CultureInfo("es-ES"))); + +public partial class Interop +{ + [JSImport("INTERNAL.loadSatelliteAssemblies")] + public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad); +} diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/Properties/AssemblyInfo.cs b/src/mono/wasm/testassets/WasmSatelliteLoading/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000000..9ad9b578f20649 --- /dev/null +++ b/src/mono/wasm/testassets/WasmSatelliteLoading/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +[assembly:System.Runtime.Versioning.SupportedOSPlatform("browser")] diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/WasmSatelliteLoading.csproj b/src/mono/wasm/testassets/WasmSatelliteLoading/WasmSatelliteLoading.csproj new file mode 100644 index 00000000000000..3d1eaae555931e --- /dev/null +++ b/src/mono/wasm/testassets/WasmSatelliteLoading/WasmSatelliteLoading.csproj @@ -0,0 +1,8 @@ + + + net8.0 + browser-wasm + Exe + true + + diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/runtimeconfig.template.json b/src/mono/wasm/testassets/WasmSatelliteLoading/runtimeconfig.template.json new file mode 100644 index 00000000000000..8f0557352c6ed3 --- /dev/null +++ b/src/mono/wasm/testassets/WasmSatelliteLoading/runtimeconfig.template.json @@ -0,0 +1,11 @@ +{ + "wasmHostProperties": { + "perHostConfig": [ + { + "name": "browser", + "html-path": "index.html", + "Host": "browser" + } + ] + } +} diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/words.es-ES.resx b/src/mono/wasm/testassets/WasmSatelliteLoading/words.es-ES.resx new file mode 100644 index 00000000000000..775397b15a2b9f --- /dev/null +++ b/src/mono/wasm/testassets/WasmSatelliteLoading/words.es-ES.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ciao + + + + hola + + diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/words.resx b/src/mono/wasm/testassets/WasmSatelliteLoading/words.resx new file mode 100644 index 00000000000000..c3d5a787420866 --- /dev/null +++ b/src/mono/wasm/testassets/WasmSatelliteLoading/words.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + bye + + + + hello + + diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/index.html b/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/index.html new file mode 100644 index 00000000000000..997c0d3047c08b --- /dev/null +++ b/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/index.html @@ -0,0 +1,17 @@ + + + + + + + WasmLazyLoading + + + + + + + + + + diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/main.js b/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/main.js new file mode 100644 index 00000000000000..02677760db8b4a --- /dev/null +++ b/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/main.js @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { dotnet, exit } from './_framework/dotnet.js' + +await dotnet + .withElementOnExit() + .withExitCodeLogging() + .withExitOnUnhandledError() + .create(); + +try { + await dotnet.run(); + exit(0); +} catch (e) { + exit(1, e); +} From b792851dbe5e5110561dd87d448fd76e09fffa0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 30 Jun 2023 11:35:25 +0200 Subject: [PATCH 09/57] Refactor tests - Integrate them in a single test app - Create base class for such tests --- .../wasm/Wasm.Build.Tests/BrowserRunner.cs | 8 +- .../TestAppScenarios/AppTestBase.cs | 111 ++++++++++++++++++ .../TestAppScenarios/WasmLazyLoadingTests.cs | 32 +++++ .../WasmSatelliteLoadingTests.cs | 41 +++++++ .../Wasm.Build.Tests/Wasm.Build.Tests.csproj | 12 +- .../Wasm.Build.Tests/WasmLazyLoadingTests.cs | 77 ------------ .../WasmSatelliteLoadingTests.cs | 85 -------------- .../WasmBasicTestApp/Common/Program.cs | 7 ++ .../WasmBasicTestApp/Common/TestOutput.cs | 15 +++ .../WasmBasicTestApp/LazyLoadingTest.cs | 15 +++ .../Properties/AssemblyInfo.cs | 0 .../SatelliteAssembliesTest.cs | 25 ++++ .../WasmBasicTestApp.csproj} | 0 .../words.es-ES.resx | 0 .../words.resx | 0 .../wwwroot/index.html | 0 .../WasmBasicTestApp/wwwroot/main.js | 35 ++++++ .../testassets/WasmLazyLoading/Program.cs | 18 --- .../runtimeconfig.template.json | 11 -- .../WasmLazyLoading/wwwroot/main.js | 22 ---- .../WasmSatelliteLoading/Program.cs | 21 ---- .../Properties/AssemblyInfo.cs | 4 - .../WasmSatelliteLoading.csproj | 8 -- .../runtimeconfig.template.json | 11 -- .../WasmSatelliteLoading/wwwroot/index.html | 17 --- .../WasmSatelliteLoading/wwwroot/main.js | 17 --- 26 files changed, 295 insertions(+), 297 deletions(-) create mode 100644 src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmLazyLoadingTests.cs create mode 100644 src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmSatelliteLoadingTests.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs delete mode 100644 src/mono/wasm/Wasm.Build.Tests/WasmSatelliteLoadingTests.cs create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs rename src/mono/wasm/testassets/{WasmLazyLoading => WasmBasicTestApp}/Properties/AssemblyInfo.cs (100%) create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs rename src/mono/wasm/testassets/{WasmLazyLoading/WasmLazyLoading.csproj => WasmBasicTestApp/WasmBasicTestApp.csproj} (100%) rename src/mono/wasm/testassets/{WasmSatelliteLoading => WasmBasicTestApp}/words.es-ES.resx (100%) rename src/mono/wasm/testassets/{WasmSatelliteLoading => WasmBasicTestApp}/words.resx (100%) rename src/mono/wasm/testassets/{WasmLazyLoading => WasmBasicTestApp}/wwwroot/index.html (100%) create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js delete mode 100644 src/mono/wasm/testassets/WasmLazyLoading/Program.cs delete mode 100644 src/mono/wasm/testassets/WasmLazyLoading/runtimeconfig.template.json delete mode 100644 src/mono/wasm/testassets/WasmLazyLoading/wwwroot/main.js delete mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/Program.cs delete mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/Properties/AssemblyInfo.cs delete mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/WasmSatelliteLoading.csproj delete mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/runtimeconfig.template.json delete mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/index.html delete mode 100644 src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/main.js diff --git a/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs b/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs index f7be214f24f942..f12bef8ce53155 100644 --- a/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs +++ b/src/mono/wasm/Wasm.Build.Tests/BrowserRunner.cs @@ -35,7 +35,7 @@ internal class BrowserRunner : IAsyncDisposable public BrowserRunner(ITestOutputHelper testOutput) => _testOutput = testOutput; // FIXME: options - public async Task RunAsync(ToolCommand cmd, string args, bool headless = true, Action? onConsoleMessage = null) + public async Task RunAsync(ToolCommand cmd, string args, bool headless = true, Action? onConsoleMessage = null, Func? modifyBrowserUrl = null) { TaskCompletionSource urlAvailable = new(); Action outputHandler = msg => @@ -89,10 +89,14 @@ public async Task RunAsync(ToolCommand cmd, string args, bool headless = Args = chromeArgs }); + string browserUrl = urlAvailable.Task.Result; + if (modifyBrowserUrl != null) + browserUrl = modifyBrowserUrl(browserUrl); + IPage page = await Browser.NewPageAsync(); if (onConsoleMessage is not null) page.Console += (_, msg) => onConsoleMessage(msg); - await page.GotoAsync(urlAvailable.Task.Result); + await page.GotoAsync(browserUrl); RunTask = runTask; return page; } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs new file mode 100644 index 00000000000000..2957eb317b04d1 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Authentication.ExtendedProtection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.Playwright; +using Xunit.Abstractions; + +namespace Wasm.Build.Tests.TestAppScenarios +{ + public abstract class AppTestBase : BuildTestBase + { + protected AppTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + protected string Id { get; set; } + protected string LogPath { get; set; } + + protected void CopyTestAsset(string assetName, string generatedProjectNamePrefix = null) + { + Id = $"{generatedProjectNamePrefix ?? assetName}_{Path.GetRandomFileName()}"; + InitBlazorWasmProjectDir(Id); + + LogPath = Path.Combine(s_buildEnv.LogRootPath, Id); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, assetName), Path.Combine(_projectDir!)); + } + + protected void BuildProject(string configuration) + { + CommandResult result = CreateDotNetCommand().ExecuteWithCapturedOutput("build", $"-bl:{GetBinLogFilePath()}", $"-p:Configuration={configuration}"); + result.EnsureSuccessful(); + } + + protected void PublishProject(string configuration) + { + CommandResult result = CreateDotNetCommand().ExecuteWithCapturedOutput("publish", $"-bl:{GetBinLogFilePath()}", $"-p:Configuration={configuration}"); + result.EnsureSuccessful(); + } + + protected string GetBinLogFilePath(string suffix = null) + { + if (!string.IsNullOrEmpty(suffix)) + suffix = "_" + suffix; + + return Path.Combine(LogPath, $"{Id}{suffix}.binlog"); + } + + protected ToolCommand CreateDotNetCommand() => new DotNetCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(_projectDir!) + .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir); + + protected async Task> RunSdkStyleApp(RunOptions options) + { + string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files"; + string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(options.Configuration, forPublish: options.ForPublish), "..")); + + using var runCommand = new RunCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(workingDirectory); + + var tcs = new TaskCompletionSource(); + + List testOutput = new(); + Regex exitRegex = new Regex("WASM EXIT (?[0-9]+)$"); + + await using var runner = new BrowserRunner(_testOutput); + + IPage page = null; + page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage, modifyBrowserUrl: url => url + "?test=" + options.TestScenario); + + void OnConsoleMessage(IConsoleMessage msg) + { + if (EnvironmentVariables.ShowBuildOutput) + Console.WriteLine($"[{msg.Type}] {msg.Text}"); + + _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); + + const string testOutputPrefix = "TestOutput -> "; + if (msg.Text.StartsWith(testOutputPrefix)) + testOutput.Add(msg.Text.Substring(testOutputPrefix.Length)); + + if (exitRegex.Match(msg.Text).Success) + tcs.SetResult(true); + + if (msg.Text.StartsWith("Error: Missing test scenario")) + throw new Exception(msg.Text); + + if (options.OnConsoleMessage != null) + options.OnConsoleMessage(msg, page); + } + + TimeSpan timeout = TimeSpan.FromMinutes(2); + await Task.WhenAny(tcs.Task, Task.Delay(timeout)); + if (!tcs.Task.IsCompleted || !tcs.Task.Result) + throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); + + return testOutput; + } + + protected record RunOptions( + string Configuration, + string TestScenario, + bool ForPublish = false, + Action OnConsoleMessage = null + ); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmLazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmLazyLoadingTests.cs new file mode 100644 index 00000000000000..820d7732dfb64b --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmLazyLoadingTests.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests.TestAppScenarios; + +public class WasmLazyLoadingTests : AppTestBase +{ + public WasmLazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Fact] + public async Task LazyLoadAssembly() + { + CopyTestAsset("WasmBasicTestApp", "WasmLazyLoading"); + PublishProject("Debug"); + + var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LazyLoadingTest")); + Assert.True(testOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON"); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmSatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmSatelliteLoadingTests.cs new file mode 100644 index 00000000000000..65930d721c6541 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmSatelliteLoadingTests.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.Playwright; +using Xunit.Abstractions; +using Xunit; + +#nullable enable + +namespace Wasm.Build.Tests.TestAppScenarios; + +public class WasmSatelliteLoadingTests : AppTestBase +{ + public WasmSatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Fact] + public async Task LoadSatelliteAssembly() + { + CopyTestAsset("WasmBasicTestApp", "WasmSatelliteLoading"); + PublishProject("Debug"); + + var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "SatelliteAssembliesTest")); + Assert.Collection( + testOutput, + m => Assert.Equal("default: hello", m), + m => Assert.Equal("es-ES without satellite: hello", m), + m => Assert.Equal("default: hello", m), + m => Assert.Equal("es-ES with satellite: hola", m) + ); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj index 0ff15466c29ef6..66eb1158967fbe 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -41,9 +41,12 @@ + + + + - + <_SdkWithWorkloadForTestingDirName>$([System.IO.Path]::GetDirectoryName($(SdkWithWorkloadForTestingPath))) @@ -97,8 +100,8 @@ <_RuntimePackVersions Include="$(PackageVersion)" EnvVarName="RUNTIME_PACK_VER8" /> - <_RuntimePackVersions Include="$(PackageVersionNet7)" EnvVarName="RUNTIME_PACK_VER7" Condition="'$(PackageVersionNet7)' != ''"/> - <_RuntimePackVersions Include="$(PackageVersion)" EnvVarName="RUNTIME_PACK_VER7" Condition="'$(PackageVersionNet7)' == ''"/> + <_RuntimePackVersions Include="$(PackageVersionNet7)" EnvVarName="RUNTIME_PACK_VER7" Condition="'$(PackageVersionNet7)' != ''" /> + <_RuntimePackVersions Include="$(PackageVersion)" EnvVarName="RUNTIME_PACK_VER7" Condition="'$(PackageVersionNet7)' == ''" /> <_RuntimePackVersions Include="$(PackageVersionNet6)" EnvVarName="RUNTIME_PACK_VER6" /> @@ -121,6 +124,7 @@ $(RunScriptCommand) -method $(XUnitMethodName) $(RunScriptCommand) -class $(XUnitClassName) + $(RunScriptCommand) -namespace $(XUnitNamespace) $(RunScriptCommand) -notrait category=IgnoreForCI -notrait category=failing diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs deleted file mode 100644 index 197ee8f9f1e86d..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/WasmLazyLoadingTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.IO; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Playwright; -using Xunit; -using Xunit.Abstractions; -using Xunit.Sdk; - -#nullable enable - -namespace Wasm.Build.Tests; - -public class WasmLazyLoadingTests : BuildTestBase -{ - public WasmLazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - [Fact] - public async Task LazyLoadAssembly() - { - string id = $"WasmLazyLoading_{Path.GetRandomFileName()}"; - InitBlazorWasmProjectDir(id); - - string logPath = Path.Combine(s_buildEnv.LogRootPath, id); - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "WasmLazyLoading"), Path.Combine(_projectDir!)); - - string publishLogPath = Path.Combine(logPath, $"{id}.binlog"); - CommandResult result = new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .ExecuteWithCapturedOutput("publish", $"-bl:{publishLogPath}", $"-p:Configuration=Debug"); - - result.EnsureSuccessful(); - - string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files"; - string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir("Debug", forPublish: true), "..")); - - using var runCommand = new RunCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(workingDirectory); - - var tcs = new TaskCompletionSource(); - - bool hasExpectedMessage = false; - Regex exitRegex = new Regex("WASM EXIT (?[0-9]+)$"); - - await using var runner = new BrowserRunner(_testOutput); - var page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage); - - void OnConsoleMessage(IConsoleMessage msg) - { - if (EnvironmentVariables.ShowBuildOutput) - Console.WriteLine($"[{msg.Type}] {msg.Text}"); - - _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); - - if (msg.Text.Contains("FirstName")) - hasExpectedMessage = true; - - if (exitRegex.Match(msg.Text).Success) - tcs.SetResult(true); - } - - TimeSpan timeout = TimeSpan.FromMinutes(2); - await Task.WhenAny(tcs.Task, Task.Delay(timeout)); - if (!tcs.Task.IsCompleted || !tcs.Task.Result) - throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); - - Assert.True(hasExpectedMessage, "The lazy loading application didn't emitted expected message"); - } -} diff --git a/src/mono/wasm/Wasm.Build.Tests/WasmSatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/WasmSatelliteLoadingTests.cs deleted file mode 100644 index 8231faac55e7a3..00000000000000 --- a/src/mono/wasm/Wasm.Build.Tests/WasmSatelliteLoadingTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Microsoft.Playwright; -using Xunit.Abstractions; -using Xunit; - -#nullable enable - -namespace Wasm.Build.Tests; - -public class WasmSatelliteLoadingTests : BuildTestBase -{ - public WasmSatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - [Fact] - public async Task LoadSatelliteAssembly() - { - string id = $"WasmSatelliteLoading_{Path.GetRandomFileName()}"; - InitBlazorWasmProjectDir(id); - - string logPath = Path.Combine(s_buildEnv.LogRootPath, id); - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, "WasmSatelliteLoading"), Path.Combine(_projectDir!)); - - string publishLogPath = Path.Combine(logPath, $"{id}.binlog"); - CommandResult result = new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir) - .ExecuteWithCapturedOutput("publish", $"-bl:{publishLogPath}", $"-p:Configuration=Debug"); - - result.EnsureSuccessful(); - - string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files"; - string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir("Debug", forPublish: true), "..")); - - using var runCommand = new RunCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(workingDirectory); - - var tcs = new TaskCompletionSource(); - - List testOutput = new(); - Regex exitRegex = new("WASM EXIT (?[0-9]+)$"); - - await using var runner = new BrowserRunner(_testOutput); - var page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage); - - void OnConsoleMessage(IConsoleMessage msg) - { - if (EnvironmentVariables.ShowBuildOutput) - Console.WriteLine($"[{msg.Type}] {msg.Text}"); - - _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); - - const string testOutputPrefix = "TestOutput -> "; - if (msg.Text.StartsWith(testOutputPrefix)) - testOutput.Add(msg.Text.Substring(testOutputPrefix.Length)); - - if (exitRegex.Match(msg.Text).Success) - tcs.SetResult(true); - } - - TimeSpan timeout = TimeSpan.FromMinutes(2); - await Task.WhenAny(tcs.Task, Task.Delay(timeout)); - if (!tcs.Task.IsCompleted || !tcs.Task.Result) - throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); - - Assert.Collection( - testOutput, - m => Assert.Equal("default: hello", m), - m => Assert.Equal("es-ES without satellite: hello", m), - m => Assert.Equal("default: hello", m), - m => Assert.Equal("es-ES with satellite: hola", m) - ); - } -} diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs new file mode 100644 index 00000000000000..0ca9184b4b4651 --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs @@ -0,0 +1,7 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using System.Resources; +using System.Runtime.InteropServices.JavaScript; + +System.Console.WriteLine("WasmBasicTestApp"); \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs new file mode 100644 index 00000000000000..707e3c79f45a13 --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs @@ -0,0 +1,15 @@ +using System; + +public static class TestOutput +{ + public static void WriteLine(string message) + { + Console.WriteLine("TestOutput -> " + message); + } + + public static void WriteLine(object message) + { + Console.Write("TestOutput -> "); + Console.WriteLine(message); + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs new file mode 100644 index 00000000000000..0d104931026e2b --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs @@ -0,0 +1,15 @@ +using System; +using System.Text.Json; +using System.Runtime.InteropServices.JavaScript; + +public partial class LazyLoadingTest +{ + [JSExport] + public static void Run() + { + var text = JsonSerializer.Serialize(new Person("John", "Doe")); + TestOutput.WriteLine(text); + } + + public record Person(string FirstName, string LastName); +} diff --git a/src/mono/wasm/testassets/WasmLazyLoading/Properties/AssemblyInfo.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Properties/AssemblyInfo.cs similarity index 100% rename from src/mono/wasm/testassets/WasmLazyLoading/Properties/AssemblyInfo.cs rename to src/mono/wasm/testassets/WasmBasicTestApp/Properties/AssemblyInfo.cs diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs new file mode 100644 index 00000000000000..367fa754fe4e03 --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using System.Resources; +using System.Runtime.InteropServices.JavaScript; + +public partial class SatelliteAssembliesTest +{ + [JSExport] + public static async Task Run() + { + var rm = new ResourceManager("WasmBasicTestApp.words", typeof(Program).Assembly); + TestOutput.WriteLine("default: " + rm.GetString("hello", CultureInfo.CurrentCulture)); + TestOutput.WriteLine("es-ES without satellite: " + rm.GetString("hello", new CultureInfo("es-ES"))); + + await LoadSatelliteAssemblies(new[] { "es-ES" }); + + rm = new ResourceManager("WasmBasicTestApp.words", typeof(Program).Assembly); + TestOutput.WriteLine("default: " + rm.GetString("hello", CultureInfo.CurrentCulture)); + TestOutput.WriteLine("es-ES with satellite: " + rm.GetString("hello", new CultureInfo("es-ES"))); + } + + [JSImport("INTERNAL.loadSatelliteAssemblies")] + public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad); +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmLazyLoading/WasmLazyLoading.csproj b/src/mono/wasm/testassets/WasmBasicTestApp/WasmBasicTestApp.csproj similarity index 100% rename from src/mono/wasm/testassets/WasmLazyLoading/WasmLazyLoading.csproj rename to src/mono/wasm/testassets/WasmBasicTestApp/WasmBasicTestApp.csproj diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/words.es-ES.resx b/src/mono/wasm/testassets/WasmBasicTestApp/words.es-ES.resx similarity index 100% rename from src/mono/wasm/testassets/WasmSatelliteLoading/words.es-ES.resx rename to src/mono/wasm/testassets/WasmBasicTestApp/words.es-ES.resx diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/words.resx b/src/mono/wasm/testassets/WasmBasicTestApp/words.resx similarity index 100% rename from src/mono/wasm/testassets/WasmSatelliteLoading/words.resx rename to src/mono/wasm/testassets/WasmBasicTestApp/words.resx diff --git a/src/mono/wasm/testassets/WasmLazyLoading/wwwroot/index.html b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/index.html similarity index 100% rename from src/mono/wasm/testassets/WasmLazyLoading/wwwroot/index.html rename to src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/index.html diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js new file mode 100644 index 00000000000000..ccc56f33e5016c --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { dotnet, exit } from './_framework/dotnet.js' + +const { getAssemblyExports, getConfig, INTERNAL } = await dotnet + .withElementOnExit() + .withExitCodeLogging() + .withExitOnUnhandledError() + .create(); + +const config = getConfig(); +const exports = await getAssemblyExports(config.mainAssemblyName); + +try { + const params = new URLSearchParams(location.search); + const testCase = params.get("test"); + if (testCase == null) { + exit(2, new Error("Missing test scenario. Supply query argument 'test'.")); + } + + switch (testCase) { + case "SatelliteAssembliesTest": + await exports.SatelliteAssembliesTest.Run(); + exit(0); + break; + case "LazyLoadingTest": + await INTERNAL.loadLazyAssembly("System.Text.Json.wasm"); + exports.LazyLoadingTest.Run(); + exit(0); + break; + } +} catch (e) { + exit(1, e); +} diff --git a/src/mono/wasm/testassets/WasmLazyLoading/Program.cs b/src/mono/wasm/testassets/WasmLazyLoading/Program.cs deleted file mode 100644 index 08d02be69d1b43..00000000000000 --- a/src/mono/wasm/testassets/WasmLazyLoading/Program.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Text.Json; -using System.Runtime.InteropServices.JavaScript; - -Console.WriteLine("Hello, Browser!"); - -public partial class MyClass -{ - [JSExport] - internal static string GetJson() - { - var text = JsonSerializer.Serialize(new Person("John", "Doe")); - Console.WriteLine(text); - return text; - } -} - -public record Person(string FirstName, string LastName); \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmLazyLoading/runtimeconfig.template.json b/src/mono/wasm/testassets/WasmLazyLoading/runtimeconfig.template.json deleted file mode 100644 index 8f0557352c6ed3..00000000000000 --- a/src/mono/wasm/testassets/WasmLazyLoading/runtimeconfig.template.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "wasmHostProperties": { - "perHostConfig": [ - { - "name": "browser", - "html-path": "index.html", - "Host": "browser" - } - ] - } -} diff --git a/src/mono/wasm/testassets/WasmLazyLoading/wwwroot/main.js b/src/mono/wasm/testassets/WasmLazyLoading/wwwroot/main.js deleted file mode 100644 index f0871d98bb1253..00000000000000 --- a/src/mono/wasm/testassets/WasmLazyLoading/wwwroot/main.js +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { dotnet, exit } from './_framework/dotnet.js' - -const { getAssemblyExports, getConfig, INTERNAL } = await dotnet - .withElementOnExit() - .withExitCodeLogging() - .withExitOnUnhandledError() - .create(); - -const config = getConfig(); -const exports = await getAssemblyExports(config.mainAssemblyName); - -try { - await INTERNAL.loadLazyAssembly("System.Text.Json.wasm"); - const json = await exports.MyClass.GetJson(); - console.log(json); - exit(0); -} catch (e) { - exit(1, e); -} diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/Program.cs b/src/mono/wasm/testassets/WasmSatelliteLoading/Program.cs deleted file mode 100644 index 1a590cfe0b04ae..00000000000000 --- a/src/mono/wasm/testassets/WasmSatelliteLoading/Program.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Globalization; -using System.Threading.Tasks; -using System.Resources; -using System.Runtime.InteropServices.JavaScript; - -var rm = new ResourceManager("WasmSatelliteLoading.words", typeof(Program).Assembly); -Console.WriteLine("TestOutput -> default: " + rm.GetString("hello", CultureInfo.CurrentCulture)); -Console.WriteLine("TestOutput -> es-ES without satellite: " + rm.GetString("hello", new CultureInfo("es-ES"))); - -await Interop.LoadSatelliteAssemblies(new[] { "es-ES" }); - -rm = new ResourceManager("WasmSatelliteLoading.words", typeof(Program).Assembly); -Console.WriteLine("TestOutput -> default: " + rm.GetString("hello", CultureInfo.CurrentCulture)); -Console.WriteLine("TestOutput -> es-ES with satellite: " + rm.GetString("hello", new CultureInfo("es-ES"))); - -public partial class Interop -{ - [JSImport("INTERNAL.loadSatelliteAssemblies")] - public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad); -} diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/Properties/AssemblyInfo.cs b/src/mono/wasm/testassets/WasmSatelliteLoading/Properties/AssemblyInfo.cs deleted file mode 100644 index 9ad9b578f20649..00000000000000 --- a/src/mono/wasm/testassets/WasmSatelliteLoading/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,4 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -[assembly:System.Runtime.Versioning.SupportedOSPlatform("browser")] diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/WasmSatelliteLoading.csproj b/src/mono/wasm/testassets/WasmSatelliteLoading/WasmSatelliteLoading.csproj deleted file mode 100644 index 3d1eaae555931e..00000000000000 --- a/src/mono/wasm/testassets/WasmSatelliteLoading/WasmSatelliteLoading.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - net8.0 - browser-wasm - Exe - true - - diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/runtimeconfig.template.json b/src/mono/wasm/testassets/WasmSatelliteLoading/runtimeconfig.template.json deleted file mode 100644 index 8f0557352c6ed3..00000000000000 --- a/src/mono/wasm/testassets/WasmSatelliteLoading/runtimeconfig.template.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "wasmHostProperties": { - "perHostConfig": [ - { - "name": "browser", - "html-path": "index.html", - "Host": "browser" - } - ] - } -} diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/index.html b/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/index.html deleted file mode 100644 index 997c0d3047c08b..00000000000000 --- a/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - WasmLazyLoading - - - - - - - - - - diff --git a/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/main.js b/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/main.js deleted file mode 100644 index 02677760db8b4a..00000000000000 --- a/src/mono/wasm/testassets/WasmSatelliteLoading/wwwroot/main.js +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { dotnet, exit } from './_framework/dotnet.js' - -await dotnet - .withElementOnExit() - .withExitCodeLogging() - .withExitOnUnhandledError() - .create(); - -try { - await dotnet.run(); - exit(0); -} catch (e) { - exit(1, e); -} From e860e40fc353589e9b37ca711f0c3188acc592ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 30 Jun 2023 13:09:36 +0200 Subject: [PATCH 10/57] Fix checking is lazy assembly was already loaded --- src/mono/wasm/runtime/assets.ts | 4 ++-- src/mono/wasm/runtime/lazyLoading.ts | 8 ++------ .../runtime/loader/blazor/WebAssemblyResourceLoader.ts | 8 +++++++- src/mono/wasm/runtime/loader/globals.ts | 1 + src/mono/wasm/runtime/types/internal.ts | 1 + 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 8eacc484dde5fb..5070a6d75853e2 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -73,9 +73,9 @@ export function instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Ar if (asset.behavior === "assembly") { // this is reading flag inside the DLL about the existence of PDB // it doesn't relate to whether the .pdb file is downloaded at all - const hasPpdb = cwraps.mono_wasm_add_assembly(virtualName, offset!, bytes.length); + const hasPdb = cwraps.mono_wasm_add_assembly(virtualName, offset!, bytes.length); - if (!hasPpdb) { + if (!hasPdb) { const index = loaderHelpers._loaded_files.findIndex(element => element.file == virtualName); loaderHelpers._loaded_files.splice(index, 1); } diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index eed438ea64ab33..b58e6510223d24 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -1,7 +1,6 @@ /* eslint-disable no-prototype-builtins */ -import { mono_wasm_get_loaded_files } from "./assets"; -import { INTERNAL, runtimeHelpers } from "./globals"; +import { INTERNAL, loaderHelpers, runtimeHelpers } from "./globals"; import type { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; export async function loadLazyAssembly(assemblyNameToLoad: string): Promise { @@ -17,8 +16,7 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise f.includes(assemblyNameToLoad))) { + if (loaderHelpers.loadedAssemblies.some(f => f.includes(assemblyNameToLoad))) { return false; } @@ -42,8 +40,6 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise Date: Fri, 30 Jun 2023 14:26:04 +0200 Subject: [PATCH 11/57] Introduce WasmDebugLevel to Wasm SDK. Default values (when WasmDebugLevel is not set) - Build (debug) => debugBuild=true & debugLevel=-1 => -1 - Build (release) => debugBuild=true & debugLevel=0 => 0 - Publish (debug) => debugBuild=false & debugLevel=-1 => 0 - Publish (release) => debugBuild=false & debugLevel=0 => 0 --- .../Microsoft.NET.Sdk.WebAssembly.Browser.targets | 10 ++++++++-- src/mono/wasm/runtime/dotnet.d.ts | 8 ++++---- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 6 ++++++ src/mono/wasm/runtime/types/index.ts | 2 +- .../GenerateWasmBootJson.cs | 12 +++++++++++- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index 4186a7a43b17b9..0ae1485a9b15c5 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -166,7 +166,8 @@ Copyright (c) .NET Foundation. All rights reserved. - <_TargetingNET80OrLater>$([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '8.0')) + <_TargetingNET80OrLater>false + <_TargetingNET80OrLater Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '8.0'))">true <_BlazorEnableTimeZoneSupport>$(BlazorEnableTimeZoneSupport) <_BlazorEnableTimeZoneSupport Condition="'$(_BlazorEnableTimeZoneSupport)' == ''">true @@ -178,11 +179,14 @@ Copyright (c) .NET Foundation. All rights reserved. <_WasmEnableThreads Condition="'$(_WasmEnableThreads)' == ''">false <_WasmEnableWebcil>$(WasmEnableWebcil) - <_WasmEnableWebcil Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp' or '$(_TargetingNET80OrLater)' != 'true'">false + <_WasmEnableWebcil Condition="'$(_TargetingNET80OrLater)' != 'true'">false <_WasmEnableWebcil Condition="'$(_WasmEnableWebcil)' == ''">true <_BlazorWebAssemblyStartupMemoryCache>$(BlazorWebAssemblyStartupMemoryCache) <_BlazorWebAssemblyJiterpreter>$(BlazorWebAssemblyJiterpreter) <_BlazorWebAssemblyRuntimeOptions>$(BlazorWebAssemblyRuntimeOptions) + <_WasmDebugLevel>$(WasmDebugLevel) + <_WasmDebugLevel Condition="'$(_TargetingNET80OrLater)' == 'true' and '$(_WasmDebugLevel)' == ''">0 + <_WasmDebugLevel Condition="'$(_TargetingNET80OrLater)' == 'true' and ('$(_WasmDebugLevel)' == '' or '$(_WasmDebugLevel)' == '0') and ('$(DebuggerSupport)' == 'true' or '$(Configuration)' == 'Debug')">-1 $(OutputPath)$(PublishDirName)\ @@ -341,6 +345,7 @@ Copyright (c) .NET Foundation. All rights reserved. AssemblyPath="@(IntermediateAssembly)" Resources="@(_WasmOutputWithHash)" DebugBuild="true" + DebugLevel="$(_WasmDebugLevel)" LinkerEnabled="false" CacheBootResources="$(BlazorCacheBootResources)" OutputPath="$(_WasmBuildBootJsonPath)" @@ -528,6 +533,7 @@ Copyright (c) .NET Foundation. All rights reserved. AssemblyPath="@(IntermediateAssembly)" Resources="@(_WasmPublishBootResourceWithHash)" DebugBuild="false" + DebugLevel="$(_WasmDebugLevel)" LinkerEnabled="$(PublishTrimmed)" CacheBootResources="$(BlazorCacheBootResources)" OutputPath="$(IntermediateOutputPath)blazor.publish.boot.json" diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index d27feb612ba44e..263bf7da20de33 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -120,7 +120,7 @@ type MonoConfig = { /** * debugLevel > 0 enables debugging and sets the debug log level to debugLevel * debugLevel == 0 disables debugging and enables interpreter optimizations - * debugLevel < 0 enabled debugging and disables debug logging. + * debugLevel < 0 enables debugging and disables debug logging. */ debugLevel?: number; /** @@ -210,9 +210,9 @@ interface AssetEntry extends ResourceRequest { } type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads" | "js-module-runtime" | "js-module-dotnet" | "js-module-native" | "symbols"; type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". -"invariant" | // operate in invariant globalization mode. -"hybrid" | // operate in hybrid globalization mode with small ICU files, using native platform functions -"auto"; + "invariant" | // operate in invariant globalization mode. + "hybrid" | // operate in hybrid globalization mode with small ICU files, using native platform functions + "auto"; type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean; config?: MonoConfig; diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 1420b3e472744f..3274ef9d939d99 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -102,6 +102,12 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl moduleConfig.assetsHash = resourceLoader.bootConfig.resources.hash; moduleConfig.assets = assets; moduleConfig.globalizationMode = "icu"; + + // Default values (when WasmDebugLevel is not set) + // - Build (debug) => debugBuild=true & debugLevel=-1 => -1 + // - Build (release) => debugBuild=true & debugLevel=0 => 0 + // - Publish (debug) => debugBuild=false & debugLevel=-1 => 0 + // - Publish (release) => debugBuild=false & debugLevel=0 => 0 moduleConfig.debugLevel = hasDebuggingEnabled(resourceLoader.bootConfig) ? resourceLoader.bootConfig.debugLevel : 0; moduleConfig.mainAssemblyName = resourceLoader.bootConfig.entryAssembly; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 247c40252805f4..2f1a42aee91e2e 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -54,7 +54,7 @@ export type MonoConfig = { /** * debugLevel > 0 enables debugging and sets the debug log level to debugLevel * debugLevel == 0 disables debugging and enables interpreter optimizations - * debugLevel < 0 enabled debugging and disables debug logging. + * debugLevel < 0 enables debugging and disables debug logging. */ debugLevel?: number, /** diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 9690097816258d..5c3b8c193ed3cb 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -30,6 +30,8 @@ public class GenerateWasmBootJson : Task [Required] public bool DebugBuild { get; set; } + public string DebugLevel { get; set; } + [Required] public bool LinkerEnabled { get; set; } @@ -103,7 +105,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) entryAssembly = entryAssemblyName, cacheBootResources = CacheBootResources, debugBuild = DebugBuild, - debugLevel = DebugBuild ? 1 : 0, + debugLevel = ParseOptionalInt(DebugLevel) ?? (DebugBuild ? 1 : 0), linkerEnabled = LinkerEnabled, resources = new ResourcesData(), config = new List(), @@ -328,6 +330,14 @@ void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resour return boolValue; } + private static int? ParseOptionalInt(string value) + { + if (string.IsNullOrEmpty(value) || !int.TryParse(value, out var intValue)) + return null; + + return intValue; + } + private void AddToAdditionalResources(ITaskItem resource, Dictionary additionalResources, string resourceName, string behavior) { if (!additionalResources.ContainsKey(resourceName)) From 3fe3fc451cded51f3dab6c73ab6d395e758a07f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 3 Jul 2023 14:02:36 +0200 Subject: [PATCH 12/57] bootConfig.aspnetCoreBrowserTools -> env:__ASPNETCORE_BROWSER_TOOLS --- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 3274ef9d939d99..13915640b6072a 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -240,6 +240,11 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl environmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = resourceLoader.bootConfig.modifiableAssemblies; } + if (resourceLoader.bootConfig.aspnetCoreBrowserTools) { + // See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000 + environmentVariables["__ASPNETCORE_BROWSER_TOOLS"] = resourceLoader.bootConfig.aspnetCoreBrowserTools; + } + if (moduleConfig.applicationCulture) { // If a culture is specified via start options use that to initialize the Emscripten \ .NET culture. environmentVariables["LANG"] = `${moduleConfig.applicationCulture}.UTF-8`; From 1347c2d6cd27bdf52592bcbeecabb00277b899e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 3 Jul 2023 14:12:55 +0200 Subject: [PATCH 13/57] Alternatives for withStartupOptions --- src/mono/wasm/runtime/dotnet.d.ts | 18 +++++++++-- src/mono/wasm/runtime/loader/run.ts | 48 ++++++++++++++++++++++++---- src/mono/wasm/runtime/types/index.ts | 12 +++++++ 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 263bf7da20de33..6951397f6d8d6f 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -81,6 +81,9 @@ interface DotnetHostBuilder { withDebugging(level: number): DotnetHostBuilder; withMainAssembly(mainAssemblyName: string): DotnetHostBuilder; withApplicationArgumentsFromQuery(): DotnetHostBuilder; + withApplicationEnvironment(applicationEnvironment?: string): DotnetHostBuilder; + withApplicationCulture(applicationCulture?: string): DotnetHostBuilder; + withResourceLoader(loadBootResource?: LoadBootResourceCallback): DotnetHostBuilder; create(): Promise; run(): Promise; } @@ -168,6 +171,15 @@ type MonoConfig = { */ loadBootResource?: LoadBootResourceCallback; }; +/** + * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched + * from a custom source, such as an external CDN. + * @param type The type of the resource to be loaded. + * @param name The name of the resource to be loaded. + * @param defaultUri The URI from which the framework would fetch the resource by default. The URI may be relative or absolute. + * @param integrity The integrity string representing the expected content in the response. + * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. + */ type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; interface ResourceRequest { name: string; @@ -210,9 +222,9 @@ interface AssetEntry extends ResourceRequest { } type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads" | "js-module-runtime" | "js-module-dotnet" | "js-module-native" | "symbols"; type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". - "invariant" | // operate in invariant globalization mode. - "hybrid" | // operate in hybrid globalization mode with small ICU files, using native platform functions - "auto"; +"invariant" | // operate in invariant globalization mode. +"hybrid" | // operate in hybrid globalization mode with small ICU files, using native platform functions +"auto"; type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean; config?: MonoConfig; diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 97db13e3205d5f..de951f57d07f6e 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { MonoConfig, DotnetHostBuilder, DotnetModuleConfig, RuntimeAPI, WebAssemblyStartOptions } from "../types"; +import type { MonoConfig, DotnetHostBuilder, DotnetModuleConfig, RuntimeAPI, WebAssemblyStartOptions, LoadBootResourceCallback } from "../types"; import type { MonoConfigInternal, EmscriptenModuleInternal, RuntimeModuleExportsInternal, NativeModuleExportsInternal, } from "../types/internal"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, exportedRuntimeAPI, globalObjectsRoot } from "./globals"; @@ -276,12 +276,46 @@ export class HostBuilder implements DotnetHostBuilder { } withStartupOptions(startupOptions: Partial): DotnetHostBuilder { - deep_merge_config(monoConfig, { - applicationEnvironment: startupOptions.environment, - applicationCulture: startupOptions.applicationCulture, - loadBootResource: startupOptions.loadBootResource, - }); - return this; + return this + .withApplicationEnvironment(startupOptions.environment) + .withApplicationCulture(startupOptions.applicationCulture) + .withResourceLoader(startupOptions.loadBootResource); + } + + withApplicationEnvironment(applicationEnvironment?: string): DotnetHostBuilder { + try { + deep_merge_config(monoConfig, { + applicationEnvironment, + }); + return this; + } catch (err) { + mono_exit(1, err); + throw err; + } + } + + withApplicationCulture(applicationCulture?: string): DotnetHostBuilder { + try { + deep_merge_config(monoConfig, { + applicationCulture, + }); + return this; + } catch (err) { + mono_exit(1, err); + throw err; + } + } + + withResourceLoader(loadBootResource?: LoadBootResourceCallback): DotnetHostBuilder { + try { + deep_merge_config(monoConfig, { + loadBootResource + }); + return this; + } catch (err) { + mono_exit(1, err); + throw err; + } } async create(): Promise { diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 2f1a42aee91e2e..33a1c89c3a22fd 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -14,6 +14,9 @@ export interface DotnetHostBuilder { withDebugging(level: number): DotnetHostBuilder withMainAssembly(mainAssemblyName: string): DotnetHostBuilder withApplicationArgumentsFromQuery(): DotnetHostBuilder + withApplicationEnvironment(applicationEnvironment?: string): DotnetHostBuilder; + withApplicationCulture(applicationCulture?: string): DotnetHostBuilder; + withResourceLoader(loadBootResource?: LoadBootResourceCallback): DotnetHostBuilder; create(): Promise run(): Promise } @@ -105,6 +108,15 @@ export type MonoConfig = { loadBootResource?: LoadBootResourceCallback; }; +/** + * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched + * from a custom source, such as an external CDN. + * @param type The type of the resource to be loaded. + * @param name The name of the resource to be loaded. + * @param defaultUri The URI from which the framework would fetch the resource by default. The URI may be relative or absolute. + * @param integrity The integrity string representing the expected content in the response. + * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. + */ export type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; export interface ResourceRequest { From 919f23810d258ed2cbd03c7b63c2df4935ef097a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 3 Jul 2023 14:49:27 +0200 Subject: [PATCH 14/57] Import JS intializers --- src/mono/wasm/runtime/dotnet.d.ts | 6 +++ .../runtime/loader/blazor/_Integration.ts | 1 + .../wasm/runtime/loader/jsInitializers.ts | 45 +++++++++++++++++++ src/mono/wasm/runtime/loader/run.ts | 7 +++ src/mono/wasm/runtime/types/index.ts | 7 +++ src/mono/wasm/runtime/types/internal.ts | 3 +- 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/mono/wasm/runtime/loader/jsInitializers.ts diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 6951397f6d8d6f..e24c40157824c0 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -170,6 +170,12 @@ type MonoConfig = { * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. */ loadBootResource?: LoadBootResourceCallback; + /** + * exports from registered javascript library modules + * + * TODO MF: Describe what could be here + */ + libraryInitializers?: any[]; }; /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 13915640b6072a..c440ba8306f3c5 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -21,6 +21,7 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, loadBootResource?: LoadBootResourceCallback) { INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, loadBootResource); + loaderHelpers.bootConfig = bootConfigResult.bootConfig; mapBootConfigToMonoConfig(loaderHelpers.config, bootConfigResult.applicationEnvironment); if (ENVIRONMENT_IS_WEB) { diff --git a/src/mono/wasm/runtime/loader/jsInitializers.ts b/src/mono/wasm/runtime/loader/jsInitializers.ts new file mode 100644 index 00000000000000..a10eb422747cb6 --- /dev/null +++ b/src/mono/wasm/runtime/loader/jsInitializers.ts @@ -0,0 +1,45 @@ +import { MonoConfig } from "../types"; +import { BootJsonData } from "../types/blazor"; + +export async function fetchInitializers(moduleConfig: MonoConfig, bootConfig: BootJsonData): Promise { + const libraryInitializers = bootConfig.resources.libraryInitializers; + if (!libraryInitializers) { + return; + } + + if (!moduleConfig.libraryInitializers) { + moduleConfig.libraryInitializers = []; + } + + const initializerFiles = Object.keys(libraryInitializers); + await Promise.all(initializerFiles.map(f => importInitializer(f))); + + function adjustPath(path: string): string { + // This is the same we do in JS interop with the import callback + const base = document.baseURI; + path = base.endsWith("/") ? `${base}${path}` : `${base}/${path}`; + return path; + } + + async function importInitializer(path: string): Promise { + const adjustedPath = adjustPath(path); + const initializer = await import(/* webpackIgnore: true */ adjustedPath); + + moduleConfig.libraryInitializers!.push(initializer); + } +} + +export async function invokeOnRuntimeReady(moduleConfig: MonoConfig) { + const initializerPromises = []; + if (moduleConfig.libraryInitializers) { + for (let i = 0; i < moduleConfig.libraryInitializers.length; i++) { + const initializer = moduleConfig.libraryInitializers[i]; + initializer as { onRuntimeReady: () => Promise }; + if (initializer?.onRuntimeReady) { + initializerPromises.push(initializer?.onRuntimeReady()); + } + } + + await Promise.all(initializerPromises); + } +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index de951f57d07f6e..45ef9934aa9820 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -13,6 +13,7 @@ import { detect_features_and_polyfill } from "./polyfills"; import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; import { setupPreloadChannelToMainThread } from "./worker"; +import { fetchInitializers, invokeOnRuntimeReady } from "./jsInitializers"; const module = globalObjectsRoot.module; @@ -445,12 +446,18 @@ async function createEmscriptenMain(): Promise { }); init_globalization(); + // TODO call mono_download_assets(); here in parallel ? const es6Modules = await Promise.all(promises); initializeModules(es6Modules as any); await runtimeHelpers.dotnetReady.promise; + if (loaderHelpers.bootConfig) { + await fetchInitializers(module.config!, loaderHelpers.bootConfig); + await invokeOnRuntimeReady(module.config!); + } + return exportedRuntimeAPI; } diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 33a1c89c3a22fd..2965b8f2381f78 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -106,6 +106,13 @@ export type MonoConfig = { * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. */ loadBootResource?: LoadBootResourceCallback; + + /** + * exports from registered javascript library modules + * + * TODO MF: Describe what could be here + */ + libraryInitializers?: any[]; }; /** diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 539bb00be20c5a..1823645e6553a5 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -95,6 +95,7 @@ export interface AssetEntryInternal extends AssetEntry { export type LoaderHelpers = { config: MonoConfigInternal; + bootConfig: BootJsonData; diagnosticTracing: boolean; maxParallelDownloads: number; @@ -137,7 +138,7 @@ export type LoaderHelpers = { hasDebuggingEnabled(bootConfig: BootJsonData): boolean, isChromium: boolean, - isFirefox: boolean, + isFirefox: boolean } export type RuntimeHelpers = { config: MonoConfigInternal; From 1e8ef5da441cb77dc38dde2b1bcd8c1863b12d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 3 Jul 2023 17:55:09 +0200 Subject: [PATCH 15/57] Pass runtime API to onRuntimeReady library initializer --- src/mono/wasm/runtime/loader/jsInitializers.ts | 9 +++++---- src/mono/wasm/runtime/loader/run.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mono/wasm/runtime/loader/jsInitializers.ts b/src/mono/wasm/runtime/loader/jsInitializers.ts index a10eb422747cb6..757ae1f237e6e9 100644 --- a/src/mono/wasm/runtime/loader/jsInitializers.ts +++ b/src/mono/wasm/runtime/loader/jsInitializers.ts @@ -1,4 +1,4 @@ -import { MonoConfig } from "../types"; +import { MonoConfig, RuntimeAPI } from "../types"; import { BootJsonData } from "../types/blazor"; export async function fetchInitializers(moduleConfig: MonoConfig, bootConfig: BootJsonData): Promise { @@ -29,14 +29,15 @@ export async function fetchInitializers(moduleConfig: MonoConfig, bootConfig: Bo } } -export async function invokeOnRuntimeReady(moduleConfig: MonoConfig) { +export async function invokeOnRuntimeReady(api: RuntimeAPI) { + const moduleConfig = api.getConfig(); const initializerPromises = []; if (moduleConfig.libraryInitializers) { for (let i = 0; i < moduleConfig.libraryInitializers.length; i++) { const initializer = moduleConfig.libraryInitializers[i]; - initializer as { onRuntimeReady: () => Promise }; + initializer as { onRuntimeReady: (api: RuntimeAPI) => Promise }; if (initializer?.onRuntimeReady) { - initializerPromises.push(initializer?.onRuntimeReady()); + initializerPromises.push(initializer?.onRuntimeReady(api)); } } diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 45ef9934aa9820..c98f0bd892843d 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -455,7 +455,7 @@ async function createEmscriptenMain(): Promise { if (loaderHelpers.bootConfig) { await fetchInitializers(module.config!, loaderHelpers.bootConfig); - await invokeOnRuntimeReady(module.config!); + await invokeOnRuntimeReady(globalObjectsRoot.api); } return exportedRuntimeAPI; From 2c73ca2f22f8f35d1545d131bcb8318c6e11a0a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 3 Jul 2023 17:55:19 +0200 Subject: [PATCH 16/57] Library initializer test --- ...azyLoadingTests.cs => LazyLoadingTests.cs} | 6 +-- .../LibraryInitializerTests.cs | 38 +++++++++++++++++++ ...adingTests.cs => SatelliteLoadingTests.cs} | 6 +-- .../LibraryInitializerTest.cs | 14 +++++++ .../wwwroot/WasmBasicTestApp.lib.module.js | 10 +++++ .../WasmBasicTestApp/wwwroot/main.js | 3 ++ 6 files changed, 71 insertions(+), 6 deletions(-) rename src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/{WasmLazyLoadingTests.cs => LazyLoadingTests.cs} (78%) create mode 100644 src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs rename src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/{WasmSatelliteLoadingTests.cs => SatelliteLoadingTests.cs} (81%) create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmLazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs similarity index 78% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmLazyLoadingTests.cs rename to src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs index 820d7732dfb64b..35daccb408105d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmLazyLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs @@ -13,9 +13,9 @@ namespace Wasm.Build.Tests.TestAppScenarios; -public class WasmLazyLoadingTests : AppTestBase +public class LazyLoadingTests : AppTestBase { - public WasmLazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + public LazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } @@ -23,7 +23,7 @@ public WasmLazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFix [Fact] public async Task LazyLoadAssembly() { - CopyTestAsset("WasmBasicTestApp", "WasmLazyLoading"); + CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests"); PublishProject("Debug"); var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LazyLoadingTest")); diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs new file mode 100644 index 00000000000000..39467ee65b6e79 --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Microsoft.Playwright; +using Xunit.Abstractions; +using Xunit; + +#nullable enable + +namespace Wasm.Build.Tests.TestAppScenarios; + +public class LibraryInitializerTests : AppTestBase +{ + public LibraryInitializerTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Fact] + public async Task LoadLibraryInitializer() + { + CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests"); + PublishProject("Debug"); + + var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LibraryInitializerTest")); + Assert.Collection( + testOutput, + m => Assert.Equal("Run from LibraryInitializer", m) + ); + } +} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmSatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs similarity index 81% rename from src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmSatelliteLoadingTests.cs rename to src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs index 65930d721c6541..046b1154428f8d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/WasmSatelliteLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs @@ -16,9 +16,9 @@ namespace Wasm.Build.Tests.TestAppScenarios; -public class WasmSatelliteLoadingTests : AppTestBase +public class SatelliteLoadingTests : AppTestBase { - public WasmSatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + public SatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) : base(output, buildContext) { } @@ -26,7 +26,7 @@ public WasmSatelliteLoadingTests(ITestOutputHelper output, SharedBuildPerTestCla [Fact] public async Task LoadSatelliteAssembly() { - CopyTestAsset("WasmBasicTestApp", "WasmSatelliteLoading"); + CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTests"); PublishProject("Debug"); var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "SatelliteAssembliesTest")); diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs new file mode 100644 index 00000000000000..d23c1fb9c5e795 --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs @@ -0,0 +1,14 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using System.Resources; +using System.Runtime.InteropServices.JavaScript; + +public partial class LibraryInitializerTest +{ + [JSExport] + public static void Run() + { + TestOutput.WriteLine("Run from LibraryInitializer"); + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js new file mode 100644 index 00000000000000..5c0cf2cfd42314 --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js @@ -0,0 +1,10 @@ +export async function onRuntimeReady({ getAssemblyExports }) { + const params = new URLSearchParams(location.search); + const testCase = params.get("test"); + if (testCase == "LibraryInitializerTest") { + const config = getConfig(); + const exports = await getAssemblyExports(config.mainAssemblyName); + + exports.LibraryInitializerTest.Run(); + } +} \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js index ccc56f33e5016c..8fdc82f0b26097 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js @@ -29,6 +29,9 @@ try { exports.LazyLoadingTest.Run(); exit(0); break; + case "LibraryInitializerTest": + exit(0); + break; } } catch (e) { exit(1, e); From 8fbb58f17d2cf70522dff3c00f69e3f3be63a36c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 3 Jul 2023 17:57:19 +0200 Subject: [PATCH 17/57] Ensure loadedAssemblies has value --- .../wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts index cd90ffb2b9a7ed..cef5cc3e096c05 100644 --- a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts +++ b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts @@ -37,6 +37,9 @@ export class WebAssemblyResourceLoader { const absoluteUrl = toAbsoluteUri(url); if (resourceType == "assembly") { + if (!loaderHelpers.loadedAssemblies) + loaderHelpers.loadedAssemblies = []; + loaderHelpers.loadedAssemblies.push(absoluteUrl); } return { name, url: absoluteUrl, response }; From 3e8575d5e851e8a7d2118d68fd0012e8e219c73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 3 Jul 2023 20:49:46 +0200 Subject: [PATCH 18/57] Register new WBT for CI run --- eng/testing/scenarios/BuildWasmAppsJobsList.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index 64f6abf5a1a650..ee53644517fe7e 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -27,3 +27,6 @@ Wasm.Build.Tests.WasmNativeDefaultsTests Wasm.Build.Tests.WasmRunOutOfAppBundleTests Wasm.Build.Tests.WasmSIMDTests Wasm.Build.Tests.WasmTemplateTests +Wasm.Build.Tests.TestAppScenarios.LazyLoadingTests +Wasm.Build.Tests.TestAppScenarios.LibraryInitializerTests +Wasm.Build.Tests.TestAppScenarios.SatelliteLoadingTests From be9831fb835c3cdcca6f19e7280928521b8b2a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 4 Jul 2023 10:27:13 +0200 Subject: [PATCH 19/57] Fix resolving URL when baseURI contains a query string --- src/mono/wasm/runtime/loader/assets.ts | 16 ++++++++++++++++ .../wasm/runtime/loader/blazor/_Integration.ts | 11 ++--------- ...{jsInitializers.ts => libraryInitializers.ts} | 10 ++-------- src/mono/wasm/runtime/loader/run.ts | 2 +- .../wwwroot/WasmBasicTestApp.lib.module.js | 2 +- 5 files changed, 22 insertions(+), 19 deletions(-) rename src/mono/wasm/runtime/loader/{jsInitializers.ts => libraryInitializers.ts} (82%) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index da53c3207b2fde..cd8ad2a999f186 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -415,3 +415,19 @@ export function cleanupAsset(asset: AssetEntryInternal) { asset.pendingDownload = null as any; // GC asset.buffer = null as any; // GC } + +export function appendUniqueQuery(attemptUrl: string): string { + if (loaderHelpers.assetUniqueQuery) { + attemptUrl = attemptUrl + loaderHelpers.assetUniqueQuery; + } + + return attemptUrl; +} + +export function toAbsoluteBaseUri(path: string) { + if ("URL" in globalThis) { + return new URL(path, document.baseURI).toString(); + } + + return "/" + path; +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index b9ef32803db3c8..038295b699ac6b 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -10,6 +10,7 @@ import { BootConfigResult } from "./BootConfig"; import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader"; import { hasDebuggingEnabled } from "./_Polyfill"; import { ICUDataMode } from "../../types/blazor"; +import { appendUniqueQuery, toAbsoluteBaseUri } from "../assets"; let resourceLoader: WebAssemblyResourceLoader; @@ -78,14 +79,6 @@ export function setupModuleForBlazor(module: DotnetModuleInternal) { loaderHelpers.downloadResource = downloadResource; // polyfills were already assigned } -function appendUniqueQuery(attemptUrl: string): string { - if (loaderHelpers.assetUniqueQuery) { - attemptUrl = attemptUrl + loaderHelpers.assetUniqueQuery; - } - - return attemptUrl; -} - export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, applicationEnvironment: string) { const resources = resourceLoader.bootConfig.resources; @@ -213,7 +206,7 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl if (config === "appsettings.json" || config === `appsettings.${applicationEnvironment}.json`) { assets.push({ name: config, - resolvedUrl: appendUniqueQuery((document ? document.baseURI : "/") + config), + resolvedUrl: appendUniqueQuery(toAbsoluteBaseUri(config)), behavior: "vfs", }); } diff --git a/src/mono/wasm/runtime/loader/jsInitializers.ts b/src/mono/wasm/runtime/loader/libraryInitializers.ts similarity index 82% rename from src/mono/wasm/runtime/loader/jsInitializers.ts rename to src/mono/wasm/runtime/loader/libraryInitializers.ts index 757ae1f237e6e9..059e9c018244b1 100644 --- a/src/mono/wasm/runtime/loader/jsInitializers.ts +++ b/src/mono/wasm/runtime/loader/libraryInitializers.ts @@ -1,5 +1,6 @@ import { MonoConfig, RuntimeAPI } from "../types"; import { BootJsonData } from "../types/blazor"; +import { appendUniqueQuery, toAbsoluteBaseUri } from "./assets"; export async function fetchInitializers(moduleConfig: MonoConfig, bootConfig: BootJsonData): Promise { const libraryInitializers = bootConfig.resources.libraryInitializers; @@ -14,15 +15,8 @@ export async function fetchInitializers(moduleConfig: MonoConfig, bootConfig: Bo const initializerFiles = Object.keys(libraryInitializers); await Promise.all(initializerFiles.map(f => importInitializer(f))); - function adjustPath(path: string): string { - // This is the same we do in JS interop with the import callback - const base = document.baseURI; - path = base.endsWith("/") ? `${base}${path}` : `${base}/${path}`; - return path; - } - async function importInitializer(path: string): Promise { - const adjustedPath = adjustPath(path); + const adjustedPath = appendUniqueQuery(toAbsoluteBaseUri(path)); const initializer = await import(/* webpackIgnore: true */ adjustedPath); moduleConfig.libraryInitializers!.push(initializer); diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index c98f0bd892843d..999993da1bad8a 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -13,7 +13,7 @@ import { detect_features_and_polyfill } from "./polyfills"; import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; import { setupPreloadChannelToMainThread } from "./worker"; -import { fetchInitializers, invokeOnRuntimeReady } from "./jsInitializers"; +import { fetchInitializers, invokeOnRuntimeReady } from "./libraryInitializers"; const module = globalObjectsRoot.module; diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js index 5c0cf2cfd42314..a958155d0dc0da 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js @@ -1,4 +1,4 @@ -export async function onRuntimeReady({ getAssemblyExports }) { +export async function onRuntimeReady({ getAssemblyExports, getConfig }) { const params = new URLSearchParams(location.search); const testCase = params.get("test"); if (testCase == "LibraryInitializerTest") { From 5c329b5a421f7486c97e1caafa07c3ba7ba012a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 4 Jul 2023 11:10:31 +0200 Subject: [PATCH 20/57] Resources in mono config --- src/mono/wasm/runtime/dotnet.d.ts | 33 +++++++++++++++++-- .../runtime/loader/blazor/_Integration.ts | 3 ++ src/mono/wasm/runtime/types/index.ts | 29 ++++++++++++++-- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index e24c40157824c0..65566da6e6b0a9 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -171,11 +171,40 @@ type MonoConfig = { */ loadBootResource?: LoadBootResourceCallback; /** - * exports from registered javascript library modules + * exports from library es6 modules * - * TODO MF: Describe what could be here + * nuget packages can contain wwwroot/*.lib.module.js which are treated as es6 modules + * runtime calls 'onRuntimeReady' and pass in runtime API + * blazor calls 'beforeStart' and 'afterStarted' */ libraryInitializers?: any[]; + /** + * A definition of assets to load along with the runtime. + * + * WIP currently only extensions are defined! + */ + resources?: ResourceGroups$1; +}; +type ResourceExtensions = { + [extensionName: string]: ResourceList$1; +}; +interface ResourceGroups$1 { + readonly hash?: string; + readonly assembly?: ResourceList$1; + readonly lazyAssembly?: ResourceList$1; + readonly pdb?: ResourceList$1; + readonly runtime?: ResourceList$1; + readonly satelliteResources?: { + [cultureName: string]: ResourceList$1; + }; + readonly libraryInitializers?: ResourceList$1; + readonly extensions?: ResourceExtensions; + readonly vfs?: { + [virtualPath: string]: ResourceList$1; + }; +} +type ResourceList$1 = { + [name: string]: string; }; /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 038295b699ac6b..a6c220268ca95f 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -96,6 +96,9 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl moduleConfig.assetsHash = resourceLoader.bootConfig.resources.hash; moduleConfig.assets = assets; moduleConfig.globalizationMode = "icu"; + moduleConfig.resources = { + extensions: resources.extensions + }; // Default values (when WasmDebugLevel is not set) // - Build (debug) => debugBuild=true & debugLevel=-1 => -1 diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 2965b8f2381f78..a6652d92f591c0 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -108,13 +108,38 @@ export type MonoConfig = { loadBootResource?: LoadBootResourceCallback; /** - * exports from registered javascript library modules + * exports from library es6 modules * - * TODO MF: Describe what could be here + * nuget packages can contain wwwroot/*.lib.module.js which are treated as es6 modules + * runtime calls 'onRuntimeReady' and pass in runtime API + * blazor calls 'beforeStart' and 'afterStarted' */ libraryInitializers?: any[]; + + /** + * A definition of assets to load along with the runtime. + * + * WIP currently only extensions are defined! + */ + resources?: ResourceGroups; }; +export type ResourceExtensions = { [extensionName: string]: ResourceList }; + +export interface ResourceGroups { + readonly hash?: string; + readonly assembly?: ResourceList; // nullable only temporarily + readonly lazyAssembly?: ResourceList; // nullable only temporarily + readonly pdb?: ResourceList; + readonly runtime?: ResourceList; // nullable only temporarily + readonly satelliteResources?: { [cultureName: string]: ResourceList }; + readonly libraryInitializers?: ResourceList, + readonly extensions?: ResourceExtensions + readonly vfs?: { [virtualPath: string]: ResourceList }; +} + +export type ResourceList = { [name: string]: string }; + /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched * from a custom source, such as an external CDN. From cc65ac91d86587dba8f40cf08fe4221d2773375b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 4 Jul 2023 11:30:56 +0200 Subject: [PATCH 21/57] Unify GlobalizationMode and ICUDataMode --- src/mono/wasm/runtime/dotnet.d.ts | 11 +++++++---- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 12 ++++++------ src/mono/wasm/runtime/loader/config.ts | 1 - src/mono/wasm/runtime/loader/icu.ts | 7 ++++--- src/mono/wasm/runtime/types/index.ts | 12 +++++++----- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 65566da6e6b0a9..ee1f0f037036f4 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -256,10 +256,13 @@ interface AssetEntry extends ResourceRequest { pendingDownload?: LoadingResource; } type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads" | "js-module-runtime" | "js-module-dotnet" | "js-module-native" | "symbols"; -type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". -"invariant" | // operate in invariant globalization mode. -"hybrid" | // operate in hybrid globalization mode with small ICU files, using native platform functions -"auto"; +declare const enum GlobalizationMode { + Sharded = "sharded", + All = "all", + Invariant = "invariant", + Custom = "custom", + Hybrid = "hybrid" +} type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean; config?: MonoConfig; diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index a6c220268ca95f..33c5bd476cf33d 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { DotnetModuleInternal, MonoConfigInternal } from "../../types/internal"; -import type { AssetBehaviours, AssetEntry, LoadBootResourceCallback, LoadingResource, WebAssemblyBootResourceType } from "../../types"; +import { GlobalizationMode, type AssetBehaviours, type AssetEntry, type LoadBootResourceCallback, type LoadingResource, type WebAssemblyBootResourceType } from "../../types"; import type { BootJsonData } from "../../types/blazor"; import { ENVIRONMENT_IS_WEB, INTERNAL, loaderHelpers } from "../globals"; @@ -95,7 +95,6 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl moduleConfig.remoteSources = (resourceLoader.bootConfig.resources as any).remoteSources; moduleConfig.assetsHash = resourceLoader.bootConfig.resources.hash; moduleConfig.assets = assets; - moduleConfig.globalizationMode = "icu"; moduleConfig.resources = { extensions: resources.extensions }; @@ -229,7 +228,7 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl } if (!hasIcuData) { - moduleConfig.globalizationMode = "invariant"; + moduleConfig.globalizationMode = GlobalizationMode.Invariant; } if (resourceLoader.bootConfig.modifiableAssemblies) { @@ -262,24 +261,25 @@ function getICUResourceName(bootConfig: BootJsonData, moduleConfig: MonoConfigIn .keys(bootConfig.resources.runtime) .filter(n => n.startsWith("icudt") && n.endsWith(".dat")); if (icuFiles.length === 1) { - moduleConfig.globalizationMode = "icu"; + moduleConfig.globalizationMode = GlobalizationMode.Custom; const customIcuFile = icuFiles[0]; return customIcuFile; } } if (bootConfig.icuDataMode === ICUDataMode.Hybrid) { - moduleConfig.globalizationMode = "hybrid"; + moduleConfig.globalizationMode = GlobalizationMode.Hybrid; const reducedICUResourceName = "icudt_hybrid.dat"; return reducedICUResourceName; } if (!culture || bootConfig.icuDataMode === ICUDataMode.All) { - moduleConfig.globalizationMode = "icu"; + moduleConfig.globalizationMode = GlobalizationMode.All; const combinedICUResourceName = "icudt.dat"; return combinedICUResourceName; } + moduleConfig.globalizationMode = GlobalizationMode.Sharded; const prefix = culture.split("-")[0]; if (prefix === "en" || [ diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index c269d7748b2328..c074bfe3ad958b 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -41,7 +41,6 @@ export function normalizeConfig() { config.environmentVariables = config.environmentVariables || {}; config.assets = config.assets || []; config.runtimeOptions = config.runtimeOptions || []; - config.globalizationMode = config.globalizationMode || "auto"; if (config.debugLevel === undefined && BuildConfiguration === "Debug") { config.debugLevel = -1; diff --git a/src/mono/wasm/runtime/loader/icu.ts b/src/mono/wasm/runtime/loader/icu.ts index 74990738b72665..bffc1e3dd13460 100644 --- a/src/mono/wasm/runtime/loader/icu.ts +++ b/src/mono/wasm/runtime/loader/icu.ts @@ -1,17 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { GlobalizationMode } from "../types"; import { ENVIRONMENT_IS_WEB, loaderHelpers } from "./globals"; import { mono_log_info, mono_log_debug } from "./logging"; export function init_globalization() { - loaderHelpers.invariantMode = loaderHelpers.config.globalizationMode === "invariant"; + loaderHelpers.invariantMode = loaderHelpers.config.globalizationMode == GlobalizationMode.Invariant; loaderHelpers.preferredIcuAsset = get_preferred_icu_asset(); if (!loaderHelpers.invariantMode) { if (loaderHelpers.preferredIcuAsset) { mono_log_debug("ICU data archive(s) available, disabling invariant mode"); - } else if (loaderHelpers.config.globalizationMode !== "icu") { + } else if (loaderHelpers.config.globalizationMode !== GlobalizationMode.Custom && loaderHelpers.config.globalizationMode !== GlobalizationMode.All && loaderHelpers.config.globalizationMode !== GlobalizationMode.Sharded) { mono_log_debug("ICU data archive(s) not available, using invariant globalization mode"); loaderHelpers.invariantMode = true; loaderHelpers.preferredIcuAsset = null; @@ -25,7 +26,7 @@ export function init_globalization() { const invariantEnv = "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"; const hybridEnv = "DOTNET_SYSTEM_GLOBALIZATION_HYBRID"; const env_variables = loaderHelpers.config.environmentVariables!; - if (env_variables[hybridEnv] === undefined && loaderHelpers.config.globalizationMode === "hybrid") { + if (env_variables[hybridEnv] === undefined && loaderHelpers.config.globalizationMode === GlobalizationMode.Hybrid) { env_variables[hybridEnv] = "1"; } else if (env_variables[invariantEnv] === undefined && loaderHelpers.invariantMode) { diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index a6652d92f591c0..5ae35df15b1586 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -208,11 +208,13 @@ export type AssetBehaviours = | "js-module-native" // the javascript module for threads | "symbols" // the javascript module for threads -export type GlobalizationMode = - "icu" | // load ICU globalization data from any runtime assets with behavior "icu". - "invariant" | // operate in invariant globalization mode. - "hybrid" | // operate in hybrid globalization mode with small ICU files, using native platform functions - "auto" // (default): if "icu" behavior assets are present, use ICU, otherwise invariant. +export const enum GlobalizationMode { + Sharded = "sharded", // load sharded ICU data + All = "all", // load all ICU data + Invariant = "invariant", // operate in invariant globalization mode. + Custom = "custom", // use user defined icu file + Hybrid = "hybrid" // operate in hybrid globalization mode with small ICU files, using native platform functions +} export type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean, From 6789eddad4b6ac693ca1d2c618c81d664b5589a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 4 Jul 2023 11:47:13 +0200 Subject: [PATCH 22/57] Include extensions in monoConfig --- src/mono/wasm/runtime/dotnet.d.ts | 9 +++++++++ src/mono/wasm/runtime/loader/blazor/_Integration.ts | 1 + src/mono/wasm/runtime/types/blazor.ts | 2 ++ src/mono/wasm/runtime/types/index.ts | 9 ++++++--- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index ee1f0f037036f4..791864d775fd60 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -184,6 +184,12 @@ type MonoConfig = { * WIP currently only extensions are defined! */ resources?: ResourceGroups$1; + /** + * config extensions declared in MSBuild items @(WasmBootConfigExtension) + */ + extensions?: { + [name: string]: any; + }; }; type ResourceExtensions = { [extensionName: string]: ResourceList$1; @@ -360,6 +366,9 @@ interface BootJsonData { readonly pthreadPoolSize: number; modifiableAssemblies: string | null; aspnetCoreBrowserTools: string | null; + readonly extensions?: { + [name: string]: any; + }; } type BootJsonDataExtension = { [extensionName: string]: ResourceList; diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 33c5bd476cf33d..fd87ba5e7916a8 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -95,6 +95,7 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl moduleConfig.remoteSources = (resourceLoader.bootConfig.resources as any).remoteSources; moduleConfig.assetsHash = resourceLoader.bootConfig.resources.hash; moduleConfig.assets = assets; + moduleConfig.extensions = resourceLoader.bootConfig.extensions; moduleConfig.resources = { extensions: resources.extensions }; diff --git a/src/mono/wasm/runtime/types/blazor.ts b/src/mono/wasm/runtime/types/blazor.ts index 20cfb1a97c0cf0..367e6a2ded936b 100644 --- a/src/mono/wasm/runtime/types/blazor.ts +++ b/src/mono/wasm/runtime/types/blazor.ts @@ -21,6 +21,8 @@ export interface BootJsonData { // These properties are tacked on, and not found in the boot.json file modifiableAssemblies: string | null; aspnetCoreBrowserTools: string | null; + + readonly extensions?: { [name: string]: any }; } export type BootJsonDataExtension = { [extensionName: string]: ResourceList }; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 5ae35df15b1586..13b1305e33f78b 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -117,11 +117,14 @@ export type MonoConfig = { libraryInitializers?: any[]; /** - * A definition of assets to load along with the runtime. - * - * WIP currently only extensions are defined! + * definition of assets to load along with the runtime. */ resources?: ResourceGroups; + + /** + * config extensions declared in MSBuild items @(WasmBootConfigExtension) + */ + extensions?: { [name: string]: any }; }; export type ResourceExtensions = { [extensionName: string]: ResourceList }; From 37b50faf0742a6fa44ff0e9da1843ef64c3cc434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 4 Jul 2023 11:50:00 +0200 Subject: [PATCH 23/57] Generate dotnet.d.ts --- src/mono/wasm/runtime/dotnet.d.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 791864d775fd60..1585830a6f09e4 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -179,9 +179,7 @@ type MonoConfig = { */ libraryInitializers?: any[]; /** - * A definition of assets to load along with the runtime. - * - * WIP currently only extensions are defined! + * definition of assets to load along with the runtime. */ resources?: ResourceGroups$1; /** From 9b2a5d444a99f02bc5b3c7cba3bc4240b2b8106d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 4 Jul 2023 13:42:33 +0200 Subject: [PATCH 24/57] Load libraryInitializers early and add onRuntimeConfigLoaded --- .../LibraryInitializerTests.cs | 3 +- src/mono/wasm/runtime/dotnet.d.ts | 2 +- .../runtime/loader/blazor/_Integration.ts | 1 - src/mono/wasm/runtime/loader/config.ts | 4 ++ .../runtime/loader/libraryInitializers.ts | 51 ++++++++++++------- src/mono/wasm/runtime/loader/run.ts | 8 +-- src/mono/wasm/runtime/types/index.ts | 2 +- src/mono/wasm/runtime/types/internal.ts | 1 - .../LibraryInitializerTest.cs | 1 + .../wwwroot/WasmBasicTestApp.lib.module.js | 4 ++ 10 files changed, 48 insertions(+), 29 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs index 39467ee65b6e79..378ae54f2dd838 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs @@ -32,7 +32,8 @@ public async Task LoadLibraryInitializer() var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LibraryInitializerTest")); Assert.Collection( testOutput, - m => Assert.Equal("Run from LibraryInitializer", m) + m => Assert.Equal("Run from LibraryInitializer", m), + m => Assert.Equal("LIBRARY_INITIALIZER_TEST = 1", m) ); } } diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 1585830a6f09e4..a09ab149c281e2 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -174,7 +174,7 @@ type MonoConfig = { * exports from library es6 modules * * nuget packages can contain wwwroot/*.lib.module.js which are treated as es6 modules - * runtime calls 'onRuntimeReady' and pass in runtime API + * runtime calls 'onRuntimeConfigLoaded(config: MonoConfig)' and 'onRuntimeReady(api: RuntimeAPI)' * blazor calls 'beforeStart' and 'afterStarted' */ libraryInitializers?: any[]; diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index fd87ba5e7916a8..ee8ae24b4c3f2e 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -22,7 +22,6 @@ export async function loadBootConfig(config: MonoConfigInternal, module: DotnetM export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, loadBootResource?: LoadBootResourceCallback) { INTERNAL.resourceLoader = resourceLoader = await WebAssemblyResourceLoader.initAsync(bootConfigResult.bootConfig, loadBootResource); - loaderHelpers.bootConfig = bootConfigResult.bootConfig; mapBootConfigToMonoConfig(loaderHelpers.config, bootConfigResult.applicationEnvironment); if (ENVIRONMENT_IS_WEB) { diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index c074bfe3ad958b..d0e69cdeaa4e2b 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -9,6 +9,7 @@ import { initializeBootConfig, loadBootConfig } from "./blazor/_Integration"; import { BootConfigResult } from "./blazor/BootConfig"; import { BootJsonData } from "../types/blazor"; import { mono_log_error, mono_log_debug } from "./logging"; +import { fetchInitializers, invokeOnRuntimeConfigLoaded } from "./libraryInitializers"; export function deep_merge_config(target: MonoConfigInternal, source: MonoConfigInternal): MonoConfigInternal { const providedConfig: MonoConfigInternal = { ...source }; @@ -103,6 +104,9 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi normalizeConfig(); + await fetchInitializers(loaderHelpers.config); + await invokeOnRuntimeConfigLoaded(loaderHelpers.config); + if (module.onConfigLoaded) { try { await module.onConfigLoaded(loaderHelpers.config, exportedRuntimeAPI); diff --git a/src/mono/wasm/runtime/loader/libraryInitializers.ts b/src/mono/wasm/runtime/loader/libraryInitializers.ts index 059e9c018244b1..3474e6829408e1 100644 --- a/src/mono/wasm/runtime/loader/libraryInitializers.ts +++ b/src/mono/wasm/runtime/loader/libraryInitializers.ts @@ -1,17 +1,17 @@ import { MonoConfig, RuntimeAPI } from "../types"; -import { BootJsonData } from "../types/blazor"; import { appendUniqueQuery, toAbsoluteBaseUri } from "./assets"; +import { normalizeConfig } from "./config"; -export async function fetchInitializers(moduleConfig: MonoConfig, bootConfig: BootJsonData): Promise { - const libraryInitializers = bootConfig.resources.libraryInitializers; - if (!libraryInitializers) { - return; - } - +export async function fetchInitializers(moduleConfig: MonoConfig): Promise { if (!moduleConfig.libraryInitializers) { moduleConfig.libraryInitializers = []; } + const libraryInitializers = moduleConfig.resources?.libraryInitializers; + if (!libraryInitializers) { + return; + } + const initializerFiles = Object.keys(libraryInitializers); await Promise.all(initializerFiles.map(f => importInitializer(f))); @@ -23,18 +23,33 @@ export async function fetchInitializers(moduleConfig: MonoConfig, bootConfig: Bo } } -export async function invokeOnRuntimeReady(api: RuntimeAPI) { - const moduleConfig = api.getConfig(); - const initializerPromises = []; - if (moduleConfig.libraryInitializers) { - for (let i = 0; i < moduleConfig.libraryInitializers.length; i++) { - const initializer = moduleConfig.libraryInitializers[i]; - initializer as { onRuntimeReady: (api: RuntimeAPI) => Promise }; - if (initializer?.onRuntimeReady) { - initializerPromises.push(initializer?.onRuntimeReady(api)); - } +export async function invokeOnRuntimeConfigLoaded(config: MonoConfig) { + mono_assert(config.libraryInitializers, "Initialization hasn't been done yet"); + + const promises = []; + for (let i = 0; i < config.libraryInitializers.length; i++) { + const initializer = config.libraryInitializers[i] as { onRuntimeConfigLoaded: (config: MonoConfig) => Promise }; + if (initializer?.onRuntimeConfigLoaded) { + promises.push(initializer?.onRuntimeConfigLoaded(config)); } + } + + await Promise.all(promises); + if (promises.length > 0) + normalizeConfig(); +} - await Promise.all(initializerPromises); +export async function invokeOnRuntimeReady(api: RuntimeAPI) { + const config = api.getConfig(); + mono_assert(config.libraryInitializers, "Initialization hasn't been done yet"); + + const promises = []; + for (let i = 0; i < config.libraryInitializers.length; i++) { + const initializer = config.libraryInitializers[i] as { onRuntimeReady: (api: RuntimeAPI) => Promise }; + if (initializer?.onRuntimeReady) { + promises.push(initializer?.onRuntimeReady(api)); + } } + + await Promise.all(promises); } \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 999993da1bad8a..92e96f3427f307 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -13,7 +13,7 @@ import { detect_features_and_polyfill } from "./polyfills"; import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; import { setupPreloadChannelToMainThread } from "./worker"; -import { fetchInitializers, invokeOnRuntimeReady } from "./libraryInitializers"; +import { invokeOnRuntimeReady } from "./libraryInitializers"; const module = globalObjectsRoot.module; @@ -452,11 +452,7 @@ async function createEmscriptenMain(): Promise { initializeModules(es6Modules as any); await runtimeHelpers.dotnetReady.promise; - - if (loaderHelpers.bootConfig) { - await fetchInitializers(module.config!, loaderHelpers.bootConfig); - await invokeOnRuntimeReady(globalObjectsRoot.api); - } + await invokeOnRuntimeReady(globalObjectsRoot.api); return exportedRuntimeAPI; } diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 13b1305e33f78b..a91b2925612b50 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -111,7 +111,7 @@ export type MonoConfig = { * exports from library es6 modules * * nuget packages can contain wwwroot/*.lib.module.js which are treated as es6 modules - * runtime calls 'onRuntimeReady' and pass in runtime API + * runtime calls 'onRuntimeConfigLoaded(config: MonoConfig)' and 'onRuntimeReady(api: RuntimeAPI)' * blazor calls 'beforeStart' and 'afterStarted' */ libraryInitializers?: any[]; diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 1823645e6553a5..48f161684a8221 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -95,7 +95,6 @@ export interface AssetEntryInternal extends AssetEntry { export type LoaderHelpers = { config: MonoConfigInternal; - bootConfig: BootJsonData; diagnosticTracing: boolean; maxParallelDownloads: number; diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs index d23c1fb9c5e795..d467a7c02a929f 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs @@ -10,5 +10,6 @@ public partial class LibraryInitializerTest public static void Run() { TestOutput.WriteLine("Run from LibraryInitializer"); + TestOutput.WriteLine($"LIBRARY_INITIALIZER_TEST = {Environment.GetEnvironmentVariable("LIBRARY_INITIALIZER_TEST")}"); } } \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js index a958155d0dc0da..7071f7488021bd 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js @@ -1,3 +1,7 @@ +export function onRuntimeConfigLoaded(config) { + config.environmentVariables["LIBRARY_INITIALIZER_TEST"] = 1; +} + export async function onRuntimeReady({ getAssemblyExports, getConfig }) { const params = new URLSearchParams(location.search); const testCase = params.get("test"); From d8c1b93b3635777cb30b6d17eace6c43829d267c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 4 Jul 2023 13:54:45 +0200 Subject: [PATCH 25/57] Fix test --- .../WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js index 7071f7488021bd..90ef62668bd8cc 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js @@ -1,5 +1,5 @@ export function onRuntimeConfigLoaded(config) { - config.environmentVariables["LIBRARY_INITIALIZER_TEST"] = 1; + config.environmentVariables["LIBRARY_INITIALIZER_TEST"] = "1"; } export async function onRuntimeReady({ getAssemblyExports, getConfig }) { From 1a90ef1dbd8111a4188bc5d0f492ab7c6a9b1a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 4 Jul 2023 14:58:31 +0200 Subject: [PATCH 26/57] Fix application environment. Add tests --- .../TestAppScenarios/AppSettingsTests.cs | 42 +++++++++++++++++++ .../TestAppScenarios/AppTestBase.cs | 8 +++- .../wasm/runtime/loader/blazor/BootConfig.ts | 7 +++- src/mono/wasm/runtime/loader/config.ts | 2 - .../WasmBasicTestApp/AppSettingsTest.cs | 18 ++++++++ .../wwwroot/appsettings.Development.json | 4 ++ .../wwwroot/appsettings.Production.json | 4 ++ .../WasmBasicTestApp/wwwroot/appsettings.json | 12 ++++++ .../WasmBasicTestApp/wwwroot/main.js | 32 ++++++++++---- 9 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.Development.json create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.Production.json create mode 100644 src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.json diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs new file mode 100644 index 00000000000000..6ef9761760266f --- /dev/null +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests.TestAppScenarios; + +public class AppSettingsTests : AppTestBase +{ + public AppSettingsTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [Theory] + [InlineData("Development")] + [InlineData("Production")] + public async Task LoadAppSettingsBasedOnApplicationEnvironment(string applicationEnvironment) + { + CopyTestAsset("WasmBasicTestApp", "AppSettingsTests"); + PublishProject("Debug"); + + var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "AppSettingsTest", BrowserQueryString: new Dictionary { ["applicationEnvironment"] = applicationEnvironment })); + Assert.Collection( + testOutput, + m => Assert.Equal(GetFileExistenceMessage("/appsettings.json", true), m), + m => Assert.Equal(GetFileExistenceMessage("/appsettings.Development.json", applicationEnvironment == "Development"), m), + m => Assert.Equal(GetFileExistenceMessage("/appsettings.Production.json", applicationEnvironment == "Production"), m) + ); + } + + // Synchronize with AppSettingsTest + private static string GetFileExistenceMessage(string path, bool expected) => $"'{path}' exists '{expected}'"; +} diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs index 2957eb317b04d1..2145c73d2d712c 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs @@ -70,7 +70,12 @@ protected async Task> RunSdkStyleApp(RunOptions opti await using var runner = new BrowserRunner(_testOutput); IPage page = null; - page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage, modifyBrowserUrl: url => url + "?test=" + options.TestScenario); + + string queryString = "?test=" + options.TestScenario; + if (options.BrowserQueryString != null) + queryString += "&" + string.Join("&", options.BrowserQueryString.Select(kvp => $"{kvp.Key}={kvp.Value}")); + + page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage, modifyBrowserUrl: url => url + queryString); void OnConsoleMessage(IConsoleMessage msg) { @@ -104,6 +109,7 @@ void OnConsoleMessage(IConsoleMessage msg) protected record RunOptions( string Configuration, string TestScenario, + Dictionary BrowserQueryString = null, bool ForPublish = false, Action OnConsoleMessage = null ); diff --git a/src/mono/wasm/runtime/loader/blazor/BootConfig.ts b/src/mono/wasm/runtime/loader/blazor/BootConfig.ts index 272b699c7e43ec..2687b72b029338 100644 --- a/src/mono/wasm/runtime/loader/blazor/BootConfig.ts +++ b/src/mono/wasm/runtime/loader/blazor/BootConfig.ts @@ -10,7 +10,12 @@ export class BootConfigResult { } static fromFetchResponse(bootConfigResponse: Response, bootConfig: BootJsonData, environment: string | undefined): BootConfigResult { - const applicationEnvironment = environment || (loaderHelpers.getApplicationEnvironment && loaderHelpers.getApplicationEnvironment(bootConfigResponse)) || "Production"; + const applicationEnvironment = environment + || (loaderHelpers.getApplicationEnvironment && loaderHelpers.getApplicationEnvironment(bootConfigResponse)) + || bootConfigResponse.headers.get("Blazor-Environment") + || bootConfigResponse.headers.get("DotNet-Environment") + || "Production"; + bootConfig.modifiableAssemblies = bootConfigResponse.headers.get("DOTNET-MODIFIABLE-ASSEMBLIES"); bootConfig.aspnetCoreBrowserTools = bootConfigResponse.headers.get("ASPNETCORE-BROWSER-TOOLS"); diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index d0e69cdeaa4e2b..fc16c69415d2e8 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -79,8 +79,6 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi } mono_log_debug("mono_wasm_load_config"); try { - loaderHelpers.config.applicationEnvironment = loaderHelpers.config.applicationEnvironment ?? "Production"; - if (loaderHelpers.config.loadBootResource) { // If we have custom loadBootResource await loadBootConfig(loaderHelpers.config, module); diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs new file mode 100644 index 00000000000000..c1f6ae95acee08 --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs @@ -0,0 +1,18 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Runtime.InteropServices.JavaScript; + +public partial class AppSettingsTest +{ + [JSExport] + public static void Run() + { + PrintFileExistence("/appsettings.json"); + PrintFileExistence("/appsettings.Development.json"); + PrintFileExistence("/appsettings.Production.json"); + } + + // Synchronize with AppSettingsTests + private static void PrintFileExistence(string path) => TestOutput.WriteLine($"'{path}' exists '{File.Exists(path)}'"); +} diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.Development.json b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.Development.json new file mode 100644 index 00000000000000..3a9ec3802e537a --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.Development.json @@ -0,0 +1,4 @@ +{ + "key2": "Development key2-value", + "key3": "Development key3-value" +} diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.Production.json b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.Production.json new file mode 100644 index 00000000000000..539fe7d8318e91 --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.Production.json @@ -0,0 +1,4 @@ +{ + "key2": "Prod key2-value", + "key3": "Prod key3-value" +} diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.json b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.json new file mode 100644 index 00000000000000..6534159aa03afd --- /dev/null +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/appsettings.json @@ -0,0 +1,12 @@ +{ + "key1": "Default key1-value", + "key2": "Default key2-value", + "Logging": { + "PrependMessage": { + "Message": "Custom logger", + "LogLevel": { + "Default": "Warning" + } + } + } +} diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js index 8fdc82f0b26097..0c844ad054e715 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js @@ -3,22 +3,32 @@ import { dotnet, exit } from './_framework/dotnet.js' -const { getAssemblyExports, getConfig, INTERNAL } = await dotnet +// Read test case from query string +const params = new URLSearchParams(location.search); +const testCase = params.get("test"); +if (testCase == null) { + exit(2, new Error("Missing test scenario. Supply query argument 'test'.")); +} + +// Prepare base runtime parameters +dotnet .withElementOnExit() .withExitCodeLogging() - .withExitOnUnhandledError() - .create(); + .withExitOnUnhandledError(); + +// Modify runtime start based on test case +switch (testCase) { + case "AppSettingsTest": + dotnet.withApplicationEnvironment(params.get("applicationEnvironment")); + break; +} +const { getAssemblyExports, getConfig, INTERNAL } = await dotnet.create(); const config = getConfig(); const exports = await getAssemblyExports(config.mainAssemblyName); +// Run the test case try { - const params = new URLSearchParams(location.search); - const testCase = params.get("test"); - if (testCase == null) { - exit(2, new Error("Missing test scenario. Supply query argument 'test'.")); - } - switch (testCase) { case "SatelliteAssembliesTest": await exports.SatelliteAssembliesTest.Run(); @@ -32,6 +42,10 @@ try { case "LibraryInitializerTest": exit(0); break; + case "AppSettingsTest": + exports.AppSettingsTest.Run(); + exit(0); + break; } } catch (e) { exit(1, e); From 4e0b921d057fa58d78375b3d1eb020a9d39aedbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 09:21:40 +0200 Subject: [PATCH 27/57] Drop .NET 8.0 condition on default value for WasmDebugLevel --- .../build/Microsoft.NET.Sdk.WebAssembly.Browser.targets | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index c586c279ddc9f7..c488923660fb62 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -187,8 +187,8 @@ Copyright (c) .NET Foundation. All rights reserved. <_BlazorWebAssemblyJiterpreter>$(BlazorWebAssemblyJiterpreter) <_BlazorWebAssemblyRuntimeOptions>$(BlazorWebAssemblyRuntimeOptions) <_WasmDebugLevel>$(WasmDebugLevel) - <_WasmDebugLevel Condition="'$(_TargetingNET80OrLater)' == 'true' and '$(_WasmDebugLevel)' == ''">0 - <_WasmDebugLevel Condition="'$(_TargetingNET80OrLater)' == 'true' and ('$(_WasmDebugLevel)' == '' or '$(_WasmDebugLevel)' == '0') and ('$(DebuggerSupport)' == 'true' or '$(Configuration)' == 'Debug')">-1 + <_WasmDebugLevel Condition="'$(_WasmDebugLevel)' == ''">0 + <_WasmDebugLevel Condition="'$(_WasmDebugLevel)' == '0' and ('$(DebuggerSupport)' == 'true' or '$(Configuration)' == 'Debug')">-1 $(OutputPath)$(PublishDirName)\ From b1aa9779ec983af673122c0e3bb34afc57bb9c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 09:29:06 +0200 Subject: [PATCH 28/57] Use funcs from loaderHelpers --- src/mono/wasm/runtime/globals.ts | 2 -- src/mono/wasm/runtime/lazyLoading.ts | 6 +++--- src/mono/wasm/runtime/satelliteAssemblies.ts | 4 ++-- src/mono/wasm/runtime/types/internal.ts | 3 --- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/mono/wasm/runtime/globals.ts b/src/mono/wasm/runtime/globals.ts index 315cb6695da3a1..22fef7474b2ae3 100644 --- a/src/mono/wasm/runtime/globals.ts +++ b/src/mono/wasm/runtime/globals.ts @@ -59,8 +59,6 @@ export function setRuntimeGlobals(globalObjects: GlobalObjects) { beforeOnRuntimeInitialized: createPromiseController(), afterOnRuntimeInitialized: createPromiseController(), afterPostRun: createPromiseController(), - hasDebuggingEnabled: loaderHelpers.hasDebuggingEnabled, - locateFile: loaderHelpers.locateFile, }); Object.assign(globalObjects.module.config!, {}) as any; diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index b58e6510223d24..afbf7eae59dd6a 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -22,14 +22,14 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise response.arrayBuffer()); + const dllBytesPromise = resourceLoader.loadResource(dllNameToLoad, loaderHelpers.locateFile(dllNameToLoad), lazyAssemblies[dllNameToLoad], "assembly").response.then(response => response.arrayBuffer()); let dll = null; let pdb = null; if (shouldLoadPdb) { - const pdbBytesPromise = await resourceLoader.loadResource(pdbNameToLoad, runtimeHelpers.locateFile(pdbNameToLoad), lazyAssemblies[pdbNameToLoad], "pdb").response.then(response => response.arrayBuffer()); + const pdbBytesPromise = await resourceLoader.loadResource(pdbNameToLoad, loaderHelpers.locateFile(pdbNameToLoad), lazyAssemblies[pdbNameToLoad], "pdb").response.then(response => response.arrayBuffer()); const [dllBytes, pdbBytes] = await Promise.all([dllBytesPromise, pdbBytesPromise]); dll = new Uint8Array(dllBytes); diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index db0f8086c0c4aa..0f776fc1d096c4 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -1,6 +1,6 @@ /* eslint-disable no-prototype-builtins */ -import { INTERNAL, runtimeHelpers } from "./globals"; +import { INTERNAL, loaderHelpers, runtimeHelpers } from "./globals"; import type { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; import { LoadingResource } from "./types"; @@ -13,7 +13,7 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise await Promise.all(culturesToLoad! .filter(culture => satelliteResources.hasOwnProperty(culture)) - .map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => runtimeHelpers.locateFile(fileName), "assembly")) + .map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => loaderHelpers.locateFile(fileName), "assembly")) .reduce((previous, next) => previous.concat(next), new Array()) .map(async resource => { const response = await resource.response; diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 48f161684a8221..ceb48dc1b07148 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -183,9 +183,6 @@ export type RuntimeHelpers = { instantiate_asset: (asset: AssetEntry, url: string, bytes: Uint8Array) => void, instantiate_symbols_asset: (pendingAsset: AssetEntryInternal) => Promise, jiterpreter_dump_stats?: (x: boolean) => string, - - hasDebuggingEnabled(bootConfig: BootJsonData): boolean, - locateFile: (path: string, prefix?: string) => string, } export type AOTProfilerOptions = { From fa9dbbedabec80038c9f7cada95c15b564bc84f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 09:29:19 +0200 Subject: [PATCH 29/57] Check document and document.baseURI --- src/mono/wasm/runtime/loader/assets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index cd8ad2a999f186..c513657c59ed48 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -425,7 +425,7 @@ export function appendUniqueQuery(attemptUrl: string): string { } export function toAbsoluteBaseUri(path: string) { - if ("URL" in globalThis) { + if ("URL" in globalThis && document && document.baseURI) { return new URL(path, document.baseURI).toString(); } From 3a3ab7a83c332b4d30e37817c5e75d8e4b716aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 09:29:28 +0200 Subject: [PATCH 30/57] Licence headers --- .../wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs | 5 ++++- src/mono/wasm/runtime/lazyLoading.ts | 3 +++ src/mono/wasm/runtime/loader/libraryInitializers.ts | 3 +++ src/mono/wasm/runtime/satelliteAssemblies.ts | 3 +++ src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs | 3 +++ src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs | 3 +++ .../wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs | 3 +++ src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs | 3 +++ .../testassets/WasmBasicTestApp/LibraryInitializerTest.cs | 3 +++ .../testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs | 3 +++ .../WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js | 3 +++ 11 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs index 2145c73d2d712c..b3bd5eafefd985 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs @@ -1,4 +1,7 @@ -using System; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index afbf7eae59dd6a..1ee4aadba2faf3 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + /* eslint-disable no-prototype-builtins */ import { INTERNAL, loaderHelpers, runtimeHelpers } from "./globals"; diff --git a/src/mono/wasm/runtime/loader/libraryInitializers.ts b/src/mono/wasm/runtime/loader/libraryInitializers.ts index 3474e6829408e1..0b703e0a87214c 100644 --- a/src/mono/wasm/runtime/loader/libraryInitializers.ts +++ b/src/mono/wasm/runtime/loader/libraryInitializers.ts @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + import { MonoConfig, RuntimeAPI } from "../types"; import { appendUniqueQuery, toAbsoluteBaseUri } from "./assets"; import { normalizeConfig } from "./config"; diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index 0f776fc1d096c4..2c03a3021a63a9 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + /* eslint-disable no-prototype-builtins */ import { INTERNAL, loaderHelpers, runtimeHelpers } from "./globals"; diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs index c1f6ae95acee08..e78198b4842612 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.IO; using System.Text.Json; diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs index 0ca9184b4b4651..de8a3953d1e983 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Globalization; using System.Threading.Tasks; diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs index 707e3c79f45a13..c7723fd638805e 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; public static class TestOutput diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs index 0d104931026e2b..2b866aa624ad10 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Text.Json; using System.Runtime.InteropServices.JavaScript; diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs index d467a7c02a929f..489ec459a858b6 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Globalization; using System.Threading.Tasks; diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs index 367fa754fe4e03..cc0b28530ea606 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using System; using System.Globalization; using System.Threading.Tasks; diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js index 90ef62668bd8cc..64c19049c959d2 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js @@ -1,3 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + export function onRuntimeConfigLoaded(config) { config.environmentVariables["LIBRARY_INITIALIZER_TEST"] = "1"; } From b0fe3749f46dbe5fd28bfd86c60366538bafa207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 09:34:52 +0200 Subject: [PATCH 31/57] Comments in tests --- src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs | 1 + src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs index e78198b4842612..2e09f35eb00299 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/AppSettingsTest.cs @@ -11,6 +11,7 @@ public partial class AppSettingsTest [JSExport] public static void Run() { + // Check file presence in VFS based on application environment PrintFileExistence("/appsettings.json"); PrintFileExistence("/appsettings.Development.json"); PrintFileExistence("/appsettings.Production.json"); diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs index 2b866aa624ad10..f46c707c1fcec0 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs @@ -10,6 +10,8 @@ public partial class LazyLoadingTest [JSExport] public static void Run() { + // System.Text.Json is marked as lazy loaded in the csproj, this method can be called only after the assembly is lazy loaded + // In the test case it is done in the JS before call to this method var text = JsonSerializer.Serialize(new Person("John", "Doe")); TestOutput.WriteLine(text); } From eab8e45ee55d21f20f5205ad74a64bb50e5a26c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 09:44:18 +0200 Subject: [PATCH 32/57] File scoped namespace --- .../TestAppScenarios/AppTestBase.cs | 155 +++++++++--------- 1 file changed, 77 insertions(+), 78 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs index b3bd5eafefd985..3eef4225327058 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs @@ -12,109 +12,108 @@ using Microsoft.Playwright; using Xunit.Abstractions; -namespace Wasm.Build.Tests.TestAppScenarios +namespace Wasm.Build.Tests.TestAppScenarios; + +public abstract class AppTestBase : BuildTestBase { - public abstract class AppTestBase : BuildTestBase + protected AppTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) { - protected AppTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) - : base(output, buildContext) - { - } - - protected string Id { get; set; } - protected string LogPath { get; set; } - - protected void CopyTestAsset(string assetName, string generatedProjectNamePrefix = null) - { - Id = $"{generatedProjectNamePrefix ?? assetName}_{Path.GetRandomFileName()}"; - InitBlazorWasmProjectDir(Id); + } - LogPath = Path.Combine(s_buildEnv.LogRootPath, Id); - Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, assetName), Path.Combine(_projectDir!)); - } + protected string Id { get; set; } + protected string LogPath { get; set; } - protected void BuildProject(string configuration) - { - CommandResult result = CreateDotNetCommand().ExecuteWithCapturedOutput("build", $"-bl:{GetBinLogFilePath()}", $"-p:Configuration={configuration}"); - result.EnsureSuccessful(); - } + protected void CopyTestAsset(string assetName, string generatedProjectNamePrefix = null) + { + Id = $"{generatedProjectNamePrefix ?? assetName}_{Path.GetRandomFileName()}"; + InitBlazorWasmProjectDir(Id); - protected void PublishProject(string configuration) - { - CommandResult result = CreateDotNetCommand().ExecuteWithCapturedOutput("publish", $"-bl:{GetBinLogFilePath()}", $"-p:Configuration={configuration}"); - result.EnsureSuccessful(); - } + LogPath = Path.Combine(s_buildEnv.LogRootPath, Id); + Utils.DirectoryCopy(Path.Combine(BuildEnvironment.TestAssetsPath, assetName), Path.Combine(_projectDir!)); + } - protected string GetBinLogFilePath(string suffix = null) - { - if (!string.IsNullOrEmpty(suffix)) - suffix = "_" + suffix; + protected void BuildProject(string configuration) + { + CommandResult result = CreateDotNetCommand().ExecuteWithCapturedOutput("build", $"-bl:{GetBinLogFilePath()}", $"-p:Configuration={configuration}"); + result.EnsureSuccessful(); + } - return Path.Combine(LogPath, $"{Id}{suffix}.binlog"); - } + protected void PublishProject(string configuration) + { + CommandResult result = CreateDotNetCommand().ExecuteWithCapturedOutput("publish", $"-bl:{GetBinLogFilePath()}", $"-p:Configuration={configuration}"); + result.EnsureSuccessful(); + } - protected ToolCommand CreateDotNetCommand() => new DotNetCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(_projectDir!) - .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir); + protected string GetBinLogFilePath(string suffix = null) + { + if (!string.IsNullOrEmpty(suffix)) + suffix = "_" + suffix; - protected async Task> RunSdkStyleApp(RunOptions options) - { - string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files"; - string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(options.Configuration, forPublish: options.ForPublish), "..")); + return Path.Combine(LogPath, $"{Id}{suffix}.binlog"); + } - using var runCommand = new RunCommand(s_buildEnv, _testOutput) - .WithWorkingDirectory(workingDirectory); + protected ToolCommand CreateDotNetCommand() => new DotNetCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(_projectDir!) + .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir); - var tcs = new TaskCompletionSource(); + protected async Task> RunSdkStyleApp(RunOptions options) + { + string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files"; + string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(options.Configuration, forPublish: options.ForPublish), "..")); - List testOutput = new(); - Regex exitRegex = new Regex("WASM EXIT (?[0-9]+)$"); + using var runCommand = new RunCommand(s_buildEnv, _testOutput) + .WithWorkingDirectory(workingDirectory); - await using var runner = new BrowserRunner(_testOutput); + var tcs = new TaskCompletionSource(); - IPage page = null; + List testOutput = new(); + Regex exitRegex = new Regex("WASM EXIT (?[0-9]+)$"); - string queryString = "?test=" + options.TestScenario; - if (options.BrowserQueryString != null) - queryString += "&" + string.Join("&", options.BrowserQueryString.Select(kvp => $"{kvp.Key}={kvp.Value}")); + await using var runner = new BrowserRunner(_testOutput); - page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage, modifyBrowserUrl: url => url + queryString); + IPage page = null; - void OnConsoleMessage(IConsoleMessage msg) - { - if (EnvironmentVariables.ShowBuildOutput) - Console.WriteLine($"[{msg.Type}] {msg.Text}"); + string queryString = "?test=" + options.TestScenario; + if (options.BrowserQueryString != null) + queryString += "&" + string.Join("&", options.BrowserQueryString.Select(kvp => $"{kvp.Key}={kvp.Value}")); - _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); + page = await runner.RunAsync(runCommand, runArgs, onConsoleMessage: OnConsoleMessage, modifyBrowserUrl: url => url + queryString); - const string testOutputPrefix = "TestOutput -> "; - if (msg.Text.StartsWith(testOutputPrefix)) - testOutput.Add(msg.Text.Substring(testOutputPrefix.Length)); + void OnConsoleMessage(IConsoleMessage msg) + { + if (EnvironmentVariables.ShowBuildOutput) + Console.WriteLine($"[{msg.Type}] {msg.Text}"); - if (exitRegex.Match(msg.Text).Success) - tcs.SetResult(true); + _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); - if (msg.Text.StartsWith("Error: Missing test scenario")) - throw new Exception(msg.Text); + const string testOutputPrefix = "TestOutput -> "; + if (msg.Text.StartsWith(testOutputPrefix)) + testOutput.Add(msg.Text.Substring(testOutputPrefix.Length)); - if (options.OnConsoleMessage != null) - options.OnConsoleMessage(msg, page); - } + if (exitRegex.Match(msg.Text).Success) + tcs.SetResult(true); - TimeSpan timeout = TimeSpan.FromMinutes(2); - await Task.WhenAny(tcs.Task, Task.Delay(timeout)); - if (!tcs.Task.IsCompleted || !tcs.Task.Result) - throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); + if (msg.Text.StartsWith("Error: Missing test scenario")) + throw new Exception(msg.Text); - return testOutput; + if (options.OnConsoleMessage != null) + options.OnConsoleMessage(msg, page); } - protected record RunOptions( - string Configuration, - string TestScenario, - Dictionary BrowserQueryString = null, - bool ForPublish = false, - Action OnConsoleMessage = null - ); + TimeSpan timeout = TimeSpan.FromMinutes(2); + await Task.WhenAny(tcs.Task, Task.Delay(timeout)); + if (!tcs.Task.IsCompleted || !tcs.Task.Result) + throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); + + return testOutput; } + + protected record RunOptions( + string Configuration, + string TestScenario, + Dictionary BrowserQueryString = null, + bool ForPublish = false, + Action OnConsoleMessage = null + ); } From 81a7474f39862c41320ac157fc5c3fdc0650055f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 09:48:41 +0200 Subject: [PATCH 33/57] Drop export for BootJsonData and ICUDataMode --- src/mono/wasm/runtime/dotnet.d.ts | 83 +++------------------ src/mono/wasm/runtime/types/export-types.ts | 3 +- 2 files changed, 13 insertions(+), 73 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index a09ab149c281e2..3e945fca303344 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -181,7 +181,7 @@ type MonoConfig = { /** * definition of assets to load along with the runtime. */ - resources?: ResourceGroups$1; + resources?: ResourceGroups; /** * config extensions declared in MSBuild items @(WasmBootConfigExtension) */ @@ -190,24 +190,24 @@ type MonoConfig = { }; }; type ResourceExtensions = { - [extensionName: string]: ResourceList$1; + [extensionName: string]: ResourceList; }; -interface ResourceGroups$1 { +interface ResourceGroups { readonly hash?: string; - readonly assembly?: ResourceList$1; - readonly lazyAssembly?: ResourceList$1; - readonly pdb?: ResourceList$1; - readonly runtime?: ResourceList$1; + readonly assembly?: ResourceList; + readonly lazyAssembly?: ResourceList; + readonly pdb?: ResourceList; + readonly runtime?: ResourceList; readonly satelliteResources?: { - [cultureName: string]: ResourceList$1; + [cultureName: string]: ResourceList; }; - readonly libraryInitializers?: ResourceList$1; + readonly libraryInitializers?: ResourceList; readonly extensions?: ResourceExtensions; readonly vfs?: { - [virtualPath: string]: ResourceList$1; + [virtualPath: string]: ResourceList; }; } -type ResourceList$1 = { +type ResourceList = { [name: string]: string; }; /** @@ -345,65 +345,6 @@ type ModuleAPI = { type CreateDotnetRuntimeType = (moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)) => Promise; type WebAssemblyBootResourceType = "assembly" | "pdb" | "dotnetjs" | "dotnetwasm" | "globalization" | "manifest" | "configuration"; -interface BootJsonData { - readonly entryAssembly: string; - readonly resources: ResourceGroups; - /** Gets a value that determines if this boot config was produced from a non-published build (i.e. dotnet build or dotnet run) */ - readonly debugBuild: boolean; - readonly debugLevel: number; - readonly linkerEnabled: boolean; - readonly cacheBootResources: boolean; - readonly config: string[]; - readonly icuDataMode: ICUDataMode; - readonly startupMemoryCache: boolean | undefined; - readonly runtimeOptions: string[] | undefined; - readonly environmentVariables?: { - [name: string]: string; - }; - readonly diagnosticTracing?: boolean; - readonly pthreadPoolSize: number; - modifiableAssemblies: string | null; - aspnetCoreBrowserTools: string | null; - readonly extensions?: { - [name: string]: any; - }; -} -type BootJsonDataExtension = { - [extensionName: string]: ResourceList; -}; -interface ResourceGroups { - readonly hash?: string; - readonly assembly: ResourceList; - readonly lazyAssembly: ResourceList; - readonly pdb?: ResourceList; - readonly runtime: ResourceList; - readonly satelliteResources?: { - [cultureName: string]: ResourceList; - }; - readonly libraryInitializers?: ResourceList; - readonly extensions?: BootJsonDataExtension; - readonly runtimeAssets: ExtendedResourceList; - readonly vfs?: { - [virtualPath: string]: ResourceList; - }; -} -type ResourceList = { - [name: string]: string; -}; -type ExtendedResourceList = { - [name: string]: { - hash: string; - behavior: string; - }; -}; -declare enum ICUDataMode { - Sharded = 0, - All = 1, - Invariant = 2, - Custom = 3, - Hybrid = 4 -} - interface IDisposable { dispose(): void; get isDisposed(): boolean; @@ -438,4 +379,4 @@ declare global { } declare const createDotnetRuntime: CreateDotnetRuntimeType; -export { AssetEntry, BootJsonData, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, ICUDataMode, IMemoryView, ModuleAPI, MonoConfig, ResourceRequest, RuntimeAPI, createDotnetRuntime as default, dotnet, exit }; +export { AssetEntry, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, IMemoryView, ModuleAPI, MonoConfig, ResourceRequest, RuntimeAPI, createDotnetRuntime as default, dotnet, exit }; diff --git a/src/mono/wasm/runtime/types/export-types.ts b/src/mono/wasm/runtime/types/export-types.ts index 379b817aa95852..c76fd3468575d8 100644 --- a/src/mono/wasm/runtime/types/export-types.ts +++ b/src/mono/wasm/runtime/types/export-types.ts @@ -5,7 +5,6 @@ import type { IMemoryView } from "../marshal"; import type { CreateDotnetRuntimeType, DotnetModuleConfig, RuntimeAPI, MonoConfig, ModuleAPI, AssetEntry, ResourceRequest } from "."; import type { EmscriptenModule } from "./emscripten"; import type { dotnet, exit } from "../loader/index"; -import type { BootJsonData, ICUDataMode } from "./blazor"; // ----------------------------------------------------------- // this files has all public exports from the dotnet.js module @@ -22,6 +21,6 @@ export default createDotnetRuntime; export { EmscriptenModule, - RuntimeAPI, ModuleAPI, DotnetModuleConfig, CreateDotnetRuntimeType, MonoConfig, IMemoryView, AssetEntry, ResourceRequest, BootJsonData, ICUDataMode, + RuntimeAPI, ModuleAPI, DotnetModuleConfig, CreateDotnetRuntimeType, MonoConfig, IMemoryView, AssetEntry, ResourceRequest, dotnet, exit }; From 5f8b211e6c561c39c425d0da27aabaeeb3eb84f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 10:22:05 +0200 Subject: [PATCH 34/57] Update comment --- src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs index f46c707c1fcec0..0d68b1216816c3 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/LazyLoadingTest.cs @@ -10,7 +10,7 @@ public partial class LazyLoadingTest [JSExport] public static void Run() { - // System.Text.Json is marked as lazy loaded in the csproj, this method can be called only after the assembly is lazy loaded + // System.Text.Json is marked as lazy loaded in the csproj ("BlazorWebAssemblyLazyLoad"), this method can be called only after the assembly is lazy loaded // In the test case it is done in the JS before call to this method var text = JsonSerializer.Serialize(new Person("John", "Doe")); TestOutput.WriteLine(text); From 6a40be512430e2667b0f61c35dc77a4383e67be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 10:26:51 +0200 Subject: [PATCH 35/57] Remove extra text from test --- .../Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs | 1 - .../wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs index 378ae54f2dd838..facbb05564bccf 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs @@ -32,7 +32,6 @@ public async Task LoadLibraryInitializer() var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LibraryInitializerTest")); Assert.Collection( testOutput, - m => Assert.Equal("Run from LibraryInitializer", m), m => Assert.Equal("LIBRARY_INITIALIZER_TEST = 1", m) ); } diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs index 489ec459a858b6..9f670a99068fa4 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs @@ -12,7 +12,6 @@ public partial class LibraryInitializerTest [JSExport] public static void Run() { - TestOutput.WriteLine("Run from LibraryInitializer"); TestOutput.WriteLine($"LIBRARY_INITIALIZER_TEST = {Environment.GetEnvironmentVariable("LIBRARY_INITIALIZER_TEST")}"); } } \ No newline at end of file From 101af8ecaf6271b013814c63454e319aaad506a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 10:30:23 +0200 Subject: [PATCH 36/57] Fix no-prototype-builtins --- src/mono/wasm/runtime/lazyLoading.ts | 6 ++---- src/mono/wasm/runtime/satelliteAssemblies.ts | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/mono/wasm/runtime/lazyLoading.ts b/src/mono/wasm/runtime/lazyLoading.ts index 1ee4aadba2faf3..8402e7f37e2217 100644 --- a/src/mono/wasm/runtime/lazyLoading.ts +++ b/src/mono/wasm/runtime/lazyLoading.ts @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -/* eslint-disable no-prototype-builtins */ - import { INTERNAL, loaderHelpers, runtimeHelpers } from "./globals"; import type { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; @@ -14,7 +12,7 @@ export async function loadLazyAssembly(assemblyNameToLoad: string): Promise response.arrayBuffer()); diff --git a/src/mono/wasm/runtime/satelliteAssemblies.ts b/src/mono/wasm/runtime/satelliteAssemblies.ts index 2c03a3021a63a9..612d757efe8591 100644 --- a/src/mono/wasm/runtime/satelliteAssemblies.ts +++ b/src/mono/wasm/runtime/satelliteAssemblies.ts @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -/* eslint-disable no-prototype-builtins */ - import { INTERNAL, loaderHelpers, runtimeHelpers } from "./globals"; import type { WebAssemblyResourceLoader } from "./loader/blazor/WebAssemblyResourceLoader"; import { LoadingResource } from "./types"; @@ -15,7 +13,7 @@ export async function loadSatelliteAssemblies(culturesToLoad: string[]): Promise } await Promise.all(culturesToLoad! - .filter(culture => satelliteResources.hasOwnProperty(culture)) + .filter(culture => Object.prototype.hasOwnProperty.call(satelliteResources, culture)) .map(culture => resourceLoader.loadResources(satelliteResources[culture], fileName => loaderHelpers.locateFile(fileName), "assembly")) .reduce((previous, next) => previous.concat(next), new Array()) .map(async resource => { From bbfea4792d04e14dbde5b012a718e2019afbc2d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 10:33:29 +0200 Subject: [PATCH 37/57] Comment with marshaled signature --- src/mono/wasm/runtime/types/internal.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index ceb48dc1b07148..f8ecc7cc9cd499 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -323,6 +323,7 @@ export interface JavaScriptExports { // the marshaled signature is: string GetManagedStackTrace(GCHandle exception) get_managed_stack_trace(exception_gc_handle: GCHandle): string | null + // the marshaled signature is: void LoadSatelliteAssembly(byte[] dll) load_satellite_assembly(dll: Uint8Array): void; // the marshaled signature is: void LoadLazyAssembly(byte[] dll, byte[] pdb) From d6939a9be9ba76ecb5917689e6bf55c41d5ac598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 12:19:46 +0200 Subject: [PATCH 38/57] Failed lazy load test. Fix double set result on error --- .../TestAppScenarios/AppSettingsTests.cs | 9 ++++-- .../TestAppScenarios/AppTestBase.cs | 28 ++++++++++++++----- .../TestAppScenarios/LazyLoadingTests.cs | 22 +++++++++++++-- .../LibraryInitializerTests.cs | 4 +-- .../TestAppScenarios/SatelliteLoadingTests.cs | 4 +-- .../WasmBasicTestApp/wwwroot/main.js | 4 ++- 6 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs index 6ef9761760266f..965ae20558ec3e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppSettingsTests.cs @@ -28,9 +28,14 @@ public async Task LoadAppSettingsBasedOnApplicationEnvironment(string applicatio CopyTestAsset("WasmBasicTestApp", "AppSettingsTests"); PublishProject("Debug"); - var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "AppSettingsTest", BrowserQueryString: new Dictionary { ["applicationEnvironment"] = applicationEnvironment })); + var result = await RunSdkStyleApp(new( + Configuration: "Debug", + ForPublish: true, + TestScenario: "AppSettingsTest", + BrowserQueryString: new Dictionary { ["applicationEnvironment"] = applicationEnvironment } + )); Assert.Collection( - testOutput, + result.TestOutput, m => Assert.Equal(GetFileExistenceMessage("/appsettings.json", true), m), m => Assert.Equal(GetFileExistenceMessage("/appsettings.Development.json", applicationEnvironment == "Development"), m), m => Assert.Equal(GetFileExistenceMessage("/appsettings.Production.json", applicationEnvironment == "Production"), m) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs index 3eef4225327058..2e0907344000c5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/AppTestBase.cs @@ -57,7 +57,7 @@ protected string GetBinLogFilePath(string suffix = null) .WithWorkingDirectory(_projectDir!) .WithEnvironmentVariable("NUGET_PACKAGES", _nugetPackagesDir); - protected async Task> RunSdkStyleApp(RunOptions options) + protected async Task RunSdkStyleApp(RunOptions options) { string runArgs = $"{s_xharnessRunnerCommand} wasm webserver --app=. --web-server-use-default-files"; string workingDirectory = Path.GetFullPath(Path.Combine(FindBlazorBinFrameworkDir(options.Configuration, forPublish: options.ForPublish), "..")); @@ -65,9 +65,10 @@ protected async Task> RunSdkStyleApp(RunOptions opti using var runCommand = new RunCommand(s_buildEnv, _testOutput) .WithWorkingDirectory(workingDirectory); - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); List testOutput = new(); + List consoleOutput = new(); Regex exitRegex = new Regex("WASM EXIT (?[0-9]+)$"); await using var runner = new BrowserRunner(_testOutput); @@ -86,13 +87,15 @@ void OnConsoleMessage(IConsoleMessage msg) Console.WriteLine($"[{msg.Type}] {msg.Text}"); _testOutput.WriteLine($"[{msg.Type}] {msg.Text}"); + consoleOutput.Add(msg.Text); const string testOutputPrefix = "TestOutput -> "; if (msg.Text.StartsWith(testOutputPrefix)) testOutput.Add(msg.Text.Substring(testOutputPrefix.Length)); - if (exitRegex.Match(msg.Text).Success) - tcs.SetResult(true); + var exitMatch = exitRegex.Match(msg.Text); + if (exitMatch.Success) + tcs.TrySetResult(int.Parse(exitMatch.Groups["exitCode"].Value)); if (msg.Text.StartsWith("Error: Missing test scenario")) throw new Exception(msg.Text); @@ -103,10 +106,14 @@ void OnConsoleMessage(IConsoleMessage msg) TimeSpan timeout = TimeSpan.FromMinutes(2); await Task.WhenAny(tcs.Task, Task.Delay(timeout)); - if (!tcs.Task.IsCompleted || !tcs.Task.Result) + if (!tcs.Task.IsCompleted) throw new Exception($"Timed out after {timeout.TotalSeconds}s waiting for process to exit"); - return testOutput; + int wasmExitCode = tcs.Task.Result; + if (options.ExpectedExitCode != null && wasmExitCode != options.ExpectedExitCode) + throw new Exception($"Expected exit code {options.ExpectedExitCode} but got {wasmExitCode}"); + + return new(wasmExitCode, testOutput, consoleOutput); } protected record RunOptions( @@ -114,6 +121,13 @@ protected record RunOptions( string TestScenario, Dictionary BrowserQueryString = null, bool ForPublish = false, - Action OnConsoleMessage = null + Action OnConsoleMessage = null, + int? ExpectedExitCode = 0 + ); + + protected record RunResult( + int ExitCode, + IReadOnlyCollection TestOutput, + IReadOnlyCollection ConsoleOutput ); } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs index 35daccb408105d..022f700775ba9d 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LazyLoadingTests.cs @@ -21,12 +21,28 @@ public LazyLoadingTests(ITestOutputHelper output, SharedBuildPerTestClassFixture } [Fact] - public async Task LazyLoadAssembly() + public async Task LoadLazyAssemblyBeforeItIsNeeded() { CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests"); PublishProject("Debug"); - var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LazyLoadingTest")); - Assert.True(testOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON"); + var result = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LazyLoadingTest")); + Assert.True(result.TestOutput.Any(m => m.Contains("FirstName")), "The lazy loading test didn't emit expected message with JSON"); + } + + [Fact] + public async Task FailOnMissingLazyAssembly() + { + CopyTestAsset("WasmBasicTestApp", "LazyLoadingTests"); + PublishProject("Debug"); + + var result = await RunSdkStyleApp(new( + Configuration: "Debug", + ForPublish: true, + TestScenario: "LazyLoadingTest", + BrowserQueryString: new Dictionary { ["loadRequiredAssembly"] = "false" }, + ExpectedExitCode: 1 + )); + Assert.True(result.ConsoleOutput.Any(m => m.Contains("Could not load file or assembly") && m.Contains("System.Text.Json")), "The lazy loading test didn't emit expected error message"); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs index facbb05564bccf..6031f76b3f58ec 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs @@ -29,9 +29,9 @@ public async Task LoadLibraryInitializer() CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests"); PublishProject("Debug"); - var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LibraryInitializerTest")); + var result = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LibraryInitializerTest")); Assert.Collection( - testOutput, + result.TestOutput, m => Assert.Equal("LIBRARY_INITIALIZER_TEST = 1", m) ); } diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs index 046b1154428f8d..22b41ea798dbec 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/SatelliteLoadingTests.cs @@ -29,9 +29,9 @@ public async Task LoadSatelliteAssembly() CopyTestAsset("WasmBasicTestApp", "SatelliteLoadingTests"); PublishProject("Debug"); - var testOutput = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "SatelliteAssembliesTest")); + var result = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "SatelliteAssembliesTest")); Assert.Collection( - testOutput, + result.TestOutput, m => Assert.Equal("default: hello", m), m => Assert.Equal("es-ES without satellite: hello", m), m => Assert.Equal("default: hello", m), diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js index 0c844ad054e715..127d506e3c9a6d 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/main.js @@ -35,7 +35,9 @@ try { exit(0); break; case "LazyLoadingTest": - await INTERNAL.loadLazyAssembly("System.Text.Json.wasm"); + if (params.get("loadRequiredAssembly") !== "false") { + await INTERNAL.loadLazyAssembly("System.Text.Json.wasm"); + } exports.LazyLoadingTest.Run(); exit(0); break; From a33e227218a3379c05a30ed030bb8fc7c756d0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 7 Jul 2023 15:05:40 +0200 Subject: [PATCH 39/57] Catch and log errors from library intializers --- .../LibraryInitializerTests.cs | 15 +++++++++++ .../runtime/loader/libraryInitializers.ts | 27 ++++++++++++++----- .../wwwroot/WasmBasicTestApp.lib.module.js | 7 ++++- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs index 6031f76b3f58ec..d4efc73318b98e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs @@ -35,4 +35,19 @@ public async Task LoadLibraryInitializer() m => Assert.Equal("LIBRARY_INITIALIZER_TEST = 1", m) ); } + + [Fact] + public async Task LogAndSwallowError() + { + CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests"); + PublishProject("Debug"); + + var result = await RunSdkStyleApp(new( + Configuration: "Debug", + ForPublish: true, + TestScenario: "LibraryInitializerTest", + BrowserQueryString: new Dictionary { ["throwError"] = "true" } + )); + Assert.True(result.ConsoleOutput.Any(m => m.Contains("MONO_WASM: Failed to invoke 'onRuntimeConfigLoaded' on library initializer: Error: Error thrown from library initializer")), "The library initializer test didn't emit expected error message"); + } } diff --git a/src/mono/wasm/runtime/loader/libraryInitializers.ts b/src/mono/wasm/runtime/loader/libraryInitializers.ts index 0b703e0a87214c..7bbcc2b3060057 100644 --- a/src/mono/wasm/runtime/loader/libraryInitializers.ts +++ b/src/mono/wasm/runtime/loader/libraryInitializers.ts @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { mono_log_warn } from "./logging"; import { MonoConfig, RuntimeAPI } from "../types"; import { appendUniqueQuery, toAbsoluteBaseUri } from "./assets"; import { normalizeConfig } from "./config"; @@ -19,10 +20,14 @@ export async function fetchInitializers(moduleConfig: MonoConfig): Promise await Promise.all(initializerFiles.map(f => importInitializer(f))); async function importInitializer(path: string): Promise { - const adjustedPath = appendUniqueQuery(toAbsoluteBaseUri(path)); - const initializer = await import(/* webpackIgnore: true */ adjustedPath); + try { + const adjustedPath = appendUniqueQuery(toAbsoluteBaseUri(path)); + const initializer = await import(/* webpackIgnore: true */ adjustedPath); - moduleConfig.libraryInitializers!.push(initializer); + moduleConfig.libraryInitializers!.push(initializer); + } catch (error) { + mono_log_warn(`Failed to import library initializer '${path}': ${error}`); + } } } @@ -32,8 +37,8 @@ export async function invokeOnRuntimeConfigLoaded(config: MonoConfig) { const promises = []; for (let i = 0; i < config.libraryInitializers.length; i++) { const initializer = config.libraryInitializers[i] as { onRuntimeConfigLoaded: (config: MonoConfig) => Promise }; - if (initializer?.onRuntimeConfigLoaded) { - promises.push(initializer?.onRuntimeConfigLoaded(config)); + if (initializer.onRuntimeConfigLoaded) { + promises.push(logAndSwallowError("onRuntimeConfigLoaded", () => initializer.onRuntimeConfigLoaded(config))); } } @@ -49,10 +54,18 @@ export async function invokeOnRuntimeReady(api: RuntimeAPI) { const promises = []; for (let i = 0; i < config.libraryInitializers.length; i++) { const initializer = config.libraryInitializers[i] as { onRuntimeReady: (api: RuntimeAPI) => Promise }; - if (initializer?.onRuntimeReady) { - promises.push(initializer?.onRuntimeReady(api)); + if (initializer.onRuntimeReady) { + promises.push(logAndSwallowError("onRuntimeReady", () => initializer.onRuntimeReady(api))); } } await Promise.all(promises); +} + +async function logAndSwallowError(methodName: string, callback: () => Promise | undefined): Promise { + try { + await callback(); + } catch (error) { + mono_log_warn(`Failed to invoke '${methodName}' on library initializer: ${error}`); + } } \ No newline at end of file diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js index 64c19049c959d2..200f17f6abaebf 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/wwwroot/WasmBasicTestApp.lib.module.js @@ -1,12 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +const params = new URLSearchParams(location.search); + export function onRuntimeConfigLoaded(config) { config.environmentVariables["LIBRARY_INITIALIZER_TEST"] = "1"; + + if (params.get("throwError") === "true") { + throw new Error("Error thrown from library initializer"); + } } export async function onRuntimeReady({ getAssemblyExports, getConfig }) { - const params = new URLSearchParams(location.search); const testCase = params.get("test"); if (testCase == "LibraryInitializerTest") { const config = getConfig(); From e67550f039d65d95b5780f0a78fec9e1c74eae57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Mon, 10 Jul 2023 15:06:24 +0200 Subject: [PATCH 40/57] Split library initializers by required download place --- ...rosoft.NET.Sdk.WebAssembly.Browser.targets | 6 ++- src/mono/wasm/runtime/dotnet.d.ts | 7 +++- src/mono/wasm/runtime/loader/assets.ts | 8 ---- src/mono/wasm/runtime/loader/config.ts | 3 +- .../runtime/loader/libraryInitializers.ts | 31 +++++++++++---- src/mono/wasm/runtime/loader/run.ts | 1 + src/mono/wasm/runtime/types/index.ts | 6 ++- src/mono/wasm/sln/WasmBuild.sln | 14 +++++-- .../BootJsonData.cs | 6 ++- .../GenerateWasmBootJson.cs | 39 ++++++++++++++++++- 10 files changed, 91 insertions(+), 30 deletions(-) diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index c488923660fb62..260028b24dc1c1 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -360,7 +360,8 @@ Copyright (c) .NET Foundation. All rights reserved. StartupMemoryCache="$(_BlazorWebAssemblyStartupMemoryCache)" Jiterpreter="$(_BlazorWebAssemblyJiterpreter)" RuntimeOptions="$(_BlazorWebAssemblyRuntimeOptions)" - Extensions="@(WasmBootConfigExtension)" /> + Extensions="@(WasmBootConfigExtension)" + TargetingNET80OrLater="$(_TargetingNET80OrLater)" /> @@ -548,7 +549,8 @@ Copyright (c) .NET Foundation. All rights reserved. StartupMemoryCache="$(_BlazorWebAssemblyStartupMemoryCache)" Jiterpreter="$(_BlazorWebAssemblyJiterpreter)" RuntimeOptions="$(_BlazorWebAssemblyRuntimeOptions)" - Extensions="@(WasmBootConfigExtension)" /> + Extensions="@(WasmBootConfigExtension)" + TargetingNET80OrLater="$(_TargetingNET80OrLater)" /> diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 66c0e0678243c5..e5f5b68ce60f68 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -197,7 +197,10 @@ interface ResourceGroups { readonly satelliteResources?: { [cultureName: string]: ResourceList; }; - readonly libraryInitializers?: ResourceList; + readonly libraryInitializers?: { + readonly onRuntimeConfigLoaded: ResourceList; + readonly onRuntimeReady: ResourceList; + }; readonly extensions?: ResourceExtensions; readonly vfs?: { [virtualPath: string]: ResourceList; @@ -255,7 +258,7 @@ interface AssetEntry extends ResourceRequest { */ pendingDownload?: LoadingResource; } -type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads" | "js-module-runtime" | "js-module-dotnet" | "js-module-native" | "symbols"; +type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads" | "js-module-runtime" | "js-module-dotnet" | "js-module-native" | "js-module-library-initializer" | "symbols"; declare const enum GlobalizationMode { Sharded = "sharded", All = "all", diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index d11a6fbbb79619..ffdce4cb6c0402 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -424,14 +424,6 @@ export function cleanupAsset(asset: AssetEntryInternal) { asset.buffer = null as any; // GC } -export function appendUniqueQuery(attemptUrl: string): string { - if (loaderHelpers.assetUniqueQuery) { - attemptUrl = attemptUrl + loaderHelpers.assetUniqueQuery; - } - - return attemptUrl; -} - export function toAbsoluteBaseUri(path: string) { if ("URL" in globalThis && document && document.baseURI) { return new URL(path, document.baseURI).toString(); diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index e2e4f943ccd01f..6174336f7e5269 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -9,7 +9,7 @@ import { initializeBootConfig, loadBootConfig } from "./blazor/_Integration"; import { BootConfigResult } from "./blazor/BootConfig"; import { BootJsonData } from "../types/blazor"; import { mono_log_error, mono_log_debug } from "./logging"; -import { fetchInitializers, invokeOnRuntimeConfigLoaded } from "./libraryInitializers"; +import { invokeOnRuntimeConfigLoaded } from "./libraryInitializers"; export function deep_merge_config(target: MonoConfigInternal, source: MonoConfigInternal): MonoConfigInternal { const providedConfig: MonoConfigInternal = { ...source }; @@ -101,7 +101,6 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi normalizeConfig(); - await fetchInitializers(loaderHelpers.config); await invokeOnRuntimeConfigLoaded(loaderHelpers.config); if (module.onConfigLoaded) { diff --git a/src/mono/wasm/runtime/loader/libraryInitializers.ts b/src/mono/wasm/runtime/loader/libraryInitializers.ts index 7bbcc2b3060057..6c1c2e02ce0f81 100644 --- a/src/mono/wasm/runtime/loader/libraryInitializers.ts +++ b/src/mono/wasm/runtime/loader/libraryInitializers.ts @@ -6,12 +6,19 @@ import { MonoConfig, RuntimeAPI } from "../types"; import { appendUniqueQuery, toAbsoluteBaseUri } from "./assets"; import { normalizeConfig } from "./config"; -export async function fetchInitializers(moduleConfig: MonoConfig): Promise { - if (!moduleConfig.libraryInitializers) { - moduleConfig.libraryInitializers = []; +export type LibraryInitializerTypes = + "onRuntimeConfigLoaded" + | "onRuntimeReady"; + +async function fetchInitializers(config: MonoConfig, type: LibraryInitializerTypes): Promise { + if (!config.libraryInitializers) { + config.libraryInitializers = []; } - const libraryInitializers = moduleConfig.resources?.libraryInitializers; + const libraryInitializers = type == "onRuntimeConfigLoaded" + ? config.resources?.libraryInitializers?.onRuntimeConfigLoaded + : config.resources?.libraryInitializers?.onRuntimeReady; + if (!libraryInitializers) { return; } @@ -21,10 +28,10 @@ export async function fetchInitializers(moduleConfig: MonoConfig): Promise async function importInitializer(path: string): Promise { try { - const adjustedPath = appendUniqueQuery(toAbsoluteBaseUri(path)); + const adjustedPath = appendUniqueQuery(toAbsoluteBaseUri(path), "js-module-library-initializer"); const initializer = await import(/* webpackIgnore: true */ adjustedPath); - moduleConfig.libraryInitializers!.push(initializer); + config.libraryInitializers!.push(initializer); } catch (error) { mono_log_warn(`Failed to import library initializer '${path}': ${error}`); } @@ -32,7 +39,11 @@ export async function fetchInitializers(moduleConfig: MonoConfig): Promise } export async function invokeOnRuntimeConfigLoaded(config: MonoConfig) { - mono_assert(config.libraryInitializers, "Initialization hasn't been done yet"); + await fetchInitializers(config, "onRuntimeConfigLoaded"); + + if (!config.libraryInitializers) { + return; + } const promises = []; for (let i = 0; i < config.libraryInitializers.length; i++) { @@ -49,7 +60,11 @@ export async function invokeOnRuntimeConfigLoaded(config: MonoConfig) { export async function invokeOnRuntimeReady(api: RuntimeAPI) { const config = api.getConfig(); - mono_assert(config.libraryInitializers, "Initialization hasn't been done yet"); + await fetchInitializers(config, "onRuntimeReady"); + + if (!config.libraryInitializers) { + return; + } const promises = []; for (let i = 0; i < config.libraryInitializers.length; i++) { diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 92e96f3427f307..ce2c6c2f91ce40 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -452,6 +452,7 @@ async function createEmscriptenMain(): Promise { initializeModules(es6Modules as any); await runtimeHelpers.dotnetReady.promise; + await invokeOnRuntimeReady(globalObjectsRoot.api); return exportedRuntimeAPI; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index f72104e6fc4397..17a250ef5c00f6 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -132,7 +132,10 @@ export interface ResourceGroups { readonly pdb?: ResourceList; readonly runtime?: ResourceList; // nullable only temporarily readonly satelliteResources?: { [cultureName: string]: ResourceList }; - readonly libraryInitializers?: ResourceList, + readonly libraryInitializers?: { + readonly onRuntimeConfigLoaded: ResourceList, + readonly onRuntimeReady: ResourceList, + }, readonly extensions?: ResourceExtensions readonly vfs?: { [virtualPath: string]: ResourceList }; } @@ -205,6 +208,7 @@ export type AssetBehaviours = | "js-module-runtime" // the javascript module for threads | "js-module-dotnet" // the javascript module for threads | "js-module-native" // the javascript module for threads + | "js-module-library-initializer" // the javascript module that came from nuget package | "symbols" // the javascript module for threads export const enum GlobalizationMode { diff --git a/src/mono/wasm/sln/WasmBuild.sln b/src/mono/wasm/sln/WasmBuild.sln index 63ad36cf1624f1..b2ce61ddc35137 100755 --- a/src/mono/wasm/sln/WasmBuild.sln +++ b/src/mono/wasm/sln/WasmBuild.sln @@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WasmSymbolicator", "..\symb EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplyUpdateReferencedAssembly", "..\debugger\tests\ApplyUpdateReferencedAssembly\ApplyUpdateReferencedAssembly.csproj", "{75477B6F-DC8E-4002-88B8-017C992C568E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.WebAssembly.Pack.Tasks", "..\..\..\tasks\Microsoft.NET.Sdk.WebAssembly.Pack.Tasks\Microsoft.NET.Sdk.WebAssembly.Pack.Tasks.csproj", "{5EEC2925-2021-4830-B7E9-72BB8B2C283D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,10 +59,6 @@ Global {F5AE2AF5-3C30-45E3-A0C6-D962C51FE5E7}.Debug|Any CPU.Build.0 = Debug|Any CPU {F5AE2AF5-3C30-45E3-A0C6-D962C51FE5E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5AE2AF5-3C30-45E3-A0C6-D962C51FE5E7}.Release|Any CPU.Build.0 = Release|Any CPU - {75477B6F-DC8E-4002-88B8-017C992C568E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {75477B6F-DC8E-4002-88B8-017C992C568E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {75477B6F-DC8E-4002-88B8-017C992C568E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {75477B6F-DC8E-4002-88B8-017C992C568E}.Release|Any CPU.Build.0 = Release|Any CPU {C7099764-EC2E-4FAF-9057-0321893DE4F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C7099764-EC2E-4FAF-9057-0321893DE4F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {C7099764-EC2E-4FAF-9057-0321893DE4F8}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -69,6 +67,14 @@ Global {ABC41254-EC2E-4FAF-9057-091ABF4DE4F8}.Debug|Any CPU.Build.0 = Debug|Any CPU {ABC41254-EC2E-4FAF-9057-091ABF4DE4F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {ABC41254-EC2E-4FAF-9057-091ABF4DE4F8}.Release|Any CPU.Build.0 = Release|Any CPU + {75477B6F-DC8E-4002-88B8-017C992C568E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75477B6F-DC8E-4002-88B8-017C992C568E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75477B6F-DC8E-4002-88B8-017C992C568E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75477B6F-DC8E-4002-88B8-017C992C568E}.Release|Any CPU.Build.0 = Release|Any CPU + {5EEC2925-2021-4830-B7E9-72BB8B2C283D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5EEC2925-2021-4830-B7E9-72BB8B2C283D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5EEC2925-2021-4830-B7E9-72BB8B2C283D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5EEC2925-2021-4830-B7E9-72BB8B2C283D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index bf4beb053507fe..c365f2ca6919fe 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -129,9 +129,13 @@ public class ResourcesData /// /// JavaScript module initializers that Blazor will be in charge of loading. + /// + /// For .NET < 8 this contains Dictionary + /// For .NET >= 8 this contains Dictionary> + /// - First key is "" or "" /// [DataMember(EmitDefaultValue = false)] - public ResourceHashesByNameDictionary libraryInitializers { get; set; } + public object libraryInitializers { get; set; } /// /// Extensions created by users customizing the initialization process. The format of the file(s) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 5c3b8c193ed3cb..01af6f92e99deb 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -56,6 +56,9 @@ public class GenerateWasmBootJson : Task public string RuntimeOptions { get; set; } + [Required] + public bool TargetingNET80OrLater { get; set; } + [Required] public string OutputPath { get; set; } @@ -216,8 +219,29 @@ public void WriteBootJson(Stream output, string entryAssemblyName) string.Equals(assetTraitValue, "JSLibraryModule", StringComparison.OrdinalIgnoreCase)) { Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a library initializer resource.", resource.ItemSpec); - resourceData.libraryInitializers ??= new(); - resourceList = resourceData.libraryInitializers; + + if (TargetingNET80OrLater) + { + resourceData.libraryInitializers ??= new Dictionary(); + if (File.Exists(resource.ItemSpec)) + { + string fileContent = File.ReadAllText(resource.ItemSpec); + if (fileContent.Contains("onRuntimeConfigLoaded") || fileContent.Contains("beforeStart")) + resourceList = EnsureLibraryInitializers(resourceData, "onRuntimeConfigLoaded"); + else + resourceList = EnsureLibraryInitializers(resourceData, "onRuntimeReady"); + } + else + { + resourceList = EnsureLibraryInitializers(resourceData, "onRuntimeConfigLoaded"); + } + } + else + { + resourceData.libraryInitializers ??= new ResourceHashesByNameDictionary(); + resourceList = (ResourceHashesByNameDictionary)resourceData.libraryInitializers; + } + var targetPath = resource.GetMetadata("TargetPath"); Debug.Assert(!string.IsNullOrEmpty(targetPath), "Target path for '{0}' must exist.", resource.ItemSpec); AddResourceToList(resource, resourceList, targetPath); @@ -322,6 +346,17 @@ void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resour } } + private static ResourceHashesByNameDictionary EnsureLibraryInitializers(ResourcesData resourceData, string key) + { + ResourceHashesByNameDictionary resourceList; + if (resourceData.libraryInitializers.TryGetValue(key, out var rl)) + resourceList = (ResourceHashesByNameDictionary)rl; + else + resourceData.libraryInitializers[key] = resourceList = new(); + + return resourceList; + } + private static bool? ParseOptionalBool(string value) { if (string.IsNullOrEmpty(value) || !bool.TryParse(value, out var boolValue)) From ce9b2cc78f54ddabc1178a80f551c4b8eac654e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 11 Jul 2023 10:17:22 +0200 Subject: [PATCH 41/57] Fix build --- .../GenerateWasmBootJson.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 01af6f92e99deb..09d976fb4e6a6d 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -348,11 +348,13 @@ void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resour private static ResourceHashesByNameDictionary EnsureLibraryInitializers(ResourcesData resourceData, string key) { + var libraryInitializers = (Dictionary)resourceData.libraryInitializers; + ResourceHashesByNameDictionary resourceList; - if (resourceData.libraryInitializers.TryGetValue(key, out var rl)) + if (libraryInitializers.TryGetValue(key, out var rl)) resourceList = (ResourceHashesByNameDictionary)rl; else - resourceData.libraryInitializers[key] = resourceList = new(); + libraryInitializers[key] = resourceList = new(); return resourceList; } From 2680a37879d9aaa1e8859427082fef9898d4b9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 11 Jul 2023 10:30:55 +0200 Subject: [PATCH 42/57] Missing end lines --- src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs | 2 +- src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs | 2 +- .../wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs | 2 +- .../wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs index de8a3953d1e983..d297fa288a6909 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs @@ -7,4 +7,4 @@ using System.Resources; using System.Runtime.InteropServices.JavaScript; -System.Console.WriteLine("WasmBasicTestApp"); \ No newline at end of file +System.Console.WriteLine("WasmBasicTestApp"); diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs index c7723fd638805e..5755b753b5e492 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Common/TestOutput.cs @@ -15,4 +15,4 @@ public static void WriteLine(object message) Console.Write("TestOutput -> "); Console.WriteLine(message); } -} \ No newline at end of file +} diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs index 9f670a99068fa4..0b9b3f9d5ef8aa 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/LibraryInitializerTest.cs @@ -14,4 +14,4 @@ public static void Run() { TestOutput.WriteLine($"LIBRARY_INITIALIZER_TEST = {Environment.GetEnvironmentVariable("LIBRARY_INITIALIZER_TEST")}"); } -} \ No newline at end of file +} diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs index cc0b28530ea606..b8dc5b0dbf8844 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/SatelliteAssembliesTest.cs @@ -25,4 +25,4 @@ public static async Task Run() [JSImport("INTERNAL.loadSatelliteAssemblies")] public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad); -} \ No newline at end of file +} From 0c3db1c10fe2361fc623e53bf5f5c7f446189390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 11 Jul 2023 13:19:49 +0200 Subject: [PATCH 43/57] Fix boot json generator for new library initializers schema --- .../BootJsonData.cs | 13 +++++++-- .../GenerateWasmBootJson.cs | 29 +++++++------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index c365f2ca6919fe..081300fa06f995 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -130,8 +130,8 @@ public class ResourcesData /// /// JavaScript module initializers that Blazor will be in charge of loading. /// - /// For .NET < 8 this contains Dictionary - /// For .NET >= 8 this contains Dictionary> + /// For .NET < 8 it's + /// For .NET >= 8 it's /// - First key is "" or "" /// [DataMember(EmitDefaultValue = false)] @@ -155,7 +155,16 @@ public class ResourcesData [DataMember(EmitDefaultValue = false)] public List remoteSources { get; set; } +} + +[DataContract] +public class TypedLibraryInitializers +{ + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary onRuntimeConfigLoaded { get; set; } + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary onRuntimeReady { get; set; } } public enum ICUDataMode : int diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 09d976fb4e6a6d..9a6d0d5a210525 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -216,24 +216,26 @@ public void WriteBootJson(Stream output, string entryAssemblyName) resourceList = resourceData.runtime; } else if (string.Equals("JSModule", assetTraitName, StringComparison.OrdinalIgnoreCase) && - string.Equals(assetTraitValue, "JSLibraryModule", StringComparison.OrdinalIgnoreCase)) + string.Equals(assetTraitValue, "JSLibraryModule", StringComparison.OrdinalIgnoreCase)) { Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a library initializer resource.", resource.ItemSpec); if (TargetingNET80OrLater) { - resourceData.libraryInitializers ??= new Dictionary(); + resourceData.libraryInitializers ??= new TypedLibraryInitializers(); + TypedLibraryInitializers libraryInitializers = (TypedLibraryInitializers)resourceData.libraryInitializers; + if (File.Exists(resource.ItemSpec)) { string fileContent = File.ReadAllText(resource.ItemSpec); if (fileContent.Contains("onRuntimeConfigLoaded") || fileContent.Contains("beforeStart")) - resourceList = EnsureLibraryInitializers(resourceData, "onRuntimeConfigLoaded"); + resourceList = libraryInitializers.onRuntimeConfigLoaded ??= new(); else - resourceList = EnsureLibraryInitializers(resourceData, "onRuntimeReady"); + resourceList = libraryInitializers.onRuntimeReady ??= new(); } else { - resourceList = EnsureLibraryInitializers(resourceData, "onRuntimeConfigLoaded"); + resourceList = libraryInitializers.onRuntimeConfigLoaded ??= new(); } } else @@ -330,7 +332,9 @@ public void WriteBootJson(Stream output, string entryAssemblyName) var serializer = new DataContractJsonSerializer(typeof(BootJsonData), new DataContractJsonSerializerSettings { - UseSimpleDictionaryFormat = true + UseSimpleDictionaryFormat = true, + KnownTypes = new[] { typeof(TypedLibraryInitializers) }, + EmitTypeInformation = EmitTypeInformation.Never }); using var writer = JsonReaderWriterFactory.CreateJsonWriter(output, Encoding.UTF8, ownsStream: false, indent: true); @@ -346,19 +350,6 @@ void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resour } } - private static ResourceHashesByNameDictionary EnsureLibraryInitializers(ResourcesData resourceData, string key) - { - var libraryInitializers = (Dictionary)resourceData.libraryInitializers; - - ResourceHashesByNameDictionary resourceList; - if (libraryInitializers.TryGetValue(key, out var rl)) - resourceList = (ResourceHashesByNameDictionary)rl; - else - libraryInitializers[key] = resourceList = new(); - - return resourceList; - } - private static bool? ParseOptionalBool(string value) { if (string.IsNullOrEmpty(value) || !bool.TryParse(value, out var boolValue)) From 77ea58eb81b588720e9af284534dc82f9fd93445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 11 Jul 2023 13:43:12 +0200 Subject: [PATCH 44/57] Make way to override content check for library initializer load place --- ...rosoft.NET.Sdk.WebAssembly.Browser.targets | 8 ++++++-- .../GenerateWasmBootJson.cs | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index 260028b24dc1c1..c1947d8f3f534a 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -361,7 +361,9 @@ Copyright (c) .NET Foundation. All rights reserved. Jiterpreter="$(_BlazorWebAssemblyJiterpreter)" RuntimeOptions="$(_BlazorWebAssemblyRuntimeOptions)" Extensions="@(WasmBootConfigExtension)" - TargetingNET80OrLater="$(_TargetingNET80OrLater)" /> + TargetingNET80OrLater="$(_TargetingNET80OrLater)" + LibraryInitializerOnRuntimeConfigLoaded="@(WasmLibraryInitializerOnRuntimeConfigLoaded)" + LibraryInitializerOnRuntimeReady="@(WasmLibraryInitializerOnRuntimeReady)" /> @@ -550,7 +552,9 @@ Copyright (c) .NET Foundation. All rights reserved. Jiterpreter="$(_BlazorWebAssemblyJiterpreter)" RuntimeOptions="$(_BlazorWebAssemblyRuntimeOptions)" Extensions="@(WasmBootConfigExtension)" - TargetingNET80OrLater="$(_TargetingNET80OrLater)" /> + TargetingNET80OrLater="$(_TargetingNET80OrLater)" + LibraryInitializerOnRuntimeConfigLoaded="@(WasmLibraryInitializerOnRuntimeConfigLoaded)" + LibraryInitializerOnRuntimeReady="@(WasmLibraryInitializerOnRuntimeReady)" /> diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 9a6d0d5a210525..a0d5b8df62e8a4 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -59,6 +59,10 @@ public class GenerateWasmBootJson : Task [Required] public bool TargetingNET80OrLater { get; set; } + public ITaskItem[] LibraryInitializerOnRuntimeConfigLoaded { get; set; } + + public ITaskItem[] LibraryInitializerOnRuntimeReady { get; set; } + [Required] public string OutputPath { get; set; } @@ -143,6 +147,9 @@ public void WriteBootJson(Stream output, string entryAssemblyName) result.runtimeOptions = runtimeOptions.ToArray(); } + var libraryInitializerOnRuntimeConfigLoaded = LibraryInitializerOnRuntimeConfigLoaded?.Select(s => s.GetMetadata("FullPath")).ToArray() ?? Array.Empty(); + var libraryInitializerOnRuntimeReady = LibraryInitializerOnRuntimeReady?.Select(s => s.GetMetadata("FullPath")).ToArray() ?? Array.Empty(); + // Build a two-level dictionary of the form: // - assembly: // - UriPath (e.g., "System.Text.Json.dll") @@ -225,10 +232,18 @@ public void WriteBootJson(Stream output, string entryAssemblyName) resourceData.libraryInitializers ??= new TypedLibraryInitializers(); TypedLibraryInitializers libraryInitializers = (TypedLibraryInitializers)resourceData.libraryInitializers; - if (File.Exists(resource.ItemSpec)) + if (libraryInitializerOnRuntimeConfigLoaded.Contains(resource.ItemSpec)) + { + resourceList = libraryInitializers.onRuntimeConfigLoaded ??= new(); + } + else if (libraryInitializerOnRuntimeReady.Contains(resource.ItemSpec)) + { + resourceList = libraryInitializers.onRuntimeReady ??= new(); + } + else if (File.Exists(resource.ItemSpec)) { string fileContent = File.ReadAllText(resource.ItemSpec); - if (fileContent.Contains("onRuntimeConfigLoaded") || fileContent.Contains("beforeStart")) + if (fileContent.Contains("onRuntimeConfigLoaded") || fileContent.Contains("beforeStart") || fileContent.Contains("afterStarted")) resourceList = libraryInitializers.onRuntimeConfigLoaded ??= new(); else resourceList = libraryInitializers.onRuntimeReady ??= new(); From 9af8dfc0024b4c0a4e79c80849f0cc2d7074db6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 11 Jul 2023 14:19:27 +0200 Subject: [PATCH 45/57] Drop duplicate initialization of loaderHelpers.loadedAssemblies --- .../wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts index cef5cc3e096c05..cd90ffb2b9a7ed 100644 --- a/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts +++ b/src/mono/wasm/runtime/loader/blazor/WebAssemblyResourceLoader.ts @@ -37,9 +37,6 @@ export class WebAssemblyResourceLoader { const absoluteUrl = toAbsoluteUri(url); if (resourceType == "assembly") { - if (!loaderHelpers.loadedAssemblies) - loaderHelpers.loadedAssemblies = []; - loaderHelpers.loadedAssemblies.push(absoluteUrl); } return { name, url: absoluteUrl, response }; From 42d982dd32cdf649a0c3d54afbb3bda3fa39736e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 11 Jul 2023 17:37:16 +0200 Subject: [PATCH 46/57] Feedback --- .../Wasm.Build.Tests/Wasm.Build.Tests.csproj | 4 - src/mono/wasm/runtime/dotnet.d.ts | 74 +++++++++++++-- src/mono/wasm/runtime/types/index.ts | 95 ++++++++++++++----- .../BootJsonData.cs | 1 - 4 files changed, 140 insertions(+), 34 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj index 66eb1158967fbe..75b32f3c86e664 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj +++ b/src/mono/wasm/Wasm.Build.Tests/Wasm.Build.Tests.csproj @@ -41,10 +41,6 @@ - - - - diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index e5f5b68ce60f68..ece258d9a8483f 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -159,11 +159,6 @@ type MonoConfig = { /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched * from a custom source, such as an external CDN. - * @param type The type of the resource to be loaded. - * @param name The name of the resource to be loaded. - * @param defaultUri The URI from which the framework would fetch the resource by default. The URI may be relative or absolute. - * @param integrity The integrity string representing the expected content in the response. - * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. */ loadBootResource?: LoadBootResourceCallback; /** @@ -258,12 +253,79 @@ interface AssetEntry extends ResourceRequest { */ pendingDownload?: LoadingResource; } -type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads" | "js-module-runtime" | "js-module-dotnet" | "js-module-native" | "js-module-library-initializer" | "symbols"; +type AssetBehaviours = +/** + * Load asset as a managed resource assembly. + */ +"resource" +/** + * Load asset as a managed assembly. + */ + | "assembly" +/** + * Load asset as a managed debugging information. + */ + | "pdb" +/** + * Store asset into the native heap. + */ + | "heap" +/** + * Load asset as an ICU data archive. + */ + | "icu" +/** + * Load asset into the virtual filesystem (for fopen, File.Open, etc). + */ + | "vfs" +/** + * The binary of the dotnet runtime. + */ + | "dotnetwasm" +/** + * The javascript module for threads. + */ + | "js-module-threads" +/** + * The javascript module for threads. + */ + | "js-module-runtime" +/** + * The javascript module for threads. + */ + | "js-module-dotnet" +/** + * The javascript module for threads. + */ + | "js-module-native" +/** + * The javascript module that came from nuget package . + */ + | "js-module-library-initializer" +/** + * The javascript module for threads. + */ + | "symbols"; declare const enum GlobalizationMode { + /** + * Load sharded ICU data. + */ Sharded = "sharded", + /** + * Load all ICU data. + */ All = "all", + /** + * Operate in invariant globalization mode. + */ Invariant = "invariant", + /** + * Use user defined icu file. + */ Custom = "custom", + /** + * Operate in hybrid globalization mode with small ICU files, using native platform functions. + */ Hybrid = "hybrid" } type DotnetModuleConfig = { diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 17a250ef5c00f6..96bdf0ae20f7d8 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -95,11 +95,6 @@ export type MonoConfig = { /** * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched * from a custom source, such as an external CDN. - * @param type The type of the resource to be loaded. - * @param name The name of the resource to be loaded. - * @param defaultUri The URI from which the framework would fetch the resource by default. The URI may be relative or absolute. - * @param integrity The integrity string representing the expected content in the response. - * @returns A URI string or a Response promise to override the loading process, or null/undefined to allow the default loading behavior. */ loadBootResource?: LoadBootResourceCallback; @@ -197,26 +192,80 @@ export interface AssetEntry extends ResourceRequest { } export type AssetBehaviours = - "resource" // load asset as a managed resource assembly - | "assembly" // load asset as a managed assembly - | "pdb" // load asset as a managed debugging information - | "heap" // store asset into the native heap - | "icu" // load asset as an ICU data archive - | "vfs" // load asset into the virtual filesystem (for fopen, File.Open, etc) - | "dotnetwasm" // the binary of the dotnet runtime - | "js-module-threads" // the javascript module for threads - | "js-module-runtime" // the javascript module for threads - | "js-module-dotnet" // the javascript module for threads - | "js-module-native" // the javascript module for threads - | "js-module-library-initializer" // the javascript module that came from nuget package - | "symbols" // the javascript module for threads + /** + * Load asset as a managed resource assembly. + */ + "resource" + /** + * Load asset as a managed assembly. + */ + | "assembly" + /** + * Load asset as a managed debugging information. + */ + | "pdb" + /** + * Store asset into the native heap. + */ + | "heap" + /** + * Load asset as an ICU data archive. + */ + | "icu" + /** + * Load asset into the virtual filesystem (for fopen, File.Open, etc). + */ + | "vfs" + /** + * The binary of the dotnet runtime. + */ + | "dotnetwasm" + /** + * The javascript module for threads. + */ + | "js-module-threads" + /** + * The javascript module for threads. + */ + | "js-module-runtime" + /** + * The javascript module for threads. + */ + | "js-module-dotnet" + /** + * The javascript module for threads. + */ + | "js-module-native" + /** + * The javascript module that came from nuget package . + */ + | "js-module-library-initializer" + /** + * The javascript module for threads. + */ + | "symbols" // export const enum GlobalizationMode { - Sharded = "sharded", // load sharded ICU data - All = "all", // load all ICU data - Invariant = "invariant", // operate in invariant globalization mode. - Custom = "custom", // use user defined icu file - Hybrid = "hybrid" // operate in hybrid globalization mode with small ICU files, using native platform functions + /** + * Load sharded ICU data. + */ + Sharded = "sharded", // + /** + * Load all ICU data. + */ + All = "all", + /** + * Operate in invariant globalization mode. + */ + Invariant = "invariant", + /** + * Use user defined icu file. + */ + Custom = "custom", + /** + * Operate in hybrid globalization mode with small ICU files, using native platform functions. + */ + Hybrid = "hybrid" } export type DotnetModuleConfig = { diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index 081300fa06f995..de865298898e6e 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -132,7 +132,6 @@ public class ResourcesData /// /// For .NET < 8 it's /// For .NET >= 8 it's - /// - First key is "" or "" /// [DataMember(EmitDefaultValue = false)] public object libraryInitializers { get; set; } From 872bc57829c75d5fe63cb7d994e57676601a9111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Tue, 11 Jul 2023 17:58:06 +0200 Subject: [PATCH 47/57] Move libraryInitializers and loadBootResource to loaderHelpers --- src/mono/wasm/runtime/dotnet.d.ts | 18 ++++-------- src/mono/wasm/runtime/export-api.ts | 2 ++ src/mono/wasm/runtime/libraryInitializers.ts | 8 +++++ .../runtime/loader/blazor/_Integration.ts | 4 +-- src/mono/wasm/runtime/loader/config.ts | 4 +-- .../runtime/loader/libraryInitializers.ts | 29 +++++++++++-------- src/mono/wasm/runtime/loader/run.ts | 4 +-- src/mono/wasm/runtime/types/index.ts | 21 ++++---------- src/mono/wasm/runtime/types/internal.ts | 5 +++- 9 files changed, 47 insertions(+), 48 deletions(-) create mode 100644 src/mono/wasm/runtime/libraryInitializers.ts diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index ece258d9a8483f..4d601a1e356af2 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -83,6 +83,10 @@ interface DotnetHostBuilder { withApplicationArgumentsFromQuery(): DotnetHostBuilder; withApplicationEnvironment(applicationEnvironment?: string): DotnetHostBuilder; withApplicationCulture(applicationCulture?: string): DotnetHostBuilder; + /** + * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched + * from a custom source, such as an external CDN. + */ withResourceLoader(loadBootResource?: LoadBootResourceCallback): DotnetHostBuilder; create(): Promise; run(): Promise; @@ -156,19 +160,6 @@ type MonoConfig = { * Gets the application culture. This is a name specified in the BCP 47 format. See https://tools.ietf.org/html/bcp47 */ applicationCulture?: string; - /** - * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched - * from a custom source, such as an external CDN. - */ - loadBootResource?: LoadBootResourceCallback; - /** - * exports from library es6 modules - * - * nuget packages can contain wwwroot/*.lib.module.js which are treated as es6 modules - * runtime calls 'onRuntimeConfigLoaded(config: MonoConfig)' and 'onRuntimeReady(api: RuntimeAPI)' - * blazor calls 'beforeStart' and 'afterStarted' - */ - libraryInitializers?: any[]; /** * definition of assets to load along with the runtime. */ @@ -347,6 +338,7 @@ type APIType = { getAssemblyExports(assemblyName: string): Promise; setModuleImports(moduleName: string, moduleImports: any): void; getConfig: () => MonoConfig; + getLibraryInitializerExports(): any[]; setHeapB32: (offset: NativePointer, value: number | boolean) => void; setHeapU8: (offset: NativePointer, value: number) => void; setHeapU16: (offset: NativePointer, value: number) => void; diff --git a/src/mono/wasm/runtime/export-api.ts b/src/mono/wasm/runtime/export-api.ts index 1b1b997429ac81..688c66b2cc365b 100644 --- a/src/mono/wasm/runtime/export-api.ts +++ b/src/mono/wasm/runtime/export-api.ts @@ -9,6 +9,7 @@ import { getB32, getF32, getF64, getI16, getI32, getI52, getI64Big, getI8, getU1 import { mono_run_main, mono_run_main_and_exit } from "./run"; import { mono_wasm_setenv } from "./startup"; import { runtimeHelpers } from "./globals"; +import { getLibraryInitializerExports } from "./libraryInitializers"; export function export_api(): any { const api: APIType = { @@ -20,6 +21,7 @@ export function export_api(): any { getConfig: (): MonoConfig => { return runtimeHelpers.config; }, + getLibraryInitializerExports: getLibraryInitializerExports, setHeapB32: setB32, setHeapU8: setU8, setHeapU16: setU16, diff --git a/src/mono/wasm/runtime/libraryInitializers.ts b/src/mono/wasm/runtime/libraryInitializers.ts new file mode 100644 index 00000000000000..c402033faf8c18 --- /dev/null +++ b/src/mono/wasm/runtime/libraryInitializers.ts @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { loaderHelpers } from "./globals"; + +export function getLibraryInitializerExports(): any[] { + return loaderHelpers.libraryInitializers ?? []; +} \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 357c671ec209b2..b4e0f6e24c7b03 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -15,9 +15,9 @@ import { appendUniqueQuery, toAbsoluteBaseUri } from "../assets"; let resourceLoader: WebAssemblyResourceLoader; export async function loadBootConfig(config: MonoConfigInternal, module: DotnetModuleInternal) { - const bootConfigPromise = BootConfigResult.initAsync(config.loadBootResource, config.applicationEnvironment); + const bootConfigPromise = BootConfigResult.initAsync(loaderHelpers.loadBootResource, config.applicationEnvironment); const bootConfigResult: BootConfigResult = await bootConfigPromise; - await initializeBootConfig(bootConfigResult, module, config.loadBootResource); + await initializeBootConfig(bootConfigResult, module, loaderHelpers.loadBootResource); } export async function initializeBootConfig(bootConfigResult: BootConfigResult, module: DotnetModuleInternal, loadBootResource?: LoadBootResourceCallback) { diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index 6174336f7e5269..f78ddef1ba9303 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -78,7 +78,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi } mono_log_debug("mono_wasm_load_config"); try { - if (loaderHelpers.config.loadBootResource) { + if (loaderHelpers.loadBootResource) { // If we have custom loadBootResource await loadBootConfig(loaderHelpers.config, module); } else { @@ -89,7 +89,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi if (loadedAnyConfig.resources) { // If we found boot config schema normalizeConfig(); - await initializeBootConfig(BootConfigResult.fromFetchResponse(configResponse, loadedAnyConfig as BootJsonData, loaderHelpers.config.applicationEnvironment), module, loaderHelpers.config.loadBootResource); + await initializeBootConfig(BootConfigResult.fromFetchResponse(configResponse, loadedAnyConfig as BootJsonData, loaderHelpers.config.applicationEnvironment), module, loaderHelpers.loadBootResource); } else { // Otherwise we found mono config schema const loadedConfig = loadedAnyConfig as MonoConfigInternal; diff --git a/src/mono/wasm/runtime/loader/libraryInitializers.ts b/src/mono/wasm/runtime/loader/libraryInitializers.ts index 6c1c2e02ce0f81..d795e08909af3d 100644 --- a/src/mono/wasm/runtime/loader/libraryInitializers.ts +++ b/src/mono/wasm/runtime/loader/libraryInitializers.ts @@ -5,14 +5,15 @@ import { mono_log_warn } from "./logging"; import { MonoConfig, RuntimeAPI } from "../types"; import { appendUniqueQuery, toAbsoluteBaseUri } from "./assets"; import { normalizeConfig } from "./config"; +import { loaderHelpers } from "./globals"; export type LibraryInitializerTypes = "onRuntimeConfigLoaded" | "onRuntimeReady"; -async function fetchInitializers(config: MonoConfig, type: LibraryInitializerTypes): Promise { - if (!config.libraryInitializers) { - config.libraryInitializers = []; +async function fetchLibraryInitializers(config: MonoConfig, type: LibraryInitializerTypes): Promise { + if (!loaderHelpers.libraryInitializers) { + loaderHelpers.libraryInitializers = []; } const libraryInitializers = type == "onRuntimeConfigLoaded" @@ -31,7 +32,7 @@ async function fetchInitializers(config: MonoConfig, type: LibraryInitializerTyp const adjustedPath = appendUniqueQuery(toAbsoluteBaseUri(path), "js-module-library-initializer"); const initializer = await import(/* webpackIgnore: true */ adjustedPath); - config.libraryInitializers!.push(initializer); + loaderHelpers.libraryInitializers!.push(initializer); } catch (error) { mono_log_warn(`Failed to import library initializer '${path}': ${error}`); } @@ -39,15 +40,15 @@ async function fetchInitializers(config: MonoConfig, type: LibraryInitializerTyp } export async function invokeOnRuntimeConfigLoaded(config: MonoConfig) { - await fetchInitializers(config, "onRuntimeConfigLoaded"); + await fetchLibraryInitializers(config, "onRuntimeConfigLoaded"); - if (!config.libraryInitializers) { + if (!loaderHelpers.libraryInitializers) { return; } const promises = []; - for (let i = 0; i < config.libraryInitializers.length; i++) { - const initializer = config.libraryInitializers[i] as { onRuntimeConfigLoaded: (config: MonoConfig) => Promise }; + for (let i = 0; i < loaderHelpers.libraryInitializers.length; i++) { + const initializer = loaderHelpers.libraryInitializers[i] as { onRuntimeConfigLoaded: (config: MonoConfig) => Promise }; if (initializer.onRuntimeConfigLoaded) { promises.push(logAndSwallowError("onRuntimeConfigLoaded", () => initializer.onRuntimeConfigLoaded(config))); } @@ -60,15 +61,15 @@ export async function invokeOnRuntimeConfigLoaded(config: MonoConfig) { export async function invokeOnRuntimeReady(api: RuntimeAPI) { const config = api.getConfig(); - await fetchInitializers(config, "onRuntimeReady"); + await fetchLibraryInitializers(config, "onRuntimeReady"); - if (!config.libraryInitializers) { + if (!loaderHelpers.libraryInitializers) { return; } const promises = []; - for (let i = 0; i < config.libraryInitializers.length; i++) { - const initializer = config.libraryInitializers[i] as { onRuntimeReady: (api: RuntimeAPI) => Promise }; + for (let i = 0; i < loaderHelpers.libraryInitializers.length; i++) { + const initializer = loaderHelpers.libraryInitializers[i] as { onRuntimeReady: (api: RuntimeAPI) => Promise }; if (initializer.onRuntimeReady) { promises.push(logAndSwallowError("onRuntimeReady", () => initializer.onRuntimeReady(api))); } @@ -83,4 +84,8 @@ async function logAndSwallowError(methodName: string, callback: () => Promise run(): Promise @@ -92,21 +97,6 @@ export type MonoConfig = { */ applicationCulture?: string, - /** - * Overrides the built-in boot resource loading mechanism so that boot resources can be fetched - * from a custom source, such as an external CDN. - */ - loadBootResource?: LoadBootResourceCallback; - - /** - * exports from library es6 modules - * - * nuget packages can contain wwwroot/*.lib.module.js which are treated as es6 modules - * runtime calls 'onRuntimeConfigLoaded(config: MonoConfig)' and 'onRuntimeReady(api: RuntimeAPI)' - * blazor calls 'beforeStart' and 'afterStarted' - */ - libraryInitializers?: any[]; - /** * definition of assets to load along with the runtime. */ @@ -290,6 +280,7 @@ export type APIType = { getAssemblyExports(assemblyName: string): Promise, setModuleImports(moduleName: string, moduleImports: any): void, getConfig: () => MonoConfig, + getLibraryInitializerExports(): any[], // memory management setHeapB32: (offset: NativePointer, value: number | boolean) => void, diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index 8f86284ed38c8d..a1d52f5c087cc0 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadingResource, MonoConfig, ResourceRequest, RuntimeAPI } from "."; +import type { AssetBehaviours, AssetEntry, DotnetModuleConfig, LoadBootResourceCallback, LoadingResource, MonoConfig, ResourceRequest, RuntimeAPI } from "."; import type { BootJsonData } from "./blazor"; import type { CharPtr, EmscriptenModule, ManagedPointer, NativePointer, VoidPtr, Int32Ptr } from "./emscripten"; @@ -136,6 +136,9 @@ export type LoaderHelpers = { hasDebuggingEnabled(bootConfig: BootJsonData): boolean, + loadBootResource?: LoadBootResourceCallback; + libraryInitializers?: any[]; + isChromium: boolean, isFirefox: boolean } From cd52d284aabe79f77d3e20137e2163886f87b033 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 12 Jul 2023 10:08:44 +0200 Subject: [PATCH 48/57] Abort startup on library initializer error. Replace getLibraryInitializerExports with invokeLibraryInitializers API --- src/mono/wasm/runtime/dotnet.d.ts | 2 +- src/mono/wasm/runtime/export-api.ts | 5 +- src/mono/wasm/runtime/libraryInitializers.ts | 8 ---- src/mono/wasm/runtime/loader/config.ts | 4 +- src/mono/wasm/runtime/loader/globals.ts | 2 + .../runtime/loader/libraryInitializers.ts | 46 +++++-------------- src/mono/wasm/runtime/loader/run.ts | 4 +- src/mono/wasm/runtime/types/index.ts | 2 +- src/mono/wasm/runtime/types/internal.ts | 3 +- 9 files changed, 24 insertions(+), 52 deletions(-) delete mode 100644 src/mono/wasm/runtime/libraryInitializers.ts diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 4d601a1e356af2..0718e341222aa6 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -338,7 +338,7 @@ type APIType = { getAssemblyExports(assemblyName: string): Promise; setModuleImports(moduleName: string, moduleImports: any): void; getConfig: () => MonoConfig; - getLibraryInitializerExports(): any[]; + invokeLibraryInitializers: (functionName: string, args: any[]) => Promise; setHeapB32: (offset: NativePointer, value: number | boolean) => void; setHeapU8: (offset: NativePointer, value: number) => void; setHeapU16: (offset: NativePointer, value: number) => void; diff --git a/src/mono/wasm/runtime/export-api.ts b/src/mono/wasm/runtime/export-api.ts index 688c66b2cc365b..1b4b596bdb6e33 100644 --- a/src/mono/wasm/runtime/export-api.ts +++ b/src/mono/wasm/runtime/export-api.ts @@ -8,8 +8,7 @@ import { mono_wasm_set_module_imports } from "./invoke-js"; import { getB32, getF32, getF64, getI16, getI32, getI52, getI64Big, getI8, getU16, getU32, getU52, getU8, localHeapViewF32, localHeapViewF64, localHeapViewI16, localHeapViewI32, localHeapViewI64Big, localHeapViewI8, localHeapViewU16, localHeapViewU32, localHeapViewU8, setB32, setF32, setF64, setI16, setI32, setI52, setI64Big, setI8, setU16, setU32, setU52, setU8 } from "./memory"; import { mono_run_main, mono_run_main_and_exit } from "./run"; import { mono_wasm_setenv } from "./startup"; -import { runtimeHelpers } from "./globals"; -import { getLibraryInitializerExports } from "./libraryInitializers"; +import { loaderHelpers, runtimeHelpers } from "./globals"; export function export_api(): any { const api: APIType = { @@ -21,7 +20,7 @@ export function export_api(): any { getConfig: (): MonoConfig => { return runtimeHelpers.config; }, - getLibraryInitializerExports: getLibraryInitializerExports, + invokeLibraryInitializers: loaderHelpers.invokeLibraryInitializers, setHeapB32: setB32, setHeapU8: setU8, setHeapU16: setU16, diff --git a/src/mono/wasm/runtime/libraryInitializers.ts b/src/mono/wasm/runtime/libraryInitializers.ts deleted file mode 100644 index c402033faf8c18..00000000000000 --- a/src/mono/wasm/runtime/libraryInitializers.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { loaderHelpers } from "./globals"; - -export function getLibraryInitializerExports(): any[] { - return loaderHelpers.libraryInitializers ?? []; -} \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/config.ts b/src/mono/wasm/runtime/loader/config.ts index f78ddef1ba9303..fc728d7e7974bd 100644 --- a/src/mono/wasm/runtime/loader/config.ts +++ b/src/mono/wasm/runtime/loader/config.ts @@ -9,7 +9,7 @@ import { initializeBootConfig, loadBootConfig } from "./blazor/_Integration"; import { BootConfigResult } from "./blazor/BootConfig"; import { BootJsonData } from "../types/blazor"; import { mono_log_error, mono_log_debug } from "./logging"; -import { invokeOnRuntimeConfigLoaded } from "./libraryInitializers"; +import { invokeLibraryInitializers } from "./libraryInitializers"; export function deep_merge_config(target: MonoConfigInternal, source: MonoConfigInternal): MonoConfigInternal { const providedConfig: MonoConfigInternal = { ...source }; @@ -101,7 +101,7 @@ export async function mono_wasm_load_config(module: DotnetModuleInternal): Promi normalizeConfig(); - await invokeOnRuntimeConfigLoaded(loaderHelpers.config); + await invokeLibraryInitializers("onRuntimeConfigLoaded", [loaderHelpers.config], "onRuntimeConfigLoaded"); if (module.onConfigLoaded) { try { diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index 571fa6e780614f..aa9bde6689aa2c 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -8,6 +8,7 @@ import { assertIsControllablePromise, createPromiseController, getPromiseControl import { mono_download_assets, resolve_asset_path } from "./assets"; import { setup_proxy_console } from "./logging"; import { hasDebuggingEnabled } from "./blazor/_Polyfill"; +import { invokeLibraryInitializers } from "./libraryInitializers"; export const ENVIRONMENT_IS_NODE = typeof process == "object" && typeof process.versions == "object" && typeof process.versions.node == "string"; export const ENVIRONMENT_IS_WEB = typeof window == "object"; @@ -87,6 +88,7 @@ export function setLoaderGlobals( setup_proxy_console, hasDebuggingEnabled, + invokeLibraryInitializers, } as Partial); } diff --git a/src/mono/wasm/runtime/loader/libraryInitializers.ts b/src/mono/wasm/runtime/loader/libraryInitializers.ts index d795e08909af3d..67da83e4c3332e 100644 --- a/src/mono/wasm/runtime/loader/libraryInitializers.ts +++ b/src/mono/wasm/runtime/loader/libraryInitializers.ts @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. import { mono_log_warn } from "./logging"; -import { MonoConfig, RuntimeAPI } from "../types"; +import { MonoConfig } from "../types"; import { appendUniqueQuery, toAbsoluteBaseUri } from "./assets"; -import { normalizeConfig } from "./config"; import { loaderHelpers } from "./globals"; +import { abort_startup } from "./exit"; export type LibraryInitializerTypes = "onRuntimeConfigLoaded" @@ -32,60 +32,38 @@ async function fetchLibraryInitializers(config: MonoConfig, type: LibraryInitial const adjustedPath = appendUniqueQuery(toAbsoluteBaseUri(path), "js-module-library-initializer"); const initializer = await import(/* webpackIgnore: true */ adjustedPath); - loaderHelpers.libraryInitializers!.push(initializer); + loaderHelpers.libraryInitializers!.push({ scriptName: path, exports: initializer }); } catch (error) { mono_log_warn(`Failed to import library initializer '${path}': ${error}`); } } } -export async function invokeOnRuntimeConfigLoaded(config: MonoConfig) { - await fetchLibraryInitializers(config, "onRuntimeConfigLoaded"); - - if (!loaderHelpers.libraryInitializers) { - return; - } - - const promises = []; - for (let i = 0; i < loaderHelpers.libraryInitializers.length; i++) { - const initializer = loaderHelpers.libraryInitializers[i] as { onRuntimeConfigLoaded: (config: MonoConfig) => Promise }; - if (initializer.onRuntimeConfigLoaded) { - promises.push(logAndSwallowError("onRuntimeConfigLoaded", () => initializer.onRuntimeConfigLoaded(config))); - } +export async function invokeLibraryInitializers(functionName: string, args: any[], type?: LibraryInitializerTypes) { + if (type) { + await fetchLibraryInitializers(loaderHelpers.config, type); } - await Promise.all(promises); - if (promises.length > 0) - normalizeConfig(); -} - -export async function invokeOnRuntimeReady(api: RuntimeAPI) { - const config = api.getConfig(); - await fetchLibraryInitializers(config, "onRuntimeReady"); - if (!loaderHelpers.libraryInitializers) { return; } const promises = []; for (let i = 0; i < loaderHelpers.libraryInitializers.length; i++) { - const initializer = loaderHelpers.libraryInitializers[i] as { onRuntimeReady: (api: RuntimeAPI) => Promise }; - if (initializer.onRuntimeReady) { - promises.push(logAndSwallowError("onRuntimeReady", () => initializer.onRuntimeReady(api))); + const initializer = loaderHelpers.libraryInitializers[i]; + if (initializer.exports[functionName]) { + promises.push(abortStartupOnError(initializer.scriptName, functionName, () => initializer.exports[functionName](...args))); } } await Promise.all(promises); } -async function logAndSwallowError(methodName: string, callback: () => Promise | undefined): Promise { +async function abortStartupOnError(scriptName: string, methodName: string, callback: () => Promise | undefined): Promise { try { await callback(); } catch (error) { - mono_log_warn(`Failed to invoke '${methodName}' on library initializer: ${error}`); + mono_log_warn(`Failed to invoke '${methodName}' on library initializer '${scriptName}': ${error}`); + abort_startup(error, true); } -} - -export function getLibraryInitializerExports(): any[] { - return loaderHelpers.libraryInitializers ?? []; } \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 9190a2e75d78bf..04441af81d7732 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -13,7 +13,7 @@ import { detect_features_and_polyfill } from "./polyfills"; import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; import { setupPreloadChannelToMainThread } from "./worker"; -import { invokeOnRuntimeReady } from "./libraryInitializers"; +import { invokeLibraryInitializers } from "./libraryInitializers"; const module = globalObjectsRoot.module; @@ -451,7 +451,7 @@ async function createEmscriptenMain(): Promise { await runtimeHelpers.dotnetReady.promise; - await invokeOnRuntimeReady(globalObjectsRoot.api); + await invokeLibraryInitializers("onRuntimeReady", [globalObjectsRoot.api], "onRuntimeReady"); return exportedRuntimeAPI; } diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index 493473f38965a3..d0cae5102b9318 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -280,7 +280,7 @@ export type APIType = { getAssemblyExports(assemblyName: string): Promise, setModuleImports(moduleName: string, moduleImports: any): void, getConfig: () => MonoConfig, - getLibraryInitializerExports(): any[], + invokeLibraryInitializers: (functionName: string, args: any[]) => Promise, // memory management setHeapB32: (offset: NativePointer, value: number | boolean) => void, diff --git a/src/mono/wasm/runtime/types/internal.ts b/src/mono/wasm/runtime/types/internal.ts index a1d52f5c087cc0..d088b3de5368ea 100644 --- a/src/mono/wasm/runtime/types/internal.ts +++ b/src/mono/wasm/runtime/types/internal.ts @@ -137,7 +137,8 @@ export type LoaderHelpers = { hasDebuggingEnabled(bootConfig: BootJsonData): boolean, loadBootResource?: LoadBootResourceCallback; - libraryInitializers?: any[]; + invokeLibraryInitializers: (functionName: string, args: any[]) => Promise, + libraryInitializers?: { scriptName: string, exports: any }[]; isChromium: boolean, isFirefox: boolean From 39732dcfb02efd5681677fbb31974b1edf4d6cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 12 Jul 2023 12:19:54 +0200 Subject: [PATCH 49/57] Replace toAbsoluteBaseUri with locateFile --- src/mono/wasm/runtime/loader/assets.ts | 8 -------- .../wasm/runtime/loader/blazor/_Integration.ts | 14 +++++++++++--- .../wasm/runtime/loader/libraryInitializers.ts | 4 ++-- src/mono/wasm/runtime/loader/polyfills.ts | 4 ++++ .../GenerateWasmBootJson.cs | 10 +++++++++- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/mono/wasm/runtime/loader/assets.ts b/src/mono/wasm/runtime/loader/assets.ts index ffdce4cb6c0402..0f6ab16d464cb3 100644 --- a/src/mono/wasm/runtime/loader/assets.ts +++ b/src/mono/wasm/runtime/loader/assets.ts @@ -423,11 +423,3 @@ export function cleanupAsset(asset: AssetEntryInternal) { asset.pendingDownload = null as any; // GC asset.buffer = null as any; // GC } - -export function toAbsoluteBaseUri(path: string) { - if ("URL" in globalThis && document && document.baseURI) { - return new URL(path, document.baseURI).toString(); - } - - return "/" + path; -} \ No newline at end of file diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index b4e0f6e24c7b03..122ae34beeacac 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -10,7 +10,7 @@ import { BootConfigResult } from "./BootConfig"; import { WebAssemblyResourceLoader } from "./WebAssemblyResourceLoader"; import { hasDebuggingEnabled } from "./_Polyfill"; import { ICUDataMode } from "../../types/blazor"; -import { appendUniqueQuery, toAbsoluteBaseUri } from "../assets"; +import { appendUniqueQuery } from "../assets"; let resourceLoader: WebAssemblyResourceLoader; @@ -207,8 +207,8 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl const config = resourceLoader.bootConfig.config[i]; if (config === "appsettings.json" || config === `appsettings.${applicationEnvironment}.json`) { assets.push({ - name: config, - resolvedUrl: appendUniqueQuery(toAbsoluteBaseUri(config), "vfs"), + name: fileName(config), + resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(config), "vfs"), behavior: "vfs", }); } @@ -255,6 +255,14 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl } } +function fileName(name: string) { + let lastIndexOfSlash = name.lastIndexOf("/"); + if (lastIndexOfSlash >= 0) { + lastIndexOfSlash++; + } + return name.substring(lastIndexOfSlash); +} + function getICUResourceName(bootConfig: BootJsonData, moduleConfig: MonoConfigInternal, culture: string | undefined): string { if (bootConfig.icuDataMode === ICUDataMode.Custom) { const icuFiles = Object diff --git a/src/mono/wasm/runtime/loader/libraryInitializers.ts b/src/mono/wasm/runtime/loader/libraryInitializers.ts index 67da83e4c3332e..1790fa48bfbf13 100644 --- a/src/mono/wasm/runtime/loader/libraryInitializers.ts +++ b/src/mono/wasm/runtime/loader/libraryInitializers.ts @@ -3,7 +3,7 @@ import { mono_log_warn } from "./logging"; import { MonoConfig } from "../types"; -import { appendUniqueQuery, toAbsoluteBaseUri } from "./assets"; +import { appendUniqueQuery } from "./assets"; import { loaderHelpers } from "./globals"; import { abort_startup } from "./exit"; @@ -29,7 +29,7 @@ async function fetchLibraryInitializers(config: MonoConfig, type: LibraryInitial async function importInitializer(path: string): Promise { try { - const adjustedPath = appendUniqueQuery(toAbsoluteBaseUri(path), "js-module-library-initializer"); + const adjustedPath = appendUniqueQuery(loaderHelpers.locateFile(path), "js-module-library-initializer"); const initializer = await import(/* webpackIgnore: true */ adjustedPath); loaderHelpers.libraryInitializers!.push({ scriptName: path, exports: initializer }); diff --git a/src/mono/wasm/runtime/loader/polyfills.ts b/src/mono/wasm/runtime/loader/polyfills.ts index 0b5e2be1967f80..204e644ffd00f2 100644 --- a/src/mono/wasm/runtime/loader/polyfills.ts +++ b/src/mono/wasm/runtime/loader/polyfills.ts @@ -15,6 +15,10 @@ export async function detect_features_and_polyfill(module: DotnetModuleInternal) loaderHelpers.scriptUrl = normalizeFileUrl(scriptUrlQuery); loaderHelpers.scriptDirectory = normalizeDirectoryUrl(loaderHelpers.scriptUrl); loaderHelpers.locateFile = (path) => { + if ("URL" in globalThis) { + return new URL(path, loaderHelpers.scriptDirectory).toString(); + } + if (isPathAbsolute(path)) return path; return loaderHelpers.scriptDirectory + path; }; diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index a0d5b8df62e8a4..8b80fc85a7b818 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -261,6 +261,10 @@ public void WriteBootJson(Stream output, string entryAssemblyName) var targetPath = resource.GetMetadata("TargetPath"); Debug.Assert(!string.IsNullOrEmpty(targetPath), "Target path for '{0}' must exist.", resource.ItemSpec); + + if (TargetingNET80OrLater) + targetPath = "../" + targetPath; // This needs condition once WasmRuntimeAssetsLocation is supported in Wasm SDK + AddResourceToList(resource, resourceList, targetPath); continue; } @@ -324,7 +328,11 @@ public void WriteBootJson(Stream output, string entryAssemblyName) { foreach (var configFile in ConfigurationFiles) { - result.config.Add(Path.GetFileName(configFile.ItemSpec)); + string configUrl = Path.GetFileName(configFile.ItemSpec); + if (TargetingNET80OrLater) + configUrl = "../" + configUrl; // This needs condition once WasmRuntimeAssetsLocation is supported in Wasm SDK + + result.config.Add(configUrl); } } From 88ceb2a6c855963de61864caf57e2cb464d282e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 12 Jul 2023 13:55:25 +0200 Subject: [PATCH 50/57] Update WBT --- .../TestAppScenarios/LibraryInitializerTests.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs index d4efc73318b98e..bd33d2b34cb84e 100644 --- a/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/LibraryInitializerTests.cs @@ -26,7 +26,7 @@ public LibraryInitializerTests(ITestOutputHelper output, SharedBuildPerTestClass [Fact] public async Task LoadLibraryInitializer() { - CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests"); + CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_LoadLibraryInitializer"); PublishProject("Debug"); var result = await RunSdkStyleApp(new(Configuration: "Debug", ForPublish: true, TestScenario: "LibraryInitializerTest")); @@ -37,17 +37,18 @@ public async Task LoadLibraryInitializer() } [Fact] - public async Task LogAndSwallowError() + public async Task AbortStartupOnError() { - CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests"); + CopyTestAsset("WasmBasicTestApp", "LibraryInitializerTests_AbortStartupOnError"); PublishProject("Debug"); var result = await RunSdkStyleApp(new( Configuration: "Debug", ForPublish: true, TestScenario: "LibraryInitializerTest", - BrowserQueryString: new Dictionary { ["throwError"] = "true" } + BrowserQueryString: new Dictionary { ["throwError"] = "true" }, + ExpectedExitCode: 1 )); - Assert.True(result.ConsoleOutput.Any(m => m.Contains("MONO_WASM: Failed to invoke 'onRuntimeConfigLoaded' on library initializer: Error: Error thrown from library initializer")), "The library initializer test didn't emit expected error message"); + Assert.True(result.ConsoleOutput.Any(m => m.Contains("MONO_WASM: Failed to invoke 'onRuntimeConfigLoaded' on library initializer '../WasmBasicTestApp.lib.module.js': Error: Error thrown from library initializer")), "The library initializer test didn't emit expected error message"); } } From 72661c2e493652fa13089ece5e757139e1763523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 12 Jul 2023 13:55:51 +0200 Subject: [PATCH 51/57] Export invokeLibraryInitializers early so onConfigLoaded callbacks can use it --- src/mono/wasm/runtime/loader/globals.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/wasm/runtime/loader/globals.ts b/src/mono/wasm/runtime/loader/globals.ts index aa9bde6689aa2c..2c7ae35f76192a 100644 --- a/src/mono/wasm/runtime/loader/globals.ts +++ b/src/mono/wasm/runtime/loader/globals.ts @@ -45,7 +45,8 @@ export function setLoaderGlobals( exportedRuntimeAPI = globalObjects.api; INTERNAL = globalObjects.internal; Object.assign(exportedRuntimeAPI, { - INTERNAL + INTERNAL, + invokeLibraryInitializers }); Object.assign(globalObjects.module, { From 8d4656b26dae474f628bf3708e140db737a08974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 12 Jul 2023 14:06:13 +0200 Subject: [PATCH 52/57] Use config fileName when checking file name --- src/mono/wasm/runtime/loader/blazor/_Integration.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/runtime/loader/blazor/_Integration.ts b/src/mono/wasm/runtime/loader/blazor/_Integration.ts index 122ae34beeacac..8e5879770618b9 100644 --- a/src/mono/wasm/runtime/loader/blazor/_Integration.ts +++ b/src/mono/wasm/runtime/loader/blazor/_Integration.ts @@ -205,9 +205,10 @@ export function mapBootConfigToMonoConfig(moduleConfig: MonoConfigInternal, appl for (let i = 0; i < resourceLoader.bootConfig.config.length; i++) { const config = resourceLoader.bootConfig.config[i]; - if (config === "appsettings.json" || config === `appsettings.${applicationEnvironment}.json`) { + const configFileName = fileName(config); + if (configFileName === "appsettings.json" || configFileName === `appsettings.${applicationEnvironment}.json`) { assets.push({ - name: fileName(config), + name: configFileName, resolvedUrl: appendUniqueQuery(loaderHelpers.locateFile(config), "vfs"), behavior: "vfs", }); From 5d895dbe49a7fa1960619ff01a04bcc8c21e5c4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 12 Jul 2023 14:27:14 +0200 Subject: [PATCH 53/57] Export GlobalizationMode --- src/mono/wasm/runtime/dotnet.d.ts | 2 +- src/mono/wasm/runtime/types/export-types.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 0718e341222aa6..65b2dfdf8399ad 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -432,4 +432,4 @@ declare global { } declare const createDotnetRuntime: CreateDotnetRuntimeType; -export { AssetEntry, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, IMemoryView, ModuleAPI, MonoConfig, ResourceRequest, RuntimeAPI, createDotnetRuntime as default, dotnet, exit }; +export { AssetEntry, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, GlobalizationMode, IMemoryView, ModuleAPI, MonoConfig, ResourceRequest, RuntimeAPI, createDotnetRuntime as default, dotnet, exit }; diff --git a/src/mono/wasm/runtime/types/export-types.ts b/src/mono/wasm/runtime/types/export-types.ts index c76fd3468575d8..aa7f7e09d2db09 100644 --- a/src/mono/wasm/runtime/types/export-types.ts +++ b/src/mono/wasm/runtime/types/export-types.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { IMemoryView } from "../marshal"; -import type { CreateDotnetRuntimeType, DotnetModuleConfig, RuntimeAPI, MonoConfig, ModuleAPI, AssetEntry, ResourceRequest } from "."; +import type { CreateDotnetRuntimeType, DotnetModuleConfig, RuntimeAPI, MonoConfig, ModuleAPI, AssetEntry, ResourceRequest, GlobalizationMode } from "."; import type { EmscriptenModule } from "./emscripten"; import type { dotnet, exit } from "../loader/index"; @@ -21,6 +21,6 @@ export default createDotnetRuntime; export { EmscriptenModule, - RuntimeAPI, ModuleAPI, DotnetModuleConfig, CreateDotnetRuntimeType, MonoConfig, IMemoryView, AssetEntry, ResourceRequest, + RuntimeAPI, ModuleAPI, DotnetModuleConfig, CreateDotnetRuntimeType, MonoConfig, IMemoryView, AssetEntry, ResourceRequest, GlobalizationMode, dotnet, exit }; From 0d62773edf9bcc4bdb21e978abff63fb39fd5d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Wed, 12 Jul 2023 17:31:26 +0200 Subject: [PATCH 54/57] Feedback --- .../wasm/testassets/WasmBasicTestApp/Common/Program.cs | 6 ------ .../GenerateWasmBootJson.cs | 8 ++++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs b/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs index d297fa288a6909..4f38c02031c7ec 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/Common/Program.cs @@ -1,10 +1,4 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Globalization; -using System.Threading.Tasks; -using System.Resources; -using System.Runtime.InteropServices.JavaScript; - System.Console.WriteLine("WasmBasicTestApp"); diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 8b80fc85a7b818..8ef04f5b14297f 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -147,8 +147,8 @@ public void WriteBootJson(Stream output, string entryAssemblyName) result.runtimeOptions = runtimeOptions.ToArray(); } - var libraryInitializerOnRuntimeConfigLoaded = LibraryInitializerOnRuntimeConfigLoaded?.Select(s => s.GetMetadata("FullPath")).ToArray() ?? Array.Empty(); - var libraryInitializerOnRuntimeReady = LibraryInitializerOnRuntimeReady?.Select(s => s.GetMetadata("FullPath")).ToArray() ?? Array.Empty(); + string[] libraryInitializerOnRuntimeConfigLoadedFullPaths = LibraryInitializerOnRuntimeConfigLoaded?.Select(s => s.GetMetadata("FullPath")).ToArray() ?? Array.Empty(); + string[] libraryInitializerOnRuntimeReadyFullPath = LibraryInitializerOnRuntimeReady?.Select(s => s.GetMetadata("FullPath")).ToArray() ?? Array.Empty(); // Build a two-level dictionary of the form: // - assembly: @@ -232,11 +232,11 @@ public void WriteBootJson(Stream output, string entryAssemblyName) resourceData.libraryInitializers ??= new TypedLibraryInitializers(); TypedLibraryInitializers libraryInitializers = (TypedLibraryInitializers)resourceData.libraryInitializers; - if (libraryInitializerOnRuntimeConfigLoaded.Contains(resource.ItemSpec)) + if (libraryInitializerOnRuntimeConfigLoadedFullPaths.Contains(resource.ItemSpec)) { resourceList = libraryInitializers.onRuntimeConfigLoaded ??= new(); } - else if (libraryInitializerOnRuntimeReady.Contains(resource.ItemSpec)) + else if (libraryInitializerOnRuntimeReadyFullPath.Contains(resource.ItemSpec)) { resourceList = libraryInitializers.onRuntimeReady ??= new(); } From 9722b6b20b4e76b33019e63df2842be21d7996c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 13 Jul 2023 10:00:04 +0200 Subject: [PATCH 55/57] Fix URL polyfill usage in locateFile --- src/mono/wasm/runtime/loader/polyfills.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/mono/wasm/runtime/loader/polyfills.ts b/src/mono/wasm/runtime/loader/polyfills.ts index 204e644ffd00f2..eed7a45fff7181 100644 --- a/src/mono/wasm/runtime/loader/polyfills.ts +++ b/src/mono/wasm/runtime/loader/polyfills.ts @@ -4,6 +4,15 @@ import { INTERNAL, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, loaderHelpers, ENV let node_fs: any | undefined = undefined; let node_url: any | undefined = undefined; +const URLPolyfill = class URL { + private url; + constructor(url: string) { + this.url = url; + } + toString() { + return this.url; + } +}; export async function detect_features_and_polyfill(module: DotnetModuleInternal): Promise { @@ -15,7 +24,7 @@ export async function detect_features_and_polyfill(module: DotnetModuleInternal) loaderHelpers.scriptUrl = normalizeFileUrl(scriptUrlQuery); loaderHelpers.scriptDirectory = normalizeDirectoryUrl(loaderHelpers.scriptUrl); loaderHelpers.locateFile = (path) => { - if ("URL" in globalThis) { + if ("URL" in globalThis && globalThis.URL !== (URLPolyfill as any)) { return new URL(path, loaderHelpers.scriptDirectory).toString(); } @@ -51,15 +60,7 @@ export async function detect_features_and_polyfill(module: DotnetModuleInternal) } if (typeof globalThis.URL === "undefined") { - globalThis.URL = class URL { - private url; - constructor(url: string) { - this.url = url; - } - toString() { - return this.url; - } - } as any; + globalThis.URL = URLPolyfill as any; } } From e8b282d4b811b7ae5a4ada385ea4665f5578900c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 13 Jul 2023 10:11:54 +0200 Subject: [PATCH 56/57] Pass TargetFrameworkVersion to the GenerateWasmBootJson instead of bool for .NET 8 --- ...rosoft.NET.Sdk.WebAssembly.Browser.targets | 4 +- .../GenerateWasmBootJson.cs | 61 +++++++++++-------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index c1947d8f3f534a..13d920fd52ecda 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -361,7 +361,7 @@ Copyright (c) .NET Foundation. All rights reserved. Jiterpreter="$(_BlazorWebAssemblyJiterpreter)" RuntimeOptions="$(_BlazorWebAssemblyRuntimeOptions)" Extensions="@(WasmBootConfigExtension)" - TargetingNET80OrLater="$(_TargetingNET80OrLater)" + TargetFrameworkVersion="$(TargetFrameworkVersion)" LibraryInitializerOnRuntimeConfigLoaded="@(WasmLibraryInitializerOnRuntimeConfigLoaded)" LibraryInitializerOnRuntimeReady="@(WasmLibraryInitializerOnRuntimeReady)" /> @@ -552,7 +552,7 @@ Copyright (c) .NET Foundation. All rights reserved. Jiterpreter="$(_BlazorWebAssemblyJiterpreter)" RuntimeOptions="$(_BlazorWebAssemblyRuntimeOptions)" Extensions="@(WasmBootConfigExtension)" - TargetingNET80OrLater="$(_TargetingNET80OrLater)" + TargetFrameworkVersion="$(TargetFrameworkVersion)" LibraryInitializerOnRuntimeConfigLoaded="@(WasmLibraryInitializerOnRuntimeConfigLoaded)" LibraryInitializerOnRuntimeReady="@(WasmLibraryInitializerOnRuntimeReady)" /> diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 8ef04f5b14297f..38446ce8d24122 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -10,7 +10,6 @@ using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Text; -using System.Xml; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using ResourceHashesByNameDictionary = System.Collections.Generic.Dictionary; @@ -57,7 +56,7 @@ public class GenerateWasmBootJson : Task public string RuntimeOptions { get; set; } [Required] - public bool TargetingNET80OrLater { get; set; } + public string TargetFrameworkVersion { get; set; } public ITaskItem[] LibraryInitializerOnRuntimeConfigLoaded { get; set; } @@ -88,25 +87,6 @@ public override bool Execute() // Internal for tests public void WriteBootJson(Stream output, string entryAssemblyName) { - var icuDataMode = ICUDataMode.Sharded; - - if (string.Equals(InvariantGlobalization, "true", StringComparison.OrdinalIgnoreCase)) - { - icuDataMode = ICUDataMode.Invariant; - } - else if (IsHybridGlobalization) - { - icuDataMode = ICUDataMode.Hybrid; - } - else if (LoadAllICUData) - { - icuDataMode = ICUDataMode.All; - } - else if (LoadCustomIcuData) - { - icuDataMode = ICUDataMode.Custom; - } - var result = new BootJsonData { entryAssembly = entryAssemblyName, @@ -116,7 +96,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) linkerEnabled = LinkerEnabled, resources = new ResourcesData(), config = new List(), - icuDataMode = icuDataMode, + icuDataMode = GetIcuDataMode(), startupMemoryCache = ParseOptionalBool(StartupMemoryCache), }; @@ -227,7 +207,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) { Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a library initializer resource.", resource.ItemSpec); - if (TargetingNET80OrLater) + if (IsTargeting80OrLater()) { resourceData.libraryInitializers ??= new TypedLibraryInitializers(); TypedLibraryInitializers libraryInitializers = (TypedLibraryInitializers)resourceData.libraryInitializers; @@ -262,7 +242,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) var targetPath = resource.GetMetadata("TargetPath"); Debug.Assert(!string.IsNullOrEmpty(targetPath), "Target path for '{0}' must exist.", resource.ItemSpec); - if (TargetingNET80OrLater) + if (IsTargeting80OrLater()) targetPath = "../" + targetPath; // This needs condition once WasmRuntimeAssetsLocation is supported in Wasm SDK AddResourceToList(resource, resourceList, targetPath); @@ -329,7 +309,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) foreach (var configFile in ConfigurationFiles) { string configUrl = Path.GetFileName(configFile.ItemSpec); - if (TargetingNET80OrLater) + if (IsTargeting80OrLater()) configUrl = "../" + configUrl; // This needs condition once WasmRuntimeAssetsLocation is supported in Wasm SDK result.config.Add(configUrl); @@ -373,6 +353,20 @@ void AddResourceToList(ITaskItem resource, ResourceHashesByNameDictionary resour } } + private ICUDataMode GetIcuDataMode() + { + if (string.Equals(InvariantGlobalization, "true", StringComparison.OrdinalIgnoreCase)) + return ICUDataMode.Invariant; + else if (IsHybridGlobalization) + return ICUDataMode.Hybrid; + else if (LoadAllICUData) + return ICUDataMode.All; + else if (LoadCustomIcuData) + return ICUDataMode.Custom; + + return ICUDataMode.Sharded; + } + private static bool? ParseOptionalBool(string value) { if (string.IsNullOrEmpty(value) || !bool.TryParse(value, out var boolValue)) @@ -406,4 +400,21 @@ private bool TryGetLazyLoadedAssembly(string fileName, out ITaskItem lazyLoadedA { return (lazyLoadedAssembly = LazyLoadedAssemblies?.SingleOrDefault(a => a.ItemSpec == fileName)) != null; } + + private Version? parsedTargetFrameworkVersion; + private static readonly Version version80 = new Version(8, 0); + + private bool IsTargeting80OrLater() + { + if (parsedTargetFrameworkVersion == null) + { + string tfv = TargetFrameworkVersion; + if (tfv.StartsWith("v")) + tfv = tfv.Substring(1); + + parsedTargetFrameworkVersion = Version.Parse(tfv); + } + + return parsedTargetFrameworkVersion >= version80; + } } From 934e7e475e3ce71539d2065ae3e06d19a664b46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Thu, 13 Jul 2023 12:08:10 +0200 Subject: [PATCH 57/57] Backward compatibility (1) --- src/mono/wasm/runtime/dotnet.d.ts | 7 ++-- .../runtime/loader/libraryInitializers.ts | 4 +-- src/mono/wasm/runtime/types/blazor.ts | 1 + src/mono/wasm/runtime/types/index.ts | 7 ++-- .../BootJsonData.cs | 15 +++++--- .../GenerateWasmBootJson.cs | 36 +++++++++---------- 6 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 65b2dfdf8399ad..6d55a73a3cc936 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -183,9 +183,10 @@ interface ResourceGroups { readonly satelliteResources?: { [cultureName: string]: ResourceList; }; - readonly libraryInitializers?: { - readonly onRuntimeConfigLoaded: ResourceList; - readonly onRuntimeReady: ResourceList; + readonly libraryInitializers?: ResourceList; + readonly libraryStartupModules?: { + readonly onRuntimeConfigLoaded?: ResourceList; + readonly onRuntimeReady?: ResourceList; }; readonly extensions?: ResourceExtensions; readonly vfs?: { diff --git a/src/mono/wasm/runtime/loader/libraryInitializers.ts b/src/mono/wasm/runtime/loader/libraryInitializers.ts index 1790fa48bfbf13..f9f5b472aed5e9 100644 --- a/src/mono/wasm/runtime/loader/libraryInitializers.ts +++ b/src/mono/wasm/runtime/loader/libraryInitializers.ts @@ -17,8 +17,8 @@ async function fetchLibraryInitializers(config: MonoConfig, type: LibraryInitial } const libraryInitializers = type == "onRuntimeConfigLoaded" - ? config.resources?.libraryInitializers?.onRuntimeConfigLoaded - : config.resources?.libraryInitializers?.onRuntimeReady; + ? config.resources?.libraryStartupModules?.onRuntimeConfigLoaded + : config.resources?.libraryStartupModules?.onRuntimeReady; if (!libraryInitializers) { return; diff --git a/src/mono/wasm/runtime/types/blazor.ts b/src/mono/wasm/runtime/types/blazor.ts index 367e6a2ded936b..205bac41d7e190 100644 --- a/src/mono/wasm/runtime/types/blazor.ts +++ b/src/mono/wasm/runtime/types/blazor.ts @@ -35,6 +35,7 @@ export interface ResourceGroups { readonly runtime: ResourceList; readonly satelliteResources?: { [cultureName: string]: ResourceList }; readonly libraryInitializers?: ResourceList, + readonly libraryStartupModules?: { onRuntimeConfigLoaded: ResourceList, onRuntimeReady: ResourceList }, readonly extensions?: BootJsonDataExtension readonly runtimeAssets: ExtendedResourceList; readonly vfs?: { [virtualPath: string]: ResourceList }; diff --git a/src/mono/wasm/runtime/types/index.ts b/src/mono/wasm/runtime/types/index.ts index d0cae5102b9318..2a6aac14f2c661 100644 --- a/src/mono/wasm/runtime/types/index.ts +++ b/src/mono/wasm/runtime/types/index.ts @@ -117,9 +117,10 @@ export interface ResourceGroups { readonly pdb?: ResourceList; readonly runtime?: ResourceList; // nullable only temporarily readonly satelliteResources?: { [cultureName: string]: ResourceList }; - readonly libraryInitializers?: { - readonly onRuntimeConfigLoaded: ResourceList, - readonly onRuntimeReady: ResourceList, + readonly libraryInitializers?: ResourceList, + readonly libraryStartupModules?: { + readonly onRuntimeConfigLoaded?: ResourceList, + readonly onRuntimeReady?: ResourceList }, readonly extensions?: ResourceExtensions readonly vfs?: { [virtualPath: string]: ResourceList }; diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs index de865298898e6e..81943d6ce83b00 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/BootJsonData.cs @@ -129,12 +129,17 @@ public class ResourcesData /// /// JavaScript module initializers that Blazor will be in charge of loading. - /// - /// For .NET < 8 it's - /// For .NET >= 8 it's + /// Used in .NET < 8 + /// + [DataMember(EmitDefaultValue = false)] + public ResourceHashesByNameDictionary libraryInitializers { get; set; } + + /// + /// JavaScript module initializers that runtime will be in charge of loading. + /// Used in .NET >= 8 /// [DataMember(EmitDefaultValue = false)] - public object libraryInitializers { get; set; } + public TypedLibraryStartupModules libraryStartupModules { get; set; } /// /// Extensions created by users customizing the initialization process. The format of the file(s) @@ -157,7 +162,7 @@ public class ResourcesData } [DataContract] -public class TypedLibraryInitializers +public class TypedLibraryStartupModules { [DataMember(EmitDefaultValue = false)] public ResourceHashesByNameDictionary onRuntimeConfigLoaded { get; set; } diff --git a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs index 38446ce8d24122..a423251b4cb584 100644 --- a/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs +++ b/src/tasks/Microsoft.NET.Sdk.WebAssembly.Pack.Tasks/GenerateWasmBootJson.cs @@ -207,45 +207,41 @@ public void WriteBootJson(Stream output, string entryAssemblyName) { Log.LogMessage(MessageImportance.Low, "Candidate '{0}' is defined as a library initializer resource.", resource.ItemSpec); + var targetPath = resource.GetMetadata("TargetPath"); + Debug.Assert(!string.IsNullOrEmpty(targetPath), "Target path for '{0}' must exist.", resource.ItemSpec); + + resourceList = resourceData.libraryInitializers ??= new ResourceHashesByNameDictionary(); + AddResourceToList(resource, resourceList, targetPath); + if (IsTargeting80OrLater()) { - resourceData.libraryInitializers ??= new TypedLibraryInitializers(); - TypedLibraryInitializers libraryInitializers = (TypedLibraryInitializers)resourceData.libraryInitializers; + var libraryStartupModules = resourceData.libraryStartupModules ??= new TypedLibraryStartupModules(); if (libraryInitializerOnRuntimeConfigLoadedFullPaths.Contains(resource.ItemSpec)) { - resourceList = libraryInitializers.onRuntimeConfigLoaded ??= new(); + resourceList = libraryStartupModules.onRuntimeConfigLoaded ??= new(); } else if (libraryInitializerOnRuntimeReadyFullPath.Contains(resource.ItemSpec)) { - resourceList = libraryInitializers.onRuntimeReady ??= new(); + resourceList = libraryStartupModules.onRuntimeReady ??= new(); } else if (File.Exists(resource.ItemSpec)) { string fileContent = File.ReadAllText(resource.ItemSpec); if (fileContent.Contains("onRuntimeConfigLoaded") || fileContent.Contains("beforeStart") || fileContent.Contains("afterStarted")) - resourceList = libraryInitializers.onRuntimeConfigLoaded ??= new(); + resourceList = libraryStartupModules.onRuntimeConfigLoaded ??= new(); else - resourceList = libraryInitializers.onRuntimeReady ??= new(); + resourceList = libraryStartupModules.onRuntimeReady ??= new(); } else { - resourceList = libraryInitializers.onRuntimeConfigLoaded ??= new(); + resourceList = libraryStartupModules.onRuntimeConfigLoaded ??= new(); } - } - else - { - resourceData.libraryInitializers ??= new ResourceHashesByNameDictionary(); - resourceList = (ResourceHashesByNameDictionary)resourceData.libraryInitializers; - } - - var targetPath = resource.GetMetadata("TargetPath"); - Debug.Assert(!string.IsNullOrEmpty(targetPath), "Target path for '{0}' must exist.", resource.ItemSpec); - if (IsTargeting80OrLater()) - targetPath = "../" + targetPath; // This needs condition once WasmRuntimeAssetsLocation is supported in Wasm SDK + string newTargetPath = "../" + targetPath; // This needs condition once WasmRuntimeAssetsLocation is supported in Wasm SDK + AddResourceToList(resource, resourceList, newTargetPath); + } - AddResourceToList(resource, resourceList, targetPath); continue; } else if (string.Equals("WasmResource", assetTraitName, StringComparison.OrdinalIgnoreCase) && @@ -336,7 +332,7 @@ public void WriteBootJson(Stream output, string entryAssemblyName) var serializer = new DataContractJsonSerializer(typeof(BootJsonData), new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true, - KnownTypes = new[] { typeof(TypedLibraryInitializers) }, + KnownTypes = new[] { typeof(TypedLibraryStartupModules) }, EmitTypeInformation = EmitTypeInformation.Never });