Loading SWFs into AIR 1.5.X and LoaderInfo.sharedEvents

Today I had a bit of a nightmare with regards to loading SWFs into AIR. Specifically, I am loading my SWFs from somewhere inside app-storage://, and those SWFs use Flash CS4 UI components. These components extend UIComponent which accesses the stage obje…

Today I had a bit of a nightmare with regards to loading SWFs into AIR. Specifically, I am loading my SWFs from somewhere inside app-storage://, and those SWFs use Flash CS4 UI components. These components extend UIComponent which accesses the stage object. When you attempt to load and add this child SWF to the display list of your AIR app, it generates a SecurityErrorEvent which stops things, dead.

The AIR app owns the stage object, there is only one Stage instance, and depending on what security sandbox the content SWF is loaded into, the child SWF is not allowed access to that stage because it could run amok.

The problem is, when you load a SWF file in AIR, it’ll be run in one of 5 security sandboxes, you can trace Security.sandboxType to see which. These range from the full-access “application” sandbox when loading from the un-modifiable app:// directory, to “local-with-file/network” when loading from app-storage://, to the “remote” sandbox when loading from a server. The latter allows you to use Security.allowDomain() and cross-domain policy files to exert some control as you would in web-based Flash. I won’t go into the exact details of all 5 types here, Adobe have a good article on the sandboxes here.

Either way, I needed to load SWFs from app-storage:// as this is where my application downloads content to, and this content may refer to stage, simply by using Flash CS4 UI Components. Unfortunately there doesn’t seem to be a way to use Loader/SWFLoader/Image to load a SWF from this directory and give it the relevant trust; no amount of tweaking LoaderContexts or ApplicationDomains seemed to allow this. The problem is well documented online, but the solution was hard to come by.

It turns out that you can use a URLLoader to load the raw binary data for a SWF, and then use Loader.loadBytes() or SWFLoader.load(bytes) to bypass this restriction. Word of warning: this is NOT recommended unless you have full control over where your SWFs are coming from, you are effectively allowing arbitrary code to be run within your application with full access to the file system etc.

Below I show a code snippet that illustrates the main points, I’m missing out the rest of the class where I define the members, import classes etc as that’s probably quite obvious.


public function TestLoadBytes()
{
  urlLoader = new URLLoader();
  urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
  urlLoader.addEventListener(Event.COMPLETE, urlLoaderCompleteHandler, false, 0, true);
  urlLoader.addEventListener(IOErrorEvent.IO_ERROR, urlLoaderIOErrorHandler, false, 0, true);
  urlLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, urlLoaderHttpStatusHandler, false, 0, true);
  urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, urlLoaderSecurityErrorHandler, false, 0, true);
			
  swfLoader = new Loader();
  swfLoader.contentLoaderInfo.addEventListener(Event.INIT, animLoadCompleteHandler, false, 0, true);

  urlLoader.load(new URLRequest("app-storage://path/to/file.swf"));
}

protected function urlLoaderCompleteHandler(event:Event):void
{
  var lc:LoaderContext = new LoaderContext(false, null);
  lc.allowLoadBytesCodeExecution = true;
			
  swfLoader.loadBytes(urlLoader.data, lc);
}

You may notice here I’m also setting “allowLoadBytesCodeExecution” on the LoaderContext for the URLLoader. This is to enable ActionScript execution in the loaded SWF. Again, I should emphasise the warning, but the only other workaround I’m aware of is to use a HTMLControl to display your loaded SWF which allows you to specify a sandbox and also gives that SWF its own Stage, but I couldn’t face running a load of HTMLControls that for the sake of getting stage access in a child SWF.

If you don’t have 100% control over the SWFs you are loading you can put in place validation checks and measures to increase security. There’s a good discussion of this in the comments over at Ethan Malasky’s blog.

So far I haven’t mentioned how to communicate with our newly loaded SWFs… this is where AIR differs from the web Flash Player. The LoaderInfo class gains a “sharedEvents” EventDispatcher instance, which can be used to dispatch events between the two sandboxes. There’s also a mechanism to call functions between the two (similar to LocalConnection), that can be found in LoaderInfo.parentSandboxBridge and LoaderInfo.childSandboxBridge properties respectively.

Again the docs provide a good rundown, even though they are geared towards using the JavaScript/HTML techniques, the classes and techniques remain the same for AS3/Flash. But here’s a quick sample on how to listen to events using the sharedEvents dispatcher object.

In the parent SWF we listen to the Event.COMPLETE event for a SWFLoader instance:


protected function swfLoaderCompleteHandler(event:Event):void
{
  swfLoader.contentLoaderInfo.sharedEvents.addEventListener(MyEvent.SOME_EVENT, someEventHandler, false, 0, true);			
}

In the SWF being loaded, we dispatch events in a similar way:


function someClickHandler(event:MouseEvent):void
{
  loaderInfo.sharedEvents.dispatchEvent(new MyEvent(MyEvent.SOME_EVENT, "SomeParam", someFuncRef, true));
}

I haven’t covered the use of the parent/childSandbox bridge objects which can be used to directly expose and call methods because you can achieve much the same using this sharedEvents approach in a more decoupled way. For example the Event you are dispatching can contain a “callback” function reference that the parent SWF can call when an asyncronous operation has completed.

15 thoughts on “Loading SWFs into AIR 1.5.X and LoaderInfo.sharedEvents”

  1. I should point out I haven’t really used “best practices” in my code snippets throughout but I wanted to keep them brief.

  2. This has been extremely helpful.

    I do have a question: I have an AIR app that loads a SWF, and the above worked perfectly; however, that loaded swf in turn needs to load other swfs.

    I am running into issues, the exception thrown is “Loader.loadBytes() is not permitted to load content with executable code.” , and I cant use lc.allowLoadBytesCodeExecution , because it is available only in AIR.

    Any ideas?

  3. I think in your case try using lc[“allowLoadBytesCodeExecution”] = true; inside the SWF you are loading, I’m using this technique to utilise the AIR-only parentSandboxBridge property in my regular Flash 10 (web) SWFs.

    Using the array access operators allows for AIR code to be ran in SWFs that are published for the web as the code is not compile-time checked.

  4. Hi Richard.
    Excellent article!!!
    As a new AIR developer I came across the Security violation stuff when my AIR app (running in desktop mode) tried to load a LOCAL .swf file. (I am using Flex Builder 3 to build my AIR app).

    Question: any suggestion as of how to get around this issue when the.swf file is on the same box as the AIR app itself

    Cheers,
    DTR

  5. Hi DT,

    I’m not sure I follow. In this case the file is on the same box as the AIR app itself. Do you mean a local server or something else perhaps?

    Did this technique not work in that situation?

    Rich

  6. Hey Rich,
    Sorry for the radio silence & thx for responding to my
    email.

    WRT your questions:
    1. What i meant by local (on the same machine) was exactly that, i.e. I have the AIR app plus the .swf file on the same machine. When I tried my code, i got a Security violation, hence looking on the net …
    2. No, i did not your suggested solution. If i try to use your solution for my app, what would be the correct URL syntax for passing to your loader routine? For example, let’s say that my AIR app (bar.air) & the swf that is trying to load (foo.swf) are located in the directory/folder C:myApp. In this case what would be the correct URL for the foo.swf?
    urlLoader.load(new URLRequest(“C:myAppfoo.swf”));
    urlLoader.load(new URLRequest(“myAppfoo.swf”));
    urlLoader.load(new URLRequest(“foo.swf”));
    ?

    Again, thx 4 yr help.
    DT

  7. Modifications to the previous posting:
    I suppose the correct path is:
    urlLoader.load(new URLRequest(“app-storage://myApp/foo.swf”)). Correct?

    DT

  8. In most cases it’s easier to use the File.applicationStorage.resolvePath(“path/to/file.swf”).url as that saves you coding the entire path yourself. If the SWF you are loading is in the app-storage directiory this method should work for you (the app storage directory is usually called Local Storage, so the path is relative to that, you don’t include anything like the app name. It’s also important to set the LoaderContext as described too just in case.

  9. Hi Richard,

    I’m struggling too with the air sanboxes 😉
    So the article was a interesting read. What worries me is your advice. The technique you are using is really a security problem. Are you aware that any man in the middle can take over your system this way and that man in the middel attacks are not hard at al (according to adobe security experts).

    So this technique could be a lot saver if:
    – you sign the loaded swf’s and validate them before giving them full system access. I don’t know if you can sign flash files but with adt you can.
    – load them over a secure channel

    I thought i’d mention this because readers of the blog might be unaware of the man in the middle security risks this technique can lead to.

    cheers!

    Arnoud

  10. Hi Arnoud,

    Yes, the problem is, without this technique, AIR becomes useless for loading any local SWF that has components in it, I can’t see a way to use AIR for many applications if this is not possible. If it’s user created content I would not use this however.

    I absolutely agree with your proposals for security on top of this, thanks for adding these here.

  11. Hello Richard,
    thanks for the insightful post.

    Did you manage to load any SWF from app-storage into the local-with-network security sandbox?
    The official documentation also mentions this possibility, but I couldn’t achieve this regardless if I compile the loaded SWF with -use/network=true or not. It is placed in the localWithFile sandbox in both cases.
    Any suggestions? (I couldn’t dig up much information about this topic)

    Thanks

  12. Hi Richard,

    thanks for this post. I read it when I was looking into building a modular AIR application, with functional modules loaded at runtime.

    I’ve now come up with a framework for loading signed and unsigned swfs into different sandboxes. It’s all in a nice neat wrapper with user feedback during the installation process as well.

    All the code and testable examples are at http://flair-flash-flex-air.blogspot.com/2009/09/framework-for-modular-air-applications.html

    Thanks for getting me started.

  13. Hey Rich!
    Great post, I ran into similar problem and thanks to your post everything works like a charm now.

    Shared events work as well.

    Tom

Comments are closed.