IEではRequireJS+jQueryで$(document).ready()で設定したcallbackが実行されないことがある&その対応
とりあえずRequireJS0.14.5+jQuery1.4.3,RequireJS0.15.0+jQuery1.4.4で発生。
IEは6~8っぽい。
なんで気づいたかというと
- IEでjQueryUI等の動作がおかしくなった
- CSS Box Modelがサポートされている状態なのに$.boxModel(=$.support.boxModel)がtrueでない
- falseですらなくundefinedだった
- $.boxModelの検出はjQuery(function(){})内で行われていた。
- $(document).ready()と同じ
検証
対象:RequireJS0.15.0+jQuery1.4.4
環境:IE7(+CompanionJS)
今、仕事で作ってるアプリで使っているallplugins-require.jsとjquery-1.4.4.jsに
以下のようにconsole.logを追加した。
後、仕事で作っているコードの中のrequire.ready callbackの頭に、
console.log('require.ready');というlog出力を追加した。
jQueryについてはjQuery.readyの中でlog出力し、
後はtimerやevent経由でjQuery.readyを呼んでいる箇所について、
どこで設定されたものが呼ばれたか分かるようにlog出力した。
$(document).ready()で設定されたcallbackは、
jQuery.readyによって実行されるが、準備が出来ていないと判断される場合は実行されない。
jQuery.readyWaitが0でjQuery.isReadyがtrueの場合が準備できている状態である。
そして、RequireJSは内部でjQuery.readyWaitを増減するので、
その箇所でlog出力をするようにした。
diff -r 4ba54b5cad32 static/js/jquery-1.4.4.js
--- a/static/js/jquery-1.4.4.js Wed Nov 17 15:35:04 2010 +0900
+++ b/static/js/jquery-1.4.4.js Wed Nov 17 20:29:59 2010 +0900
@@ -413,6 +413,7 @@
// Handle when the DOM is ready
ready: function( wait ) {
+ console.log('jQuery.ready:'+jQuery.readyWait+','+jQuery.isReady);
// A third-party is pushing the ready event forwards
if ( wait === true ) {
jQuery.readyWait--;
@@ -454,7 +455,7 @@
}
}
},
-
+
bindReady: function() {
if ( readyBound ) {
return;
@@ -466,7 +467,10 @@
// browser event has already occurred.
if ( document.readyState === "complete" ) {
// Handle it asynchronously to allow scripts the opportunity to delay ready
- return setTimeout( jQuery.ready, 1 );
+ return setTimeout( function(){
+ console.log('jQuery.ready document.readyState === "complete"');
+ jQuery.ready();
+ }, 1 );
}
// Mozilla, Opera and webkit nightlies currently support this event
@@ -475,7 +479,10 @@
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
// A fallback to window.onload, that will always work
- window.addEventListener( "load", jQuery.ready, false );
+ window.addEventListener( "load", function(){
+ console.log('jQuery.ready window.onload');
+ jQuery.ready();
+ }, false );
// If IE event model is used
} else if ( document.attachEvent ) {
@@ -484,7 +491,10 @@
document.attachEvent("onreadystatechange", DOMContentLoaded);
// A fallback to window.onload, that will always work
- window.attachEvent( "onload", jQuery.ready );
+ window.attachEvent( "onload", function(){
+ console.log('jQuery.ready() window.onload');
+ jQuery.ready();
+ } );
// If IE and not a frame
// continually check to see if the document is ready
@@ -901,6 +911,7 @@
}
// and execute any waiting functions
+ console.log('jQuery.ready() doScrollCheck');
jQuery.ready();
}diff -r 4ba54b5cad32 static/js/allplugins-require.js
--- a/static/js/allplugins-require.js Wed Nov 17 15:35:04 2010 +0900
+++ b/static/js/allplugins-require.js Wed Nov 17 20:29:34 2010 +0900
@@ -704,6 +704,7 @@
if (context.scriptCount) {
$.readyWait += 1;
context.jQueryIncremented = true;
+ console.log('jQueryCheck :' + $.readyWait + ',' + $.isReady);
}
}
}
@@ -953,6 +954,7 @@
if (context.jQuery && !context.jQueryIncremented) {
context.jQuery.readyWait += 1;
context.jQueryIncremented = true;
+ console.log('req.load :' + $.readyWait + ',' + $.isReady);
}
}
}
@@ -1679,6 +1681,7 @@
if (context.jQueryIncremented) {
context.jQuery.readyWait -= 1;
context.jQueryIncremented = false;
+ console.log('req.callReady :' + $.readyWait + ',' + $.isReady);
}
}
}
結果
Firefox3.6+Firebug1.5の場合
jQueryCheck :2,false jQuery.ready:2,false require.ready req.callReady :0,true jQuery.ready window.onload jQuery.ready:0,true
- jQueryCheckが呼ばれてreadyStateがインクリメントされる
- (中略)
- require.ready callbackを呼び出し終えてreq.callReadyがデクリメントされ0になる
- window.onloadのタイミングでjQuery.readyが呼ばれる
- $(document).ready()で設定したcallbackが実行される
IE7+CompanionJSの場合
Console [332]= jQueryCheck :2,false Console [333]= jQuery.ready:2,false Console [334]= jQuery.ready() window.onload Console [335]= jQuery.ready:1,true Console [336]= require.ready Console [337]= req.callReady :0,true
- jQueryCheckが呼ばれてreadyStateがインクリメントされる
- (中略)
- window.onloadのタイミングでjQuery.readyが呼ばれる
- readyState≠0なので、$(document).ready()で設定したcallbackが実行されない
- require.ready callbackを呼び出し終えてreq.callReadyがデクリメントされ0になる
jQueryによってjQuery.readyが呼ばれる最後のタイミングはwindow.onloadなので、
$(document).ready()で設定したcallbackは何時まで経っても実行されない。
フィードバックとか
仕事のコード抜きで再現させられたらしようかと。
とりあえずの対策
require.ready callbackとreq.callReadyの実行は同一のrun loopなので、
以下のような内容のJavaScriptをrequireしてやれば良い。
(function () { var windowLoaded = false; $(window).bind('load',function(){ windowLoaded = true; }); require.ready(function() { if ($.readyWait !== 0 && windowLoaded) { setTimeout(function() { $.ready(); }); } }); })();
Console [360]= jQueryCheck :2,false Console [361]= jQuery.ready:2,false Console [362]= jQuery.ready() window.onload Console [363]= jQuery.ready:1,true Console [364]= require.ready Console [365]= req.callReady :0,true Console [366]= jQuery.ready:0,true