From ead035b1774e91b871929b74a00726b6b9d23025 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 17 Aug 2021 06:48:45 +0200 Subject: [PATCH] [wasm] Redesign of JS objects marshaling and lifecycle (#57098) * thenable and datetime cleanup * removed ownsHandle for readability, removed AnyRef from hierarchy, removed old refcount debug code * rename _csOwnedObjects * rename _csOwnedObjects * redirect to Inflight counter * renames * rename and move * rename * New+BindCoreObject -> CreateCsOwnedObject single call * remove refcount frames * differentiate gcHandles * get rid of weak GCHandle * feedback * added MappedType enum * naming consistency * feedback * rename _cs_owned_objects_by_js_handle * split and rename files * shouldAddInflight to more methods * improved in-flight references * assert for JSObject not disposed * introduce in-flight assert * move should_add_in_flight arg to fisrt position, so that we could bind it in convert * doc * cleanup * doc * implement fallback for missing support of FInalizationRegistry/WeakRef in onlder browsers * feedback * no polyfill feedback * feedback * name consistency feedback * use symbols for helper fields * feedback --- .../Common/src/Interop/Browser/Interop.Runtime.cs | 34 +- ...ivate.Runtime.InteropServices.JavaScript.csproj | 4 +- .../Runtime/InteropServices/JavaScript/AnyRef.cs | 77 --- .../Runtime/InteropServices/JavaScript/Array.cs | 9 +- .../InteropServices/JavaScript/ArrayBuffer.cs | 7 +- .../InteropServices/JavaScript/CoreObject.cs | 10 +- .../Runtime/InteropServices/JavaScript/DataView.cs | 15 +- .../InteropServices/JavaScript/Float32Array.cs | 2 +- .../InteropServices/JavaScript/Float64Array.cs | 2 +- .../Runtime/InteropServices/JavaScript/Function.cs | 16 +- .../InteropServices/JavaScript/HostObject.cs | 17 +- .../InteropServices/JavaScript/Int16Array.cs | 2 +- .../InteropServices/JavaScript/Int32Array.cs | 2 +- .../InteropServices/JavaScript/Int8Array.cs | 2 +- .../JavaScript/JSObject.References.cs | 110 ++++ .../Runtime/InteropServices/JavaScript/JSObject.cs | 84 +-- .../Runtime/InteropServices/JavaScript/Map.cs | 5 +- .../InteropServices/JavaScript/Runtime.CS.Owned.cs | 146 ++++++ .../InteropServices/JavaScript/Runtime.JS.Owned.cs | 130 +++++ .../Runtime/InteropServices/JavaScript/Runtime.cs | 306 +---------- .../JavaScript/SharedArrayBuffer.cs | 4 +- .../InteropServices/JavaScript/TypedArray.cs | 39 +- .../InteropServices/JavaScript/Uint16Array.cs | 2 +- .../InteropServices/JavaScript/Uint32Array.cs | 2 +- .../InteropServices/JavaScript/Uint8Array.cs | 2 +- .../JavaScript/Uint8ClampedArray.cs | 2 +- .../InteropServices/JavaScript/DelegateTests.cs | 6 +- .../InteropServices/JavaScript/JavaScriptTests.cs | 33 ++ src/mono/wasm/runtime/binding_support.js | 564 +++++++++++---------- src/mono/wasm/runtime/corebindings.c | 14 +- 30 files changed, 816 insertions(+), 832 deletions(-) delete mode 100644 src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs create mode 100644 src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs create mode 100644 src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.CS.Owned.cs create mode 100644 src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.JS.Owned.cs diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index ef12917..5a61a86 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -2,11 +2,7 @@ // 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; @@ -34,11 +30,9 @@ internal static partial class Interop 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)] @@ -75,32 +69,16 @@ internal static partial class Interop return res as System.Runtime.InteropServices.JavaScript.Function; } - public static int New(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)] @@ -125,7 +103,7 @@ internal static partial class Interop } } - public static void ReleaseInFlight(object? obj) + internal static void ReleaseInFlight(object? obj) { JSObject? jsObj = obj as JSObject; jsObj?.ReleaseInFlight(); diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj index 9d77947..e699180 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj @@ -8,6 +8,8 @@ + + @@ -28,7 +30,7 @@ - + diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs deleted file mode 100644 index 2bd4afb..0000000 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs +++ /dev/null @@ -1,77 +0,0 @@ -// 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 - } -} diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Array.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Array.cs index 591c125..cd13b0c 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Array.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Array.cs @@ -14,15 +14,14 @@ namespace System.Runtime.InteropServices.JavaScript /// Initializes a new instance of the Array class. /// /// Parameters. - public Array(params object[] _params) : base(Interop.Runtime.New(_params)) + public Array(params object[] _params) : base(nameof(Array), _params) { } /// /// Initializes a new instance of the Array/> class. /// /// Js handle. - /// Whether or not the handle is owned by the clr or not. - internal Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle) + internal Array(IntPtr jsHandle) : base(jsHandle) { } /// @@ -82,6 +81,8 @@ namespace System.Runtime.InteropServices.JavaScript { get { + AssertNotDisposed(); + object indexValue = Interop.Runtime.GetByIndex(JSHandle, i, out int exception); if (exception != 0) @@ -91,6 +92,8 @@ namespace System.Runtime.InteropServices.JavaScript } set { + AssertNotDisposed(); + object res = Interop.Runtime.SetByIndex(JSHandle, i, value, out int exception); if (exception != 0) diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/ArrayBuffer.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/ArrayBuffer.cs index aa292b0..9ba3fb5 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/ArrayBuffer.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/ArrayBuffer.cs @@ -10,22 +10,21 @@ namespace System.Runtime.InteropServices.JavaScript /// /// Initializes a new instance of the JavaScript Core ArrayBuffer class. /// - public ArrayBuffer() : base(Interop.Runtime.New()) + public ArrayBuffer() : base(nameof(ArrayBuffer)) { } /// /// Initializes a new instance of the JavaScript Core ArrayBuffer class. /// /// Length. - public ArrayBuffer(int length) : base(Interop.Runtime.New(length)) + public ArrayBuffer(int length) : base(nameof(ArrayBuffer), length) { } /// /// Initializes a new instance of the JavaScript Core ArrayBuffer class. /// /// Js handle. - /// Whether or not the handle is owned by the clr or not. - internal ArrayBuffer(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle) + internal ArrayBuffer(IntPtr jsHandle) : base(jsHandle) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs index 33c4750..6a887fe 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs @@ -18,14 +18,12 @@ namespace System.Runtime.InteropServices.JavaScript /// 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) + { + } } } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DataView.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DataView.cs index fef6c6c..8023e35 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DataView.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DataView.cs @@ -15,7 +15,7 @@ namespace System.Runtime.InteropServices.JavaScript /// Initializes a new instance of the DataView class. /// /// ArrayBuffer to use as the storage backing the new DataView object. - public DataView(ArrayBuffer buffer) : base(Runtime.New(buffer)) + public DataView(ArrayBuffer buffer) : base(nameof(DataView), buffer) { } /// @@ -23,7 +23,7 @@ namespace System.Runtime.InteropServices.JavaScript /// /// ArrayBuffer to use as the storage backing the new DataView object. /// 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. - public DataView(ArrayBuffer buffer, int byteOffset) : base(Runtime.New(buffer, byteOffset)) + public DataView(ArrayBuffer buffer, int byteOffset) : base(nameof(DataView), buffer, byteOffset) { } /// @@ -32,14 +32,14 @@ namespace System.Runtime.InteropServices.JavaScript /// ArrayBuffer to use as the storage backing the new DataView object. /// 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. /// The number of elements in the byte array. If unspecified, the view's length will match the buffer's length. - public DataView(ArrayBuffer buffer, int byteOffset, int byteLength) : base(Runtime.New(buffer, byteOffset, byteLength)) + public DataView(ArrayBuffer buffer, int byteOffset, int byteLength) : base(nameof(DataView), buffer, byteOffset, byteLength) { } /// /// Initializes a new instance of the DataView class. /// /// SharedArrayBuffer to use as the storage backing the new DataView object. - public DataView(SharedArrayBuffer buffer) : base(Runtime.New(buffer)) + public DataView(SharedArrayBuffer buffer) : base(nameof(DataView), buffer) { } /// @@ -47,7 +47,7 @@ namespace System.Runtime.InteropServices.JavaScript /// /// SharedArrayBuffer to use as the storage backing the new DataView object. /// 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. - public DataView(SharedArrayBuffer buffer, int byteOffset) : base(Runtime.New(buffer, byteOffset)) + public DataView(SharedArrayBuffer buffer, int byteOffset) : base(nameof(DataView), buffer, byteOffset) { } /// @@ -56,15 +56,14 @@ namespace System.Runtime.InteropServices.JavaScript /// SharedArrayBuffer to use as the storage backing the new DataView object. /// 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. /// The number of elements in the byte array. If unspecified, the view's length will match the buffer's length. - public DataView(SharedArrayBuffer buffer, int byteOffset, int byteLength) : base(Runtime.New(buffer, byteOffset, byteLength)) + public DataView(SharedArrayBuffer buffer, int byteOffset, int byteLength) : base(nameof(DataView), buffer, byteOffset, byteLength) { } /// /// Initializes a new instance of the DataView class. /// /// Js handle. - /// Managed owned - internal DataView(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle) + internal DataView(IntPtr jsHandle) : base(jsHandle) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float32Array.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float32Array.cs index 319ff28..2549df6 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float32Array.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float32Array.cs @@ -23,7 +23,7 @@ namespace System.Runtime.InteropServices.JavaScript 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) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float64Array.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float64Array.cs index a063aa8..e0aa9c5 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float64Array.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float64Array.cs @@ -23,7 +23,7 @@ namespace System.Runtime.InteropServices.JavaScript 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) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Function.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Function.cs index e96884b..674a0b2 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Function.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Function.cs @@ -16,10 +16,10 @@ namespace System.Runtime.InteropServices.JavaScript /// public class Function : CoreObject { - public Function(params object[] args) : base(Interop.Runtime.New(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) { } /// @@ -28,7 +28,7 @@ namespace System.Runtime.InteropServices.JavaScript /// The apply. /// This argument. /// Arguments. - 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); /// /// 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. @@ -36,7 +36,7 @@ namespace System.Runtime.InteropServices.JavaScript /// The bind. /// This argument. /// Arguments. - 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); /// /// Calls a function with a given `this` value and arguments provided individually. @@ -44,12 +44,18 @@ namespace System.Runtime.InteropServices.JavaScript /// The result of calling the function with the specified `this` value and arguments. /// 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. /// Optional. Arguments for the function. - 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); } + + /// + /// Calls a function with a null `this` value. + /// + /// The result of calling the function. + public object Call() => Call(null); } } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs index b9dec0f..ac6ebf6 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs @@ -19,22 +19,9 @@ namespace System.Runtime.InteropServices.JavaScript 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) { } } } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int16Array.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int16Array.cs index 60ac468..3636226 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int16Array.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int16Array.cs @@ -23,7 +23,7 @@ namespace System.Runtime.InteropServices.JavaScript 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) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int32Array.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int32Array.cs index 7ef2051..651fcaf 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int32Array.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int32Array.cs @@ -23,7 +23,7 @@ namespace System.Runtime.InteropServices.JavaScript 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) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int8Array.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int8Array.cs index 267b6db..9f00372 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int8Array.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int8Array.cs @@ -32,7 +32,7 @@ namespace System.Runtime.InteropServices.JavaScript 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) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs new file mode 100644 index 0000000..c9b443e --- /dev/null +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs @@ -0,0 +1,110 @@ +// 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}')"; + } + } +} diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs index 7aac370..19cb346 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs @@ -1,9 +1,6 @@ // 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 @@ -16,25 +13,8 @@ namespace System.Runtime.InteropServices.JavaScript /// JSObjects are wrappers for a native JavaScript object, and /// they retain a reference to the JavaScript object for the lifetime of this C# object. /// - 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(), 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) - { } - /// /// Invoke a named method of the object, or throws a JSException on error. /// @@ -56,6 +36,8 @@ namespace System.Runtime.InteropServices.JavaScript /// 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); @@ -72,6 +54,8 @@ namespace System.Runtime.InteropServices.JavaScript public int AddEventListener(string name, Action listener, EventListenerOptions? options = null) { + AssertNotDisposed(); + var optionsDict = options.HasValue ? new JSObject() : null; @@ -105,6 +89,8 @@ namespace System.Runtime.InteropServices.JavaScript public void RemoveEventListener(string name, Action? listener, EventListenerOptions? options = null) { + AssertNotDisposed(); + if (listener == null) return; var jsfunc = Runtime.GetJSOwnedObjectGCHandle(listener); @@ -113,6 +99,8 @@ namespace System.Runtime.InteropServices.JavaScript 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); @@ -142,6 +130,8 @@ namespace System.Runtime.InteropServices.JavaScript /// public object GetObjectProperty(string name) { + AssertNotDisposed(); + object propertyValue = Interop.Runtime.GetObjectProperty(JSHandle, name, out int exception); if (exception != 0) throw new JSException((string)propertyValue); @@ -162,9 +152,11 @@ namespace System.Runtime.InteropServices.JavaScript /// 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}')"); } /// @@ -190,53 +182,5 @@ namespace System.Runtime.InteropServices.JavaScript /// true, if the specified property is enumerable, false otherwise. /// The String name or Symbol of the property to test. 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}')"; - } } } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Map.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Map.cs index 41a9e5f..5f8b010 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Map.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Map.cs @@ -14,15 +14,14 @@ namespace System.Runtime.InteropServices.JavaScript /// /// Initializes a new instance of the Map class. /// - public Map() : base(Runtime.New()) + public Map() : base(nameof(Map)) { } /// /// Initializes a new instance of the Map class. /// /// Js handle. - /// Whether or not the handle is owned by the clr or not. - internal Map(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle) + internal Map(IntPtr jsHandle) : base(jsHandle) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.CS.Owned.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.CS.Owned.cs new file mode 100644 index 0000000..370525c --- /dev/null +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.CS.Owned.cs @@ -0,0 +1,146 @@ +// 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> _csOwnedObjects = new Dictionary>(); + + public static JSObject? GetCSOwnedObjectByJSHandle(int jsHandle, int shouldAddInflight) + { + lock (_csOwnedObjects) + { + if (_csOwnedObjects.TryGetValue(jsHandle, out WeakReference? 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? 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, 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(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, + } + } +} diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.JS.Owned.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.JS.Owned.cs new file mode 100644 index 0000000..0f7d988 --- /dev/null +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.JS.Owned.cs @@ -0,0 +1,130 @@ +// 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 GCHandleFromJSOwnedObject = new Dictionary(); + + + 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(); + 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 tcs = (TaskCompletionSource)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 tcs = (TaskCompletionSource)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 tcs = (TaskCompletionSource)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(); + } + 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(); + } + } + } + } +} diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs index 85dfa7f..65fb97f 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs @@ -1,22 +1,14 @@ // 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> _boundObjects = new Dictionary>(); - private static object JSOwnedObjectLock = new object(); - // we use this to maintain identity of GCHandle for a managed object - private static Dictionary GCHandleFromJSOwnedObject = new Dictionary(); - private const string TaskGetResultName = "get_Result"; private static readonly MethodInfo _taskGetResultMethodInfo = typeof(Task<>).GetMethod(TaskGetResultName)!; @@ -35,193 +27,16 @@ namespace System.Runtime.InteropServices.JavaScript return Interop.Runtime.CompileFunction(snippet); } - public static int New(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? 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(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? 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(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(); - 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 tcs = (TaskCompletionSource)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 tcs = (TaskCompletionSource)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 tcs = (TaskCompletionSource)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; - } - - /// - /// when true, we would create Normal GCHandle to the JSObject, so that it would not get collected before passing it back to managed code - 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; @@ -313,48 +128,6 @@ namespace System.Runtime.InteropServices.JavaScript 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(); - } - 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(); - } - } - } - /// /// Gets the MethodInfo for the Task{T}.Result property getter. /// @@ -407,80 +180,5 @@ namespace System.Runtime.InteropServices.JavaScript { 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? 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(); - } - } } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs index 463c81c..6abcb98 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs @@ -11,10 +11,10 @@ namespace System.Runtime.InteropServices.JavaScript /// Initializes a new instance of the JavaScript Core SharedArrayBuffer class. /// /// The size, in bytes, of the array buffer to create. - public SharedArrayBuffer(int length) : base(Interop.Runtime.New(length)) + public SharedArrayBuffer(int length) : base(nameof(SharedArrayBuffer), length) { } - internal SharedArrayBuffer(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle) + internal SharedArrayBuffer(IntPtr jsHandle) : base(jsHandle) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/TypedArray.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/TypedArray.cs index 6d36661..6a45c28 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/TypedArray.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/TypedArray.cs @@ -1,9 +1,7 @@ // 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 { @@ -49,33 +47,35 @@ namespace System.Runtime.InteropServices.JavaScript /// /// Represents a JavaScript TypedArray. /// - public abstract class TypedArray : CoreObject, ITypedArray, ITypedArray where U : struct + public abstract class TypedArray : CoreObject, ITypedArray, ITypedArray + where U : struct + where T: JSObject { - protected TypedArray() : base(Interop.Runtime.New()) + protected TypedArray() : base(typeof(T).Name) { } - protected TypedArray(int length) : base(Interop.Runtime.New(length)) + protected TypedArray(int length) : base(typeof(T).Name, length) { } - protected TypedArray(ArrayBuffer buffer) : base(Interop.Runtime.New(buffer)) + protected TypedArray(ArrayBuffer buffer) : base(typeof(T).Name, buffer) { } - protected TypedArray(ArrayBuffer buffer, int byteOffset) : base(Interop.Runtime.New(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(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(buffer)) + protected TypedArray(SharedArrayBuffer buffer) : base(typeof(T).Name, buffer) { } - protected TypedArray(SharedArrayBuffer buffer, int byteOffset) : base(Interop.Runtime.New(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(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() @@ -131,6 +131,8 @@ namespace System.Runtime.InteropServices.JavaScript { get { + AssertNotDisposed(); + object jsValue = Interop.Runtime.GetByIndex(JSHandle, i, out int exception); if (exception != 0) @@ -141,6 +143,8 @@ namespace System.Runtime.InteropServices.JavaScript } set { + AssertNotDisposed(); + object res = Interop.Runtime.SetByIndex(JSHandle, i, value, out int exception); if (exception != 0) @@ -154,12 +158,13 @@ namespace System.Runtime.InteropServices.JavaScript 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) @@ -186,13 +191,17 @@ namespace System.Runtime.InteropServices.JavaScript object res = Interop.Runtime.TypedArrayFrom((int)ptr, 0, span.Length, Unsafe.SizeOf(), (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 span) { + AssertNotDisposed(); + ReadOnlySpan bytes = MemoryMarshal.AsBytes(span); fixed (byte* ptr = bytes) { @@ -205,6 +214,8 @@ namespace System.Runtime.InteropServices.JavaScript public unsafe int CopyFrom(ReadOnlySpan span) { + AssertNotDisposed(); + // source has to be instantiated. if (span == null || span.Length == 0) { diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint16Array.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint16Array.cs index cbd342c..53a2379 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint16Array.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint16Array.cs @@ -24,7 +24,7 @@ namespace System.Runtime.InteropServices.JavaScript 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) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint32Array.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint32Array.cs index b5b4c17..c341db5 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint32Array.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint32Array.cs @@ -24,7 +24,7 @@ namespace System.Runtime.InteropServices.JavaScript 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) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8Array.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8Array.cs index 3b42c6f..8008940 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8Array.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8Array.cs @@ -34,7 +34,7 @@ namespace System.Runtime.InteropServices.JavaScript 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) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8ClampedArray.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8ClampedArray.cs index ed66bee..debaddf 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8ClampedArray.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8ClampedArray.cs @@ -34,7 +34,7 @@ namespace System.Runtime.InteropServices.JavaScript 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) { } /// diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs index da5ad21..f5ef8d1 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs @@ -246,6 +246,10 @@ namespace System.Runtime.InteropServices.JavaScript.Tests var temp = new bool[attempts]; Action cb = (JSObject envt) => { +#if DEBUG + envt.AssertNotDisposed(); + envt.AssertInFlight(0); +#endif var data = (int)envt.GetObjectProperty("data"); temp[data] = true; }; @@ -445,7 +449,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests dummy:dummy, }"); - var obj = (JSObject)factory.Call(tcs.Task); + var obj = (JSObject)factory.Call(null, tcs.Task); var dummy = obj.GetObjectProperty("dummy"); Assert.IsType>(dummy); } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs index 2ad9116..edbb866 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs @@ -439,5 +439,38 @@ namespace System.Runtime.InteropServices.JavaScript.Tests 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); + } + } } diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index 4bd589b..6e2fb3c 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -5,11 +5,12 @@ var BindingSupportLib = { $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; @@ -34,23 +35,31 @@ var BindingSupportLib = { // 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']); @@ -117,36 +126,40 @@ var BindingSupportLib = { 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; @@ -156,7 +169,13 @@ var BindingSupportLib = { 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) { @@ -165,7 +184,7 @@ var BindingSupportLib = { // 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) { @@ -180,6 +199,23 @@ var BindingSupportLib = { 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) @@ -191,27 +227,40 @@ var BindingSupportLib = { // 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; @@ -230,35 +279,60 @@ var BindingSupportLib = { 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 @@ -269,16 +343,20 @@ var BindingSupportLib = { // 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; @@ -304,36 +382,40 @@ var BindingSupportLib = { 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; @@ -512,7 +594,7 @@ var BindingSupportLib = { 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]); @@ -522,7 +604,7 @@ var BindingSupportLib = { 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); } @@ -533,6 +615,11 @@ var BindingSupportLib = { }, // 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; @@ -545,17 +632,10 @@ var BindingSupportLib = { } }, - _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; }, @@ -579,7 +659,7 @@ var BindingSupportLib = { 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: @@ -591,7 +671,7 @@ var BindingSupportLib = { 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); @@ -599,8 +679,8 @@ var BindingSupportLib = { 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: @@ -660,21 +740,22 @@ var BindingSupportLib = { 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: @@ -698,31 +779,50 @@ var BindingSupportLib = { 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 @@ -741,7 +841,7 @@ var BindingSupportLib = { // 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); @@ -888,30 +988,23 @@ var BindingSupportLib = { 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) { @@ -925,34 +1018,6 @@ var BindingSupportLib = { } }, - 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; @@ -1010,8 +1075,11 @@ var BindingSupportLib = { 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}); @@ -1626,7 +1694,7 @@ var BindingSupportLib = { 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); @@ -1638,90 +1706,38 @@ var BindingSupportLib = { 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); @@ -1740,7 +1756,7 @@ var BindingSupportLib = { 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 { @@ -1748,10 +1764,8 @@ var BindingSupportLib = { 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) @@ -1783,10 +1797,8 @@ var BindingSupportLib = { 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"); @@ -1817,7 +1829,6 @@ var BindingSupportLib = { var result = false; var js_value = BINDING._unbox_mono_obj_root(valueRoot); - BINDING.mono_wasm_save_LMF(); if (createIfNotExist) { js_obj[property] = js_value; @@ -1842,7 +1853,6 @@ var BindingSupportLib = { } } - BINDING.mono_wasm_unwind_LMF(); return BINDING._box_js_bool (result); } finally { nameRoot.release(); @@ -1860,7 +1870,7 @@ var BindingSupportLib = { 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"); @@ -1881,11 +1891,9 @@ var BindingSupportLib = { } 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 (); @@ -1914,35 +1922,22 @@ var BindingSupportLib = { 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 (); @@ -1961,7 +1956,7 @@ var BindingSupportLib = { 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 { @@ -1973,13 +1968,15 @@ var BindingSupportLib = { 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"); @@ -2001,7 +1998,8 @@ var BindingSupportLib = { 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 (); @@ -2013,12 +2011,14 @@ var BindingSupportLib = { } 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 (); @@ -2030,7 +2030,8 @@ var BindingSupportLib = { } 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); @@ -2049,6 +2050,11 @@ var BindingSupportLib = { ? 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 @@ -2067,7 +2073,7 @@ var BindingSupportLib = { 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; @@ -2078,6 +2084,16 @@ var BindingSupportLib = { // 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); diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index bfd162f..ae096d8 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -16,9 +16,8 @@ extern MonoObject* mono_wasm_get_by_index (int js_handle, int property_index, in 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); @@ -39,7 +38,7 @@ EM_JS(MonoObject*, compile_function, (int snippet_ptr, int len, int *is_exceptio + '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) { @@ -47,7 +46,7 @@ EM_JS(MonoObject*, compile_function, (int snippet_ptr, int len, int *is_exceptio 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); } }); @@ -75,9 +74,8 @@ void core_initialize_internals () 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); -- 2.7.4