[wasm] Redesign of JS objects marshaling and lifecycle (#57098)
authorPavel Savara <pavelsavara@microsoft.com>
Tue, 17 Aug 2021 04:48:45 +0000 (06:48 +0200)
committerGitHub <noreply@github.com>
Tue, 17 Aug 2021 04:48:45 +0000 (23:48 -0500)
* 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

30 files changed:
src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs [deleted file]
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Array.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/ArrayBuffer.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/DataView.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float32Array.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Float64Array.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Function.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int16Array.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int32Array.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Int8Array.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.References.cs [new file with mode: 0644]
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Map.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.CS.Owned.cs [new file with mode: 0644]
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.JS.Owned.cs [new file with mode: 0644]
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/SharedArrayBuffer.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/TypedArray.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint16Array.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint32Array.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8Array.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Uint8ClampedArray.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs
src/mono/wasm/runtime/binding_support.js
src/mono/wasm/runtime/corebindings.c

index ef12917..5a61a86 100644 (file)
@@ -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<T>(params object[] parms)
-        {
-            object res = New(typeof(T).Name, parms, out int exception);
-            if (exception != 0)
-                throw new JSException((string)res);
-            return (int)res;
-        }
-
-        public static int New(string hostClassName, params object[] parms)
-        {
-            object res = New(hostClassName, parms, out int exception);
-            if (exception != 0)
-                throw new JSException((string)res);
-            return (int)res;
-        }
-
         public static object GetGlobalObject(string? str = null)
         {
             int exception;
-            object globalHandle = Runtime.GetGlobalObject(str, out exception);
+            object jsObj = GetGlobalObject(str, out exception);
 
             if (exception != 0)
                 throw new JSException($"Error obtaining a handle to global {str}");
 
-            ReleaseInFlight(globalHandle);
-            return globalHandle;
+            ReleaseInFlight(jsObj);
+            return jsObj;
         }
 
         [MethodImplAttribute(MethodImplOptions.NoInlining)]
@@ -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();
index 9d77947..e699180 100644 (file)
@@ -8,6 +8,8 @@
   <ItemGroup Condition="'$(TargetsBrowser)' == 'true'">
     <Compile Include="$(CommonPath)Interop\Browser\Interop.Runtime.cs" Link="Common\Interop\Browser\Interop.Runtime.cs" />
     <Compile Include="System\Runtime\InteropServices\JavaScript\Runtime.cs" />
+    <Compile Include="System\Runtime\InteropServices\JavaScript\Runtime.CS.Owned.cs" />
+    <Compile Include="System\Runtime\InteropServices\JavaScript\Runtime.JS.Owned.cs" />
     <Compile Include="System\Runtime\InteropServices\JavaScript\JSException.cs" />
     <Compile Include="System\Runtime\InteropServices\JavaScript\JSObject.cs" />
     <Compile Include="System\Runtime\InteropServices\JavaScript\DataView.cs" />
@@ -28,7 +30,7 @@
     <Compile Include="System\Runtime\InteropServices\JavaScript\CoreObject.cs" />
     <Compile Include="System\Runtime\InteropServices\JavaScript\HostObject.cs" />
     <Compile Include="System\Runtime\InteropServices\JavaScript\Function.cs" />
-    <Compile Include="System\Runtime\InteropServices\JavaScript\AnyRef.cs" />
+    <Compile Include="System\Runtime\InteropServices\JavaScript\JSObject.References.cs" />
   </ItemGroup>
   <ItemGroup>
     <Reference Include="System.Runtime" />
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 (file)
index 2bd4afb..0000000
+++ /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
-    }
-}
index 591c125..cd13b0c 100644 (file)
@@ -14,15 +14,14 @@ namespace System.Runtime.InteropServices.JavaScript
         /// Initializes a new instance of the Array class.
         /// </summary>
         /// <param name="_params">Parameters.</param>
-        public Array(params object[] _params) : base(Interop.Runtime.New<Array>(_params))
+        public Array(params object[] _params) : base(nameof(Array), _params)
         { }
 
         /// <summary>
         /// Initializes a new instance of the Array/> class.
         /// </summary>
         /// <param name="jsHandle">Js handle.</param>
-        /// <param name="ownsHandle">Whether or not the handle is owned by the clr or not.</param>
-        internal Array(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+        internal Array(IntPtr jsHandle) : base(jsHandle)
         { }
 
         /// <summary>
@@ -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)
index aa292b0..9ba3fb5 100644 (file)
@@ -10,22 +10,21 @@ namespace System.Runtime.InteropServices.JavaScript
         /// <summary>
         /// Initializes a new instance of the JavaScript Core ArrayBuffer class.
         /// </summary>
-        public ArrayBuffer() : base(Interop.Runtime.New<ArrayBuffer>())
+        public ArrayBuffer() : base(nameof(ArrayBuffer))
         { }
 
         /// <summary>
         /// Initializes a new instance of the JavaScript Core ArrayBuffer class.
         /// </summary>
         /// <param name="length">Length.</param>
-        public ArrayBuffer(int length) : base(Interop.Runtime.New<ArrayBuffer>(length))
+        public ArrayBuffer(int length) : base(nameof(ArrayBuffer), length)
         { }
 
         /// <summary>
         /// Initializes a new instance of the JavaScript Core ArrayBuffer class.
         /// </summary>
         /// <param name="jsHandle">Js handle.</param>
-        /// <param name="ownsHandle">Whether or not the handle is owned by the clr or not.</param>
-        internal ArrayBuffer(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+        internal ArrayBuffer(IntPtr jsHandle) : base(jsHandle)
         { }
 
         /// <summary>
index 33c4750..6a887fe 100644 (file)
@@ -18,14 +18,12 @@ namespace System.Runtime.InteropServices.JavaScript
     /// </remarks>
     public abstract class CoreObject : JSObject
     {
-        protected CoreObject(int jsHandle) : base(jsHandle, true)
+        internal CoreObject(IntPtr jsHandle) : base(jsHandle)
         {
-            object result = Interop.Runtime.BindCoreObject(jsHandle, GCHandleValue, out int exception);
-            if (exception != 0)
-                throw new JSException(SR.Format(SR.CoreObjectErrorBinding, result));
         }
 
-        internal CoreObject(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
-        { }
+        protected CoreObject(string typeName, params object[] _params) : base(typeName, _params)
+        {
+        }
     }
 }
index fef6c6c..8023e35 100644 (file)
@@ -15,7 +15,7 @@ namespace System.Runtime.InteropServices.JavaScript
         /// Initializes a new instance of the DataView class.
         /// </summary>
         /// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param>
-        public DataView(ArrayBuffer buffer) : base(Runtime.New<DataView>(buffer))
+        public DataView(ArrayBuffer buffer) : base(nameof(DataView), buffer)
         { }
 
         /// <summary>
@@ -23,7 +23,7 @@ namespace System.Runtime.InteropServices.JavaScript
         /// </summary>
         /// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param>
         /// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param>
-        public DataView(ArrayBuffer buffer, int byteOffset) : base(Runtime.New<DataView>(buffer, byteOffset))
+        public DataView(ArrayBuffer buffer, int byteOffset) : base(nameof(DataView), buffer, byteOffset)
         { }
 
         /// <summary>
@@ -32,14 +32,14 @@ namespace System.Runtime.InteropServices.JavaScript
         /// <param name="buffer">ArrayBuffer to use as the storage backing the new DataView object.</param>
         /// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param>
         /// <param name="byteLength">The number of elements in the byte array. If unspecified, the view's length will match the buffer's length.</param>
-        public DataView(ArrayBuffer buffer, int byteOffset, int byteLength) : base(Runtime.New<DataView>(buffer, byteOffset, byteLength))
+        public DataView(ArrayBuffer buffer, int byteOffset, int byteLength) : base(nameof(DataView), buffer, byteOffset, byteLength)
         { }
 
         /// <summary>
         /// Initializes a new instance of the DataView class.
         /// </summary>
         /// <param name="buffer">SharedArrayBuffer to use as the storage backing the new DataView object.</param>
-        public DataView(SharedArrayBuffer buffer) : base(Runtime.New<DataView>(buffer))
+        public DataView(SharedArrayBuffer buffer) : base(nameof(DataView), buffer)
         { }
 
         /// <summary>
@@ -47,7 +47,7 @@ namespace System.Runtime.InteropServices.JavaScript
         /// </summary>
         /// <param name="buffer">SharedArrayBuffer to use as the storage backing the new DataView object.</param>
         /// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param>
-        public DataView(SharedArrayBuffer buffer, int byteOffset) : base(Runtime.New<DataView>(buffer, byteOffset))
+        public DataView(SharedArrayBuffer buffer, int byteOffset) : base(nameof(DataView), buffer, byteOffset)
         { }
 
         /// <summary>
@@ -56,15 +56,14 @@ namespace System.Runtime.InteropServices.JavaScript
         /// <param name="buffer">SharedArrayBuffer to use as the storage backing the new DataView object.</param>
         /// <param name="byteOffset">The offset, in bytes, to the first byte in the above buffer for the new view to reference. If unspecified, the buffer view starts with the first byte.</param>
         /// <param name="byteLength">The number of elements in the byte array. If unspecified, the view's length will match the buffer's length.</param>
-        public DataView(SharedArrayBuffer buffer, int byteOffset, int byteLength) : base(Runtime.New<DataView>(buffer, byteOffset, byteLength))
+        public DataView(SharedArrayBuffer buffer, int byteOffset, int byteLength) : base(nameof(DataView), buffer, byteOffset, byteLength)
         { }
 
         /// <summary>
         /// Initializes a new instance of the DataView class.
         /// </summary>
         /// <param name="jsHandle">Js handle.</param>
-        /// <param name="ownsHandle">Managed owned</param>
-        internal DataView(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+        internal DataView(IntPtr jsHandle) : base(jsHandle)
         { }
 
         /// <summary>
index 319ff28..2549df6 100644 (file)
@@ -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)
         { }
 
         /// <summary>
index a063aa8..e0aa9c5 100644 (file)
@@ -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)
         { }
 
         /// <summary>
index e96884b..674a0b2 100644 (file)
@@ -16,10 +16,10 @@ namespace System.Runtime.InteropServices.JavaScript
     /// </remarks>
     public class Function : CoreObject
     {
-        public Function(params object[] args) : base(Interop.Runtime.New<Function>(args))
+        public Function(params object[] args) : base(nameof(Function), args)
         { }
 
-        internal Function(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+        internal Function(IntPtr jsHandle) : base(jsHandle)
         { }
 
         /// <summary>
@@ -28,7 +28,7 @@ namespace System.Runtime.InteropServices.JavaScript
         /// <returns>The apply.</returns>
         /// <param name="thisArg">This argument.</param>
         /// <param name="argsArray">Arguments.</param>
-        public object Apply(object? thisArg = null, object[]? argsArray = null) => Invoke("apply", thisArg, argsArray);
+        public object Apply(object? thisArg, object[]? argsArray = null) => Invoke("apply", thisArg, argsArray);
 
         /// <summary>
         /// Creates a new Function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
@@ -36,7 +36,7 @@ namespace System.Runtime.InteropServices.JavaScript
         /// <returns>The bind.</returns>
         /// <param name="thisArg">This argument.</param>
         /// <param name="argsArray">Arguments.</param>
-        public Function Bind(object? thisArg = null, object[]? argsArray = null) => (Function)Invoke("bind", thisArg, argsArray);
+        public Function Bind(object? thisArg, object[]? argsArray = null) => (Function)Invoke("bind", thisArg, argsArray);
 
         /// <summary>
         /// Calls a function with a given `this` value and arguments provided individually.
@@ -44,12 +44,18 @@ namespace System.Runtime.InteropServices.JavaScript
         /// <returns>The result of calling the function with the specified `this` value and arguments.</returns>
         /// <param name="thisArg">Optional (null value). The value of this provided for the call to a function. Note that this may not be the actual value seen by the method: if the method is a function in non-strict mode, null and undefined will be replaced with the global object and primitive values will be converted to objects.</param>
         /// <param name="argsArray">Optional. Arguments for the function.</param>
-        public object Call(object? thisArg = null, params object[] argsArray)
+        public object Call(object? thisArg, params object[] argsArray)
         {
             object?[] argsList = new object[argsArray.Length + 1];
             argsList[0] = thisArg;
             System.Array.Copy(argsArray, 0, argsList, 1, argsArray.Length);
             return Invoke("call", argsList);
         }
+
+        /// <summary>
+        /// Calls a function with a null `this` value.
+        /// </summary>
+        /// <returns>The result of calling the function.</returns>
+        public object Call() => Call(null);
     }
 }
index b9dec0f..ac6ebf6 100644 (file)
@@ -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)
         { }
     }
 }
index 60ac468..3636226 100644 (file)
@@ -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)
         { }
 
         /// <summary>
index 7ef2051..651fcaf 100644 (file)
@@ -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)
         { }
 
         /// <summary>
index 267b6db..9f00372 100644 (file)
@@ -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)
         { }
 
         /// <summary>
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 (file)
index 0000000..c9b443e
--- /dev/null
@@ -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}')";
+        }
+    }
+}
index 7aac370..19cb346 100644 (file)
@@ -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.
     /// </summary>
-    public class JSObject : AnyRef, IJSObject, IDisposable
+    public partial class JSObject : IJSObject, IDisposable
     {
-        // to detect redundant calls
-        public bool IsDisposed { get; private set; }
-
-        public JSObject() : this(Interop.Runtime.New<object>(), true)
-        {
-            object result = Interop.Runtime.BindCoreObject(JSHandle, GCHandleValue, out int exception);
-            if (exception != 0)
-                throw new JSException(SR.Format(SR.JSObjectErrorBinding, result));
-
-        }
-
-        internal JSObject(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
-        { }
-
-        internal JSObject(int jsHandle, bool ownsHandle) : base((IntPtr)jsHandle, ownsHandle)
-        { }
-
         /// <summary>
         ///   Invoke a named method of the object, or throws a JSException on error.
         /// </summary>
@@ -56,6 +36,8 @@ namespace System.Runtime.InteropServices.JavaScript
         /// </returns>
         public object Invoke(string method, params object?[] args)
         {
+            AssertNotDisposed();
+
             object res = Interop.Runtime.InvokeJSWithArgs(JSHandle, method, args, out int exception);
             if (exception != 0)
                 throw new JSException((string)res);
@@ -72,6 +54,8 @@ namespace System.Runtime.InteropServices.JavaScript
 
         public int AddEventListener(string name, Action<JSObject> 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<JSObject>? 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
         /// </returns>
         public object GetObjectProperty(string name)
         {
+            AssertNotDisposed();
+
             object propertyValue = Interop.Runtime.GetObjectProperty(JSHandle, name, out int exception);
             if (exception != 0)
                 throw new JSException((string)propertyValue);
@@ -162,9 +152,11 @@ namespace System.Runtime.InteropServices.JavaScript
         /// <param name="hasOwnProperty"></param>
         public void SetObjectProperty(string name, object value, bool createIfNotExists = true, bool hasOwnProperty = false)
         {
+            AssertNotDisposed();
+
             object setPropResult = Interop.Runtime.SetObjectProperty(JSHandle, name, value, createIfNotExists, hasOwnProperty, out int exception);
             if (exception != 0)
-                throw new JSException($"Error setting {name} on (js-obj js '{JSHandle}' .NET '{GCHandleValue})");
+                throw new JSException($"Error setting {name} on (js-obj js '{JSHandle}')");
         }
 
         /// <summary>
@@ -190,53 +182,5 @@ namespace System.Runtime.InteropServices.JavaScript
         /// <returns><c>true</c>, if the specified property is enumerable, <c>false</c> otherwise.</returns>
         /// <param name="prop">The String name or Symbol of the property to test.</param>
         public bool PropertyIsEnumerable(string prop) => (bool)Invoke("propertyIsEnumerable", prop);
-
-        internal void FreeHandle()
-        {
-            Runtime.ReleaseJSObject(this);
-            SetHandleAsInvalid();
-            IsDisposed = true;
-            FreeGCHandle();
-        }
-
-        public override bool Equals([NotNullWhen(true)] object? obj) => obj is JSObject other && JSHandle == other.JSHandle;
-
-        public override int GetHashCode() => JSHandle;
-
-        protected override bool ReleaseHandle()
-        {
-            bool ret = false;
-
-#if DEBUG_HANDLE
-            Console.WriteLine($"Release Handle handle:{handle}");
-            try
-            {
-#endif
-            FreeHandle();
-            ret = true;
-
-#if DEBUG_HANDLE
-            }
-            catch (Exception exception)
-            {
-                Console.WriteLine($"ReleaseHandle: {exception.Message}");
-                ret = true;  // Avoid a second assert.
-                throw;
-            }
-            finally
-            {
-                if (!ret)
-                {
-                    Console.WriteLine($"ReleaseHandle failed. handle:{handle}");
-                }
-            }
-#endif
-            return ret;
-        }
-
-        public override string ToString()
-        {
-            return $"(js-obj js '{GCHandleValue}')";
-        }
     }
 }
index 41a9e5f..5f8b010 100644 (file)
@@ -14,15 +14,14 @@ namespace System.Runtime.InteropServices.JavaScript
         /// <summary>
         /// Initializes a new instance of the Map class.
         /// </summary>
-        public Map() : base(Runtime.New<Map>())
+        public Map() : base(nameof(Map))
         { }
 
         /// <summary>
         /// Initializes a new instance of the Map class.
         /// </summary>
         /// <param name="jsHandle">Js handle.</param>
-        /// <param name="ownsHandle">Whether or not the handle is owned by the clr or not.</param>
-        internal Map(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+        internal Map(IntPtr jsHandle) : base(jsHandle)
         { }
 
         /// <summary>
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 (file)
index 0000000..370525c
--- /dev/null
@@ -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<int, WeakReference<JSObject>> _csOwnedObjects = new Dictionary<int, WeakReference<JSObject>>();
+
+        public static JSObject? GetCSOwnedObjectByJSHandle(int jsHandle, int shouldAddInflight)
+        {
+            lock (_csOwnedObjects)
+            {
+                if (_csOwnedObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference))
+                {
+                    reference.TryGetTarget(out JSObject? jsObject);
+                    if (shouldAddInflight != 0 && jsObject != null)
+                    {
+                        jsObject.AddInFlight();
+                    }
+                    return jsObject;
+                }
+            }
+            return null;
+
+        }
+
+        public static int TryGetCSOwnedObjectJSHandle(object rawObj, int shouldAddInflight)
+        {
+            JSObject? jsObject = rawObj as JSObject;
+            if (jsObject != null && shouldAddInflight != 0)
+            {
+                jsObject.AddInFlight();
+            }
+            return jsObject?.JSHandle ?? 0;
+        }
+
+        public static int GetCSOwnedObjectJSHandle(JSObject jsObject, int shouldAddInflight)
+        {
+            jsObject.AssertNotDisposed();
+
+            if (shouldAddInflight != 0)
+            {
+                jsObject.AddInFlight();
+            }
+            return jsObject.JSHandle;
+        }
+
+        public static JSObject CreateCSOwnedProxy(IntPtr jsHandle, MappedType mappedType, int shouldAddInflight)
+        {
+            JSObject? jsObject = null;
+
+            lock (_csOwnedObjects)
+            {
+                if (!_csOwnedObjects.TryGetValue((int)jsHandle, out WeakReference<JSObject>? reference) ||
+                    !reference.TryGetTarget(out jsObject) ||
+                    jsObject.IsDisposed)
+                {
+                    jsObject = mappedType switch
+                    {
+                        MappedType.JSObject => new JSObject(jsHandle),
+                        MappedType.Array => new Array(jsHandle),
+                        MappedType.ArrayBuffer => new ArrayBuffer(jsHandle),
+                        MappedType.DataView => new DataView(jsHandle),
+                        MappedType.Function => new Function(jsHandle),
+                        MappedType.Map => new Map(jsHandle),
+                        MappedType.SharedArrayBuffer => new SharedArrayBuffer(jsHandle),
+                        MappedType.Int8Array => new Int8Array(jsHandle),
+                        MappedType.Uint8Array => new Uint8Array(jsHandle),
+                        MappedType.Uint8ClampedArray => new Uint8ClampedArray(jsHandle),
+                        MappedType.Int16Array => new Int16Array(jsHandle),
+                        MappedType.Uint16Array => new Uint16Array(jsHandle),
+                        MappedType.Int32Array => new Int32Array(jsHandle),
+                        MappedType.Uint32Array => new Uint32Array(jsHandle),
+                        MappedType.Float32Array => new Float32Array(jsHandle),
+                        MappedType.Float64Array => new Float64Array(jsHandle),
+                        _ => throw new ArgumentOutOfRangeException(nameof(mappedType))
+                    };
+                    _csOwnedObjects[(int)jsHandle] = new WeakReference<JSObject>(jsObject, trackResurrection: true);
+                }
+            }
+            if (shouldAddInflight != 0)
+            {
+                jsObject.AddInFlight();
+            }
+
+            return jsObject;
+        }
+
+        #region used from C# side
+
+        internal static bool ReleaseCSOwnedObject(JSObject objToRelease)
+        {
+            objToRelease.AssertNotDisposed();
+
+            lock (_csOwnedObjects)
+            {
+                _csOwnedObjects.Remove(objToRelease.JSHandle);
+                Interop.Runtime.ReleaseCSOwnedObject(objToRelease.JSHandle);
+            }
+            return true;
+        }
+
+        internal static IntPtr CreateCSOwnedObject(JSObject proxy, string typeName, params object[] parms)
+        {
+            object res = Interop.Runtime.CreateCSOwnedObject(typeName, parms, out int exception);
+            if (exception != 0)
+                throw new JSException((string)res);
+
+            var jsHandle = (int)res;
+
+            lock (_csOwnedObjects)
+            {
+                _csOwnedObjects[jsHandle] = new WeakReference<JSObject>(proxy, trackResurrection: true);
+            }
+
+            return (IntPtr)jsHandle;
+        }
+
+        #endregion
+
+
+        // please keep BINDING wasm_type_symbol in sync
+        public enum MappedType
+        {
+            JSObject = 0,
+            Array = 1,
+            ArrayBuffer = 2,
+            DataView = 3,
+            Function = 4,
+            Map = 5,
+            SharedArrayBuffer = 6,
+            Int8Array = 10,
+            Uint8Array = 11,
+            Uint8ClampedArray = 12,
+            Int16Array = 13,
+            Uint16Array = 14,
+            Int32Array = 15,
+            Uint32Array = 16,
+            Float32Array = 17,
+            Float64Array = 18,
+        }
+    }
+}
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 (file)
index 0000000..0f7d988
--- /dev/null
@@ -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<object, int> GCHandleFromJSOwnedObject = new Dictionary<object, int>();
+
+
+        public static object GetJSOwnedObjectByGCHandle(int gcHandle)
+        {
+            GCHandle h = (GCHandle)(IntPtr)gcHandle;
+            return h.Target!;
+        }
+
+        // A JSOwnedObject is a managed object with its lifetime controlled by javascript.
+        // The managed side maintains a strong reference to the object, while the JS side
+        //  maintains a weak reference and notifies the managed side if the JS wrapper object
+        //  has been reclaimed by the JS GC. At that point, the managed side will release its
+        //  strong references, allowing the managed object to be collected.
+        // This ensures that things like delegates and promises will never 'go away' while JS
+        //  is expecting to be able to invoke or await them.
+        public static int GetJSOwnedObjectGCHandle(object obj)
+        {
+            if (obj == null)
+                return 0;
+
+            int result;
+            lock (JSOwnedObjectLock)
+            {
+                if (GCHandleFromJSOwnedObject.TryGetValue(obj, out result))
+                    return result;
+
+                result = (int)(IntPtr)GCHandle.Alloc(obj, GCHandleType.Normal);
+                GCHandleFromJSOwnedObject[obj] = result;
+                return result;
+            }
+        }
+
+        // The JS layer invokes this method when the JS wrapper for a JS owned object
+        //  has been collected by the JS garbage collector
+        public static void ReleaseJSOwnedObjectByGCHandle(int gcHandle)
+        {
+            GCHandle handle = (GCHandle)(IntPtr)gcHandle;
+            lock (JSOwnedObjectLock)
+            {
+                GCHandleFromJSOwnedObject.Remove(handle.Target!);
+                handle.Free();
+            }
+        }
+
+        public static int CreateTaskSource()
+        {
+            var tcs = new TaskCompletionSource<object>();
+            return GetJSOwnedObjectGCHandle(tcs);
+        }
+
+        public static void SetTaskSourceResult(int tcsGCHandle, object result)
+        {
+            GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
+            // this is JS owned Normal handle. We always have a Target
+            TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
+            tcs.SetResult(result);
+        }
+
+        public static void SetTaskSourceFailure(int tcsGCHandle, string reason)
+        {
+            GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
+            // this is JS owned Normal handle. We always have a Target
+            TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
+            tcs.SetException(new JSException(reason));
+        }
+
+        public static object GetTaskSourceTask(int tcsGCHandle)
+        {
+            GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
+            // this is JS owned Normal handle. We always have a Target
+            TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
+            return tcs.Task;
+        }
+
+        public static void SetupJSContinuation(Task task, JSObject continuationObj)
+        {
+            if (task.IsCompleted)
+                Complete();
+            else
+                task.GetAwaiter().OnCompleted(Complete);
+
+            void Complete()
+            {
+                try
+                {
+                    if (task.Exception == null)
+                    {
+                        object? result;
+                        Type task_type = task.GetType();
+                        if (task_type == typeof(Task))
+                        {
+                            result = System.Array.Empty<object>();
+                        }
+                        else
+                        {
+                            result = GetTaskResultMethodInfo(task_type)?.Invoke(task, null);
+                        }
+
+                        continuationObj.Invoke("resolve", result);
+                    }
+                    else
+                    {
+                        continuationObj.Invoke("reject", task.Exception.ToString());
+                    }
+                }
+                catch (Exception e)
+                {
+                    continuationObj.Invoke("reject", e.ToString());
+                }
+                finally
+                {
+                    continuationObj.Dispose();
+                }
+            }
+        }
+    }
+}
index 85dfa7f..65fb97f 100644 (file)
@@ -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<int, WeakReference<JSObject>> _boundObjects = new Dictionary<int, WeakReference<JSObject>>();
-        private static object JSOwnedObjectLock = new object();
-        // we use this to maintain identity of GCHandle for a managed object
-        private static Dictionary<object, int> GCHandleFromJSOwnedObject = new Dictionary<object, int>();
-
         private const string TaskGetResultName = "get_Result";
         private static readonly MethodInfo _taskGetResultMethodInfo = typeof(Task<>).GetMethod(TaskGetResultName)!;
 
@@ -35,193 +27,16 @@ namespace System.Runtime.InteropServices.JavaScript
             return Interop.Runtime.CompileFunction(snippet);
         }
 
-        public static int New<T>(params object[] parms)
-        {
-            return Interop.Runtime.New(typeof(T).Name, parms);
-        }
-
-        public static int New(string hostClassName, params object[] parms)
-        {
-            return Interop.Runtime.New(hostClassName, parms);
-        }
-
         public static object GetGlobalObject(string? str = null)
         {
             return Interop.Runtime.GetGlobalObject(str);
         }
 
-        public static void DumpAotProfileData (ref byte buf, int len, string extraArg)
+        public static void DumpAotProfileData(ref byte buf, int len, string extraArg)
         {
             Interop.Runtime.DumpAotProfileData(ref buf, len, extraArg);
         }
 
-        public static int BindJSObject(int jsHandle, bool ownsHandle, int mappedType)
-        {
-            JSObject? target = null;
-
-            lock (_boundObjects)
-            {
-                if (!_boundObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
-                    !reference.TryGetTarget(out target) ||
-                    target.IsDisposed)
-                {
-                    IntPtr jsIntPtr = (IntPtr)jsHandle;
-                    target = mappedType > 0 ? BindJSType(jsIntPtr, ownsHandle, mappedType) : new JSObject(jsIntPtr, ownsHandle);
-                    _boundObjects[jsHandle] = new WeakReference<JSObject>(target, trackResurrection: true);
-                }
-            }
-
-            target.AddInFlight();
-
-            return target.GCHandleValue;
-        }
-
-        public static int BindCoreCLRObject(int jsHandle, int gcHandle)
-        {
-            GCHandle h = (GCHandle)(IntPtr)gcHandle;
-            JSObject? obj = null;
-
-            lock (_boundObjects)
-            {
-                if (_boundObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? wr))
-                {
-
-                    if (!wr.TryGetTarget(out JSObject? instance) || (instance.GCHandleValue != (int)(IntPtr)h && h.IsAllocated))
-                    {
-                        throw new JSException(SR.Format(SR.MultipleHandlesPointingJsId, jsHandle));
-                    }
-
-                    obj = instance;
-                }
-                else if (h.Target is JSObject instance)
-                {
-                    _boundObjects.Add(jsHandle, new WeakReference<JSObject>(instance, trackResurrection: true));
-                    obj = instance;
-                }
-            }
-
-            return obj?.GCHandleValue ?? 0;
-        }
-
-        private static JSObject BindJSType(IntPtr jsIntPtr, bool ownsHandle, int coreType) =>
-            coreType switch
-            {
-                1 => new Array(jsIntPtr, ownsHandle),
-                2 => new ArrayBuffer(jsIntPtr, ownsHandle),
-                3 => new DataView(jsIntPtr, ownsHandle),
-                4 => new Function(jsIntPtr, ownsHandle),
-                5 => new Map(jsIntPtr, ownsHandle),
-                6 => new SharedArrayBuffer(jsIntPtr, ownsHandle),
-                10 => new Int8Array(jsIntPtr, ownsHandle),
-                11 => new Uint8Array(jsIntPtr, ownsHandle),
-                12 => new Uint8ClampedArray(jsIntPtr, ownsHandle),
-                13 => new Int16Array(jsIntPtr, ownsHandle),
-                14 => new Uint16Array(jsIntPtr, ownsHandle),
-                15 => new Int32Array(jsIntPtr, ownsHandle),
-                16 => new Uint32Array(jsIntPtr, ownsHandle),
-                17 => new Float32Array(jsIntPtr, ownsHandle),
-                18 => new Float64Array(jsIntPtr, ownsHandle),
-                _ => throw new ArgumentOutOfRangeException(nameof(coreType))
-            };
-
-        internal static bool ReleaseJSObject(JSObject objToRelease)
-        {
-            Interop.Runtime.ReleaseHandle(objToRelease.JSHandle, out int exception);
-            if (exception != 0)
-                throw new JSException($"Error releasing handle on (js-obj js '{objToRelease.JSHandle}' mono '{objToRelease.GCHandleValue})");
-
-            lock (_boundObjects)
-            {
-                _boundObjects.Remove(objToRelease.JSHandle);
-            }
-            return true;
-        }
-
-        public static int CreateTaskSource()
-        {
-            var tcs= new TaskCompletionSource<object>();
-            return GetJSOwnedObjectGCHandle(tcs);
-        }
-
-        public static void SetTaskSourceResult(int tcsGCHandle, object result)
-        {
-            GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
-            // this is JS owned Normal handle. We always have a Target
-            TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
-            tcs.SetResult(result);
-        }
-
-        public static void SetTaskSourceFailure(int tcsGCHandle, string reason)
-        {
-            GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
-            // this is JS owned Normal handle. We always have a Target
-            TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
-            tcs.SetException(new JSException(reason));
-        }
-
-        public static object GetTaskSourceTask(int tcsGCHandle)
-        {
-            GCHandle handle = (GCHandle)(IntPtr)tcsGCHandle;
-            // this is JS owned Normal handle. We always have a Target
-            TaskCompletionSource<object> tcs = (TaskCompletionSource<object>)handle.Target!;
-            return tcs.Task;
-        }
-
-        // A JSOwnedObject is a managed object with its lifetime controlled by javascript.
-        // The managed side maintains a strong reference to the object, while the JS side
-        //  maintains a weak reference and notifies the managed side if the JS wrapper object
-        //  has been reclaimed by the JS GC. At that point, the managed side will release its
-        //  strong references, allowing the managed object to be collected.
-        // This ensures that things like delegates and promises will never 'go away' while JS
-        //  is expecting to be able to invoke or await them.
-        public static int GetJSOwnedObjectGCHandle (object o) {
-            if (o == null)
-                return 0;
-
-            int result;
-            lock (JSOwnedObjectLock) {
-                if (GCHandleFromJSOwnedObject.TryGetValue(o, out result))
-                    return result;
-
-                result = (int)(IntPtr)GCHandle.Alloc(o, GCHandleType.Normal);
-                GCHandleFromJSOwnedObject[o] = result;
-                return result;
-            }
-        }
-
-        // The JS layer invokes this method when the JS wrapper for a JS owned object
-        //  has been collected by the JS garbage collector
-        public static void ReleaseJSOwnedObjectByHandle (int gcHandle) {
-            GCHandle handle = (GCHandle)(IntPtr)gcHandle;
-            lock (JSOwnedObjectLock) {
-                GCHandleFromJSOwnedObject.Remove(handle.Target!);
-                handle.Free();
-            }
-        }
-
-        public static int GetJSObjectId(object rawObj)
-        {
-            JSObject? jsObject = rawObj as JSObject;
-            return jsObject?.JSHandle ?? -1;
-        }
-
-        /// <param name="gcHandle"></param>
-        /// <param name="shouldAddInflight">when true, we would create Normal GCHandle to the JSObject, so that it would not get collected before passing it back to managed code</param>
-        public static object? GetDotNetObject(int gcHandle, int shouldAddInflight)
-        {
-            GCHandle h = (GCHandle)(IntPtr)gcHandle;
-
-            if (h.Target is JSObject jso)
-            {
-                if (shouldAddInflight != 0)
-                {
-                    jso.AddInFlight();
-                }
-                return jso;
-            }
-            return h.Target;
-        }
-
         public static bool IsSimpleArray(object a)
         {
             return a is System.Array arr && arr.Rank == 1 && arr.GetLowerBound(0) == 0;
@@ -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<object>();
-                        }
-                        else
-                        {
-                            result = GetTaskResultMethodInfo(task_type)?.Invoke(task, null);
-                        }
-
-                        continuationObj.Invoke("resolve", result);
-                    }
-                    else
-                    {
-                        continuationObj.Invoke("reject", task.Exception.ToString());
-                    }
-                }
-                catch (Exception e)
-                {
-                    continuationObj.Invoke("reject", e.ToString());
-                }
-                finally
-                {
-                    continuationObj.Dispose();
-                }
-            }
-        }
-
         /// <summary>
         /// Gets the MethodInfo for the Task{T}.Result property getter.
         /// </summary>
@@ -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<JSObject>? reference))
-                {
-                    reference.TryGetTarget(out JSObject? target);
-                    Debug.Assert(target != null, $"\tSafeHandleReleaseByHandle: did not find active target {jsHandle}");
-                    SafeHandleRelease(target);
-                }
-                else
-                {
-                    Debug.Fail($"\tSafeHandleReleaseByHandle: did not find reference for {jsHandle}");
-                }
-            }
-        }
-
-        public static IntPtr SafeHandleGetHandle(SafeHandle safeHandle, bool addRef)
-        {
-#if DEBUG_HANDLE
-            Debug.WriteLine($"SafeHandleGetHandle: {safeHandle.DangerousGetHandle()} / addRef {addRef}");
-#endif
-            if (addRef && !SafeHandleAddRef(safeHandle)) return IntPtr.Zero;
-            return safeHandle.DangerousGetHandle();
-        }
-
     }
 }
index 463c81c..6abcb98 100644 (file)
@@ -11,10 +11,10 @@ namespace System.Runtime.InteropServices.JavaScript
         /// Initializes a new instance of the JavaScript Core SharedArrayBuffer class.
         /// </summary>
         /// <param name="length">The size, in bytes, of the array buffer to create.</param>
-        public SharedArrayBuffer(int length) : base(Interop.Runtime.New<SharedArrayBuffer>(length))
+        public SharedArrayBuffer(int length) : base(nameof(SharedArrayBuffer), length)
         { }
 
-        internal SharedArrayBuffer(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+        internal SharedArrayBuffer(IntPtr jsHandle) : base(jsHandle)
         { }
 
         /// <summary>
index 6d36661..6a45c28 100644 (file)
@@ -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
     /// <summary>
     /// Represents a JavaScript TypedArray.
     /// </summary>
-    public abstract class TypedArray<T, U> : CoreObject, ITypedArray, ITypedArray<T, U> where U : struct
+    public abstract class TypedArray<T, U> : CoreObject, ITypedArray, ITypedArray<T, U>
+        where U : struct
+        where T: JSObject
     {
-        protected TypedArray() : base(Interop.Runtime.New<T>())
+        protected TypedArray() : base(typeof(T).Name)
         { }
 
-        protected TypedArray(int length) : base(Interop.Runtime.New<T>(length))
+        protected TypedArray(int length) : base(typeof(T).Name, length)
         { }
 
-        protected TypedArray(ArrayBuffer buffer) : base(Interop.Runtime.New<T>(buffer))
+        protected TypedArray(ArrayBuffer buffer) : base(typeof(T).Name, buffer)
         { }
 
-        protected TypedArray(ArrayBuffer buffer, int byteOffset) : base(Interop.Runtime.New<T>(buffer, byteOffset))
+        protected TypedArray(ArrayBuffer buffer, int byteOffset) : base(typeof(T).Name, buffer, byteOffset)
         { }
 
-        protected TypedArray(ArrayBuffer buffer, int byteOffset, int length) : base(Interop.Runtime.New<T>(buffer, byteOffset, length))
+        protected TypedArray(ArrayBuffer buffer, int byteOffset, int length) : base(typeof(T).Name, buffer, byteOffset, length)
         { }
 
-        protected TypedArray(SharedArrayBuffer buffer) : base(Interop.Runtime.New<T>(buffer))
+        protected TypedArray(SharedArrayBuffer buffer) : base(typeof(T).Name, buffer)
         { }
 
-        protected TypedArray(SharedArrayBuffer buffer, int byteOffset) : base(Interop.Runtime.New<T>(buffer, byteOffset))
+        protected TypedArray(SharedArrayBuffer buffer, int byteOffset) : base(typeof(T).Name, buffer, byteOffset)
         { }
 
-        protected TypedArray(SharedArrayBuffer buffer, int byteOffset, int length) : base(Interop.Runtime.New<T>(buffer, byteOffset, length))
+        protected TypedArray(SharedArrayBuffer buffer, int byteOffset, int length) : base(typeof(T).Name, buffer, byteOffset, length)
         { }
 
-        internal TypedArray(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle)
+        internal TypedArray(IntPtr jsHandle) : base(jsHandle)
         { }
 
         public TypedArrayTypeCode GetTypedArrayType()
@@ -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<U>(), (int)type, out int exception);
                 if (exception != 0)
                     throw new JSException((string)res);
-                return (T)res;
+                var r = (T)res;
+                r.ReleaseInFlight();
+                return r;
             }
 
         }
 
         public unsafe int CopyTo(Span<U> span)
         {
+            AssertNotDisposed();
+
             ReadOnlySpan<byte> bytes = MemoryMarshal.AsBytes(span);
             fixed (byte* ptr = bytes)
             {
@@ -205,6 +214,8 @@ namespace System.Runtime.InteropServices.JavaScript
 
         public unsafe int CopyFrom(ReadOnlySpan<U> span)
         {
+            AssertNotDisposed();
+
             // source has to be instantiated.
             if (span == null || span.Length == 0)
             {
index cbd342c..53a2379 100644 (file)
@@ -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)
         { }
 
         /// <summary>
index b5b4c17..c341db5 100644 (file)
@@ -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)
         { }
 
         /// <summary>
index 3b42c6f..8008940 100644 (file)
@@ -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)
         { }
 
         /// <summary>
index ed66bee..debaddf 100644 (file)
@@ -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)
         { }
 
         /// <summary>
index da5ad21..f5ef8d1 100644 (file)
@@ -246,6 +246,10 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             var temp = new bool[attempts];
             Action<JSObject> cb = (JSObject envt) =>
             {
+#if DEBUG
+                envt.AssertNotDisposed();
+                envt.AssertInFlight(0);
+#endif
                 var data = (int)envt.GetObjectProperty("data");
                 temp[data] = true;
             };
@@ -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<Task<int>>(dummy);
         }
index 2ad9116..edbb866 100644 (file)
@@ -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);
+        }
+
     }
 }
index 4bd589b..6e2fb3c 100644 (file)
@@ -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);
index bfd162f..ae096d8 100644 (file)
@@ -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);