Jay Harris is Cpt. LoadTest

a .net developers blog on improving user experience of humans and coders
Home | About | Speaking | Contact | Archives | RSS
 
Filed under: Flash

All right. As mentioned before, here it is: I found a bug in mx.managers.DepthManager in Flash 8, though I’m sure it affects all ActionScript 2.0 versions of the DepthManager to date. The bug involves a lack of error handling when DepthManager encounters an object without a depth, or rather, without a getDepth() method. It shows up through any use of the DepthManager’s methods, the PopUpManager (mx.managers.PopUpManager), the Alert class (mx.controls.Alert), etc., when an object on the stage does not have a getDepth function.

What Happens?
The issue manifests itself in many variations, usually by objects placed with the DepthManager inexplicably replacing previous objects placed with the DepthManager. Most notably, it manifests itself as modal windows (via PopUpManager) and Alerts not rendering to the screen, even though the modality seems to have worked and all other objects on the screen no longer can receive input.

Why does this happen?
The DepthManager works off a rather inefficient array to maintain its records. An object within the DepthManager, the DepthTable, is an indexed array containing record of every object on the stage and its depth. The DepthManager stores a reference to the object within the array at the position corresponding to the depth of the object. If the stage has one object at a depth of 32,000, depthTable[32000] = myObject, and we have a 32,000-length array with one object stored in it [an inefficiency]. Values in the manager’s depth table are based on available positions in the array, such as kTopmost being greater than the last movieclip or object in the array [it iterates one-by-one through each position in our 32000-length array; another inefficiency].

Excerpt from mx.managers.DepthManager.as

var t:String = typeof(i);
if (t == “movieclip” || (t == “object” && i.__getTextFormat != undefined))
if (i._parent == this) {
depthTable[i.getDepth()] = i;
}

However, the DepthManager code is assuming that the object has a getDepth function. If the object does not have the function, references to that function return undefined, setting depthTable[undefined] = i, which blows chunks all over the place, and any reference to any of DepthManager’s depth-returning methods will always return zero.

That zero is why nothing works anymore; the Alert class creates the new Window-class-like object, then creates a transparent movieclip below that object to trap all inputs, creating a sense of modality. The Alert class uses the DepthManager to create itself at kTopmost, which DepthManager says is zero. The Alert class then uses the DepthManager to create the modality movieclip above itself, kTopmost, which it then hopes to swapDepth with to get itself back above the modality clip. However, the Alert class, with itself at depth zero, creates the modality clip at depth zero because DepthManager said it was the kTopmost depth. Creating an object at depth zero toasts any object already at depth zero, thus the modality clip blows away the Alert clip and we get a SWF that no longer responds to anything, and no Alert ‘OK’ button to restore peace in the world. Likewise happens when a modal window is created through the PopUpManager.

So what can I do about it?
How do you get around this? Simple. Don’t put anything on the stage that extends from Object. If you put it on the stage, extend it from MovieClip. Even though the DepthManager code (above) explicitly calls getDepth on MovieClip or Object, it does not check to see if the object in question actually has a getDepth method. Object does not have getDepth by default, but MovieClip does. I suppose you could write your own getDepth function for your Object-inheriting class, or you could rewrite the DepthManager (which would mean also rewriting PopUpManager, Alert, ComboBox, and a half-dozen other controls), but it is much simpler to extend from MovieClip.

Why it happened to me
I have a movie that is 170KB. There’s a lot of crazy mojo in there, and I want my prel0ader to load within the first 1KB, not after 170KB. So, I created my preloader, removed “Export in First Frame” from everything, and set my classes to export in frame 5 (after the end of my preloader loop). However, this made a few of my Object-extending components no longer work anymore since they were not referenced in the “Assets” layer of any of the other components (no reference=no load, much like the DataBindingClasses), to I had to drag them to the stage to ensure that they loaded.

*Poof*

My world exploded.

I lost two days of work tracking this little punk down. And you already know the rest of the story.

Thursday, February 8, 2007 10:23:12 PM (Eastern Standard Time, UTC-05:00)  #    Comments [0] - Trackback