[wasm] Redesign of managed objects marshaling and lifecycle (#56538)
authorPavel Savara <pavelsavara@microsoft.com>
Sat, 7 Aug 2021 06:06:48 +0000 (08:06 +0200)
committerGitHub <noreply@github.com>
Sat, 7 Aug 2021 06:06:48 +0000 (08:06 +0200)
file cycle of JS owned C# instances is driven by FinalizationRegistry

- got rid of Runtime._weakDelegateTable and Runtime._rawToJS
- got rid of JSObject.WeakRawObject and JSObject.RawObject
- GCHandle instead of JSObject double proxy for plain managed ref types
- GCHandle instead of int sequence for delegates + redesign of invocation
- GCHandle for task + redesign of invocation
- improved in-flight retention of thenable/promise and Task
- explicitly delegate type of parameter for EventListener
- moved and renamed some binding functions
- renamed all handles to jsHandle or gcHandle as appropriate
- removed jsHandle math
- cleanup of unused functions
- improved error messages for invalid handles
- more unit tests

12 files changed:
src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.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/HostObject.cs
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/Runtime.cs
src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj
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-test.js
src/mono/wasm/runtime/binding_support.js
src/mono/wasm/runtime/corebindings.c

index 67509d1..ef12917 100644 (file)
@@ -21,41 +21,37 @@ internal static partial class Interop
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
         internal static extern object CompileFunction(string str, out int exceptionalResult);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object InvokeJSWithArgs(int jsObjHandle, string method, object?[] parms, out int exceptionalResult);
+        internal static extern object InvokeJSWithArgs(int jsHandle, string method, object?[] parms, out int exceptionalResult);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object GetObjectProperty(int jsObjHandle, string propertyName, out int exceptionalResult);
+        internal static extern object GetObjectProperty(int jsHandle, string propertyName, out int exceptionalResult);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object SetObjectProperty(int jsObjHandle, string propertyName, object value, bool createIfNotExists, bool hasOwnProperty, out int exceptionalResult);
+        internal static extern object SetObjectProperty(int jsHandle, string propertyName, object value, bool createIfNotExists, bool hasOwnProperty, out int exceptionalResult);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object GetByIndex(int jsObjHandle, int index, out int exceptionalResult);
+        internal static extern object GetByIndex(int jsHandle, int index, out int exceptionalResult);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object SetByIndex(int jsObjHandle, int index, object? value, out int exceptionalResult);
+        internal static extern object SetByIndex(int jsHandle, int index, object? value, out int exceptionalResult);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
         internal static extern object GetGlobalObject(string? globalName, out int exceptionalResult);
 
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object ReleaseHandle(int jsObjHandle, out int exceptionalResult);
+        internal static extern object ReleaseHandle(int jsHandle, out int exceptionalResult);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object ReleaseObject(int jsObjHandle, out int exceptionalResult);
-        [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object BindCoreObject(int jsObjHandle, int gcHandle, out int exceptionalResult);
-        [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object BindHostObject(int jsObjHandle, int gcHandle, out int exceptionalResult);
+        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);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object TypedArrayToArray(int jsObjHandle, out int exceptionalResult);
+        internal static extern object TypedArrayToArray(int jsHandle, out int exceptionalResult);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object TypedArrayCopyTo(int jsObjHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult);
+        internal static extern object TypedArrayCopyTo(int jsHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
         internal static extern object TypedArrayFrom(int arrayPtr, int begin, int end, int bytesPerElement, int type, out int exceptionalResult);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern object TypedArrayCopyFrom(int jsObjHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult);
+        internal static extern object TypedArrayCopyFrom(int jsHandle, int arrayPtr, int begin, int end, int bytesPerElement, out int exceptionalResult);
 
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern string? AddEventListener(int jsObjHandle, string name, int weakDelegateHandle, int optionsObjHandle);
+        internal static extern string? AddEventListener(int jsHandle, string name, int gcHandle, int optionsJsHandle);
         [MethodImplAttribute(MethodImplOptions.InternalCall)]
-        internal static extern string? RemoveEventListener(int jsObjHandle, string name, int weakDelegateHandle, bool capture);
+        internal static extern string? RemoveEventListener(int jsHandle, string name, int gcHandle, bool capture);
 
         // / <summary>
         // / Execute the provided string in the JavaScript context
index 58587eb..2bd4afb 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.Diagnostics;
-using System.Runtime.InteropServices;
 using System.Threading;
 using Microsoft.Win32.SafeHandles;
 
@@ -16,9 +14,6 @@ namespace System.Runtime.InteropServices.JavaScript
         private GCHandle AnyRefHandle;
         public int JSHandle => (int)handle;
 
-        internal AnyRef(int jsHandle, bool ownsHandle) : this((IntPtr)jsHandle, ownsHandle)
-        { }
-
         internal AnyRef(IntPtr jsHandle, bool ownsHandle) : base(ownsHandle)
         {
             SetHandle(jsHandle);
@@ -26,7 +21,7 @@ namespace System.Runtime.InteropServices.JavaScript
             InFlight = null;
             InFlightCounter = 0;
         }
-        internal int Int32Handle => (int)(IntPtr)AnyRefHandle;
+        internal int GCHandleValue => (int)(IntPtr)AnyRefHandle;
 
         internal void AddInFlight()
         {
index 791a3ab..33c4750 100644 (file)
@@ -20,7 +20,7 @@ namespace System.Runtime.InteropServices.JavaScript
     {
         protected CoreObject(int jsHandle) : base(jsHandle, true)
         {
-            object result = Interop.Runtime.BindCoreObject(jsHandle, Int32Handle, out int exception);
+            object result = Interop.Runtime.BindCoreObject(jsHandle, GCHandleValue, out int exception);
             if (exception != 0)
                 throw new JSException(SR.Format(SR.CoreObjectErrorBinding, result));
         }
index 1d3b3bf..b9dec0f 100644 (file)
@@ -27,9 +27,9 @@ namespace System.Runtime.InteropServices.JavaScript
 
     public abstract class HostObjectBase : JSObject, IHostObject
     {
-        protected HostObjectBase(int jHandle) : base(jHandle, true)
+        protected HostObjectBase(int jsHandle) : base(jsHandle, true)
         {
-            object result = Interop.Runtime.BindHostObject(jHandle, Int32Handle, out int exception);
+            object result = Interop.Runtime.BindCoreObject(jsHandle, GCHandleValue, out int exception);
             if (exception != 0)
                 throw new JSException(SR.Format(SR.HostObjectErrorBinding, result));
         }
index 85eb828..7aac370 100644 (file)
@@ -18,16 +18,12 @@ namespace System.Runtime.InteropServices.JavaScript
     /// </summary>
     public class JSObject : AnyRef, IJSObject, IDisposable
     {
-        internal object? RawObject;
-
-        private WeakReference<Delegate>? WeakRawObject;
-
         // to detect redundant calls
         public bool IsDisposed { get; private set; }
 
         public JSObject() : this(Interop.Runtime.New<object>(), true)
         {
-            object result = Interop.Runtime.BindCoreObject(JSHandle, Int32Handle, out int exception);
+            object result = Interop.Runtime.BindCoreObject(JSHandle, GCHandleValue, out int exception);
             if (exception != 0)
                 throw new JSException(SR.Format(SR.JSObjectErrorBinding, result));
 
@@ -39,16 +35,6 @@ namespace System.Runtime.InteropServices.JavaScript
         internal JSObject(int jsHandle, bool ownsHandle) : base((IntPtr)jsHandle, ownsHandle)
         { }
 
-        internal JSObject(int jsHandle, object rawObj) : base(jsHandle, false)
-        {
-            RawObject = rawObj;
-        }
-
-        internal JSObject(int jsHandle, Delegate rawDelegate, bool ownsHandle = true) : base(jsHandle, ownsHandle)
-        {
-            WeakRawObject = new WeakReference<Delegate>(rawDelegate, trackResurrection: false);
-        }
-
         /// <summary>
         ///   Invoke a named method of the object, or throws a JSException on error.
         /// </summary>
@@ -84,7 +70,7 @@ namespace System.Runtime.InteropServices.JavaScript
             public object? Signal;
         }
 
-        public int AddEventListener(string name, Delegate listener, EventListenerOptions? options = null)
+        public int AddEventListener(string name, Action<JSObject> listener, EventListenerOptions? options = null)
         {
             var optionsDict = options.HasValue
                 ? new JSObject()
@@ -94,7 +80,7 @@ namespace System.Runtime.InteropServices.JavaScript
                 if (options?.Signal != null)
                     throw new NotImplementedException("EventListenerOptions.Signal");
 
-                var jsfunc = Runtime.GetJSOwnedObjectHandle(listener);
+                var jsfunc = Runtime.GetJSOwnedObjectGCHandle(listener);
                 // int exception;
                 if (options.HasValue) {
                     // TODO: Optimize this
@@ -117,17 +103,17 @@ namespace System.Runtime.InteropServices.JavaScript
             }
         }
 
-        public void RemoveEventListener(string name, Delegate? listener, EventListenerOptions? options = null)
+        public void RemoveEventListener(string name, Action<JSObject>? listener, EventListenerOptions? options = null)
         {
             if (listener == null)
                 return;
-            var jsfunc = Runtime.GetJSOwnedObjectHandle(listener);
+            var jsfunc = Runtime.GetJSOwnedObjectGCHandle(listener);
             RemoveEventListener(name, jsfunc, options);
         }
 
-        public void RemoveEventListener(string name, int listenerHandle, EventListenerOptions? options = null)
+        public void RemoveEventListener(string name, int listenerGCHandle, EventListenerOptions? options = null)
         {
-            var ret = Interop.Runtime.RemoveEventListener(JSHandle, name, listenerHandle, options?.Capture ?? false);
+            var ret = Interop.Runtime.RemoveEventListener(JSHandle, name, listenerGCHandle, options?.Capture ?? false);
             if (ret != null)
                 throw new JSException(ret);
         }
@@ -178,7 +164,7 @@ namespace System.Runtime.InteropServices.JavaScript
         {
             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 '{Int32Handle} raw '{RawObject != null})");
+                throw new JSException($"Error setting {name} on (js-obj js '{JSHandle}' .NET '{GCHandleValue})");
         }
 
         /// <summary>
@@ -205,19 +191,11 @@ namespace System.Runtime.InteropServices.JavaScript
         /// <param name="prop">The String name or Symbol of the property to test.</param>
         public bool PropertyIsEnumerable(string prop) => (bool)Invoke("propertyIsEnumerable", prop);
 
-        internal bool IsWeakWrapper => WeakRawObject?.TryGetTarget(out _) == true;
-
-        internal object? GetWrappedObject()
-        {
-            return RawObject ?? (WeakRawObject is WeakReference<Delegate> wr && wr.TryGetTarget(out Delegate? d) ? d : null);
-        }
         internal void FreeHandle()
         {
             Runtime.ReleaseJSObject(this);
             SetHandleAsInvalid();
             IsDisposed = true;
-            RawObject = null;
-            WeakRawObject = null;
             FreeGCHandle();
         }
 
@@ -258,7 +236,7 @@ namespace System.Runtime.InteropServices.JavaScript
 
         public override string ToString()
         {
-            return $"(js-obj js '{Int32Handle}' raw '{RawObject != null}' weak_raw '{WeakRawObject != null}')";
+            return $"(js-obj js '{GCHandleValue}')";
         }
     }
 }
index d30808f..85dfa7f 100644 (file)
@@ -13,12 +13,9 @@ namespace System.Runtime.InteropServices.JavaScript
     public static class Runtime
     {
         private static readonly Dictionary<int, WeakReference<JSObject>> _boundObjects = new Dictionary<int, WeakReference<JSObject>>();
-        private static readonly Dictionary<object, JSObject> _rawToJS = new Dictionary<object, JSObject>();
-        // _weakDelegateTable is a ConditionalWeakTable with the Delegate and associated JSObject:
-        // Key Lifetime:
-        //    Once the key dies, the dictionary automatically removes the key/value entry.
-        // No need to lock as it is thread safe.
-        private static readonly ConditionalWeakTable<Delegate, JSObject> _weakDelegateTable = new ConditionalWeakTable<Delegate, 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)!;
@@ -48,23 +45,6 @@ namespace System.Runtime.InteropServices.JavaScript
             return Interop.Runtime.New(hostClassName, parms);
         }
 
-        public static void FreeObject(object obj)
-        {
-            if (obj is Delegate)
-            {
-                return;
-            }
-
-            JSObject? jsobj;
-            lock (_rawToJS)
-            {
-                if (!_rawToJS.Remove(obj, out jsobj))
-                {
-                    throw new JSException(SR.Format(SR.ErrorReleasingObject, obj));
-                }
-            }
-        }
-
         public static object GetGlobalObject(string? str = null)
         {
             return Interop.Runtime.GetGlobalObject(str);
@@ -75,51 +55,52 @@ namespace System.Runtime.InteropServices.JavaScript
             Interop.Runtime.DumpAotProfileData(ref buf, len, extraArg);
         }
 
-        public static int BindJSObject(int jsId, bool ownsHandle, int mappedType)
+        public static int BindJSObject(int jsHandle, bool ownsHandle, int mappedType)
         {
             JSObject? target = null;
 
             lock (_boundObjects)
             {
-                if (!_boundObjects.TryGetValue(jsId, out WeakReference<JSObject>? reference) ||
+                if (!_boundObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference) ||
                     !reference.TryGetTarget(out target) ||
                     target.IsDisposed)
                 {
-                    IntPtr jsIntPtr = (IntPtr)jsId;
+                    IntPtr jsIntPtr = (IntPtr)jsHandle;
                     target = mappedType > 0 ? BindJSType(jsIntPtr, ownsHandle, mappedType) : new JSObject(jsIntPtr, ownsHandle);
-                    _boundObjects[jsId] = new WeakReference<JSObject>(target, trackResurrection: true);
+                    _boundObjects[jsHandle] = new WeakReference<JSObject>(target, trackResurrection: true);
                 }
             }
 
             target.AddInFlight();
 
-            return target.Int32Handle;
+            return target.GCHandleValue;
         }
 
-        public static int BindCoreCLRObject(int jsId, int gcHandle)
+        public static int BindCoreCLRObject(int jsHandle, int gcHandle)
         {
             GCHandle h = (GCHandle)(IntPtr)gcHandle;
             JSObject? obj = null;
 
             lock (_boundObjects)
             {
-                if (_boundObjects.TryGetValue(jsId, out WeakReference<JSObject>? wr))
+                if (_boundObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? wr))
                 {
-                    if (!wr.TryGetTarget(out JSObject? instance) || (instance.Int32Handle != (int)(IntPtr)h && h.IsAllocated))
+
+                    if (!wr.TryGetTarget(out JSObject? instance) || (instance.GCHandleValue != (int)(IntPtr)h && h.IsAllocated))
                     {
-                        throw new JSException(SR.Format(SR.MultipleHandlesPointingJsId, jsId));
+                        throw new JSException(SR.Format(SR.MultipleHandlesPointingJsId, jsHandle));
                     }
 
                     obj = instance;
                 }
                 else if (h.Target is JSObject instance)
                 {
-                    _boundObjects.Add(jsId, new WeakReference<JSObject>(instance, trackResurrection: true));
+                    _boundObjects.Add(jsHandle, new WeakReference<JSObject>(instance, trackResurrection: true));
                     obj = instance;
                 }
             }
 
-            return obj?.Int32Handle ?? 0;
+            return obj?.GCHandleValue ?? 0;
         }
 
         private static JSObject BindJSType(IntPtr jsIntPtr, bool ownsHandle, int coreType) =>
@@ -147,7 +128,7 @@ namespace System.Runtime.InteropServices.JavaScript
         {
             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.Int32Handle} raw '{objToRelease.RawObject != null}' weak raw '{objToRelease.IsWeakWrapper}'   )");
+                throw new JSException($"Error releasing handle on (js-obj js '{objToRelease.JSHandle}' mono '{objToRelease.GCHandleValue})");
 
             lock (_boundObjects)
             {
@@ -156,73 +137,36 @@ namespace System.Runtime.InteropServices.JavaScript
             return true;
         }
 
-        public static void UnBindRawJSObjectAndFree(int gcHandle)
+        public static int CreateTaskSource()
         {
-            GCHandle h = (GCHandle)(IntPtr)gcHandle;
-            JSObject? obj = h.Target as JSObject;
-            lock (_rawToJS)
-            {
-                if (obj?.RawObject != null)
-                {
-                    _rawToJS.Remove(obj.RawObject);
-                    obj.FreeHandle();
-                }
-            }
+            var tcs= new TaskCompletionSource<object>();
+            return GetJSOwnedObjectGCHandle(tcs);
         }
 
-        public static object CreateTaskSource(int jsId)
-        {
-            return new TaskCompletionSource<object>();
-        }
-
-        public static void SetTaskSourceResult(TaskCompletionSource<object> tcs, object result)
+        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(TaskCompletionSource<object> tcs, string reason)
+        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 int GetTaskAndBind(TaskCompletionSource<object> tcs, int jsId)
-        {
-            return BindExistingObject(tcs.Task, jsId);
-        }
-
-        public static int BindExistingObject(object rawObj, int jsId)
+        public static object GetTaskSourceTask(int tcsGCHandle)
         {
-            JSObject? jsObject;
-            if (rawObj is Delegate dele)
-            {
-                jsObject = new JSObject(jsId, dele);
-                lock (_boundObjects)
-                {
-                    _boundObjects.Add(jsId, new WeakReference<JSObject>(jsObject));
-                }
-                lock (_weakDelegateTable)
-                {
-                    _weakDelegateTable.Add(dele, jsObject);
-                }
-            }
-            else
-            {
-                lock (_rawToJS)
-                {
-                    if (!_rawToJS.TryGetValue(rawObj, out jsObject))
-                    {
-                        _rawToJS.Add(rawObj, jsObject = new JSObject(jsId, rawObj));
-                    }
-                }
-            }
-            return jsObject.Int32Handle;
+            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;
         }
 
-        private static int NextJSOwnedObjectID = 1;
-        private static object JSOwnedObjectLock = new object();
-        private static Dictionary<object, int> IDFromJSOwnedObject = new Dictionary<object, int>();
-        private static Dictionary<int, object> JSOwnedObjectFromID = new Dictionary<int, object>();
-
         // 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
@@ -230,79 +174,34 @@ namespace System.Runtime.InteropServices.JavaScript
         //  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 GetJSOwnedObjectHandle (object o) {
+        public static int GetJSOwnedObjectGCHandle (object o) {
             if (o == null)
                 return 0;
 
             int result;
             lock (JSOwnedObjectLock) {
-                if (IDFromJSOwnedObject.TryGetValue(o, out result))
+                if (GCHandleFromJSOwnedObject.TryGetValue(o, out result))
                     return result;
 
-                result = NextJSOwnedObjectID++;
-                IDFromJSOwnedObject[o] = result;
-                JSOwnedObjectFromID[result] = o;
+                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 id) {
+        public static void ReleaseJSOwnedObjectByHandle (int gcHandle) {
+            GCHandle handle = (GCHandle)(IntPtr)gcHandle;
             lock (JSOwnedObjectLock) {
-                if (!JSOwnedObjectFromID.TryGetValue(id, out object? o))
-                    throw new Exception($"JS-owned object with id {id} was already released");
-                IDFromJSOwnedObject.Remove(o);
-                JSOwnedObjectFromID.Remove(id);
+                GCHandleFromJSOwnedObject.Remove(handle.Target!);
+                handle.Free();
             }
         }
 
-        // The JS layer invokes this API when the JS wrapper for a delegate is invoked.
-        // In multiple places this function intentionally returns false instead of throwing
-        //  in an unexpected condition. This is done because unexpected conditions of this
-        //  type are usually caused by a JS object (i.e. a WebSocket) receiving an event
-        //  after its managed owner has been disposed - throwing in that case is unwanted.
-        public static bool TryInvokeJSOwnedDelegateByHandle (int id, JSObject? arg1) {
-            Delegate? del;
-            lock (JSOwnedObjectLock) {
-                if (!JSOwnedObjectFromID.TryGetValue(id, out object? o))
-                    return false;
-                del = (Delegate)o;
-            }
-
-            if (del == null)
-                return false;
-
-// error CS0117: 'Array' does not contain a definition for 'Empty' [/home/kate/Projects/dotnet-runtime-wasm/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System.Private.Runtime.InteropServices.JavaScript.csproj]
-#pragma warning disable CA1825
-
-            if (arg1 != null)
-                del.DynamicInvoke(new object[] { arg1 });
-            else
-                del.DynamicInvoke(new object[0]);
-
-#pragma warning restore CA1825
-
-            return true;
-        }
-
         public static int GetJSObjectId(object rawObj)
         {
-            JSObject? jsObject;
-            if (rawObj is Delegate dele)
-            {
-                lock (_weakDelegateTable)
-                {
-                    _weakDelegateTable.TryGetValue(dele, out jsObject);
-                }
-            }
-            else
-            {
-                lock (_rawToJS)
-                {
-                    _rawToJS.TryGetValue(rawObj, out jsObject);
-                }
-            }
+            JSObject? jsObject = rawObj as JSObject;
             return jsObject?.JSHandle ?? -1;
         }
 
@@ -318,7 +217,7 @@ namespace System.Runtime.InteropServices.JavaScript
                 {
                     jso.AddInFlight();
                 }
-                return jso.GetWrappedObject() ?? jso;
+                return jso;
             }
             return h.Target;
         }
@@ -452,7 +351,6 @@ namespace System.Runtime.InteropServices.JavaScript
                 finally
                 {
                     continuationObj.Dispose();
-                    FreeObject(task);
                 }
             }
         }
@@ -555,22 +453,22 @@ namespace System.Runtime.InteropServices.JavaScript
 #endif
         }
 
-        public static void SafeHandleReleaseByHandle(int jsId)
+        public static void SafeHandleReleaseByHandle(int jsHandle)
         {
 #if DEBUG_HANDLE
-            Debug.WriteLine($"SafeHandleReleaseByHandle: {jsId}");
+            Debug.WriteLine($"SafeHandleReleaseByHandle: {jsHandle}");
 #endif
             lock (_boundObjects)
             {
-                if (_boundObjects.TryGetValue(jsId, out WeakReference<JSObject>? reference))
+                if (_boundObjects.TryGetValue(jsHandle, out WeakReference<JSObject>? reference))
                 {
                     reference.TryGetTarget(out JSObject? target);
-                    Debug.Assert(target != null, $"\tSafeHandleReleaseByHandle: did not find active target {jsId}");
+                    Debug.Assert(target != null, $"\tSafeHandleReleaseByHandle: did not find active target {jsHandle}");
                     SafeHandleRelease(target);
                 }
                 else
                 {
-                    Debug.Fail($"\tSafeHandleReleaseByHandle: did not find reference for {jsId}");
+                    Debug.Fail($"\tSafeHandleReleaseByHandle: did not find reference for {jsHandle}");
                 }
             }
         }
index e97ca51..c38d7c3 100644 (file)
@@ -3,6 +3,7 @@
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <TargetFrameworks>$(NetCoreAppCurrent)-Browser</TargetFrameworks>
     <TestRuntime>true</TestRuntime>
+    <WasmXHarnessArgs>$(WasmXHarnessArgs) --engine-arg=--expose-gc</WasmXHarnessArgs>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="System\Runtime\InteropServices\JavaScript\JavaScriptTests.cs" />
index 2414a6c..da5ad21 100644 (file)
@@ -1,8 +1,8 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System.Runtime.InteropServices.JavaScript;
 using System.Collections.Generic;
+using System.Threading.Tasks;
 using Xunit;
 
 namespace System.Runtime.InteropServices.JavaScript.Tests
@@ -144,7 +144,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             ");
             Assert.Equal($"  Hello, {testStr}!  GoodMorning, {testStr}!", HelperMarshal._delegateCallResult);
         }
-        
+
         [Theory]
         [InlineData("CreateDelegateFromAnonymousMethod_VoidString")]
         [InlineData("CreateDelegateFromLambda_VoidString")]
@@ -178,7 +178,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
 
         [Theory]
         [MemberData(nameof(ArrayType_TestData))]
-        public static void InvokeFunctionAcceptingArrayTypes(Function objectPrototype, string creator, JSObject arrayType )
+        public static void InvokeFunctionAcceptingArrayTypes(Function objectPrototype, string creator, JSObject arrayType)
         {
             HelperMarshal._funcActionBufferObjectResultValue = arrayType;
             Assert.Equal(10, HelperMarshal._funcActionBufferObjectResultValue.Length);
@@ -195,5 +195,259 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             Assert.Equal(HelperMarshal._funcActionBufferObjectResultValue.Length, HelperMarshal._funcActionBufferResultLengthValue);
             Assert.Equal($"[object {creator}]", objectPrototype.Call(HelperMarshal._funcActionBufferObjectResultValue));
         }
+
+        [Fact]
+        public static void DispatchToDelegate()
+        {
+            var factory = new Function(@"return {
+                callback: null,
+                eventFactory:function(data){
+                    return {
+                        data:data
+                    };
+                },
+                fireEvent: function (evt) {
+                    this.callback(evt);
+                }
+            };");
+            var dispatcher = (JSObject)factory.Call();
+            var temp = new bool[2];
+            Action<JSObject> cb = (JSObject envt) =>
+            {
+                var data = (int)envt.GetObjectProperty("data");
+                temp[data] = true;
+            };
+            dispatcher.SetObjectProperty("callback", cb);
+            var evnt0 = dispatcher.Invoke("eventFactory", 0);
+            var evnt1 = dispatcher.Invoke("eventFactory", 1);
+            dispatcher.Invoke("fireEvent", evnt0);
+            dispatcher.Invoke("fireEvent", evnt0);
+            dispatcher.Invoke("fireEvent", evnt1);
+            Assert.True(temp[0]);
+            Assert.True(temp[1]);
+        }
+
+        [Fact]
+        public static void EventsAreNotCollected()
+        {
+            const int attempts = 100; // we fire 100 events in a loop, to try that it's GC same
+            var factory = new Function(@"return {
+                callback: null,
+                eventFactory:function(data){
+                    return {
+                        data:data
+                    };
+                },
+                fireEvent: function (evt) {
+                    this.callback(evt);
+                }
+            };");
+            var dispatcher = (JSObject)factory.Call();
+            var temp = new bool[attempts];
+            Action<JSObject> cb = (JSObject envt) =>
+            {
+                var data = (int)envt.GetObjectProperty("data");
+                temp[data] = true;
+            };
+            dispatcher.SetObjectProperty("callback", cb);
+
+            var evnt = dispatcher.Invoke("eventFactory", 0);
+            for (int i = 0; i < attempts; i++)
+            {
+                var evnti = dispatcher.Invoke("eventFactory", i);
+                dispatcher.Invoke("fireEvent", evnti);
+                dispatcher.Invoke("fireEvent", evnt);
+                Runtime.InvokeJS("if (globalThis.gc) globalThis.gc();");// needs v8 flag --expose-gc
+            }
+        }
+
+
+        [Fact]
+        public static void NullDelegate()
+        {
+            var factory = new Function("delegate", "callback", @"
+                callback(delegate);
+            ");
+
+            Delegate check = null;
+            Action<Delegate> callback = (Delegate data) =>
+            {
+                check = data;
+            };
+            factory.Call(null, null, callback);
+            Assert.Null(check);
+        }
+
+        [Fact]
+        public static async Task ResolveStringPromise()
+        {
+            var factory = new Function(@"
+                return new Promise((resolve, reject) => {
+                  setTimeout(() => {
+                    resolve('foo');
+                  }, 10);
+                });");
+
+            var promise = (Task<object>)factory.Call();
+            var value = await promise;
+
+            Assert.Equal("foo", (string)value);
+            
+        }
+
+        [Fact]
+        public static async Task ResolveJSObjectPromise()
+        {
+            for (int i = 0; i < 10; i++)
+            {
+                var factory = new Function(@"
+                return new Promise((resolve, reject) => {
+                  setTimeout(() => {
+                    resolve({foo:'bar'});
+                  }, 10);
+                });");
+
+                var promise = (Task<object>)factory.Call();
+                var value = (JSObject)await promise;
+
+                Assert.Equal("bar", value.GetObjectProperty("foo"));
+                Runtime.InvokeJS("if (globalThis.gc) globalThis.gc();");// needs v8 flag --expose-gc
+            }
+        }
+
+        [Fact]
+        public static async Task RejectPromise()
+        {
+            var factory = new Function(@"
+                return new Promise((resolve, reject) => {
+                  setTimeout(() => {
+                    reject('fail');
+                  }, 10);
+                });");
+
+            var promise = (Task<object>)factory.Call();
+
+            var ex = await Assert.ThrowsAsync<JSException>(async () => await promise);
+            Assert.Equal("fail", ex.Message);
+        }
+
+        [Fact]
+        public static async Task RejectPromiseError()
+        {
+            var factory = new Function(@"
+                return new Promise((resolve, reject) => {
+                  setTimeout(() => {
+                    reject(new Error('fail'));
+                  }, 10);
+                });");
+
+            var promise = (Task<object>)factory.Call();
+
+            var ex = await Assert.ThrowsAsync<JSException>(async () => await promise);
+            Assert.Equal("Error: fail", ex.Message);
+        }
+
+
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/56963")]
+        [Fact]
+        public static void RoundtripPromise()
+        {
+            var factory = new Function(@"
+                var dummy=new Promise((resolve, reject) => {});
+                return {
+                    dummy:dummy,
+                    check:(promise)=>{
+                        return promise===dummy ? 1:0;
+                    }
+                }");
+
+            var obj = (JSObject)factory.Call();
+            var dummy = obj.GetObjectProperty("dummy");
+            Assert.IsType<Task<object>>(dummy);
+            var check = obj.Invoke("check", dummy);
+            Assert.Equal(1, check);
+        }
+
+
+        [Fact]
+        public static async Task ResolveTask()
+        {
+            var tcs = new TaskCompletionSource<int>();
+            var factory = new Function("task", "callback", @"
+                return task.then((data)=>{
+                    callback(data);
+                })
+            ");
+
+            int check = 0;
+            Action<int> callback = (int data) =>
+            {
+                check = data;
+            };
+            Task<int> task = tcs.Task;
+            // we are testing that Task is marshaled as thenable
+            var promise = (Task<object>)factory.Call(null, task, callback);
+            tcs.SetResult(1);
+            // the result value is not applied until we await the promise
+            Assert.Equal(0, check);
+            await promise;
+            // but it's set after we do
+            Assert.Equal(1, check);
+        }
+
+        [Fact]
+        public static async Task RejectTask()
+        {
+            var tcs = new TaskCompletionSource<int>();
+            var factory = new Function("task", "callback", @"
+                return task.catch((reason)=>{
+                    callback(reason);
+                })
+            ");
+
+            string check = null;
+            Action<string> callback = (string data) =>
+            {
+                check = data;
+            };
+            var promise = (Task<object>)factory.Call(null, tcs.Task, callback);
+            Assert.Null(check);
+            tcs.SetException(new Exception("test"));
+            Assert.Null(check);
+            await promise;
+            Assert.Contains("System.Exception: test", check);
+        }
+
+        [Fact]
+        public static void NullTask()
+        {
+            var tcs = new TaskCompletionSource<int>();
+            var factory = new Function("task", "callback", @"
+                callback(task);
+            ");
+
+            Task check = Task.FromResult(1);
+            Action<Task> callback = (Task data) =>
+            {
+                check = data;
+            };
+            factory.Call(null, null, callback);
+            Assert.Null(check);
+        }
+
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/56963")]
+        [Fact]
+        public static void RoundtripTask()
+        {
+            var tcs = new TaskCompletionSource<int>();
+            var factory = new Function("dummy", @"
+                return {
+                    dummy:dummy,
+                }");
+
+            var obj = (JSObject)factory.Call(tcs.Task);
+            var dummy = obj.GetObjectProperty("dummy");
+            Assert.IsType<Task<int>>(dummy);
+        }
     }
 }
index 54a8037..2ad9116 100644 (file)
@@ -3,7 +3,6 @@
 
 using System.Collections.Generic;
 using System.Linq;
-using System.Runtime.InteropServices.JavaScript;
 using System.Threading.Tasks;
 using Xunit;
 
@@ -152,6 +151,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
                     var x = new byte[100 + attempt / 100];
                     if (attempt % 1000 == 0)
                     {
+                        Runtime.InvokeJS("if (globalThis.gc) globalThis.gc();");// needs v8 flag --expose-gc
                         GC.Collect();
                     }
                 }
@@ -224,9 +224,14 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             }
         }
 
-        private static JSObject SetupListenerTest (string prefix) {
-            Runtime.InvokeJS($"globalThis.{prefix} = {{" + @"
+        private static JSObject SetupListenerTest () {
+            var factory = new Function(@"return {
     listeners: [],
+    eventFactory:function(data){
+        return {
+            data:data
+        };
+    },
     addEventListener: function (name, listener, options) {
         if (name === 'throwError')
             throw new Error('throwError throwing');
@@ -274,28 +279,55 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
     },
 };
 ");
-            return (JSObject)Runtime.GetGlobalObject(prefix);
+            return (JSObject)factory.Call();
         }
 
         [Fact]
         public static void AddEventListenerWorks () {
-            var temp = new bool[1];
-            var obj = SetupListenerTest("addEventListenerWorks");
-            obj.AddEventListener("test", () => {
-                temp[0] = true;
+            var temp = new bool[2];
+            var obj = SetupListenerTest();
+            obj.AddEventListener("test", (JSObject envt) => {
+                var data = (int)envt.GetObjectProperty("data");
+                temp[data] = true;
             });
-            obj.Invoke("fireEvent", "test");
+            var evnt0 = obj.Invoke("eventFactory", 0);
+            var evnt1 = obj.Invoke("eventFactory", 1);
+            obj.Invoke("fireEvent", "test", evnt0);
+            obj.Invoke("fireEvent", "test", evnt1);
             Assert.True(temp[0]);
+            Assert.True(temp[1]);
+        }
+
+        [Fact]
+        public static void EventsAreNotCollected()
+        {
+            const int attempts = 100; // we fire 100 events in a loop, to try that it's GC same
+            var temp = new bool[100];
+            var obj = SetupListenerTest();
+            obj.AddEventListener("test", (JSObject envt) => {
+                var data = (int)envt.GetObjectProperty("data");
+                temp[data] = true;
+            });
+            var evnt = obj.Invoke("eventFactory", 0);
+            for (int i = 0; i < attempts; i++)
+            {
+                var evnti = obj.Invoke("eventFactory", 0);
+                obj.Invoke("fireEvent", "test", evnt);
+                obj.Invoke("fireEvent", "test", evnti);
+                // we are trying to test that managed side doesn't lose strong reference to evnt instance
+                Runtime.InvokeJS("if (globalThis.gc) globalThis.gc();");// needs v8 flag --expose-gc
+                GC.Collect();
+            }
         }
 
         [Fact]
         public static void AddEventListenerPassesOptions () {
             var log = new List<string>();
-            var obj = SetupListenerTest("addEventListenerPassesOptions");
-            obj.AddEventListener("test", () => {
+            var obj = SetupListenerTest();
+            obj.AddEventListener("test", (JSObject envt) => {
                 log.Add("Capture");
             }, new JSObject.EventListenerOptions { Capture = true });
-            obj.AddEventListener("test", () => {
+            obj.AddEventListener("test", (JSObject envt) => {
                 log.Add("Non-capture");
             }, new JSObject.EventListenerOptions { Capture = false });
             obj.Invoke("fireEvent", "test");
@@ -305,8 +337,8 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
 
         [Fact]
         public static void AddEventListenerForwardsExceptions () {
-            var obj = SetupListenerTest("addEventListenerForwardsExceptions");
-            obj.AddEventListener("test", () => {
+            var obj = SetupListenerTest();
+            obj.AddEventListener("test", (JSObject envt) => {
                 throw new Exception("Test exception");
             });
             var exc = Assert.Throws<JSException>(() => {
@@ -315,7 +347,7 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
             Assert.Contains("Test exception", exc.Message);
 
             exc = Assert.Throws<JSException>(() => {
-                obj.AddEventListener("throwError", () => {
+                obj.AddEventListener("throwError", (JSObject envt) => {
                     throw new Exception("Should not be called");
                 });
             });
@@ -325,8 +357,8 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
 
         [Fact]
         public static void RemovedEventListenerIsNotCalled () {
-            var obj = SetupListenerTest("removedEventListenerIsNotCalled");
-            Action del = () => {
+            var obj = SetupListenerTest();
+            Action<JSObject> del = (JSObject envt) => {
                 throw new Exception("Should not be called");
             };
             obj.AddEventListener("test", del);
@@ -341,8 +373,8 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         [Fact]
         public static void RegisterSameEventListener () {
             var counter = new int[1];
-            var obj = SetupListenerTest("registerSameDelegateTwice");
-            Action del = () => {
+            var obj = SetupListenerTest();
+            Action<JSObject> del = (JSObject envt) => {
                 counter[0]++;
             };
 
@@ -367,8 +399,8 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
 
         [Fact]
         public static void UseAddEventListenerResultToRemove () {
-            var obj = SetupListenerTest("useAddEventListenerResultToRemove");
-            Action del = () => {
+            var obj = SetupListenerTest();
+            Action<JSObject> del = (JSObject envt) => {
                 throw new Exception("Should not be called");
             };
             var handle = obj.AddEventListener("test", del);
@@ -383,9 +415,9 @@ namespace System.Runtime.InteropServices.JavaScript.Tests
         [Fact]
         public static void RegisterSameEventListenerToMultipleSources () {
             var counter = new int[1];
-            var a = SetupListenerTest("registerSameEventListenerToMultipleSourcesA");
-            var b = SetupListenerTest("registerSameEventListenerToMultipleSourcesB");
-            Action del = () => {
+            var a = SetupListenerTest();
+            var b = SetupListenerTest();
+            Action<JSObject> del = (JSObject envt) => {
                 counter[0]++;
             };
 
index 6796330..e4e7c88 100644 (file)
@@ -23,7 +23,13 @@ function proxyMethod (prefix, func, asJson) {
                if(payload === undefined) payload = 'undefined';
                else if(payload === null) payload = 'null';
                else if(typeof payload === 'function') payload = payload.toString();
-               else if(typeof payload !== 'string') payload = JSON.stringify(payload);
+               else if(typeof payload !== 'string') {
+                       try{
+                               payload = JSON.stringify(payload);
+                       }catch(e){
+                               payload = payload.toString();
+                       }
+               }
 
                if (asJson) {
                        func (JSON.stringify({
index 85b8944..4bd589b 100644 (file)
@@ -6,7 +6,7 @@ var BindingSupportLib = {
        $BINDING: {
                BINDING_ASM: "[System.Private.Runtime.InteropServices.JavaScript]System.Runtime.InteropServices.JavaScript.Runtime",
                mono_wasm_object_registry: [],
-               mono_wasm_ref_counter: 0,
+               mono_wasm_ref_counter: 1,
                mono_wasm_free_list: [],
                mono_wasm_owned_objects_frames: [],
                mono_wasm_owned_objects_LMF: [],
@@ -123,18 +123,18 @@ var BindingSupportLib = {
 
                        this._bind_js_obj = bind_runtime_method ("BindJSObject", "iii");
                        this._bind_core_clr_obj = bind_runtime_method ("BindCoreCLRObject", "ii");
-                       this._bind_existing_obj = bind_runtime_method ("BindExistingObject", "mi");
-                       this._unbind_raw_obj_and_free = bind_runtime_method ("UnBindRawJSObjectAndFree", "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._is_simple_array = bind_runtime_method ("IsSimpleArray", "m");
                        this.setup_js_cont = get_method ("SetupJSContinuation");
 
-                       this.create_tcs = get_method ("CreateTaskSource");
-                       this.set_tcs_result = get_method ("SetTaskSourceResult");
-                       this.set_tcs_failure = get_method ("SetTaskSourceFailure");
-                       this.tcs_get_task_and_bind = get_method ("GetTaskAndBind");
+                       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.get_call_sig = get_method ("GetCallSignature");
 
                        this._object_to_string = bind_runtime_method ("ObjectToString", "m");
@@ -142,12 +142,9 @@ var BindingSupportLib = {
                        this.create_date_time = get_method ("CreateDateTime");
                        this.create_uri = get_method ("CreateUri");
 
-                       this.safehandle_addref = get_method ("SafeHandleAddRef");
-                       this.safehandle_release = get_method ("SafeHandleRelease");
                        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.try_invoke_js_owned_delegate_by_handle = bind_runtime_method ("TryInvokeJSOwnedDelegateByHandle", "io");
 
                        this._are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function");
 
@@ -162,49 +159,184 @@ var BindingSupportLib = {
                        this._js_owned_object_registry = new FinalizationRegistry(this._js_owned_object_finalized.bind(this));
                },
 
-               _get_weak_delegate_from_handle: function (id) {
-                       var result = null;
+               _js_owned_object_finalized: function (gc_handle) {
+                       // The JS object associated with this gc_handle has been collected by the JS GC.
+                       // As such, it's not possible for this gc_handle to be invoked by JS anymore, so
+                       //  we can release the tracking weakref (it's null now, by definition),
+                       //  and tell the C# side to stop holding a reference to the managed object.
+                       this._js_owned_object_table.delete(gc_handle);
+                       this.release_js_owned_object_by_handle(gc_handle);
+               },
+
+               _lookup_js_owned_object: function (gc_handle) {
+                       if (!gc_handle)
+                               return null;
+                       var wr = this._js_owned_object_table.get(gc_handle);
+                       if (wr) {
+                               return wr.deref();
+                               // TODO: could this be null before _js_owned_object_finalized was called ?
+                               // TODO: are there race condition consequences ?
+                       }
+                       return null;
+               },
+
+               _wrap_js_thenable_as_task: function (thenable) {
+                       this.bindings_lazy_init ();
+                       if (!thenable)
+                               return null;
+
+                       // hold strong JS reference to thenable while in flight
+                       // ideally, this should be hold alive by lifespan of the resulting C# Task, but this is good cheap aproximation
+                       var thenable_js_handle = BINDING.mono_wasm_get_js_handle(thenable);
+
+                       // Note that we do not implement promise/task roundtrip. 
+                       // With more complexity we could recover original instance when this Task is marshaled back to JS.
+                       // TODO optimization: return the tcs.Task on this same call instead of get_tcs_task
+                       const tcs_gc_handle = this.create_tcs();
+                       thenable.then ((result) => {
+                               this.set_tcs_result(tcs_gc_handle, result);
+                               // let go of the thenable reference
+                               this._mono_wasm_release_js_handle(thenable_js_handle);
+                       }, (reason) => {
+                               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);
+                       });
+
+                       // collect the TaskCompletionSource with its Task after js doesn't hold the thenable anymore
+                       this._js_owned_object_registry.register(thenable, tcs_gc_handle);
+
+                       // returns raw pointer to tcs.Task
+                       return this.get_tcs_task(tcs_gc_handle);
+               },
+
+               _unbox_task_root_as_promise: function (root) {
+                       this.bindings_lazy_init ();
+                       if (root.value === 0)
+                               return null;
+
+                       if (!this._are_promises_supported)
+                               throw new Error ("Promises are not supported thus 'System.Threading.Tasks.Task' can not work in this context.");
+
+                       // get strong reference to Task
+                       const gc_handle = this._get_js_owned_object_gc_handle(root.value);
+
+                       // see if we have js owned instance for this gc_handle already
+                       var result = this._lookup_js_owned_object(gc_handle);
 
-                       // Look up this handle in the weak delegate table, and if we find a matching weakref,
-                       //  deref it to try and get a JS function. This function may have been collected.
-                       if (this._js_owned_object_table.has(id)) {
-                               var wr = this._js_owned_object_table.get(id);
-                               result = wr.deref();
-                               // Note that we could check for a null result (i.e. function was GC'd) here and
-                               //  opt to abort since resurrecting a given ID probably shouldn't happen.
-                               // However, this design makes resurrecting an ID harmless, so there's not any
-                               //  value in doing that (and we would then need to differentiate 'new' vs 'get')
-                               // Tracking whether an ID is being resurrected also would require us to keep track
-                               //  of every ID that has ever been used, which will harm performance long-term.
-                       }
-
-                       // If the function for this handle was already collected (or was never created),
-                       //  we create a new function that will invoke the corresponding C# delegate when
-                       //  called, and store it into our weak mapping (so we can look it up again by id)
-                       //  and register it with the finalization registry so that the C# side can release
-                       //  the associated object references
+                       // If the promise for this gc_handle was already collected (or was never created)
                        if (!result) {
-                               result = (arg1) => {
-                                       if (!this.try_invoke_js_owned_delegate_by_handle(id, arg1))
-                                               // Because lifetime is managed by JavaScript, it *is* an error for this 
-                                               //  invocation to ever fail. If we have a JS wrapper for an ID, there
-                                               //  should be no way for the managed delegate to have disappeared.
-                                               throw new Error(`JS-owned delegate invocation failed for id ${id}`);
-                               };
 
-                               this._js_owned_object_table.set(id, new WeakRef(result));
-                               this._js_owned_object_registry.register(result, id);
+                               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
+                                       };
+                               });
+
+                               // register C# side of the continuation
+                               this.call_method (this.setup_js_cont, null, "mo", [ 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);
+
+                               // register for instance reuse
+                               this._js_owned_object_table.set(gc_handle, new WeakRef(result));
                        }
+
                        return result;
                },
 
-               _js_owned_object_finalized: function (id) {
-                       // The JS function associated with this ID has been collected by the JS GC.
-                       // As such, it's not possible for this ID to be invoked by JS anymore, so
-                       //  we can release the tracking weakref (it's null now, by definition),
-                       //  and tell the C# side to stop holding a reference to the managed delegate.
-                       this._js_owned_object_table.delete(id);
-                       this.release_js_owned_object_by_handle(id);
+               _unbox_ref_type_root_as_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)
+                               return this.mono_wasm_get_jsobj_from_js_handle(js_handle);
+                       // otherwise this is C# only object
+       
+                       // get strong reference to Object
+                       const gc_handle = this._get_js_owned_object_gc_handle(root.value);
+
+                       // see if we have js owned instance for this gc_handle already
+                       var result = this._lookup_js_owned_object(gc_handle);
+
+                       // 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
+                               }
+       
+                               // 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));
+                       }
+
+                       return result;
+               },
+
+               _wrap_delegate_root_as_function: function (root) {
+                       this.bindings_lazy_init ();
+                       if (root.value === 0)
+                               return null;
+
+                       // get strong reference to the Delegate
+                       const gc_handle = this._get_js_owned_object_gc_handle(root.value);
+                       return this._wrap_delegate_gc_handle_as_function(gc_handle);
+               },
+
+               _wrap_delegate_gc_handle_as_function: function (gc_handle) {
+                       this.bindings_lazy_init ();
+
+                       // see if we have js owned instance for this gc_handle already
+                       var result = this._lookup_js_owned_object(gc_handle);
+
+                       // If the function for this gc_handle was already collected (or was never created)
+                       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));
+                                       try {
+                                               return BINDING.call_method (result.__mono_delegate_invoke__, delegateRoot.value, result.__mono_delegate_invoke_sig__, arguments);
+                                       } finally {
+                                               delegateRoot.release();
+                                       }
+                               };
+
+                               // bind the method
+                               const delegateRoot = MONO.mono_wasm_new_root (BINDING.wasm_get_raw_obj(gc_handle, false));
+                               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__){
+                                                       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);
+                                       }
+                               } 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);
+
+                               // register for instance reuse
+                               this._js_owned_object_table.set(gc_handle, new WeakRef(result));
+                       }
+
+                       return result;
                },
 
                // Ensures the string is already interned on both the managed and JavaScript sides,
@@ -283,7 +415,7 @@ var BindingSupportLib = {
                        else if (typeof (string) === "symbol")
                                return this.js_string_to_mono_string_interned (string);
                        else if (typeof (string) !== "string")
-                               throw new Error ("Expected string argument");
+                               throw new Error ("Expected string argument, got "+ typeof (string));
 
                        // Always use an interned pointer for empty strings
                        if (string.length === 0)
@@ -325,7 +457,7 @@ var BindingSupportLib = {
 
                get_js_obj: function (js_handle) {
                        if (js_handle > 0)
-                               return this.mono_wasm_require_handle(js_handle);
+                               return this.mono_wasm_get_jsobj_from_js_handle(js_handle);
                        return null;
                },
 
@@ -400,6 +532,7 @@ var BindingSupportLib = {
                        }
                },
 
+               // this is only used from Blazor
                unbox_mono_obj: function (mono_obj) {
                        if (mono_obj === 0)
                                return undefined;
@@ -412,40 +545,10 @@ var BindingSupportLib = {
                        }
                },
 
-               _unbox_delegate_root: function (root) {
-                       var obj = this.extract_js_obj_root (root);
-                       obj.__mono_delegate_alive__ = true;
-                       // FIXME: Should we root the object as long as this function has not been GCd?
-                       return function () {
-                               // TODO: Just use Function.bind
-                               return BINDING.invoke_delegate (obj, arguments);
-                       };
-               },
-
-               _unbox_task_root: function (root) {
-                       if (!this._are_promises_supported)
-                               throw new Error ("Promises are not supported thus 'System.Threading.Tasks.Task' can not work in this context.");
-
-                       var obj = this.extract_js_obj_root (root);
-                       var cont_obj = null;
-                       var promise = new Promise (function (resolve, reject) {
-                               cont_obj = {
-                                       resolve: resolve,
-                                       reject: reject
-                               };
-                       });
-
-                       // FIXME: Lifetime management/pinning?
-                       this.call_method (this.setup_js_cont, null, "mo", [ root.value, cont_obj ]);
-                       obj.__mono_js_cont__ = cont_obj.__mono_gchandle__;
-                       cont_obj.__mono_js_task__ = obj.__mono_gchandle__;
-                       return promise;
-               },
-
                _unbox_safehandle_root: function (root) {
                        var addRef = true;
                        var js_handle = this.call_method(this.safehandle_get_handle, null, "mi", [ root.value, addRef ]);
-                       var requiredObject = BINDING.mono_wasm_require_handle (js_handle);
+                       var js_obj = BINDING.mono_wasm_get_jsobj_from_js_handle (js_handle);
                        if (addRef)
                        {
                                if (typeof this.mono_wasm_owned_objects_LMF === "undefined")
@@ -453,7 +556,7 @@ var BindingSupportLib = {
 
                                this.mono_wasm_owned_objects_LMF.push(js_handle);
                        }
-                       return requiredObject;
+                       return js_obj;
                },
 
                _unbox_mono_obj_root_with_known_nonprimitive_type: function (root, type) {
@@ -472,11 +575,11 @@ var BindingSupportLib = {
                                case 4: //vts
                                        throw new Error ("no idea on how to unbox value types");
                                case 5: // delegate
-                                       return this._unbox_delegate_root (root);
+                                       return this._wrap_delegate_root_as_function (root);
                                case 6: // Task
-                                       return this._unbox_task_root (root);
+                                       return this._unbox_task_root_as_promise (root);
                                case 7: // ref type
-                                       return this.extract_js_obj_root (root);
+                                       return this._unbox_ref_type_root_as_object (root);
                                case 10: // arrays
                                case 11:
                                case 12:
@@ -528,24 +631,6 @@ var BindingSupportLib = {
                        }
                },
 
-               create_task_completion_source: function () {
-                       return this.call_method (this.create_tcs, null, "i", [ -1 ]);
-               },
-
-               set_task_result: function (tcs, result) {
-                       tcs.is_mono_tcs_result_set = true;
-                       this.call_method (this.set_tcs_result, null, "oo", [ tcs, result ]);
-                       if (tcs.is_mono_tcs_task_bound)
-                               this.free_task_completion_source(tcs);
-               },
-
-               set_task_failure: function (tcs, reason) {
-                       tcs.is_mono_tcs_result_set = true;
-                       this.call_method (this.set_tcs_failure, null, "os", [ tcs, reason.toString () ]);
-                       if (tcs.is_mono_tcs_task_bound)
-                               this.free_task_completion_source(tcs);
-               },
-
                // https://github.com/Planeshifter/emscripten-examples/blob/master/01_PassingArrays/sum_post.js
                js_typedarray_to_heap: function(typedArray){
                        var numBytes = typedArray.length * typedArray.BYTES_PER_ELEMENT;
@@ -614,18 +699,7 @@ var BindingSupportLib = {
                                case typeof js_obj === "boolean":
                                        return this._box_js_bool (js_obj);
                                case isThenable() === true:
-                                       var the_task = this.try_extract_mono_obj (js_obj);
-                                       if (the_task)
-                                               return the_task;
-                                       // FIXME: We need to root tcs for an appropriate timespan, at least until the Task
-                                       //  is resolved
-                                       var tcs = this.create_task_completion_source ();
-                                       js_obj.then (function (result) {
-                                               BINDING.set_task_result (tcs, result);
-                                       }, function (reason) {
-                                               BINDING.set_task_failure (tcs, reason);
-                                       })
-                                       return this.get_task_and_bind (tcs, js_obj);
+                                       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() ]);
@@ -633,6 +707,7 @@ var BindingSupportLib = {
                                        return this.extract_mono_obj (js_obj);
                        }
                },
+
                js_to_mono_uri: function (js_obj) {
                        this.bindings_lazy_init ();
 
@@ -647,6 +722,7 @@ var BindingSupportLib = {
                                        return this.extract_mono_obj (js_obj);
                        }
                },
+               
                has_backing_array_buffer: function (js_obj) {
                        return typeof SharedArrayBuffer !== 'undefined'
                                ? js_obj.buffer instanceof ArrayBuffer || js_obj.buffer instanceof SharedArrayBuffer
@@ -802,6 +878,7 @@ var BindingSupportLib = {
                        this.typedarray_copy_from(newTypedArray, pinned_array, begin, end, bytes_per_element);
                        return newTypedArray;
                },
+
                js_to_mono_enum: function (js_obj, method, parmIdx) {
                        this.bindings_lazy_init ();
 
@@ -810,40 +887,31 @@ var BindingSupportLib = {
 
                        return js_obj | 0;
                },
-               wasm_binding_obj_new: function (js_obj_id, ownsHandle, type)
-               {
-                       return this._bind_js_obj (js_obj_id, ownsHandle, type);
-               },
-               wasm_bind_existing: function (mono_obj, js_id)
-               {
-                       return this._bind_existing_obj (mono_obj, js_id);
-               },
-
-               wasm_bind_core_clr_obj: function (js_id, gc_handle)
-               {
-                       return this._bind_core_clr_obj (js_id, gc_handle);
-               },
 
-               wasm_get_js_id: function (mono_obj)
+               wasm_bind_core_clr_obj: function (js_handle, gc_handle)
                {
-                       return this._get_js_id (mono_obj);
+                       return this._bind_core_clr_obj (js_handle, gc_handle);
                },
 
-               // when should_add_in_flight === true, the JSObject would be temporarily hold by Normal GCHandle, so that it would not get collected during transition to the managed stack.
-               // its InFlight handle would be freed when the instance arrives to managed side via Interop.Runtime.ReleaseInFlight
-               wasm_get_raw_obj: function (gchandle, should_add_in_flight)
+               // 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)
                {
-                       if(!gchandle){
+                       if(!gc_handle){
                                return 0;
                        }
 
-                       return this._get_raw_mono_obj (gchandle, should_add_in_flight ? 1 : 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" || typeof js_obj.__mono_gchandle__ === "undefined")
+                       if (js_obj === null || typeof js_obj === "undefined")
                                return 0;
-                       return this.wasm_get_raw_obj (js_obj.__mono_gchandle__, true);
+                       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;
                },
 
                mono_method_get_call_signature: function(method, mono_obj) {
@@ -857,49 +925,23 @@ var BindingSupportLib = {
                        }
                },
 
-               get_task_and_bind: function (tcs, js_obj) {
-                       var gc_handle = this.mono_wasm_free_list.length ? this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++;
-                       var task_gchandle = this.call_method (this.tcs_get_task_and_bind, null, "oi", [ tcs, gc_handle + 1 ]);
-                       js_obj.__mono_gchandle__ = task_gchandle;
-                       this.mono_wasm_object_registry[gc_handle] = js_obj;
-                       this.free_task_completion_source(tcs);
-                       tcs.is_mono_tcs_task_bound = true;
-                       js_obj.__mono_bound_tcs__ = tcs.__mono_gchandle__;
-                       tcs.__mono_bound_task__ = js_obj.__mono_gchandle__;
-                       return this.wasm_get_raw_obj (js_obj.__mono_gchandle__, true);
-               },
-
-               free_task_completion_source: function (tcs) {
-                       if (tcs.is_mono_tcs_result_set)
-                       {
-                               this._unbind_raw_obj_and_free (tcs.__mono_gchandle__);
-                       }
-                       if (tcs.__mono_bound_task__)
-                       {
-                               this._unbind_raw_obj_and_free (tcs.__mono_bound_task__);
-                       }
-               },
-
                extract_mono_obj: function (js_obj) {
                        if (js_obj === null || typeof js_obj === "undefined")
                                return 0;
 
                        var result = null;
-                       var gc_handle = js_obj.__mono_gchandle__;
-                       if (gc_handle) {
-                               result = this.wasm_get_raw_obj (gc_handle, true);
+                       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) {
-
-                                       if (typeof js_obj.__mono_delegate_alive__ !== "undefined") {
-                                               console.log("The delegate target that is being invoked is no longer available.  Please check if it has been prematurely GC'd.");
-                                               return null;
-                                       }
-               
-                                       delete js_obj.__mono_gchandle__;
-                                       delete js_obj.is_mono_bridged_obj;
+                                       delete js_obj.__mono_gc_handle__;
                                }
                        }
 
@@ -911,35 +953,6 @@ var BindingSupportLib = {
                        return result;
                },
 
-               extract_js_obj: function (mono_obj) {
-                       if (mono_obj === 0)
-                               return null;
-                       var root = MONO.mono_wasm_new_root (mono_obj);
-                       try {
-                               return this.extract_js_obj_root (root);
-                       } finally {
-                               root.release();
-                       }
-               },
-
-               extract_js_obj_root: function (root) {
-                       if (root.value === 0)
-                               return null;
-
-                       var js_id = this.wasm_get_js_id (root.value);
-                       if (js_id > 0)
-                               return this.mono_wasm_require_handle(js_id);
-
-                       var gcHandle = this.mono_wasm_free_list.length ? this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++;
-                       var js_obj = {
-                               __mono_gchandle__: this.wasm_bind_existing(root.value, gcHandle + 1),
-                               is_mono_bridged_obj: true
-                       };
-
-                       this.mono_wasm_object_registry[gcHandle] = js_obj;
-                       return js_obj;
-               },
-
                _create_named_function: function (name, argumentNames, body, closure) {
                        var result = null, keys = null, closureArgumentList = null, closureArgumentNames = null;
 
@@ -1359,7 +1372,6 @@ var BindingSupportLib = {
 
                                buffer = converter.compiled_variadic_function (scratchBuffer, argsRootBuffer, method, args);
                        }
-
                        return this._call_method_with_converted_args (method, this_arg, converter, buffer, is_result_marshaled, argsRootBuffer);
                },
 
@@ -1536,37 +1548,6 @@ var BindingSupportLib = {
                        return this._create_named_function(displayName, argumentNames, bodyJs, closure);
                },
 
-               invoke_delegate: function (delegate_obj, js_args) {
-                       this.bindings_lazy_init ();
-
-                       // Check to make sure the delegate is still alive on the CLR side of things.
-                       if (typeof delegate_obj.__mono_delegate_alive__ !== "undefined") {
-                               if (!delegate_obj.__mono_delegate_alive__) {
-                                       // HACK: It is possible (though unlikely) for a delegate to be invoked after it's been collected
-                                       //  if it's being used as a JavaScript event handler and the host environment decides to fire events
-                                       //  at a point where we've already disposed of the object the event handler is attached to.
-                                       // As such, we log here instead of throwing an error. We may want to not log at all...
-                                       console.log("The delegate target that is being invoked is no longer available.  Please check if it has been prematurely GC'd.");
-                                       return;
-                               }
-                       }
-
-                       var delegateRoot = MONO.mono_wasm_new_root (this.extract_mono_obj (delegate_obj));
-                       try {
-                               if (typeof delegate_obj.__mono_delegate_invoke__ === "undefined")
-                                       delegate_obj.__mono_delegate_invoke__ = this.mono_wasm_get_delegate_invoke(delegateRoot.value);
-                               if (!delegate_obj.__mono_delegate_invoke__)
-                                       throw new Error("System.Delegate Invoke method can not be resolved.");
-
-                               if (typeof delegate_obj.__mono_delegate_invoke_sig__ === "undefined")
-                                       delegate_obj.__mono_delegate_invoke_sig__ = Module.mono_method_get_call_signature (delegate_obj.__mono_delegate_invoke__, delegateRoot.value);
-
-                               return this.call_method (delegate_obj.__mono_delegate_invoke__, delegateRoot.value, delegate_obj.__mono_delegate_invoke_sig__, js_args);
-                       } finally {
-                               delegateRoot.release();
-                       }
-               },
-
                resolve_method_fqn: function (fqn) {
                        this.bindings_lazy_init ();
 
@@ -1660,79 +1641,54 @@ var BindingSupportLib = {
                // 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_gchandle__;
+                               gc_handle = js_obj.__mono_gc_handle__;
 
                                if (typeof gc_handle === "undefined") {
-                                       var handle = this.mono_wasm_free_list.length ?
-                                                               this.mono_wasm_free_list.pop() : this.mono_wasm_ref_counter++;
-                                       js_obj.__mono_jshandle__ = handle;
                                        // Obtain the JS -> C# type mapping.
                                        var wasm_type = js_obj[Symbol.for("wasm type")];
-                                       js_obj.__owns_handle__ = true;
-                                       gc_handle = js_obj.__mono_gchandle__ = this.wasm_binding_obj_new(handle + 1, js_obj.__owns_handle__, typeof wasm_type === "undefined" ? -1 : wasm_type);
-                                       this.mono_wasm_object_registry[handle] = js_obj;
-                                       // as this instance was just created, it was already created with Inflight strong GCHandle, so we do not have to do it again
+
+                                       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 GCHandle before passing it to managed
+                       // 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_require_handle: function(handle) {
-                       if (handle > 0)
-                               return this.mono_wasm_object_registry[handle - 1];
+               mono_wasm_get_jsobj_from_js_handle: function(js_handle) {
+                       if (js_handle > 0)
+                               return this.mono_wasm_object_registry[js_handle];
                        return null;
                },
-               mono_wasm_unregister_obj: function(js_id) {
-                       var obj = this.mono_wasm_object_registry[js_id - 1];
-                       if (typeof obj  !== "undefined" && obj !== null) {
-                               // if this is the global object then do not
-                               // unregister it.
-                               if (globalThis === obj)
-                                       return obj;
-
-                               var gc_handle = obj.__mono_gchandle__;
-                               if (typeof gc_handle  !== "undefined") {
-
-                                       obj.__mono_gchandle__ = undefined;
-                                       obj.__mono_jshandle__ = undefined;
-
-                                       // If we are unregistering a delegate then mark it as not being alive
-                                       //  so that attempts will not be made to invoke it even if a JS-side
-                                       //  reference to it remains (registered as an event handler, etc)
-                                       if (typeof obj.__mono_delegate_alive__ !== "undefined")
-                                               obj.__mono_delegate_alive__ = false;
-
-                                       this.mono_wasm_object_registry[js_id - 1] = undefined;
-                                       this.mono_wasm_free_list.push(js_id - 1);
-                               }
+               mono_wasm_get_js_handle: function(js_obj) {
+                       if(js_obj.__mono_js_handle__){
+                               return js_obj.__mono_js_handle__;
                        }
-                       return obj;
-               },
-               mono_wasm_free_handle: function(handle) {
-                       this.mono_wasm_unregister_obj(handle);
+                       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;
+                       return js_handle;
                },
-               mono_wasm_free_raw_object: function(js_id) {
-                       var obj = this.mono_wasm_object_registry[js_id - 1];
+               _mono_wasm_release_js_handle: function(js_handle) {
+                       var obj = BINDING.mono_wasm_object_registry[js_handle];
                        if (typeof obj  !== "undefined" && obj !== null) {
                                // if this is the global object then do not
                                // unregister it.
                                if (globalThis === obj)
                                        return obj;
 
-                               var gc_handle = obj.__mono_gchandle__;
-                               if (typeof gc_handle  !== "undefined") {
-
-                                       obj.__mono_gchandle__ = undefined;
-                                       obj.__mono_jshandle__ = undefined;
-
-                                       this.mono_wasm_object_registry[js_id - 1] = undefined;
-                                       this.mono_wasm_free_list.push(js_id - 1);
+                               if (typeof obj.__mono_js_handle__  !== "undefined") {
+                                       obj.__mono_gc_handle__ = undefined;
+                                       obj.__mono_js_handle__ = undefined;
                                }
+
+                               BINDING.mono_wasm_object_registry[js_handle] = undefined;
+                               BINDING.mono_wasm_free_list.push(js_handle);
                        }
                        return obj;
                },
@@ -1767,22 +1723,21 @@ var BindingSupportLib = {
                        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);
                try {
                        BINDING.bindings_lazy_init ();
 
-                       var obj = BINDING.get_js_obj (js_handle);
-                       if (!obj) {
+                       var js_name = BINDING.conv_string (nameRoot.value);
+                       if (!js_name || (typeof(js_name) !== "string")) {
                                setValue (is_exception, 1, "i32");
-                               return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'");
+                               return BINDING.js_string_to_mono_string ("ERR12: Invalid method name object '" + nameRoot.value + "'");
                        }
 
-                       var js_name = BINDING.conv_string (nameRoot.value);
-                       if (!js_name || (typeof(js_name) !== "string")) {
+                       var obj = BINDING.get_js_obj (js_handle);
+                       if (!obj) {
                                setValue (is_exception, 1, "i32");
-                               return BINDING.js_string_to_mono_string ("Invalid method name object '" + nameRoot.value + "'");
+                               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);
@@ -1813,18 +1768,18 @@ var BindingSupportLib = {
 
                var nameRoot = MONO.mono_wasm_new_root (property_name);
                try {
-                       var obj = BINDING.mono_wasm_require_handle (js_handle);
-                       if (!obj) {
-                               setValue (is_exception, 1, "i32");
-                               return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'");
-                       }
-
                        var js_name = BINDING.conv_string (nameRoot.value);
                        if (!js_name) {
                                setValue (is_exception, 1, "i32");
                                return BINDING.js_string_to_mono_string ("Invalid property name object '" + nameRoot.value + "'");
                        }
 
+                       var obj = BINDING.mono_wasm_get_jsobj_from_js_handle (js_handle);
+                       if (!obj) {
+                               setValue (is_exception, 1, "i32");
+                               return BINDING.js_string_to_mono_string ("ERR01: Invalid JS object handle '" + js_handle + "' while geting '"+js_name+"'");
+                       }
+
                        var res;
                        try {
                                var m = obj [js_name];
@@ -1847,42 +1802,42 @@ var BindingSupportLib = {
                var valueRoot = MONO.mono_wasm_new_root (value), nameRoot = MONO.mono_wasm_new_root (property_name);
                try {
                        BINDING.bindings_lazy_init ();
-                       var requireObject = BINDING.mono_wasm_require_handle (js_handle);
-                       if (!requireObject) {
-                               setValue (is_exception, 1, "i32");
-                               return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'");
-                       }
-
                        var property = BINDING.conv_string (nameRoot.value);
                        if (!property) {
                                setValue (is_exception, 1, "i32");
                                return BINDING.js_string_to_mono_string ("Invalid property name object '" + property_name + "'");
                        }
 
+                       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 ("ERR02: Invalid JS object handle '" + js_handle + "' while setting '"+property+"'");
+                       }
+
                        var result = false;
 
                        var js_value = BINDING._unbox_mono_obj_root(valueRoot);
                        BINDING.mono_wasm_save_LMF();
 
                        if (createIfNotExist) {
-                               requireObject[property] = js_value;
+                               js_obj[property] = js_value;
                                result = true;
                        }
                        else {
                                result = false;
                                if (!createIfNotExist)
                                {
-                                       if (!requireObject.hasOwnProperty(property))
+                                       if (!js_obj.hasOwnProperty(property))
                                                return false;
                                }
                                if (hasOwnProperty === true) {
-                                       if (requireObject.hasOwnProperty(property)) {
-                                               requireObject[property] = js_value;
+                                       if (js_obj.hasOwnProperty(property)) {
+                                               js_obj[property] = js_value;
                                                result = true;
                                        }
                                }
                                else {
-                                       requireObject[property] = js_value;
+                                       js_obj[property] = js_value;
                                        result = true;
                                }
 
@@ -1897,10 +1852,10 @@ var BindingSupportLib = {
        mono_wasm_get_by_index: function(js_handle, property_index, is_exception) {
                BINDING.bindings_lazy_init ();
 
-               var obj = BINDING.mono_wasm_require_handle (js_handle);
+               var obj = BINDING.mono_wasm_get_jsobj_from_js_handle (js_handle);
                if (!obj) {
                        setValue (is_exception, 1, "i32");
-                       return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'");
+                       return BINDING.js_string_to_mono_string ("ERR03: Invalid JS object handle '" + js_handle + "' while getting ["+property_index+"]");
                }
 
                try {
@@ -1919,10 +1874,10 @@ var BindingSupportLib = {
                try {
                        BINDING.bindings_lazy_init ();
 
-                       var obj = BINDING.mono_wasm_require_handle (js_handle);
+                       var obj = BINDING.mono_wasm_get_jsobj_from_js_handle (js_handle);
                        if (!obj) {
                                setValue (is_exception, 1, "i32");
-                               return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'");
+                               return BINDING.js_string_to_mono_string ("ERR04: Invalid JS object handle '" + js_handle + "' while setting ["+property_index+"]");
                        }
 
                        var js_value = BINDING._unbox_mono_obj_root(valueRoot);
@@ -1971,39 +1926,20 @@ var BindingSupportLib = {
        },
        mono_wasm_release_handle: function(js_handle, is_exception) {
                BINDING.bindings_lazy_init ();
-
-               BINDING.mono_wasm_free_handle(js_handle);
-       },
-       mono_wasm_release_object: function(js_handle, is_exception) {
-               BINDING.bindings_lazy_init ();
-
-               BINDING.mono_wasm_free_raw_object(js_handle);
+               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 requireObject = BINDING.mono_wasm_require_handle (js_handle);
-               if (!requireObject) {
+               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 ("Invalid JS object handle '" + js_handle + "'");
+                       return BINDING.js_string_to_mono_string ("ERR05: Invalid JS object handle '" + js_handle + "'");
                }
 
                BINDING.wasm_bind_core_clr_obj(js_handle, gc_handle );
-               requireObject.__mono_gchandle__ = gc_handle;
-               requireObject.__js_handle__ = js_handle;
-               return gc_handle;
-       },
-       mono_wasm_bind_host_object: function(js_handle, gc_handle, is_exception) {
-               BINDING.bindings_lazy_init ();
-
-               var requireObject = BINDING.mono_wasm_require_handle (js_handle);
-               if (!requireObject) {
-                       setValue (is_exception, 1, "i32");
-                       return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'");
-               }
-
-               BINDING.wasm_bind_core_clr_obj(js_handle, gc_handle );
-               requireObject.__mono_gchandle__ = 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) {
@@ -2042,9 +1978,8 @@ var BindingSupportLib = {
                                };
 
                                var res = allocator(coreObj, js_args);
-                               var gc_handle = BINDING.mono_wasm_free_list.length ? BINDING.mono_wasm_free_list.pop() : BINDING.mono_wasm_ref_counter++;
-                               BINDING.mono_wasm_object_registry[gc_handle] = res;
-                               return BINDING.mono_wasm_convert_return_value(gc_handle + 1);
+                               var js_handle = BINDING.mono_wasm_get_js_handle(res);
+                               return BINDING.mono_wasm_convert_return_value(js_handle);
                        } catch (e) {
                                var res = e.toString ();
                                setValue (is_exception, 1, "i32");
@@ -2057,28 +1992,27 @@ var BindingSupportLib = {
                        nameRoot.release();
                }
        },
-
        mono_wasm_typed_array_to_array: function(js_handle, is_exception) {
                BINDING.bindings_lazy_init ();
 
-               var requireObject = BINDING.mono_wasm_require_handle (js_handle);
-               if (!requireObject) {
+               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 ("Invalid JS object handle '" + js_handle + "'");
+                       return BINDING.js_string_to_mono_string ("ERR06: Invalid JS object handle '" + js_handle + "'");
                }
 
-               return BINDING.js_typed_array_to_array(requireObject);
+               return BINDING.js_typed_array_to_array(js_obj);
        },
        mono_wasm_typed_array_copy_to: function(js_handle, pinned_array, begin, end, bytes_per_element, is_exception) {
                BINDING.bindings_lazy_init ();
 
-               var requireObject = BINDING.mono_wasm_require_handle (js_handle);
-               if (!requireObject) {
+               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 ("Invalid JS object handle '" + js_handle + "'");
+                       return BINDING.js_string_to_mono_string ("ERR07: Invalid JS object handle '" + js_handle + "'");
                }
 
-               var res = BINDING.typedarray_copy_to(requireObject, pinned_array, begin, end, bytes_per_element);
+               var res = BINDING.typedarray_copy_to(js_obj, pinned_array, begin, end, bytes_per_element);
                return BINDING.js_to_mono_obj (res)
        },
        mono_wasm_typed_array_from: function(pinned_array, begin, end, bytes_per_element, type, is_exception) {
@@ -2089,30 +2023,30 @@ var BindingSupportLib = {
        mono_wasm_typed_array_copy_from: function(js_handle, pinned_array, begin, end, bytes_per_element, is_exception) {
                BINDING.bindings_lazy_init ();
 
-               var requireObject = BINDING.mono_wasm_require_handle (js_handle);
-               if (!requireObject) {
+               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 ("Invalid JS object handle '" + js_handle + "'");
+                       return BINDING.js_string_to_mono_string ("ERR08: Invalid JS object handle '" + js_handle + "'");
                }
 
-               var res = BINDING.typedarray_copy_from(requireObject, pinned_array, begin, end, bytes_per_element);
+               var res = BINDING.typedarray_copy_from(js_obj, pinned_array, begin, end, bytes_per_element);
                return BINDING.js_to_mono_obj (res)
        },
-
-       mono_wasm_add_event_listener: function (objHandle, name, listenerId, optionsHandle) {
+       mono_wasm_add_event_listener: function (objHandle, name, listener_gc_handle, optionsHandle) {
                var nameRoot = MONO.mono_wasm_new_root (name);
                try {
                        BINDING.bindings_lazy_init ();
-                       var obj = BINDING.mono_wasm_require_handle(objHandle);
+                       var sName = BINDING.conv_string(nameRoot.value);
+
+                       var obj = BINDING.mono_wasm_get_jsobj_from_js_handle(objHandle);
                        if (!obj)
-                               throw new Error("Invalid JS object handle");
-                       var listener = BINDING._get_weak_delegate_from_handle(listenerId);
+                               throw new Error("ERR09: Invalid JS object handle for '"+sName+"'");
+                       var listener = BINDING._wrap_delegate_gc_handle_as_function(listener_gc_handle);
                        if (!listener)
-                               throw new Error("Invalid listener ID");
-                       var sName = BINDING.conv_string(nameRoot.value);
+                               throw new Error("ERR10: Invalid listener gc_handle");
 
                        var options = optionsHandle
-                               ? BINDING.mono_wasm_require_handle(optionsHandle)
+                               ? BINDING.mono_wasm_get_jsobj_from_js_handle(optionsHandle)
                                : null;
 
                        if (options)
@@ -2126,15 +2060,14 @@ var BindingSupportLib = {
                        nameRoot.release();
                }
        },
-
-       mono_wasm_remove_event_listener: function (objHandle, name, listenerId, capture) {
+       mono_wasm_remove_event_listener: function (objHandle, name, listener_gc_handle, capture) {
                var nameRoot = MONO.mono_wasm_new_root (name);
                try {
                        BINDING.bindings_lazy_init ();
-                       var obj = BINDING.mono_wasm_require_handle(objHandle);
+                       var obj = BINDING.mono_wasm_get_jsobj_from_js_handle(objHandle);
                        if (!obj)
-                               throw new Error("Invalid JS object handle");
-                       var listener = BINDING._get_weak_delegate_from_handle(listenerId);
+                               throw new Error("ERR11: Invalid JS object handle");
+                       var listener = BINDING._wrap_delegate_gc_handle_as_function(listener_gc_handle);
                        // Removing a nonexistent listener should not be treated as an error
                        if (!listener)
                                return;
index 9ff03d9..bfd162f 100644 (file)
@@ -17,10 +17,8 @@ extern MonoObject* mono_wasm_set_object_property (int js_handle, MonoString *pro
 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 void* mono_wasm_release_object (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 int mono_wasm_bind_host_object (int js_handle, int gc_handle, 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);
@@ -78,9 +76,7 @@ void core_initialize_internals ()
        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::ReleaseObject", mono_wasm_release_object);
        mono_add_internal_call ("Interop/Runtime::BindCoreObject", mono_wasm_bind_core_object);
-       mono_add_internal_call ("Interop/Runtime::BindHostObject", mono_wasm_bind_host_object);
        mono_add_internal_call ("Interop/Runtime::New", mono_wasm_new);
        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);
@@ -89,7 +85,6 @@ void core_initialize_internals ()
        mono_add_internal_call ("Interop/Runtime::CompileFunction", mono_wasm_compile_function);
        mono_add_internal_call ("Interop/Runtime::AddEventListener", mono_wasm_add_event_listener);
        mono_add_internal_call ("Interop/Runtime::RemoveEventListener", mono_wasm_remove_event_listener);
-
 }
 
 // Int8Array           | int8_t        | byte or SByte (signed byte)