diff --git a/src/mono/browser/runtime/dotnet.d.ts b/src/mono/browser/runtime/dotnet.d.ts index 958a2ebf543c33..be1d1445d355e3 100644 --- a/src/mono/browser/runtime/dotnet.d.ts +++ b/src/mono/browser/runtime/dotnet.d.ts @@ -115,6 +115,10 @@ interface DotnetHostBuilder { * Sets a callback that is invoked after each resource finishes downloading. */ withDownloadResourceProgress(callback?: (resourcesLoaded: number, totalResources: number) => void): DotnetHostBuilder; + /** + * Sets a callback that is invoked after the runtime configuration is loaded. + */ + withConfigLoaded(callback?: (config: MonoConfig) => void | Promise): 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. @@ -446,9 +450,6 @@ declare const enum GlobalizationMode { } type DotnetModuleConfig = { config?: MonoConfig; - onConfigLoaded?: (config: MonoConfig) => void | Promise; - onDotnetReady?: () => void | Promise; - onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; imports?: any; exports?: string[]; } & Partial; diff --git a/src/mono/browser/runtime/loader/assets.ts b/src/mono/browser/runtime/loader/assets.ts index b2fc92a80891c4..b499bcc6392c75 100644 --- a/src/mono/browser/runtime/loader/assets.ts +++ b/src/mono/browser/runtime/loader/assets.ts @@ -12,6 +12,7 @@ import { mono_exit } from "./exit"; import { getIcuResourceName } from "./icu"; import { makeURLAbsoluteWithApplicationBase } from "./polyfills"; import { mono_log_info } from "./logging"; +import { loaderCallbacks } from "./callbacks"; let throttlingPromise: PromiseAndController | undefined; @@ -628,8 +629,7 @@ function download_resource (asset: AssetEntryInternal): LoadingResource { } resourcesLoaded++; - if (loaderHelpers.onDownloadResourceProgress) - loaderHelpers.onDownloadResourceProgress(resourcesLoaded, totalResources.size); + loaderCallbacks.downloadResourceProgress?.(resourcesLoaded, totalResources.size); }); return response; } catch (err) { diff --git a/src/mono/browser/runtime/loader/callbacks.ts b/src/mono/browser/runtime/loader/callbacks.ts new file mode 100644 index 00000000000000..63984a8aa40baa --- /dev/null +++ b/src/mono/browser/runtime/loader/callbacks.ts @@ -0,0 +1,40 @@ +// 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, RuntimeAPI } from "../types"; + +export const loaderCallbacks: { + configLoaded?: (config: MonoConfig, api: RuntimeAPI) => void | Promise; + dotnetReady?: () => void | Promise; + downloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; +} = {}; + +type LegacyModuleCallbacks = { + onConfigLoaded?: (config: MonoConfig, api: RuntimeAPI) => void | Promise; + onDotnetReady?: () => void | Promise; + onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; +}; + +/** + * Moves the legacy `Module` callbacks into the loader state, so that they are never stored on the Emscripten module. + * Returns a copy of the configuration without those callbacks, which can be merged into the Emscripten module. + */ +export function extractLegacyModuleCallbacks (moduleConfig: T): T { + const legacy = moduleConfig as LegacyModuleCallbacks; + if (!legacy || (legacy.onConfigLoaded === undefined && legacy.onDotnetReady === undefined && legacy.onDownloadResourceProgress === undefined)) { + return moduleConfig; + } + + const { onConfigLoaded, onDotnetReady, onDownloadResourceProgress, ...rest } = moduleConfig as T & LegacyModuleCallbacks; + if (onConfigLoaded !== undefined) { + loaderCallbacks.configLoaded = onConfigLoaded; + } + if (onDotnetReady !== undefined) { + loaderCallbacks.dotnetReady = onDotnetReady; + } + if (onDownloadResourceProgress !== undefined) { + loaderCallbacks.downloadResourceProgress = onDownloadResourceProgress; + } + + return rest as unknown as T; +} diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index 13a58da1a58132..a90a5a52b728a9 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -11,6 +11,7 @@ import { mono_log_error, mono_log_debug } from "./logging"; import { importLibraryInitializers, invokeLibraryInitializers } from "./libraryInitializers"; import { mono_exit } from "./exit"; import { browserVirtualAppBase } from "./globals"; +import { loaderCallbacks } from "./callbacks"; export function deep_merge_config (target: MonoConfigInternal, source: MonoConfigInternal): MonoConfigInternal { // no need to merge the same object @@ -240,14 +241,11 @@ export async function mono_wasm_load_config (module: DotnetModuleInternal): Prom await importLibraryInitializers(loaderHelpers.config.resources?.modulesAfterConfigLoaded); await invokeLibraryInitializers("onRuntimeConfigLoaded", [loaderHelpers.config]); - if (module.onConfigLoaded) { - try { - await module.onConfigLoaded(loaderHelpers.config, exportedRuntimeAPI); - normalizeConfig(); - } catch (err: any) { - mono_log_error("onConfigLoaded() failed", err); - throw err; - } + try { + await loaderCallbacks.configLoaded?.(loaderHelpers.config, exportedRuntimeAPI); + } catch (err: any) { + mono_log_error("onConfigLoaded() failed", err); + throw err; } normalizeConfig(); @@ -268,4 +266,3 @@ export function isDebuggingSupported (): boolean { return loaderHelpers.isChromium || loaderHelpers.isFirefox; } - diff --git a/src/mono/browser/runtime/loader/polyfills.ts b/src/mono/browser/runtime/loader/polyfills.ts index ff843a229f2d51..addce1137d4fae 100644 --- a/src/mono/browser/runtime/loader/polyfills.ts +++ b/src/mono/browser/runtime/loader/polyfills.ts @@ -3,7 +3,6 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; -import type { DotnetModuleInternal } from "../types/internal"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, loaderHelpers, ENVIRONMENT_IS_WEB, mono_assert } from "./globals"; let node_fs: any | undefined = undefined; @@ -34,7 +33,7 @@ export function verifyEnvironment () { } } -export async function detect_features_and_polyfill (module: DotnetModuleInternal): Promise { +export async function detect_features_and_polyfill (): Promise { if (ENVIRONMENT_IS_SHELL) { if (typeof globalThis.atob !== "function") { const b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -81,8 +80,6 @@ export async function detect_features_and_polyfill (module: DotnetModuleInternal loaderHelpers.out = console.log; // eslint-disable-next-line no-console loaderHelpers.err = console.error; - loaderHelpers.onDownloadResourceProgress = module.onDownloadResourceProgress; - if (ENVIRONMENT_IS_WEB && globalThis.navigator) { const navigator: any = globalThis.navigator; const brands = navigator.userAgentData && navigator.userAgentData.brands; diff --git a/src/mono/browser/runtime/loader/run.ts b/src/mono/browser/runtime/loader/run.ts index 6bf993b19e267c..f1eeb47bdae5a9 100644 --- a/src/mono/browser/runtime/loader/run.ts +++ b/src/mono/browser/runtime/loader/run.ts @@ -16,6 +16,7 @@ import { runtimeHelpers, loaderHelpers } from "./globals"; import { init_globalization } from "./icu"; import { setupPreloadChannelToMainThread } from "./worker"; import { importLibraryInitializers, invokeLibraryInitializers } from "./libraryInitializers"; +import { extractLegacyModuleCallbacks, loaderCallbacks } from "./callbacks"; export class HostBuilder implements DotnetHostBuilder { @@ -24,7 +25,7 @@ export class HostBuilder implements DotnetHostBuilder { // internal withModuleConfig (moduleConfig: DotnetModuleConfig): DotnetHostBuilder { try { - deep_merge_module(emscriptenModule, moduleConfig); + deep_merge_module(emscriptenModule, extractLegacyModuleCallbacks(moduleConfig)); return this; } catch (err) { mono_exit(1, err); @@ -217,9 +218,27 @@ export class HostBuilder implements DotnetHostBuilder { withDownloadResourceProgress (callback?: (resourcesLoaded: number, totalResources: number) => void): DotnetHostBuilder { try { - deep_merge_module(emscriptenModule, { - onDownloadResourceProgress: callback - }); + loaderCallbacks.downloadResourceProgress = callback; + return this; + } catch (err) { + mono_exit(1, err); + throw err; + } + } + + withConfigLoaded (callback?: (config: MonoConfig) => void | Promise): DotnetHostBuilder { + try { + loaderCallbacks.configLoaded = callback; + return this; + } catch (err) { + mono_exit(1, err); + throw err; + } + } + + withDotnetReady (callback?: () => void | Promise): DotnetHostBuilder { + try { + loaderCallbacks.dotnetReady = callback; return this; } catch (err) { mono_exit(1, err); @@ -320,7 +339,7 @@ async function prepareEmscripten (moduleFactory: DotnetModuleConfig | ((api: Run throw new Error("Can't use moduleFactory callback of createDotnetRuntime function."); } - await detect_features_and_polyfill(emscriptenModule); + await detect_features_and_polyfill(); } export async function createEmscripten (moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)): Promise { @@ -474,7 +493,7 @@ async function createEmscriptenWorker (): Promise { setup_proxy_console("main", globalThis.console, globalThis.location.origin); } - await detect_features_and_polyfill(emscriptenModule); + await detect_features_and_polyfill(); await mono_download_assets(); diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index cd0263a7cb80be..8a075665c966d5 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -21,6 +21,7 @@ import { replace_linker_placeholders } from "./exports-binding"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; import { interp_pgo_load_data, interp_pgo_save_data } from "./interp-pgo"; import { mono_log_debug, mono_log_error, mono_log_info, mono_log_warn } from "./logging"; +import { loaderCallbacks } from "./loader/callbacks"; // threads import { populateEmscriptenPool, mono_wasm_init_threads } from "./pthreads"; @@ -356,13 +357,11 @@ export function postRunWorker () { async function mono_wasm_after_user_runtime_initialized (): Promise { mono_log_debug("mono_wasm_after_user_runtime_initialized"); try { - if (Module.onDotnetReady) { - try { - await Module.onDotnetReady(); - } catch (err: any) { - mono_log_error("onDotnetReady () failed", err); - throw err; - } + try { + await loaderCallbacks.dotnetReady?.(); + } catch (err: any) { + mono_log_error("onDotnetReady () failed", err); + throw err; } } catch (err: any) { mono_log_error("mono_wasm_after_user_runtime_initialized () failed", err); diff --git a/src/mono/browser/runtime/types/index.ts b/src/mono/browser/runtime/types/index.ts index e268651744ccb2..71a470df520b6b 100644 --- a/src/mono/browser/runtime/types/index.ts +++ b/src/mono/browser/runtime/types/index.ts @@ -63,6 +63,10 @@ export interface DotnetHostBuilder { * Sets a callback that is invoked after each resource finishes downloading. */ withDownloadResourceProgress(callback?: (resourcesLoaded: number, totalResources: number) => void): DotnetHostBuilder; + /** + * Sets a callback that is invoked after the runtime configuration is loaded. + */ + withConfigLoaded(callback?: (config: MonoConfig) => void | Promise): 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. @@ -475,10 +479,6 @@ export const enum GlobalizationMode { export type DotnetModuleConfig = { config?: MonoConfig, - onConfigLoaded?: (config: MonoConfig) => void | Promise; - onDotnetReady?: () => void | Promise; - onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; - imports?: any; exports?: string[]; } & Partial diff --git a/src/mono/browser/runtime/types/internal.ts b/src/mono/browser/runtime/types/internal.ts index 657203e787b9a1..4ac76c490e4b7a 100644 --- a/src/mono/browser/runtime/types/internal.ts +++ b/src/mono/browser/runtime/types/internal.ts @@ -165,7 +165,6 @@ export type LoaderHelpers = { err(message: string): void; retrieve_asset_download(asset: AssetEntry): Promise; - onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; installUnhandledErrorHandler: () => void; loadBootResource?: LoadBootResourceCallback; @@ -445,7 +444,6 @@ export declare interface EmscriptenModuleInternal { getWasmTableEntry(index: number): any; removeRunDependency(id: string): void; addRunDependency(id: string): void; - onConfigLoaded?: (config: MonoConfig, api: RuntimeAPI) => void | Promise; safeSetTimeout(func: Function, timeout: number): number; runtimeKeepalivePush(): void; runtimeKeepalivePop(): void; diff --git a/src/mono/browser/test-main.mjs b/src/mono/browser/test-main.mjs index b256d8bba5ab56..b07c7f92e76ed4 100644 --- a/src/mono/browser/test-main.mjs +++ b/src/mono/browser/test-main.mjs @@ -271,10 +271,8 @@ function configureRuntime(dotnet, runArgs) { const modulesToLoad = runArgs.environmentVariables["NPM_MODULES"]; if (modulesToLoad) { - dotnet.withModuleConfig({ - onConfigLoaded: async (config) => { - await loadNodeModules(config, modulesToLoad) - } + dotnet.withConfigLoaded(async (config) => { + await loadNodeModules(config, modulesToLoad) }) } } diff --git a/src/mono/sample/wasm/browser-advanced/wwwroot/main.js b/src/mono/sample/wasm/browser-advanced/wwwroot/main.js index 14584da8619a57..fa24146c47653a 100644 --- a/src/mono/sample/wasm/browser-advanced/wwwroot/main.js +++ b/src/mono/sample/wasm/browser-advanced/wwwroot/main.js @@ -44,17 +44,21 @@ try { }] } }) + .withConfigLoaded((config) => { + // This is called during emscripten `dotnet.wasm` instantiation, after we fetched config. + console.log('user code onConfigLoaded'); + // config is loaded and could be tweaked before the rest of the runtime startup sequence + config.environmentVariables["MONO_LOG_LEVEL"] = "debug"; + config.browserProfilerOptions = { + sampleIntervalMs: 5.15, + callSpec: "N:Sample" // needs to match AOT profile + }; + }) + .withDotnetReady(() => { + // This is called after all assets are loaded. + console.log('user code onDotnetReady'); + }) .withModuleConfig({ - onConfigLoaded: (config) => { - // This is called during emscripten `dotnet.wasm` instantiation, after we fetched config. - console.log('user code Module.onConfigLoaded'); - // config is loaded and could be tweaked before the rest of the runtime startup sequence - config.environmentVariables["MONO_LOG_LEVEL"] = "debug"; - config.browserProfilerOptions = { - sampleIntervalMs: 5.15, - callSpec: "N:Sample" // needs to match AOT profile - }; - }, preInit: [() => { console.log('user code Module.preInit'); }], preRun: [() => { console.log('user code Module.preRun'); }], onRuntimeInitialized: () => { @@ -62,10 +66,6 @@ try { // here we could use API passed into this callback // Module.FS.chdir("/"); }, - onDotnetReady: () => { - // This is called after all assets are loaded. - console.log('user code Module.onDotnetReady'); - }, postRun: [() => { console.log('user code Module.postRun'); }], out: (text) => { console.log("ADVANCED:" + text) }, }) diff --git a/src/mono/sample/wasm/browser-shutdown/wwwroot/main.js b/src/mono/sample/wasm/browser-shutdown/wwwroot/main.js index 4f45b487cbf8ac..139b22851d001a 100644 --- a/src/mono/sample/wasm/browser-shutdown/wwwroot/main.js +++ b/src/mono/sample/wasm/browser-shutdown/wwwroot/main.js @@ -11,13 +11,11 @@ try { const { setModuleImports, getAssemblyExports, getConfig } = await dotnet .withModuleConfig() .withConfig({ appendElementOnExit: true, exitOnUnhandledError: true, logExitCode: true }) - .withModuleConfig({ - onConfigLoaded: () => { - // you can test abort of the startup by opening http://localhost:8000/?throwError=true - const params = new URLSearchParams(location.search); - if (params.get("throwError") === "true") { - throw new Error("Error thrown from OnConfigLoaded"); - } + .withConfigLoaded(() => { + // you can test abort of the startup by opening http://localhost:8000/?throwError=true + const params = new URLSearchParams(location.search); + if (params.get("throwError") === "true") { + throw new Error("Error thrown from OnConfigLoaded"); } }) .create(); diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js index de733e4122c460..a4616938267b3c 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js @@ -154,11 +154,9 @@ switch (testCase) { break; case "DownloadThenInit": let dtConfigLoadedCalled = false; - dotnet.withModuleConfig({ - onConfigLoaded: () => { - dtConfigLoadedCalled = true; - testOutput("onConfigLoaded called"); - } + dotnet.withConfigLoaded(() => { + dtConfigLoadedCalled = true; + testOutput("onConfigLoaded called"); }); const originalFetch = globalThis.fetch; globalThis.fetch = (url, fetchArgs) => { @@ -179,11 +177,9 @@ switch (testCase) { loadBootResourceCalled = true; return defaultUri; }); - dotnet.withModuleConfig({ - onConfigLoaded: () => { - hcConfigLoadedCalled = true; - testOutput("onConfigLoaded called"); - } + dotnet.withConfigLoaded(() => { + hcConfigLoadedCalled = true; + testOutput("onConfigLoaded called"); }); const originalFetch3 = globalThis.fetch; globalThis.fetch = (url, fetchArgs) => { @@ -254,23 +250,21 @@ switch (testCase) { break; case "BufferedAssetsTest": const originalFetch4 = globalThis.fetch.bind(globalThis); - dotnet.withModuleConfig({ - onConfigLoaded: (config) => { - const bufferedAssets = [ - ...config.resources.wasmNative, - ...config.resources.coreAssembly, - ...config.resources.assembly, - ...(config.resources.corePdb ?? []), - ...(config.resources.pdb ?? []), - ...config.resources.wasmSymbols, - ]; - for (const asset of bufferedAssets) { - const url = new URL(asset.resolvedUrl ?? `./_framework/${asset.name}`, location.href); - asset.buffer = originalFetch4(url).then(r => { - if (!r.ok) throw new Error(`Failed to fetch buffered asset '${url}': ${r.status} ${r.statusText}`); - return r.arrayBuffer(); - }); - } + dotnet.withConfigLoaded((config) => { + const bufferedAssets = [ + ...config.resources.wasmNative, + ...config.resources.coreAssembly, + ...config.resources.assembly, + ...(config.resources.corePdb ?? []), + ...(config.resources.pdb ?? []), + ...config.resources.wasmSymbols, + ]; + for (const asset of bufferedAssets) { + const url = new URL(asset.resolvedUrl ?? `./_framework/${asset.name}`, location.href); + asset.buffer = originalFetch4(url).then(r => { + if (!r.ok) throw new Error(`Failed to fetch buffered asset '${url}': ${r.status} ${r.statusText}`); + return r.arrayBuffer(); + }); } }); break; diff --git a/src/native/libs/Common/JavaScript/loader/assets.ts b/src/native/libs/Common/JavaScript/loader/assets.ts index eaf4508a140905..02e4bd7c96bd94 100644 --- a/src/native/libs/Common/JavaScript/loader/assets.ts +++ b/src/native/libs/Common/JavaScript/loader/assets.ts @@ -3,12 +3,13 @@ import type { JsModuleExports, JsAsset, AssemblyAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal, WebAssemblyBootResourceType, AssetEntryInternal, PromiseCompletionSource, LoadBootResourceCallback, InstantiateWasmSuccessCallback, SymbolsAsset, AssetBehaviors, VfsAsset } from "./types"; -import { dotnetAssert, dotnetLogger, dotnetInternals, dotnetBrowserHostExports, dotnetUpdateInternals, Module, dotnetDiagnosticsExports, dotnetNativeBrowserExports, dotnetApi } from "./cross-module"; +import { dotnetAssert, dotnetLogger, dotnetInternals, dotnetBrowserHostExports, dotnetUpdateInternals, dotnetDiagnosticsExports, dotnetNativeBrowserExports, dotnetApi } from "./cross-module"; import { ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB, browserVirtualAppBase } from "./per-module"; import { createPromiseCompletionSource, delay } from "./promise-completion-source"; import { locateFile, makeURLAbsoluteWithApplicationBase } from "./bootstrap"; import { fetchLike, responseLike } from "./polyfills"; import { loaderConfig } from "./config"; +import { loaderCallbacks } from "./callbacks"; let throttlingPCS: PromiseCompletionSource | undefined; let currentParallelDownloads = 0; @@ -551,9 +552,7 @@ function onDownloadedAsset(asset: AssetEntryInternal): void { finishThrottling(asset); } ++downloadedAssetsCount; - if (Module.onDownloadResourceProgress) { - Module.onDownloadResourceProgress(downloadedAssetsCount, totalAssetsToDownload); - } + loaderCallbacks.downloadResourceProgress?.(downloadedAssetsCount, totalAssetsToDownload); // release memory asset.buffer = null!; asset.pendingDownload = undefined; diff --git a/src/native/libs/Common/JavaScript/loader/callbacks.ts b/src/native/libs/Common/JavaScript/loader/callbacks.ts new file mode 100644 index 00000000000000..62ae168f9652fd --- /dev/null +++ b/src/native/libs/Common/JavaScript/loader/callbacks.ts @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import type { LoaderConfig } from "./types"; + +export const loaderCallbacks: { + configLoaded?: (config: LoaderConfig) => void | Promise; + dotnetReady?: () => void | Promise; + downloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; +} = {}; + +type LegacyModuleCallbacks = { + onConfigLoaded?: (config: LoaderConfig) => void | Promise; + onDotnetReady?: () => void | Promise; + onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; +}; + +/** + * Moves the legacy `Module` callbacks into the loader state, so that they are never stored on the Emscripten module. + * Returns a copy of the configuration without those callbacks, which can be merged into the Emscripten module. + */ +export function extractLegacyModuleCallbacks(moduleConfig: T): T { + const legacy = moduleConfig as LegacyModuleCallbacks; + if (!legacy || (legacy.onConfigLoaded === undefined && legacy.onDotnetReady === undefined && legacy.onDownloadResourceProgress === undefined)) { + return moduleConfig; + } + + const { onConfigLoaded, onDotnetReady, onDownloadResourceProgress, ...rest } = moduleConfig as T & LegacyModuleCallbacks; + if (onConfigLoaded !== undefined) { + loaderCallbacks.configLoaded = onConfigLoaded; + } + if (onDotnetReady !== undefined) { + loaderCallbacks.dotnetReady = onDotnetReady; + } + if (onDownloadResourceProgress !== undefined) { + loaderCallbacks.downloadResourceProgress = onDownloadResourceProgress; + } + + return rest as unknown as T; +} diff --git a/src/native/libs/Common/JavaScript/loader/dotnet.d.ts b/src/native/libs/Common/JavaScript/loader/dotnet.d.ts index f9856d0eca1c3f..4220f6abea87fa 100644 --- a/src/native/libs/Common/JavaScript/loader/dotnet.d.ts +++ b/src/native/libs/Common/JavaScript/loader/dotnet.d.ts @@ -103,6 +103,10 @@ interface DotnetHostBuilder { * Sets a callback that is invoked after each resource finishes downloading. */ withDownloadResourceProgress(callback?: (resourcesLoaded: number, totalResources: number) => void): DotnetHostBuilder; + /** + * Sets a callback that is invoked after the runtime configuration is loaded. + */ + withConfigLoaded(callback?: (config: LoaderConfig) => void | Promise): 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. @@ -427,9 +431,6 @@ declare const enum GlobalizationMode { } type DotnetModuleConfig = { config?: LoaderConfig; - onConfigLoaded?: (config: LoaderConfig) => void | Promise; - onDotnetReady?: () => void | Promise; - onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; imports?: any; exports?: string[]; } & Partial; diff --git a/src/native/libs/Common/JavaScript/loader/host-builder.ts b/src/native/libs/Common/JavaScript/loader/host-builder.ts index 3f044dfc71c9c1..62b999d16e3c82 100644 --- a/src/native/libs/Common/JavaScript/loader/host-builder.ts +++ b/src/native/libs/Common/JavaScript/loader/host-builder.ts @@ -4,6 +4,7 @@ import type { DotnetHostBuilder, LoaderConfig, RuntimeAPI, LoadBootResourceCallback, DotnetModuleConfig } from "./types"; import { Module, dotnetApi } from "./cross-module"; +import { extractLegacyModuleCallbacks, loaderCallbacks } from "./callbacks"; import { loaderConfig, mergeLoaderConfig, validateLoaderConfig } from "./config"; import { createRuntime } from "./run"; import { exit } from "./exit"; @@ -92,7 +93,15 @@ export class HostBuilder implements DotnetHostBuilder { return this; } withDownloadResourceProgress(callback?: (resourcesLoaded: number, totalResources: number) => void): DotnetHostBuilder { - Module.onDownloadResourceProgress = callback; + loaderCallbacks.downloadResourceProgress = callback; + return this; + } + withConfigLoaded(callback?: (config: LoaderConfig) => void | Promise): DotnetHostBuilder { + loaderCallbacks.configLoaded = callback; + return this; + } + withDotnetReady(callback?: () => void | Promise): DotnetHostBuilder { + loaderCallbacks.dotnetReady = callback; return this; } withResourceLoader(loadBootResource?: LoadBootResourceCallback): DotnetHostBuilder { @@ -102,7 +111,7 @@ export class HostBuilder implements DotnetHostBuilder { // internal withModuleConfig(moduleConfig: DotnetModuleConfig): DotnetHostBuilder { - Object.assign(Module, moduleConfig); + Object.assign(Module, extractLegacyModuleCallbacks(moduleConfig)); return this; } diff --git a/src/native/libs/Common/JavaScript/loader/run.ts b/src/native/libs/Common/JavaScript/loader/run.ts index 6604334d3ce51e..1d03b3989472da 100644 --- a/src/native/libs/Common/JavaScript/loader/run.ts +++ b/src/native/libs/Common/JavaScript/loader/run.ts @@ -11,6 +11,7 @@ import { loaderConfig, validateLoaderConfig } from "./config"; import { fetchAssembly, fetchIcu, fetchNativeSymbols, fetchPdb, fetchSatelliteAssemblies, fetchVfs, fetchMainWasm, loadDotnetModule, loadJSModule, nativeModulePromiseController, verifyAllAssetsDownloaded, callLibraryInitializerOnRuntimeReady, callLibraryInitializerOnRuntimeConfigLoaded, prefetchAllResources, prefetchJSModuleLinks, resolveAllDownloadsQueued } from "./assets"; import { initPolyfillsLoader } from "./polyfills"; import { validateEngineFeatures } from "./bootstrap"; +import { loaderCallbacks } from "./callbacks"; const runMainPromiseController = createPromiseCompletionSource(); @@ -44,9 +45,7 @@ export async function createRuntime(downloadOnly: boolean, httpCacheOnly: boolea Module.runtimeKeepalivePush(); await initializeCoreCLR(); - if (typeof Module.onDotnetReady === "function") { - await Module.onDotnetReady(); - } + await loaderCallbacks.dotnetReady?.(); const resources = loaderConfig.resources; // modulesAfterRuntimeReady were only prefetched during download(), now load and call onRuntimeReady. @@ -58,15 +57,13 @@ export async function createRuntime(downloadOnly: boolean, httpCacheOnly: boolea const resources = loaderConfig.resources; - // Run config initialization once: onConfigLoaded, modulesAfterConfigLoaded, polyfills. + // Run config initialization once: callbacks, modulesAfterConfigLoaded, polyfills. // This must happen before any asset fetches so that URL overrides take effect. let modulesAfterConfigLoadedPromises: [JsAsset, Promise][] = []; if (!configInitialized) { await validateEngineFeatures(); - if (typeof Module.onConfigLoaded === "function") { - await Module.onConfigLoaded(loaderConfig); - } + await loaderCallbacks.configLoaded?.(loaderConfig); validateLoaderConfig(); modulesAfterConfigLoadedPromises = normalizeCollection(resources.modulesAfterConfigLoaded).map((a) => [a, callLibraryInitializerOnRuntimeConfigLoaded(a)]); @@ -89,7 +86,7 @@ export async function createRuntime(downloadOnly: boolean, httpCacheOnly: boolea Module.printErr = Module.err; } - // after onConfigLoaded hooks that could install polyfills, our polyfills can be initialized + // after config-loaded callbacks that could install polyfills, our polyfills can be initialized await initPolyfillsLoader(); configInitialized = true; @@ -181,9 +178,7 @@ export async function createRuntime(downloadOnly: boolean, httpCacheOnly: boolea return; } - if (typeof Module.onDotnetReady === "function") { - await Module.onDotnetReady(); - } + await loaderCallbacks.dotnetReady?.(); await Promise.all([...modulesAfterConfigLoadedPromises, ...modulesAfterRuntimeReadyPromises].map(callLibraryInitializerOnRuntimeReady)); diff --git a/src/native/libs/Common/JavaScript/types/internal.ts b/src/native/libs/Common/JavaScript/types/internal.ts index cdf67e75aca812..a5dfd0928fe6cb 100644 --- a/src/native/libs/Common/JavaScript/types/internal.ts +++ b/src/native/libs/Common/JavaScript/types/internal.ts @@ -24,8 +24,6 @@ export type CSFnHandle = { export type MemOffset = number | VoidPtr | NativePointer | ManagedPointer; export type NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer; -// how we extended emscripten Module -export type DotnetModule = EmscriptenModule & DotnetModuleConfig; export type DotnetModuleInternal = EmscriptenModule & DotnetModuleConfig & EmscriptenModuleInternal; // these are values from the last re-link with emcc/workload @@ -43,7 +41,7 @@ export type EmscriptenInternals = { updateMemoryViews: () => void, }; -export type EmscriptenModuleInternal = EmscriptenModule & DotnetModuleConfig & { +export type EmscriptenModuleInternal = EmscriptenModule & { runtimeKeepalivePush(): void; runtimeKeepalivePop(): void; print(message: string): void; diff --git a/src/native/libs/Common/JavaScript/types/public-api.ts b/src/native/libs/Common/JavaScript/types/public-api.ts index 0da1d6021fdd42..050f331f586d95 100644 --- a/src/native/libs/Common/JavaScript/types/public-api.ts +++ b/src/native/libs/Common/JavaScript/types/public-api.ts @@ -67,6 +67,10 @@ export interface DotnetHostBuilder { * Sets a callback that is invoked after each resource finishes downloading. */ withDownloadResourceProgress(callback?: (resourcesLoaded: number, totalResources: number) => void): DotnetHostBuilder; + /** + * Sets a callback that is invoked after the runtime configuration is loaded. + */ + withConfigLoaded(callback?: (config: LoaderConfig) => void | Promise): 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. @@ -452,9 +456,6 @@ export declare const enum GlobalizationMode { export type DotnetModuleConfig = { config?: LoaderConfig; - onConfigLoaded?: (config: LoaderConfig) => void | Promise; - onDotnetReady?: () => void | Promise; - onDownloadResourceProgress?: (resourcesLoaded: number, totalResources: number) => void; imports?: any; exports?: string[]; } & Partial; @@ -748,4 +749,3 @@ export declare function exit(exitCode: number, reason?: any): void; export declare const dotnet: DotnetHostBuilder; export declare const createDotnetRuntime: CreateDotnetRuntimeType; -