/* * * 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. * */ /* * The Bridge class, responsible for navigating AS instances */ function FABridge(target,bridgeName) { this.target = target; this.remoteTypeCache = {}; this.remoteInstanceCache = {}; this.remoteFunctionCache = {}; this.localFunctionCache = {}; this.bridgeID = FABridge.nextBridgeID++; this.name = bridgeName; this.nextLocalFuncID = 0; FABridge.instances[this.name] = this; FABridge.idMap[this.bridgeID] = this; return this; } // type codes for packed values FABridge.TYPE_ASINSTANCE = 1; FABridge.TYPE_ASFUNCTION = 2; FABridge.TYPE_JSFUNCTION = 3; FABridge.TYPE_ANONYMOUS = 4; FABridge.initCallbacks = {} FABridge.argsToArray = function(args) { var result = []; for (var i = 0; i < args.length; i++) { result[i] = args[i]; } return result; } function instanceFactory(objID) { this.fb_instance_id = objID; return this; } function FABridge__invokeJSFunction(args) { var funcID = args[0]; var throughArgs = args.concat();//FABridge.argsToArray(arguments); throughArgs.shift(); var bridge = FABridge.extractBridgeFromID(funcID); return bridge.invokeLocalFunction(funcID, throughArgs); } FABridge.addInitializationCallback = function(bridgeName, callback) { var inst = FABridge.instances[bridgeName]; if (inst != undefined) { callback.call(inst); return; } var callbackList = FABridge.initCallbacks[bridgeName]; if(callbackList == null) { FABridge.initCallbacks[bridgeName] = callbackList = []; } callbackList.push(callback); } function FABridge__bridgeInitialized(bridgeName) { var objects = document.getElementsByTagName("object"); var ol = objects.length; var activeObjects = []; if (ol > 0) { for (var i = 0; i < ol; i++) { if (typeof objects[i].SetVariable != "undefined") { activeObjects[activeObjects.length] = objects[i]; } } } var embeds = document.getElementsByTagName("embed"); var el = embeds.length; var activeEmbeds = []; if (el > 0) { for (var j = 0; j < el; j++) { if (typeof embeds[j].SetVariable != "undefined") { activeEmbeds[activeEmbeds.length] = embeds[j]; } } } var aol = activeObjects.length; var ael = activeEmbeds.length; var searchStr = "bridgeName="+ bridgeName; if ((aol == 1 && !ael) || (aol == 1 && ael == 1)) { FABridge.attachBridge(activeObjects[0], bridgeName); } else if (ael == 1 && !aol) { FABridge.attachBridge(activeEmbeds[0], bridgeName); } else { var flash_found = false; if (aol > 1) { for (var k = 0; k < aol; k++) { var params = activeObjects[k].childNodes; for (var l = 0; l < params.length; l++) { var param = params[l]; if (param.nodeType == 1 && param.tagName.toLowerCase() == "param" && param["name"].toLowerCase() == "flashvars" && param["value"].indexOf(searchStr) >= 0) { FABridge.attachBridge(activeObjects[k], bridgeName); flash_found = true; break; } } if (flash_found) { break; } } } if (!flash_found && ael > 1) { for (var m = 0; m < ael; m++) { var flashVars = activeEmbeds[m].attributes.getNamedItem("flashVars").nodeValue; if (flashVars.indexOf(searchStr) >= 0) { FABridge.attachBridge(activeEmbeds[m], bridgeName); break; } } } } return true; } // used to track multiple bridge instances, since callbacks from AS are global across the page. FABridge.nextBridgeID = 0; FABridge.instances = {}; FABridge.idMap = {}; FABridge.refCount = 0; FABridge.extractBridgeFromID = function(id) { var bridgeID = (id >> 16); return FABridge.idMap[bridgeID]; } FABridge.attachBridge = function(instance, bridgeName) { var newBridgeInstance = new FABridge(instance, bridgeName); FABridge[bridgeName] = newBridgeInstance; /* FABridge[bridgeName] = function() { return newBridgeInstance.root(); } */ var callbacks = FABridge.initCallbacks[bridgeName]; if (callbacks == null) { return; } for (var i = 0; i < callbacks.length; i++) { callbacks[i].call(newBridgeInstance); } delete FABridge.initCallbacks[bridgeName] } // some methods can't be proxied. You can use the explicit get,set, and call methods if necessary. FABridge.blockedMethods = { toString: true, get: true, set: true, call: true }; FABridge.prototype = { // bootstrapping root: function() { return this.deserialize(this.target.getRoot()); }, //clears all of the AS objects in the cache maps releaseASObjects: function() { return this.target.releaseASObjects(); }, //clears a specific object in AS from the type maps releaseNamedASObject: function(value) { if(typeof(value) != "object") { return false; } else { var ret = this.target.releaseNamedASObject(value.fb_instance_id); return ret; } }, //create a new AS Object create: function(className) { return this.deserialize(this.target.create(className)); }, // utilities makeID: function(token) { return (this.bridgeID << 16) + token; }, // low level access to the flash object //get a named property from an AS object getPropertyFromAS: function(objRef, propName) { if (FABridge.refCount > 0) { throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround."); } else { FABridge.refCount++; retVal = this.target.getPropFromAS(objRef, propName); retVal = this.handleError(retVal); FABridge.refCount--; return retVal; } }, //set a named property on an AS object setPropertyInAS: function(objRef,propName, value) { if (FABridge.refCount > 0) { throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround."); } else { FABridge.refCount++; retVal = this.target.setPropInAS(objRef,propName, this.serialize(value)); retVal = this.handleError(retVal); FABridge.refCount--; return retVal; } }, //call an AS function callASFunction: function(funcID, args) { if (FABridge.refCount > 0) { throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround."); } else { FABridge.refCount++; retVal = this.target.invokeASFunction(funcID, this.serialize(args)); retVal = this.handleError(retVal); FABridge.refCount--; return retVal; } }, //call a method on an AS object callASMethod: function(objID, funcName, args) { if (FABridge.refCount > 0) { throw new Error("You are trying to call recursively into the Flash Player which is not allowed. In most cases the JavaScript setTimeout function, can be used as a workaround."); } else { FABridge.refCount++; args = this.serialize(args); retVal = this.target.invokeASMethod(objID, funcName, args); retVal = this.handleError(retVal); FABridge.refCount--; return retVal; } }, // responders to remote calls from flash //callback from flash that executes a local JS function //used mostly when setting js functions as callbacks on events invokeLocalFunction: function(funcID, args) { var result; var func = this.localFunctionCache[funcID]; if(func != undefined) { result = this.serialize(func.apply(null, this.deserialize(args))); } return result; }, // Object Types and Proxies // accepts an object reference, returns a type object matching the obj reference. getTypeFromName: function(objTypeName) { return this.remoteTypeCache[objTypeName]; }, //create an AS proxy for the given object ID and type createProxy: function(objID, typeName) { var objType = this.getTypeFromName(typeName); instanceFactory.prototype = objType; var instance = new instanceFactory(objID); this.remoteInstanceCache[objID] = instance; return instance; }, //return the proxy associated with the given object ID getProxy: function(objID) { return this.remoteInstanceCache[objID]; }, // accepts a type structure, returns a constructed type addTypeDataToCache: function(typeData) { newType = new ASProxy(this, typeData.name); var accessors = typeData.accessors; for (var i = 0; i < accessors.length; i++) { this.addPropertyToType(newType, accessors[i]); } var methods = typeData.methods; for (var i = 0; i < methods.length; i++) { if (FABridge.blockedMethods[methods[i]] == undefined) { this.addMethodToType(newType, methods[i]); } } this.remoteTypeCache[newType.typeName] = newType; return newType; }, //add a property to a typename; used to define the properties that can be called on an AS proxied object addPropertyToType: function(ty, propName) { var c = propName.charAt(0); var setterName; var getterName; if(c >= "a" && c <= "z") { getterName = "get" + c.toUpperCase() + propName.substr(1); setterName = "set" + c.toUpperCase() + propName.substr(1); } else { getterName = "get" + propName; setterName = "set" + propName; } ty[setterName] = function(val) { this.bridge.setPropertyInAS(this.fb_instance_id, propName, val); } ty[getterName] = function() { return this.bridge.deserialize(this.bridge.getPropertyFromAS(this.fb_instance_id, propName)); } }, //add a method to a typename; used to define the methods that can be callefd on an AS proxied object addMethodToType: function(ty, methodName) { ty[methodName] = function() { return this.bridge.deserialize(this.bridge.callASMethod(this.fb_instance_id, methodName, FABridge.argsToArray(arguments))); } }, // Function Proxies //returns the AS proxy for the specified function ID getFunctionProxy: function(funcID) { var bridge = this; if (this.remoteFunctionCache[funcID] == null) { this.remoteFunctionCache[funcID] = function() { bridge.callASFunction(funcID, FABridge.argsToArray(arguments)); } } return this.remoteFunctionCache[funcID]; }, //reutrns the ID of the given function; if it doesnt exist it is created and added to the local cache getFunctionID: function(func) { if (func.__bridge_id__ == undefined) { func.__bridge_id__ = this.makeID(this.nextLocalFuncID++); this.localFunctionCache[func.__bridge_id__] = func; } return func.__bridge_id__; }, // serialization / deserialization serialize: function(value) { var result = {}; var t = typeof(value); //primitives are kept as such if (t == "number" || t == "string" || t == "boolean" || t == null || t == undefined) { result = value; } else if (value instanceof Array) { //arrays are serializesd recursively result = []; for (var i = 0; i < value.length; i++) { result[i] = this.serialize(value[i]); } } else if (t == "function") { //js functions are assigned an ID and stored in the local cache result.type = FABridge.TYPE_JSFUNCTION; result.value = this.getFunctionID(value); } else if (value instanceof ASProxy) { result.type = FABridge.TYPE_ASINSTANCE; result.value = value.fb_instance_id; } else { result.type = FABridge.TYPE_ANONYMOUS; result.value = value; } return result; }, //on deserialization we always check the return for the specific error code that is used to marshall NPE's into JS errors // the unpacking is done by returning the value on each pachet for objects/arrays deserialize: function(packedValue) { var result; var t = typeof(packedValue); if (t == "number" || t == "string" || t == "boolean" || packedValue == null || packedValue == undefined) { result = this.handleError(packedValue); } else if (packedValue instanceof Array) { result = []; for (var i = 0; i < packedValue.length; i++) { result[i] = this.deserialize(packedValue[i]); } } else if (t == "object") { for(var i = 0; i < packedValue.newTypes.length; i++) { this.addTypeDataToCache(packedValue.newTypes[i]); } for (var aRefID in packedValue.newRefs) { this.createProxy(aRefID, packedValue.newRefs[aRefID]); } if (packedValue.type == FABridge.TYPE_PRIMITIVE) { result = packedValue.value; } else if (packedValue.type == FABridge.TYPE_ASFUNCTION) { result = this.getFunctionProxy(packedValue.value); } else if (packedValue.type == FABridge.TYPE_ASINSTANCE) { result = this.getProxy(packedValue.value); } else if (packedValue.type == FABridge.TYPE_ANONYMOUS) { result = packedValue.value; } } return result; }, //increases the reference count for the given object addRef: function(obj) { this.target.incRef(obj.fb_instance_id); }, //decrease the reference count for the given object and release it if needed release:function(obj) { this.target.releaseRef(obj.fb_instance_id); }, // check the given value for the components of the hard-coded error code : __FLASHERROR // used to marshall NPE's into flash handleError: function(value) { if (typeof(value)=="string" && value.indexOf("__FLASHERROR")==0) { var myErrorMessage = value.split("||"); if(FABridge.refCount > 0 ) { FABridge.refCount--; } throw new Error(myErrorMessage[1]); return value; } else { return value; } } }; // The root ASProxy class that facades a flash object ASProxy = function(bridge, typeName) { this.bridge = bridge; this.typeName = typeName; return this; }; //methods available on each ASProxy object ASProxy.prototype = { get: function(propName) { return this.bridge.deserialize(this.bridge.getPropertyFromAS(this.fb_instance_id, propName)); }, set: function(propName, value) { this.bridge.setPropertyInAS(this.fb_instance_id, propName, value); }, call: function(funcName, args) { this.bridge.callASMethod(this.fb_instance_id, funcName, args); }, addRef: function() { this.bridge.addRef(this); }, release: function() { this.bridge.release(this); } };