//////////////////////////////////////////////////////////////////////////////// // // 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.automation { import flash.display.DisplayObject; import flash.display.DisplayObjectContainer; import flash.display.Stage; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.FocusEvent; import flash.events.IEventDispatcher; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.geom.Point; import flash.system.ApplicationDomain; import flash.utils.Dictionary; import flash.utils.clearTimeout; import flash.utils.getDefinitionByName; import flash.utils.getQualifiedClassName; import flash.utils.getQualifiedSuperclassName; import flash.utils.setTimeout; import mx.automation.delegates.DragManagerAutomationImpl; import mx.automation.events.AutomationAirEvent; import mx.automation.events.AutomationEvent; import mx.automation.events.AutomationRecordEvent; import mx.automation.events.AutomationReplayEvent; import mx.automation.events.EventDetails; import mx.automation.events.MarshalledAutomationEvent; import mx.controls.Alert; import mx.core.Application; import mx.core.Container; import mx.core.EventPriority; import mx.core.IChildList; import mx.core.IDeferredInstantiationUIComponent; import mx.core.IFlexModuleFactory; import mx.core.IRawChildrenContainer; import mx.core.ISWFBridgeProvider; import mx.core.IUIComponent; import mx.core.UIComponent; import mx.core.mx_internal; import mx.events.DragEvent; import mx.events.FlexChangeEvent; import mx.events.FlexEvent; import mx.events.InterManagerRequest; import mx.events.SandboxMouseEvent; import mx.managers.IMarshalSystemManager; import mx.managers.ISystemManager; import mx.managers.SystemManager; import mx.managers.SystemManagerProxy; import mx.modules.ModuleManager; import mx.resources.IResourceManager; import mx.resources.ResourceManager; import mx.styles.IStyleClient; use namespace mx_internal; [Mixin] [ResourceBundle("automation_agent")] /** * Provides the interface for manipulating the automation hierarchy, * and for recording and replaying events. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class AutomationManager extends EventDispatcher implements IAutomationManager2, IAutomationObjectHelper, IAutomationMouseSimulator, IAutomationDebugTracer { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Class constants // //-------------------------------------------------------------------------- /** * @private */ private static const MOUSE_CLICK_TYPES:Array = [ MouseEvent.MOUSE_OVER, MouseEvent.MOUSE_DOWN, MouseEvent.MOUSE_UP, MouseEvent.CLICK ]; /** * @private */ private static const KEY_CLICK_TYPES:Array = [ KeyboardEvent.KEY_DOWN, KeyboardEvent.KEY_UP ]; //-------------------------------------------------------------------------- // // Class variables // //-------------------------------------------------------------------------- /** * @private * Dictionary of all app domains/systemManagers */ private static var allSystemManagers:Dictionary = new Dictionary(true); /** * @private * The highest place we can listen for events in our DOM */ private static var _mainListenerObj:IEventDispatcher; /** * @private * The uniqueAppID of this applicaiton as decided by the * root AutomationManager * This field will be the applicaiton.id for the root appliction */ private static var _uniqueApplicationId:String; /** * @private * The start point of this application in screen coordinates */ private static var _appStartPoint:Point; /** * @private * the system manager of the current applicaiton domain */ private static var sm1:ISystemManager; private static var _sm1MSm:IMarshalSystemManager; /** * @private * The popup's of the current appliation domain */ private static var popUpObjects:Array; /** * @private * The daragProxy from the sub application */ private static var currentDragProxyHolder:Array; private static var allAirWindowsToIdDictionary:Dictionary = new Dictionary(true); private static var allAirIdToWindowsDictionary:Dictionary = new Dictionary(true); private static var allAirWindowList:Array = new Array(); private static var lastRegisteredWindowCount:int = 0; private static const airWindowIdFixedString:String = "_AIRWindow_"; public static const airWindowIndicatorPropertyName:String = "isAIRWindow"; //-------------------------------------------------------------------------- // // Class methods // //-------------------------------------------------------------------------- private static function get mainListenerObj():IEventDispatcher { if(!_mainListenerObj) initMainListeners(); return _mainListenerObj; } private static function get sm1MSm():IMarshalSystemManager { if(!_sm1MSm) initMainListeners(); return _sm1MSm; } /** * @private * Function invoked by the SystemManager. Creates AutomationManager singleton. */ public static function init(root:DisplayObject):void { if(!Automation.initialized) { sm1 = root as ISystemManager; var sysMgr:SystemManager = root as SystemManager; var tempOj:Object = sysMgr.topLevelSystemManager.getImplementation("mx.managers::IMarshalSystemManager"); _sm1MSm = IMarshalSystemManager(sysMgr.topLevelSystemManager.getImplementation("mx.managers::IMarshalSystemManager") ); //sm1MSm = root as IMarshalSystemManager; // add event listener for the new sand_box_bridge event. // whenver we get the new bridge //mainListenerObj = getMainListenerObject(sm1); Automation.automationManager = new AutomationManager; AutomationHelper.registerSystemManager(sm1); } } /** * @private */ private static function isChild(parent:DisplayObject, child:DisplayObject):Boolean { while (child != null) { if (parent == child) return true; child = child.parent; } return false; } /** * @private */ private static function comparePropertyValues(lhs:Object, rhs:Object):Boolean { //we should probably be use the DefaultPropertyCodec to transcoding help here //parts coming in from the testing tool should be properly typed, but they aren't //so pretty much lhs will always be a String or RegExp if (lhs == null && rhs == null) return true; if ((lhs is String || lhs is Array) && lhs.length == 0 && rhs == null) return true; /* Commenting the trimming part below because XMLList is now retruning non-trimmed strings*/ //For strings we are trimming because otherwise, it returns false when compared with XML.toString() //because it returns trimmed strings /*if(rhs is String) rhs = trim(rhs as String); if(lhs is String) lhs = trim(lhs as String);*/ if ((lhs is XML ) && (lhs as XML).toXMLString.length == 0 && rhs == null) return true; if(rhs == null) { if ((lhs is XMLList ) && ((lhs as XMLList).length() == 1)) { var currentVar:XML = lhs[0]; if(currentVar.toXMLString().length == 0) return true; } } if (rhs == null) return false; if(rhs is Boolean) return (rhs == Boolean(Number(lhs))); if (lhs is Array) { if (!(rhs is Array)) return false; if (lhs.length != rhs.length) return false; for (var no:int = 0; no < lhs.length; ++no) { if (!comparePropertyValues(lhs[no], rhs[no])) return false; } return true; } else if (lhs is RegExp) return lhs.test(rhs.toString()); else if (lhs is String) return lhs == rhs.toString(); else return lhs == rhs; } /** * Applies [f] to each item in [list] by calling f(list[i]) * for i=0..[list].length. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ private static function map(f:Function, list:Array):void { for (var i:int = 0; i < list.length; i++) { f(list[i]); } } private static function isWhitespace( ch:String ):Boolean { return ch == '\r' || ch == '\n' || ch == '\f' || ch == '\t' || ch == ' '; } private static function trim(string:String):String { var n:int = string.length; var i:int; while(n>0) { if(isWhitespace(string.charAt(0))) { string = string.substring(1,n); n--; } else break; } n = string.length; while(n>0) { if(isWhitespace(string.charAt(n-1))) { string = string.substring(0,n-1); n--; } else break; } return string; } /** * @private */ private function childAddedHandler(event:Event):void { if (!Automation.delegateDictionary) return; var object:DisplayObject = event.target as DisplayObject; if (object && object.root && object.root is DisplayObject && !allSystemManagers[object.root]) allSystemManagers[object.root] = object.root; var delegateCreated:Boolean = createDelegate(event.target as DisplayObject); addDelegates(event.target as DisplayObject); if(delegateCreated == false) { var component:IAutomationObject = event.target as IAutomationObject; if(!component) // the obejct is not an IAutomationObject of the main applicaiton { if(object.parent ) { // try to get the parents classname. var className:String = getQualifiedClassName(object.parent); // when we get Alerts which are part of the another application // we cannot create the delegate here. Here we should send the details // to the other application and let them handle the same. if((className == "mx.managers::SystemManagerProxy")|| ((object.hasOwnProperty("className")&& (object["className"] == "DragProxy")))|| (className =="mx.managers.dragClasses::DragProxy")) //if(className == "mx.managers::SystemManagerProxy") { var tempEventObj:MarshalledAutomationEvent = new MarshalledAutomationEvent( MarshalledAutomationEvent.POPUP_HANDLER_REQUEST); var tempArr:Array = new Array(); tempArr.push(object); tempEventObj.interAppDataToSubApp = tempArr; dispatchMarshalledEventToSubApplications(tempEventObj); } } } } } /** * @private * Given a object returns the SystemManager object which contains * the applicationDomain containing the object class. */ private static function getSWFRoot(object:DisplayObject):DisplayObject { var className:String = getQualifiedClassName(object); var domain:ApplicationDomain; var compClass:Class; for (var p:* in allSystemManagers) { domain = p.loaderInfo.applicationDomain; try { compClass = Class(domain.getDefinition(className)); if (object is compClass) return p as DisplayObject; } catch(e:Error) { //exception means that the application domain //doesn't contain the object class. } } //we have failed to find the application domain in the dictionary. //try the nearest systemManager instance. var sm:DisplayObject = object; while(sm && !(sm is ISystemManager)) { sm = sm.parent; } if(sm) { domain = sm.loaderInfo.applicationDomain; try { compClass = Class(domain.getDefinition(className)); if (object is compClass) return sm; } catch(e:Error) { // we didnt get the current object in any of the system manager's domain // and we got this exception. It is quite possible that the class // is an internal class. so let us rerutn the last parent's system manager. // FLEXENT-1088, 1090, we should not return the parent's sm as this will prevent // the module's app domain getting tried out. //return sm; } } return null; } /** * @private */ private static function createDelegate(obj:DisplayObject):Boolean { var component:IAutomationObject = obj as IAutomationObject; //if(!(obj is IAutomationObject || component == null || component.automationDelegate)) // the above looks to be wrong as we were adding the delegate for the same object more than once // so change as follows if((component == null)||(component.automationDelegate)) { return false; } var retValue:Boolean = false; var appDomain:ApplicationDomain; var className:String = getQualifiedClassName(obj); var message:String; if(!className) { message = "class name for the object could not be obtained " + obj.toString(); Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",message); return false; } var sm:DisplayObject = getSWFRoot(obj); if(!sm) { var factory:IFlexModuleFactory = ModuleManager.getAssociatedFactory(obj); if (factory != null) { appDomain = ApplicationDomain(factory.info()["currentDomain"]); } else { message = "Factory module failure"; Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",message); } } else { appDomain = (sm.loaderInfo) ? sm.loaderInfo.applicationDomain : ApplicationDomain.currentDomain; } var delegateClass:Class = null; var compClass:Class = null; var mainComponentClass:Class = null; try { if(appDomain) { compClass = appDomain.getDefinition(className) as Class; mainComponentClass = compClass; delegateClass = Automation.delegateDictionary[compClass] as Class; } else { message = "Failed in getting the definition or the class or getting the delegate. " + "Automation will not work for this component. " + className; Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",message); } } catch(e:Error) { message = "Failed in getting the definition or the class or getting the delegate. " + "Automation will not work for this component. " + className; Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",message); Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",e.message); //return false; } if(!delegateClass && appDomain) { var componentClass:String = className; do { try { className = getQualifiedSuperclassName(appDomain.getDefinition(className)); if(className) { compClass = appDomain.getDefinition(className) as Class; delegateClass = Automation.delegateDictionary[compClass] as Class; } } catch(e:Error) { Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",e.message); break; } } while(!delegateClass && className); Automation.delegateDictionary[mainComponentClass] = delegateClass; //trace("Added mapping for : " + componentClass); if(!className) { message = "super class name for the object could not be obtained "+ componentClass; Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",message); return false; } } var c:Class = delegateClass; if (c) { try { var delegate:Object = new c (obj); } catch(e:Error) { Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",e.message); message = "Delegate object couldnot be created"; Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",message); } try { component.automationDelegate = delegate; retValue = true; } catch(e:Error) { Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",e.message); message = "object created but delegates not set"; Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",message); } } else{ message = "Unable to find definition for class : " + className; Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegate()",message); } return retValue; } /** * @private * Do a tree walk and add all children you can find. */ private static function addDelegates(o:DisplayObject):void { var child:DisplayObject ; var i:int; if (o is DisplayObjectContainer) { var doc:DisplayObjectContainer = DisplayObjectContainer(o); if (o is IRawChildrenContainer) { // trace("using view rawChildren"); var rawChildren:IChildList = IRawChildrenContainer(o).rawChildren; // recursively visit and add children of components // we don't do this for containers because we get individual // adds for the individual children for (i = 0; i < rawChildren.numChildren; i++) { try { child = rawChildren.getChildAt(i); createDelegate(child); addDelegates(child); } catch(error:SecurityError) { // Ignore this child if we can't access it } } } else { // trace("using container's children"); // recursively visit and add children of components // we don't do this for containers because we get individual // adds for the individual children for (i = 0; i < doc.numChildren; i++) { try { child = doc.getChildAt(i); createDelegate(child); addDelegates(child); } catch(error:SecurityError) { // Ignore this child if we can't access it } } } } // do special creation of repeater delegates as they do not // get added as children of Containers var container:Container; var repeaters:Array; var count:int; if(o is Container) { container = o as Container; repeaters = container.childRepeaters; // change for https://bugs.adobe.com/jira/browse/FLEXENT-1044 //if(!repeaters) // return; count = repeaters?repeaters.length:0; for(i = 0; i < count; ++i) { createDelegate(repeaters[i]); } } // do special creation of repeater delegates as they do not // get added as children of Containers if(o.parent is Container) { container = o.parent as Container; repeaters = container.childRepeaters; // change for https://bugs.adobe.com/jira/browse/FLEXENT-1044 //if(!repeaters) // return; count = repeaters?repeaters.length:0; for(i = 0; i < count; ++i) { var repeater:IAutomationObject = repeaters[i] as IAutomationObject; if(repeater && !repeater.automationDelegate) createDelegate(repeaters[i]); } } // let us add one more level of check for the repeaters. // change for https://bugs.adobe.com/jira/browse/FLEXENT-1044 if(o is UIComponent) { var uiComp:UIComponent = o as UIComponent; repeaters = uiComp.repeaters; count = repeaters? repeaters.length : 0; for(i = 0; i < count; ++i) { var repeater1:IAutomationObject = repeaters[i] as IAutomationObject; if(repeater1 && !repeater1.automationDelegate) createDelegate(repeaters[i]); } } } //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * @private * Constructor */ public function AutomationManager() { super(); //if(mainListenerObj) { // when the application is completed we need to add listener to the existing bridge sm1.addEventListener(FlexEvent.APPLICATION_COMPLETE, applicationCompleteHandler,false,EventPriority.DEFAULT); sm1.addEventListener(FlexChangeEvent.ADD_CHILD_BRIDGE , childBridgeHandler); sm1.addEventListener(Event.ADDED, childAddedHandler, false, 0, true); // FLEXENT-894 or 895 it was observerd that popupmenubutton menu popup object is // creatd before application completion. So we need to listen to the event // from the main app before the application completion. // this event is removed lated when we listen to the after application completion //mainListenerObj.addEventListener(MarshalledAutomationEvent.POPUP_HANDLER_REQUEST , popupHandlerBeforeApplicationCompletion, false, 0, true); // FLEXENT-1002 // when the sdk changes happened with the IMarshaledSystemManager, since the mainListenerObj was not available here // we moved the following line to the applicaiton completion handler. But we need this handled before the application completion // the popups of the application domain is added to the sandbox root application, we can depend on the sanbox to add to the listener for this // event. sm1.getSandboxRoot().addEventListener(MarshalledAutomationEvent.POPUP_HANDLER_REQUEST , popupHandlerBeforeApplicationCompletion, false, 0, true); } } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private */ private var lastMouseTarget:IEventDispatcher = null; /** * @private */ private var hierarchyCacheCounter:int = 0; /** * @private */ private var rebuildPartCache:Boolean = true; /** * @private * Holds automationIDPart objects for reuse. * The IDParts are cached for the duration of one recording/function call. */ private var cachedParts:Dictionary = null; /** * @private * Holds the array of automation children for a container. * The children are cached for the duration of one recording/function call. */ private var cachedChildren:Dictionary = null; /** * @private */ private var cachedCompositor:Dictionary = null; /** * @private */ private var cachingEvents:Boolean = false; /** * @private */ private var cachedTargetOriginator:EventDispatcher = null; /** * @private */ private var eventCache:Array = []; /** * @private */ private var flushCacheTimeoutID:int = -1; /** * @private */ private var recordedEventInCurrentMouseSequence:Boolean = false; /** * @private */ private var inMouseSequence:Boolean = false; /** * @private */ private var synchronization:Array = []; /** * @private */ private var _currentMousePositions:Array = []; /** * @private */ private var _prevMouseTargets:Array = []; /** * @private * Used for accessing localized Error messages. */ private var resourceManager:IResourceManager = ResourceManager.getInstance(); //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // automationEnvironment //---------------------------------- /** * @private * Storage for the automationEnvironment property. */ private static var _automationEnvironment:IAutomationEnvironment; private static var _automationEnvironmentString:String; private static var _automationEnvironmentHandlingClassName:String; /** * @private */ public function get automationEnvironment():Object { //For AIR apps it is possible that environment details for main app are set //after the child apps request handlers are handled. So it can be null for child apps //intially. We need a way to get the environment details for AIR apps when actually needed if(!_automationEnvironment) //happens only for AIR apps { // we will listen to the initial details from our parent. var initialStatusRequest:MarshalledAutomationEvent = new MarshalledAutomationEvent(MarshalledAutomationEvent.INITIAL_DETAILS_REQUEST); _inInitialDetailsRequestProcessing = true; dispatchToParent(initialStatusRequest); } return _automationEnvironment; } /** * @private */ public function set automationEnvironment(value:Object):void { _automationEnvironment = value as IAutomationEnvironment; // we expect this method to be called only on the top root applicaiton } /** * @private */ public function get automationEnvironmentString():String { return _automationEnvironmentString; } /** * @private */ public function set automationEnvironmentString(value:String):void { _automationEnvironmentString = value; } public function getUniqueApplicationID():String { if (_uniqueApplicationId == null) { if(sm1.isTopLevelRoot() == false) { // we should the following approach only for the sub // applicaiton in the main security domain // applications across security domain cannot access the // parents of the system manager if(sm1.isTopLevel() && (sm1.topLevelSystemManager.getSandboxRoot() == sm1.getSandboxRoot()) ) { // we need to get the id of the current application from the parent dispatchUniqueAppIdRequestEvent(); var currentApplicationId:String = Automation.getMainApplication().className; _uniqueApplicationId = currentApplicationId + "_"+ _uniqueApplicationId; //trace(_uniqueApplicationId + " - "+ classNameArray.join("|")); } else { // we expect this loop to reach for the sub applicaiton is // different security domain var temp:int = 0; } } else { if(Automation.getMainApplication().hasOwnProperty("applicationID"))// this should work for AIR app's { _uniqueApplicationId = Automation.getMainApplication().applicationID; } else _uniqueApplicationId = Automation.getMainApplication().id; if(!_uniqueApplicationId) _uniqueApplicationId = AutomationHelper.getAppTitle(); } } return _uniqueApplicationId; } //This method is used only by Flex apps which are loaded from air apps // to get the start point of their main air app in screen coordinates public function getStartPointInScreenCoordinates(windowId:String):Point { var startPointRequest:MarshalledAutomationEvent = new MarshalledAutomationEvent(MarshalledAutomationEvent.START_POINT_REQUEST); _inStartPointRequestProcessing = true; var tempArray:Array = []; tempArray.push(windowId); startPointRequest.interAppDataToMainApp = tempArray; dispatchToParent(startPointRequest); //reply handler for the above event (startPointReplyHandler) would store the //start point in the variable _appStartPoint return _appStartPoint; } private function dispatchStartPointRequestEvent(windowId:String):void { var startPointRequest:MarshalledAutomationEvent = new MarshalledAutomationEvent(MarshalledAutomationEvent.START_POINT_REQUEST); _inStartPointRequestProcessing = true; var tempArray:Array = []; tempArray.push(windowId); startPointRequest.interAppDataToMainApp = tempArray; dispatchToBridgeParent(startPointRequest); } private static function getChildIndex1(parent:DisplayObjectContainer, child:DisplayObject):int { try { return parent.getChildIndex(child); } catch(e:Error) { if (parent is IRawChildrenContainer) return IRawChildrenContainer(parent).rawChildren.getChildIndex(child); throw e; } throw new Error("FocusManager.getChildIndex failed"); // shouldn't ever get here } /** * @private */ public function set automationEnvironmentHandlingClassName(className:String):void { _automationEnvironmentHandlingClassName = className; } /** * @private */ public function get automationEnvironmentHandlingClassName():String { return _automationEnvironmentHandlingClassName; } //---------------------------------- // recording //---------------------------------- /** * @private * Storage for the recording property. */ private var _recording:Boolean = false; /** * @private */ public function get recording():Boolean { return _recording; } //---------------------------------- // replaying //---------------------------------- /** * @private * Storage for the replaying property. */ private var _replaying:Boolean = false; /** * @private */ public function get replaying():Boolean { return _replaying; } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * @private */ public function getParent( obj:IAutomationObject, parentToStopAt:IAutomationObject = null, ignoreShowInHierarchy:Boolean = false):IAutomationObject { while (obj) { var parent:IAutomationObject; if(obj is IAutomationObject) parent = (obj as IAutomationObject).automationOwner as IAutomationObject; if (!parent && (obj is IUIComponent ) &&(IUIComponent(obj).systemManager != null) && (IUIComponent(obj).systemManager.document != null) && (obj != IUIComponent(obj).systemManager.document)) { var doc:Object = IUIComponent(obj).systemManager.document; parent = (doc as IAutomationObject); } else { var parentObj:DisplayObject; if(obj is IAutomationObject) parentObj = (obj as IAutomationObject).automationOwner as DisplayObject; var parentClassName:String = getQualifiedClassName(parentObj); if(parentClassName == "mx.managers::SystemManagerProxy") { parent = (Automation.getMainApplication() as IAutomationObject); } } if (parent && (parent == parentToStopAt || ignoreShowInHierarchy || showInHierarchy(parent))) { return parent; } else { obj = parent; } } return null; } /** * @private */ public function getChildrenFromIDPart( obj:IAutomationObject, part:AutomationIDPart = null, ignoreShowInHierarchy:Boolean = false):Array { if (part == null) { return getChildren(obj, ignoreShowInHierarchy); } else { // important to do this check and not rely on it being checked // when getChildren is eventually called by resolveIDPart because // resolving always ignores the show in hierarchy and automation // composite flags because resolving can be delegated to children // that are not visible in the hieararchy (see comment in scoreChild) if (!obj || !(obj is IAutomationObject) || !(ignoreShowInHierarchy || showInHierarchy(obj))) { return []; } return resolveIDPart(obj, part); } } private function getApplicationChildren(obj:IAutomationObject):Array { var result:Array = []; if ( (obj is IUIComponent) && (IUIComponent(obj).systemManager != null)&&(obj == IUIComponent(obj).systemManager.document)) { var sm:IChildList = null; if(IUIComponent(obj).systemManager is IChildList) sm = IChildList(IUIComponent(obj).systemManager); var x:DisplayObject; var delegate:IAutomationObject; var count:int = sm?sm.numChildren:0; for (var i:int = 0; i < count; i++) { //check that the automationParent is null because //popup menus will all be children of SM but only one //is the root, the rest are automation children of other menus x = sm.getChildAt(i); delegate = (x as IAutomationObject); if (delegate && delegate != obj && (!(delegate.automationOwner is IAutomationObject) || delegate.automationOwner == obj)) { result.push(delegate); } } var popupChildren:IChildList = null; if(IUIComponent(obj).systemManager && (IUIComponent(obj).systemManager.popUpChildren) is IChildList ) popupChildren= IChildList(IUIComponent(obj).systemManager.popUpChildren); var count1:int = popupChildren? popupChildren.numChildren:0; for (i = 0; i < count1; i++) { //check that the automationParent is null because //popup menus will all be children of SM but only one //is the root, the rest are automation children of other menus x = popupChildren.getChildAt(i); delegate = (x as IAutomationObject); if (delegate && delegate != obj && (!(delegate.automationOwner is IAutomationObject) || delegate.automationOwner == obj)) { result.push(delegate); } } } return result; } /** * @private */ public function getChildren(obj:IAutomationObject, ignoreShowInHierarchy:Boolean = false):Array { if (!obj || !(obj is IAutomationObject) || !(ignoreShowInHierarchy || showInHierarchy(obj))) { return []; } var result:Array = cachedChildren != null ? cachedChildren[obj] : null; if (result == null) { result = getChildrenRecursively(obj); if( (obj is IUIComponent) && (IUIComponent(obj).systemManager != null) && (obj == IUIComponent(obj).systemManager.document)) { var children:Array = getApplicationChildren(obj); result = result ? result.concat(children) : children; } result = result || []; if (hierarchyCacheCounter > 0) cachedChildren[obj] = result; } return result; } /** * @private */ private function getChildrenRecursively( aoc:IAutomationObject):Array { var result:Array = null; // code modified below to avoid the usage of numAutomationChildren and // getAutomationChildAt in a loop //var childList:Array = aoc.getAutomationChildren(); var childList:Array = getAutomationChildrenArray(aoc); var numAutomationChildren:int = childList?childList.length:0; //var numAutomationChildren:int = aoc.numAutomationChildren; for (var i:int = 0; i < numAutomationChildren; i++) { //var ao:IAutomationObject = aoc.getAutomationChildAt(i); var ao:IAutomationObject = childList[i] as IAutomationObject; if(ao) { if (isAutomationComposite(ao)) continue; if (! result) result = []; result.push(ao); if (showInHierarchy(ao)) continue; // we dont need this check as this check itself needs to // calculate all its children //if (ao.numAutomationChildren > 0) { var x:Array = getChildrenRecursively(ao) if (x && x.length) result = result ? result.concat(x) : x; } } } return result; } /** * @private */ public function getAutomationName(obj:IAutomationObject):String { if (!obj) return null; var result:Object = createIDPart(obj); return result.automationName; } /** * @private */ public function getAutomationClassName(obj:IAutomationObject):String { if (!obj) return null; if(automationEnvironment) { var automationClass:IAutomationClass = automationEnvironment.getAutomationClassByInstance(obj); return automationClass ? automationClass.name : null; } else return null; } /** * @private */ public function getProperties(obj:IAutomationObject, names:Array = null, forVerification:Boolean = true, forDescription:Boolean = true):Array { if (!obj) return null; try { incrementCacheCounter(); // in the marshalle application if the tool libraries have not // handled the requriemetns all applications will not be getting the // env details. if(!automationEnvironment) return null; var automationClass:IAutomationClass = automationEnvironment.getAutomationClassByInstance(obj); var propertMap:Object = automationClass.propertyNameMap; var i:int; var result:Array = []; if (!names) { var propertyDescriptors:Array = automationClass.getPropertyDescriptors(obj, forVerification, forDescription); names = []; for (i = 0; i < propertyDescriptors.length; i++) { names[i] = propertyDescriptors[i].name; } } var part:Object = createIDPartForSpecifiedProperties(names,obj as IAutomationObject); for (i = 0; i < names.length; i++) { var propertyDescriptor:IAutomationPropertyDescriptor = propertMap[ names[i] ]; var value:Object = (propertyDescriptor ? getPropertyValueFromPart(part,obj, propertyDescriptor) : null); //don't convert to String, testing tools want it //delivered in the correct type result.push(value); } decrementCacheCounter(); } catch(e:Error) { decrementCacheCounter(); throw e; } return result; } /** * @private */ public function getTabularData(obj:IAutomationObject):IAutomationTabularData { return obj.automationTabularData as IAutomationTabularData; } /** * @private */ public function replayAutomatableEvent(event:AutomationReplayEvent):Boolean { var re:AutomationReplayEvent = event as AutomationReplayEvent; // check the recorded line count whether it is the max allowed limit var recordedLinesCount:Number= Automation.incrementRecordedLinesCount(); var licencePresent:Boolean = Automation.isLicensePresent(); if((recordedLinesCount > Automation.recordReplayLimit ) && (licencePresent == false)) { _replaying = false; if(Automation.errorShown == false) { var warningMessage:String = resourceManager.getString( "automation_agent", "replayLimitReached"); Alert.show( warningMessage ); Automation.errorShown = true; } return false; } // required to make MouseMove work if (re.replayableEvent is MouseEvent || ("triggerEvent" in re.replayableEvent && re.replayableEvent["triggerEvent"] is MouseEvent)) { var evDispatcher:IEventDispatcher = re.automationObject as IEventDispatcher; var rollOver:MouseEvent = new MouseEvent(MouseEvent.ROLL_OVER, false); replayMouseEventInternal(evDispatcher, rollOver); var mouseOver:MouseEvent = new MouseEvent(MouseEvent.MOUSE_OVER); replayMouseEventInternal(evDispatcher, mouseOver); } if (! isVisible(re.automationObject as DisplayObject)) { var message:String = resourceManager.getString( "automation_agent", "notVisible", [re.automationObject.automationName]); throw new AutomationError(message, AutomationError.OBJECT_NOT_VISIBLE); } pushMouseSimulator(re.automationObject, re.replayableEvent); _replaying = true; var uiObject:IAutomationObject = re.automationObject as IAutomationObject; if (uiObject && !(uiObject.automationVisible && uiObject.automationEnabled)) { re.succeeded = false; } else { re.succeeded = re.automationObject.replayAutomatableEvent(re.replayableEvent); } _replaying = false; popMouseSimulator(); return dispatchEvent(re); } /** * @private * */ // commented out the sandbox mouse events as it was causing the event overflow when we have the air window // in the application. Found all the trial cases worked even without that. when we face an issue we need to // analyse the WindowedSystemManager -> System Manager otherSystemManagerMouseListener sequence to analyse the // event overflow reason. public function beginRecording():void { if (!recording) { _recording = true; sm1.addEventListener(AutomationRecordEvent.RECORD, recordHandler, false, EventPriority.DEFAULT_HANDLER, true); sm1.getSandboxRoot().addEventListener(MouseEvent.MOUSE_DOWN, captureIDFromMouseDownEvent, true, 0, true); //sm1.getSandboxRoot().addEventListener(SandboxMouseEvent.MOUSE_DOWN_SOMEWHERE, // captureIDFromMouseDownEvent, true, 0, true); sm1.addEventListener(KeyboardEvent.KEY_DOWN, captureIDFromKeyDownEvent, true, 0, true); //ideally we would listen in the bubble phase so //we'd get this last and all components have had a chance //to react and record events, but some components are stopping //the propagation so capture first and flush events //in a delayed manner sm1.getSandboxRoot().addEventListener(MouseEvent.CLICK, onEndMouseSequence, true, 0, true); sm1.getSandboxRoot().addEventListener(MouseEvent.DOUBLE_CLICK, onEndMouseSequence, true, 0, true); //sm1.getSandboxRoot().addEventListener(SandboxMouseEvent.CLICK_SOMEWHERE, // onEndMouseSequence, true, 0, true); // sm1.getSandboxRoot().addEventListener(SandboxMouseEvent.DOUBLE_CLICK_SOMEWHERE, // onEndMouseSequence, true, 0, true); sm1.addEventListener(KeyboardEvent.KEY_UP, onEndKeySequence, true, 0, true); //Ideally we'd flush events after the last click (or double click) //however the player has a bug where it doesn't always send click //events (and also there can be legitimate times when a click //event won't come through, souch as a mouse down, mouse move off //the component then a mouse up), so do a timed flush after the //mouse up (it needs to be after any click events that might occur) sm1.getSandboxRoot().addEventListener(MouseEvent.MOUSE_UP, onEndMouseSequence, true, 0, true); //sm1.getSandboxRoot().addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, //onEndMouseSequence, true, 0, true); sm1.getSandboxRoot().addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler, false, 0, true); dispatchEvent(new AutomationEvent); // if we are the top level automation Manager, we should have recieved this call. // we need to inform other managers about this record starting if(sm1.isTopLevelRoot()) { var beginRecordMarshalledEvent:MarshalledAutomationEvent = new MarshalledAutomationEvent (MarshalledAutomationEvent.BEGIN_RECORDING); dispatchMarshalledEventToSubApplications(beginRecordMarshalledEvent); } } } /** * @private */ public function endRecording():void { if (recording) { _recording = false; dispatchEvent(new AutomationEvent(AutomationEvent.END_RECORD)); sm1.removeEventListener(AutomationRecordEvent.RECORD, recordHandler); sm1.getSandboxRoot().removeEventListener(MouseEvent.MOUSE_DOWN, captureIDFromMouseDownEvent, true); sm1.getSandboxRoot().removeEventListener(SandboxMouseEvent.MOUSE_DOWN_SOMEWHERE, captureIDFromMouseDownEvent, true); sm1.removeEventListener(KeyboardEvent.KEY_DOWN, captureIDFromKeyDownEvent, true); sm1.getSandboxRoot().removeEventListener(MouseEvent.CLICK, onEndMouseSequence, true); sm1.getSandboxRoot().removeEventListener(MouseEvent.DOUBLE_CLICK, onEndMouseSequence, true); sm1.getSandboxRoot().removeEventListener(MouseEvent.MOUSE_UP, onEndMouseSequence, true); sm1.getSandboxRoot().removeEventListener(SandboxMouseEvent.CLICK_SOMEWHERE, onEndMouseSequence, true); sm1.getSandboxRoot().removeEventListener(SandboxMouseEvent.DOUBLE_CLICK_SOMEWHERE, onEndMouseSequence, true); sm1.getSandboxRoot().removeEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, onEndMouseSequence, true); sm1.removeEventListener(KeyboardEvent.KEY_UP, onEndKeySequence, true); clearHierarchyCache(); clearEventCache(); recordedEventInCurrentMouseSequence = false; inMouseSequence = false; } } public function getElementFromPoint2(x:int, y:int,windowId:String ):IAutomationObject { var stage:Stage = getAIRWindow(windowId).stage; return getElementFromPointOnRequiredWindow(x,y,stage); } private function getElementFromPointOnRequiredWindow(x:int, y:int, requiredStage:Stage):IAutomationObject { var o:Array = requiredStage.getObjectsUnderPoint(new Point(x, y)); for (var i:int = o.length - 1; i >= 0; i--) { var displayObject:DisplayObject = o[i]; while (displayObject != null) { // don't use showInHierarchy because that would prevent // checkpoints on things like boxes var delegate:IAutomationObject = (displayObject as IAutomationObject); if (delegate && // check that it's an IAutomationObject before // checking visible since some components // such as stage (which aren't IAutomationObjects) // will yell and shout if you call visible on them displayObject.visible) { var obj:IAutomationObject ; if(isAutomationComposite(delegate)) obj = getAutomationComposite(delegate); else obj = delegate; return obj; } displayObject = displayObject.parent; } } return null; } /** * @private */ public function getElementFromPoint(x:int, y:int):IAutomationObject { //use the stage, not the system manager to find elements //because popups do not appear as children of the system manager //and so things like alerts wouldn't be found var stage:Stage = Automation.getMainApplication().stage; return getElementFromPointOnRequiredWindow(x,y,stage); } /** * @private */ public function getRectangle(obj:DisplayObject):Array { var p:Point = new Point(0,0); p = obj.localToGlobal(p); // it was observed that the start points and the width and height are getting as // non interger values, which makes those values as zero at the other end. // so we are converting to the near int and passing. return [int(p.x), int(p.y),int( p.x + obj.width), int(p.y + obj.height)]; } /** * @private */ public function isVisible(obj:DisplayObject):Boolean { while (obj && obj != obj.root && obj != obj.stage) { if (!obj.visible) return false; obj = obj.parent; } return true; } /** * @private */ private function getDistanceFromOriginalEvent(event:AutomationRecordEvent):int { var distance:int = 0; var displayObject:DisplayObject = cachedTargetOriginator as DisplayObject; while (displayObject != null && displayObject != event.automationObject) { ++distance; displayObject = displayObject.parent; } return displayObject == event.automationObject ? distance : int.MAX_VALUE; } /** * @private */ public function onEndKeySequence(event:KeyboardEvent):void { /* if (flushCachedEvents() == null) onFinishEventSequence(); inMouseSequence = false; */ } /** * @private */ public function onEndMouseSequence(event:Event):void { if (flushCacheTimeoutID != -1) clearTimeout(flushCacheTimeoutID); if(event.type == MouseEvent.MOUSE_UP || event.type == SandboxMouseEvent.MOUSE_UP_SOMEWHERE || event.type == SandboxMouseEvent.CLICK_SOMEWHERE) rebuildPartCache = true; // we're in the capture phase of mouse up sometimes, // so put in the timeout in either case. so don't try // to optimize by checking eventCache.length flushCacheTimeoutID = setTimeout(endMouseSequence, event.type == MouseEvent.MOUSE_UP || event.type == SandboxMouseEvent.MOUSE_UP_SOMEWHERE || event.type == SandboxMouseEvent.CLICK_SOMEWHERE ? 500 : 1); } /** * @private */ protected function keyFocusChangeHandler(event:FocusEvent):void { var focusTarget:Object = event.target; // check whether the focus target is from the same application // else do not record this event // we want to avoid the recording of the bubbled event from other applications if(focusTarget.root != sm1) return; var dispatcher:IAutomationObject = null; if(Automation.getMainApplication() is IAutomationObject) dispatcher = IAutomationObject(Automation.getMainApplication()); var ao:IAutomationObject = (focusTarget as IAutomationObject); if (ao) dispatcher = (getAutomationComposite(ao) || ao); recordAutomatableEvent(dispatcher, event); } /** * @private */ private function endMouseSequence():void { if (flushCacheTimeoutID != -1) { clearTimeout(flushCacheTimeoutID); flushCacheTimeoutID = -1; } if (flushCachedEvents() == null) onFinishEventSequence(); inMouseSequence = false; } /** * @private * * This is only public because the test harness needs to call this * due to a bug in the player. No one should call this. */ public function flushCachedEvents():Event { var event:AutomationRecordEvent; if (eventCache.length > 0) { var closestEvents:Array = []; var closestDistance:int = int.MAX_VALUE; for (var i:int = 0; i < eventCache.length; ++i) { event = eventCache[i]; var distance:int = getDistanceFromOriginalEvent(event); if (distance < closestDistance) { closestDistance = distance; closestEvents = []; closestEvents.push(event); } else if (distance == closestDistance) { closestEvents.push(event); } } if (closestEvents.length > 0) event = closestEvents[0]; } if (event != null) { dispatchRecordEvent(event, true); // at this place we record a new line hence increment the counter // since this happened with a previous decrement, we need not check the // boundary conditions var recordedLinesCount:Number= Automation.incrementRecordedLinesCount(); } return event; } /** * @private */ public function resolveIDToSingleObject(rid:AutomationID, currentParent:IAutomationObject = null):IAutomationObject { var childArray:Array = resolveID(rid, currentParent); var message:String; if (childArray == null || childArray.length == 0) { message = resourceManager.getString( "automation_agent", "idNotResolved", [rid.toString()]); throw new AutomationError(message, AutomationError.OBJECT_NOT_FOUND); } if (childArray.length > 1) { message = resourceManager.getString( "automation_agent", "matchesMsg", [childArray.length, rid.toString().replace(/\n/, ' ')]) + ":\n"; for (var i:int = 0; i < childArray.length; i++) { message += AutomationClass.getClassName(childArray[i]) + "(" + childArray[i].automationName + ")\n"; } throw new AutomationError(message, AutomationError.OBJECT_NOT_UNIQUE); } return (childArray[0] as IAutomationObject); } private function isApplication(part:AutomationIDPart):Boolean { // for Air we have top level parents which are notapplications // they will be of the class // TBD this is just for prototype. This will not work if the user // has extended the Window class /*if(part["className"] == "mx.core.Window") return true;*/ if(part.hasOwnProperty(AutomationManager.airWindowIndicatorPropertyName)) return true; if(scoreChild((Automation.getMainApplication() as IAutomationObject), part,true) >= 0) return true; return false; } /** * @private */ public function resolveID(rid:AutomationID, currentParent:IAutomationObject = null):Array { var part:AutomationIDPart; var id:AutomationID = rid.clone(); var message:String; if (currentParent == null) { //remove the application part = id.removeFirst(); if (!isApplication(part)) { message = resourceManager.getString( "automation_agent", "rootApplication",[ id.toString()]); throw new AutomationError(message, AutomationError.ILLEGAL_RUNTIME_ID); } // check for the AIR window.we can get the current parent as null even for // window object if (part.hasOwnProperty(AutomationManager.airWindowIndicatorPropertyName)) { // get the automationName //var currentAutomationName:String = part["automationName"]; var currentAutomationName:String = getPassedUniqueName(part); currentParent = getAIRWindow(currentAutomationName) as IAutomationObject; } else currentParent = (Automation.getMainApplication() as IAutomationObject); } var result:Array = null; var currentChildArray:Array = [currentParent]; while (true) { if (id.isEmpty()) { result = currentChildArray; break; } // contains part for resolving part = id.removeFirst(); // child found by resolving currentChildArray = currentParent.resolveAutomationIDPart(part); //check for nothing found if (currentChildArray.length == 0) { //null results are legal on the last node for regex searches //because it just means there was no match, but not legal //when still traversing the parent nodes if (!id.isEmpty()) { message = resourceManager.getString( "automation_agent", "notResolved", [part.automationName, part.className, currentParent.automationName]); throw new AutomationError(message, AutomationError.OBJECT_NOT_FOUND); } } else { //check for too many parents found if (currentChildArray.length > 1 && !id.isEmpty()) { message = resourceManager.getString( "automation_agent", "matchesMsg", [currentChildArray.length, part.toString()]); throw new AutomationError(message, AutomationError.OBJECT_NOT_UNIQUE); } //check for parent = child if (currentChildArray[0] == currentParent) { message = resourceManager.getString( "automation_agent", "resolvedTo", [currentParent, currentChildArray[0]]); throw new AutomationError(message, AutomationError.ILLEGAL_OPERATION); } //traverse into the next parent if (currentChildArray[0].numAutomationChildren > 0) currentParent = currentChildArray[0] ; //check for nothing found else if (!id.isEmpty()) { message = resourceManager.getString( "automation_agent", "idResolved",[ id.toString()]); throw new AutomationError(message, AutomationError.ILLEGAL_RUNTIME_ID); } } } return result; } /** * @private */ public function resolveIDPartToSingleObject(parent:IAutomationObject, part:AutomationIDPart):IAutomationObject { var rid:AutomationID = new AutomationID(); rid.addFirst(part); return resolveIDToSingleObject(rid, parent); } /** * @private */ public function resolveIDPart(parent:IAutomationObject, part:AutomationIDPart):Array { var rid:AutomationID = new AutomationID(); rid.addFirst(part); return resolveID(rid, parent); } /** * @private */ public function createID(obj:IAutomationObject, relativeToParent:IAutomationObject = null):AutomationID { var result:AutomationID = new AutomationID(); if (obj == relativeToParent) return result; do { //if relativeToParent is not in the hiearchy, then we need to do a special //getParent so that we don't skip this parent var parent:IAutomationObject = getParent(obj, relativeToParent, true); // use the real parent for creating child ids var part:AutomationIDPart = createIDPart(obj, parent); result.addFirst(part); // respect showInHierarchy when walking parent chain obj = getParent(obj, relativeToParent); if (obj == relativeToParent) break; } while (obj); return result; } /** * @private */ public function createIDPart(obj:IAutomationObject, parent:IAutomationObject = null):AutomationIDPart { if (parent == null) parent = getParent(obj, null, true); var part:AutomationIDPart = (cachedParts ? cachedParts[obj] as AutomationIDPart : null); if (!part) { part = (parent ? parent.createAutomationIDPart(obj) as AutomationIDPart : helpCreateIDPart(null, obj)); if (hierarchyCacheCounter > 0) cachedParts[obj] = part; } return part; } /** * @private */ public function showInHierarchy(obj:IAutomationObject):Boolean { return obj == null || !(obj is IAutomationObject) || (!isAutomationComposite(obj) && obj.showInAutomationHierarchy); } /** * @private * * Helper implementation of IAutomationIDHelper. Resolves an id based * on a set of properties. This should not be used, instead use * resolveID, resolveIDToSingleObject, or resolveIDPart. */ public function helpResolveIDPart(parent:IAutomationObject, partObj:Object):Array { var part:AutomationIDPart = partObj as AutomationIDPart; if(!part) return []; // trace("--- searching for child [" + ObjectUtil.toString(part) + // "] in parent [" + parent.automationName + "]"); //Because resolving can be delegated to a child composite //we need to ignore hierarchy. An example is //ComboBox composites List. List will call helpResolveIDPart //but will appear to have no children since it's not in the //hierarchy, so pass true to getChildren to ignore showInHierarchy //Note that resolving off ComboBox instead of List would //not be appropriate since ComboBox may have other children (such //as edit propertyName or button) var children:Array = getChildren(parent, true); var winners:Array = getWinners(children,part,false); if(winners.length > 1) winners = getWinners(children,part,true); return winners; } /** * @private * */ private function getWinners(children:Array, part:AutomationIDPart,forceIndexCalculation:Boolean):Array { //Because resolving can be delegated to a child composite //we need to ignore hierarchy. An example is //ComboBox composites List. List will call helpResolveIDPart //but will appear to have no children since it's not in the //hierarchy, so pass true to getChildren to ignore showInHierarchy //Note that resolving off ComboBox instead of List would //not be appropriate since ComboBox may have other children (such //as edit propertyName or button) var winners:Array = []; var bestScore:int = -1; for (var i:int = 0; i < children.length; i++) { var child:IAutomationObject = children[i]; /* if (!child) { var message:String = resourceManager.getString( "automation_agent", "nullReturned", [i, parent.automationName, children.length]); throw new Error(message); } */ // we are commenting out the checks above for the following reason // this check stops the automation of the application if any cheild of the application // is null. Initially since the automation framework was supporting only flex, all components // by default will be inheriting IAutomationObject. But when the flash-flex compoenets got added // to the application, it broke the automation of other flex components also because of this check. // reason: flex-flash components were not implementing the IAutomationObjects and hence the components // corresponding to that became null and hence the automation stopped. // to avoid such a scenario we have commented out this check. // and the flash-flex components are planning to be changed to implement this interface soon. // till then the usage of this change will allow the users to continue with automation of other components. var score:int = -1; if (child != null) { score = scoreChild(child, part,forceIndexCalculation); } if (score == -1) continue; // we are not processing all possible objects to match // if we got an object with all the required properties of the part. if(score == int.MAX_VALUE) return [child]; if (score > bestScore) { bestScore = score; winners = []; } if (score == bestScore) winners.push(child); } return winners; } /** * @private * * Helper implementation of IAutomationIDHelper. Creates an id for * a given child. This should not be used, instead use createID, * or createIDPart. */ public function helpCreateIDPart(parent:IAutomationObject, child:IAutomationObject, automationNameCallback:Function = null, automationIndexCallback:Function = null):AutomationIDPart { var part:AutomationIDPart = new AutomationIDPart(); if(!automationEnvironment) return part; var automationClass:IAutomationClass = automationEnvironment.getAutomationClassByInstance(child); if(!automationClass) return part; var propertyDescriptors:Array = automationClass.getPropertyDescriptors(child, false, true); if(!propertyDescriptors) return part; //It doesn't matter if a property is null //add it anyways, because the callee asked for it //and not adding it will confuse QTP since we've //told it already about the properties in the env file //If this causes a problem and we need to add the if //null checks back, then be sure to update QTPAdapter //to not return null properties in Learn and ActiveScreen for (var propNo:int = 0; propNo < propertyDescriptors.length; ++propNo) { var propertyName:String = propertyDescriptors[propNo].name; if (propertyName == "id") { part.id = child is IDeferredInstantiationUIComponent ? IDeferredInstantiationUIComponent(child).id : null; if ((part.id == null) && (parent == null)) { //trace ("inside the helpCreateIDPart - id "+ child.automationName); // currently we are in the application object. // this is a temp fix till we have AIR delegates in place. // we need the application iD of this component instead of the id if(Automation.getMainApplication().hasOwnProperty("applicationID"))// this should work for AIR app's { part.id = Automation.getMainApplication().applicationID; //trace ("inside the helpCreateIDPart - id "+ part.id ); } else { //we are in flex app hosted from Air app part.id = processAppIDFromUniqueAppID(); } } } else if (propertyName == "automationName") part.automationName = (automationNameCallback == null ? child.automationName : automationNameCallback(child)); else if (propertyName == "automationIndex") //note that parent can be null if it's the parentApplication part.automationIndex = (automationIndexCallback == null ? getChildIndex(getParent(child), child) : automationIndexCallback(child)); else if (propertyName == "className") part.className = AutomationClass.getClassName(child); else if (propertyName == "automationClassName") part.automationClassName = getAutomationClassName(child); else if (propertyName == AutomationManager.airWindowIndicatorPropertyName) { // we added this property to identify the airtoplevel windows part.isAIRWindow = true; } else { if (propertyName in child) part[propertyName] = child[propertyName]; else if (child is IStyleClient) part[propertyName] = IStyleClient(child).getStyle(propertyName); else { var message:String = resourceManager.getString( "automation_agent", "notDefined", [propertyName, child]); traceMessage("AutomationManager", "helpCreateIDPart()", message); // throw new Error(message); } } } if ("automationName" in part && ((part.automationName == null)||(part.automationName.length == 0))) part.automationName = part.automationIndex; return part; } private function processAppIDFromUniqueAppID():String { var appId:String = getUniqueApplicationID(); var index:int = appId.lastIndexOf("_"); if(index != -1) { appId = appId.substring(index + 1, appId.length ); } else { appId = null; } return appId; } /** * @private */ private function isClassAvailable(className:String):Boolean { try { if(getDefinitionByName(className) != null) return true; } catch(e:Error) { return false; } return false; } /** * Dispatch the event as a replayable event. Causes the * ReplayableEventEvent with REPLAYABLE_EVENT type to be fired. * However, this method will not attempt the dispatch if there are no * listeners. * * @param eventReplayer The IEventReplayer dispatching this * event since event.target may not be * accurate * @param event The event that represents the replayable event. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function recordAutomatableEvent(recorder:IAutomationObject, event:Event, cacheable:Boolean = false):void { // this method should not get called with null event. // However during some usage of the events being dispatched programmatically // the trigger events may be null and the record of the same is called with the // null. Eventhough this is not the expected way of usage, a check is added. if (event == null) return; if (!recording) return; var message:String; var re:AutomationRecordEvent; if (event is AutomationRecordEvent) re = event as AutomationRecordEvent; else { re = new AutomationRecordEvent(AutomationRecordEvent.RECORD); re.automationObject = recorder; re.replayableEvent = event; re.cacheable = cacheable; } //swallow orphan clicks when not following a mouseDown if (re.replayableEvent.type == MouseEvent.CLICK && !inMouseSequence) return; // in the marshalled application if the tool libraries have not // handled the requriemetns all applications will not be getting the // env details. if(!automationEnvironment) return ; var automationClass:IAutomationClass = automationEnvironment.getAutomationClassByInstance(re.automationObject as IAutomationObject); if (automationClass == null) { message = resourceManager.getString( "automation_agent", "classNotFound", [AutomationClass.getClassName(re.automationObject)]); throw new Error(message); } var eventDescriptor:IAutomationEventDescriptor = automationClass.getDescriptorForEvent(re.replayableEvent); if (eventDescriptor == null) { message = resourceManager.getString( "automation_agent", "methodNotFound", [AutomationClass.getClassName(re.replayableEvent), automationClass]); throw new Error(message); } re.name = eventDescriptor.name; re.args = eventDescriptor.record(re.automationObject, re.replayableEvent); // check the recorded line count whether it is the max allowed limit var recordedLinesCount:Number= Automation.incrementRecordedLinesCount(); var licencePresent:Boolean = Automation.isLicensePresent(); if((recordedLinesCount > Automation.recordReplayLimit ) && (licencePresent == false)) { endRecording(); var warningMessage:String = resourceManager.getString( "automation_agent", "recordLimitReached"); Alert.show( warningMessage ); return; } // if the components are part of the Popup winodws (e.g Alert and objects // hosted by the popUpManager and if they belong to the non root applicaiotn // they are hosted by the main application. So it looks like the events dispatched on // them does not reach the appropriate application. So to handle the special case, // we directly call the record Handler. if(isObjectChildOfSystemManagerProxy (re.automationObject) ) recordHandler(re); if(getQualifiedClassName(re.automationObject) == "mx.controls::FlexNativeMenu") recordHandler(re); if (re.bubbles && re.automationObject is IEventDispatcher) IEventDispatcher(re.automationObject).dispatchEvent(re); else recordHandler(re); } /** * @private */ public function isObjectChildOfSystemManagerProxy(automationObject:IAutomationObject):Boolean { var obj:DisplayObject = automationObject as DisplayObject; if(obj == null) //this happens for FlexNativeMenu in AIR as it is not a DisplayObject return false; if(obj.parent == null) { // when the focus of the popup objects are taken to some other application // it is obsreverd that the parent is becoming null. // dont know whether it is an issue with sdk. // however since we have the list of poppup objects applicable to us // we can check in that. if((lastRemovedpopUpObject == automationObject as DisplayObject ) || (popUpObjects.indexOf(obj) != -1)) return true; } while(obj.parent) { if(obj.parent is SystemManagerProxy) return true; obj = obj.parent; } return false; } /** * @private */ public function isObjectPopUp(automationObject:IAutomationObject):Boolean { var obj:DisplayObject = automationObject as DisplayObject; if(obj == null) //this happens for FlexNativeMenu in AIR as it is not a DisplayObject return false; if(obj.parent == null) { // when the focus of the popup objects are taken to some other application // it is obsreverd that the parent is becoming null. // dont know whether it is an issue with sdk. // however since we have the list of poppup objects applicable to us // we can check in that. if((lastRemovedpopUpObject == automationObject as DisplayObject ) || (popUpObjects.indexOf(obj) != -1)) return true; } // we need to find out whether the object belongs to the system manager before it belongs to an application // this is also needed to find the popups from the main application. var applicationFound:Boolean = (obj is Application || isSparkApplication(obj))?true:false; while(obj.parent) { if(obj.parent is SystemManagerProxy) return true; else { if(obj.parent is Application || isSparkApplication(obj.parent)) applicationFound = true; else if ((obj.parent == sm1)&&(!applicationFound)) return true; } obj = obj.parent; } return false; } /** * @private */ public function recordHandler(te:Event):void { var re:AutomationRecordEvent = te as AutomationRecordEvent; if(re == null) return; if (re.isDefaultPrevented()) { // decrement the recording counter // as the recording does not happen here var recordedLinesCount1:Number= Automation.decrementRecordedLinesCount(); return; } if (!isAutomationComposite(re.automationObject)) { if (re.cacheable && cachingEvents) { eventCache.push(re); // decrement the recording counter // as the recording does not happen here var recordedLinesCount2:Number= Automation.decrementRecordedLinesCount(); } else dispatchRecordEvent(re, false); } else { // decrement the recording counter // as the recording does not happen here var recordedLinesCount3:Number= Automation.decrementRecordedLinesCount(); } } /** * @private */ public function addSynchronization(isComplete:Function, target:Object = null):void { synchronization.push({isComplete: isComplete, target: target }); } /** * @private */ public function isSynchronized(target:IAutomationObject):Boolean { for (var i:int = 0; i < synchronization.length; i++) { if (synchronization[i].isComplete()) synchronization.splice(i--, 1); else if (target == synchronization[i].target || (target && target == synchronization[i].target) || synchronization[i].target == null) return false; } return true; } /** * @private */ public function getMemberFromObject(obj:Object, name:String):Object { var part:Object; var component:Object; part = createIDPart(obj as IAutomationObject); component = obj; var result:Object = null; if (part != null && name in part) result = part[name]; else if (name in obj) result = obj[name]; else if (component != null) { if (name in component) result = component[name]; else if (component is IStyleClient) result = IStyleClient(component).getStyle(name); } return result; } /** * @private */ public function getPropertyValue(obj:Object, pd:IAutomationPropertyDescriptor, relativeParent:IAutomationObject = null):Object { return getMemberFromObject(obj, pd.name); } //-------------------------------------------------------------------------- // // Mouse simulator methods // //-------------------------------------------------------------------------- /** * @private */ public function getMouseX(item:DisplayObject):Number { return item.globalToLocal(_currentMousePositions[_currentMousePositions.length - 1]).x; } /** * @private */ public function getMouseY(item:DisplayObject):Number { return item.globalToLocal(_currentMousePositions[_currentMousePositions.length - 1]).y; } /** * @private */ private function replayMouseEventInternal(target:IEventDispatcher, event:MouseEvent):Boolean { //feed mouseOut and rollOut events to the last-clicked object, for defocussing purposes. if (lastMouseTarget && lastMouseTarget != target) { var sendMouseEvents:Boolean = true; // check whether the new target is child of the last target. // if it is a child we should not send mouseOut, rollOut events if(lastMouseTarget is DisplayObjectContainer && target is DisplayObject) { if(DisplayObjectContainer(lastMouseTarget).contains(target as DisplayObject)) { // make the inner most component as the last target. // should we have a stack of these inner components // so that we can playback rollOut for all of them? lastMouseTarget = target; sendMouseEvents = false; } } if(sendMouseEvents) { var mouseOut:MouseEvent = new MouseEvent(MouseEvent.MOUSE_OUT); var rollOut:MouseEvent = new MouseEvent(MouseEvent.ROLL_OUT, false); lastMouseTarget.dispatchEvent(mouseOut); lastMouseTarget.dispatchEvent(rollOut); lastMouseTarget = target as IEventDispatcher; } } if(!lastMouseTarget) lastMouseTarget = target as IEventDispatcher; return target.dispatchEvent(event); } private static var fakeMouseX:QName = new QName(mx_internal, "_mouseX"); private static var fakeMouseY:QName = new QName(mx_internal, "_mouseY"); /** * @private */ private function pushMouseSimulator(targetObj:Object, eventObj:Object):void { var target:DisplayObject = (targetObj is DisplayObject ? DisplayObject(targetObj) : null); var event:MouseEvent = (eventObj is MouseEvent ? MouseEvent(eventObj) : null); var pt:Point = (event != null ? new Point(event.localX, event.localY) : new Point(0, 0)); ; pt = target != null ? target.localToGlobal(pt) : pt; _currentMousePositions.push(pt); _prevMouseTargets.push(target); try { target.root[fakeMouseX] = pt.x; target.root[fakeMouseY] = pt.y; } catch(e:Error) { traceMessage("AutomationManager", "pushMouseSimulator()", AutomationConstants.invalidInAIR); } Automation.mouseSimulator = this; } /** * @private */ private function popMouseSimulator():void { _currentMousePositions.pop(); _prevMouseTargets.pop(); var target:Object = _prevMouseTargets[_prevMouseTargets.length-1]; if(target && target.root) { try { target.root[fakeMouseX] = _currentMousePositions[_currentMousePositions.length-1].x; target.root[fakeMouseY] = _currentMousePositions[_currentMousePositions.length-1].y; } catch(e:Error) { traceMessage("AutomationManager", "popMouseSimulator()", AutomationConstants.invalidInAIR); } } else { //var sm:ISystemManager = Application.application.systemManager; try { sm1[fakeMouseX] = undefined; sm1[fakeMouseY] = undefined; } catch(e:Error) { traceMessage("AutomationManager", "popMouseSimulator()", AutomationConstants.invalidInAIR); } if (!_currentMousePositions.length) Automation.mouseSimulator = null; } } //-------------------------------------------------------------------------- // // Replay helper methods // //-------------------------------------------------------------------------- /** * @private */ public function replayKeyboardEvent(to:IEventDispatcher, event:KeyboardEvent):Boolean { return replayKeyDownKeyUp(to, event.keyCode, event.ctrlKey, event.shiftKey); } /** * @private */ public function replayKeyDownKeyUp(to:IEventDispatcher, keyCode:uint, ctrlKey:Boolean = false, shiftKey:Boolean = false, altKey:Boolean = false):Boolean { map(function(type:String):void { var event:KeyboardEvent = new KeyboardEvent(type); event.keyCode = keyCode; event.ctrlKey = ctrlKey; event.shiftKey = shiftKey; event.altKey = altKey; to.dispatchEvent(event); }, KEY_CLICK_TYPES); return true; } /** * @private */ public function replayMouseEvent(target:IEventDispatcher, event:MouseEvent):Boolean { pushMouseSimulator(target, event); replayMouseEventInternal(target, event); popMouseSimulator(); return true; } /** * @private */ public function replayClick(to:IEventDispatcher, sourceEvent:MouseEvent = null):Boolean { sourceEvent = sourceEvent || new MouseEvent(MouseEvent.CLICK); pushMouseSimulator(to, sourceEvent); map(function(type:String):void { var localX:int = sourceEvent && !isNaN(sourceEvent.localX) ? sourceEvent.localX : 2; var localY:int = sourceEvent && !isNaN(sourceEvent.localY) ? sourceEvent.localY : 2; var event:MouseEvent = new MouseEvent(type, true, false, localX, localY); event.ctrlKey = sourceEvent.ctrlKey; event.shiftKey = sourceEvent.shiftKey; event.altKey = sourceEvent.altKey; event.buttonDown = (type == MouseEvent.MOUSE_DOWN); replayMouseEventInternal(to, event); }, MOUSE_CLICK_TYPES); popMouseSimulator(); return true; } /** * @private */ public function replayClickOffStage():Boolean { var x:Number = Automation.getMainApplication().screen.left - 1000; var y:Number = Automation.getMainApplication().screen.top - 1000; map(function(type:String):void { var event:MouseEvent = new MouseEvent(type); event.localX = x; event.localY = y; replayMouseEventInternal( IEventDispatcher(Automation.getMainApplication()), event); }, MOUSE_CLICK_TYPES); return true; } /** * @private * The cache of IDParts and children cannot be maintained indefinetely because * they get stale as application state chagnes. Hence control is required over * the life of cache. The incrementCacheCounter and decrementCacheCounter * APIs provide this control. The calls to these two APIs should be matched to maintian * cache in a healthy state. */ public function incrementCacheCounter():int { ++hierarchyCacheCounter; if (cachedParts == null) cachedParts = new Dictionary(); if (cachedChildren == null) cachedChildren = new Dictionary(); if(cachedCompositor == null) cachedCompositor = new Dictionary(); return hierarchyCacheCounter; } /** * @private * Takes a flag to force clear the cache. */ public function decrementCacheCounter(clearNow:Boolean = false):int { if (clearNow || hierarchyCacheCounter < 1) hierarchyCacheCounter = 1; --hierarchyCacheCounter; if (hierarchyCacheCounter <= 0) clearHierarchyCache(); return hierarchyCacheCounter; } /** * @private * Helper function to clear the cache contents. */ private function clearHierarchyCache():void { hierarchyCacheCounter = 0; cachedParts = null; cachedChildren = null; cachedCompositor = null; } /** * @private */ private function clearEventCache():void { eventCache = []; cachingEvents = false; cachedTargetOriginator = null; } /** * @private */ private function dispatchRecordEvent(event:AutomationRecordEvent, cacheable:Boolean = false):void { //the event is disqualifed if another event has already //been dispatched in the current mouse down, up, click sequence //should we limit this check to just cacheable event? maybe not //but it may impact the text events that are cached if (!cacheable || !inMouseSequence || !recordedEventInCurrentMouseSequence) { if (inMouseSequence) { //Events that are recorded during a mouse sequence, but are //not from the same location of the current mouse target //do not count. This is to avoid issues from text fields //that do their own event caching, dispatch events late //and look like they are part of the current mouse sequence //but aren't. For example, in a datagrid, if a user clicks //on an editable item and types something, that typing is //queued, if they then click on another item, the text field //will flush the typing. After that the data grid will record //a new item click. We don't want to through that out because //text field did some caching. They are unrelated. Do this //check by looking seeing if the current target is on the path //between cachedTargetOriginator and the root. //trace(event.methodName + " " + getDistanceFromOriginalEvent(event) + ", r:" + event.automationObject + ", ct:" + cachedTargetOriginator); if (getDistanceFromOriginalEvent(event) != int.MAX_VALUE) recordedEventInCurrentMouseSequence = true; } dispatchEvent(event); } //The cache should only be cleared when the last event has occurred //as a result of a low-level interaction. It's possible to dispatch //several non-cached events during a mouse sequence, such as drag scroll events if (cacheable || !inMouseSequence) onFinishEventSequence(); } public function recordCustomAutomationEvent(event:AutomationRecordEvent):Boolean { if(!recording) return false; if(event.type != AutomationRecordEvent.CUSTOM_RECORD) return false; if(!event.automationObject) return false; // create the AutomatioRecord event var eventObj:AutomationRecordEvent = new AutomationRecordEvent( AutomationRecordEvent.RECORD, event.bubbles,event.cancelable,event.automationObject,event.replayableEvent, event.args,event.name,event.cacheable); if(!eventObj.args) eventObj.args = new Array(); if(!eventObj.name) eventObj.name = "NoNamePassedByUser"; eventObj.recordTriggeredByCustomHandling = true; //dispatch this new event dispatchEvent(eventObj); return true; } /** * @private */ private function onFinishEventSequence():void { rebuildPartCache = true; clearEventCache(); decrementCacheCounter(); } /** * @private */ private function getChildIndex(parent:IAutomationObject, child:IAutomationObject):String { //parent shouldn't be null, unless it's the parentApplication if (parent != null) { parent = showInHierarchy(parent) ? parent : getParent(parent); var parentsChildren:Array = getChildren(parent); for (var childNo:int = 0; childNo < parentsChildren.length; ++childNo) { if (child == parentsChildren[childNo]) return "index:" + childNo; } } return "index:-1"; } /** * @private */ private function captureIDFromMouseDownEvent(event:Event):void { captureID(event); cachingEvents = true; cachedTargetOriginator = EventDispatcher(event.target); inMouseSequence = true; recordedEventInCurrentMouseSequence = false; } /** * @private */ private function captureIDFromKeyDownEvent(event:Event):void { /* captureID(event); cachingEvents = true; cachedTargetOriginator = EventDispatcher(event.target); inMouseSequence = true; recordedEventInCurrentMouseSequence = false; */ } /** * @private */ private function captureID(event:Event):void { if (flushCacheTimeoutID != -1) clearTimeout(flushCacheTimeoutID); //flush cached events because: // //a) sometimes there is no CLICK or DOUBLE_CLICK event //the events will only get flushed after a time out of about //500 MS. It's possible for someone to mouse down before that //timeout so check to here to see if any events need to be flushed // //b) keyboard events are never involved in caching flushCachedEvents(); if (rebuildPartCache) { //force the clear of the cache because even though //we match the increment here with a decrement after //an automation event is recorded, not all //low-level events will result in an automation //event being dispatched decrementCacheCounter(true); incrementCacheCounter(); var o:Object = event.target; while (o != null) { if (o is IAutomationObject) { if (isAutomationComposite(o as IAutomationObject)) o = getAutomationComposite(o as IAutomationObject); else break; } else o = DisplayObject(o).parent; } var obj:IAutomationObject = (o as IAutomationObject); while (obj) { var ao:IAutomationObject = (obj as IAutomationObject); // Earlier we were just using automationOwner as parent which is different // from the parent we get while calculating id using createIdPart(). // http://bugs.adobe.com/jira/browse/FLEXENT-1126 // http://bugs.adobe.com/jira/browse/FLEXENT-1251 // So using getParent() method here also which returns main application as // parent in special cases. var parent:IAutomationObject = getParent(obj, null, true); cachedParts[obj] = (parent ? parent.createAutomationIDPart(ao) : helpCreateIDPart(null, ao)); if(isAutomationComposite(ao)) { obj = getAutomationComposite(ao) as IAutomationObject; } else { obj = parent; } } rebuildPartCache = false; } } /** * @private */ private var className2previousClassName:Object = {}; /** * @private */ private function scoreChild(child:IAutomationObject, part:Object,calculateIndexAnyway:Boolean):int { // this method is a very expensive method // which tries to find the score for the passed child with the // passed part. //get the properties to score against from getProperties //rather than introspecting the automation object //directly, this will get any cached values and ensure //we use the same logic for automationName // first let us check whether the automation class name is matching, // if not we can give a negative score and return. var propertyNames:Array = []; var automationIndexNeeded:Boolean = false; var automationClassNameRequired:Boolean = false; var propertyName:String; for (propertyName in part) { if(propertyName != "automationIndex") { if(propertyName == "automationClassName") automationClassNameRequired = true; else propertyNames.push(propertyName); } else automationIndexNeeded = true; } var n:int = 0; var i:int = 0; //special case automationClassName because it's not a real property //and we currently aren't putting it in the environment info so //helpCreateIDPart will not populate it even if you ask for it //because it has no descriptor // we can ignore the other properties, if this is not equal. var partPropertyValue:Object = null; if (automationClassNameRequired) { childPropertyValue = getAutomationClassName(child); partPropertyValue = part["automationClassName"]; equal = comparePropertyValues(partPropertyValue, childPropertyValue); //if they are not equal it is possible that a script recorded in one version //is being used in another. So we check for compatible versions of this class if((!equal)&&(automationEnvironment)) { var previousVersionClassNames:Array = className2previousClassName[childPropertyValue]; if( previousVersionClassNames == null) { // in the marshalled application if the tool libraries have not // handled the requriemetns all applications will not be getting the // env details. var automationClass:IAutomationClass = automationEnvironment.getAutomationClassByInstance(child); if(automationClass is IAutomationClass2) { previousVersionClassNames = IAutomationClass2(automationClass).previousVersionClassNames; if(previousVersionClassNames == null) previousVersionClassNames = []; className2previousClassName[childPropertyValue] = previousVersionClassNames; } } n = previousVersionClassNames.length; for( i = 0; i < n; i++) { equal = comparePropertyValues(partPropertyValue, previousVersionClassNames[i]); if(equal) break; } } } if((!equal)&&(automationClassNameRequired)) { // we can ignore the other properties, if this is not equal. score = -1; allEqual = false; } else { var childProperties:Array = getProperties(child, propertyNames); var childPropertyMap:Object = {}; for (var childPropertyNo:int = 0; childPropertyNo < childProperties.length; ++childPropertyNo) { childPropertyMap[propertyNames[childPropertyNo]] = childProperties[childPropertyNo]; } var score:int = 0; var allEqual:Boolean = true; var criticalPropertiesToMatch:Array=["automationIndex","automationClassName","className","automationName","id"]; var criticalPropertiesMismatchFound:Boolean = false; var automationNameNeeded:Boolean = false; // iterate through part for (propertyName in part) { // calculating automation index is a very expensive process. // so we will delay it till all other required properties matches. if((propertyName.toLowerCase() != "automationindex")&&(propertyName.toLowerCase() != "automationclassname")) { var equal:Boolean; partPropertyValue = part[propertyName]; var childPropertyValue:Object = (propertyName in childPropertyMap) ? childPropertyMap[propertyName] : null; equal = comparePropertyValues(partPropertyValue, childPropertyValue); if((!equal)&&(propertyName.toLowerCase() == "automationname")&&((childPropertyValue=="")||(childPropertyValue==null))) { // in some components if the automationName is not specified, it is considered using the automationIndex. // since we are not creating automation index, we will leave it the benefit of doubt and will // create it again when we are forming automation index. automationNameNeeded = true; equal = true; } if (equal) ++score; else { // check what is not matching is not the critical property if(criticalPropertiesToMatch.indexOf(propertyName) != -1) criticalPropertiesMismatchFound = true; allEqual = false; } } } if(allEqual || (!criticalPropertiesMismatchFound) || calculateIndexAnyway) { var requiredPropList:Array = new Array(); if( automationIndexNeeded) requiredPropList.push("automationIndex"); if(automationNameNeeded) requiredPropList.push("automationName"); if(requiredPropList.length) { var childProperties1:Array = getProperties(child, requiredPropList); childPropertyMap = {}; for (var childPropertyNo1:int = 0; childPropertyNo1 < childProperties1.length; childPropertyNo1++) { childPropertyMap[requiredPropList[childPropertyNo1]] = childProperties1[childPropertyNo1]; } if(childProperties1 && (childProperties1.length == requiredPropList.length)) { n = childProperties1.length; for(i = 0; i < n; i++) { //equal = comparePropertyValues("automationIndex", childProperties[0]); var partPropertyValue1:Object = part[requiredPropList[i]]; propertyName = requiredPropList[i]; var childPropertyValue1:Object = (propertyName in childPropertyMap) ? childPropertyMap[propertyName] : null; equal = comparePropertyValues(partPropertyValue1, childPropertyValue1); if (equal) ++score; else { // check what is not matching is not the critical property if(criticalPropertiesToMatch.indexOf(propertyName) != -1) criticalPropertiesMismatchFound = true; allEqual = false; } } } } } if(criticalPropertiesMismatchFound) { score = -1; } } if(allEqual) { if(automationIndexNeeded) score = int.MAX_VALUE; // in this case we dont need to analyse the children for further match // there will be only one child with index matching and all other properties also matched. else score = int.MAX_VALUE-1; } return score; } /** * @private */ public function getAutomationComposite(obj:IAutomationObject):IAutomationObject { if(cachedCompositor) { var val:IAutomationObject = cachedCompositor[obj]; if(val != null) { if(val != obj) return val; else return null; } } var childFound:IAutomationObject = compositeAnalysis(obj); if(cachedCompositor) cachedCompositor[obj] = childFound; if(childFound == obj) return null; return childFound; } private function compositeAnalysis(obj:IAutomationObject):IAutomationObject { var hierarchyArray:Array = [obj]; //build an array of parents till we reach null. // do not call is getParent as it would call isAutomationComposite var parent:IAutomationObject = getParent(obj, null, true); while(parent) { hierarchyArray.push(parent); parent = getParent(parent, null, true); } //start from the top finding each child // if we do not find any child then the given object is composite. // if we find all the children till the given object then it is not a composite. parent = hierarchyArray.pop(); var childFound:IAutomationObject = parent; if(!hierarchyArray.length) return childFound; var childToBeFound:IAutomationObject = hierarchyArray.pop(); var ch:IAutomationObject; do { // code modified below to avoid the usage of numAutomationChildren and // getAutomationChildAt in a loop //var childList:Array = parent.getAutomationChildren(); var childList:Array = getAutomationChildrenArray(parent); var numChildren:int = childList?childList.length:0; //var numChildren:int = parent.numAutomationChildren; for(var i:int = 0; i < numChildren; ++i) { //ch = parent.getAutomationChildAt(i); ch = childList[i] as IAutomationObject; if(ch) { if(cachedCompositor) cachedCompositor[ch] = ch; if(ch == childToBeFound) { childFound = childToBeFound; break; } } } // have we found the child? if(i == numChildren) { if((childToBeFound is IUIComponent)&&(parent is IUIComponent )) { // if ((IUIComponent(obj).systemManager != null) && (parent == IUIComponent(parent).systemManager.document)) // https://bugs.adobe.com/jira/browse/FLEXENT-1038 . // when the passed object is a non ui object like repeater, using the same for the // above check will result in skipping the below condition and hence // it skips the identification of repeaters in popup objects like title window // which are application children. //if ((IUIComponent(childToBeFound).systemManager != null) && (parent == IUIComponent(parent).systemManager.document)) // chnaging the condition checking for https://bugs.adobe.com/jira/browse/FLEXENT-1044 if ((IUIComponent(childToBeFound)) && (IUIComponent(childToBeFound).systemManager != null) && (IUIComponent(parent))&& (IUIComponent(parent).systemManager)&& (parent == IUIComponent(parent).systemManager.document)) { var children:Array = getApplicationChildren(parent); numChildren = children.length; for(i = 0; i < numChildren; ++i) { ch = children[i]; if(cachedCompositor) cachedCompositor[ch] = ch; if(ch == childToBeFound) { childFound = childToBeFound; break; } } } // have we found the child? if(i == numChildren) { if(cachedCompositor) cachedCompositor[childToBeFound] = parent; childToBeFound = hierarchyArray.pop(); continue; } } } parent = childToBeFound; childToBeFound = hierarchyArray.pop(); } while(childToBeFound); return childFound; } /** * @private */ public function isAutomationComposite(obj:IAutomationObject):Boolean { if(cachedCompositor) { var val:IAutomationObject = cachedCompositor[obj]; if(val != null) { if(val == obj) return false; else return true; } } var childFound:IAutomationObject = compositeAnalysis(obj); if(cachedCompositor) cachedCompositor[obj] = childFound; if(obj == childFound) return false; return true; } /** * @private */ private function uniqueAppIdReplyHandler(event:Event):void { // Marshalling events are needeed across applicaiton domain // so this conversion shall fail in the same domain if(event is MarshalledAutomationEvent) return; if(_inUniqueAppIdRequestProcessing == false) return; _inUniqueAppIdRequestProcessing = false; // in the reply event we expect two parameter if(sm1.isTopLevelRoot() == false) { if(mainListenerObj) { _uniqueApplicationId = event["interAppDataToSubApp"][0] as String; } } } /** * @private * this method is to get the provider for the swf bridge from different possible system managers * In the main application of the AIR, we can have different system managers if we have the * air windows open. so we need to check in all of them to find the bridge provider and use * the identification based on the origin of the bridge provider. */ private function getSwfBridgeProviderDetails(bridgeParent:IEventDispatcher):Object { var providerType:int = -1; var providerName:String; // the bridge can be in the current application or in the windows. // this will be applicable for the root application only as only the root application can // have child windows. // first let us search in the main applicaiton system manager. var bp:ISWFBridgeProvider; if(sm1MSm && (sm1MSm.swfBridgeGroup )) { bp = sm1MSm.swfBridgeGroup.getChildBridgeProvider(bridgeParent); initUniqueAppId(); providerName = _uniqueApplicationId; providerType = -1; } if (!bp) { // now let us seach in the windows. var count:int = allAirWindowList.length; var index:int = 0; while((!bp) && (index < count)) { var currentWindow:IUIComponent = allAirWindowList[index] as IUIComponent; if(currentWindow) { var currentWindowSysManager:IMarshalSystemManager = currentWindow.systemManager as IMarshalSystemManager; if(currentWindowSysManager) { if(currentWindowSysManager.swfBridgeGroup) bp = currentWindowSysManager.swfBridgeGroup.getChildBridgeProvider(bridgeParent); if(bp) { providerName = getAIRWindowUniqueID(currentWindow as DisplayObject); providerType = index; } } } index++; } } var returnObject:Object = new Object(); returnObject["ISWFBridgeProvider"] = bp; returnObject["providerType"] = providerType; returnObject["providerName"] = providerName; return returnObject; } private function initUniqueAppId():void { if(!_uniqueApplicationId) { if(sm1.isTopLevelRoot() == false) { // we need to get the uniqueapplication id from the parentapplication dispatchUniqueAppIdRequestEvent(); _uniqueApplicationId = Automation.getMainApplication().className + "_" + _uniqueApplicationId; } else { _uniqueApplicationId = Automation.getMainApplication().className; } } } private function uniqueAppIdRequestHandler(event:Event):void { // Marshalling events are needeed across applicaiton domain // so this conversion shall fail in the same domain if(event is MarshalledAutomationEvent) return; // we get the bridge corresponding to the requesting application // we need to find the swfLoader corresponding to the bridge // get its index in its parents till we reach the sm of the // current application. //var bp:ISWFBridgeProvider = sm1.swfBridgeGroup. // getChildBridgeProvider(event["interAppDataToMainApp"][0]); var providerType:int = -1; // -1 indicates the main application and any othe value indicates the window index var providerName:String; var returnObject:Object = getSwfBridgeProviderDetails ((event["interAppDataToMainApp"][0])); var bp:ISWFBridgeProvider = returnObject["ISWFBridgeProvider"]; providerType = returnObject["providerType"]; providerName = returnObject["providerName"]; if(bp) { var unique_id_processed:String = getObjectIdInCurrentApplication(bp as DisplayObject); // initUniqueAppId(); moved inside the getSwfBridgeProviderDetails //unique_id_processed = unique_id_processed + "_" + _uniqueApplicationId; unique_id_processed = unique_id_processed + "_" + providerName; if(mainListenerObj) { // unique id handling logic var event1:MarshalledAutomationEvent = new MarshalledAutomationEvent( MarshalledAutomationEvent.UNIQUE_APPID_REPLY); var temp:Array = new Array(); temp.push(unique_id_processed); event1.interAppDataToSubApp = temp; //dispatchMarshalledEventToSubApplications(event1); dispatchToSwfBridgeChildren(event1); } } } private var inEndRecordDetailsPassingDownToChildren:Boolean = false; public function marhsalledEndRecordHandler(event:Event):void { if(!sm1MSm) return; if(sm1MSm.useSWFBridge() == false) return; // Marshalling events are needeed across applicaiton domain // so this conversion shall fail in the same domain if(event is MarshalledAutomationEvent) return; if(inEndRecordDetailsPassingDownToChildren == true) return; endRecording(); inEndRecordDetailsPassingDownToChildren = true; // we should pass the information to our children also var event1:MarshalledAutomationEvent = MarshalledAutomationEvent.marshal(event); dispatchMarshalledEventToSubApplications(event1); inEndRecordDetailsPassingDownToChildren = false; } private var inBeginRecordDetailsPassingDownToChildren:Boolean = false; public function marhsalledBeginRecordHandler(event:Event):void { if(!sm1MSm) return; if(sm1MSm.useSWFBridge() == false) // we are the root application return; // Marshalling events are needeed across applicaiton domain // so this conversion shall fail in the same domain if(event is MarshalledAutomationEvent) return; if(inBeginRecordDetailsPassingDownToChildren == true) return; beginRecording(); inBeginRecordDetailsPassingDownToChildren = true; // we should pass the information to our children also var event1:MarshalledAutomationEvent = MarshalledAutomationEvent.marshal(event); dispatchMarshalledEventToSubApplications(event1); inBeginRecordDetailsPassingDownToChildren = false; } private function dispatchMarshalledEventToSubApplications(event:Event):void { dispatchToAllChildren(event); } // FLEXENT-894 or 895 it was observerd that popupmenubutton menu popup object is // creatd before application completion. So we need to listen to the event // from the main app before the application completion. // we cannot process these objects as and when we get it, as our appDomain may not be // ready then. So we store it and once the application creation is over we process these // objects private var possiblePopupsBeforeAppComplete:Array; private function popupHandlerBeforeApplicationCompletion (event:Event):void { var currentAppObj:IAutomationObject = event["interAppDataToSubApp"][0] as IAutomationObject; if(!(currentAppObj is IUIComponent) || currentAppObj == null || currentAppObj.automationDelegate) { return ; } else { if(!possiblePopupsBeforeAppComplete) possiblePopupsBeforeAppComplete = new Array(); // these are our objects. And some may be popups. // let us add to a list and keep it so that once our application is complete // we will try to add the delegate for the same. possiblePopupsBeforeAppComplete.push(currentAppObj); } } // FLEXENT-894 or 895 it was observerd that popupmenubutton menu popup object is // creatd before application completion. // So we store it and once the application creation is over we process these // objects private function processPopupsBeforeApplicationComplete():void { if(possiblePopupsBeforeAppComplete) { var count:int = possiblePopupsBeforeAppComplete.length; var index:int = 0; while(index < count) { handlePopupObject(possiblePopupsBeforeAppComplete.shift()); index++; } } } private var inPopupDataSendingDownToChildren:Boolean = false; private function popupHandler (event:Event):void { if(event is MarshalledAutomationEvent) return ; if(inPopupDataSendingDownToChildren) return; var currentAppObj:DisplayObject = event["interAppDataToSubApp"][0] as DisplayObject; handlePopupObject(currentAppObj, event); } private function handlePopupObject(currentAppObj:DisplayObject, event:Event=null):void { if(createDelegate(currentAppObj)== true) { // we got a new popup. // we need to add this to our pop up list also // this is neeeded beause in automation we need to consider the popup's as the // children of the application which has created the same if(!popUpObjects) popUpObjects = new Array(); if(currentAppObj as IUIComponent) { if( (currentAppObj as IUIComponent).owner == (currentAppObj as IUIComponent).parent ) popUpObjects.push(currentAppObj); else if ((currentAppObj as IUIComponent).owner is Application || isSparkApplication((currentAppObj as IUIComponent).owner)) // for the popups which are direct children of application we need to store popUpObjects.push(currentAppObj); } /* if(!((currentAppObj as IUIComponent)&& ((currentAppObj as IUIComponent).owner != (currentAppObj as IUIComponent).parent))) popUpObjects.push(currentAppObj); */ addDelegates(currentAppObj); // we only added the delegate for the outer object. // we need to add for the children also. currentAppObj.addEventListener(FlexEvent.CREATION_COMPLETE, popupCompleteHandler); currentAppObj.parent.addEventListener(FlexEvent.REMOVE, popupRemoveHandler); } else { inPopupDataSendingDownToChildren = true; var eventX:MarshalledAutomationEvent; if(event) { // we should pass the information to our children also eventX= MarshalledAutomationEvent.marshal(event); } else { eventX = new MarshalledAutomationEvent( MarshalledAutomationEvent.POPUP_HANDLER_REQUEST); var tempArr:Array = new Array(); tempArr.push(currentAppObj); eventX.interAppDataToSubApp = tempArr; } dispatchMarshalledEventToSubApplications(eventX); inPopupDataSendingDownToChildren = false; } } private static var lastRemovedpopUpObject:DisplayObject; private static function popupRemoveHandler(event:Event):void { lastRemovedpopUpObject = null; if(!popUpObjects) { Automation.automationDebugTracer.traceMessage("AutomationManager", "popupRemoveHandler()", "How did we get an obejct to remove without adding ? "); return; } var obejctToBeRemoved:Object = event.target; // remove the current objects from the popUps var currentCount:int = popUpObjects.length; var index:int = 0; var requiredCount: int = 0; var tempArray:Array = new Array(); var objFound:Boolean = false; while (index < currentCount) { if( ((popUpObjects[index]as DisplayObject).parent) != obejctToBeRemoved) tempArray.push(popUpObjects[index]); else lastRemovedpopUpObject = popUpObjects[index]; index ++; } popUpObjects = tempArray; } public function getPopUpChildren():Array { return popUpObjects; } public function getPopUpChildrenCount():Number { if (!popUpObjects) return 0; return popUpObjects.length; } public function getPopUpChildObject(index:int):IAutomationObject { if(index < popUpObjects.length) return popUpObjects[index] as IAutomationObject; return null; } private function popupCompleteHandler(event:Event):void { var currentObject:DisplayObjectContainer = event.target as DisplayObjectContainer; addPopupChildDelegates(currentObject); currentObject.addEventListener(Event.ADDED, childAddedHandler, false, 0, true); } private static function addPopupChildDelegates(currentObject: DisplayObjectContainer):void { if(currentObject) { var childCount:int = currentObject.numChildren; var index:int = 0; while(index < childCount) { var childObj:DisplayObject = currentObject.getChildAt(index); createDelegate(childObj); addDelegates(childObj); addPopupChildDelegates(childObj as DisplayObjectContainer); index++; } } } private static function dragproxyStoreRequesthandler(event:Event):void { // we will only store one dragProxyObject if(!currentDragProxyHolder) currentDragProxyHolder = new Array(); currentDragProxyHolder[0] = event["interAppDataToMainApp"][0] as DisplayObject; } private function dragproxyRetrieveRequesthandler(event:Event):void { var tempEventObj:MarshalledAutomationEvent = new MarshalledAutomationEvent( MarshalledAutomationEvent.DRAG_DROP_PROXY_RETRIEVE_REPLY); var tempArr:Array = new Array(); if(currentDragProxyHolder && currentDragProxyHolder.length!= 0) tempArr.push(currentDragProxyHolder[0]); else tempArr.push(null); tempEventObj.interAppDataToSubApp = tempArr; currentDragProxyHolder = new Array(); dispatchMarshalledEventToSubApplications(tempEventObj); } public function storeDragProxy(dragProxy:Object):void { // if the drag start happened in the sandbox root we wont get the event // corresponding to the dragProxyReciever, so we need to store it explicitly if(!currentDragProxyHolder) currentDragProxyHolder = new Array(); currentDragProxyHolder[0] = dragProxy; } private static function dragProxyReciever(event:Event):void { if(event["name"] == "popUpChildren.addChild") { if(!currentDragProxyHolder) currentDragProxyHolder = new Array(); currentDragProxyHolder[0] = event["value"]; } } private var inSynchronizationSendingToChildren:Boolean = false; private function synchronizationHandler(event:Event):void { // we get this event when an application has started drag start // and we get this in all applications. if(event is MarshalledAutomationEvent) return; if(inSynchronizationSendingToChildren) return; // we need this method so that it calls the synchronization // method to clear the current status. isSynchronized(null); inSynchronizationSendingToChildren = true; // we should pass the information to our children also var event1:MarshalledAutomationEvent = MarshalledAutomationEvent.marshal(event); dispatchMarshalledEventToSubApplications(event1); inSynchronizationSendingToChildren = false; } private static function isCurrentAppSandboxRoot(passedSystemManager:ISystemManager):Boolean { var retVal:Boolean = false; try { // get the root of the system manager of the current application if(passedSystemManager["root"] && (passedSystemManager.getSandboxRoot() == passedSystemManager["root"])) retVal = true; } catch(e:Error) { } return retVal; } private static function getMainListenerObject(passedSystemManager:ISystemManager, passedMarshaledSystemManager:IMarshalSystemManager):IEventDispatcher { var requiredObj:IEventDispatcher ; if(passedSystemManager) { // we need to use the sanbox root if we belong to the // same security domain as that of the root applucation requiredObj = passedSystemManager.getSandboxRoot(); //var passedMarshaledSystemManager:IMarshalSystemManager = passedSystemManager as IMarshalSystemManager; if (passedMarshaledSystemManager && passedMarshaledSystemManager.useSWFBridge() && (isCurrentAppSandboxRoot(passedSystemManager) == true)) { // this application is not the root level application, but it is // first application of the security domain. So this objects's mainListenerObj is // parent brige requiredObj = passedMarshaledSystemManager.swfBridgeGroup.parentBridge; } } return requiredObj; } private function addListenerToParentApplication(obj:IEventDispatcher):void { // as an application X we need to listen to the events coming from the parent. // these events are always sent from the parent to the child. So these events are listened by // the children on their parent. When we recieve the event,if we are not handling these, we should // pass the same to the children. i.e always sent from the parent -> child. These events are // handled by the children. i,e these are always added on the parent. obj.addEventListener(MarshalledAutomationEvent.BEGIN_RECORDING, marhsalledBeginRecordHandler,false,0,true); obj.addEventListener(MarshalledAutomationEvent.END_RECORDING, marhsalledEndRecordHandler,false,0,true); //obj.addEventListener(MarshalledAutomationEvent.ENV_DETAILS , envDetailsEventHandler, false, 0, true); if((obj as SystemManager)&& (obj as SystemManager).getSandboxRoot()) ((obj as SystemManager).getSandboxRoot()).removeEventListener(MarshalledAutomationEvent.POPUP_HANDLER_REQUEST , popupHandlerBeforeApplicationCompletion, false); obj.addEventListener(MarshalledAutomationEvent.POPUP_HANDLER_REQUEST , popupHandler, false, 0, true); obj.addEventListener(MarshalledAutomationEvent.UPDATE_SYCHRONIZATION,synchronizationHandler, false, 0, true); obj.addEventListener(MarshalledAutomationEvent.INITIAL_DETAILS_REPLY,initialDetailsReplyHandler, false, 0, true); obj.addEventListener(MarshalledAutomationEvent.START_POINT_REPLY,startPointReplyHandler, false, 0, true); obj.addEventListener(MarshalledAutomationEvent.UNIQUE_APPID_REPLY , uniqueAppIdReplyHandler, false, 0, true); obj.addEventListener(MarshalledAutomationEvent.DRAG_DROP_PERFORM_REQUEST_TO_SUB_APP ,dragDropPerformRequesthandlerInSubApp, false, 0, true); } private function addListenerToChildApplications(obj:IEventDispatcher):void { // as an application x, we need to listen to these events coming from the children. // these events will be processed only by the root application. so if we are not the root applicaiton // we just need to dispatch these events on the parent. So always these events are from the child -> Parent. // i.e these events are always handled by the parent. So the listneres of the events should be added to the children. obj.addEventListener(MarshalledAutomationEvent.DRAG_DROP_PROXY_RETRIEVE_REQUEST, dragproxyRetrieveRequesthandler); obj.addEventListener(InterManagerRequest.SYSTEM_MANAGER_REQUEST,dragProxyReciever, false, 0, true); obj.addEventListener(MarshalledAutomationEvent.UPDATE_SYCHRONIZATION,synchronizationHandler, false, 0, true); obj.addEventListener(MarshalledAutomationEvent.INITIAL_DETAILS_REQUEST,initialDetailsRequestHandler, false, 0, true); obj.addEventListener(MarshalledAutomationEvent.UNIQUE_APPID_REQUEST , uniqueAppIdRequestHandler, false, 0, true); obj.addEventListener(MarshalledAutomationEvent.START_POINT_REQUEST , startPointRequestHandler, false, 0, true); obj.addEventListener(MarshalledAutomationEvent.DRAG_DROP_PERFORM_REQUEST_TO_ROOT_APP,dragDropPerformRequesthandlerInRootApp, false, 0, true); } private function dragDropPerformRequesthandlerInSubApp(event:Event):void { if(event is MarshalledAutomationEvent) return; // take the details and check whether the object belongs to us. var details:Array = event["interAppDataToSubApp"]; if(details && details.length == 2) { var target:IUIComponent = details[0] as IUIComponent; if(target) { DragManagerAutomationImpl.setForcefulDragStart(); var passedEventObj:Object = details[1]; var dragEventInCurrentDomain:DragEvent = new DragEvent(passedEventObj["type"]); dragEventInCurrentDomain.action = passedEventObj["action"]; dragEventInCurrentDomain.localX = passedEventObj["localX"]; dragEventInCurrentDomain.localY= passedEventObj["localY"]; DragManagerAutomationImpl.recordAutomatableDragDrop1(target as DisplayObject , dragEventInCurrentDomain); } } } private function dragDropPerformRequesthandlerInRootApp(event:Event):void { if(event is MarshalledAutomationEvent) return; // take the details and check whether the object belongs to us. var details:Array = event["interAppDataToMainApp"]; if(details.length == 2) { var target:IUIComponent = details[0] as IUIComponent; if(target) { DragManagerAutomationImpl.setForcefulDragStart(); var passedEventObj:Object = details[1]; var dragEventInCurrentDomain:DragEvent = new DragEvent(passedEventObj["type"]); dragEventInCurrentDomain.action = passedEventObj["action"]; dragEventInCurrentDomain.localX = passedEventObj["localX"]; dragEventInCurrentDomain.localY= passedEventObj["localY"]; DragManagerAutomationImpl.recordAutomatableDragDrop1(target as DisplayObject ,dragEventInCurrentDomain ); } else { var eventObj:MarshalledAutomationEvent = new MarshalledAutomationEvent(MarshalledAutomationEvent.DRAG_DROP_PERFORM_REQUEST_TO_SUB_APP ); eventObj.interAppDataToSubApp = details; dispatchToAllChildren(eventObj); } } } private static var listenerAdded:Boolean = false; /* this funciton is to add the required event listeners of automation manger */ private function addListenerToAllApplications(passedListenerObj:IEventDispatcher, passedSystemManager:ISystemManager,passedMarshaledSystemManager:IMarshalSystemManager,fromAirWindow:Boolean = false):void { //var passedMarshaledSystemManager:IMarshalSystemManager = passedSystemManager as IMarshalSystemManager; if(!passedMarshaledSystemManager) return; // we need to take all the children and add listener to all the child // bridges in these application. // an application in a different securty domain or applicaiton domain, when they // have their child application, they have a bridge corresponding to their child application // so the main application need to listen to all their child applications. addListenerToParentApplication(passedListenerObj); if(passedMarshaledSystemManager.useSWFBridge()) { // we need a special listener to the uniqueIdReply // this event we will listen always to the parentBridge rather than the main // communication object var bridgeParent:IEventDispatcher = passedMarshaledSystemManager.swfBridgeGroup.parentBridge; if(bridgeParent) { bridgeParent.addEventListener(MarshalledAutomationEvent.UNIQUE_APPID_REPLY, uniqueAppIdReplyHandler, false, 0, true); bridgeParent.addEventListener(MarshalledAutomationEvent.START_POINT_REPLY, startPointReplyHandler, false, 0, true); } } // only the sandbox root applicaiton needs to listen to the communication over the main listener object if (fromAirWindow || (isCurrentAppSandboxRoot(passedSystemManager as ISystemManager))) addListenerToChildApplications(passedSystemManager.getSandboxRoot()); if (!passedMarshaledSystemManager.swfBridgeGroup) return; var children:Array = passedMarshaledSystemManager.swfBridgeGroup.getChildBridges(); for (var i:int; i < children.length; i++) { var childBridge:IEventDispatcher = IEventDispatcher(children[i]); addListenerToChildApplications(childBridge); } listenerAdded = true; } public function dispatchToParent(event:Event):void { if(mainListenerObj.hasEventListener(event.type)) mainListenerObj.dispatchEvent(event); } private function dispatchToBridgeParent(event:Event):void { if(!sm1MSm) return; var bridgeParent:IEventDispatcher = sm1MSm.swfBridgeGroup.parentBridge; if(bridgeParent.hasEventListener(event.type)) bridgeParent.dispatchEvent(event); } private function dispatchToSwfBridgeChildren(event:Event):void { dispatchToSwfBridgeChildrenOfPassedSystemManager(sm1,sm1MSm,event); // we need to consider the possible swf bridge children from the windows also dispatchToSwfBridgeChildrenOfAllWindows(event); } private function dispatchToSwfBridgeChildrenOfAllWindows(event:Event):void { var count:int = allAirWindowList.length; var index:int = 0; while(index < count) { var currentWindow:IUIComponent = allAirWindowList[index] as IUIComponent; if(currentWindow) { var currentWindowSysManager:ISystemManager = currentWindow.systemManager; var currentWindowMarshaledSysManager:IMarshalSystemManager = IMarshalSystemManager( currentWindow.systemManager.getImplementation("mx.managers::IMarshalSystemManager")); if(currentWindowSysManager) { dispatchToSwfBridgeChildrenOfPassedSystemManager(currentWindowSysManager,currentWindowMarshaledSysManager,event); } } index++; } } private function dispatchToSwfBridgeChildrenOfPassedSystemManager(passedSystemManager:ISystemManager, passedMarshalledSystemManger:IMarshalSystemManager, event:Event):void { //var passedMarshalledSystemManger:IMarshalSystemManager = passedSystemManager as IMarshalSystemManager; if(!passedMarshalledSystemManger) return; if (!passedMarshalledSystemManger.swfBridgeGroup) return; var children:Array = passedMarshalledSystemManger.swfBridgeGroup.getChildBridges(); for (var i:int; i < children.length; i++) { var childBridge:IEventDispatcher = IEventDispatcher(children[i]); if(childBridge.hasEventListener(event.type)) childBridge.dispatchEvent(event); } } public function dispatchToAllChildren(event:Event):void { dispatchToAllChildrenOfPassedSystemManager(sm1,sm1MSm,event,false); // we need to consider the possible children of the windows also. dispathcToAllWindowChildApplications(event); } private function dispathcToAllWindowChildApplications(event:Event):void { var count:int = allAirWindowList.length; var index:int = 0; while(index < count) { var currentWindow:IUIComponent = allAirWindowList[index] as IUIComponent; if(currentWindow) { var currentWindowSysManager:ISystemManager = currentWindow.systemManager; var currentWindowMarshaledSysManager:IMarshalSystemManager = IMarshalSystemManager(currentWindow.systemManager.getImplementation("mx.managers::IMarshalSystemManager")); if(currentWindowSysManager) { dispatchToAllChildrenOfPassedSystemManager(currentWindowSysManager,currentWindowMarshaledSysManager,event,true); } } index++; } } private function dispatchToAllChildrenOfPassedSystemManager(passedSystemManager:ISystemManager, passedMarshalledSystemManger:IMarshalSystemManager,event:Event, fromAirWindow:Boolean):void { //var passedMarshalledSystemManger:IMarshalSystemManager = passedSystemManager as IMarshalSystemManager; if(!passedMarshalledSystemManger) return; // some children (of the same application domain) // will be listening to the sandboxroot of the main app only // hence we need to dispatch event on this object also. if(fromAirWindow || (isCurrentAppSandboxRoot(passedSystemManager))) passedSystemManager.getSandboxRoot().dispatchEvent(event); if (!passedMarshalledSystemManger.swfBridgeGroup) return; var children:Array = passedMarshalledSystemManger.swfBridgeGroup.getChildBridges(); for (var i:int; i < children.length; i++) { var childBridge:IEventDispatcher = IEventDispatcher(children[i]); if(childBridge.hasEventListener(event.type)) childBridge.dispatchEvent(event); } } private var _inInitialDetailsRequestProcessing:Boolean = false; private var _inStartPointRequestProcessing:Boolean = false; private static function initMainListeners():void { if(!_sm1MSm) _sm1MSm = IMarshalSystemManager(sm1.getImplementation("mx.managers::IMarshalSystemManager")); if(!_mainListenerObj) _mainListenerObj = getMainListenerObject(sm1,_sm1MSm); } private function applicationCompleteHandler(event:Event):void { if(!(event is FlexEvent)) return; initMainListeners(); //_mainListenerObj.addEventListener(MarshalledAutomationEvent.POPUP_HANDLER_REQUEST , popupHandlerBeforeApplicationCompletion, false, 0, true); // add listener to the child bridges addListenerToAllApplications(_mainListenerObj,sm1,sm1MSm); // process the possible popops processPopupsBeforeApplicationComplete(); // we will listen to the initial details from our parent. var initialStatusRequest:MarshalledAutomationEvent = new MarshalledAutomationEvent(MarshalledAutomationEvent.INITIAL_DETAILS_REQUEST); _inInitialDetailsRequestProcessing = true; dispatchToParent(initialStatusRequest); } private var eventDetailsFromToolToChildren:Array; public function addEventListenersToAllParentApplications(eventDetailsArray:Array):void { // we dont need to store the events as we will never get a new parent. addEventListeners(mainListenerObj,eventDetailsArray); } public function addEventListenersToAllChildApplications(eventDetailsArray:Array):void { eventDetailsFromToolToChildren = eventDetailsArray; addEventListenersToAllChildApplicationsOfPassedSystemManager(sm1,sm1MSm,eventDetailsFromToolToChildren,false); // we need to consider the child application of the windows also addEventListenersToAllChildApplicationsOfAllWindows(eventDetailsFromToolToChildren); } private function addEventListenersToAllChildApplicationsOfAllWindows(eventDetailsArray:Array):void { var count:int = allAirWindowList.length; var index:int = 0; while(index < count) { var currentWindow:IUIComponent = allAirWindowList[index] as IUIComponent; if(currentWindow) { var currentWindowSysManager:ISystemManager = currentWindow.systemManager; var currentWindowMarshalSysManager:IMarshalSystemManager = IMarshalSystemManager( currentWindow.systemManager.getImplementation("mx.manager::IMarshalSystemManager")); if(!currentWindowSysManager) { addEventListenersToAllChildApplicationsOfPassedSystemManager(currentWindowSysManager,currentWindowMarshalSysManager,eventDetailsArray,true); } } index++; } } private function addEventListenersToAllChildApplicationsOfPassedSystemManager(passedSystemManager:ISystemManager, passedMarshalledSystemManger:IMarshalSystemManager, eventDetailsArray:Array,fromAirWindow:Boolean):void { //var passedMarshalledSystemManger:IMarshalSystemManager = passedSystemManager as IMarshalSystemManager; if(!passedMarshalledSystemManger) return; if(fromAirWindow || (isCurrentAppSandboxRoot(passedSystemManager))) addEventListeners(passedSystemManager.getSandboxRoot(),eventDetailsArray); if (!passedMarshalledSystemManger.swfBridgeGroup) return; var children:Array = passedMarshalledSystemManger.swfBridgeGroup.getChildBridges(); for (var i:int; i < children.length; i++) { var childBridge:IEventDispatcher = IEventDispatcher(children[i]); addEventListeners(childBridge,eventDetailsArray); } } private function childBridgeHandler(event:FlexChangeEvent):void { // get the new bridge of the current application and add listeners to the same var currentBridge:IEventDispatcher = event.data as IEventDispatcher; handleBridge(currentBridge); } private function handleBridge(currentBridge:IEventDispatcher):void { if(currentBridge) { addEventListeners(currentBridge,eventDetailsFromToolToChildren); addListenerToChildApplications(currentBridge); } } private function addEventListeners(obj:IEventDispatcher, eventDetailsArray:Array):void { // it is quite possible that tool library has not supported marshaling // and hence these arrays would not have been initialised if(!eventDetailsArray) return; var count:int = eventDetailsArray.length; var index:int = 0; while(index < count) { var currentEventDetailsObj:EventDetails = eventDetailsArray[index] as EventDetails; if(currentEventDetailsObj) { obj.addEventListener(currentEventDetailsObj.eventType, currentEventDetailsObj.handlerFunction, currentEventDetailsObj.useCapture, currentEventDetailsObj.priority, currentEventDetailsObj.useWeekRef); } index++; } } private function initialDetailsRequestHandler(event:Event):void { if (event is MarshalledAutomationEvent) return; var replyEvent:MarshalledAutomationEvent = new MarshalledAutomationEvent(MarshalledAutomationEvent.INITIAL_DETAILS_REPLY); // order of the elements in the array should not changed across versions. // if some information is needed in further versions it needs to be added after this // and handler can handle the data appropriately. var tempArr:Array = new Array(); tempArr.push(_recording); tempArr.push(_automationEnvironmentHandlingClassName); tempArr.push(_automationEnvironmentString); replyEvent.interAppDataToSubApp = tempArr; dispatchToAllChildren(replyEvent); } private function startPointRequestHandler(event:Event):void { if (event is MarshalledAutomationEvent) return; var windowId:String = event["interAppDataToMainApp"][0] as String; var replyEvent:MarshalledAutomationEvent = new MarshalledAutomationEvent(MarshalledAutomationEvent.START_POINT_REPLY); if(sm1.isTopLevelRoot() == false) { dispatchStartPointRequestEvent(windowId); var tempArr:Array = new Array(); //var p:Point = AutomationHelper.getStageStartPointInScreenCoords(windowId); tempArr.push(_appStartPoint.x); tempArr.push(_appStartPoint.y); replyEvent.interAppDataToSubApp = tempArr; dispatchToAllChildren(replyEvent); } if(sm1.isTopLevelRoot() == true) { //start point request should be handled only by main application //i.e., top level root application var tempArr1:Array = new Array(); var p:Point = AutomationHelper.getStageStartPointInScreenCoords(windowId); tempArr1.push(p.x); tempArr1.push(p.y); replyEvent.interAppDataToSubApp = tempArr1; dispatchToAllChildren(replyEvent); } } private function initialDetailsReplyHandler(event:Event):void { // Marshalling events are needeed across applicaiton domain // so this conversion shall fail in the same domain if(event is MarshalledAutomationEvent) return; if (!_inInitialDetailsRequestProcessing) return; _inInitialDetailsRequestProcessing = false; if(sm1.isTopLevelRoot() == false) { try { var interAppData:Array = event["interAppDataToSubApp"]; _recording = interAppData[0] as Boolean; if(_recording == true) { _recording = false; // to make the beginRecording call happen beginRecording(); } _automationEnvironmentString = interAppData[2]; _automationEnvironmentHandlingClassName = interAppData[1]; var envClass:Class = Class(getDefinitionByName(_automationEnvironmentHandlingClassName)); //( "mx.automation.tool.ToolEnvironment")); if (envClass != null) { _automationEnvironment = new envClass(new XML(_automationEnvironmentString)); } } catch(e:Error) { } } } private function startPointReplyHandler(event:Event):void { // Marshalling events are needeed across application domain // so this conversion shall fail in the same domain if(event is MarshalledAutomationEvent) return; if (!_inStartPointRequestProcessing) return; _inStartPointRequestProcessing = false; if(sm1.isTopLevelRoot() == false) { try { var interAppData:Array = event["interAppDataToSubApp"]; _appStartPoint = new Point(interAppData[0] as Number, interAppData[1] as Number); } catch(e:Error) { } } } private function getObjectIdInCurrentApplication(objectPassed:DisplayObject):String { if(!objectPassed) return null; var idString:String = null; var orderArraay:Array = new Array(); try { var object:DisplayObject = objectPassed; var parent:DisplayObjectContainer = object.parent; while(object && parent && (parent!= sm1)) { orderArraay.push(getChildIndex1(parent,object)); object = parent; parent = object.parent; } idString = orderArraay.join(""); } catch(e:Error) { } return idString; } private var _inUniqueAppIdRequestProcessing:Boolean = false; private function dispatchUniqueAppIdRequestEvent():void { if(!sm1MSm) return; _inUniqueAppIdRequestProcessing = true; var appIdReqEvent:MarshalledAutomationEvent = new MarshalledAutomationEvent( MarshalledAutomationEvent.UNIQUE_APPID_REQUEST); var tempArray:Array = new Array(); tempArray.push(sm1MSm.swfBridgeGroup.parentBridge); appIdReqEvent.interAppDataToMainApp = tempArray; dispatchToBridgeParent(appIdReqEvent); } public function registerNewApplication(application:DisplayObject):void { if(!(application is Application || isSparkApplication(application))) return; if(application.root == sm1) return; application.root.addEventListener(FlexChangeEvent.ADD_CHILD_BRIDGE , childBridgeHandler); handleAlreadyPresentBridges(application); } private function handleAlreadyPresentBridges(application:DisplayObject):void { var currentSysManager:IMarshalSystemManager = (application as IUIComponent).systemManager as IMarshalSystemManager if(!currentSysManager) return; var childBridges:Array; if(currentSysManager && currentSysManager.swfBridgeGroup) childBridges = currentSysManager.swfBridgeGroup.getChildBridges(); if(childBridges && childBridges.length) { var len:int = childBridges.length; var index:int = 0; while(index < len) { handleBridge(childBridges[index] as IEventDispatcher); index++; } } } public function registerNewFlexNativeMenu(menu:Object, sm:DisplayObject):void { createDelegateForFlexNativeMenu(menu, sm); } private function createDelegateForFlexNativeMenu(menu:Object, sm:DisplayObject):Boolean { var component:IAutomationObject = menu as IAutomationObject; var retValue:Boolean = false; var appDomain:ApplicationDomain; var className:String = "mx.controls::FlexNativeMenu"; if(!sm) { var factory:IFlexModuleFactory = ModuleManager.getAssociatedFactory(menu); if (factory != null) { appDomain = ApplicationDomain(factory.info()["currentDomain"]); } else { var message:String = "Factory module failure"; traceMessage("AutomationManager","createDelegateForFlexNativeMenu()",message); } } else { appDomain = (sm.loaderInfo) ? sm.loaderInfo.applicationDomain : ApplicationDomain.currentDomain; } var compClass:Class = appDomain.getDefinition(className) as Class; var mainComponentClass:Class = compClass; var delegateClass:Class = Automation.delegateDictionary[compClass] as Class; if(!delegateClass) { var componentClass:String = className; do { try { className = getQualifiedSuperclassName(appDomain.getDefinition(className)); compClass = appDomain.getDefinition(className) as Class; delegateClass = Automation.delegateDictionary[compClass] as Class; } catch(e:Error) { traceMessage("AutomationManager","createDelegateForFlexNativeMenu()",e.message); break; } } while(!delegateClass); Automation.delegateDictionary[mainComponentClass] = delegateClass; //trace("Added mapping for : " + componentClass); } var c:Class = delegateClass; if (c) { try { var delegate:Object = new c (menu); } catch(e:Error) { Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegateForFlexNativeMenu()",e.message); Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegateForFlexNativeMenu()","Delegate object could not be created"); } try { component.automationDelegate = delegate; retValue = true; } catch(e:Error) { Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegateForFlexNativeMenu()",e.message); Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegateForFlexNativeMenu()","object created but delegates not set"); } } else Automation.automationDebugTracer.traceMessage("AutomationManager","createDelegateForFlexNativeMenu()", "Unable to find definition for class : " + className); return retValue; } public function registerNewWindow(newWindow:DisplayObject):void { // we need to find a uniqueId for this window in Automation Manager // we are forming the unique id string as // 'applicationName'::_::AIRWindow_index lastRegisteredWindowCount++; var currentWindowId:String = Automation.getMainApplication().automationName+ airWindowIdFixedString+ String(lastRegisteredWindowCount); // we need bi directional mapping as we get the id we need to identify the window // later from the plugin allAirWindowsToIdDictionary[newWindow] = currentWindowId; allAirIdToWindowsDictionary[currentWindowId] = newWindow; // store the window in the list so that we can analyse the windows for eventhandling part. allAirWindowList.push(newWindow); // we need to dispatch an event so that tool library will try to coomunicate // to the plugin using this id to get associate the hwnd with this id. dispatchEvent(new AutomationAirEvent(AutomationAirEvent.NEW_AIR_WINDOW, true,true,currentWindowId)); inMouseSequence = false; createDelegate(newWindow); addDelegates(newWindow); newWindow.stage.addEventListener(Event.ADDED, childAddedHandler, false, 0, true); var sm2:ISystemManager = (newWindow as IUIComponent).systemManager; var sm2MSm:IMarshalSystemManager = IMarshalSystemManager((newWindow as IUIComponent).systemManager.getImplementation("mx.managers::IMarshalSystemManager")); if(!sm2) return; var windowMainListenerObj:IEventDispatcher = getMainListenerObject(sm2,sm2MSm); addListenerToAllApplications(windowMainListenerObj,sm2,sm2MSm,true); addEventListenersToAllChildApplicationsOfPassedSystemManager(sm2,sm2MSm,eventDetailsFromToolToChildren,true); sm2.addEventListener(FlexChangeEvent.ADD_CHILD_BRIDGE , childBridgeHandler); sm2.addEventListener(AutomationRecordEvent.RECORD, recordHandler, false, EventPriority.DEFAULT_HANDLER, true); sm2.getSandboxRoot().addEventListener(MouseEvent.MOUSE_DOWN, captureIDFromMouseDownEvent, true, 0, true); sm2.addEventListener(KeyboardEvent.KEY_DOWN, captureIDFromKeyDownEvent, true, 0, true); //ideally we would listen in the bubble phase so //we'd get this last and all components have had a chance //to react and record events, but some components are stopping //the propagation so capture first and flush events //in a delayed manner sm2.getSandboxRoot().addEventListener(MouseEvent.CLICK, onEndMouseSequence, true, 0, true); sm2.getSandboxRoot().addEventListener(MouseEvent.DOUBLE_CLICK, onEndMouseSequence, true, 0, true); // sm2.getSandboxRoot().addEventListener(SandboxMouseEvent.CLICK_SOMEWHERE, // onEndMouseSequence, true, 0, true); // sm2.getSandboxRoot().addEventListener(SandboxMouseEvent.DOUBLE_CLICK_SOMEWHERE, // onEndMouseSequence, true, 0, true); sm2.addEventListener(KeyboardEvent.KEY_UP, onEndKeySequence, true, 0, true); //Ideally we'd flush events after the last click (or double click) //however the player has a bug where it doesn't always send click //events (and also there can be legitimate times when a click //event won't come through, souch as a mouse down, mouse move off //the component then a mouse up), so do a timed flush after the //mouse up (it needs to be after any click events that might occur) sm2.getSandboxRoot().addEventListener(MouseEvent.MOUSE_UP, onEndMouseSequence, true, 0, true); //sm2.getSandboxRoot().addEventListener(SandboxMouseEvent.MOUSE_UP_SOMEWHERE, // onEndMouseSequence, true, 0, true); sm2.getSandboxRoot().addEventListener(FocusEvent.KEY_FOCUS_CHANGE, keyFocusChangeHandler, false, 0, true); } public function getAIRWindowUniqueID(newWindow:DisplayObject):String { return allAirWindowsToIdDictionary[newWindow]; } public function getApplicationNameFromAutomationIDPart(objectIdPart:AutomationIDPart):String { // check whether the current class is an AIR window as it is the another object // which can be the first level // we have added a property by name 'applicationName' which is applicable only // for the top level windows for AIR if (objectIdPart.hasOwnProperty(AutomationManager.airWindowIndicatorPropertyName)) { // get the automationName //var currentAutomationName:String = objectIdPart["automationName"]; var currentAutomationName:String = getPassedUniqueName(objectIdPart); var appNameArray:Array = currentAutomationName.split(airWindowIdFixedString); if(appNameArray && appNameArray.length) return appNameArray[0]; } // if we reach here we are not the AIR top level window // hence we can use the automationName to get the automation class name //return (objectIdPart["automationName"].toString()); return getPassedUniqueName(objectIdPart); } private function getPassedUniqueName(part:AutomationIDPart):String { if(part.hasOwnProperty("automationName")) { if(part["automationName"] is RegExp) { var obj1:RegExp = part["automationName"] as RegExp; return obj1.source; } else return (part["automationName"].toString()); } else if(part.hasOwnProperty("id")) { if(part["id"] is RegExp) { var obj:RegExp = part["id"] as RegExp; return obj.source; } else return (part["id"].toString()); } else { // if we dont get any of the above conditions, we cannot support the // marhslling. Hence we assume it is a single application scenario // and get the current application name return getUniqueApplicationID(); } } public function getAIRWindow(windowId:String):DisplayObject { if(windowId=="") return Automation.getMainApplication() as DisplayObject; return allAirIdToWindowsDictionary[windowId]; } public function getAIRWindowUniqueIDFromObjectIDString(objectId:String ):String { var objectID:AutomationID = AutomationID.parse(objectId); // clone the automationID var rid:AutomationID = objectID.clone(); //remove the application var objectIdPart:AutomationIDPart = rid.removeFirst(); return getAIRWindowUniqueIDFromAutomationIDPart(objectIdPart); } public function getAIRWindowUniqueIDFromAutomationIDPart(objectIdPart:AutomationIDPart ):String { // check whether the current class is an AIR window as it is the another object // which can be the first level // we have added a property by name 'applicationName' which is applicable only // for the top level windows for AIR if (objectIdPart.hasOwnProperty(AutomationManager.airWindowIndicatorPropertyName)) { // get the automationName //return objectIdPart["automationName"]; return getPassedUniqueName(objectIdPart); } // if we reach here we are not the AIR top level window // hence we can use the automationName to get the automation class name return null; } public function getTopApplicationIndex(objectList:Array):int { // we need to find the application which can be on the top. This is based on // the order of the SWFLoader not based on the order of applicaiton loading. // let us for the application name list. if(objectList) { var count:int = objectList.length; if(count > 1) { var appNames:Array = new Array(); var selectedObjectIndices:Array = new Array(); var index:int = 0; while(index < count) { var curentObj:Object = objectList[index]; appNames.push(curentObj["applicationName"]); selectedObjectIndices.push(index); index++; } var requiredIndex:int = 0; var higherOrderObject:Array = new Array(); var indexToSort:int = 1; while(selectedObjectIndices.length > 1) { var resultObj:Object = sortCurrentList(appNames,selectedObjectIndices,indexToSort); appNames = new Array(); selectedObjectIndices = new Array(); appNames = resultObj["slectedAppNames"] as Array; selectedObjectIndices = resultObj["selectedObjectIndices"] as Array; indexToSort = indexToSort+2; } return selectedObjectIndices[0]; } else return 0; } return -1; } private function sortCurrentList(passedNamesArray:Array , passesSelectedObjectIndices:Array, indexToSort:int):Object { // find the higher order swf loader number from the current object. var highestSwfOrder:int = -1; var count:int = passedNamesArray.length; var index:int = 0; var selectedAppNames:Array = new Array(); var selectedObjectIndices:Array = new Array(); while(index < count) { var splitNames:Array = (passedNamesArray[index]as String).split("_");// TBD use constant if(splitNames.length > indexToSort) { var currentOrderNumber:int = int(splitNames[splitNames.length-indexToSort-1]); if( currentOrderNumber > highestSwfOrder) { highestSwfOrder = currentOrderNumber; // clear the current order info arrays selectedAppNames = new Array(); selectedObjectIndices = new Array(); selectedAppNames.push(passedNamesArray[index]); selectedObjectIndices.push(passesSelectedObjectIndices[index]); } else if (currentOrderNumber == highestSwfOrder) { selectedAppNames.push(passedNamesArray[index]); selectedObjectIndices.push(passesSelectedObjectIndices[index]); } } index++; } var resultObj:Object = { slectedAppNames:selectedAppNames, selectedObjectIndices:selectedObjectIndices}; return resultObj; } public function getAutomationChildrenArray(object:Object):Array { var automationObject:IAutomationObject = object as IAutomationObject; var childArray:Array = null; if (automationObject) { childArray = automationObject.getAutomationChildren(); } /* if(object.hasOwnProperty("getAutomationChildren") ) return object.getAutomationChildren(); if(object.automationDelegate.hasOwnProperty("getAutomationChildren") ) return object.automationDelegate.getAutomationChildren(); */ if(childArray) return childArray; // let us ensure that we are not returning a null array. return new Array(); } public function getPropertyValueFromPart(part:Object,obj:Object, pd:IAutomationPropertyDescriptor, relativeParent:IAutomationObject = null):Object { return getMemberFromPartOrObject(part,obj, pd.name); } public function getMemberFromPartOrObject(part:Object,obj:Object, name:String):Object { //var part:Object; var component:Object; //part = createIDPart(obj as IAutomationObject); component = obj; var result:Object = null; if (part != null && name in part) result = part[name]; else if (name in obj) result = obj[name]; else if (component != null) { if (name in component) result = component[name]; else if (component is IStyleClient) result = IStyleClient(component).getStyle(name); } return result; } /** * @private */ /* public function createIDPartForSpecifiedProperties(properties:Array, obj:IAutomationObject, parent:IAutomationObject = null):AutomationIDPart { // create the part only with the requried properties if (parent == null) parent = getParent(obj, null, true); return helpCreateIDPartWithRequiredProperties(parent, obj,properties); } */ /** * @private */ public function createIDPartForSpecifiedProperties(properties:Array,obj:IAutomationObject, parent:IAutomationObject = null):AutomationIDPart { if (parent == null) parent = getParent(obj, null, true); var parentObj:Object = parent as Object; // temp solution till we get the interfaces part of UIComponent var part:AutomationIDPart = parent ? parent.createAutomationIDPartWithRequiredProperties(obj,properties) as AutomationIDPart : helpCreateIDPartWithRequiredProperties(null, obj,properties); /* var part:AutomationIDPart = (parentObj &&(parentObj.automationDelegate) && (parentObj.automationDelegate.hasOwnProperty("createAutomationIDPartWithRequiredProperties"))) ? parentObj.automationDelegate.createAutomationIDPartWithRequiredProperties(obj,properties) as AutomationIDPart : helpCreateIDPartWithRequiredProperties(null, obj,properties); */ return part; } /** * @private * * Helper implementation of IAutomationIDHelper. Creates an id for * a given child. This should not be used, instead use createID, * or createIDPart. */ public function helpCreateIDPartWithRequiredProperties(parent:IAutomationObject, child:IAutomationObject,properyNamesList:Array, automationNameCallback:Function = null, automationIndexCallback:Function = null):AutomationIDPart { if(!properyNamesList) return helpCreateIDPart(parent,child,automationNameCallback,automationIndexCallback); else return getIdPart(parent,child,properyNamesList,automationNameCallback,automationIndexCallback); } private function getIdPart(parent:IAutomationObject, child:IAutomationObject,properyNamesList:Array = null, automationNameCallback:Function = null, automationIndexCallback:Function = null):AutomationIDPart { var part:AutomationIDPart = new AutomationIDPart(); //It doesn't matter if a property is null //add it anyways, because the callee asked for it //and not adding it will confuse QTP since we've //told it already about the properties in the env file //If this causes a problem and we need to add the if //null checks back, then be sure to update ToolAdapter //to not return null properties in Learn and ActiveScreen var indexCalculated:Boolean = false; for (var propNo:int = 0; propNo < properyNamesList.length; ++propNo) { var propertyName:String = properyNamesList[propNo]; if (propertyName == "id") { part.id = child is IDeferredInstantiationUIComponent ? IDeferredInstantiationUIComponent(child).id : null; if ((part.id == null) && (parent == null)) { //trace ("inside the helpCreateIDPart - id "+ child.automationName); // currently we are in the application object. // this is a temp fix till we have AIR delegates in place. // we need the application iD of this component instead of the id if(Automation.getMainApplication().hasOwnProperty("applicationID"))// this should work for AIR app's { part.id = Automation.getMainApplication().applicationID; //trace ("inside the helpCreateIDPart - id "+ part.id ); } else { //we are in flex app hosted from Air app part.id = processAppIDFromUniqueAppID(); } } } else if (propertyName == "automationName") part.automationName = (automationNameCallback == null ? child.automationName : automationNameCallback(child)); else if (propertyName == "automationIndex") { //note that parent can be null if it's the parentApplication part.automationIndex = (automationIndexCallback == null ? getChildIndex(getParent(child), child) : automationIndexCallback(child)); indexCalculated = true; } else if (propertyName == "className") part.className = AutomationClass.getClassName(child); else if (propertyName == "automationClassName") part.automationClassName = getAutomationClassName(child); else if (propertyName == AutomationManager.airWindowIndicatorPropertyName) { // we added this property to identify the airtoplevel windows part.isAIRWindow = true; } else { if (propertyName in child) part[propertyName] = child[propertyName]; else { part[propertyName] = ""; /// TBD var message:String = resourceManager.getString( "automation_agent", "notDefined", [propertyName, child]); //trace(message); /* throw new Error(message); */ } } } if ("automationName" in part && part.automationName == null) { if(indexCalculated) part.automationName = part.automationIndex; else { // calculate the index and assign the same as the automation value part.automationName = (automationIndexCallback == null ? getChildIndex(getParent(child), child) : automationIndexCallback(child)); } } return part; } public function traceMessage(className:String, methodName:String, message:String):void { trace(className+":"+methodName+" - "+message); } private function isSparkApplication(obj:DisplayObject):Boolean { if(AutomationHelper.isRequiredSparkClassPresent()){ var sparkAppClass:Class = Class(ApplicationDomain.currentDomain.getDefinition("spark.components.Application")); if(obj is sparkAppClass) return true; } return false; } } }