Browse Source

Track TypeReference registrations and use as prototype (#1661)

Marko Lahma 1 year ago
parent
commit
0242ac5a81

+ 5 - 0
Jint.Tests/Runtime/InteropTests.TypeReference.cs

@@ -195,6 +195,11 @@ public partial class InteropTests
 
 
         Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c);"));
         Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c);"));
         Assert.Equal(123, _engine.Evaluate("c.abc"));
         Assert.Equal(123, _engine.Evaluate("c.abc"));
+
+        // engine uses registered type reference
+        _engine.SetValue("c2", new Dependency());
+        Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c2);"));
+        Assert.Equal(123, _engine.Evaluate("c2.abc"));
     }
     }
 
 
     private class Injectable
     private class Injectable

+ 9 - 0
Jint/Engine.cs

@@ -61,6 +61,9 @@ namespace Jint
         // cache of types used when resolving CLR type names
         // cache of types used when resolving CLR type names
         internal readonly Dictionary<string, Type?> TypeCache = new();
         internal readonly Dictionary<string, Type?> TypeCache = new();
 
 
+        // we use registered type reference as prototype if it's known
+        internal Dictionary<Type,TypeReference>? _typeReferences;
+
         // cache for already wrapped CLR objects to keep object identity
         // cache for already wrapped CLR objects to keep object identity
         internal ConditionalWeakTable<object, ObjectInstance>? _objectWrapperCache;
         internal ConditionalWeakTable<object, ObjectInstance>? _objectWrapperCache;
 
 
@@ -1562,6 +1565,12 @@ namespace Jint
             _error = error;
             _error = error;
         }
         }
 
 
+        internal void RegisterTypeReference(TypeReference reference)
+        {
+            _typeReferences ??= new Dictionary<Type, TypeReference>();
+            _typeReferences[reference.ReferenceType] = reference;
+        }
+
         public void Dispose()
         public void Dispose()
         {
         {
             if (_objectWrapperCache is null)
             if (_objectWrapperCache is null)

+ 7 - 0
Jint/Runtime/Interop/DefaultObjectConverter.cs

@@ -110,6 +110,13 @@ namespace Jint
                         else
                         else
                         {
                         {
                             var wrapped = engine.Options.Interop.WrapObjectHandler.Invoke(engine, value, type);
                             var wrapped = engine.Options.Interop.WrapObjectHandler.Invoke(engine, value, type);
+
+                            if (ReferenceEquals(wrapped?.GetPrototypeOf(), engine.Realm.Intrinsics.Object.PrototypeObject)
+                                && engine._typeReferences?.TryGetValue(t, out var typeReference) == true)
+                            {
+                                wrapped.SetPrototypeOf(typeReference);
+                            }
+
                             result = wrapped;
                             result = wrapped;
 
 
                             if (engine.Options.Interop.TrackObjectWrapperIdentity && wrapped is not null)
                             if (engine.Options.Interop.TrackObjectWrapperIdentity && wrapped is not null)

+ 3 - 1
Jint/Runtime/Interop/TypeReference.cs

@@ -40,7 +40,9 @@ namespace Jint.Runtime.Interop
 
 
         public static TypeReference CreateTypeReference(Engine engine, Type type)
         public static TypeReference CreateTypeReference(Engine engine, Type type)
         {
         {
-            return new TypeReference(engine, type);
+            var reference = new TypeReference(engine, type);
+            engine.RegisterTypeReference(reference);
+            return reference;
         }
         }
 
 
         protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)
         protected internal override JsValue Call(JsValue thisObject, JsValue[] arguments)