Saturday, April 17, 2010

1 Item Remaining Problem In IE

There are a bunch of different bugs in IE which manifest themselves as a page which doesn't finish loading and displays "X items remaining" (or "1 item remaining") in the status bar. C.M. Palmer on Stack Overflow has a pretty good answer for one of these bugs. I recently found an answer to another.

One of the symptoms of this particular bug (and probably others) is that it only happens in certain environments. I finally got access to one client's environment where this happened, and was able to track it down. Intermittently, and for no apparent reason, IE would fail to load the following script:

<script defer src=//:></script>

Yes, that's a script element with a src of "//:". This ugly hack is actually the best way to simulate the DOMContentLoaded event in IE (the event which signals that the HTML page has been parsed and its document object-model is fully built), as Dean Edwards and friends have figured out (they also tried src values of javascript:void(0), javascript:false, and //0 before settling on //:).

Fortunately, there's another trick that works for this purpose, as Diego Perini discovered: skip this ugly script element, and poll to check if document.body.doScroll() is working yet. The downside is that because it requires a timer to call doScroll() every X milliseconds, you won't be notified of the DOM's readiness quite as quickly as when using the "//:" hack (whereby you listen for onreadystatechange events on the script element — so you're notified of the readiness immediately, rather than having to poll for it).

The upside, of course, is that the doScroll() method works more reliably, with no "1 item remaining" for which to wait indefinitely. Another problem I had noticed in the past with the script element method was that IE sometimes would fire the onreadystate complete event before the DOM was loaded (and I had worked around that by checking if the page's footer element existed, and if not, waiting until it did). I haven't found any holes with this doScroll() technique yet, though, so I'm happy so far.

The 1.6.x version of the Prototype JavaScript Framework used the script technique (and I'm sure other libraries have, or still do); the 1.7 version has been updated with the doScroll() method. Here's the specific code it uses for the doScroll() technique (I've added comments where it uses Prototype-specific code, in case someone wants to adapt it to a non-Prototype environment):

(function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ var timer; function fireContentLoadedEvent() { if (document.loaded) return; if (timer) window.clearTimeout(timer); document.loaded = true; // raise the Prototype dom:loaded function // (your custom ondomcontentloaded code goes here) document.fire('dom:loaded'); } function checkReadyState() { if (document.readyState === 'complete') { // Prototype for removeEventListener()/detachEvent() document.stopObserving('readystatechange', checkReadyState); fireContentLoadedEvent(); } } function pollDoScroll() { try { document.documentElement.doScroll('left'); } catch(e) { // Prototype for setTimeout() timer = pollDoScroll.defer(); return; } fireContentLoadedEvent(); } if (document.addEventListener) { document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); } else { // Prototype for addEventListener()/attachEvent() document.observe('readystatechange', checkReadyState); if (window == top) // Prototype for setTimeout() timer = pollDoScroll.defer(); } // Prototype for addEventListener()/attachEvent() Event.observe(window, 'load', fireContentLoadedEvent); })();

5 comments:

  1. "with no "1 item remaining" for which to wait indefinitely"

    Could you elborate why the inclusion of a script tag with ths rc set to //: could hang the page load indefinately?

    Thanks in adavnce.

    ReplyDelete
  2. If IE never fires the onreadystatechange=complete event for that script element (with src=//:), then any javascript you've setup to execute when that event is fired will never execute; and if IE never fires the window onload event (because it thinks it's still loading that script element), then any javascript you've setup to execute on window load will never execute. So the parts of the page that you've setup to render or become interactive (by attaching click events etc) when those events fire will be missing or inoperable -- from you users' perspective, the page is hanging.

    ReplyDelete
  3. Thanks for the reply Justin. That makes sense.When you experienced this issue were you ever able to recreate it reliably or was it just observed? I have 99% sure i have the same issue and have been able to recreate it but not reliably i.e. Since this is a defered external header script, it appears it should be executed according to the sequence outlined here

    http://www.iecustomizer.com/msmvp/jsdefer.htm

    So what you are syaing is that at times IE will look at the markup and start processing it then caught hung up at loading that script element? i.e. it never parses the line after that?

    ReplyDelete
  4. My recollection is that it happened on like 1/4 of the page loads on this particular client's machine (never was able to reproduce it anywhere else). IE wasn't hanging processing the markup -- it rendered the static parts of the page -- it just wasn't firing the onreadystatechange event for that script (nor the window load event).

    ReplyDelete
  5. Thanks Justin.
    My issue is that nothing is rendered whatsoever. I'm using the ExtJs 2.0 library which dynamically creates this type of script element very early in the header. On all the pages that do not load, i see this deferred script element in the IE interpreted markup but as it's an externel defered script in the header it should be defered till after the body has been parsed. Very unusual. Thanks for answering my questions here.

    ReplyDelete