Ticket #6165 (new defect)
Calling dojo.connect within event fires event in Internet Explorer
| Reported by: | guest | Owned by: | sjmiles |
|---|---|---|---|
| Priority: | normal | Milestone: | 1.4 |
| Component: | Events | Version: | 1.1b1 |
| Severity: | normal | Keywords: | |
| Cc: | simon@… |
Description
email:simon@cambridgesemantics.com
This bug manifests itself in all versions of dojo including the current version in trunk.
The example below illustrates the problem. Firefox will work perfectly. Internet Explorer will go into an infinite loop.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Testing dojo.connect</title>
<style type="text/css">
@import "../resources/dojo.css";
@import "../../dijit/themes/tundra/tundra.css";
</style>
<script type="text/javascript"
src="../dojo.js" djConfig="isDebug: true"></script>
<script type="text/javascript">
var obj2 = {
handler : function() {
dojo.disconnect(this.handle);
obj1.handle = dojo.connect(objEventRaiser, 'func', obj1, 'handler')
}
};
var obj1 = {
handler : function() {
dojo.disconnect(this.handle);
obj2.handle = dojo.connect(objEventRaiser, 'func', obj1, 'handler')
}
};
var objEventRaiser = {
func : function() {}
}
obj1.handle = dojo.connect(objEventRaiser, 'func', obj1, 'handler')
function go() {
objEventRaiser.func();
}
</script>
</head>
<body class="tundra">
<button onclick="go()">Go</button>
</body>
</html>
The code above works perfectly in firefox but causes an infinite loop in Internet Explorer. This is caused by different behavior of a for( in ) loop in Internet Explorer. The code for getDispatcher in connect.js has a for (in) loop. If an item is added to the c._listeners array (using dojo.connect) whilst the for in loop is executing then that member is added to the array currently being looped over and therefore causing the event which has just been connected to to fire.
getDispatcher: function(){
// following comments pulled out-of-line to prevent cloning them
// in the returned function.
// - indices (i) that are really in the array of listeners (ls) will
// not be in Array.prototype. This is the 'sparse array' trick
// that keeps us safe from libs that take liberties with built-in
// objects
// - listener is invoked with current scope (this)
return function(){
var ap=Array.prototype, c=arguments.callee, ls=c._listeners, t=c.target;
// return value comes from original target function
var r=t && t.apply(this, arguments);
// invoke listeners after target function
for(var i in ls){
if(!(i in ap)){
ls[i].apply(this, arguments);
}
}
// return value comes from original target function
return r;
}
},
One solution would be to copy the contents of the array and loop using those - something like this
getDispatcher: function(){
// following comments pulled out-of-line to prevent cloning them
// in the returned function.
// - indices (i) that are really in the array of listeners (ls) will
// not be in Array.prototype. This is the 'sparse array' trick
// that keeps us safe from libs that take liberties with built-in
// objects
// - listener is invoked with current scope (this)
return function(){
var ap=Array.prototype, c=arguments.callee, ls=c._listeners, t=c.target;
// return value comes from original target function
var r=t && t.apply(this, arguments);
// invoke listeners after target function
// copy ls to a new array so that if a member is
// removed whilst in the loop it does not cause an issue in IE
var _ls = [].concat(ls);
for(var i in ls){
if(!(i in ap)){
ls[i].apply(this, arguments);
}
}
// return value comes from original target function
return r;
}
},