private void DrainQueue() { // It's possible items could be enqueued between when we check for the count // and dequeue but the event is set then and we'll come back into DrainQueue. // (note: don't use a for loop here because as the counter is incremented // Count is decremented and we'll only process half the queue) while (_q.Count > 0) { // pull an item off the queue, process, then clear it QueueItem item = (QueueItem)_q.Dequeue(); if (!_quitting) { try { item.Process(); } catch (Exception e) { if (Misc.IsCriticalException(e)) { throw; } // Eat it. // There's no place to let this exception percolate out // to so we'll stop it here. } } } }
// OnEventObjectDestroy - process an EventObjectDestroy WinEvent. private void OnEventObjectDestroy(int eventId, IntPtr hwnd, int idObject, int idChild, uint eventTime) { // Check if still alive. Ignore caret destroys - we're only interesed in 'real' objects here... if (idObject != UnsafeNativeMethods.OBJID_CARET && _accCurrent != null) { bool fDead = false; try { int dwState = _accCurrent.State; IntPtr hwndCur = _accCurrent.Window; if (hwndCur == IntPtr.Zero || !SafeNativeMethods.IsWindow(NativeMethods.HWND.Cast(hwndCur))) { fDead = true; } } catch (Exception e) { if (Misc.IsCriticalException(e)) { throw; } fDead = true; } if (fDead) { // It's dead... HandleFocusChange(IntPtr.Zero, null, 0, 0, eventTime); } } }
//------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ #region Internal Methods // The method that gets called from CallbackQueue's thread. Uses Post to invoke the callback on the proper thread. internal static void InvokeClientHandler(Delegate clientCallback, AutomationElement srcEl, AutomationEventArgs args) { try { if (args is AutomationPropertyChangedEventArgs) { ((AutomationPropertyChangedEventHandler)clientCallback)(srcEl, (AutomationPropertyChangedEventArgs)args); } else if (args is StructureChangedEventArgs) { ((StructureChangedEventHandler)clientCallback)(srcEl, (StructureChangedEventArgs)args); } else if (args is InternalAutomationFocusChangedEventArgs) { AutomationFocusChangedEventArgs realArgs = ((InternalAutomationFocusChangedEventArgs)args)._args; // For focus events, check that the event is actually more recent than the last one (see note at top of file). // Since the timestamps can wrap around, subtract and measure the delta instead of just comparing them. // Any events that appear to have taken place within the 5 seconds before the last event we got will be ignored. // (Because of wraparound, certain before- and after- time spans share the same deltas; 5000ms before has the // same value as MAXUINT-5000ms after. Since we're just trying to filter out very recent event race conditions, // confine this test to a small window, just the most recent 5 seconds. That means you'd have to wait a *very* // long time without any focus changes before getting a false positive here.) uint eventTime = ((InternalAutomationFocusChangedEventArgs)args)._eventTime; if (_lastFocusEventTime != 0) { uint delta = _lastFocusEventTime - eventTime; // Exclude events that happend before the last one, but do allow any that happened "at the same time", // (delta==0) since they likely actually happened after, but within the resolution of the event timer. if (delta < 5000 && delta != 0) { return; } } _lastFocusEventTime = eventTime; ((AutomationFocusChangedEventHandler)clientCallback)(srcEl, realArgs); } else { ((AutomationEventHandler)clientCallback)(srcEl, args); } } catch (Exception e) { if (Misc.IsCriticalException(e)) { throw; } // Since we can't predict what exceptions an outside client might throw intentionally ignore all } }
private bool IsCriticalMSAAException(Exception e) { // Some OLEACC proxies produce out-of-memory for non-critical reasons: // notably, the treeview proxy will raise this if the target HWND no longer exists, // GetWindowThreadProcessID fails and it therefore won't be able to allocate shared // memory in the target process, so it incorrectly assumes OOM. // Some Native impls (media player) return E_POINTER, which COM Interop translates // into NullRefException; need to ignore those too. // should ignore those return(!(e is OutOfMemoryException) && !(e is NullReferenceException) && Misc.IsCriticalException(e)); }
//------------------------------------------------------ // // Private Methods // //------------------------------------------------------ #region Private Methods // queue winevents so that the get processed in the order we receive them. If we just // dispatch them as we get them, we'll end up getting some _while_ we are processing others, // and end up completing those events out of order, making the event order appear backwards. // This code checks whether we are currently processing an event, and if so, queues it so that // we process it when we're done with the current event. private void WinEventReentrancyFilter(int winEventHook, int eventId, IntPtr hwnd, int idObject, int idChild, int eventThread, uint eventTime) { if (_fBusy) { _qEvents.Enqueue(new WinEvent(eventId, hwnd, idObject, idChild, eventTime)); } else { _fBusy = true; try { PreWinEventProc(eventId, hwnd, idObject, idChild, eventTime); // deliver this event } catch (Exception e) { if (Misc.IsCriticalException(e)) { throw; } // ignore exceptions for now since we've no way to let clients add exception handlers } while (_qEvents.Count > 0) { WinEvent e = (WinEvent)_qEvents.Dequeue(); // process queued events try { PreWinEventProc(e._eventId, e._hwnd, e._idObject, e._idChild, e._eventTime); } catch (Exception ex) { if (Misc.IsCriticalException(ex)) { throw; } // ignore exceptions for now since we've no way to let clients add exception handlers } } _fBusy = false; } }
// Given an entry from one of the hash-tables or lists, check if it matches the image/classname, and if so, call the // factory method to create the proxy. // (Because full classname matching is done via hash-table lookup, this only needs to do string comparisons // for partial classname matches.) static private IRawElementProviderSimple GetProxyFromEntry(ProxyScoping findType, object entry, ref string imageName, NativeMethods.HWND hwnd, int idChild, int idObject, string classNameForPartialMatch) { // First, determine if the entry matches, and if so, extract the factory callback... ClientSideProviderFactoryCallback factoryCallback = null; // The entry may be a ClientSideProviderFactoryCallback or ClientSideProviderDescription... if (findType == ProxyScoping.ImageOnlyHandlers || findType == ProxyScoping.FallbackHandlers) { // Handle the fallback and image cases specially. The array for these is an array // of ClientSideProviderFactoryCallbacks, not ClientSideProviderDescription. factoryCallback = (ClientSideProviderFactoryCallback)entry; } else { // Other cases use ClientSideProviderDescription... ClientSideProviderDescription pi = (ClientSideProviderDescription)entry; // Get the image name if necessary... #pragma warning suppress 6507 // Null and Empty string mean different things here. if (imageName == null && pi.ImageName != null) { imageName = GetImageName(hwnd); } if (pi.ImageName == null || pi.ImageName == imageName) { // Check if we have a match for this entry... switch (findType) { case ProxyScoping.ExactMatchApparentClassName: factoryCallback = pi.ClientSideProviderFactoryCallback; break; case ProxyScoping.ExactMatchRealClassName: if ((pi.Flags & ClientSideProviderMatchIndicator.DisallowBaseClassNameMatch) == 0) { factoryCallback = pi.ClientSideProviderFactoryCallback; } break; case ProxyScoping.PartialMatchApparentClassName: if (classNameForPartialMatch.IndexOf(pi.ClassName, StringComparison.Ordinal) >= 0) { factoryCallback = pi.ClientSideProviderFactoryCallback; } break; case ProxyScoping.PartialMatchRealClassName: if (classNameForPartialMatch.IndexOf(pi.ClassName, StringComparison.Ordinal) >= 0 && ((pi.Flags & ClientSideProviderMatchIndicator.DisallowBaseClassNameMatch) == 0)) { factoryCallback = pi.ClientSideProviderFactoryCallback; } break; default: Debug.Assert(false, "unexpected switch() case:"); break; } } } // Second part: did we get a match? If so, use the factory callback to obtain an instance... if (factoryCallback == null) { return(null); } // if we get an exception creating a proxy just don't create the proxy and let the UIAutomation default proxy be used // This will still allow the tree to be navigated and some properties to be made availible. // Catching all exceptions here doesn't follow .NET guidelines, but is it ok in this scenario? try { return(factoryCallback(hwnd, idChild, idObject)); } catch (Exception e) { if (Misc.IsCriticalException(e)) { throw; } return(null); } }
// We need to treat MSAA's FOCUS winevents differently depending on the OBJID - // OBJID_CLIENT gets routed to the proxies; _MENU and _SYSMENU get speical treatment. private AutomationElement GetFocusedElementFromWinEvent(IntPtr hwnd, int idObject, int idChild) { try { IRawElementProviderSimple provider = null; // These are the only object types that oleacc proxies allow to take focus. // (Native IAccessibles can send focus for other custom OBJID valus, but those are no use // to us.) // Try and get providers for them ourself - if we don't get anything, then // defer to core to get the element for the HWND itself. if (idObject == UnsafeNativeMethods.OBJID_CLIENT) { // regular focus - pass it off to a proxy... provider = ProxyManager.ProxyProviderFromHwnd(NativeMethods.HWND.Cast(hwnd), idChild, UnsafeNativeMethods.OBJID_CLIENT); } else if (idObject == UnsafeNativeMethods.OBJID_MENU) { // menubar focus - see if there's a menubar pseudo-proxy registered... ClientSideProviderFactoryCallback factory = ProxyManager.NonClientMenuBarProxyFactory; if (factory != null) { provider = factory(hwnd, idChild, idObject); } } else if (idObject == UnsafeNativeMethods.OBJID_SYSMENU) { // system menu box focus - see if there's a sysmenu pseudo-proxy registered... ClientSideProviderFactoryCallback factory = ProxyManager.NonClientSysMenuProxyFactory; if (factory != null) { provider = factory(hwnd, idChild, idObject); } } else if (idObject <= 0) { return(null); } else { // This covers OBJID_CLIENT and custom OBJID cases. // Pass it to the proxy manager: most proxies will just handle OBJID_CLIENT, // but the MSAA proxy can potentally handle other OBJID values. provider = ProxyManager.ProxyProviderFromHwnd(NativeMethods.HWND.Cast(hwnd), idChild, idObject); } if (provider != null) { // Ask the fragment root if any of its children really have the focus IRawElementProviderFragmentRoot fragment = provider as IRawElementProviderFragmentRoot; if (fragment != null) { // if we get back something that is different than what we started with and its not null // use that instead. This is here to get the subset link in the listview but could be usefull // for listview subitems as well. IRawElementProviderSimple realFocus = fragment.GetFocus(); if (realFocus != null && !Object.ReferenceEquals(realFocus, provider)) { provider = realFocus; } } SafeNodeHandle hnode = UiaCoreApi.UiaNodeFromProvider(provider); return(AutomationElement.Wrap(hnode)); } else { // Didn't find a proxy to handle this hwnd - pass off to core... return(AutomationElement.FromHandle(hwnd)); } } catch (Exception e) { if (Misc.IsCriticalException(e)) { throw; } return(null); } }