blob: 48bcf47fdaa62586b94d52eaf77458613fd6d6c7 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package mx.modules
{
import flash.utils.ByteArray;
import mx.core.IFlexModuleFactory;
import mx.events.Request;
/**
* The ModuleManager class centrally manages dynamically loaded modules.
* It maintains a mapping of URLs to modules.
* A module can exist in a state where it is already loaded
* (and ready for use), or in a not-loaded-yet state.
* The ModuleManager dispatches events that indicate module status.
* Clients can register event handlers and then call the
* <code>load()</code> method, which dispatches events when the factory is ready
* (or immediately, if it was already loaded).
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class ModuleManager
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
/**
* Get the IModuleInfo interface associated with a particular URL.
* There is no requirement that this URL successfully load,
* but the ModuleManager returns a unique IModuleInfo handle for each unique URL.
*
* @param url A URL that represents the location of the module.
*
* @return The IModuleInfo interface associated with a particular URL.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function getModule(url:String):IModuleInfo
{
return getSingleton().getModule(url);
}
/**
* See if the referenced object is associated with (or, in the managed
* ApplicationDomain of) a known IFlexModuleFactory implementation.
*
* @param object The object that the ModuleManager tries to create.
*
* @return Returns the IFlexModuleFactory implementation, or <code>null</code>
* if the object type cannot be created from the factory.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function getAssociatedFactory(
object:Object):IFlexModuleFactory
{
return getSingleton().getAssociatedFactory(object);
}
/**
* @private
* Typed as Object, for now. Ideally this should be IModuleManager.
*/
private static function getSingleton():Object
{
if (!ModuleManagerGlobals.managerSingleton)
ModuleManagerGlobals.managerSingleton = new ModuleManagerImpl();
return ModuleManagerGlobals.managerSingleton;
}
}
}
import flash.display.Loader;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.system.Security;
import flash.system.SecurityDomain;
import flash.utils.ByteArray;
import flash.utils.Dictionary;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
import mx.core.IFlexModuleFactory;
import mx.events.ModuleEvent;
import mx.modules.IModuleInfo;
////////////////////////////////////////////////////////////////////////////////
//
// Helper class: ModuleManagerImpl
//
////////////////////////////////////////////////////////////////////////////////
import mx.events.Request;
/**
* @private
* ModuleManagerImpl is the Module Manager singleton,
* hidden from direct access by the ModuleManager class.
* See the documentation for ModuleManager for the details on this class.
*/
class ModuleManagerImpl extends EventDispatcher
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function ModuleManagerImpl()
{
super();
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var moduleDictionary:Dictionary = new Dictionary(true);
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
public function getAssociatedFactory(object:Object):IFlexModuleFactory
{
var className:String = getQualifiedClassName(object);
for (var m:Object in moduleDictionary)
{
var info:ModuleInfo = m as ModuleInfo;
if (!info.ready)
continue;
var domain:ApplicationDomain = info.applicationDomain;
if (domain.hasDefinition(className))
{
var cls:Class = Class(domain.getDefinition(className));
if (cls && (object is cls))
return info.factory;
}
}
return null;
}
/**
* @private
*/
public function getModule(url:String):IModuleInfo
{
var info:ModuleInfo = null;
for (var m:Object in moduleDictionary)
{
var mi:ModuleInfo = m as ModuleInfo;
if (moduleDictionary[mi] == url)
{
info = mi;
break;
}
}
if (!info)
{
info = new ModuleInfo(url);
moduleDictionary[info] = url;
}
return new ModuleInfoProxy(info);
}
}
////////////////////////////////////////////////////////////////////////////////
//
// Helper class: ModuleInfo
//
////////////////////////////////////////////////////////////////////////////////
/**
* @private
* The ModuleInfo class encodes the loading state of a module.
* It isn't used directly, because there needs to be only one single
* ModuleInfo per URL, even if that URL is loaded multiple times,
* yet individual clients need their own dedicated events dispatched
* without re-dispatching to clients that already received their events.
* ModuleInfoProxy holds the public IModuleInfo implementation
* that can be externally manipulated.
*/
class ModuleInfo extends EventDispatcher
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function ModuleInfo(url:String)
{
super();
_url = url;
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var factoryInfo:FactoryInfo;
/**
* @private
*/
private var loader:Loader;
/**
* @private
*/
private var numReferences:int = 0;
/**
* @private
*/
private var parentModuleFactory:IFlexModuleFactory;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// applicationDomain
//----------------------------------
/**
* @private
*/
public function get applicationDomain():ApplicationDomain
{
return factoryInfo ? factoryInfo.applicationDomain : null;
}
//----------------------------------
// error
//----------------------------------
/**
* @private
* Storage for the error property.
*/
private var _error:Boolean = false;
/**
* @private
*/
public function get error():Boolean
{
return _error;
}
//----------------------------------
// factory
//----------------------------------
/**
* @private
*/
public function get factory():IFlexModuleFactory
{
return factoryInfo ? factoryInfo.factory : null;
}
//----------------------------------
// loaded
//----------------------------------
/**
* @private
* Storage for the loader property.
*/
private var _loaded:Boolean = false;
/**
* @private
*/
public function get loaded():Boolean
{
return _loaded;
}
//----------------------------------
// ready
//----------------------------------
/**
* @private
* Storage for the ready property.
*/
private var _ready:Boolean = false;
/**
* @private
*/
public function get ready():Boolean
{
return _ready;
}
//----------------------------------
// setup
//----------------------------------
/**
* @private
* Storage for the setup property.
*/
private var _setup:Boolean = false;
/**
* @private
*/
public function get setup():Boolean
{
return _setup;
}
//----------------------------------
// size
//----------------------------------
/**
* @private
*/
public function get size():int
{
return factoryInfo ? factoryInfo.bytesTotal : 0;
}
//----------------------------------
// url
//----------------------------------
/**
* @private
* Storage for the url property.
*/
private var _url:String;
/**
* @private
*/
public function get url():String
{
return _url;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
public function load(applicationDomain:ApplicationDomain = null,
securityDomain:SecurityDomain = null,
bytes:ByteArray = null,
moduleFactory:IFlexModuleFactory = null):void
{
if (_loaded)
return;
_loaded = true;
parentModuleFactory = moduleFactory;
// If bytes are supplied, then load the bytes instead of loading
// from the url.
if (bytes)
{
loadBytes(applicationDomain, bytes);
return;
}
if (_url.indexOf("published://") == 0)
return;
var r:URLRequest = new URLRequest(_url);
var c:LoaderContext = new LoaderContext();
c.applicationDomain =
applicationDomain ?
applicationDomain :
new ApplicationDomain(ApplicationDomain.currentDomain);
// setting securityDomain is not allowed on non-REMOTE sandboxes
if (securityDomain != null && Security.sandboxType == Security.REMOTE)
c.securityDomain = securityDomain;
loader = new Loader();
loader.contentLoaderInfo.addEventListener(
Event.INIT, initHandler);
loader.contentLoaderInfo.addEventListener(
Event.COMPLETE, completeHandler);
loader.contentLoaderInfo.addEventListener(
ProgressEvent.PROGRESS, progressHandler);
loader.contentLoaderInfo.addEventListener(
IOErrorEvent.IO_ERROR, errorHandler);
loader.contentLoaderInfo.addEventListener(
SecurityErrorEvent.SECURITY_ERROR, errorHandler);
loader.load(r, c);
}
/**
* @private
*/
private function loadBytes(applicationDomain:ApplicationDomain, bytes:ByteArray):void
{
var c:LoaderContext = new LoaderContext();
c.applicationDomain =
applicationDomain ?
applicationDomain :
new ApplicationDomain(ApplicationDomain.currentDomain);
// If the AIR flag is available then set it to true so we can
// load the module without a security error.
if ("allowLoadBytesCodeExecution" in c)
c["allowLoadBytesCodeExecution"] = true;
loader = new Loader();
loader.contentLoaderInfo.addEventListener(
Event.INIT, initHandler);
loader.contentLoaderInfo.addEventListener(
Event.COMPLETE, completeHandler);
loader.contentLoaderInfo.addEventListener(
IOErrorEvent.IO_ERROR, errorHandler);
loader.contentLoaderInfo.addEventListener(
SecurityErrorEvent.SECURITY_ERROR, errorHandler);
loader.loadBytes(bytes, c);
}
/**
* @private
*/
public function resurrect():void
{
// If the module is not ready then don't try to resurrect it.
// You can only resurrect a module that is in the ready state.
// We return here and do not destroy the current state because
// we may have started loading a module that is not yet ready.
if (!_ready)
return;
//trace("Module[", url, "] resurrect");
if (!factoryInfo)
{
if (_loaded)
dispatchEvent(new ModuleEvent(ModuleEvent.UNLOAD));
loader = null;
_loaded = false;
_setup = false;
_ready = false;
_error = false;
}
}
/**
* @private
*/
public function release():void
{
// If the module is ready, then keep it in the
// module dictionary.
if (!_ready)
{
// Otherwise we just drop it
unload();
}
}
/**
* @private
*/
private function clearLoader():void
{
if (loader)
{
if (loader.contentLoaderInfo)
{
loader.contentLoaderInfo.removeEventListener(
Event.INIT, initHandler);
loader.contentLoaderInfo.removeEventListener(
Event.COMPLETE, completeHandler);
loader.contentLoaderInfo.removeEventListener(
ProgressEvent.PROGRESS, progressHandler);
loader.contentLoaderInfo.removeEventListener(
IOErrorEvent.IO_ERROR, errorHandler);
loader.contentLoaderInfo.removeEventListener(
SecurityErrorEvent.SECURITY_ERROR, errorHandler);
}
try
{
if (loader.content)
{
loader.content.removeEventListener("ready", readyHandler);
loader.content.removeEventListener("error", moduleErrorHandler);
}
}
catch(error:Error)
{
// we might get unloaded because of a security error
// which will disallow access to loader.content
// so if we get an error here, just ignore it.
}
if (_loaded)
{
try
{
loader.close();
}
catch(error:Error)
{
}
}
try
{
loader.unload();
}
catch(error:Error)
{
}
loader = null;
}
}
/**
* @private
*/
public function unload():void
{
clearLoader();
if (_loaded)
dispatchEvent(new ModuleEvent(ModuleEvent.UNLOAD));
factoryInfo = null;
parentModuleFactory = null;
_loaded = false;
_setup = false;
_ready = false;
_error = false;
}
/**
* @private
*/
public function publish(factory:IFlexModuleFactory):void
{
if (factoryInfo)
return; // can't re-publish without unloading.
if (_url.indexOf("published://") != 0)
return;
factoryInfo = new FactoryInfo();
factoryInfo.factory = factory;
_loaded = true;
_setup = true;
_ready = true;
_error = false;
dispatchEvent(new ModuleEvent(ModuleEvent.SETUP));
dispatchEvent(new ModuleEvent(ModuleEvent.PROGRESS));
dispatchEvent(new ModuleEvent(ModuleEvent.READY));
}
/**
* @private
*/
public function addReference():void
{
++numReferences;
}
/**
* @private
*/
public function removeReference():void
{
--numReferences;
if (numReferences == 0)
release();
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
public function initHandler(event:Event):void
{
//trace("child load of " + _url + " fired init");
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;
}
loader.content.addEventListener("ready", readyHandler);
loader.content.addEventListener("error", moduleErrorHandler);
loader.content.addEventListener(Request.GET_PARENT_FLEX_MODULE_FACTORY_REQUEST,
getFlexModuleFactoryRequestHandler, false, 0, true);
try
{
factoryInfo.applicationDomain =
loader.contentLoaderInfo.applicationDomain;
}
catch(error:Error)
{
}
_setup = true;
dispatchEvent(new ModuleEvent(ModuleEvent.SETUP));
}
/**
* @private
*/
public function progressHandler(event:ProgressEvent):void
{
var moduleEvent:ModuleEvent = new ModuleEvent(
ModuleEvent.PROGRESS, event.bubbles, event.cancelable);
moduleEvent.bytesLoaded = event.bytesLoaded;
moduleEvent.bytesTotal = event.bytesTotal;
dispatchEvent(moduleEvent);
}
/**
* @private
*/
public function completeHandler(event:Event):void
{
//trace("child load of " + _url + " is complete");
var moduleEvent:ModuleEvent = new ModuleEvent(
ModuleEvent.PROGRESS, event.bubbles, event.cancelable);
moduleEvent.bytesLoaded = loader.contentLoaderInfo.bytesLoaded;
moduleEvent.bytesTotal = loader.contentLoaderInfo.bytesTotal;
dispatchEvent(moduleEvent);
}
/**
* @private
*/
public function errorHandler(event:ErrorEvent):void
{
_error = true;
var moduleEvent:ModuleEvent = new ModuleEvent(
ModuleEvent.ERROR, event.bubbles, event.cancelable);
moduleEvent.bytesLoaded = 0;
moduleEvent.bytesTotal = 0;
moduleEvent.errorText = event.text;
dispatchEvent(moduleEvent);
//trace("child load of " + _url + " generated an error " + event);
}
/**
* @private
*/
public function getFlexModuleFactoryRequestHandler(request:Request):void
{
request.value = parentModuleFactory;
}
/**
* @private
*/
public function readyHandler(event:Event):void
{
//trace("child load of " + _url + " is ready");
_ready = true;
factoryInfo.bytesTotal = loader.contentLoaderInfo.bytesTotal;
var moduleEvent:ModuleEvent = new ModuleEvent(ModuleEvent.READY);
moduleEvent.bytesLoaded = loader.contentLoaderInfo.bytesLoaded;
moduleEvent.bytesTotal = loader.contentLoaderInfo.bytesTotal;
clearLoader();
dispatchEvent(moduleEvent);
}
/**
* @private
*/
public function moduleErrorHandler(event:Event):void
{
//trace("Error: child load of " + _url + ");
_ready = true;
factoryInfo.bytesTotal = loader.contentLoaderInfo.bytesTotal;
clearLoader();
var errorEvent:ModuleEvent;
if (event is ModuleEvent)
errorEvent = ModuleEvent(event);
else
errorEvent = new ModuleEvent(ModuleEvent.ERROR);
dispatchEvent(errorEvent);
}
}
////////////////////////////////////////////////////////////////////////////////
//
// Helper class: FactoryInfo
//
////////////////////////////////////////////////////////////////////////////////
/**
* @private
* Used for weak dictionary references to a GC-able module.
*/
class FactoryInfo
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function FactoryInfo()
{
super();
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// factory
//----------------------------------
/**
* @private
*/
public var factory:IFlexModuleFactory;
//----------------------------------
// applicationDomain
//----------------------------------
/**
* @private
*/
public var applicationDomain:ApplicationDomain;
//----------------------------------
// bytesTotal
//----------------------------------
/**
* @private
*/
public var bytesTotal:int = 0;
}
////////////////////////////////////////////////////////////////////////////////
//
// Helper class: ModuleInfoProxy
//
////////////////////////////////////////////////////////////////////////////////
/**
* @private
* ModuleInfoProxy implements IModuleInfo and allows each caller of load()
* to have their own dedicated module events, while still using the same
* backing load state.
*/
class ModuleInfoProxy extends EventDispatcher implements IModuleInfo
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function ModuleInfoProxy(info:ModuleInfo)
{
super();
this.info = info;
info.addEventListener(ModuleEvent.SETUP, moduleEventHandler, false, 0, true);
info.addEventListener(ModuleEvent.PROGRESS, moduleEventHandler, false, 0, true);
info.addEventListener(ModuleEvent.READY, moduleEventHandler, false, 0, true);
info.addEventListener(ModuleEvent.ERROR, moduleEventHandler, false, 0, true);
info.addEventListener(ModuleEvent.UNLOAD, moduleEventHandler, false, 0, true);
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var info:ModuleInfo;
/**
* @private
*/
private var referenced:Boolean = false;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// data
//----------------------------------
/**
* @private
* Storage for the data property.
*/
private var _data:Object;
/**
* @private
*/
public function get data():Object
{
return _data;
}
/**
* @private
*/
public function set data(value:Object):void
{
_data = value;
}
//----------------------------------
// error
//----------------------------------
/**
* @private
*/
public function get error():Boolean
{
return info.error;
}
//----------------------------------
// factory
//----------------------------------
/**
* @private
*/
public function get factory():IFlexModuleFactory
{
return info.factory;
}
//----------------------------------
// loaded
//----------------------------------
/**
* @private
*/
public function get loaded():Boolean
{
return info.loaded;
}
//----------------------------------
// ready
//----------------------------------
/**
* @private
*/
public function get ready():Boolean
{
return info.ready;
}
//----------------------------------
// setup
//----------------------------------
/**
* @private
*/
public function get setup():Boolean
{
return info.setup;
}
//----------------------------------
// url
//----------------------------------
/**
* @private
*/
public function get url():String
{
return info.url;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
public function publish(factory:IFlexModuleFactory):void
{
info.publish(factory);
}
/**
* @private
*/
public function load(applicationDomain:ApplicationDomain = null,
securityDomain:SecurityDomain = null,
bytes:ByteArray = null,
moduleFactory:IFlexModuleFactory = null):void
{
info.resurrect();
if (!referenced)
{
info.addReference();
referenced = true;
}
//trace("Module[", url, "] load");
if (info.error)
{
//trace("Module[", url, "] load is in error state");
dispatchEvent(new ModuleEvent(ModuleEvent.ERROR));
}
else if (info.loaded)
{
//trace("Module[", url, "] load is already loaded");
if (info.setup)
{
//trace("Module[", url, "] load is already set up");
dispatchEvent(new ModuleEvent(ModuleEvent.SETUP));
if (info.ready)
{
//trace("Module[", url, "] load is already ready");
var moduleEvent:ModuleEvent =
new ModuleEvent(ModuleEvent.PROGRESS);
moduleEvent.bytesLoaded = info.size;
moduleEvent.bytesTotal = info.size;
dispatchEvent(moduleEvent);
dispatchEvent(new ModuleEvent(ModuleEvent.READY));
}
}
}
else
{
info.load(applicationDomain, securityDomain, bytes, moduleFactory);
}
}
/**
* @private
*/
public function release():void
{
if (referenced)
{
info.removeReference();
referenced = false;
}
}
/**
* @private
*/
public function unload():void
{
info.unload();
info.removeEventListener(ModuleEvent.SETUP, moduleEventHandler);
info.removeEventListener(ModuleEvent.PROGRESS, moduleEventHandler);
info.removeEventListener(ModuleEvent.READY, moduleEventHandler);
info.removeEventListener(ModuleEvent.ERROR, moduleEventHandler);
info.removeEventListener(ModuleEvent.UNLOAD, moduleEventHandler);
}
//--------------------------------------------------------------------------
//
// Event handlers
//
//--------------------------------------------------------------------------
/**
* @private
*/
private function moduleEventHandler(event:ModuleEvent):void
{
dispatchEvent(event);
}
}