diff --git a/src/Components/Web.JS/@types/dotnet/dotnet.d.ts b/src/Components/Web.JS/@types/dotnet/dotnet.d.ts index 3c35d593e671..2d0681bedfdf 100644 --- a/src/Components/Web.JS/@types/dotnet/dotnet.d.ts +++ b/src/Components/Web.JS/@types/dotnet/dotnet.d.ts @@ -3,24 +3,7 @@ //! //! This is generated file, see src/mono/wasm/runtime/rollup.config.js -//! This is not considered public API with backward compatibility guarantees. - -interface DotnetHostBuilder { - withConfig(config: MonoConfig): DotnetHostBuilder; - withConfigSrc(configSrc: string): DotnetHostBuilder; - withApplicationArguments(...args: string[]): DotnetHostBuilder; - withEnvironmentVariable(name: string, value: string): DotnetHostBuilder; - withEnvironmentVariables(variables: { - [i: string]: string; - }): DotnetHostBuilder; - withVirtualWorkingDirectory(vfsPath: string): DotnetHostBuilder; - withDiagnosticTracing(enabled: boolean): DotnetHostBuilder; - withDebugging(level: number): DotnetHostBuilder; - withMainAssembly(mainAssemblyName: string): DotnetHostBuilder; - withApplicationArgumentsFromQuery(): DotnetHostBuilder; - create(): Promise; - run(): Promise; -} +//! This is not considered public API with backward compatibility guarantees. declare interface NativePointer { __brandNativePointer: "NativePointer"; @@ -35,18 +18,28 @@ declare interface Int32Ptr extends NativePointer { __brand: "Int32Ptr"; } declare interface EmscriptenModule { + /** @deprecated Please use growableHeapI8() instead.*/ HEAP8: Int8Array; + /** @deprecated Please use growableHeapI16() instead.*/ HEAP16: Int16Array; + /** @deprecated Please use growableHeapI32() instead. */ HEAP32: Int32Array; + /** @deprecated Please use growableHeapI64() instead. */ + HEAP64: BigInt64Array; + /** @deprecated Please use growableHeapU8() instead. */ HEAPU8: Uint8Array; + /** @deprecated Please use growableHeapU16() instead. */ HEAPU16: Uint16Array; + /** @deprecated Please use growableHeapU32() instead */ HEAPU32: Uint32Array; + /** @deprecated Please use growableHeapF32() instead */ HEAPF32: Float32Array; + /** @deprecated Please use growableHeapF64() instead. */ HEAPF64: Float64Array; _malloc(size: number): VoidPtr; _free(ptr: VoidPtr): void; - print(message: string): void; - printErr(message: string): void; + out(message: string): void; + err(message: string): void; ccall(ident: string, returnType?: string | null, argTypes?: string[], args?: any[], opts?: any): T; cwrap(ident: string, returnType: string, argTypes?: string[], opts?: any): T; cwrap(ident: string, ...args: any[]): T; @@ -55,17 +48,13 @@ declare interface EmscriptenModule { getValue(ptr: number, type: string, noSafe?: number | boolean): number; UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string; UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string; + stringToUTF8Array(str: string, heap: Uint8Array, outIdx: number, maxBytesToWrite: number): void; 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; addFunction(fn: Function, signature: string): number; - getWasmTableEntry(index: number): any; stackSave(): VoidPtr; stackRestore(stack: VoidPtr): void; stackAlloc(size: number): VoidPtr; - ready: Promise; instantiateWasm?: InstantiateWasmCallBack; preInit?: (() => any)[] | (() => any); preRun?: (() => any)[] | (() => any); @@ -79,6 +68,29 @@ type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: W type InstantiateWasmCallBack = (imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any; declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array; +interface DotnetHostBuilder { + withConfig(config: MonoConfig): DotnetHostBuilder; + withConfigSrc(configSrc: string): DotnetHostBuilder; + withApplicationArguments(...args: string[]): DotnetHostBuilder; + withEnvironmentVariable(name: string, value: string): DotnetHostBuilder; + withEnvironmentVariables(variables: { + [i: string]: string; + }): DotnetHostBuilder; + withVirtualWorkingDirectory(vfsPath: string): DotnetHostBuilder; + withDiagnosticTracing(enabled: boolean): DotnetHostBuilder; + withDebugging(level: number): DotnetHostBuilder; + withMainAssembly(mainAssemblyName: string): 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; +} type MonoConfig = { /** * The subfolder containing managed assemblies and pdbs. This is relative to dotnet.js script. @@ -115,7 +127,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; /** @@ -136,11 +148,63 @@ type MonoConfig = { * If true, the snapshot of runtime's memory will be stored in the browser and used for faster startup next time. Default is false. */ startupMemoryCache?: boolean; + /** + * hash of assets + */ + assetsHash?: string; /** * application environment */ applicationEnvironment?: string; + /** + * Gets the application culture. This is a name specified in the BCP 47 format. See https://tools.ietf.org/html/bcp47 + */ + applicationCulture?: string; + /** + * definition of assets to load along with the runtime. + */ + resources?: ResourceGroups; + /** + * config extensions declared in MSBuild items @(WasmBootConfigExtension) + */ + extensions?: { + [name: string]: any; + }; +}; +type ResourceExtensions = { + [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?: { + readonly onRuntimeConfigLoaded: ResourceList; + readonly onRuntimeReady: ResourceList; + }; + readonly extensions?: ResourceExtensions; + readonly vfs?: { + [virtualPath: string]: ResourceList; + }; +} +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. + * @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; behavior: AssetBehaviours; @@ -180,15 +244,86 @@ interface AssetEntry extends ResourceRequest { */ pendingDownload?: LoadingResource; } -type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads"; -type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". -"invariant" | // operate in invariant globalization mode. -"auto"; +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 = { disableDotnet6Compatibility?: boolean; config?: MonoConfig; configSrc?: string; - onConfigLoaded?: (config: MonoConfig & BootJsonData) => void | Promise; + onConfigLoaded?: (config: MonoConfig) => void | Promise; onDotnetReady?: () => void | Promise; onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; getApplicationEnvironment?: (bootConfigResponse: Response) => string | null; @@ -203,6 +338,7 @@ type APIType = { getAssemblyExports(assemblyName: string): Promise; setModuleImports(moduleName: string, moduleImports: any): void; getConfig: () => MonoConfig; + 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; @@ -227,6 +363,15 @@ type APIType = { getHeapI64Big: (offset: NativePointer) => bigint; getHeapF32: (offset: NativePointer) => number; getHeapF64: (offset: NativePointer) => number; + localHeapViewI8: () => Int8Array; + localHeapViewI16: () => Int16Array; + localHeapViewI32: () => Int32Array; + localHeapViewI64Big: () => BigInt64Array; + localHeapViewU8: () => Uint8Array; + localHeapViewU16: () => Uint16Array; + localHeapViewU32: () => Uint32Array; + localHeapViewF32: () => Float32Array; + localHeapViewF64: () => Float64Array; }; type RuntimeAPI = { /** @@ -250,8 +395,8 @@ type ModuleAPI = { dotnet: DotnetHostBuilder; exit: (code: number, reason?: any) => void; }; -declare function createDotnetRuntime(moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)): Promise; -type CreateDotnetRuntimeType = typeof createDotnetRuntime; +type CreateDotnetRuntimeType = (moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)) => Promise; +type WebAssemblyBootResourceType = "assembly" | "pdb" | "dotnetjs" | "dotnetwasm" | "globalization" | "manifest" | "configuration"; interface IDisposable { dispose(): void; @@ -277,56 +422,14 @@ interface IMemoryView extends IDisposable { get byteLength(): number; } -declare global { - function getDotnetRuntime(runtimeId: number): RuntimeAPI | undefined; -} - -declare const dotnet: ModuleAPI["dotnet"]; -declare const exit: ModuleAPI["exit"]; +declare function mono_exit(exit_code: number, reason?: any): void; -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 linkerEnabled: boolean; - readonly cacheBootResources: boolean; - readonly config: string[]; - readonly icuDataMode: ICUDataMode; - readonly startupMemoryCache: boolean | undefined; - readonly runtimeOptions: string[] | undefined; - - // These properties are tacked on, and not found in the boot.json file - modifiableAssemblies: string | null; - aspnetCoreBrowserTools: string | null; -} +declare const dotnet: DotnetHostBuilder; +declare const exit: typeof mono_exit; -type BootJsonDataExtension = { [extensionName: string]: ResourceList }; - -interface ResourceGroups { - 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; -} - -type ResourceList = { [name: string]: string }; -type ExtendedResourceList = { - [name: string]: { - hash: string, - behavior: string - } -}; - -declare const enum ICUDataMode { - Sharded, - All, - Invariant, - Custom +declare global { + function getDotnetRuntime(runtimeId: number): RuntimeAPI | undefined; } +declare const createDotnetRuntime: CreateDotnetRuntimeType; -export { AssetEntry, CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, IMemoryView, ModuleAPI, MonoConfig, ResourceRequest, RuntimeAPI, BootJsonData, ICUDataMode, 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/Components/Web.JS/src/Boot.WebAssembly.Common.ts b/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts index f6d14414898a..895f52ff6724 100644 --- a/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts +++ b/src/Components/Web.JS/src/Boot.WebAssembly.Common.ts @@ -8,8 +8,7 @@ import * as Environment from './Environment'; import { BINDING, monoPlatform, dispatcher } from './Platform/Mono/MonoPlatform'; import { renderBatch, getRendererer, attachRootComponentToElement, attachRootComponentToLogicalElement } from './Rendering/Renderer'; import { SharedMemoryRenderBatch } from './Rendering/RenderBatch/SharedMemoryRenderBatch'; -import { WebAssemblyResourceLoader } from './Platform/WebAssemblyResourceLoader'; -import { Pointer } from './Platform/Platform'; +import { PlatformApi, Pointer } from './Platform/Platform'; import { WebAssemblyStartOptions } from './Platform/WebAssemblyStartOptions'; import { addDispatchEventMiddleware } from './Rendering/WebRendererInteropMethods'; import { JSInitializer } from './JSInitializers/JSInitializers'; @@ -107,21 +106,18 @@ export async function startWebAssembly(options?: Partial { - if (typeof Module !== 'undefined' && Module.printErr) { + if (typeof Module !== 'undefined' && Module.err) { // Logs it, and causes the error UI to appear - Module.printErr(error); + Module.err(error); } else { // The error must have happened so early we didn't yet set up the error UI, so just log to console console.error(error); diff --git a/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts b/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts deleted file mode 100644 index 0d97deb1ff5d..000000000000 --- a/src/Components/Web.JS/src/JSInitializers/JSInitializers.WebAssembly.ts +++ /dev/null @@ -1,19 +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 { BootJsonData } from 'dotnet'; -import { WebAssemblyStartOptions } from '../Platform/WebAssemblyStartOptions'; -import { JSInitializer } from './JSInitializers'; - -export async function fetchAndInvokeInitializers(bootConfig: BootJsonData, options: Partial): Promise { - const initializers = bootConfig.resources.libraryInitializers; - const jsInitializer = new JSInitializer(); - if (initializers) { - await jsInitializer.importInitializersAsync( - Object.keys(initializers), - [options, bootConfig.resources.extensions] - ); - } - - return jsInitializer; -} diff --git a/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts b/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts index 4cc1b035eefb..7cd81e392b4d 100644 --- a/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts +++ b/src/Components/Web.JS/src/JSInitializers/JSInitializers.ts @@ -2,16 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. import { Blazor } from '../GlobalExports'; -import { rendererAttached } from "../Rendering/WebRendererInteropMethods"; +import { rendererAttached } from '../Rendering/WebRendererInteropMethods'; type BeforeBlazorStartedCallback = (...args: unknown[]) => Promise; export type AfterBlazorStartedCallback = (blazor: typeof Blazor) => Promise; -type BlazorInitializer = { beforeStart: BeforeBlazorStartedCallback, afterStarted: AfterBlazorStartedCallback }; +export type BlazorInitializer = { beforeStart: BeforeBlazorStartedCallback, afterStarted: AfterBlazorStartedCallback }; export class JSInitializer { private afterStartedCallbacks: AfterBlazorStartedCallback[] = []; async importInitializersAsync(initializerFiles: string[], initializerArguments: unknown[]): Promise { + // This code is not called on WASM, because library intializers are imported by runtime. + await Promise.all(initializerFiles.map(f => importAndInvokeInitializer(this, f))); function adjustPath(path: string): string { diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoDebugger.ts b/src/Components/Web.JS/src/Platform/Mono/MonoDebugger.ts index 57cd33de460a..1c6798193376 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoDebugger.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoDebugger.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 { WebAssemblyResourceLoader } from '../WebAssemblyResourceLoader'; +import { MonoConfig } from 'dotnet'; const navigatorUA = navigator as MonoNavigatorUserAgent; const brands = navigatorUA.userAgentData && navigatorUA.userAgentData.brands; @@ -11,27 +11,22 @@ const currentBrowserIsChromeOrEdge = brands && brands.length > 0 : (window as any).chrome; const platform = navigatorUA.userAgentData?.platform ?? navigator.platform; -let hasReferencedPdbs = false; -let debugBuild = false; - -export function hasDebuggingEnabled(): boolean { - return (hasReferencedPdbs || debugBuild) && (currentBrowserIsChromeOrEdge || navigator.userAgent.includes('Firefox')); +function hasDebuggingEnabled(config: MonoConfig): boolean { + return config.debugLevel !== 0 && (currentBrowserIsChromeOrEdge || navigator.userAgent.includes('Firefox')); } -export function attachDebuggerHotkey(resourceLoader: WebAssemblyResourceLoader): void { - hasReferencedPdbs = !!resourceLoader.bootConfig.resources.pdb; - debugBuild = resourceLoader.bootConfig.debugBuild; +export function attachDebuggerHotkey(config: MonoConfig): void { // Use the combination shift+alt+D because it isn't used by the major browsers // for anything else by default const altKeyName = platform.match(/^Mac/i) ? 'Cmd' : 'Alt'; - if (hasDebuggingEnabled()) { + if (hasDebuggingEnabled(config)) { console.info(`Debugging hotkey: Shift+${altKeyName}+D (when application has focus)`); } // Even if debugging isn't enabled, we register the hotkey so we can report why it's not enabled document.addEventListener('keydown', evt => { if (evt.shiftKey && (evt.metaKey || evt.altKey) && evt.code === 'KeyD') { - if (!debugBuild && !hasReferencedPdbs) { + if (!hasDebuggingEnabled(config)) { console.error('Cannot start debugging, because the application was not compiled with debugging enabled.'); } else if (navigator.userAgent.includes('Firefox')) { launchFirefoxDebugger(); diff --git a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts index 4f465f62075c..5178842558a0 100644 --- a/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts +++ b/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts @@ -5,15 +5,13 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable no-prototype-builtins */ import { DotNet } from '@microsoft/dotnet-js-interop'; -import { attachDebuggerHotkey, hasDebuggingEnabled } from './MonoDebugger'; +import { attachDebuggerHotkey } from './MonoDebugger'; import { showErrorNotification } from '../../BootErrors'; -import { WebAssemblyResourceLoader, LoadingResource } from '../WebAssemblyResourceLoader'; import { Platform, System_Array, Pointer, System_Object, System_String, HeapLock, PlatformApi } from '../Platform'; import { WebAssemblyBootResourceType, WebAssemblyStartOptions } from '../WebAssemblyStartOptions'; import { Blazor } from '../../GlobalExports'; -import { DotnetModuleConfig, EmscriptenModule, MonoConfig, ModuleAPI, BootJsonData, ICUDataMode, RuntimeAPI } from 'dotnet'; +import { DotnetModuleConfig, EmscriptenModule, MonoConfig, ModuleAPI, RuntimeAPI, GlobalizationMode } from 'dotnet'; import { BINDINGType, MONOType } from 'dotnet/dotnet-legacy'; -import { fetchAndInvokeInitializers } from '../../JSInitializers/JSInitializers.WebAssembly'; // initially undefined and only fully initialized after createEmscriptenModuleInstance() export let BINDING: BINDINGType = undefined as any; @@ -28,8 +26,6 @@ const maxSafeNumberHighPart = Math.pow(2, 21) - 1; // The high-order int32 from let currentHeapLock: MonoHeapLock | null = null; -let applicationEnvironment = 'Production'; - // Memory access helpers // The implementations are exactly equivalent to what the global getValue(addr, type) function does, // except without having to parse the 'type' parameter, and with less risk of mistakes at the call site @@ -59,9 +55,9 @@ export const monoPlatform: Platform = { return createRuntimeInstance(options); }, - callEntryPoint: async function callEntryPoint(assemblyName: string): Promise { + callEntryPoint: async function callEntryPoint(): Promise { try { - await runtime.runMain(assemblyName, []); + await runtime.runMain(runtime.getConfig().mainAssemblyName!, []); } catch (error) { console.error(error); showErrorNotification(); @@ -150,63 +146,18 @@ export const monoPlatform: Platform = { }, }; -type LoadBootResourceCallback = (type: WebAssemblyBootResourceType, name: string, defaultUri: string, integrity: string) => string | Promise | null | undefined; - -async function loadBootConfigAsync(loadBootResource?: LoadBootResourceCallback, environment?: string): Promise { - const loaderResponse = loadBootResource !== undefined ? - loadBootResource('manifest', 'blazor.boot.json', '_framework/blazor.boot.json', '') : - defaultLoadBlazorBootJson('_framework/blazor.boot.json'); - - let bootConfigResponse: Response; - - if (!loaderResponse) { - bootConfigResponse = await defaultLoadBlazorBootJson('_framework/blazor.boot.json'); - } else if (typeof loaderResponse === 'string') { - bootConfigResponse = await defaultLoadBlazorBootJson(loaderResponse); - } else { - bootConfigResponse = await loaderResponse; - } - - // While we can expect an ASP.NET Core hosted application to include the environment, other - // hosts may not. Assume 'Production' in the absence of any specified value. - applicationEnvironment = environment || bootConfigResponse.headers.get('Blazor-Environment') || 'Production'; - const bootConfig: BootJsonData = await bootConfigResponse.json(); - bootConfig.modifiableAssemblies = bootConfigResponse.headers.get('DOTNET-MODIFIABLE-ASSEMBLIES'); - bootConfig.aspnetCoreBrowserTools = bootConfigResponse.headers.get('ASPNETCORE-BROWSER-TOOLS'); - - return bootConfig; - - function defaultLoadBlazorBootJson(url: string): Promise { - return fetch(url, { - method: 'GET', - credentials: 'include', - cache: 'no-cache', - }); - } -} - async function importDotnetJs(startOptions: Partial): Promise { const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate; if (!browserSupportsNativeWebAssembly) { throw new Error('This browser does not support WebAssembly.'); } - const bootConfig = await loadBootConfigAsync(startOptions.loadBootResource, startOptions.environment); - - // The dotnet.*.js file has a version or hash in its name as a form of cache-busting. This is needed - // because it's the only part of the loading process that can't use cache:'no-cache' (because it's - // not a 'fetch') and isn't controllable by the developer (so they can't put in their own cache-busting - // querystring). So, to find out the exact URL we have to search the boot manifest. - const dotnetJsResourceName = Object - .keys(bootConfig.resources.runtime) - .filter(n => n.startsWith('dotnet.') && n.endsWith('.js'))[0]; - const dotnetJsContentHash = bootConfig.resources.runtime[dotnetJsResourceName]; - let src = `_framework/${dotnetJsResourceName}`; + let src = '_framework/dotnet.js'; // Allow overriding the URI from which the dotnet.*.js file is loaded if (startOptions.loadBootResource) { const resourceType: WebAssemblyBootResourceType = 'dotnetjs'; - const customSrc = startOptions.loadBootResource(resourceType, dotnetJsResourceName, src, dotnetJsContentHash); + const customSrc = startOptions.loadBootResource(resourceType, 'dotnet.js', src, ''); if (typeof (customSrc) === 'string') { src = customSrc; } else if (customSrc) { @@ -215,86 +166,64 @@ async function importDotnetJs(startOptions: Partial): P } } - // For consistency with WebAssemblyResourceLoader, we only enforce SRI if caching is allowed - if (bootConfig.cacheBootResources) { - const scriptElem = document.createElement('link'); - scriptElem.rel = 'modulepreload'; - scriptElem.href = src; - scriptElem.crossOrigin = 'anonymous'; - // it will make dynamic import fail if the hash doesn't match - // It's currently only validated by chromium browsers - // Firefox doesn't break on it, but doesn't validate it either - scriptElem.integrity = dotnetJsContentHash; - document.head.appendChild(scriptElem); - } - const absoluteSrc = (new URL(src, document.baseURI)).toString(); return await import(/* webpackIgnore: true */ absoluteSrc); } -function prepareRuntimeConfig(options: Partial, platformApi: any): DotnetModuleConfig { +function prepareRuntimeConfig(options: Partial): DotnetModuleConfig { const config: MonoConfig = { maxParallelDownloads: 1000000, // disable throttling parallel downloads enableDownloadRetry: false, // disable retry downloads - applicationEnvironment: applicationEnvironment, + applicationEnvironment: options.environment, }; - const onConfigLoaded = async (bootConfig: BootJsonData & MonoConfig): Promise => { - if (!bootConfig.environmentVariables) { - bootConfig.environmentVariables = {}; - } - - if (bootConfig.icuDataMode === ICUDataMode.Sharded) { - bootConfig.environmentVariables['__BLAZOR_SHARDED_ICU'] = '1'; + const onConfigLoaded = async (loadedConfig: MonoConfig, { invokeLibraryInitializers }) => { + if (!loadedConfig.environmentVariables) { + loadedConfig.environmentVariables = {}; } - if (bootConfig.aspnetCoreBrowserTools) { - // See https://github.com/dotnet/aspnetcore/issues/37357#issuecomment-941237000 - bootConfig.environmentVariables['__ASPNETCORE_BROWSER_TOOLS'] = bootConfig.aspnetCoreBrowserTools; + if (loadedConfig.globalizationMode === GlobalizationMode.Sharded) { + loadedConfig.environmentVariables['__BLAZOR_SHARDED_ICU'] = '1'; } - Blazor._internal.getApplicationEnvironment = () => bootConfig.applicationEnvironment!; + Blazor._internal.getApplicationEnvironment = () => loadedConfig.applicationEnvironment!; - platformApi.jsInitializer = await fetchAndInvokeInitializers(bootConfig, options); + const initializerArguments = [options, loadedConfig.resources?.extensions ?? {}]; + await invokeLibraryInitializers('beforeStart', initializerArguments); }; const moduleConfig = (window['Module'] || {}) as typeof Module; // TODO (moduleConfig as any).preloadPlugins = []; // why do we need this ? const dotnetModuleConfig: DotnetModuleConfig = { ...moduleConfig, - onConfigLoaded, + onConfigLoaded: (onConfigLoaded as (config: MonoConfig) => void | Promise), onDownloadResourceProgress: setProgress, config, disableDotnet6Compatibility: false, - print, - printErr, + out: print, + err: printErr, }; return dotnetModuleConfig; } async function createRuntimeInstance(options: Partial): Promise { - const platformApi: Partial = {}; const { dotnet } = await importDotnetJs(options); - const moduleConfig = prepareRuntimeConfig(options, platformApi); + const moduleConfig = prepareRuntimeConfig(options); const anyDotnet = (dotnet as any); anyDotnet.withStartupOptions(options).withModuleConfig(moduleConfig); runtime = await dotnet.create(); - const { MONO: mono, BINDING: binding, Module: module, setModuleImports, INTERNAL: mono_internal } = runtime; + const { MONO: mono, BINDING: binding, Module: module, setModuleImports, INTERNAL: mono_internal, getConfig, invokeLibraryInitializers } = runtime; Module = module; BINDING = binding; MONO = mono; MONO_INTERNAL = mono_internal; - const resourceLoader = MONO_INTERNAL.resourceLoader; - platformApi.resourceLoader = resourceLoader; - attachDebuggerHotkey(resourceLoader); + attachDebuggerHotkey(getConfig()); Blazor._internal.dotNetCriticalError = printErr; - Blazor._internal.loadLazyAssembly = (assemblyNameToLoad) => loadLazyAssembly(MONO_INTERNAL.resourceLoader, assemblyNameToLoad); - Blazor._internal.loadSatelliteAssemblies = (culturesToLoad, loader) => loadSatelliteAssemblies(resourceLoader, culturesToLoad, loader); setModuleImports('blazor-internal', { Blazor: { _internal: Blazor._internal }, }); @@ -306,7 +235,9 @@ async function createRuntimeInstance(options: Partial): }); attachInteropInvoker(); - return platformApi as PlatformApi; + return { + invokeLibraryInitializers, + }; } function setProgress(resourcesLoaded, totalResources) { @@ -325,55 +256,6 @@ const printErr = line => { showErrorNotification(); }; -async function loadSatelliteAssemblies(resourceLoader: WebAssemblyResourceLoader, culturesToLoad: string[], loader: (wrapper: { dll: Uint8Array }) => void): Promise { - 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) }; - loader(wrapper); - })); -} - -async function loadLazyAssembly(resourceLoader: WebAssemblyResourceLoader, assemblyNameToLoad: string): Promise<{ dll: Uint8Array, pdb: Uint8Array | null }> { - 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() && resources.pdb && lazyAssemblies.hasOwnProperty(pdbNameToLoad); - - const dllBytesPromise = resourceLoader.loadResource(dllNameToLoad, `_framework/${dllNameToLoad}`, lazyAssemblies[dllNameToLoad], 'assembly').response.then(response => response.arrayBuffer()); - 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 getArrayDataPointer(array: System_Array): number { return array + 12; // First byte from here is length, then following bytes are entries } @@ -416,15 +298,6 @@ function attachInteropInvoker(): void { }); } -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; -} - function assertHeapIsNotLocked() { if (currentHeapLock) { throw new Error('Assertion failed - heap is currently locked'); diff --git a/src/Components/Web.JS/src/Platform/Platform.ts b/src/Components/Web.JS/src/Platform/Platform.ts index 10f42ff15a1f..dbe5523c1796 100644 --- a/src/Components/Web.JS/src/Platform/Platform.ts +++ b/src/Components/Web.JS/src/Platform/Platform.ts @@ -3,13 +3,11 @@ import { MonoObject, MonoString, MonoArray } from 'dotnet/dotnet-legacy'; import { WebAssemblyStartOptions } from './WebAssemblyStartOptions'; -import { WebAssemblyResourceLoader } from './WebAssemblyResourceLoader'; -import { JSInitializer } from '../JSInitializers/JSInitializers'; export interface Platform { start(options: Partial): Promise; - callEntryPoint(assemblyName: string): Promise; + callEntryPoint(): Promise; toUint8Array(array: System_Array): Uint8Array; @@ -30,8 +28,7 @@ export interface Platform { } export type PlatformApi = { - resourceLoader: WebAssemblyResourceLoader, - jsInitializer: JSInitializer + invokeLibraryInitializers(functionName: string, args: unknown[]): Promise; } export interface HeapLock { diff --git a/src/Components/Web.JS/src/Platform/WebAssemblyResourceLoader.ts b/src/Components/Web.JS/src/Platform/WebAssemblyResourceLoader.ts deleted file mode 100644 index ca9d588a30ad..000000000000 --- a/src/Components/Web.JS/src/Platform/WebAssemblyResourceLoader.ts +++ /dev/null @@ -1,19 +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 { BootJsonData } from 'dotnet'; -import { WebAssemblyBootResourceType } from './WebAssemblyStartOptions'; - -export interface WebAssemblyResourceLoader { - readonly bootConfig: BootJsonData; - loadResources(resources: ResourceList, url: (name: string) => string, resourceType: WebAssemblyBootResourceType): LoadingResource[]; - loadResource(name: string, url: string, contentHash: string, resourceType: WebAssemblyBootResourceType): LoadingResource; -} - -export type ResourceList = { [name: string]: string }; - -export interface LoadingResource { - name: string; - url: string; - response: Promise; -} diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs index dcf08f581bf5..060bd827b93f 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs @@ -4,8 +4,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.InteropServices.JavaScript; -using System.Runtime.Loader; -using System.Runtime.Versioning; namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting; @@ -71,16 +69,7 @@ public virtual async ValueTask LoadCurrentCultureResourcesAsync() return; } - await WebAssemblyCultureProviderInterop.LoadSatelliteAssemblies(culturesToLoad, LoadSatelliteAssembly); - } - - [SupportedOSPlatform("browser")] - private void LoadSatelliteAssembly(JSObject wrapper) - { - var dllBytes = wrapper.GetPropertyAsByteArray("dll")!; - using var stream = new MemoryStream(dllBytes); - AssemblyLoadContext.Default.LoadFromStream(stream); - wrapper.Dispose(); + await WebAssemblyCultureProviderInterop.LoadSatelliteAssemblies(culturesToLoad); } internal static string[] GetCultures(CultureInfo cultureInfo) @@ -109,8 +98,7 @@ internal static string[] GetCultures(CultureInfo cultureInfo) private partial class WebAssemblyCultureProviderInterop { - [JSImport("Blazor._internal.loadSatelliteAssemblies", "blazor-internal")] - public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad, - [JSMarshalAs>] Action assemblyLoader); + [JSImport("INTERNAL.loadSatelliteAssemblies")] + public static partial Task LoadSatelliteAssemblies(string[] culturesToLoad); } } diff --git a/src/Components/WebAssembly/WebAssembly/src/Services/LazyAssemblyLoader.cs b/src/Components/WebAssembly/WebAssembly/src/Services/LazyAssemblyLoader.cs index 3cff52948eb1..16a5ce5cb302 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Services/LazyAssemblyLoader.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Services/LazyAssemblyLoader.cs @@ -18,8 +18,6 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Services; /// public sealed partial class LazyAssemblyLoader { - private HashSet? _loadedAssemblyCache; - /// /// Initializes a new instance of . /// @@ -68,67 +66,42 @@ private static Task> LoadAssembliesInServerAsync(IEnumerab [RequiresUnreferencedCode("Types and members the loaded assemblies depend on might be removed")] [SupportedOSPlatform("browser")] - private async Task> LoadAssembliesInClientAsync(IEnumerable assembliesToLoad) + private static async Task> LoadAssembliesInClientAsync(IEnumerable assembliesToLoad) { - if (_loadedAssemblyCache is null) - { - var loadedAssemblyCache = new HashSet(StringComparer.Ordinal); - var appDomainAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - for (var i = 0; i < appDomainAssemblies.Length; i++) - { - var assembly = appDomainAssemblies[i]; - loadedAssemblyCache.Add(assembly.GetName().Name + ".dll"); - } + var newAssembliesToLoad = assembliesToLoad.ToList(); + var loadedAssemblies = new List(); + var pendingLoads = newAssembliesToLoad.Select(LazyAssemblyLoaderInterop.LoadLazyAssembly); - _loadedAssemblyCache = loadedAssemblyCache; - } + var loadedStatus = await Task.WhenAll(pendingLoads); + int i = 0; - // Check to see if the assembly has already been loaded and avoids reloading it if so. - // Note: in the future, as an extra precaution, we can call `Assembly.Load` and check - // to see if it throws FileNotFound to ensure that an assembly hasn't been loaded - // between when the cache of loaded assemblies was instantiated in the constructor - // and the invocation of this method. - var newAssembliesToLoad = new List(); - foreach (var assemblyToLoad in assembliesToLoad) + List? allAssemblies = null; + foreach (var loaded in loadedStatus) { - if (!_loadedAssemblyCache.Contains(assemblyToLoad)) + if (loaded) { - newAssembliesToLoad.Add(assemblyToLoad); + if (allAssemblies == null) + { + allAssemblies = AssemblyLoadContext.Default.Assemblies.ToList(); + } + + var assemblyName = Path.GetFileNameWithoutExtension(newAssembliesToLoad[i]); + var assembly = AssemblyLoadContext.Default.Assemblies.FirstOrDefault(a => a.GetName().Name == assemblyName); + if (assembly != null) + { + loadedAssemblies.Add(assembly); + } } - } - if (newAssembliesToLoad.Count == 0) - { - return Array.Empty(); + i++; } - var loadedAssemblies = new List(); - var pendingLoads = newAssembliesToLoad.Select(assemblyToLoad => LoadAssembly(assemblyToLoad, loadedAssemblies)); - - await Task.WhenAll(pendingLoads); return loadedAssemblies; } - [RequiresUnreferencedCode("Types and members the loaded assemblies depend on might be removed")] - [SupportedOSPlatform("browser")] - private async Task LoadAssembly(string assemblyToLoad, 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); - - } - private partial class LazyAssemblyLoaderInterop { - [JSImport("Blazor._internal.loadLazyAssembly", "blazor-internal")] - public static partial Task LoadLazyAssembly(string assemblyToLoad); + [JSImport("INTERNAL.loadLazyAssembly")] + public static partial Task LoadLazyAssembly(string assemblyToLoad); } }