//////////////////////////////////////////////////////////////////////////////// // // 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.utils { import flash.display.DisplayObject; import flash.display.LoaderInfo; import flash.events.IEventDispatcher; import flash.system.Capabilities; import flash.utils.Dictionary; import mx.core.ApplicationDomainTarget; import mx.core.IFlexModuleFactory; import mx.core.mx_internal; import mx.core.RSLData; import mx.events.Request; import mx.managers.SystemManagerGlobals; import flash.display.Loader; use namespace mx_internal; /** * The LoaderUtil class defines utility methods for use with Flex RSLs and * generic Loader instances. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class LoaderUtil { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Class variables // //-------------------------------------------------------------------------- /** * @private * * An array of search strings and filters. These are used in the normalizeURL * method. normalizeURL is used to remove special Flash Player markup from * urls, but the array could be appended to by the user to modify urls in other * ways. * * Each object in the array has two fields: * * 1. searchString - the string to search the url * 2. filterFunction - a function that accepts an url and an index to the first * occurrence of the search string in the url. The function may modify the url * and return a new url. A filterFunction is only called once, for the first * occurrence of where the searchString was found. If there * are multiple strings in the url that need to be processed the filterFunction * should handle all of them on the call. A filter function should * be defined as follows: * * @param url the url to process. * @param index the index of the first occurrence of the seachString in the url. * @return the new url. * * function filterFunction(url:String, index:int):String * */ mx_internal static var urlFilters:Array = [ { searchString: "/[[DYNAMIC]]/", filterFunction: dynamicURLFilter}, { searchString: "/[[IMPORT]]/", filterFunction: importURLFilter} ]; //-------------------------------------------------------------------------- // // Class methods // //-------------------------------------------------------------------------- /** * The root URL of a cross-domain RSL contains special text * appended to the end of the URL. * This method normalizes the URL specified in the specified LoaderInfo instance * to remove the appended text, if present. * Classes accessing LoaderInfo.url should call this method * to normalize the URL before using it. * This method also encodes the url by calling the encodeURI() method * on it. If you want the unencoded url, you must call unencodeURI() on * the results. * * @param loaderInfo A LoaderInfo instance. * * @return A normalized LoaderInfo.url property. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function normalizeURL(loaderInfo:LoaderInfo):String { var url:String = loaderInfo.url; var index:int; var searchString:String; var urlFilter:Function; var n:uint = LoaderUtil.urlFilters.length; for (var i:uint = 0; i < n; i++) { searchString = LoaderUtil.urlFilters[i].searchString; if ((index = url.indexOf(searchString)) != -1) { urlFilter = LoaderUtil.urlFilters[i].filterFunction; url = urlFilter(url, index); } } // On the mac, the player doesn't like filenames with high-ascii // characters. Calling encodeURI fixes this problem. We restrict // this call to mac-only since it causes problems on Windows. if (isMac()) return encodeURI(url); return url; } /** * @private * * Use this method when you want to load resources with relative URLs. * * Combine a root url with a possibly relative url to get a absolute url. * Use this method to convert a relative url to an absolute URL that is * relative to a root URL. * * @param rootURL An url that will form the root of the absolute url. * If the rootURL does not specify a file name it must be * terminated with a slash. For example, "http://a.com" is incorrect, it * should be terminated with a slash, "http://a.com/". If the rootURL is * taken from loaderInfo, it must be passed thru normalizeURL * before being passed to this function. * * When loading resources relative to an application, the rootURL is * typically the loaderInfo.url of the application. * * @param url The url of the resource to load (may be relative). * * @return If url is already an absolute URL, then it is * returned as is. If url is relative, then an absolute URL is * returned where url is relative to rootURL. */ public static function createAbsoluteURL(rootURL:String, url:String):String { var absoluteURL:String = url; // make relative paths relative to the SWF loading it, not the top-level SWF if (rootURL && !(url.indexOf(":") > -1 || url.indexOf("/") == 0 || url.indexOf("\\") == 0)) { // First strip off the search string and then any url fragments. var index:int; if ((index = rootURL.indexOf("?")) != -1 ) rootURL = rootURL.substring(0, index); if ((index = rootURL.indexOf("#")) != -1 ) rootURL = rootURL.substring(0, index); // If the url starts from the current directory, then just skip // over the "./". // If the url start from the parent directory, the we need to // modify the rootURL. var lastIndex:int = Math.max(rootURL.lastIndexOf("\\"), rootURL.lastIndexOf("/")); if (url.indexOf("./") == 0) { url = url.substring(2); } else { while (url.indexOf("../") == 0) { url = url.substring(3); lastIndex = Math.max(rootURL.lastIndexOf("\\", lastIndex - 1), rootURL.lastIndexOf("/", lastIndex - 1)); } } if (lastIndex != -1) absoluteURL = rootURL.substr(0, lastIndex + 1) + url; } return absoluteURL; } /** * @private * * Takes a list of required rsls and determines: * - which RSLs have not been loaded * - the application domain and IModuleFactory where the * RSL should be loaded * * @param moduleFactory The module factory of the application or module * to get load information for. If the moduleFactory has not loaded the * module, then its parent is asked for load information. Each successive * parent is asked until the load information is found or there are no * more parents to ask. Only parents in parent ApplicationDomains are * searched. Applications in different security domains or sibling * ApplicationDomains do not share RSLs. * * @param rsls An array of RSLs that are required for * moduleFactory. Each RSL is in an array of RSLData where * the first element is the primary RSL and the remaining elements are * failover RSLs. * @return Array of RSLData that represents the RSLs to load. RSLs that are * already loaded are not in the listed. */ mx_internal static function processRequiredRSLs(moduleFactory:IFlexModuleFactory, rsls:Array):Array { var rslsToLoad:Array = []; // of Array, where each element is an array // of RSLData (primary and failover), return value var topLevelModuleFactory:IFlexModuleFactory = SystemManagerGlobals.topLevelSystemManagers[0]; var currentModuleFactory:IFlexModuleFactory = topLevelModuleFactory; var parentModuleFactory:IFlexModuleFactory = null; var loaded:Dictionary = new Dictionary(); // contains rsls that are loaded var loadedLength:int = 0; var resolved:Dictionary = new Dictionary(); // contains rsls that have the app domain resolved var resolvedLength:int = 0; var moduleFactories:Array = null; // Start at the top level module factory and work our way down the // module factory chain checking if the any of the rsls are loaded // and resolving application domain targets. // We start at the top level module factory because the default rsls // will all be loaded here and we won't often have to check other // module factories. while (currentModuleFactory != moduleFactory) { // Need to loop over all the rsls, to see which one are loaded // and resolve application domains. var n:int = rsls.length; for (var i:int = 0; i < n; i++) { var rsl:Array = rsls[i]; // Check if the RSL has already been loaded. if (!loaded[rsl]) { if (isRSLLoaded(currentModuleFactory, rsl[0].digest)) { loaded[rsl] = 1; loadedLength++; // We may find an rsl loaded in a module factory as we work // our way down the module factory list. If we find one then // remove it. if (currentModuleFactory != topLevelModuleFactory) { var index:int = rslsToLoad.indexOf(rsl); if (index != -1) rslsToLoad.splice(index, 1); } } else if (rslsToLoad.indexOf(rsl) == -1) { rslsToLoad.push(rsl); // assume we have to load it } } // If the rsl is already loaded or already resolved then // skip resolving it. if (!loaded[rsl] && resolved[rsl] == null) { // Get the parent module factory if we are going to need to // resolve the application domain target. if (!parentModuleFactory && RSLData(rsl[0]).applicationDomainTarget == ApplicationDomainTarget.PARENT) { parentModuleFactory = getParentModuleFactory(moduleFactory); } // Resolve the application domain target. if (resolveApplicationDomainTarget(rsl, moduleFactory, currentModuleFactory, parentModuleFactory, topLevelModuleFactory)) { resolved[rsl] = 1; resolvedLength++; } } } // If process all rsls then get out. if (loadedLength + resolvedLength >= rsls.length) break; // If we didn't find everything in the top level module factory then work // down towards the rsl's owning module factory. // Build up the module factory parent chain so we can traverse it. if (!moduleFactories) { moduleFactories = [moduleFactory]; currentModuleFactory = moduleFactory; while (currentModuleFactory != topLevelModuleFactory) { currentModuleFactory = getParentModuleFactory(currentModuleFactory); // If we couldn't get the parent module factory, then we // will have to load into the highest application domain // that is available. We won't be able to get a parent // if a module was loaded without specifying a parent // module factory. if (!currentModuleFactory) break; if (currentModuleFactory != topLevelModuleFactory) moduleFactories.push(currentModuleFactory); if (!parentModuleFactory) parentModuleFactory = currentModuleFactory; } } currentModuleFactory = moduleFactories.pop(); } return rslsToLoad; } /** * @private * Test whether a url is on the local filesystem. We can only * really tell this with URLs that begin with "file:" or a * Windows-style drive notation such as "C:". This fails some * cases like the "/" notation on Mac/Unix. * * @param url * the url to check against * * @return * true if url is local, false if not or unable to determine **/ mx_internal static function isLocal(url:String):Boolean { return (url.indexOf("file:") == 0 || url.indexOf(":") == 1); } /** * @private * Currently (FP 10.x) the ActiveX player (Explorer on Windows) does not * handle encoded URIs containing UTF-8 on the local filesystem, but * it does handle those same URIs unencoded. The plug-in requires * encoded URIs. * * @param url * url to properly encode, may be fully or partially encoded with encodeURI * * @param local * true indicates the url is on the local filesystem * * @return * encoded url that may be loaded with a URLRequest **/ mx_internal static function OSToPlayerURI(url:String, local:Boolean):String { // First strip off the search string and any url fragments so // they will not be decoded/encoded. // Next decode the url. // Before returning the decoded or encoded string add the search // string and url fragment back. var searchStringIndex:int; var fragmentUrlIndex:int; var decoded:String = url; if ((searchStringIndex = decoded.indexOf("?")) != -1 ) { decoded = decoded.substring(0, searchStringIndex); } if ((fragmentUrlIndex = decoded.indexOf("#")) != -1 ) decoded = decoded.substring(0, fragmentUrlIndex); try { // decode the url decoded = decodeURI(decoded); } catch (e:Error) { // malformed url, but some are legal on the file system } // create the string to hold the the search string url fragments. var extraString:String = null; if (searchStringIndex != -1 || fragmentUrlIndex != -1) { var index:int = searchStringIndex; if (searchStringIndex == -1 || (fragmentUrlIndex != -1 && fragmentUrlIndex < searchStringIndex)) { index = fragmentUrlIndex; } extraString = url.substr(index); } if (local && flash.system.Capabilities.playerType == "ActiveX") { if (extraString) return decoded + extraString; else return decoded; } if (extraString) return encodeURI(decoded) + extraString; else return encodeURI(decoded); } /** * @private * Get the parent module factory. * * @param moduleFactory The module factory to get the parent of. * * @return the parent module factory if available, null otherwise. */ private static function getParentModuleFactory(moduleFactory:IFlexModuleFactory):IFlexModuleFactory { var request:Request = new Request(Request.GET_PARENT_FLEX_MODULE_FACTORY_REQUEST); DisplayObject(moduleFactory).dispatchEvent(request); return request.value as IFlexModuleFactory; } /** * @private * Resolve the application domain target. * * @param rsl to resolve. * @param moduleFactory The module factory loading the RSLs. * @param currentModuleFactory The module factory to search for placeholders. * @param parentModuleFactory The rsl's parent module factory. * @param topLevelModuleFactory The top-level module factory. * * @return true if the application domain target was resolved, * false otherwise. */ private static function resolveApplicationDomainTarget(rsl:Array, moduleFactory:IFlexModuleFactory, currentModuleFactory:IFlexModuleFactory, parentModuleFactory:IFlexModuleFactory, topLevelModuleFactory:IFlexModuleFactory):Boolean { var resolvedRSL:Boolean = false; var targetModuleFactory:IFlexModuleFactory = null; var applicationDomainTarget:String = rsl[0].applicationDomainTarget; if (isLoadedIntoTopLevelApplicationDomain(moduleFactory)) { targetModuleFactory = topLevelModuleFactory; } else if (applicationDomainTarget == ApplicationDomainTarget.DEFAULT) { if (hasPlaceholderRSL(currentModuleFactory, rsl[0].digest)) { targetModuleFactory = currentModuleFactory; } } else if (applicationDomainTarget == ApplicationDomainTarget.TOP_LEVEL) { targetModuleFactory = topLevelModuleFactory; } else if (applicationDomainTarget == ApplicationDomainTarget.CURRENT) { resolvedRSL = true; } else if (applicationDomainTarget == ApplicationDomainTarget.PARENT) { // If there is no parent, ignore the target and load into the current // app domain. targetModuleFactory = parentModuleFactory; } else { resolvedRSL = true; // bogus target, load into current application domain } if (resolvedRSL || targetModuleFactory) { if (targetModuleFactory) updateRSLModuleFactory(rsl, targetModuleFactory); return true; } return false; } /** * @private * Determine if the moduleFactory has loaded an rsl that matches the * specified digest. * * @param moduleFactory The module factory to search. * @param digest The digest to search for. * @return true if a loaded rsl matching the digest was found. */ private static function isRSLLoaded(moduleFactory:IFlexModuleFactory, digest:String):Boolean { var preloadedRSLs:Dictionary = moduleFactory.preloadedRSLs; if (preloadedRSLs) { // loop over the rsls to find a matching digest for each (var rsl:Vector. in preloadedRSLs) { var n:int = rsl.length; for (var i:int = 0; i < n; i++) { if (rsl[i].digest == digest) { return true; } } } } return false; } /** * @private * * Determine if the moduleFactory has a placeholder rsl that matches the * specified digest. * * @param moduleFactory The module factory to search. * @param digest The digest to search for. * @return true if a placeholder rsl matching the digest was found. */ private static function hasPlaceholderRSL(moduleFactory:IFlexModuleFactory, digest:String):Boolean { var phRSLs:Array = moduleFactory.info()["placeholderRsls"]; if (phRSLs) { // loop over the rsls to find a matching digest var n:int = phRSLs.length; for (var i:int = 0; i < n; i++) { var rsl:Object = phRSLs[i]; var m:int = rsl.length; for (var j:int = 0; j < m; j++) { if (rsl[j].digest == digest) { return true; } } } } return false; } /** * @private * Test if a module factory has been loaded into the top-level application domain. * * @return true if loaded into the top-level application domain, false otherwise. */ private static function isLoadedIntoTopLevelApplicationDomain(moduleFactory:IFlexModuleFactory):Boolean { if (moduleFactory is DisplayObject) { var displayObject:DisplayObject = DisplayObject(moduleFactory); var loaderInfo:LoaderInfo = displayObject.loaderInfo; if (loaderInfo && loaderInfo.applicationDomain && loaderInfo.applicationDomain.parentDomain == null) { return true; } } return false; } /** * @private * * Update the module factory of an rsl, both the primary rsl and all * failover rsls. * * @param rsl One RSL represented by an array of RSLData. The * first element in the array is the primary rsl, the others are failovers. * @param moduleFactory The moduleFactory to set in the primary and * failover rsls. */ private static function updateRSLModuleFactory(rsl:Array, moduleFactory:IFlexModuleFactory):void { var n:int = rsl.length; for (var i:int = 0; i < n; i++) { rsl[i].moduleFactory = moduleFactory; } } /** * @private */ private static function isMac():Boolean { return Capabilities.os.substring(0, 3) == "Mac"; } /** * @private * * Strip off the DYNAMIC string(s) appended to the url. */ private static function dynamicURLFilter(url:String, index:int):String { return url.substring(0, index); } /** * @private * * Add together the protocol plus everything after "/[[IMPORT]]/". */ private static function importURLFilter(url:String, index:int):String { var protocolIndex:int = url.indexOf("://"); return url.substring(0,protocolIndex + 3) + url.substring(index + 12); } } }