Ticket #6165 (new defect)

Opened 5 months ago

Last modified 3 months ago

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;
		}
	},

Change History

Changed 4 months ago by bill

  • milestone set to 1.4

Hmm, it's a pretty obscure case to call dojo.connect() within an event handler for the thing you are connecting to but anyway Scott should either set a milestone for this or mark as "wontfix". I'll temporarily set the milestone to 1.4.

Changed 3 months ago by guest

It is a bug that we are experiencing and means that we have to patch our dojo source tree for each dojo release. The only way we could think to work around this bug (without it being fixed in the dojo source tree) is to use a setTimeout to hook the event once it has returned from from the event - this seems like a less than ideal solution.

Note: See TracTickets for help on using tickets.