I have discovered a nasty bug with Flash CS5 and a TLF TextField in a loaded swf.
If you use TLF Engine in a loaded swf, you cannot call a public function of a document class (runtime error function doesn't exist), you cannot cast the swf as its document class or cast it as an interface the document class implements (both cause a coercion error).
It's an extremely common use case. I need one swf to load another swf and I need that other swf to have TLF TextFields in it, and I also need to call public functions on my swf, or cast the loader.content as the proper class or interface.
You can download a trivial example here:
TLF_Fail.zip
In the zip, there are two .fla files, main.fla and other.fla, each with a document class. The other.fla has a TLF TextField on its stage and its document class has a public function customFunc() that traces that it was called. The Main document class creates a loader, loads the other.fla and when the COMPLETE event fires, it tries to call the public function or tries to cast the document class as the document class or cast the document class as its interface. Each of these causes an error.
Main.as
package
{
import flash.display.Loader;
import flash.display.MovieClip;
import flash.events.Event;
import flash.net.URLRequest;
public class Main extends MovieClip
{
private var loader:Loader;
public function Main()
{
super();
init();
}
private function init():void
{
addChild(loader = new Loader());
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
loader.load(new URLRequest("other.swf"));
}
private function onLoadComplete(event:Event):void
{
// this says that there's no such function on the __Preloader__
MovieClip(loader.content).customFunc();
// this causes a coercion error
//Other(loader.content);
// this causes a coercion error
//ICustom(loader.content).customFunc();
}
}
}
Other.as
package
{
import flash.display.MovieClip;
import fl.text.TLFTextField;
public class Other extends MovieClip implements ICustom
{
public var TLF_Foo:TLFTextField;
public function Other()
{
super();
}
public function customFunc():void
{
trace("custom func called");
}
}
}
ICustom.as
package
{
public interface ICustom
{
function customFunc():void;
}
}
Here are the error messages:
MovieClip(loader.content).customFunc();
ReferenceError: Error #1069: Property customFunc not found on Other__Preloader__ and there is no default value.
at Main/onLoadComplete()
Other(loader.content);
TypeError: Error #1034: Type Coercion failed: cannot convert Other__Preloader__@23c88121 to Other.
at Main/onLoadComplete()
ReferenceError: Error #1056: Cannot create property __rslLoaders on Other.
at fl.rsl::RSLPreloader/contentComplete()
ICustom(loader.content);
TypeError: Error #1034: Type Coercion failed: cannot convert Other__Preloader__@23c98121 to ICustom.
at Main/onLoadComplete()
There are tens of thousands of developers worldwide who have a big stake in this issue being resolved because the Gaia Framework casts all of its pages as interfaces and calls functions on them and this bug makes it so developers cannot use the Gaia Framework with the TLF Engine.
Adobe, please fix this issue.
UPDATE 1
I decided to run a test to see how long before the loader.content is my swf and not Adobe's TLF Preloader. It never happens.
private var count:int;
private function onLoadComplete(event:Event):void
{
addEventListener(Event.ENTER_FRAME, onFrame);
}
private function onFrame(event:Event):void
{
trace(++count + ": " + loader.content);
}
Here's the output:
1: [object Other__Preloader__]
2: [object Other__Preloader__]
3: [object Other__Preloader__]
4: [object Other__Preloader__]
5: [object Other__Preloader__]
...
89: [object Other__Preloader__]
90: [object Other__Preloader__]
91: [object Other__Preloader__]
When you load a swf that has TLF inside it, the loader.content is never your swf.
UPDATE 2
If you go into the Actionscript settings of the swf using the TLF Engine and changed the Runtime Shared Library Settings Default linkage to "Merged into code" the errors go away. But, the problem with this workaround is that the swf increases in size by 125k.
This means that for enterprise sites that use multiple swfs, every swf that uses TLF has to increase by 125k. I can imagine that this will dissuade enterprise customers like Disney, HBO, Ford and other big companies who serve millions of visitors daily from using the TLF engine on their sites.
UPDATE 3
I've done some more investigating. I trace the parent stack when ADDED_TO_STAGE is fired inside the loaded swf. The ADDED_TO_STAGE event fires twice. Here's what happens.
When you use Loader to load a swf that has an RSL (which, in this case, is the TLF engine), and you load that swf, Adobe's Preloader is what it actually loads. Then, Adobe's Preloader creates a Loader and adds that Loader to its display stack, alongside a Shape that is the visual part of their preloader. Adobe's Preloader then tells its Loader to load your swf.
Once your swf loads, it's already on the stage, so ADDED_TO_STAGE is fired. Then, Adobe's Preloader calls removeChild() on the Loader and the Shape, and then calls addChild(loader.content), which triggers another ADDED_TO_STAGE event. After that, your loaded swf's parent is their Preloader, not their Preloader's Loader.
Now here's where it gets worse. The Preloader attempts to add a property called __rslLoaders to your document class. If you reference the document class of the loaded swf in your Main document class before you load your swf (such as you write code that casts the loaded swf as your document class, i.e. Other(loader.content) you get a runtime error:
ReferenceError: Error #1056: Cannot create property __rslLoaders on Other.
at fl.rsl::RSLPreloader/contentComplete()
The Loader you use to load your swf fires its COMPLETE event before Adobe's Preloader is finished loading your swf. You cannot listen to Adobe's preloader to know when your swf has actually been loaded and is available, and, as far as I can tell, Adobe's Preloader provides no way to target your swf.
By building it this way, the author of Adobe's preloader has decided for all of us that nobody who uses TLF should need to target the timeline of a loaded swf without jumping through some major hoops. Here are the hoops.
Because you cannot bubble events from within the other.swf through the Loader that loaded it to the Main class, your loading class has no way of knowing when your loaded swf is actually available unless you do an ENTER_FRAME listener and loop through the children of MovieClip(loader.content) and look for your interface class (as mentioned above, you cannot cast as the document class or you get a runtime reference error, and you cannot change the ApplicationDomain or the SecurityDomain of Adobe's Preloader's Loader). Then, you need to store a reference to your swf to be able to access it, which makes unloading it more of a chore (I actually haven't tested to see if Flash truly lets go of the loaded tlf swf).
private var otherSWF:ICustom;
private function onLoadComplete(event:Event):void
{
addEventListener(Event.ENTER_FRAME, onFrame);
}
private function onFrame(event:Event):void
{
var i:int = MovieClip(loader.content).numChildren;
while (i--)
{
if (MovieClip(loader.content).getChildAt(i) as ICustom != null)
{
otherSWF = MovieClip(loader.content).getChildAt(i) as ICustom;
removeEventListener(Event.ENTER_FRAME, onFrame);
break;
}
}
}
In my trivial example running on my local machine, it takes 5 frames before my loaded swf is available.