//------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors internal CalloutQueueItem(Delegate clientCallback, UiaCoreApi.UiaCacheResponse cacheResponse, AutomationEventArgs e, UiaCoreApi.UiaCacheRequest cacheRequest) { _clientCallback = clientCallback; _cacheResponse = cacheResponse; _e = e; _cacheRequest = cacheRequest; }
//------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors internal ClientSideQueueItem(Delegate clientCallback, AutomationElement srcEl, UiaCoreApi.UiaCacheRequest request, AutomationEventArgs e) { _clientCallback = clientCallback; _srcEl = srcEl; _request = request; _e = e; }
//------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods /// <summary> /// Push this CacheRequest onto this thread's stack of CacheRequests, /// making it the current CacheRequest for this thread. /// </summary> /// <remarks> /// Use Pop to remove this CacheRequest from the stack, making the previously /// active CacheRequest active again. CacheRequests can be pushed multiple times, /// and on multiple threads; but for each thread, must be popped off in the /// same order as they were pushed. /// /// A CacheRequest cannot be modified while it is pushed on any thread; attempting /// to modify it will generate an InvalidOperationException. /// </remarks> public void Push() { // pushing multiple times is legal, // and pushing on different threads is also legal, // so no preconditions to check for here. AutomationProperty[] propertyArray = (AutomationProperty[])_properties.ToArray(typeof(AutomationProperty)); AutomationPattern[] patternArray = (AutomationPattern[])_patterns.ToArray(typeof(AutomationPattern)); // _threadStack is thread local storage (TLS) based, so can be // accessed outside of the lock. if (_threadStack == null) { _threadStack = new Stack(); } _threadStack.Push(this); lock (_instanceLock) { _refCount++; // Generate a new UiaCacheRequest if (_uiaCacheRequest == null) { _uiaCacheRequest = new UiaCoreApi.UiaCacheRequest(_viewCondition, _scope, propertyArray, patternArray, _automationElementMode); } } }
// Raise events for rawEl internal static void RaiseEventInThisClientOnly(AutomationEvent eventId, AutomationElement rawEl, AutomationEventArgs e) { // This version of RaiseEventInThisClientOnly can be called with a local (proxied) or remote (native)AutomationElement lock (_classLock) { if (_listeners == null) { return; } AutomationElement el = rawEl; foreach (EventListenerClientSide listener in _listeners) { // Is this event a type this listener is interested in? if (listener.EventListener.EventId == eventId) { // Did this event happen on an element this listener is interested in? if (rawEl == null || listener.WithinScope(rawEl)) { UiaCoreApi.UiaCacheRequest cacheRequest = listener.EventListener.CacheRequest; CBQ.PostWorkItem(new ClientSideQueueItem(listener.ClientCallback, el, cacheRequest, e)); } } } } }
//------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ #region Internal Methods internal static AutomationElement BuildAutomationElementsFromResponse( UiaCoreApi.UiaCacheRequest cacheRequest, UiaCoreApi.UiaCacheResponse response) { if (response.TreeStructure == null) { Debug.Assert(response.RequestedData == null, "both RequestedData and TreeStructure should be null or non-null"); return(null); } // FrozenCacheRequest should not be null one new AE code, but may // still be null on old code paths - eg. top level window events - where // prefetching is not yet enabled. if (cacheRequest == null) { cacheRequest = CacheRequest.DefaultUiaCacheRequest; } // ParseTreeDescription is the method that parses the returned data // and builds up the tree, setting properties on each node as it goes along... // index and propIndex keep track of where it is, and we check afterwards // that all are pointing to the end, to ensure that everything matched // up as expected. int index = 0; int propIndex = 0; bool askedForChildren = (cacheRequest.TreeScope & TreeScope.Children) != 0; bool askedForDescendants = (cacheRequest.TreeScope & TreeScope.Descendants) != 0; AutomationElement root = ParseTreeDescription(response.TreeStructure, response.RequestedData, ref index, ref propIndex, cacheRequest, askedForChildren, askedForDescendants); if (index != response.TreeStructure.Length) { Debug.Assert(false, "Internal error: got malformed tree description string (extra chars at end)"); return(null); } if (response.RequestedData != null && propIndex != response.RequestedData.GetLength(0)) { Debug.Assert(false, "Internal error: mismatch between count of property buckets and nodes claiming them"); return(null); } // Properties are wrapped (eg. pattern classes inserted in front of interface) as // they are being returned to the caller, in the AutomationElement accessors, not here. return(root); }
//------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ #region Internal Methods internal UiaCoreApi.UiaCacheRequest GetUiaCacheRequest() { if (_uiaCacheRequest == null) { AutomationProperty[] propertiesArray = (AutomationProperty[])_properties.ToArray(typeof(AutomationProperty)); AutomationPattern[] patternsArray = (AutomationPattern[])_patterns.ToArray(typeof(AutomationPattern)); lock (_instanceLock) { _uiaCacheRequest = new UiaCoreApi.UiaCacheRequest(_viewCondition, _scope, propertiesArray, patternsArray, _automationElementMode); } } return(_uiaCacheRequest); }
// Private ctor used by Clone() private CacheRequest(Condition viewCondition, TreeScope scope, ArrayList properties, ArrayList patterns, AutomationElementMode automationElementMode, UiaCoreApi.UiaCacheRequest uiaCacheRequest) { _instanceLock = new object(); _viewCondition = viewCondition; _scope = scope; _properties = properties; _patterns = patterns; _automationElementMode = automationElementMode; _uiaCacheRequest = uiaCacheRequest; }
//------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors // full ctor internal EventListener( AutomationEvent eventId, TreeScope scope, AutomationProperty [] properties, UiaCoreApi.UiaCacheRequest cacheRequest ) { _eventId = eventId; _scope = scope; if (properties != null) { _properties = (AutomationProperty[])properties.Clone(); } else { _properties = null; } _cacheRequest = cacheRequest; }
//------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ #region Internal Methods internal UiaCoreApi.UiaCacheRequest GetUiaCacheRequest() { if (_uiaCacheRequest == null) { AutomationProperty[] propertiesArray = (AutomationProperty[])_properties.ToArray(typeof(AutomationProperty)); AutomationPattern[] patternsArray = (AutomationPattern[])_patterns.ToArray(typeof(AutomationPattern)); lock (_instanceLock) { _uiaCacheRequest = new UiaCoreApi.UiaCacheRequest(_viewCondition, _scope, propertiesArray, patternsArray, _automationElementMode); } } return _uiaCacheRequest; }
//------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods /// <summary> /// Push this CacheRequest onto this thread's stack of CacheRequests, /// making it the current CacheRequest for this thread. /// </summary> /// <remarks> /// Use Pop to remove this CacheRequest from the stack, making the previously /// active CacheRequest active again. CacheRequests can be pushed multiple times, /// and on multiple threads; but for each thread, must be popped off in the /// same order as they were pushed. /// /// A CacheRequest cannot be modified while it is pushed on any thread; attempting /// to modify it will generate an InvalidOperationException. /// </remarks> public void Push() { // pushing multiple times is legal, // and pushing on different threads is also legal, // so no preconditions to check for here. AutomationProperty[] propertyArray = (AutomationProperty[])_properties.ToArray(typeof(AutomationProperty)); AutomationPattern[] patternArray = (AutomationPattern[])_patterns.ToArray(typeof(AutomationPattern)); // _threadStack is thread local storage (TLS) based, so can be // accessed outside of the lock. if (_threadStack == null) { _threadStack = new Stack(); } _threadStack.Push(this); lock (_instanceLock) { _refCount++; // Generate a new UiaCacheRequest if (_uiaCacheRequest == null) { _uiaCacheRequest = new UiaCoreApi.UiaCacheRequest(_viewCondition, _scope, propertyArray, patternArray, _automationElementMode); } } }
//------------------------------------------------------ // // Private Methods // //------------------------------------------------------ #region Private Methods // Parses the string as returned from ElementSearcher - see ElementSearcher.cs // for a description of the format string. Summary is that it is a lisp-like // set of parens indicating tree structure (eg. "(()())" indicates a node containing // two child nodes), but uses 'P' instead of an open paran to indicate that the // corresonding node has a property array that needs to be associated with it. // // index is the current position in the tree strucure string, // propIndex is the current position in the array of property arrays // (an array of properties returned for each element that matches the // condition specified in the Searcher condition.) private static AutomationElement ParseTreeDescription(string treeDescription, object[,] properties, ref int index, ref int propIndex, UiaCoreApi.UiaCacheRequest cacheRequest, bool askedForChildren, bool askedForDescendants) { // Check that this is a 'begin node' tag (with or without properties)... if (string.IsNullOrEmpty(treeDescription)) { return(null); } char c = treeDescription[index]; if (c != '(' && c != 'P') { return(null); } bool havePropertiesForThisNode = c == 'P'; index++; SafeNodeHandle hnode = null; // If we have information for this node, and we want full remote // references back, then extract the hnode from the first slot of that // element's property row... if (havePropertiesForThisNode && cacheRequest.AutomationElementMode == AutomationElementMode.Full) { hnode = (SafeNodeHandle)properties[propIndex, 0]; } // Attach properties if present... object[,] cachedValues = null; int cachedValueIndex = 0; if (havePropertiesForThisNode) { cachedValues = properties; cachedValueIndex = propIndex; propIndex++; } AutomationElement node = new AutomationElement(hnode, cachedValues, cachedValueIndex, cacheRequest); if (askedForChildren || askedForDescendants) { // If we did request children or descendants at this level, then set the // cached first child to null - it may get overwritten with // an actual value later; but the key thing is that it gets // set so we can distinguish the "asked, but doesn't have one" from // the "didn't ask" case. (Internally, AutomationElement uses // 'this' to indicate the later case, and throws an exception if // you ask for the children without having previously asked // for them in a CacheRequest.) node.SetCachedFirstChild(null); } // Add in children... AutomationElement prevChild = null; for (; ;) { // Recursively parse the string... AutomationElement child = ParseTreeDescription(treeDescription, properties, ref index, ref propIndex, cacheRequest, askedForDescendants, askedForDescendants); if (child == null) { break; } // Then link child node into tree... child.SetCachedParent(node); if (prevChild == null) { node.SetCachedFirstChild(child); } else { prevChild.SetCachedNextSibling(child); } prevChild = child; } // Ensure that end node tag is present... if (treeDescription[index] != ')') { Debug.Assert(false, "Internal error: Got malformed tree description string, missing closing paren"); return(null); } index++; return(node); }