blob: c6fe2802aa1488ce233ae0e5c10198a27f79ffcd [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.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 mx.utils.Platform;
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 <code>LoaderInfo.url</code> 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 <code>LoaderInfo.url</code> 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 (Platform.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 <code>rootURL</code> 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 <code>normalizeURL</code>
* 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 <code>url</code> is already an absolute URL, then it is
* returned as is. If <code>url</code> is relative, then an absolute URL is
* returned where <code>url</code> is relative to <code>rootURL</code>.
*/
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
* <code>moduleFactory</code>. 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.<RSLData> 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
*
* 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);
}
}
}