CLR object method redirection

Mar 9, 2010 at 4:41 PM

I'm working on a headless .NET-based web browser (think HtmlUnit, but written in C#). To work as a viable browser, I need to be able to execute JavaScript, and I need to implement the W3C HTML DOM. Since the methods defined by the DOM are case-sensitive, I thought I could get away with just wrapping my CLR DOM objects with appropriately cased method names, and passing them to Jint. However, I'm running into a problem with some method names. The "checked" attribute on an input element, for example, is the lower-case word "checked", but it's also a keyword in C#, so you can't have a property or method named that. Ideally, I'd like to be able to intercept calls from the JavaScript interpreter, and tell it to use another method or property (say, "checked_") in my CLR object.

To that end, I'd like to know if such a thing is possible in Jint. I've not found anything like that, but I admit I don't know the code all that well. If it's not available, I have a proposal for two methods to solve the issue. One proposal would be to create an interface, say IRedirectable, which would expose a hashtable of method names. The ExecutionVisitor would test for the existence of this interface, and use the map to redirect the call to the correct method.

// Added to Jint
public interface IRedirectable
{
    Hashtable MemberRedirectionMap { get; }
}

// Change to ExecutionVisitor.Visit(PropertyExpression expression)
// Insert after line 1835
IRedirectable redirectableScope = CurrentScope.Value as IRedirectable;
if (redirectableScope != null && redirectableScope.MemberRedirectionMap.ContainsKey(propertyName))
{
     propertyName = redirectableScope.MemberRedirectionMap[propertyName].ToString();
}

// User's CLR object looks like this:
public class JsTestClass : IRedirectable
{
    private Hashtable map;
    public JsTestClass()
    {
        map = new Hashtable();
        map.Add("redirectme", "ThisIsRedirected");
    }

    public string ThisIsRedirected()
    {
        return "Redirected Hello World";
    }

    public Hashtable MemberRedirectionMap
    {
        get { return map; }
    }
}

The second method would involve creating an attribute with which you decorate your methods in your CLR class. This would be a potentially more invasive change to Jint, in that it would need to be implemented in more places (CachedMethodInvoker, CachedReflectionPropertyGetter, and perhaps CachedReflectionFieldGetter), and it may involve a perf hit. On the other hand, the C# code is much cleaner, and easier on the eyes.

// Added to Jint
public class JavaScriptNameAttribute : Attribute
{
    private string name;
    public JavaScriptNameAttribute(string name)
    {
        this.name = name;
    }

    public string Name
    {
        get { return this.name; }
        set { this.name = value; }
    }
}

// Added to CachedMethodInvoker.Invoke (at line 69).
// Corresponding changes would need to be made for properties and fields.
// Comparison to method argument is now to methodName
// instead of m.Name.
string methodName = m.Name;
object[] attributes = m.GetCustomAttributes(typeof(JavaScriptNameAttribute), true);
if (attributes.Length > 0)
{
    JavaScriptNameAttribute jsNameAttribute = attributes[0] as JavaScriptNameAttribute;
    methodName = jsNameAttribute.Name;
}

// User CLR object
public class JsTestClass
{
    public JsTestClass()
    {
    }

    [JavaScriptName("redirectme")]
    public string ThisIsRedirected()
    {
        return "Redirected Hello World";
    }
}

In both cases, the following code will work correctly, even though there is no method called "redirectme" on the test instance of the JsTestClass object.

JintEngine eng = new JintEngine();
JsTestClass test = new JsTestClass();
eng.SetParameter("test", test);
string result = eng.Run("return test.redirectme();").ToString();
Console.WriteLine("Script result: {0}", result);

I've tried both methods with the Jint source code and the unit tests still run identically to the unmodified code. Would anyone besides me find it useful to be able to present a different API to the JavaScript interpreter than to the rest of your application?

Mar 9, 2010 at 6:35 PM

I know it's bad form to reply to your own post, but I wanted to point out that the Attributes method I suggested above is a little more complicated than the example code. It involves adding code to JsClr.cs as well so that the redirected property and method names are properly resolved during execution.

Mar 10, 2010 at 10:54 AM

Why don't you simply write

bool @checked { get; set; }

in your C# code instead?

Mar 11, 2010 at 1:59 AM

Well yes that would solve my immediate problem, but I'm actually interested in exploring the possibilities of presenting a facade to the API of your application for scripting purposes without resorting to the wrapper classes so common in facade pattern implementations. If I'm truly the only person who sees any benefit in such a feature, I'll be happy to let it go, creating a private modification to the library if I want it badly enough.

Mar 12, 2010 at 2:15 AM

You are not the only one for such a feature. Me too. But I'm more inclined to the IDynamicMetaObjectProvider way which comes with .NET 4.0. It will allow you write true dynamic object in C# and expose it to scripts.