diff --git a/src/Components/Web.JS/@types/dotnet/dotnet.d.ts b/src/Components/Web.JS/@types/dotnet/dotnet.d.ts index 0baceaec3c1a..43a1a208aa7c 100644 --- a/src/Components/Web.JS/@types/dotnet/dotnet.d.ts +++ b/src/Components/Web.JS/@types/dotnet/dotnet.d.ts @@ -3,7 +3,7 @@ //! //! This is generated file, see src/mono/wasm/runtime/rollup.config.js -//! This is not considered public API with backward compatibility guarantees. +//! This is not considered public API with backward compatibility guarantees. declare interface ManagedPointer { __brandManagedPointer: "ManagedPointer"; @@ -43,17 +43,21 @@ declare interface EmscriptenModule { UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string; FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string; + FS_readFile(filename: string, opts: any): any; removeRunDependency(id: string): void; addRunDependency(id: string): void; + stackSave(): VoidPtr; + stackRestore(stack: VoidPtr): void; + stackAlloc(size: number): VoidPtr; ready: Promise; + instantiateWasm?: (imports: WebAssembly.Imports, successCallback: (instance: WebAssembly.Instance) => void) => any; preInit?: (() => any)[]; preRun?: (() => any)[]; + onRuntimeInitialized?: () => any; postRun?: (() => any)[]; onAbort?: { (error: any): void; }; - onRuntimeInitialized?: () => any; - instantiateWasm: (imports: any, successCallback: Function) => any; } declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; @@ -64,6 +68,11 @@ declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Arra * For small numbers of roots, it is preferable to use the mono_wasm_new_root and mono_wasm_new_roots APIs instead. */ declare function mono_wasm_new_root_buffer(capacity: number, name?: string): WasmRootBuffer; +/** + * Allocates a WasmRoot pointing to a root provided and controlled by external code. Typicaly on managed stack. + * Releasing this root will not de-allocate the root space. You still need to call .release(). + */ +declare function mono_wasm_new_external_root(address: VoidPtr | MonoObjectRef): WasmRoot; /** * Allocates temporary storage for a pointer into the managed heap. * Pointers stored here will be visible to the GC, ensuring that the object they point to aren't moved or collected. @@ -71,7 +80,7 @@ declare function mono_wasm_new_root_buffer(capacity: number, name?: string): Was * The result object has get() and set(value) methods, along with a .value property. * When you are done using the root you must call its .release() method. */ -declare function mono_wasm_new_root(value?: T | undefined): WasmRoot; +declare function mono_wasm_new_root(value?: T | undefined): WasmRoot; /** * Releases 1 or more root or root buffer objects. * Multiple objects may be passed on the argument list. @@ -90,26 +99,29 @@ declare class WasmRootBuffer { constructor(offset: VoidPtr, capacity: number, ownsAllocation: boolean, name?: string); _throw_index_out_of_range(): void; _check_in_range(index: number): void; - get_address(index: number): NativePointer; + get_address(index: number): MonoObjectRef; get_address_32(index: number): number; get(index: number): ManagedPointer; set(index: number, value: ManagedPointer): ManagedPointer; + copy_value_from_address(index: number, sourceAddress: MonoObjectRef): void; _unsafe_get(index: number): number; _unsafe_set(index: number, value: ManagedPointer | NativePointer): void; clear(): void; release(): void; toString(): string; } -declare class WasmRoot { - private __buffer; - private __index; - constructor(buffer: WasmRootBuffer, index: number); - get_address(): NativePointer; +interface WasmRoot { + get_address(): MonoObjectRef; get_address_32(): number; + get address(): MonoObjectRef; get(): T; set(value: T): T; get value(): T; set value(value: T); + copy_from_address(source: MonoObjectRef): void; + copy_to_address(destination: MonoObjectRef): void; + copy_from(source: WasmRoot): void; + copy_to(destination: WasmRoot): void; valueOf(): T; clear(): void; release(): void; @@ -125,13 +137,16 @@ interface MonoString extends MonoObject { interface MonoArray extends MonoObject { __brand: "MonoArray"; } +interface MonoObjectRef extends ManagedPointer { + __brandMonoObjectRef: "MonoObjectRef"; +} declare type MonoConfig = { - isError: false; - assembly_root: string; - assets: AllAssetEntryTypes[]; + isError?: false; + assembly_root?: string; + assets?: AssetEntry[]; debug_level?: number; enable_debugging?: number; - globalization_mode: GlobalizationMode; + globalization_mode?: GlobalizationMode; diagnostic_tracing?: boolean; remote_sources?: string[]; environment_variables?: { @@ -141,48 +156,35 @@ declare type MonoConfig = { aot_profiler_options?: AOTProfilerOptions; coverage_profiler_options?: CoverageProfilerOptions; ignore_pdb_load_errors?: boolean; + wait_for_debugger?: number; }; declare type MonoConfigError = { isError: true; message: string; error: any; }; -declare type AllAssetEntryTypes = AssetEntry | AssemblyEntry | SatelliteAssemblyEntry | VfsEntry | IcuData; -declare type AssetEntry = { +interface ResourceRequest { + name: string; + behavior: AssetBehaviours; + resolvedUrl?: string; + hash?: string; +} +interface AssetEntry extends ResourceRequest { name: string; behavior: AssetBehaviours; virtual_path?: string; culture?: string; + resolvedUrl?: string; + hash?: string; load_remote?: boolean; is_optional?: boolean; -}; -interface AssemblyEntry extends AssetEntry { - name: "assembly"; -} -interface SatelliteAssemblyEntry extends AssetEntry { - name: "resource"; - culture: string; -} -interface VfsEntry extends AssetEntry { - name: "vfs"; - virtual_path: string; -} -interface IcuData extends AssetEntry { - name: "icu"; - load_remote: boolean; -} -declare const enum AssetBehaviours { - Resource = "resource", - Assembly = "assembly", - Heap = "heap", - ICU = "icu", - VFS = "vfs" -} -declare const enum GlobalizationMode { - ICU = "icu", - INVARIANT = "invariant", - AUTO = "auto" + buffer?: ArrayBuffer; + pending?: LoadingResource; } +declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm"; +declare type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". + "invariant" | // operate in invariant globalization mode. + "auto"; declare type AOTProfilerOptions = { write_at?: string; send_to?: string; @@ -191,14 +193,23 @@ declare type CoverageProfilerOptions = { write_at?: string; send_to?: string; }; +interface EventPipeSessionOptions { + collectRundownEvents?: boolean; + providers: string; +} declare type DotnetModuleConfig = { disableDotnet6Compatibility?: boolean; - config?: MonoConfig | MonoConfigError; + config?: MonoConfig; configSrc?: string; - scriptDirectory?: string; - onConfigLoaded?: () => void; - onDotnetReady?: () => void; + onConfigLoaded?: (config: MonoConfig) => void | Promise; + onDotnetReady?: () => void | Promise; + preInitAsync?: (() => Promise)[]; + preRunAsync?: (() => Promise)[]; + postRunAsync?: (() => Promise)[]; + onRuntimeInitializedAsync?: (() => Promise)[]; imports?: DotnetModuleConfigImports; + exports?: string[]; + downloadResource?: (request: ResourceRequest) => LoadingResource; } & Partial; declare type DotnetModuleConfigImports = { require?: (name: string) => any; @@ -221,11 +232,59 @@ declare type DotnetModuleConfigImports = { }; url?: any; }; +interface LoadingResource { + name: string; + url: string; + response: Promise; +} + +declare type EventPipeSessionID = bigint; +interface EventPipeSession { + get sessionID(): EventPipeSessionID; + start(): void; + stop(): void; + getTraceBlob(): Blob; +} +declare const eventLevel: { + readonly LogAlways: 0; + readonly Critical: 1; + readonly Error: 2; + readonly Warning: 3; + readonly Informational: 4; + readonly Verbose: 5; +}; +declare type EventLevel = typeof eventLevel; +declare type UnnamedProviderConfiguration = Partial<{ + keyword_mask: string | 0; + level: number; + args: string; +}>; +interface ProviderConfiguration extends UnnamedProviderConfiguration { + name: string; +} +declare class SessionOptionsBuilder { + private _rundown?; + private _providers; + constructor(); + static get Empty(): SessionOptionsBuilder; + static get DefaultProviders(): SessionOptionsBuilder; + setRundownEnabled(enabled: boolean): SessionOptionsBuilder; + addProvider(provider: ProviderConfiguration): SessionOptionsBuilder; + addRuntimeProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder; + addRuntimePrivateProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder; + addSampleProfilerProvider(overrideOptions?: UnnamedProviderConfiguration): SessionOptionsBuilder; + build(): EventPipeSessionOptions; +} +interface Diagnostics { + EventLevel: EventLevel; + SessionOptionsBuilder: typeof SessionOptionsBuilder; + createEventPipeSession(options?: EventPipeSessionOptions): EventPipeSession | null; +} declare function mono_wasm_runtime_ready(): void; declare function mono_wasm_setenv(name: string, value: string): void; -declare function mono_load_runtime_and_bcl_args(config: MonoConfig | MonoConfigError | undefined): Promise; +declare function mono_wasm_load_runtime(unused?: string, debug_level?: number): void; declare function mono_wasm_load_data_archive(data: Uint8Array, prefix: string): boolean; /** * Loads the mono config file (typically called mono-config.json) asynchroniously @@ -235,46 +294,130 @@ declare function mono_wasm_load_data_archive(data: Uint8Array, prefix: string): * @throws Will throw an error if the config file loading fails */ declare function mono_wasm_load_config(configFilePath: string): Promise; +/** +* @deprecated +*/ +declare function mono_load_runtime_and_bcl_args(cfg?: MonoConfig | MonoConfigError | undefined): Promise; declare function mono_wasm_load_icu_data(offset: VoidPtr): boolean; +/** + * @deprecated Not GC or thread safe + */ declare function conv_string(mono_obj: MonoString): string | null; +declare function conv_string_root(root: WasmRoot): string | null; +declare function js_string_to_mono_string_root(string: string, result: WasmRoot): void; +/** + * @deprecated Not GC or thread safe + */ declare function js_string_to_mono_string(string: string): MonoString; +/** + * @deprecated Not GC or thread safe. For blazor use only + */ declare function js_to_mono_obj(js_obj: any): MonoObject; +declare function js_to_mono_obj_root(js_obj: any, result: WasmRoot, should_add_in_flight: boolean): void; +declare function js_typed_array_to_array_root(js_obj: any, result: WasmRoot): void; +/** + * @deprecated Not GC or thread safe + */ declare function js_typed_array_to_array(js_obj: any): MonoArray; declare function unbox_mono_obj(mono_obj: MonoObject): any; +declare function unbox_mono_obj_root(root: WasmRoot): any; declare function mono_array_to_js_array(mono_array: MonoArray): any[] | null; +declare function mono_array_root_to_js_array(arrayRoot: WasmRoot): any[] | null; declare function mono_bind_static_method(fqn: string, signature?: string): Function; declare function mono_call_assembly_entry_point(assembly: string, args?: any[], signature?: string): number; declare function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr; -declare type _MemOffset = number | VoidPtr | NativePointer; +declare type _MemOffset = number | VoidPtr | NativePointer | ManagedPointer; +declare type _NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer; +declare function setB32(offset: _MemOffset, value: number | boolean): void; declare function setU8(offset: _MemOffset, value: number): void; declare function setU16(offset: _MemOffset, value: number): void; -declare function setU32(offset: _MemOffset, value: number): void; +declare function setU32(offset: _MemOffset, value: _NumberOrPointer): void; declare function setI8(offset: _MemOffset, value: number): void; declare function setI16(offset: _MemOffset, value: number): void; declare function setI32(offset: _MemOffset, value: number): void; -declare function setI64(offset: _MemOffset, value: number): void; +/** + * Throws for values which are not 52 bit integer. See Number.isSafeInteger() + */ +declare function setI52(offset: _MemOffset, value: number): void; +/** + * Throws for values which are not 52 bit integer or are negative. See Number.isSafeInteger(). + */ +declare function setU52(offset: _MemOffset, value: number): void; +declare function setI64Big(offset: _MemOffset, value: bigint): void; declare function setF32(offset: _MemOffset, value: number): void; declare function setF64(offset: _MemOffset, value: number): void; +declare function getB32(offset: _MemOffset): boolean; declare function getU8(offset: _MemOffset): number; declare function getU16(offset: _MemOffset): number; declare function getU32(offset: _MemOffset): number; declare function getI8(offset: _MemOffset): number; declare function getI16(offset: _MemOffset): number; declare function getI32(offset: _MemOffset): number; -declare function getI64(offset: _MemOffset): number; +/** + * Throws for Number.MIN_SAFE_INTEGER > value > Number.MAX_SAFE_INTEGER + */ +declare function getI52(offset: _MemOffset): number; +/** + * Throws for 0 > value > Number.MAX_SAFE_INTEGER + */ +declare function getU52(offset: _MemOffset): number; +declare function getI64Big(offset: _MemOffset): bigint; declare function getF32(offset: _MemOffset): number; declare function getF64(offset: _MemOffset): number; declare function mono_run_main_and_exit(main_assembly_name: string, args: string[]): Promise; declare function mono_run_main(main_assembly_name: string, args: string[]): Promise; +interface IDisposable { + dispose(): void; + get isDisposed(): boolean; +} +declare class ManagedObject implements IDisposable { + dispose(): void; + get isDisposed(): boolean; + toString(): string; +} +declare class ManagedError extends Error implements IDisposable { + constructor(message: string); + get stack(): string | undefined; + dispose(): void; + get isDisposed(): boolean; + toString(): string; +} +declare const enum MemoryViewType { + Byte = 0, + Int32 = 1, + Double = 2 +} +interface IMemoryView { + /** + * 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_wasm_get_assembly_exports(assembly: string): Promise; + declare const MONO: { mono_wasm_setenv: typeof mono_wasm_setenv; mono_wasm_load_bytes_into_heap: typeof mono_wasm_load_bytes_into_heap; @@ -285,50 +428,97 @@ declare const MONO: { mono_load_runtime_and_bcl_args: typeof mono_load_runtime_and_bcl_args; mono_wasm_new_root_buffer: typeof mono_wasm_new_root_buffer; mono_wasm_new_root: typeof mono_wasm_new_root; + mono_wasm_new_external_root: typeof mono_wasm_new_external_root; mono_wasm_release_roots: typeof mono_wasm_release_roots; mono_run_main: typeof mono_run_main; mono_run_main_and_exit: typeof mono_run_main_and_exit; + mono_wasm_get_assembly_exports: typeof mono_wasm_get_assembly_exports; mono_wasm_add_assembly: (name: string, data: VoidPtr, size: number) => number; - mono_wasm_load_runtime: (unused: string, debug_level: number) => void; + mono_wasm_load_runtime: typeof mono_wasm_load_runtime; config: MonoConfig | MonoConfigError; loaded_files: string[]; + setB32: typeof setB32; setI8: typeof setI8; setI16: typeof setI16; setI32: typeof setI32; - setI64: typeof setI64; + setI52: typeof setI52; + setU52: typeof setU52; + setI64Big: typeof setI64Big; setU8: typeof setU8; setU16: typeof setU16; setU32: typeof setU32; setF32: typeof setF32; setF64: typeof setF64; + getB32: typeof getB32; getI8: typeof getI8; getI16: typeof getI16; getI32: typeof getI32; - getI64: typeof getI64; + getI52: typeof getI52; + getU52: typeof getU52; + getI64Big: typeof getI64Big; getU8: typeof getU8; getU16: typeof getU16; getU32: typeof getU32; getF32: typeof getF32; getF64: typeof getF64; + diagnostics: Diagnostics; }; declare type MONOType = typeof MONO; declare const BINDING: { + /** + * @deprecated Not GC or thread safe + */ mono_obj_array_new: (size: number) => MonoArray; + /** + * @deprecated Not GC or thread safe + */ mono_obj_array_set: (array: MonoArray, idx: number, obj: MonoObject) => void; + /** + * @deprecated Not GC or thread safe + */ js_string_to_mono_string: typeof js_string_to_mono_string; + /** + * @deprecated Not GC or thread safe + */ js_typed_array_to_array: typeof js_typed_array_to_array; - js_to_mono_obj: typeof js_to_mono_obj; + /** + * @deprecated Not GC or thread safe + */ mono_array_to_js_array: typeof mono_array_to_js_array; + /** + * @deprecated Not GC or thread safe + */ + js_to_mono_obj: typeof js_to_mono_obj; + /** + * @deprecated Not GC or thread safe + */ conv_string: typeof conv_string; + /** + * @deprecated Not GC or thread safe + */ + unbox_mono_obj: typeof unbox_mono_obj; + /** + * @deprecated Renamed to conv_string_root + */ + conv_string_rooted: typeof conv_string_root; + mono_obj_array_new_ref: (size: number, result: MonoObjectRef) => void; + mono_obj_array_set_ref: (array: MonoObjectRef, idx: number, obj: MonoObjectRef) => void; + js_string_to_mono_string_root: typeof js_string_to_mono_string_root; + js_typed_array_to_array_root: typeof js_typed_array_to_array_root; + js_to_mono_obj_root: typeof js_to_mono_obj_root; + conv_string_root: typeof conv_string_root; + unbox_mono_obj_root: typeof unbox_mono_obj_root; + mono_array_root_to_js_array: typeof mono_array_root_to_js_array; bind_static_method: typeof mono_bind_static_method; call_assembly_entry_point: typeof mono_call_assembly_entry_point; - unbox_mono_obj: typeof unbox_mono_obj; }; declare type BINDINGType = typeof BINDING; interface DotnetPublicAPI { MONO: typeof MONO; BINDING: typeof BINDING; INTERNAL: any; + EXPORTS: any; + IMPORTS: any; Module: EmscriptenModule; RuntimeId: number; RuntimeBuildInfo: { @@ -343,4 +533,33 @@ declare global { function getDotnetRuntime(runtimeId: number): DotnetPublicAPI | undefined; } -export { BINDINGType, CreateDotnetRuntimeType, DotnetModuleConfig, DotnetPublicAPI, EmscriptenModule, MONOType, MonoArray, MonoObject, MonoString, VoidPtr, createDotnetRuntime as default }; +/** + * Span class is JS wrapper for System.Span. This view doesn't own the memory, nor pin the underlying array. + * It's ideal to be used on call from C# with the buffer pinned there or with unmanaged memory. + * It is disposed at the end of the call to JS. + */ +declare class Span implements IMemoryView, IDisposable { + dispose(): void; + get isDisposed(): boolean; + set(source: TypedArray, targetOffset?: number | undefined): void; + copyTo(target: TypedArray, sourceOffset?: number | undefined): void; + slice(start?: number | undefined, end?: number | undefined): TypedArray; + get length(): number; + get byteLength(): number; +} +/** + * ArraySegment class is JS wrapper for System.ArraySegment. + * This wrapper would also pin the underlying array and hold GCHandleType.Pinned until this JS instance is collected. + * User could dispose it manualy. + */ +declare class ArraySegment implements IMemoryView, IDisposable { + dispose(): void; + get isDisposed(): boolean; + set(source: TypedArray, targetOffset?: number | undefined): void; + copyTo(target: TypedArray, sourceOffset?: number | undefined): void; + slice(start?: number | undefined, end?: number | undefined): TypedArray; + get length(): number; + get byteLength(): number; +} + +export { ArraySegment, AssetBehaviours, AssetEntry, BINDINGType, CreateDotnetRuntimeType, DotnetModuleConfig, DotnetPublicAPI, EmscriptenModule, IMemoryView, LoadingResource, MONOType, ManagedError, ManagedObject, MemoryViewType, MonoArray, MonoConfig, MonoObject, MonoString, ResourceRequest, Span, VoidPtr, createDotnetRuntime as default }; diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index 0aa9efa09d7e..c4bee243d541 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -12,14 +12,13 @@ import { Platform, System_Array, Pointer, System_Object, System_String, HeapLock import { WebAssemblyBootResourceType } from '../WebAssemblyStartOptions'; import { BootJsonData, ICUDataMode } from '../BootConfig'; import { Blazor } from '../../GlobalExports'; -import { DotnetPublicAPI, BINDINGType, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, MONOType } from 'dotnet'; +import { DotnetPublicAPI, BINDINGType, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, MONOType, MonoArray, MonoObject, MonoConfig, AssetEntry, ResourceRequest } from 'dotnet'; // initially undefined and only fully initialized after createEmscriptenModuleInstance() export let BINDING: BINDINGType = undefined as any; export let MONO: MONOType = undefined as any; export let Module: DotnetModuleConfig & EmscriptenModule = undefined as any; -const appBinDirName = 'appBinDir'; const uint64HighOrderShift = Math.pow(2, 32); const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from Number.MAX_SAFE_INTEGER @@ -144,13 +143,13 @@ export const monoPlatform: Platform = { return ((baseAddress as any as number) + (fieldOffset || 0)) as any as T; }, - beginHeapLock: function() { + beginHeapLock: function () { assertHeapIsNotLocked(); currentHeapLock = new MonoHeapLock(); return currentHeapLock; }, - invokeWhenHeapUnlocked: function(callback) { + invokeWhenHeapUnlocked: function (callback) { // This is somewhat like a sync context. If we're not locked, just pass through the call directly. if (!currentHeapLock) { callback(); @@ -225,62 +224,104 @@ async function importDotnetJs(resourceLoader: WebAssemblyResourceLoader): Promis async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoader): Promise { let runtimeReadyResolve: (data: DotnetPublicAPI) => void = undefined as any; - let runtimeReadyReject: (reason?: any) => void = undefined as any; - const runtimeReady = new Promise((resolve, reject) => { + const runtimeReady = new Promise((resolve) => { runtimeReadyResolve = resolve; - runtimeReadyReject = reject; }); const dotnetJsBeingLoaded = importDotnetJs(resourceLoader); const resources = resourceLoader.bootConfig.resources; const moduleConfig = (window['Module'] || {}) as typeof Module; - const suppressMessages = ['DEBUGGING ENABLED']; - - const print = line => (suppressMessages.indexOf(line) < 0 && console.log(line)); - - const printErr = line => { - // If anything writes to stderr, treat it as a critical exception. The underlying runtime writes - // to stderr if a truly critical problem occurs outside .NET code. Note that .NET unhandled - // exceptions also reach this, but via a different code path - see dotNetCriticalError below. - console.error(line); - showErrorNotification(); - }; const existingPreRun = moduleConfig.preRun || []; - const existingPostRun = moduleConfig.postRun || []; + const existingPostRunAsync = moduleConfig.postRunAsync || []; (moduleConfig as any).preloadPlugins = []; - // Begin loading the .dll/.pdb/.wasm files, but don't block here. Let other loading processes run in parallel. + const assets: AssetEntry[] = []; + const environmentVariables = {}; + const monoConfig: MonoConfig = { + diagnostic_tracing: true, + assets, + environment_variables: environmentVariables, + enable_debugging: hasDebuggingEnabled() ? 1 : 0, + }; + const typesMap: { [key: string]: WebAssemblyBootResourceType } = { + 'assembly': 'assembly', + 'pdb': 'pdb', + 'icu': 'globalization', + 'dotnetwasm': 'dotnetwasm', + }; + const downloadResource = (request: ResourceRequest): LoadingResource => { + const type = typesMap[request.behavior]; + const loaded = resourceLoader.loadResource(request.name, request.resolvedUrl!, request.hash!, type); + loaded.url = toAbsoluteUrl(loaded.url); + return loaded; + }; const dotnetWasmResourceName = 'dotnet.wasm'; - const assembliesBeingLoaded = resourceLoader.loadResources(resources.assembly, filename => `_framework/${filename}`, 'assembly'); - const pdbsBeingLoaded = resourceLoader.loadResources(resources.pdb || {}, filename => `_framework/${filename}`, 'pdb'); - const wasmBeingLoaded = resourceLoader.loadResource( - /* name */ dotnetWasmResourceName, - /* url */ `_framework/${dotnetWasmResourceName}`, - /* hash */ resourceLoader.bootConfig.resources.runtime[dotnetWasmResourceName], - /* type */ 'dotnetwasm' - ); - + assets.push({ + name: dotnetWasmResourceName, + resolvedUrl: `_framework/${dotnetWasmResourceName}`, + hash: resourceLoader.bootConfig.resources.runtime[dotnetWasmResourceName], + behavior: 'dotnetwasm', + }); + for (const filename in resources.assembly) { + const asset: AssetEntry = { + name: filename, + resolvedUrl: `_framework/${filename}`, + hash: resources.assembly[filename], + behavior: 'assembly', + }; + assets.push(asset); + } + for (const filename in resources.pdb) { + const asset: AssetEntry = { + name: filename, + resolvedUrl: `_framework/${filename}`, + hash: resources.pdb[filename], + behavior: 'pdb', + }; + assets.push(asset); + } const dotnetTimeZoneResourceName = 'dotnet.timezones.blat'; - let timeZoneResource: LoadingResource | undefined; if (resourceLoader.bootConfig.resources.runtime.hasOwnProperty(dotnetTimeZoneResourceName)) { - timeZoneResource = resourceLoader.loadResource( - dotnetTimeZoneResourceName, - `_framework/${dotnetTimeZoneResourceName}`, - resourceLoader.bootConfig.resources.runtime[dotnetTimeZoneResourceName], - 'globalization' - ); + assets.push({ + name: dotnetTimeZoneResourceName, + resolvedUrl: `_framework/${dotnetTimeZoneResourceName}`, + hash: resourceLoader.bootConfig.resources.runtime[dotnetTimeZoneResourceName], + behavior: 'vfs', + }); } - - let icuDataResource: LoadingResource | undefined; if (resourceLoader.bootConfig.icuDataMode !== ICUDataMode.Invariant) { const applicationCulture = resourceLoader.startOptions.applicationCulture || (navigator.languages && navigator.languages[0]); const icuDataResourceName = getICUResourceName(resourceLoader.bootConfig, applicationCulture); - icuDataResource = resourceLoader.loadResource( - icuDataResourceName, - `_framework/${icuDataResourceName}`, - resourceLoader.bootConfig.resources.runtime[icuDataResourceName], - 'globalization' - ); + assets.push({ + name: icuDataResourceName, + resolvedUrl: `_framework/${icuDataResourceName}`, + hash: resourceLoader.bootConfig.resources.runtime[icuDataResourceName], + behavior: 'icu', + }); + environmentVariables['DOTNET_SYSTEM_GLOBALIZATION_INVARIANT'] = '1'; + } + + // blazor could start downloading bit earlier than the runtime would + assets.forEach(asset => { + asset.pending = downloadResource(asset); + }); + + if (resourceLoader.bootConfig.icuDataMode === ICUDataMode.Sharded) { + environmentVariables['__BLAZOR_SHARDED_ICU'] = '1'; + + if (resourceLoader.startOptions.applicationCulture) { + // If a culture is specified via start options use that to initialize the Emscripten \ .NET culture. + environmentVariables['LANG'] = `${resourceLoader.startOptions.applicationCulture}.UTF-8`; + } + } + if (resourceLoader.bootConfig.modifiableAssemblies) { + // Configure the app to enable hot reload in Development. + 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; } const createDotnetRuntime = await dotnetJsBeingLoaded; @@ -291,218 +332,34 @@ async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourc BINDING = binding; MONO = mono; - // Override the mechanism for fetching the main wasm file so we can connect it to our cache - const instantiateWasm = (imports, successCallback) => { - (async () => { - let compiledInstance: WebAssembly.Instance; - try { - const dotnetWasmResource = await wasmBeingLoaded; - compiledInstance = await compileWasmModule(dotnetWasmResource, imports); - } catch (ex) { - printErr((ex as Error).toString()); - throw ex; - } - successCallback(compiledInstance); - })(); - return []; // No exports - }; - - const onRuntimeInitialized = () => { - if (!icuDataResource) { - // Use invariant culture if the app does not carry icu data. - MONO.mono_wasm_setenv('DOTNET_SYSTEM_GLOBALIZATION_INVARIANT', '1'); - } - }; - const preRun = () => { - if (timeZoneResource) { - loadTimezone(timeZoneResource); - } - - if (icuDataResource) { - loadICUData(icuDataResource); - } - - // Fetch the assemblies and PDBs in the background, telling Mono to wait until they are loaded - // Mono requires the assembly filenames to have a '.dll' extension, so supply such names regardless - // of the extensions in the URLs. This allows loading assemblies with arbitrary filenames. - assembliesBeingLoaded.forEach(r => addResourceAsAssembly(r, changeExtension(r.name, '.dll'))); - pdbsBeingLoaded.forEach(r => addResourceAsAssembly(r, r.name)); - - Blazor._internal.dotNetCriticalError = (message) => { - printErr(BINDING.conv_string(message) || '(null)'); - }; - - // Wire-up callbacks for satellite assemblies. Blazor will call these as part of the application - // startup sequence to load satellite assemblies for the application's culture. - Blazor._internal.getSatelliteAssemblies = (culturesToLoadDotNetArray) => { - const culturesToLoad = BINDING.mono_array_to_js_array(culturesToLoadDotNetArray); - const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources; - - if (satelliteResources) { - const resourcePromises = 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 => (await resource.response).arrayBuffer())); - - return BINDING.js_to_mono_obj(resourcePromises.then(resourcesToLoad => { - if (resourcesToLoad.length) { - Blazor._internal.readSatelliteAssemblies = () => { - const array = BINDING.mono_obj_array_new(resourcesToLoad.length); - for (let i = 0; i < resourcesToLoad.length; i++) { - BINDING.mono_obj_array_set(array, i, BINDING.js_typed_array_to_array(new Uint8Array(resourcesToLoad[i]))); - } - return array as any; - }; - } - - return resourcesToLoad.length; - })); - } - return BINDING.js_to_mono_obj(Promise.resolve(0)); - }; - - const lazyResources: { - assemblies?: (ArrayBuffer | null)[], - pdbs?: (ArrayBuffer | null)[] - } = {}; - Blazor._internal.getLazyAssemblies = (assembliesToLoadDotNetArray) => { - const assembliesToLoad = BINDING.mono_array_to_js_array(assembliesToLoadDotNetArray); - const lazyAssemblies = resourceLoader.bootConfig.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 assembliesMarkedAsLazy = assembliesToLoad!.filter(assembly => lazyAssemblies.hasOwnProperty(assembly)); - - if (assembliesMarkedAsLazy.length !== assembliesToLoad!.length) { - const notMarked = assembliesToLoad!.filter(assembly => !assembliesMarkedAsLazy.includes(assembly)); - throw new Error(`${notMarked.join()} must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.`); - } - - let pdbPromises: Promise<(ArrayBuffer | null)[]> | undefined; - if (hasDebuggingEnabled()) { - const pdbs = resourceLoader.bootConfig.resources.pdb; - const pdbsToLoad = assembliesMarkedAsLazy.map(a => changeExtension(a, '.pdb')); - if (pdbs) { - pdbPromises = Promise.all(pdbsToLoad - .map(pdb => lazyAssemblies.hasOwnProperty(pdb) ? resourceLoader.loadResource(pdb, `_framework/${pdb}`, lazyAssemblies[pdb], 'pdb') : null) - .map(async resource => resource ? (await resource.response).arrayBuffer() : null)); - } - } - - const resourcePromises = Promise.all(assembliesMarkedAsLazy - .map(assembly => resourceLoader.loadResource(assembly, `_framework/${assembly}`, lazyAssemblies[assembly], 'assembly')) - .map(async resource => (await resource.response).arrayBuffer())); - - - return BINDING.js_to_mono_obj(Promise.all([resourcePromises, pdbPromises]).then(values => { - lazyResources['assemblies'] = values[0]; - lazyResources['pdbs'] = values[1]; - if (lazyResources['assemblies'].length) { - Blazor._internal.readLazyAssemblies = () => { - const { assemblies } = lazyResources; - if (!assemblies) { - return BINDING.mono_obj_array_new(0); - } - const assemblyBytes = BINDING.mono_obj_array_new(assemblies.length); - for (let i = 0; i < assemblies.length; i++) { - const assembly = assemblies[i] as ArrayBuffer; - BINDING.mono_obj_array_set(assemblyBytes, i, BINDING.js_typed_array_to_array(new Uint8Array(assembly))); - } - return assemblyBytes as any; - }; - - Blazor._internal.readLazyPdbs = () => { - const { assemblies, pdbs } = lazyResources; - if (!assemblies) { - return BINDING.mono_obj_array_new(0); - } - const pdbBytes = BINDING.mono_obj_array_new(assemblies.length); - for (let i = 0; i < assemblies.length; i++) { - const pdb = pdbs && pdbs[i] ? new Uint8Array(pdbs[i] as ArrayBufferLike) : new Uint8Array(); - BINDING.mono_obj_array_set(pdbBytes, i, BINDING.js_typed_array_to_array(pdb)); - } - return pdbBytes as any; - }; - } - - return lazyResources['assemblies'].length; - })); - }; + Blazor._internal.dotNetCriticalError = dotNetCriticalError; + Blazor._internal.getSatelliteAssemblies = (culturesToLoad: MonoArray): MonoObject => getSatelliteAssemblies(resourceLoader, culturesToLoad); + Blazor._internal.getLazyAssemblies = (assembliesToLoad: MonoArray): MonoObject => getLazyAssemblies(resourceLoader, assembliesToLoad); }; - const postRun = () => { + // eslint-disable-next-line require-await + const postRunAsync = async () => { if (resourceLoader.bootConfig.debugBuild && resourceLoader.bootConfig.cacheBootResources) { resourceLoader.logToConsole(); } - resourceLoader.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background - - if (resourceLoader.bootConfig.icuDataMode === ICUDataMode.Sharded) { - MONO.mono_wasm_setenv('__BLAZOR_SHARDED_ICU', '1'); - - if (resourceLoader.startOptions.applicationCulture) { - // If a culture is specified via start options use that to initialize the Emscripten \ .NET culture. - MONO.mono_wasm_setenv('LANG', `${resourceLoader.startOptions.applicationCulture}.UTF-8`); - } - } - let timeZone = 'UTC'; - try { - timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - // eslint-disable-next-line no-empty - } catch { } - MONO.mono_wasm_setenv('TZ', timeZone || 'UTC'); - if (resourceLoader.bootConfig.modifiableAssemblies) { - // Configure the app to enable hot reload in Development. - MONO.mono_wasm_setenv('DOTNET_MODIFIABLE_ASSEMBLIES', resourceLoader.bootConfig.modifiableAssemblies); - } + setTimeout(() => { + resourceLoader.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background + }); - if (resourceLoader.bootConfig.aspnetCoreBrowserTools) { - // See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000 - MONO.mono_wasm_setenv('__ASPNETCORE_BROWSER_TOOLS', resourceLoader.bootConfig.aspnetCoreBrowserTools); - } - - // -1 enables debugging with logging disabled. 0 disables debugging entirely. - MONO.mono_wasm_load_runtime(appBinDirName, hasDebuggingEnabled() ? -1 : 0); - MONO.mono_wasm_runtime_ready(); attachInteropInvoker(); runtimeReadyResolve(api); }; - async function addResourceAsAssembly(dependency: LoadingResource, loadAsName: string) { - const runDependencyId = `blazor:${dependency.name}`; - Module.addRunDependency(runDependencyId); - - try { - // Wait for the data to be loaded and verified - const dataBuffer = await dependency.response.then(r => r.arrayBuffer()); - - // Load it into the Mono runtime - const data = new Uint8Array(dataBuffer); - const heapAddress = Module._malloc(data.length); - const heapMemory = new Uint8Array(Module.HEAPU8.buffer, heapAddress as any, data.length); - heapMemory.set(data); - MONO.mono_wasm_add_assembly(loadAsName, heapAddress, data.length); - MONO.loaded_files.push(toAbsoluteUrl(dependency.url)); - } catch (errorInfo) { - runtimeReadyReject(errorInfo); - return; - } - - Module.removeRunDependency(runDependencyId); - } - const dotnetModuleConfig: DotnetModuleConfig = { ...moduleConfig, + config: monoConfig, disableDotnet6Compatibility: false, + downloadResource, preRun: [preRun, ...existingPreRun], - postRun: [postRun, ...existingPostRun], + postRunAsync: [postRunAsync, ...existingPostRunAsync], print, printErr, - instantiateWasm, - onRuntimeInitialized, }; return dotnetModuleConfig; @@ -511,6 +368,120 @@ async function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourc return await runtimeReady; } +const suppressMessages = ['DEBUGGING ENABLED']; +const print = line => (suppressMessages.indexOf(line) < 0 && console.log(line)); +const printErr = line => { + // If anything writes to stderr, treat it as a critical exception. The underlying runtime writes + // to stderr if a truly critical problem occurs outside .NET code. Note that .NET unhandled + // exceptions also reach this, but via a different code path - see dotNetCriticalError below. + console.error(line); + showErrorNotification(); +}; + +function dotNetCriticalError(message) { + printErr(BINDING.conv_string(message) || '(null)'); +} + +// Wire-up callbacks for satellite assemblies. Blazor will call these as part of the application +// startup sequence to load satellite assemblies for the application's culture. +function getSatelliteAssemblies(resourceLoader: WebAssemblyResourceLoader, culturesToLoadDotNetArray: MonoArray) { + const culturesToLoad = BINDING.mono_array_to_js_array(culturesToLoadDotNetArray); + const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources; + + if (satelliteResources) { + const resourcePromises = 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 => (await resource.response).arrayBuffer())); + + return BINDING.js_to_mono_obj(resourcePromises.then(resourcesToLoad => { + if (resourcesToLoad.length) { + Blazor._internal.readSatelliteAssemblies = () => { + const array = BINDING.mono_obj_array_new(resourcesToLoad.length); + for (let i = 0; i < resourcesToLoad.length; i++) { + BINDING.mono_obj_array_set(array, i, BINDING.js_typed_array_to_array(new Uint8Array(resourcesToLoad[i]))); + } + return array as any; + }; + } + + return resourcesToLoad.length; + })); + } + return BINDING.js_to_mono_obj(Promise.resolve(0)); +} + +const lazyResources: { + assemblies?: (ArrayBuffer | null)[], + pdbs?: (ArrayBuffer | null)[] +} = {}; +function getLazyAssemblies(resourceLoader: WebAssemblyResourceLoader, assembliesToLoadDotNetArray: MonoArray) { + const assembliesToLoad = BINDING.mono_array_to_js_array(assembliesToLoadDotNetArray); + const lazyAssemblies = resourceLoader.bootConfig.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 assembliesMarkedAsLazy = assembliesToLoad!.filter(assembly => lazyAssemblies.hasOwnProperty(assembly)); + + if (assembliesMarkedAsLazy.length !== assembliesToLoad!.length) { + const notMarked = assembliesToLoad!.filter(assembly => !assembliesMarkedAsLazy.includes(assembly)); + throw new Error(`${notMarked.join()} must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.`); + } + + let pdbPromises: Promise<(ArrayBuffer | null)[]> | undefined; + if (hasDebuggingEnabled()) { + const pdbs = resourceLoader.bootConfig.resources.pdb; + const pdbsToLoad = assembliesMarkedAsLazy.map(a => changeExtension(a, '.pdb')); + if (pdbs) { + pdbPromises = Promise.all(pdbsToLoad + .map(pdb => lazyAssemblies.hasOwnProperty(pdb) ? resourceLoader.loadResource(pdb, `_framework/${pdb}`, lazyAssemblies[pdb], 'pdb') : null) + .map(async resource => resource ? (await resource.response).arrayBuffer() : null)); + } + } + + const resourcePromises = Promise.all(assembliesMarkedAsLazy + .map(assembly => resourceLoader.loadResource(assembly, `_framework/${assembly}`, lazyAssemblies[assembly], 'assembly')) + .map(async resource => (await resource.response).arrayBuffer())); + + + return BINDING.js_to_mono_obj(Promise.all([resourcePromises, pdbPromises]).then(values => { + lazyResources['assemblies'] = values[0]; + lazyResources['pdbs'] = values[1]; + if (lazyResources['assemblies'].length) { + Blazor._internal.readLazyAssemblies = () => { + const { assemblies } = lazyResources; + if (!assemblies) { + return BINDING.mono_obj_array_new(0); + } + const assemblyBytes = BINDING.mono_obj_array_new(assemblies.length); + for (let i = 0; i < assemblies.length; i++) { + const assembly = assemblies[i] as ArrayBuffer; + BINDING.mono_obj_array_set(assemblyBytes, i, BINDING.js_typed_array_to_array(new Uint8Array(assembly))); + } + return assemblyBytes as any; + }; + + Blazor._internal.readLazyPdbs = () => { + const { assemblies, pdbs } = lazyResources; + if (!assemblies) { + return BINDING.mono_obj_array_new(0); + } + const pdbBytes = BINDING.mono_obj_array_new(assemblies.length); + for (let i = 0; i < assemblies.length; i++) { + const pdb = pdbs && pdbs[i] ? new Uint8Array(pdbs[i] as ArrayBufferLike) : new Uint8Array(); + BINDING.mono_obj_array_set(pdbBytes, i, BINDING.js_typed_array_to_array(pdb)); + } + return pdbBytes as any; + }; + } + + return lazyResources['assemblies'].length; + })); +} + const anchorTagForAbsoluteUrlConversions = document.createElement('a'); function toAbsoluteUrl(possiblyRelativeUrl: string) { anchorTagForAbsoluteUrlConversions.href = possiblyRelativeUrl; @@ -572,20 +543,6 @@ function attachInteropInvoker(): void { }); } -async function loadTimezone(timeZoneResource: LoadingResource): Promise { - const runDependencyId = 'blazor:timezonedata'; - Module.addRunDependency(runDependencyId); - - const request = await timeZoneResource.response; - const arrayBuffer = await request.arrayBuffer(); - - Module['FS_createPath']('/', 'usr', true, true); - Module['FS_createPath']('/usr/', 'share', true, true); - Module['FS_createPath']('/usr/share/', 'zoneinfo', true, true); - MONO.mono_wasm_load_data_archive(new Uint8Array(arrayBuffer), '/usr/share/zoneinfo/'); - - Module.removeRunDependency(runDependencyId); -} function getICUResourceName(bootConfig: BootJsonData, culture: string | undefined): string { const combinedICUResourceName = 'icudt.dat'; @@ -613,41 +570,6 @@ function getICUResourceName(bootConfig: BootJsonData, culture: string | undefine } } -async function loadICUData(icuDataResource: LoadingResource): Promise { - const runDependencyId = 'blazor:icudata'; - Module.addRunDependency(runDependencyId); - - const request = await icuDataResource.response; - const array = new Uint8Array(await request.arrayBuffer()); - - const offset = MONO.mono_wasm_load_bytes_into_heap(array); - if (!MONO.mono_wasm_load_icu_data(offset)) { - throw new Error('Error loading ICU asset.'); - } - Module.removeRunDependency(runDependencyId); -} - -async function compileWasmModule(wasmResource: LoadingResource, imports: any): Promise { - // This is the same logic as used in emscripten's generated js. We can't use emscripten's js because - // it doesn't provide any method for supplying a custom response provider, and we want to integrate - // with our resource loader cache. - - if (typeof WebAssembly['instantiateStreaming'] === 'function') { - try { - const streamingResult = await WebAssembly['instantiateStreaming'](wasmResource.response, imports); - return streamingResult.instance; - } catch (ex) { - console.info('Streaming compilation failed. Falling back to ArrayBuffer instantiation. ', ex); - } - } - - // If that's not available or fails (e.g., due to incorrect content-type header), - // fall back to ArrayBuffer instantiation - const arrayBuffer = await wasmResource.response.then(r => r.arrayBuffer()); - const arrayBufferResult = await WebAssembly.instantiate(arrayBuffer, imports); - return arrayBufferResult.instance; -} - function changeExtension(filename: string, newExtensionWithLeadingDot: string) { const lastDotIndex = filename.lastIndexOf('.'); if (lastDotIndex < 0) {