Callbacks for some host objects are not executing

Dec 10, 2015 at 10:11 PM
Hi,

It appears that ClearScript 5.4.4 broke callbacks for some host objects. In ClearScript 5.4.3 the callbacks work.

I am exposing IE HTML window object as a global host object to the script engine. My scripts use addEventListener, setTimeout. The callbacks set by the window host object are never executed.

The callbacks on my private host objects work.

I tried setting WindowsScriptEngineFlags.DoNotEnableVTablePatching just to see if that helps but it did not.
Coordinator
Dec 10, 2015 at 10:28 PM
Hi frolovm,

Hmm, this is probably an unintended consequence of this fix. We'll take a look ASAP.

Thanks for reporting it!
Coordinator
Dec 11, 2015 at 3:40 AM
Hi again,

Some questions:
  1. How are you acquiring and exposing the window object?
  2. What is your script code passing into setTimeout()?
Sample code would be best, but a detailed description should work as well.

Cheers!
Dec 11, 2015 at 5:38 PM
I obtain the window object in DWebBrowserEvents2_Event.NavigateComplete2 event. Following is some stripped down code snippets:
public void OnNavigateComplete(object pDisp, ref object URL)
{
    var browser = pDisp as IWebBrowser2;
    var document2 = browser?.Document as IHTMLDocument2;
    var window = document2?.parentWindow as IHTMLWindow2;

    var engine = new JScriptEngine("{16d51579-a30b-4c8b-a276-0ff4dc41e755}", null, WindowsScriptEngineFlags.EnableStandardsMode | WindowsScriptEngineFlags.MarshalNullAsDispatch | WindowsScriptEngineFlags.MarshalArraysByValue);
    engine.AddHostObject("window", HostItemFlags.GlobalMembers, window);
}
And the JavaScript snippet:

global is the window object.

contentPort is a private host object.
contentPort.addMessageListener(function (msg) {

    if (global.document.readyState !== "complete") {
        global.addEventListener('load', function () {
            console.log('message document load');
        }, false);
        console.log("Message received - waiting for document load");
    } else {
        setTimeout(function () {
            console.log('message timeout');
        });
        console.log("Message received");
    }
}
The console output displays either "Message received" or "Message received - waiting for document load" but never "message document load" or "message timeout"

The private host object callback works but the window callbacks do not.
Coordinator
Dec 11, 2015 at 6:37 PM
Thanks. Some more questions as we try to replicate your environment:
  1. Where are the browser events coming from? It looks like you're using a native browser control, or perhaps you're working on a plug-in?
  2. Out of curiosity, why does the JavaScript code use global rather than window or simply the root object?
Dec 11, 2015 at 7:06 PM
  1. The code is part of a BHO. I subscribe to the events in IObjectWithSite.SetSite
  2. For historical reasons. The script is wrapped into a self-executing function which passes window object as the global parameter. You may have noticed that setTimeout is not using global.
Coordinator
Dec 11, 2015 at 8:44 PM
Hi frolovm,

We don't have a BHO test harness, so we're testing with a simple WPF app that uses the WebBrowser XAML control, which exposes the same underlying COM objects.

We can confirm that passing an external script function to setTimeout appears to succeed but the function is never invoked. However, we can't confirm that it works with ClearScript 5.4.3 either. In fact, with stock 5.4.3 the setTimeout call throws an exception, and with your changes here it appears to behave exactly like 5.4.4.

You say that you had it working with 5.4.3. Had you by any chance applied any additional changes?

Interestingly, this works on both 5.4.4 and your modified 5.4.3:
window.onTimer = function() { /* ... */ };
setTimeout('onTimer()');
Thanks!
Coordinator
Dec 14, 2015 at 11:28 PM
Edited Dec 14, 2015 at 11:29 PM
Hi again,

Further investigation reveals that the MSHTML+JScript implementation of setTimeout requires that the supplied callback be a function within the DOM's private script collection, so it isn't clear how it ever could have worked with an external function exposed by ClearScript.

This requirement might be a cross-site security measure. Fortunately, as you can see above, it is relatively easy to work around :)

Good luck!
Dec 22, 2015 at 8:15 PM
Edited Dec 22, 2015 at 8:15 PM
Hi,

I've created a fork named StandardsPlusMode. It is based on 5.4.3 version of ClearScript. I've made changes to it to enable the latest Javascript features in the JScriptEngine. This is the code I use in the BHO and it allows me to use the window object and all callbacks work.

The same changes integrated in 5.4.4 version of ClearScript break callbacks.

Instantiate with the following code:
var engine = new JScriptEngine("{16d51579-a30b-4c8b-a276-0ff4dc41e755}", null, WindowsScriptEngineFlags.EnableStandardsPlusMode);
Coordinator
Dec 23, 2015 at 4:41 AM
Hi frolovm,

Thanks for sharing your changes. We've reproduced the issue and are investigating.

Apparently the limitation mentioned above applies only to legacy JScript, whereas Chakra's requirements are somewhat different. It looks like ClearScript 5.4.3 satisfied those requirements, but 5.4.4 doesn't. ClearScript doesn't support Chakra, but we'd like to get to the bottom of this.

Thanks again!
Coordinator
Dec 28, 2015 at 9:27 PM
Edited Dec 28, 2015 at 9:29 PM
Hi again,

Well, this has turned into quite an investigation. Our findings:

  1. The MSHTML/JScript window object requires that callbacks be recognizable as JScript functions, not simply IDispatchEx instances. Contrary to earlier findings, legacy JScript and pre-Edge Chakra have identical requirements in this area, although they don't seem to recognize each other's functions. Interestingly, functions belonging to other instances of the same script engine are apparently permitted.
  2. Your change in HostItem.cs assumes that window (actually any COM object) can be treated as a script object belonging to the current script engine. This enables your callback scenario by tricking ClearScript into giving window direct access to the callback. That is, when your script issues a call such as window.setTimeout(callback), ClearScript observes that window and callback are script objects belonging to the same script engine and concludes that no proxy is required. The resulting proxy-free (direct) access allows window to recognize callback as a JScript function.
  3. While it enables your scenario, your change in HostItem.cs is generally incorrect. Most COM objects are not script objects; in fact, most script engines don't support COM at all. In this particular case, window is a COM object, and it is natively compatible with your script engine (Chakra), but there's no way for ClearScript to detect that or to determine that direct access is actually desirable. Only the application has that knowledge.
Given all this, we've decided to add HostItemFlags.DirectAccess (commit). Here's how you might use it in your BHO:
engine.AddHostObject("window", HostItemFlags.GlobalMembers | HostItemFlags.DirectAccess, window);
This exposes window for script access that bypasses ClearScript completely. It should yield better performance, and all callbacks should work. Give it a try. You'll still need your other modifications that enable Chakra support within ClearScript.

Good luck!
Jan 4, 2016 at 9:05 PM
This fix appears to work. Thanks.