diff --git a/eng/native.props b/eng/native.props
deleted file mode 100644
index 6d7f605c9dbc17..00000000000000
--- a/eng/native.props
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
- <_RuntimeVariant />
- <_RuntimeVariant Condition="'$(WasmEnableThreads)' == 'true'">-threads
-
- <_IcuDir Condition="'$(PkgMicrosoft_NETCore_Runtime_ICU_Transport)' != '' and '$(TargetsWasm)' == 'true'">$(PkgMicrosoft_NETCore_Runtime_ICU_Transport)/runtimes/$(TargetOS)-$(TargetArchitecture)$(_RuntimeVariant)/native
- <_TzdDir Condition="'$(PkgSystem_Runtime_TimeZoneData)' != '' and '$(TargetsWasm)' == 'true'">$([MSBuild]::NormalizePath('$(PkgSystem_Runtime_TimeZoneData)', 'contentFiles', 'any', 'any', 'data'))
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/eng/native.wasm.targets b/eng/native.wasm.targets
new file mode 100644
index 00000000000000..23217c416c7fe3
--- /dev/null
+++ b/eng/native.wasm.targets
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+ <_RuntimeVariant />
+ <_RuntimeVariant Condition="'$(WasmEnableThreads)' == 'true'">-threads
+
+ <_IcuDir Condition="'$(PkgMicrosoft_NETCore_Runtime_ICU_Transport)' != '' and '$(TargetsWasm)' == 'true'">$(PkgMicrosoft_NETCore_Runtime_ICU_Transport)/runtimes/$(TargetOS)-$(TargetArchitecture)$(_RuntimeVariant)/native
+ <_TzdDir Condition="'$(PkgSystem_Runtime_TimeZoneData)' != '' and '$(TargetsWasm)' == 'true'">$([MSBuild]::NormalizePath('$(PkgSystem_Runtime_TimeZoneData)', 'contentFiles', 'any', 'any', 'data'))
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_EmccExportedRuntimeMethods>@(EmccExportedRuntimeMethod -> '%(Identity)',',')
+ <_EmccExportedFunctions>@(EmccExportedFunction -> '%(Identity)',',')
+
+
+
+
+
+
+
+
+
+
diff --git a/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets
index 3c5a83a93d255d..3c32cbcc12c784 100644
--- a/eng/testing/tests.browser.targets
+++ b/eng/testing/tests.browser.targets
@@ -12,6 +12,7 @@
$([MSBuild]::NormalizeDirectory($(BrowserProjectRoot), 'emsdk'))
true
+ false
<_WasmMainJSFileName Condition="'$(WasmMainJSPath)' != ''">$([System.IO.Path]::GetFileName('$(WasmMainJSPath)'))
<_WasmStrictVersionMatch Condition="'$(ContinuousIntegrationBuild)' == 'true'">true
@@ -34,6 +35,9 @@
false
true
true
+
+
+ false
diff --git a/eng/testing/workloads-browser.targets b/eng/testing/workloads-browser.targets
index ad279f9f506223..6cb7dd24c520b6 100644
--- a/eng/testing/workloads-browser.targets
+++ b/eng/testing/workloads-browser.targets
@@ -8,7 +8,7 @@
-
+
-
+
<_RuntimePackNugetAvailable Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono.$(RIDForWorkload).*$(PackageVersionForWorkloadManifests).nupkg" />
<_RuntimePackNugetAvailable Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.Mono.*.$(RIDForWorkload).*$(PackageVersionForWorkloadManifests).nupkg" />
<_RuntimePackNugetAvailable Remove="@(_RuntimePackNugetAvailable)" Condition="$([System.String]::new('%(_RuntimePackNugetAvailable.FileName)').EndsWith('.symbols'))" />
+
+ <_RuntimePackNugetAvailable Include="$(LibrariesShippingPackagesDir)Microsoft.NETCore.App.Runtime.$(RIDForWorkload).*$(PackageVersionForWorkloadManifests).nupkg" />
+ <_RuntimePackNugetAvailable Remove="@(_RuntimePackNugetAvailable)" Condition="$([System.String]::new('%(_RuntimePackNugetAvailable.FileName)').EndsWith('.symbols'))" />
+
@@ -87,7 +91,7 @@
{
+ // 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: {},
+ runtimeId: 0,
+ runtimeBuildInfo: {
+ productVersion: "corerun",
+ gitHash: "corerun",
+ buildConfiguration: "corerun",
+ wasmEnableThreads: false,
+ wasmEnableSIMD: true,
+ wasmEnableExceptionHandling: true,
+ },
+ };
+ dotnetInternals = [
+ runtimeApi,
+ [],
+ ];
+
+ createDotnetRuntime(runtimeApi.Module);
+}
+selfRun();
diff --git a/src/coreclr/hosts/corerun/wasm/libCorerun.pre.js b/src/coreclr/hosts/corerun/wasm/libCorerun.pre.js
deleted file mode 100644
index b38606f0b06c19..00000000000000
--- a/src/coreclr/hosts/corerun/wasm/libCorerun.pre.js
+++ /dev/null
@@ -1,20 +0,0 @@
-var dotnetInternals = [
- {
- Module: Module,
- },
- [],
-];
-var basePreRun = () => {
- // 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";
-};
-
-// Append to or set the preRun array
-Module.preRun = Module.preRun || [];
-Module.preRun.push(basePreRun);
\ No newline at end of file
diff --git a/src/coreclr/runtime.proj b/src/coreclr/runtime.proj
index d70f30b12ec4f2..b6320da46f5f3f 100644
--- a/src/coreclr/runtime.proj
+++ b/src/coreclr/runtime.proj
@@ -11,11 +11,11 @@
-
+
<_CMakeArgs Include="$(CMakeArgs)" />
diff --git a/src/coreclr/vm/wasm/callhelpers.cpp b/src/coreclr/vm/wasm/callhelpers.cpp
index b4d7bdb752a197..7e05e5a02142a1 100644
--- a/src/coreclr/vm/wasm/callhelpers.cpp
+++ b/src/coreclr/vm/wasm/callhelpers.cpp
@@ -484,6 +484,12 @@ namespace
(*fptr)(ARG_I32(0), ARG_I32(1), ARG_I32(2), ARG_IND(3), ARG_IND(4), ARG_I32(5));
}
+ void CallFunc_I32_I32_IND_RetVoid (PCODE pcode, int8_t *pArgs, int8_t *pRet)
+ {
+ void (*fptr)(int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t))pcode;
+ (*fptr)(ARG_I32(0), ARG_I32(1), ARG_IND(2));
+ }
+
void CallFunc_I32_I32_IND_IND_I32_RetVoid (PCODE pcode, int8_t *pArgs, int8_t *pRet)
{
void (*fptr)(int32_t, int32_t, int32_t, int32_t, int32_t) = (void (*)(int32_t, int32_t, int32_t, int32_t, int32_t))pcode;
@@ -709,6 +715,7 @@ const StringToWasmSigThunk g_wasmThunks[] = {
{ "viiinn", (void*)&CallFunc_I32_I32_I32_IND_IND_RetVoid },
{ "viiinni", (void*)&CallFunc_I32_I32_I32_IND_IND_I32_RetVoid },
{ "viinni", (void*)&CallFunc_I32_I32_IND_IND_I32_RetVoid },
+ { "viin", (void*)&CallFunc_I32_I32_IND_RetVoid },
{ "viinnii", (void*)&CallFunc_I32_I32_IND_IND_I32_I32_RetVoid },
{ "vil", (void*)&CallFunc_I32_I64_RetVoid },
{ "vili", (void*)&CallFunc_I32_I64_I32_RetVoid },
@@ -752,7 +759,7 @@ static void Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJo
// Lazy lookup of MethodDesc for the function export scenario.
if (!MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid)
{
- LookupMethodByName("System.Threading.ThreadPool, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, System.Private.CoreLib", "BackgroundJobHandler", &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid);
+ LookupMethodByName("System.Threading.ThreadPool, System.Private.CoreLib", "BackgroundJobHandler", &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid);
}
ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, nullptr, 0, nullptr);
}
@@ -768,7 +775,7 @@ static void Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler
// Lazy lookup of MethodDesc for the function export scenario.
if (!MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid)
{
- LookupMethodByName("System.Threading.TimerQueue, System.Private.CoreLib, Version=10.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, System.Private.CoreLib", "TimerHandler", &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid);
+ LookupMethodByName("System.Threading.TimerQueue, System.Private.CoreLib", "TimerHandler", &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid);
}
ExecuteInterpretedMethodFromUnmanaged(MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, nullptr, 0, nullptr);
}
@@ -778,6 +785,27 @@ extern "C" void SystemJS_ExecuteTimerCallback()
Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler();
}
+static MethodDesc* MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_GetManagedStackTrace_IntPtr_RetVoid = nullptr;
+static void Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_GetManagedStackTrace(void* arg0)
+{
+ int64_t args[1] =
+ {
+ (int64_t)arg0
+ };
+
+ // Lazy lookup of MethodDesc for the function export scenario.
+ if (!MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_GetManagedStackTrace_IntPtr_RetVoid)
+ {
+ LookupMethodByName("System.Runtime.InteropServices.JavaScript.JavaScriptExports", "GetManagedStackTrace", &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_GetManagedStackTrace_IntPtr_RetVoid);
+ }
+ ExecuteInterpretedMethodFromUnmanaged(MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_GetManagedStackTrace_IntPtr_RetVoid, (int8_t*)args, sizeof(args), nullptr);
+}
+
+extern "C" void SystemInteropJS_GetManagedStackTrace(void* arg0)
+{
+ Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_GetManagedStackTrace(arg0);
+}
+
extern const ReverseThunkMapEntry g_ReverseThunks[] =
{
{ 100678287, { &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler } },
diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp
index 742e3a947da208..efe518f3063ae9 100644
--- a/src/coreclr/vm/wasm/helpers.cpp
+++ b/src/coreclr/vm/wasm/helpers.cpp
@@ -618,6 +618,10 @@ namespace
if (!GetSignatureKey(sig, keyBuffer, keyBufferLen))
return NULL;
+ void* thunk = LookupThunk(keyBuffer);
+ if (thunk == NULL)
+ printf("WASM Calli missing for Key: %s\n", keyBuffer);
+
return LookupThunk(keyBuffer);
}
diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.CoreCLR.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.CoreCLR.cs
new file mode 100644
index 00000000000000..d8afeca4a383d4
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.CoreCLR.cs
@@ -0,0 +1,45 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static unsafe partial class Runtime
+ {
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_BindJSImportST")]
+ public static unsafe partial nint BindJSImportST(void* signature);
+
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_InvokeJSImportST")]
+ public static partial void InvokeJSImportST(int importHandle, nint args);
+
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_ReleaseCSOwnedObject")]
+ internal static partial void ReleaseCSOwnedObject(nint jsHandle);
+
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_InvokeJSFunction")]
+ public static partial void InvokeJSFunction(nint functionHandle, nint data);
+
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_ResolveOrRejectPromise")]
+ public static partial void ResolveOrRejectPromise(nint data);
+
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_RegisterGCRoot")]
+ public static partial nint RegisterGCRoot(void* start, int bytesSize, IntPtr name);
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_DeregisterGCRoot")]
+ public static partial void DeregisterGCRoot(nint handle);
+
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_CancelPromise")]
+ public static partial void CancelPromise(nint gcHandle);
+
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_BindAssemblyExports")]
+ public static partial void BindAssemblyExports(IntPtr assemblyNamePtr);
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_GetAssemblyExport")]
+ public static partial void GetAssemblyExport(IntPtr assemblyNamePtr, IntPtr namespacePtr, IntPtr classnamePtr, IntPtr methodNamePtr, int signatureHash, IntPtr* monoMethodPtrPtr);
+
+ // TODO-WASM: delete once we switch to CoreCLR only
+ [LibraryImport(Libraries.JavaScriptNative, EntryPoint = "SystemInteropJS_AssemblyGetEntryPoint")]
+ public static partial void AssemblyGetEntryPoint(IntPtr assemblyNamePtr, int auto_insert_breakpoint, void** monoMethodPtrPtr);
+ }
+}
diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.Mono.cs
similarity index 100%
rename from src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
rename to src/libraries/Common/src/Interop/Browser/Interop.Runtime.Mono.cs
diff --git a/src/libraries/Common/src/Interop/Unix/Interop.Libraries.cs b/src/libraries/Common/src/Interop/Unix/Interop.Libraries.cs
index 7ec51da64f2541..f7c988025cbc92 100644
--- a/src/libraries/Common/src/Interop/Unix/Interop.Libraries.cs
+++ b/src/libraries/Common/src/Interop/Unix/Interop.Libraries.cs
@@ -13,6 +13,7 @@ internal static partial class Libraries
internal const string CryptoNative = "libSystem.Security.Cryptography.Native.OpenSsl";
internal const string CompressionNative = "libSystem.IO.Compression.Native";
internal const string GlobalizationNative = "libSystem.Globalization.Native";
+ internal const string JavaScriptNative = "libSystem.Runtime.InteropServices.JavaScript.Native";
internal const string IOPortsNative = "libSystem.IO.Ports.Native";
internal const string HostPolicy = "libhostpolicy";
}
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj
index 9643ac9aed0f7f..b31c43f683b309 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj
@@ -12,6 +12,7 @@
SR.SystemRuntimeInteropServicesJavaScript_PlatformNotSupported
true
true
+ true
false
$(DefineConstants);FEATURE_WASM_MANAGED_THREADS
$(DefineConstants);ENABLE_JS_INTEROP_BY_VALUE
@@ -23,7 +24,9 @@
-
+
+
+
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs
index 559a7921352a3b..07f5af47b8305c 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs
@@ -229,6 +229,7 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer)
}
}
+ [UnmanagedCallersOnly(EntryPoint = "SystemInteropJS_GetManagedStackTrace")]
// the marshaled signature is: string GetManagedStackTrace(GCHandle exception)
public static void GetManagedStackTrace(JSMarshalerArgument* arguments_buffer)
{
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn3.11.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn3.11.Tests.csproj
deleted file mode 100644
index bde7513156c634..00000000000000
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn3.11.Tests.csproj
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
- 3.11
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn4.4.Tests.csproj b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn4.4.Tests.csproj
deleted file mode 100644
index 0fa1bccdb970ba..00000000000000
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn4.4.Tests.csproj
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- 4.4
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/libraries/pretest.proj b/src/libraries/pretest.proj
index fa615121c17659..96ed94ed4873a0 100644
--- a/src/libraries/pretest.proj
+++ b/src/libraries/pretest.proj
@@ -19,13 +19,16 @@
-
-
+
+
+
diff --git a/src/mono/browser/test-main.js b/src/mono/browser/test-main.js
index 872e14a2ce4cd9..35a1924c804ccb 100644
--- a/src/mono/browser/test-main.js
+++ b/src/mono/browser/test-main.js
@@ -5,6 +5,7 @@
// Run runtime tests under a JS shell or a browser
//
import { dotnet, exit } from './_framework/dotnet.js';
+import { config } from './_framework/dotnet.boot.js';
/*****************************************************************************
@@ -253,6 +254,7 @@ globalThis.App = App; // Necessary as tests use it
function configureRuntime(dotnet, runArgs) {
dotnet
+ .withConfig(config)
.withVirtualWorkingDirectory(runArgs.workingDirectory)
.withEnvironmentVariables(runArgs.environmentVariables)
.withDiagnosticTracing(runArgs.diagnosticTracing)
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 9ecdb110c4abd1..dd7b9bd653ff94 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
@@ -195,6 +195,9 @@ Copyright (c) .NET Foundation. All rights reserved.
<_WasmEnableWebcil>$(WasmEnableWebcil)
<_WasmEnableWebcil Condition="'$(_TargetingNET80OrLater)' != 'true'">false
<_WasmEnableWebcil Condition="'$(_WasmEnableWebcil)' == ''">true
+
+ <_WasmEnableWebcil Condition="'$(RuntimeFlavor)' == 'CoreCLR'">false
+
<_BlazorWebAssemblyJiterpreter>$(BlazorWebAssemblyJiterpreter)
<_BlazorWebAssemblyRuntimeOptions>$(BlazorWebAssemblyRuntimeOptions)
<_WasmInlineBootConfig>$(WasmInlineBootConfig)
diff --git a/src/native/corehost/browserhost/CMakeLists.txt b/src/native/corehost/browserhost/CMakeLists.txt
index c53d396e275a34..5f4e23c2680474 100644
--- a/src/native/corehost/browserhost/CMakeLists.txt
+++ b/src/native/corehost/browserhost/CMakeLists.txt
@@ -109,14 +109,15 @@ target_link_options(browserhost PRIVATE
-sMAXIMUM_MEMORY=2147483648
-sALLOW_MEMORY_GROWTH=1
-sSTACK_SIZE=5MB
- -sWASM_BIGINT=1
-sMODULARIZE=1
-sEXPORT_ES6=1
-sEXIT_RUNTIME=0
- -sEXPORTED_RUNTIME_METHODS=BROWSER_HOST,cwrap,ccall,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAP64,HEAPU64,HEAPF32,HEAPF64,safeSetTimeout,maybeExit,exitJS,abort,lengthBytesUTF8,UTF8ToString,stringToUTF8Array
- -sEXPORTED_FUNCTIONS=_posix_memalign,_malloc,_free,stackAlloc,stackRestore,stackSave,___cpp_exception,_GetDotNetRuntimeContractDescriptor,_BrowserHost_InitializeCoreCLR,_BrowserHost_ExecuteAssembly
+ -sEXPORTED_RUNTIME_METHODS=BROWSER_HOST,${CMAKE_EMCC_EXPORTED_RUNTIME_METHODS}
+ -sEXPORTED_FUNCTIONS=_BrowserHost_InitializeCoreCLR,_BrowserHost_ExecuteAssembly,${CMAKE_EMCC_EXPORTED_FUNCTIONS}
-sEXPORT_NAME=createDotnetRuntime
- -lnodefs.js)
+ -sENVIRONMENT=web,webview,worker,node,shell
+ -lnodefs.js
+ -Wl,-error-limit=0)
target_link_libraries(browserhost PUBLIC
BrowserHost-Static
diff --git a/src/native/corehost/browserhost/browserhost.cpp b/src/native/corehost/browserhost/browserhost.cpp
index 1d37e4c9abc564..1350d13ad59b7d 100644
--- a/src/native/corehost/browserhost/browserhost.cpp
+++ b/src/native/corehost/browserhost/browserhost.cpp
@@ -68,11 +68,11 @@ static const void* pinvoke_override(const char* library_name, const char* entry_
{
return SystemResolveDllImport(entry_point_name);
}
- if (strcmp(library_name, "libSystem.JavaScript") == 0)
+ if (strcmp(library_name, "libSystem.JavaScript.Native") == 0)
{
return SystemJSResolveDllImport(entry_point_name);
}
- if (strcmp(library_name, "libSystem.Runtime.InteropServices.JavaScript") == 0)
+ if (strcmp(library_name, "libSystem.Runtime.InteropServices.JavaScript.Native") == 0)
{
return SystemJSInteropResolveDllImport(entry_point_name);
}
diff --git a/src/native/corehost/browserhost/host/host.ts b/src/native/corehost/browserhost/host/host.ts
index 1646d9b1eda8e7..c7435db8352481 100644
--- a/src/native/corehost/browserhost/host/host.ts
+++ b/src/native/corehost/browserhost/host/host.ts
@@ -1,12 +1,12 @@
// 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, VoidPtr, VoidPtrPtr } from "./types";
+import type { CharPtr, VfsAsset, VoidPtr, VoidPtrPtr } from "./types";
import { } from "./cross-linked"; // ensure ambient symbols are declared
const loadedAssemblies: Map = new Map();
-export function registerDllBytes(bytes: Uint8Array, asset: { name: string }) {
+export function registerDllBytes(bytes: Uint8Array, asset: { name: string, virtualPath: string }) {
const sp = Module.stackSave();
try {
const sizeOfPtr = 4;
@@ -16,13 +16,52 @@ export function registerDllBytes(bytes: Uint8Array, asset: { name: string }) {
}
const ptr = Module.HEAPU32[ptrPtr as any >>> 2];
- Module.HEAPU8.set(bytes, ptr);
+ Module.HEAPU8.set(bytes, ptr >>> 0);
loadedAssemblies.set(asset.name, { ptr, length: bytes.length });
+ loadedAssemblies.set(asset.virtualPath, { ptr, length: bytes.length });
} finally {
Module.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");
+ }
+
+ dotnetLogger.debug(`Creating directory '${parentDirectory}'`);
+
+ Module.FS_createPath(
+ "/", parentDirectory, true, true // fixme: should canWrite be false?
+ );
+ } else {
+ parentDirectory = "/";
+ }
+
+ dotnetLogger.debug(`Creating file '${fileName}' in directory '${parentDirectory}'`);
+
+ Module.FS_createDataFile(
+ parentDirectory, fileName,
+ bytes, true /* canRead */, true /* canWrite */, true /* canOwn */
+ );
+}
+
// 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 = Module.UTF8ToString(pathPtr);
diff --git a/src/native/corehost/browserhost/host/index.ts b/src/native/corehost/browserhost/host/index.ts
index 0cd4f70582e677..a01c7d4145544d 100644
--- a/src/native/corehost/browserhost/host/index.ts
+++ b/src/native/corehost/browserhost/host/index.ts
@@ -5,7 +5,7 @@ import type { InternalExchange, BrowserHostExports, RuntimeAPI, BrowserHostExpor
import { InternalExchangeIndex } from "./types";
import { } from "./cross-linked"; // ensure ambient symbols are declared
-import { runMain, runMainAndExit, registerDllBytes } from "./host";
+import { runMain, runMainAndExit, registerDllBytes, installVfsFile } from "./host";
export function dotnetInitializeModule(internals: InternalExchange): void {
if (!Array.isArray(internals)) throw new Error("Expected internals to be an array");
@@ -19,12 +19,14 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
internals[InternalExchangeIndex.BrowserHostExportsTable] = browserHostExportsToTable({
registerDllBytes,
+ installVfsFile,
});
dotnetUpdateInternals(internals, dotnetUpdateInternalsSubscriber);
- function browserHostExportsToTable(map:BrowserHostExports):BrowserHostExportsTable {
+ function browserHostExportsToTable(map: BrowserHostExports): BrowserHostExportsTable {
// keep in sync with browserHostExportsFromTable()
return [
map.registerDllBytes,
+ map.installVfsFile,
];
}
}
diff --git a/src/native/corehost/browserhost/libBrowserHost.footer.js b/src/native/corehost/browserhost/libBrowserHost.footer.js
index b7afcafefbee88..acf3f568d84d29 100644
--- a/src/native/corehost/browserhost/libBrowserHost.footer.js
+++ b/src/native/corehost/browserhost/libBrowserHost.footer.js
@@ -19,7 +19,7 @@
const exports = {};
libBrowserHost(exports);
- let commonDeps = ["$libBrowserHostFn", "$DOTNET", "$DOTNET_INTEROP", "$ENV"];
+ let commonDeps = ["$libBrowserHostFn", "$DOTNET", "$DOTNET_INTEROP", "$ENV", "$FS", "$NODEFS"];
const lib = {
$BROWSER_HOST: {
selfInitialize: () => {
@@ -54,6 +54,14 @@
// WASM-TODO: remove once globalization is loaded via ICU
ENV["DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"] = "true";
+
+ if (ENVIRONMENT_IS_NODE) {
+ Module.preInit = [() => {
+ FS.mkdir("/managed");
+ FS.mount(NODEFS, { root: "." }, "/managed");
+ FS.chdir("/managed");
+ }];
+ }
}
},
},
diff --git a/src/native/corehost/browserhost/loader/bootstrap.ts b/src/native/corehost/browserhost/loader/bootstrap.ts
index bbf6042498b990..8ae13b9410d333 100644
--- a/src/native/corehost/browserhost/loader/bootstrap.ts
+++ b/src/native/corehost/browserhost/loader/bootstrap.ts
@@ -1,13 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import type { LoadBootResourceCallback, JsModuleExports, JsAsset, AssemblyAsset, PdbAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal } from "./types";
+import type { LoadBootResourceCallback, JsModuleExports, JsAsset, AssemblyAsset, PdbAsset, WasmAsset, IcuAsset, EmscriptenModuleInternal, LoaderConfig, DotnetHostBuilder } from "./types";
import { dotnetAssert, dotnetGetInternals, dotnetBrowserHostExports, dotnetUpdateInternals } from "./cross-module";
import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL } from "./per-module";
import { getLoaderConfig } from "./config";
import { BrowserHost_InitializeCoreCLR } from "./run";
-import { createPromiseController } from "./promise-controller";
+import { createPromiseCompletionSource } from "./promise-completion-source";
+import { nodeFs } from "./polyfills";
const scriptUrlQuery = /*! webpackIgnore: true */import.meta.url;
const queryIndex = scriptUrlQuery.indexOf("?");
@@ -15,7 +16,7 @@ const modulesUniqueQuery = queryIndex > 0 ? scriptUrlQuery.substring(queryIndex)
const scriptUrl = normalizeFileUrl(scriptUrlQuery);
const scriptDirectory = normalizeDirectoryUrl(scriptUrl);
-const nativeModulePromiseController = createPromiseController(() => {
+const nativeModulePromiseController = createPromiseCompletionSource(() => {
dotnetUpdateInternals(dotnetGetInternals());
});
@@ -30,10 +31,12 @@ export async function createRuntime(downloadOnly: boolean, loadBootResource?: Lo
const config = getLoaderConfig();
if (!config.resources || !config.resources.coreAssembly || !config.resources.coreAssembly.length) throw new Error("Invalid config, resources is not set");
+ const nativeModulePromise = loadJSModule(config.resources.jsModuleNative[0], loadBootResource);
+ const runtimeModulePromise = loadJSModule(config.resources.jsModuleRuntime[0], loadBootResource);
const coreAssembliesPromise = Promise.all(config.resources.coreAssembly.map(fetchDll));
+ const coreVfsPromise = Promise.all((config.resources.coreVfs || []).map(fetchVfs));
const assembliesPromise = Promise.all(config.resources.assembly.map(fetchDll));
- const runtimeModulePromise = loadJSModule(config.resources.jsModuleRuntime[0], loadBootResource);
- const nativeModulePromise = loadJSModule(config.resources.jsModuleNative[0], loadBootResource);
+ const vfsPromise = Promise.all((config.resources.vfs || []).map(fetchVfs));
// WASM-TODO fetchWasm(config.resources.wasmNative[0]);// start loading early, no await
const nativeModule = await nativeModulePromise;
@@ -45,6 +48,8 @@ export async function createRuntime(downloadOnly: boolean, loadBootResource?: Lo
await nativeModulePromiseController.promise;
await coreAssembliesPromise;
+ await coreVfsPromise;
+ await vfsPromise;
if (!downloadOnly) {
BrowserHost_InitializeCoreCLR();
@@ -73,7 +78,17 @@ async function fetchDll(asset: AssemblyAsset): Promise {
dotnetBrowserHostExports.registerDllBytes(bytes, asset);
}
-async function fetchBytes(asset: WasmAsset|AssemblyAsset|PdbAsset|IcuAsset): Promise {
+async function fetchVfs(asset: AssemblyAsset): Promise {
+ if (asset.name && !asset.resolvedUrl) {
+ asset.resolvedUrl = locateFile(asset.name);
+ }
+ const bytes = await fetchBytes(asset);
+ await nativeModulePromiseController.promise;
+
+ dotnetBrowserHostExports.installVfsFile(bytes, asset);
+}
+
+async function fetchBytes(asset: WasmAsset | AssemblyAsset | PdbAsset | IcuAsset): Promise {
dotnetAssert.check(asset && asset.resolvedUrl, "Bad asset.resolvedUrl");
if (ENVIRONMENT_IS_NODE) {
const { promises: fs } = await import("fs");
@@ -129,3 +144,57 @@ function isPathAbsolute(path: string): boolean {
// windows http://C:/x.json
return protocolRx.test(path);
}
+
+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;
+}
+
+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 config: LoaderConfig = {
+ mainAssemblyName,
+ runtimeConfig,
+ virtualWorkingDirectory: mountedDir,
+ 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,
+ }
+ };
+ dotnet.withConfig(config);
+ dotnet.withApplicationArguments(...globalThis.process.argv.slice(3));
+}
diff --git a/src/native/corehost/browserhost/loader/dotnet.d.ts b/src/native/corehost/browserhost/loader/dotnet.d.ts
index c55cc22cf164e5..bb4e95306d7729 100644
--- a/src/native/corehost/browserhost/loader/dotnet.d.ts
+++ b/src/native/corehost/browserhost/loader/dotnet.d.ts
@@ -767,7 +767,7 @@ interface IMemoryView extends IDisposable {
get length(): number;
get byteLength(): number;
}
-declare function exit(exit_code: number, reason?: any): void;
+declare function exit(exitCode: number, reason?: any): void;
declare const dotnet: DotnetHostBuilder;
declare global {
function getDotnetRuntime(runtimeId: number): RuntimeAPI | undefined;
diff --git a/src/native/corehost/browserhost/loader/dotnet.ts b/src/native/corehost/browserhost/loader/dotnet.ts
index 8f764e324da7e8..f9fc7fe6d1138c 100644
--- a/src/native/corehost/browserhost/loader/dotnet.ts
+++ b/src/native/corehost/browserhost/loader/dotnet.ts
@@ -12,13 +12,16 @@ import type { DotnetHostBuilder } from "./types";
import { HostBuilder } from "./host-builder";
import { initPolyfills, initPolyfillsAsync } from "./polyfills";
-import { registerRuntime } from "./runtime-list";
import { exit } from "./exit";
import { dotnetInitializeModule } from ".";
+import { selfHostNodeJS } from "./run";
initPolyfills();
-registerRuntime(dotnetInitializeModule());
+dotnetInitializeModule();
await initPolyfillsAsync();
export const dotnet: DotnetHostBuilder | undefined = new HostBuilder() as DotnetHostBuilder;
export { exit };
+
+// 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 5ba4d306139b7c..9ee194abadd1be 100644
--- a/src/native/corehost/browserhost/loader/exit.ts
+++ b/src/native/corehost/browserhost/loader/exit.ts
@@ -5,12 +5,12 @@ import { dotnetLogger } from "./cross-module";
import { ENVIRONMENT_IS_NODE } from "./per-module";
// WASM-TODO: redirect to host.ts
-export function exit(exit_code: number, reason: any): void {
+export function exit(exitCode: number, reason: any): void {
if (reason) {
const reasonStr = (typeof reason === "object") ? `${reason.message || ""}\n${reason.stack || ""}` : reason.toString();
dotnetLogger.error(reasonStr);
}
if (ENVIRONMENT_IS_NODE) {
- (globalThis as any).process.exit(exit_code);
+ (globalThis as any).process.exit(exitCode);
}
}
diff --git a/src/native/corehost/browserhost/loader/host-builder.ts b/src/native/corehost/browserhost/loader/host-builder.ts
index 4114eaa1aeec65..960f4151cd398b 100644
--- a/src/native/corehost/browserhost/loader/host-builder.ts
+++ b/src/native/corehost/browserhost/loader/host-builder.ts
@@ -98,6 +98,42 @@ export class HostBuilder implements DotnetHostBuilder {
Object.assign(Module, moduleConfig);
return this;
}
+ withExitOnUnhandledError(): DotnetHostBuilder {
+ mergeLoaderConfig({
+ exitOnUnhandledError: true
+ });
+ return this;
+ }
+ withExitCodeLogging(): DotnetHostBuilder {
+ mergeLoaderConfig({
+ logExitCode: true
+ });
+ return this;
+ }
+ withElementOnExit(): DotnetHostBuilder {
+ mergeLoaderConfig({
+ appendElementOnExit: true
+ });
+ return this;
+ }
+ withInteropCleanupOnExit(): DotnetHostBuilder {
+ mergeLoaderConfig({
+ //TODO
+ });
+ return this;
+ }
+ withDumpThreadsOnNonZeroExit(): DotnetHostBuilder {
+ mergeLoaderConfig({
+ //TODO
+ });
+ return this;
+ }
+ withConsoleForwarding(): DotnetHostBuilder {
+ mergeLoaderConfig({
+ //TODO
+ });
+ return this;
+ }
async download(): Promise {
try {
diff --git a/src/native/corehost/browserhost/loader/index.ts b/src/native/corehost/browserhost/loader/index.ts
index 92002a3b56df66..63eb64cdd35c5a 100644
--- a/src/native/corehost/browserhost/loader/index.ts
+++ b/src/native/corehost/browserhost/loader/index.ts
@@ -14,10 +14,11 @@ import GitHash from "consts:gitHash";
import { netLoaderConfig, getLoaderConfig } from "./config";
import { exit } from "./exit";
import { invokeLibraryInitializers } from "./lib-initializers";
-import { check, error, info, warn } from "./logging";
+import { check, error, info, warn, debug } from "./logging";
import { dotnetAssert, dotnetLoaderExports, dotnetLogger, dotnetUpdateInternals, dotnetUpdateInternalsSubscriber } from "./cross-module";
import { rejectRunMainPromise, resolveRunMainPromise, getRunMainPromise } from "./run";
+import { createPromiseCompletionSource, getPromiseCompletionSource, isControllablePromise } from "./promise-completion-source";
export function dotnetInitializeModule(): RuntimeAPI {
@@ -38,7 +39,7 @@ export function dotnetInitializeModule(): RuntimeAPI {
invokeLibraryInitializers,
};
- const internals:InternalExchange = [
+ const internals: InternalExchange = [
dotnetApi as RuntimeAPI, //0
[], //1
netLoaderConfig, //2
@@ -53,9 +54,13 @@ export function dotnetInitializeModule(): RuntimeAPI {
getRunMainPromise,
rejectRunMainPromise,
resolveRunMainPromise,
+ createPromiseCompletionSource,
+ isControllablePromise,
+ getPromiseCompletionSource,
};
Object.assign(dotnetLoaderExports, loaderFunctions);
const logger: LoggerType = {
+ debug,
info,
warn,
error,
@@ -70,9 +75,10 @@ export function dotnetInitializeModule(): RuntimeAPI {
dotnetUpdateInternals(internals, dotnetUpdateInternalsSubscriber);
return dotnetApi as RuntimeAPI;
- function loaderExportsToTable(logger:LoggerType, assert:AssertType, dotnetLoaderExports:LoaderExports):LoaderExportsTable {
+ function loaderExportsToTable(logger: LoggerType, assert: AssertType, dotnetLoaderExports: LoaderExports): LoaderExportsTable {
// keep in sync with loaderExportsFromTable()
return [
+ logger.debug,
logger.info,
logger.warn,
logger.error,
@@ -80,6 +86,9 @@ export function dotnetInitializeModule(): RuntimeAPI {
dotnetLoaderExports.resolveRunMainPromise,
dotnetLoaderExports.rejectRunMainPromise,
dotnetLoaderExports.getRunMainPromise,
+ dotnetLoaderExports.createPromiseCompletionSource,
+ dotnetLoaderExports.isControllablePromise,
+ dotnetLoaderExports.getPromiseCompletionSource,
];
}
}
diff --git a/src/native/corehost/browserhost/loader/logging.ts b/src/native/corehost/browserhost/loader/logging.ts
index 92398ccbaebf40..ce6ab84cf94f6e 100644
--- a/src/native/corehost/browserhost/loader/logging.ts
+++ b/src/native/corehost/browserhost/loader/logging.ts
@@ -14,6 +14,13 @@ export function check(condition: unknown, messageFactory: string | (() => string
const prefix = "DOTNET: ";
+export function debug(msg: string | (() => string), ...data: any) {
+ if (typeof msg === "function") {
+ msg = msg();
+ }
+ console.debug(prefix + msg, ...data);
+}
+
export function info(msg: string, ...data: any) {
console.info(prefix + msg, ...data);
}
diff --git a/src/native/corehost/browserhost/loader/polyfills.ts b/src/native/corehost/browserhost/loader/polyfills.ts
index 5a00c81950616b..917a1deedc1265 100644
--- a/src/native/corehost/browserhost/loader/polyfills.ts
+++ b/src/native/corehost/browserhost/loader/polyfills.ts
@@ -4,20 +4,6 @@
import { ENVIRONMENT_IS_NODE } from "./per-module";
export function initPolyfills(): void {
- if (typeof globalThis.WeakRef !== "function") {
- class WeakRefPolyfill {
- private _value: T | undefined;
-
- constructor(value: T) {
- this._value = value;
- }
-
- deref(): T | undefined {
- return this._value;
- }
- }
- globalThis.WeakRef = WeakRefPolyfill as any;
- }
if (typeof globalThis.fetch !== "function") {
globalThis.fetch = fetchLike as any;
}
@@ -62,10 +48,31 @@ export async function initPolyfillsAsync(): Promise {
// WASM-TODO: performance polyfill for V8
}
+let _nodeFs: any | undefined = undefined;
+let _nodeUrl: any | undefined = undefined;
+
+export async function nodeFs(): Promise {
+ if (ENVIRONMENT_IS_NODE && !_nodeFs) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore:
+ _nodeFs = await import(/*! webpackIgnore: true */"fs");
+ }
+ return _nodeFs;
+}
+
+export async function nodeUrl(): Promise {
+ if (ENVIRONMENT_IS_NODE && !_nodeUrl) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore:
+ _nodeUrl = await import(/*! webpackIgnore: true */"node:url");
+ }
+ return _nodeUrl;
+}
+
export async function fetchLike(url: string, init?: RequestInit): Promise {
- let node_fs: any | undefined = undefined;
- let node_url: any | undefined = undefined;
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) {
@@ -73,19 +80,11 @@ export async function fetchLike(url: string, init?: RequestInit): Promise{
ok: true,
headers: {
@@ -102,8 +101,6 @@ export async function fetchLike(url: string, init?: RequestInit): Promisee.xml
- // https://bugs.chromium.org/p/v8/issues/detail?id=12541
return {
ok: true,
url,
diff --git a/src/native/corehost/browserhost/loader/promise-controller.ts b/src/native/corehost/browserhost/loader/promise-completion-source.ts
similarity index 66%
rename from src/native/corehost/browserhost/loader/promise-controller.ts
rename to src/native/corehost/browserhost/loader/promise-completion-source.ts
index cce6bd67a6dd95..efb9f673feb4dc 100644
--- a/src/native/corehost/browserhost/loader/promise-controller.ts
+++ b/src/native/corehost/browserhost/loader/promise-completion-source.ts
@@ -1,17 +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 { ControllablePromise, PromiseController } from "./types";
+import type { ControllablePromise, PromiseCompletionSource } from "./types";
/// a unique symbol used to mark a promise as controllable
-export const promiseControlSymbol = Symbol.for("wasm promise control");
-
-// WASM-TODO: PromiseCompletionSource
+export const promiseCompletionSourceSymbol = Symbol.for("wasm promise control");
/// Creates a new promise together with a controller that can be used to resolve or reject that promise.
/// Optionally takes callbacks to be called immediately after a promise is resolved or rejected.
-export function createPromiseController(afterResolve?: () => void, afterReject?: () => void): PromiseController {
- let promiseControl: PromiseController = null as unknown as PromiseController;
+export function createPromiseCompletionSource(afterResolve?: () => void, afterReject?: () => void): PromiseCompletionSource {
+ let promiseControl: PromiseCompletionSource = null as unknown as PromiseCompletionSource;
const promise = new Promise((resolve, reject) => {
promiseControl = {
isDone: false,
@@ -41,16 +39,16 @@ export function createPromiseController(afterResolve?: () => void, afterRejec
});
(promiseControl).promise = promise;
const controllablePromise = promise as ControllablePromise;
- (controllablePromise as any)[promiseControlSymbol] = promiseControl;
+ (controllablePromise as any)[promiseCompletionSourceSymbol] = promiseControl;
return promiseControl;
}
-export function getPromiseController(promise: ControllablePromise): PromiseController;
-export function getPromiseController(promise: Promise): PromiseController | undefined {
- return (promise as any)[promiseControlSymbol];
+export function getPromiseCompletionSource(promise: ControllablePromise): PromiseCompletionSource;
+export function getPromiseCompletionSource(promise: Promise): PromiseCompletionSource | undefined {
+ return (promise as any)[promiseCompletionSourceSymbol];
}
export function isControllablePromise(promise: Promise): promise is ControllablePromise {
- return (promise as any)[promiseControlSymbol] !== undefined;
+ return (promise as any)[promiseCompletionSourceSymbol] !== undefined;
}
diff --git a/src/native/corehost/browserhost/loader/run.ts b/src/native/corehost/browserhost/loader/run.ts
index 3daf381b2b0bb4..06b1df3421d272 100644
--- a/src/native/corehost/browserhost/loader/run.ts
+++ b/src/native/corehost/browserhost/loader/run.ts
@@ -1,14 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+import { DotnetHostBuilder } from "../types";
+import { findResources, isNodeHosted, isShellHosted } from "./bootstrap";
import { Module, dotnetAssert } from "./cross-module";
import { exit } from "./exit";
-import { createPromiseController } from "./promise-controller";
+import { createPromiseCompletionSource } from "./promise-completion-source";
let CoreCLRInitialized = false;
-const runMainPromiseController = createPromiseController();
+const runMainPromiseController = createPromiseCompletionSource();
-export function BrowserHost_InitializeCoreCLR():void {
+export function BrowserHost_InitializeCoreCLR(): void {
dotnetAssert.check(!CoreCLRInitialized, "CoreCLR should be initialized just once");
CoreCLRInitialized = true;
@@ -22,14 +24,29 @@ export function BrowserHost_InitializeCoreCLR():void {
}
}
-export function resolveRunMainPromise(exitCode:number):void {
+export function resolveRunMainPromise(exitCode: number): void {
runMainPromiseController.resolve(exitCode);
}
-export function rejectRunMainPromise(reason:any):void {
+export function rejectRunMainPromise(reason: any): void {
runMainPromiseController.reject(reason);
}
-export function getRunMainPromise():Promise {
+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.run();
+ } 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/corehost/browserhost/sample/CMakeLists.txt b/src/native/corehost/browserhost/sample/CMakeLists.txt
index f4fd154250aa62..35b44bc3d347fe 100644
--- a/src/native/corehost/browserhost/sample/CMakeLists.txt
+++ b/src/native/corehost/browserhost/sample/CMakeLists.txt
@@ -5,12 +5,14 @@
# WASM-TODO: implement proper in-tree project via MSBuild and WASM SDK
set(SAMPLE_ASSETS
- index.html
- main.mjs
- dotnet.boot.js
+ # index.html
+ # main.mjs
+ # dotnet.boot.js
${SHARED_LIB_DESTINATION}/dotnet.js
${SHARED_LIB_DESTINATION}/dotnet.js.map
${SHARED_LIB_DESTINATION}/dotnet.d.ts
+ ${SHARED_LIB_DESTINATION}/dotnet.diagnostics.js
+ ${SHARED_LIB_DESTINATION}/dotnet.diagnostics.js.map
${SHARED_LIB_DESTINATION}/dotnet.runtime.js
${SHARED_LIB_DESTINATION}/dotnet.runtime.js.map
diff --git a/src/native/corehost/corehost.proj b/src/native/corehost/corehost.proj
index 23fff553c69459..52214740f05be9 100644
--- a/src/native/corehost/corehost.proj
+++ b/src/native/corehost/corehost.proj
@@ -5,12 +5,13 @@
package's targets into the build.
-->
-
+
true
GenerateRuntimeVersionFile
$(BuildCoreHostDependsOn);InitializeSourceControlInformationFromSourceControlManager
+ $(BuildCoreHostDependsOn);GenerateEmccExports;ResolveRuntimeFilesFromLocalBuild
$(ArtifactsObjDir)$(TargetRid).$(Configuration)\
$(ArtifactsObjDir)_version.h
@@ -166,6 +167,23 @@
-->
+
+
+
+ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(LibrariesSharedFrameworkDir)package.json" />
+ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(LibrariesSharedFrameworkDir)dotnet.d.ts" />
+ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(LibrariesSharedFrameworkDir)*.map" />
+ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(LibrariesSharedFrameworkDir)*.js" />
+ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(LibrariesSharedFrameworkDir)*.a" />
+ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(LibrariesSharedFrameworkDir)*.dat" />
+ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(HostSharedFrameworkDir)libBrowserHost.a" />
+ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(HostSharedFrameworkDir)dotnet.native.js" />
+ <_MicrosoftNetCoreAppRuntimePackNativeDirFiles Include="$(HostSharedFrameworkDir)dotnet.native.wasm" />
+
+
+
, subscriber?:InternalExchangeSubscriber) => void;
- export const dotnetUpdateInternalsSubscriber:(internals:InternalExchange) => void;
+ export const dotnetAssert: AssertType;
+ export const dotnetLogger: LoggerType;
+ export const dotnetLoaderExports: LoaderExports;
+ export const dotnetRuntimeExports: RuntimeExports;
+ export const dotnetBrowserUtilsExports: BrowserUtilsExports;
+ export const dotnetUpdateInternals: (internals?: Partial, subscriber?: InternalExchangeSubscriber) => void;
+ export const dotnetUpdateInternalsSubscriber: (internals: InternalExchange) => void;
// ambient in the emscripten closure
- export const Module:EmscriptenModuleInternal;
+ export const Module: EmscriptenModuleInternal;
export const ENVIRONMENT_IS_NODE: boolean;
- export const ENVIRONMENT_IS_SHELL:boolean;
+ export const ENVIRONMENT_IS_SHELL: boolean;
export const ENVIRONMENT_IS_WEB: boolean;
- export const ENVIRONMENT_IS_WORKER:boolean;
+ export const ENVIRONMENT_IS_WORKER: boolean;
export const ENVIRONMENT_IS_SIDECAR: boolean;
export const VoidPtrNull: VoidPtr;
export const CharPtrNull: CharPtr;
export const NativePointerNull: NativePointer;
+ export function safeSetTimeout(func: Function, timeout: number): number;
+ export function maybeExit(): void;
+ export function exitJS(status: number, implicit?: boolean | number): void;
+
}
diff --git a/src/native/libs/Common/JavaScript/cross-module/index.ts b/src/native/libs/Common/JavaScript/cross-module/index.ts
index 8ff21c04f224de..620843533886a5 100644
--- a/src/native/libs/Common/JavaScript/cross-module/index.ts
+++ b/src/native/libs/Common/JavaScript/cross-module/index.ts
@@ -95,24 +95,35 @@ export function dotnetUpdateInternalsSubscriber() {
// keep in sync with runtimeExportsToTable()
function runtimeExportsFromTable(table: RuntimeExportsTable, runtime: RuntimeExports): void {
- Object.assign(runtime, {
- });
+ const runtimerLocal: RuntimeExports = {
+ bindJSImportST: table[0],
+ invokeJSImportST: table[1],
+ releaseCSOwnedObject: table[2],
+ resolveOrRejectPromise: table[3],
+ cancelPromise: table[4],
+ invokeJSFunction: table[5],
+ };
+ Object.assign(runtime, runtimerLocal);
}
// keep in sync with loaderExportsToTable()
function loaderExportsFromTable(table: LoaderExportsTable, logger: LoggerType, assert: AssertType, dotnetLoaderExports: LoaderExports): void {
const loggerLocal: LoggerType = {
- info: table[0],
- warn: table[1],
- error: table[2],
+ debug: table[0],
+ info: table[1],
+ warn: table[2],
+ error: table[3],
};
const assertLocal: AssertType = {
- check: table[3],
+ check: table[4],
};
const loaderExportsLocal: LoaderExports = {
- resolveRunMainPromise: table[4],
- rejectRunMainPromise: table[5],
- getRunMainPromise: table[6],
+ resolveRunMainPromise: table[5],
+ rejectRunMainPromise: table[6],
+ getRunMainPromise: table[7],
+ createPromiseCompletionSource: table[8],
+ isControllablePromise: table[9],
+ getPromiseCompletionSource: table[10],
};
Object.assign(dotnetLoaderExports, loaderExportsLocal);
Object.assign(logger, loggerLocal);
@@ -123,6 +134,7 @@ export function dotnetUpdateInternalsSubscriber() {
function browserHostExportsFromTable(table: BrowserHostExportsTable, native: BrowserHostExports): void {
const nativeLocal: BrowserHostExports = {
registerDllBytes: table[0],
+ installVfsFile: table[1],
};
Object.assign(native, nativeLocal);
}
@@ -130,6 +142,7 @@ export function dotnetUpdateInternalsSubscriber() {
// keep in sync with interopJavaScriptExportsToTable()
function interopJavaScriptExportsFromTable(table: InteropJavaScriptExportsTable, interop: InteropJavaScriptExports): void {
const interopLocal: InteropJavaScriptExports = {
+ SystemInteropJS_GetManagedStackTrace: table[0],
};
Object.assign(interop, interopLocal);
}
@@ -148,6 +161,7 @@ export function dotnetUpdateInternalsSubscriber() {
stringToUTF16: table[1],
stringToUTF16Ptr: table[2],
stringToUTF8Ptr: table[3],
+ zeroRegion: table[4],
};
Object.assign(interop, interopLocal);
}
diff --git a/src/native/libs/Common/JavaScript/types/exchange.ts b/src/native/libs/Common/JavaScript/types/exchange.ts
index e553bbf67dcf40..9e6db95e3289f0 100644
--- a/src/native/libs/Common/JavaScript/types/exchange.ts
+++ b/src/native/libs/Common/JavaScript/types/exchange.ts
@@ -1,18 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import type { registerDllBytes } from "../../../../corehost/browserhost/host/host";
-import type { check, error, info, warn } from "../../../../corehost/browserhost/loader/logging";
+import type { installVfsFile, registerDllBytes } from "../../../../corehost/browserhost/host/host";
+import type { check, error, info, warn, debug } from "../../../../corehost/browserhost/loader/logging";
+import type { createPromiseCompletionSource, getPromiseCompletionSource, isControllablePromise } from "../../../../corehost/browserhost/loader/promise-completion-source";
import type { resolveRunMainPromise, rejectRunMainPromise, getRunMainPromise } from "../../../../corehost/browserhost/loader/run";
+import type { zeroRegion } from "../../../System.Native.Browser/utils/memory";
import type { stringToUTF16, stringToUTF16Ptr, stringToUTF8Ptr, utf16ToString } from "../../../System.Native.Browser/utils/strings";
+import type { bindJSImportST, invokeJSFunction, invokeJSImportST } from "../../../System.Runtime.InteropServices.JavaScript.Native/interop/invoke-js";
+import type { releaseCSOwnedObject } from "../../../System.Runtime.InteropServices.JavaScript.Native/interop/gc-handles";
+import type { resolveOrRejectPromise } from "../../../System.Runtime.InteropServices.JavaScript.Native/interop/marshal-to-js";
+import type { cancelPromise } from "../../../System.Runtime.InteropServices.JavaScript.Native/interop/cancelable-promise";
export type RuntimeExports = {
+ bindJSImportST: typeof bindJSImportST,
+ invokeJSImportST: typeof invokeJSImportST,
+ releaseCSOwnedObject: typeof releaseCSOwnedObject,
+ resolveOrRejectPromise: typeof resolveOrRejectPromise,
+ cancelPromise: typeof cancelPromise,
+ invokeJSFunction: typeof invokeJSFunction,
}
export type RuntimeExportsTable = [
+ typeof bindJSImportST,
+ typeof invokeJSImportST,
+ typeof releaseCSOwnedObject,
+ typeof resolveOrRejectPromise,
+ typeof cancelPromise,
+ typeof invokeJSFunction,
]
export type LoggerType = {
+ debug: typeof debug,
info: typeof info,
warn: typeof warn,
error: typeof error,
@@ -26,9 +45,13 @@ export type LoaderExports = {
resolveRunMainPromise: typeof resolveRunMainPromise,
rejectRunMainPromise: typeof rejectRunMainPromise,
getRunMainPromise: typeof getRunMainPromise,
+ createPromiseCompletionSource: typeof createPromiseCompletionSource,
+ isControllablePromise: typeof isControllablePromise,
+ getPromiseCompletionSource: typeof getPromiseCompletionSource,
}
export type LoaderExportsTable = [
+ typeof debug,
typeof info,
typeof warn,
typeof error,
@@ -36,20 +59,27 @@ export type LoaderExportsTable = [
typeof resolveRunMainPromise,
typeof rejectRunMainPromise,
typeof getRunMainPromise,
+ typeof createPromiseCompletionSource,
+ typeof isControllablePromise,
+ typeof getPromiseCompletionSource,
]
export type BrowserHostExports = {
registerDllBytes: typeof registerDllBytes
+ installVfsFile: typeof installVfsFile
}
export type BrowserHostExportsTable = [
typeof registerDllBytes,
+ typeof installVfsFile,
]
export type InteropJavaScriptExports = {
+ SystemInteropJS_GetManagedStackTrace: typeof _SystemInteropJS_GetManagedStackTrace,
}
export type InteropJavaScriptExportsTable = [
+ typeof _SystemInteropJS_GetManagedStackTrace,
]
export type NativeBrowserExports = {
@@ -63,6 +93,7 @@ export type BrowserUtilsExports = {
stringToUTF16: typeof stringToUTF16,
stringToUTF16Ptr: typeof stringToUTF16Ptr,
stringToUTF8Ptr: typeof stringToUTF8Ptr,
+ zeroRegion: typeof zeroRegion,
}
export type BrowserUtilsExportsTable = [
@@ -70,4 +101,5 @@ export type BrowserUtilsExportsTable = [
typeof stringToUTF16,
typeof stringToUTF16Ptr,
typeof stringToUTF8Ptr,
+ typeof zeroRegion,
]
diff --git a/src/native/libs/Common/JavaScript/types/internal.ts b/src/native/libs/Common/JavaScript/types/internal.ts
index 1c8dfc922ef92e..f71ff8c3ba83e8 100644
--- a/src/native/libs/Common/JavaScript/types/internal.ts
+++ b/src/native/libs/Common/JavaScript/types/internal.ts
@@ -94,7 +94,7 @@ export interface ControllablePromise extends Promise {
}
/// Just a pair of a promise and its controller
-export interface PromiseController {
+export interface PromiseCompletionSource {
readonly promise: ControllablePromise;
isDone: boolean;
resolve: (value: T | PromiseLike) => void;
diff --git a/src/native/libs/Common/JavaScript/types/public-api.ts b/src/native/libs/Common/JavaScript/types/public-api.ts
index 6b8c76d373dcef..b7b3bc35358b6d 100644
--- a/src/native/libs/Common/JavaScript/types/public-api.ts
+++ b/src/native/libs/Common/JavaScript/types/public-api.ts
@@ -724,7 +724,7 @@ export interface IMemoryView extends IDisposable {
get byteLength(): number;
}
-export declare function exit(exit_code: number, reason?: any): void;
+export declare function exit(exitCode: number, reason?: any): void;
export declare const dotnet: DotnetHostBuilder;
diff --git a/src/native/libs/System.Native.Browser/diagnostics/index.ts b/src/native/libs/System.Native.Browser/diagnostics/index.ts
new file mode 100644
index 00000000000000..e016d244e745c6
--- /dev/null
+++ b/src/native/libs/System.Native.Browser/diagnostics/index.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 const dummyDiagnosticsExport = 42;
diff --git a/src/native/libs/System.Native.Browser/entrypoints.c b/src/native/libs/System.Native.Browser/entrypoints.c
index a9fdc1e9ca941f..17ca2e9361e503 100644
--- a/src/native/libs/System.Native.Browser/entrypoints.c
+++ b/src/native/libs/System.Native.Browser/entrypoints.c
@@ -13,6 +13,7 @@ EXTERN_C int32_t SystemJS_RandomBytes(uint8_t* buffer, int32_t bufferLength);
EXTERN_C uint16_t* SystemJS_GetLocaleInfo (const uint16_t* locale, int32_t localeLength, const uint16_t* culture, int32_t cultureLength, const uint16_t* result, int32_t resultMaxLength, int *resultLength);
EXTERN_C void SystemJS_ScheduleTimer (int shortestDueTimeMs);
EXTERN_C void SystemJS_ScheduleBackgroundJob ();
+EXTERN_C void SystemJS_ConsoleClear ();
static const Entry s_browserNative[] =
{
@@ -20,6 +21,7 @@ static const Entry s_browserNative[] =
DllImportEntry(SystemJS_GetLocaleInfo)
DllImportEntry(SystemJS_ScheduleTimer)
DllImportEntry(SystemJS_ScheduleBackgroundJob)
+ DllImportEntry(SystemJS_ConsoleClear)
};
EXTERN_C const void* SystemJSResolveDllImport(const char* name);
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 af47de94d39bdb..75d01139c89e4d 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
@@ -19,7 +19,7 @@
const exports = {};
libNativeBrowser(exports);
- let commonDeps = ["$BROWSER_UTILS"];
+ let commonDeps = ["$BROWSER_UTILS", "GetDotNetRuntimeContractDescriptor"];
const lib = {
$DOTNET: {
selfInitialize: () => {
diff --git a/src/native/libs/System.Native.Browser/native/cross-linked.ts b/src/native/libs/System.Native.Browser/native/cross-linked.ts
index 0ccc52a6322530..89ab6f2bd32d83 100644
--- a/src/native/libs/System.Native.Browser/native/cross-linked.ts
+++ b/src/native/libs/System.Native.Browser/native/cross-linked.ts
@@ -5,6 +5,7 @@
import { } from "../../Common/JavaScript/cross-linked";
declare global {
export const DOTNET: any;
+ export function _GetDotNetRuntimeContractDescriptor(): void;
export function _SystemJS_ExecuteTimerCallback(): void;
export function _SystemJS_ExecuteBackgroundJobCallback(): void;
}
diff --git a/src/native/libs/System.Native.Browser/native/index.ts b/src/native/libs/System.Native.Browser/native/index.ts
index b85cd23d022fc0..776f6258ff8859 100644
--- a/src/native/libs/System.Native.Browser/native/index.ts
+++ b/src/native/libs/System.Native.Browser/native/index.ts
@@ -6,7 +6,7 @@ import { InternalExchangeIndex } from "../types";
export { SystemJS_RandomBytes } from "./crypto";
export { SystemJS_GetLocaleInfo } from "./globalization-locale";
-export { SystemJS_RejectMainPromise, SystemJS_ResolveMainPromise } from "./main";
+export { SystemJS_RejectMainPromise, SystemJS_ResolveMainPromise, SystemJS_ConsoleClear } from "./main";
export { SystemJS_ScheduleTimer, SystemJS_ScheduleBackgroundJob } from "./timer";
export function dotnetInitializeModule(internals: InternalExchange): void {
@@ -15,7 +15,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
dotnetUpdateInternals(internals, dotnetUpdateInternalsSubscriber);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- function nativeBrowserExportsToTable(map:NativeBrowserExports):NativeBrowserExportsTable {
+ function nativeBrowserExportsToTable(map: NativeBrowserExports): NativeBrowserExportsTable {
// keep in sync with nativeBrowserExportsFromTable()
return [
];
diff --git a/src/native/libs/System.Native.Browser/native/main.ts b/src/native/libs/System.Native.Browser/native/main.ts
index 72ec5fa9634119..cac449056a116a 100644
--- a/src/native/libs/System.Native.Browser/native/main.ts
+++ b/src/native/libs/System.Native.Browser/native/main.ts
@@ -3,16 +3,16 @@
import { } from "./cross-linked"; // ensure ambient symbols are declared
-export function SystemJS_ResolveMainPromise(exitCode: number) {
+export function SystemJS_ResolveMainPromise(exitCode: number): void {
if (dotnetLoaderExports.resolveRunMainPromise) {
dotnetLoaderExports.resolveRunMainPromise(exitCode);
} else {
// this is for corerun, which does not use the promise
- Module.exitJS(exitCode, true);
+ exitJS(exitCode, true);
}
}
-export function SystemJS_RejectMainPromise(messagePtr: number, messageLength: number, stackTracePtr: number, stackTraceLength: number) {
+export function SystemJS_RejectMainPromise(messagePtr: number, messageLength: number, stackTracePtr: number, stackTraceLength: number): void {
const message = dotnetBrowserUtilsExports.utf16ToString(messagePtr, messagePtr + messageLength * 2);
const stackTrace = dotnetBrowserUtilsExports.utf16ToString(stackTracePtr, stackTracePtr + stackTraceLength * 2);
const error = new Error(message);
@@ -24,3 +24,8 @@ export function SystemJS_RejectMainPromise(messagePtr: number, messageLength: nu
throw error;
}
}
+
+export function SystemJS_ConsoleClear(): void {
+ // eslint-disable-next-line no-console
+ console.clear();
+}
diff --git a/src/native/libs/System.Native.Browser/native/timer.ts b/src/native/libs/System.Native.Browser/native/timer.ts
index a6ce86de14611c..89eb6a213b78bb 100644
--- a/src/native/libs/System.Native.Browser/native/timer.ts
+++ b/src/native/libs/System.Native.Browser/native/timer.ts
@@ -8,10 +8,10 @@ export function SystemJS_ScheduleTimer(shortestDueTimeMs: number): void {
globalThis.clearTimeout(DOTNET.lastScheduledTimerId);
DOTNET.lastScheduledTimerId = undefined;
}
- DOTNET.lastScheduledTimerId = Module.safeSetTimeout(SystemJS_ScheduleTimerTick, shortestDueTimeMs);
+ DOTNET.lastScheduledTimerId = safeSetTimeout(SystemJS_ScheduleTimerTick, shortestDueTimeMs);
function SystemJS_ScheduleTimerTick(): void {
- Module.maybeExit();
+ maybeExit();
_SystemJS_ExecuteTimerCallback();
}
}
@@ -22,10 +22,10 @@ export function SystemJS_ScheduleBackgroundJob(): void {
globalThis.clearTimeout(DOTNET.lastScheduledThreadPoolId);
DOTNET.lastScheduledThreadPoolId = undefined;
}
- DOTNET.lastScheduledThreadPoolId = Module.safeSetTimeout(SystemJS_ScheduleBackgroundJobTick, 0);
+ DOTNET.lastScheduledThreadPoolId = safeSetTimeout(SystemJS_ScheduleBackgroundJobTick, 0);
function SystemJS_ScheduleBackgroundJobTick(): void {
- Module.maybeExit();
+ maybeExit();
_SystemJS_ExecuteBackgroundJobCallback();
}
}
diff --git a/src/native/libs/System.Native.Browser/utils/cdac.ts b/src/native/libs/System.Native.Browser/utils/cdac.ts
new file mode 100644
index 00000000000000..6a080a8fece04c
--- /dev/null
+++ b/src/native/libs/System.Native.Browser/utils/cdac.ts
@@ -0,0 +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 { RuntimeAPI } from "./types";
+import { Module } from "./cross-module";
+
+export function registerCDAC(runtimeApi: RuntimeAPI): void {
+ runtimeApi.INTERNAL.GetDotNetRuntimeContractDescriptor = () => _GetDotNetRuntimeContractDescriptor();
+ runtimeApi.INTERNAL.GetDotNetRuntimeHeap = (ptr: number, length: number) => Module.HEAPU8.subarray(ptr >>> 0, (ptr >>> 0) + length);
+}
diff --git a/src/native/libs/System.Native.Browser/utils/host.ts b/src/native/libs/System.Native.Browser/utils/host.ts
index 0742706f90fec5..bc885d426c3a35 100644
--- a/src/native/libs/System.Native.Browser/utils/host.ts
+++ b/src/native/libs/System.Native.Browser/utils/host.ts
@@ -11,13 +11,13 @@ import { ENVIRONMENT_IS_NODE } from "./per-module";
// - install global handler for unhandled exceptions and promise rejections
// - raise ExceptionHandling.RaiseAppDomainUnhandledExceptionEvent()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export function exit(exit_code: number, reason: any): void {
+export function exit(exitCode: number, reason: any): void {
if (reason) {
const reasonStr = (typeof reason === "object") ? `${reason.message || ""}\n${reason.stack || ""}` : reason.toString();
dotnetLogger.error(reasonStr);
}
if (ENVIRONMENT_IS_NODE) {
- (globalThis as any).process.exit(exit_code);
+ (globalThis as any).process.exit(exitCode);
}
}
diff --git a/src/native/libs/System.Native.Browser/utils/index.ts b/src/native/libs/System.Native.Browser/utils/index.ts
index ca9e9517f2f934..8328cb2b486a57 100644
--- a/src/native/libs/System.Native.Browser/utils/index.ts
+++ b/src/native/libs/System.Native.Browser/utils/index.ts
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import type { InternalExchange, BrowserUtilsExports, RuntimeAPI, BrowserUtilsExportsTable } from "../types";
+import type { InternalExchange, BrowserUtilsExports, RuntimeAPI, BrowserUtilsExportsTable } from "./types";
import { InternalExchangeIndex } from "../types";
import { } from "./cross-module"; // ensure ambient symbols are declared
@@ -9,12 +9,22 @@ import {
setHeapB32, setHeapB8, setHeapU8, setHeapU16, setHeapU32, setHeapI8, setHeapI16, setHeapI32, setHeapI52, setHeapU52, setHeapI64Big, setHeapF32, setHeapF64,
getHeapB32, getHeapB8, getHeapU8, getHeapU16, getHeapU32, getHeapI8, getHeapI16, getHeapI32, getHeapI52, getHeapU52, getHeapI64Big, getHeapF32, getHeapF64,
localHeapViewI8, localHeapViewI16, localHeapViewI32, localHeapViewI64Big, localHeapViewU8, localHeapViewU16, localHeapViewU32, localHeapViewF32, localHeapViewF64,
+ zeroRegion,
} from "./memory";
import { stringToUTF16, stringToUTF16Ptr, stringToUTF8Ptr, utf16ToString } from "./strings";
import { exit, setEnvironmentVariable } from "./host";
import { dotnetUpdateInternals, dotnetUpdateInternalsSubscriber } from "../utils/cross-module";
+import { initPolyfills } from "../utils/polyfills";
+import { registerRuntime } from "./runtime-list";
+import { registerCDAC } from "./cdac";
export function dotnetInitializeModule(internals: InternalExchange): void {
+ initPolyfills();
+ const runtimeApi = internals[InternalExchangeIndex.RuntimeAPI];
+ if (typeof runtimeApi !== "object") throw new Error("Expected internals to have RuntimeAPI");
+ registerRuntime(runtimeApi);
+ registerCDAC(runtimeApi);
+
if (!Array.isArray(internals)) throw new Error("Expected internals to be an array");
const runtimeApiLocal: Partial = {
setEnvironmentVariable,
@@ -23,8 +33,6 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
getHeapB32, getHeapB8, getHeapU8, getHeapU16, getHeapU32, getHeapI8, getHeapI16, getHeapI32, getHeapI52, getHeapU52, getHeapI64Big, getHeapF32, getHeapF64,
localHeapViewI8, localHeapViewI16, localHeapViewI32, localHeapViewI64Big, localHeapViewU8, localHeapViewU16, localHeapViewU32, localHeapViewF32, localHeapViewF64,
};
- const runtimeApi = internals[InternalExchangeIndex.RuntimeAPI];
- if (typeof runtimeApi !== "object") throw new Error("Expected internals to have RuntimeAPI");
Object.assign(runtimeApi, runtimeApiLocal);
internals[InternalExchangeIndex.BrowserUtilsExportsTable] = browserUtilsExportsToTable({
@@ -32,6 +40,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
stringToUTF16,
stringToUTF16Ptr,
stringToUTF8Ptr,
+ zeroRegion,
});
dotnetUpdateInternals(internals, dotnetUpdateInternalsSubscriber);
function browserUtilsExportsToTable(map: BrowserUtilsExports): BrowserUtilsExportsTable {
@@ -41,6 +50,7 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
map.stringToUTF16,
map.stringToUTF16Ptr,
map.stringToUTF8Ptr,
+ map.zeroRegion,
];
}
}
diff --git a/src/native/libs/System.Native.Browser/utils/memory.ts b/src/native/libs/System.Native.Browser/utils/memory.ts
index 07153b163c9dd2..a29dc6a38de565 100644
--- a/src/native/libs/System.Native.Browser/utils/memory.ts
+++ b/src/native/libs/System.Native.Browser/utils/memory.ts
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-import type { CharPtr, MemOffset, NumberOrPointer, VoidPtr } from "../types";
+import type { CharPtr, MemOffset, NumberOrPointer, VoidPtr } from "./types";
import { Module, dotnetAssert, dotnetLogger } from "./cross-module";
const max_int64_big = BigInt("9223372036854775807");
@@ -25,12 +25,12 @@ export function setHeapB8(offset: MemOffset, value: number | boolean): void {
const boolValue = !!value;
if (typeof (value) === "number")
assertIntInRange(value, 0, 1);
- Module.HEAPU8[offset] = boolValue ? 1 : 0;
+ Module.HEAPU8[offset >>> 0] = boolValue ? 1 : 0;
}
export function setHeapU8(offset: MemOffset, value: number): void {
assertIntInRange(value, 0, 0xFF);
- Module.HEAPU8[offset] = value;
+ Module.HEAPU8[offset >>> 0] = value;
}
export function setHeapU16(offset: MemOffset, value: number): void {
@@ -122,11 +122,11 @@ export function getHeapB32(offset: MemOffset): boolean {
}
export function getHeapB8(offset: MemOffset): boolean {
- return !!(Module.HEAPU8[offset]);
+ return !!(Module.HEAPU8[offset >>> 0]);
}
export function getHeapU8(offset: MemOffset): number {
- return Module.HEAPU8[offset];
+ return Module.HEAPU8[offset >>> 0];
}
export function getHeapU16(offset: MemOffset): number {
@@ -244,7 +244,7 @@ export function localHeapViewF64(): Float64Array {
export function copyBytes(srcPtr: VoidPtr, dstPtr: VoidPtr, bytes: number): void {
const heap = localHeapViewU8();
- heap.copyWithin(dstPtr as any, srcPtr as any, srcPtr as any + bytes);
+ heap.copyWithin(dstPtr as any >>> 0, srcPtr as any >>> 0, (srcPtr as any >>> 0) + bytes);
}
export function isSharedArrayBuffer(buffer: any): buffer is SharedArrayBuffer {
@@ -272,10 +272,10 @@ export function viewOrCopy(view: Uint8Array, start: CharPtr, end: CharPtr): Uint
// this condition should be eliminated by rollup on non-threading builds
const needsCopy = isSharedArrayBuffer(view.buffer);
return needsCopy
- ? view.slice(start, end)
- : view.subarray(start, end);
+ ? view.slice(start >>> 0, end >>> 0)
+ : view.subarray(start >>> 0, end >>> 0);
}
export function zeroRegion(byteOffset: VoidPtr, sizeBytes: number): void {
- localHeapViewU8().fill(0, byteOffset, byteOffset + sizeBytes);
+ localHeapViewU8().fill(0, byteOffset >>> 0, (byteOffset >>> 0) + sizeBytes);
}
diff --git a/src/native/libs/System.Native.Browser/utils/polyfills.ts b/src/native/libs/System.Native.Browser/utils/polyfills.ts
new file mode 100644
index 00000000000000..fbd524d8474367
--- /dev/null
+++ b/src/native/libs/System.Native.Browser/utils/polyfills.ts
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+export function initPolyfills(): void {
+ if (typeof globalThis.WeakRef !== "function") {
+ class WeakRefPolyfill {
+ private _value: T | undefined;
+
+ constructor(value: T) {
+ this._value = value;
+ }
+
+ deref(): T | undefined {
+ return this._value;
+ }
+ }
+ globalThis.WeakRef = WeakRefPolyfill as any;
+ }
+}
diff --git a/src/native/corehost/browserhost/loader/runtime-list.ts b/src/native/libs/System.Native.Browser/utils/runtime-list.ts
similarity index 100%
rename from src/native/corehost/browserhost/loader/runtime-list.ts
rename to src/native/libs/System.Native.Browser/utils/runtime-list.ts
diff --git a/src/native/libs/System.Native.Browser/utils/strings.ts b/src/native/libs/System.Native.Browser/utils/strings.ts
index 85aecade9378f4..5609f121d2cbe5 100644
--- a/src/native/libs/System.Native.Browser/utils/strings.ts
+++ b/src/native/libs/System.Native.Browser/utils/strings.ts
@@ -20,6 +20,8 @@ export function stringsInit(): void {
}
export function stringToUTF16(dstPtr: number, endPtr: number, text: string) {
+ dstPtr = dstPtr >>> 0;
+ endPtr = endPtr >>> 0;
const heapI16 = dotnetApi.localHeapViewU16();
const len = text.length;
for (let i = 0; i < len; i++) {
@@ -45,6 +47,8 @@ export function stringToUTF8Ptr(str: string): CharPtr {
}
export function utf16ToString(startPtr: number, endPtr: number): string {
+ startPtr = startPtr >>> 0;
+ endPtr = endPtr >>> 0;
stringsInit();
if (textDecoderUtf16) {
const subArray = viewOrCopy(dotnetApi.localHeapViewU8(), startPtr as any, endPtr as any);
@@ -56,6 +60,8 @@ export function utf16ToString(startPtr: number, endPtr: number): string {
// V8 does not provide TextDecoder
export function utf16ToStringLoop(startPtr: number, endPtr: number): string {
+ startPtr = startPtr >>> 0;
+ endPtr = endPtr >>> 0;
let str = "";
const heapU16 = dotnetApi.localHeapViewU16();
for (let i = startPtr; i < endPtr; i += 2) {
diff --git a/src/native/libs/System.Native.Browser/utils/types.ts b/src/native/libs/System.Native.Browser/utils/types.ts
new file mode 100644
index 00000000000000..2786379af7369f
--- /dev/null
+++ b/src/native/libs/System.Native.Browser/utils/types.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 "../types";
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/entrypoints.c b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/entrypoints.c
index a8016806dc103c..dc41cc1b5bada8 100644
--- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/entrypoints.c
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/entrypoints.c
@@ -9,11 +9,21 @@
#endif//EXTERN_C
// implemented in JavaScript
-EXTERN_C int32_t SystemInteropJS_InvokeJSImportST(uint8_t* buffer, int32_t bufferLength);
+EXTERN_C void* SystemInteropJS_BindJSImportST(void* signature);
+EXTERN_C void SystemInteropJS_InvokeJSImportST(int32_t functionHandle, void *args);
+EXTERN_C void SystemInteropJS_ReleaseCSOwnedObject (int32_t jsHandle);
+EXTERN_C void SystemInteropJS_ResolveOrRejectPromise (void *args);
+EXTERN_C void SystemInteropJS_CancelPromise (int32_t taskHolderGCHandle);
+EXTERN_C void SystemInteropJS_InvokeJSFunction (int32_t functionJSSHandle, void *args);
static const Entry s_browserNative[] =
{
+ DllImportEntry(SystemInteropJS_BindJSImportST)
DllImportEntry(SystemInteropJS_InvokeJSImportST)
+ DllImportEntry(SystemInteropJS_ReleaseCSOwnedObject)
+ DllImportEntry(SystemInteropJS_ResolveOrRejectPromise)
+ DllImportEntry(SystemInteropJS_CancelPromise)
+ DllImportEntry(SystemInteropJS_InvokeJSFunction)
};
EXTERN_C const void* SystemJSInteropResolveDllImport(const char* name);
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
new file mode 100644
index 00000000000000..16918163da632e
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/cancelable-promise.ts
@@ -0,0 +1,134 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { ManagedObject } from "./marshaled-types";
+import { ControllablePromise, GCHandle, MarshalerToCs } from "./types";
+import { isRuntimeRunning } from "./utils";
+import { lookupJsOwnedObject, teardownManagedProxy } from "./gc-handles";
+import { marshalCsObjectToCs } from "./marshal-to-cs";
+import { completeTask } from "./managed-exports";
+
+const promiseHolderSymbol = Symbol.for("wasm promise_holder");
+
+export function isThenable(js_obj: any): boolean {
+ // When using an external Promise library like Bluebird the Promise.resolve may not be sufficient
+ // to identify the object as a Promise.
+ return Promise.resolve(js_obj) === js_obj ||
+ ((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function");
+}
+
+export function wrapAsCancelablePromise(fn: () => Promise): ControllablePromise {
+ const pcs = dotnetLoaderExports.createPromiseCompletionSource();
+ const inner = fn();
+ inner.then((data) => pcs.resolve(data)).catch((reason) => pcs.reject(reason));
+ return pcs.promise;
+}
+
+export function wrapAsCancelable(inner: Promise): ControllablePromise {
+ const pcs = dotnetLoaderExports.createPromiseCompletionSource();
+ inner.then((data) => pcs.resolve(data)).catch((reason) => pcs.reject(reason));
+ return pcs.promise;
+}
+
+export function cancelPromise(task_holder_gc_handle: GCHandle): void {
+ // cancelation should not arrive earlier than the promise created by marshaling in SystemInteropJS_InvokeJSImportSync
+ Module.safeSetTimeout(() => {
+ if (!isRuntimeRunning()) {
+ dotnetLogger.debug("This promise can't be canceled, mono runtime already exited.");
+ return;
+ }
+ const holder = lookupJsOwnedObject(task_holder_gc_handle) as PromiseHolder;
+ dotnetAssert.check(!!holder, () => `Expected Promise for GCHandle ${task_holder_gc_handle}`);
+ holder.cancel();
+ }, 0);
+}
+
+export class PromiseHolder extends ManagedObject {
+ public isResolved = false;
+ public isPosted = false;
+ public isPostponed = false;
+ public data: any = null;
+ public reason: any = undefined;
+ public constructor(public promise: Promise,
+ private gc_handle: GCHandle,
+ private res_converter?: MarshalerToCs) {
+ super();
+ }
+
+ resolve(data: any) {
+ if (!isRuntimeRunning()) {
+ dotnetLogger.debug("This promise resolution can't be propagated to managed code, runtime already exited.");
+ return;
+ }
+ dotnetAssert.check(!this.isResolved, "resolve could be called only once");
+ dotnetAssert.check(!this.isDisposed, "resolve is already disposed.");
+ this.isResolved = true;
+ this.completeTaskWrapper(data, null);
+ }
+
+ reject(reason: any) {
+ if (!isRuntimeRunning()) {
+ dotnetLogger.debug("This promise rejection can't be propagated to managed code, runtime already exited.");
+ return;
+ }
+ if (!reason) {
+ reason = new Error() as any;
+ }
+ dotnetAssert.check(!this.isResolved, "reject could be called only once");
+ dotnetAssert.check(!this.isDisposed, "resolve is already disposed.");
+ this.isResolved = true;
+ this.completeTaskWrapper(null, reason);
+ }
+
+ cancel() {
+ if (!isRuntimeRunning()) {
+ dotnetLogger.debug("This promise cancelation can't be propagated to managed code, runtime already exited.");
+ return;
+ }
+ dotnetAssert.check(!this.isResolved, "cancel could be called only once");
+ dotnetAssert.check(!this.isDisposed, "resolve is already disposed.");
+
+ if (this.isPostponed) {
+ // there was racing resolve/reject which was postponed, to retain valid GCHandle
+ // in this case we just finish the original resolve/reject
+ // and we need to use the postponed data/reason
+ this.isResolved = true;
+ if (this.reason !== undefined) {
+ this.completeTaskWrapper(null, this.reason);
+ } else {
+ this.completeTaskWrapper(this.data, null);
+ }
+ } else {
+ // there is no racing resolve/reject, we can reject/cancel the promise
+ const promise = this.promise;
+ assertIsControllablePromise(promise);
+ const pcs = dotnetLoaderExports.getPromiseCompletionSource(promise);
+
+ const reason = new Error("OperationCanceledException") as any;
+ reason[promiseHolderSymbol] = this;
+ pcs.reject(reason);
+ }
+ }
+
+ // we can do this just once, because it will be dispose the GCHandle
+ completeTaskWrapper(data: any, reason: any) {
+ try {
+ dotnetAssert.check(!this.isPosted, "Promise is already posted to managed.");
+ this.isPosted = true;
+
+ // we can unregister the GC handle just on JS side
+ teardownManagedProxy(this, this.gc_handle, /*skipManaged: */ true);
+ // order of operations with teardown_managed_proxy matters
+ // so that managed user code running in the continuation could allocate the same GCHandle number and the local registry would be already ok with that
+ completeTask(this.gc_handle, reason, data, this.res_converter || marshalCsObjectToCs);
+ } catch (ex) {
+ // there is no point to propagate the exception into the unhandled promise rejection
+ }
+ }
+}
+
+export function assertIsControllablePromise(promise: Promise): asserts promise is ControllablePromise {
+ if (!dotnetLoaderExports.isControllablePromise(promise)) {
+ throw new Error("Expected a controllable promise.");
+ }
+}
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/gc-handles.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/gc-handles.ts
new file mode 100644
index 00000000000000..7df6fa8599e72c
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/gc-handles.ts
@@ -0,0 +1,333 @@
+// 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 { dotnetAssert, dotnetLogger } from "./cross-module";
+
+import type { GCHandle, JSHandle, WeakRefInternal } from "./types";
+import { GCHandleNull } from "./types";
+import { assertJsInterop, isRuntimeRunning } from "./utils";
+import { useWeakRef, createStrongRef, createWeakRef } from "./weak-ref";
+import { jsImportWrapperByFnHandle } from "./invoke-js";
+import { boundCsFunctionSymbol, importedJsFunctionSymbol, proxyDebugSymbol } from "./marshal";
+import { exportsByAssembly } from "./invoke-cs";
+import { releaseJsOwnedObjectByGcHandle } from "./managed-exports";
+
+const useFinalizationRegistry = typeof globalThis.FinalizationRegistry === "function";
+let jsOwnedObjectRegistry: FinalizationRegistry;
+
+// this is array, not map. We maintain list of gaps in _JsHandleFreeList so that it could be as compact as possible
+// 0th element is always null, because JSHandle == 0 is invalid handle.
+const _CsOwnedObjectsByJsHandle: any[] = [null];
+const _CsOwnedObjectsByJsvHandle: any[] = [null];
+const _JsHandleFreeList: JSHandle[] = [];
+let _nextJSHandle = 1;
+
+export const jsOwnedObjectTable = new Map>();
+
+const _GcvHandleFreeList: GCHandle[] = [];
+let nextGcvHandle = -2;
+
+// GCVHandle is like GCHandle, but it's not tracked and allocated by the coreCLR GC, but just by JS.
+// It's used when we need to create GCHandle-like identity ahead of time, before calling coreCLR.
+// they have negative values, so that they don't collide with GCHandles.
+export function allocGcvHandle(): GCHandle {
+ const gcvHandle = _GcvHandleFreeList.length ? _GcvHandleFreeList.pop() : nextGcvHandle--;
+ return gcvHandle as any;
+}
+
+export function freeGcvHandle(gcvHandle: GCHandle): void {
+ _GcvHandleFreeList.push(gcvHandle);
+}
+
+export function isJsvHandle(jsHandle: JSHandle): boolean {
+ return (jsHandle as any) < -1;
+}
+
+export function isJsHandle(jsHandle: JSHandle): boolean {
+ return (jsHandle as any) > 0;
+}
+
+export function isGcvHandle(gcHandle: GCHandle): boolean {
+ return (gcHandle as any) < -1;
+}
+
+// NOTE: FinalizationRegistry and WeakRef are missing on Safari below 14.1
+if (useFinalizationRegistry) {
+ jsOwnedObjectRegistry = new globalThis.FinalizationRegistry(_jsOwnedObjectFinalized);
+}
+
+export const jsOwnedGcHandleSymbol = Symbol.for("wasm jsOwnedGcHandle");
+export const csOwnedJsHandleSymbol = Symbol.for("wasm cs_owned_jsHandle");
+export const doNotForceDispose = Symbol.for("wasm doNotForceDispose");
+
+
+export function getJSObjectFromJSHandle(jsHandle: JSHandle): any {
+ if (isJsHandle(jsHandle))
+ return _CsOwnedObjectsByJsHandle[jsHandle];
+ if (isJsvHandle(jsHandle))
+ return _CsOwnedObjectsByJsvHandle[0 - jsHandle];
+ return null;
+}
+
+export function getJsHandleFromJSObject(jsObj: any): JSHandle {
+ assertJsInterop();
+ if (jsObj[csOwnedJsHandleSymbol]) {
+ return jsObj[csOwnedJsHandleSymbol];
+ }
+ const jsHandle = _JsHandleFreeList.length ? _JsHandleFreeList.pop() : _nextJSHandle++;
+
+ // note _cs_owned_objects_by_jsHandle is list, not Map. That's why we maintain _jsHandle_free_list.
+ _CsOwnedObjectsByJsHandle[jsHandle] = jsObj;
+
+ if (Object.isExtensible(jsObj)) {
+ const isPrototype = typeof jsObj === "function" && Object.prototype.hasOwnProperty.call(jsObj, "prototype");
+ if (!isPrototype) {
+ jsObj[csOwnedJsHandleSymbol] = jsHandle;
+ }
+ }
+ // else
+ // The consequence of not adding the csOwnedJsHandleSymbol is, that we could have multiple JSHandles and multiple proxy instances.
+ // Throwing exception would prevent us from creating any proxy of non-extensible things.
+ // If we have weakmap instead, we would pay the price of the lookup for all proxies, not just non-extensible objects.
+
+ return jsHandle as JSHandle;
+}
+
+export function registerWithJsvHandle(jsObj: any, jsvHandle: JSHandle) {
+ assertJsInterop();
+ // note _cs_owned_objects_by_jsHandle is list, not Map. That's why we maintain _jsHandle_free_list.
+ _CsOwnedObjectsByJsvHandle[0 - jsvHandle] = jsObj;
+
+ if (Object.isExtensible(jsObj)) {
+ jsObj[csOwnedJsHandleSymbol] = jsvHandle;
+ }
+}
+
+// note: in MT, this is called from locked JSProxyContext. Don't call anything that would need locking.
+export function releaseCSOwnedObject(jsHandle: JSHandle): void {
+ let obj: any;
+ if (isJsHandle(jsHandle)) {
+ obj = _CsOwnedObjectsByJsHandle[jsHandle];
+ _CsOwnedObjectsByJsHandle[jsHandle] = undefined;
+ _JsHandleFreeList.push(jsHandle);
+ } else if (isJsvHandle(jsHandle)) {
+ obj = _CsOwnedObjectsByJsvHandle[0 - jsHandle];
+ _CsOwnedObjectsByJsvHandle[0 - jsHandle] = undefined;
+ // see free list in JSProxyContext.FreeJSVHandle
+ }
+ dotnetAssert.check(obj !== undefined && obj !== null, "ObjectDisposedException");
+ if (typeof obj[csOwnedJsHandleSymbol] !== "undefined") {
+ obj[csOwnedJsHandleSymbol] = undefined;
+ }
+}
+
+export function setupManagedProxy(owner: any, gcHandle: GCHandle): void {
+ assertJsInterop();
+ // keep the gcHandle so that we could easily convert it back to original C# object for roundtrip
+ owner[jsOwnedGcHandleSymbol] = gcHandle;
+
+ // NOTE: this would be leaking C# objects when the browser doesn't support FinalizationRegistry/WeakRef
+ if (useFinalizationRegistry) {
+ // register for GC of the C# object after the JS side is done with the object
+ jsOwnedObjectRegistry.register(owner, gcHandle, owner);
+ }
+
+ // register for instance reuse
+ // NOTE: this would be leaking C# objects when the browser doesn't support FinalizationRegistry/WeakRef
+ const wr = createWeakRef(owner);
+ jsOwnedObjectTable.set(gcHandle, wr);
+}
+
+export function upgradeManagedProxyToStrongRef(owner: any, gcHandle: GCHandle): void {
+ const sr = createStrongRef(owner);
+ if (useFinalizationRegistry) {
+ jsOwnedObjectRegistry.unregister(owner);
+ }
+ jsOwnedObjectTable.set(gcHandle, sr);
+}
+
+export function teardownManagedProxy(owner: any, gcHandle: GCHandle, skipManaged?: boolean): void {
+ assertJsInterop();
+ // The JS object associated with this gcHandle has been collected by the JS GC.
+ // As such, it's not possible for this gcHandle to be invoked by JS anymore, so
+ // we can release the tracking weakref (it's null now, by definition),
+ // and tell the C# side to stop holding a reference to the managed object.
+ // "The FinalizationRegistry callback is called potentially multiple times"
+ if (owner) {
+ gcHandle = owner[jsOwnedGcHandleSymbol];
+ owner[jsOwnedGcHandleSymbol] = GCHandleNull;
+ if (useFinalizationRegistry) {
+ jsOwnedObjectRegistry.unregister(owner);
+ }
+ }
+ if (gcHandle !== GCHandleNull && jsOwnedObjectTable.delete(gcHandle) && !skipManaged) {
+ if (isRuntimeRunning() && !forceDisposeProxiesInProgress) {
+ releaseJsOwnedObjectByGcHandle(gcHandle);
+ }
+ }
+ if (isGcvHandle(gcHandle)) {
+ freeGcvHandle(gcHandle);
+ }
+}
+
+export function assertNotDisposed(result: any): GCHandle {
+ const gcHandle = result[jsOwnedGcHandleSymbol];
+ dotnetAssert.check(gcHandle != GCHandleNull, "ObjectDisposedException");
+ return gcHandle;
+}
+
+function _jsOwnedObjectFinalized(gcHandle: GCHandle): void {
+ if (!isRuntimeRunning()) {
+ // We're shutting down, so don't bother doing anything else.
+ return;
+ }
+ teardownManagedProxy(null, gcHandle);
+}
+
+export function lookupJsOwnedObject(gcHandle: GCHandle): any {
+ if (!gcHandle)
+ return null;
+ const wr = jsOwnedObjectTable.get(gcHandle);
+ if (wr) {
+ // this could be null even before _jsOwnedObjectFinalized was called
+ // TODO: are there race condition consequences ?
+ return wr.deref();
+ }
+ return null;
+}
+
+let forceDisposeProxiesInProgress = false;
+
+// when we arrive here from UninstallWebWorkerInterop, the C# will unregister the handles too.
+// when called from elsewhere, C# side could be unbalanced!!
+export function forceDisposeProxies(disposeMethods: boolean, verbose: boolean): void {
+ let keepSomeCsAlive = false;
+ let keepSomeJsAlive = false;
+ forceDisposeProxiesInProgress = true;
+
+ let doneImports = 0;
+ let doneExports = 0;
+ let doneGCHandles = 0;
+ let doneJSHandles = 0;
+ // dispose all proxies to C# objects
+ const gcHandles = [...jsOwnedObjectTable.keys()];
+ for (const gcHandle of gcHandles) {
+ const wr = jsOwnedObjectTable.get(gcHandle);
+ const obj = wr && wr.deref();
+ if (useFinalizationRegistry && obj) {
+ jsOwnedObjectRegistry.unregister(obj);
+ }
+
+ if (obj) {
+ const keepAlive = typeof obj[doNotForceDispose] === "boolean" && obj[doNotForceDispose];
+ if (verbose) {
+ const proxyDebug = BuildConfiguration === "Debug" ? obj[proxyDebugSymbol] : undefined;
+ if (BuildConfiguration === "Debug" && proxyDebug) {
+ dotnetLogger.warn(`${proxyDebug} ${typeof obj} was still alive. ${keepAlive ? "keeping" : "disposing"}.`);
+ } else {
+ dotnetLogger.warn(`Proxy of C# ${typeof obj} with GCHandle ${gcHandle} was still alive. ${keepAlive ? "keeping" : "disposing"}.`);
+ }
+ }
+ if (!keepAlive) {
+ const promiseControl = dotnetLoaderExports.createPromiseCompletionSource(obj);
+ if (promiseControl) {
+ promiseControl.reject(new Error("WebWorker which is origin of the Task is being terminated."));
+ }
+ if (typeof obj.dispose === "function") {
+ obj.dispose();
+ }
+ if (obj[jsOwnedGcHandleSymbol] === gcHandle) {
+ obj[jsOwnedGcHandleSymbol] = GCHandleNull;
+ }
+ if (!useWeakRef && wr) wr.dispose!();
+ doneGCHandles++;
+ } else {
+ keepSomeCsAlive = true;
+ }
+ }
+ }
+ if (!keepSomeCsAlive) {
+ jsOwnedObjectTable.clear();
+ if (useFinalizationRegistry) {
+ jsOwnedObjectRegistry = new globalThis.FinalizationRegistry(_jsOwnedObjectFinalized);
+ }
+ }
+ const freeJsHandle = (jsHandle: number, list: any[]): void => {
+ const obj = list[jsHandle];
+ const keepAlive = obj && typeof obj[doNotForceDispose] === "boolean" && obj[doNotForceDispose];
+ if (!keepAlive) {
+ list[jsHandle] = undefined;
+ }
+ if (obj) {
+ if (verbose) {
+ const proxyDebug = BuildConfiguration === "Debug" ? obj[proxyDebugSymbol] : undefined;
+ if (BuildConfiguration === "Debug" && proxyDebug) {
+ dotnetLogger.warn(`${proxyDebug} ${typeof obj} was still alive. ${keepAlive ? "keeping" : "disposing"}.`);
+ } else {
+ dotnetLogger.warn(`Proxy of JS ${typeof obj} with JSHandle ${jsHandle} was still alive. ${keepAlive ? "keeping" : "disposing"}.`);
+ }
+ }
+ if (!keepAlive) {
+ const promiseControl = dotnetLoaderExports.createPromiseCompletionSource(obj);
+ if (promiseControl) {
+ promiseControl.reject(new Error("WebWorker which is origin of the Task is being terminated."));
+ }
+ if (typeof obj.dispose === "function") {
+ obj.dispose();
+ }
+ if (obj[csOwnedJsHandleSymbol] === jsHandle) {
+ obj[csOwnedJsHandleSymbol] = undefined;
+ }
+ doneJSHandles++;
+ } else {
+ keepSomeJsAlive = true;
+ }
+ }
+ };
+ // dispose all proxies to JS objects
+ for (let jsHandle = 0; jsHandle < _CsOwnedObjectsByJsHandle.length; jsHandle++) {
+ freeJsHandle(jsHandle, _CsOwnedObjectsByJsHandle);
+ }
+ for (let jsvHandle = 0; jsvHandle < _CsOwnedObjectsByJsvHandle.length; jsvHandle++) {
+ freeJsHandle(jsvHandle, _CsOwnedObjectsByJsvHandle);
+ }
+ if (!keepSomeJsAlive) {
+ _CsOwnedObjectsByJsHandle.length = 1;
+ _CsOwnedObjectsByJsvHandle.length = 1;
+ _nextJSHandle = 1;
+ _JsHandleFreeList.length = 0;
+ }
+ _GcvHandleFreeList.length = 0;
+ nextGcvHandle = -2;
+
+ if (disposeMethods) {
+ // dispose all [JSImport]
+ for (const boundFn of jsImportWrapperByFnHandle) {
+ if (boundFn) {
+ const closure = (boundFn)[importedJsFunctionSymbol];
+ if (closure) {
+ closure.disposed = true;
+ doneImports++;
+ }
+ }
+ }
+ jsImportWrapperByFnHandle.length = 1;
+
+ // dispose all [JSExport]
+ const assemblyExports = [...exportsByAssembly.values()];
+ for (const assemblyExport of assemblyExports) {
+ for (const exportName in assemblyExport) {
+ const boundFn = assemblyExport[exportName];
+ const closure = boundFn[boundCsFunctionSymbol];
+ if (closure) {
+ closure.disposed = true;
+ doneExports++;
+ }
+ }
+ }
+ exportsByAssembly.clear();
+ }
+ dotnetLogger.info(`forceDisposeProxies done: ${doneImports} imports, ${doneExports} exports, ${doneGCHandles} GCHandles, ${doneJSHandles} JSHandles.`);
+}
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/index.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/index.ts
index 199fdaeef77062..ddfca8e9a21043 100644
--- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/index.ts
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/index.ts
@@ -4,7 +4,12 @@
import type { InternalExchange, RuntimeAPI, RuntimeExports, RuntimeExportsTable } from "./types";
import { InternalExchangeIndex } from "../types";
import { dotnetUpdateInternals, dotnetUpdateInternalsSubscriber } from "./cross-module";
-import { ENVIRONMENT_IS_NODE } from "./per-module";
+import { bindJSImportST, invokeJSFunction, invokeJSImportST, setModuleImports } from "./invoke-js";
+import { getAssemblyExports } from "./invoke-cs";
+import { initializeMarshalersToJs, resolveOrRejectPromise } from "./marshal-to-js";
+import { initializeMarshalersToCs } from "./marshal-to-cs";
+import { releaseCSOwnedObject } from "./gc-handles";
+import { cancelPromise } from "./cancelable-promise";
export function dotnetInitializeModule(internals: InternalExchange): void {
if (!Array.isArray(internals)) throw new Error("Expected internals to be an array");
@@ -17,25 +22,28 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
Object.assign(runtimeApi, runtimeApiLocal);
internals[InternalExchangeIndex.RuntimeExportsTable] = runtimeExportsToTable({
+ bindJSImportST,
+ invokeJSImportST,
+ releaseCSOwnedObject,
+ resolveOrRejectPromise,
+ cancelPromise,
+ invokeJSFunction,
});
dotnetUpdateInternals(internals, dotnetUpdateInternalsSubscriber);
+ initializeMarshalersToJs();
+ initializeMarshalersToCs();
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- function runtimeExportsToTable(map:RuntimeExports):RuntimeExportsTable {
+ function runtimeExportsToTable(map: RuntimeExports): RuntimeExportsTable {
// keep in sync with runtimeExportsFromTable()
return [
+ bindJSImportST,
+ invokeJSImportST,
+ releaseCSOwnedObject,
+ resolveOrRejectPromise,
+ cancelPromise,
+ invokeJSFunction,
];
}
}
-
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export async function getAssemblyExports(assemblyName: string): Promise {
- throw new Error("Not implemented");
- return ENVIRONMENT_IS_NODE; // dummy
-}
-
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export function setModuleImports(moduleName: string, moduleImports: any): void {
- throw new Error("Not implemented");
-}
-
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-cs.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-cs.ts
new file mode 100644
index 00000000000000..eaf6af56a661c7
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-cs.ts
@@ -0,0 +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 { bindAssemblyExports } from "./managed-exports";
+import { assertJsInterop } from "./utils";
+
+export const exportsByAssembly: Map = new Map();
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export async function getAssemblyExports(assemblyName: string): Promise {
+ assertJsInterop();
+ const result = exportsByAssembly.get(assemblyName);
+ if (!result) {
+ await bindAssemblyExports(assemblyName);
+ }
+
+ return exportsByAssembly.get(assemblyName) || {};
+}
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-js.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-js.ts
new file mode 100644
index 00000000000000..1e4144d7fbd18e
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-js.ts
@@ -0,0 +1,307 @@
+// 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 { dotnetBrowserUtilsExports, dotnetApi, dotnetAssert, dotnetLogger, VoidPtrNull, Module } from "./cross-module";
+
+import type { BindingClosure, BoundMarshalerToJs, JSFnHandle, JSFunctionSignature, JSHandle, JSMarshalerArguments, VoidPtr, WrappedJSFunction } from "./types";
+import { MarshalerType, MeasuredBlock } from "./types";
+import { getSig, getSignatureArgumentCount, getSignatureFunctionName, getSignatureHandle, getSignatureModuleName, getSignatureType, getSignatureVersion, importedJsFunctionSymbol, isReceiverShouldFree, jsInteropState, boundJsFunctionSymbol } from "./marshal";
+import { assertJsInterop, assertRuntimeRunning, endMeasure, fixupPointer, normalizeException, startMeasure } from "./utils";
+import { bindArgMarshalToJs } from "./marshal-to-js";
+import { getJSObjectFromJSHandle } from "./gc-handles";
+import { bindArgMarshalToCs, marshalExceptionToCs } from "./marshal-to-cs";
+
+export const jsImportWrapperByFnHandle: Function[] = [null];// 0th slot is dummy, main thread we free them on shutdown. On web worker thread we free them when worker is detached.
+export const importedModulesPromises: Map> = new Map();
+export const importedModules: Map> = new Map();
+
+export function setModuleImports(moduleName: string, moduleImports: any): void {
+ importedModules.set(moduleName, moduleImports);
+ dotnetLogger.debug(() => `added module imports '${moduleName}'`);
+}
+
+export function bindJSImportST(signature: JSFunctionSignature): VoidPtr {
+ try {
+ signature = fixupPointer(signature, 0);
+ bindJsImport(signature);
+ return VoidPtrNull;
+ } catch (ex: any) {
+ return dotnetBrowserUtilsExports.stringToUTF16Ptr(normalizeException(ex));
+ }
+}
+
+export function invokeJSImportST(functionHandle: JSFnHandle, args: JSMarshalerArguments) {
+ assertRuntimeRunning();
+ args = fixupPointer(args, 0);
+ const boundFn = jsImportWrapperByFnHandle[functionHandle];
+ dotnetAssert.check(boundFn, () => `Imported function handle expected ${functionHandle}`);
+ boundFn(args);
+}
+
+function bindJsImport(signature: JSFunctionSignature): Function {
+ assertJsInterop();
+ const mark = startMeasure();
+
+ const version = getSignatureVersion(signature);
+ dotnetAssert.check(version === 2, () => `Signature version ${version} mismatch.`);
+
+ const jsFunctionName = getSignatureFunctionName(signature)!;
+ const jsModuleName = getSignatureModuleName(signature)!;
+ const functionHandle = getSignatureHandle(signature);
+
+ dotnetLogger.debug(() => `Binding [JSImport] ${jsFunctionName} from ${jsModuleName} module`);
+
+ const fn = lookupJsImport(jsFunctionName, jsModuleName);
+ const argsCount = getSignatureArgumentCount(signature);
+
+ const argMarshalers: (BoundMarshalerToJs)[] = new Array(argsCount);
+ const argCleanup: (Function | undefined)[] = new Array(argsCount);
+ let hasCleanup = false;
+ for (let index = 0; index < argsCount; index++) {
+ const sig = getSig(signature, index + 2);
+ const marshalerType = getSignatureType(sig);
+ const argMarshaler = bindArgMarshalToJs(sig, marshalerType, index + 2);
+ dotnetAssert.check(argMarshaler, "ERR42: argument marshaler must be resolved");
+ argMarshalers[index] = argMarshaler;
+ if (marshalerType === MarshalerType.Span) {
+ argCleanup[index] = (jsArg: any) => {
+ if (jsArg) {
+ jsArg.dispose();
+ }
+ };
+ hasCleanup = true;
+ }
+ }
+ const resSig = getSig(signature, 1);
+ const resmarshalerType = getSignatureType(resSig);
+ const resConverter = bindArgMarshalToCs(resSig, resmarshalerType, 1);
+
+ const isDiscardNoWait = resmarshalerType == MarshalerType.DiscardNoWait;
+ const isAsync = resmarshalerType == MarshalerType.Task || resmarshalerType == MarshalerType.TaskPreCreated;
+
+ const closure: BindingClosure = {
+ fn,
+ fqn: jsModuleName + ":" + jsFunctionName,
+ argsCount,
+ argMarshalers,
+ resConverter,
+ hasCleanup,
+ argCleanup,
+ isDiscardNoWait,
+ isAsync,
+ isDisposed: false,
+ };
+ let boundFn: WrappedJSFunction;
+ if (isAsync || isDiscardNoWait || hasCleanup) {
+ boundFn = bindFn(closure);
+ } else {
+ if (argsCount == 0 && !resConverter) {
+ boundFn = bind_fn_0V(closure);
+ } else if (argsCount == 1 && !resConverter) {
+ boundFn = bind_fn_1V(closure);
+ } else if (argsCount == 1 && resConverter) {
+ boundFn = bind_fn_1R(closure);
+ } else if (argsCount == 2 && resConverter) {
+ boundFn = bind_fn_2R(closure);
+ } else {
+ boundFn = bindFn(closure);
+ }
+ }
+
+ let wrappedFn: WrappedJSFunction = boundFn;
+
+
+ // this is just to make debugging easier by naming the function in the stack trace.
+ // It's not CSP compliant and possibly not performant, that's why it's only enabled in debug builds
+ // in Release configuration, it would be a trimmed by rollup
+ if (BuildConfiguration === "Debug" && !jsInteropState.cspPolicy) {
+ try {
+ const fname = jsFunctionName.replaceAll(".", "_");
+ const url = `//# sourceURL=https://dotnet/JSImport/${fname}`;
+ const body = `return (function JSImport_${fname}(){ return fn.apply(this, arguments)});`;
+ wrappedFn = new Function("fn", url + "\r\n" + body)(wrappedFn);
+ } catch (ex) {
+ jsInteropState.cspPolicy = true;
+ }
+ }
+
+ (wrappedFn)[importedJsFunctionSymbol] = closure;
+
+ jsImportWrapperByFnHandle[functionHandle] = wrappedFn;
+
+ endMeasure(mark, MeasuredBlock.bindJsFunction, jsFunctionName);
+
+ return wrappedFn;
+}
+
+function bind_fn_0V(closure: BindingClosure) {
+ const fn = closure.fn;
+ const fqn = closure.fqn;
+ (closure) = null;
+ return function boundFn_0V(args: JSMarshalerArguments) {
+ const mark = startMeasure();
+ try {
+ // call user function
+ fn();
+ } catch (ex) {
+ marshalExceptionToCs(args, ex);
+ } finally {
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function bind_fn_1V(closure: BindingClosure) {
+ const fn = closure.fn;
+ const marshaler1 = closure.argMarshalers[0]!;
+ const fqn = closure.fqn;
+ (closure) = null;
+ return function boundFn_1V(args: JSMarshalerArguments) {
+ const mark = startMeasure();
+ try {
+ const arg1 = marshaler1(args);
+ // call user function
+ fn(arg1);
+ } catch (ex) {
+ marshalExceptionToCs(args, ex);
+ } finally {
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function bind_fn_1R(closure: BindingClosure) {
+ const fn = closure.fn;
+ const marshaler1 = closure.argMarshalers[0]!;
+ const resConverter = closure.resConverter!;
+ const fqn = closure.fqn;
+ (closure) = null;
+ return function boundFn_1R(args: JSMarshalerArguments) {
+ const mark = startMeasure();
+ try {
+ const arg1 = marshaler1(args);
+ // call user function
+ const jsResult = fn(arg1);
+ resConverter(args, jsResult);
+ } catch (ex) {
+ marshalExceptionToCs(args, ex);
+ } finally {
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function bind_fn_2R(closure: BindingClosure) {
+ const fn = closure.fn;
+ const marshaler1 = closure.argMarshalers[0]!;
+ const marshaler2 = closure.argMarshalers[1]!;
+ const resConverter = closure.resConverter!;
+ const fqn = closure.fqn;
+ (closure) = null;
+ return function boundFn_2R(args: JSMarshalerArguments) {
+ const mark = startMeasure();
+ try {
+ const arg1 = marshaler1(args);
+ const arg2 = marshaler2(args);
+ // call user function
+ const jsResult = fn(arg1, arg2);
+ resConverter(args, jsResult);
+ } catch (ex) {
+ marshalExceptionToCs(args, ex);
+ } finally {
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function bindFn(closure: BindingClosure) {
+ const argsCount = closure.argsCount;
+ const argMarshalers = closure.argMarshalers;
+ const resConverter = closure.resConverter;
+ const argCleanup = closure.argCleanup;
+ const hasCleanup = closure.hasCleanup;
+ const fn = closure.fn;
+ const fqn = closure.fqn;
+ (closure) = null;
+ return function boundFn(args: JSMarshalerArguments) {
+ const receiverShouldFree = isReceiverShouldFree(args);
+ const mark = startMeasure();
+ try {
+ const jsArgs = new Array(argsCount);
+ for (let index = 0; index < argsCount; index++) {
+ const marshaler = argMarshalers[index]!;
+ const jsArg = marshaler(args);
+ jsArgs[index] = jsArg;
+ }
+
+ // call user function
+ const jsResult = fn(...jsArgs);
+
+ if (resConverter) {
+ resConverter(args, jsResult);
+ }
+
+ if (hasCleanup) {
+ for (let index = 0; index < argsCount; index++) {
+ const cleanup = argCleanup[index];
+ if (cleanup) {
+ cleanup(jsArgs[index]);
+ }
+ }
+ }
+ } catch (ex) {
+ marshalExceptionToCs(args, ex);
+ } finally {
+ if (receiverShouldFree) {
+ Module._free(args as any);
+ }
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function lookupJsImport(functionName: string, jsModuleName: string | null): Function {
+ dotnetAssert.check(functionName && typeof functionName === "string", "functionName must be string");
+
+ let scope: any = {};
+ const parts = functionName.split(".");
+ if (jsModuleName) {
+ scope = importedModules.get(jsModuleName);
+ dotnetAssert.check(scope, () => `ES6 module ${jsModuleName} was not imported yet, please call JSHost.ImportAsync() first in order to invoke ${functionName}.`);
+ } else if (parts[0] === "INTERNAL") {
+ scope = dotnetApi.INTERNAL;
+ parts.shift();
+ } else if (parts[0] === "globalThis") {
+ scope = globalThis;
+ parts.shift();
+ }
+
+ for (let i = 0; i < parts.length - 1; i++) {
+ const part = parts[i];
+ const newscope = scope[part];
+ if (!newscope) {
+ throw new Error(`${part} not found while looking up ${functionName}`);
+ }
+ scope = newscope;
+ }
+
+ const fname = parts[parts.length - 1];
+ const fn = scope[fname];
+
+ if (typeof (fn) !== "function") {
+ throw new Error(`${functionName} must be a Function but was ${typeof fn}`);
+ }
+
+ // if the function was already bound to some object it would stay bound to original object. That's good.
+ return fn.bind(scope);
+}
+
+export function invokeJSFunction(functionJSHandle: JSHandle, args: JSMarshalerArguments): void {
+ assertRuntimeRunning();
+ const boundFn = getJSObjectFromJSHandle(functionJSHandle);
+ dotnetAssert.check(boundFn && typeof (boundFn) === "function" && boundFn[boundJsFunctionSymbol], () => `Bound function handle expected ${functionJSHandle}`);
+ args = fixupPointer(args, 0);
+ boundFn(args);
+}
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/managed-exports.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/managed-exports.ts
new file mode 100644
index 00000000000000..9b7ef2ae7de91c
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/managed-exports.ts
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { dotnetInteropJSExports, Module } from "./cross-module";
+import { allocStackFrame, getArg, setArgType, setGcHandle as setGcHandle } from "./marshal";
+import { marshalStringToJs } from "./marshal-to-js";
+import { MarshalerType, type GCHandle, type MarshalerToCs, type MarshalerToJs } from "./types";
+import { assertRuntimeRunning, isRuntimeRunning } from "./utils";
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function releaseJsOwnedObjectByGcHandle(gcHandle: GCHandle) {
+ // TODO-WASM
+}
+
+export function getManagedStackTrace(exceptionGCHandle: GCHandle): string {
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 3;
+ const args = allocStackFrame(size);
+
+ const arg1 = getArg(args, 2);
+ setArgType(arg1, MarshalerType.Exception);
+ setGcHandle(arg1, exceptionGCHandle);
+
+ dotnetInteropJSExports.SystemInteropJS_GetManagedStackTrace(args);
+
+ const res = getArg(args, 1);
+ return marshalStringToJs(res)!;
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function callDelegate(callbackGcHandle: GCHandle, arg1Js: any, arg2Js: any, arg3Js: any, resConverter?: MarshalerToJs, arg1Converter?: MarshalerToCs, arg2Converter?: MarshalerToCs, arg3Converter?: MarshalerToCs) {
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function completeTask(holder_gc_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs) {
+
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function bindAssemblyExports(assemblyName: string): Promise {
+ // TODO-WASM
+ return Promise.resolve();
+}
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal-to-cs.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal-to-cs.ts
new file mode 100644
index 00000000000000..291bb9093a09d7
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal-to-cs.ts
@@ -0,0 +1,513 @@
+// 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 { dotnetBrowserUtilsExports, dotnetApi, dotnetAssert, Module } from "./cross-module";
+
+import type { BoundMarshalerToCs, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, TypedArray } from "./types";
+import { JavaScriptMarshalerArgSize, MarshalerType } from "./types";
+import { arrayElementSize, boundJsFunctionSymbol, getArg, getSignatureArg1Type, getSignatureArg2Type, getSignatureArg3Type, getSignatureResType, jsToCsMarshalers, jsInteropState, proxyDebugSymbol, setArgBool, setArgDate, setArgElementType, setArgF32, setArgF64, setArgI16, setArgI32, setArgI52, setArgI64Big, setArgIntptr, setArgLength, setArgProxyContext, setArgType, setArgU16, setArgU8, setGcHandle, setJsHandle, getArgType, getArgGcHandle } from "./marshal";
+import { getMarshalerToJsByType } from "./marshal-to-js";
+import { assertNotDisposed, csOwnedJsHandleSymbol, jsOwnedGcHandleSymbol, getJsHandleFromJSObject, allocGcvHandle, setupManagedProxy } from "./gc-handles";
+import { fixupPointer } from "./utils";
+import { ArraySegment, ManagedError, ManagedObject, MemoryViewType, Span } from "./marshaled-types";
+import { isThenable, PromiseHolder } from "./cancelable-promise";
+
+export const jsinteropDoc = "For more information see https://aka.ms/dotnet-wasm-jsinterop";
+
+export function initializeMarshalersToCs(): void {
+ if (jsToCsMarshalers.size == 0) {
+ jsToCsMarshalers.set(MarshalerType.Array, marshalArrayToCs);
+ jsToCsMarshalers.set(MarshalerType.Span, _marshalSpanToCs);
+ jsToCsMarshalers.set(MarshalerType.ArraySegment, _marshalArraySegmentToCs);
+ jsToCsMarshalers.set(MarshalerType.Boolean, marshalBoolToCs);
+ jsToCsMarshalers.set(MarshalerType.Byte, _marshalByteToCs);
+ jsToCsMarshalers.set(MarshalerType.Char, _marshalCharToCs);
+ jsToCsMarshalers.set(MarshalerType.Int16, _marshalInt16ToCs);
+ jsToCsMarshalers.set(MarshalerType.Int32, _marshalInt32ToCs);
+ jsToCsMarshalers.set(MarshalerType.Int52, _marshalInt52ToCs);
+ jsToCsMarshalers.set(MarshalerType.BigInt64, _marshalBigint64ToCs);
+ jsToCsMarshalers.set(MarshalerType.Double, _marshalDoubleToCs);
+ jsToCsMarshalers.set(MarshalerType.Single, _marshalFloatToCs);
+ jsToCsMarshalers.set(MarshalerType.IntPtr, marshalIntptrToCs);
+ jsToCsMarshalers.set(MarshalerType.DateTime, _marshalDateTimeToCs);
+ jsToCsMarshalers.set(MarshalerType.DateTimeOffset, _marshalDateTimeOffsetToCs);
+ jsToCsMarshalers.set(MarshalerType.String, marshalStringToCs);
+ jsToCsMarshalers.set(MarshalerType.Exception, marshalExceptionToCs);
+ jsToCsMarshalers.set(MarshalerType.JSException, marshalExceptionToCs);
+ jsToCsMarshalers.set(MarshalerType.JSObject, marshalJsObjectToCs);
+ jsToCsMarshalers.set(MarshalerType.Object, marshalCsObjectToCs);
+ jsToCsMarshalers.set(MarshalerType.Task, marshalTaskToCs);
+ jsToCsMarshalers.set(MarshalerType.TaskResolved, marshalTaskToCs);
+ jsToCsMarshalers.set(MarshalerType.TaskRejected, marshalTaskToCs);
+ jsToCsMarshalers.set(MarshalerType.Action, _marshalFunctionToCs);
+ jsToCsMarshalers.set(MarshalerType.Function, _marshalFunctionToCs);
+ jsToCsMarshalers.set(MarshalerType.None, _marshalNullToCs);// also void
+ jsToCsMarshalers.set(MarshalerType.Discard, _marshalNullToCs);// also void
+ jsToCsMarshalers.set(MarshalerType.Void, _marshalNullToCs);// also void
+ jsToCsMarshalers.set(MarshalerType.DiscardNoWait, _marshalNullToCs);// also void
+ }
+}
+
+export function bindArgMarshalToCs(sig: JSMarshalerType, marshalerType: MarshalerType, index: number): BoundMarshalerToCs | undefined {
+ if (marshalerType === MarshalerType.None || marshalerType === MarshalerType.Void || marshalerType === MarshalerType.Discard || marshalerType === MarshalerType.DiscardNoWait) {
+ return undefined;
+ }
+ let resMarshaler: MarshalerToCs | undefined = undefined;
+ let arg1Marshaler: MarshalerToJs | undefined = undefined;
+ let arg2Marshaler: MarshalerToJs | undefined = undefined;
+ let arg3Marshaler: MarshalerToJs | undefined = undefined;
+
+ arg1Marshaler = getMarshalerToJsByType(getSignatureArg1Type(sig));
+ arg2Marshaler = getMarshalerToJsByType(getSignatureArg2Type(sig));
+ arg3Marshaler = getMarshalerToJsByType(getSignatureArg3Type(sig));
+ const marshalerTypeRes = getSignatureResType(sig);
+ resMarshaler = getMarshalerToCsByType(marshalerTypeRes);
+ if (marshalerType === MarshalerType.Nullable) {
+ // nullable has nested type information, it's stored in res slot of the signature. The marshaler is the same as for non-nullable primitive type.
+ marshalerType = marshalerTypeRes;
+ }
+ const converter = getMarshalerToCsByType(marshalerType)!;
+ const elementType = getSignatureArg1Type(sig);
+
+ const argOffset = index * JavaScriptMarshalerArgSize;
+ return (args: JSMarshalerArguments, value: any) => {
+ converter(args + argOffset, value, elementType, resMarshaler, arg1Marshaler, arg2Marshaler, arg3Marshaler);
+ };
+}
+
+export function getMarshalerToCsByType(marshalerType: MarshalerType): MarshalerToCs | undefined {
+ if (marshalerType === MarshalerType.None || marshalerType === MarshalerType.Void) {
+ return undefined;
+ }
+ const converter = jsToCsMarshalers.get(marshalerType);
+ dotnetAssert.check(converter && typeof converter === "function", () => `ERR30: Unknown converter for type ${marshalerType}`);
+ return converter;
+}
+
+export function marshalBoolToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.Boolean);
+ setArgBool(arg, value);
+ }
+}
+
+function _marshalByteToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.Byte);
+ setArgU8(arg, value);
+ }
+}
+
+function _marshalCharToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.Char);
+ setArgU16(arg, value);
+ }
+}
+
+function _marshalInt16ToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.Int16);
+ setArgI16(arg, value);
+ }
+}
+
+function _marshalInt32ToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.Int32);
+ setArgI32(arg, value);
+ }
+}
+
+function _marshalInt52ToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.Int52);
+ setArgI52(arg, value);
+ }
+}
+
+function _marshalBigint64ToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.BigInt64);
+ setArgI64Big(arg, value);
+ }
+}
+
+function _marshalDoubleToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.Double);
+ setArgF64(arg, value);
+ }
+}
+
+function _marshalFloatToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.Single);
+ setArgF32(arg, value);
+ }
+}
+
+export function marshalIntptrToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.IntPtr);
+ setArgIntptr(arg, value);
+ }
+}
+
+function _marshalDateTimeToCs(arg: JSMarshalerArgument, value: Date): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ dotnetAssert.check(value instanceof Date, "Value is not a Date");
+ setArgType(arg, MarshalerType.DateTime);
+ setArgDate(arg, value);
+ }
+}
+
+function _marshalDateTimeOffsetToCs(arg: JSMarshalerArgument, value: Date): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ dotnetAssert.check(value instanceof Date, "Value is not a Date");
+ setArgType(arg, MarshalerType.DateTimeOffset);
+ setArgDate(arg, value);
+ }
+}
+
+export function marshalStringToCs(arg: JSMarshalerArgument, value: string) {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ setArgType(arg, MarshalerType.String);
+ dotnetAssert.check(typeof value === "string", "Value is not a String");
+ _marshalStringToCsImpl(arg, value);
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function _marshalStringToCsImpl(arg: JSMarshalerArgument, value: string) {
+ const bufferLen = value.length * 2;
+ const buffer = Module._malloc(bufferLen);// together with Marshal.FreeHGlobal
+ dotnetBrowserUtilsExports.stringToUTF16(buffer as any, buffer as any + bufferLen, value);
+ setArgIntptr(arg, buffer);
+ setArgLength(arg, value.length);
+}
+
+function _marshalNullToCs(arg: JSMarshalerArgument) {
+ setArgType(arg, MarshalerType.None);
+}
+
+function _marshalFunctionToCs(arg: JSMarshalerArgument, value: Function, _?: MarshalerType, resConverter?: MarshalerToCs, arg1Converter?: MarshalerToJs, arg2Converter?: MarshalerToJs, arg3Converter?: MarshalerToJs): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ return;
+ }
+ dotnetAssert.check(value && value instanceof Function, "Value is not a Function");
+
+ // TODO: we could try to cache value -> existing JSHandle
+ const wrapper: any = function delegateWrapper(args: JSMarshalerArguments) {
+ const exc = getArg(args, 0);
+ const res = getArg(args, 1);
+ const arg1 = getArg(args, 2);
+ const arg2 = getArg(args, 3);
+ const arg3 = getArg(args, 4);
+
+ const previousPendingSynchronousCall = jsInteropState.isPendingSynchronousCall;
+ try {
+ dotnetAssert.check(!wrapper.isDisposed, "Function is disposed and should not be invoked anymore.");
+
+ let arg1Js: any = undefined;
+ let arg2Js: any = undefined;
+ let arg3Js: any = undefined;
+ if (arg1Converter) {
+ arg1Js = arg1Converter(arg1);
+ }
+ if (arg2Converter) {
+ arg2Js = arg2Converter(arg2);
+ }
+ if (arg3Converter) {
+ arg3Js = arg3Converter(arg3);
+ }
+ jsInteropState.isPendingSynchronousCall = true; // this is always synchronous call for now
+ const resJs = value(arg1Js, arg2Js, arg3Js);
+ if (resConverter) {
+ resConverter(res, resJs);
+ }
+
+ } catch (ex) {
+ marshalExceptionToCs(exc, ex);
+ } finally {
+ jsInteropState.isPendingSynchronousCall = previousPendingSynchronousCall;
+ }
+ };
+
+ wrapper[boundJsFunctionSymbol] = true;
+ wrapper.isDisposed = false;
+ wrapper.dispose = () => {
+ wrapper.isDisposed = true;
+ };
+ const boundFunctionHandle = getJsHandleFromJSObject(wrapper)!;
+ if (BuildConfiguration === "Debug") {
+ wrapper[proxyDebugSymbol] = `Proxy of JS Function with JSHandle ${boundFunctionHandle}: ${value.toString()}`;
+ }
+ setJsHandle(arg, boundFunctionHandle);
+ setArgType(arg, MarshalerType.Function);//TODO or action ?
+}
+
+export function marshalTaskToCs(arg: JSMarshalerArgument, value: Promise, _?: MarshalerType, resConverter?: MarshalerToCs) {
+ const handleIsPreallocated = getArgType(arg) == MarshalerType.TaskPreCreated;
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ }
+ dotnetAssert.check(isThenable(value), "Value is not a Promise");
+
+ const gcHandle = handleIsPreallocated ? getArgGcHandle(arg) : allocGcvHandle();
+ if (!handleIsPreallocated) {
+ setGcHandle(arg, gcHandle);
+ setArgType(arg, MarshalerType.Task);
+ }
+
+ const holder = new PromiseHolder(value, gcHandle, resConverter);
+ setupManagedProxy(holder, gcHandle);
+
+ if (BuildConfiguration === "Debug") {
+ (holder as any)[proxyDebugSymbol] = `PromiseHolder with GCHandle ${gcHandle}`;
+ }
+
+ value.then(data => holder.resolve(data), reason => holder.reject(reason));
+
+ throw new Error("TODO-WASM");
+}
+
+export function marshalExceptionToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else if (value instanceof ManagedError) {
+ setArgType(arg, MarshalerType.Exception);
+ // this is managed exception round-trip
+ const gcHandle = assertNotDisposed(value);
+ setGcHandle(arg, gcHandle);
+ } else {
+ dotnetAssert.check(typeof value === "object" || typeof value === "string", () => `Value is not an Error ${typeof value}`);
+ setArgType(arg, MarshalerType.JSException);
+ const message = value.toString();
+ _marshalStringToCsImpl(arg, message);
+ const knownJsHandle = value[csOwnedJsHandleSymbol];
+ if (knownJsHandle) {
+ setJsHandle(arg, knownJsHandle);
+ } else {
+ const jsHandle = getJsHandleFromJSObject(value)!;
+ if (BuildConfiguration === "Debug" && Object.isExtensible(value)) {
+ value[proxyDebugSymbol] = `JS Error with JSHandle ${jsHandle}`;
+ }
+ setJsHandle(arg, jsHandle);
+ }
+ }
+}
+
+export function marshalJsObjectToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === undefined || value === null) {
+ setArgType(arg, MarshalerType.None);
+ setArgProxyContext(arg);
+ } else {
+ // if value was ManagedObject, it would be double proxied, but the C# signature requires that
+ dotnetAssert.check(value[jsOwnedGcHandleSymbol] === undefined, () => `JSObject proxy of ManagedObject proxy is not supported. ${jsinteropDoc}`);
+ dotnetAssert.check(typeof value === "function" || typeof value === "object", () => `JSObject proxy of ${typeof value} is not supported`);
+
+ setArgType(arg, MarshalerType.JSObject);
+ const jsHandle = getJsHandleFromJSObject(value)!;
+ if (BuildConfiguration === "Debug" && Object.isExtensible(value)) {
+ value[proxyDebugSymbol] = `JS Object with JSHandle ${jsHandle}`;
+ }
+ setJsHandle(arg, jsHandle);
+ }
+}
+
+export function marshalCsObjectToCs(arg: JSMarshalerArgument, value: any): void {
+ if (value === undefined || value === null) {
+ setArgType(arg, MarshalerType.None);
+ setArgProxyContext(arg);
+ } else {
+ const gcHandle = value[jsOwnedGcHandleSymbol];
+ const jsType = typeof (value);
+ if (gcHandle === undefined) {
+ if (jsType === "string" || jsType === "symbol") {
+ setArgType(arg, MarshalerType.String);
+ _marshalStringToCsImpl(arg, value);
+ } else if (jsType === "number") {
+ setArgType(arg, MarshalerType.Double);
+ setArgF64(arg, value);
+ } else if (jsType === "bigint") {
+ // we do it because not all bigint values could fit into Int64
+ throw new Error("NotImplementedException: bigint");
+ } else if (jsType === "boolean") {
+ setArgType(arg, MarshalerType.Boolean);
+ setArgBool(arg, value);
+ } else if (value instanceof Date) {
+ setArgType(arg, MarshalerType.DateTime);
+ setArgDate(arg, value);
+ } else if (value instanceof Error) {
+ marshalExceptionToCs(arg, value);
+ } else if (value instanceof Uint8Array) {
+ marshalArrayToCsImpl(arg, value, MarshalerType.Byte);
+ } else if (value instanceof Float64Array) {
+ marshalArrayToCsImpl(arg, value, MarshalerType.Double);
+ } else if (value instanceof Int32Array) {
+ marshalArrayToCsImpl(arg, value, MarshalerType.Int32);
+ } else if (Array.isArray(value)) {
+ marshalArrayToCsImpl(arg, value, MarshalerType.Object);
+ } else if (value instanceof Int16Array
+ || value instanceof Int8Array
+ || value instanceof Uint8ClampedArray
+ || value instanceof Uint16Array
+ || value instanceof Uint32Array
+ || value instanceof Float32Array
+ ) {
+ throw new Error("NotImplementedException: TypedArray");
+ } else if (isThenable(value)) {
+ marshalTaskToCs(arg, value);
+ } else if (value instanceof Span) {
+ throw new Error("NotImplementedException: Span");
+ } else if (jsType == "object") {
+ const jsHandle = getJsHandleFromJSObject(value);
+ setArgType(arg, MarshalerType.JSObject);
+ if (BuildConfiguration === "Debug" && Object.isExtensible(value)) {
+ value[proxyDebugSymbol] = `JS Object with JSHandle ${jsHandle}`;
+ }
+ setJsHandle(arg, jsHandle);
+ } else {
+ throw new Error(`JSObject proxy is not supported for ${jsType} ${value}`);
+ }
+ } else {
+ assertNotDisposed(value);
+ if (value instanceof ArraySegment) {
+ throw new Error("NotImplementedException: ArraySegment. " + jsinteropDoc);
+ } else if (value instanceof ManagedError) {
+ setArgType(arg, MarshalerType.Exception);
+ setGcHandle(arg, gcHandle);
+ } else if (value instanceof ManagedObject) {
+ setArgType(arg, MarshalerType.Object);
+ setGcHandle(arg, gcHandle);
+ } else {
+ throw new Error("NotImplementedException " + jsType + ". " + jsinteropDoc);
+ }
+ }
+ }
+}
+
+export function marshalArrayToCs(arg: JSMarshalerArgument, value: Array | TypedArray | undefined | null, elementType?: MarshalerType): void {
+ dotnetAssert.check(!!elementType, "Expected valid elementType parameter");
+ marshalArrayToCsImpl(arg, value, elementType);
+}
+
+export function marshalArrayToCsImpl(arg: JSMarshalerArgument, value: Array | TypedArray | undefined | null, elementType: MarshalerType): void {
+ if (value === null || value === undefined) {
+ setArgType(arg, MarshalerType.None);
+ } else {
+ const elementSize = arrayElementSize(elementType);
+ dotnetAssert.check(elementSize != -1, () => `Element type ${elementType} not supported`);
+ const length = value.length;
+ const bufferLength = elementSize * length;
+ const bufferPtr = Module._malloc(bufferLength) as any;
+ if (elementType == MarshalerType.String) {
+ dotnetAssert.check(Array.isArray(value), "Value is not an Array");
+ dotnetBrowserUtilsExports.zeroRegion(bufferPtr, bufferLength);
+ for (let index = 0; index < length; index++) {
+ const elementArg = getArg(bufferPtr, index);
+ marshalStringToCs(elementArg, value[index]);
+ }
+ } else if (elementType == MarshalerType.Object) {
+ dotnetAssert.check(Array.isArray(value), "Value is not an Array");
+ dotnetBrowserUtilsExports.zeroRegion(bufferPtr, bufferLength);
+ for (let index = 0; index < length; index++) {
+ const elementArg = getArg(bufferPtr, index);
+ marshalCsObjectToCs(elementArg, value[index]);
+ }
+ } else if (elementType == MarshalerType.JSObject) {
+ dotnetAssert.check(Array.isArray(value), "Value is not an Array");
+ dotnetBrowserUtilsExports.zeroRegion(bufferPtr, bufferLength);
+ for (let index = 0; index < length; index++) {
+ const elementArg = getArg(bufferPtr, index);
+ marshalJsObjectToCs(elementArg, value[index]);
+ }
+ } else if (elementType == MarshalerType.Byte) {
+ dotnetAssert.check(Array.isArray(value) || value instanceof Uint8Array, "Value is not an Array or Uint8Array");
+ const bufferOffset = fixupPointer(bufferPtr, 0);
+ const targetView = dotnetApi.localHeapViewU8().subarray(bufferOffset, bufferOffset + length);
+ targetView.set(value);
+ } else if (elementType == MarshalerType.Int32) {
+ dotnetAssert.check(Array.isArray(value) || value instanceof Int32Array, "Value is not an Array or Int32Array");
+ const bufferOffset = fixupPointer(bufferPtr, 2);
+ const targetView = dotnetApi.localHeapViewI32().subarray(bufferOffset, bufferOffset + length);
+ targetView.set(value);
+ } else if (elementType == MarshalerType.Double) {
+ dotnetAssert.check(Array.isArray(value) || value instanceof Float64Array, "Value is not an Array or Float64Array");
+ const bufferOffset = fixupPointer(bufferPtr, 3);
+ const targetView = dotnetApi.localHeapViewF64().subarray(bufferOffset, bufferOffset + length);
+ targetView.set(value);
+ } else {
+ throw new Error("not implemented");
+ }
+ setArgIntptr(arg, bufferPtr);
+ setArgType(arg, MarshalerType.Array);
+ setArgElementType(arg, elementType);
+ setArgLength(arg, value.length);
+ }
+}
+
+function _marshalSpanToCs(arg: JSMarshalerArgument, value: Span, elementType?: MarshalerType): void {
+ dotnetAssert.check(!!elementType, "Expected valid elementType parameter");
+ dotnetAssert.check(!value.isDisposed, "ObjectDisposedException");
+ checkViewType(elementType, value._viewType);
+
+ setArgType(arg, MarshalerType.Span);
+ setArgIntptr(arg, value._pointer);
+ setArgLength(arg, value.length);
+}
+
+// this only supports round-trip
+function _marshalArraySegmentToCs(arg: JSMarshalerArgument, value: ArraySegment, elementType?: MarshalerType): void {
+ dotnetAssert.check(!!elementType, "Expected valid elementType parameter");
+ const gcHandle = assertNotDisposed(value);
+ dotnetAssert.check(gcHandle, "Only roundtrip of ArraySegment instance created by C#");
+ checkViewType(elementType, value._viewType);
+ setArgType(arg, MarshalerType.ArraySegment);
+ setArgIntptr(arg, value._pointer);
+ setArgLength(arg, value.length);
+ setGcHandle(arg, gcHandle);
+}
+
+function checkViewType(elementType: MarshalerType, viewType: MemoryViewType) {
+ if (elementType == MarshalerType.Byte) {
+ dotnetAssert.check(MemoryViewType.Byte == viewType, "Expected MemoryViewType.Byte");
+ } else if (elementType == MarshalerType.Int32) {
+ dotnetAssert.check(MemoryViewType.Int32 == viewType, "Expected MemoryViewType.Int32");
+ } else if (elementType == MarshalerType.Double) {
+ dotnetAssert.check(MemoryViewType.Double == viewType, "Expected MemoryViewType.Double");
+ } else {
+ throw new Error(`NotImplementedException ${elementType} `);
+ }
+}
+
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal-to-js.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal-to-js.ts
new file mode 100644
index 00000000000000..99a9e69386ef97
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal-to-js.ts
@@ -0,0 +1,554 @@
+// 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 { dotnetBrowserUtilsExports, dotnetLoaderExports, dotnetApi, dotnetAssert, dotnetLogger, Module } from "./cross-module";
+
+import type { BoundMarshalerToJs, JSHandle, JSMarshalerArgument, JSMarshalerArguments, JSMarshalerType, MarshalerToCs, MarshalerToJs, TypedArray } from "./types";
+import { GCHandleNull, JavaScriptMarshalerArgSize, MarshalerType } from "./types";
+import { arrayElementSize, csToJsMarshalers, getArg, getArgBool, getArgDate, getArgElementType, getArgF32, getArgF64, getArgGcHandle, getArgI16, getArgI32, getArgI52, getArgI64Big, getArgIntptr, getArgJsHandle, getArgLength, getArgType, getArgU16, getArgU8, getSignatureArg1Type, getSignatureArg2Type, getSignatureArg3Type, getSignatureResType, proxyDebugSymbol, setArgType, setJsHandle } from "./marshal";
+import { getMarshalerToCsByType, jsinteropDoc, marshalExceptionToCs } from "./marshal-to-cs";
+import { lookupJsOwnedObject, getJsHandleFromJSObject, getJSObjectFromJSHandle, registerWithJsvHandle, releaseCSOwnedObject, setupManagedProxy, teardownManagedProxy } from "./gc-handles";
+import { assertRuntimeRunning, fixupPointer, isRuntimeRunning } from "./utils";
+import { ArraySegment, ManagedError, ManagedObject, MemoryViewType, Span } from "./marshaled-types";
+import { callDelegate } from "./managed-exports";
+
+export function initializeMarshalersToJs(): void {
+ if (csToJsMarshalers.size == 0) {
+ csToJsMarshalers.set(MarshalerType.Array, _marshalArrayToJs);
+ csToJsMarshalers.set(MarshalerType.Span, _marshalSpanToJs);
+ csToJsMarshalers.set(MarshalerType.ArraySegment, _marshalArraySegmentToJs);
+ csToJsMarshalers.set(MarshalerType.Boolean, _marshalBoolToJs);
+ csToJsMarshalers.set(MarshalerType.Byte, _marshalByteToJs);
+ csToJsMarshalers.set(MarshalerType.Char, _marshalCharToJs);
+ csToJsMarshalers.set(MarshalerType.Int16, _marshalInt16ToJs);
+ csToJsMarshalers.set(MarshalerType.Int32, marshalInt32ToJs);
+ csToJsMarshalers.set(MarshalerType.Int52, _marshalInt52ToJs);
+ csToJsMarshalers.set(MarshalerType.BigInt64, _marshalBigint64ToJs);
+ csToJsMarshalers.set(MarshalerType.Single, _marshalFloatToJs);
+ csToJsMarshalers.set(MarshalerType.IntPtr, _marshalIntptrToJs);
+ csToJsMarshalers.set(MarshalerType.Double, _marshalDoubleToJs);
+ csToJsMarshalers.set(MarshalerType.String, marshalStringToJs);
+ csToJsMarshalers.set(MarshalerType.Exception, marshalExceptionToJs);
+ csToJsMarshalers.set(MarshalerType.JSException, marshalExceptionToJs);
+ csToJsMarshalers.set(MarshalerType.JSObject, _marshalJsObjectToJs);
+ csToJsMarshalers.set(MarshalerType.Object, _marshalCsObjectToJs);
+ csToJsMarshalers.set(MarshalerType.DateTime, _marshalDatetimeToJs);
+ csToJsMarshalers.set(MarshalerType.DateTimeOffset, _marshalDatetimeToJs);
+ csToJsMarshalers.set(MarshalerType.Task, marshalTaskToJs);
+ csToJsMarshalers.set(MarshalerType.TaskRejected, marshalTaskToJs);
+ csToJsMarshalers.set(MarshalerType.TaskResolved, marshalTaskToJs);
+ csToJsMarshalers.set(MarshalerType.TaskPreCreated, beginMarshalTaskToJs);
+ csToJsMarshalers.set(MarshalerType.Action, _marshalDelegateToJs);
+ csToJsMarshalers.set(MarshalerType.Function, _marshalDelegateToJs);
+ csToJsMarshalers.set(MarshalerType.None, _marshalNullToJs);
+ csToJsMarshalers.set(MarshalerType.Void, _marshalNullToJs);
+ csToJsMarshalers.set(MarshalerType.Discard, _marshalNullToJs);
+ csToJsMarshalers.set(MarshalerType.DiscardNoWait, _marshalNullToJs);
+ }
+}
+
+export function bindArgMarshalToJs(sig: JSMarshalerType, marshalerType: MarshalerType, index: number): BoundMarshalerToJs | undefined {
+ if (marshalerType === MarshalerType.None || marshalerType === MarshalerType.Void || marshalerType === MarshalerType.Discard || marshalerType === MarshalerType.DiscardNoWait) {
+ return undefined;
+ }
+
+ let resMarshaler: MarshalerToJs | undefined = undefined;
+ let arg1Marshaler: MarshalerToCs | undefined = undefined;
+ let arg2Marshaler: MarshalerToCs | undefined = undefined;
+ let arg3Marshaler: MarshalerToCs | undefined = undefined;
+
+ arg1Marshaler = getMarshalerToCsByType(getSignatureArg1Type(sig));
+ arg2Marshaler = getMarshalerToCsByType(getSignatureArg2Type(sig));
+ arg3Marshaler = getMarshalerToCsByType(getSignatureArg3Type(sig));
+ const marshalerTypeRes = getSignatureResType(sig);
+ resMarshaler = getMarshalerToJsByType(marshalerTypeRes);
+ if (marshalerType === MarshalerType.Nullable) {
+ // nullable has nested type information, it's stored in res slot of the signature. The marshaler is the same as for non-nullable primitive type.
+ marshalerType = marshalerTypeRes;
+ }
+ const converter = getMarshalerToJsByType(marshalerType)!;
+ const elementType = getSignatureArg1Type(sig);
+
+ const argOffset = index * JavaScriptMarshalerArgSize;
+ return (args: JSMarshalerArguments) => {
+ return converter(args + argOffset, elementType, resMarshaler, arg1Marshaler, arg2Marshaler, arg3Marshaler);
+ };
+}
+
+export function getMarshalerToJsByType(marshalerType: MarshalerType): MarshalerToJs | undefined {
+ if (marshalerType === MarshalerType.None || marshalerType === MarshalerType.Void) {
+ return undefined;
+ }
+ const converter = csToJsMarshalers.get(marshalerType);
+ dotnetAssert.check(converter && typeof converter === "function", () => `ERR41: Unknown converter for type ${marshalerType}. ${jsinteropDoc}`);
+ return converter;
+}
+
+function _marshalBoolToJs(arg: JSMarshalerArgument): boolean | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return getArgBool(arg);
+}
+
+function _marshalByteToJs(arg: JSMarshalerArgument): number | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return getArgU8(arg);
+}
+
+function _marshalCharToJs(arg: JSMarshalerArgument): number | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return getArgU16(arg);
+}
+
+function _marshalInt16ToJs(arg: JSMarshalerArgument): number | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return getArgI16(arg);
+}
+
+export function marshalInt32ToJs(arg: JSMarshalerArgument): number | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return getArgI32(arg);
+}
+
+function _marshalInt52ToJs(arg: JSMarshalerArgument): number | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return getArgI52(arg);
+}
+
+function _marshalBigint64ToJs(arg: JSMarshalerArgument): bigint | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return getArgI64Big(arg);
+}
+
+function _marshalFloatToJs(arg: JSMarshalerArgument): number | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return getArgF32(arg);
+}
+
+function _marshalDoubleToJs(arg: JSMarshalerArgument): number | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return getArgF64(arg);
+}
+
+function _marshalIntptrToJs(arg: JSMarshalerArgument): number | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ return getArgIntptr(arg);
+}
+
+function _marshalNullToJs(): null {
+ return null;
+}
+
+function _marshalDatetimeToJs(arg: JSMarshalerArgument): Date | null {
+ const type = getArgType(arg);
+ if (type === MarshalerType.None) {
+ return null;
+ }
+ return getArgDate(arg);
+}
+
+// NOTE: at the moment, this can't dispatch async calls (with Task/Promise return type). Therefore we don't have to worry about pre-created Task.
+function _marshalDelegateToJs(arg: JSMarshalerArgument, _?: MarshalerType, resConverter?: MarshalerToJs, arg1Converter?: MarshalerToCs, arg2Converter?: MarshalerToCs, arg3Converter?: MarshalerToCs): Function | null {
+ const type = getArgType(arg);
+ if (type === MarshalerType.None) {
+ return null;
+ }
+
+ const gcHandle = getArgGcHandle(arg);
+ let result = lookupJsOwnedObject(gcHandle);
+ if (result === null || result === undefined) {
+ // this will create new Function for the C# delegate
+ result = (arg1Js: any, arg2Js: any, arg3Js: any): any => {
+ dotnetAssert.check(!result.isDisposed, "Delegate is disposed and should not be invoked anymore.");
+ // arg numbers are shifted by one, the real first is a gc handle of the callback
+ return callDelegate(gcHandle, arg1Js, arg2Js, arg3Js, resConverter, arg1Converter, arg2Converter, arg3Converter);
+ };
+ result.dispose = () => {
+ if (!result.isDisposed) {
+ result.isDisposed = true;
+ teardownManagedProxy(result, gcHandle);
+ }
+ };
+ result.isDisposed = false;
+ if (BuildConfiguration === "Debug") {
+ (result as any)[proxyDebugSymbol] = `C# Delegate with GCHandle ${gcHandle}`;
+ }
+ setupManagedProxy(result, gcHandle);
+ }
+
+ return result;
+}
+
+export class TaskHolder {
+ constructor(public promise: Promise, public resolveOrReject: (type: MarshalerType, jsHandle: JSHandle, argInner: JSMarshalerArgument) => void) {
+ }
+}
+
+export function marshalTaskToJs(arg: JSMarshalerArgument, _?: MarshalerType, resConverter?: MarshalerToJs): Promise | null {
+ const type = getArgType(arg);
+ // this path is used only when Task is passed as argument to JSImport and virtual JSHandle would be used
+ dotnetAssert.check(type != MarshalerType.TaskPreCreated, "Unexpected Task type: TaskPreCreated");
+
+ // if there is synchronous result, return it
+ const promise = tryMarshalSyncTaskToJs(arg, type, resConverter);
+ if (promise !== false) {
+ return promise;
+ }
+
+ const jsvHandle = getArgJsHandle(arg);
+ const holder = createTaskHolder(resConverter);
+ registerWithJsvHandle(holder, jsvHandle);
+ if (BuildConfiguration === "Debug") {
+ (holder as any)[proxyDebugSymbol] = `TaskHolder with JSVHandle ${jsvHandle}`;
+ }
+
+ return holder.promise;
+}
+
+export function beginMarshalTaskToJs(arg: JSMarshalerArgument, _?: MarshalerType, resConverter?: MarshalerToJs): Promise | null {
+ // this path is used when Task is returned from JSExport/call_entry_point
+ const holder = createTaskHolder(resConverter);
+ const jsHandle = getJsHandleFromJSObject(holder);
+ if (BuildConfiguration === "Debug") {
+ (holder as any)[proxyDebugSymbol] = `TaskHolder with JSHandle ${jsHandle}`;
+ }
+ setJsHandle(arg, jsHandle);
+ setArgType(arg, MarshalerType.TaskPreCreated);
+ return holder.promise;
+}
+
+export function endMarshalTaskToJs(args: JSMarshalerArguments, resConverter: MarshalerToJs | undefined, eagerPromise: Promise | null) {
+ // this path is used when Task is returned from JSExport/call_entry_point
+ const res = getArg(args, 1);
+ const type = getArgType(res);
+
+ // if there is no synchronous result, return eagerPromise we created earlier
+ if (type === MarshalerType.TaskPreCreated) {
+ return eagerPromise;
+ }
+
+ // otherwise drop the eagerPromise's handle
+ const jsHandle = getJsHandleFromJSObject(eagerPromise);
+ releaseCSOwnedObject(jsHandle);
+
+ // get the synchronous result
+ const promise = tryMarshalSyncTaskToJs(res, type, resConverter);
+
+ // make sure we got the result
+ dotnetAssert.check(promise !== false, () => `Expected synchronous result, got: ${type}`);
+
+ return promise;
+}
+
+function tryMarshalSyncTaskToJs(arg: JSMarshalerArgument, type: MarshalerType, resConverter?: MarshalerToJs): Promise | null | false {
+ if (type === MarshalerType.None) {
+ return null;
+ }
+ if (type === MarshalerType.TaskRejected) {
+ return Promise.reject(marshalExceptionToJs(arg));
+ }
+ if (type === MarshalerType.TaskResolved) {
+ const elementType = getArgElementType(arg);
+ if (elementType === MarshalerType.Void) {
+ return Promise.resolve();
+ }
+ // this will change the type to the actual type of the result
+ setArgType(arg, elementType);
+ if (!resConverter) {
+ // when we arrived here from _marshalCsObjectToJs
+ resConverter = csToJsMarshalers.get(elementType);
+ }
+ dotnetAssert.check(resConverter, () => `Unknown subConverter for type ${elementType}. ${jsinteropDoc}`);
+
+ const val = resConverter(arg);
+ return Promise.resolve(val);
+ }
+ return false;
+}
+
+function createTaskHolder(resConverter?: MarshalerToJs) {
+ const pcs = dotnetLoaderExports.createPromiseCompletionSource();
+ const holder = new TaskHolder(pcs.promise, (type, jsHandle, argInner) => {
+ if (type === MarshalerType.TaskRejected) {
+ const reason = marshalExceptionToJs(argInner);
+ pcs.reject(reason);
+ } else if (type === MarshalerType.TaskResolved) {
+ const type = getArgType(argInner);
+ if (type === MarshalerType.Void) {
+ pcs.resolve(undefined);
+ } else {
+ if (!resConverter) {
+ // when we arrived here from _marshalCsObjectToJs
+ resConverter = csToJsMarshalers.get(type);
+ }
+ dotnetAssert.check(resConverter, () => `Unknown subConverter for type ${type}. ${jsinteropDoc}`);
+
+ const jsValue = resConverter!(argInner);
+ pcs.resolve(jsValue);
+ }
+ } else {
+ dotnetAssert.check(false, () => `Unexpected type ${type}`);
+ }
+ releaseCSOwnedObject(jsHandle);
+ });
+ return holder;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function marshalStringToJs(arg: JSMarshalerArgument): string | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ const buffer = getArgIntptr(arg);
+ const len = getArgLength(arg) * 2;
+ const value = dotnetBrowserUtilsExports.utf16ToString(buffer, buffer + len);
+ Module._free(buffer as any);
+ return value;
+}
+
+export function marshalExceptionToJs(arg: JSMarshalerArgument): Error | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ if (type == MarshalerType.JSException) {
+ // this is JSException roundtrip
+ const jsHandle = getArgJsHandle(arg);
+ const jsObj = getJSObjectFromJSHandle(jsHandle);
+ return jsObj;
+ }
+
+ const gcHandle = getArgGcHandle(arg);
+ let result = lookupJsOwnedObject(gcHandle);
+ if (result === null || result === undefined) {
+ // this will create new ManagedError
+ const message = marshalStringToJs(arg);
+ result = new ManagedError(message!);
+
+ if (BuildConfiguration === "Debug") {
+ (result as any)[proxyDebugSymbol] = `C# Exception with GCHandle ${gcHandle}`;
+ }
+ setupManagedProxy(result, gcHandle);
+ }
+
+ return result;
+}
+
+function _marshalJsObjectToJs(arg: JSMarshalerArgument): any {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ const jsHandle = getArgJsHandle(arg);
+ const jsObj = getJSObjectFromJSHandle(jsHandle);
+ dotnetAssert.check(jsObj !== undefined, () => `JS object JSHandle ${jsHandle} was not found`);
+ return jsObj;
+}
+
+function _marshalCsObjectToJs(arg: JSMarshalerArgument): any {
+ const marshalerType = getArgType(arg);
+ if (marshalerType == MarshalerType.None) {
+ return null;
+ }
+ if (marshalerType == MarshalerType.JSObject) {
+ const jsHandle = getArgJsHandle(arg);
+ const jsObj = getJSObjectFromJSHandle(jsHandle);
+ return jsObj;
+ }
+
+ if (marshalerType == MarshalerType.Array) {
+ const elementType = getArgElementType(arg);
+ return _marshalArrayToJs_impl(arg, elementType);
+ }
+
+ if (marshalerType == MarshalerType.Object) {
+ const gcHandle = getArgGcHandle(arg);
+ if (gcHandle === GCHandleNull) {
+ return null;
+ }
+
+ // see if we have js owned instance for this gcHandle already
+ let result = lookupJsOwnedObject(gcHandle);
+
+ // If the JS object for this gcHandle was already collected (or was never created)
+ if (!result) {
+ result = new ManagedObject();
+ if (BuildConfiguration === "Debug") {
+ (result as any)[proxyDebugSymbol] = `C# Object with GCHandle ${gcHandle}`;
+ }
+ setupManagedProxy(result, gcHandle);
+ }
+
+ return result;
+ }
+
+ // other types
+ const converter = csToJsMarshalers.get(marshalerType);
+ dotnetAssert.check(converter, () => `Unknown converter for type ${marshalerType}. ${jsinteropDoc}`);
+ return converter(arg);
+}
+
+function _marshalArrayToJs(arg: JSMarshalerArgument, elementType?: MarshalerType): Array | TypedArray | null {
+ dotnetAssert.check(!!elementType, "Expected valid elementType parameter");
+ return _marshalArrayToJs_impl(arg, elementType);
+}
+
+function _marshalArrayToJs_impl(arg: JSMarshalerArgument, elementType: MarshalerType): Array | TypedArray | null {
+ const type = getArgType(arg);
+ if (type == MarshalerType.None) {
+ return null;
+ }
+ const elementSize = arrayElementSize(elementType);
+ dotnetAssert.check(elementSize != -1, () => `Element type ${elementType} not supported`);
+ const bufferPtr = getArgIntptr(arg);
+ const length = getArgLength(arg);
+ let result: Array | TypedArray | null = null;
+ if (elementType == MarshalerType.String) {
+ result = new Array(length);
+ for (let index = 0; index < length; index++) {
+ const elementArg = getArg(bufferPtr, index);
+ result[index] = marshalStringToJs(elementArg);
+ }
+ } else if (elementType == MarshalerType.Object) {
+ result = new Array(length);
+ for (let index = 0; index < length; index++) {
+ const elementArg = getArg(bufferPtr, index);
+ result[index] = _marshalCsObjectToJs(elementArg);
+ }
+ } else if (elementType == MarshalerType.JSObject) {
+ result = new Array(length);
+ for (let index = 0; index < length; index++) {
+ const elementArg = getArg(bufferPtr, index);
+ result[index] = _marshalJsObjectToJs(elementArg);
+ }
+ } else if (elementType == MarshalerType.Byte) {
+ const bufferOffset = fixupPointer(bufferPtr, 0);
+ const sourceView = dotnetApi.localHeapViewU8().subarray(bufferOffset, bufferOffset + length);
+ result = sourceView.slice();//copy
+ } else if (elementType == MarshalerType.Int32) {
+ const bufferOffset = fixupPointer(bufferPtr, 2);
+ const sourceView = dotnetApi.localHeapViewI32().subarray(bufferOffset, bufferOffset + length);
+ result = sourceView.slice();//copy
+ } else if (elementType == MarshalerType.Double) {
+ const bufferOffset = fixupPointer(bufferPtr, 3);
+ const sourceView = dotnetApi.localHeapViewF64().subarray(bufferOffset, bufferOffset + length);
+ result = sourceView.slice();//copy
+ } else {
+ throw new Error(`NotImplementedException ${elementType}. ${jsinteropDoc}`);
+ }
+ Module._free(bufferPtr);
+ return result;
+}
+
+function _marshalSpanToJs(arg: JSMarshalerArgument, elementType?: MarshalerType): Span {
+ dotnetAssert.check(!!elementType, "Expected valid elementType parameter");
+
+ const bufferPtr = getArgIntptr(arg);
+ const length = getArgLength(arg);
+ let result: Span | null = null;
+ if (elementType == MarshalerType.Byte) {
+ result = new Span(bufferPtr, length, MemoryViewType.Byte);
+ } else if (elementType == MarshalerType.Int32) {
+ result = new Span(bufferPtr, length, MemoryViewType.Int32);
+ } else if (elementType == MarshalerType.Double) {
+ result = new Span(bufferPtr, length, MemoryViewType.Double);
+ } else {
+ throw new Error(`NotImplementedException ${elementType}. ${jsinteropDoc}`);
+ }
+ return result;
+}
+
+function _marshalArraySegmentToJs(arg: JSMarshalerArgument, elementType?: MarshalerType): ArraySegment {
+ dotnetAssert.check(!!elementType, "Expected valid elementType parameter");
+
+ const bufferPtr = getArgIntptr(arg);
+ const length = getArgLength(arg);
+ let result: ArraySegment | null = null;
+ if (elementType == MarshalerType.Byte) {
+ result = new ArraySegment(bufferPtr, length, MemoryViewType.Byte);
+ } else if (elementType == MarshalerType.Int32) {
+ result = new ArraySegment(bufferPtr, length, MemoryViewType.Int32);
+ } else if (elementType == MarshalerType.Double) {
+ result = new ArraySegment(bufferPtr, length, MemoryViewType.Double);
+ } else {
+ throw new Error(`NotImplementedException ${elementType}. ${jsinteropDoc}`);
+ }
+ const gcHandle = getArgGcHandle(arg);
+ if (BuildConfiguration === "Debug") {
+ (result as any)[proxyDebugSymbol] = `C# ArraySegment with GCHandle ${gcHandle}`;
+ }
+ setupManagedProxy(result, gcHandle);
+
+ return result;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function resolveOrRejectPromise(args: JSMarshalerArguments): void {
+ if (!isRuntimeRunning()) {
+ dotnetLogger.debug("This promise resolution/rejection can't be propagated to managed code, mono runtime already exited.");
+ return;
+ }
+ args = fixupPointer(args, 0);
+ const exc = getArg(args, 0);
+ // TODO-WASM const receiver_should_free = WasmEnableThreads && is_receiver_should_free(args);
+ try {
+ assertRuntimeRunning();
+
+ const res = getArg(args, 1);
+ const argHandle = getArg(args, 2);
+ const argValue = getArg(args, 3);
+
+ const type = getArgType(argHandle);
+ const jsHandle = getArgJsHandle(argHandle);
+
+ const holder = getJSObjectFromJSHandle(jsHandle) as TaskHolder;
+ dotnetAssert.check(holder, () => `Cannot find Promise for JSHandle ${jsHandle}`);
+
+ holder.resolveOrReject(type, jsHandle, argValue);
+ /* TODO-WASM if (receiver_should_free) {
+ // this works together with AllocHGlobal in JSFunctionBinding.ResolveOrRejectPromise
+ free(args as any);
+ } else {*/
+ setArgType(res, MarshalerType.Void);
+ setArgType(exc, MarshalerType.None);
+ //}
+
+ } catch (ex: any) {
+ /* TODO-WASM if (receiver_should_free) {
+ mono_assert(false, () => `Failed to resolve or reject promise ${ex}`);
+ }*/
+ marshalExceptionToCs(exc, ex);
+ }
+}
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts
new file mode 100644
index 00000000000000..93168b71096416
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts
@@ -0,0 +1,330 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { dotnetBrowserUtilsExports, dotnetApi, dotnetAssert, Module } from "./cross-module";
+
+import type { GCHandle, JSFunctionSignature, JSHandle, JSMarshalerType, JSMarshalerArgument, JSMarshalerArguments, MarshalerToCs, MarshalerToJs, VoidPtr, PThreadPtr } from "./types";
+import { JavaScriptMarshalerArgSize, JSBindingHeaderOffsets, JSBindingTypeOffsets, JSMarshalerArgumentOffsets, JSMarshalerSignatureHeaderSize, JSMarshalerTypeSize, MarshalerType } from "./types";
+
+export const jsInteropState = {
+ isPendingSynchronousCall: false,
+ proxyGCHandle: undefined as GCHandle | undefined,
+ cspPolicy: false,
+};
+
+export const csToJsMarshalers = new Map();
+export const jsToCsMarshalers = new Map();
+export const boundCsFunctionSymbol = Symbol.for("wasm bound_cs_function");
+export const boundJsFunctionSymbol = Symbol.for("wasm bound_js_function");
+export const importedJsFunctionSymbol = Symbol.for("wasm imported_js_function");
+export const proxyDebugSymbol = Symbol.for("wasm proxyDebug");
+
+export function allocStackFrame(size: number): JSMarshalerArguments {
+ const bytes = JavaScriptMarshalerArgSize * size;
+ const args = Module.stackAlloc(bytes) as any;
+ dotnetBrowserUtilsExports.zeroRegion(args, bytes);
+ setArgsContext(args);
+ return args;
+}
+
+export function getArg(args: JSMarshalerArguments, index: number): JSMarshalerArgument {
+ dotnetAssert.check(args, "Null args");
+ return args + (index * JavaScriptMarshalerArgSize);
+}
+
+export function isArgsException(args: JSMarshalerArguments): boolean {
+ dotnetAssert.check(args, "Null args");
+ const exceptionType = getArgType(args);
+ return exceptionType !== MarshalerType.None;
+}
+
+export function isReceiverShouldFree(args: JSMarshalerArguments): boolean {
+ dotnetAssert.check(args, "Null args");
+ return dotnetApi.getHeapB8(args + JSMarshalerArgumentOffsets.ReceiverShouldFree);
+}
+
+export function getSyncDoneSemaphorePtr(args: JSMarshalerArguments): VoidPtr {
+ dotnetAssert.check(args, "Null args");
+ return dotnetApi.getHeapI32(args + JSMarshalerArgumentOffsets.SyncDoneSemaphorePtr) as any;
+}
+
+export function getCallerNativeTid(args: JSMarshalerArguments): PThreadPtr {
+ dotnetAssert.check(args, "Null args");
+ return dotnetApi.getHeapI32(args + JSMarshalerArgumentOffsets.CallerNativeTID) as any;
+}
+
+export function setReceiverShouldFree(args: JSMarshalerArguments): void {
+ dotnetApi.setHeapB8(args + JSMarshalerArgumentOffsets.ReceiverShouldFree, true);
+}
+
+export function setArgsContext(args: JSMarshalerArguments): void {
+ dotnetAssert.check(args, "Null args");
+ const exc = getArg(args, 0);
+ const res = getArg(args, 1);
+ setArgProxyContext(exc);
+ setArgProxyContext(res);
+}
+
+export function getSig(signature: JSFunctionSignature, index: number): JSMarshalerType {
+ dotnetAssert.check(signature, "Null signatures");
+ return signature + (index * JSMarshalerTypeSize) + JSMarshalerSignatureHeaderSize;
+}
+
+export function getSignatureType(sig: JSMarshalerType): MarshalerType {
+ dotnetAssert.check(sig, "Null sig");
+ return dotnetApi.getHeapU8(sig + JSBindingTypeOffsets.Type);
+}
+
+export function getSignatureResType(sig: JSMarshalerType): MarshalerType {
+ dotnetAssert.check(sig, "Null sig");
+ return dotnetApi.getHeapU8(sig + JSBindingTypeOffsets.ResultMarshalerType);
+}
+
+export function getSignatureArg1Type(sig: JSMarshalerType): MarshalerType {
+ dotnetAssert.check(sig, "Null sig");
+ return dotnetApi.getHeapU8(sig + JSBindingTypeOffsets.Arg1MarshalerType);
+}
+
+export function getSignatureArg2Type(sig: JSMarshalerType): MarshalerType {
+ dotnetAssert.check(sig, "Null sig");
+ return dotnetApi.getHeapU8(sig + JSBindingTypeOffsets.Arg2MarshalerType);
+}
+
+export function getSignatureArg3Type(sig: JSMarshalerType): MarshalerType {
+ dotnetAssert.check(sig, "Null sig");
+ return dotnetApi.getHeapU8(sig + JSBindingTypeOffsets.Arg3MarshalerType);
+}
+
+export function getSignatureArgumentCount(signature: JSFunctionSignature): number {
+ dotnetAssert.check(signature, "Null signatures");
+ return dotnetApi.getHeapI32(signature + JSBindingHeaderOffsets.ArgumentCount);
+}
+
+export function getSignatureVersion(signature: JSFunctionSignature): number {
+ dotnetAssert.check(signature, "Null signatures");
+ return dotnetApi.getHeapI32(signature + JSBindingHeaderOffsets.Version);
+}
+
+export function getSignatureHandle(signature: JSFunctionSignature): number {
+ dotnetAssert.check(signature, "Null signatures");
+ return dotnetApi.getHeapI32(signature + JSBindingHeaderOffsets.ImportHandle);
+}
+
+export function getSignatureFunctionName(signature: JSFunctionSignature): string | null {
+ dotnetAssert.check(signature, "Null signatures");
+ const functionNameOffset = dotnetApi.getHeapI32(signature + JSBindingHeaderOffsets.FunctionNameOffset);
+ if (functionNameOffset === 0) return null;
+ const functionNameLength = dotnetApi.getHeapI32(signature + JSBindingHeaderOffsets.FunctionNameLength);
+ dotnetAssert.check(functionNameOffset, "Null name");
+ return dotnetBrowserUtilsExports.utf16ToString(signature + functionNameOffset, signature + functionNameOffset + functionNameLength);
+}
+
+export function getSignatureModuleName(signature: JSFunctionSignature): string | null {
+ dotnetAssert.check(signature, "Null signatures");
+ const moduleNameOffset = dotnetApi.getHeapI32(signature + JSBindingHeaderOffsets.ModuleNameOffset);
+ if (moduleNameOffset === 0) return null;
+ const moduleNameLength = dotnetApi.getHeapI32(signature + JSBindingHeaderOffsets.ModuleNameLength);
+ return dotnetBrowserUtilsExports.utf16ToString(signature + moduleNameOffset, signature + moduleNameOffset + moduleNameLength);
+}
+
+export function getSigType(sig: JSMarshalerType): MarshalerType {
+ dotnetAssert.check(sig, "Null signatures");
+ return dotnetApi.getHeapU8(sig);
+}
+
+export function getArgType(arg: JSMarshalerArgument): MarshalerType {
+ dotnetAssert.check(arg, "Null arg");
+ const type = dotnetApi.getHeapU8(arg + JSMarshalerArgumentOffsets.Type);
+ return type;
+}
+
+export function getArgElementType(arg: JSMarshalerArgument): MarshalerType {
+ dotnetAssert.check(arg, "Null arg");
+ const type = dotnetApi.getHeapU8(arg + JSMarshalerArgumentOffsets.ElementType);
+ return type;
+}
+
+export function setArgType(arg: JSMarshalerArgument, type: MarshalerType): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapU8(arg + JSMarshalerArgumentOffsets.Type, type);
+}
+
+export function setArgElementType(arg: JSMarshalerArgument, type: MarshalerType): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapU8(arg + JSMarshalerArgumentOffsets.ElementType, type);
+}
+
+export function getArgBool(arg: JSMarshalerArgument): boolean {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapB8(arg);
+}
+
+export function getArgU8(arg: JSMarshalerArgument): number {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapU8(arg);
+}
+
+export function getArgU16(arg: JSMarshalerArgument): number {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapU16(arg);
+}
+
+export function getArgI16(arg: JSMarshalerArgument): number {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapI16(arg);
+}
+
+export function getArgI32(arg: JSMarshalerArgument): number {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapI32(arg);
+}
+
+export function getArgIntptr(arg: JSMarshalerArgument): number {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapU32(arg);
+}
+
+export function getArgI52(arg: JSMarshalerArgument): number {
+ dotnetAssert.check(arg, "Null arg");
+ // we know that the range check and conversion from Int64 was be done on C# side
+ return dotnetApi.getHeapF64(arg);
+}
+
+export function getArgI64Big(arg: JSMarshalerArgument): bigint {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapI64Big(arg);
+}
+
+export function getArgDate(arg: JSMarshalerArgument): Date {
+ dotnetAssert.check(arg, "Null arg");
+ const unixTime = dotnetApi.getHeapF64(arg);
+ const date = new Date(unixTime);
+ return date;
+}
+
+export function getArgF32(arg: JSMarshalerArgument): number {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapF32(arg);
+}
+
+export function getArgF64(arg: JSMarshalerArgument): number {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapF64(arg);
+}
+
+export function setArgBool(arg: JSMarshalerArgument, value: boolean): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetAssert.check(typeof value === "boolean", () => `Value is not a Boolean: ${value} (${typeof (value)})`);
+ dotnetApi.setHeapB8(arg, value);
+}
+
+export function setArgU8(arg: JSMarshalerArgument, value: number): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapU8(arg, value);
+}
+
+export function setArgU16(arg: JSMarshalerArgument, value: number): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapU16(arg, value);
+}
+
+export function setArgI16(arg: JSMarshalerArgument, value: number): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapI16(arg, value);
+}
+
+export function setArgI32(arg: JSMarshalerArgument, value: number): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapI32(arg, value);
+}
+
+export function setArgIntptr(arg: JSMarshalerArgument, value: VoidPtr): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapU32(arg, value);
+}
+
+export function setArgI52(arg: JSMarshalerArgument, value: number): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetAssert.check(Number.isSafeInteger(value), () => `Value is not an integer: ${value} (${typeof (value)})`);
+ // we know that conversion to Int64 would be done on C# side
+ dotnetApi.setHeapF64(arg, value);
+}
+
+export function setArgI64Big(arg: JSMarshalerArgument, value: bigint): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapI64Big(arg, value);
+}
+
+export function setArgDate(arg: JSMarshalerArgument, value: Date): void {
+ dotnetAssert.check(arg, "Null arg");
+ // getTime() is always UTC
+ const unixTime = value.getTime();
+ dotnetApi.setHeapF64(arg, unixTime);
+}
+
+export function setArgF64(arg: JSMarshalerArgument, value: number): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapF64(arg, value);
+}
+
+export function setArgF32(arg: JSMarshalerArgument, value: number): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapF32(arg, value);
+}
+
+export function getArgJsHandle(arg: JSMarshalerArgument): JSHandle {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapI32(arg + JSMarshalerArgumentOffsets.JSHandle);
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function setArgProxyContext(arg: JSMarshalerArgument): void {
+ /*TODO-WASM threads only
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapI32(arg + JSMarshalerArgumentOffsets.ContextHandle, jsInteropState.proxyGCHandle);
+ */
+}
+
+export function setJsHandle(arg: JSMarshalerArgument, jsHandle: JSHandle): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapI32(arg + JSMarshalerArgumentOffsets.JSHandle, jsHandle);
+ setArgProxyContext(arg);
+}
+
+export function getArgGcHandle(arg: JSMarshalerArgument): GCHandle {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapI32(arg + JSMarshalerArgumentOffsets.GCHandle);
+}
+
+export function setGcHandle(arg: JSMarshalerArgument, gcHandle: GCHandle): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapI32(arg + JSMarshalerArgumentOffsets.GCHandle, gcHandle);
+ setArgProxyContext(arg);
+}
+
+export function getArgLength(arg: JSMarshalerArgument): number {
+ dotnetAssert.check(arg, "Null arg");
+ return dotnetApi.getHeapI32(arg + JSMarshalerArgumentOffsets.Length);
+}
+
+export function setArgLength(arg: JSMarshalerArgument, size: number): void {
+ dotnetAssert.check(arg, "Null arg");
+ dotnetApi.setHeapI32(arg + JSMarshalerArgumentOffsets.Length, size);
+}
+
+export function getSignatureMarshaler(signature: JSFunctionSignature, index: number): JSHandle {
+ dotnetAssert.check(signature, "Null signatures");
+ const sig = getSig(signature, index);
+ return dotnetApi.getHeapU32(sig + JSBindingHeaderOffsets.ImportHandle);
+}
+
+export function arrayElementSize(elementType: MarshalerType): number {
+ return elementType == MarshalerType.Byte ? 1
+ : elementType == MarshalerType.Int32 ? 4
+ : elementType == MarshalerType.Int52 ? 8
+ : elementType == MarshalerType.Double ? 8
+ : elementType == MarshalerType.String ? JavaScriptMarshalerArgSize
+ : elementType == MarshalerType.Object ? JavaScriptMarshalerArgSize
+ : elementType == MarshalerType.JSObject ? JavaScriptMarshalerArgSize
+ : -1;
+}
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts
new file mode 100644
index 00000000000000..1f3b1fe6f0700f
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts
@@ -0,0 +1,166 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { TypedArray, VoidPtr } from "../types";
+import { jsOwnedGcHandleSymbol, teardownManagedProxy } from "./gc-handles";
+import { getManagedStackTrace } from "./managed-exports";
+import { GCHandleNull, IMemoryView } from "./types";
+import { isRuntimeRunning } from "./utils";
+
+export const enum MemoryViewType {
+ Byte = 0,
+ Int32 = 1,
+ Double = 2,
+}
+
+abstract class MemoryView implements IMemoryView {
+ protected constructor(public _pointer: VoidPtr, public _length: number, public _viewType: MemoryViewType) {
+ }
+
+ abstract dispose(): void;
+ abstract get isDisposed(): boolean;
+
+ _unsafe_create_view(): TypedArray {
+ // this view must be short lived so that it doesn't fail after wasm memory growth
+ // for that reason we also don't give the view out to end user and provide set/slice/copyTo API instead
+ const view = this._viewType == MemoryViewType.Byte ? new Uint8Array(dotnetApi.localHeapViewU8().buffer, this._pointer as any >>> 0, this._length)
+ : this._viewType == MemoryViewType.Int32 ? new Int32Array(dotnetApi.localHeapViewI32().buffer, this._pointer as any >>> 0, this._length)
+ : this._viewType == MemoryViewType.Double ? new Float64Array(dotnetApi.localHeapViewF64().buffer, this._pointer as any >>> 0, this._length)
+ : null;
+ if (!view) throw new Error("NotImplementedException");
+ return view;
+ }
+
+ set(source: TypedArray, targetOffset?: number): void {
+ dotnetAssert.check(!this.isDisposed, "ObjectDisposedException");
+ const targetView = this._unsafe_create_view();
+ dotnetAssert.check(source && targetView && source.constructor === targetView.constructor, () => `Expected ${targetView.constructor}`);
+ targetView.set(source, targetOffset || 0 >>> 0);
+ // TODO consider memory write barrier
+ }
+
+ copyTo(target: TypedArray, sourceOffset?: number): void {
+ dotnetAssert.check(!this.isDisposed, "ObjectDisposedException");
+ const sourceView = this._unsafe_create_view();
+ dotnetAssert.check(target && sourceView && target.constructor === sourceView.constructor, () => `Expected ${sourceView.constructor}`);
+ const trimmedSource = sourceView.subarray(sourceOffset || 0 >>> 0);
+ // TODO consider memory read barrier
+ target.set(trimmedSource);
+ }
+
+ slice(start?: number, end?: number): TypedArray {
+ dotnetAssert.check(!this.isDisposed, "ObjectDisposedException");
+ const sourceView = this._unsafe_create_view();
+ // TODO consider memory read barrier
+ return sourceView.slice(start || 0 >>> 0, end || 0 >>> 0);
+ }
+
+ get length(): number {
+ dotnetAssert.check(!this.isDisposed, "ObjectDisposedException");
+ return this._length;
+ }
+
+ get byteLength(): number {
+ dotnetAssert.check(!this.isDisposed, "ObjectDisposedException");
+ return this._viewType == MemoryViewType.Byte ? this._length
+ : this._viewType == MemoryViewType.Int32 ? this._length << 2
+ : this._viewType == MemoryViewType.Double ? this._length << 3
+ : 0;
+ }
+}
+
+
+export class Span extends MemoryView {
+ private _isDisposed = false;
+ public constructor(pointer: VoidPtr, length: number, viewType: MemoryViewType) {
+ super(pointer, length, viewType);
+ }
+ dispose(): void {
+ this._isDisposed = true;
+ }
+ get isDisposed(): boolean {
+ return this._isDisposed;
+ }
+}
+
+export class ArraySegment extends MemoryView {
+ public constructor(pointer: VoidPtr, length: number, viewType: MemoryViewType) {
+ super(pointer, length, viewType);
+ }
+
+ dispose(): void {
+ teardownManagedProxy(this, GCHandleNull);
+ }
+
+ get isDisposed(): boolean {
+ return (this as any)[jsOwnedGcHandleSymbol] === GCHandleNull;
+ }
+}
+
+export interface IDisposable {
+ dispose(): void;
+ get isDisposed(): boolean;
+}
+
+export class ManagedObject implements IDisposable {
+ dispose(): void {
+ teardownManagedProxy(this, GCHandleNull);
+ }
+
+ get isDisposed(): boolean {
+ return (this as any)[jsOwnedGcHandleSymbol] === GCHandleNull;
+ }
+
+ toString(): string {
+ return `CsObject(gcHandle: ${(this as any)[jsOwnedGcHandleSymbol]})`;
+ }
+}
+
+export class ManagedError extends Error implements IDisposable {
+ private superStack: any;
+ private managedStack: any;
+ constructor(message: string) {
+ super(message);
+ this.superStack = Object.getOwnPropertyDescriptor(this, "stack"); // this works on Chrome
+ Object.defineProperty(this, "stack", {
+ get: this.getManageStack,
+ });
+ }
+
+ getSuperStack() {
+ if (this.superStack) {
+ if (this.superStack.value !== undefined)
+ return this.superStack.value;
+ if (this.superStack.get !== undefined)
+ return this.superStack.get.call(this);
+ }
+ return super.stack; // this works on FF
+ }
+
+ getManageStack() {
+ if (this.managedStack) {
+ return this.managedStack;
+ }
+ if (!isRuntimeRunning()) {
+ this.managedStack = "... omitted managed stack trace.\n" + this.getSuperStack();
+ return this.managedStack;
+ }
+ const gcHandle = (this as any)[jsOwnedGcHandleSymbol];
+ if (gcHandle !== GCHandleNull) {
+ const managedStack = getManagedStackTrace(gcHandle);
+ if (managedStack) {
+ this.managedStack = managedStack + "\n" + this.getSuperStack();
+ return this.managedStack;
+ }
+ }
+ return this.getSuperStack();
+ }
+
+ dispose(): void {
+ teardownManagedProxy(this, GCHandleNull);
+ }
+
+ get isDisposed(): boolean {
+ return (this as any)[jsOwnedGcHandleSymbol] === GCHandleNull;
+ }
+}
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/types.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/types.ts
index 95021536b19918..6a5ea7dafb42ac 100644
--- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/types.ts
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/types.ts
@@ -19,4 +19,149 @@ export interface JSMarshalerArgument extends NativePointer {
__brand: "JSMarshalerArgument"
}
+export type PThreadPtr = {
+ __brand: "PThreadPtr" // like pthread_t in C
+}
+export type GCHandle = {
+ __brand: "GCHandle"
+}
+export type JSHandle = {
+ __brand: "JSHandle"
+}
+export type JSFnHandle = {
+ __brand: "JSFnHandle"
+}
+
+export type WeakRefInternal = WeakRef & {
+ dispose?: () => void
+}
+
+export const JSHandleDisposed: JSHandle = -1;
+export const JSHandleNull: JSHandle = 0;
+export const GCHandleNull: GCHandle = 0;
+export const GCHandleInvalid: GCHandle = -1;
+
+export type MarshalerToJs = (arg: JSMarshalerArgument, elementType?: MarshalerType, resConverter?: MarshalerToJs, arg1Converter?: MarshalerToCs, arg2Converter?: MarshalerToCs, arg3Converter?: MarshalerToCs) => any;
+export type MarshalerToCs = (arg: JSMarshalerArgument, value: any, elementType?: MarshalerType, resConverter?: MarshalerToCs, arg1Converter?: MarshalerToJs, arg2Converter?: MarshalerToJs, arg3Converter?: MarshalerToJs) => void;
+export type BoundMarshalerToJs = (args: JSMarshalerArguments) => any;
+export type BoundMarshalerToCs = (args: JSMarshalerArguments, value: any) => void;
+// please keep in sync with src\libraries\System.Runtime.InteropServices.JavaScript\src\System\Runtime\InteropServices\JavaScript\MarshalerType.cs
+export const enum MarshalerType {
+ None = 0,
+ Void = 1,
+ Discard,
+ Boolean,
+ Byte,
+ Char,
+ Int16,
+ Int32,
+ Int52,
+ BigInt64,
+ Double,
+ Single,
+ IntPtr,
+ JSObject,
+ Object,
+ String,
+ Exception,
+ DateTime,
+ DateTimeOffset,
+
+ Nullable,
+ Task,
+ Array,
+ ArraySegment,
+ Span,
+ Action,
+ Function,
+ DiscardNoWait,
+
+ // only on runtime
+ JSException,
+ TaskResolved,
+ TaskRejected,
+ TaskPreCreated,
+}
+
+export type WrappedJSFunction = (args: JSMarshalerArguments) => void;
+
+export type BindingClosure = {
+ fn: Function,
+ fqn: string,
+ isDisposed: boolean,
+ argsCount: number,
+ argMarshalers: (BoundMarshalerToJs)[],
+ resConverter: BoundMarshalerToCs | undefined,
+ hasCleanup: boolean,
+ isDiscardNoWait: boolean,
+ isAsync: boolean,
+ argCleanup: (Function | undefined)[]
+}
+
+// TODO-WASM: drop mono prefixes, move the type
+export const enum MeasuredBlock {
+ emscriptenStartup = "mono.emscriptenStartup",
+ instantiateWasm = "mono.instantiateWasm",
+ preRun = "mono.preRun",
+ preRunWorker = "mono.preRunWorker",
+ onRuntimeInitialized = "mono.onRuntimeInitialized",
+ postRun = "mono.postRun",
+ postRunWorker = "mono.postRunWorker",
+ startRuntime = "mono.startRuntime",
+ loadRuntime = "mono.loadRuntime",
+ bindingsInit = "mono.bindingsInit",
+ bindJsFunction = "mono.bindJsFunction:",
+ bindCsFunction = "mono.bindCsFunction:",
+ callJsFunction = "mono.callJsFunction:",
+ callCsFunction = "mono.callCsFunction:",
+ getAssemblyExports = "mono.getAssemblyExports:",
+ instantiateAsset = "mono.instantiateAsset:",
+}
+
+export const JavaScriptMarshalerArgSize = 32;
+// keep in sync with JSMarshalerArgumentImpl offsets
+export const enum JSMarshalerArgumentOffsets {
+ /* eslint-disable @typescript-eslint/no-duplicate-enum-values */
+ BooleanValue = 0,
+ ByteValue = 0,
+ CharValue = 0,
+ Int16Value = 0,
+ Int32Value = 0,
+ Int64Value = 0,
+ SingleValue = 0,
+ DoubleValue = 0,
+ IntPtrValue = 0,
+ JSHandle = 4,
+ GCHandle = 4,
+ Length = 8,
+ Type = 12,
+ ElementType = 13,
+ ContextHandle = 16,
+ ReceiverShouldFree = 20,
+ CallerNativeTID = 24,
+ SyncDoneSemaphorePtr = 28,
+}
+export const JSMarshalerTypeSize = 32;
+// keep in sync with JSFunctionBinding.JSBindingType
+export const enum JSBindingTypeOffsets {
+ Type = 0,
+ ResultMarshalerType = 16,
+ Arg1MarshalerType = 20,
+ Arg2MarshalerType = 24,
+ Arg3MarshalerType = 28,
+}
+export const JSMarshalerSignatureHeaderSize = 4 * 8; // without Exception and Result
+// keep in sync with JSFunctionBinding.JSBindingHeader
+export const enum JSBindingHeaderOffsets {
+ Version = 0,
+ ArgumentCount = 4,
+ ImportHandle = 8,
+ FunctionNameOffset = 16,
+ FunctionNameLength = 20,
+ ModuleNameOffset = 24,
+ ModuleNameLength = 28,
+ Exception = 32,
+ Result = 64,
+}
+
export * from "../types";
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
new file mode 100644
index 00000000000000..767c87619acc8d
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/utils.ts
@@ -0,0 +1,49 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+export function fixupPointer(signature: any, shiftAmount: number): any {
+ return ((signature as any) >>> shiftAmount) as any;
+}
+
+export function normalizeException(ex: any) {
+ let res = "unknown exception";
+ if (ex) {
+ res = ex.toString();
+ const stack = ex.stack;
+ if (stack) {
+ // Some JS runtimes insert the error message at the top of the stack, some don't,
+ // so normalize it by using the stack as the result if it already contains the error
+ if (stack.startsWith(res))
+ res = stack;
+ else
+ res += "\n" + stack;
+ }
+
+ // TODO-WASM
+ // res = mono_wasm_symbolicate_string(res);
+ }
+ return res;
+}
+
+export function isRuntimeRunning(): boolean {
+ // TODO-WASM
+ return true;
+}
+
+export function assertRuntimeRunning(): void {
+ // TODO-WASM
+}
+
+export function assertJsInterop(): void {
+ // TODO-WASM
+}
+
+export function startMeasure(): number {
+ // TODO-WASM
+ return 0;
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export function endMeasure(mark: number, fqn: string, additionalInfo: string): void {
+ // TODO-WASM
+}
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/weak-ref.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/weak-ref.ts
new file mode 100644
index 00000000000000..baf4d1bb99a65a
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/weak-ref.ts
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { WeakRefInternal } from "./types";
+
+export const useWeakRef = typeof globalThis.WeakRef === "function";
+
+export function createWeakRef(jsObj: T): WeakRefInternal {
+ if (useWeakRef) {
+ return new WeakRef(jsObj);
+ } else {
+ // this is trivial WeakRef replacement, which holds strong reference, instead of weak one, when the browser doesn't support it
+ return createStrongRef(jsObj);
+ }
+}
+
+export function createStrongRef(jsObj: T): WeakRefInternal {
+ return {
+ deref: () => {
+ return jsObj;
+ },
+ dispose: () => {
+ jsObj = null!;
+ }
+ };
+}
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 3544781dd9fa2a..788a8fa526448d 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
@@ -19,7 +19,7 @@
const exports = {};
libInteropJavaScriptNative(exports);
- let commonDeps = ["$DOTNET"];
+ let commonDeps = ["$DOTNET", "SystemInteropJS_GetManagedStackTrace"];
const lib = {
$DOTNET_INTEROP: {
selfInitialize: () => {
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/native/cross-linked.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/native/cross-linked.ts
index ea10f4707c6a6c..bdafb667cb1443 100644
--- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/native/cross-linked.ts
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/native/cross-linked.ts
@@ -2,4 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
import { } from "../../Common/JavaScript/cross-linked";
+import type { JSMarshalerArguments } from "../interop/types";
+declare global {
+ export function _SystemInteropJS_GetManagedStackTrace(args: JSMarshalerArguments): void;
+}
diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/native/index.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/native/index.ts
index 3dc342b2746f37..cb8762f0f06224 100644
--- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/native/index.ts
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/native/index.ts
@@ -1,24 +1,43 @@
// 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, InteropJavaScriptExports, InteropJavaScriptExportsTable, JSFnHandle, JSMarshalerArguments } from "../interop/types";
-import { InternalExchangeIndex } from "../types";
+import type { InternalExchange, InteropJavaScriptExports, InteropJavaScriptExportsTable, JSFnHandle, JSFunctionSignature, JSMarshalerArguments, VoidPtr } from "../interop/types";
+import { GCHandle, InternalExchangeIndex, JSHandle } from "../types";
import { } from "./cross-linked"; // ensure ambient symbols are declared
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export function SystemInteropJS_InvokeJSImportST(function_handle: JSFnHandle, args: JSMarshalerArguments) {
- // WASM-TODO implementation
- dotnetLogger.error("SystemInteropJS_InvokeJSImportST called");
- return - 1;
+export function SystemInteropJS_BindJSImportST(signature: JSFunctionSignature): VoidPtr {
+ return dotnetRuntimeExports.bindJSImportST(signature);
+}
+
+export function SystemInteropJS_InvokeJSImportST(functionHandle: JSFnHandle, args: JSMarshalerArguments): void {
+ dotnetRuntimeExports.invokeJSImportST(functionHandle, args);
+}
+
+export function SystemInteropJS_ReleaseCSOwnedObject(jsHandle: JSHandle): void {
+ dotnetRuntimeExports.releaseCSOwnedObject(jsHandle);
+}
+
+export function SystemInteropJS_ResolveOrRejectPromise(args: JSMarshalerArguments): void {
+ dotnetRuntimeExports.resolveOrRejectPromise(args);
+}
+
+export function SystemInteropJS_CancelPromise(taskHolderGCHandle: GCHandle): void {
+ dotnetRuntimeExports.cancelPromise(taskHolderGCHandle);
+}
+
+export function SystemInteropJS_InvokeJSFunction(functionJSSHandle: JSHandle, args: JSMarshalerArguments): void {
+ dotnetRuntimeExports.invokeJSFunction(functionJSSHandle, args);
}
export function dotnetInitializeModule(internals: InternalExchange): void {
internals[InternalExchangeIndex.InteropJavaScriptExportsTable] = interopJavaScriptExportsToTable({
+ SystemInteropJS_GetManagedStackTrace: (args) => _SystemInteropJS_GetManagedStackTrace(args),
});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function interopJavaScriptExportsToTable(map: InteropJavaScriptExports): InteropJavaScriptExportsTable {
// keep in sync with interopJavaScriptExportsFromTable()
return [
+ map.SystemInteropJS_GetManagedStackTrace,
];
}
dotnetUpdateInternals(internals, dotnetUpdateInternalsSubscriber);
diff --git a/src/native/libs/build-native.proj b/src/native/libs/build-native.proj
index 4b586b6d3e9d13..855e0830097c07 100644
--- a/src/native/libs/build-native.proj
+++ b/src/native/libs/build-native.proj
@@ -1,5 +1,5 @@
-
+
@@ -26,7 +26,7 @@
+ DependsOnTargets="AcquireWasiSdk;GenerateNativeVersionFile;GenerateEmccExports">
<_BuildNativeEnvironmentVariables>WASI_SDK_PATH=$(RuntimeBuildWasiSdkPath)
diff --git a/src/native/rollup.config.defines.js b/src/native/rollup.config.defines.js
index 55d08ee96d2919..30853589eb2b08 100644
--- a/src/native/rollup.config.defines.js
+++ b/src/native/rollup.config.defines.js
@@ -18,7 +18,7 @@ if (process.env.ProductVersion === undefined) {
export const configuration = process.env.Configuration !== "Release" && process.env.Configuration !== "RELEASE" ? "Debug" : "Release";
export const productVersion = process.env.ProductVersion;
export const isContinuousIntegrationBuild = process.env.ContinuousIntegrationBuild === "true" ? true : false;
-export const staticLibDestination = process.env.StaticLibDestination;
+export const staticLibDestination = process.env.StaticLibDestination || "../../artifacts/bin/browser-wasm.Debug/corehost";
console.log(`Rollup configuration: Configuration=${configuration}, ProductVersion=${productVersion}, ContinuousIntegrationBuild=${isContinuousIntegrationBuild}`);
diff --git a/src/native/rollup.config.js b/src/native/rollup.config.js
index 5c69511139bfbc..c3ac696471176b 100644
--- a/src/native/rollup.config.js
+++ b/src/native/rollup.config.js
@@ -89,6 +89,21 @@ const libBrowserUtils = configure({
}
});
+const dotnetDiagnosticsJS = configure({
+ input: "./libs/System.Native.Browser/diagnostics/index.ts",
+ output: [{
+ file: staticLibDestination + "/dotnet.diagnostics.js",
+ }],
+ terser: {
+ compress: {
+ module: true,
+ }, mangle: {
+ module: true,
+ keep_classnames,
+ }
+ }
+});
+
const dotnetRuntimeJS = configure({
input: "./libs/System.Runtime.InteropServices.JavaScript.Native/dotnet.runtime.ts",
output: [{
@@ -149,6 +164,7 @@ export default defineConfig([
dotnetDTS,
libNativeBrowser,
libBrowserUtils,
+ dotnetDiagnosticsJS,
dotnetRuntimeJS,
libInteropJavaScriptNative,
libBrowserHost,