From e49b9bd7ae706bbcbefc767d6572232acbb20940 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Sat, 7 Aug 2021 08:06:48 +0200 Subject: [PATCH] [wasm] Redesign of managed objects marshaling and lifecycle (#56538) 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 --- .../src/Interop/Browser/Interop.Runtime.cs | 28 +- .../InteropServices/JavaScript/AnyRef.cs | 7 +- .../InteropServices/JavaScript/CoreObject.cs | 2 +- .../InteropServices/JavaScript/HostObject.cs | 4 +- .../InteropServices/JavaScript/JSObject.cs | 40 +- .../InteropServices/JavaScript/Runtime.cs | 196 ++---- ...me.InteropServices.JavaScript.Tests.csproj | 1 + .../JavaScript/DelegateTests.cs | 260 ++++++- .../JavaScript/JavaScriptTests.cs | 80 ++- src/mono/wasm/runtime-test.js | 8 +- src/mono/wasm/runtime/binding_support.js | 657 ++++++++---------- src/mono/wasm/runtime/corebindings.c | 5 - 12 files changed, 688 insertions(+), 600 deletions(-) diff --git a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs index 67509d17a362e..ef12917e5edce 100644 --- a/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs +++ b/src/libraries/Common/src/Interop/Browser/Interop.Runtime.cs @@ -21,41 +21,37 @@ internal static partial class Runtime [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); // / // / Execute the provided string in the JavaScript context diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs index 58587eb15a479..2bd4afb20deb4 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/AnyRef.cs @@ -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 @@ public abstract class AnyRef : SafeHandleMinusOneIsInvalid 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 @@ internal AnyRef(IntPtr jsHandle, bool ownsHandle) : base(ownsHandle) InFlight = null; InFlightCounter = 0; } - internal int Int32Handle => (int)(IntPtr)AnyRefHandle; + internal int GCHandleValue => (int)(IntPtr)AnyRefHandle; internal void AddInFlight() { diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs index 791a3ab5480c7..33c475027d452 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/CoreObject.cs @@ -20,7 +20,7 @@ public abstract class CoreObject : JSObject { 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)); } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs index 1d3b3bf4d8676..b9dec0f68c5cd 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/HostObject.cs @@ -27,9 +27,9 @@ public HostObject(string hostName, params object[] _params) : base(Interop.Runti 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)); } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs index 85eb8285f3932..7aac370f5d117 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSObject.cs @@ -18,16 +18,12 @@ public interface IJSObject /// public class JSObject : AnyRef, IJSObject, IDisposable { - internal object? RawObject; - - private WeakReference? WeakRawObject; - // to detect redundant calls public bool IsDisposed { get; private set; } public JSObject() : this(Interop.Runtime.New(), 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 @@ internal JSObject(IntPtr jsHandle, bool ownsHandle) : base(jsHandle, ownsHandle) 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(rawDelegate, trackResurrection: false); - } - /// /// Invoke a named method of the object, or throws a JSException on error. /// @@ -84,7 +70,7 @@ public struct EventListenerOptions { public object? Signal; } - public int AddEventListener(string name, Delegate listener, EventListenerOptions? options = null) + public int AddEventListener(string name, Action listener, EventListenerOptions? options = null) { var optionsDict = options.HasValue ? new JSObject() @@ -94,7 +80,7 @@ public int AddEventListener(string name, Delegate listener, EventListenerOptions 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 @@ public int AddEventListener(string name, Delegate listener, EventListenerOptions } } - public void RemoveEventListener(string name, Delegate? listener, EventListenerOptions? options = null) + public void RemoveEventListener(string name, Action? 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 @@ public void SetObjectProperty(string name, object value, bool createIfNotExists { 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})"); } /// @@ -205,19 +191,11 @@ public int Length /// The String name or Symbol of the property to test. public bool PropertyIsEnumerable(string prop) => (bool)Invoke("propertyIsEnumerable", prop); - internal bool IsWeakWrapper => WeakRawObject?.TryGetTarget(out _) == true; - - internal object? GetWrappedObject() - { - return RawObject ?? (WeakRawObject is WeakReference 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 @@ protected override bool ReleaseHandle() public override string ToString() { - return $"(js-obj js '{Int32Handle}' raw '{RawObject != null}' weak_raw '{WeakRawObject != null}')"; + return $"(js-obj js '{GCHandleValue}')"; } } } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs index d30808f44d17d..85dfa7f145e3c 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Runtime.cs @@ -13,12 +13,9 @@ namespace System.Runtime.InteropServices.JavaScript public static class Runtime { private static readonly Dictionary> _boundObjects = new Dictionary>(); - private static readonly Dictionary _rawToJS = new Dictionary(); - // _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 _weakDelegateTable = new ConditionalWeakTable(); + private static object JSOwnedObjectLock = new object(); + // we use this to maintain identity of GCHandle for a managed object + private static Dictionary GCHandleFromJSOwnedObject = new Dictionary(); private const string TaskGetResultName = "get_Result"; private static readonly MethodInfo _taskGetResultMethodInfo = typeof(Task<>).GetMethod(TaskGetResultName)!; @@ -48,23 +45,6 @@ public static int New(string hostClassName, params object[] parms) 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 @@ public static void DumpAotProfileData (ref byte buf, int len, string extraArg) 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? reference) || + if (!_boundObjects.TryGetValue(jsHandle, out WeakReference? 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(target, trackResurrection: true); + _boundObjects[jsHandle] = new WeakReference(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? wr)) + if (_boundObjects.TryGetValue(jsHandle, out WeakReference? 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(instance, trackResurrection: true)); + _boundObjects.Add(jsHandle, new WeakReference(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 @@ internal static bool ReleaseJSObject(JSObject objToRelease) { Interop.Runtime.ReleaseHandle(objToRelease.JSHandle, out int exception); if (exception != 0) - throw new JSException($"Error releasing handle on (js-obj js '{objToRelease.JSHandle}' mono '{objToRelease.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 @@ internal static bool ReleaseJSObject(JSObject objToRelease) 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(); + return GetJSOwnedObjectGCHandle(tcs); } - public static object CreateTaskSource(int jsId) - { - return new TaskCompletionSource(); - } - - public static void SetTaskSourceResult(TaskCompletionSource 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 tcs = (TaskCompletionSource)handle.Target!; tcs.SetResult(result); } - public static void SetTaskSourceFailure(TaskCompletionSource 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 tcs = (TaskCompletionSource)handle.Target!; tcs.SetException(new JSException(reason)); } - public static int GetTaskAndBind(TaskCompletionSource 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)); - } - 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 tcs = (TaskCompletionSource)handle.Target!; + return tcs.Task; } - private static int NextJSOwnedObjectID = 1; - private static object JSOwnedObjectLock = new object(); - private static Dictionary IDFromJSOwnedObject = new Dictionary(); - private static Dictionary JSOwnedObjectFromID = new Dictionary(); - // 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 @@ public static int BindExistingObject(object rawObj, int jsId) // 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 @@ public static int GetJSObjectId(object rawObj) { jso.AddInFlight(); } - return jso.GetWrappedObject() ?? jso; + return jso; } return h.Target; } @@ -452,7 +351,6 @@ void Complete() finally { continuationObj.Dispose(); - FreeObject(task); } } } @@ -555,22 +453,22 @@ public static void SafeHandleRelease(SafeHandle safeHandle) #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? reference)) + if (_boundObjects.TryGetValue(jsHandle, out WeakReference? 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}"); } } } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj index e97ca510a5384..c38d7c36049db 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System.Private.Runtime.InteropServices.JavaScript.Tests.csproj @@ -3,6 +3,7 @@ true $(NetCoreAppCurrent)-Browser true + $(WasmXHarnessArgs) --engine-arg=--expose-gc diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs index 2414a6ca17ccd..da5ad2169a210 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/DelegateTests.cs @@ -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 @@ public static void InvokeMultiCastDelegate_VoidString(string creator, string tes "); Assert.Equal($" Hello, {testStr}! GoodMorning, {testStr}!", HelperMarshal._delegateCallResult); } - + [Theory] [InlineData("CreateDelegateFromAnonymousMethod_VoidString")] [InlineData("CreateDelegateFromLambda_VoidString")] @@ -178,7 +178,7 @@ public static IEnumerable ArrayType_TestData() [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 @@ public static void InvokeFunctionAcceptingArrayTypes(Function objectPrototype, s 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 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 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 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)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)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)factory.Call(); + + var ex = await Assert.ThrowsAsync(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)factory.Call(); + + var ex = await Assert.ThrowsAsync(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>(dummy); + var check = obj.Invoke("check", dummy); + Assert.Equal(1, check); + } + + + [Fact] + public static async Task ResolveTask() + { + var tcs = new TaskCompletionSource(); + var factory = new Function("task", "callback", @" + return task.then((data)=>{ + callback(data); + }) + "); + + int check = 0; + Action callback = (int data) => + { + check = data; + }; + Task task = tcs.Task; + // we are testing that Task is marshaled as thenable + var promise = (Task)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(); + var factory = new Function("task", "callback", @" + return task.catch((reason)=>{ + callback(reason); + }) + "); + + string check = null; + Action callback = (string data) => + { + check = data; + }; + var promise = (Task)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(); + var factory = new Function("task", "callback", @" + callback(task); + "); + + Task check = Task.FromResult(1); + Action 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(); + var factory = new Function("dummy", @" + return { + dummy:dummy, + }"); + + var obj = (JSObject)factory.Call(tcs.Task); + var dummy = obj.GetObjectProperty("dummy"); + Assert.IsType>(dummy); + } } } diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs index 54a8037a1c46a..2ad9116e58606 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/JavaScriptTests.cs @@ -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 @@ public static async Task BagIterator() 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 @@ public static IEnumerable ToEnumerable(this JSObject iterrator) } } - 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 @@ public static IEnumerable ToEnumerable(this JSObject iterrator) }, }; "); - 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(); - 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 @@ public static IEnumerable ToEnumerable(this JSObject iterrator) [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(() => { @@ -315,7 +347,7 @@ public static IEnumerable ToEnumerable(this JSObject iterrator) Assert.Contains("Test exception", exc.Message); exc = Assert.Throws(() => { - obj.AddEventListener("throwError", () => { + obj.AddEventListener("throwError", (JSObject envt) => { throw new Exception("Should not be called"); }); }); @@ -325,8 +357,8 @@ public static IEnumerable ToEnumerable(this JSObject iterrator) [Fact] public static void RemovedEventListenerIsNotCalled () { - var obj = SetupListenerTest("removedEventListenerIsNotCalled"); - Action del = () => { + var obj = SetupListenerTest(); + Action del = (JSObject envt) => { throw new Exception("Should not be called"); }; obj.AddEventListener("test", del); @@ -341,8 +373,8 @@ public static IEnumerable ToEnumerable(this JSObject iterrator) [Fact] public static void RegisterSameEventListener () { var counter = new int[1]; - var obj = SetupListenerTest("registerSameDelegateTwice"); - Action del = () => { + var obj = SetupListenerTest(); + Action del = (JSObject envt) => { counter[0]++; }; @@ -367,8 +399,8 @@ public static IEnumerable ToEnumerable(this JSObject iterrator) [Fact] public static void UseAddEventListenerResultToRemove () { - var obj = SetupListenerTest("useAddEventListenerResultToRemove"); - Action del = () => { + var obj = SetupListenerTest(); + Action del = (JSObject envt) => { throw new Exception("Should not be called"); }; var handle = obj.AddEventListener("test", del); @@ -383,9 +415,9 @@ public static IEnumerable ToEnumerable(this JSObject iterrator) [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 del = (JSObject envt) => { counter[0]++; }; diff --git a/src/mono/wasm/runtime-test.js b/src/mono/wasm/runtime-test.js index 6796330a98012..e4e7c88896bf0 100644 --- a/src/mono/wasm/runtime-test.js +++ b/src/mono/wasm/runtime-test.js @@ -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({ diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index 85b894482c570..4bd589b559d8a 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -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; diff --git a/src/mono/wasm/runtime/corebindings.c b/src/mono/wasm/runtime/corebindings.c index 9ff03d9a88ec6..bfd162f5e5b98 100644 --- a/src/mono/wasm/runtime/corebindings.c +++ b/src/mono/wasm/runtime/corebindings.c @@ -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)