Sunday, July 12, 2009

Unloading external content doesn't work right

Flash conveniently dictates the use of Loader class to load external files, including SWFs. But what the developers ignored was how the SWF unloading takes place...in theory it doesn't. Besides being an irritation, this can also be exploited as a security flaw.

Symptoms
Loading, then unloading a SWF - with an embedded clip or some sound playing - still keeps that sound going regardless of NO VISIBLE instance of the clip anywhere on the stage or in the memory. In addition (if you check the memory consumption), repeated loads and unloads increase the memory consumption in direction relation to the size of the files being loaded.

Diagnosis
What happens here is memory mismanagement on part of the AVM2 (Actionscript Virtual Machine 2, that was built for Actionscript 3). Dig deeper, and its the garbage handling mechanism at fault. Much of this has to do with what the unload function does. Here's how the story goes:
  1. You write ActionScript 3 code to load/unload the SWF. (Yes, this bug is AS3-specific)
  2. Flash lets loaded child SWFs communicate with the parent container. Hence the events that the SWF monitors are linked to the main timeline. ENTER_FRAME and TIMER events are the lethal events here. [keep that in mind]
  3. Unload() simply removes a reference to the loaded content, and leaves the rest for the garbage collector.
  4. But the garbage collector works on the principle that a resource will be removed only when it has no references. Because the SWF is still linked to the main timeline, it stays in the memory. The SWF - technically - keeps playing.
  5. As a result of the SWF still out there, any sounds that the SWF had been playing continue to play; alongside, your movie file size bloats up because the garbage stays there in the garbage box (and stinks it up).

With nothing but the Flash IDE and its accompanying manual, one can get stuck for hours.

Prognosis (or the future course of events that you take)
There is no straight solution to this. Nothing can be done to fix the issue. Moreover, nothing on part of the parent swf can help (with a trivial exception).
  • Calling SoundMixer.stopAll() method in the parent SWF: This will stop all sounds playing. This is rarely helpful, since there will be other sounds playing as well.

  • Remove select event listeners: Open the FLA file of the one that's being loaded, and remove any ENTER_FRAME or Timer event listeners. These are the ones that communicate with the parent movie.

  • Clean Up on Event.UNLOAD: When unload() is called on an SWF loaded through the Loader component, an 'unload' event is fired. Use this to clean up your child SWF...remove/delete all (dynamic) variables, movie clips, event listeners and timers. Stop any sounds. Stop the advance of the main timeline. Stop any external content from loading (Loader, URLLoader, Socket). This is your best bet.



  • Some enlightening documentation of this bug can be found here, here.