Flash Player Bug: Removing an event listener in another listener for the same type fails

by Josh Tynjala

Update: My bug report has been marked Not a Bug by Adobe. As Joeri mentions in this post’s comments, and Charles Liss explains in the bug report comments, Flash Player makes a copy of the event listeners internally when dispatchEvent() is called. It’s by design that a call to removeEventListener() will affect the original list of listeners, but it will not affect the copied list. I followed up with a request to describe this behavior in the API documentation so that others who run into the same weird behavior might have a better chance of learning why Flash Player seems to be behaving incorrectly.

I’ve been struggling off and on for days trying to figure out why a certain display object that I’d been fading in and out sometimes got stuck with partial visibility. Even when I explicitly set the alpha value to 0.0 or 1.0 after removing the animation and all its listeners, there was still the occasional situation where it would clearly receive a different value. I finally tracked down the following bug in Flash Player. If two functions are subscribed to the same event type from the same event dispatcher, and the first listener removes the second listener, the second listener is still called by Flash Player.

The following class demonstrates the problem.

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.EventDispatcher;

	public class EventErrorTest extends Sprite
	{
		public static var dispatcher:EventDispatcher = new EventDispatcher();

		public function EventErrorTest()
		{
			dispatcher.addEventListener(Event.COMPLETE, dispatcherComplete1Handler);
			dispatcher.addEventListener(Event.COMPLETE, dispatcherComplete2Handler);
			dispatcher.dispatchEvent(new Event(Event.COMPLETE));
			dispatcher.dispatchEvent(new Event(Event.COMPLETE));
		}

		private function dispatcherComplete1Handler(event:Event):void
		{
			dispatcher.removeEventListener(Event.COMPLETE, dispatcherComplete2Handler);
			trace("1");
		}

		private function dispatcherComplete2Handler(event:Event):void
		{
			trace("2");
		}
	}
}

The expected output is as follows:

1
1

Instead, my output console displays this instead:

1
2
1

Clearly, the second listener is still called the first time the event is dispatched. If you move the call to removeEventListener() to the line before the first call to dispatchEvent(), the output appears as expected.

I discovered this bug while using Grant Skinner’s excellent GTween library for AS3 in the current game I’ve developing. In my game, certain animations may be paused following the completion of other animations. Since (internally) all GTween instances listen to a static timing object, pausing a GTween inside an event listener attached to another GTween causes the supposedly-paused GTween to update the target’s properties one last time.

If you happen to be using GTween, and you encounter this same issue, adding the following line at the beginning of handleTick() in GTween.as should fix the problem:

if (_paused) return;

This event bug has been filed in Adobe’s public Flash Player database. You’re welcome to vote for it.

About the Author

Josh Tynjala is an indie game developer, entrepreneur, Flash and Flex mercenary, and bowler hat enthusiast.

Discussion
  1. I don’t think this is a bug. When an event is being dispatched, the event dispatcher makes a copy of the event listeners list. So this list can’t be changed during the dispatch of an event. Removing listeners won’t do a thing.
    The only way (or at least one way) controlling this is to use the ‘stopPropagation()’ and ‘stopImmediatePropagation()’ calls to the event self.

    posted by Joeri van Oostveen on 04.01.2009
  2. Joeri, I considered that possibility, and I assume that’s probably how Flash Player is implemented. Even considering that, I still argue that it’s behaving incorrectly. If I remove an event listener before it is called, then I should not receive that event. I don’t care if I do it in another event listener or anywhere else. This implementation opens up a can of worms that only the most experienced developers will be able to easily understand. Honestly, who considers the possibility that an event might still be received after you’ve explicitly called removeEventListener()? Only the most superstitious coders, in my opinion.

    posted by Josh Tynjala on 04.01.2009
  3. But that’s the thing. You’re NOT removing it before the other event listener is being called. Like Joeri said, it’s a propagation stream, and in order to interrupt it (like what you are trying to do) is why they have stopPropagation() in the first place. They already solved for your problem, you just don’t like the solution.

    posted by Gabriel Mariani on 04.01.2009
  4. I also believe this is working as intended… I understand the confusion, but it’s not really a Flash Player bug.

    I’d recommend setting the priority of the event listener you want to capture first higher than the others, and then use event.stopImmediatePropagation() in your listener to stop it from reaching subsequent listeners in the event chain.

    posted by Elliot Chong on 04.01.2009
  5. Thanks for the comments, folks. Personally, I’m still not convinced. For the record, I have no problem with event propagation and priorities. I use these features often. What’s interesting is that you all make this suggestion to use them, but Grant probably won’t be able to do that in GTween. He has an arbitrary number of event listeners where the only way to calculate a useful priority is after removing one of the listeners (and unless I’m wrong, you can’t change priorities at that point!). My GTween workaround above will probably be enough, but it certainly feels like more like a hack than a fix.

    posted by Josh Tynjala on 04.01.2009
  6. “pausing a GTween inside an event listener attached to another GTween causes the supposedly-paused GTween to update the target’s properties one last time”

    If you place a stopImmediatePropagation() immediately after your pause statement for a tween, then priority should be more or less irrelevant in that instance. The final value will still be the the one set right when you paused it, as nothing will be updated after that.

    I’m unsure of when GTween would need any different behavior, as developers are tasked with assigning the listeners for the tween’s updates, completes, etc. Could you give an example for when a developer using GTween wouldn’t be able to use this method effectively?

    posted by Elliot Chong on 04.02.2009
  7. Elliot, calling stopImmediatePropogation() on a GTween event won’t help at all. That will stop one GTween instance from updating further listeners, but it will have no effect on other GTween instances. If A is paused during B‘s event, A will still receive the "tick" event from the internal timing mechanism and update one last time. Stopping propagation on a GTween instance’s events does not stop propagation of events from this timer, and it definitely shouldn’t because that could affect instances that have not been paused.

    posted by Josh Tynjala on 04.02.2009
  8. “When an event is being dispatched, the event dispatcher makes a copy of the event listeners list. So this list can’t be changed during the dispatch of an event.”
    As a developer, how would I possibly know this (without reading your post). If the event list is locked during event dispatching, I would at least expect some kind of exception to be thrown to let me know that I’m doing something wrong by trying to modify the list. Also, if the list can’t be changed during dispatch, wouldn’t the output be:
    1
    2
    1
    2

    posted by adampasz on 04.05.2009
  9. Never mind my second question. I see that the event is dispatched from the *copy* of the list, but removeEventListener operates on the original list.

    posted by adampasz on 04.05.2009
  10. Hey Josh,

    The reason the event is still caught by dispatcherComplete2Handler is because this is Object Orientated Programming.

    If you have mainly done procedural programming, you would expect the first handler to remove the second handler’s listener… in OOP, both objects hear the event at the same time – so by the time you have removed the listener, it is too late.

    Hope this helps out.
    Dave.

    posted by David on 09.07.2009
  11. Hi David

    “in OOP, both objects hear the event at the same time –”

    I personally dont believe this statement is true, Actionscript is a single threaded stack based language , thus everything is sequenced according to the stack. Its simply as others have commented, a matter of the API (event.stopPropagation() , event.stopImmediatePropagation() ) been the entry points into event stack manipulation. I side with Josh in that its a matter of how flash is built and im sure it could be possible to engineer flash to update the copied list via interpreting some flag set by the removeEvent operation with each iteration of proccessing the listeners list.

    posted by ian pretorius on 11.19.2009
Share Your Thoughts

To display code in comments: <pre>Code here. May be multiline. Format XML with &gt; and &lt; entities.</pre>

Some HTML allowed in comments: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>