Skip to content

Commit

Permalink
Provide a way to override interop members (#758)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored Jul 16, 2020
1 parent 4978e9f commit 68733e1
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 36 deletions.
16 changes: 16 additions & 0 deletions Jint.Tests/Runtime/Domain/HiddenMembers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Jint.Tests.Runtime.Domain
{
public class HiddenMembers
{
public string Member1 { get; set; } = "Member1";
public string Member2 { get; set; } = "Member2";
public string Method1()
{
return "Method1";
}
public string Method2()
{
return "Method2";
}
}
}
44 changes: 44 additions & 0 deletions Jint.Tests/Runtime/InteropTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Jint.Native;
using Jint.Native.Array;
using Jint.Native.Object;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;
using Jint.Tests.Runtime.Converters;
using Jint.Tests.Runtime.Domain;
Expand Down Expand Up @@ -2173,5 +2174,48 @@ public void ShouldBeAbleToJsonStringifyClrObjects()
clrValue = engine.Execute("showProps(jsObj, 'theObject')").GetCompletionValue().AsString();
Assert.Equal(jsValue, clrValue);
}

[Fact]
public void ShouldHideSpecificMembers()
{
var engine = new Engine(options => options.SetMemberAccessor((e, target, member) =>
{
if (target is HiddenMembers)
{
if (member == nameof(HiddenMembers.Member2) || member == nameof(HiddenMembers.Method2))
{
return JsValue.Undefined;
}
}

return null;
}));

engine.SetValue("m", new HiddenMembers());

Assert.Equal("Member1", engine.Execute("m.Member1").GetCompletionValue().ToString());
Assert.Equal("undefined", engine.Execute("m.Member2").GetCompletionValue().ToString());
Assert.Equal("Method1", engine.Execute("m.Method1()").GetCompletionValue().ToString());
// check the method itself, not its invokation as it would mean invoking "undefined"
Assert.Equal("undefined", engine.Execute("m.Method2").GetCompletionValue().ToString());
}

[Fact]
public void ShouldOverrideMembers()
{
var engine = new Engine(options => options.SetMemberAccessor((e, target, member) =>
{
if (target is HiddenMembers && member == nameof(HiddenMembers.Member1))
{
return "Orange";
}

return null;
}));

engine.SetValue("m", new HiddenMembers());

Assert.Equal("Orange", engine.Execute("m.Member1").GetCompletionValue().ToString());
}
}
}
35 changes: 1 addition & 34 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,40 +108,6 @@ public class Engine
internal readonly PropertyDescriptor _callerCalleeArgumentsThrowerConfigurable;
internal readonly PropertyDescriptor _callerCalleeArgumentsThrowerNonConfigurable;

internal readonly struct ClrPropertyDescriptorFactoriesKey : IEquatable<ClrPropertyDescriptorFactoriesKey>
{
public ClrPropertyDescriptorFactoriesKey(Type type, Key propertyName)
{
Type = type;
PropertyName = propertyName;
}

private readonly Type Type;
private readonly Key PropertyName;

public bool Equals(ClrPropertyDescriptorFactoriesKey other)
{
return Type == other.Type && PropertyName == other.PropertyName;
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is ClrPropertyDescriptorFactoriesKey other && Equals(other);
}

public override int GetHashCode()
{
unchecked
{
return (Type.GetHashCode() * 397) ^ PropertyName.GetHashCode();
}
}
}

internal readonly Dictionary<ClrPropertyDescriptorFactoriesKey, Func<Engine, object, PropertyDescriptor>> ClrPropertyDescriptorFactories =
new Dictionary<ClrPropertyDescriptorFactoriesKey, Func<Engine, object, PropertyDescriptor>>();

Expand Down Expand Up @@ -238,6 +204,7 @@ public Engine(Action<Engine, Options> options)

ClrTypeConverter = new DefaultTypeConverter(this);
}


internal LexicalEnvironment GlobalEnvironment { get; }
public GlobalObject Global { get; }
Expand Down
20 changes: 20 additions & 0 deletions Jint/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

namespace Jint
{
public delegate JsValue MemberAccessorDelegate(Engine engine, object target, string member);

public sealed class Options
{
private readonly List<IConstraint> _constraints = new List<IConstraint>();
Expand All @@ -19,6 +21,7 @@ public sealed class Options
private bool _allowClrWrite = true;
private readonly List<IObjectConverter> _objectConverters = new List<IObjectConverter>();
private Func<Engine, object, ObjectInstance> _wrapObjectHandler;
private MemberAccessorDelegate _memberAccessor;
private int _maxRecursionDepth = -1;
private TimeSpan _regexTimeoutInterval = TimeSpan.FromSeconds(10);
private CultureInfo _culture = CultureInfo.CurrentCulture;
Expand Down Expand Up @@ -86,6 +89,22 @@ public Options SetWrapObjectHandler(Func<Engine, object, ObjectInstance> wrapObj
return this;
}


/// <summary>
/// Registers a delegate that is called when CLR members are invoked. This allows
/// to change what values are returned for specific CLR objects, or if any value
/// is returned at all.
/// </summary>
/// <param name="accessor">
/// The delegate to invoke for each CLR member. If the delegate
/// returns <c>null</c>, the standard evaluation is performed.
/// </param>
public Options SetMemberAccessor(MemberAccessorDelegate accessor)
{
_memberAccessor = accessor;
return this;
}

/// <summary>
/// Allows scripts to call CLR types directly like <example>System.IO.File</example>
/// </summary>
Expand Down Expand Up @@ -198,6 +217,7 @@ public Options SetReferencesResolver(IReferenceResolver resolver)
internal List<IConstraint> _Constraints => _constraints;

internal Func<Engine, object, ObjectInstance> _WrapObjectHandler => _wrapObjectHandler;
internal MemberAccessorDelegate _MemberAccessor => _memberAccessor;

internal int MaxRecursionDepth => _maxRecursionDepth;

Expand Down
38 changes: 38 additions & 0 deletions Jint/Runtime/Interop/ClrPropertyDescriptorFactoriesKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;

namespace Jint.Runtime.Interop
{
internal readonly struct ClrPropertyDescriptorFactoriesKey : IEquatable<ClrPropertyDescriptorFactoriesKey>
{
public ClrPropertyDescriptorFactoriesKey(Type type, Key propertyName)
{
Type = type;
PropertyName = propertyName;
}

private readonly Type Type;
private readonly Key PropertyName;

public bool Equals(ClrPropertyDescriptorFactoriesKey other)
{
return Type == other.Type && PropertyName == other.PropertyName;
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is ClrPropertyDescriptorFactoriesKey other && Equals(other);
}

public override int GetHashCode()
{
unchecked
{
return (Type.GetHashCode() * 397) ^ PropertyName.GetHashCode();
}
}
}
}
16 changes: 14 additions & 2 deletions Jint/Runtime/Interop/ObjectWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,21 @@ public override PropertyDescriptor GetOwnProperty(JsValue property)
SetProperty(GlobalSymbolRegistry.Iterator, iteratorProperty);
return iteratorProperty;
}


var memberAccessor = Engine.Options._MemberAccessor;

if (memberAccessor != null)
{
var result = memberAccessor.Invoke(Engine, Target, property.ToString());

if (result != null)
{
return new PropertyDescriptor(result, PropertyFlag.OnlyEnumerable);
}
}

var type = Target.GetType();
var key = new Engine.ClrPropertyDescriptorFactoriesKey(type, property.ToString());
var key = new ClrPropertyDescriptorFactoriesKey(type, property.ToString());

if (!_engine.ClrPropertyDescriptorFactories.TryGetValue(key, out var factory))
{
Expand Down

0 comments on commit 68733e1

Please sign in to comment.