Loading a Remote Module into a Local App

At work, we deal a lot with loading modules residing on remote servers into our applications running locally. By locally I mean from a local path on our hard drives (the default Flex Builder run/debug settings), not on a local web server instance (localhost). Depending on what type of module we are loading, we would normally use one of Flex’s built-in functions to load modules:

// Loading a language resource module:
resourceManager.loadResourceModule('http://aaronhardy.com/en_US_ResourceModule.swf');

// Loading a style module (e.g., a compiled font swf):
StyleManager.loadStyleDeclarations("http://aaronhardy.com/fonts/Astroid.swf");

// Loading miscellaneous modules:
var moduleInfo:IModuleInfo = ModuleManager.getModule('http://aaronhardy.com/MyModule.swf');
moduleInfo.load();

But these methods of importing modules result in errors like the following:

Unable to load resource module from http://aaronhardy.com/en_US_ResourceModule.swf
Error: Unable to load style(SWF is not a loadable module): http://aaronhardy.com/fonts/Astroid.swf.

One way to solve these issues is by using a crossdomain policy file on the server and then using a local apache instance to run the application. In my talks with the folks at Adobe, this seems to be their standard workflow which may be why we haven't heard more about these problems. However, my workflow is to NOT have a local web hosting server running and I don't want to have one running just to get around these errors.

As of Flex SDK 3.2, Adobe introduced a new parameter to IModuleInfo.load() for raw module bytes. As a result, we can now retrieve the remote module’s raw bytes using a URLLoader and then load them directly as a module using the IModuleInfo.load() method. The module is now part of the same security domain as the main application and no errors are thrown.

I’ve bundled up this logic into a class called ModuleMarshaller which is found below.

package com.aaronhardy
{
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.utils.ByteArray;

import mx.events.ModuleEvent;
import mx.modules.IModuleInfo;
import mx.modules.ModuleManager;

/**
* Dispatched once module loading is complete.
*/
[Event(name="ready", type="mx.events.ModuleEvent")]

/**
* Dispatched if an error is encountered while loading the module.
*/
[Event(name="error", type="mx.events.ModuleEvent")]

/**
* Loads a remote module. Specifically, this is a workaround to allow an application being run
* locally (not using a web server) to load a module residing on a remote server.
* Usually, when attemping to do so using resourceManager.loadResourceModule(),
* StyleManager.loadStyleDeclarations(), or IModuleInfo.load() without passing in bytes
* results in errors such as the following:
*
* Error: Unable to load resource module from http://aaronhardy.com/locales/en_US.swf
* Error: Unable to load style(SWF is not a loadable module): http://aaronhardy.com/fonts/Astroid.swf.
*
* Be aware that any module imported with this class may have access to anything within
* your application's security sandbox.
*/
public class ModuleMarshaller extends EventDispatcher
{
protected var url:String;

/**
* Appears to need to be stored in the class scope, otherwise garbage collection
* wipes out some of the event handling within IModuleInfo.
*/
protected var module:IModuleInfo;

/**
* Constructor.
* @param url The external module to load into the application.
*/
public function ModuleMarshaller(url:String)
{
this.url = url;
}

/**
* Starts the loading process by loading in the module as bytes.
*/
public function loadModule():URLLoader
{
var urlRequest:URLRequest = new URLRequest(url);
var urlLoader:URLLoader = new URLLoader(urlRequest);
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, bytesLoadedHandler);
urlLoader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler);
return urlLoader;
}

/**
* Once the module bytes are loaded, convert them back into a module within the
* application's security domain.
*/
protected function bytesLoadedHandler(event:Event):void
{
var styleModuleBytes:ByteArray = ByteArray(URLLoader(event.target).data);
module = ModuleManager.getModule(url);
module.addEventListener(ModuleEvent.READY, modReadyHandler);
module.addEventListener(ModuleEvent.ERROR, errorHandler);
module.load(null, null, styleModuleBytes);
}

/**
* Once the module information is loaded, use the factory to create an instance of the
* module.
*/
protected function modReadyHandler(event:ModuleEvent):void
{
ModuleManager.getModule(url).factory.create();
dispatchEvent(event.clone());
}

/**
* Reports errors that may have occurred.
*/
protected function errorHandler(event:Event):void
{
if (event is ModuleEvent)
{
dispatchEvent(event.clone());
}
else if (event is ErrorEvent)
{
dispatchEvent(new ModuleEvent(ModuleEvent.ERROR, false, false, 0, 0,
ErrorEvent(event).text));
}
}

}
}

If you're loading a style module, you may also want this class, which ensures the style module takes effect in your application immediately after the module is loaded.

package com.aaronhardy
{
import mx.events.ModuleEvent;

/**
* Provides the module marshalling functionality of ModuleMarshaller with the ability to
* update styles immediately after the module is loaded.
*/
public class StyleModuleMarshaller extends ModuleMarshaller
{
/**
* If true, forces an immediate update of the application's styles after the module is
* loaded.
*/
protected var updateStylesImmediately:Boolean = true;

/**
* Constructor.
* @param url The external module to load into the application.
* @param update If true, forces an immediate update of the application's styles after
* the module is loaded.
*/
public function StyleModuleMarshaller(url:String, updateStylesImmediately:Boolean=true)
{
super(url);
this.updateStylesImmediately = updateStylesImmediately;
}

/**
* @inheritDoc
*/
override protected function modReadyHandler(event:ModuleEvent):void
{
ModuleManager.getModule(url).factory.create();

if (updateStylesImmediately)
{
mx.core.Singleton.getInstance("mx.styles::IStyleManager2").styleDeclarationsChanged();
}

dispatchEvent(event.clone());
}
}
}

The Dirty Details

So, why does the ModuleMarshaller function without errors while Flex’s built-in classes do not? You might not care, but in case you do, here’s what I know — and don’t know. First, all the built-in functions end up using ModuleInfo.load() found in ModuleManager.as. This function uses a Loader to load in the contents of the module. When the loaded SWF file is accessible and ready for use (i.e., the Loader’s LoaderInfo object dispatches an Event.INIT event), ModuleInfo.initHandler() is called. In this function we have the following segment of code:

factoryInfo = new FactoryInfo();

try
{
factoryInfo.factory = loader.content as IFlexModuleFactory;
}
catch(error:Error)
{
}

if (!factoryInfo.factory)
{
var moduleEvent:ModuleEvent = new ModuleEvent(
ModuleEvent.ERROR, event.bubbles, event.cancelable);
moduleEvent.bytesLoaded = 0;
moduleEvent.bytesTotal = 0;
moduleEvent.errorText = "SWF is not a loadable module";
dispatchEvent(moduleEvent);
return;
}

Notice how it takes the content from the loader and attempt to cast it as an IFlexModuleFactory object. In this case, loader.content is NOT an IFlexModuleFactory instance, resulting in factoryInfo.factory being set to null. Later in the function, it checks to see if factoryInfo.factory is null. If it is, an error is thrown. This is the origin of the errors you see in your application.

So why does ModuleMarshaller work? When ModuleMarshaller makes this call:

module.load(null, null, styleModuleBytes);

It ultimately ends up in the same ModuleInfo.initHandler() function as we saw above. In both cases the Flex Builder debugger shows that loader.content is of type GeneratedResourceModule1975496797516746634_mx_core_FlexModuleFactory. However, when we load the module using the raw bytes loaded with a URLLoader, it IS able to cast the object as an IFlexModuleFactory object, resulting in a non-null factoryInfo.factory object and avoiding any error.

Why such similarity yet one is able to be cast while the other is not? I don’t know. That’s as far as I can go in my debugging quests with what’s open source. I assume it has to do with the Flash Player security but I haven’t read anything that would specifically explain why the content cannot be cast as an IFlexModuleFactory object when using built-in Flex module loading methods. If you can fill in the gap or have questions, please feel free to post a comment. Bon appetit!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.