diff --git a/Jint.Tests/Runtime/InteropTests.TypeReference.cs b/Jint.Tests/Runtime/InteropTests.TypeReference.cs index ed4aa6a607..dc1557f26f 100644 --- a/Jint.Tests/Runtime/InteropTests.TypeReference.cs +++ b/Jint.Tests/Runtime/InteropTests.TypeReference.cs @@ -195,6 +195,11 @@ public void CanRegisterToStringTag() Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c);")); 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 diff --git a/Jint/Engine.cs b/Jint/Engine.cs index cfa2399b20..45b4b0e3d4 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -61,6 +61,9 @@ public sealed partial class Engine : IDisposable // cache of types used when resolving CLR type names internal readonly Dictionary TypeCache = new(); + // we use registered type reference as prototype if it's known + internal Dictionary? _typeReferences; + // cache for already wrapped CLR objects to keep object identity internal ConditionalWeakTable? _objectWrapperCache; @@ -1562,6 +1565,12 @@ internal void SignalError(ErrorDispatchInfo error) _error = error; } + internal void RegisterTypeReference(TypeReference reference) + { + _typeReferences ??= new Dictionary(); + _typeReferences[reference.ReferenceType] = reference; + } + public void Dispose() { if (_objectWrapperCache is null) diff --git a/Jint/Runtime/Interop/DefaultObjectConverter.cs b/Jint/Runtime/Interop/DefaultObjectConverter.cs index f9739bbfb2..acf36264c9 100644 --- a/Jint/Runtime/Interop/DefaultObjectConverter.cs +++ b/Jint/Runtime/Interop/DefaultObjectConverter.cs @@ -110,6 +110,13 @@ public static bool TryConvert(Engine engine, object value, Type? type, [NotNullW else { 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; if (engine.Options.Interop.TrackObjectWrapperIdentity && wrapped is not null) diff --git a/Jint/Runtime/Interop/TypeReference.cs b/Jint/Runtime/Interop/TypeReference.cs index a5dd6de24f..d340795568 100644 --- a/Jint/Runtime/Interop/TypeReference.cs +++ b/Jint/Runtime/Interop/TypeReference.cs @@ -40,7 +40,9 @@ public static TypeReference CreateTypeReference(Engine engine) 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)