mirror of https://github.com/dotnet/runtime
[browser] introduce JSProxyContext (#95959)
This commit is contained in:
parent
5cf6892d8d
commit
8bd23f0792
|
@ -187,10 +187,6 @@ node_modules/
|
||||||
*.metaproj
|
*.metaproj
|
||||||
*.metaproj.tmp
|
*.metaproj.tmp
|
||||||
bin.localpkg/
|
bin.localpkg/
|
||||||
src/mono/wasm/runtime/dotnet.d.ts.sha256
|
|
||||||
src/mono/wasm/runtime/dotnet-legacy.d.ts.sha256
|
|
||||||
|
|
||||||
src/mono/sample/wasm/browser-nextjs/public/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
# RIA/Silverlight projects
|
||||||
Generated_Code/
|
Generated_Code/
|
||||||
|
|
|
@ -34,7 +34,7 @@ internal static partial class Interop
|
||||||
|
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
public static extern void InstallWebWorkerInterop();
|
public static extern void InstallWebWorkerInterop(IntPtr proxyContextGCHandle);
|
||||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||||
public static extern void UninstallWebWorkerInterop();
|
public static extern void UninstallWebWorkerInterop();
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
|
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
|
||||||
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TargetsWindows</DefineConstants>
|
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'windows'">$(DefineConstants);TargetsWindows</DefineConstants>
|
||||||
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGETS_BROWSER</DefineConstants>
|
<DefineConstants Condition="'$(TargetPlatformIdentifier)' == 'browser'">$(DefineConstants);TARGETS_BROWSER</DefineConstants>
|
||||||
|
<!-- Active issue https://github.com/dotnet/runtime/issues/96173 -->
|
||||||
|
<_XUnitBackgroundExec>false</_XUnitBackgroundExec>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetOS)' == 'browser'">
|
<PropertyGroup Condition="'$(TargetOS)' == 'browser'">
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
|
|
||||||
<!-- This WASM test is problematic and slow right now. This sets the xharness timeout but there is also override in sendtohelix-browser.targets -->
|
<!-- This WASM test is problematic and slow right now. This sets the xharness timeout but there is also override in sendtohelix-browser.targets -->
|
||||||
<WasmXHarnessTestsTimeout>01:15:00</WasmXHarnessTestsTimeout>
|
<WasmXHarnessTestsTimeout>01:15:00</WasmXHarnessTestsTimeout>
|
||||||
|
|
||||||
|
<!-- Active issue https://github.com/dotnet/runtime/issues/96173 -->
|
||||||
|
<_XUnitBackgroundExec>false</_XUnitBackgroundExec>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -21,6 +21,7 @@ namespace Microsoft.Interop.JavaScript
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO order parameters in such way that affinity capturing parameters are emitted first
|
||||||
public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext context)
|
public override IEnumerable<StatementSyntax> Generate(TypePositionInfo info, StubCodeContext context)
|
||||||
{
|
{
|
||||||
string argName = context.GetAdditionalIdentifier(info, "js_arg");
|
string argName = context.GetAdditionalIdentifier(info, "js_arg");
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
<Compile Include="System\Runtime\InteropServices\JavaScript\JSImportAttribute.cs" />
|
<Compile Include="System\Runtime\InteropServices\JavaScript\JSImportAttribute.cs" />
|
||||||
<Compile Include="System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" />
|
<Compile Include="System\Runtime\InteropServices\JavaScript\CancelablePromise.cs" />
|
||||||
<Compile Include="System\Runtime\InteropServices\JavaScript\SynchronizationContextExtensions.cs" />
|
<Compile Include="System\Runtime\InteropServices\JavaScript\SynchronizationContextExtensions.cs" />
|
||||||
|
<Compile Include="System\Runtime\InteropServices\JavaScript\JSProxyContext.cs" />
|
||||||
|
|
||||||
<Compile Include="System\Runtime\InteropServices\JavaScript\MarshalerType.cs" />
|
<Compile Include="System\Runtime\InteropServices\JavaScript\MarshalerType.cs" />
|
||||||
<Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.BigInt64.cs" />
|
<Compile Include="System\Runtime\InteropServices\JavaScript\Marshaling\JSMarshalerArgument.BigInt64.cs" />
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace System.Runtime.InteropServices.JavaScript
|
namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
@ -21,13 +22,24 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder;
|
JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder;
|
||||||
if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise");
|
if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise");
|
||||||
|
|
||||||
|
#if !FEATURE_WASM_THREADS
|
||||||
#if FEATURE_WASM_THREADS
|
if (holder.IsDisposed)
|
||||||
holder.SynchronizationContext!.Send(static (JSHostImplementation.PromiseHolder holder) =>
|
|
||||||
{
|
{
|
||||||
#endif
|
return;
|
||||||
|
}
|
||||||
_CancelPromise(holder.GCHandle);
|
_CancelPromise(holder.GCHandle);
|
||||||
#if FEATURE_WASM_THREADS
|
#else
|
||||||
|
holder.ProxyContext.SynchronizationContext.Post(static (object? h) =>
|
||||||
|
{
|
||||||
|
var holder = (JSHostImplementation.PromiseHolder)h!;
|
||||||
|
lock (holder.ProxyContext)
|
||||||
|
{
|
||||||
|
if (holder.IsDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_CancelPromise(holder.GCHandle);
|
||||||
}, holder);
|
}, holder);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -42,15 +54,27 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder;
|
JSHostImplementation.PromiseHolder? holder = promise.AsyncState as JSHostImplementation.PromiseHolder;
|
||||||
if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise");
|
if (holder == null) throw new InvalidOperationException("Expected Task converted from JS Promise");
|
||||||
|
|
||||||
|
#if !FEATURE_WASM_THREADS
|
||||||
#if FEATURE_WASM_THREADS
|
if (holder.IsDisposed)
|
||||||
holder.SynchronizationContext!.Send((JSHostImplementation.PromiseHolder holder) =>
|
|
||||||
{
|
{
|
||||||
#endif
|
return;
|
||||||
|
}
|
||||||
|
_CancelPromise(holder.GCHandle);
|
||||||
|
callback.Invoke(state);
|
||||||
|
#else
|
||||||
|
holder.ProxyContext.SynchronizationContext.Post(_ =>
|
||||||
|
{
|
||||||
|
lock (holder.ProxyContext)
|
||||||
|
{
|
||||||
|
if (holder.IsDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_CancelPromise(holder.GCHandle);
|
_CancelPromise(holder.GCHandle);
|
||||||
callback.Invoke(state);
|
callback.Invoke(state);
|
||||||
#if FEATURE_WASM_THREADS
|
}, null);
|
||||||
}, holder);
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,11 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // initialized and set by caller
|
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // initialized and set by caller
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
// when we arrive here, we are on the thread which owns the proxies
|
||||||
|
arg_exc.AssertCurrentThreadContext();
|
||||||
|
#endif
|
||||||
|
|
||||||
arg_1.ToManaged(out IntPtr entrypointPtr);
|
arg_1.ToManaged(out IntPtr entrypointPtr);
|
||||||
if (entrypointPtr == IntPtr.Zero)
|
if (entrypointPtr == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
|
@ -103,6 +108,10 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3];
|
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
// when we arrive here, we are on the thread which owns the proxies
|
||||||
|
arg_exc.AssertCurrentThreadContext();
|
||||||
|
#endif
|
||||||
arg_1.ToManaged(out byte[]? dllBytes);
|
arg_1.ToManaged(out byte[]? dllBytes);
|
||||||
arg_2.ToManaged(out byte[]? pdbBytes);
|
arg_2.ToManaged(out byte[]? pdbBytes);
|
||||||
|
|
||||||
|
@ -121,6 +130,10 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];
|
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
// when we arrive here, we are on the thread which owns the proxies
|
||||||
|
arg_exc.AssertCurrentThreadContext();
|
||||||
|
#endif
|
||||||
arg_1.ToManaged(out byte[]? dllBytes);
|
arg_1.ToManaged(out byte[]? dllBytes);
|
||||||
|
|
||||||
if (dllBytes != null)
|
if (dllBytes != null)
|
||||||
|
@ -140,32 +153,12 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
|
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
|
||||||
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; // initialized and set by caller
|
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2]; // initialized and set by caller
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var gcHandle = arg_1.slot.GCHandle;
|
// when we arrive here, we are on the thread which owns the proxies
|
||||||
if (IsGCVHandle(gcHandle))
|
var ctx = arg_exc.AssertCurrentThreadContext();
|
||||||
{
|
ctx.ReleaseJSOwnedObjectByGCHandle(arg_1.slot.GCHandle);
|
||||||
if (ThreadJsOwnedHolders.Remove(gcHandle, out PromiseHolder? holder))
|
|
||||||
{
|
|
||||||
holder.GCHandle = IntPtr.Zero;
|
|
||||||
holder.Callback!(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GCHandle handle = (GCHandle)gcHandle;
|
|
||||||
var target = handle.Target!;
|
|
||||||
if (target is PromiseHolder holder)
|
|
||||||
{
|
|
||||||
holder.GCHandle = IntPtr.Zero;
|
|
||||||
holder.Callback!(null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ThreadJsOwnedObjects.Remove(target);
|
|
||||||
}
|
|
||||||
handle.Free();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -185,6 +178,11 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// arg_4 set by JS caller when there are arguments
|
// arg_4 set by JS caller when there are arguments
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
// when we arrive here, we are on the thread which owns the proxies
|
||||||
|
arg_exc.AssertCurrentThreadContext();
|
||||||
|
#endif
|
||||||
|
|
||||||
GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle;
|
GCHandle callback_gc_handle = (GCHandle)arg_1.slot.GCHandle;
|
||||||
if (callback_gc_handle.Target is ToManagedCallback callback)
|
if (callback_gc_handle.Target is ToManagedCallback callback)
|
||||||
{
|
{
|
||||||
|
@ -210,34 +208,34 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
|
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
|
||||||
// arg_2 set by caller when this is SetException call
|
// arg_2 set by caller when this is SetException call
|
||||||
// arg_3 set by caller when this is SetResult call
|
// arg_3 set by caller when this is SetResult call
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var holderGCHandle = arg_1.slot.GCHandle;
|
// when we arrive here, we are on the thread which owns the proxies
|
||||||
if (IsGCVHandle(holderGCHandle))
|
var ctx = arg_exc.AssertCurrentThreadContext();
|
||||||
|
var holder = ctx.GetPromiseHolder(arg_1.slot.GCHandle);
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
lock (ctx)
|
||||||
{
|
{
|
||||||
if (ThreadJsOwnedHolders.Remove(holderGCHandle, out PromiseHolder? holder))
|
if (holder.Callback == null)
|
||||||
{
|
{
|
||||||
holder.GCHandle = IntPtr.Zero;
|
holder.CallbackReady = new ManualResetEventSlim(false);
|
||||||
// arg_2, arg_3 are processed by the callback
|
|
||||||
holder.Callback!(arguments_buffer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
if (holder.CallbackReady != null)
|
||||||
{
|
{
|
||||||
GCHandle handle = (GCHandle)holderGCHandle;
|
#pragma warning disable CA1416 // Validate platform compatibility
|
||||||
var target = handle.Target!;
|
holder.CallbackReady?.Wait();
|
||||||
if (target is PromiseHolder holder)
|
#pragma warning restore CA1416 // Validate platform compatibility
|
||||||
{
|
|
||||||
holder.GCHandle = IntPtr.Zero;
|
|
||||||
// arg_2, arg_3 are processed by the callback
|
|
||||||
holder.Callback!(arguments_buffer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ThreadJsOwnedObjects.Remove(target);
|
|
||||||
}
|
|
||||||
handle.Free();
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
var callback = holder.Callback!;
|
||||||
|
ctx.ReleasePromiseHolder(arg_1.slot.GCHandle);
|
||||||
|
|
||||||
|
// arg_2, arg_3 are processed by the callback
|
||||||
|
// JSProxyContext.PopOperation() is called by the callback
|
||||||
|
callback!(arguments_buffer);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -254,6 +252,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
|
ref JSMarshalerArgument arg_1 = ref arguments_buffer[2];// initialized and set by caller
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// when we arrive here, we are on the thread which owns the proxies
|
||||||
|
arg_exc.AssertCurrentThreadContext();
|
||||||
|
|
||||||
GCHandle exception_gc_handle = (GCHandle)arg_1.slot.GCHandle;
|
GCHandle exception_gc_handle = (GCHandle)arg_1.slot.GCHandle;
|
||||||
if (exception_gc_handle.Target is Exception exception)
|
if (exception_gc_handle.Target is Exception exception)
|
||||||
{
|
{
|
||||||
|
@ -275,17 +276,10 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// this is here temporarily, until JSWebWorker becomes public API
|
// this is here temporarily, until JSWebWorker becomes public API
|
||||||
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")]
|
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicMethods, "System.Runtime.InteropServices.JavaScript.JSWebWorker", "System.Runtime.InteropServices.JavaScript")]
|
||||||
// the marshaled signature is:
|
// the marshaled signature is:
|
||||||
// void InstallSynchronizationContext()
|
// void InstallMainSynchronizationContext()
|
||||||
public static void InstallSynchronizationContext (JSMarshalerArgument* arguments_buffer) {
|
public static void InstallMainSynchronizationContext()
|
||||||
ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame()
|
{
|
||||||
try
|
InstallWebWorkerInterop(true);
|
||||||
{
|
|
||||||
InstallWebWorkerInterop(true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
arg_exc.ToJS(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -7,19 +7,6 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
internal static unsafe partial class JavaScriptImports
|
internal static unsafe partial class JavaScriptImports
|
||||||
{
|
{
|
||||||
public static void ResolveOrRejectPromise(Span<JSMarshalerArgument> arguments)
|
|
||||||
{
|
|
||||||
fixed (JSMarshalerArgument* ptr = arguments)
|
|
||||||
{
|
|
||||||
Interop.Runtime.ResolveOrRejectPromise(ptr);
|
|
||||||
ref JSMarshalerArgument exceptionArg = ref arguments[0];
|
|
||||||
if (exceptionArg.slot.Type != MarshalerType.None)
|
|
||||||
{
|
|
||||||
JSHostImplementation.ThrowException(ref exceptionArg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !DISABLE_LEGACY_JS_INTEROP
|
#if !DISABLE_LEGACY_JS_INTEROP
|
||||||
#region legacy
|
#region legacy
|
||||||
|
|
||||||
|
|
|
@ -32,17 +32,10 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
|
||||||
public static void GetCSOwnedObjectByJSHandleRef(nint jsHandle, int shouldAddInflight, out JSObject? result)
|
public static void GetCSOwnedObjectByJSHandleRef(nint jsHandle, int shouldAddInflight, out JSObject? result)
|
||||||
{
|
{
|
||||||
if (JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference))
|
#if FEATURE_WASM_THREADS
|
||||||
{
|
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
||||||
reference.TryGetTarget(out JSObject? jsObject);
|
#endif
|
||||||
if (shouldAddInflight != 0)
|
result = JSProxyContext.MainThreadContext.GetCSOwnedObjectByJSHandle(jsHandle, shouldAddInflight);
|
||||||
{
|
|
||||||
jsObject?.AddInFlight();
|
|
||||||
}
|
|
||||||
result = jsObject;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
result = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntPtr GetCSOwnedObjectJSHandleRef(in JSObject jsObject, int shouldAddInflight)
|
public static IntPtr GetCSOwnedObjectJSHandleRef(in JSObject jsObject, int shouldAddInflight)
|
||||||
|
@ -71,32 +64,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
||||||
#endif
|
#endif
|
||||||
|
jsObject = JSProxyContext.MainThreadContext.CreateCSOwnedProxy(jsHandle, mappedType, shouldAddInflight);
|
||||||
JSObject? res = null;
|
|
||||||
|
|
||||||
if (!JSHostImplementation.ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
|
|
||||||
!reference.TryGetTarget(out res) ||
|
|
||||||
res.IsDisposed)
|
|
||||||
{
|
|
||||||
#pragma warning disable CS0612 // Type or member is obsolete
|
|
||||||
res = mappedType switch
|
|
||||||
{
|
|
||||||
LegacyHostImplementation.MappedType.JSObject => new JSObject(jsHandle),
|
|
||||||
LegacyHostImplementation.MappedType.Array => new Array(jsHandle),
|
|
||||||
LegacyHostImplementation.MappedType.ArrayBuffer => new ArrayBuffer(jsHandle),
|
|
||||||
LegacyHostImplementation.MappedType.DataView => new DataView(jsHandle),
|
|
||||||
LegacyHostImplementation.MappedType.Function => new Function(jsHandle),
|
|
||||||
LegacyHostImplementation.MappedType.Uint8Array => new Uint8Array(jsHandle),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(mappedType))
|
|
||||||
};
|
|
||||||
#pragma warning restore CS0612 // Type or member is obsolete
|
|
||||||
JSHostImplementation.ThreadCsOwnedObjects[jsHandle] = new WeakReference<JSObject>(res, trackResurrection: true);
|
|
||||||
}
|
|
||||||
if (shouldAddInflight != 0)
|
|
||||||
{
|
|
||||||
res.AddInFlight();
|
|
||||||
}
|
|
||||||
jsObject = res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void GetJSOwnedObjectByGCHandleRef(int gcHandle, out object result)
|
public static void GetJSOwnedObjectByGCHandleRef(int gcHandle, out object result)
|
||||||
|
@ -107,7 +75,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
|
||||||
public static IntPtr GetJSOwnedObjectGCHandleRef(in object obj)
|
public static IntPtr GetJSOwnedObjectGCHandleRef(in object obj)
|
||||||
{
|
{
|
||||||
return JSHostImplementation.GetJSOwnedObjectGCHandle(obj, GCHandleType.Normal);
|
return JSProxyContext.MainThreadContext.GetJSOwnedObjectGCHandle(obj, GCHandleType.Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntPtr CreateTaskSource()
|
public static IntPtr CreateTaskSource()
|
||||||
|
|
|
@ -47,10 +47,10 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
var currentTID = JSSynchronizationContext.CurrentJSSynchronizationContext?.TargetTID;
|
if (!jsException.ProxyContext.IsCurrentThread())
|
||||||
if (jsException.OwnerTID != currentTID)
|
|
||||||
{
|
{
|
||||||
return bs;
|
// if we are on another thread, it would be too expensive and risky to obtain lazy stack trace.
|
||||||
|
return bs + Environment.NewLine + "... omitted JavaScript stack trace from another thread.";
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
string? jsStackTrace = jsException.GetPropertyAsString("stack");
|
string? jsStackTrace = jsException.GetPropertyAsString("stack");
|
||||||
|
|
|
@ -30,9 +30,6 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
internal static volatile uint nextImportHandle = 1;
|
internal static volatile uint nextImportHandle = 1;
|
||||||
internal int ImportHandle;
|
internal int ImportHandle;
|
||||||
internal bool IsAsync;
|
internal bool IsAsync;
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
internal bool IsThreadCaptured;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
internal struct JSBindingHeader
|
internal struct JSBindingHeader
|
||||||
|
@ -177,7 +174,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
if (RuntimeInformation.OSArchitecture != Architecture.Wasm)
|
if (RuntimeInformation.OSArchitecture != Architecture.Wasm)
|
||||||
throw new PlatformNotSupportedException();
|
throw new PlatformNotSupportedException();
|
||||||
|
|
||||||
return BindJSFunctionImpl(functionName, moduleName, signatures);
|
return BindJSImportImpl(functionName, moduleName, signatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -220,15 +217,19 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments)
|
internal static unsafe void InvokeJSImportImpl(JSFunctionBinding signature, Span<JSMarshalerArgument> arguments)
|
||||||
{
|
{
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
var targetContext = JSProxyContext.SealJSImportCapturing();
|
||||||
|
JSProxyContext.AssertIsInteropThread();
|
||||||
|
arguments[0].slot.ContextHandle = targetContext.ContextHandle;
|
||||||
|
arguments[1].slot.ContextHandle = targetContext.ContextHandle;
|
||||||
|
#else
|
||||||
|
var targetContext = JSProxyContext.MainThreadContext;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (signature.IsAsync)
|
if (signature.IsAsync)
|
||||||
{
|
{
|
||||||
// pre-allocate the result handle and Task
|
// pre-allocate the result handle and Task
|
||||||
#if FEATURE_WASM_THREADS
|
var holder = new JSHostImplementation.PromiseHolder(targetContext);
|
||||||
JSSynchronizationContext.AssertWebWorkerContext();
|
|
||||||
var holder = new JSHostImplementation.PromiseHolder(JSSynchronizationContext.CurrentJSSynchronizationContext!);
|
|
||||||
#else
|
|
||||||
var holder = new JSHostImplementation.PromiseHolder();
|
|
||||||
#endif
|
|
||||||
arguments[1].slot.Type = MarshalerType.TaskPreCreated;
|
arguments[1].slot.Type = MarshalerType.TaskPreCreated;
|
||||||
arguments[1].slot.GCHandle = holder.GCHandle;
|
arguments[1].slot.GCHandle = holder.GCHandle;
|
||||||
}
|
}
|
||||||
|
@ -253,10 +254,10 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static unsafe JSFunctionBinding BindJSFunctionImpl(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures)
|
internal static unsafe JSFunctionBinding BindJSImportImpl(string functionName, string moduleName, ReadOnlySpan<JSMarshalerType> signatures)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
JSSynchronizationContext.AssertWebWorkerContext();
|
JSProxyContext.AssertIsInteropThread();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var signature = JSHostImplementation.GetMethodSignature(signatures, functionName, moduleName);
|
var signature = JSHostImplementation.GetMethodSignature(signatures, functionName, moduleName);
|
||||||
|
@ -284,5 +285,19 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal static unsafe void ResolveOrRejectPromise(Span<JSMarshalerArgument> arguments)
|
||||||
|
{
|
||||||
|
fixed (JSMarshalerArgument* ptr = arguments)
|
||||||
|
{
|
||||||
|
Interop.Runtime.ResolveOrRejectPromise(ptr);
|
||||||
|
ref JSMarshalerArgument exceptionArg = ref arguments[0];
|
||||||
|
if (exceptionArg.slot.Type != MarshalerType.None)
|
||||||
|
{
|
||||||
|
JSHostImplementation.ThrowException(ref exceptionArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
JSSynchronizationContext.AssertWebWorkerContext();
|
JSProxyContext.AssertIsInteropThread();
|
||||||
#endif
|
#endif
|
||||||
return JavaScriptImports.GetGlobalThis();
|
return JavaScriptImports.GetGlobalThis();
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
JSSynchronizationContext.AssertWebWorkerContext();
|
JSProxyContext.AssertIsInteropThread();
|
||||||
#endif
|
#endif
|
||||||
return JavaScriptImports.GetDotnetInstance();
|
return JavaScriptImports.GetDotnetInstance();
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
public static Task<JSObject> ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken = default)
|
public static Task<JSObject> ImportAsync(string moduleName, string moduleUrl, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
JSSynchronizationContext.AssertWebWorkerContext();
|
JSProxyContext.AssertIsInteropThread();
|
||||||
#endif
|
#endif
|
||||||
return JSHostImplementation.ImportAsync(moduleName, moduleUrl, cancellationToken);
|
return JSHostImplementation.ImportAsync(moduleName, moduleUrl, cancellationToken);
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
return JSSynchronizationContext.CurrentJSSynchronizationContext ?? JSSynchronizationContext.MainJSSynchronizationContext!;
|
return (JSProxyContext.ExecutionContext ?? JSProxyContext.MainThreadContext).SynchronizationContext;
|
||||||
#else
|
#else
|
||||||
return null!;
|
return null!;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -12,34 +12,24 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
|
||||||
public sealed class PromiseHolder
|
public sealed class PromiseHolder
|
||||||
{
|
{
|
||||||
public nint GCHandle; // could be also virtual GCVHandle
|
public readonly nint GCHandle; // could be also virtual GCVHandle
|
||||||
public ToManagedCallback? Callback;
|
public ToManagedCallback? Callback;
|
||||||
|
public JSProxyContext ProxyContext;
|
||||||
|
public bool IsDisposed;
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
// the JavaScript object could only exist on the single web worker and can't migrate to other workers
|
public ManualResetEventSlim? CallbackReady;
|
||||||
internal JSSynchronizationContext SynchronizationContext;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if FEATURE_WASM_THREADS
|
public PromiseHolder(JSProxyContext targetContext)
|
||||||
// TODO possibly unify signature with non-MT and pass null
|
|
||||||
public PromiseHolder(JSSynchronizationContext targetContext)
|
|
||||||
{
|
{
|
||||||
GCHandle = (IntPtr)InteropServices.GCHandle.Alloc(this, GCHandleType.Normal);
|
GCHandle = (IntPtr)InteropServices.GCHandle.Alloc(this, GCHandleType.Normal);
|
||||||
SynchronizationContext = targetContext;
|
ProxyContext = targetContext;
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
public PromiseHolder()
|
|
||||||
{
|
|
||||||
GCHandle = (IntPtr)InteropServices.GCHandle.Alloc(this, GCHandleType.Normal);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public PromiseHolder(nint gcvHandle)
|
public PromiseHolder(JSProxyContext targetContext, nint gcvHandle)
|
||||||
{
|
{
|
||||||
GCHandle = gcvHandle;
|
GCHandle = gcvHandle;
|
||||||
#if FEATURE_WASM_THREADS
|
ProxyContext = targetContext;
|
||||||
JSSynchronizationContext.AssertWebWorkerContext();
|
|
||||||
SynchronizationContext = JSSynchronizationContext.CurrentJSSynchronizationContext!;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
@ -16,115 +15,6 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
private const string TaskGetResultName = "get_Result";
|
private const string TaskGetResultName = "get_Result";
|
||||||
private static MethodInfo? s_taskGetResultMethodInfo;
|
private static MethodInfo? s_taskGetResultMethodInfo;
|
||||||
// we use this to maintain identity of JSHandle for a JSObject proxy
|
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
[ThreadStatic]
|
|
||||||
#endif
|
|
||||||
private static Dictionary<nint, WeakReference<JSObject>>? s_csOwnedObjects;
|
|
||||||
|
|
||||||
public static Dictionary<nint, WeakReference<JSObject>> ThreadCsOwnedObjects
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
s_csOwnedObjects ??= new();
|
|
||||||
return s_csOwnedObjects;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we use this to maintain identity of GCHandle for a managed object
|
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
[ThreadStatic]
|
|
||||||
#endif
|
|
||||||
private static Dictionary<object, nint>? s_jsOwnedObjects;
|
|
||||||
|
|
||||||
public static Dictionary<object, nint> ThreadJsOwnedObjects
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
s_jsOwnedObjects ??= new Dictionary<object, nint>(ReferenceEqualityComparer.Instance);
|
|
||||||
return s_jsOwnedObjects;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is similar to GCHandle, but the GCVHandle is allocated on JS side and this keeps the C# proxy alive
|
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
[ThreadStatic]
|
|
||||||
#endif
|
|
||||||
private static Dictionary<nint, PromiseHolder>? s_jsOwnedHolders;
|
|
||||||
|
|
||||||
public static Dictionary<nint, PromiseHolder> ThreadJsOwnedHolders
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
s_jsOwnedHolders ??= new Dictionary<nint, PromiseHolder>();
|
|
||||||
return s_jsOwnedHolders;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSVHandle is like JSHandle, but it's not tracked and allocated by the JS side
|
|
||||||
// It's used when we need to create JSHandle-like identity ahead of time, before calling JS.
|
|
||||||
// they have negative values, so that they don't collide with JSHandles.
|
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
[ThreadStatic]
|
|
||||||
#endif
|
|
||||||
public static nint NextJSVHandle;
|
|
||||||
|
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
[ThreadStatic]
|
|
||||||
#endif
|
|
||||||
private static List<nint>? s_JSVHandleFreeList;
|
|
||||||
public static List<nint> JSVHandleFreeList
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
s_JSVHandleFreeList ??= new();
|
|
||||||
return s_JSVHandleFreeList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static nint AllocJSVHandle()
|
|
||||||
{
|
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
// TODO, when Task is passed to JSImport as parameter, it could be sent from another thread (in the future)
|
|
||||||
// and so we need to use JSVHandleFreeList of the target thread
|
|
||||||
JSSynchronizationContext.AssertWebWorkerContext();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (JSVHandleFreeList.Count > 0)
|
|
||||||
{
|
|
||||||
var jsvHandle = JSVHandleFreeList[JSVHandleFreeList.Count];
|
|
||||||
JSVHandleFreeList.RemoveAt(JSVHandleFreeList.Count - 1);
|
|
||||||
return jsvHandle;
|
|
||||||
}
|
|
||||||
if (NextJSVHandle == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
NextJSVHandle = -2;
|
|
||||||
}
|
|
||||||
return NextJSVHandle--;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void FreeJSVHandle(nint jsvHandle)
|
|
||||||
{
|
|
||||||
JSVHandleFreeList.Add(jsvHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsGCVHandle(nint gcHandle)
|
|
||||||
{
|
|
||||||
return gcHandle < -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void ReleaseCSOwnedObject(nint jsHandle)
|
|
||||||
{
|
|
||||||
if (jsHandle != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
JSSynchronizationContext.AssertWebWorkerContext();
|
|
||||||
#endif
|
|
||||||
ThreadCsOwnedObjects.Remove(jsHandle);
|
|
||||||
Interop.Runtime.ReleaseCSOwnedObject(jsHandle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool GetTaskResultDynamic(Task task, out object? value)
|
public static bool GetTaskResultDynamic(Task task, out object? value)
|
||||||
{
|
{
|
||||||
|
@ -143,30 +33,6 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// A JSOwnedObject is a managed object with its lifetime controlled by javascript.
|
|
||||||
// The managed side maintains a strong reference to the object, while the JS side
|
|
||||||
// maintains a weak reference and notifies the managed side if the JS wrapper object
|
|
||||||
// has been reclaimed by the JS GC. At that point, the managed side will release its
|
|
||||||
// strong references, allowing the managed object to be collected.
|
|
||||||
// This ensures that things like delegates and promises will never 'go away' while JS
|
|
||||||
// is expecting to be able to invoke or await them.
|
|
||||||
public static IntPtr GetJSOwnedObjectGCHandle(object obj, GCHandleType handleType = GCHandleType.Normal)
|
|
||||||
{
|
|
||||||
if (obj == null)
|
|
||||||
{
|
|
||||||
return IntPtr.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
IntPtr gcHandle;
|
|
||||||
if (ThreadJsOwnedObjects.TryGetValue(obj, out gcHandle))
|
|
||||||
{
|
|
||||||
return gcHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
IntPtr result = (IntPtr)GCHandle.Alloc(obj, handleType);
|
|
||||||
ThreadJsOwnedObjects[obj] = result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr)
|
public static RuntimeMethodHandle GetMethodHandleFromIntPtr(IntPtr ptr)
|
||||||
|
@ -282,7 +148,6 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
signature.Result = types[0]._signatureType;
|
signature.Result = types[0]._signatureType;
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
signature.ImportHandle = (int)Interlocked.Increment(ref JSFunctionBinding.nextImportHandle);
|
signature.ImportHandle = (int)Interlocked.Increment(ref JSFunctionBinding.nextImportHandle);
|
||||||
signature.IsThreadCaptured = false;
|
|
||||||
#else
|
#else
|
||||||
signature.ImportHandle = (int)JSFunctionBinding.nextImportHandle++;
|
signature.ImportHandle = (int)JSFunctionBinding.nextImportHandle++;
|
||||||
#endif
|
#endif
|
||||||
|
@ -290,12 +155,6 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
for (int i = 0; i < argsCount; i++)
|
for (int i = 0; i < argsCount; i++)
|
||||||
{
|
{
|
||||||
var type = signature.Sigs[i] = types[i + 1]._signatureType;
|
var type = signature.Sigs[i] = types[i + 1]._signatureType;
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
if (i > 0 && (type.Type == MarshalerType.JSObject || type.Type == MarshalerType.JSException))
|
|
||||||
{
|
|
||||||
signature.IsThreadCaptured = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
signature.IsAsync = types[0]._signatureType.Type == MarshalerType.Task;
|
signature.IsAsync = types[0]._signatureType.Type == MarshalerType.Task;
|
||||||
|
|
||||||
|
@ -330,24 +189,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
signature.Sigs = null;
|
signature.Sigs = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JSObject CreateCSOwnedProxy(nint jsHandle)
|
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")]
|
||||||
{
|
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
JSSynchronizationContext.AssertWebWorkerContext();
|
|
||||||
#endif
|
|
||||||
JSObject? res;
|
|
||||||
|
|
||||||
if (!ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
|
|
||||||
!reference.TryGetTarget(out res) ||
|
|
||||||
res.IsDisposed)
|
|
||||||
{
|
|
||||||
res = new JSObject(jsHandle);
|
|
||||||
ThreadCsOwnedObjects[jsHandle] = new WeakReference<JSObject>(res, trackResurrection: true);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")]
|
|
||||||
public static void LoadLazyAssembly(byte[] dllBytes, byte[]? pdbBytes)
|
public static void LoadLazyAssembly(byte[] dllBytes, byte[]? pdbBytes)
|
||||||
{
|
{
|
||||||
if (pdbBytes == null)
|
if (pdbBytes == null)
|
||||||
|
@ -356,7 +198,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes));
|
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes), new MemoryStream(pdbBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")]
|
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "It's always part of the single compilation (and trimming) unit.")]
|
||||||
public static void LoadSatelliteAssembly(byte[] dllBytes)
|
public static void LoadSatelliteAssembly(byte[] dllBytes)
|
||||||
{
|
{
|
||||||
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes));
|
AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(dllBytes));
|
||||||
|
@ -365,92 +207,33 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
public static void InstallWebWorkerInterop(bool isMainThread)
|
public static void InstallWebWorkerInterop(bool isMainThread)
|
||||||
{
|
{
|
||||||
Interop.Runtime.InstallWebWorkerInterop();
|
var ctx = new JSSynchronizationContext(isMainThread);
|
||||||
var currentTID = GetNativeThreadId();
|
ctx.previousSynchronizationContext = SynchronizationContext.Current;
|
||||||
var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext;
|
SynchronizationContext.SetSynchronizationContext(ctx);
|
||||||
if (ctx == null)
|
|
||||||
|
var proxyContext = ctx.ProxyContext;
|
||||||
|
JSProxyContext.CurrentThreadContext = proxyContext;
|
||||||
|
JSProxyContext.ExecutionContext = proxyContext;
|
||||||
|
if (isMainThread)
|
||||||
{
|
{
|
||||||
ctx = new JSSynchronizationContext(Thread.CurrentThread, currentTID);
|
JSProxyContext.MainThreadContext = proxyContext;
|
||||||
ctx.previousSynchronizationContext = SynchronizationContext.Current;
|
|
||||||
JSSynchronizationContext.CurrentJSSynchronizationContext = ctx;
|
|
||||||
SynchronizationContext.SetSynchronizationContext(ctx);
|
|
||||||
if (isMainThread)
|
|
||||||
{
|
|
||||||
JSSynchronizationContext.MainJSSynchronizationContext = ctx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (ctx.TargetTID != currentTID)
|
|
||||||
{
|
|
||||||
Environment.FailFast($"JSSynchronizationContext.Install has wrong native thread id {ctx.TargetTID} != {currentTID}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.AwaitNewData();
|
ctx.AwaitNewData();
|
||||||
|
|
||||||
|
Interop.Runtime.InstallWebWorkerInterop(proxyContext.ContextHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UninstallWebWorkerInterop()
|
public static void UninstallWebWorkerInterop()
|
||||||
{
|
{
|
||||||
var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext;
|
var ctx = JSProxyContext.CurrentThreadContext;
|
||||||
var uninstallJSSynchronizationContext = ctx != null;
|
if (ctx == null) throw new InvalidOperationException();
|
||||||
if (uninstallJSSynchronizationContext)
|
var syncContext = ctx.SynchronizationContext;
|
||||||
|
if (SynchronizationContext.Current == syncContext)
|
||||||
{
|
{
|
||||||
try
|
SynchronizationContext.SetSynchronizationContext(syncContext.previousSynchronizationContext);
|
||||||
{
|
|
||||||
foreach (var jsObjectWeak in ThreadCsOwnedObjects.Values)
|
|
||||||
{
|
|
||||||
if (jsObjectWeak.TryGetTarget(out var jso))
|
|
||||||
{
|
|
||||||
jso.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SynchronizationContext.SetSynchronizationContext(ctx!.previousSynchronizationContext);
|
|
||||||
JSSynchronizationContext.CurrentJSSynchronizationContext = null;
|
|
||||||
ctx.isDisposed = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Environment.FailFast($"Unexpected error in UninstallWebWorkerInterop, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}. " + ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
ctx.Dispose();
|
||||||
{
|
|
||||||
if (ThreadCsOwnedObjects.Count > 0)
|
|
||||||
{
|
|
||||||
Environment.FailFast($"There should be no JSObjects proxies on this thread, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}");
|
|
||||||
}
|
|
||||||
if (ThreadJsOwnedObjects.Count > 0)
|
|
||||||
{
|
|
||||||
Environment.FailFast($"There should be no JS proxies of managed objects on this thread, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Interop.Runtime.UninstallWebWorkerInterop();
|
|
||||||
|
|
||||||
if (uninstallJSSynchronizationContext)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var gch in ThreadJsOwnedObjects.Values)
|
|
||||||
{
|
|
||||||
GCHandle gcHandle = (GCHandle)gch;
|
|
||||||
gcHandle.Free();
|
|
||||||
}
|
|
||||||
foreach (var holder in ThreadJsOwnedHolders.Values)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
holder.Callback!.Invoke(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Environment.FailFast($"Unexpected error in UninstallWebWorkerInterop, ManagedThreadId: {Thread.CurrentThread.ManagedThreadId}. " + ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ThreadCsOwnedObjects.Clear();
|
|
||||||
ThreadJsOwnedObjects.Clear();
|
|
||||||
JSVHandleFreeList.Clear();
|
|
||||||
NextJSVHandle = IntPtr.Zero;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "external_eventloop")]
|
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "external_eventloop")]
|
||||||
|
@ -460,15 +243,6 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
GetThreadExternalEventloop(thread) = true;
|
GetThreadExternalEventloop(thread) = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "thread_id")]
|
|
||||||
private static extern ref long GetThreadNativeThreadId(Thread @this);
|
|
||||||
|
|
||||||
public static IntPtr GetNativeThreadId()
|
|
||||||
{
|
|
||||||
return (int)GetThreadNativeThreadId(Thread.CurrentThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices.JavaScript;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace System.Runtime.InteropServices.JavaScript
|
namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
|
@ -18,7 +20,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
internal JSMarshalerArgumentImpl slot;
|
internal JSMarshalerArgumentImpl slot;
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Explicit, Pack = 16, Size = 16)]
|
[StructLayout(LayoutKind.Explicit, Pack = 32, Size = 32)]
|
||||||
internal struct JSMarshalerArgumentImpl
|
internal struct JSMarshalerArgumentImpl
|
||||||
{
|
{
|
||||||
[FieldOffset(0)]
|
[FieldOffset(0)]
|
||||||
|
@ -55,6 +57,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
internal MarshalerType Type;
|
internal MarshalerType Type;
|
||||||
[FieldOffset(13)]
|
[FieldOffset(13)]
|
||||||
internal MarshalerType ElementType;
|
internal MarshalerType ElementType;
|
||||||
|
|
||||||
|
[FieldOffset(16)]
|
||||||
|
internal IntPtr ContextHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -64,6 +69,98 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
public unsafe void Initialize()
|
public unsafe void Initialize()
|
||||||
{
|
{
|
||||||
slot.Type = MarshalerType.None;
|
slot.Type = MarshalerType.None;
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
// we know that this is at the start of some JSImport call, but we don't know yet what would be the target thread
|
||||||
|
// also this is called multiple times
|
||||||
|
JSProxyContext.JSImportWithUnknownContext();
|
||||||
|
slot.ContextHandle = IntPtr.Zero;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal unsafe void InitializeWithContext(JSProxyContext knownProxyContext)
|
||||||
|
{
|
||||||
|
slot.Type = MarshalerType.None;
|
||||||
|
slot.ContextHandle = knownProxyContext.ContextHandle;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// this is always called from ToManaged() marshaler
|
||||||
|
#pragma warning disable CA1822 // Mark members as static
|
||||||
|
internal JSProxyContext ToManagedContext
|
||||||
|
#pragma warning restore CA1822 // Mark members as static
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
#if !FEATURE_WASM_THREADS
|
||||||
|
return JSProxyContext.MainThreadContext;
|
||||||
|
#else
|
||||||
|
// ContextHandle always has to be set
|
||||||
|
// during JSImport, this is marshaling result/exception and it would be set by:
|
||||||
|
// - InvokeJSImport implementation
|
||||||
|
// - ActionJS.InvokeJS
|
||||||
|
// - ResolveVoidPromise/ResolvePromise/RejectPromise
|
||||||
|
// during JSExport, this is marshaling parameters and it would be set by:
|
||||||
|
// - alloc_stack_frame
|
||||||
|
// - set_js_handle/set_gc_handle
|
||||||
|
var proxyContextGCHandle = (GCHandle)slot.ContextHandle;
|
||||||
|
if (proxyContextGCHandle == default)
|
||||||
|
{
|
||||||
|
Environment.FailFast($"ContextHandle not set, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
}
|
||||||
|
var argumentContext = (JSProxyContext)proxyContextGCHandle.Target!;
|
||||||
|
return argumentContext;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is always called from ToJS() marshaler
|
||||||
|
#pragma warning disable CA1822 // Mark members as static
|
||||||
|
internal JSProxyContext ToJSContext
|
||||||
|
#pragma warning restore CA1822 // Mark members as static
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
#if !FEATURE_WASM_THREADS
|
||||||
|
return JSProxyContext.MainThreadContext;
|
||||||
|
#else
|
||||||
|
if (JSProxyContext.CapturingState == JSProxyContext.JSImportOperationState.JSImportParams)
|
||||||
|
{
|
||||||
|
// we are called from ToJS, during JSImport
|
||||||
|
// we need to check for captured or default context
|
||||||
|
return JSProxyContext.CurrentOperationContext;
|
||||||
|
}
|
||||||
|
// ContextHandle must be set be set by JS side of JSExport, and we are marshaling result of JSExport
|
||||||
|
var proxyContextGCHandle = slot.ContextHandle;
|
||||||
|
if (proxyContextGCHandle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Environment.FailFast($"ContextHandle not set, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
}
|
||||||
|
var argumentContext = (JSProxyContext)((GCHandle)proxyContextGCHandle).Target!;
|
||||||
|
return argumentContext;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure that we are on a thread with JS interop and that it matches the target of the argument
|
||||||
|
#pragma warning disable CA1822 // Mark members as static
|
||||||
|
internal JSProxyContext AssertCurrentThreadContext()
|
||||||
|
#pragma warning restore CA1822 // Mark members as static
|
||||||
|
{
|
||||||
|
#if !FEATURE_WASM_THREADS
|
||||||
|
return JSProxyContext.MainThreadContext;
|
||||||
|
#else
|
||||||
|
var currentThreadContext = JSProxyContext.CurrentThreadContext;
|
||||||
|
if (currentThreadContext == null)
|
||||||
|
{
|
||||||
|
Environment.FailFast($"Must be called on same thread with JS interop, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
}
|
||||||
|
if (slot.ContextHandle != currentThreadContext.ContextHandle)
|
||||||
|
{
|
||||||
|
Environment.FailFast($"Must be called on same thread which created the stack frame, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
}
|
||||||
|
return currentThreadContext;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,53 +10,37 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
public partial class JSObject
|
public partial class JSObject
|
||||||
{
|
{
|
||||||
internal nint JSHandle;
|
internal nint JSHandle;
|
||||||
|
internal JSProxyContext ProxyContext;
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
private readonly object _thisLock = new object();
|
|
||||||
private SynchronizationContext? m_SynchronizationContext;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public SynchronizationContext SynchronizationContext
|
public SynchronizationContext SynchronizationContext
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
return m_SynchronizationContext!;
|
return ProxyContext.SynchronizationContext;
|
||||||
#else
|
#else
|
||||||
throw new PlatformNotSupportedException();
|
throw new PlatformNotSupportedException();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
// the JavaScript object could only exist on the single web worker and can't migrate to other workers
|
|
||||||
internal nint OwnerTID;
|
|
||||||
#endif
|
|
||||||
#if !DISABLE_LEGACY_JS_INTEROP
|
#if !DISABLE_LEGACY_JS_INTEROP
|
||||||
internal GCHandle? InFlight;
|
internal GCHandle? InFlight;
|
||||||
internal int InFlightCounter;
|
internal int InFlightCounter;
|
||||||
#endif
|
#endif
|
||||||
private bool _isDisposed;
|
internal bool _isDisposed;
|
||||||
|
|
||||||
internal JSObject(IntPtr jsHandle)
|
internal JSObject(IntPtr jsHandle, JSProxyContext ctx)
|
||||||
{
|
{
|
||||||
|
ProxyContext = ctx;
|
||||||
JSHandle = jsHandle;
|
JSHandle = jsHandle;
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
var ctx = JSSynchronizationContext.CurrentJSSynchronizationContext;
|
|
||||||
if (ctx == null)
|
|
||||||
{
|
|
||||||
Environment.FailFast("Missing CurrentJSSynchronizationContext");
|
|
||||||
}
|
|
||||||
m_SynchronizationContext = ctx;
|
|
||||||
OwnerTID = ctx!.TargetTID;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !DISABLE_LEGACY_JS_INTEROP
|
#if !DISABLE_LEGACY_JS_INTEROP
|
||||||
internal void AddInFlight()
|
internal void AddInFlight()
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(IsDisposed, this);
|
ObjectDisposedException.ThrowIf(IsDisposed, this);
|
||||||
lock (this)
|
lock (ProxyContext)
|
||||||
{
|
{
|
||||||
InFlightCounter++;
|
InFlightCounter++;
|
||||||
if (InFlightCounter == 1)
|
if (InFlightCounter == 1)
|
||||||
|
@ -72,7 +56,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// we only want JSObject to be disposed (from GC finalizer) once there is no in-flight reference and also no natural C# reference
|
// we only want JSObject to be disposed (from GC finalizer) once there is no in-flight reference and also no natural C# reference
|
||||||
internal void ReleaseInFlight()
|
internal void ReleaseInFlight()
|
||||||
{
|
{
|
||||||
lock (this)
|
lock (ProxyContext)
|
||||||
{
|
{
|
||||||
Debug.Assert(InFlightCounter != 0, "InFlightCounter != 0");
|
Debug.Assert(InFlightCounter != 0, "InFlightCounter != 0");
|
||||||
|
|
||||||
|
@ -94,19 +78,17 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JSSynchronizationContext.AssertWebWorkerContext();
|
|
||||||
var currentTID = JSSynchronizationContext.CurrentJSSynchronizationContext!.TargetTID;
|
|
||||||
|
|
||||||
if (value is JSObject jsObject)
|
if (value is JSObject jsObject)
|
||||||
{
|
{
|
||||||
if (jsObject.OwnerTID != currentTID)
|
if (!jsObject.ProxyContext.IsCurrentThread())
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created.");
|
throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (value is JSException jsException)
|
else if (value is JSException jsException)
|
||||||
{
|
{
|
||||||
if (jsException.jsException != null && jsException.jsException.OwnerTID != currentTID)
|
if (jsException.jsException != null && !jsException.jsException.ProxyContext.IsCurrentThread())
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created.");
|
throw new InvalidOperationException("The JavaScript object can be used only on the thread where it was created.");
|
||||||
}
|
}
|
||||||
|
@ -123,43 +105,24 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString() => $"(js-obj js '{JSHandle}')";
|
public override string ToString() => $"(js-obj js '{JSHandle}')";
|
||||||
|
|
||||||
internal void DisposeLocal()
|
internal void DisposeImpl(bool skipJsCleanup = false)
|
||||||
{
|
|
||||||
JSHostImplementation.ThreadCsOwnedObjects.Remove(JSHandle);
|
|
||||||
_isDisposed = true;
|
|
||||||
JSHandle = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposeThis()
|
|
||||||
{
|
{
|
||||||
if (!_isDisposed)
|
if (!_isDisposed)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
if (SynchronizationContext == SynchronizationContext.Current)
|
if (ProxyContext.IsCurrentThread())
|
||||||
{
|
{
|
||||||
lock (_thisLock)
|
JSProxyContext.ReleaseCSOwnedObject(this, skipJsCleanup);
|
||||||
{
|
|
||||||
JSHostImplementation.ReleaseCSOwnedObject(JSHandle);
|
|
||||||
_isDisposed = true;
|
|
||||||
JSHandle = IntPtr.Zero;
|
|
||||||
m_SynchronizationContext = null;
|
|
||||||
} //lock
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SynchronizationContext.Post(static (object? s) =>
|
ProxyContext.SynchronizationContext.Post(static (object? s) =>
|
||||||
{
|
{
|
||||||
var self = (JSObject)s!;
|
var x = ((JSObject self, bool skipJS))s!;
|
||||||
lock (self._thisLock)
|
JSProxyContext.ReleaseCSOwnedObject(x.self, x.skipJS);
|
||||||
{
|
}, (this, skipJsCleanup));
|
||||||
JSHostImplementation.ReleaseCSOwnedObject(self.JSHandle);
|
|
||||||
self._isDisposed = true;
|
|
||||||
self.JSHandle = IntPtr.Zero;
|
|
||||||
self.m_SynchronizationContext = null;
|
|
||||||
} //lock
|
|
||||||
}, this);
|
|
||||||
#else
|
#else
|
||||||
JSHostImplementation.ReleaseCSOwnedObject(JSHandle);
|
JSProxyContext.ReleaseCSOwnedObject(this, skipJsCleanup);
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
JSHandle = IntPtr.Zero;
|
JSHandle = IntPtr.Zero;
|
||||||
#endif
|
#endif
|
||||||
|
@ -168,7 +131,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
|
||||||
~JSObject()
|
~JSObject()
|
||||||
{
|
{
|
||||||
DisposeThis();
|
DisposeImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -176,8 +139,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
DisposeThis();
|
DisposeImpl();
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,596 @@
|
||||||
|
// 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.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
|
using static System.Runtime.InteropServices.JavaScript.JSHostImplementation;
|
||||||
|
|
||||||
|
namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
{
|
||||||
|
internal sealed class JSProxyContext : IDisposable
|
||||||
|
{
|
||||||
|
private bool _isDisposed;
|
||||||
|
|
||||||
|
// we use this to maintain identity of JSHandle for a JSObject proxy
|
||||||
|
private readonly Dictionary<nint, WeakReference<JSObject>> ThreadCsOwnedObjects = new();
|
||||||
|
// we use this to maintain identity of GCHandle for a managed object
|
||||||
|
private readonly Dictionary<object, nint> ThreadJsOwnedObjects = new(ReferenceEqualityComparer.Instance);
|
||||||
|
// this is similar to GCHandle, but the GCVHandle is allocated on JS side and this keeps the C# proxy alive
|
||||||
|
private readonly Dictionary<nint, PromiseHolder> ThreadJsOwnedHolders = new();
|
||||||
|
// JSVHandle is like JSHandle, but it's not tracked and allocated by the JS side
|
||||||
|
// It's used when we need to create JSHandle-like identity ahead of time, before calling JS.
|
||||||
|
// they have negative values, so that they don't collide with JSHandles.
|
||||||
|
private nint NextJSVHandle = -2;
|
||||||
|
private readonly List<nint> JSVHandleFreeList = new();
|
||||||
|
|
||||||
|
#if !FEATURE_WASM_THREADS
|
||||||
|
private JSProxyContext()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
public nint ContextHandle;
|
||||||
|
public nint NativeTID;
|
||||||
|
public int ManagedTID;
|
||||||
|
public bool IsMainThread;
|
||||||
|
public JSSynchronizationContext SynchronizationContext;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool IsCurrentThread()
|
||||||
|
{
|
||||||
|
return ManagedTID == Thread.CurrentThread.ManagedThreadId;
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "thread_id")]
|
||||||
|
private static extern ref long GetThreadNativeThreadId(Thread @this);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static IntPtr GetNativeThreadId()
|
||||||
|
{
|
||||||
|
return (int)GetThreadNativeThreadId(Thread.CurrentThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSProxyContext(bool isMainThread, JSSynchronizationContext synchronizationContext)
|
||||||
|
{
|
||||||
|
SynchronizationContext = synchronizationContext;
|
||||||
|
NativeTID = GetNativeThreadId();
|
||||||
|
ManagedTID = Thread.CurrentThread.ManagedThreadId;
|
||||||
|
IsMainThread = isMainThread;
|
||||||
|
ContextHandle = (nint)GCHandle.Alloc(this, GCHandleType.Normal);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#region Current operation context
|
||||||
|
|
||||||
|
#if !FEATURE_WASM_THREADS
|
||||||
|
public static readonly JSProxyContext MainThreadContext = new();
|
||||||
|
public static JSProxyContext CurrentThreadContext => MainThreadContext;
|
||||||
|
public static JSProxyContext CurrentOperationContext => MainThreadContext;
|
||||||
|
public static JSProxyContext PushOperationWithCurrentThreadContext()
|
||||||
|
{
|
||||||
|
// in single threaded build we don't have to keep stack of operations and the context/thread is always the same
|
||||||
|
return MainThreadContext;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
// Context of the main thread
|
||||||
|
private static JSProxyContext? _MainThreadContext;
|
||||||
|
public static JSProxyContext MainThreadContext
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get => _MainThreadContext!;
|
||||||
|
set => _MainThreadContext = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum JSImportOperationState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
JSImportParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static JSProxyContext? _CapturedOperationContext;
|
||||||
|
[ThreadStatic]
|
||||||
|
private static JSImportOperationState _CapturingState;
|
||||||
|
|
||||||
|
public static JSImportOperationState CapturingState => _CapturingState;
|
||||||
|
|
||||||
|
// there will be call to JS from JSImport generated code, but we don't know which target thread yet
|
||||||
|
public static void JSImportWithUnknownContext()
|
||||||
|
{
|
||||||
|
// it would be ideal to assert here, that we arrived here with JSImportOperationState.None
|
||||||
|
// but any exception during JSImportOperationState.JSImportParams phase could make this state un-balanced
|
||||||
|
// typically this would be exception which is validating the marshaled value
|
||||||
|
// manually re-setting _CapturingState on each throw site would be possible, but fragile
|
||||||
|
// luckily, we always reset it here before any new JSImport call
|
||||||
|
// so the code which could interact with _CapturedOperationContext value will receive fresh values
|
||||||
|
_CapturingState = JSImportOperationState.JSImportParams;
|
||||||
|
_CapturedOperationContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// there will be no capture during following call to JS
|
||||||
|
public static void JSImportNoCapture()
|
||||||
|
{
|
||||||
|
_CapturingState = JSImportOperationState.None;
|
||||||
|
_CapturedOperationContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we are at the end of marshaling of the JSImport parameters
|
||||||
|
public static JSProxyContext SealJSImportCapturing()
|
||||||
|
{
|
||||||
|
if (_CapturingState != JSImportOperationState.JSImportParams)
|
||||||
|
{
|
||||||
|
Environment.FailFast($"Method only allowed during JSImport capturing phase, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
}
|
||||||
|
_CapturingState = JSImportOperationState.None;
|
||||||
|
var capturedOperationContext = _CapturedOperationContext;
|
||||||
|
_CapturedOperationContext = null;
|
||||||
|
|
||||||
|
if (capturedOperationContext != null)
|
||||||
|
{
|
||||||
|
return capturedOperationContext;
|
||||||
|
}
|
||||||
|
// it could happen that we are in operation, in which we didn't capture target thread/context
|
||||||
|
var executionContext = ExecutionContext;
|
||||||
|
if (executionContext != null)
|
||||||
|
{
|
||||||
|
// we could will call JS on the current thread (or child task), if it has the JS interop installed
|
||||||
|
return executionContext;
|
||||||
|
}
|
||||||
|
// otherwise we will call JS on the main thread, which always has JS interop
|
||||||
|
return MainThreadContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is called only during marshaling (in) parameters of JSImport, which have existing ProxyContext (thread affinity)
|
||||||
|
// together with CurrentOperationContext is will validate that all parameters of the call have same context/affinity
|
||||||
|
public static void CaptureContextFromParameter(JSProxyContext parameterContext)
|
||||||
|
{
|
||||||
|
if (_CapturingState != JSImportOperationState.JSImportParams)
|
||||||
|
{
|
||||||
|
Environment.FailFast($"Method only allowed during JSImport capturing phase, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var capturedContext = _CapturedOperationContext;
|
||||||
|
|
||||||
|
if (capturedContext == null)
|
||||||
|
{
|
||||||
|
_CapturedOperationContext = capturedContext;
|
||||||
|
}
|
||||||
|
else if (parameterContext != capturedContext)
|
||||||
|
{
|
||||||
|
_CapturedOperationContext = null;
|
||||||
|
_CapturingState = JSImportOperationState.None;
|
||||||
|
throw new InvalidOperationException("All JSObject proxies need to have same thread affinity. See https://aka.ms/dotnet-JS-interop-threads");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context flowing from parent thread into child tasks.
|
||||||
|
// Could be null on threads which don't have JS interop, like managed thread pool threads. Unless they inherit it from the current Task
|
||||||
|
// TODO flow it also with ExecutionContext to child threads ?
|
||||||
|
private static readonly AsyncLocal<JSProxyContext?> _currentThreadContext = new AsyncLocal<JSProxyContext?>();
|
||||||
|
public static JSProxyContext? ExecutionContext
|
||||||
|
{
|
||||||
|
get => _currentThreadContext.Value;
|
||||||
|
set => _currentThreadContext.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
public static JSProxyContext? CurrentThreadContext;
|
||||||
|
|
||||||
|
// This is context to dispatch into. In order of preference
|
||||||
|
// - captured context by arguments of current/pending JSImport call
|
||||||
|
// - current thread context, for calls from JSWebWorker threads with the interop installed
|
||||||
|
// - main thread, for calls from any other thread, like managed thread pool or `new Thread`
|
||||||
|
public static JSProxyContext CurrentOperationContext
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_CapturingState != JSImportOperationState.JSImportParams)
|
||||||
|
{
|
||||||
|
Environment.FailFast($"Method only allowed during JSImport capturing phase, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
}
|
||||||
|
var capturedOperationContext = _CapturedOperationContext;
|
||||||
|
if (capturedOperationContext != null)
|
||||||
|
{
|
||||||
|
return capturedOperationContext;
|
||||||
|
}
|
||||||
|
// it could happen that we are in operation, in which we didn't capture target thread/context
|
||||||
|
var executionContext = ExecutionContext;
|
||||||
|
if (executionContext != null)
|
||||||
|
{
|
||||||
|
// capture this fallback for validation of all other parameters
|
||||||
|
_CapturedOperationContext = executionContext;
|
||||||
|
|
||||||
|
// we could will call JS on the current thread (or child task), if it has the JS interop installed
|
||||||
|
return executionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise we will call JS on the main thread, which always has JS interop
|
||||||
|
var mainThreadContext = MainThreadContext;
|
||||||
|
|
||||||
|
// capture this fallback for validation of all other parameters
|
||||||
|
// such validation could fail if Task is marshaled earlier than JSObject and uses different target context
|
||||||
|
_CapturedOperationContext = mainThreadContext;
|
||||||
|
|
||||||
|
return mainThreadContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static JSProxyContext AssertIsInteropThread()
|
||||||
|
{
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
var ctx = CurrentThreadContext;
|
||||||
|
if (ctx == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Please use dedicated worker for working with JavaScript interop, ManagedThreadId:{Thread.CurrentThread.ManagedThreadId}. See https://aka.ms/dotnet-JS-interop-threads");
|
||||||
|
}
|
||||||
|
if (ctx._isDisposed)
|
||||||
|
{
|
||||||
|
ObjectDisposedException.ThrowIf(ctx._isDisposed, ctx);
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
#else
|
||||||
|
return MainThreadContext;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Handles
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool IsJSVHandle(nint jsHandle)
|
||||||
|
{
|
||||||
|
return jsHandle < -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static bool IsGCVHandle(nint gcHandle)
|
||||||
|
{
|
||||||
|
return gcHandle < -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public nint AllocJSVHandle()
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (JSVHandleFreeList.Count > 0)
|
||||||
|
{
|
||||||
|
var jsvHandle = JSVHandleFreeList[JSVHandleFreeList.Count - 1];
|
||||||
|
JSVHandleFreeList.RemoveAt(JSVHandleFreeList.Count - 1);
|
||||||
|
return jsvHandle;
|
||||||
|
}
|
||||||
|
if (NextJSVHandle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
NextJSVHandle = -2;
|
||||||
|
}
|
||||||
|
return NextJSVHandle--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FreeJSVHandle(nint jsvHandle)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
JSVHandleFreeList.Add(jsvHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A JSOwnedObject is a managed object with its lifetime controlled by javascript.
|
||||||
|
// The managed side maintains a strong reference to the object, while the JS side
|
||||||
|
// maintains a weak reference and notifies the managed side if the JS wrapper object
|
||||||
|
// has been reclaimed by the JS GC. At that point, the managed side will release its
|
||||||
|
// strong references, allowing the managed object to be collected.
|
||||||
|
// This ensures that things like delegates and promises will never 'go away' while JS
|
||||||
|
// is expecting to be able to invoke or await them.
|
||||||
|
public IntPtr GetJSOwnedObjectGCHandle(object obj, GCHandleType handleType = GCHandleType.Normal)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
return IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (ThreadJsOwnedObjects.TryGetValue(obj, out IntPtr gcHandle))
|
||||||
|
{
|
||||||
|
return gcHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntPtr result = (IntPtr)GCHandle.Alloc(obj, handleType);
|
||||||
|
ThreadJsOwnedObjects[obj] = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PromiseHolder CreatePromiseHolder()
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
return new PromiseHolder(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PromiseHolder GetPromiseHolder(nint gcHandle)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
PromiseHolder? holder;
|
||||||
|
if (IsGCVHandle(gcHandle))
|
||||||
|
{
|
||||||
|
if (!ThreadJsOwnedHolders.TryGetValue(gcHandle, out holder))
|
||||||
|
{
|
||||||
|
holder = new PromiseHolder(this, gcHandle);
|
||||||
|
ThreadJsOwnedHolders.Add(gcHandle, holder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
holder = (PromiseHolder)((GCHandle)gcHandle).Target!;
|
||||||
|
}
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void ReleasePromiseHolder(nint holderGCHandle)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
PromiseHolder? holder;
|
||||||
|
if (IsGCVHandle(holderGCHandle))
|
||||||
|
{
|
||||||
|
if (!ThreadJsOwnedHolders.Remove(holderGCHandle, out holder))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("ReleasePromiseHolder expected PromiseHolder " + holderGCHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GCHandle handle = (GCHandle)holderGCHandle;
|
||||||
|
var target = handle.Target!;
|
||||||
|
if (target is PromiseHolder holder2)
|
||||||
|
{
|
||||||
|
holder = holder2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("ReleasePromiseHolder expected PromiseHolder" + holderGCHandle);
|
||||||
|
}
|
||||||
|
handle.Free();
|
||||||
|
}
|
||||||
|
holder.IsDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void ReleaseJSOwnedObjectByGCHandle(nint gcHandle)
|
||||||
|
{
|
||||||
|
ToManagedCallback? holderCallback = null;
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
PromiseHolder? holder = null;
|
||||||
|
if (IsGCVHandle(gcHandle))
|
||||||
|
{
|
||||||
|
if (!ThreadJsOwnedHolders.Remove(gcHandle, out holder))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("ReleaseJSOwnedObjectByGCHandle expected in ThreadJsOwnedHolders");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GCHandle handle = (GCHandle)gcHandle;
|
||||||
|
var target = handle.Target!;
|
||||||
|
if (target is PromiseHolder holder2)
|
||||||
|
{
|
||||||
|
holder = holder2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!ThreadJsOwnedObjects.Remove(target))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("ReleaseJSOwnedObjectByGCHandle expected in ThreadJsOwnedObjects");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handle.Free();
|
||||||
|
}
|
||||||
|
if (holder != null)
|
||||||
|
{
|
||||||
|
holderCallback = holder.Callback;
|
||||||
|
holder.IsDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
holderCallback?.Invoke(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSObject CreateCSOwnedProxy(nint jsHandle)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
JSObject? res;
|
||||||
|
if (!ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
|
||||||
|
!reference.TryGetTarget(out res) ||
|
||||||
|
res.IsDisposed)
|
||||||
|
{
|
||||||
|
res = new JSObject(jsHandle, this);
|
||||||
|
ThreadCsOwnedObjects[jsHandle] = new WeakReference<JSObject>(res, trackResurrection: true);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ReleaseCSOwnedObject(JSObject proxy, bool skipJS)
|
||||||
|
{
|
||||||
|
if (proxy.IsDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var ctx = proxy.ProxyContext;
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
if (!ctx.IsCurrentThread())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"ReleaseCSOwnedObject has to run on the thread with same affinity as the proxy. ManagedThreadId: {Environment.CurrentManagedThreadId} JSHandle: {proxy.JSHandle}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
lock (ctx)
|
||||||
|
{
|
||||||
|
if (proxy.IsDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
proxy._isDisposed = true;
|
||||||
|
GC.SuppressFinalize(proxy);
|
||||||
|
var jsHandle = proxy.JSHandle;
|
||||||
|
if (!ctx.ThreadCsOwnedObjects.Remove(jsHandle))
|
||||||
|
{
|
||||||
|
Environment.FailFast($"ReleaseCSOwnedObject expected to find registration for JSHandle: {jsHandle}, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
};
|
||||||
|
if (!skipJS)
|
||||||
|
{
|
||||||
|
Interop.Runtime.ReleaseCSOwnedObject(jsHandle);
|
||||||
|
}
|
||||||
|
if (IsJSVHandle(jsHandle))
|
||||||
|
{
|
||||||
|
ctx.FreeJSVHandle(jsHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Legacy
|
||||||
|
|
||||||
|
// legacy
|
||||||
|
public void RegisterCSOwnedObject(JSObject proxy)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
ThreadCsOwnedObjects[(int)proxy.JSHandle] = new WeakReference<JSObject>(proxy, trackResurrection: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// legacy
|
||||||
|
public JSObject? GetCSOwnedObjectByJSHandle(nint jsHandle, int shouldAddInflight)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference))
|
||||||
|
{
|
||||||
|
reference.TryGetTarget(out JSObject? jsObject);
|
||||||
|
if (shouldAddInflight != 0)
|
||||||
|
{
|
||||||
|
jsObject?.AddInFlight();
|
||||||
|
}
|
||||||
|
return jsObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// legacy
|
||||||
|
public JSObject CreateCSOwnedProxy(nint jsHandle, LegacyHostImplementation.MappedType mappedType, int shouldAddInflight)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
JSObject? res = null;
|
||||||
|
if (!ThreadCsOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
|
||||||
|
!reference.TryGetTarget(out res) ||
|
||||||
|
res.IsDisposed)
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0612 // Type or member is obsolete
|
||||||
|
res = mappedType switch
|
||||||
|
{
|
||||||
|
LegacyHostImplementation.MappedType.JSObject => new JSObject(jsHandle, JSProxyContext.MainThreadContext),
|
||||||
|
LegacyHostImplementation.MappedType.Array => new Array(jsHandle),
|
||||||
|
LegacyHostImplementation.MappedType.ArrayBuffer => new ArrayBuffer(jsHandle),
|
||||||
|
LegacyHostImplementation.MappedType.DataView => new DataView(jsHandle),
|
||||||
|
LegacyHostImplementation.MappedType.Function => new Function(jsHandle),
|
||||||
|
LegacyHostImplementation.MappedType.Uint8Array => new Uint8Array(jsHandle),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(mappedType))
|
||||||
|
};
|
||||||
|
#pragma warning restore CS0612 // Type or member is obsolete
|
||||||
|
ThreadCsOwnedObjects[jsHandle] = new WeakReference<JSObject>(res, trackResurrection: true);
|
||||||
|
}
|
||||||
|
if (shouldAddInflight != 0)
|
||||||
|
{
|
||||||
|
res.AddInFlight();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Dispose
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
if (!IsCurrentThread())
|
||||||
|
{
|
||||||
|
Environment.FailFast($"JSProxyContext must be disposed on the thread which owns it, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
}
|
||||||
|
((GCHandle)ContextHandle).Free();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
List<WeakReference<JSObject>> copy = new(ThreadCsOwnedObjects.Values);
|
||||||
|
foreach (var jsObjectWeak in copy)
|
||||||
|
{
|
||||||
|
if (jsObjectWeak.TryGetTarget(out var jso))
|
||||||
|
{
|
||||||
|
jso.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
Interop.Runtime.UninstallWebWorkerInterop();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
foreach (var gch in ThreadJsOwnedObjects.Values)
|
||||||
|
{
|
||||||
|
GCHandle gcHandle = (GCHandle)gch;
|
||||||
|
gcHandle.Free();
|
||||||
|
}
|
||||||
|
foreach (var holder in ThreadJsOwnedHolders.Values)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
holder.Callback!.Invoke(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadCsOwnedObjects.Clear();
|
||||||
|
ThreadJsOwnedObjects.Clear();
|
||||||
|
JSVHandleFreeList.Clear();
|
||||||
|
NextJSVHandle = IntPtr.Zero;
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
SynchronizationContext.Dispose();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~JSProxyContext()
|
||||||
|
{
|
||||||
|
Dispose(disposing: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,9 +6,8 @@
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using WorkItemQueueType = System.Threading.Channels.Channel<System.Runtime.InteropServices.JavaScript.JSSynchronizationContext.WorkItem>;
|
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSHostImplementation;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using WorkItemQueueType = System.Threading.Channels.Channel<System.Runtime.InteropServices.JavaScript.JSSynchronizationContext.WorkItem>;
|
||||||
|
|
||||||
namespace System.Runtime.InteropServices.JavaScript
|
namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
|
@ -21,17 +20,12 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class JSSynchronizationContext : SynchronizationContext
|
internal sealed class JSSynchronizationContext : SynchronizationContext
|
||||||
{
|
{
|
||||||
|
internal readonly JSProxyContext ProxyContext;
|
||||||
private readonly Action _DataIsAvailable;// don't allocate Action on each call to UnsafeOnCompleted
|
private readonly Action _DataIsAvailable;// don't allocate Action on each call to UnsafeOnCompleted
|
||||||
public readonly Thread TargetThread;
|
|
||||||
public readonly IntPtr TargetTID;
|
|
||||||
private readonly WorkItemQueueType Queue;
|
private readonly WorkItemQueueType Queue;
|
||||||
|
|
||||||
internal static JSSynchronizationContext? MainJSSynchronizationContext;
|
|
||||||
|
|
||||||
[ThreadStatic]
|
|
||||||
internal static JSSynchronizationContext? CurrentJSSynchronizationContext;
|
|
||||||
internal SynchronizationContext? previousSynchronizationContext;
|
internal SynchronizationContext? previousSynchronizationContext;
|
||||||
internal bool isDisposed;
|
internal bool _isDisposed;
|
||||||
|
|
||||||
internal readonly struct WorkItem
|
internal readonly struct WorkItem
|
||||||
{
|
{
|
||||||
|
@ -47,42 +41,33 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal JSSynchronizationContext(Thread targetThread, IntPtr targetThreadId)
|
public JSSynchronizationContext(bool isMainThread)
|
||||||
: this(
|
|
||||||
targetThread, targetThreadId,
|
|
||||||
Channel.CreateUnbounded<WorkItem>(
|
|
||||||
new UnboundedChannelOptions { SingleWriter = false, SingleReader = true, AllowSynchronousContinuations = true }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
}
|
ProxyContext = new JSProxyContext(isMainThread, this);
|
||||||
|
Queue = Channel.CreateUnbounded<WorkItem>(new UnboundedChannelOptions { SingleWriter = false, SingleReader = true, AllowSynchronousContinuations = true });
|
||||||
internal static void AssertWebWorkerContext()
|
|
||||||
{
|
|
||||||
#if FEATURE_WASM_THREADS
|
|
||||||
if (CurrentJSSynchronizationContext == null)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Please use dedicated worker for working with JavaScript interop. See https://aka.ms/dotnet-JS-interop-threads");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private JSSynchronizationContext(Thread targetThread, IntPtr targetTID, WorkItemQueueType queue)
|
|
||||||
{
|
|
||||||
TargetThread = targetThread;
|
|
||||||
TargetTID = targetTID;
|
|
||||||
Queue = queue;
|
|
||||||
_DataIsAvailable = DataIsAvailable;
|
_DataIsAvailable = DataIsAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal JSSynchronizationContext(JSProxyContext proxyContext, WorkItemQueueType queue, Action dataIsAvailable)
|
||||||
|
{
|
||||||
|
ProxyContext = proxyContext;
|
||||||
|
Queue = queue;
|
||||||
|
_DataIsAvailable = dataIsAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
public override SynchronizationContext CreateCopy()
|
public override SynchronizationContext CreateCopy()
|
||||||
{
|
{
|
||||||
return new JSSynchronizationContext(TargetThread, TargetTID, Queue);
|
return new JSSynchronizationContext(ProxyContext, Queue, _DataIsAvailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AwaitNewData()
|
internal void AwaitNewData()
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(isDisposed, this);
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
// FIXME: there could be abandoned work, but here we have no way how to propagate the failure
|
||||||
|
// ObjectDisposedException.ThrowIf(_isDisposed, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var vt = Queue.Reader.WaitToReadAsync();
|
var vt = Queue.Reader.WaitToReadAsync();
|
||||||
if (vt.IsCompleted)
|
if (vt.IsCompleted)
|
||||||
|
@ -103,16 +88,16 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
// While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn.
|
// While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn.
|
||||||
// Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever.
|
// Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever.
|
||||||
TargetThreadScheduleBackgroundJob(TargetTID, (void*)(delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler);
|
TargetThreadScheduleBackgroundJob(ProxyContext.NativeTID, (void*)(delegate* unmanaged[Cdecl]<void>)&BackgroundJobHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Post(SendOrPostCallback d, object? state)
|
public override void Post(SendOrPostCallback d, object? state)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(isDisposed, this);
|
ObjectDisposedException.ThrowIf(_isDisposed, this);
|
||||||
|
|
||||||
var workItem = new WorkItem(d, state, null);
|
var workItem = new WorkItem(d, state, null);
|
||||||
if (!Queue.Writer.TryWrite(workItem))
|
if (!Queue.Writer.TryWrite(workItem))
|
||||||
Environment.FailFast("JSSynchronizationContext.Post failed");
|
Environment.FailFast($"JSSynchronizationContext.Post failed, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// This path can only run when threading is enabled
|
// This path can only run when threading is enabled
|
||||||
|
@ -120,9 +105,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
|
||||||
public override void Send(SendOrPostCallback d, object? state)
|
public override void Send(SendOrPostCallback d, object? state)
|
||||||
{
|
{
|
||||||
ObjectDisposedException.ThrowIf(isDisposed, this);
|
ObjectDisposedException.ThrowIf(_isDisposed, this);
|
||||||
|
|
||||||
if (Thread.CurrentThread == TargetThread)
|
if (ProxyContext.IsCurrentThread())
|
||||||
{
|
{
|
||||||
d(state);
|
d(state);
|
||||||
return;
|
return;
|
||||||
|
@ -132,7 +117,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
var workItem = new WorkItem(d, state, signal);
|
var workItem = new WorkItem(d, state, signal);
|
||||||
if (!Queue.Writer.TryWrite(workItem))
|
if (!Queue.Writer.TryWrite(workItem))
|
||||||
Environment.FailFast("JSSynchronizationContext.Send failed");
|
Environment.FailFast($"JSSynchronizationContext.Send failed, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
|
||||||
signal.Wait();
|
signal.Wait();
|
||||||
}
|
}
|
||||||
|
@ -147,14 +132,16 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// this callback will arrive on the target thread, called from mono_background_exec
|
// this callback will arrive on the target thread, called from mono_background_exec
|
||||||
private static void BackgroundJobHandler()
|
private static void BackgroundJobHandler()
|
||||||
{
|
{
|
||||||
CurrentJSSynchronizationContext!.Pump();
|
var ctx = JSProxyContext.AssertIsInteropThread();
|
||||||
|
ctx.SynchronizationContext.Pump();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Pump()
|
private void Pump()
|
||||||
{
|
{
|
||||||
if (isDisposed)
|
if (_isDisposed)
|
||||||
{
|
{
|
||||||
// FIXME: there could be abandoned work, but here we have no way how to propagate the failure
|
// FIXME: there could be abandoned work, but here we have no way how to propagate the failure
|
||||||
|
// ObjectDisposedException.ThrowIf(_isDisposed, this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
|
@ -181,9 +168,28 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless.
|
// If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless.
|
||||||
if(!isDisposed) AwaitNewData();
|
if (!_isDisposed) AwaitNewData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_isDisposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
Queue.Writer.Complete();
|
||||||
|
}
|
||||||
|
previousSynchronizationContext = null;
|
||||||
|
_isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,15 +32,19 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
|
||||||
public static async Task<T> RunAsync<T>(Func<Task<T>> body, CancellationToken cancellationToken)
|
public static async Task<T> RunAsync<T>(Func<Task<T>> body, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO remove main thread condition later
|
if (JSProxyContext.MainThreadContext.IsCurrentThread())
|
||||||
if (Thread.CurrentThread.ManagedThreadId == 1) await JavaScriptImports.ThreadAvailable().ConfigureAwait(false);
|
{
|
||||||
|
await JavaScriptImports.ThreadAvailable().ConfigureAwait(false);
|
||||||
|
}
|
||||||
return await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false);
|
return await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task RunAsync(Func<Task> body, CancellationToken cancellationToken)
|
public static async Task RunAsync(Func<Task> body, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// TODO remove main thread condition later
|
if (JSProxyContext.MainThreadContext.IsCurrentThread())
|
||||||
if (Thread.CurrentThread.ManagedThreadId == 1) await JavaScriptImports.ThreadAvailable().ConfigureAwait(false);
|
{
|
||||||
|
await JavaScriptImports.ThreadAvailable().ConfigureAwait(false);
|
||||||
|
}
|
||||||
await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false);
|
await RunAsyncImpl(body, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,19 +16,19 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="_params">Parameters.</param>
|
/// <param name="_params">Parameters.</param>
|
||||||
public Array(params object[] _params)
|
public Array(params object[] _params)
|
||||||
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Array), _params))
|
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Array), _params), JSProxyContext.MainThreadContext)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
||||||
#endif
|
#endif
|
||||||
LegacyHostImplementation.RegisterCSOwnedObject(this);
|
JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the Array/> class.
|
/// Initializes a new instance of the Array/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jsHandle">Js handle.</param>
|
/// <param name="jsHandle">Js handle.</param>
|
||||||
internal Array(IntPtr jsHandle) : base(jsHandle)
|
internal Array(IntPtr jsHandle) : base(jsHandle, JSProxyContext.MainThreadContext)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -11,19 +11,19 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="length">Length.</param>
|
/// <param name="length">Length.</param>
|
||||||
public ArrayBuffer(int length)
|
public ArrayBuffer(int length)
|
||||||
: base(JavaScriptImports.CreateCSOwnedObject(nameof(ArrayBuffer), new object[] { length }))
|
: base(JavaScriptImports.CreateCSOwnedObject(nameof(ArrayBuffer), new object[] { length }), JSProxyContext.MainThreadContext)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
||||||
#endif
|
#endif
|
||||||
LegacyHostImplementation.RegisterCSOwnedObject(this);
|
JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the JavaScript Core ArrayBuffer class.
|
/// Initializes a new instance of the JavaScript Core ArrayBuffer class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jsHandle">Js handle.</param>
|
/// <param name="jsHandle">Js handle.</param>
|
||||||
internal ArrayBuffer(IntPtr jsHandle) : base(jsHandle)
|
internal ArrayBuffer(IntPtr jsHandle) : base(jsHandle, JSProxyContext.MainThreadContext)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -15,12 +15,12 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param>
|
/// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param>
|
||||||
public DataView(ArrayBuffer buffer)
|
public DataView(ArrayBuffer buffer)
|
||||||
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer }))
|
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer }), JSProxyContext.MainThreadContext)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
||||||
#endif
|
#endif
|
||||||
LegacyHostImplementation.RegisterCSOwnedObject(this);
|
JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -29,12 +29,12 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param>
|
/// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param>
|
||||||
/// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param>
|
/// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param>
|
||||||
public DataView(ArrayBuffer buffer, int byteOffset)
|
public DataView(ArrayBuffer buffer, int byteOffset)
|
||||||
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer, byteOffset }))
|
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer, byteOffset }), JSProxyContext.MainThreadContext)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
||||||
#endif
|
#endif
|
||||||
LegacyHostImplementation.RegisterCSOwnedObject(this);
|
JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -44,19 +44,19 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param>
|
/// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param>
|
||||||
/// <param name="byteLength">The number of elements in the byte array. If unspecified, the view's length will match the buffer's length.</param>
|
/// <param name="byteLength">The number of elements in the byte array. If unspecified, the view's length will match the buffer's length.</param>
|
||||||
public DataView(ArrayBuffer buffer, int byteOffset, int byteLength)
|
public DataView(ArrayBuffer buffer, int byteOffset, int byteLength)
|
||||||
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer, byteOffset, byteLength }))
|
: base(JavaScriptImports.CreateCSOwnedObject(nameof(DataView), new object[] { buffer, byteOffset, byteLength }), JSProxyContext.MainThreadContext)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
||||||
#endif
|
#endif
|
||||||
LegacyHostImplementation.RegisterCSOwnedObject(this);
|
JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the DataView class.
|
/// Initializes a new instance of the DataView class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jsHandle">Js handle.</param>
|
/// <param name="jsHandle">Js handle.</param>
|
||||||
internal DataView(IntPtr jsHandle) : base(jsHandle)
|
internal DataView(IntPtr jsHandle) : base(jsHandle, JSProxyContext.MainThreadContext)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -16,15 +16,15 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
public class Function : JSObject
|
public class Function : JSObject
|
||||||
{
|
{
|
||||||
public Function(params object[] args)
|
public Function(params object[] args)
|
||||||
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Function), args))
|
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Function), args), JSProxyContext.MainThreadContext)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
||||||
#endif
|
#endif
|
||||||
LegacyHostImplementation.RegisterCSOwnedObject(this);
|
JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Function(IntPtr jsHandle) : base(jsHandle)
|
internal Function(IntPtr jsHandle) : base(jsHandle, JSProxyContext.MainThreadContext)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -18,12 +18,6 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
jsObj?.ReleaseInFlight();
|
jsObj?.ReleaseInFlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static void RegisterCSOwnedObject(JSObject proxy)
|
|
||||||
{
|
|
||||||
JSHostImplementation.ThreadCsOwnedObjects[(int)proxy.JSHandle] = new WeakReference<JSObject>(proxy, trackResurrection: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MarshalType GetMarshalTypeFromType(Type type)
|
public static MarshalType GetMarshalTypeFromType(Type type)
|
||||||
{
|
{
|
||||||
if (type is null)
|
if (type is null)
|
||||||
|
|
|
@ -9,24 +9,24 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
public sealed class Uint8Array : JSObject
|
public sealed class Uint8Array : JSObject
|
||||||
{
|
{
|
||||||
public Uint8Array(int length)
|
public Uint8Array(int length)
|
||||||
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Uint8Array), new object[] { length }))
|
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Uint8Array), new object[] { length }), JSProxyContext.MainThreadContext)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
||||||
#endif
|
#endif
|
||||||
LegacyHostImplementation.RegisterCSOwnedObject(this);
|
JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uint8Array(ArrayBuffer buffer)
|
public Uint8Array(ArrayBuffer buffer)
|
||||||
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Uint8Array), new object[] { buffer }))
|
: base(JavaScriptImports.CreateCSOwnedObject(nameof(Uint8Array), new object[] { buffer }), JSProxyContext.MainThreadContext)
|
||||||
{
|
{
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
LegacyHostImplementation.ThrowIfLegacyWorkerThread();
|
||||||
#endif
|
#endif
|
||||||
LegacyHostImplementation.RegisterCSOwnedObject(this);
|
JSProxyContext.MainThreadContext.RegisterCSOwnedObject(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Uint8Array(IntPtr jsHandle) : base(jsHandle)
|
internal Uint8Array(IntPtr jsHandle) : base(jsHandle, JSProxyContext.MainThreadContext)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public int Length
|
public int Length
|
||||||
|
|
|
@ -133,7 +133,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
slot.Type = MarshalerType.ArraySegment;
|
slot.Type = MarshalerType.ArraySegment;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned);
|
||||||
var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array));
|
var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array));
|
||||||
slot.IntPtrValue = refPtr + value.Offset;
|
slot.IntPtrValue = refPtr + value.Offset;
|
||||||
slot.Length = value.Count;
|
slot.Length = value.Count;
|
||||||
|
|
|
@ -135,7 +135,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
slot.Type = MarshalerType.ArraySegment;
|
slot.Type = MarshalerType.ArraySegment;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned);
|
||||||
var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array));
|
var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array));
|
||||||
slot.IntPtrValue = refPtr + (value.Offset * sizeof(double));
|
slot.IntPtrValue = refPtr + (value.Offset * sizeof(double));
|
||||||
slot.Length = value.Count;
|
slot.Length = value.Count;
|
||||||
|
|
|
@ -33,7 +33,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
if (slot.JSHandle != IntPtr.Zero)
|
if (slot.JSHandle != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
// this is JSException round-trip
|
// this is JSException round-trip
|
||||||
jsException = JSHostImplementation.CreateCSOwnedProxy(slot.JSHandle);
|
var ctx = ToManagedContext;
|
||||||
|
jsException = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
string? message;
|
string? message;
|
||||||
|
@ -65,11 +66,21 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
var jse = cpy as JSException;
|
var jse = cpy as JSException;
|
||||||
if (jse != null && jse.jsException != null)
|
if (jse != null && jse.jsException != null)
|
||||||
{
|
{
|
||||||
|
ObjectDisposedException.ThrowIf(jse.jsException.IsDisposed, value);
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
JSObject.AssertThreadAffinity(value);
|
JSObject.AssertThreadAffinity(value);
|
||||||
|
var ctx = jse.jsException.ProxyContext;
|
||||||
|
if (JSProxyContext.CapturingState == JSProxyContext.JSImportOperationState.JSImportParams)
|
||||||
|
{
|
||||||
|
JSProxyContext.CaptureContextFromParameter(ctx);
|
||||||
|
slot.ContextHandle = ctx.ContextHandle;
|
||||||
|
}
|
||||||
|
else if (slot.ContextHandle != ctx.ContextHandle)
|
||||||
|
{
|
||||||
|
Environment.FailFast($"ContextHandle mismatch, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
// this is JSException roundtrip
|
// this is JSException roundtrip
|
||||||
ObjectDisposedException.ThrowIf(jse.jsException.IsDisposed, value);
|
|
||||||
slot.Type = MarshalerType.JSException;
|
slot.Type = MarshalerType.JSException;
|
||||||
slot.JSHandle = jse.jsException.JSHandle;
|
slot.JSHandle = jse.jsException.JSHandle;
|
||||||
}
|
}
|
||||||
|
@ -77,7 +88,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
ToJS(cpy.Message);
|
ToJS(cpy.Message);
|
||||||
slot.Type = MarshalerType.Exception;
|
slot.Type = MarshalerType.Exception;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cpy);
|
|
||||||
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cpy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
private JSObject JSObject;
|
private JSObject JSObject;
|
||||||
|
|
||||||
public ActionJS(IntPtr jsHandle)
|
public ActionJS(JSObject holder)
|
||||||
{
|
{
|
||||||
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle);
|
JSObject = holder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvokeJS()
|
public void InvokeJS()
|
||||||
|
@ -26,8 +26,14 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4];
|
Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4];
|
||||||
ref JSMarshalerArgument args_exception = ref arguments[0];
|
ref JSMarshalerArgument args_exception = ref arguments[0];
|
||||||
ref JSMarshalerArgument args_return = ref arguments[1];
|
ref JSMarshalerArgument args_return = ref arguments[1];
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
args_exception.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_return.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
args_exception.Initialize();
|
args_exception.Initialize();
|
||||||
args_return.Initialize();
|
args_return.Initialize();
|
||||||
|
#endif
|
||||||
|
|
||||||
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
||||||
}
|
}
|
||||||
|
@ -39,9 +45,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
private ArgumentToJSCallback<T> Arg1Marshaler;
|
private ArgumentToJSCallback<T> Arg1Marshaler;
|
||||||
private JSObject JSObject;
|
private JSObject JSObject;
|
||||||
|
|
||||||
public ActionJS(IntPtr jsHandle, ArgumentToJSCallback<T> arg1Marshaler)
|
public ActionJS(JSObject holder, ArgumentToJSCallback<T> arg1Marshaler)
|
||||||
{
|
{
|
||||||
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle);
|
JSObject = holder;
|
||||||
Arg1Marshaler = arg1Marshaler;
|
Arg1Marshaler = arg1Marshaler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,10 +62,18 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument args_return = ref arguments[1];
|
ref JSMarshalerArgument args_return = ref arguments[1];
|
||||||
ref JSMarshalerArgument args_arg1 = ref arguments[2];
|
ref JSMarshalerArgument args_arg1 = ref arguments[2];
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
args_exception.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_return.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg1.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
args_exception.Initialize();
|
args_exception.Initialize();
|
||||||
args_return.Initialize();
|
args_return.Initialize();
|
||||||
|
#endif
|
||||||
Arg1Marshaler(ref args_arg1, arg1);
|
Arg1Marshaler(ref args_arg1, arg1);
|
||||||
|
|
||||||
|
|
||||||
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,9 +84,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
private ArgumentToJSCallback<T2> Arg2Marshaler;
|
private ArgumentToJSCallback<T2> Arg2Marshaler;
|
||||||
private JSObject JSObject;
|
private JSObject JSObject;
|
||||||
|
|
||||||
public ActionJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler)
|
public ActionJS(JSObject holder, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler)
|
||||||
{
|
{
|
||||||
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle);
|
JSObject = holder;
|
||||||
Arg1Marshaler = arg1Marshaler;
|
Arg1Marshaler = arg1Marshaler;
|
||||||
Arg2Marshaler = arg2Marshaler;
|
Arg2Marshaler = arg2Marshaler;
|
||||||
}
|
}
|
||||||
|
@ -89,11 +103,20 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument args_arg1 = ref arguments[2];
|
ref JSMarshalerArgument args_arg1 = ref arguments[2];
|
||||||
ref JSMarshalerArgument args_arg2 = ref arguments[3];
|
ref JSMarshalerArgument args_arg2 = ref arguments[3];
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
args_exception.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_return.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg1.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg2.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
args_exception.Initialize();
|
args_exception.Initialize();
|
||||||
args_return.Initialize();
|
args_return.Initialize();
|
||||||
|
#endif
|
||||||
Arg1Marshaler(ref args_arg1, arg1);
|
Arg1Marshaler(ref args_arg1, arg1);
|
||||||
Arg2Marshaler(ref args_arg2, arg2);
|
Arg2Marshaler(ref args_arg2, arg2);
|
||||||
|
|
||||||
|
|
||||||
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,9 +128,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
private ArgumentToJSCallback<T3> Arg3Marshaler;
|
private ArgumentToJSCallback<T3> Arg3Marshaler;
|
||||||
private JSObject JSObject;
|
private JSObject JSObject;
|
||||||
|
|
||||||
public ActionJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler)
|
public ActionJS(JSObject holder, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler)
|
||||||
{
|
{
|
||||||
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle);
|
JSObject = holder;
|
||||||
Arg1Marshaler = arg1Marshaler;
|
Arg1Marshaler = arg1Marshaler;
|
||||||
Arg2Marshaler = arg2Marshaler;
|
Arg2Marshaler = arg2Marshaler;
|
||||||
Arg3Marshaler = arg3Marshaler;
|
Arg3Marshaler = arg3Marshaler;
|
||||||
|
@ -126,12 +149,22 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument args_arg2 = ref arguments[3];
|
ref JSMarshalerArgument args_arg2 = ref arguments[3];
|
||||||
ref JSMarshalerArgument args_arg3 = ref arguments[4];
|
ref JSMarshalerArgument args_arg3 = ref arguments[4];
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
args_exception.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_return.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg1.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg2.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg3.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
args_exception.Initialize();
|
args_exception.Initialize();
|
||||||
args_return.Initialize();
|
args_return.Initialize();
|
||||||
|
#endif
|
||||||
Arg1Marshaler(ref args_arg1, arg1);
|
Arg1Marshaler(ref args_arg1, arg1);
|
||||||
Arg2Marshaler(ref args_arg2, arg2);
|
Arg2Marshaler(ref args_arg2, arg2);
|
||||||
Arg3Marshaler(ref args_arg3, arg3);
|
Arg3Marshaler(ref args_arg3, arg3);
|
||||||
|
|
||||||
|
|
||||||
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,7 +182,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = new ActionJS(slot.JSHandle).InvokeJS;
|
var ctx = ToManagedContext;
|
||||||
|
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
value = new ActionJS(holder).InvokeJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -167,7 +202,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = new ActionJS<T>(slot.JSHandle, arg1Marshaler).InvokeJS;
|
var ctx = ToManagedContext;
|
||||||
|
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
value = new ActionJS<T>(holder, arg1Marshaler).InvokeJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -187,7 +224,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = new ActionJS<T1, T2>(slot.JSHandle, arg1Marshaler, arg2Marshaler).InvokeJS;
|
var ctx = ToManagedContext;
|
||||||
|
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
value = new ActionJS<T1, T2>(holder, arg1Marshaler, arg2Marshaler).InvokeJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -209,7 +248,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = new ActionJS<T1, T2, T3>(slot.JSHandle, arg1Marshaler, arg2Marshaler, arg3Marshaler).InvokeJS;
|
var ctx = ToManagedContext;
|
||||||
|
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
value = new ActionJS<T1, T2, T3>(holder, arg1Marshaler, arg2Marshaler, arg3Marshaler).InvokeJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class FuncJS<TResult>
|
private sealed class FuncJS<TResult>
|
||||||
|
@ -217,9 +258,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
private JSObject JSObject;
|
private JSObject JSObject;
|
||||||
private ArgumentToManagedCallback<TResult> ResMarshaler;
|
private ArgumentToManagedCallback<TResult> ResMarshaler;
|
||||||
|
|
||||||
public FuncJS(IntPtr jsHandle, ArgumentToManagedCallback<TResult> resMarshaler)
|
public FuncJS(JSObject holder, ArgumentToManagedCallback<TResult> resMarshaler)
|
||||||
{
|
{
|
||||||
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle);
|
JSObject = holder;
|
||||||
ResMarshaler = resMarshaler;
|
ResMarshaler = resMarshaler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,12 +276,19 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4];
|
Span<JSMarshalerArgument> arguments = stackalloc JSMarshalerArgument[4];
|
||||||
ref JSMarshalerArgument args_exception = ref arguments[0];
|
ref JSMarshalerArgument args_exception = ref arguments[0];
|
||||||
ref JSMarshalerArgument args_return = ref arguments[1];
|
ref JSMarshalerArgument args_return = ref arguments[1];
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
args_exception.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_return.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
args_exception.Initialize();
|
args_exception.Initialize();
|
||||||
args_return.Initialize();
|
args_return.Initialize();
|
||||||
|
#endif
|
||||||
|
|
||||||
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
||||||
|
|
||||||
ResMarshaler(ref args_return, out TResult res);
|
ResMarshaler(ref args_return, out TResult res);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,9 +300,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
private ArgumentToManagedCallback<TResult> ResMarshaler;
|
private ArgumentToManagedCallback<TResult> ResMarshaler;
|
||||||
private JSObject JSObject;
|
private JSObject JSObject;
|
||||||
|
|
||||||
public FuncJS(IntPtr jsHandle, ArgumentToJSCallback<T> arg1Marshaler, ArgumentToManagedCallback<TResult> resMarshaler)
|
public FuncJS(JSObject holder, ArgumentToJSCallback<T> arg1Marshaler, ArgumentToManagedCallback<TResult> resMarshaler)
|
||||||
{
|
{
|
||||||
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle);
|
JSObject = holder;
|
||||||
Arg1Marshaler = arg1Marshaler;
|
Arg1Marshaler = arg1Marshaler;
|
||||||
ResMarshaler = resMarshaler;
|
ResMarshaler = resMarshaler;
|
||||||
}
|
}
|
||||||
|
@ -270,8 +318,15 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument args_return = ref arguments[1];
|
ref JSMarshalerArgument args_return = ref arguments[1];
|
||||||
ref JSMarshalerArgument args_arg1 = ref arguments[2];
|
ref JSMarshalerArgument args_arg1 = ref arguments[2];
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
args_exception.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_return.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg1.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
args_exception.Initialize();
|
args_exception.Initialize();
|
||||||
args_return.Initialize();
|
args_return.Initialize();
|
||||||
|
#endif
|
||||||
Arg1Marshaler(ref args_arg1, arg1);
|
Arg1Marshaler(ref args_arg1, arg1);
|
||||||
|
|
||||||
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
||||||
|
@ -288,9 +343,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
private ArgumentToManagedCallback<TResult> ResMarshaler;
|
private ArgumentToManagedCallback<TResult> ResMarshaler;
|
||||||
private JSObject JSObject;
|
private JSObject JSObject;
|
||||||
|
|
||||||
public FuncJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToManagedCallback<TResult> resMarshaler)
|
public FuncJS(JSObject holder, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToManagedCallback<TResult> resMarshaler)
|
||||||
{
|
{
|
||||||
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle);
|
JSObject = holder;
|
||||||
Arg1Marshaler = arg1Marshaler;
|
Arg1Marshaler = arg1Marshaler;
|
||||||
Arg2Marshaler = arg2Marshaler;
|
Arg2Marshaler = arg2Marshaler;
|
||||||
ResMarshaler = resMarshaler;
|
ResMarshaler = resMarshaler;
|
||||||
|
@ -308,8 +363,16 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument args_arg1 = ref arguments[2];
|
ref JSMarshalerArgument args_arg1 = ref arguments[2];
|
||||||
ref JSMarshalerArgument args_arg2 = ref arguments[3];
|
ref JSMarshalerArgument args_arg2 = ref arguments[3];
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
args_exception.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_return.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg1.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg2.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
args_exception.Initialize();
|
args_exception.Initialize();
|
||||||
args_return.Initialize();
|
args_return.Initialize();
|
||||||
|
#endif
|
||||||
Arg1Marshaler(ref args_arg1, arg1);
|
Arg1Marshaler(ref args_arg1, arg1);
|
||||||
Arg2Marshaler(ref args_arg2, arg2);
|
Arg2Marshaler(ref args_arg2, arg2);
|
||||||
|
|
||||||
|
@ -328,9 +391,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
private ArgumentToManagedCallback<TResult> ResMarshaler;
|
private ArgumentToManagedCallback<TResult> ResMarshaler;
|
||||||
private JSObject JSObject;
|
private JSObject JSObject;
|
||||||
|
|
||||||
public FuncJS(IntPtr jsHandle, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler, ArgumentToManagedCallback<TResult> resMarshaler)
|
public FuncJS(JSObject holder, ArgumentToJSCallback<T1> arg1Marshaler, ArgumentToJSCallback<T2> arg2Marshaler, ArgumentToJSCallback<T3> arg3Marshaler, ArgumentToManagedCallback<TResult> resMarshaler)
|
||||||
{
|
{
|
||||||
JSObject = JSHostImplementation.CreateCSOwnedProxy(jsHandle);
|
JSObject = holder;
|
||||||
Arg1Marshaler = arg1Marshaler;
|
Arg1Marshaler = arg1Marshaler;
|
||||||
Arg2Marshaler = arg2Marshaler;
|
Arg2Marshaler = arg2Marshaler;
|
||||||
Arg3Marshaler = arg3Marshaler;
|
Arg3Marshaler = arg3Marshaler;
|
||||||
|
@ -350,15 +413,24 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument args_arg2 = ref arguments[3];
|
ref JSMarshalerArgument args_arg2 = ref arguments[3];
|
||||||
ref JSMarshalerArgument args_arg3 = ref arguments[4];
|
ref JSMarshalerArgument args_arg3 = ref arguments[4];
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
args_exception.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_return.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg1.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg2.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
args_arg3.InitializeWithContext(JSObject.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
args_exception.Initialize();
|
args_exception.Initialize();
|
||||||
args_return.Initialize();
|
args_return.Initialize();
|
||||||
|
#endif
|
||||||
Arg1Marshaler(ref args_arg1, arg1);
|
Arg1Marshaler(ref args_arg1, arg1);
|
||||||
Arg2Marshaler(ref args_arg2, arg2);
|
Arg2Marshaler(ref args_arg2, arg2);
|
||||||
Arg3Marshaler(ref args_arg3, arg3);
|
Arg3Marshaler(ref args_arg3, arg3);
|
||||||
|
|
||||||
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
JSFunctionBinding.InvokeJSFunction(JSObject, arguments);
|
||||||
|
|
||||||
ResMarshaler(ref args_return, out TResult res);
|
ResMarshaler(ref args_return, out TResult res);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,7 +450,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = new FuncJS<TResult>(slot.JSHandle, resMarshaler).InvokeJS;
|
var ctx = ToManagedContext;
|
||||||
|
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
value = new FuncJS<TResult>(holder, resMarshaler).InvokeJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -398,7 +472,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = new FuncJS<T, TResult>(slot.JSHandle, arg1Marshaler, resMarshaler).InvokeJS;
|
var ctx = ToManagedContext;
|
||||||
|
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
value = new FuncJS<T, TResult>(holder, arg1Marshaler, resMarshaler).InvokeJS;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,7 +497,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = new FuncJS<T1, T2, TResult>(slot.JSHandle, arg1Marshaler, arg2Marshaler, resMarshaler).InvokeJS;
|
var ctx = ToManagedContext;
|
||||||
|
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
value = new FuncJS<T1, T2, TResult>(holder, arg1Marshaler, arg2Marshaler, resMarshaler).InvokeJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -444,8 +522,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
value = null;
|
value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var ctx = ToManagedContext;
|
||||||
value = new FuncJS<T1, T2, T3, TResult>(slot.JSHandle, arg1Marshaler, arg2Marshaler, arg3Marshaler, resMarshaler).InvokeJS;
|
var holder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
value = new FuncJS<T1, T2, T3, TResult>(holder, arg1Marshaler, arg2Marshaler, arg3Marshaler, resMarshaler).InvokeJS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -463,7 +542,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// eventual exception is handled by C# caller
|
// eventual exception is handled by C# caller
|
||||||
};
|
};
|
||||||
slot.Type = MarshalerType.Function;
|
slot.Type = MarshalerType.Function;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -484,7 +564,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// eventual exception is handled by C# caller
|
// eventual exception is handled by C# caller
|
||||||
};
|
};
|
||||||
slot.Type = MarshalerType.Action;
|
slot.Type = MarshalerType.Action;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -509,7 +590,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// eventual exception is handled by C# caller
|
// eventual exception is handled by C# caller
|
||||||
};
|
};
|
||||||
slot.Type = MarshalerType.Action;
|
slot.Type = MarshalerType.Action;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -538,7 +620,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// eventual exception is handled by C# caller
|
// eventual exception is handled by C# caller
|
||||||
};
|
};
|
||||||
slot.Type = MarshalerType.Action;
|
slot.Type = MarshalerType.Action;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -559,7 +642,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// eventual exception is handled by C# caller
|
// eventual exception is handled by C# caller
|
||||||
};
|
};
|
||||||
slot.Type = MarshalerType.Function;
|
slot.Type = MarshalerType.Function;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -584,7 +668,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// eventual exception is handled by C# caller
|
// eventual exception is handled by C# caller
|
||||||
};
|
};
|
||||||
slot.Type = MarshalerType.Function;
|
slot.Type = MarshalerType.Function;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -613,7 +698,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// eventual exception is handled by C# caller
|
// eventual exception is handled by C# caller
|
||||||
};
|
};
|
||||||
slot.Type = MarshalerType.Function;
|
slot.Type = MarshalerType.Function;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -646,7 +732,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// eventual exception is handled by C# caller
|
// eventual exception is handled by C# caller
|
||||||
};
|
};
|
||||||
slot.Type = MarshalerType.Function;
|
slot.Type = MarshalerType.Function;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(cb);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,8 +132,9 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
slot.Type = MarshalerType.None;
|
slot.Type = MarshalerType.None;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var ctx = ToJSContext;
|
||||||
slot.Type = MarshalerType.ArraySegment;
|
slot.Type = MarshalerType.ArraySegment;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned);
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(value.Array, GCHandleType.Pinned);
|
||||||
var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array));
|
var refPtr = (IntPtr)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(value.Array));
|
||||||
slot.IntPtrValue = refPtr + (value.Offset * sizeof(int));
|
slot.IntPtrValue = refPtr + (value.Offset * sizeof(int));
|
||||||
slot.Length = value.Count;
|
slot.Length = value.Count;
|
||||||
|
|
|
@ -20,7 +20,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
value = null;
|
value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
value = JSHostImplementation.CreateCSOwnedProxy(slot.JSHandle);
|
var ctx = ToManagedContext;
|
||||||
|
value = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -34,13 +35,25 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
slot.Type = MarshalerType.None;
|
slot.Type = MarshalerType.None;
|
||||||
|
// Note: when null JSObject is passed as argument, it can't be used to capture the target thread in JSProxyContext.CapturedInstance
|
||||||
|
// in case there is no other argument to capture it from, the call will be dispatched according to JSProxyContext.Default
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
ObjectDisposedException.ThrowIf(value.IsDisposed, value);
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
JSObject.AssertThreadAffinity(value);
|
JSObject.AssertThreadAffinity(value);
|
||||||
|
var ctx = value.ProxyContext;
|
||||||
|
if (JSProxyContext.CapturingState == JSProxyContext.JSImportOperationState.JSImportParams)
|
||||||
|
{
|
||||||
|
JSProxyContext.CaptureContextFromParameter(ctx);
|
||||||
|
slot.ContextHandle = ctx.ContextHandle;
|
||||||
|
}
|
||||||
|
else if (slot.ContextHandle != ctx.ContextHandle)
|
||||||
|
{
|
||||||
|
Environment.FailFast($"ContextHandle mismatch, ManagedThreadId: {Environment.CurrentManagedThreadId}. {Environment.NewLine} {Environment.StackTrace}");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
ObjectDisposedException.ThrowIf(value.IsDisposed, value);
|
|
||||||
slot.Type = MarshalerType.JSObject;
|
slot.Type = MarshalerType.JSObject;
|
||||||
slot.JSHandle = value.JSHandle;
|
slot.JSHandle = value.JSHandle;
|
||||||
}
|
}
|
||||||
|
|
|
@ -317,7 +317,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
slot.Type = MarshalerType.Object;
|
slot.Type = MarshalerType.Object;
|
||||||
slot.GCHandle = JSHostImplementation.GetJSOwnedObjectGCHandle(value);
|
var ctx = ToJSContext;
|
||||||
|
slot.GCHandle = ctx.GetJSOwnedObjectGCHandle(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Threading;
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSHostImplementation;
|
using static System.Runtime.InteropServices.JavaScript.JSHostImplementation;
|
||||||
|
|
||||||
namespace System.Runtime.InteropServices.JavaScript
|
namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
@ -17,7 +19,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// <typeparam name="T">Type of the marshaled value.</typeparam>
|
/// <typeparam name="T">Type of the marshaled value.</typeparam>
|
||||||
/// <param name="arg">The low-level argument representation.</param>
|
/// <param name="arg">The low-level argument representation.</param>
|
||||||
/// <param name="value">The value to be marshaled.</param>
|
/// <param name="value">The value to be marshaled.</param>
|
||||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
|
[EditorBrowsableAttribute(EditorBrowsableState.Never)]
|
||||||
public delegate void ArgumentToManagedCallback<T>(ref JSMarshalerArgument arg, out T value);
|
public delegate void ArgumentToManagedCallback<T>(ref JSMarshalerArgument arg, out T value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -27,7 +29,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// <typeparam name="T">Type of the marshaled value.</typeparam>
|
/// <typeparam name="T">Type of the marshaled value.</typeparam>
|
||||||
/// <param name="arg">The low-level argument representation.</param>
|
/// <param name="arg">The low-level argument representation.</param>
|
||||||
/// <param name="value">The value to be marshaled.</param>
|
/// <param name="value">The value to be marshaled.</param>
|
||||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
|
[EditorBrowsableAttribute(EditorBrowsableState.Never)]
|
||||||
public delegate void ArgumentToJSCallback<T>(ref JSMarshalerArgument arg, T value);
|
public delegate void ArgumentToJSCallback<T>(ref JSMarshalerArgument arg, T value);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -43,30 +45,38 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
value = null;
|
value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PromiseHolder holder = GetPromiseHolder(slot.GCHandle);
|
var ctx = ToManagedContext;
|
||||||
TaskCompletionSource tcs = new TaskCompletionSource(holder);
|
lock (ctx)
|
||||||
ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) =>
|
|
||||||
{
|
{
|
||||||
if (arguments_buffer == null)
|
PromiseHolder holder = ctx.GetPromiseHolder(slot.GCHandle);
|
||||||
|
TaskCompletionSource tcs = new TaskCompletionSource(holder);
|
||||||
|
ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) =>
|
||||||
{
|
{
|
||||||
tcs.TrySetException(new TaskCanceledException("WebWorker which is origin of the Promise is being terminated."));
|
if (arguments_buffer == null)
|
||||||
return;
|
{
|
||||||
}
|
tcs.TrySetException(new TaskCanceledException("WebWorker which is origin of the Promise is being terminated."));
|
||||||
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // set by caller when this is SetException call
|
return;
|
||||||
// arg_3 set by caller when this is SetResult call, un-used here
|
}
|
||||||
if (arg_2.slot.Type != MarshalerType.None)
|
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // set by caller when this is SetException call
|
||||||
{
|
// arg_3 set by caller when this is SetResult call, un-used here
|
||||||
arg_2.ToManaged(out Exception? fail);
|
if (arg_2.slot.Type != MarshalerType.None)
|
||||||
tcs.SetException(fail!);
|
{
|
||||||
}
|
arg_2.ToManaged(out Exception? fail);
|
||||||
else
|
tcs.SetException(fail!);
|
||||||
{
|
}
|
||||||
tcs.SetResult();
|
else
|
||||||
}
|
{
|
||||||
// eventual exception is handled by caller
|
tcs.SetResult();
|
||||||
};
|
}
|
||||||
holder.Callback = callback;
|
// eventual exception is handled by caller
|
||||||
value = tcs.Task;
|
};
|
||||||
|
holder.Callback = callback;
|
||||||
|
value = tcs.Task;
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
// if the other thread created it, signal that it's ready
|
||||||
|
holder.CallbackReady?.Set();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -74,8 +84,8 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
/// It's used by JSImport code generator and should not be used by developers in source code.
|
/// It's used by JSImport code generator and should not be used by developers in source code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The value to be marshaled.</param>
|
/// <param name="value">The value to be marshaled.</param>
|
||||||
/// <param name="marshaler">The generated callback which marshals the result value of the <see cref="System.Threading.Tasks.Task"/>.</param>
|
/// <param name="marshaler">The generated callback which marshals the result value of the <see cref="Task"/>.</param>
|
||||||
/// <typeparam name="T">Type of marshaled result of the <see cref="System.Threading.Tasks.Task"/>.</typeparam>
|
/// <typeparam name="T">Type of marshaled result of the <see cref="Task"/>.</typeparam>
|
||||||
public unsafe void ToManaged<T>(out Task<T>? value, ArgumentToManagedCallback<T> marshaler)
|
public unsafe void ToManaged<T>(out Task<T>? value, ArgumentToManagedCallback<T> marshaler)
|
||||||
{
|
{
|
||||||
// there is no nice way in JS how to check that JS promise is already resolved, to send MarshalerType.TaskRejected, MarshalerType.TaskResolved
|
// there is no nice way in JS how to check that JS promise is already resolved, to send MarshalerType.TaskRejected, MarshalerType.TaskResolved
|
||||||
|
@ -84,52 +94,43 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
value = null;
|
value = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PromiseHolder holder = GetPromiseHolder(slot.GCHandle);
|
var ctx = ToManagedContext;
|
||||||
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>(holder);
|
lock (ctx)
|
||||||
ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) =>
|
|
||||||
{
|
{
|
||||||
if (arguments_buffer == null)
|
var holder = ctx.GetPromiseHolder(slot.GCHandle);
|
||||||
|
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>(holder);
|
||||||
|
ToManagedCallback callback = (JSMarshalerArgument* arguments_buffer) =>
|
||||||
{
|
{
|
||||||
tcs.TrySetException(new TaskCanceledException("WebWorker which is origin of the Promise is being terminated."));
|
if (arguments_buffer == null)
|
||||||
return;
|
{
|
||||||
}
|
tcs.TrySetException(new TaskCanceledException("WebWorker which is origin of the Promise is being terminated."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // set by caller when this is SetException call
|
ref JSMarshalerArgument arg_2 = ref arguments_buffer[3]; // set by caller when this is SetException call
|
||||||
ref JSMarshalerArgument arg_3 = ref arguments_buffer[4]; // set by caller when this is SetResult call
|
ref JSMarshalerArgument arg_3 = ref arguments_buffer[4]; // set by caller when this is SetResult call
|
||||||
if (arg_2.slot.Type != MarshalerType.None)
|
if (arg_2.slot.Type != MarshalerType.None)
|
||||||
{
|
{
|
||||||
arg_2.ToManaged(out Exception? fail);
|
arg_2.ToManaged(out Exception? fail);
|
||||||
if (fail == null) throw new InvalidOperationException(SR.FailedToMarshalException);
|
if (fail == null) throw new InvalidOperationException(SR.FailedToMarshalException);
|
||||||
tcs.SetException(fail);
|
tcs.SetException(fail);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
marshaler(ref arg_3, out T result);
|
marshaler(ref arg_3, out T result);
|
||||||
tcs.SetResult(result);
|
tcs.SetResult(result);
|
||||||
}
|
}
|
||||||
// eventual exception is handled by caller
|
// eventual exception is handled by caller
|
||||||
};
|
};
|
||||||
holder.Callback = callback;
|
holder.Callback = callback;
|
||||||
value = tcs.Task;
|
value = tcs.Task;
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
// if the other thread created it, signal that it's ready
|
||||||
|
holder.CallbackReady?.Set();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO unregister and collect pending PromiseHolder also when no C# is awaiting ?
|
|
||||||
private static PromiseHolder GetPromiseHolder(nint gcHandle)
|
|
||||||
{
|
|
||||||
PromiseHolder holder;
|
|
||||||
if (IsGCVHandle(gcHandle))
|
|
||||||
{
|
|
||||||
// this path should only happen when the Promise is passed as argument of JSExport
|
|
||||||
holder = new PromiseHolder(gcHandle);
|
|
||||||
// TODO for MT this must hit the ThreadJsOwnedHolders in the correct thread
|
|
||||||
ThreadJsOwnedHolders.Add(gcHandle, holder);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
holder = (PromiseHolder)((GCHandle)gcHandle).Target!;
|
|
||||||
}
|
|
||||||
return holder;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void ToJSDynamic(Task? value)
|
internal void ToJSDynamic(Task? value)
|
||||||
{
|
{
|
||||||
|
@ -167,10 +168,12 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ctx = ToJSContext;
|
||||||
|
|
||||||
if (slot.Type != MarshalerType.TaskPreCreated)
|
if (slot.Type != MarshalerType.TaskPreCreated)
|
||||||
{
|
{
|
||||||
// this path should only happen when the Task is passed as argument of JSImport
|
// this path should only happen when the Task is passed as argument of JSImport
|
||||||
slot.JSHandle = AllocJSVHandle();
|
slot.JSHandle = ctx.AllocJSVHandle();
|
||||||
slot.Type = MarshalerType.Task;
|
slot.Type = MarshalerType.Task;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -179,7 +182,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// promise and handle is pre-allocated in slot.JSHandle
|
// promise and handle is pre-allocated in slot.JSHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
var taskHolder = new JSObject(slot.JSHandle);
|
var taskHolder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
task.ContinueWith(Complete, taskHolder, TaskScheduler.FromCurrentSynchronizationContext());
|
task.ContinueWith(Complete, taskHolder, TaskScheduler.FromCurrentSynchronizationContext());
|
||||||
|
@ -245,10 +248,12 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ctx = ToJSContext;
|
||||||
|
|
||||||
if (slot.Type != MarshalerType.TaskPreCreated)
|
if (slot.Type != MarshalerType.TaskPreCreated)
|
||||||
{
|
{
|
||||||
// this path should only happen when the Task is passed as argument of JSImport
|
// this path should only happen when the Task is passed as argument of JSImport
|
||||||
slot.JSHandle = AllocJSVHandle();
|
slot.JSHandle = ctx.AllocJSVHandle();
|
||||||
slot.Type = MarshalerType.Task;
|
slot.Type = MarshalerType.Task;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -257,7 +262,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// promise and handle is pre-allocated in slot.JSHandle
|
// promise and handle is pre-allocated in slot.JSHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
var taskHolder = new JSObject(slot.JSHandle);
|
var taskHolder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
task.ContinueWith(Complete, taskHolder, TaskScheduler.FromCurrentSynchronizationContext());
|
task.ContinueWith(Complete, taskHolder, TaskScheduler.FromCurrentSynchronizationContext());
|
||||||
|
@ -316,10 +321,11 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ctx = ToJSContext;
|
||||||
if (slot.Type != MarshalerType.TaskPreCreated)
|
if (slot.Type != MarshalerType.TaskPreCreated)
|
||||||
{
|
{
|
||||||
// this path should only happen when the Task is passed as argument of JSImport
|
// this path should only happen when the Task is passed as argument of JSImport
|
||||||
slot.JSHandle = AllocJSVHandle();
|
slot.JSHandle = ctx.AllocJSVHandle();
|
||||||
slot.Type = MarshalerType.Task;
|
slot.Type = MarshalerType.Task;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -328,7 +334,7 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// promise and handle is pre-allocated in slot.JSHandle
|
// promise and handle is pre-allocated in slot.JSHandle
|
||||||
}
|
}
|
||||||
|
|
||||||
var taskHolder = new JSObject(slot.JSHandle);
|
var taskHolder = ctx.CreateCSOwnedProxy(slot.JSHandle);
|
||||||
|
|
||||||
#if FEATURE_WASM_THREADS
|
#if FEATURE_WASM_THREADS
|
||||||
task.ContinueWith(Complete, new HolderAndMarshaler<T>(taskHolder, marshaler), TaskScheduler.FromCurrentSynchronizationContext());
|
task.ContinueWith(Complete, new HolderAndMarshaler<T>(taskHolder, marshaler), TaskScheduler.FromCurrentSynchronizationContext());
|
||||||
|
@ -357,14 +363,26 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
{
|
{
|
||||||
holder.AssertNotDisposed();
|
holder.AssertNotDisposed();
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
JSObject.AssertThreadAffinity(holder);
|
||||||
|
#endif
|
||||||
|
|
||||||
Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4];
|
Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4];
|
||||||
ref JSMarshalerArgument exc = ref args[0];
|
ref JSMarshalerArgument exc = ref args[0];
|
||||||
ref JSMarshalerArgument res = ref args[1];
|
ref JSMarshalerArgument res = ref args[1];
|
||||||
ref JSMarshalerArgument arg_handle = ref args[2];
|
ref JSMarshalerArgument arg_handle = ref args[2];
|
||||||
ref JSMarshalerArgument arg_value = ref args[3];
|
ref JSMarshalerArgument arg_value = ref args[3];
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
exc.InitializeWithContext(holder.ProxyContext);
|
||||||
|
res.InitializeWithContext(holder.ProxyContext);
|
||||||
|
arg_value.InitializeWithContext(holder.ProxyContext);
|
||||||
|
arg_handle.InitializeWithContext(holder.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
exc.Initialize();
|
exc.Initialize();
|
||||||
res.Initialize();
|
res.Initialize();
|
||||||
|
#endif
|
||||||
|
|
||||||
// should update existing promise
|
// should update existing promise
|
||||||
arg_handle.slot.Type = MarshalerType.TaskRejected;
|
arg_handle.slot.Type = MarshalerType.TaskRejected;
|
||||||
|
@ -373,14 +391,24 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// should fail it with exception
|
// should fail it with exception
|
||||||
arg_value.ToJS(ex);
|
arg_value.ToJS(ex);
|
||||||
|
|
||||||
JavaScriptImports.ResolveOrRejectPromise(args);
|
// we can free the JSHandle here and the holder.resolve_or_reject will do the rest
|
||||||
|
holder.DisposeImpl(skipJsCleanup: true);
|
||||||
|
|
||||||
holder.DisposeLocal();
|
#if !FEATURE_WASM_THREADS
|
||||||
|
// order of operations with DisposeImpl matters
|
||||||
|
JSFunctionBinding.ResolveOrRejectPromise(args);
|
||||||
|
#else
|
||||||
|
// order of operations with DisposeImpl matters
|
||||||
|
JSFunctionBinding.ResolveOrRejectPromise(args);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ResolveVoidPromise(JSObject holder)
|
private static void ResolveVoidPromise(JSObject holder)
|
||||||
{
|
{
|
||||||
holder.AssertNotDisposed();
|
holder.AssertNotDisposed();
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
JSObject.AssertThreadAffinity(holder);
|
||||||
|
#endif
|
||||||
|
|
||||||
Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4];
|
Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4];
|
||||||
ref JSMarshalerArgument exc = ref args[0];
|
ref JSMarshalerArgument exc = ref args[0];
|
||||||
|
@ -388,8 +416,16 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument arg_handle = ref args[2];
|
ref JSMarshalerArgument arg_handle = ref args[2];
|
||||||
ref JSMarshalerArgument arg_value = ref args[3];
|
ref JSMarshalerArgument arg_value = ref args[3];
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
exc.InitializeWithContext(holder.ProxyContext);
|
||||||
|
res.InitializeWithContext(holder.ProxyContext);
|
||||||
|
arg_value.InitializeWithContext(holder.ProxyContext);
|
||||||
|
arg_handle.InitializeWithContext(holder.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
exc.Initialize();
|
exc.Initialize();
|
||||||
res.Initialize();
|
res.Initialize();
|
||||||
|
#endif
|
||||||
|
|
||||||
// should update existing promise
|
// should update existing promise
|
||||||
arg_handle.slot.Type = MarshalerType.TaskResolved;
|
arg_handle.slot.Type = MarshalerType.TaskResolved;
|
||||||
|
@ -397,14 +433,24 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
|
|
||||||
arg_value.slot.Type = MarshalerType.Void;
|
arg_value.slot.Type = MarshalerType.Void;
|
||||||
|
|
||||||
JavaScriptImports.ResolveOrRejectPromise(args);
|
// we can free the JSHandle here and the holder.resolve_or_reject will do the rest
|
||||||
|
holder.DisposeImpl(skipJsCleanup: true);
|
||||||
|
|
||||||
holder.DisposeLocal();
|
#if !FEATURE_WASM_THREADS
|
||||||
|
// order of operations with DisposeImpl matters
|
||||||
|
JSFunctionBinding.ResolveOrRejectPromise(args);
|
||||||
|
#else
|
||||||
|
// order of operations with DisposeImpl matters
|
||||||
|
JSFunctionBinding.ResolveOrRejectPromise(args);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ResolvePromise<T>(JSObject holder, T value, ArgumentToJSCallback<T> marshaler)
|
private static void ResolvePromise<T>(JSObject holder, T value, ArgumentToJSCallback<T> marshaler)
|
||||||
{
|
{
|
||||||
holder.AssertNotDisposed();
|
holder.AssertNotDisposed();
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
JSObject.AssertThreadAffinity(holder);
|
||||||
|
#endif
|
||||||
|
|
||||||
Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4];
|
Span<JSMarshalerArgument> args = stackalloc JSMarshalerArgument[4];
|
||||||
ref JSMarshalerArgument exc = ref args[0];
|
ref JSMarshalerArgument exc = ref args[0];
|
||||||
|
@ -412,8 +458,16 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
ref JSMarshalerArgument arg_handle = ref args[2];
|
ref JSMarshalerArgument arg_handle = ref args[2];
|
||||||
ref JSMarshalerArgument arg_value = ref args[3];
|
ref JSMarshalerArgument arg_value = ref args[3];
|
||||||
|
|
||||||
|
#if FEATURE_WASM_THREADS
|
||||||
|
exc.InitializeWithContext(holder.ProxyContext);
|
||||||
|
res.InitializeWithContext(holder.ProxyContext);
|
||||||
|
arg_value.InitializeWithContext(holder.ProxyContext);
|
||||||
|
arg_handle.InitializeWithContext(holder.ProxyContext);
|
||||||
|
JSProxyContext.JSImportNoCapture();
|
||||||
|
#else
|
||||||
exc.Initialize();
|
exc.Initialize();
|
||||||
res.Initialize();
|
res.Initialize();
|
||||||
|
#endif
|
||||||
|
|
||||||
// should update existing promise
|
// should update existing promise
|
||||||
arg_handle.slot.Type = MarshalerType.TaskResolved;
|
arg_handle.slot.Type = MarshalerType.TaskResolved;
|
||||||
|
@ -422,9 +476,16 @@ namespace System.Runtime.InteropServices.JavaScript
|
||||||
// and resolve it with value
|
// and resolve it with value
|
||||||
marshaler(ref arg_value, value);
|
marshaler(ref arg_value, value);
|
||||||
|
|
||||||
JavaScriptImports.ResolveOrRejectPromise(args);
|
// we can free the JSHandle here and the holder.resolve_or_reject will do the rest
|
||||||
|
holder.DisposeImpl(skipJsCleanup: true);
|
||||||
|
|
||||||
holder.DisposeLocal();
|
#if !FEATURE_WASM_THREADS
|
||||||
|
// order of operations with DisposeImpl matters
|
||||||
|
JSFunctionBinding.ResolveOrRejectPromise(args);
|
||||||
|
#else
|
||||||
|
// order of operations with DisposeImpl matters
|
||||||
|
JSFunctionBinding.ResolveOrRejectPromise(args);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,5 +23,6 @@
|
||||||
<None Include="$(CompilerGeneratedFilesOutputPath)\..\browser-wasm\generated\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JsExportGenerator\JSExports.g.cs" />
|
<None Include="$(CompilerGeneratedFilesOutputPath)\..\browser-wasm\generated\Microsoft.Interop.JavaScript.JSImportGenerator\Microsoft.Interop.JavaScript.JsExportGenerator\JSExports.g.cs" />
|
||||||
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" />
|
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\JavaScriptTestHelper.mjs" />
|
||||||
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.js" />
|
<WasmExtraFilesToDeploy Include="System\Runtime\InteropServices\JavaScript\SecondRuntimeTest.js" />
|
||||||
|
<ProjectReference Include="$(LibrariesProjectRoot)System.Runtime.InteropServices.JavaScript\src\System.Runtime.InteropServices.JavaScript.csproj" SkipUseReferenceAssembly="true"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System.Runtime.CompilerServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
#pragma warning disable xUnit1026 // Theory methods should use all of their parameters
|
#pragma warning disable xUnit1026 // Theory methods should use all of their parameters
|
||||||
|
|
||||||
namespace System.Runtime.InteropServices.JavaScript.Tests
|
namespace System.Runtime.InteropServices.JavaScript.Tests
|
||||||
|
@ -16,7 +17,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public unsafe void StructSize()
|
public unsafe void StructSize()
|
||||||
{
|
{
|
||||||
Assert.Equal(16, sizeof(JSMarshalerArgument));
|
Assert.Equal(32, sizeof(JSMarshalerArgument));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
@ -378,7 +379,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(MarshalObjectArrayCasesThrow))]
|
[MemberData(nameof(MarshalObjectArrayCasesThrow))]
|
||||||
public unsafe void JsImportObjectArrayThrows(object[]? expected)
|
public void JsImportObjectArrayThrows(object[]? expected)
|
||||||
{
|
{
|
||||||
Assert.Throws<NotSupportedException>(() => JavaScriptTestHelper.echo1_ObjectArray(expected));
|
Assert.Throws<NotSupportedException>(() => JavaScriptTestHelper.echo1_ObjectArray(expected));
|
||||||
}
|
}
|
||||||
|
@ -1959,15 +1960,15 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void JsExportTest<T>(T value
|
private void JsExportTest<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(T value
|
||||||
, Func<T, string, T> invoke, string echoName, string jsType, string? jsClass = null)
|
, Func<T, string, T> invoke, string echoName, string jsType, string? jsClass = null)
|
||||||
{
|
{
|
||||||
T res;
|
T res;
|
||||||
res = invoke(value, echoName);
|
res = invoke(value, echoName);
|
||||||
Assert.Equal(value, res);
|
Assert.Equal<T>(value, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void JsImportTest<T>(T value
|
private void JsImportTest<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(T value
|
||||||
, Action<T> store1
|
, Action<T> store1
|
||||||
, Func<T> retrieve1
|
, Func<T> retrieve1
|
||||||
, Func<T, T> echo1
|
, Func<T, T> echo1
|
||||||
|
|
|
@ -999,19 +999,20 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
|
||||||
{
|
{
|
||||||
if (_module == null)
|
if (_module == null)
|
||||||
{
|
{
|
||||||
// Log("JavaScriptTestHelper.mjs importing");
|
_module = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs"); ;
|
||||||
_module = await JSHost.ImportAsync("JavaScriptTestHelper", "../JavaScriptTestHelper.mjs");
|
await Setup(); ;
|
||||||
await Setup();
|
|
||||||
// Log("JavaScriptTestHelper.mjs imported");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var p = echopromise_String("aaa");
|
||||||
|
await p;
|
||||||
|
|
||||||
// this gives browser chance to serve UI thread event loop before every test
|
// this gives browser chance to serve UI thread event loop before every test
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task DisposeAsync()
|
public static Task DisposeAsync()
|
||||||
{
|
{
|
||||||
_module.Dispose();
|
_module?.Dispose();
|
||||||
_module = null;
|
_module = null;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
|
@ -367,7 +367,15 @@ export function backback(arg1, arg2, arg3) {
|
||||||
// console.log('backback A')
|
// console.log('backback A')
|
||||||
return (brg1, brg2) => {
|
return (brg1, brg2) => {
|
||||||
// console.log('backback B')
|
// console.log('backback B')
|
||||||
return arg1(brg1 + arg2, brg2 + arg3);
|
try {
|
||||||
|
var res = arg1(brg1 + arg2, brg2 + arg3);
|
||||||
|
// console.log('backback C')
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
// console.log('backback E ' + e)
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
!Makefile
|
!Makefile
|
||||||
.stamp-wasm-install-and-select*
|
.stamp-wasm-install-and-select*
|
||||||
emsdk
|
emsdk
|
||||||
|
|
||||||
|
runtime/dotnet.d.ts.sha256
|
||||||
|
runtime/dotnet-legacy.d.ts.sha256
|
||||||
|
|
|
@ -42,7 +42,7 @@ extern void* mono_wasm_invoke_js_blazor (MonoString **exceptionMessage, void *ca
|
||||||
#endif /* DISABLE_LEGACY_JS_INTEROP */
|
#endif /* DISABLE_LEGACY_JS_INTEROP */
|
||||||
|
|
||||||
#ifndef DISABLE_THREADS
|
#ifndef DISABLE_THREADS
|
||||||
extern void mono_wasm_install_js_worker_interop ();
|
extern void mono_wasm_install_js_worker_interop (int context_gc_handle);
|
||||||
extern void mono_wasm_uninstall_js_worker_interop ();
|
extern void mono_wasm_uninstall_js_worker_interop ();
|
||||||
#endif /* DISABLE_THREADS */
|
#endif /* DISABLE_THREADS */
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,7 @@ const fn_signatures: SigLine[] = [
|
||||||
[true, "mono_wasm_profiler_init_browser", "void", ["number"]],
|
[true, "mono_wasm_profiler_init_browser", "void", ["number"]],
|
||||||
[false, "mono_wasm_exec_regression", "number", ["number", "string"]],
|
[false, "mono_wasm_exec_regression", "number", ["number", "string"]],
|
||||||
[false, "mono_wasm_invoke_method_bound", "number", ["number", "number", "number"]],
|
[false, "mono_wasm_invoke_method_bound", "number", ["number", "number", "number"]],
|
||||||
|
[false, "mono_wasm_invoke_method_raw", "number", ["number", "number"]],
|
||||||
[true, "mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]],
|
[true, "mono_wasm_write_managed_pointer_unsafe", "void", ["number", "number"]],
|
||||||
[true, "mono_wasm_copy_managed_pointer", "void", ["number", "number"]],
|
[true, "mono_wasm_copy_managed_pointer", "void", ["number", "number"]],
|
||||||
[true, "mono_wasm_i52_to_f64", "number", ["number", "number"]],
|
[true, "mono_wasm_i52_to_f64", "number", ["number", "number"]],
|
||||||
|
@ -229,6 +230,7 @@ export interface t_Cwraps {
|
||||||
mono_wasm_set_main_args(argc: number, argv: VoidPtr): void;
|
mono_wasm_set_main_args(argc: number, argv: VoidPtr): void;
|
||||||
mono_wasm_exec_regression(verbose_level: number, image: string): number;
|
mono_wasm_exec_regression(verbose_level: number, image: string): number;
|
||||||
mono_wasm_invoke_method_bound(method: MonoMethod, args: JSMarshalerArguments, fail: MonoStringRef): number;
|
mono_wasm_invoke_method_bound(method: MonoMethod, args: JSMarshalerArguments, fail: MonoStringRef): number;
|
||||||
|
mono_wasm_invoke_method_raw(method: MonoMethod, fail: MonoStringRef): number;
|
||||||
mono_wasm_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void;
|
mono_wasm_write_managed_pointer_unsafe(destination: VoidPtr | MonoObjectRef, pointer: ManagedPointer): void;
|
||||||
mono_wasm_copy_managed_pointer(destination: VoidPtr | MonoObjectRef, source: VoidPtr | MonoObjectRef): void;
|
mono_wasm_copy_managed_pointer(destination: VoidPtr | MonoObjectRef, source: VoidPtr | MonoObjectRef): void;
|
||||||
mono_wasm_i52_to_f64(source: VoidPtr, error: Int32Ptr): number;
|
mono_wasm_i52_to_f64(source: VoidPtr, error: Int32Ptr): number;
|
||||||
|
|
|
@ -322,6 +322,27 @@ mono_wasm_invoke_method_bound (MonoMethod *method, void* args /*JSMarshalerArgum
|
||||||
return is_err;
|
return is_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_KEEPALIVE int
|
||||||
|
mono_wasm_invoke_method_raw (MonoMethod *method, MonoString **out_exc)
|
||||||
|
{
|
||||||
|
PVOLATILE(MonoObject) temp_exc = NULL;
|
||||||
|
|
||||||
|
int is_err = 0;
|
||||||
|
|
||||||
|
MONO_ENTER_GC_UNSAFE;
|
||||||
|
mono_runtime_invoke (method, NULL, NULL, (MonoObject **)&temp_exc);
|
||||||
|
|
||||||
|
if (temp_exc && out_exc) {
|
||||||
|
PVOLATILE(MonoObject) exc2 = NULL;
|
||||||
|
store_volatile((MonoObject**)out_exc, (MonoObject*)mono_object_to_string ((MonoObject*)temp_exc, (MonoObject **)&exc2));
|
||||||
|
if (exc2)
|
||||||
|
store_volatile((MonoObject**)out_exc, (MonoObject*)mono_string_new (root_domain, "Exception Double Fault"));
|
||||||
|
is_err = 1;
|
||||||
|
}
|
||||||
|
MONO_EXIT_GC_UNSAFE;
|
||||||
|
return is_err;
|
||||||
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE MonoMethod*
|
EMSCRIPTEN_KEEPALIVE MonoMethod*
|
||||||
mono_wasm_assembly_get_entry_point (MonoAssembly *assembly, int auto_insert_breakpoint)
|
mono_wasm_assembly_get_entry_point (MonoAssembly *assembly, int auto_insert_breakpoint)
|
||||||
{
|
{
|
||||||
|
|
|
@ -98,6 +98,7 @@ export function register_with_jsv_handle(js_obj: any, jsv_handle: JSHandle) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// note: in MT, this is called from locked JSProxyContext. Don't call anything that would need locking.
|
||||||
export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void {
|
export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void {
|
||||||
let obj: any;
|
let obj: any;
|
||||||
if (is_js_handle(js_handle)) {
|
if (is_js_handle(js_handle)) {
|
||||||
|
@ -108,6 +109,7 @@ export function mono_wasm_release_cs_owned_object(js_handle: JSHandle): void {
|
||||||
else if (is_jsv_handle(js_handle)) {
|
else if (is_jsv_handle(js_handle)) {
|
||||||
obj = _cs_owned_objects_by_jsv_handle[0 - <any>js_handle];
|
obj = _cs_owned_objects_by_jsv_handle[0 - <any>js_handle];
|
||||||
_cs_owned_objects_by_jsv_handle[0 - <any>js_handle] = undefined;
|
_cs_owned_objects_by_jsv_handle[0 - <any>js_handle] = undefined;
|
||||||
|
// see free list in JSProxyContext.FreeJSVHandle
|
||||||
}
|
}
|
||||||
mono_assert(obj !== undefined && obj !== null, "ObjectDisposedException");
|
mono_assert(obj !== undefined && obj !== null, "ObjectDisposedException");
|
||||||
if (typeof obj[cs_owned_js_handle_symbol] !== "undefined") {
|
if (typeof obj[cs_owned_js_handle_symbol] !== "undefined") {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import { wrap_as_cancelable_promise } from "./cancelable-promise";
|
import { wrap_as_cancelable_promise } from "./cancelable-promise";
|
||||||
import { ENVIRONMENT_IS_NODE, Module, loaderHelpers, mono_assert } from "./globals";
|
import { ENVIRONMENT_IS_NODE, Module, loaderHelpers, mono_assert } from "./globals";
|
||||||
import { MemoryViewType, Span } from "./marshal";
|
import { MemoryViewType, Span } from "./marshal";
|
||||||
|
import { assert_synchronization_context } from "./pthreads/shared";
|
||||||
import type { VoidPtr } from "./types/emscripten";
|
import type { VoidPtr } from "./types/emscripten";
|
||||||
import { ControllablePromise } from "./types/internal";
|
import { ControllablePromise } from "./types/internal";
|
||||||
|
|
||||||
|
@ -112,6 +113,7 @@ export function http_wasm_fetch_bytes(url: string, header_names: string[], heade
|
||||||
|
|
||||||
export function http_wasm_fetch(url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], abort_controller: AbortController, body: Uint8Array | ReadableStream | null): ControllablePromise<ResponseExtension> {
|
export function http_wasm_fetch(url: string, header_names: string[], header_values: string[], option_names: string[], option_values: any[], abort_controller: AbortController, body: Uint8Array | ReadableStream | null): ControllablePromise<ResponseExtension> {
|
||||||
verifyEnvironment();
|
verifyEnvironment();
|
||||||
|
assert_synchronization_context();
|
||||||
mono_assert(url && typeof url === "string", "expected url string");
|
mono_assert(url && typeof url === "string", "expected url string");
|
||||||
mono_assert(header_names && header_values && Array.isArray(header_names) && Array.isArray(header_values) && header_names.length === header_values.length, "expected headerNames and headerValues arrays");
|
mono_assert(header_names && header_values && Array.isArray(header_names) && Array.isArray(header_values) && header_names.length === header_values.length, "expected headerNames and headerValues arrays");
|
||||||
mono_assert(option_names && option_values && Array.isArray(option_names) && Array.isArray(option_values) && option_names.length === option_values.length, "expected headerNames and headerValues arrays");
|
mono_assert(option_names && option_values && Array.isArray(option_names) && Array.isArray(option_values) && option_names.length === option_values.length, "expected headerNames and headerValues arrays");
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { bind_arg_marshal_to_cs } from "./marshal-to-cs";
|
||||||
import { marshal_exception_to_js, bind_arg_marshal_to_js, end_marshal_task_to_js } from "./marshal-to-js";
|
import { marshal_exception_to_js, bind_arg_marshal_to_js, end_marshal_task_to_js } from "./marshal-to-js";
|
||||||
import {
|
import {
|
||||||
get_arg, get_sig, get_signature_argument_count, is_args_exception,
|
get_arg, get_sig, get_signature_argument_count, is_args_exception,
|
||||||
bound_cs_function_symbol, get_signature_version, alloc_stack_frame, get_signature_type,
|
bound_cs_function_symbol, get_signature_version, alloc_stack_frame, get_signature_type, set_args_context,
|
||||||
} from "./marshal";
|
} from "./marshal";
|
||||||
import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots";
|
import { mono_wasm_new_external_root, mono_wasm_new_root } from "./roots";
|
||||||
import { monoStringToString } from "./strings";
|
import { monoStringToString } from "./strings";
|
||||||
|
@ -356,8 +356,9 @@ export function invoke_method_and_handle_exception(method: MonoMethod, args: JSM
|
||||||
assert_bindings();
|
assert_bindings();
|
||||||
const fail_root = mono_wasm_new_root<MonoString>();
|
const fail_root = mono_wasm_new_root<MonoString>();
|
||||||
try {
|
try {
|
||||||
|
set_args_context(args);
|
||||||
const fail = cwraps.mono_wasm_invoke_method_bound(method, args, fail_root.address);
|
const fail = cwraps.mono_wasm_invoke_method_bound(method, args, fail_root.address);
|
||||||
if (fail) throw new Error("ERR24: Unexpected error: " + monoStringToString(fail_root));
|
if (fail) runtimeHelpers.abort("ERR24: Unexpected error: " + monoStringToString(fail_root));
|
||||||
if (is_args_exception(args)) {
|
if (is_args_exception(args)) {
|
||||||
const exc = get_arg(args, 0);
|
const exc = get_arg(args, 0);
|
||||||
throw marshal_exception_to_js(exc);
|
throw marshal_exception_to_js(exc);
|
||||||
|
@ -368,6 +369,18 @@ export function invoke_method_and_handle_exception(method: MonoMethod, args: JSM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function invoke_method_raw(method: MonoMethod): void {
|
||||||
|
assert_bindings();
|
||||||
|
const fail_root = mono_wasm_new_root<MonoString>();
|
||||||
|
try {
|
||||||
|
const fail = cwraps.mono_wasm_invoke_method_raw(method, fail_root.address);
|
||||||
|
if (fail) runtimeHelpers.abort("ERR24: Unexpected error: " + monoStringToString(fail_root));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
fail_root.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const exportsByAssembly: Map<string, any> = new Map();
|
export const exportsByAssembly: Map<string, any> = new Map();
|
||||||
function _walk_exports_to_set_function(assembly: string, namespace: string, classname: string, methodname: string, signature_hash: number, fn: Function): void {
|
function _walk_exports_to_set_function(assembly: string, namespace: string, classname: string, methodname: string, signature_hash: number, fn: Function): void {
|
||||||
const parts = `${namespace}.${classname}`.replace(/\//g, ".").split(".");
|
const parts = `${namespace}.${classname}`.replace(/\//g, ".").split(".");
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { GCHandle, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } fro
|
||||||
import cwraps from "./cwraps";
|
import cwraps from "./cwraps";
|
||||||
import { runtimeHelpers, Module, loaderHelpers, mono_assert } from "./globals";
|
import { runtimeHelpers, Module, loaderHelpers, mono_assert } from "./globals";
|
||||||
import { alloc_stack_frame, get_arg, set_arg_type, set_gc_handle } from "./marshal";
|
import { alloc_stack_frame, get_arg, set_arg_type, set_gc_handle } from "./marshal";
|
||||||
import { invoke_method_and_handle_exception } from "./invoke-cs";
|
import { invoke_method_and_handle_exception, invoke_method_raw } from "./invoke-cs";
|
||||||
import { marshal_array_to_cs, marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs";
|
import { marshal_array_to_cs, marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs";
|
||||||
import { marshal_int32_to_js, end_marshal_task_to_js, marshal_string_to_js, begin_marshal_task_to_js } from "./marshal-to-js";
|
import { marshal_int32_to_js, end_marshal_task_to_js, marshal_string_to_js, begin_marshal_task_to_js } from "./marshal-to-js";
|
||||||
import { do_not_force_dispose } from "./gc-handles";
|
import { do_not_force_dispose } from "./gc-handles";
|
||||||
|
@ -24,8 +24,8 @@ export function init_managed_exports(): void {
|
||||||
if (!runtimeHelpers.runtime_interop_exports_class)
|
if (!runtimeHelpers.runtime_interop_exports_class)
|
||||||
throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class";
|
throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class";
|
||||||
|
|
||||||
const install_sync_context = MonoWasmThreads ? get_method("InstallSynchronizationContext") : undefined;
|
const install_main_synchronization_context = MonoWasmThreads ? get_method("InstallMainSynchronizationContext") : undefined;
|
||||||
mono_assert(!MonoWasmThreads || install_sync_context, "Can't find InstallSynchronizationContext method");
|
mono_assert(!MonoWasmThreads || install_main_synchronization_context, "Can't find InstallMainSynchronizationContext method");
|
||||||
const call_entry_point = get_method("CallEntrypoint");
|
const call_entry_point = get_method("CallEntrypoint");
|
||||||
mono_assert(call_entry_point, "Can't find CallEntrypoint method");
|
mono_assert(call_entry_point, "Can't find CallEntrypoint method");
|
||||||
const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle");
|
const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle");
|
||||||
|
@ -188,17 +188,8 @@ export function init_managed_exports(): void {
|
||||||
Module.stackRestore(sp);
|
Module.stackRestore(sp);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (MonoWasmThreads && install_main_synchronization_context) {
|
||||||
if (MonoWasmThreads && install_sync_context) {
|
runtimeHelpers.javaScriptExports.install_main_synchronization_context = () => invoke_method_raw(install_main_synchronization_context);
|
||||||
runtimeHelpers.javaScriptExports.install_synchronization_context = () => {
|
|
||||||
const sp = Module.stackSave();
|
|
||||||
try {
|
|
||||||
const args = alloc_stack_frame(2);
|
|
||||||
invoke_method_and_handle_exception(install_sync_context, args);
|
|
||||||
} finally {
|
|
||||||
Module.stackRestore(sp);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -337,10 +337,14 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise<any>, _?:
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
mono_assert(!holder.isDisposed, "This promise can't be propagated to managed code, because the Task was already freed.");
|
mono_assert(!holder.isDisposed, "This promise can't be propagated to managed code, because the Task was already freed.");
|
||||||
if (MonoWasmThreads)
|
if (MonoWasmThreads) {
|
||||||
settleUnsettledPromise();
|
settleUnsettledPromise();
|
||||||
|
}
|
||||||
|
// we can unregister the GC handle on JS side
|
||||||
|
teardown_managed_proxy(holder, gc_handle, 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
|
||||||
runtimeHelpers.javaScriptExports.complete_task(gc_handle, null, data, res_converter || _marshal_cs_object_to_cs);
|
runtimeHelpers.javaScriptExports.complete_task(gc_handle, null, data, res_converter || _marshal_cs_object_to_cs);
|
||||||
teardown_managed_proxy(holder, gc_handle, true); // this holds holder alive for finalizer, until the promise is freed, (holding promise instead would not work)
|
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
runtimeHelpers.abort(ex);
|
runtimeHelpers.abort(ex);
|
||||||
|
@ -352,10 +356,13 @@ function _marshal_task_to_cs(arg: JSMarshalerArgument, value: Promise<any>, _?:
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
mono_assert(!holder.isDisposed, "This promise can't be propagated to managed code, because the Task was already freed.");
|
mono_assert(!holder.isDisposed, "This promise can't be propagated to managed code, because the Task was already freed.");
|
||||||
if (MonoWasmThreads)
|
if (MonoWasmThreads) {
|
||||||
settleUnsettledPromise();
|
settleUnsettledPromise();
|
||||||
|
}
|
||||||
|
// we can unregister the GC handle on JS side
|
||||||
|
teardown_managed_proxy(holder, gc_handle, true);
|
||||||
|
// order of operations with teardown_managed_proxy matters
|
||||||
runtimeHelpers.javaScriptExports.complete_task(gc_handle, reason, null, undefined);
|
runtimeHelpers.javaScriptExports.complete_task(gc_handle, reason, null, undefined);
|
||||||
teardown_managed_proxy(holder, gc_handle, true); // this holds holder alive for finalizer, until the promise is freed
|
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
runtimeHelpers.abort(ex);
|
runtimeHelpers.abort(ex);
|
||||||
|
|
|
@ -265,7 +265,7 @@ export function end_marshal_task_to_js(args: JSMarshalerArguments, res_converter
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise drop the eagerPromise's handle
|
// otherwise drop the eagerPromise's handle
|
||||||
const js_handle = get_arg_js_handle(res);
|
const js_handle = mono_wasm_get_js_handle(eagerPromise);
|
||||||
mono_wasm_release_cs_owned_object(js_handle);
|
mono_wasm_release_cs_owned_object(js_handle);
|
||||||
|
|
||||||
// get the synchronous result
|
// get the synchronous result
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const bound_js_function_symbol = Symbol.for("wasm bound_js_function");
|
||||||
export const imported_js_function_symbol = Symbol.for("wasm imported_js_function");
|
export const imported_js_function_symbol = Symbol.for("wasm imported_js_function");
|
||||||
export const proxy_debug_symbol = Symbol.for("wasm proxy_debug");
|
export const proxy_debug_symbol = Symbol.for("wasm proxy_debug");
|
||||||
|
|
||||||
export const JavaScriptMarshalerArgSize = 16;
|
export const JavaScriptMarshalerArgSize = 32;
|
||||||
export const JSMarshalerTypeSize = 32;
|
export const JSMarshalerTypeSize = 32;
|
||||||
export const JSMarshalerSignatureHeaderSize = 4 * 8; // without Exception and Result
|
export const JSMarshalerSignatureHeaderSize = 4 * 8; // without Exception and Result
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ export function alloc_stack_frame(size: number): JSMarshalerArguments {
|
||||||
const bytes = JavaScriptMarshalerArgSize * size;
|
const bytes = JavaScriptMarshalerArgSize * size;
|
||||||
const args = Module.stackAlloc(bytes) as any;
|
const args = Module.stackAlloc(bytes) as any;
|
||||||
_zero_region(args, bytes);
|
_zero_region(args, bytes);
|
||||||
|
set_args_context(args);
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,6 +41,15 @@ export function is_args_exception(args: JSMarshalerArguments): boolean {
|
||||||
return exceptionType !== MarshalerType.None;
|
return exceptionType !== MarshalerType.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function set_args_context(args: JSMarshalerArguments): void {
|
||||||
|
if (!MonoWasmThreads) return;
|
||||||
|
mono_assert(args, "Null args");
|
||||||
|
const exc = get_arg(args, 0);
|
||||||
|
const res = get_arg(args, 1);
|
||||||
|
set_arg_proxy_context(exc);
|
||||||
|
set_arg_proxy_context(res);
|
||||||
|
}
|
||||||
|
|
||||||
export function get_sig(signature: JSFunctionSignature, index: number): JSMarshalerType {
|
export function get_sig(signature: JSFunctionSignature, index: number): JSMarshalerType {
|
||||||
mono_assert(signature, "Null signatures");
|
mono_assert(signature, "Null signatures");
|
||||||
return <any>signature + (index * JSMarshalerTypeSize) + JSMarshalerSignatureHeaderSize;
|
return <any>signature + (index * JSMarshalerTypeSize) + JSMarshalerSignatureHeaderSize;
|
||||||
|
@ -252,9 +262,16 @@ export function get_arg_js_handle(arg: JSMarshalerArgument): JSHandle {
|
||||||
return <any>getI32(<any>arg + 4);
|
return <any>getI32(<any>arg + 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function set_arg_proxy_context(arg: JSMarshalerArgument): void {
|
||||||
|
if (!MonoWasmThreads) return;
|
||||||
|
mono_assert(arg, "Null arg");
|
||||||
|
setI32(<any>arg + 16, <any>runtimeHelpers.proxy_context_gc_handle);
|
||||||
|
}
|
||||||
|
|
||||||
export function set_js_handle(arg: JSMarshalerArgument, jsHandle: JSHandle): void {
|
export function set_js_handle(arg: JSMarshalerArgument, jsHandle: JSHandle): void {
|
||||||
mono_assert(arg, "Null arg");
|
mono_assert(arg, "Null arg");
|
||||||
setI32(<any>arg + 4, <any>jsHandle);
|
setI32(<any>arg + 4, <any>jsHandle);
|
||||||
|
set_arg_proxy_context(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_arg_gc_handle(arg: JSMarshalerArgument): GCHandle {
|
export function get_arg_gc_handle(arg: JSMarshalerArgument): GCHandle {
|
||||||
|
@ -265,6 +282,7 @@ export function get_arg_gc_handle(arg: JSMarshalerArgument): GCHandle {
|
||||||
export function set_gc_handle(arg: JSMarshalerArgument, gcHandle: GCHandle): void {
|
export function set_gc_handle(arg: JSMarshalerArgument, gcHandle: GCHandle): void {
|
||||||
mono_assert(arg, "Null arg");
|
mono_assert(arg, "Null arg");
|
||||||
setI32(<any>arg + 4, <any>gcHandle);
|
setI32(<any>arg + 4, <any>gcHandle);
|
||||||
|
set_arg_proxy_context(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_string_root(arg: JSMarshalerArgument): WasmRoot<MonoString> {
|
export function get_string_root(arg: JSMarshalerArgument): WasmRoot<MonoString> {
|
||||||
|
@ -331,7 +349,7 @@ export class ManagedError extends Error implements IDisposable {
|
||||||
if (this.managed_stack) {
|
if (this.managed_stack) {
|
||||||
return this.managed_stack;
|
return this.managed_stack;
|
||||||
}
|
}
|
||||||
if (loaderHelpers.is_runtime_running() && (!MonoWasmThreads || runtimeHelpers.jsSynchronizationContextInstalled)) {
|
if (loaderHelpers.is_runtime_running() && (!MonoWasmThreads || runtimeHelpers.proxy_context_gc_handle)) {
|
||||||
const gc_handle = (<any>this)[js_owned_gc_handle_symbol];
|
const gc_handle = (<any>this)[js_owned_gc_handle_symbol];
|
||||||
if (gc_handle !== GCHandleNull) {
|
if (gc_handle !== GCHandleNull) {
|
||||||
const managed_stack = runtimeHelpers.javaScriptExports.get_managed_stack_trace(gc_handle);
|
const managed_stack = runtimeHelpers.javaScriptExports.get_managed_stack_trace(gc_handle);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { mono_log_debug } from "../../logging";
|
||||||
import { bindings_init } from "../../startup";
|
import { bindings_init } from "../../startup";
|
||||||
import { forceDisposeProxies } from "../../gc-handles";
|
import { forceDisposeProxies } from "../../gc-handles";
|
||||||
import { pthread_self } from "../worker";
|
import { pthread_self } from "../worker";
|
||||||
|
import { GCHandle, GCHandleNull } from "../../types/internal";
|
||||||
|
|
||||||
export interface PThreadInfo {
|
export interface PThreadInfo {
|
||||||
readonly pthreadId: pthreadPtr;
|
readonly pthreadId: pthreadPtr;
|
||||||
|
@ -166,11 +167,11 @@ export function isMonoWorkerMessagePreload(message: MonoWorkerMessage): message
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mono_wasm_install_js_worker_interop(): void {
|
export function mono_wasm_install_js_worker_interop(context_gc_handle: GCHandle): void {
|
||||||
if (!MonoWasmThreads) return;
|
if (!MonoWasmThreads) return;
|
||||||
bindings_init();
|
bindings_init();
|
||||||
if (!runtimeHelpers.jsSynchronizationContextInstalled) {
|
if (!runtimeHelpers.proxy_context_gc_handle) {
|
||||||
runtimeHelpers.jsSynchronizationContextInstalled = true;
|
runtimeHelpers.proxy_context_gc_handle = context_gc_handle;
|
||||||
mono_log_debug("Installed JSSynchronizationContext");
|
mono_log_debug("Installed JSSynchronizationContext");
|
||||||
}
|
}
|
||||||
Module.runtimeKeepalivePush();
|
Module.runtimeKeepalivePush();
|
||||||
|
@ -184,19 +185,19 @@ export function mono_wasm_install_js_worker_interop(): void {
|
||||||
export function mono_wasm_uninstall_js_worker_interop(): void {
|
export function mono_wasm_uninstall_js_worker_interop(): void {
|
||||||
if (!MonoWasmThreads) return;
|
if (!MonoWasmThreads) return;
|
||||||
mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "JS interop is not installed on this worker.");
|
mono_assert(runtimeHelpers.mono_wasm_bindings_is_ready, "JS interop is not installed on this worker.");
|
||||||
mono_assert(runtimeHelpers.jsSynchronizationContextInstalled, "JSSynchronizationContext is not installed on this worker.");
|
mono_assert(runtimeHelpers.proxy_context_gc_handle, "JSSynchronizationContext is not installed on this worker.");
|
||||||
|
|
||||||
forceDisposeProxies(true, runtimeHelpers.diagnosticTracing);
|
forceDisposeProxies(true, runtimeHelpers.diagnosticTracing);
|
||||||
Module.runtimeKeepalivePop();
|
Module.runtimeKeepalivePop();
|
||||||
|
|
||||||
runtimeHelpers.jsSynchronizationContextInstalled = false;
|
runtimeHelpers.proxy_context_gc_handle = GCHandleNull;
|
||||||
runtimeHelpers.mono_wasm_bindings_is_ready = false;
|
runtimeHelpers.mono_wasm_bindings_is_ready = false;
|
||||||
set_thread_info(pthread_self ? pthread_self.pthreadId : 0, true, false, false);
|
set_thread_info(pthread_self ? pthread_self.pthreadId : 0, true, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function assert_synchronization_context(): void {
|
export function assert_synchronization_context(): void {
|
||||||
if (MonoWasmThreads) {
|
if (MonoWasmThreads) {
|
||||||
mono_assert(runtimeHelpers.jsSynchronizationContextInstalled, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads");
|
mono_assert(runtimeHelpers.proxy_context_gc_handle, "Please use dedicated worker for working with JavaScript interop. See https://github.com/dotnet/runtime/blob/main/src/mono/wasm/threads.md#JS-interop-on-dedicated-threads");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -289,8 +289,7 @@ async function onRuntimeInitializedAsync(userOnRuntimeInitialized: () => void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MonoWasmThreads) {
|
if (MonoWasmThreads) {
|
||||||
runtimeHelpers.javaScriptExports.install_synchronization_context();
|
runtimeHelpers.javaScriptExports.install_main_synchronization_context();
|
||||||
runtimeHelpers.jsSynchronizationContextInstalled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready();
|
if (!runtimeHelpers.mono_wasm_runtime_is_ready) mono_wasm_runtime_ready();
|
||||||
|
|
|
@ -194,7 +194,7 @@ export type RuntimeHelpers = {
|
||||||
getMemory(): WebAssembly.Memory,
|
getMemory(): WebAssembly.Memory,
|
||||||
getWasmIndirectFunctionTable(): WebAssembly.Table,
|
getWasmIndirectFunctionTable(): WebAssembly.Table,
|
||||||
runtimeReady: boolean,
|
runtimeReady: boolean,
|
||||||
jsSynchronizationContextInstalled: boolean,
|
proxy_context_gc_handle: GCHandle,
|
||||||
cspPolicy: boolean,
|
cspPolicy: boolean,
|
||||||
|
|
||||||
allAssetsInMemory: PromiseAndController<void>,
|
allAssetsInMemory: PromiseAndController<void>,
|
||||||
|
@ -348,8 +348,8 @@ export interface JavaScriptExports {
|
||||||
// the marshaled signature is: Task<int>? CallEntrypoint(MonoMethod* entrypointPtr, string[] args)
|
// the marshaled signature is: Task<int>? CallEntrypoint(MonoMethod* entrypointPtr, string[] args)
|
||||||
call_entry_point(entry_point: MonoMethod, args?: string[]): Promise<number>;
|
call_entry_point(entry_point: MonoMethod, args?: string[]): Promise<number>;
|
||||||
|
|
||||||
// the marshaled signature is: void InstallSynchronizationContext()
|
// the marshaled signature is: void InstallMainSynchronizationContext()
|
||||||
install_synchronization_context(): void;
|
install_main_synchronization_context(): void;
|
||||||
|
|
||||||
// the marshaled signature is: string GetManagedStackTrace(GCHandle exception)
|
// the marshaled signature is: string GetManagedStackTrace(GCHandle exception)
|
||||||
get_managed_stack_trace(exception_gc_handle: GCHandle): string | null
|
get_managed_stack_trace(exception_gc_handle: GCHandle): string | null
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { mono_log_warn } from "./logging";
|
||||||
import { viewOrCopy, utf8ToStringRelaxed, stringToUTF8 } from "./strings";
|
import { viewOrCopy, utf8ToStringRelaxed, stringToUTF8 } from "./strings";
|
||||||
import { IDisposable } from "./marshal";
|
import { IDisposable } from "./marshal";
|
||||||
import { wrap_as_cancelable } from "./cancelable-promise";
|
import { wrap_as_cancelable } from "./cancelable-promise";
|
||||||
|
import { assert_synchronization_context } from "./pthreads/shared";
|
||||||
|
|
||||||
const wasm_ws_pending_send_buffer = Symbol.for("wasm ws_pending_send_buffer");
|
const wasm_ws_pending_send_buffer = Symbol.for("wasm ws_pending_send_buffer");
|
||||||
const wasm_ws_pending_send_buffer_offset = Symbol.for("wasm ws_pending_send_buffer_offset");
|
const wasm_ws_pending_send_buffer_offset = Symbol.for("wasm ws_pending_send_buffer_offset");
|
||||||
|
@ -44,6 +45,7 @@ function verifyEnvironment() {
|
||||||
|
|
||||||
export function ws_wasm_create(uri: string, sub_protocols: string[] | null, receive_status_ptr: VoidPtr, onClosed: (code: number, reason: string) => void): WebSocketExtension {
|
export function ws_wasm_create(uri: string, sub_protocols: string[] | null, receive_status_ptr: VoidPtr, onClosed: (code: number, reason: string) => void): WebSocketExtension {
|
||||||
verifyEnvironment();
|
verifyEnvironment();
|
||||||
|
assert_synchronization_context();
|
||||||
mono_assert(uri && typeof uri === "string", () => `ERR12: Invalid uri ${typeof uri}`);
|
mono_assert(uri && typeof uri === "string", () => `ERR12: Invalid uri ${typeof uri}`);
|
||||||
mono_assert(typeof onClosed === "function", () => `ERR12: Invalid onClosed ${typeof onClosed}`);
|
mono_assert(typeof onClosed === "function", () => `ERR12: Invalid onClosed ${typeof onClosed}`);
|
||||||
|
|
||||||
|
|
|
@ -28,3 +28,5 @@ yarn-error.log*
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
|
||||||
|
public
|
Loading…
Reference in New Issue