// The .NET Foundation licenses this file to you under the MIT license.
using System;
-using System.Collections.Generic;
-using System.Reflection;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
using JSObject = System.Runtime.InteropServices.JavaScript.JSObject;
using JSException = System.Runtime.InteropServices.JavaScript.JSException;
internal static extern object GetGlobalObject(string? globalName, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern object ReleaseHandle(int jsHandle, out int exceptionalResult);
+ internal static extern object ReleaseCSOwnedObject(int jsHandle);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern object BindCoreObject(int jsHandle, int gcHandle, out int exceptionalResult);
- [MethodImplAttribute(MethodImplOptions.InternalCall)]
- internal static extern object New(string className, object[] parms, out int exceptionalResult);
+ internal static extern object CreateCSOwnedObject(string className, object[] parms, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
internal static extern object TypedArrayToArray(int jsHandle, out int exceptionalResult);
[MethodImplAttribute(MethodImplOptions.InternalCall)]
return res as System.Runtime.InteropServices.JavaScript.Function;
}
- public static int New<T>(params object[] parms)
- {
- object res = New(typeof(T).Name, parms, out int exception);
- if (exception != 0)
- throw new JSException((string)res);
- return (int)res;
- }
-
- public static int New(string hostClassName, params object[] parms)
- {
- object res = New(hostClassName, parms, out int exception);
- if (exception != 0)
- throw new JSException((string)res);
- return (int)res;
- }
-
public static object GetGlobalObject(string? str = null)
{
int exception;
- object globalHandle = Runtime.GetGlobalObject(str, out exception);
+ object jsObj = GetGlobalObject(str, out exception);
if (exception != 0)
throw new JSException($"Error obtaining a handle to global {str}");
- ReleaseInFlight(globalHandle);
- return globalHandle;
+ ReleaseInFlight(jsObj);
+ return jsObj;
}
[MethodImplAttribute(MethodImplOptions.NoInlining)]
}
}
- public static void ReleaseInFlight(object? obj)
+ internal static void ReleaseInFlight(object? obj)
{
JSObject? jsObj = obj as JSObject;
jsObj?.ReleaseInFlight();
<ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
<Compile Include="$(CommonPath)Interop\Browser\Interop.Runtime.cs" Link="Common\Interop\Browser\Interop.Runtime.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Runtime.cs" />
+ <Compile Include="System\Runtime\InteropServices\JavaScript\Runtime.CS.Owned.cs" />
+ <Compile Include="System\Runtime\InteropServices\JavaScript\Runtime.JS.Owned.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSException.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\JSObject.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\DataView.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\CoreObject.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\HostObject.cs" />
<Compile Include="System\Runtime\InteropServices\JavaScript\Function.cs" />
- <Compile Include="System\Runtime\InteropServices\JavaScript\AnyRef.cs" />
+ <Compile Include="System\Runtime\InteropServices\JavaScript\JSObject.References.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Runtime" />
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics;
-using System.Threading;
-using Microsoft.Win32.SafeHandles;
-
-namespace System.Runtime.InteropServices.JavaScript
-{
- public abstract class AnyRef : SafeHandleMinusOneIsInvalid
- {
- private GCHandle? InFlight;
- private int InFlightCounter;
- private GCHandle AnyRefHandle;
- public int JSHandle => (int)handle;
-
- internal AnyRef(IntPtr jsHandle, bool ownsHandle) : base(ownsHandle)
- {
- SetHandle(jsHandle);
- AnyRefHandle = GCHandle.Alloc(this, ownsHandle ? GCHandleType.Weak : GCHandleType.Normal);
- InFlight = null;
- InFlightCounter = 0;
- }
- internal int GCHandleValue => (int)(IntPtr)AnyRefHandle;
-
- internal void AddInFlight()
- {
- lock (this)
- {
- InFlightCounter++;
- if (InFlightCounter == 1)
- {
- Debug.Assert(InFlight == null);
- InFlight = GCHandle.Alloc(this, GCHandleType.Normal);
- }
- }
- }
-
- internal void ReleaseInFlight()
- {
- lock (this)
- {
- Debug.Assert(InFlightCounter != 0);
-
- InFlightCounter--;
- if (InFlightCounter == 0)
- {
- Debug.Assert(InFlight.HasValue);
- InFlight.Value.Free();
- InFlight = null;
- }
- }
- }
-
-
- protected void FreeGCHandle()
- {
- AnyRefHandle.Free();
- }
-#if DEBUG_HANDLE
- private int _refCount;
-
- internal void AddRef()
- {
- Interlocked.Increment(ref _refCount);
- }
-
- internal void Release()
- {
- Debug.Assert(_refCount > 0, "AnyRefSafeHandle: Release() called more times than AddRef");
- Interlocked.Decrement(ref _refCount);
- }
-
- internal int RefCount => _refCount;
-#endif
- }
-}
/// Initializes a new instance of the Array class.
/// </summary>
/// <param name="_params">Parameters.</param>
- public Array(params object[] _params) : base(Interop.Runtime.New<Array>(_params))
+ public Array(params object[] _params) : base(nameof(Array), _params)
{ }
/// <summary>
/// Initializes a new instance of the Array/> class.
/// </summary>
/// <param name="jsHandle">Js handle.</param>
- /// <param name="ownsHandle">Whether or not the handle is owned by the clr or not.</param>
- internal Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Array(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
{
get
{
+ AssertNotDisposed();
+
object indexValue = Interop.Runtime.GetByIndex(JSHandle, i, out int exception);
if (exception != 0)
}
set
{
+ AssertNotDisposed();
+
object res = Interop.Runtime.SetByIndex(JSHandle, i, value, out int exception);
if (exception != 0)
/// <summary>
/// Initializes a new instance of the JavaScript Core ArrayBuffer class.
/// </summary>
- public ArrayBuffer() : base(Interop.Runtime.New<ArrayBuffer>())
+ public ArrayBuffer() : base(nameof(ArrayBuffer))
{ }
/// <summary>
/// Initializes a new instance of the JavaScript Core ArrayBuffer class.
/// </summary>
/// <param name="length">Length.</param>
- public ArrayBuffer(int length) : base(Interop.Runtime.New<ArrayBuffer>(length))
+ public ArrayBuffer(int length) : base(nameof(ArrayBuffer), length)
{ }
/// <summary>
/// Initializes a new instance of the JavaScript Core ArrayBuffer class.
/// </summary>
/// <param name="jsHandle">Js handle.</param>
- /// <param name="ownsHandle">Whether or not the handle is owned by the clr or not.</param>
- internal ArrayBuffer(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal ArrayBuffer(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
/// </remarks>
public abstract class CoreObject : JSObject
{
- protected CoreObject(int jsHandle) : base(jsHandle, true)
+ internal CoreObject(IntPtr jsHandle) : base(jsHandle)
{
- object result = Interop.Runtime.BindCoreObject(jsHandle, GCHandleValue, out int exception);
- if (exception != 0)
- throw new JSException(SR.Format(SR.CoreObjectErrorBinding, result));
}
- internal CoreObject(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
- { }
+ protected CoreObject(string typeName, params object[] _params) : base(typeName, _params)
+ {
+ }
}
}
/// Initializes a new instance of the DataView class.
/// </summary>
/// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param>
- public DataView(ArrayBuffer buffer) : base(Runtime.New<DataView>(buffer))
+ public DataView(ArrayBuffer buffer) : base(nameof(DataView), buffer)
{ }
/// <summary>
/// </summary>
/// <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>
- public DataView(ArrayBuffer buffer, int byteOffset) : base(Runtime.New<DataView>(buffer, byteOffset))
+ public DataView(ArrayBuffer buffer, int byteOffset) : base(nameof(DataView), buffer, byteOffset)
{ }
/// <summary>
/// <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="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) : base(Runtime.New<DataView>(buffer, byteOffset, byteLength))
+ public DataView(ArrayBuffer buffer, int byteOffset, int byteLength) : base(nameof(DataView), buffer, byteOffset, byteLength)
{ }
/// <summary>
/// Initializes a new instance of the DataView class.
/// </summary>
/// <param name="buffer">SharedArrayBuffer to use as the storage backing the new DataView object.</param>
- public DataView(SharedArrayBuffer buffer) : base(Runtime.New<DataView>(buffer))
+ public DataView(SharedArrayBuffer buffer) : base(nameof(DataView), buffer)
{ }
/// <summary>
/// </summary>
/// <param name="buffer">SharedArrayBuffer 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>
- public DataView(SharedArrayBuffer buffer, int byteOffset) : base(Runtime.New<DataView>(buffer, byteOffset))
+ public DataView(SharedArrayBuffer buffer, int byteOffset) : base(nameof(DataView), buffer, byteOffset)
{ }
/// <summary>
/// <param name="buffer">SharedArrayBuffer 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="byteLength">The number of elements in the byte array. If unspecified, the view's length will match the buffer's length.</param>
- public DataView(SharedArrayBuffer buffer, int byteOffset, int byteLength) : base(Runtime.New<DataView>(buffer, byteOffset, byteLength))
+ public DataView(SharedArrayBuffer buffer, int byteOffset, int byteLength) : base(nameof(DataView), buffer, byteOffset, byteLength)
{ }
/// <summary>
/// Initializes a new instance of the DataView class.
/// </summary>
/// <param name="jsHandle">Js handle.</param>
- /// <param name="ownsHandle">Managed owned</param>
- internal DataView(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal DataView(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
public Float32Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { }
- internal Float32Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Float32Array(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
public Float64Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { }
- internal Float64Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Float64Array(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
/// </remarks>
public class Function : CoreObject
{
- public Function(params object[] args) : base(Interop.Runtime.New<Function>(args))
+ public Function(params object[] args) : base(nameof(Function), args)
{ }
- internal Function(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Function(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
/// <returns>The apply.</returns>
/// <param name="thisArg">This argument.</param>
/// <param name="argsArray">Arguments.</param>
- public object Apply(object? thisArg = null, object[]? argsArray = null) => Invoke("apply", thisArg, argsArray);
+ public object Apply(object? thisArg, object[]? argsArray = null) => Invoke("apply", thisArg, argsArray);
/// <summary>
/// Creates a new Function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
/// <returns>The bind.</returns>
/// <param name="thisArg">This argument.</param>
/// <param name="argsArray">Arguments.</param>
- public Function Bind(object? thisArg = null, object[]? argsArray = null) => (Function)Invoke("bind", thisArg, argsArray);
+ public Function Bind(object? thisArg, object[]? argsArray = null) => (Function)Invoke("bind", thisArg, argsArray);
/// <summary>
/// Calls a function with a given `this` value and arguments provided individually.
/// <returns>The result of calling the function with the specified `this` value and arguments.</returns>
/// <param name="thisArg">Optional (null value). The value of this provided for the call to a function. Note that this may not be the actual value seen by the method: if the method is a function in non-strict mode, null and undefined will be replaced with the global object and primitive values will be converted to objects.</param>
/// <param name="argsArray">Optional. Arguments for the function.</param>
- public object Call(object? thisArg = null, params object[] argsArray)
+ public object Call(object? thisArg, params object[] argsArray)
{
object?[] argsList = new object[argsArray.Length + 1];
argsList[0] = thisArg;
System.Array.Copy(argsArray, 0, argsList, 1, argsArray.Length);
return Invoke("call", argsList);
}
+
+ /// <summary>
+ /// Calls a function with a null `this` value.
+ /// </summary>
+ /// <returns>The result of calling the function.</returns>
+ public object Call() => Call(null);
}
}
public interface IHostObject
{ }
- public class HostObject : HostObjectBase
+ public class HostObject : JSObject, IHostObject
{
- public HostObject(string hostName, params object[] _params) : base(Interop.Runtime.New(hostName, _params))
- { }
- }
-
- public abstract class HostObjectBase : JSObject, IHostObject
- {
- protected HostObjectBase(int jsHandle) : base(jsHandle, true)
- {
- object result = Interop.Runtime.BindCoreObject(jsHandle, GCHandleValue, out int exception);
- if (exception != 0)
- throw new JSException(SR.Format(SR.HostObjectErrorBinding, result));
- }
-
- internal HostObjectBase(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ public HostObject(string typeName, params object[] _params) : base(typeName, _params)
{ }
}
}
public Int16Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { }
- internal Int16Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Int16Array(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
public Int32Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { }
- internal Int32Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Int32Array(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
public Int8Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length)
{ }
- internal Int8Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Int8Array(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using Microsoft.Win32.SafeHandles;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ public partial class JSObject : SafeHandleMinusOneIsInvalid
+ {
+ private GCHandle? InFlight;
+ private int InFlightCounter;
+ public int JSHandle => (int)handle;
+ public bool IsDisposed { get; private set; }
+
+ public JSObject() : base(true)
+ {
+ InFlight = null;
+ InFlightCounter = 0;
+
+ var jsHandle = Runtime.CreateCSOwnedObject(this, nameof(Object));
+ SetHandle(jsHandle);
+ }
+
+ protected JSObject(string typeName, object[] _params) : base(true)
+ {
+ InFlight = null;
+ InFlightCounter = 0;
+
+ var jsHandle = Runtime.CreateCSOwnedObject(this, typeName, _params);
+ SetHandle(jsHandle);
+ }
+
+ internal JSObject(IntPtr jsHandle) : base(true)
+ {
+ SetHandle(jsHandle);
+ InFlight = null;
+ InFlightCounter = 0;
+ }
+
+ internal void AddInFlight()
+ {
+ AssertNotDisposed();
+ lock (this)
+ {
+ InFlightCounter++;
+ if (InFlightCounter == 1)
+ {
+ Debug.Assert(InFlight == null);
+ InFlight = GCHandle.Alloc(this, GCHandleType.Normal);
+ }
+ }
+ }
+
+ // Note that we could not use SafeHandle.DangerousAddRef() and DangerousRelease()
+ // because we could get to zero InFlightCounter multiple times accross lifetime of the JSObject
+ // 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()
+ {
+ lock (this)
+ {
+ Debug.Assert(InFlightCounter != 0);
+
+ InFlightCounter--;
+ if (InFlightCounter == 0)
+ {
+ Debug.Assert(InFlight.HasValue);
+ InFlight.Value.Free();
+ InFlight = null;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if DEBUG
+ public void AssertNotDisposed()
+#else
+ internal void AssertNotDisposed()
+#endif
+ {
+ if (IsDisposed) throw new ObjectDisposedException($"Cannot access a disposed {GetType().Name}.");
+ }
+
+#if DEBUG
+ public void AssertInFlight(int expectedInFlightCount)
+ {
+ if (InFlightCounter != expectedInFlightCount) throw new InvalidProgramException($"Invalid InFlightCounter for JSObject {JSHandle}, expected: {expectedInFlightCount}, actual: {InFlightCounter}");
+ }
+#endif
+
+ protected override bool ReleaseHandle()
+ {
+ Runtime.ReleaseCSOwnedObject(this);
+ SetHandleAsInvalid();
+ IsDisposed = true;
+ return true;
+ }
+
+ public override bool Equals([NotNullWhen(true)] object? obj) => obj is JSObject other && JSHandle == other.JSHandle;
+
+ public override int GetHashCode() => JSHandle;
+
+ public override string ToString()
+ {
+ return $"(js-obj js '{JSHandle}')";
+ }
+ }
+}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Diagnostics.CodeAnalysis;
-using Console = System.Diagnostics.Debug;
-
namespace System.Runtime.InteropServices.JavaScript
{
public interface IJSObject
/// JSObjects are wrappers for a native JavaScript object, and
/// they retain a reference to the JavaScript object for the lifetime of this C# object.
/// </summary>
- public class JSObject : AnyRef, IJSObject, IDisposable
+ public partial class JSObject : IJSObject, IDisposable
{
- // to detect redundant calls
- public bool IsDisposed { get; private set; }
-
- public JSObject() : this(Interop.Runtime.New<object>(), true)
- {
- object result = Interop.Runtime.BindCoreObject(JSHandle, GCHandleValue, out int exception);
- if (exception != 0)
- throw new JSException(SR.Format(SR.JSObjectErrorBinding, result));
-
- }
-
- internal JSObject(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
- { }
-
- internal JSObject(int jsHandle, bool ownsHandle) : base((IntPtr)jsHandle, ownsHandle)
- { }
-
/// <summary>
/// Invoke a named method of the object, or throws a JSException on error.
/// </summary>
/// </returns>
public object Invoke(string method, params object?[] args)
{
+ AssertNotDisposed();
+
object res = Interop.Runtime.InvokeJSWithArgs(JSHandle, method, args, out int exception);
if (exception != 0)
throw new JSException((string)res);
public int AddEventListener(string name, Action<JSObject> listener, EventListenerOptions? options = null)
{
+ AssertNotDisposed();
+
var optionsDict = options.HasValue
? new JSObject()
: null;
public void RemoveEventListener(string name, Action<JSObject>? listener, EventListenerOptions? options = null)
{
+ AssertNotDisposed();
+
if (listener == null)
return;
var jsfunc = Runtime.GetJSOwnedObjectGCHandle(listener);
public void RemoveEventListener(string name, int listenerGCHandle, EventListenerOptions? options = null)
{
+ AssertNotDisposed();
+
var ret = Interop.Runtime.RemoveEventListener(JSHandle, name, listenerGCHandle, options?.Capture ?? false);
if (ret != null)
throw new JSException(ret);
/// </returns>
public object GetObjectProperty(string name)
{
+ AssertNotDisposed();
+
object propertyValue = Interop.Runtime.GetObjectProperty(JSHandle, name, out int exception);
if (exception != 0)
throw new JSException((string)propertyValue);
/// <param name="hasOwnProperty"></param>
public void SetObjectProperty(string name, object value, bool createIfNotExists = true, bool hasOwnProperty = false)
{
+ AssertNotDisposed();
+
object setPropResult = Interop.Runtime.SetObjectProperty(JSHandle, name, value, createIfNotExists, hasOwnProperty, out int exception);
if (exception != 0)
- throw new JSException($"Error setting {name} on (js-obj js '{JSHandle}' .NET '{GCHandleValue})");
+ throw new JSException($"Error setting {name} on (js-obj js '{JSHandle}')");
}
/// <summary>
/// <returns><c>true</c>, if the specified property is enumerable, <c>false</c> otherwise.</returns>
/// <param name="prop">The String name or Symbol of the property to test.</param>
public bool PropertyIsEnumerable(string prop) => (bool)Invoke("propertyIsEnumerable", prop);
-
- internal void FreeHandle()
- {
- Runtime.ReleaseJSObject(this);
- SetHandleAsInvalid();
- IsDisposed = true;
- FreeGCHandle();
- }
-
- public override bool Equals([NotNullWhen(true)] object? obj) => obj is JSObject other && JSHandle == other.JSHandle;
-
- public override int GetHashCode() => JSHandle;
-
- protected override bool ReleaseHandle()
- {
- bool ret = false;
-
-#if DEBUG_HANDLE
- Console.WriteLine($"Release Handle handle:{handle}");
- try
- {
-#endif
- FreeHandle();
- ret = true;
-
-#if DEBUG_HANDLE
- }
- catch (Exception exception)
- {
- Console.WriteLine($"ReleaseHandle: {exception.Message}");
- ret = true; // Avoid a second assert.
- throw;
- }
- finally
- {
- if (!ret)
- {
- Console.WriteLine($"ReleaseHandle failed. handle:{handle}");
- }
- }
-#endif
- return ret;
- }
-
- public override string ToString()
- {
- return $"(js-obj js '{GCHandleValue}')";
- }
}
}
/// <summary>
/// Initializes a new instance of the Map class.
/// </summary>
- public Map() : base(Runtime.New<Map>())
+ public Map() : base(nameof(Map))
{ }
/// <summary>
/// Initializes a new instance of the Map class.
/// </summary>
/// <param name="jsHandle">Js handle.</param>
- /// <param name="ownsHandle">Whether or not the handle is owned by the clr or not.</param>
- internal Map(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Map(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
--- /dev/null
+// 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;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ public static partial class Runtime
+ {
+ private static readonly Dictionary<int, WeakReference<JSObject>> _csOwnedObjects = new Dictionary<int, WeakReference<JSObject>>();
+
+ public static JSObject? GetCSOwnedObjectByJSHandle(int jsHandle, int shouldAddInflight)
+ {
+ lock (_csOwnedObjects)
+ {
+ if (_csOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference))
+ {
+ reference.TryGetTarget(out JSObject? jsObject);
+ if (shouldAddInflight != 0 && jsObject != null)
+ {
+ jsObject.AddInFlight();
+ }
+ return jsObject;
+ }
+ }
+ return null;
+
+ }
+
+ public static int TryGetCSOwnedObjectJSHandle(object rawObj, int shouldAddInflight)
+ {
+ JSObject? jsObject = rawObj as JSObject;
+ if (jsObject != null && shouldAddInflight != 0)
+ {
+ jsObject.AddInFlight();
+ }
+ return jsObject?.JSHandle ?? 0;
+ }
+
+ public static int GetCSOwnedObjectJSHandle(JSObject jsObject, int shouldAddInflight)
+ {
+ jsObject.AssertNotDisposed();
+
+ if (shouldAddInflight != 0)
+ {
+ jsObject.AddInFlight();
+ }
+ return jsObject.JSHandle;
+ }
+
+ public static JSObject CreateCSOwnedProxy(IntPtr jsHandle, MappedType mappedType, int shouldAddInflight)
+ {
+ JSObject? jsObject = null;
+
+ lock (_csOwnedObjects)
+ {
+ if (!_csOwnedObjects.TryGetValue((int)jsHandle, out WeakReference<JSObject>? reference) ||
+ !reference.TryGetTarget(out jsObject) ||
+ jsObject.IsDisposed)
+ {
+ jsObject = mappedType switch
+ {
+ MappedType.JSObject => new JSObject(jsHandle),
+ MappedType.Array => new Array(jsHandle),
+ MappedType.ArrayBuffer => new ArrayBuffer(jsHandle),
+ MappedType.DataView => new DataView(jsHandle),
+ MappedType.Function => new Function(jsHandle),
+ MappedType.Map => new Map(jsHandle),
+ MappedType.SharedArrayBuffer => new SharedArrayBuffer(jsHandle),
+ MappedType.Int8Array => new Int8Array(jsHandle),
+ MappedType.Uint8Array => new Uint8Array(jsHandle),
+ MappedType.Uint8ClampedArray => new Uint8ClampedArray(jsHandle),
+ MappedType.Int16Array => new Int16Array(jsHandle),
+ MappedType.Uint16Array => new Uint16Array(jsHandle),
+ MappedType.Int32Array => new Int32Array(jsHandle),
+ MappedType.Uint32Array => new Uint32Array(jsHandle),
+ MappedType.Float32Array => new Float32Array(jsHandle),
+ MappedType.Float64Array => new Float64Array(jsHandle),
+ _ => throw new ArgumentOutOfRangeException(nameof(mappedType))
+ };
+ _csOwnedObjects[(int)jsHandle] = new WeakReference<JSObject>(jsObject, trackResurrection: true);
+ }
+ }
+ if (shouldAddInflight != 0)
+ {
+ jsObject.AddInFlight();
+ }
+
+ return jsObject;
+ }
+
+ #region used from C# side
+
+ internal static bool ReleaseCSOwnedObject(JSObject objToRelease)
+ {
+ objToRelease.AssertNotDisposed();
+
+ lock (_csOwnedObjects)
+ {
+ _csOwnedObjects.Remove(objToRelease.JSHandle);
+ Interop.Runtime.ReleaseCSOwnedObject(objToRelease.JSHandle);
+ }
+ return true;
+ }
+
+ internal static IntPtr CreateCSOwnedObject(JSObject proxy, string typeName, params object[] parms)
+ {
+ object res = Interop.Runtime.CreateCSOwnedObject(typeName, parms, out int exception);
+ if (exception != 0)
+ throw new JSException((string)res);
+
+ var jsHandle = (int)res;
+
+ lock (_csOwnedObjects)
+ {
+ _csOwnedObjects[jsHandle] = new WeakReference<JSObject>(proxy, trackResurrection: true);
+ }
+
+ return (IntPtr)jsHandle;
+ }
+
+ #endregion
+
+
+ // please keep BINDING wasm_type_symbol in sync
+ public enum MappedType
+ {
+ JSObject = 0,
+ Array = 1,
+ ArrayBuffer = 2,
+ DataView = 3,
+ Function = 4,
+ Map = 5,
+ SharedArrayBuffer = 6,
+ Int8Array = 10,
+ Uint8Array = 11,
+ Uint8ClampedArray = 12,
+ Int16Array = 13,
+ Uint16Array = 14,
+ Int32Array = 15,
+ Uint32Array = 16,
+ Float32Array = 17,
+ Float64Array = 18,
+ }
+ }
+}
--- /dev/null
+// 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.Threading.Tasks;
+
+namespace System.Runtime.InteropServices.JavaScript
+{
+ public static partial class Runtime
+ {
+ private static object JSOwnedObjectLock = new object();
+ // we use this to maintain identity of GCHandle for a managed object
+ private static Dictionary<object, int> GCHandleFromJSOwnedObject = new Dictionary<object, int>();
+
+
+ public static object GetJSOwnedObjectByGCHandle(int gcHandle)
+ {
+ GCHandle h = (GCHandle)(IntPtr)gcHandle;
+ return h.Target!;
+ }
+
+ // 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 int GetJSOwnedObjectGCHandle(object obj)
+ {
+ if (obj == null)
+ return 0;
+
+ int result;
+ lock (JSOwnedObjectLock)
+ {
+ if (GCHandleFromJSOwnedObject.TryGetValue(obj, out result))
+ return result;
+
+ result = (int)(IntPtr)GCHandle.Alloc(obj, GCHandleType.Normal);
+ GCHandleFromJSOwnedObject[obj] = result;
+ return result;
+ }
+ }
+
+ // The JS layer invokes this method when the JS wrapper for a JS owned object
+ // has been collected by the JS garbage collector
+ public static void ReleaseJSOwnedObjectByGCHandle(int gcHandle)
+ {
+ GCHandle handle = (GCHandle)(IntPtr)gcHandle;
+ lock (JSOwnedObjectLock)
+ {
+ GCHandleFromJSOwnedObject.Remove(handle.Target!);
+ handle.Free();
+ }
+ }
+
+ public static int CreateTaskSource()
+ {
+ var tcs = new TaskCompletionSource<object>();
+ return GetJSOwnedObjectGCHandle(tcs);
+ }
+
+ public static void SetTaskSourceResult(int tcsGCHandle, object result)
+ {
+ GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
+ // this is JS owned Normal handle. We always have a Target
+ TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
+ tcs.SetResult(result);
+ }
+
+ public static void SetTaskSourceFailure(int tcsGCHandle, string reason)
+ {
+ GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
+ // this is JS owned Normal handle. We always have a Target
+ TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
+ tcs.SetException(new JSException(reason));
+ }
+
+ public static object GetTaskSourceTask(int tcsGCHandle)
+ {
+ GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
+ // this is JS owned Normal handle. We always have a Target
+ TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
+ return tcs.Task;
+ }
+
+ public static void SetupJSContinuation(Task task, JSObject continuationObj)
+ {
+ if (task.IsCompleted)
+ Complete();
+ else
+ task.GetAwaiter().OnCompleted(Complete);
+
+ void Complete()
+ {
+ try
+ {
+ if (task.Exception == null)
+ {
+ object? result;
+ Type task_type = task.GetType();
+ if (task_type == typeof(Task))
+ {
+ result = System.Array.Empty<object>();
+ }
+ else
+ {
+ result = GetTaskResultMethodInfo(task_type)?.Invoke(task, null);
+ }
+
+ continuationObj.Invoke("resolve", result);
+ }
+ else
+ {
+ continuationObj.Invoke("reject", task.Exception.ToString());
+ }
+ }
+ catch (Exception e)
+ {
+ continuationObj.Invoke("reject", e.ToString());
+ }
+ finally
+ {
+ continuationObj.Dispose();
+ }
+ }
+ }
+ }
+}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Collections.Generic;
-using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
-using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace System.Runtime.InteropServices.JavaScript
{
- public static class Runtime
+ public static partial class Runtime
{
- private static readonly Dictionary<int, WeakReference<JSObject>> _boundObjects = new Dictionary<int, WeakReference<JSObject>>();
- private static object JSOwnedObjectLock = new object();
- // we use this to maintain identity of GCHandle for a managed object
- private static Dictionary<object, int> GCHandleFromJSOwnedObject = new Dictionary<object, int>();
-
private const string TaskGetResultName = "get_Result";
private static readonly MethodInfo _taskGetResultMethodInfo = typeof(Task<>).GetMethod(TaskGetResultName)!;
return Interop.Runtime.CompileFunction(snippet);
}
- public static int New<T>(params object[] parms)
- {
- return Interop.Runtime.New(typeof(T).Name, parms);
- }
-
- public static int New(string hostClassName, params object[] parms)
- {
- return Interop.Runtime.New(hostClassName, parms);
- }
-
public static object GetGlobalObject(string? str = null)
{
return Interop.Runtime.GetGlobalObject(str);
}
- public static void DumpAotProfileData (ref byte buf, int len, string extraArg)
+ public static void DumpAotProfileData(ref byte buf, int len, string extraArg)
{
Interop.Runtime.DumpAotProfileData(ref buf, len, extraArg);
}
- public static int BindJSObject(int jsHandle, bool ownsHandle, int mappedType)
- {
- JSObject? target = null;
-
- lock (_boundObjects)
- {
- if (!_boundObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
- !reference.TryGetTarget(out target) ||
- target.IsDisposed)
- {
- IntPtr jsIntPtr = (IntPtr)jsHandle;
- target = mappedType > 0 ? BindJSType(jsIntPtr, ownsHandle, mappedType) : new JSObject(jsIntPtr, ownsHandle);
- _boundObjects[jsHandle] = new WeakReference<JSObject>(target, trackResurrection: true);
- }
- }
-
- target.AddInFlight();
-
- return target.GCHandleValue;
- }
-
- public static int BindCoreCLRObject(int jsHandle, int gcHandle)
- {
- GCHandle h = (GCHandle)(IntPtr)gcHandle;
- JSObject? obj = null;
-
- lock (_boundObjects)
- {
- if (_boundObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? wr))
- {
-
- if (!wr.TryGetTarget(out JSObject? instance) || (instance.GCHandleValue != (int)(IntPtr)h && h.IsAllocated))
- {
- throw new JSException(SR.Format(SR.MultipleHandlesPointingJsId, jsHandle));
- }
-
- obj = instance;
- }
- else if (h.Target is JSObject instance)
- {
- _boundObjects.Add(jsHandle, new WeakReference<JSObject>(instance, trackResurrection: true));
- obj = instance;
- }
- }
-
- return obj?.GCHandleValue ?? 0;
- }
-
- private static JSObject BindJSType(IntPtr jsIntPtr, bool ownsHandle, int coreType) =>
- coreType switch
- {
- 1 => new Array(jsIntPtr, ownsHandle),
- 2 => new ArrayBuffer(jsIntPtr, ownsHandle),
- 3 => new DataView(jsIntPtr, ownsHandle),
- 4 => new Function(jsIntPtr, ownsHandle),
- 5 => new Map(jsIntPtr, ownsHandle),
- 6 => new SharedArrayBuffer(jsIntPtr, ownsHandle),
- 10 => new Int8Array(jsIntPtr, ownsHandle),
- 11 => new Uint8Array(jsIntPtr, ownsHandle),
- 12 => new Uint8ClampedArray(jsIntPtr, ownsHandle),
- 13 => new Int16Array(jsIntPtr, ownsHandle),
- 14 => new Uint16Array(jsIntPtr, ownsHandle),
- 15 => new Int32Array(jsIntPtr, ownsHandle),
- 16 => new Uint32Array(jsIntPtr, ownsHandle),
- 17 => new Float32Array(jsIntPtr, ownsHandle),
- 18 => new Float64Array(jsIntPtr, ownsHandle),
- _ => throw new ArgumentOutOfRangeException(nameof(coreType))
- };
-
- internal static bool ReleaseJSObject(JSObject objToRelease)
- {
- Interop.Runtime.ReleaseHandle(objToRelease.JSHandle, out int exception);
- if (exception != 0)
- throw new JSException($"Error releasing handle on (js-obj js '{objToRelease.JSHandle}' mono '{objToRelease.GCHandleValue})");
-
- lock (_boundObjects)
- {
- _boundObjects.Remove(objToRelease.JSHandle);
- }
- return true;
- }
-
- public static int CreateTaskSource()
- {
- var tcs= new TaskCompletionSource<object>();
- return GetJSOwnedObjectGCHandle(tcs);
- }
-
- public static void SetTaskSourceResult(int tcsGCHandle, object result)
- {
- GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
- // this is JS owned Normal handle. We always have a Target
- TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
- tcs.SetResult(result);
- }
-
- public static void SetTaskSourceFailure(int tcsGCHandle, string reason)
- {
- GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
- // this is JS owned Normal handle. We always have a Target
- TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
- tcs.SetException(new JSException(reason));
- }
-
- public static object GetTaskSourceTask(int tcsGCHandle)
- {
- GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
- // this is JS owned Normal handle. We always have a Target
- TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
- return tcs.Task;
- }
-
- // 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 int GetJSOwnedObjectGCHandle (object o) {
- if (o == null)
- return 0;
-
- int result;
- lock (JSOwnedObjectLock) {
- if (GCHandleFromJSOwnedObject.TryGetValue(o, out result))
- return result;
-
- result = (int)(IntPtr)GCHandle.Alloc(o, GCHandleType.Normal);
- GCHandleFromJSOwnedObject[o] = result;
- return result;
- }
- }
-
- // The JS layer invokes this method when the JS wrapper for a JS owned object
- // has been collected by the JS garbage collector
- public static void ReleaseJSOwnedObjectByHandle (int gcHandle) {
- GCHandle handle = (GCHandle)(IntPtr)gcHandle;
- lock (JSOwnedObjectLock) {
- GCHandleFromJSOwnedObject.Remove(handle.Target!);
- handle.Free();
- }
- }
-
- public static int GetJSObjectId(object rawObj)
- {
- JSObject? jsObject = rawObj as JSObject;
- return jsObject?.JSHandle ?? -1;
- }
-
- /// <param name="gcHandle"></param>
- /// <param name="shouldAddInflight">when true, we would create Normal GCHandle to the JSObject, so that it would not get collected before passing it back to managed code</param>
- public static object? GetDotNetObject(int gcHandle, int shouldAddInflight)
- {
- GCHandle h = (GCHandle)(IntPtr)gcHandle;
-
- if (h.Target is JSObject jso)
- {
- if (shouldAddInflight != 0)
- {
- jso.AddInFlight();
- }
- return jso;
- }
- return h.Target;
- }
-
public static bool IsSimpleArray(object a)
{
return a is System.Array arr && arr.Rank == 1 && arr.GetLowerBound(0) == 0;
return new string(res);
}
- public static void SetupJSContinuation(Task task, JSObject continuationObj)
- {
- if (task.IsCompleted)
- Complete();
- else
- task.GetAwaiter().OnCompleted(Complete);
-
- void Complete()
- {
- try
- {
- if (task.Exception == null)
- {
- object? result;
- Type task_type = task.GetType();
- if (task_type == typeof(Task))
- {
- result = System.Array.Empty<object>();
- }
- else
- {
- result = GetTaskResultMethodInfo(task_type)?.Invoke(task, null);
- }
-
- continuationObj.Invoke("resolve", result);
- }
- else
- {
- continuationObj.Invoke("reject", task.Exception.ToString());
- }
- }
- catch (Exception e)
- {
- continuationObj.Invoke("reject", e.ToString());
- }
- finally
- {
- continuationObj.Dispose();
- }
- }
- }
-
/// <summary>
/// Gets the MethodInfo for the Task{T}.Result property getter.
/// </summary>
{
return new Uri(uri);
}
-
- public static bool SafeHandleAddRef(SafeHandle safeHandle)
- {
- bool _addRefSucceeded = false;
-#if DEBUG_HANDLE
- var _anyref = safeHandle as AnyRef;
-#endif
- try
- {
- safeHandle.DangerousAddRef(ref _addRefSucceeded);
-#if DEBUG_HANDLE
- if (_addRefSucceeded && _anyref != null)
- _anyref.AddRef();
-#endif
- }
- catch
- {
- if (_addRefSucceeded)
- {
- safeHandle.DangerousRelease();
-#if DEBUG_HANDLE
- if (_anyref != null)
- _anyref.Release();
-#endif
- _addRefSucceeded = false;
- }
- }
-#if DEBUG_HANDLE
- Debug.WriteLine($"\tSafeHandleAddRef: {safeHandle.DangerousGetHandle()} / RefCount: {((_anyref == null) ? 0 : _anyref.RefCount)}");
-#endif
- return _addRefSucceeded;
- }
-
- public static void SafeHandleRelease(SafeHandle safeHandle)
- {
- safeHandle.DangerousRelease();
-#if DEBUG_HANDLE
- var _anyref = safeHandle as AnyRef;
- if (_anyref != null)
- {
- _anyref.Release();
- Debug.WriteLine($"\tSafeHandleRelease: {safeHandle.DangerousGetHandle()} / RefCount: {_anyref.RefCount}");
- }
-#endif
- }
-
- public static void SafeHandleReleaseByHandle(int jsHandle)
- {
-#if DEBUG_HANDLE
- Debug.WriteLine($"SafeHandleReleaseByHandle: {jsHandle}");
-#endif
- lock (_boundObjects)
- {
- if (_boundObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference))
- {
- reference.TryGetTarget(out JSObject? target);
- Debug.Assert(target != null, $"\tSafeHandleReleaseByHandle: did not find active target {jsHandle}");
- SafeHandleRelease(target);
- }
- else
- {
- Debug.Fail($"\tSafeHandleReleaseByHandle: did not find reference for {jsHandle}");
- }
- }
- }
-
- public static IntPtr SafeHandleGetHandle(SafeHandle safeHandle, bool addRef)
- {
-#if DEBUG_HANDLE
- Debug.WriteLine($"SafeHandleGetHandle: {safeHandle.DangerousGetHandle()} / addRef {addRef}");
-#endif
- if (addRef && !SafeHandleAddRef(safeHandle)) return IntPtr.Zero;
- return safeHandle.DangerousGetHandle();
- }
-
}
}
/// Initializes a new instance of the JavaScript Core SharedArrayBuffer class.
/// </summary>
/// <param name="length">The size, in bytes, of the array buffer to create.</param>
- public SharedArrayBuffer(int length) : base(Interop.Runtime.New<SharedArrayBuffer>(length))
+ public SharedArrayBuffer(int length) : base(nameof(SharedArrayBuffer), length)
{ }
- internal SharedArrayBuffer(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal SharedArrayBuffer(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
namespace System.Runtime.InteropServices.JavaScript
{
/// <summary>
/// Represents a JavaScript TypedArray.
/// </summary>
- public abstract class TypedArray<T, U> : CoreObject, ITypedArray, ITypedArray<T, U> where U : struct
+ public abstract class TypedArray<T, U> : CoreObject, ITypedArray, ITypedArray<T, U>
+ where U : struct
+ where T: JSObject
{
- protected TypedArray() : base(Interop.Runtime.New<T>())
+ protected TypedArray() : base(typeof(T).Name)
{ }
- protected TypedArray(int length) : base(Interop.Runtime.New<T>(length))
+ protected TypedArray(int length) : base(typeof(T).Name, length)
{ }
- protected TypedArray(ArrayBuffer buffer) : base(Interop.Runtime.New<T>(buffer))
+ protected TypedArray(ArrayBuffer buffer) : base(typeof(T).Name, buffer)
{ }
- protected TypedArray(ArrayBuffer buffer, int byteOffset) : base(Interop.Runtime.New<T>(buffer, byteOffset))
+ protected TypedArray(ArrayBuffer buffer, int byteOffset) : base(typeof(T).Name, buffer, byteOffset)
{ }
- protected TypedArray(ArrayBuffer buffer, int byteOffset, int length) : base(Interop.Runtime.New<T>(buffer, byteOffset, length))
+ protected TypedArray(ArrayBuffer buffer, int byteOffset, int length) : base(typeof(T).Name, buffer, byteOffset, length)
{ }
- protected TypedArray(SharedArrayBuffer buffer) : base(Interop.Runtime.New<T>(buffer))
+ protected TypedArray(SharedArrayBuffer buffer) : base(typeof(T).Name, buffer)
{ }
- protected TypedArray(SharedArrayBuffer buffer, int byteOffset) : base(Interop.Runtime.New<T>(buffer, byteOffset))
+ protected TypedArray(SharedArrayBuffer buffer, int byteOffset) : base(typeof(T).Name, buffer, byteOffset)
{ }
- protected TypedArray(SharedArrayBuffer buffer, int byteOffset, int length) : base(Interop.Runtime.New<T>(buffer, byteOffset, length))
+ protected TypedArray(SharedArrayBuffer buffer, int byteOffset, int length) : base(typeof(T).Name, buffer, byteOffset, length)
{ }
- internal TypedArray(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal TypedArray(IntPtr jsHandle) : base(jsHandle)
{ }
public TypedArrayTypeCode GetTypedArrayType()
{
get
{
+ AssertNotDisposed();
+
object jsValue = Interop.Runtime.GetByIndex(JSHandle, i, out int exception);
if (exception != 0)
}
set
{
+ AssertNotDisposed();
+
object res = Interop.Runtime.SetByIndex(JSHandle, i, value, out int exception);
if (exception != 0)
if (jsValue == null)
return null;
- Type type = jsValue.GetType();
return (U)Convert.ChangeType(jsValue, typeof(U));
}
public U[] ToArray()
{
+ AssertNotDisposed();
+
object res = Interop.Runtime.TypedArrayToArray(JSHandle, out int exception);
if (exception != 0)
object res = Interop.Runtime.TypedArrayFrom((int)ptr, 0, span.Length, Unsafe.SizeOf<U>(), (int)type, out int exception);
if (exception != 0)
throw new JSException((string)res);
- return (T)res;
+ var r = (T)res;
+ r.ReleaseInFlight();
+ return r;
}
}
public unsafe int CopyTo(Span<U> span)
{
+ AssertNotDisposed();
+
ReadOnlySpan<byte> bytes = MemoryMarshal.AsBytes(span);
fixed (byte* ptr = bytes)
{
public unsafe int CopyFrom(ReadOnlySpan<U> span)
{
+ AssertNotDisposed();
+
// source has to be instantiated.
if (span == null || span.Length == 0)
{
public Uint16Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { }
- internal Uint16Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Uint16Array(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
public Uint32Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length) { }
- internal Uint32Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Uint32Array(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
public Uint8Array(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length)
{ }
- internal Uint8Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Uint8Array(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
public Uint8ClampedArray(SharedArrayBuffer buffer, int byteOffset, int length) : base(buffer, byteOffset, length)
{ }
- internal Uint8ClampedArray(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+ internal Uint8ClampedArray(IntPtr jsHandle) : base(jsHandle)
{ }
/// <summary>
var temp = new bool[attempts];
Action<JSObject> cb = (JSObject envt) =>
{
+#if DEBUG
+ envt.AssertNotDisposed();
+ envt.AssertInFlight(0);
+#endif
var data = (int)envt.GetObjectProperty("data");
temp[data] = true;
};
dummy:dummy,
}");
- var obj = (JSObject)factory.Call(tcs.Task);
+ var obj = (JSObject)factory.Call(null, tcs.Task);
var dummy = obj.GetObjectProperty("dummy");
Assert.IsType<Task<int>>(dummy);
}
b.Invoke("fireEvent", "test");
Assert.Equal(3, counter[0]);
}
+
+ [Fact]
+ public static void RoundtripCSDate()
+ {
+ var factory = new Function("dummy", @"
+ return {
+ dummy:dummy,
+ }");
+ var date = new DateTime(2021, 01, 01, 12, 34, 45);
+
+ var obj = (JSObject)factory.Call(null, date);
+ var dummy = (DateTime)obj.GetObjectProperty("dummy");
+ Assert.Equal(date, dummy);
+ }
+
+ [Fact]
+ public static void RoundtripJSDate()
+ {
+ var factory = new Function(@"
+ var dummy = new Date(2021, 00, 01, 12, 34, 45, 567);
+ return {
+ dummy:dummy,
+ check:(value) => {
+ return value.valueOf()==dummy.valueOf() ? 1 : 0;
+ },
+ }");
+ var obj = (JSObject)factory.Call();
+
+ var date = (DateTime)obj.GetObjectProperty("dummy");
+ var check = (int)obj.Invoke("check", date);
+ Assert.Equal(1, check);
+ }
+
}
}
$BINDING__postset: 'BINDING.export_functions (Module);',
$BINDING: {
BINDING_ASM: "[System.Private.Runtime.InteropServices.JavaScript]System.Runtime.InteropServices.JavaScript.Runtime",
- mono_wasm_object_registry: [],
- mono_wasm_ref_counter: 1,
- mono_wasm_free_list: [],
- mono_wasm_owned_objects_frames: [],
- mono_wasm_owned_objects_LMF: [],
+
+ // this is array, not map. We maintain list of gaps in _js_handle_free_list so that it could be as compact as possible
+ _cs_owned_objects_by_js_handle: [],
+ _js_handle_free_list: [],
+ _next_js_handle: 1,
+
mono_wasm_marshal_enum_as_int: true,
mono_bindings_init: function (binding_asm) {
this.BINDING_ASM = binding_asm;
// avoid infinite recursion
this.init = true;
-
- Array.prototype[Symbol.for("wasm type")] = 1;
- ArrayBuffer.prototype[Symbol.for("wasm type")] = 2;
- DataView.prototype[Symbol.for("wasm type")] = 3;
- Function.prototype[Symbol.for("wasm type")] = 4;
- Map.prototype[Symbol.for("wasm type")] = 5;
+ this.wasm_type_symbol = Symbol.for("wasm type");
+ this.js_owned_gc_handle_symbol = Symbol.for("wasm js_owned_gc_handle");
+ this.cs_owned_js_handle_symbol = Symbol.for("wasm cs_owned_js_handle");
+ this.delegate_invoke_symbol = Symbol.for("wasm delegate_invoke");
+ this.delegate_invoke_signature_symbol = Symbol.for("wasm delegate_invoke_signature");
+ this.listener_registration_count_symbol = Symbol.for("wasm listener_registration_count");
+
+ // please keep System.Runtime.InteropServices.JavaScript.Runtime.MappedType in sync
+ Object.prototype[this.wasm_type_symbol] = 0;
+ Array.prototype[this.wasm_type_symbol] = 1;
+ ArrayBuffer.prototype[this.wasm_type_symbol] = 2;
+ DataView.prototype[this.wasm_type_symbol] = 3;
+ Function.prototype[this.wasm_type_symbol] = 4;
+ Map.prototype[this.wasm_type_symbol] = 5;
if (typeof SharedArrayBuffer !== 'undefined')
- SharedArrayBuffer.prototype[Symbol.for("wasm type")] = 6;
- Int8Array.prototype[Symbol.for("wasm type")] = 10;
- Uint8Array.prototype[Symbol.for("wasm type")] = 11;
- Uint8ClampedArray.prototype[Symbol.for("wasm type")] = 12;
- Int16Array.prototype[Symbol.for("wasm type")] = 13;
- Uint16Array.prototype[Symbol.for("wasm type")] = 14;
- Int32Array.prototype[Symbol.for("wasm type")] = 15;
- Uint32Array.prototype[Symbol.for("wasm type")] = 16;
- Float32Array.prototype[Symbol.for("wasm type")] = 17;
- Float64Array.prototype[Symbol.for("wasm type")] = 18;
+ SharedArrayBuffer.prototype[this.wasm_type_symbol] = 6;
+ Int8Array.prototype[this.wasm_type_symbol] = 10;
+ Uint8Array.prototype[this.wasm_type_symbol] = 11;
+ Uint8ClampedArray.prototype[this.wasm_type_symbol] = 12;
+ Int16Array.prototype[this.wasm_type_symbol] = 13;
+ Uint16Array.prototype[this.wasm_type_symbol] = 14;
+ Int32Array.prototype[this.wasm_type_symbol] = 15;
+ Uint32Array.prototype[this.wasm_type_symbol] = 16;
+ Float32Array.prototype[this.wasm_type_symbol] = 17;
+ Float64Array.prototype[this.wasm_type_symbol] = 18;
this.assembly_load = Module.cwrap ('mono_wasm_assembly_load', 'number', ['string']);
this.find_corlib_class = Module.cwrap ('mono_wasm_find_corlib_class', 'number', ['string', 'string']);
return BINDING.bind_method (method, 0, signature, "BINDINGS_" + method_name);
};
+ this.get_call_sig = get_method ("GetCallSignature");
+
// NOTE: The bound methods have a _ prefix on their names to ensure
// that any code relying on the old get_method/call_method pattern will
// break in a more understandable way.
- this._bind_js_obj = bind_runtime_method ("BindJSObject", "iii");
- this._bind_core_clr_obj = bind_runtime_method ("BindCoreCLRObject", "ii");
- this._get_js_owned_object_gc_handle = bind_runtime_method ("GetJSOwnedObjectGCHandle", "m");
- this._get_js_id = bind_runtime_method ("GetJSObjectId", "m");
- this._get_raw_mono_obj = bind_runtime_method ("GetDotNetObject", "ii!");
+ this._get_cs_owned_object_by_js_handle = bind_runtime_method ("GetCSOwnedObjectByJSHandle", "ii!");
+ this._get_cs_owned_object_js_handle = bind_runtime_method ("GetCSOwnedObjectJSHandle", 'mi');
+ this._try_get_cs_owned_object_js_handle = bind_runtime_method ("TryGetCSOwnedObjectJSHandle", "mi");
+ this._create_cs_owned_proxy = bind_runtime_method ("CreateCSOwnedProxy", "iii!");
- this._is_simple_array = bind_runtime_method ("IsSimpleArray", "m");
- this.setup_js_cont = get_method ("SetupJSContinuation");
+ this._get_js_owned_object_by_gc_handle = bind_runtime_method ("GetJSOwnedObjectByGCHandle", "i!");
+ this._get_js_owned_object_gc_handle = bind_runtime_method ("GetJSOwnedObjectGCHandle", "m");
+ this._release_js_owned_object_by_gc_handle = bind_runtime_method ("ReleaseJSOwnedObjectByGCHandle", "i");
- this.create_tcs = bind_runtime_method ("CreateTaskSource","");
- this.set_tcs_result = bind_runtime_method ("SetTaskSourceResult","io");
- this.set_tcs_failure = bind_runtime_method ("SetTaskSourceFailure","is");
- this.get_tcs_task = bind_runtime_method ("GetTaskSourceTask","i!");
+ this._create_tcs = bind_runtime_method ("CreateTaskSource","");
+ this._set_tcs_result = bind_runtime_method ("SetTaskSourceResult","io");
+ this._set_tcs_failure = bind_runtime_method ("SetTaskSourceFailure","is");
+ this._get_tcs_task = bind_runtime_method ("GetTaskSourceTask","i!");
+ this._setup_js_cont = bind_runtime_method ("SetupJSContinuation", "mo");
- this.get_call_sig = get_method ("GetCallSignature");
-
this._object_to_string = bind_runtime_method ("ObjectToString", "m");
- this.get_date_value = get_method ("GetDateValue");
- this.create_date_time = get_method ("CreateDateTime");
- this.create_uri = get_method ("CreateUri");
-
- this.safehandle_get_handle = get_method ("SafeHandleGetHandle");
- this.safehandle_release_by_handle = get_method ("SafeHandleReleaseByHandle");
- this.release_js_owned_object_by_handle = bind_runtime_method ("ReleaseJSOwnedObjectByHandle", "i");
+ this._get_date_value = bind_runtime_method ("GetDateValue", "m");
+ this._create_date_time = bind_runtime_method ("CreateDateTime", "d!");
+ this._create_uri = bind_runtime_method ("CreateUri","s!");
+ this._is_simple_array = bind_runtime_method ("IsSimpleArray", "m");
this._are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function");
+ this.isThenable = (js_obj) => {
+ // When using an external Promise library like Bluebird the Promise.resolve may not be sufficient
+ // to identify the object as a Promise.
+ return Promise.resolve(js_obj) === js_obj ||
+ ((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function")
+ };
this._empty_string = "";
this._empty_string_ptr = 0;
this._interned_js_string_table = new Map ();
this._js_owned_object_table = new Map ();
- this._js_owned_object_registry = new FinalizationRegistry(this._js_owned_object_finalized.bind(this));
+ // NOTE: FinalizationRegistry and WeakRef are missing on Safari below 14.1
+ this._use_finalization_registry = typeof globalThis.FinalizationRegistry === "function";
+ this._use_weak_ref = typeof globalThis.WeakRef === "function";
+
+ if (this._use_finalization_registry) {
+ this._js_owned_object_registry = new globalThis.FinalizationRegistry(this._js_owned_object_finalized.bind(this));
+ }
},
_js_owned_object_finalized: function (gc_handle) {
// we can release the tracking weakref (it's null now, by definition),
// and tell the C# side to stop holding a reference to the managed object.
this._js_owned_object_table.delete(gc_handle);
- this.release_js_owned_object_by_handle(gc_handle);
+ this._release_js_owned_object_by_gc_handle(gc_handle);
},
_lookup_js_owned_object: function (gc_handle) {
return null;
},
+ _register_js_owned_object: function (gc_handle, js_obj) {
+ var wr;
+ if (this._use_weak_ref) {
+ wr = new WeakRef(js_obj);
+ }
+ else {
+ // this is trivial WeakRef replacement, which holds strong refrence, instead of weak one, when the browser doesn't support it
+ wr = {
+ deref: () => {
+ return js_obj;
+ }
+ }
+ }
+
+ this._js_owned_object_table.set(gc_handle, wr);
+ },
+
_wrap_js_thenable_as_task: function (thenable) {
this.bindings_lazy_init ();
if (!thenable)
// Note that we do not implement promise/task roundtrip.
// With more complexity we could recover original instance when this Task is marshaled back to JS.
- // TODO optimization: return the tcs.Task on this same call instead of get_tcs_task
- const tcs_gc_handle = this.create_tcs();
+ // TODO optimization: return the tcs.Task on this same call instead of _get_tcs_task
+ const tcs_gc_handle = this._create_tcs();
thenable.then ((result) => {
- this.set_tcs_result(tcs_gc_handle, result);
+ this._set_tcs_result(tcs_gc_handle, result);
// let go of the thenable reference
this._mono_wasm_release_js_handle(thenable_js_handle);
+
+ // when FinalizationRegistry is not supported by this browser, we will do immediate cleanup after use
+ if (!this._use_finalization_registry) {
+ this._release_js_owned_object_by_gc_handle(tcs_gc_handle);
+ }
}, (reason) => {
- this.set_tcs_failure(tcs_gc_handle, reason ? reason.toString() : "");
+ this._set_tcs_failure(tcs_gc_handle, reason ? reason.toString() : "");
// let go of the thenable reference
this._mono_wasm_release_js_handle(thenable_js_handle);
+
+ // when FinalizationRegistry is not supported by this browser, we will do immediate cleanup after use
+ if (!this._use_finalization_registry) {
+ this._release_js_owned_object_by_gc_handle(tcs_gc_handle);
+ }
});
// collect the TaskCompletionSource with its Task after js doesn't hold the thenable anymore
- this._js_owned_object_registry.register(thenable, tcs_gc_handle);
+ if (this._use_finalization_registry) {
+ this._js_owned_object_registry.register(thenable, tcs_gc_handle);
+ }
// returns raw pointer to tcs.Task
- return this.get_tcs_task(tcs_gc_handle);
+ return this._get_tcs_task(tcs_gc_handle);
},
_unbox_task_root_as_promise: function (root) {
this.bindings_lazy_init ();
+ const self = this;
if (root.value === 0)
return null;
var cont_obj = null;
// note that we do not implement promise/task roundtrip
// With more complexity we could recover original instance when this promise is marshaled back to C#.
- var result = new Promise (function (resolve, reject) {
- cont_obj = {
- resolve: resolve,
- reject: reject
- };
+ var result = new Promise(function (resolve, reject) {
+ if (self._use_finalization_registry) {
+ cont_obj = {
+ resolve: resolve,
+ reject: reject
+ };
+ } else {
+ // when FinalizationRegistry is not supported by this browser, we will do immediate cleanup after use
+ cont_obj = {
+ resolve: function () {
+ const res = resolve.apply(null, arguments);
+ self._js_owned_object_table.delete(gc_handle);
+ self._release_js_owned_object_by_gc_handle(gc_handle);
+ return res;
+ },
+ reject: function () {
+ const res = reject.apply(null, arguments);
+ self._js_owned_object_table.delete(gc_handle);
+ self._release_js_owned_object_by_gc_handle(gc_handle);
+ return res;
+ }
+ };
+ }
});
// register C# side of the continuation
- this.call_method (this.setup_js_cont, null, "mo", [ root.value, cont_obj ]);
+ this._setup_js_cont (root.value, cont_obj );
// register for GC of the Task after the JS side is done with the promise
- this._js_owned_object_registry.register(result, gc_handle);
+ if (this._use_finalization_registry) {
+ this._js_owned_object_registry.register(result, gc_handle);
+ }
// register for instance reuse
- this._js_owned_object_table.set(gc_handle, new WeakRef(result));
+ this._register_js_owned_object(gc_handle, result);
}
return result;
},
- _unbox_ref_type_root_as_object: function (root) {
+ _unbox_ref_type_root_as_js_object: function (root) {
this.bindings_lazy_init ();
if (root.value === 0)
return null;
// this could be JSObject proxy of a js native object
- var js_handle = this._get_js_id (root.value);
- if (js_handle > 0)
+ // we don't need in-flight reference as we already have it rooted here
+ var js_handle = this._try_get_cs_owned_object_js_handle (root.value, false);
+ if (js_handle) {
+ if (js_handle===-1){
+ throw new Error("Cannot access a disposed JSObject at " + root.value);
+ }
return this.mono_wasm_get_jsobj_from_js_handle(js_handle);
+ }
// otherwise this is C# only object
// get strong reference to Object
// If the JS object for this gc_handle was already collected (or was never created)
if (!result) {
- result = {
- // keep the gc_handle so that we could easily convert it back to original C# object for roundtrip
- __js_owned_gc_handle__ : gc_handle
+ result = {};
+
+ // keep the gc_handle so that we could easily convert it back to original C# object for roundtrip
+ result[BINDING.js_owned_gc_handle_symbol]=gc_handle;
+
+ // NOTE: this would be leaking C# objects when the browser doesn't support FinalizationRegistry/WeakRef
+ if (this._use_finalization_registry) {
+ // register for GC of the C# object after the JS side is done with the object
+ this._js_owned_object_registry.register(result, gc_handle);
}
-
- // register for GC of the C# object after the JS side is done with the object
- this._js_owned_object_registry.register(result, gc_handle);
// register for instance reuse
- this._js_owned_object_table.set(gc_handle, new WeakRef(result));
+ // NOTE: this would be leaking C# objects when the browser doesn't support FinalizationRegistry/WeakRef
+ this._register_js_owned_object(gc_handle, result);
}
return result;
if (!result) {
// note that we do not implement function/delegate roundtrip
result = function() {
- const delegateRoot = MONO.mono_wasm_new_root (BINDING.wasm_get_raw_obj(gc_handle, false));
+ const delegateRoot = MONO.mono_wasm_new_root (BINDING.get_js_owned_object_by_gc_handle(gc_handle));
try {
- return BINDING.call_method (result.__mono_delegate_invoke__, delegateRoot.value, result.__mono_delegate_invoke_sig__, arguments);
+ return BINDING.call_method (result[BINDING.delegate_invoke_symbol], delegateRoot.value, result[BINDING.delegate_invoke_signature_symbol], arguments);
} finally {
delegateRoot.release();
}
};
// bind the method
- const delegateRoot = MONO.mono_wasm_new_root (BINDING.wasm_get_raw_obj(gc_handle, false));
+ const delegateRoot = MONO.mono_wasm_new_root (BINDING.get_js_owned_object_by_gc_handle(gc_handle));
try {
- if (typeof result.__mono_delegate_invoke__ === "undefined"){
- result.__mono_delegate_invoke__ = BINDING.mono_wasm_get_delegate_invoke(delegateRoot.value);
- if (!result.__mono_delegate_invoke__){
+ if (typeof result[BINDING.delegate_invoke_symbol] === "undefined"){
+ result[BINDING.delegate_invoke_symbol] = BINDING.mono_wasm_get_delegate_invoke(delegateRoot.value);
+ if (!result[BINDING.delegate_invoke_symbol]){
throw new Error("System.Delegate Invoke method can not be resolved.");
}
}
- if (typeof result.__mono_delegate_invoke_sig__ === "undefined"){
- result.__mono_delegate_invoke_sig__ = Module.mono_method_get_call_signature (result.__mono_delegate_invoke__, delegateRoot.value);
+ if (typeof result[BINDING.delegate_invoke_signature_symbol] === "undefined"){
+ result[BINDING.delegate_invoke_signature_symbol] = Module.mono_method_get_call_signature (result[BINDING.delegate_invoke_symbol], delegateRoot.value);
}
} finally {
delegateRoot.release();
}
- // register for GC of the deleate after the JS side is done with the function
- this._js_owned_object_registry.register(result, gc_handle);
+ // NOTE: this would be leaking C# objects when the browser doesn't support FinalizationRegistry. Except in case of EventListener where we cleanup after unregistration.
+ if (this._use_finalization_registry) {
+ // register for GC of the deleate after the JS side is done with the function
+ this._js_owned_object_registry.register(result, gc_handle);
+ }
// register for instance reuse
- this._js_owned_object_table.set(gc_handle, new WeakRef(result));
+ // NOTE: this would be leaking C# objects when the browser doesn't support FinalizationRegistry/WeakRef. Except in case of EventListener where we cleanup after unregistration.
+ this._register_js_owned_object(gc_handle, result);
}
return result;
return res;
},
- js_array_to_mono_array: function (js_array, asString = false) {
+ js_array_to_mono_array: function (js_array, asString, should_add_in_flight) {
var mono_array = asString ? this.mono_wasm_string_array_new (js_array.length) : this.mono_obj_array_new (js_array.length);
let [arrayRoot, elemRoot] = MONO.mono_wasm_new_roots ([mono_array, 0]);
if (asString)
obj = obj.toString ();
- elemRoot.value = this.js_to_mono_obj (obj);
+ elemRoot.value = this._js_to_mono_obj (should_add_in_flight, obj);
this.mono_obj_array_set (arrayRoot.value, i, elemRoot.value);
}
},
// this is only used from Blazor
+ js_to_mono_obj: function (js_obj) {
+ return this._js_to_mono_obj(false, js_obj)
+ },
+
+ // this is only used from Blazor
unbox_mono_obj: function (mono_obj) {
if (mono_obj === 0)
return undefined;
}
},
- _unbox_safehandle_root: function (root) {
- var addRef = true;
- var js_handle = this.call_method(this.safehandle_get_handle, null, "mi", [ root.value, addRef ]);
+ _unbox_cs_owned_root_as_js_object: function (root) {
+ // we don't need in-flight reference as we already have it rooted here
+ var js_handle = this._get_cs_owned_object_js_handle(root.value, false);
var js_obj = BINDING.mono_wasm_get_jsobj_from_js_handle (js_handle);
- if (addRef)
- {
- if (typeof this.mono_wasm_owned_objects_LMF === "undefined")
- this.mono_wasm_owned_objects_LMF = [];
-
- this.mono_wasm_owned_objects_LMF.push(js_handle);
- }
return js_obj;
},
case 6: // Task
return this._unbox_task_root_as_promise (root);
case 7: // ref type
- return this._unbox_ref_type_root_as_object (root);
+ return this._unbox_ref_type_root_as_js_object (root);
case 10: // arrays
case 11:
case 12:
case 18:
throw new Error ("Marshalling of primitive arrays are not supported. Use the corresponding TypedArray instead.");
case 20: // clr .NET DateTime
- var dateValue = this.call_method(this.get_date_value, null, "m", [ root.value ]);
+ var dateValue = this._get_date_value(root.value);
return new Date(dateValue);
case 21: // clr .NET DateTimeOffset
var dateoffsetValue = this._object_to_string (root.value);
case 22: // clr .NET Uri
var uriValue = this._object_to_string (root.value);
return uriValue;
- case 23: // clr .NET SafeHandle
- return this._unbox_safehandle_root (root);
+ case 23: // clr .NET SafeHandle/JSObject
+ return this._unbox_cs_owned_root_as_js_object (root);
case 30:
return undefined;
default:
return this.mono_wasm_box_primitive (this._class_boolean, this._box_buffer, 4);
},
- js_to_mono_obj: function (js_obj) {
+ _js_to_mono_uri: function (should_add_in_flight, js_obj) {
this.bindings_lazy_init ();
- // determines if the javascript object is a Promise or Promise like which can happen
- // when using an external Promise library. The javascript object should be marshalled
- // as managed Task objects.
- //
- // Example is when Bluebird is included in a web page using a script tag, it overwrites the
- // global Promise object by default with its own version of Promise.
- function isThenable() {
- // When using an external Promise library the Promise.resolve may not be sufficient
- // to identify the object as a Promise.
- return Promise.resolve(js_obj) === js_obj ||
- ((typeof js_obj === "object" || typeof js_obj === "function") && typeof js_obj.then === "function")
+ switch (true) {
+ case js_obj === null:
+ case typeof js_obj === "undefined":
+ return 0;
+ case typeof js_obj === "symbol":
+ case typeof js_obj === "string":
+ return this._create_uri(js_obj)
+ default:
+ return this._extract_mono_obj (should_add_in_flight, js_obj);
}
+ },
+ _js_to_mono_obj: function (should_add_in_flight, js_obj) {
+ this.bindings_lazy_init ();
switch (true) {
case js_obj === null:
return this.js_string_to_mono_string_interned (js_obj);
case typeof js_obj === "boolean":
return this._box_js_bool (js_obj);
- case isThenable() === true:
+ case this.isThenable(js_obj) === true:
return this._wrap_js_thenable_as_task (js_obj);
case js_obj.constructor.name === "Date":
- // We may need to take into account the TimeZone Offset
- return this.call_method(this.create_date_time, null, "d!", [ js_obj.getTime() ]);
+ // getTime() is always UTC
+ return this._create_date_time(js_obj.getTime());
default:
- return this.extract_mono_obj (js_obj);
+ return this._extract_mono_obj (should_add_in_flight, js_obj);
}
},
- js_to_mono_uri: function (js_obj) {
- this.bindings_lazy_init ();
+ _extract_mono_obj: function (should_add_in_flight, js_obj) {
+ if (js_obj === null || typeof js_obj === "undefined")
+ return 0;
- switch (true) {
- case js_obj === null:
- case typeof js_obj === "undefined":
- return 0;
- case typeof js_obj === "symbol":
- case typeof js_obj === "string":
- return this.call_method(this.create_uri, null, "s!", [ js_obj ])
- default:
- return this.extract_mono_obj (js_obj);
+ var result = null;
+ if (js_obj[BINDING.js_owned_gc_handle_symbol]) {
+ // for js_owned_gc_handle we don't want to create new proxy
+ // since this is strong gc_handle we don't need to in-flight reference
+ result = this.get_js_owned_object_by_gc_handle (js_obj[BINDING.js_owned_gc_handle_symbol]);
+ return result;
+ }
+ if (js_obj[BINDING.cs_owned_js_handle_symbol]) {
+ result = this.get_cs_owned_object_by_js_handle (js_obj[BINDING.cs_owned_js_handle_symbol], should_add_in_flight);
+
+ // It's possible the managed object corresponding to this JS object was collected,
+ // in which case we need to make a new one.
+ if (!result) {
+ delete js_obj[BINDING.cs_owned_js_handle_symbol];
+ }
+ }
+
+ if (!result) {
+ // Obtain the JS -> C# type mapping.
+ const wasm_type = js_obj[this.wasm_type_symbol];
+ const wasm_type_id = typeof wasm_type === "undefined" ? 0 : wasm_type;
+
+ var js_handle = BINDING.mono_wasm_get_js_handle(js_obj);
+
+ result = this._create_cs_owned_proxy(js_handle, wasm_type_id, should_add_in_flight);
}
+
+ return result;
},
-
+
has_backing_array_buffer: function (js_obj) {
return typeof SharedArrayBuffer !== 'undefined'
? js_obj.buffer instanceof ArrayBuffer || js_obj.buffer instanceof SharedArrayBuffer
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
if (!!(this.has_backing_array_buffer(js_obj) && js_obj.BYTES_PER_ELEMENT))
{
- var arrayType = js_obj[Symbol.for("wasm type")];
+ var arrayType = js_obj[this.wasm_type_symbol];
var heapBytes = this.js_typedarray_to_heap(js_obj);
var bufferArray = this.mono_typed_array_new(heapBytes.byteOffset, js_obj.length, js_obj.BYTES_PER_ELEMENT, arrayType);
Module._free(heapBytes.byteOffset);
return js_obj | 0;
},
- wasm_bind_core_clr_obj: function (js_handle, gc_handle)
+ get_js_owned_object_by_gc_handle: function (gc_handle)
{
- return this._bind_core_clr_obj (js_handle, gc_handle);
+ if(!gc_handle){
+ return 0;
+ }
+ // this is always strong gc_handle
+ return this._get_js_owned_object_by_gc_handle (gc_handle);
},
// when should_add_in_flight === true, the JSObject would be temporarily hold by Normal gc_handle, so that it would not get collected during transition to the managed stack.
// its InFlight gc_handle would be freed when the instance arrives to managed side via Interop.Runtime.ReleaseInFlight
- wasm_get_raw_obj: function (gc_handle, should_add_in_flight)
+ get_cs_owned_object_by_js_handle: function (js_handle, should_add_in_flight)
{
- if(!gc_handle){
+ if(!js_handle){
return 0;
}
-
- return this._get_raw_mono_obj (gc_handle, should_add_in_flight ? 1 : 0);
- },
-
- try_extract_mono_obj:function (js_obj) {
- if (js_obj === null || typeof js_obj === "undefined")
- return 0;
- if(js_obj.__js_owned_gc_handle__)
- return this.wasm_get_raw_obj (js_obj.__js_owned_gc_handle__, true);
- if(js_obj.__mono_gc_handle__)
- return this.wasm_get_raw_obj (js_obj.__mono_gc_handle__, true);
- return 0;
+ return this._get_cs_owned_object_by_js_handle (js_handle, should_add_in_flight);
},
mono_method_get_call_signature: function(method, mono_obj) {
}
},
- extract_mono_obj: function (js_obj) {
- if (js_obj === null || typeof js_obj === "undefined")
- return 0;
-
- var result = null;
- if (js_obj.__js_owned_gc_handle__) {
- // for __js_owned_gc_handle__ we don't want to create new proxy
- result = this.wasm_get_raw_obj (js_obj.__js_owned_gc_handle__, true);
- return result;
- }
- if (js_obj.__mono_gc_handle__) {
- result = this.wasm_get_raw_obj (js_obj.__mono_gc_handle__, true);
-
- // It's possible the managed object corresponding to this JS object was collected,
- // in which case we need to make a new one.
- if (!result) {
- delete js_obj.__mono_gc_handle__;
- }
- }
-
- if (!result) {
- var { gc_handle: new_gc_handle, should_add_in_flight } = this.mono_wasm_register_obj(js_obj);
- result = this.wasm_get_raw_obj (new_gc_handle, should_add_in_flight);
- }
-
- return result;
- },
-
_create_named_function: function (name, argumentNames, body, closure) {
var result = null, keys = null, closureArgumentList = null, closureArgumentNames = null;
result.set ('m', { steps: [{ }], size: 0});
result.set ('s', { steps: [{ convert: this.js_string_to_mono_string.bind (this) }], size: 0, needs_root: true });
result.set ('S', { steps: [{ convert: this.js_string_to_mono_string_interned.bind (this) }], size: 0, needs_root: true });
- result.set ('o', { steps: [{ convert: this.js_to_mono_obj.bind (this) }], size: 0, needs_root: true });
- result.set ('u', { steps: [{ convert: this.js_to_mono_uri.bind (this) }], size: 0, needs_root: true });
+ // note we also bind first argument to false for both _js_to_mono_obj and _js_to_mono_uri,
+ // because we will root the reference, so we don't need in-flight reference
+ // also as those are callback arguments and we don't have platform code which would release the in-flight reference on C# end
+ result.set ('o', { steps: [{ convert: this._js_to_mono_obj.bind (this, false) }], size: 0, needs_root: true });
+ result.set ('u', { steps: [{ convert: this._js_to_mono_uri.bind (this, false) }], size: 0, needs_root: true });
// result.set ('k', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i64'}], size: 8});
result.set ('j', { steps: [{ convert: this.js_to_mono_enum.bind (this), indirect: 'i32'}], size: 8});
try {
var args = [...arguments];
if (args.length > 0 && Array.isArray (args[0]))
- args[0] = BINDING.js_array_to_mono_array (args[0], true);
+ args[0] = BINDING.js_array_to_mono_array (args[0], true, false);
let result = BINDING.call_method (method, null, signature, args);
return Promise.resolve (result);
call_assembly_entry_point: function (assembly, args, signature) {
return this.bind_assembly_entry_point (assembly, signature) (...args)
},
- // Object wrapping helper functions to handle reference handles that will
- // be used in managed code.
- mono_wasm_register_obj: function(js_obj) {
- var gc_handle = undefined;
- if (js_obj !== null && typeof js_obj !== "undefined")
- {
- gc_handle = js_obj.__mono_gc_handle__;
-
- if (typeof gc_handle === "undefined") {
- // Obtain the JS -> C# type mapping.
- var wasm_type = js_obj[Symbol.for("wasm type")];
-
- var js_handle = BINDING.mono_wasm_get_js_handle(js_obj);
- gc_handle = js_obj.__mono_gc_handle__ = this._bind_js_obj(js_handle, true, typeof wasm_type === "undefined" ? -1 : wasm_type);
- // as this instance was just created, it was already created with Inflight strong gc_handle, so we do not have to do it again
- return { gc_handle, should_add_in_flight: false };
- }
- }
- // this is pre-existing instance, we need to add Inflight strong gc_handle before passing it to managed
- return { gc_handle, should_add_in_flight: true };
- },
mono_wasm_get_jsobj_from_js_handle: function(js_handle) {
if (js_handle > 0)
- return this.mono_wasm_object_registry[js_handle];
+ return this._cs_owned_objects_by_js_handle[js_handle];
return null;
},
mono_wasm_get_js_handle: function(js_obj) {
- if(js_obj.__mono_js_handle__){
- return js_obj.__mono_js_handle__;
+ if(js_obj[BINDING.cs_owned_js_handle_symbol]){
+ return js_obj[BINDING.cs_owned_js_handle_symbol];
}
- var js_handle = this.mono_wasm_free_list.length ? this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++;
- // note mono_wasm_object_registry is list, not Map. That's why we maintain mono_wasm_free_list.
- this.mono_wasm_object_registry[js_handle] = js_obj;
- js_obj.__mono_js_handle__ = js_handle;
+ var js_handle = this._js_handle_free_list.length ? this._js_handle_free_list.pop() : this._next_js_handle++;
+ // note _cs_owned_objects_by_js_handle is list, not Map. That's why we maintain _js_handle_free_list.
+ this._cs_owned_objects_by_js_handle[js_handle] = js_obj;
+ js_obj[BINDING.cs_owned_js_handle_symbol] = js_handle;
return js_handle;
},
_mono_wasm_release_js_handle: function(js_handle) {
- var obj = BINDING.mono_wasm_object_registry[js_handle];
+ var obj = BINDING._cs_owned_objects_by_js_handle[js_handle];
if (typeof obj !== "undefined" && obj !== null) {
// if this is the global object then do not
// unregister it.
if (globalThis === obj)
return obj;
- if (typeof obj.__mono_js_handle__ !== "undefined") {
- obj.__mono_gc_handle__ = undefined;
- obj.__mono_js_handle__ = undefined;
+ if (typeof obj[BINDING.cs_owned_js_handle_symbol] !== "undefined") {
+ obj[BINDING.cs_owned_js_handle_symbol] = undefined;
}
- BINDING.mono_wasm_object_registry[js_handle] = undefined;
- BINDING.mono_wasm_free_list.push(js_handle);
+ BINDING._cs_owned_objects_by_js_handle[js_handle] = undefined;
+ BINDING._js_handle_free_list.push(js_handle);
}
return obj;
},
- mono_wasm_parse_args_root : function (argsRoot) {
- var js_args = this._mono_array_root_to_js_array(argsRoot);
- this.mono_wasm_save_LMF();
- return js_args;
- },
- mono_wasm_save_LMF : function () {
- //console.log("save LMF: " + BINDING.mono_wasm_owned_objects_frames.length)
- BINDING.mono_wasm_owned_objects_frames.push(BINDING.mono_wasm_owned_objects_LMF);
- BINDING.mono_wasm_owned_objects_LMF = undefined;
- },
- mono_wasm_unwind_LMF : function () {
- var __owned_objects__ = this.mono_wasm_owned_objects_frames.pop();
- // Release all managed objects that are loaded into the LMF
- if (typeof __owned_objects__ !== "undefined")
- {
- // TODO: Look into passing the array of owned object handles in one pass.
- var refidx;
- for (refidx = 0; refidx < __owned_objects__.length; refidx++)
- {
- var ownerRelease = __owned_objects__[refidx];
- this.call_method(this.safehandle_release_by_handle, null, "i", [ ownerRelease ]);
- }
- }
- //console.log("restore LMF: " + BINDING.mono_wasm_owned_objects_frames.length)
-
- },
- mono_wasm_convert_return_value: function (ret) {
- this.mono_wasm_unwind_LMF();
- return this.js_to_mono_obj (ret);
- },
},
mono_wasm_invoke_js_with_args: function(js_handle, method_name, args, is_exception) {
let argsRoot = MONO.mono_wasm_new_root (args), nameRoot = MONO.mono_wasm_new_root (method_name);
return BINDING.js_string_to_mono_string ("ERR13: Invalid JS object handle '" + js_handle + "' while invoking '"+js_name+"'");
}
- var js_args = BINDING.mono_wasm_parse_args_root(argsRoot);
+ var js_args = BINDING._mono_array_root_to_js_array(argsRoot);
var res;
try {
if (typeof m === "undefined")
throw new Error("Method: '" + js_name + "' not found for: '" + Object.prototype.toString.call(obj) + "'");
var res = m.apply (obj, js_args);
- return BINDING.mono_wasm_convert_return_value(res);
+ return BINDING._js_to_mono_obj(true, res);
} catch (e) {
- // make sure we release object reference counts on errors.
- BINDING.mono_wasm_unwind_LMF();
var res = e.toString ();
setValue (is_exception, 1, "i32");
if (res === null || res === undefined)
var res;
try {
var m = obj [js_name];
- if (m === Object(m) && obj.__is_mono_proxied__)
- m.__is_mono_proxied__ = true;
- return BINDING.js_to_mono_obj (m);
+ return BINDING._js_to_mono_obj (true, m);
} catch (e) {
var res = e.toString ();
setValue (is_exception, 1, "i32");
var result = false;
var js_value = BINDING._unbox_mono_obj_root(valueRoot);
- BINDING.mono_wasm_save_LMF();
if (createIfNotExist) {
js_obj[property] = js_value;
}
}
- BINDING.mono_wasm_unwind_LMF();
return BINDING._box_js_bool (result);
} finally {
nameRoot.release();
try {
var m = obj [property_index];
- return BINDING.js_to_mono_obj (m);
+ return BINDING._js_to_mono_obj (true, m);
} catch (e) {
var res = e.toString ();
setValue (is_exception, 1, "i32");
}
var js_value = BINDING._unbox_mono_obj_root(valueRoot);
- BINDING.mono_wasm_save_LMF();
try {
obj [property_index] = js_value;
- BINDING.mono_wasm_unwind_LMF();
return true;
} catch (e) {
var res = e.toString ();
globalObj = globalThis[js_name];
}
+ // TODO returning null may be useful when probing for browser features
if (globalObj === null || typeof globalObj === undefined) {
setValue (is_exception, 1, "i32");
return BINDING.js_string_to_mono_string ("Global object '" + js_name + "' not found.");
}
- return BINDING.js_to_mono_obj (globalObj);
+ return BINDING._js_to_mono_obj (true, globalObj);
} finally {
nameRoot.release();
}
},
- mono_wasm_release_handle: function(js_handle, is_exception) {
+ mono_wasm_release_cs_owned_object: function(js_handle) {
BINDING.bindings_lazy_init ();
BINDING._mono_wasm_release_js_handle(js_handle);
},
- mono_wasm_bind_core_object: function(js_handle, gc_handle, is_exception) {
- BINDING.bindings_lazy_init ();
-
- var js_obj = BINDING.mono_wasm_get_jsobj_from_js_handle (js_handle);
- if (!js_obj) {
- setValue (is_exception, 1, "i32");
- return BINDING.js_string_to_mono_string ("ERR05: Invalid JS object handle '" + js_handle + "'");
- }
-
- BINDING.wasm_bind_core_clr_obj(js_handle, gc_handle );
- js_obj.__mono_gc_handle__ = gc_handle;
- js_obj.__mono_js_handle__ = js_handle;
- return gc_handle;
- },
- mono_wasm_new: function (core_name, args, is_exception) {
+ mono_wasm_create_cs_owned_object: function (core_name, args, is_exception) {
var argsRoot = MONO.mono_wasm_new_root (args), nameRoot = MONO.mono_wasm_new_root (core_name);
try {
BINDING.bindings_lazy_init ();
return BINDING.js_string_to_mono_string ("JavaScript host object '" + js_name + "' not found.");
}
- var js_args = BINDING.mono_wasm_parse_args_root(argsRoot);
+ var js_args = BINDING._mono_array_root_to_js_array(argsRoot);
try {
if (js_args)
argsList = argsList.concat (js_args);
var tempCtor = constructor.bind.apply (constructor, argsList);
- var obj = new tempCtor ();
- return obj;
+ var js_obj = new tempCtor ();
+ return js_obj;
};
- var res = allocator(coreObj, js_args);
- var js_handle = BINDING.mono_wasm_get_js_handle(res);
- return BINDING.mono_wasm_convert_return_value(js_handle);
+ var js_obj = allocator(coreObj, js_args);
+ var js_handle = BINDING.mono_wasm_get_js_handle(js_obj);
+ // returns boxed js_handle int, because on exception we need to return String on same method signature
+ // here we don't have anything to in-flight reference, as the JSObject doesn't exist yet
+ return BINDING._js_to_mono_obj(false, js_handle);
} catch (e) {
var res = e.toString ();
setValue (is_exception, 1, "i32");
return BINDING.js_string_to_mono_string ("ERR06: Invalid JS object handle '" + js_handle + "'");
}
- return BINDING.js_typed_array_to_array(js_obj);
+ // returns pointer to C# array
+ return BINDING.js_typed_array_to_array(js_obj, false);
},
mono_wasm_typed_array_copy_to: function(js_handle, pinned_array, begin, end, bytes_per_element, is_exception) {
BINDING.bindings_lazy_init ();
}
var res = BINDING.typedarray_copy_to(js_obj, pinned_array, begin, end, bytes_per_element);
- return BINDING.js_to_mono_obj (res)
+ // returns num_of_bytes boxed
+ return BINDING._js_to_mono_obj (false, res)
},
mono_wasm_typed_array_from: function(pinned_array, begin, end, bytes_per_element, type, is_exception) {
BINDING.bindings_lazy_init ();
var res = BINDING.typed_array_from(pinned_array, begin, end, bytes_per_element, type);
- return BINDING.js_to_mono_obj (res)
+ // returns JS typed array like Int8Array, to be wraped with JSObject proxy
+ return BINDING._js_to_mono_obj (true, res)
},
mono_wasm_typed_array_copy_from: function(js_handle, pinned_array, begin, end, bytes_per_element, is_exception) {
BINDING.bindings_lazy_init ();
}
var res = BINDING.typedarray_copy_from(js_obj, pinned_array, begin, end, bytes_per_element);
- return BINDING.js_to_mono_obj (res)
+ // returns num_of_bytes boxed
+ return BINDING._js_to_mono_obj (false, res)
},
mono_wasm_add_event_listener: function (objHandle, name, listener_gc_handle, optionsHandle) {
var nameRoot = MONO.mono_wasm_new_root (name);
? BINDING.mono_wasm_get_jsobj_from_js_handle(optionsHandle)
: null;
+ if(!BINDING._use_finalization_registry){
+ // we are counting registrations because same delegate could be registered into multiple sources
+ listener[BINDING.listener_registration_count_symbol] = listener[BINDING.listener_registration_count_symbol] ? listener[BINDING.listener_registration_count_symbol] + 1 : 1;
+ }
+
if (options)
obj.addEventListener(sName, listener, options);
else
var obj = BINDING.mono_wasm_get_jsobj_from_js_handle(objHandle);
if (!obj)
throw new Error("ERR11: Invalid JS object handle");
- var listener = BINDING._wrap_delegate_gc_handle_as_function(listener_gc_handle);
+ var listener = BINDING._lookup_js_owned_object(listener_gc_handle);
// Removing a nonexistent listener should not be treated as an error
if (!listener)
return;
// because that same delegate may have been used as an event listener for
// other events or event targets. The GC will automatically clean it up
// and trigger the FinalizationRegistry handler if it's unused
+
+ // When FinalizationRegistry is not supported by this browser, we cleanup manuall after unregistration
+ if (!BINDING._use_finalization_registry) {
+ listener[BINDING.listener_registration_count_symbol]--;
+ if (listener[BINDING.listener_registration_count_symbol] === 0) {
+ BINDING._js_owned_object_table.delete(listener_gc_handle);
+ BINDING._release_js_owned_object_by_gc_handle(listener_gc_handle);
+ }
+ }
+
return 0;
} catch (exc) {
return BINDING.js_string_to_mono_string(exc.message);
extern MonoObject* mono_wasm_set_object_property (int js_handle, MonoString *propertyName, MonoObject *value, int createIfNotExist, int hasOwnProperty, int *is_exception);
extern MonoObject* mono_wasm_set_by_index (int js_handle, int property_index, MonoObject *value, int *is_exception);
extern MonoObject* mono_wasm_get_global_object (MonoString *global_name, int *is_exception);
-extern void* mono_wasm_release_handle (int js_handle, int *is_exception);
-extern MonoObject* mono_wasm_new (MonoString *core_name, MonoArray *args, int *is_exception);
-extern int mono_wasm_bind_core_object (int js_handle, int gc_handle, int *is_exception);
+extern void* mono_wasm_release_cs_owned_object (int js_handle);
+extern MonoObject* mono_wasm_create_cs_owned_object (MonoString *core_name, MonoArray *args, int *is_exception);
extern MonoObject* mono_wasm_typed_array_to_array (int js_handle, int *is_exception);
extern MonoObject* mono_wasm_typed_array_copy_to (int js_handle, int ptr, int begin, int end, int bytes_per_element, int *is_exception);
extern MonoObject* mono_wasm_typed_array_from (int ptr, int begin, int end, int bytes_per_element, int type, int *is_exception);
+ 'Please use `return` statement to return a function.');
}
setValue (is_exception, 0, "i32");
- return BINDING.js_to_mono_obj (func);
+ return BINDING.js_to_mono_obj (func, true);
}
catch (e)
{
setValue (is_exception, 1, "i32");
if (res === null || res === undefined)
res = "unknown exception";
- return BINDING.js_to_mono_obj (res);
+ return BINDING.js_to_mono_obj (res, true);
}
});
mono_add_internal_call ("Interop/Runtime::SetObjectProperty", mono_wasm_set_object_property);
mono_add_internal_call ("Interop/Runtime::SetByIndex", mono_wasm_set_by_index);
mono_add_internal_call ("Interop/Runtime::GetGlobalObject", mono_wasm_get_global_object);
- mono_add_internal_call ("Interop/Runtime::ReleaseHandle", mono_wasm_release_handle);
- mono_add_internal_call ("Interop/Runtime::BindCoreObject", mono_wasm_bind_core_object);
- mono_add_internal_call ("Interop/Runtime::New", mono_wasm_new);
+ mono_add_internal_call ("Interop/Runtime::CreateCSOwnedObject", mono_wasm_create_cs_owned_object);
+ mono_add_internal_call ("Interop/Runtime::ReleaseCSOwnedObject", mono_wasm_release_cs_owned_object);
mono_add_internal_call ("Interop/Runtime::TypedArrayToArray", mono_wasm_typed_array_to_array);
mono_add_internal_call ("Interop/Runtime::TypedArrayCopyTo", mono_wasm_typed_array_copy_to);
mono_add_internal_call ("Interop/Runtime::TypedArrayFrom", mono_wasm_typed_array_from);