Bug Tracker

Modify

Ticket #223 (closed bug: worksforme)

Opened 8 years ago

Last modified 2 years ago

IE6 jQuery.ready bug in jQuery Rev. 249 (released 8/31/06)

Reported by: Josh Owned by:
Priority: major Milestone: 1.0
Component: core Version: 1.0
Keywords: IE6 ready hack Cc:
Blocking: Blocked by:

Description

IE6 (unreliably) generates the following error message at line 1200 of the latest unpacked version of jQuery:

'null' is null or not an object

See line 1200:

script.onreadystatechange = function() {

if ( this.readyState != "complete" ) return; this.parentNode.removeChild( this ); jQuery.ready();

};

The error results from a failure on the previous line:

var script = document.getElementById("ie_init");

This leaves the script variable as null. I was able to eliminate the error with the following:

if (script != null) {

script.onreadystatechange = function() {

if ( this.readyState != "complete" ) return; this.parentNode.removeChild( this ); jQuery.ready();

};

}

Unfortunately this means the script has to fallback on the window.onload event, which is obviously undesirable on slow-loading pages. The flaw seems to be in Matthias Miller's hack. There is also a discussion on the hack itself and the need for it, as referenced in the jQuery source code:

 http://www.outofhanwell.com/blog/index.php?title=the_window_onload_problem_revisited

Obviously this hack is not ideal since it uses the deprecated document.write method, but then, neither is IE.

Change History

comment:1 Changed 8 years ago by Josh

I should add in case it's not obvious from the js code... The function will only have to fallback on window.onload on the occasion where it would have otherwise failed and given the is null or not an object? error.

comment:2 Changed 8 years ago by brandon

I haven't been able to reproduce this.

comment:3 Changed 8 years ago by richard.chri

The bug is worse than that. JQuery has a very nasty concurrency issue with IE which (bless its 100% polyester Micro$oft $ock$) actually uses real execution threads when handling events. This is almost certainly the case with other broswers too, but I was unable to reproduce this - possibly due to differing threading models.

There is a very nasty concurrency issue which I discovered when experimenting with thickbox

namely, when thickbox does

$(document).ready(TB_init);

something else may be calling Ready() at the same time resulting in the function (in this case TB_init getting pushed onto a queue that never gets pop'd again.

Sadly Javascript being a totally brokenn language has very little support for thread identifications (what's a thread anyway?) - no way of getting thread id's. No sleep function. No mutexes.

I was half way through trying to implement some hack for this when I thought about googling... Fortunately, I came across some rather nice code for implementing a busy-wait polling mechanism for mutual exclusion on the web. Obviously without 'sleep' there's no other way to do it.

original code and article can be found at:

 http://www.developer.com/lang/jscript/article.php/3592016

I hacked this up so as to do the following:

added mutex code:

// Mutex stuff...

var NEXT_CMD_ID = 0; 

function Map() { 
  this.map  = new Object(); 
  // Map API 
  this.add      = function(k,o){ this.map[k] = o;    } 
  this.remove   = function( k ){ delete this.map[k]; } 
  this.get      = function( k ){ return k==null ? null : this.map[k]; } 
  this.first    = function(   ){ return this.get( this.nextKey( ) ); } 
  this.next     = function( k ){ return this.get( this.nextKey(k) ); } 
  this.nextKey  = function( k ){
    for (i in this.map) {
      if (!k) return i; 
      if (k==i) k=null; /*tricky*/ 
    } 
    return null; 
  }
}

function Mutex( cmdObject ) { 
  // define static variable and method 
  if (!Mutex.Wait) Mutex.Wait = new Map(); 
  Mutex.SLICE = function( cmdID, startID ) { 
    Mutex.Wait.get(cmdID).attempt( Mutex.Wait.get(startID) ); 
  } 
  // define instance method 
  this.attempt = function( start ) { 
    for (var j=start; j; j=Mutex.Wait.next(j.c.id)) { 
      if (j.enter || (j.number && (j.number < this.number || 
           (j.number == this.number && j.c.id < this.c.id) ) ) ) 
       return setTimeout("Mutex.SLICE("+this.c.id+","+j.c.id+")",10); 
    } 
    this.c.go(); //run with exclusive access 
    this.number = 0;           //release exclusive access 
    Mutex.Wait.remove( this.c.id );
    return null;
  } 
  // constructor logic 
  this.c        = cmdObject; 
  Mutex.Wait.add( this.c.id, this ); //enter and number are “false” 
  this.enter    = true; 
  this.number   = (new Date()).getTime(); 
  this.enter    = false; 
  this.attempt( Mutex.Wait.first() ); 
} 

function MutexCommand( f, args ){ 
        this.id = ++NEXT_CMD_ID;
        this.result = null;
        this.go = function(){
                try { f( args ); }
                catch ( e ) { alert( e.message() ); }
        } 
}

then changed the ready(f) functions to the following - it could be done more neatly, but this was just an example (similar changes were made to wrap the ready() function:

ready: function(f) {
    //alert( "creating 1" );
    var foo = function(args) {
     //   alert( "callback 1 fired" );
        var jq = args.jq;
        var pass_func = args.f;
        // If the DOM is already ready
        if ( jq.isReady ) {
            // Execute the function immediately
            f.apply( document );
        }
        // Otherwise, remember the function for later
        else {
            // Add the function to the wait list
            jq.readyList.push( pass_func );
        }
    }
    var args = new Object();
    args.f = f;
    args.jq = jQuery;
    //alert( "passing 1" );
    new Mutex(new MutexCommand( foo, args ) );
    return this;

}

Finally, the document.write hack is bad (tm). Sure it's cute in a nasty sort of way, and kudos to the original author. (IE's utter uselessness drives me up the wall so often I would love to blow redmond up, and half the users of the internet for using it ;) but I digress... ) However, it really doesn't work nicely if jquery.js is itself dynamically written to the DOM or even (again) with concurrency issues. Much better (IMHO) is to use the same timer type hack as for safari (and fall back on the onload method). Certainly with IE 6, this works fine.

The other thing to watch out for though is that (at least on IE 6 and with dynamic inclusion), IE will give back a readyState of "loaded" rather than complete. Thus the original code for IE's hack becomes more simply (if not more nicely) :

    } else if ( jQuery.browser.msie ) {        
        var func = function() {
                if ( document.readyState == "complete" || document.readyState == "loaded" ) {
                    jQuery.ready();
                } else {
                    window.setTimeout = setInterval( func, 200 );
                }
            };
        window.setTimeout( func, 200 );

Again, this could be done better, but I've had issues with setInterval in IE before. Since I (i.e. clients) don't care about IE versions < 6 nowadays, this suits me. :)

comment:4 Changed 8 years ago by richard.chri

I should add that the "null" error the orginal bug was raised against is a symptom of the document.write hack not working. It can be easily reproduced in IE 6 by simply dynamically including jquery.js (say via a DOM write method after the page has already loaded, rather than by AJAX and eval) and watching IE barf with the mentioned error. The 'script' comes out null.

Incidentally, my patches were against 1.0.2 (svn rev 413) of the code. I also did some clean-up to remove the voluminous amount of warnings/fake-errors given by firefox's javascript console when running through the functions - but I don't think those affected any of the code snippets shown. Whilst they don't really matter, it would be nice if someone code run using firefox and just clean those up properly. :)

comment:5 Changed 8 years ago by Josh

To lend some weight to Richard's last statement, I definitely should have mentioned in my original bug report that I was dynamically adding jquery, and then subsequent javascript, to the DOM.

In addition to the small fix I mentioned, I have since decided that the added html simplicity is not worth the added bugs, and am migrating all of my javascript includes to the main html doc rather than doing it on the fly from a single global javascript.

Now to put my thinking cap on and re-read the longer post above with the javascript hacks...

comment:6 Changed 7 years ago by joern

  • Status changed from new to closed
  • Resolution set to worksforme

Please correct me if I got it wrong, but to summarize this: If you include jQuery dynamically, you can't rely on jQuery DOM ready functionality. "Fixing" this causes way too much problems, therefore: If you really need the dynamic inclusion, find something else for the DOM ready event.

Please follow the  bug reporting guidlines and use  jsFiddle when providing test cases and demonstrations instead of pasting the code in the ticket.

View

Add a comment

Modify Ticket

Action
as closed
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.