IE ActiveX(”htmlfile”) Transport, Part II
by Michael CarterNovember 18th, 2007In 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:
- Remove the reference to transferDoc
- 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.










November 20th, 2007 at 6:58 am
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
December 11th, 2007 at 4:35 am
[...] 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 [...]
February 5th, 2008 at 2:11 pm
[...] 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. [...]
March 14th, 2008 at 5:54 pm
[...] (polling, long polling, callback polling, iframe streaming, htmlfile streaming, xhr streaming, multipart streaming, [...]
April 11th, 2008 at 3:35 am
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
April 12th, 2008 at 1:37 am
ah ok got it:
transferDoc.parentWindow is the object which is referenced as parent in the ActiveX htmlfile.
cheers,
July 1st, 2008 at 6:17 am
htmlfile error ‘80004005′
in vbs
December 15th, 2008 at 3:40 am
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?
March 4th, 2009 at 4:15 am
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.
March 20th, 2009 at 11:04 am
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.
April 13th, 2009 at 12:35 am
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.
April 17th, 2009 at 2:49 pm
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!
January 15th, 2010 at 12:08 am
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?
May 26th, 2010 at 2:20 pm
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!