//////////////////////////////////////////////////////////////////////////////// // // 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 * load() 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 null * 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); } }