diff --git a/src/coreclr/hosts/corerun/CMakeLists.txt b/src/coreclr/hosts/corerun/CMakeLists.txt index 37f92d61e71ef4..1ff47656b4765a 100644 --- a/src/coreclr/hosts/corerun/CMakeLists.txt +++ b/src/coreclr/hosts/corerun/CMakeLists.txt @@ -68,11 +68,13 @@ else() "${STATIC_LIB_DESTINATION}/libSystem.Native.Browser.js") set(JS_SYSTEM_BROWSER_UTILS "${STATIC_LIB_DESTINATION}/libSystem.Native.Browser.Utils.js") - set(JS_CORE_RUN + set(JS_CORE_RUN_EXTPOST "${CMAKE_CURRENT_SOURCE_DIR}/wasm/libCorerun.extpost.js") + set(JS_CORE_RUN + "${CMAKE_CURRENT_SOURCE_DIR}/wasm/libCorerun.js") set_target_properties(corerun PROPERTIES - LINK_DEPENDS "${JS_CORE_RUN};${JS_SYSTEM_NATIVE_BROWSER};${JS_SYSTEM_BROWSER_UTILS};" - LINK_FLAGS "--js-library ${JS_SYSTEM_NATIVE_BROWSER} --js-library ${JS_SYSTEM_BROWSER_UTILS} --extern-post-js ${JS_CORE_RUN}" + LINK_DEPENDS "${JS_CORE_RUN};${JS_CORE_RUN_EXTPOST};${JS_SYSTEM_NATIVE_BROWSER};${JS_SYSTEM_BROWSER_UTILS};" + LINK_FLAGS "--js-library ${JS_SYSTEM_NATIVE_BROWSER} --js-library ${JS_SYSTEM_BROWSER_UTILS} --js-library ${JS_CORE_RUN} --extern-post-js ${JS_CORE_RUN_EXTPOST}" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") target_link_options(corerun PRIVATE -sINITIAL_MEMORY=134217728 @@ -82,7 +84,7 @@ else() -sMODULARIZE=1 -sEXPORT_ES6=1 -sEXIT_RUNTIME=1 - -sEXPORTED_RUNTIME_METHODS=ENV,${CMAKE_EMCC_EXPORTED_RUNTIME_METHODS} + -sEXPORTED_RUNTIME_METHODS=CORERUN,${CMAKE_EMCC_EXPORTED_RUNTIME_METHODS} -sEXPORTED_FUNCTIONS=_main,${CMAKE_EMCC_EXPORTED_FUNCTIONS} -sEXPORT_NAME=createDotnetRuntime -sENVIRONMENT=node,shell,web diff --git a/src/coreclr/hosts/corerun/corerun.cpp b/src/coreclr/hosts/corerun/corerun.cpp index 351ce85f6c2da1..1ec3030bf1f359 100644 --- a/src/coreclr/hosts/corerun/corerun.cpp +++ b/src/coreclr/hosts/corerun/corerun.cpp @@ -365,20 +365,6 @@ static bool HOST_CONTRACT_CALLTYPE external_assembly_probe( return false; } -#ifdef TARGET_BROWSER -bool is_node() -{ - return EM_ASM_INT({ - if (typeof process !== 'undefined' && - process.versions && - process.versions.node) { - return 1; - } - return 0; - }); -} -#endif // TARGET_BROWSER - static int run(const configuration& config) { platform_specific_actions actions; @@ -620,12 +606,11 @@ static int run(const configuration& config) } #ifdef TARGET_BROWSER - if (!is_node()) - { - // In browser we don't shutdown the runtime here as we want to keep it alive - return 0; - } -#endif // TARGET_BROWSER + // In browser we don't shutdown the CoreCLR VM here as we want to keep it alive + // the actual exit code would be set by SystemJS_ResolveMainPromise + // the process is kept alive by pending async work via safeSetTimeout() -> runtimeKeepalivePush() + return 0; +#else // TARGET_BROWSER int latched_exit_code = 0; result = coreclr_shutdown2_func(CurrentClrInstance, CurrentAppDomainId, &latched_exit_code); @@ -641,6 +626,8 @@ static int run(const configuration& config) ::free((void*)s_core_libs_path); ::free((void*)s_core_root_path); return exit_code; + +#endif // TARGET_BROWSER } // Display the command line options diff --git a/src/coreclr/hosts/corerun/wasm/libCorerun.extpost.js b/src/coreclr/hosts/corerun/wasm/libCorerun.extpost.js index 80c0efdcb3e102..bad11a160c1f50 100644 --- a/src/coreclr/hosts/corerun/wasm/libCorerun.extpost.js +++ b/src/coreclr/hosts/corerun/wasm/libCorerun.extpost.js @@ -2,23 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. /* eslint-disable no-undef */ -/* eslint-disable space-before-function-paren */ /* eslint-disable @typescript-eslint/no-unused-vars */ var fetch = fetch || undefined; var dotnetNativeModuleLoaded = false; var dotnetInternals = null; export function selfRun() { const Module = {}; - const corePreRun = () => { - // copy all node/shell env variables to emscripten env - if (globalThis.process && globalThis.process.env) { - for (const [key, value] of Object.entries(process.env)) { - Module.ENV[key] = value; - } - } - - Module.ENV["DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"] = "true"; - }; - Module.preRun = [corePreRun]; - const runtimeApi = { Module, INTERNAL: {}, @@ -31,12 +18,22 @@ export function selfRun() { wasmEnableSIMD: true, wasmEnableExceptionHandling: true, }, + exit: (exitCode, error) => { + // do not propagate ExitStatus exception + if (error && typeof error.status !== "number") { + console.error(error); + } + process.exit(exitCode); + }, }; dotnetInternals = [ runtimeApi, [], ]; + // this will create the emscripten emulator and run corerun.cpp main() + // but the nodejs process will be kept alive by any pending async work createDotnetRuntime(runtimeApi.Module); } + selfRun(); diff --git a/src/coreclr/hosts/corerun/wasm/libCorerun.js b/src/coreclr/hosts/corerun/wasm/libCorerun.js new file mode 100644 index 00000000000000..4567fba9ded304 --- /dev/null +++ b/src/coreclr/hosts/corerun/wasm/libCorerun.js @@ -0,0 +1,50 @@ +//! Licensed to the .NET Foundation under one or more agreements. +//! The .NET Foundation licenses this file to you under the MIT license. + +/* eslint-disable no-undef */ +function libCoreRunFactory() { + let commonDeps = [ + "$DOTNET", + "$ENV", + "$FS", + "$NODEFS", + "$NODERAWFS", + ]; + const mergeCoreRun = { + $CORERUN: { + selfInitialize: () => { + const browserVirtualAppBase = "/";// keep in sync other places that define browserVirtualAppBase + FS.createPath("/", browserVirtualAppBase, true, true); + + // copy all node/shell env variables to emscripten env + if (globalThis.process && globalThis.process.env) { + for (const [key, value] of Object.entries(process.env)) { + ENV[key] = value; + } + } + + ENV["DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"] = "true"; + + }, + }, + $CORERUN__postset: "CORERUN.selfInitialize()", + $CORERUN__deps: commonDeps, + }; + const patchNODERAWFS = { + cwd: () => { + // drop windows drive letter for NODEFS cwd to pretend we are in unix + const path = process.cwd(); + return NODEFS.isWindows + ? path.replace(/^[a-zA-Z]:/, "").replace(/\\/g, "/") + : path; + } + } + + autoAddDeps(mergeCoreRun, "$CORERUN"); + addToLibrary(mergeCoreRun); + if (LibraryManager.library.$NODERAWFS) { + Object.assign(LibraryManager.library.$NODERAWFS, patchNODERAWFS); + } +} + +libCoreRunFactory(); diff --git a/src/coreclr/pal/src/arch/wasm/stubs.cpp b/src/coreclr/pal/src/arch/wasm/stubs.cpp index 05113bb8600056..7405e744889fd3 100644 --- a/src/coreclr/pal/src/arch/wasm/stubs.cpp +++ b/src/coreclr/pal/src/arch/wasm/stubs.cpp @@ -18,8 +18,19 @@ DBG_DebugBreak() { #ifdef _DEBUG DBG_PrintInterpreterStack(); -#endif // _DEBUG + double start = emscripten_get_now(); emscripten_debugger(); + double end = emscripten_get_now(); + // trying to guess if the debugger was attached + if (end - start < 100) + { + // If the debugger was not attached, abort the process + // to match other platforms and fail fast + emscripten_throw_string("Debugger not attached"); + } +#else // _DEBUG + emscripten_throw_string("Debug break called in release build."); +#endif // _DEBUG } /* context */ diff --git a/src/coreclr/pal/src/debug/debug.cpp b/src/coreclr/pal/src/debug/debug.cpp index 64dd8b7e0caadd..4cf61fee790b00 100644 --- a/src/coreclr/pal/src/debug/debug.cpp +++ b/src/coreclr/pal/src/debug/debug.cpp @@ -65,6 +65,10 @@ SET_DEFAULT_DEBUG_CHANNEL(DEBUG); // some headers have code with asserts, so do #endif #endif // __APPLE__ +#ifdef __EMSCRIPTEN__ +#include +#endif // __EMSCRIPTEN__ + #if HAVE_MACH_EXCEPTIONS #include "../exception/machexception.h" #endif // HAVE_MACH_EXCEPTIONS @@ -751,6 +755,13 @@ PAL_ProbeMemory( DWORD cbBuffer, BOOL fWriteAccess) { +#if defined(__EMSCRIPTEN__) + if ((uintptr_t)((PBYTE)pBuffer + cbBuffer) <= emscripten_get_heap_size()) + { + return TRUE; + } + return FALSE; +#else // __EMSCRIPTEN__ int fds[2]; int flags; @@ -807,6 +818,7 @@ PAL_ProbeMemory( close(fds[1]); return result; +#endif // __EMSCRIPTEN__ } } // extern "C" diff --git a/src/coreclr/vm/precode_portable.cpp b/src/coreclr/vm/precode_portable.cpp index 8ca3980ccff52b..40c4bdce3958bb 100644 --- a/src/coreclr/vm/precode_portable.cpp +++ b/src/coreclr/vm/precode_portable.cpp @@ -32,7 +32,7 @@ void* PortableEntryPoint::GetActualCode(PCODE addr) STANDARD_VM_CONTRACT; PortableEntryPoint* portableEntryPoint = ToPortableEntryPoint(addr); - _ASSERTE(portableEntryPoint->HasNativeCode()); + _ASSERTE_ALL_BUILDS(portableEntryPoint->HasNativeCode()); return portableEntryPoint->_pActualCode; } @@ -41,7 +41,7 @@ void PortableEntryPoint::SetActualCode(PCODE addr, PCODE actualCode) STANDARD_VM_CONTRACT; PortableEntryPoint* portableEntryPoint = ToPortableEntryPoint(addr); - _ASSERTE(actualCode != (PCODE)NULL); + _ASSERTE_ALL_BUILDS(actualCode != (PCODE)NULL); // This is a lock free write. It can either be NULL or was already set to the same value. _ASSERTE(!portableEntryPoint->HasNativeCode() || portableEntryPoint->_pActualCode == (void*)PCODEToPINSTR(actualCode)); diff --git a/src/libraries/Common/tests/TestUtilities/System/AssemblyPathHelper.cs b/src/libraries/Common/tests/TestUtilities/System/AssemblyPathHelper.cs index e2ee4cfb678d6d..03e201f3cc140b 100644 --- a/src/libraries/Common/tests/TestUtilities/System/AssemblyPathHelper.cs +++ b/src/libraries/Common/tests/TestUtilities/System/AssemblyPathHelper.cs @@ -13,9 +13,11 @@ public static string GetAssemblyLocation(Assembly a) // Note, in Browser, assemblies are loaded from memory and in that case, Assembly.Location will return an empty // string. For these tests, the assemblies will also be available in the VFS, so just specify the assembly name // plus extension. + const string browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase + return (PlatformDetection.IsNotBrowser) ? a.Location - : "/" + a.GetName().Name + ".dll"; + : browserVirtualAppBase + a.GetName().Name + ".dll"; } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/ModuleTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/ModuleTests.cs index be54c889ef34cf..df758d8faca60c 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/ModuleTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Reflection/ModuleTests.cs @@ -92,7 +92,11 @@ public void FullyQualifiedName() // Browser will include the path (/), so strip it if (PlatformDetection.IsBrowser && loc.Length > 1) { - loc = loc.Substring(1); + const string browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase + if (loc.StartsWith(browserVirtualAppBase, StringComparison.Ordinal)) + { + loc = loc.Substring(browserVirtualAppBase.Length); + } } Assert.Equal(loc, Module.FullyQualifiedName); diff --git a/src/libraries/sendtohelix-browser.targets b/src/libraries/sendtohelix-browser.targets index c90dd51c6b3974..7c85827282ef65 100644 --- a/src/libraries/sendtohelix-browser.targets +++ b/src/libraries/sendtohelix-browser.targets @@ -32,8 +32,9 @@ <_ShippingPackagesPath>$([MSBuild]::NormalizeDirectory($(ArtifactsDir), 'packages', $(Configuration), 'Shipping')) - $(Scenario)-ST- - $(Scenario)-MT- + $(Scenario)-CLR-ST- + $(Scenario)-MONO-ST- + $(Scenario)-MONO-MT- - diff --git a/src/mono/browser/runtime/assets.ts b/src/mono/browser/runtime/assets.ts index e2ceb3f366589f..a899e1eb690565 100644 --- a/src/mono/browser/runtime/assets.ts +++ b/src/mono/browser/runtime/assets.ts @@ -5,7 +5,7 @@ import type { AssetEntryInternal } from "./types/internal"; import cwraps from "./cwraps"; import { wasm_load_icu_data } from "./icu"; -import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; +import { Module, browserVirtualAppBase, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; import { mono_log_info, mono_log_debug, parseSymbolMapFile } from "./logging"; import { mono_wasm_load_bytes_into_heap_persistent } from "./memory"; import { endMeasure, MeasuredBlock, startMeasure } from "./profiler"; @@ -44,31 +44,18 @@ export function instantiate_asset (asset: AssetEntry, url: string, bytes: Uint8A const lastSlash = virtualName.lastIndexOf("/"); let parentDirectory = (lastSlash > 0) ? virtualName.substring(0, lastSlash) - : null; + : browserVirtualAppBase; let fileName = (lastSlash > 0) ? virtualName.substring(lastSlash + 1) : virtualName; if (fileName.startsWith("/")) fileName = fileName.substring(1); - if (parentDirectory) { - if (!parentDirectory.startsWith("/")) - parentDirectory = "/" + parentDirectory; - - mono_log_debug(`Creating directory '${parentDirectory}'`); - - Module.FS_createPath( - "/", parentDirectory, true, true // fixme: should canWrite be false? - ); - } else { - parentDirectory = "/"; - } + if (!parentDirectory.startsWith("/")) + parentDirectory = browserVirtualAppBase + parentDirectory; mono_log_debug(() => `Creating file '${fileName}' in directory '${parentDirectory}'`); - - Module.FS_createDataFile( - parentDirectory, fileName, - bytes, true /* canRead */, true /* canWrite */, true /* canOwn */ - ); + Module.FS_createPath("/", parentDirectory, true, true); + Module.FS_createDataFile(parentDirectory, fileName, bytes, true /* canRead */, true /* canWrite */, true /* canOwn */); break; } default: diff --git a/src/mono/browser/runtime/driver.c b/src/mono/browser/runtime/driver.c index bec1286055708a..3b8d0347cae62e 100644 --- a/src/mono/browser/runtime/driver.c +++ b/src/mono/browser/runtime/driver.c @@ -204,8 +204,9 @@ int initialize_runtime() appctx_keys [0] = "APP_CONTEXT_BASE_DIRECTORY"; appctx_keys [1] = "RUNTIME_IDENTIFIER"; + const char *browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase const char *appctx_values[2]; - appctx_values [0] = "/"; + appctx_values [0] = browserVirtualAppBase; appctx_values [1] = "browser-wasm"; // this does not support loading runtimeConfig.json part of boot.config.json diff --git a/src/mono/browser/runtime/globals.ts b/src/mono/browser/runtime/globals.ts index 7802b54317a851..76fe9c0763cae7 100644 --- a/src/mono/browser/runtime/globals.ts +++ b/src/mono/browser/runtime/globals.ts @@ -23,6 +23,7 @@ export const ENVIRONMENT_IS_SIDECAR = ENVIRONMENT_IS_WEB_WORKER && typeof dotnet export const ENVIRONMENT_IS_WORKER = ENVIRONMENT_IS_WEB_WORKER && !ENVIRONMENT_IS_SIDECAR; // we redefine what ENVIRONMENT_IS_WORKER, we replace it in emscripten internals, so that sidecar works export const ENVIRONMENT_IS_WEB = typeof window == "object" || (ENVIRONMENT_IS_WEB_WORKER && !ENVIRONMENT_IS_NODE); export const ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE; +export const browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase // these are imported and re-exported from emscripten internals export let ENVIRONMENT_IS_PTHREAD: boolean; diff --git a/src/mono/browser/runtime/loader/config.ts b/src/mono/browser/runtime/loader/config.ts index 8f5cf705b1d8fa..1c2f73a6aa2417 100644 --- a/src/mono/browser/runtime/loader/config.ts +++ b/src/mono/browser/runtime/loader/config.ts @@ -12,6 +12,7 @@ import { importLibraryInitializers, invokeLibraryInitializers } from "./libraryI import { mono_exit } from "./exit"; import { makeURLAbsoluteWithApplicationBase } from "./polyfills"; import { appendUniqueQuery } from "./assets"; +import { browserVirtualAppBase } from "./globals"; export function deep_merge_config (target: MonoConfigInternal, source: MonoConfigInternal): MonoConfigInternal { // no need to merge the same object @@ -190,6 +191,10 @@ export function normalizeConfig () { config.debugLevel = -1; } + if (config.virtualWorkingDirectory === undefined) { + config.virtualWorkingDirectory = browserVirtualAppBase; + } + if (!config.applicationEnvironment) { config.applicationEnvironment = "Production"; } diff --git a/src/mono/browser/runtime/loader/exit.ts b/src/mono/browser/runtime/loader/exit.ts index 2fed154835e5c2..b57d06b993cc48 100644 --- a/src/mono/browser/runtime/loader/exit.ts +++ b/src/mono/browser/runtime/loader/exit.ts @@ -313,7 +313,7 @@ function fatal_handler (event: any, reason: any, type: string) { mono_exit(1, reason); } } catch (err) { - // no not re-throw from the fatal handler + // do not re-throw from the fatal handler } } diff --git a/src/mono/browser/runtime/loader/globals.ts b/src/mono/browser/runtime/loader/globals.ts index 21293314654d34..cbfbb1b267dc09 100644 --- a/src/mono/browser/runtime/loader/globals.ts +++ b/src/mono/browser/runtime/loader/globals.ts @@ -31,6 +31,7 @@ export const ENVIRONMENT_IS_SIDECAR = ENVIRONMENT_IS_WEB_WORKER && typeof dotnet export const ENVIRONMENT_IS_WORKER = ENVIRONMENT_IS_WEB_WORKER && !ENVIRONMENT_IS_SIDECAR; // we redefine what ENVIRONMENT_IS_WORKER, we replace it in emscripten internals, so that sidecar works export const ENVIRONMENT_IS_WEB = typeof window == "object" || (ENVIRONMENT_IS_WEB_WORKER && !ENVIRONMENT_IS_NODE); export const ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE; +export const browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase export let runtimeHelpers: RuntimeHelpers = {} as any; export let loaderHelpers: LoaderHelpers = {} as any; diff --git a/src/mono/browser/runtime/startup.ts b/src/mono/browser/runtime/startup.ts index 3eaae69ec3fe74..7b2a3e20d3b5d6 100644 --- a/src/mono/browser/runtime/startup.ts +++ b/src/mono/browser/runtime/startup.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import BuildConfiguration from "consts:configuration"; import { DotnetModuleInternal, CharPtrNull, MainToWorkerMessageType } from "./types/internal"; -import { exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, createPromiseController, mono_assert } from "./globals"; +import { exportedRuntimeAPI, INTERNAL, loaderHelpers, Module, runtimeHelpers, createPromiseController, mono_assert, browserVirtualAppBase } from "./globals"; import cwraps, { init_c_exports, threads_c_functions as tcwraps } from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; import { toBase64StringImpl } from "./base64"; @@ -218,18 +218,9 @@ async function onRuntimeInitializedAsync (userOnRuntimeInitialized: (module:Emsc if (runtimeHelpers.config.virtualWorkingDirectory) { const FS = Module.FS; - const cwd = runtimeHelpers.config.virtualWorkingDirectory; - try { - const wds = FS.stat(cwd); - if (!wds) { - Module.FS_createPath("/", cwd, true, true); - } else { - mono_assert(wds && FS.isDir(wds.mode), () => `FS.chdir: ${cwd} is not a directory`); - } - } catch (e) { - Module.FS_createPath("/", cwd, true, true); - } - FS.chdir(cwd); + FS.createPath("/", browserVirtualAppBase, true, true); + FS.createPath("/", runtimeHelpers.config.virtualWorkingDirectory, true, true); + FS.chdir(runtimeHelpers.config.virtualWorkingDirectory); } if (runtimeHelpers.config.interpreterPgo) diff --git a/src/mono/browser/test-main.js b/src/mono/browser/test-main.js index 61c5282e7271b4..7e93cfbe4f19f3 100644 --- a/src/mono/browser/test-main.js +++ b/src/mono/browser/test-main.js @@ -24,6 +24,7 @@ export const ENVIRONMENT_IS_SIDECAR = ENVIRONMENT_IS_WEB_WORKER && typeof dotnet export const ENVIRONMENT_IS_WORKER = ENVIRONMENT_IS_WEB_WORKER && !ENVIRONMENT_IS_SIDECAR; // we redefine what ENVIRONMENT_IS_WORKER, we replace it in emscripten internals, so that sidecar works export const ENVIRONMENT_IS_WEB = typeof window == "object" || (ENVIRONMENT_IS_WEB_WORKER && !ENVIRONMENT_IS_NODE); export const ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE; +export const browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase export const isFirefox = !!(ENVIRONMENT_IS_WEB && navigator.userAgent.includes("Firefox")); export const isChromium = !!(ENVIRONMENT_IS_WEB && navigator.userAgentData && navigator.userAgentData.brands.some(b => b.brand === "Google Chrome" || b.brand === "Microsoft Edge" || b.brand === "Chromium")); @@ -111,7 +112,7 @@ function initRunArgs(runArgs) { // set defaults runArgs.applicationArguments = runArgs.applicationArguments === undefined ? [] : runArgs.applicationArguments; runArgs.profilers = runArgs.profilers === undefined ? [] : runArgs.profilers; - runArgs.workingDirectory = runArgs.workingDirectory === undefined ? '/' : runArgs.workingDirectory; + runArgs.workingDirectory = runArgs.workingDirectory === undefined ? browserVirtualAppBase : runArgs.workingDirectory; runArgs.environmentVariables = runArgs.environmentVariables === undefined ? {} : runArgs.environmentVariables; runArgs.runtimeArgs = runArgs.runtimeArgs === undefined ? [] : runArgs.runtimeArgs; runArgs.enableGC = runArgs.enableGC === undefined ? true : runArgs.enableGC; diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets index b9a8373cf97696..148ea1df48d85b 100644 --- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets +++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Browser.targets @@ -249,7 +249,7 @@ Copyright (c) .NET Foundation. All rights reserved. - /%(WasmFilesToIncludeInFileSystem.Identity) + %(WasmFilesToIncludeInFileSystem.Identity) diff --git a/src/mono/sample/mbr/browser/WasmDelta.csproj b/src/mono/sample/mbr/browser/WasmDelta.csproj index 6fbdf7b9c0c4e8..14b8992d888f4d 100644 --- a/src/mono/sample/mbr/browser/WasmDelta.csproj +++ b/src/mono/sample/mbr/browser/WasmDelta.csproj @@ -31,7 +31,7 @@ - \%(_DeltaFileForPublish.Filename)%(_DeltaFileForPublish.Extension) + %(_DeltaFileForPublish.Filename)%(_DeltaFileForPublish.Extension) diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index f0772311623403..60fa96d7a33649 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -94,7 +94,7 @@ - + diff --git a/src/mono/sample/wasm/console-v8/main.mjs b/src/mono/sample/wasm/console-v8/wwwroot/main.mjs similarity index 100% rename from src/mono/sample/wasm/console-v8/main.mjs rename to src/mono/sample/wasm/console-v8/wwwroot/main.mjs diff --git a/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs b/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs index 11b360a89eb032..4ac3f3b8d2dd0f 100644 --- a/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/AppSettingsTests.cs @@ -70,8 +70,9 @@ public async Task LoadAppSettingsBasedOnApplicationEnvironment(bool publish, str ? await RunForPublishWithWebServer(runOptions) : await RunForBuildWithDotnetRun(runOptions); - Assert.Contains(result.TestOutput, m => m.Contains("'/appsettings.json' exists 'True'")); - Assert.Contains(result.TestOutput, m => m.Contains($"'/appsettings.Development.json' exists '{expectedApplicationEnvironment == "Development"}'")); - Assert.Contains(result.TestOutput, m => m.Contains($"'/appsettings.Production.json' exists '{expectedApplicationEnvironment == "Production"}'")); + const string browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase + Assert.Contains(result.TestOutput, m => m.Contains($"'{browserVirtualAppBase}appsettings.json' exists 'True'")); + Assert.Contains(result.TestOutput, m => m.Contains($"'{browserVirtualAppBase}appsettings.Development.json' exists '{expectedApplicationEnvironment == "Development"}'")); + Assert.Contains(result.TestOutput, m => m.Contains($"'{browserVirtualAppBase}appsettings.Production.json' exists '{expectedApplicationEnvironment == "Production"}'")); } } diff --git a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs index b13e2e420ad112..ea1918a0c5dbb5 100644 --- a/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs +++ b/src/mono/wasm/Wasm.Build.Tests/Common/BuildEnvironment.cs @@ -111,7 +111,7 @@ public BuildEnvironment() $" {nameof(IsRunningOnCI)} is true but {nameof(IsWorkloadWithMultiThreadingForDefaultFramework)} is false."); } - UseWebcil = EnvironmentVariables.UseWebcil && EnvironmentVariables.RuntimeFlavor != "CoreCLR"; // TODO-WASM: CoreCLR support for Webcil + UseWebcil = EnvironmentVariables.UseWebcil && EnvironmentVariables.RuntimeFlavor != "CoreCLR"; // TODO-WASM: CoreCLR support for Webcil https://github.com/dotnet/runtime/issues/120248 if (EnvironmentVariables.BuiltNuGetsPath is null || !Directory.Exists(EnvironmentVariables.BuiltNuGetsPath)) throw new Exception($"Cannot find 'BUILT_NUGETS_PATH={EnvironmentVariables.BuiltNuGetsPath}'"); diff --git a/src/mono/wasm/Wasm.Build.Tests/FilesToIncludeInFileSystemTests.cs b/src/mono/wasm/Wasm.Build.Tests/FilesToIncludeInFileSystemTests.cs index 48c2c754161165..bd325c29218ebc 100644 --- a/src/mono/wasm/Wasm.Build.Tests/FilesToIncludeInFileSystemTests.cs +++ b/src/mono/wasm/Wasm.Build.Tests/FilesToIncludeInFileSystemTests.cs @@ -51,6 +51,6 @@ public async Task LoadFilesToVfs(bool publish) Assert.Contains(result.TestOutput, m => m.Contains("'/myfiles/Vfs1.txt' exists 'True' with content 'Vfs1.txt'")); Assert.Contains(result.TestOutput, m => m.Contains("'/myfiles/Vfs2.txt' exists 'True' with content 'Vfs2.txt'")); - Assert.Contains(result.TestOutput, m => m.Contains("'/subdir/subsubdir/Vfs3.txt' exists 'True' with content 'Vfs3.txt'")); + Assert.Contains(result.TestOutput, m => m.Contains("'subdir/subsubdir/Vfs3.txt' exists 'True' with content 'Vfs3.txt'")); } } diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/AppSettingsTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/App/AppSettingsTest.cs index 2e09f35eb00299..f46e873ae3b1ab 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/AppSettingsTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/AppSettingsTest.cs @@ -11,10 +11,12 @@ public partial class AppSettingsTest [JSExport] public static void Run() { + const string browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase + // Check file presence in VFS based on application environment - PrintFileExistence("/appsettings.json"); - PrintFileExistence("/appsettings.Development.json"); - PrintFileExistence("/appsettings.Production.json"); + PrintFileExistence(browserVirtualAppBase + "appsettings.json"); + PrintFileExistence(browserVirtualAppBase + "appsettings.Development.json"); + PrintFileExistence(browserVirtualAppBase + "appsettings.Production.json"); } // Synchronize with AppSettingsTests diff --git a/src/mono/wasm/testassets/WasmBasicTestApp/App/FilesToIncludeInFileSystemTest.cs b/src/mono/wasm/testassets/WasmBasicTestApp/App/FilesToIncludeInFileSystemTest.cs index edd3b96e5e2613..e3f9bcf06d362c 100644 --- a/src/mono/wasm/testassets/WasmBasicTestApp/App/FilesToIncludeInFileSystemTest.cs +++ b/src/mono/wasm/testassets/WasmBasicTestApp/App/FilesToIncludeInFileSystemTest.cs @@ -14,7 +14,7 @@ public static void Run() // Check file presence in VFS based on application environment PrintFileExistence("/myfiles/Vfs1.txt"); PrintFileExistence("/myfiles/Vfs2.txt"); - PrintFileExistence("/subdir/subsubdir/Vfs3.txt"); + PrintFileExistence("subdir/subsubdir/Vfs3.txt"); } // Synchronize with FilesToIncludeInFileSystemTests diff --git a/src/native/corehost/browserhost/CMakeLists.txt b/src/native/corehost/browserhost/CMakeLists.txt index 95137e02461c56..1870c40091b6f3 100644 --- a/src/native/corehost/browserhost/CMakeLists.txt +++ b/src/native/corehost/browserhost/CMakeLists.txt @@ -116,11 +116,11 @@ target_link_options(browserhost PRIVATE -sMODULARIZE=1 -sEXPORT_ES6=1 -sEXIT_RUNTIME=1 + -sALLOW_TABLE_GROWTH=1 -sEXPORTED_RUNTIME_METHODS=BROWSER_HOST,${CMAKE_EMCC_EXPORTED_RUNTIME_METHODS} -sEXPORTED_FUNCTIONS=${CMAKE_EMCC_EXPORTED_FUNCTIONS} -sEXPORT_NAME=createDotnetRuntime -sENVIRONMENT=web,webview,worker,node,shell - -lnodefs.js -Wl,-error-limit=0) target_link_libraries(browserhost PRIVATE diff --git a/src/native/corehost/browserhost/host/assets.ts b/src/native/corehost/browserhost/host/assets.ts new file mode 100644 index 00000000000000..54f279e68f7236 --- /dev/null +++ b/src/native/corehost/browserhost/host/assets.ts @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import type { CharPtr, VfsAsset, VoidPtr, VoidPtrPtr } from "./types"; +import { _ems_ } from "../../../libs/Common/JavaScript/ems-ambient"; + +import { dotnetAssert, dotnetLogger } from "./cross-module"; +import { browserVirtualAppBase, ENVIRONMENT_IS_WEB } from "./per-module"; + +const hasInstantiateStreaming = typeof WebAssembly !== "undefined" && typeof WebAssembly.instantiateStreaming === "function"; +const loadedAssemblies: Map = new Map(); +// eslint-disable-next-line @typescript-eslint/no-unused-vars +let wasmMemory: WebAssembly.Memory = undefined as any; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +let wasmMainTable: WebAssembly.Table = undefined as any; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function registerPdbBytes(bytes: Uint8Array, virtualPath: string) { + // WASM-TODO: https://github.com/dotnet/runtime/issues/122921 +} + +export function registerDllBytes(bytes: Uint8Array, virtualPath: string) { + const sp = _ems_.stackSave(); + try { + const sizeOfPtr = 4; + const ptrPtr = _ems_.stackAlloc(sizeOfPtr); + if (_ems_._posix_memalign(ptrPtr as any, 16, bytes.length)) { + throw new Error("posix_memalign failed"); + } + + const ptr = _ems_.HEAPU32[ptrPtr as any >>> 2]; + _ems_.HEAPU8.set(bytes, ptr >>> 0); + const name = virtualPath.substring(virtualPath.lastIndexOf("/") + 1); + + _ems_.dotnetLogger.debug(`Registered assembly '${virtualPath}' (name: '${name}') at ${ptr.toString(16)} length ${bytes.length}`); + loadedAssemblies.set(virtualPath, { ptr, length: bytes.length }); + loadedAssemblies.set(name, { ptr, length: bytes.length }); + } finally { + _ems_.stackRestore(sp); + } +} + +export function BrowserHost_ExternalAssemblyProbe(pathPtr: CharPtr, outDataStartPtr: VoidPtrPtr, outSize: VoidPtr) { + const path = _ems_.UTF8ToString(pathPtr); + const assembly = loadedAssemblies.get(path); + if (assembly) { + _ems_.HEAPU32[outDataStartPtr as any >>> 2] = assembly.ptr; + // int64_t target + _ems_.HEAPU32[outSize as any >>> 2] = assembly.length; + _ems_.HEAPU32[((outSize as any) + 4) >>> 2] = 0; + return true; + } + _ems_.dotnetLogger.debug(`Assembly not found: '${path}'`); + _ems_.HEAPU32[outDataStartPtr as any >>> 2] = 0; + _ems_.HEAPU32[outSize as any >>> 2] = 0; + _ems_.HEAPU32[((outSize as any) + 4) >>> 2] = 0; + return false; +} + +export function loadIcuData(bytes: Uint8Array) { + const sp = _ems_.stackSave(); + try { + const sizeOfPtr = 4; + const ptrPtr = _ems_.stackAlloc(sizeOfPtr); + if (_ems_._posix_memalign(ptrPtr as any, 16, bytes.length)) { + throw new Error("posix_memalign failed for ICU data"); + } + + const ptr = _ems_.HEAPU32[ptrPtr as any >>> 2]; + _ems_.HEAPU8.set(bytes, ptr >>> 0); + + const result = _ems_._wasm_load_icu_data(ptr as unknown as VoidPtr); + if (!result) { + throw new Error("Failed to initialize ICU data"); + } + } finally { + _ems_.stackRestore(sp); + } +} + +export function installVfsFile(bytes: Uint8Array, asset: VfsAsset) { + const virtualName: string = typeof (asset.virtualPath) === "string" + ? asset.virtualPath + : asset.name; + const lastSlash = virtualName.lastIndexOf("/"); + let parentDirectory = (lastSlash > 0) + ? virtualName.substring(0, lastSlash) + : browserVirtualAppBase; + let fileName = (lastSlash > 0) + ? virtualName.substring(lastSlash + 1) + : virtualName; + if (fileName.startsWith("/")) { + fileName = fileName.substring(1); + } + if (!parentDirectory.startsWith("/")) { + parentDirectory = browserVirtualAppBase + parentDirectory; + } + + _ems_.dotnetLogger.debug(`Creating file '${fileName}' in directory '${parentDirectory}'`); + _ems_.FS.createPath("/", parentDirectory, true, true); + _ems_.FS.createDataFile(parentDirectory, fileName, bytes, true /* canRead */, true /* canWrite */, true /* canOwn */); +} + +export async function instantiateWasm(wasmPromise: Promise, imports: WebAssembly.Imports, isStreaming: boolean, isMainModule: boolean): Promise<{ instance: WebAssembly.Instance; module: WebAssembly.Module; }> { + let instance: WebAssembly.Instance; + let module: WebAssembly.Module; + const res = await checkResponseOk(wasmPromise); + if (!hasInstantiateStreaming || !isStreaming || !res.isMimeTypeOk) { + const data = await res.arrayBuffer(); + module = await WebAssembly.compile(data); + instance = await WebAssembly.instantiate(module, imports); + } else { + const instantiated = await WebAssembly.instantiateStreaming(wasmPromise, imports); + instance = instantiated.instance; + module = instantiated.module; + } + if (isMainModule) { + wasmMemory = instance.exports.memory as WebAssembly.Memory; + wasmMainTable = instance.exports.__indirect_function_table as WebAssembly.Table; + } + return { instance, module }; +} + +async function checkResponseOk(wasmPromise: Promise | undefined): Promise { + dotnetAssert.check(wasmPromise, "WASM binary promise was not initialized"); + const res = (await wasmPromise) as Response & { isMimeTypeOk?: boolean }; + if (!res || res.ok === false) { + throw new Error(`Failed to load WebAssembly module. HTTP status: ${res?.status} ${res?.statusText}`); + } + const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined; + res.isMimeTypeOk = true; + if (ENVIRONMENT_IS_WEB && contentType !== "application/wasm") { + dotnetLogger.warn("WebAssembly resource does not have the expected content type \"application/wasm\", so falling back to slower ArrayBuffer instantiation."); + res.isMimeTypeOk = false; + } + return res; +} + diff --git a/src/native/corehost/browserhost/host/cross-module.ts b/src/native/corehost/browserhost/host/cross-module.ts new file mode 100644 index 00000000000000..d5e72ee0889671 --- /dev/null +++ b/src/native/corehost/browserhost/host/cross-module.ts @@ -0,0 +1,4 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +export * from "../../../libs/Common/JavaScript/cross-module"; diff --git a/src/native/corehost/browserhost/host/host.ts b/src/native/corehost/browserhost/host/host.ts index 2f9f263fc7975c..a8107f16ee36e4 100644 --- a/src/native/corehost/browserhost/host/host.ts +++ b/src/native/corehost/browserhost/host/host.ts @@ -1,95 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { CharPtr, CharPtrPtr, VfsAsset, VoidPtr, VoidPtrPtr } from "./types"; +import type { CharPtrPtr, VoidPtr } from "./types"; import { _ems_ } from "../../../libs/Common/JavaScript/ems-ambient"; - -const loadedAssemblies: Map = new Map(); - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export function registerPdbBytes(bytes: Uint8Array, asset: { name: string, virtualPath: string }) { - // WASM-TODO: https://github.com/dotnet/runtime/issues/122921 -} - -export function registerDllBytes(bytes: Uint8Array, asset: { name: string, virtualPath: string }) { - const sp = _ems_.stackSave(); - try { - const sizeOfPtr = 4; - const ptrPtr = _ems_.stackAlloc(sizeOfPtr); - if (_ems_._posix_memalign(ptrPtr as any, 16, bytes.length)) { - throw new Error("posix_memalign failed"); - } - - const ptr = _ems_.HEAPU32[ptrPtr as any >>> 2]; - _ems_.HEAPU8.set(bytes, ptr >>> 0); - loadedAssemblies.set(asset.virtualPath, { ptr, length: bytes.length }); - if (!asset.virtualPath.startsWith("/")) { - loadedAssemblies.set("/" + asset.virtualPath, { ptr, length: bytes.length }); - } - } finally { - _ems_.stackRestore(sp); - } -} - -export function loadIcuData(bytes: Uint8Array) { - const sp = _ems_.stackSave(); - try { - const sizeOfPtr = 4; - const ptrPtr = _ems_.stackAlloc(sizeOfPtr); - if (_ems_._posix_memalign(ptrPtr as any, 16, bytes.length)) { - throw new Error("posix_memalign failed for ICU data"); - } - - const ptr = _ems_.HEAPU32[ptrPtr as any >>> 2]; - _ems_.HEAPU8.set(bytes, ptr >>> 0); - - const result = _ems_._wasm_load_icu_data(ptr as unknown as VoidPtr); - if (!result) { - throw new Error("Failed to initialize ICU data"); - } - } finally { - _ems_.stackRestore(sp); - } -} - -export function installVfsFile(bytes: Uint8Array, asset: VfsAsset) { - const virtualName: string = typeof (asset.virtualPath) === "string" - ? asset.virtualPath - : asset.name; - const lastSlash = virtualName.lastIndexOf("/"); - let parentDirectory = (lastSlash > 0) - ? virtualName.substring(0, lastSlash) - : null; - let fileName = (lastSlash > 0) - ? virtualName.substring(lastSlash + 1) - : virtualName; - if (fileName.startsWith("/")) - fileName = fileName.substring(1); - if (parentDirectory) { - if (!parentDirectory.startsWith("/")) - parentDirectory = "/" + parentDirectory; - - if (parentDirectory.startsWith("/managed")) { - throw new Error("Cannot create files under /managed virtual directory as it is reserved for NodeFS mounting"); - } - - _ems_.dotnetLogger.debug(`Creating directory '${parentDirectory}'`); - - _ems_.FS.createPath( - "/", parentDirectory, true, true // fixme: should canWrite be false? - ); - } else { - parentDirectory = "/"; - } - - _ems_.dotnetLogger.debug(`Creating file '${fileName}' in directory '${parentDirectory}'`); - - _ems_.FS.createDataFile( - parentDirectory, fileName, - bytes, true /* canRead */, true /* canWrite */, true /* canOwn */ - ); -} - +import { browserVirtualAppBase } from "./per-module"; const HOST_PROPERTY_RUNTIME_CONTRACT = "HOST_RUNTIME_CONTRACT"; const HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES = "TRUSTED_PLATFORM_ASSEMBLIES"; @@ -108,14 +22,15 @@ export function initializeCoreCLR(): number { runtimeConfigProperties.set(key, "" + value); } } - const assemblyPaths = loaderConfig.resources!.assembly.map(a => "/" + a.virtualPath); - const coreAssemblyPaths = loaderConfig.resources!.coreAssembly.map(a => "/" + a.virtualPath); + + const assemblyPaths = loaderConfig.resources!.assembly.map(asset => asset.virtualPath); + const coreAssemblyPaths = loaderConfig.resources!.coreAssembly.map(asset => asset.virtualPath); const tpa = [...coreAssemblyPaths, ...assemblyPaths].join(":"); runtimeConfigProperties.set(HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES, tpa); runtimeConfigProperties.set(HOST_PROPERTY_NATIVE_DLL_SEARCH_DIRECTORIES, loaderConfig.virtualWorkingDirectory!); runtimeConfigProperties.set(HOST_PROPERTY_APP_PATHS, loaderConfig.virtualWorkingDirectory!); runtimeConfigProperties.set(HOST_PROPERTY_ENTRY_ASSEMBLY_NAME, loaderConfig.mainAssemblyName!); - runtimeConfigProperties.set(APP_CONTEXT_BASE_DIRECTORY, "/"); + runtimeConfigProperties.set(APP_CONTEXT_BASE_DIRECTORY, browserVirtualAppBase); runtimeConfigProperties.set(RUNTIME_IDENTIFIER, "browser-wasm"); runtimeConfigProperties.set(HOST_PROPERTY_RUNTIME_CONTRACT, `0x${(hostContractPtr as unknown as number).toString(16)}`); @@ -143,24 +58,6 @@ export function initializeCoreCLR(): number { return res; } -// bool BrowserHost_ExternalAssemblyProbe(const char* pathPtr, /*out*/ void **outDataStartPtr, /*out*/ int64_t* outSize); -export function BrowserHost_ExternalAssemblyProbe(pathPtr: CharPtr, outDataStartPtr: VoidPtrPtr, outSize: VoidPtr) { - const path = _ems_.UTF8ToString(pathPtr); - const assembly = loadedAssemblies.get(path); - if (assembly) { - _ems_.HEAPU32[outDataStartPtr as any >>> 2] = assembly.ptr; - // int64_t target - _ems_.HEAPU32[outSize as any >>> 2] = assembly.length; - _ems_.HEAPU32[((outSize as any) + 4) >>> 2] = 0; - return true; - } - _ems_.dotnetLogger.debug(`Assembly not found: '${path}'`); - _ems_.HEAPU32[outDataStartPtr as any >>> 2] = 0; - _ems_.HEAPU32[outSize as any >>> 2] = 0; - _ems_.HEAPU32[((outSize as any) + 4) >>> 2] = 0; - return false; -} - export async function runMain(mainAssemblyName?: string, args?: string[]): Promise { try { const config = _ems_.dotnetApi.getConfig(); @@ -200,12 +97,12 @@ export async function runMain(mainAssemblyName?: string, args?: string[]): Promi _ems_.stackRestore(sp); } } catch (error: any) { - // if the error is an ExitStatus, use its status code - if (error && typeof error.status === "number") { - return error.status; + // do not propagate ExitStatus exception + if (!error || typeof error.status !== "number") { + _ems_.dotnetApi.exit(1, error); + throw error; } - _ems_.dotnetApi.exit(1, error); - throw error; + return error.status; } } @@ -215,10 +112,11 @@ export async function runMainAndExit(mainAssemblyName?: string, args?: string[]) _ems_.dotnetApi.exit(0, null); } catch (error: any) { // do not propagate ExitStatus exception - if (error.status === undefined) { + if (!error || typeof error.status !== "number") { _ems_.dotnetApi.exit(1, error); throw error; } + return error.status; } return res; } diff --git a/src/native/corehost/browserhost/host/index.ts b/src/native/corehost/browserhost/host/index.ts index e14e67d7f5de39..8ed36684b95387 100644 --- a/src/native/corehost/browserhost/host/index.ts +++ b/src/native/corehost/browserhost/host/index.ts @@ -1,13 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { InternalExchange, BrowserHostExports, RuntimeAPI, BrowserHostExportsTable } from "./types"; +import type { InternalExchange, BrowserHostExports, RuntimeAPI, BrowserHostExportsTable, LoaderConfigInternal } from "./types"; import { InternalExchangeIndex } from "./types"; import { _ems_ } from "../../../libs/Common/JavaScript/ems-ambient"; import GitHash from "consts:gitHash"; -import { runMain, runMainAndExit, registerDllBytes, installVfsFile, loadIcuData, initializeCoreCLR, registerPdbBytes } from "./host"; +import { runMain, runMainAndExit, initializeCoreCLR } from "./host"; +import { registerPdbBytes, registerDllBytes, installVfsFile, loadIcuData, instantiateWasm, } from "./assets"; +import { Module } from "./cross-module"; export function dotnetInitializeModule(internals: InternalExchange): void { if (!Array.isArray(internals)) throw new Error("Expected internals to be an array"); @@ -28,8 +30,12 @@ export function dotnetInitializeModule(internals: InternalExchange): void { loadIcuData, initializeCoreCLR, registerPdbBytes, + instantiateWasm, }); _ems_.dotnetUpdateInternals(internals, _ems_.dotnetUpdateInternalsSubscriber); + + setupEmscripten(); + function browserHostExportsToTable(map: BrowserHostExports): BrowserHostExportsTable { // keep in sync with browserHostExportsFromTable() return [ @@ -38,8 +44,32 @@ export function dotnetInitializeModule(internals: InternalExchange): void { map.loadIcuData, map.initializeCoreCLR, map.registerPdbBytes, + map.instantiateWasm, ]; } } -export { BrowserHost_ExternalAssemblyProbe } from "./host"; +function setupEmscripten() { + const loaderConfig = _ems_.dotnetApi.getConfig() as LoaderConfigInternal; + if (!loaderConfig.resources || + !loaderConfig.resources.assembly || + !loaderConfig.resources.coreAssembly || + loaderConfig.resources.coreAssembly.length === 0 || + !loaderConfig.mainAssemblyName || + !loaderConfig.virtualWorkingDirectory || + !loaderConfig.environmentVariables) { + throw new Error("Invalid runtime config, cannot initialize the runtime."); + } + + for (const key in loaderConfig.environmentVariables) { + _ems_.ENV[key] = loaderConfig.environmentVariables[key]; + } + + Module.preInit = [() => { + _ems_.FS.createPath("/", loaderConfig.virtualWorkingDirectory!, true, true); + _ems_.FS.chdir(loaderConfig.virtualWorkingDirectory!); + }, ...(Module.preInit || [])]; + +} + +export { BrowserHost_ExternalAssemblyProbe } from "./assets"; diff --git a/src/native/corehost/browserhost/host/per-module.ts b/src/native/corehost/browserhost/host/per-module.ts new file mode 100644 index 00000000000000..4d63f6790bfbce --- /dev/null +++ b/src/native/corehost/browserhost/host/per-module.ts @@ -0,0 +1,4 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +export * from "../../../libs/Common/JavaScript/per-module"; diff --git a/src/native/corehost/browserhost/libBrowserHost.footer.js b/src/native/corehost/browserhost/libBrowserHost.footer.js index a64f55ecc6975d..b4f068b3562ed2 100644 --- a/src/native/corehost/browserhost/libBrowserHost.footer.js +++ b/src/native/corehost/browserhost/libBrowserHost.footer.js @@ -7,83 +7,87 @@ */ /* eslint-disable no-undef */ -/* eslint-disable space-before-function-paren */ -(function () { - function libFactory() { - // this executes the function at link time in order to capture exports - // this is what Emscripten does for linking JS libraries - // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#javascript-limits-in-library-files - // it would execute the code at link time and call .toString() on functions to move it to the final output - // this process would loose any closure references, unless they are passed to `__deps` and also explicitly given to the linker - // JS name mangling and minification also applies, see src\native\rollup.config.defines.js and `reserved` there - const exports = {}; - libBrowserHost(exports); +function libBrowserHostFactory() { + // this executes the function at link time in order to capture exports + // this is what Emscripten does for linking JS libraries + // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#javascript-limits-in-library-files + // it would execute the code at link time and call .toString() on functions to move it to the final output + // this process would loose any closure references, unless they are passed to `__deps` and also explicitly given to the linker + // JS name mangling and minification also applies, see src\native\rollup.config.defines.js and `reserved` there + const exports = {}; + libBrowserHost(exports); - // libBrowserHostFn is too complex for acorn-optimizer.mjs to find the dependencies - let explicitDeps = [ - "wasm_load_icu_data", "BrowserHost_CreateHostContract", "BrowserHost_InitializeCoreCLR", "BrowserHost_ExecuteAssembly" - ]; - let commonDeps = [ - "$DOTNET", "$DOTNET_INTEROP", "$ENV", "$FS", "$NODEFS", - "$libBrowserHostFn", - ...explicitDeps - ]; - const lib = { - $BROWSER_HOST: { - selfInitialize: () => { - if (typeof dotnetInternals !== "undefined") { - BROWSER_HOST.dotnetInternals = dotnetInternals; + // libBrowserHostFn is too complex for acorn-optimizer.mjs to find the dependencies + let explicitDeps = [ + "wasm_load_icu_data", + "BrowserHost_CreateHostContract", + "BrowserHost_InitializeCoreCLR", + "BrowserHost_ExecuteAssembly" + ]; + let commonDeps = [ + "$DOTNET", + "$DOTNET_INTEROP", + "$ENV", + "$FS", + "$libBrowserHostFn", + ...explicitDeps + ]; + const mergeBrowserHost = { + $BROWSER_HOST: { + selfInitialize: () => { + if (typeof dotnetInternals !== "undefined") { + BROWSER_HOST.dotnetInternals = dotnetInternals; - const exports = {}; - libBrowserHostFn(exports); - exports.dotnetInitializeModule(dotnetInternals); - BROWSER_HOST.assignExports(exports, BROWSER_HOST); - - const loaderConfig = dotnetInternals[2/*InternalExchangeIndex.LoaderConfig*/]; - if (!loaderConfig.resources.assembly || - !loaderConfig.resources.coreAssembly || - loaderConfig.resources.coreAssembly.length === 0 || - !loaderConfig.mainAssemblyName || - !loaderConfig.virtualWorkingDirectory || - !loaderConfig.environmentVariables) { - throw new Error("Invalid runtime config, cannot initialize the runtime."); - } + const exports = {}; + libBrowserHostFn(exports); + exports.dotnetInitializeModule(dotnetInternals); + BROWSER_HOST.assignExports(exports, BROWSER_HOST); + } + }, + }, + $libBrowserHostFn: mergeBrowserHost, + $BROWSER_HOST__postset: "BROWSER_HOST.selfInitialize()", + $BROWSER_HOST__deps: commonDeps, + }; - for (const key in loaderConfig.environmentVariables) { - ENV[key] = loaderConfig.environmentVariables[key]; - } + let assignExportsBuilder = ""; + let explicitImportsBuilder = ""; + for (const exportName of Reflect.ownKeys(exports)) { + const name = String(exportName); + if (name === "dotnetInitializeModule") continue; + mergeBrowserHost[name] = () => "dummy"; + assignExportsBuilder += `_${String(name)} = exports.${String(name)};\n`; + } + for (const importName of explicitDeps) { + explicitImportsBuilder += `_${importName}();\n`; + } + mergeBrowserHost.$BROWSER_HOST.assignExports = new Function("exports", assignExportsBuilder); + mergeBrowserHost.$BROWSER_HOST.explicitImports = new Function(explicitImportsBuilder); - if (ENVIRONMENT_IS_NODE) { - Module.preInit = [() => { - FS.mkdir("/managed"); - FS.mount(NODEFS, { root: "." }, "/managed"); - FS.chdir("/managed"); - }]; - } - } - }, - }, - $libBrowserHostFn: libBrowserHost, - $BROWSER_HOST__postset: "BROWSER_HOST.selfInitialize()", - $BROWSER_HOST__deps: commonDeps, - }; + autoAddDeps(mergeBrowserHost, "$BROWSER_HOST"); + addToLibrary(mergeBrowserHost); - let assignExportsBuilder = ""; - let explicitImportsBuilder = ""; - for (const exportName of Reflect.ownKeys(exports)) { - const name = String(exportName); - if (name === "dotnetInitializeModule") continue; - lib[name] = () => "dummy"; - assignExportsBuilder += `_${String(name)} = exports.${String(name)};\n`; - } - for (const importName of explicitDeps) { - explicitImportsBuilder += `_${importName}();\n`; - } - lib.$BROWSER_HOST.assignExports = new Function("exports", assignExportsBuilder); - lib.$BROWSER_HOST.explicitImports = new Function(explicitImportsBuilder); + function trim() { + return -1; + } - autoAddDeps(lib, "$BROWSER_HOST"); - addToLibrary(lib); + // TODO-WASM: fix PAL https://github.com/dotnet/runtime/issues/122506 + if (LibraryManager.library.__syscall_pipe) { + LibraryManager.library.__syscall_pipe = trim; + delete LibraryManager.library.__syscall_pipe__deps; + } + if (LibraryManager.library.__syscall_connect) { + LibraryManager.library.__syscall_connect = trim; + delete LibraryManager.library.__syscall_connect__deps; + } + if (LibraryManager.library.__syscall_sendto) { + LibraryManager.library.__syscall_sendto = trim; + delete LibraryManager.library.__syscall_sendto__deps; } - libFactory(); -})(); + if (LibraryManager.library.__syscall_socket) { + LibraryManager.library.__syscall_socket = trim; + delete LibraryManager.library.__syscall_socket__deps; + } +} + +libBrowserHostFactory(); diff --git a/src/native/corehost/browserhost/loader/assets.ts b/src/native/corehost/browserhost/loader/assets.ts index 7edbcb151e9c20..90352ff8a9cf5a 100644 --- a/src/native/corehost/browserhost/loader/assets.ts +++ b/src/native/corehost/browserhost/loader/assets.ts @@ -1,10 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { JsModuleExports, JsAsset, AssemblyAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal, InstantiateWasmSuccessCallback, WebAssemblyBootResourceType, AssetEntryInternal, PromiseCompletionSource, LoadBootResourceCallback } from "./types"; +import type { JsModuleExports, JsAsset, AssemblyAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal, WebAssemblyBootResourceType, AssetEntryInternal, PromiseCompletionSource, LoadBootResourceCallback, InstantiateWasmSuccessCallback } from "./types"; import { dotnetAssert, dotnetLogger, dotnetInternals, dotnetBrowserHostExports, dotnetUpdateInternals, Module } from "./cross-module"; -import { ENVIRONMENT_IS_WEB, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_NODE } from "./per-module"; +import { ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_NODE, browserVirtualAppBase } from "./per-module"; import { createPromiseCompletionSource, delay } from "./promise-completion-source"; import { locateFile, makeURLAbsoluteWithApplicationBase } from "./bootstrap"; import { fetchLike, responseLike } from "./polyfills"; @@ -19,8 +19,8 @@ let loadBootResourceCallback: LoadBootResourceCallback | undefined = undefined; export function setLoadBootResourceCallback(callback: LoadBootResourceCallback | undefined): void { loadBootResourceCallback = callback; } -let instantiateStreaming = typeof WebAssembly !== "undefined" && typeof WebAssembly.instantiateStreaming === "function"; export let wasmBinaryPromise: Promise | undefined = undefined; +export const mainModulePromiseController = createPromiseCompletionSource(); export const nativeModulePromiseController = createPromiseCompletionSource(() => { dotnetUpdateInternals(dotnetInternals); }); @@ -39,9 +39,9 @@ export async function loadJSModule(asset: JsAsset): Promise { } assetInternal.behavior = "js-module-dotnet"; if (typeof loadBootResourceCallback === "function") { - const type = behaviorToBlazorAssetTypeMap[assetInternal.behavior]; - dotnetAssert.check(type, `Unsupported asset behavior: ${assetInternal.behavior}`); - const customLoadResult = loadBootResourceCallback(type, assetInternal.name, asset.resolvedUrl!, assetInternal.integrity!, assetInternal.behavior); + const blazorType = behaviorToBlazorAssetTypeMap[assetInternal.behavior]; + dotnetAssert.check(blazorType, `Unsupported asset behavior: ${assetInternal.behavior}`); + const customLoadResult = loadBootResourceCallback(blazorType, assetInternal.name, asset.resolvedUrl!, assetInternal.integrity!, assetInternal.behavior); dotnetAssert.check(typeof customLoadResult === "string", "loadBootResourceCallback for JS modules must return string URL"); asset.resolvedUrl = makeURLAbsoluteWithApplicationBase(customLoadResult); } @@ -62,39 +62,14 @@ export function fetchWasm(asset: WasmAsset): Promise { assetInternal.behavior = "dotnetwasm"; if (!asset.resolvedUrl) throw new Error("Invalid config, resources is not set"); wasmBinaryPromise = loadResource(assetInternal); - if (assetInternal.buffer) { - instantiateStreaming = false; - } return wasmBinaryPromise; } -export async function instantiateWasm(imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback): Promise { - if (!instantiateStreaming) { - const res = await checkResponseOk(); - const data = await res.arrayBuffer(); - const module = await WebAssembly.compile(data); - const instance = await WebAssembly.instantiate(module, imports); - onDownloadedAsset(); - successCallback(instance, module); - } else { - const instantiated = await WebAssembly.instantiateStreaming(wasmBinaryPromise!, imports); - await checkResponseOk(); - onDownloadedAsset(); - successCallback(instantiated.instance, instantiated.module); - } - - async function checkResponseOk(): Promise { - dotnetAssert.check(wasmBinaryPromise, "WASM binary promise was not initialized"); - const res = await wasmBinaryPromise; - if (res.ok === false) { - throw new Error(`Failed to load WebAssembly module. HTTP status: ${res.status} ${res.statusText}`); - } - const contentType = res.headers && res.headers.get ? res.headers.get("Content-Type") : undefined; - if (ENVIRONMENT_IS_WEB && contentType !== "application/wasm") { - dotnetLogger.warn("WebAssembly resource does not have the expected content type \"application/wasm\", so falling back to slower ArrayBuffer instantiation."); - } - return res; - } +export async function instantiateMainWasm(imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback): Promise { + const { instance, module } = await dotnetBrowserHostExports.instantiateWasm(wasmBinaryPromise!, imports, true, true); + onDownloadedAsset(); + successCallback(instance, module); + mainModulePromiseController.resolve(instance); } export async function fetchIcu(asset: IcuAsset): Promise { @@ -115,33 +90,42 @@ export async function fetchIcu(asset: IcuAsset): Promise { export async function fetchDll(asset: AssemblyAsset): Promise { totalAssetsToDownload++; const assetInternal = asset as AssetEntryInternal; + dotnetAssert.check(assetInternal.virtualPath, "Assembly asset must have virtualPath"); if (assetInternal.name && !asset.resolvedUrl) { asset.resolvedUrl = locateFile(assetInternal.name); } assetInternal.behavior = "assembly"; + assetInternal.virtualPath = assetInternal.virtualPath.startsWith("/") + ? assetInternal.virtualPath + : browserVirtualAppBase + assetInternal.virtualPath; + const bytes = await fetchBytes(assetInternal); await nativeModulePromiseController.promise; onDownloadedAsset(); if (bytes) { - dotnetBrowserHostExports.registerDllBytes(bytes, asset); + dotnetBrowserHostExports.registerDllBytes(bytes, asset.virtualPath); } } export async function fetchPdb(asset: AssemblyAsset): Promise { totalAssetsToDownload++; const assetInternal = asset as AssetEntryInternal; + dotnetAssert.check(assetInternal.virtualPath, "PDB asset must have virtualPath"); if (assetInternal.name && !asset.resolvedUrl) { asset.resolvedUrl = locateFile(assetInternal.name); } assetInternal.behavior = "pdb"; assetInternal.isOptional = assetInternal.isOptional || loaderConfig.ignorePdbLoadErrors; + assetInternal.virtualPath = assetInternal.virtualPath.startsWith("/") + ? assetInternal.virtualPath + : browserVirtualAppBase + assetInternal.virtualPath; const bytes = await fetchBytes(assetInternal); await nativeModulePromiseController.promise; onDownloadedAsset(); if (bytes) { - dotnetBrowserHostExports.registerPdbBytes(bytes, asset); + dotnetBrowserHostExports.registerPdbBytes(bytes, asset.virtualPath); } } @@ -279,9 +263,9 @@ async function loadResourceFetch(asset: AssetEntryInternal): Promise { return asset.pendingDownload.response; } if (typeof loadBootResourceCallback === "function") { - const type = behaviorToBlazorAssetTypeMap[asset.behavior]; - dotnetAssert.check(type, `Unsupported asset behavior: ${asset.behavior}`); - const customLoadResult = loadBootResourceCallback(type, asset.name, asset.resolvedUrl!, asset.integrity!, asset.behavior); + const blazorType = behaviorToBlazorAssetTypeMap[asset.behavior]; + dotnetAssert.check(blazorType, `Unsupported asset behavior: ${asset.behavior}`); + const customLoadResult = loadBootResourceCallback(blazorType, asset.name, asset.resolvedUrl!, asset.integrity!, asset.behavior); if (typeof customLoadResult === "string") { asset.resolvedUrl = makeURLAbsoluteWithApplicationBase(customLoadResult); } else if (typeof customLoadResult === "object") { diff --git a/src/native/corehost/browserhost/loader/bootstrap.ts b/src/native/corehost/browserhost/loader/bootstrap.ts index 8d54da549bf5e1..77fa053c4e328d 100644 --- a/src/native/corehost/browserhost/loader/bootstrap.ts +++ b/src/native/corehost/browserhost/loader/bootstrap.ts @@ -1,13 +1,9 @@ // 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, DotnetHostBuilder } from "./types"; - import { exceptions, simd } from "wasm-feature-detect"; -import { GlobalizationMode } from "./types"; import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL } from "./per-module"; -import { nodeFs } from "./polyfills"; import { dotnetAssert } from "./cross-module"; const scriptUrlQuery = /*! webpackIgnore: true */import.meta.url; @@ -74,74 +70,4 @@ export function makeURLAbsoluteWithApplicationBase(url: string) { return url; } -export function isShellHosted(): boolean { - return ENVIRONMENT_IS_SHELL && typeof (globalThis as any).arguments !== "undefined"; -} - -export function isNodeHosted(): boolean { - if (!ENVIRONMENT_IS_NODE || globalThis.process.argv.length < 3) { - return false; - } - const argv1 = globalThis.process.argv[1].toLowerCase(); - const argScript = normalizeFileUrl("file:///" + locateFile(argv1)); - const importScript = normalizeFileUrl(locateFile(scriptUrl.toLowerCase())); - - return argScript === importScript; -} - -// Finds resources when running in NodeJS environment without explicit configuration -export async function findResources(dotnet: DotnetHostBuilder): Promise { - if (!ENVIRONMENT_IS_NODE) { - return; - } - const fs = await nodeFs(); - const mountedDir = "/managed"; - const files: string[] = await fs.promises.readdir("."); - const assemblies = files - // TODO-WASM: webCIL - .filter(file => file.endsWith(".dll")) - .map(filename => { - // filename without path - const name = filename.substring(filename.lastIndexOf("/") + 1); - return { virtualPath: mountedDir + "/" + filename, name }; - }); - const mainAssemblyName = globalThis.process.argv[2]; - const runtimeConfigName = mainAssemblyName.replace(/\.dll$/, ".runtimeconfig.json"); - let runtimeConfig = {}; - if (fs.existsSync(runtimeConfigName)) { - const json = await fs.promises.readFile(runtimeConfigName, { encoding: "utf8" }); - runtimeConfig = JSON.parse(json); - } - const icus = files - .filter(file => file.startsWith("icudt") && file.endsWith(".dat")) - .map(filename => { - // filename without path - const name = filename.substring(filename.lastIndexOf("/") + 1); - return { virtualPath: name, name }; - }); - - const environmentVariables: { [key: string]: string } = {}; - let globalizationMode = GlobalizationMode.All; - if (!icus.length) { - globalizationMode = GlobalizationMode.Invariant; - environmentVariables["DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"] = "1"; - } - const config: LoaderConfig = { - mainAssemblyName, - runtimeConfig, - globalizationMode, - virtualWorkingDirectory: mountedDir, - environmentVariables, - resources: { - jsModuleNative: [{ name: "dotnet.native.js" }], - jsModuleRuntime: [{ name: "dotnet.runtime.js" }], - wasmNative: [{ name: "dotnet.native.wasm", }], - coreAssembly: [{ virtualPath: mountedDir + "/System.Private.CoreLib.dll", name: "System.Private.CoreLib.dll" },], - assembly: assemblies, - icu: icus, - } - }; - dotnet.withConfig(config); - dotnet.withApplicationArguments(...globalThis.process.argv.slice(3)); -} diff --git a/src/native/corehost/browserhost/loader/config.ts b/src/native/corehost/browserhost/loader/config.ts index 5221562a2d5372..7a3766278cd368 100644 --- a/src/native/corehost/browserhost/loader/config.ts +++ b/src/native/corehost/browserhost/loader/config.ts @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { Assets, LoaderConfig, LoaderConfigInternal } from "./types"; +import { browserVirtualAppBase } from "./per-module"; export const loaderConfig: LoaderConfigInternal = {}; @@ -18,7 +19,6 @@ export function validateLoaderConfig(): void { } } - export function mergeLoaderConfig(source: Partial): void { defaultConfig(loaderConfig); normalizeConfig(source); @@ -78,7 +78,7 @@ function defaultConfig(target: LoaderConfigInternal) { if (target.loadAllSatelliteResources === undefined) target.loadAllSatelliteResources = false; if (target.debugLevel === undefined) target.debugLevel = 0; if (target.diagnosticTracing === undefined) target.diagnosticTracing = false; - if (target.virtualWorkingDirectory === undefined) target.virtualWorkingDirectory = "/"; + if (target.virtualWorkingDirectory === undefined) target.virtualWorkingDirectory = browserVirtualAppBase; if (target.maxParallelDownloads === undefined) target.maxParallelDownloads = 16; normalizeConfig(target); } diff --git a/src/native/corehost/browserhost/loader/dotnet.ts b/src/native/corehost/browserhost/loader/dotnet.ts index 6a1b68cf62618e..b9e2edb36cc9d6 100644 --- a/src/native/corehost/browserhost/loader/dotnet.ts +++ b/src/native/corehost/browserhost/loader/dotnet.ts @@ -11,12 +11,10 @@ import type { DotnetHostBuilder } from "./types"; import { HostBuilder } from "./host-builder"; -import { initPolyfills, initPolyfillsAsync } from "./polyfills"; +import { initPolyfillsAsync } from "./polyfills"; import { exit } from "./exit"; import { dotnetInitializeModule } from "."; -import { selfHostNodeJS } from "./run"; -initPolyfills(); dotnetInitializeModule(); await initPolyfillsAsync(); @@ -24,6 +22,3 @@ export const dotnet: DotnetHostBuilder | undefined = new HostBuilder() as Dotnet export { exit }; dotnet.withConfig(/*! dotnetBootConfig */{}); - -// Auto-start when in Node.js or Shell environment -selfHostNodeJS(dotnet!).catch(); diff --git a/src/native/corehost/browserhost/loader/exit.ts b/src/native/corehost/browserhost/loader/exit.ts index 78ecb485d7d96a..7f400e338d3fb1 100644 --- a/src/native/corehost/browserhost/loader/exit.ts +++ b/src/native/corehost/browserhost/loader/exit.ts @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. import type { OnExitListener } from "../types"; -import { dotnetLogger, dotnetLoaderExports, Module, dotnetBrowserUtilsExports, dotnetRuntimeExports } from "./cross-module"; -import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_WEB } from "./per-module"; +import { dotnetLogger, Module, dotnetBrowserUtilsExports, dotnetRuntimeExports, dotnetLoaderExports } from "./cross-module"; +import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, globalThisAny } from "./per-module"; export const runtimeState = { - runtimeReady: false, + creatingRuntime: false, + nativeReady: false, + dotnetReady: false, exitCode: undefined as number | undefined, exitReason: undefined as any, originalOnAbort: undefined as ((reason: any, extraJson?: string) => void) | undefined, @@ -19,7 +21,7 @@ export function isExited() { } export function isRuntimeRunning() { - return runtimeState.runtimeReady && !isExited(); + return runtimeState.dotnetReady && !isExited(); } export function addOnExitListener(cb: OnExitListener) { @@ -114,8 +116,9 @@ export function exit(exitCode: number, reason: any): void { } unregisterExit(); if (!alreadySilent) { - if (runtimeState.onExitListeners.length === 0 && !runtimeState.runtimeReady) { - dotnetLogger.error(`Exiting during runtime startup: ${message} ${stack}`); + if (runtimeState.onExitListeners.length === 0 && !runtimeState.dotnetReady) { + dotnetLogger.error(`Exiting during runtime startup: ${message}`); + dotnetLogger.debug(() => stack); } for (const listener of runtimeState.onExitListeners) { try { @@ -127,7 +130,7 @@ export function exit(exitCode: number, reason: any): void { } } } - if (!runtimeState.runtimeReady) { + if (!runtimeState.dotnetReady) { dotnetLogger.debug(() => `Aborting startup, reason: ${reason}`); dotnetLoaderExports.abortStartup(reason); } @@ -148,13 +151,16 @@ export function exit(exitCode: number, reason: any): void { } export function quitNow(exitCode: number, reason?: any): void { - if (runtimeState.runtimeReady) { + if (runtimeState.dotnetReady) { Module.runtimeKeepalivePop(); if (dotnetBrowserUtilsExports && dotnetBrowserUtilsExports.abortPosix) { - dotnetBrowserUtilsExports.abortPosix(exitCode); + dotnetBrowserUtilsExports.abortPosix(exitCode, reason, runtimeState.dotnetReady); } } if (exitCode !== 0 || !ENVIRONMENT_IS_WEB) { + if (ENVIRONMENT_IS_SHELL && typeof globalThisAny.quit === "function") { + globalThisAny.quit(exitCode); + } if (ENVIRONMENT_IS_NODE && globalThis.process && typeof globalThis.process.exit === "function") { globalThis.process.exitCode = exitCode; globalThis.process.exit(exitCode); diff --git a/src/native/corehost/browserhost/loader/host-builder.ts b/src/native/corehost/browserhost/loader/host-builder.ts index 2bc210965974a1..a2121ea8cefe71 100644 --- a/src/native/corehost/browserhost/loader/host-builder.ts +++ b/src/native/corehost/browserhost/loader/host-builder.ts @@ -132,8 +132,9 @@ export class HostBuilder implements DotnetHostBuilder { try { if (!this.dotnetApi) { await this.create(); + } else { + validateLoaderConfig(); } - validateLoaderConfig(); return this.dotnetApi!.runMain(loaderConfig.mainAssemblyName, applicationArguments); } catch (err) { exit(1, err); @@ -145,8 +146,9 @@ export class HostBuilder implements DotnetHostBuilder { try { if (!this.dotnetApi) { await this.create(); + } else { + validateLoaderConfig(); } - validateLoaderConfig(); return this.dotnetApi!.runMainAndExit(loaderConfig.mainAssemblyName, applicationArguments); } catch (err) { exit(1, err); diff --git a/src/native/corehost/browserhost/loader/index.ts b/src/native/corehost/browserhost/loader/index.ts index c1988d51328b87..6d0479fac95b62 100644 --- a/src/native/corehost/browserhost/loader/index.ts +++ b/src/native/corehost/browserhost/loader/index.ts @@ -21,7 +21,7 @@ import { check, error, info, warn, debug, fastCheck } from "./logging"; import { dotnetAssert, dotnetLoaderExports, dotnetLogger, dotnetUpdateInternals, dotnetUpdateInternalsSubscriber } from "./cross-module"; import { rejectRunMainPromise, resolveRunMainPromise, getRunMainPromise, abortStartup } from "./run"; import { createPromiseCompletionSource, getPromiseCompletionSource, isControllablePromise } from "./promise-completion-source"; -import { instantiateWasm } from "./assets"; +import { instantiateMainWasm } from "./assets"; export function dotnetInitializeModule(): RuntimeAPI { @@ -83,7 +83,7 @@ export function dotnetInitializeModule(): RuntimeAPI { // emscripten extension point const localModule: Partial = { - instantiateWasm, + instantiateWasm: instantiateMainWasm, }; Object.assign(dotnetApi.Module!, localModule); diff --git a/src/native/corehost/browserhost/loader/polyfills.ts b/src/native/corehost/browserhost/loader/polyfills.ts index 0b1cf55d303bd6..bae7aed2320ba7 100644 --- a/src/native/corehost/browserhost/loader/polyfills.ts +++ b/src/native/corehost/browserhost/loader/polyfills.ts @@ -1,10 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { dotnetAssert } from "./cross-module"; import { ENVIRONMENT_IS_NODE } from "./per-module"; -export function initPolyfills(): void { - if (typeof globalThis.fetch !== "function") { +let hasFetch = false; + +export async function initPolyfills(): Promise { + if (ENVIRONMENT_IS_NODE) { + await nodeFs(); + await nodeUrl(); + } + hasFetch = typeof (globalThis.fetch) === "function"; + if (!hasFetch) { globalThis.fetch = fetchLike as any; } } @@ -71,10 +79,6 @@ export async function nodeUrl(): Promise { export async function fetchLike(url: string, init?: RequestInit, expectedContentType?: string): Promise { try { - await nodeFs(); - await nodeUrl(); - // this need to be detected only after we import node modules in onConfigLoaded - const hasFetch = typeof (globalThis.fetch) === "function"; if (ENVIRONMENT_IS_NODE) { const isFileUrl = url.startsWith("file://"); if (!isFileUrl && hasFetch) { @@ -96,12 +100,13 @@ export async function fetchLike(url: string, init?: RequestInit, expectedContent } else if (hasFetch) { return globalThis.fetch(url, init || { credentials: "same-origin" }); } else if (typeof (read) === "function") { - const arrayBuffer = read(url, "binary"); + const isText = expectedContentType === "application/json" || expectedContentType === "text/plain"; + const arrayBuffer = read(url, isText ? "utf8" : "binary"); return responseLike(url, arrayBuffer, { status: 200, statusText: "OK", headers: { - "Content-Length": arrayBuffer.byteLength.toString(), + "Content-Length": isText ? arrayBuffer.length : arrayBuffer.byteLength.toString(), "Content-Type": expectedContentType || "application/octet-stream" } }); @@ -116,7 +121,7 @@ export async function fetchLike(url: string, init?: RequestInit, expectedContent throw new Error("No fetch implementation available"); } -export function responseLike(url: string, body: ArrayBuffer | null, options: ResponseInit): Response { +export function responseLike(url: string, body: ArrayBuffer | string | null, options: ResponseInit): Response { if (typeof globalThis.Response === "function") { const response = new Response(body, options); @@ -139,12 +144,17 @@ export function responseLike(url: string, body: ArrayBuffer | null, options: Res get: (name: string) => (options.headers as any)[name] || null }, url, - arrayBuffer: () => Promise.resolve(body), + arrayBuffer: () => { + dotnetAssert.check(body !== null && body instanceof ArrayBuffer, "Response body is not a ArrayBuffer."); + return Promise.resolve(body); + }, json: () => { - throw new Error("NotImplementedException"); + dotnetAssert.check(body !== null && typeof body === "string", "Response body is not a string."); + return Promise.resolve(JSON.parse(body)); }, text: () => { - throw new Error("NotImplementedException"); + dotnetAssert.check(body !== null && typeof body === "string", "Response body is not a string."); + return Promise.resolve(body); } }; } diff --git a/src/native/corehost/browserhost/loader/run.ts b/src/native/corehost/browserhost/loader/run.ts index bfe043e648ba04..5d6838b5ef7969 100644 --- a/src/native/corehost/browserhost/loader/run.ts +++ b/src/native/corehost/browserhost/loader/run.ts @@ -1,22 +1,22 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import type { DotnetHostBuilder, JsModuleExports, EmscriptenModuleInternal } from "./types"; +import type { JsModuleExports, EmscriptenModuleInternal } from "./types"; import { dotnetAssert, dotnetInternals, dotnetBrowserHostExports, Module } from "./cross-module"; -import { findResources, isNodeHosted, isShellHosted, validateWasmFeatures } from "./bootstrap"; import { exit, runtimeState } from "./exit"; import { createPromiseCompletionSource } from "./promise-completion-source"; import { getIcuResourceName } from "./icu"; -import { loaderConfig } from "./config"; +import { loaderConfig, validateLoaderConfig } from "./config"; import { fetchDll, fetchIcu, fetchPdb, fetchVfs, fetchWasm, loadDotnetModule, loadJSModule, nativeModulePromiseController, verifyAllAssetsDownloaded } from "./assets"; +import { initPolyfills } from "./polyfills"; +import { validateWasmFeatures } from "./bootstrap"; const runMainPromiseController = createPromiseCompletionSource(); // WASM-TODO: webCIL // WASM-TODO: downloadOnly - blazor render mode auto pre-download. Really no start. // WASM-TODO: loadAllSatelliteResources -// WASM-TODO: runtimeOptions // WASM-TODO: debugLevel // WASM-TODO: load symbolication json https://github.com/dotnet/runtime/issues/122647 @@ -24,83 +24,100 @@ const runMainPromiseController = createPromiseCompletionSource(); // ideally we want to utilize network and CPU at the same time export async function createRuntime(downloadOnly: boolean): Promise { if (!loaderConfig.resources || !loaderConfig.resources.coreAssembly || !loaderConfig.resources.coreAssembly.length) throw new Error("Invalid config, resources is not set"); + try { + runtimeState.creatingRuntime = true; - await validateWasmFeatures(); + await validateWasmFeatures(); - if (typeof Module.onConfigLoaded === "function") { - await Module.onConfigLoaded(loaderConfig); - } - const modulesAfterConfigLoaded = await Promise.all((loaderConfig.resources.modulesAfterConfigLoaded || []).map(loadJSModule)); - for (const afterConfigLoadedModule of modulesAfterConfigLoaded) { - await afterConfigLoadedModule.onRuntimeConfigLoaded?.(loaderConfig); - } + if (typeof Module.onConfigLoaded === "function") { + await Module.onConfigLoaded(loaderConfig); + } + validateLoaderConfig(); - if (loaderConfig.resources.jsModuleDiagnostics && loaderConfig.resources.jsModuleDiagnostics.length > 0) { - const diagnosticsModule = await loadDotnetModule(loaderConfig.resources.jsModuleDiagnostics[0]); - diagnosticsModule.dotnetInitializeModule(dotnetInternals); - } - const nativeModulePromise: Promise = loadDotnetModule(loaderConfig.resources.jsModuleNative[0]); - const runtimeModulePromise: Promise = loadDotnetModule(loaderConfig.resources.jsModuleRuntime[0]); - const wasmNativePromise: Promise = fetchWasm(loaderConfig.resources.wasmNative[0]); - - const coreAssembliesPromise = Promise.all(loaderConfig.resources.coreAssembly.map(fetchDll)); - const coreVfsPromise = Promise.all((loaderConfig.resources.coreVfs || []).map(fetchVfs)); - const assembliesPromise = Promise.all(loaderConfig.resources.assembly.map(fetchDll)); - const vfsPromise = Promise.all((loaderConfig.resources.vfs || []).map(fetchVfs)); - const icuResourceName = getIcuResourceName(); - const icuDataPromise = icuResourceName ? Promise.all((loaderConfig.resources.icu || []).filter(asset => asset.name === icuResourceName).map(fetchIcu)) : Promise.resolve([]); - - const corePDBsPromise = Promise.all((loaderConfig.resources.corePdb || []).map(fetchPdb)); - const pdbsPromise = Promise.all((loaderConfig.resources.pdb || []).map(fetchPdb)); - const modulesAfterRuntimeReadyPromise = Promise.all((loaderConfig.resources.modulesAfterRuntimeReady || []).map(loadJSModule)); - - const nativeModule = await nativeModulePromise; - const modulePromise = nativeModule.dotnetInitializeModule(dotnetInternals); - nativeModulePromiseController.propagateFrom(modulePromise); - - const runtimeModule = await runtimeModulePromise; - const runtimeModuleReady = runtimeModule.dotnetInitializeModule(dotnetInternals); - - await nativeModulePromiseController.promise; - await coreAssembliesPromise; - await coreVfsPromise; - await vfsPromise; - await icuDataPromise; - await wasmNativePromise; // this is just to propagate errors - if (!downloadOnly) { - Module.runtimeKeepalivePush(); - initializeCoreCLR(); - } + const modulesAfterConfigLoaded = await Promise.all((loaderConfig.resources.modulesAfterConfigLoaded || []).map(loadJSModule)); + for (const afterConfigLoadedModule of modulesAfterConfigLoaded) { + await afterConfigLoadedModule.onRuntimeConfigLoaded?.(loaderConfig); + } + + // after onConfigLoaded hooks, polyfills can be initialized + await initPolyfills(); + + if (loaderConfig.resources.jsModuleDiagnostics && loaderConfig.resources.jsModuleDiagnostics.length > 0) { + const diagnosticsModule = await loadDotnetModule(loaderConfig.resources.jsModuleDiagnostics[0]); + diagnosticsModule.dotnetInitializeModule(dotnetInternals); + } + const nativeModulePromise: Promise = loadDotnetModule(loaderConfig.resources.jsModuleNative[0]); + const runtimeModulePromise: Promise = loadDotnetModule(loaderConfig.resources.jsModuleRuntime[0]); + const wasmNativePromise: Promise = fetchWasm(loaderConfig.resources.wasmNative[0]); + + const coreAssembliesPromise = Promise.all(loaderConfig.resources.coreAssembly.map(fetchDll)); + const coreVfsPromise = Promise.all((loaderConfig.resources.coreVfs || []).map(fetchVfs)); + + const assembliesPromise = Promise.all(loaderConfig.resources.assembly.map(fetchDll)); + const vfsPromise = Promise.all((loaderConfig.resources.vfs || []).map(fetchVfs)); + + const icuResourceName = getIcuResourceName(); + const icuDataPromise = icuResourceName ? Promise.all((loaderConfig.resources.icu || []).filter(asset => asset.name === icuResourceName).map(fetchIcu)) : Promise.resolve([]); + + // WASM-TODO: also check that the debugger is linked in and check feature flags + const isDebuggingSupported = loaderConfig.debugLevel != 0; + const corePDBsPromise = isDebuggingSupported ? Promise.all((loaderConfig.resources.corePdb || []).map(fetchPdb)) : Promise.resolve([]); + const pdbsPromise = isDebuggingSupported ? Promise.all((loaderConfig.resources.pdb || []).map(fetchPdb)) : Promise.resolve([]); + const modulesAfterRuntimeReadyPromise = Promise.all((loaderConfig.resources.modulesAfterRuntimeReady || []).map(loadJSModule)); + + const nativeModule = await nativeModulePromise; + const modulePromise = nativeModule.dotnetInitializeModule(dotnetInternals); + nativeModulePromiseController.propagateFrom(modulePromise); + + const runtimeModule = await runtimeModulePromise; + const runtimeModuleReady = runtimeModule.dotnetInitializeModule(dotnetInternals); + + await nativeModulePromiseController.promise; + runtimeState.nativeReady = true; + await coreAssembliesPromise; + await coreVfsPromise; + await vfsPromise; + await icuDataPromise; + await wasmNativePromise; // this is just to propagate errors + if (!downloadOnly) { + Module.runtimeKeepalivePush(); + initializeCoreCLR(); + } - await assembliesPromise; - await corePDBsPromise; - await pdbsPromise; - await runtimeModuleReady; + await assembliesPromise; + await corePDBsPromise; + await pdbsPromise; + await runtimeModuleReady; - verifyAllAssetsDownloaded(); + verifyAllAssetsDownloaded(); - if (typeof Module.onDotnetReady === "function") { - await Module.onDotnetReady(); - } - const modulesAfterRuntimeReady = await modulesAfterRuntimeReadyPromise; - for (const afterRuntimeReadyModule of modulesAfterRuntimeReady) { - await afterRuntimeReadyModule.onRuntimeReady?.(loaderConfig); + if (typeof Module.onDotnetReady === "function") { + await Module.onDotnetReady(); + } + const modulesAfterRuntimeReady = await modulesAfterRuntimeReadyPromise; + for (const afterRuntimeReadyModule of modulesAfterRuntimeReady) { + await afterRuntimeReadyModule.onRuntimeReady?.(loaderConfig); + } + runtimeState.creatingRuntime = false; + } catch (err) { + exit(1, err); } } - export function abortStartup(reason: any): void { - nativeModulePromiseController.reject(reason); + if (runtimeState.creatingRuntime) { + nativeModulePromiseController.reject(reason); + } } function initializeCoreCLR(): void { - dotnetAssert.check(!runtimeState.runtimeReady, "CoreCLR should be initialized just once"); + dotnetAssert.check(!runtimeState.dotnetReady, "CoreCLR should be initialized just once"); const res = dotnetBrowserHostExports.initializeCoreCLR(); if (res != 0) { const reason = new Error("Failed to initialize CoreCLR"); runMainPromiseController.reject(reason); exit(res, reason); } - runtimeState.runtimeReady = true; + runtimeState.dotnetReady = true; } export function resolveRunMainPromise(exitCode: number): void { @@ -115,17 +132,4 @@ export function getRunMainPromise(): Promise { return runMainPromiseController.promise; } -// Auto-start when in NodeJS environment as a entry script -export async function selfHostNodeJS(dotnet: DotnetHostBuilder): Promise { - try { - if (isNodeHosted()) { - await findResources(dotnet); - await dotnet.runMainAndExit(); - } else if (isShellHosted()) { - // because in V8 we can't probe directories to find assemblies - throw new Error("Shell/V8 hosting is not supported"); - } - } catch (err: any) { - exit(1, err); - } -} + diff --git a/src/native/libs/Common/JavaScript/CMakeLists.txt b/src/native/libs/Common/JavaScript/CMakeLists.txt index 6a26a0c1e24e4e..880d4dac2f58ed 100644 --- a/src/native/libs/Common/JavaScript/CMakeLists.txt +++ b/src/native/libs/Common/JavaScript/CMakeLists.txt @@ -17,8 +17,11 @@ set(ROLLUP_TS_SOURCES "${CLR_SRC_NATIVE_DIR}/libs/System.Native.Browser/libSystem.Native.Browser.footer.js" "${CLR_SRC_NATIVE_DIR}/libs/System.Runtime.InteropServices.JavaScript.Native/libSystem.Runtime.InteropServices.JavaScript.Native.footer.js" + "${CLR_SRC_NATIVE_DIR}/corehost/browserhost/host/assets.ts" + "${CLR_SRC_NATIVE_DIR}/corehost/browserhost/host/cross-module.ts" "${CLR_SRC_NATIVE_DIR}/corehost/browserhost/host/host.ts" "${CLR_SRC_NATIVE_DIR}/corehost/browserhost/host/index.ts" + "${CLR_SRC_NATIVE_DIR}/corehost/browserhost/host/per-module.ts" "${CLR_SRC_NATIVE_DIR}/corehost/browserhost/host/types.ts" "${CLR_SRC_NATIVE_DIR}/corehost/browserhost/loader/assets.ts" "${CLR_SRC_NATIVE_DIR}/corehost/browserhost/loader/bootstrap.ts" diff --git a/src/native/libs/Common/JavaScript/cross-module/index.ts b/src/native/libs/Common/JavaScript/cross-module/index.ts index 0976649a3c8ca8..e2a5f86213910f 100644 --- a/src/native/libs/Common/JavaScript/cross-module/index.ts +++ b/src/native/libs/Common/JavaScript/cross-module/index.ts @@ -150,6 +150,7 @@ export function dotnetUpdateInternalsSubscriber() { loadIcuData: table[2], initializeCoreCLR: table[3], registerPdbBytes: table[4], + instantiateWasm: table[5], }; Object.assign(native, nativeLocal); } diff --git a/src/native/libs/Common/JavaScript/per-module/index.ts b/src/native/libs/Common/JavaScript/per-module/index.ts index 648a4667d4aecf..577503db58402f 100644 --- a/src/native/libs/Common/JavaScript/per-module/index.ts +++ b/src/native/libs/Common/JavaScript/per-module/index.ts @@ -3,12 +3,14 @@ import type { VoidPtr, CharPtr, NativePointer } from "../types"; +export const globalThisAny = globalThis as any; export const ENVIRONMENT_IS_NODE = typeof process === "object" && typeof process.versions === "object" && typeof process.versions.node === "string"; export const ENVIRONMENT_IS_WEB_WORKER = typeof importScripts === "function"; -export const ENVIRONMENT_IS_SIDECAR = ENVIRONMENT_IS_WEB_WORKER && typeof (globalThis as any).dotnetSidecar !== "undefined"; // sidecar is emscripten main running in a web worker +export const ENVIRONMENT_IS_SIDECAR = ENVIRONMENT_IS_WEB_WORKER && typeof globalThisAny.dotnetSidecar !== "undefined"; // sidecar is emscripten main running in a web worker export const ENVIRONMENT_IS_WORKER = ENVIRONMENT_IS_WEB_WORKER && !ENVIRONMENT_IS_SIDECAR; // we redefine what ENVIRONMENT_IS_WORKER, we replace it in emscripten internals, so that sidecar works export const ENVIRONMENT_IS_WEB = typeof window === "object" || (ENVIRONMENT_IS_WEB_WORKER && !ENVIRONMENT_IS_NODE); export const ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE; export const VoidPtrNull: VoidPtr = 0; export const CharPtrNull: CharPtr = 0; export const NativePointerNull: NativePointer = 0; +export const browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase diff --git a/src/native/libs/Common/JavaScript/types/ems-ambient.ts b/src/native/libs/Common/JavaScript/types/ems-ambient.ts index b2e2596d20c674..a921a7a6f082d1 100644 --- a/src/native/libs/Common/JavaScript/types/ems-ambient.ts +++ b/src/native/libs/Common/JavaScript/types/ems-ambient.ts @@ -42,7 +42,9 @@ export type EmsAmbientSymbolsType = EmscriptenModuleInternal & { FS: { createPath: (parent: string, path: string, canRead?: boolean, canWrite?: boolean) => string; createDataFile: (parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean) => string; + chdir: (path: string) => void; } + ENV: any; DOTNET: any; DOTNET_INTEROP: any; @@ -68,8 +70,9 @@ export type EmsAmbientSymbolsType = EmscriptenModuleInternal & { HEAPF64: Float64Array; ExitStatus: (exitCode: number) => number; - _emscripten_force_exit: (exitCode: number) => void; _exit: (exitCode: number, implicit?: boolean) => void; + abort: (reason: any) => void; + ___trap: () => void; safeSetTimeout: (func: Function, timeout: number) => number; exitJS: (status: number, implicit?: boolean | number) => void; runtimeKeepalivePop: () => void; diff --git a/src/native/libs/Common/JavaScript/types/exchange.ts b/src/native/libs/Common/JavaScript/types/exchange.ts index c72ccf8d8a3e5e..fe634c54feff20 100644 --- a/src/native/libs/Common/JavaScript/types/exchange.ts +++ b/src/native/libs/Common/JavaScript/types/exchange.ts @@ -5,7 +5,8 @@ import type { check, error, info, warn, debug, fastCheck } from "../../../../cor import type { resolveRunMainPromise, rejectRunMainPromise, getRunMainPromise, abortStartup } from "../../../../corehost/browserhost/loader/run"; import type { addOnExitListener, isExited, isRuntimeRunning, quitNow } from "../../../../corehost/browserhost/loader/exit"; -import type { installVfsFile, registerDllBytes, loadIcuData, initializeCoreCLR, registerPdbBytes } from "../../../../corehost/browserhost/host/host"; +import type { initializeCoreCLR } from "../../../../corehost/browserhost/host/host"; +import type { instantiateWasm, installVfsFile, registerDllBytes, loadIcuData, registerPdbBytes } from "../../../../corehost/browserhost/host/assets"; import type { createPromiseCompletionSource, getPromiseCompletionSource, isControllablePromise } from "../../../../corehost/browserhost/loader/promise-completion-source"; import type { isSharedArrayBuffer, zeroRegion } from "../../../System.Native.Browser/utils/memory"; @@ -94,6 +95,7 @@ export type BrowserHostExports = { loadIcuData: typeof loadIcuData initializeCoreCLR: typeof initializeCoreCLR registerPdbBytes: typeof registerPdbBytes + instantiateWasm: typeof instantiateWasm } export type BrowserHostExportsTable = [ @@ -102,6 +104,7 @@ export type BrowserHostExportsTable = [ typeof loadIcuData, typeof initializeCoreCLR, typeof registerPdbBytes, + typeof instantiateWasm, ] export type InteropJavaScriptExports = { diff --git a/src/native/libs/Common/JavaScript/types/internal.ts b/src/native/libs/Common/JavaScript/types/internal.ts index 4656b827bd911b..2c68ac970d7813 100644 --- a/src/native/libs/Common/JavaScript/types/internal.ts +++ b/src/native/libs/Common/JavaScript/types/internal.ts @@ -49,6 +49,9 @@ export type EmscriptenModuleInternal = EmscriptenModule & DotnetModuleConfig & { instantiateWasm?: InstantiateWasmCallBack; onAbort?: (reason: any, extraJson?: string) => void; onExit?: (code: number) => void; + preInit?: (() => any)[]; + preRun?: (() => any)[]; + postRun?: (() => any)[]; } export interface AssetEntryInternal extends AssetEntry { diff --git a/src/native/libs/System.Native.Browser/diagnostics/exit.ts b/src/native/libs/System.Native.Browser/diagnostics/exit.ts index abd538dc542699..fb210820f91ac7 100644 --- a/src/native/libs/System.Native.Browser/diagnostics/exit.ts +++ b/src/native/libs/System.Native.Browser/diagnostics/exit.ts @@ -139,8 +139,8 @@ function fatalHandler(event: any, reason: any, type: string) { dotnetLogger.error("Unhandled error:", reason); dotnetApi.exit(1, reason); } - } catch (err) { - // no not re-throw from the fatal handler + } catch (error: any) { + // do not re-throw from the fatal handler } } diff --git a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.js b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.js index ec3e6718e75cce..bc5dc472daf677 100644 --- a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.js +++ b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.Utils.footer.js @@ -7,53 +7,56 @@ */ /* eslint-disable no-undef */ -/* eslint-disable space-before-function-paren */ -(function () { - function libFactory() { - // this executes the function at link time in order to capture exports - // this is what Emscripten does for linking JS libraries - // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#javascript-limits-in-library-files - // it would execute the code at link time and call .toString() on functions to move it to the final output - // this process would loose any closure references, unless they are passed to `__deps` and also explicitly given to the linker - // JS name mangling and minification also applies, see src\native\rollup.config.defines.js and `reserved` there - const exports = {}; - libBrowserUtils(exports); +function libBrowserUtilsFactory() { + // this executes the function at link time in order to capture exports + // this is what Emscripten does for linking JS libraries + // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#javascript-limits-in-library-files + // it would execute the code at link time and call .toString() on functions to move it to the final output + // this process would loose any closure references, unless they are passed to `__deps` and also explicitly given to the linker + // JS name mangling and minification also applies, see src\native\rollup.config.defines.js and `reserved` there + const exports = {}; + libBrowserUtils(exports); - let commonDeps = ["$libBrowserUtilsFn", "$DOTNET", - "GetDotNetRuntimeContractDescriptor", - "emscripten_force_exit", "_exit", - "$readI53FromU64", "$readI53FromI64", "$writeI53ToI64" - ]; - const lib = { - $BROWSER_UTILS: { - selfInitialize: () => { - if (typeof dotnetInternals !== "undefined") { - BROWSER_UTILS.dotnetInternals = dotnetInternals; + let commonDeps = [ + "$libBrowserUtilsFn", + "$DOTNET", + "GetDotNetRuntimeContractDescriptor", + "_exit", + "__trap", + "$readI53FromU64", + "$readI53FromI64", + "$writeI53ToI64" + ]; + const mergeBrowserUtils = { + $BROWSER_UTILS: { + selfInitialize: () => { + if (typeof dotnetInternals !== "undefined") { + BROWSER_UTILS.dotnetInternals = dotnetInternals; - const exports = {}; - libBrowserUtilsFn(exports); - exports.dotnetInitializeModule(dotnetInternals); - } - }, + const exports = {}; + libBrowserUtilsFn(exports); + exports.dotnetInitializeModule(dotnetInternals); + } }, - $libBrowserUtilsFn: libBrowserUtils, - $BROWSER_UTILS__postset: "BROWSER_UTILS.selfInitialize()", - $BROWSER_UTILS__deps: commonDeps, - }; + }, + $libBrowserUtilsFn: mergeBrowserUtils, + $BROWSER_UTILS__postset: "BROWSER_UTILS.selfInitialize()", + $BROWSER_UTILS__deps: commonDeps, + }; - // install cross-module symbols as "_ems_" ambient symbols in Emscripten closure - // keep in sync with `reserved`+`keep_fnames` in src\native\rollup.config.defines.js - for (const exportName of Reflect.ownKeys(exports._ems_ambient_)) { - const name = String(exportName); - if (name === "dotnetInternals") continue; - if (name === "Module") continue; - const emName = "$" + name; - lib[emName] = exports._ems_ambient_[exportName]; - commonDeps.push(emName); - } - - autoAddDeps(lib, "$BROWSER_UTILS"); - addToLibrary(lib); + // install cross-module symbols as "_ems_" ambient symbols in Emscripten closure + // keep in sync with `reserved`+`keep_fnames` in src\native\rollup.config.defines.js + for (const exportName of Reflect.ownKeys(exports._ems_ambient_)) { + const name = String(exportName); + if (name === "dotnetInternals") continue; + if (name === "Module") continue; + const emName = "$" + name; + mergeBrowserUtils[emName] = exports._ems_ambient_[exportName]; + commonDeps.push(emName); } - libFactory(); -})(); + + autoAddDeps(mergeBrowserUtils, "$BROWSER_UTILS"); + addToLibrary(mergeBrowserUtils); +} + +libBrowserUtilsFactory(); diff --git a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.extpost.js b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.extpost.js index 539adb82b59f7a..0b078b2b39504e 100644 --- a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.extpost.js +++ b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.extpost.js @@ -3,7 +3,6 @@ // The .NET Foundation licenses this file to you under the MIT license. /* eslint-disable no-undef */ -/* eslint-disable space-before-function-paren */ /* eslint-disable @typescript-eslint/no-unused-vars */ var fetch = fetch || undefined; var dotnetNativeModuleLoaded = false; var dotnetInternals = null; export function dotnetInitializeModule(internals) { diff --git a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js index 43fa674788d449..36238d87189f35 100644 --- a/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js +++ b/src/native/libs/System.Native.Browser/libSystem.Native.Browser.footer.js @@ -7,47 +7,45 @@ */ /* eslint-disable no-undef */ -/* eslint-disable space-before-function-paren */ -(function () { - function libFactory() { - // this executes the function at link time in order to capture exports - // this is what Emscripten does for linking JS libraries - // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#javascript-limits-in-library-files - // it would execute the code at link time and call .toString() on functions to move it to the final output - // this process would loose any closure references, unless they are passed to `__deps` and also explicitly given to the linker - // JS name mangling and minification also applies, see src\native\rollup.config.defines.js and `reserved` there - const exports = {}; - libNativeBrowser(exports); +function libDotnetFactory() { + // this executes the function at link time in order to capture exports + // this is what Emscripten does for linking JS libraries + // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#javascript-limits-in-library-files + // it would execute the code at link time and call .toString() on functions to move it to the final output + // this process would loose any closure references, unless they are passed to `__deps` and also explicitly given to the linker + // JS name mangling and minification also applies, see src\native\rollup.config.defines.js and `reserved` there + const exports = {}; + libNativeBrowser(exports); - let commonDeps = [ - "$BROWSER_UTILS", - "SystemJS_ExecuteTimerCallback", - "SystemJS_ExecuteBackgroundJobCallback", - "SystemJS_ExecuteFinalizationCallback", - ]; - const lib = { - $DOTNET: { - selfInitialize: () => { - if (typeof dotnetInternals !== "undefined") { - DOTNET.dotnetInternals = dotnetInternals; - DOTNET.dotnetInitializeModule(dotnetInternals); - } - }, - dotnetInitializeModule: exports.dotnetInitializeModule, - gitHash: exports.gitHash, + let commonDeps = [ + "$BROWSER_UTILS", + "SystemJS_ExecuteTimerCallback", + "SystemJS_ExecuteBackgroundJobCallback", + "SystemJS_ExecuteFinalizationCallback", + ]; + const mergeDotnet = { + $DOTNET: { + selfInitialize: () => { + if (typeof dotnetInternals !== "undefined") { + DOTNET.dotnetInternals = dotnetInternals; + DOTNET.dotnetInitializeModule(dotnetInternals); + } }, - $DOTNET__deps: commonDeps, - $DOTNET__postset: "DOTNET.selfInitialize()", - }; + dotnetInitializeModule: exports.dotnetInitializeModule, + gitHash: exports.gitHash, + }, + $DOTNET__deps: commonDeps, + $DOTNET__postset: "DOTNET.selfInitialize()", + }; - for (const exportName of Reflect.ownKeys(exports)) { - const name = String(exportName); - if (name === "dotnetInitializeModule") continue; - lib[name] = exports[name]; - } - - autoAddDeps(lib, "$DOTNET"); - addToLibrary(lib); + for (const exportName of Reflect.ownKeys(exports)) { + const name = String(exportName); + if (name === "dotnetInitializeModule") continue; + mergeDotnet[name] = exports[name]; } - libFactory(); -})(); + + autoAddDeps(mergeDotnet, "$DOTNET"); + addToLibrary(mergeDotnet); +} + +libDotnetFactory(); diff --git a/src/native/libs/System.Native.Browser/native/crypto.ts b/src/native/libs/System.Native.Browser/native/crypto.ts index 6c977cf8158fed..013ba19e46f873 100644 --- a/src/native/libs/System.Native.Browser/native/crypto.ts +++ b/src/native/libs/System.Native.Browser/native/crypto.ts @@ -11,9 +11,9 @@ export function SystemJS_RandomBytes(bufferPtr: number, bufferLength: number): n const batchedQuotaMax = 65536; if (!globalThis.crypto || !globalThis.crypto.getRandomValues) { - if (!(globalThis as any)["cryptoWarnOnce"]) { - _ems_.dotnetLogger.warn("This engine doesn't support crypto.getRandomValues. Please use a modern version or provide polyfill for 'globalThis.crypto.getRandomValues'."); - (globalThis as any)["cryptoWarnOnce"] = true; + if (!_ems_.DOTNET["cryptoWarnOnce"]) { + _ems_.dotnetLogger.debug("This engine doesn't support crypto.getRandomValues. Please use a modern version or provide polyfill for 'globalThis.crypto.getRandomValues'."); + _ems_.DOTNET["cryptoWarnOnce"] = true; } return -1; } diff --git a/src/native/libs/System.Native.Browser/native/scheduling.ts b/src/native/libs/System.Native.Browser/native/scheduling.ts index f64f9f966e285e..6db3e3fb198e3f 100644 --- a/src/native/libs/System.Native.Browser/native/scheduling.ts +++ b/src/native/libs/System.Native.Browser/native/scheduling.ts @@ -15,8 +15,12 @@ export function SystemJS_ScheduleTimer(shortestDueTimeMs: number): void { try { _ems_.DOTNET.lastScheduledTimerId = undefined; _ems_._SystemJS_ExecuteTimerCallback(); - } catch (err) { - _ems_.dotnetApi.exit(1, err); + } catch (error: any) { + // do not propagate ExitStatus exception + if (!error || typeof error.status !== "number") { + _ems_.dotnetApi.exit(1, error); + throw error; + } } } } @@ -33,8 +37,12 @@ export function SystemJS_ScheduleBackgroundJob(): void { try { _ems_.DOTNET.lastScheduledThreadPoolId = undefined; _ems_._SystemJS_ExecuteBackgroundJobCallback(); - } catch (err) { - _ems_.dotnetApi.exit(1, err); + } catch (error: any) { + // do not propagate ExitStatus exception + if (!error || typeof error.status !== "number") { + _ems_.dotnetApi.exit(1, error); + throw error; + } } } } @@ -51,8 +59,12 @@ export function SystemJS_ScheduleFinalization(): void { try { _ems_.DOTNET.lastScheduledFinalizationId = undefined; _ems_._SystemJS_ExecuteFinalizationCallback(); - } catch (err) { - _ems_.dotnetApi.exit(1, err); + } catch (error: any) { + // do not propagate ExitStatus exception + if (!error || typeof error.status !== "number") { + _ems_.dotnetApi.exit(1, error); + throw error; + } } } } diff --git a/src/native/libs/System.Native.Browser/utils/host.ts b/src/native/libs/System.Native.Browser/utils/host.ts index 9d9f9c8806d2b0..5f3e01cd30640f 100644 --- a/src/native/libs/System.Native.Browser/utils/host.ts +++ b/src/native/libs/System.Native.Browser/utils/host.ts @@ -1,10 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import BuildConfiguration from "consts:configuration"; import { _ems_ } from "../../Common/JavaScript/ems-ambient"; + // eslint-disable-next-line @typescript-eslint/no-unused-vars export function setEnvironmentVariable(name: string, value: string): void { + // TODO-WASM: implement setEnvironmentVariable throw new Error("Not implemented"); } @@ -17,8 +18,12 @@ export function runBackgroundTimers(): void { _ems_._SystemJS_ExecuteTimerCallback(); _ems_._SystemJS_ExecuteBackgroundJobCallback(); _ems_._SystemJS_ExecuteFinalizationCallback(); - } catch (err) { - _ems_.dotnetApi.exit(1, err); + } catch (error: any) { + // do not propagate ExitStatus exception + if (!error || typeof error.status !== "number") { + _ems_.dotnetApi.exit(1, error); + throw error; + } } } @@ -40,20 +45,24 @@ export function abortBackgroundTimers(): void { } } -export function abortPosix(exitCode: number): void { - _ems_.ABORT = true; - _ems_.EXITSTATUS = exitCode; +export function abortPosix(exitCode: number, reason: any, nativeReady: boolean): void { try { - if (BuildConfiguration === "Debug") { - _ems_._exit(exitCode, true); + _ems_.ABORT = true; + _ems_.EXITSTATUS = exitCode; + if (exitCode === 0 && nativeReady) { + _ems_._exit(0); + return; + } else if (nativeReady) { + _ems_.___trap(); } else { - _ems_._emscripten_force_exit(exitCode); + _ems_.abort(reason); } + throw reason; } catch (error: any) { // do not propagate ExitStatus exception - if (error.status === undefined) { - _ems_.dotnetApi.exit(1, error); - throw error; + if (typeof error === "object" && (typeof error.status === "number" || error instanceof WebAssembly.RuntimeError)) { + return; } + throw error; } } diff --git a/src/native/libs/System.Native.Browser/utils/runtime-list.ts b/src/native/libs/System.Native.Browser/utils/runtime-list.ts index 32f65011ed63fa..a182e4a41112b7 100644 --- a/src/native/libs/System.Native.Browser/utils/runtime-list.ts +++ b/src/native/libs/System.Native.Browser/utils/runtime-list.ts @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +import { globalThisAny } from "./per-module"; import type { RuntimeAPI } from "./types"; let runtimeList: RuntimeList; @@ -12,7 +13,7 @@ class RuntimeList { if (api.runtimeId === undefined) { api.runtimeId = Object.keys(this.list).length; } - this.list[api.runtimeId] = new (globalThis as any).WeakRef(api); + this.list[api.runtimeId] = new globalThisAny.WeakRef(api); return api.runtimeId; } @@ -23,7 +24,6 @@ class RuntimeList { } export function registerRuntime(api: RuntimeAPI): number { - const globalThisAny = globalThis as any; // this code makes it possible to find dotnet runtime on a page via global namespace, even when there are multiple runtimes at the same time if (!globalThisAny.getDotnetRuntime) { globalThisAny.getDotnetRuntime = (runtimeId: string) => globalThisAny.getDotnetRuntime.__list.getRuntime(runtimeId); diff --git a/src/native/libs/System.Native.Browser/utils/strings.ts b/src/native/libs/System.Native.Browser/utils/strings.ts index 43f03460436195..5fbbc3f3b2e891 100644 --- a/src/native/libs/System.Native.Browser/utils/strings.ts +++ b/src/native/libs/System.Native.Browser/utils/strings.ts @@ -13,11 +13,11 @@ let stringsInitialized = false; export function stringsInit(): void { if (!stringsInitialized) { // V8 does not provide TextDecoder - if (typeof TextDecoder !== "undefined") { - textDecoderUtf16 = new TextDecoder("utf-16le"); + if (typeof globalThis.TextDecoder !== "undefined") { + textDecoderUtf16 = new globalThis.TextDecoder("utf-16le"); } - if (typeof TextEncoder !== "undefined") { - textEncoderUtf8 = new TextEncoder(); + if (typeof globalThis.TextEncoder !== "undefined") { + textEncoderUtf8 = new globalThis.TextEncoder(); } stringsInitialized = true; } diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/cancelable-promise.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/cancelable-promise.ts index 94255925825ea7..8178a64881ded7 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/cancelable-promise.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/cancelable-promise.ts @@ -3,10 +3,11 @@ import { dotnetAssert, dotnetLoaderExports, dotnetLogger } from "./cross-module"; -import { ControllablePromise, GCHandle } from "./types"; +import type { ControllablePromise, GCHandle } from "./types"; +import type { PromiseHolder } from "./marshaled-types"; + import { isRuntimeRunning } from "./utils"; import { lookupJsOwnedObject } from "./gc-handles"; -import { PromiseHolder } from "./marshaled-types"; export function isThenable(js_obj: any): boolean { // When using an external Promise library like Bluebird the Promise.resolve may not be sufficient diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/http.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/http.ts index e1d8665351df05..a51d1125df3ede 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/http.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/http.ts @@ -22,6 +22,9 @@ function verifyEnvironment() { } function commonAsserts(controller: HttpController) { + if (BuildConfiguration !== "Debug") { + return; + } assertJsInterop(); dotnetAssert.check(controller, "expected controller"); } @@ -71,7 +74,7 @@ function muteUnhandledRejection(promise: Promise) { } export function httpAbort(controller: HttpController): void { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); try { if (!controller.isAborted) { if (controller.streamWriter) { @@ -92,7 +95,7 @@ export function httpAbort(controller: HttpController): void { } export function httpTransformStreamWrite(controller: HttpController, bufferPtr: VoidPtr, bufferLength: number): ControllablePromise { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); dotnetAssert.check(bufferLength > 0, "expected bufferLength > 0"); // the bufferPtr is pinned by the caller const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); @@ -124,7 +127,7 @@ export function httpTransformStreamClose(controller: HttpController): Controllab } export function httpFetchStream(controller: HttpController, url: string, headerNames: string[], headerValues: string[], optionNames: string[], optionValues: any[]): ControllablePromise { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); const transformStream = new TransformStream(); controller.streamWriter = transformStream.writable.getWriter(); muteUnhandledRejection(controller.streamWriter.closed); @@ -134,7 +137,7 @@ export function httpFetchStream(controller: HttpController, url: string, headerN } export function httpFetchBytes(controller: HttpController, url: string, headerNames: string[], headerValues: string[], optionNames: string[], optionValues: any[], bodyPtr: VoidPtr, bodyLength: number): ControllablePromise { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); // the bodyPtr is pinned by the caller const view = new Span(bodyPtr, bodyLength, MemoryViewType.Byte); const copy = view.slice() as Uint8Array; @@ -142,7 +145,7 @@ export function httpFetchBytes(controller: HttpController, url: string, headerNa } export function httpFetch(controller: HttpController, url: string, headerNames: string[], headerValues: string[], optionNames: string[], optionValues: any[], body: Uint8Array | ReadableStream | null): ControllablePromise { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); verifyEnvironment(); assertJsInterop(); dotnetAssert.check(url && typeof url === "string", "expected url string"); @@ -189,30 +192,30 @@ export function httpFetch(controller: HttpController, url: string, headerNames: } export function httpGetResponseType(controller: HttpController): string | undefined { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); return controller.response?.type; } export function httpGetResponseStatus(controller: HttpController): number { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); return controller.response?.status ?? 0; } export function httpGetResponseHeaderNames(controller: HttpController): string[] { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); dotnetAssert.check(controller.responseHeaderNames, "expected responseHeaderNames"); return controller.responseHeaderNames; } export function httpGetResponseHeaderValues(controller: HttpController): string[] { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); dotnetAssert.check(controller.responseHeaderValues, "expected responseHeaderValues"); return controller.responseHeaderValues; } export function httpGetResponseLength(controller: HttpController): ControllablePromise { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); return wrapAsCancelablePromise(async () => { const buffer = await controller.response!.arrayBuffer(); controller.responseBuffer = buffer; @@ -236,7 +239,7 @@ export function httpGetResponseBytes(controller: HttpController, view: Span): nu } export function httpGetStreamedResponseBytes(controller: HttpController, bufferPtr: VoidPtr, bufferLength: number): ControllablePromise { - if (BuildConfiguration === "Debug") commonAsserts(controller); + commonAsserts(controller); // the bufferPtr is pinned by the caller const view = new Span(bufferPtr, bufferLength, MemoryViewType.Byte); return wrapAsCancelablePromise(async () => { diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts index a4214c5da1c7ce..425c7267825bdd 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts @@ -3,7 +3,7 @@ import type { TimeStamp } from "./types"; -import { dotnetAssert, dotnetDiagnosticsExports, dotnetLoaderExports } from "./cross-module"; +import { dotnetAssert, dotnetDiagnosticsExports, dotnetLoaderExports, Module } from "./cross-module"; import { jsInteropState } from "./marshal"; import { ENVIRONMENT_IS_WEB } from "./per-module"; @@ -64,9 +64,12 @@ export function endMeasure(start: TimeStamp, block: string, id?: string) { let textDecoderUtf8Relaxed: TextDecoder | undefined = undefined; export function utf8ToStringRelaxed(buffer: Uint8Array): string { - if (textDecoderUtf8Relaxed === undefined) { - textDecoderUtf8Relaxed = new TextDecoder("utf-8", { fatal: false }); + if (textDecoderUtf8Relaxed === undefined && typeof globalThis.TextDecoder !== "undefined") { + textDecoderUtf8Relaxed = new globalThis.TextDecoder("utf-8", { fatal: false }); + } else if (textDecoderUtf8Relaxed === undefined) { + return Module.UTF8ArrayToString(buffer, 0, buffer.byteLength); } + // TODO-WASM: When threading is enabled, TextDecoder does not accept a view of a // SharedArrayBuffer, we must make a copy of the array first. // See https://github.com/whatwg/encoding/issues/172 diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/libSystem.Runtime.InteropServices.JavaScript.Native.footer.js b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/libSystem.Runtime.InteropServices.JavaScript.Native.footer.js index de140dbfa09c7e..39654bac1eecb9 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/libSystem.Runtime.InteropServices.JavaScript.Native.footer.js +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/libSystem.Runtime.InteropServices.JavaScript.Native.footer.js @@ -7,49 +7,48 @@ */ /* eslint-disable no-undef */ -/* eslint-disable space-before-function-paren */ -(function () { - function libFactory() { - // this executes the function at link time in order to capture exports - // this is what Emscripten does for linking JS libraries - // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#javascript-limits-in-library-files - // it would execute the code at link time and call .toString() on functions to move it to the final output - // this process would loose any closure references, unless they are passed to `__deps` and also explicitly given to the linker - // JS name mangling and minification also applies, see src\native\rollup.config.defines.js and `reserved` there - const exports = {}; - libInteropJavaScriptNative(exports); +function libDotnetInteropFactory() { + // this executes the function at link time in order to capture exports + // this is what Emscripten does for linking JS libraries + // https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#javascript-limits-in-library-files + // it would execute the code at link time and call .toString() on functions to move it to the final output + // this process would loose any closure references, unless they are passed to `__deps` and also explicitly given to the linker + // JS name mangling and minification also applies, see src\native\rollup.config.defines.js and `reserved` there + const exports = {}; + libInteropJavaScriptNative(exports); - let commonDeps = ["$DOTNET", - "SystemInteropJS_GetManagedStackTrace", - "SystemInteropJS_CallDelegate", - "SystemInteropJS_CompleteTask", - "SystemInteropJS_ReleaseJSOwnedObjectByGCHandle", - "SystemInteropJS_BindAssemblyExports", - "SystemInteropJS_CallJSExport" - ]; - const lib = { - $DOTNET_INTEROP: { - selfInitialize: () => { - if (typeof dotnetInternals !== "undefined") { - DOTNET_INTEROP.dotnetInternals = dotnetInternals; - DOTNET_INTEROP.dotnetInitializeModule(dotnetInternals); - } - }, - dotnetInitializeModule: exports.dotnetInitializeModule, - gitHash: exports.gitHash, + let commonDeps = [ + "$DOTNET", + "SystemInteropJS_GetManagedStackTrace", + "SystemInteropJS_CallDelegate", + "SystemInteropJS_CompleteTask", + "SystemInteropJS_ReleaseJSOwnedObjectByGCHandle", + "SystemInteropJS_BindAssemblyExports", + "SystemInteropJS_CallJSExport" + ]; + const mergeDotnetInterop = { + $DOTNET_INTEROP: { + selfInitialize: () => { + if (typeof dotnetInternals !== "undefined") { + DOTNET_INTEROP.dotnetInternals = dotnetInternals; + DOTNET_INTEROP.dotnetInitializeModule(dotnetInternals); + } }, - $DOTNET_INTEROP__postset: "DOTNET_INTEROP.selfInitialize()", - $DOTNET_INTEROP__deps: commonDeps, - }; + dotnetInitializeModule: exports.dotnetInitializeModule, + gitHash: exports.gitHash, + }, + $DOTNET_INTEROP__postset: "DOTNET_INTEROP.selfInitialize()", + $DOTNET_INTEROP__deps: commonDeps, + }; - for (const exportName of Reflect.ownKeys(exports)) { - const name = String(exportName); - if (name === "dotnetInitializeModule" || name === "gitHash") continue; - lib[name] = exports[name]; - } - - autoAddDeps(lib, "$DOTNET_INTEROP"); - addToLibrary(lib); + for (const exportName of Reflect.ownKeys(exports)) { + const name = String(exportName); + if (name === "dotnetInitializeModule" || name === "gitHash") continue; + mergeDotnetInterop[name] = exports[name]; } - libFactory(); -})(); + + autoAddDeps(mergeDotnetInterop, "$DOTNET_INTEROP"); + addToLibrary(mergeDotnetInterop); +} + +libDotnetInteropFactory(); diff --git a/src/native/minipal/getexepath.h b/src/native/minipal/getexepath.h index c0642812477fee..eaf88f27e6a5ee 100644 --- a/src/native/minipal/getexepath.h +++ b/src/native/minipal/getexepath.h @@ -84,8 +84,8 @@ static inline char* minipal_getexepath(void) return strdup(path); #elif defined(TARGET_WASM) - // This is a packaging convention that our tooling should enforce. - return strdup("/managed"); + const char *browserVirtualAppBase = "/"; // keep in sync other places that define browserVirtualAppBase + return strdup(browserVirtualAppBase); #else #ifdef __linux__ const char* symlinkEntrypointExecutable = "/proc/self/exe";