Friday, February 06, 2009

JavaScript's Function.call() method and the Silverlight HTML Bridge

I recently discovered that the Silverlight 2.0 HTML Bridge does not support the Function.call method. (It does support the Function.apply method.) I wasn't able to find any documentation stating that Function.call is not supported, so I wanted to post it here, for anyone else investigating the same issue.

To give you some background, here's how I framed the question:

I’m trying to interface with the OpenAjax Hub from Silverlight, which uses the function.call() approach to invocation instead of function(). In JavaScript, the Function object has a call() method on it, which allows the “this” reference within the Function implementor to be replaced with another object. This allows for constructor-chaining in JavaScript, a key feature of inheritance.

When a managed object subscribes to an event in the Hub, the Hub stores a Function reference that points to the managed object’s callback method. So, for example, the managed class might have a method like this:

[ScriptableMember]
public void TestHubCallback(string eventName, object publisherData, object subscriberData)
{
Console.WriteLine("Received event named " + eventName);
}

I have demonstrated that I can invoke that callback method successfully from JavaScript, using something like this:

callback(“testEvent”, “testData”, null);

However, the Hub implementation always uses the call() method on Function to invoke the callback, passing in the global “window” reference by default, like this:

callback.call(window, “testEvent”, “testData”, null);

The idea is that the first parameter can be used to define the scope in which the subscriber callback is invoked, and “window” seems like a reasonable default value. Unfortunately, this form of invocation fails, ultimately resulting in an InvalidOperationException within the managed code that tried to publish the event. My first thought was that I should replace “window” with a reference to the managed subscriber object, since the managed object obviously won’t be able to replace its “this” reference with an HtmlWindow object. So I tried the following, where the “subscriber” variable is a reference to the managed object (which has the ScriptableType attribute, just in case):

callback.call(subscriber, “testEvent”, “testData”, null);

However, I still get the InvalidOperationException. Am I doing something wrong? Is there any way to invoke a managed delegate via this call() method, or is the call() method not supported at all by the HTML Bridge?


I ended up working around this by having my Silverlight apps call a wrapper function in the JavaScript layer when subscribing to an event. This wrapper function combines the subscriberData and the callback itself in a JavaScript Array, pushes this Array into the Hub as subscriberData, and registers its own "middleman" callback method. When that middleman callback is invoked, it takes the original callback out of the subscriberData Array, invokes that callback directly (instead of using Function.call), and passes the original subscriberData along to the Silverlight delegate. As a result, my Silverlight app cannot benefit from the scope concept in the OpenAjax Hub, but so far I see no real loss there.