SitePen Support
Orbited

IE ActiveX(”htmlfile”) Transport, Part II

by Michael CarterNovember 18th, 2007

In my last post I discussed using the ActiveX(”htmlfile”) technique to provide a usable streaming transport in Internet Explorer. The solution I provided will work, but since writing the last article I’ve made significant progress in understanding why IE behaves the way it does with respect to the streaming transport.

The previous solution amounted to creating an array of messages, pushing messages on that array from the htmlfile iframe, and popping messages off of the array in the parent window, and processing them. Here is the function we use to create that solution:

function connect_htmlfile(url, callback) {
    var transferDoc = new ActiveXObject("htmlfile");
    transferDoc.open();
    transferDoc.write(
        "<html><script>" +
        "document.domain='" + document.domain + "';" +
        "</script></html>");
    transferDoc.close();
    var ifrDiv = transferDoc.createElement("div");
    transferDoc.body.appendChild(ifrDiv);
    ifrDiv.innerHTML = "<iframe src='" + url + "'></iframe>";
    var messageQueue = [];
    transferDoc.message_queue = messageQueue;
    var check_for_msgs = function() {
        // psuedo code
        // while messageQueue.not_empty:
        //   msg = messageQueue.pop();
        //   callback(msg);
    }
    // Check updates ten times a second.
    setInterval(check_for_msgs, 100);
}

In the iframe:

<script>parent.messageQueue.append("arbitrary data")</script>

So this does indeed work, albeit with a performance hit for the callback, as well as an added worst-case 100 ms delay for each event. This is not ideal, but at least it works. Unfortunately, what wasn’t adequately explained in my previous article was why it works.

Well, I have the answer in this article. I was prompted to investigate when I received a message on the Orbited mailing list from “Ufo” explaining that it is unnecessary to push messages into a queue and de-queue them later. In fact, the htmlfile transport can be used almost identically to the standard iframe transport. This is the way I originally attempted to implement this transport, and I encountered the premature htmlfile closing issue, so I was a bit surprised by the news. Here is the working (I tested it) code suggested by Ufo on the mailing list:


function connect_htmlfile(url, callback) {
    var transferDoc = new ActiveXObject("htmlfile");
    transferDoc.open();
    transferDoc.write(
        "<html><script>" +
        "document.domain='" + document.domain + "';" +
        "</script></html>");
    transferDoc.close();
    var ifrDiv = transferDoc.createElement("div");
    transferDoc.body.appendChild(ifrDiv);
    ifrDiv.innerHTML = "<<frame src='" + url + "'></iframe>";
    transferDoc.callback = callback;
    setInterval( function () {}, 10000);
}

In the iframe:

<script>parent.callback("arbitrary data")</script>

The last line in connect_html creates a timer that operates every ten seconds and does absolutely nothing. With this line added, the transport suddenly works. Without it, we have the same problems as before. So my original solution worked because of the setInterval, not because of appending messages to a queue. But it seems to make no sense that a timer which does nothing will magically cause the ActiveX(”htmlfile”) connection not to close. I was perplexed by this until I started digging deeper.

Let’s start with the answer. This is a problem of garbage collection. The reason it works is because that last line creates an anonymous function that forms a closure over the scope in which transferDoc was originally defined. Without this closure, there is no reference to transferDoc so its marked by the garbage collector for deletion. The garbage collector doesn’t do its job immediately though. Rather, it operates after a specified interval of instructions on DOM objects. Actually, I believe the garbage collector for JavaScript objects is different than that for DOM objects, so you can do plenty of JavaScript-only manipulations without causing the garbage collection process to occur.

The new code looks like this:

function connect_htmlfile(url, callback) {
    // ... Same stuff as before ...
    dummy = function() {}
}

This will solve the problem without the setInterval, because we’re still creating a closure around the scope of transferDoc. But this causes a different problem, as first experienced by Andrew Betts and explained in the same mailing thread: once the user navigates away from the page, the htmlfile persists.

The cause of this requires a brief bit of background. In IE there are various methods of causing memory leaks. Normally that’s not a huge deal—the user ends up with a few hundred kilobytes of memory missing, but life goes on. Clearly it isn’t ideal, but neither is it fatal. However, when a live HTTP connection (the htmlfile’s iframe) fails to be garbage collected, we have a huge problem. Not only does it waste server resources to send events to a nonexistent page, it also wreaks havoc on any application logic trying to detect disconnects (for instance, a Comet chat application might want to send a “user has departed” message). Even worse though is that the user will quickly run into the two-connection-per-server limit to the Comet server.

The final solution is very simple. Remember Alex Russell’s original code for the htmlfile transport? We can use it almost exactly, if we remove the var from the transferDoc assignment. The code looks like this:


function connect_htmlfile(url, callback) {
    // no more 'var transferDoc...'
    transferDoc = new ActiveXObject("htmlfile");
    transferDoc.open();
    transferDoc.write(
        "<html><script>" +
        "document.domain='" + document.domain + "';" +
        "</script></html>");
    transferDoc.close();
    var ifrDiv = transferDoc.createElement("div");
    transferDoc.body.appendChild(ifrDiv);
    ifrDiv.innerHTML = "<iframe src='" + url + "'></iframe>";
    transferDoc.callback = callback;
}

And then for the iframe code:

<script>parent.callback(["arbitrary", "data", ["goes", "here"]);</script>

By leaving the page we lose all references to transferDoc and it is thus marked for garbage collection. But surprisingly, navigating away doesn’t always immediately close the connection. The issue is that the garbage collection doesn’t happen right away. This is perfectly fine for data structures that we no longer need, but it is unacceptable for a live HTTP connection to remain open for an unspecified time after the user has left.

The solution is to create an onunload function. The function does two things:

  1. Remove the reference to transferDoc
  2. Explicitly call the garbage collector

function htmlfile_close() {
    transferDoc = null;
    CollectGarbage();
}

Gotchas

Any additional references to transferDoc will need to be deleted before CollectGarbage() will actually close the connection. This isn’t as easy to avoid as you might think. In Orbited we still declare transferDoc as var transferDoc = … but then we attach it to the Orbited object: Orbited.transferDoc = transferDoc. The htmlfile_close function would set Orbited.transferDoc = null before garbage collection. But this failed to close the htmlfile connection! The reason was that we had created an additional and unrelated anonymous function defined in the same scope as transferDoc. This function kept a reference to the transferDoc, and even though the function never interacted with transferDoc at all, it was still never closed.

Strangely enough, even if you lose or even explicitly remove all references to the anonymous function that encloses the transferDoc variable’s scope, and all references to transferDoc are elsewhere removed, garbage collection still fails to close the connection.

Thanks

Special thanks to Andrew Betts and Ufo/Camka for discussing the problem in depth on the Orbited mailing list.

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]
Webtide

19 Responses to “IE ActiveX(”htmlfile”) Transport, Part II”

  1. Dave Says:

    Hi Michael,

    Great article, I’ve a question slightly off topic. You mention the nightmare of memory leaks - what method(s) do you employ to find and solve them? e.g. I use Drip and although according to drip there are no leaks (no dom element leaks) in the stuff I write, I see memory consumption rising on every page refresh.

    Any pointers would be appreciated.

    Cheers,
    Dave

  2. Comet Daily » Blog Archive » The Future of Comet: Part 1, Comet Today Says:

    [...] element to be added to HTML. These mechanisms, like other Comet “transports” such as htmlfile ActiveX objects, “forever frame” iframes, or XHR streaming, allow a browser to open a persistent [...]

  3. Comet Daily » Blog Archive » Buzzword Overload Says:

    [...] htmlfile: An ActiveX control in Internet Explorer. Of all the browsers, IE resists attempts to stream data the most, but the htmlfile can be a solution. [...]

  4. Comet Daily » Blog Archive » Comet Gazing: Maturity Says:

    [...] (polling, long polling, callback polling, iframe streaming, htmlfile streaming, xhr streaming, multipart streaming, [...]

  5. Jamie Taleyarkhan Says:

    Really nice article, and a good explanation. But when i try it, the callback is undefined.
    if my php/cgi flushes the following: alert(typeof parent.callback);

    we discover the callback is undefined. as per your article this shouldn’t be the case.
    Is it something to do with the version of htmlfile ActiveX?

    any ideas? thx

  6. Jamie Taleyarkhan Says:

    ah ok got it:
    transferDoc.parentWindow is the object which is referenced as parent in the ActiveX htmlfile.

    cheers,

  7. panlong Says:

    htmlfile error ‘80004005′
    in vbs

  8. Tim Says:

    I’m running into the same problem as Jamie, but have no idea what his reply means. When I try to reference the callback method, I get:

    Object doesn’t support this property or method.

    parent.callback is not defined. Any suggestions?

  9. Greg Houston Says:

    Although this is an old article, I think many would benefit if a working demo was available for download. The example code could just be a basic html file with the JavaScript in it, and a server side PHP file that updated the page with a new timestamp once a second.

    I am only able to get a single response from the PHP. As soon as I add a loop with flush and sleep in the PHP nothing happens. Then about twenty minutes later I get a single response. I’m assuming something timed out. Basically the data isn’t streaming. The PHP file is being treated like an ordinary non-streaming document.

  10. Eric Waldheim Says:

    Using this method I get about 10 seconds of msgs and then it stops working.
    When I add the trick Meteor uses to register the callback into the iframe (from meteor.js):

    register: function(ifr) {
    ifr.p = Meteor.process;

    Then message reception continues without interruption.
    So I am unconvinced that the technique explained here is the whole story.

    Thank you very much for posting this, it was extremely helpful.

  11. Tim Says:

    4 months later, I FINALLY got this working! Here’s what Jamie was so cryptically trying to say. The line:

    transferDoc.callback = callback;

    needs to be changed to read:

    transferDoc.parentWindow.callback = callback;

    Make that one little change, and you go from the maddening code posted in this article that does nothing but generate a javascript error, to a workable solution. :)

  12. snotling Says:

    Has anyone managed to get this mechanism working in a cross domain setup?
    i.e. loading the iframe from a totally different website than the “top” document.
    The “document.domain” trick only works for sub-domains unfortunately. So I’ve been running into “Permission denied” issues so far…
    I saw references on the Web about the URL fragment identifier hack, but I’m using pieces of information which would be too large to fit in for a start.
    Thanks guys!

  13. Rahul Vartak Says:

    Hi All,

    Im facing a problem wherein i have an HTML page with an IFRAME in it.
    This IFRAME loads an HTML page which has ActiveX Controls in it.

    As the default IE settings prevent an Active X Control from loading, (i.e it shows you a typical security message asking you to allow activeX before loading the entire page) im not able to see the ActiveX enabled content in the IFRAME.

    Is there any way in which i can allow ActiveX Controls to run by default?

  14. Kyle Says:

    This post just saved my life. I am using the Meteor Server (meteorserver.org) for real-time chat on our Ajax site. Everything worked fine in FF, and seemed to work with IE. But when I would refresh the page when the streaming connection was active, I would continuously get an “Object doesn’t support this property or method” error. The strange thing is that, while it was spitting out this error over and over, if I was in another application, and the IE window was behind the application I was using (and not minimized), It would bring the IE window back to the front! I mainly noticed this while in IE8’s developer mode, but I also noticed it affectly my other application in other strange ways. Adding an onunload event containing “Meteor.frameref = null; CollectGarbage();” fixed the problem. In the case of Meteor server, “Meteor.frameref” was equivalent to the “transferdoc” variable. Thanks a lot!

  15. Manh Le Says:

    Hi,
    I figured out how to overcome the permission denied problem.
    the part involves document.domain should be changed from
    transferDoc.write(
    “” +
    “document.domain=’” + document.domain + “‘;” +
    “”);

    to something like this:
    var http=[]
    http.push(”http://”);
    http.push(document.domain);
    transferDoc.write(
    “” +
    “document.domain=’” + http.join(”") + “‘;” +
    “”);

    BTW: It wont work if you just use your testing environment, such as Visual Studio local web host, I had to test my work through real internet address.
    Last thing, dont forget to change the thing as Tim suggested above.

    Still I have one question of how to solve the problem when the user clicks STOP button on the browser; For IE with this htmlfile solution, it works perfect, but for other browsers the comet connection will be disconnected

  16. Quora Says:

    Is it possible to do iframe streaming in non-IE browsers without loading throb?…

    Basically, iframe streaming works in all browsers (by have a hidden iframe to receive never ending “…..

  17. Manh Le Says:

    As far as I know Iframe streaming works in Chrome also. But for other browsers besides IE I would use XMLHttp Object, and use stage 3 to fetch the data from server.
    My comet implementation for the client catches response pretty fast for XMLHTTP streaming, but very slow on Iframe Streaming (IE)/htmlfile for the first few message FLUSHES from the Server.

  18. Simple PHP Comet example - JavaScript Joy Says:

    [...] References: http://www.zeitoun.net/articles/comet_and_php/start http://cometdaily.com/2007/11/18/ie-activexhtmlfile-transport-part-ii/ [...]

  19. Renato Elias Says:

    Hi,

    I’m reading your problem with gc, and thinking if your use with return stament ?

    function connect_htmlfile(url, callback) {
    // no more ‘var transferDoc…’
    var transferDoc = new ActiveXObject(”htmlfile”);
    transferDoc.open();
    transferDoc.write(
    “” +
    “document.domain=’” + document.domain + “‘;” +
    “”);
    transferDoc.close();
    var ifrDiv = transferDoc.createElement(”div”);
    transferDoc.body.appendChild(ifrDiv);
    ifrDiv.innerHTML = “”;
    transferDoc.callback = callback;
    return transferDoc;
    }

    var channel = connect_htmlfile(’/xxx/’ , function(data) {

    });

    and use anything ping in connect

    setInterval((function(channel) {
    //ping channel
    return channel;
    })(channel) , 1000);

Leave a Reply



Copyright 2014 Comet Daily, LLC. All Rights Reserved