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.