diff --git a/eng/testing/tests.browser.targets b/eng/testing/tests.browser.targets
index 5e88f3b4922cbd..cf0958e4301d28 100644
--- a/eng/testing/tests.browser.targets
+++ b/eng/testing/tests.browser.targets
@@ -25,6 +25,7 @@
$([MSBuild]::NormalizeDirectory($(BrowserProjectRoot), 'emsdk'))
true
+ false
<_WasmMainJSFileName Condition="'$(WasmMainJSPath)' != ''">$([System.IO.Path]::GetFileName('$(WasmMainJSPath)'))
<_WasmStrictVersionMatch Condition="'$(ContinuousIntegrationBuild)' == 'true'">true
@@ -47,6 +48,11 @@
false
true
true
+
+
+ false
+ false
+ false
diff --git a/eng/testing/workloads-browser.targets b/eng/testing/workloads-browser.targets
index 4bb3ddb7502fdf..5dba0dfca8219b 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'))" />
+
@@ -93,7 +97,7 @@
g__Callback|72_0#1:System.Private.CoreLib:System:GC */,
- { 2638830556, 1336557534, { &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid } } /* alternate key source: BackgroundJobHandler#0:System.Private.CoreLib:System.Threading:ThreadPool */,
- { 2638833091, 3378852959, { &MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid } } /* alternate key source: ConfigCallback#5:System.Private.CoreLib:System:GC */,
- { 2638826837, 1196551088, { &MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid } } /* alternate key source: EnumCalendarInfoCallback#2:System.Private.CoreLib:System.Globalization:CalendarData */,
- { 2638856334, 2613312799, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32 } } /* alternate key source: GetClassFactoryForTypeInternal#1:System.Private.CoreLib:Internal.Runtime.InteropServices:ComActivator */,
- { 2638856344, 993231473, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 } } /* alternate key source: GetFunctionPointer#6:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */,
- { 2638856347, 3422156547, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32 } } /* alternate key source: LoadAssembly#3:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */,
- { 2638856348, 542185314, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 } } /* alternate key source: LoadAssemblyAndGetFunctionPointer#6:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */,
- { 2638856345, 3765950975, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32 } } /* alternate key source: LoadAssemblyBytes#6:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */,
- { 2638812170, 343912841, { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32 } } /* alternate key source: NewExternalTypeEntry#2:System.Private.CoreLib:System.Runtime.InteropServices:TypeMapLazyDictionary */,
- { 2638812169, 3327247096, { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32 } } /* alternate key source: NewProxyTypeEntry#2:System.Private.CoreLib:System.Runtime.InteropServices:TypeMapLazyDictionary */,
- { 2638856333, 4239234100, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32 } } /* alternate key source: RegisterClassForTypeInternal#1:System.Private.CoreLib:Internal.Runtime.InteropServices:ComActivator */,
- { 2638830480, 167179540, { &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid } } /* alternate key source: TimerHandler#0:System.Private.CoreLib:System.Threading:TimerQueue */,
- { 2638856332, 2150642223, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32 } } /* alternate key source: UnregisterClassForTypeInternal#1:System.Private.CoreLib:Internal.Runtime.InteropServices:ComActivator */
+ { 2638833081, 3863938719, { &MD_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC__RegisterNoGCRegionCallback_g__Callback_7C_72_0_I32_RetVoid } } /* alternate key source: g__Callback|72_0#1:System.Private.CoreLib:System:GC */,
+ { 2638830557, 1336557534, { &MD_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_ThreadPool_BackgroundJobHandler_Void_RetVoid } } /* alternate key source: BackgroundJobHandler#0:System.Private.CoreLib:System.Threading:ThreadPool */,
+ { 363313056, 2901966433, { &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_BindAssemblyExports_I32_RetVoid, (void*)&Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_BindAssemblyExports_I32_RetVoid } } /* alternate key source: BindAssemblyExports#1:System.Runtime.InteropServices.JavaScript:System.Runtime.InteropServices.JavaScript:JavaScriptExports */,
+ { 363313059, 2601830388, { &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CallDelegate_I32_RetVoid, (void*)&Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CallDelegate_I32_RetVoid } } /* alternate key source: CallDelegate#1:System.Runtime.InteropServices.JavaScript:System.Runtime.InteropServices.JavaScript:JavaScriptExports */,
+ { 363313063, 433365813, { &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CallJSExport_I32_I32_RetVoid, (void*)&Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CallJSExport_I32_I32_RetVoid } } /* alternate key source: CallJSExport#2:System.Runtime.InteropServices.JavaScript:System.Runtime.InteropServices.JavaScript:JavaScriptExports */,
+ { 363313058, 3113228365, { &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CompleteTask_I32_RetVoid, (void*)&Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_CompleteTask_I32_RetVoid } } /* alternate key source: CompleteTask#1:System.Runtime.InteropServices.JavaScript:System.Runtime.InteropServices.JavaScript:JavaScriptExports */,
+ { 2638833090, 3378852959, { &MD_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid, (void*)&Call_System_Private_CoreLib_System_GC_ConfigCallback_I32_I32_I32_I32_I64_RetVoid } } /* alternate key source: ConfigCallback#5:System.Private.CoreLib:System:GC */,
+ { 2638826838, 1196551088, { &MD_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid, (void*)&Call_System_Private_CoreLib_System_Globalization_CalendarData_EnumCalendarInfoCallback_I32_I32_RetVoid } } /* alternate key source: EnumCalendarInfoCallback#2:System.Private.CoreLib:System.Globalization:CalendarData */,
+ { 2638856330, 2613312799, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_GetClassFactoryForTypeInternal_I32_RetI32 } } /* alternate key source: GetClassFactoryForTypeInternal#1:System.Private.CoreLib:Internal.Runtime.InteropServices:ComActivator */,
+ { 2638856340, 993231473, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_GetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 } } /* alternate key source: GetFunctionPointer#6:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */,
+ { 363313057, 1081971317, { &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_GetManagedStackTrace_I32_RetVoid, (void*)&Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_GetManagedStackTrace_I32_RetVoid } } /* alternate key source: GetManagedStackTrace#1:System.Runtime.InteropServices.JavaScript:System.Runtime.InteropServices.JavaScript:JavaScriptExports */,
+ { 2638856343, 3422156547, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssembly_I32_I32_I32_RetI32 } } /* alternate key source: LoadAssembly#3:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */,
+ { 2638856344, 542185314, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyAndGetFunctionPointer_I32_I32_I32_I32_I32_I32_RetI32 } } /* alternate key source: LoadAssemblyAndGetFunctionPointer#6:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */,
+ { 2638856341, 3765950975, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComponentActivator_LoadAssemblyBytes_I32_I32_I32_I32_I32_I32_RetI32 } } /* alternate key source: LoadAssemblyBytes#6:System.Private.CoreLib:Internal.Runtime.InteropServices:ComponentActivator */,
+ { 2638812171, 343912841, { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewExternalTypeEntry_I32_I32_RetI32 } } /* alternate key source: NewExternalTypeEntry#2:System.Private.CoreLib:System.Runtime.InteropServices:TypeMapLazyDictionary */,
+ { 2638812170, 3327247096, { &MD_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32, (void*)&Call_System_Private_CoreLib_System_Runtime_InteropServices_TypeMapLazyDictionary_NewProxyTypeEntry_I32_I32_RetI32 } } /* alternate key source: NewProxyTypeEntry#2:System.Private.CoreLib:System.Runtime.InteropServices:TypeMapLazyDictionary */,
+ { 2638856329, 4239234100, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_RegisterClassForTypeInternal_I32_RetI32 } } /* alternate key source: RegisterClassForTypeInternal#1:System.Private.CoreLib:Internal.Runtime.InteropServices:ComActivator */,
+ { 363313116, 1403522766, { &MD_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_ReleaseJSOwnedObjectByGCHandle_I32_RetVoid, (void*)&Call_System_Runtime_InteropServices_JavaScript_System_Runtime_InteropServices_JavaScript_JavaScriptExports_ReleaseJSOwnedObjectByGCHandle_I32_RetVoid } } /* alternate key source: ReleaseJSOwnedObjectByGCHandle#1:System.Runtime.InteropServices.JavaScript:System.Runtime.InteropServices.JavaScript:JavaScriptExports */,
+ { 2638830481, 167179540, { &MD_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid, (void*)&Call_System_Private_CoreLib_System_Threading_TimerQueue_TimerHandler_Void_RetVoid } } /* alternate key source: TimerHandler#0:System.Private.CoreLib:System.Threading:TimerQueue */,
+ { 2638856328, 2150642223, { &MD_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32, (void*)&Call_System_Private_CoreLib_Internal_Runtime_InteropServices_ComActivator_UnregisterClassForTypeInternal_I32_RetI32 } } /* alternate key source: UnregisterClassForTypeInternal#1:System.Private.CoreLib:Internal.Runtime.InteropServices:ComActivator */
};
const size_t g_ReverseThunksCount = sizeof(g_ReverseThunks) / sizeof(g_ReverseThunks[0]);
diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp
index 50227addc82619..fdcc82e0d927e0 100644
--- a/src/coreclr/vm/wasm/helpers.cpp
+++ b/src/coreclr/vm/wasm/helpers.cpp
@@ -624,10 +624,10 @@ namespace
return NULL;
void* thunk = LookupThunk(keyBuffer);
-#ifdef _DEBUG
+
if (thunk == NULL)
printf("WASM calli missing for key: %s\n", keyBuffer);
-#endif
+
return thunk;
}
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..540484499e53ec
--- /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* methodHandlePtr);
+
+ // 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/tests/WasmTestRunner/WasmTestRunner.cs b/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs
index 8c393b95f69c21..54fbcb29749130 100644
--- a/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs
+++ b/src/libraries/Common/tests/WasmTestRunner/WasmTestRunner.cs
@@ -3,11 +3,13 @@
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.DotNet.XHarness.TestRunners.Common;
using Microsoft.DotNet.XHarness.TestRunners.Xunit;
-using System.Runtime.CompilerServices;
public class WasmTestRunner : WasmApplicationEntryPoint
{
@@ -29,6 +31,13 @@ public static Task Main(string[] args)
{
return MainAsync(args);
}
+
+ protected override IEnumerable GetTestAssemblies()
+ {
+ AssemblyName an = new AssemblyName(Path.GetFileNameWithoutExtension(TestAssembly));
+ Assembly assembly = Assembly.Load(an);
+ return new[] { new TestAssemblyInfo(assembly, TestAssembly) };
+ }
#endif
public static async Task MainAsync(string[] args)
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..55ebce93258c36 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,9 +12,12 @@
SR.SystemRuntimeInteropServicesJavaScript_PlatformNotSupported
true
true
+ true
false
$(DefineConstants);FEATURE_WASM_MANAGED_THREADS
$(DefineConstants);ENABLE_JS_INTEROP_BY_VALUE
+ $(DefineConstants);MONO
+ $(DefineConstants);CORECLR
true
@@ -23,9 +26,12 @@
-
+
+
+
-
+
+
@@ -35,6 +41,8 @@
+
+
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.CoreCLR.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.CoreCLR.cs
new file mode 100644
index 00000000000000..9b4c8a491abfbd
--- /dev/null
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.CoreCLR.cs
@@ -0,0 +1,162 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ // this maps to src\native\libs\System.Runtime.InteropServices.JavaScript.Native\interop\managed-exports.ts
+ // the public methods are protected from trimming by DynamicDependency on JSFunctionBinding.BindJSFunction
+ internal static unsafe partial class JavaScriptExports
+ {
+ [UnmanagedCallersOnly(EntryPoint = "SystemInteropJS_ReleaseJSOwnedObjectByGCHandle")]
+ // The JS layer invokes this method when the JS wrapper for a JS owned object has been collected by the JS garbage collector
+ // the marshaled signature is: void ReleaseJSOwnedObjectByGCHandle(GCHandle gcHandle)
+ public static void ReleaseJSOwnedObjectByGCHandle(JSMarshalerArgument* argumentsBuffer)
+ {
+ ref JSMarshalerArgument argException = ref argumentsBuffer[0]; // initialized by caller in alloc_stack_frame()
+ ref JSMarshalerArgument arg1 = ref argumentsBuffer[2]; // initialized and set by caller
+
+ try
+ {
+ // when we arrive here, we are on the thread which owns the proxies or on IO thread
+ var ctx = argException.ToManagedContext;
+ ctx.ReleaseJSOwnedObjectByGCHandle(arg1.slot.GCHandle);
+ }
+ catch (Exception ex)
+ {
+ Environment.FailFast($"ReleaseJSOwnedObjectByGCHandle: Unexpected synchronous failure (ManagedThreadId {Environment.CurrentManagedThreadId}): " + ex);
+ }
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "SystemInteropJS_CallDelegate")]
+ // the marshaled signature is: TRes? CallDelegate(GCHandle callback, T1? arg1, T2? arg2, T3? arg3)
+ public static void CallDelegate(JSMarshalerArgument* argumentsBuffer)
+ {
+ ref JSMarshalerArgument argException = ref argumentsBuffer[0]; // initialized by JS caller in alloc_stack_frame()
+ // argResult is initialized by JS caller
+ ref JSMarshalerArgument arg1 = ref argumentsBuffer[2];// initialized and set by JS caller
+ // arg_2 set by JS caller when there are arguments
+ // arg_3 set by JS caller when there are arguments
+ // arg_4 set by JS caller when there are arguments
+ try
+ {
+ GCHandle callback_gc_handle = (GCHandle)arg1.slot.GCHandle;
+ if (callback_gc_handle.Target is JSHostImplementation.ToManagedCallback callback)
+ {
+ // arg_2, arg_3, arg_4, argResult are processed by the callback
+ callback(argumentsBuffer);
+ }
+ else
+ {
+ throw new InvalidOperationException(SR.NullToManagedCallback);
+ }
+ }
+ catch (Exception ex)
+ {
+ argException.ToJS(ex);
+ }
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "SystemInteropJS_CompleteTask")]
+ // the marshaled signature is: void CompleteTask(GCHandle holder, Exception? exceptionResult, T? result)
+ public static void CompleteTask(JSMarshalerArgument* argumentsBuffer)
+ {
+ ref JSMarshalerArgument argException = ref argumentsBuffer[0]; // initialized by caller in alloc_stack_frame()
+ ref JSMarshalerArgument argResult = ref argumentsBuffer[1]; // initialized by caller in alloc_stack_frame()
+ ref JSMarshalerArgument arg1 = ref argumentsBuffer[2];// initialized and set by caller
+ // arg_2 set by caller when this is SetException call
+ // arg_3 set by caller when this is SetResult call
+
+ try
+ {
+ // when we arrive here, we are on the thread which owns the proxies or on IO thread
+ var ctx = argException.ToManagedContext;
+ var holder = ctx.GetPromiseHolder(arg1.slot.GCHandle);
+ JSHostImplementation.ToManagedCallback callback;
+
+ callback = holder.Callback!;
+ ctx.ReleasePromiseHolder(arg1.slot.GCHandle);
+
+ // arg_2, arg_3 are processed by the callback
+ // JSProxyContext.PopOperation() is called by the callback
+ callback!(argumentsBuffer);
+ }
+ catch (Exception ex)
+ {
+ Environment.FailFast($"CompleteTask: Unexpected synchronous failure (ManagedThreadId {Environment.CurrentManagedThreadId}): " + ex);
+ }
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "SystemInteropJS_GetManagedStackTrace")]
+ // the marshaled signature is: string GetManagedStackTrace(GCHandle exception)
+ public static void GetManagedStackTrace(JSMarshalerArgument* argumentsBuffer)
+ {
+ ref JSMarshalerArgument argException = ref argumentsBuffer[0]; // initialized by caller in alloc_stack_frame()
+ ref JSMarshalerArgument argResult = ref argumentsBuffer[1]; // used as return value
+ ref JSMarshalerArgument arg1 = ref argumentsBuffer[2];// initialized and set by caller
+ try
+ {
+ // when we arrive here, we are on the thread which owns the proxies
+ argException.AssertCurrentThreadContext();
+
+ GCHandle exception_gc_handle = (GCHandle)arg1.slot.GCHandle;
+ if (exception_gc_handle.Target is Exception exception)
+ {
+ argResult.ToJS(exception.StackTrace);
+ }
+ else
+ {
+ throw new InvalidOperationException(SR.UnableToResolveHandleAsException);
+ }
+ }
+ catch (Exception ex)
+ {
+ argException.ToJS(ex);
+ }
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "SystemInteropJS_BindAssemblyExports")]
+ // the marshaled signature is: Task BindAssemblyExports(string assemblyName)
+ public static void BindAssemblyExports(JSMarshalerArgument* argumentsBuffer)
+ {
+ ref JSMarshalerArgument argException = ref argumentsBuffer[0]; // initialized by caller in alloc_stack_frame()
+ ref JSMarshalerArgument argResult = ref argumentsBuffer[1]; // used as return value
+ ref JSMarshalerArgument arg1 = ref argumentsBuffer[2];// initialized and set by caller
+ try
+ {
+ string? assemblyName;
+ // when we arrive here, we are on the thread which owns the proxies
+ argException.AssertCurrentThreadContext();
+ arg1.ToManaged(out assemblyName);
+
+ var result = JSHostImplementation.BindAssemblyExports(assemblyName);
+
+ argResult.ToJS(result);
+ }
+ catch (Exception ex)
+ {
+ argException.ToJS(ex);
+ }
+ }
+
+ [UnmanagedCallersOnly(EntryPoint = "SystemInteropJS_CallJSExport")]
+ public static void CallJSExport(int methodHandle, JSMarshalerArgument* argumentsBuffer)
+ {
+ ref JSMarshalerArgument argException = ref argumentsBuffer[0]; // initialized by caller in alloc_stack_frame()
+ var ctx = argException.AssertCurrentThreadContext();
+ if (!ctx.s_JSExportByHandle.TryGetValue(methodHandle, out var jsExport))
+ {
+ argException.ToJS(new InvalidOperationException("Unable to resolve JSExport by handle"));
+ return;
+ }
+ jsExport(new IntPtr(argumentsBuffer));
+ }
+ }
+}
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.Mono.cs
similarity index 100%
rename from src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs
rename to src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.Mono.cs
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs
index 788c657d2a7e2b..052d1f6c042f8b 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptImports.Generated.cs
@@ -46,7 +46,7 @@ internal static unsafe partial class JavaScriptImports
public static partial Task DynamicImport(string moduleName, string moduleUrl);
[JSImport("INTERNAL.bindCsFunction")]
- public static partial void BindCSFunction(IntPtr monoMethod, string assemblyName, string namespaceName, string shortClassName, string methodName, int signatureHash, IntPtr signature);
+ public static partial void BindCSFunction(IntPtr methodHandle, string assemblyName, string namespaceName, string shortClassName, string methodName, int signatureHash, IntPtr signature);
#if DEBUG
[JSImport("globalThis.console.log")]
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.CoreCLR.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.CoreCLR.cs
new file mode 100644
index 00000000000000..314711be519a5f
--- /dev/null
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.CoreCLR.cs
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.Loader;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ internal static partial class JSHostImplementation
+ {
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's kept from trimming by ModuleInitializerAttribute in the generated code.")]
+ [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "It's kept from trimming by ModuleInitializerAttribute in the generated code.")]
+ public static Task BindAssemblyExports(string? assemblyName)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(assemblyName);
+
+ Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(assemblyName));
+ Type? generatedInitializerType = assembly.GetType("System.Runtime.InteropServices.JavaScript.__GeneratedInitializer", throwOnError: true);
+ if (generatedInitializerType == null)
+ {
+ throw new ArithmeticException($"BindAssemblyExports: __GeneratedInitializer type not found in assembly '{assemblyName}'");
+ }
+ MethodInfo? registerMethod = generatedInitializerType.GetMethod("__Register_", BindingFlags.NonPublic | BindingFlags.Static, binder: null, Type.EmptyTypes, modifiers: null);
+ if (registerMethod == null)
+ {
+ throw new ArithmeticException($"BindAssemblyExports: __Register_ method not found in type '{generatedInitializerType.FullName}' in assembly '{assemblyName}'");
+ }
+ registerMethod.Invoke(null, null);
+ return Task.CompletedTask;
+ }
+
+ // this reflection based approach is used by JSExport assemblies generated by Net10 or below
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's kept from trimming by DynamicDependencyAttribute in the generated code.")]
+ [UnconditionalSuppressMessage("Trimming", "IL2075", Justification = "It's kept from trimming by DynamicDependencyAttribute in the generated code.")]
+ public static unsafe JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan signatures)
+ {
+ var ctx = JSProxyContext.CurrentThreadContext;
+ var (assemblyName, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName);
+ var wrapperName = $"__Wrapper_{methodName}_{signatureHash}";
+ shortClassName = shortClassName.Replace('/', '+');
+
+ // get MethodInfo from the fully qualified name
+ var assembly = Assembly.Load(new AssemblyName(assemblyName));
+ var clazz = string.IsNullOrEmpty(nameSpace)
+ ? assembly.GetType(shortClassName)
+ : assembly.GetType(nameSpace + "." + shortClassName);
+ if (clazz == null)
+ {
+ Environment.FailFast($"Can't find {nameSpace}{shortClassName} in {assemblyName} assembly");
+ }
+ var wrapperInfo = clazz.GetMethod(wrapperName, BindingFlags.Static | BindingFlags.NonPublic);
+ if (wrapperInfo == null)
+ {
+ Environment.FailFast($"Can't find method wrapper {wrapperName} in {nameSpace}.{shortClassName} in {assemblyName} assembly");
+ }
+
+ Action wrapper = (IntPtr args) =>
+ {
+ object boxedLegacyArgs = Pointer.Box((void*)args, typeof(JSMarshalerArgument*));
+ // real signature is void (JSMarshalerArgument* args)
+ wrapperInfo.Invoke(null, new object?[] { boxedLegacyArgs });
+ };
+
+ int methodHandle = ctx.NextJSExportHandle++;
+ ctx.s_JSExportByHandle[methodHandle] = wrapper;
+
+ var signature = GetMethodSignature(signatures, null, null);
+
+ // this will hit JS side possibly on another thread, depending on JSProxyContext.CurrentThreadContext
+ JavaScriptImports.BindCSFunction(methodHandle, assemblyName, nameSpace, shortClassName, methodName, signatureHash, (IntPtr)signature.Header);
+
+ FreeMethodSignatureBuffer(signature);
+
+ return signature;
+ }
+ }
+}
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Mono.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Mono.cs
new file mode 100644
index 00000000000000..0fc75826e74c8b
--- /dev/null
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.Mono.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.Loader;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ internal static partial class JSHostImplementation
+ {
+ public static Task BindAssemblyExports(string? assemblyName)
+ {
+ Interop.Runtime.BindAssemblyExports(Marshal.StringToCoTaskMemUTF8(assemblyName));
+ return Task.CompletedTask;
+ }
+
+ public static unsafe JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan signatures)
+ {
+ var (assemblyName, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName);
+
+ IntPtr monoMethod;
+ Interop.Runtime.GetAssemblyExport(
+ // FIXME: Pass UTF-16 through directly so C can work with it, doing the conversion
+ // in C# pulls in a bunch of dependencies we don't need this early in startup.
+ // I tested removing the UTF8 conversion from this specific call, but other parts
+ // of startup I can't identify still pull in UTF16->UTF8 conversion, so it's not
+ // worth it to do that yet.
+ Marshal.StringToCoTaskMemUTF8(assemblyName),
+ Marshal.StringToCoTaskMemUTF8(nameSpace),
+ Marshal.StringToCoTaskMemUTF8(shortClassName),
+ Marshal.StringToCoTaskMemUTF8(methodName),
+ signatureHash,
+ &monoMethod);
+
+ if (monoMethod == IntPtr.Zero)
+ {
+ Environment.FailFast($"Can't find {nameSpace}{shortClassName}{methodName} in {assemblyName}.dll");
+ }
+
+ var signature = GetMethodSignature(signatures, null, null);
+
+ // this will hit JS side possibly on another thread, depending on JSProxyContext.CurrentThreadContext
+ JavaScriptImports.BindCSFunction(monoMethod, assemblyName, nameSpace, shortClassName, methodName, signatureHash, (IntPtr)signature.Header);
+
+ FreeMethodSignatureBuffer(signature);
+
+ return signature;
+ }
+ }
+}
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs
index 26a133fbbc6021..c8ce61359aa8fb 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSHostImplementation.cs
@@ -273,45 +273,6 @@ public static void LoadSatelliteAssembly(byte[] dllBytes)
}
}
- public static Task BindAssemblyExports(string? assemblyName)
- {
- Interop.Runtime.BindAssemblyExports(Marshal.StringToCoTaskMemUTF8(assemblyName));
- return Task.CompletedTask;
- }
-
- public static unsafe JSFunctionBinding BindManagedFunction(string fullyQualifiedName, int signatureHash, ReadOnlySpan signatures)
- {
- var (assemblyName, nameSpace, shortClassName, methodName) = ParseFQN(fullyQualifiedName);
-
- IntPtr monoMethod;
- Interop.Runtime.GetAssemblyExport(
- // FIXME: Pass UTF-16 through directly so C can work with it, doing the conversion
- // in C# pulls in a bunch of dependencies we don't need this early in startup.
- // I tested removing the UTF8 conversion from this specific call, but other parts
- // of startup I can't identify still pull in UTF16->UTF8 conversion, so it's not
- // worth it to do that yet.
- Marshal.StringToCoTaskMemUTF8(assemblyName),
- Marshal.StringToCoTaskMemUTF8(nameSpace),
- Marshal.StringToCoTaskMemUTF8(shortClassName),
- Marshal.StringToCoTaskMemUTF8(methodName),
- signatureHash,
- &monoMethod);
-
- if (monoMethod == IntPtr.Zero)
- {
- Environment.FailFast($"Can't find {nameSpace}{shortClassName}{methodName} in {assemblyName}.dll");
- }
-
- var signature = GetMethodSignature(signatures, null, null);
-
- // this will hit JS side possibly on another thread, depending on JSProxyContext.CurrentThreadContext
- JavaScriptImports.BindCSFunction(monoMethod, assemblyName, nameSpace, shortClassName, methodName, signatureHash, (IntPtr)signature.Header);
-
- FreeMethodSignatureBuffer(signature);
-
- return signature;
- }
-
#if FEATURE_WASM_MANAGED_THREADS
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "external_eventloop")]
private static extern ref bool GetThreadExternalEventloop(Thread @this);
@@ -334,29 +295,34 @@ public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr)
// The BCL implementations of IndexOf/LastIndexOf/Trim are vectorized & fast,
// but they pull in a bunch of code that is otherwise not necessarily
// useful during early app startup, so we use simple scalar implementations
- private static int SmallIndexOf (string s, char ch, int direction = 1) {
+ private static int SmallIndexOf(string s, char ch, int direction = 1)
+ {
if (s.Length < 1)
return -1;
int start_index = (direction > 0) ? 0 : s.Length - 1,
end_index = (direction > 0) ? s.Length - 1 : 0;
- for (int i = start_index; i != end_index; i += direction) {
+ for (int i = start_index; i != end_index; i += direction)
+ {
if (s[i] == ch)
return i;
}
return -1;
}
- private static string SmallTrim (string s) {
+ private static string SmallTrim(string s)
+ {
if (s.Length < 1)
return s;
int head = 0, tail = s.Length - 1;
- while (head < s.Length) {
+ while (head < s.Length)
+ {
if (s[head] == ' ')
head++;
else
break;
}
- while (tail >= 0) {
+ while (tail >= 0)
+ {
if (s[tail] == ' ')
tail--;
else
@@ -368,7 +334,7 @@ private static string SmallTrim (string s) {
return s;
}
- public static (string assemblyName, string nameSpace, string shortClassName, string methodName) ParseFQN(string fqn)
+ private static (string assemblyName, string nameSpace, string shortClassName, string methodName) ParseFQN(string fqn)
{
var assembly = fqn.Substring(SmallIndexOf(fqn, '[') + 1, SmallIndexOf(fqn, ']') - 1);
fqn = SmallTrim(fqn);
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs
index cd1509ba8309a9..608643b9b07eb4 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSProxyContext.cs
@@ -23,6 +23,8 @@ internal sealed class JSProxyContext : IDisposable
// they have negative values, so that they don't collide with JSHandles.
private nint NextJSVHandle = -2;
private readonly List JSVHandleFreeList = new();
+ internal Dictionary> s_JSExportByHandle = new Dictionary>();
+ internal int NextJSExportHandle = 1;
#if !FEATURE_WASM_MANAGED_THREADS
private JSProxyContext()
@@ -491,7 +493,7 @@ public static void ReleaseCSOwnedObject(JSObject jso, bool skipJS)
if (!ctx.ThreadCsOwnedObjects.Remove(jsHandle))
{
Environment.FailFast($"ReleaseCSOwnedObject expected to find registration for JSHandle: {jsHandle}, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
- };
+ }
if (!skipJS)
{
#if FEATURE_WASM_MANAGED_THREADS
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj
index c959554c57a73e..64c4df3b921503 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System.Runtime.InteropServices.JavaScript.Tests.csproj
@@ -21,6 +21,7 @@
Suppress the NU1511 warning in the whole project as putting it on a P2P doesn't work: https://github.com/NuGet/Home/issues/14121 -->
$(NoWarn);NU1511
false
+ true
@@ -58,7 +59,9 @@
+
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs
index f4f2772fdda8b5..148a2722a44afe 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs
@@ -437,7 +437,7 @@ export async function backbackAsync(arg1, arg2, arg3) {
}
export async function callJavaScriptLibrary(a, b) {
- const exports = await App.runtime.getAssemblyExports("JavaScriptLibrary.dll");
+ const exports = await App.runtime.getAssemblyExports("JavaScriptLibrary");
return exports.JavaScriptLibrary.JavaScriptInterop.ExportedMethod(a, b);
}
@@ -454,7 +454,7 @@ globalThis.rebound = {
}
export async function setup() {
- dllExports = await App.runtime.getAssemblyExports("System.Runtime.InteropServices.JavaScript.Tests.dll");
+ dllExports = await App.runtime.getAssemblyExports("System.Runtime.InteropServices.JavaScript.Tests");
}
// console.log('JavaScriptTestHelper:' Object.keys(globalThis.JavaScriptTestHelper));
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js
index f711c31c93e27c..47c9e42d0e2dc8 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/SecondRuntimeTest.js
@@ -21,6 +21,6 @@ export async function runSecondRuntimeAndTestStaticState(guid) {
}
async function getIncrementStateFunction(runtime) {
- const exports = await runtime.getAssemblyExports("System.Runtime.InteropServices.JavaScript.Tests.dll");
+ const exports = await runtime.getAssemblyExports("System.Runtime.InteropServices.JavaScript.Tests");
return exports.System.Runtime.InteropServices.JavaScript.Tests.SecondRuntimeTest.Interop.IncrementState;
}
\ No newline at end of file
diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs
index e2a8cfadfaea7e..b30e9f548b7dd8 100644
--- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs
+++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/WebWorkerTestHelper.mjs
@@ -16,7 +16,7 @@ export async function setup() {
console.error(getDotnetRuntime);
throw new Error("runtime is null or undefined");
}
- dllExports = await runtime.getAssemblyExports("System.Runtime.InteropServices.JavaScript.Tests.dll");
+ dllExports = await runtime.getAssemblyExports("System.Runtime.InteropServices.JavaScript.Tests");
if (!dllExports) {
throw new Error("dllExports is null or undefined");
}
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/build/WasmApp.InTree.props b/src/mono/browser/build/WasmApp.InTree.props
index 2fef6aa17d9993..ed52bd0c7b3467 100644
--- a/src/mono/browser/build/WasmApp.InTree.props
+++ b/src/mono/browser/build/WasmApp.InTree.props
@@ -29,7 +29,7 @@
$(NetCoreAppCurrent)
$([MSBuild]::NormalizeDirectory($(MonoProjectRoot), 'browser', 'emsdk'))
false
- true
+ true
false
false
diff --git a/src/mono/browser/test-main.js b/src/mono/browser/test-main.js
index 872e14a2ce4cd9..3ecda3bbee992e 100644
--- a/src/mono/browser/test-main.js
+++ b/src/mono/browser/test-main.js
@@ -256,11 +256,11 @@ function configureRuntime(dotnet, runArgs) {
.withVirtualWorkingDirectory(runArgs.workingDirectory)
.withEnvironmentVariables(runArgs.environmentVariables)
.withDiagnosticTracing(runArgs.diagnosticTracing)
- .withExitOnUnhandledError()
- .withExitCodeLogging()
- .withElementOnExit()
- .withInteropCleanupOnExit()
- .withDumpThreadsOnNonZeroExit()
+ //TODO .withExitOnUnhandledError()
+ //TODO .withExitCodeLogging()
+ //TODO .withElementOnExit()
+ //TODO .withInteropCleanupOnExit()
+ //TODO .withDumpThreadsOnNonZeroExit()
.withConfig({
loadAllSatelliteResources: true,
jsThreadBlockingMode: "ThrowWhenBlockingWait",
@@ -299,7 +299,7 @@ function configureRuntime(dotnet, runArgs) {
dotnet.withWaitingForDebugger(-1);
}
if (runArgs.forwardConsole) {
- dotnet.withConsoleForwarding();
+ // TODO dotnet.withConsoleForwarding();
}
}
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 bad785110dabbf..7dea52587f03ec 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
@@ -203,6 +203,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)
@@ -345,6 +348,9 @@ Copyright (c) .NET Foundation. All rights reserved.
+ <_WebCilAssetsCandidates Update="@(_WebCilAssetsCandidates)">
+ %(Identity)
+
<_WasmFingerprintPatterns Include="WasmFiles" Pattern="*.wasm" Expression="#[.{fingerprint}]!" />
<_WasmFingerprintPatterns Include="DllFiles" Pattern="*.dll" Expression="#[.{fingerprint}]!" />
<_WasmFingerprintPatterns Include="DatFiles" Pattern="*.dat" Expression="#[.{fingerprint}]!" />
diff --git a/src/mono/sample/wasm/Directory.Build.props b/src/mono/sample/wasm/Directory.Build.props
index 7a509c489b84c6..06105dda891e05 100644
--- a/src/mono/sample/wasm/Directory.Build.props
+++ b/src/mono/sample/wasm/Directory.Build.props
@@ -15,6 +15,11 @@
+
+ false
+ false
+ false
+
bin
$(MSBuildProjectDirectory)\bin\wwwroot\
false
diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets
index baa756debe46da..542a378baa0517 100644
--- a/src/mono/sample/wasm/Directory.Build.targets
+++ b/src/mono/sample/wasm/Directory.Build.targets
@@ -37,6 +37,11 @@
+
+
+
+
+
Main(string[] args)
{
DisplayMeaning(42);
- return 0;
+ return Task.FromResult(0);
}
[JSImport("Sample.Test.displayMeaning", "main.js")]
internal static partial void DisplayMeaning(int meaning);
+
+ [JSExport]
+ internal static async Task PrintMeaning(Task meaningPromise)
+ {
+ Console.WriteLine("Meaning of life is " + await meaningPromise);
+ }
}
}
diff --git a/src/mono/sample/wasm/browser/wwwroot/main.js b/src/mono/sample/wasm/browser/wwwroot/main.js
index 20fd268d65b9ac..b1bc9a8b4176de 100644
--- a/src/mono/sample/wasm/browser/wwwroot/main.js
+++ b/src/mono/sample/wasm/browser/wwwroot/main.js
@@ -4,11 +4,16 @@
import { dotnet, exit } from './_framework/dotnet.js'
function displayMeaning(meaning) {
+ console.log(`Meaning of life is ${meaning}`);
document.getElementById("out").innerHTML = `${meaning}`;
}
+function delay(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
try {
- const { setModuleImports } = await dotnet
+ const { setModuleImports, getAssemblyExports, runMain } = await dotnet
.withElementOnExit()
.withExitOnUnhandledError()
.create();
@@ -21,7 +26,11 @@ try {
}
});
- await dotnet.run();
+ await runMain();
+
+ const exports = await getAssemblyExports("Wasm.Browser.Sample");
+ await exports.Sample.Test.PrintMeaning(delay(2000).then(() => 42));
+ console.log("Program has exited normally.");
}
catch (err) {
exit(2, err);
diff --git a/src/native/corehost/browserhost/host/host.ts b/src/native/corehost/browserhost/host/host.ts
index 689f960ba903e5..a72d4c1667362e 100644
--- a/src/native/corehost/browserhost/host/host.ts
+++ b/src/native/corehost/browserhost/host/host.ts
@@ -17,8 +17,10 @@ export function registerDllBytes(bytes: Uint8Array, asset: { name: string, virtu
const ptr = Module.HEAPU32[ptrPtr as any >>> 2];
Module.HEAPU8.set(bytes, ptr >>> 0);
- loadedAssemblies.set(asset.name, { ptr, length: bytes.length });
loadedAssemblies.set(asset.virtualPath, { ptr, length: bytes.length });
+ if (!asset.virtualPath.startsWith("/")) {
+ loadedAssemblies.set("/" + asset.virtualPath, { ptr, length: bytes.length });
+ }
} finally {
Module.stackRestore(sp);
}
@@ -94,6 +96,7 @@ export function BrowserHost_ExternalAssemblyProbe(pathPtr: CharPtr, outDataStart
Module.HEAPU32[((outSize as any) + 4) >>> 2] = 0;
return true;
}
+ dotnetLogger.debug(`Assembly not found: '${path}'`);
Module.HEAPU32[outDataStartPtr as any >>> 2] = 0;
Module.HEAPU32[outSize as any >>> 2] = 0;
Module.HEAPU32[((outSize as any) + 4) >>> 2] = 0;
@@ -105,6 +108,9 @@ export async function runMain(mainAssemblyName?: string, args?: string[]): Promi
if (!mainAssemblyName) {
mainAssemblyName = config.mainAssemblyName!;
}
+ if (!mainAssemblyName.endsWith(".dll")) {
+ mainAssemblyName += ".dll";
+ }
const mainAssemblyNamePtr = dotnetBrowserUtilsExports.stringToUTF8Ptr(mainAssemblyName) as any;
if (!args) {
diff --git a/src/native/corehost/browserhost/loader/assets.ts b/src/native/corehost/browserhost/loader/assets.ts
index c214e61b8f4a5b..d89d16a2904fe0 100644
--- a/src/native/corehost/browserhost/loader/assets.ts
+++ b/src/native/corehost/browserhost/loader/assets.ts
@@ -64,7 +64,7 @@ export async function createRuntime(downloadOnly: boolean, loadBootResource?: Lo
async function loadJSModule(asset: JsAsset): Promise {
if (asset.name && !asset.resolvedUrl) {
- asset.resolvedUrl = locateFile(asset.name);
+ asset.resolvedUrl = locateFile(asset.name, true);
}
if (!asset.resolvedUrl) throw new Error("Invalid config, resources is not set");
return await import(/* webpackIgnore: true */ asset.resolvedUrl);
diff --git a/src/native/corehost/browserhost/loader/bootstrap.ts b/src/native/corehost/browserhost/loader/bootstrap.ts
index b2f7843759065e..17f0a407fc3335 100644
--- a/src/native/corehost/browserhost/loader/bootstrap.ts
+++ b/src/native/corehost/browserhost/loader/bootstrap.ts
@@ -11,13 +11,21 @@ const modulesUniqueQuery = queryIndex > 0 ? scriptUrlQuery.substring(queryIndex)
const scriptUrl = normalizeFileUrl(scriptUrlQuery);
const scriptDirectory = normalizeDirectoryUrl(scriptUrl);
-export function locateFile(path: string) {
- if ("URL" in globalThis) {
- return new URL(path, scriptDirectory).toString();
+export function locateFile(path: string, isModule = false): string {
+ let res;
+ if (isPathAbsolute(path)) {
+ res = path;
+ } else if (globalThis.URL) {
+ res = new globalThis.URL(path, scriptDirectory).href;
+ } else {
+ res = scriptDirectory + path;
}
- if (isPathAbsolute(path)) return path;
- return scriptDirectory + path + modulesUniqueQuery;
+ if (isModule) {
+ res += modulesUniqueQuery;
+ }
+
+ return res;
}
function normalizeFileUrl(filename: string) {
diff --git a/src/native/corehost/browserhost/loader/host-builder.ts b/src/native/corehost/browserhost/loader/host-builder.ts
index 465f60ae7fbd7d..bad4e591ff61a6 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/corehost.proj b/src/native/corehost/corehost.proj
index 8b20592f5fd6f8..52214740f05be9 100644
--- a/src/native/corehost/corehost.proj
+++ b/src/native/corehost/corehost.proj
@@ -167,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" />
+
+
+
(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 20a0cc4e766b45..915f4113c9ac81 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, dynamicImport, getDotnetInstance, getGlobalThis, getProperty, getTypeOfProperty, hasProperty, invokeJSFunction, invokeJSImportST, setModuleImports, setProperty } from "./invoke-js";
+import { bindCsFunction, 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");
@@ -15,27 +20,39 @@ export function dotnetInitializeModule(internals: InternalExchange): void {
const runtimeApi = internals[InternalExchangeIndex.RuntimeAPI];
if (typeof runtimeApi !== "object") throw new Error("Expected internals to have RuntimeAPI");
Object.assign(runtimeApi, runtimeApiLocal);
+ Object.assign(runtimeApi.INTERNAL, {
+ hasProperty,
+ getTypeOfProperty,
+ getProperty,
+ setProperty,
+ getGlobalThis,
+ getDotnetInstance,
+ dynamicImport,
+ bindCsFunction
+ });
internals[InternalExchangeIndex.RuntimeExportsTable] = runtimeExportsToTable({
+ bindJSImportST,
+ invokeJSImportST,
+ releaseCSOwnedObject,
+ resolveOrRejectPromise,
+ cancelPromise,
+ invokeJSFunction,
});
dotnetUpdateInternals(internals, dotnetUpdateInternalsSubscriber);
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ initializeMarshalersToJs();
+ initializeMarshalersToCs();
+
function runtimeExportsToTable(map: RuntimeExports): RuntimeExportsTable {
// keep in sync with runtimeExportsFromTable()
return [
+ map.bindJSImportST,
+ map.invokeJSImportST,
+ map.releaseCSOwnedObject,
+ map.resolveOrRejectPromise,
+ map.cancelPromise,
+ map.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..bc7f753add5dff
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-cs.ts
@@ -0,0 +1,363 @@
+// 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 type { BindingClosureCS, BoundMarshalerToCs, CSFnHandle, JSFunctionSignature } from "./types";
+
+import { dotnetAssert, dotnetLogger, Module } from "./cross-module";
+
+import { bindAssemblyExports, invokeJSExport } from "./managed-exports";
+import { allocStackFrame, getSig, getSignatureType, getSignatureArgumentCount, getSignatureVersion, jsInteropState, boundCsFunctionSymbol } from "./marshal";
+import { bindArgMarshalToCs } from "./marshal-to-cs";
+import { bindArgMarshalToJs, endMarshalTaskToJs } from "./marshal-to-js";
+import { assertJsInterop, assertRuntimeRunning, endMeasure, isRuntimeRunning, startMeasure } from "./utils";
+import { MarshalerType, MeasuredBlock } from "./types";
+
+export const exportsByAssembly: Map = new Map();
+
+export async function getAssemblyExports(assemblyName: string): Promise {
+ assertJsInterop();
+ if (assemblyName.endsWith(".dll")) {
+ assemblyName = assemblyName.substring(0, assemblyName.length - 4);
+ }
+ const result = exportsByAssembly.get(assemblyName);
+ if (!result) {
+ await bindAssemblyExports(assemblyName);
+ }
+
+ return exportsByAssembly.get(assemblyName) || {};
+}
+
+export function bindCsFunction(methodHandle: CSFnHandle, assemblyName: string, namespaceName: string, shortClassName: string, methodName: string, signatureHash: number, signature: JSFunctionSignature): void {
+ const fullyQualifiedName = `[${assemblyName}] ${namespaceName}.${shortClassName}:${methodName}`;
+ const mark = startMeasure();
+ dotnetLogger.debug(() => `Binding [JSExport] ${namespaceName}.${shortClassName}:${methodName} from ${assemblyName} assembly`);
+ const version = getSignatureVersion(signature);
+ dotnetAssert.check(version === 2, () => `Signature version ${version} mismatch.`);
+
+
+ const argsCount = getSignatureArgumentCount(signature);
+
+ const argMarshalers: (BoundMarshalerToCs)[] = new Array(argsCount);
+ for (let index = 0; index < argsCount; index++) {
+ const sig = getSig(signature, index + 2);
+ const marshalerType = getSignatureType(sig);
+ const argMarshaler = bindArgMarshalToCs(sig, marshalerType, index + 2);
+ dotnetAssert.check(argMarshaler, "ERR43: argument marshaler must be resolved");
+ argMarshalers[index] = argMarshaler;
+ }
+
+ const resSig = getSig(signature, 1);
+ let resMarshalerType = getSignatureType(resSig);
+
+ const isAsync = resMarshalerType == MarshalerType.Task;
+ const isDiscardNoWait = resMarshalerType == MarshalerType.DiscardNoWait;
+ if (isAsync) {
+ resMarshalerType = MarshalerType.TaskPreCreated;
+ }
+ const resConverter = bindArgMarshalToJs(resSig, resMarshalerType, 1);
+
+ const closure: BindingClosureCS = {
+ methodHandle,
+ fullyQualifiedName,
+ argsCount,
+ argMarshalers,
+ resConverter,
+ isAsync,
+ isDiscardNoWait,
+ isDisposed: false,
+ };
+ let boundFn: Function;
+
+ if (isAsync) {
+ if (argsCount == 1 && resConverter) {
+ boundFn = bindFn_1RA(closure);
+ } else if (argsCount == 2 && resConverter) {
+ boundFn = bindFn_2RA(closure);
+ } else {
+ boundFn = bindFn(closure);
+ }
+ } else if (isDiscardNoWait) {
+ boundFn = bindFn(closure);
+ } else {
+ if (argsCount == 0 && !resConverter) {
+ boundFn = bindFn_0V(closure);
+ } else if (argsCount == 1 && !resConverter) {
+ boundFn = bindFn_1V(closure);
+ } else if (argsCount == 1 && resConverter) {
+ boundFn = bindFn_1R(closure);
+ } else if (argsCount == 2 && resConverter) {
+ boundFn = bindFn_2R(closure);
+ } else {
+ boundFn = bindFn(closure);
+ }
+ }
+
+ // this is just to make debugging easier.
+ // 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 url = `//# sourceURL=https://dotnet/JSExport/${methodName}`;
+ const body = `return (function JSExport_${methodName}(){ return fn.apply(this, arguments)});`;
+ boundFn = new Function("fn", url + "\r\n" + body)(boundFn);
+ } catch (ex) {
+ jsInteropState.cspPolicy = true;
+ }
+ }
+
+ (boundFn)[boundCsFunctionSymbol] = closure;
+
+ walkExportsToSeFunction(assemblyName, namespaceName, shortClassName, methodName, signatureHash, boundFn);
+ endMeasure(mark, MeasuredBlock.bindCsFunction, fullyQualifiedName);
+}
+
+function bindFn_0V(closure: BindingClosureCS) {
+ const method = closure.methodHandle;
+ const fqn = closure.fullyQualifiedName;
+ (closure) = null;
+ return function boundFn_0V() {
+ const mark = startMeasure();
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 2;
+ const args = allocStackFrame(size);
+ // call C# side
+ invokeJSExport(method, args);
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function bindFn_1V(closure: BindingClosureCS) {
+ const method = closure.methodHandle;
+ const marshaler1 = closure.argMarshalers[0]!;
+ const fqn = closure.fullyQualifiedName;
+ return function boundFn_1V(arg1: any) {
+ const mark = startMeasure();
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 3;
+ const args = allocStackFrame(size);
+ marshaler1(args, arg1);
+
+ // call C# side
+ invokeJSExport(method, args);
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function bindFn_1R(closure: BindingClosureCS) {
+ const method = closure.methodHandle;
+ const marshaler1 = closure.argMarshalers[0]!;
+ const resConverter = closure.resConverter!;
+ const fqn = closure.fullyQualifiedName;
+ return function boundFn_1R(arg1: any) {
+ const mark = startMeasure();
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 3;
+ const args = allocStackFrame(size);
+ marshaler1(args, arg1);
+
+ // call C# side
+ invokeJSExport(method, args);
+
+ const jsResult = resConverter(args);
+ return jsResult;
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function bindFn_1RA(closure: BindingClosureCS) {
+ const methodHandle = closure.methodHandle;
+ const marshaler1 = closure.argMarshalers[0]!;
+ const resConverter = closure.resConverter!;
+ const fqn = closure.fullyQualifiedName;
+ (closure) = null;
+ return function bindFn_1RA(arg1: any) {
+ const mark = startMeasure();
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 3;
+ const args = allocStackFrame(size);
+ marshaler1(args, arg1);
+
+ // pre-allocate the promise
+ let promise = resConverter(args);
+
+ // call C# side
+ invokeJSExport(methodHandle, args);
+
+ // in case the C# side returned synchronously
+ promise = endMarshalTaskToJs(args, undefined, promise);
+
+ return promise;
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function bindFn_2R(closure: BindingClosureCS) {
+ const method = closure.methodHandle;
+ const marshaler1 = closure.argMarshalers[0]!;
+ const marshaler2 = closure.argMarshalers[1]!;
+ const resConverter = closure.resConverter!;
+ const fqn = closure.fullyQualifiedName;
+ (closure) = null;
+ return function boundFn_2R(arg1: any, arg2: any) {
+ const mark = startMeasure();
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 4;
+ const args = allocStackFrame(size);
+ marshaler1(args, arg1);
+ marshaler2(args, arg2);
+
+ // call C# side
+ invokeJSExport(method, args);
+
+ const jsResult = resConverter(args);
+ return jsResult;
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function bindFn_2RA(closure: BindingClosureCS) {
+ const methodHandle = closure.methodHandle;
+ const marshaler1 = closure.argMarshalers[0]!;
+ const marshaler2 = closure.argMarshalers[1]!;
+ const resConverter = closure.resConverter!;
+ const fqn = closure.fullyQualifiedName;
+ (closure) = null;
+ return function bindFn_2RA(arg1: any, arg2: any) {
+ const mark = startMeasure();
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 4;
+ const args = allocStackFrame(size);
+ marshaler1(args, arg1);
+ marshaler2(args, arg2);
+
+ // pre-allocate the promise
+ let promise = resConverter(args);
+
+ // call C# side
+ invokeJSExport(methodHandle, args);
+
+ // in case the C# side returned synchronously
+ promise = endMarshalTaskToJs(args, undefined, promise);
+
+ return promise;
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function bindFn(closure: BindingClosureCS) {
+ const argsCount = closure.argsCount;
+ const argMarshalers = closure.argMarshalers;
+ const resConverter = closure.resConverter;
+ const methodHandle = closure.methodHandle;
+ const fqn = closure.fullyQualifiedName;
+ const isAsync = closure.isAsync;
+ const isDiscardNoWait = closure.isDiscardNoWait;
+ return function boundFn(...jsArgs: any[]) {
+ const mark = startMeasure();
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 2 + argsCount;
+ const args = allocStackFrame(size);
+ for (let index = 0; index < argsCount; index++) {
+ const marshaler = argMarshalers[index];
+ if (marshaler) {
+ const jsArg = jsArgs[index];
+ marshaler(args, jsArg);
+ }
+ }
+ let jsResult = undefined;
+ if (isAsync) {
+ // pre-allocate the promise
+ jsResult = resConverter!(args);
+ }
+
+ // call C# side
+ if (isAsync) {
+ invokeJSExport(methodHandle, args);
+ // in case the C# side returned synchronously
+ jsResult = endMarshalTaskToJs(args, undefined, jsResult);
+ } else if (isDiscardNoWait) {
+ // call C# side, fire and forget
+ invokeJSExport(methodHandle, args);
+ } else {
+ invokeJSExport(methodHandle, args);
+ if (resConverter) {
+ jsResult = resConverter(args);
+ }
+ }
+ return jsResult;
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+
+ endMeasure(mark, MeasuredBlock.callCsFunction, fqn);
+ }
+ };
+}
+
+function walkExportsToSeFunction(assembly: string, namespace: string, classname: string, methodname: string, signatureHash: number, fn: Function): void {
+ const parts = `${namespace}.${classname}`.replace(/\+/g, ".").replace(/\//g, ".").split(".");
+ let scope: any = undefined;
+ let assemblyScope = exportsByAssembly.get(assembly);
+ if (!assemblyScope) {
+ assemblyScope = {};
+ exportsByAssembly.set(assembly, assemblyScope);
+ exportsByAssembly.set(assembly + ".dll", assemblyScope);
+ }
+ scope = assemblyScope;
+ for (let i = 0; i < parts.length; i++) {
+ const part = parts[i];
+ if (part != "") {
+ let newscope = scope[part];
+ if (typeof newscope === "undefined") {
+ newscope = {};
+ scope[part] = newscope;
+ }
+ dotnetAssert.check(newscope, () => `${part} not found while looking up ${classname}`);
+ scope = newscope;
+ }
+ }
+
+ if (!scope[methodname]) {
+ scope[methodname] = fn;
+ }
+ scope[`${methodname}.${signatureHash}`] = fn;
+}
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..4ff7ff69ff3af0
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/invoke-js.ts
@@ -0,0 +1,359 @@
+// 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 { BindingClosureJS, 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";
+import { wrapAsCancelablePromise } from "./cancelable-promise";
+
+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: BindingClosureJS = {
+ 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: BindingClosureJS) {
+ 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: BindingClosureJS) {
+ 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: BindingClosureJS) {
+ 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: BindingClosureJS) {
+ 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: BindingClosureJS) {
+ 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);
+}
+
+export function setProperty(self: any, name: string, value: any): void {
+ dotnetAssert.check(self, "Null reference");
+ self[name] = value;
+}
+
+export function getProperty(self: any, name: string): any {
+ dotnetAssert.check(self, "Null reference");
+ return self[name];
+}
+
+export function hasProperty(self: any, name: string): boolean {
+ dotnetAssert.check(self, "Null reference");
+ return name in self;
+}
+
+export function getTypeOfProperty(self: any, name: string): string {
+ dotnetAssert.check(self, "Null reference");
+ return typeof self[name];
+}
+
+export function getGlobalThis(): any {
+ return globalThis;
+}
+
+export function getDotnetInstance(): any {
+ return dotnetApi;
+}
+
+export function dynamicImport(module_name: string, module_url: string): Promise {
+ assertJsInterop();
+ dotnetAssert.check(module_name && typeof module_name === "string", "module_name must be string");
+ dotnetAssert.check(module_url && typeof module_url === "string", "module_url must be string");
+ let promise = importedModulesPromises.get(module_name);
+ const newPromise = !promise;
+ if (newPromise) {
+ dotnetLogger.debug(() => `importing ES6 module '${module_name}' from '${module_url}'`);
+ promise = import(/*! webpackIgnore: true */module_url);
+ importedModulesPromises.set(module_name, promise);
+ }
+
+ return wrapAsCancelablePromise(async function register() {
+ const module = await promise;
+ if (newPromise) {
+ importedModules.set(module_name, module);
+ dotnetLogger.debug(() => `imported ES6 module '${module_name}' from '${module_url}'`);
+ }
+ return module;
+ });
+}
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..84028574cba352
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/managed-exports.ts
@@ -0,0 +1,160 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import type { JSMarshalerArguments, GCHandle, MarshalerToCs, MarshalerToJs, CSFnHandle } from "./types";
+
+import { dotnetAssert, dotnetInteropJSExports, Module } from "./cross-module";
+import { allocStackFrame, getArg, isArgsException, setArgType, setGcHandle } from "./marshal";
+import { marshalExceptionToCs, marshalStringToCs } from "./marshal-to-cs";
+import { beginMarshalTaskToJs, endMarshalTaskToJs, marshalExceptionToJs, marshalInt32ToJs, marshalStringToJs } from "./marshal-to-js";
+import { assertJsInterop, assertRuntimeRunning, isRuntimeRunning } from "./utils";
+import { MarshalerType } from "./types";
+
+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);
+ }
+}
+
+export function releaseJsOwnedObjectByGcHandle(gcHandle: GCHandle) {
+ dotnetAssert.check(gcHandle, "Must be valid gcHandle");
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 3;
+ const args = allocStackFrame(size);
+ const arg1 = getArg(args, 2);
+ setArgType(arg1, MarshalerType.Object);
+ setGcHandle(arg1, gcHandle);
+ // this must stay synchronous for freeGcvHandle sake, to not use-after-free
+ // also on JSWebWorker, because the message could arrive after the worker is terminated and the GCHandle of JSProxyContext is already freed
+ dotnetInteropJSExports.SystemInteropJS_ReleaseJSOwnedObjectByGCHandle(args);
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+
+ }
+}
+
+export function callDelegate(callbackGcHandle: GCHandle, arg1Js: any, arg2Js: any, arg3Js: any, resConverter?: MarshalerToJs, arg1Converter?: MarshalerToCs, arg2Converter?: MarshalerToCs, arg3Converter?: MarshalerToCs) {
+ assertRuntimeRunning();
+
+ const sp = Module.stackSave();
+ try {
+ const size = 6;
+ const args = allocStackFrame(size);
+
+ const arg1 = getArg(args, 2);
+ setArgType(arg1, MarshalerType.Object);
+ setGcHandle(arg1, callbackGcHandle);
+ // payload arg numbers are shifted by one, the real first is a gcHandle of the callback
+
+ if (arg1Converter) {
+ const arg2 = getArg(args, 3);
+ arg1Converter(arg2, arg1Js);
+ }
+ if (arg2Converter) {
+ const arg3 = getArg(args, 4);
+ arg2Converter(arg3, arg2Js);
+ }
+ if (arg3Converter) {
+ const arg4 = getArg(args, 5);
+ arg3Converter(arg4, arg3Js);
+ }
+
+ dotnetInteropJSExports.SystemInteropJS_CallDelegate(args);
+ if (isArgsException(args)) {
+ const exc = getArg(args, 0);
+ throw marshalExceptionToJs(exc);
+ }
+
+ if (resConverter) {
+ const res = getArg(args, 1);
+ return resConverter(res);
+ }
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+
+ }
+}
+
+export function completeTask(holderGcHandle: GCHandle, error?: any, data?: any, resConverter?: MarshalerToCs) {
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 5;
+ const args = allocStackFrame(size);
+ const arg1 = getArg(args, 2);
+ setArgType(arg1, MarshalerType.Object);
+ setGcHandle(arg1, holderGcHandle);
+ const arg2 = getArg(args, 3);
+ if (error) {
+ marshalExceptionToCs(arg2, error);
+ } else {
+ setArgType(arg2, MarshalerType.None);
+ const arg3 = getArg(args, 4);
+ dotnetAssert.check(resConverter, "resConverter missing");
+ resConverter(arg3, data);
+ }
+ dotnetInteropJSExports.SystemInteropJS_CompleteTask(args);
+ } finally {
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+
+ }
+}
+
+export function bindAssemblyExports(assemblyName: string): Promise {
+ assertRuntimeRunning();
+ const sp = Module.stackSave();
+ try {
+ const size = 3;
+ const args = allocStackFrame(size);
+ const res = getArg(args, 1);
+ const arg1 = getArg(args, 2);
+ marshalStringToCs(arg1, assemblyName);
+
+ // because this is async, we could pre-allocate the promise
+ let promise = beginMarshalTaskToJs(res, MarshalerType.TaskPreCreated);
+
+ dotnetInteropJSExports.SystemInteropJS_BindAssemblyExports(args);
+ if (isArgsException(args)) {
+ // TODO free pre-created promise
+ const exc = getArg(args, 0);
+ throw marshalExceptionToJs(exc);
+ }
+
+ // in case the C# side returned synchronously
+ promise = endMarshalTaskToJs(args, marshalInt32ToJs, promise);
+
+ if (promise === null || promise === undefined) {
+ promise = Promise.resolve();
+ }
+ return promise;
+ } finally {
+ // synchronously
+ if (isRuntimeRunning()) Module.stackRestore(sp);
+ }
+}
+
+export function invokeJSExport(methodHandle: CSFnHandle, args: JSMarshalerArguments): void {
+ assertJsInterop();
+ // TODO methodHandle
+ dotnetInteropJSExports.SystemInteropJS_CallJSExport(methodHandle, args);
+ if (isArgsException(args)) {
+ const exc = getArg(args, 0);
+ throw marshalExceptionToJs(exc);
+ }
+}
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..01eb3677452bfb
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal-to-cs.ts
@@ -0,0 +1,510 @@
+// 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);
+ }
+}
+
+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));
+}
+
+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..1f34f47b027846
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal-to-js.ts
@@ -0,0 +1,552 @@
+// 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;
+}
+
+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;
+}
+
+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..3f5071730a7612
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts
@@ -0,0 +1,331 @@
+// 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,
+ managedThreadTID: 0 as any as PThreadPtr,
+};
+
+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.getHeapU32(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..3028918a9026ff
--- /dev/null
+++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshaled-types.ts
@@ -0,0 +1,168 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { dotnetApi, dotnetAssert } from "./cross-module";
+
+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 471f9b3259c553..6f257a7a2dfbd4 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,8 +19,167 @@ 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 CSFnHandle = {
+ __brand: "CSFnHandle"
+}
+export interface JSFunctionSignature extends NativePointer {
+ __brand: "JSFunctionSignatures"
+}
+
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 BindingClosureJS = {
+ fn: Function,
+ fqn: string,
+ isDisposed: boolean,
+ argsCount: number,
+ argMarshalers: (BoundMarshalerToJs)[],
+ resConverter: BoundMarshalerToCs | undefined,
+ hasCleanup: boolean,
+ isDiscardNoWait: boolean,
+ isAsync: boolean,
+ argCleanup: (Function | undefined)[]
+}
+
+export type BindingClosureCS = {
+ fullyQualifiedName: string,
+ argsCount: number,
+ methodHandle: CSFnHandle,
+ argMarshalers: (BoundMarshalerToCs)[],
+ resConverter: BoundMarshalerToJs | undefined,
+ isAsync: boolean,
+ isDiscardNoWait: boolean,
+ isDisposed: boolean,
+}
+
+
+// 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/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..5967d0936ed5b0 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,14 @@
const exports = {};
libInteropJavaScriptNative(exports);
- let commonDeps = ["$DOTNET"];
+ let commonDeps = ["$DOTNET",
+ "SystemInteropJS_GetManagedStackTrace",
+ "SystemInteropJS_CallDelegate",
+ "SystemInteropJS_CompleteTask",
+ "SystemInteropJS_ReleaseJSOwnedObjectByGCHandle",
+ "SystemInteropJS_BindAssemblyExports",
+ "SystemInteropJS_CallJSExport"
+ ];
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..d1c7409d46d9d6 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,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
import { } from "../../Common/JavaScript/cross-linked";
+import type { CSFnHandle, JSMarshalerArguments } from "../interop/types";
+declare global {
+ export function _SystemInteropJS_GetManagedStackTrace(args: JSMarshalerArguments): void;
+ export function _SystemInteropJS_CallDelegate(args: JSMarshalerArguments): void;
+ export function _SystemInteropJS_CompleteTask(args: JSMarshalerArguments): void;
+ export function _SystemInteropJS_ReleaseJSOwnedObjectByGCHandle(args: JSMarshalerArguments): void;
+ export function _SystemInteropJS_BindAssemblyExports(args: JSMarshalerArguments): void;
+ export function _SystemInteropJS_CallJSExport(methodHandle: CSFnHandle, 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..573531baacdb93 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,52 @@
// 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),
+ SystemInteropJS_CallDelegate: (args) => _SystemInteropJS_CallDelegate(args),
+ SystemInteropJS_CompleteTask: (args) => _SystemInteropJS_CompleteTask(args),
+ SystemInteropJS_ReleaseJSOwnedObjectByGCHandle: (args) => _SystemInteropJS_ReleaseJSOwnedObjectByGCHandle(args),
+ SystemInteropJS_BindAssemblyExports: (args) => _SystemInteropJS_BindAssemblyExports(args),
+ SystemInteropJS_CallJSExport: (methodHandle, args) => _SystemInteropJS_CallJSExport(methodHandle, args),
});
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
function interopJavaScriptExportsToTable(map: InteropJavaScriptExports): InteropJavaScriptExportsTable {
// keep in sync with interopJavaScriptExportsFromTable()
return [
+ map.SystemInteropJS_GetManagedStackTrace,
+ map.SystemInteropJS_CallDelegate,
+ map.SystemInteropJS_CompleteTask,
+ map.SystemInteropJS_ReleaseJSOwnedObjectByGCHandle,
+ map.SystemInteropJS_BindAssemblyExports,
+ map.SystemInteropJS_CallJSExport,
];
}
dotnetUpdateInternals(internals, dotnetUpdateInternalsSubscriber);