Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Core / CSharp / System / Windows / Input / AccessKeyManager.cs / 1 / AccessKeyManager.cs
//---------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Windows.Media; using System.Windows.Interop; using MS.Internal; using System.Diagnostics; using System.Windows; using System.Security; using System.Security.Permissions; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; namespace System.Windows.Input { ////// AccessKeyManager object is created on demand and it is one per thread. /// It attached an event handler for PostProcessInput on InputManager and expose registration and /// unregistration of access keys. When the access key is pressed in calls OnAccessKey method on the target element /// public sealed class AccessKeyManager { #region Public API ////// Register the access key binding to the element that is the accesskey. /// /// When the key is pressed the element OnAccessKey method is called /// The registration element public static void Register(string key, IInputElement element) { if (element == null) { throw new ArgumentNullException("element"); } key = NormalizeKey(key); AccessKeyManager akm = AccessKeyManager.Current; lock (akm._keyToElements) { ArrayList elements = (ArrayList)akm._keyToElements[key]; if (elements == null) { elements = new ArrayList(1); akm._keyToElements[key] = elements; } else { // There were some elements there, remove dead ones PurgeDead(elements, null); } elements.Add(new WeakReference(element)); } } ////// Unregister one key bound to a particular element /// /// /// public static void Unregister(string key, IInputElement element) { if (element == null) { throw new ArgumentNullException("element"); } key = NormalizeKey(key); AccessKeyManager akm = AccessKeyManager.Current; lock (akm._keyToElements) { // Get all elements bound to this key and remove this element ArrayList elements = (ArrayList)akm._keyToElements[key]; if (elements != null) { PurgeDead(elements, element); if (elements.Count == 0) { akm._keyToElements.Remove(key); } } } } ////// Tells if there is a particular key registered at the global scope in this Context. /// /// Scope to query (for example the PresentationSource of the visual) /// ///public static bool IsKeyRegistered(object scope, string key) { key = NormalizeKey(key); AccessKeyManager akm = AccessKeyManager.Current; List targets = akm.GetTargetsForScope(scope, key, null, AccessKeyInformation.Empty); return (targets != null && targets.Count > 0); } /// /// Process the given key as if the key were pressed at the global scope in this context. /// /// scope in which to invoke the access key /// character being pressed /// True if this key has multiple matches ///false if there are no more keys that match, true otherwise ////// Executes the command on the given command source. /// ////// Critical - calls critical function (ProcessKeyForScope) /// PublicOK - always passes in false for userInitiated, which is safe /// [SecurityCritical] public static bool ProcessKey(object scope, string key, bool isMultiple) { key = NormalizeKey(key); AccessKeyManager akm = AccessKeyManager.Current; return (akm.ProcessKeyForScope(scope, key, isMultiple,false) == ProcessKeyResult.MoreMatches); } ////// Returns StringInfo.GetNextTextElement(key).ToUpperInvariant() throwing exceptions for null /// and multi-char strings. /// /// ///private static string NormalizeKey(string key) { if (key == null) { throw new ArgumentNullException("key"); } string firstCharacter = StringInfo.GetNextTextElement(key); if (key != firstCharacter) { throw new ArgumentException(SR.Get(SRID.AccessKeyManager_NotAUnicodeCharacter, "key")); } return firstCharacter.ToUpperInvariant(); } /// /// This event is used by elements that want to define a scope for accesskeys, such as Menu and Popup. /// This event will never be raised, it is used to identify classes that define new scopes. /// public static readonly RoutedEvent AccessKeyPressedEvent = EventManager.RegisterRoutedEvent( "AccessKeyPressed", RoutingStrategy.Bubble, typeof(AccessKeyPressedEventHandler), typeof(AccessKeyManager)); ////// Adds a handler for the AccessKeyPressed attached event /// /// UIElement or ContentElement that listens to this event /// Event Handler to be added public static void AddAccessKeyPressedHandler(DependencyObject element, AccessKeyPressedEventHandler handler) { UIElement.AddHandler(element, AccessKeyPressedEvent, handler); } ////// Removes a handler for the AccessKeyPressed attached event /// /// UIElement or ContentElement that listens to this event /// Event Handler to be removed public static void RemoveAccessKeyPressedHandler(DependencyObject element, AccessKeyPressedEventHandler handler) { UIElement.RemoveHandler(element, AccessKeyPressedEvent, handler); } #endregion #region Constructor ////// Critical: This code accesses InputManager.Current which is critical /// TreatAsSafe: This code does not expose it and simply adds an event handler that is internal /// [SecurityCritical, SecurityTreatAsSafe] private AccessKeyManager() { InputManager.Current.PostProcessInput += new ProcessInputEventHandler(PostProcessInput); } #endregion #region Properties ////// Access to the current context's AccessKeyManager class /// private static AccessKeyManager Current { get { if (_accessKeyManager == null) _accessKeyManager = new AccessKeyManager(); return _accessKeyManager; } } #endregion #region PostProcessInput Event Handlers private enum ProcessKeyResult { NoMatch, MoreMatches, LastMatch } ////// Critical: accesses e.StagingItem.Input /// [SecurityCritical] private void PostProcessInput(object sender, ProcessInputEventArgs e) { if (e.StagingItem.Input.Handled) return; if (e.StagingItem.Input.RoutedEvent == Keyboard.KeyDownEvent) { OnKeyDown((KeyEventArgs)e.StagingItem.Input); } else if (e.StagingItem.Input.RoutedEvent == TextCompositionManager.TextInputEvent) { OnText((TextCompositionEventArgs)e.StagingItem.Input); } } // Assumes key is already a single unicode character ////// Critical - sets the userInitiated bit on a command, which is used /// for security purposes later. /// [SecurityCritical] private ProcessKeyResult ProcessKeyForSender(object sender, string key, bool existsElsewhere, bool userInitiated) { // This comes from OnKeyDown or OnText and though it is a single character it might not be uppercased. key = key.ToUpperInvariant(); IInputElement inputElementSender = sender as IInputElement; Listtargets = GetTargetsForSender(inputElementSender, key); return ProcessKey(targets, key, existsElsewhere, userInitiated); } // Assumes key is already a single unicode character AND is uppercased /// /// Critical - sets the userInitiated bit on a command, which is used /// for security purposes later. /// [SecurityCritical] private ProcessKeyResult ProcessKeyForScope(object scope, string key, bool existsElsewhere, bool userInitiated) { Listtargets = GetTargetsForScope(scope, key, null, AccessKeyInformation.Empty); return ProcessKey(targets, key, existsElsewhere, userInitiated); } /// /// Critical - Sets calls AccessKeyPressedEventArgs setting the userInitiated bit which is used /// for security purposes later. /// [SecurityCritical] private ProcessKeyResult ProcessKey(Listtargets, string key, bool existsElsewhere, bool userInitiated) { if (targets != null) { bool oneUIElement = true; UIElement invokeUIElement = null; bool lastWasFocused = false; int chosenIndex = 0; for (int i = 0; i < targets.Count; i++) { UIElement target = targets[i] as UIElement; Debug.Assert(target != null, "Targets should only be UIElements"); if (!target.IsEnabled) continue; if (invokeUIElement == null) { invokeUIElement = target; chosenIndex = i; } else { if (lastWasFocused) { invokeUIElement = target; chosenIndex = i; } oneUIElement = false; } // lastWasFocused = target.IsKeyboardFocused; } if (invokeUIElement != null) { AccessKeyEventArgs args = new AccessKeyEventArgs(key, !oneUIElement || existsElsewhere /* == isMultiple */,userInitiated); try { invokeUIElement.InvokeAccessKey(args); } finally { args.ClearUserInitiated(); } return (chosenIndex == targets.Count - 1) ? ProcessKeyResult.LastMatch : ProcessKeyResult.MoreMatches; } } return ProcessKeyResult.NoMatch; } /// /// Critical - Calls ProcessKeyForSender, setting the userInitiated /// bit, which is used for security purposes later. /// [SecurityCritical] private void OnText(TextCompositionEventArgs e) { // AccessKeyManager handles both text and system text. string text = e.Text; if ((text == null) || (text.Length == 0)) { text = e.SystemText; } if ((text != null) && (text.Length > 0)) { if (ProcessKeyForSender(e.OriginalSource, text, false /* existsElsewhere */,e.UserInitiated) != ProcessKeyResult.NoMatch) { e.Handled = true; } } } ////// Critical - Calls ProcessKeyForSender, setting the userInitiated /// bit, which is used for security purposes later. /// [SecurityCritical] private void OnKeyDown(KeyEventArgs e) { KeyboardDevice keyboard = (KeyboardDevice)e.Device; string text = null; switch (e.RealKey) { case Key.Enter : text = "\x000D"; break; case Key.Escape : text = "\x001B"; break; } if (text != null) { if (ProcessKeyForSender(e.OriginalSource, text, false /* existsElsewhere */,e.UserInitiated) != ProcessKeyResult.NoMatch) { e.Handled = true; } } } ////// Get the list of access key targets for the sender of the keyboard event. If sender is null, /// pretend key was pressed in the active window. /// /// /// ////// /// Critical: calls Critical member GetInfoForElement() and accesses Scope from AccessKeyInformation. /// TreatAsSafe: Does not pass the sender info (which may contain a PresentationSource) out. /// [SecurityCritical, SecurityTreatAsSafe] private ListGetTargetsForSender(IInputElement sender, string key) { // Find the scope for the sender -- will be matched against the possible targets' scopes AccessKeyInformation senderInfo = GetInfoForElement(sender, key); return GetTargetsForScope(senderInfo.Scope, key, sender, senderInfo); } /// /// Critical: calls CriticalGetActiveSource() /// TreatAsSafe: Does not pass the returned scope to any other method, nor is it /// returned from GetTargetsForScope. Also returned value is not critical. /// [SecurityCritical, SecurityTreatAsSafe] private ListGetTargetsForScope(object scope, string key, IInputElement sender, AccessKeyInformation senderInfo) { // null scope defaults to the active window if (scope == null) { scope = CriticalGetActiveSource(); // if there is no active scope then give up if (scope == null) { return null; } } //Scoping: // 1) When key is pressed, find matching AKs -> S // 3) find scope for keyevent.Source // 4) find scope for everything in S. throw away those that don't match. // 5) Final selection uses S. yay! // // List possibleElements; lock (_keyToElements) { possibleElements = CopyAndPurgeDead(_keyToElements[key] as ArrayList); } if (possibleElements == null) return null; List finalTargets = new List (1); // Go through all the possible elements, find the interesting candidates for (int i = 0; i < possibleElements.Count; i++) { IInputElement element = possibleElements[i]; if (element != sender) { if (IsTargetable(element)) { AccessKeyInformation elementInfo = GetInfoForElement(element, key); if (elementInfo.target == null) continue; if (scope == elementInfo.Scope) { finalTargets.Add(elementInfo.target); } } } else { // This is the same element that sent the event so it must be in the same scope. // Just add it to the final targets if (senderInfo.target != null) { finalTargets.Add(senderInfo.target); } } } return finalTargets; } /// /// Returns scope for the given element. /// /// /// ///Scope for the given element, null means the context global scope ////// Critical: calls GetSourceForElement(), CriticalGetActiveSource(), and returns AccessKeyInformation. /// [SecurityCritical] private AccessKeyInformation GetInfoForElement(IInputElement element, string key) { AccessKeyInformation info = new AccessKeyInformation(); if (element != null) { AccessKeyPressedEventArgs args = new AccessKeyPressedEventArgs(key); element.RaiseEvent(args); info.Scope = args.Scope; info.target = args.Target; if (info.Scope == null) { info.Scope = GetSourceForElement(element); } } else { info.Scope = CriticalGetActiveSource(); } return info; } ////// Critical: calls PresentationSource.CriticalFromVisual, and returns PresentationSource /// [SecurityCritical] private PresentationSource GetSourceForElement(IInputElement element) { PresentationSource source = null; DependencyObject elementDO = element as DependencyObject; // Use internal helpers to try to find the source of the element. // Because IInputElements can move around without notification we need to // look up the source every time. if (elementDO != null) { DependencyObject containingVisual = InputElement.GetContainingVisual(elementDO); if (containingVisual != null) { source = PresentationSource.CriticalFromVisual(containingVisual); } } // NOTE: source can be null but IsTargetable(element) == true if the // element is in an orphaned tree but the tree has not yet been garbage collected. return source; } ////// Critical: calls UnsafeNativeMethod GetActiveWindow(), and Critical method /// HwndSource.FromHwnd(). /// TreatAsSafe: Does not pass any parameters to GetActiveWindow(), and does /// not expose the return value, and HwndSource.FromHwnd will demand /// UIPermissionWindow.AllWindows. /// [SecurityCritical, SecurityTreatAsSafe] private PresentationSource GetActiveSource() { IntPtr hwnd = MS.Win32.UnsafeNativeMethods.GetActiveWindow(); if (hwnd != IntPtr.Zero) return HwndSource.FromHwnd(hwnd); return null; } ////// Critical: calls UnsafeNativeMethod GetActiveWindow() and HwndSource.CriticalFromHwnd() /// [SecurityCritical] private PresentationSource CriticalGetActiveSource() { IntPtr hwnd = MS.Win32.UnsafeNativeMethods.GetActiveWindow(); if (hwnd != IntPtr.Zero) return HwndSource.CriticalFromHwnd(hwnd); return null; } private bool IsTargetable(IInputElement element) { DependencyObject uielement = InputElement.GetContainingUIElement((DependencyObject)element); // For an element to be a valid target it must be visible and enabled if (uielement != null && IsVisible(uielement) && IsEnabled(uielement)) { return true; } return false; } private static bool IsVisible(DependencyObject element) { while (element != null) { Visibility visibility; UIElement uiElem = element as UIElement; UIElement3D uiElem3D = element as UIElement3D; if (uiElem != null) { visibility = uiElem.Visibility; } else { visibility = uiElem3D.Visibility; } if (visibility != Visibility.Visible) { return false; } element = UIElementHelper.GetUIParent(element); } return true; } // returns whether the given DO is enabled or not private static bool IsEnabled(DependencyObject element) { return ((bool)element.GetValue(UIElement.IsEnabledProperty)); } private struct AccessKeyInformation { ////// Critical: Scope may contain an PresentationSource which we /// would not want exposed. /// public object Scope { [SecurityCritical] get { return _scope; } set { _scope = value; } } public UIElement target; private static AccessKeyInformation _empty = new AccessKeyInformation(); public static AccessKeyInformation Empty { get { return _empty; } } private object _scope; } private static void PurgeDead(ArrayList elements, object elementToRemove) { for (int i = 0; i < elements.Count; ) { WeakReference weakReference = (WeakReference)elements[i]; object element = weakReference.Target; if (element == null || element == elementToRemove) { elements.RemoveAt(i); } else { i++; } } } ////// Takes an ArrayList of WeakReferences, removes the dead references and returns /// a generic List of IInputElements (strong references) /// private static ListCopyAndPurgeDead(ArrayList elements) { if (elements == null) { return null; } List copy = new List (elements.Count); for (int i = 0; i < elements.Count; ) { WeakReference weakReference = (WeakReference)elements[i]; object element = weakReference.Target; if (element == null) { elements.RemoveAt(i); } else { Debug.Assert(element is IInputElement, "Element in AccessKeyManager store was not of type IInputElement"); copy.Add((IInputElement)element); i++; } } return copy; } #endregion #region Private Properties ///////////////////////////////////////////////////////////////////////////////// // Overview: Algorithm to look up access key from the element for which it is a target. // // When the AccessKeyCharacter for an element is requested we see if there // is a corresponding AccessKeyElement stashed on the element. If there is, // raise the AccessKeyPressed event on it to see if that element is still the // target for it. If not, go through all registered accesskeys and get their // targets until we find the desired element. The "primary" access key character // is the first one we find. // // Note: The algorithm ends up being O(n) for each request for AccessKeyCharacter // because there is no mapping from AccessKeyElement to its "primary" character. // Maintaining this would require storing a hash from AccessKeyElement to character // or requiring that each element registered implement an interface or some other // kind of contract. Because we don't keep track of this or enforce this, to find // the "primary" character we must go through all registered pairs of // (character, element) to find the character -- O(n). // // This ends up being just fine, because in any given context there shouldn't be // so many elements registered that this cost is at all noticable. // ///////////////////////////////////////////////////////////////////////////////// /// /// The primary access key element for an element. This is stored as a WeakReference. /// private static readonly DependencyProperty AccessKeyElementProperty = DependencyProperty.RegisterAttached("AccessKeyElement", typeof(WeakReference), typeof(AccessKeyManager)); #endregion #region Private Methods for UIAutomation internal static string InternalGetAccessKeyCharacter(DependencyObject d) { return Current.GetAccessKeyCharacter(d); } private string GetAccessKeyCharacter(DependencyObject d) { // See what the local value for AccessKeyElement is first and start with that. WeakReference cachedElementWeakRef = (WeakReference)d.GetValue(AccessKeyElementProperty); IInputElement accessKeyElement = (cachedElementWeakRef != null) ? (IInputElement)cachedElementWeakRef.Target : null; if (accessKeyElement != null) { // First figure out if the target of accessKeyElement is still "d", then go find // the "primary" character for the accessKeyElement. AccessKeyPressedEventArgs accessKeyPressedEventArgs = new AccessKeyPressedEventArgs(); accessKeyElement.RaiseEvent(accessKeyPressedEventArgs); if (accessKeyPressedEventArgs.Target == d) { // Because there is no way to get at the access key element's character from the // element (there is no interface or anything) we have to go through all registered // access keys and see if this access key element is still registered and what its // "primary" character is. foreach (DictionaryEntry entry in Current._keyToElements) { ArrayList elements = (ArrayList)entry.Value; for (int i = 0; i < elements.Count; i++) { // If this element matches accessKeyElement, then return the current character WeakReference currentElementWeakRef = (WeakReference)elements[i]; if (currentElementWeakRef.Target == accessKeyElement) { return (string)entry.Key; } } } } } // There was no access key stored or it no longer matched. Clear out the cache and figure it out again. d.ClearValue(AccessKeyElementProperty); foreach (DictionaryEntry entry in Current._keyToElements) { ArrayList elements = (ArrayList)entry.Value; for (int i = 0; i < elements.Count; i++) { // Determine the target for this element. Cache the weak reference for the element on the target. WeakReference currentElementWeakRef = (WeakReference)elements[i]; IInputElement currentElement = (IInputElement)currentElementWeakRef.Target; if (currentElement != null) { AccessKeyPressedEventArgs accessKeyPressedEventArgs = new AccessKeyPressedEventArgs(); currentElement.RaiseEvent(accessKeyPressedEventArgs); // If the target was non-null, cache the access key element on the target. // if the target matches "d", return the current character. if (accessKeyPressedEventArgs.Target != null) { accessKeyPressedEventArgs.Target.SetValue(AccessKeyElementProperty, currentElementWeakRef); if (accessKeyPressedEventArgs.Target == d) { return (string)entry.Key; } } } } } return String.Empty; } #endregion #region Data // Map: string -> ArrayList of WeakReferences to IInputElements private Hashtable _keyToElements = new Hashtable(10); [ThreadStatic] private static AccessKeyManager _accessKeyManager; #endregion } ////// The delegate type for handling a FindScope event /// public delegate void AccessKeyPressedEventHandler(object sender, AccessKeyPressedEventArgs e); ////// The inputs to an AccessKeyPressedEventHandler /// public class AccessKeyPressedEventArgs : RoutedEventArgs { #region Constructors ////// The constructor for AccessKeyPressed event args /// public AccessKeyPressedEventArgs() { RoutedEvent = AccessKeyManager.AccessKeyPressedEvent; _key = null; } ////// Constructor for AccessKeyPressed event args /// /// public AccessKeyPressedEventArgs(string key) : this() { _key = key; } #endregion #region Public Properties ////// The scope for the element that raised this event. /// public object Scope { get { return _scope; } set { _scope = value; } } ////// Target element for the element that raised this event. /// ///public UIElement Target { get { return _target; } set { _target = value; } } /// /// Key that was pressed /// ///public string Key { get { return _key; } } #endregion #region Protected Methods /// /// /// The handler to invoke. /// The current object along the event's route. protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget) { AccessKeyPressedEventHandler handler = (AccessKeyPressedEventHandler)genericHandler; handler(genericTarget, this); } #endregion #region Data private object _scope; private UIElement _target; private string _key; #endregion } ////// Information pertaining to when the access key associated with an element is pressed /// public class AccessKeyEventArgs : EventArgs { ////// /// ////// Critical - sets the userInitiated bit on a command, which is used /// for security purposes later. /// [SecurityCritical] internal AccessKeyEventArgs(string key, bool isMultiple, bool userInitiated) { _key = key; _isMultiple = isMultiple; _userInitiated = new SecurityCriticalDataForSet(userInitiated); } /// /// Critical - sets the userInitiated bit on a command, which is used /// for security purposes later. /// TreatAsSafe: Resets the user initiated bit /// [SecurityCritical,SecurityTreatAsSafe] internal void ClearUserInitiated() { _userInitiated.Value = false; } ////// The key that was pressed which invoked this access key /// ///public string Key { get { return _key; } } /// /// Were there other elements which are also invoked by this key /// ///public bool IsMultiple { get { return _isMultiple; } } internal bool UserInitiated { get { return _userInitiated.Value; } } private string _key; private bool _isMultiple; /// /// Critical - This is critical for set, setting this bool can cause the spoofing of paste /// private SecurityCriticalDataForSet_userInitiated; } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------------------- // // Copyright (C) Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Threading; using System.Windows.Media; using System.Windows.Interop; using MS.Internal; using System.Diagnostics; using System.Windows; using System.Security; using System.Security.Permissions; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; namespace System.Windows.Input { /// /// AccessKeyManager object is created on demand and it is one per thread. /// It attached an event handler for PostProcessInput on InputManager and expose registration and /// unregistration of access keys. When the access key is pressed in calls OnAccessKey method on the target element /// public sealed class AccessKeyManager { #region Public API ////// Register the access key binding to the element that is the accesskey. /// /// When the key is pressed the element OnAccessKey method is called /// The registration element public static void Register(string key, IInputElement element) { if (element == null) { throw new ArgumentNullException("element"); } key = NormalizeKey(key); AccessKeyManager akm = AccessKeyManager.Current; lock (akm._keyToElements) { ArrayList elements = (ArrayList)akm._keyToElements[key]; if (elements == null) { elements = new ArrayList(1); akm._keyToElements[key] = elements; } else { // There were some elements there, remove dead ones PurgeDead(elements, null); } elements.Add(new WeakReference(element)); } } ////// Unregister one key bound to a particular element /// /// /// public static void Unregister(string key, IInputElement element) { if (element == null) { throw new ArgumentNullException("element"); } key = NormalizeKey(key); AccessKeyManager akm = AccessKeyManager.Current; lock (akm._keyToElements) { // Get all elements bound to this key and remove this element ArrayList elements = (ArrayList)akm._keyToElements[key]; if (elements != null) { PurgeDead(elements, element); if (elements.Count == 0) { akm._keyToElements.Remove(key); } } } } ////// Tells if there is a particular key registered at the global scope in this Context. /// /// Scope to query (for example the PresentationSource of the visual) /// ///public static bool IsKeyRegistered(object scope, string key) { key = NormalizeKey(key); AccessKeyManager akm = AccessKeyManager.Current; List targets = akm.GetTargetsForScope(scope, key, null, AccessKeyInformation.Empty); return (targets != null && targets.Count > 0); } /// /// Process the given key as if the key were pressed at the global scope in this context. /// /// scope in which to invoke the access key /// character being pressed /// True if this key has multiple matches ///false if there are no more keys that match, true otherwise ////// Executes the command on the given command source. /// ////// Critical - calls critical function (ProcessKeyForScope) /// PublicOK - always passes in false for userInitiated, which is safe /// [SecurityCritical] public static bool ProcessKey(object scope, string key, bool isMultiple) { key = NormalizeKey(key); AccessKeyManager akm = AccessKeyManager.Current; return (akm.ProcessKeyForScope(scope, key, isMultiple,false) == ProcessKeyResult.MoreMatches); } ////// Returns StringInfo.GetNextTextElement(key).ToUpperInvariant() throwing exceptions for null /// and multi-char strings. /// /// ///private static string NormalizeKey(string key) { if (key == null) { throw new ArgumentNullException("key"); } string firstCharacter = StringInfo.GetNextTextElement(key); if (key != firstCharacter) { throw new ArgumentException(SR.Get(SRID.AccessKeyManager_NotAUnicodeCharacter, "key")); } return firstCharacter.ToUpperInvariant(); } /// /// This event is used by elements that want to define a scope for accesskeys, such as Menu and Popup. /// This event will never be raised, it is used to identify classes that define new scopes. /// public static readonly RoutedEvent AccessKeyPressedEvent = EventManager.RegisterRoutedEvent( "AccessKeyPressed", RoutingStrategy.Bubble, typeof(AccessKeyPressedEventHandler), typeof(AccessKeyManager)); ////// Adds a handler for the AccessKeyPressed attached event /// /// UIElement or ContentElement that listens to this event /// Event Handler to be added public static void AddAccessKeyPressedHandler(DependencyObject element, AccessKeyPressedEventHandler handler) { UIElement.AddHandler(element, AccessKeyPressedEvent, handler); } ////// Removes a handler for the AccessKeyPressed attached event /// /// UIElement or ContentElement that listens to this event /// Event Handler to be removed public static void RemoveAccessKeyPressedHandler(DependencyObject element, AccessKeyPressedEventHandler handler) { UIElement.RemoveHandler(element, AccessKeyPressedEvent, handler); } #endregion #region Constructor ////// Critical: This code accesses InputManager.Current which is critical /// TreatAsSafe: This code does not expose it and simply adds an event handler that is internal /// [SecurityCritical, SecurityTreatAsSafe] private AccessKeyManager() { InputManager.Current.PostProcessInput += new ProcessInputEventHandler(PostProcessInput); } #endregion #region Properties ////// Access to the current context's AccessKeyManager class /// private static AccessKeyManager Current { get { if (_accessKeyManager == null) _accessKeyManager = new AccessKeyManager(); return _accessKeyManager; } } #endregion #region PostProcessInput Event Handlers private enum ProcessKeyResult { NoMatch, MoreMatches, LastMatch } ////// Critical: accesses e.StagingItem.Input /// [SecurityCritical] private void PostProcessInput(object sender, ProcessInputEventArgs e) { if (e.StagingItem.Input.Handled) return; if (e.StagingItem.Input.RoutedEvent == Keyboard.KeyDownEvent) { OnKeyDown((KeyEventArgs)e.StagingItem.Input); } else if (e.StagingItem.Input.RoutedEvent == TextCompositionManager.TextInputEvent) { OnText((TextCompositionEventArgs)e.StagingItem.Input); } } // Assumes key is already a single unicode character ////// Critical - sets the userInitiated bit on a command, which is used /// for security purposes later. /// [SecurityCritical] private ProcessKeyResult ProcessKeyForSender(object sender, string key, bool existsElsewhere, bool userInitiated) { // This comes from OnKeyDown or OnText and though it is a single character it might not be uppercased. key = key.ToUpperInvariant(); IInputElement inputElementSender = sender as IInputElement; Listtargets = GetTargetsForSender(inputElementSender, key); return ProcessKey(targets, key, existsElsewhere, userInitiated); } // Assumes key is already a single unicode character AND is uppercased /// /// Critical - sets the userInitiated bit on a command, which is used /// for security purposes later. /// [SecurityCritical] private ProcessKeyResult ProcessKeyForScope(object scope, string key, bool existsElsewhere, bool userInitiated) { Listtargets = GetTargetsForScope(scope, key, null, AccessKeyInformation.Empty); return ProcessKey(targets, key, existsElsewhere, userInitiated); } /// /// Critical - Sets calls AccessKeyPressedEventArgs setting the userInitiated bit which is used /// for security purposes later. /// [SecurityCritical] private ProcessKeyResult ProcessKey(Listtargets, string key, bool existsElsewhere, bool userInitiated) { if (targets != null) { bool oneUIElement = true; UIElement invokeUIElement = null; bool lastWasFocused = false; int chosenIndex = 0; for (int i = 0; i < targets.Count; i++) { UIElement target = targets[i] as UIElement; Debug.Assert(target != null, "Targets should only be UIElements"); if (!target.IsEnabled) continue; if (invokeUIElement == null) { invokeUIElement = target; chosenIndex = i; } else { if (lastWasFocused) { invokeUIElement = target; chosenIndex = i; } oneUIElement = false; } // lastWasFocused = target.IsKeyboardFocused; } if (invokeUIElement != null) { AccessKeyEventArgs args = new AccessKeyEventArgs(key, !oneUIElement || existsElsewhere /* == isMultiple */,userInitiated); try { invokeUIElement.InvokeAccessKey(args); } finally { args.ClearUserInitiated(); } return (chosenIndex == targets.Count - 1) ? ProcessKeyResult.LastMatch : ProcessKeyResult.MoreMatches; } } return ProcessKeyResult.NoMatch; } /// /// Critical - Calls ProcessKeyForSender, setting the userInitiated /// bit, which is used for security purposes later. /// [SecurityCritical] private void OnText(TextCompositionEventArgs e) { // AccessKeyManager handles both text and system text. string text = e.Text; if ((text == null) || (text.Length == 0)) { text = e.SystemText; } if ((text != null) && (text.Length > 0)) { if (ProcessKeyForSender(e.OriginalSource, text, false /* existsElsewhere */,e.UserInitiated) != ProcessKeyResult.NoMatch) { e.Handled = true; } } } ////// Critical - Calls ProcessKeyForSender, setting the userInitiated /// bit, which is used for security purposes later. /// [SecurityCritical] private void OnKeyDown(KeyEventArgs e) { KeyboardDevice keyboard = (KeyboardDevice)e.Device; string text = null; switch (e.RealKey) { case Key.Enter : text = "\x000D"; break; case Key.Escape : text = "\x001B"; break; } if (text != null) { if (ProcessKeyForSender(e.OriginalSource, text, false /* existsElsewhere */,e.UserInitiated) != ProcessKeyResult.NoMatch) { e.Handled = true; } } } ////// Get the list of access key targets for the sender of the keyboard event. If sender is null, /// pretend key was pressed in the active window. /// /// /// ////// /// Critical: calls Critical member GetInfoForElement() and accesses Scope from AccessKeyInformation. /// TreatAsSafe: Does not pass the sender info (which may contain a PresentationSource) out. /// [SecurityCritical, SecurityTreatAsSafe] private ListGetTargetsForSender(IInputElement sender, string key) { // Find the scope for the sender -- will be matched against the possible targets' scopes AccessKeyInformation senderInfo = GetInfoForElement(sender, key); return GetTargetsForScope(senderInfo.Scope, key, sender, senderInfo); } /// /// Critical: calls CriticalGetActiveSource() /// TreatAsSafe: Does not pass the returned scope to any other method, nor is it /// returned from GetTargetsForScope. Also returned value is not critical. /// [SecurityCritical, SecurityTreatAsSafe] private ListGetTargetsForScope(object scope, string key, IInputElement sender, AccessKeyInformation senderInfo) { // null scope defaults to the active window if (scope == null) { scope = CriticalGetActiveSource(); // if there is no active scope then give up if (scope == null) { return null; } } //Scoping: // 1) When key is pressed, find matching AKs -> S // 3) find scope for keyevent.Source // 4) find scope for everything in S. throw away those that don't match. // 5) Final selection uses S. yay! // // List possibleElements; lock (_keyToElements) { possibleElements = CopyAndPurgeDead(_keyToElements[key] as ArrayList); } if (possibleElements == null) return null; List finalTargets = new List (1); // Go through all the possible elements, find the interesting candidates for (int i = 0; i < possibleElements.Count; i++) { IInputElement element = possibleElements[i]; if (element != sender) { if (IsTargetable(element)) { AccessKeyInformation elementInfo = GetInfoForElement(element, key); if (elementInfo.target == null) continue; if (scope == elementInfo.Scope) { finalTargets.Add(elementInfo.target); } } } else { // This is the same element that sent the event so it must be in the same scope. // Just add it to the final targets if (senderInfo.target != null) { finalTargets.Add(senderInfo.target); } } } return finalTargets; } /// /// Returns scope for the given element. /// /// /// ///Scope for the given element, null means the context global scope ////// Critical: calls GetSourceForElement(), CriticalGetActiveSource(), and returns AccessKeyInformation. /// [SecurityCritical] private AccessKeyInformation GetInfoForElement(IInputElement element, string key) { AccessKeyInformation info = new AccessKeyInformation(); if (element != null) { AccessKeyPressedEventArgs args = new AccessKeyPressedEventArgs(key); element.RaiseEvent(args); info.Scope = args.Scope; info.target = args.Target; if (info.Scope == null) { info.Scope = GetSourceForElement(element); } } else { info.Scope = CriticalGetActiveSource(); } return info; } ////// Critical: calls PresentationSource.CriticalFromVisual, and returns PresentationSource /// [SecurityCritical] private PresentationSource GetSourceForElement(IInputElement element) { PresentationSource source = null; DependencyObject elementDO = element as DependencyObject; // Use internal helpers to try to find the source of the element. // Because IInputElements can move around without notification we need to // look up the source every time. if (elementDO != null) { DependencyObject containingVisual = InputElement.GetContainingVisual(elementDO); if (containingVisual != null) { source = PresentationSource.CriticalFromVisual(containingVisual); } } // NOTE: source can be null but IsTargetable(element) == true if the // element is in an orphaned tree but the tree has not yet been garbage collected. return source; } ////// Critical: calls UnsafeNativeMethod GetActiveWindow(), and Critical method /// HwndSource.FromHwnd(). /// TreatAsSafe: Does not pass any parameters to GetActiveWindow(), and does /// not expose the return value, and HwndSource.FromHwnd will demand /// UIPermissionWindow.AllWindows. /// [SecurityCritical, SecurityTreatAsSafe] private PresentationSource GetActiveSource() { IntPtr hwnd = MS.Win32.UnsafeNativeMethods.GetActiveWindow(); if (hwnd != IntPtr.Zero) return HwndSource.FromHwnd(hwnd); return null; } ////// Critical: calls UnsafeNativeMethod GetActiveWindow() and HwndSource.CriticalFromHwnd() /// [SecurityCritical] private PresentationSource CriticalGetActiveSource() { IntPtr hwnd = MS.Win32.UnsafeNativeMethods.GetActiveWindow(); if (hwnd != IntPtr.Zero) return HwndSource.CriticalFromHwnd(hwnd); return null; } private bool IsTargetable(IInputElement element) { DependencyObject uielement = InputElement.GetContainingUIElement((DependencyObject)element); // For an element to be a valid target it must be visible and enabled if (uielement != null && IsVisible(uielement) && IsEnabled(uielement)) { return true; } return false; } private static bool IsVisible(DependencyObject element) { while (element != null) { Visibility visibility; UIElement uiElem = element as UIElement; UIElement3D uiElem3D = element as UIElement3D; if (uiElem != null) { visibility = uiElem.Visibility; } else { visibility = uiElem3D.Visibility; } if (visibility != Visibility.Visible) { return false; } element = UIElementHelper.GetUIParent(element); } return true; } // returns whether the given DO is enabled or not private static bool IsEnabled(DependencyObject element) { return ((bool)element.GetValue(UIElement.IsEnabledProperty)); } private struct AccessKeyInformation { ////// Critical: Scope may contain an PresentationSource which we /// would not want exposed. /// public object Scope { [SecurityCritical] get { return _scope; } set { _scope = value; } } public UIElement target; private static AccessKeyInformation _empty = new AccessKeyInformation(); public static AccessKeyInformation Empty { get { return _empty; } } private object _scope; } private static void PurgeDead(ArrayList elements, object elementToRemove) { for (int i = 0; i < elements.Count; ) { WeakReference weakReference = (WeakReference)elements[i]; object element = weakReference.Target; if (element == null || element == elementToRemove) { elements.RemoveAt(i); } else { i++; } } } ////// Takes an ArrayList of WeakReferences, removes the dead references and returns /// a generic List of IInputElements (strong references) /// private static ListCopyAndPurgeDead(ArrayList elements) { if (elements == null) { return null; } List copy = new List (elements.Count); for (int i = 0; i < elements.Count; ) { WeakReference weakReference = (WeakReference)elements[i]; object element = weakReference.Target; if (element == null) { elements.RemoveAt(i); } else { Debug.Assert(element is IInputElement, "Element in AccessKeyManager store was not of type IInputElement"); copy.Add((IInputElement)element); i++; } } return copy; } #endregion #region Private Properties ///////////////////////////////////////////////////////////////////////////////// // Overview: Algorithm to look up access key from the element for which it is a target. // // When the AccessKeyCharacter for an element is requested we see if there // is a corresponding AccessKeyElement stashed on the element. If there is, // raise the AccessKeyPressed event on it to see if that element is still the // target for it. If not, go through all registered accesskeys and get their // targets until we find the desired element. The "primary" access key character // is the first one we find. // // Note: The algorithm ends up being O(n) for each request for AccessKeyCharacter // because there is no mapping from AccessKeyElement to its "primary" character. // Maintaining this would require storing a hash from AccessKeyElement to character // or requiring that each element registered implement an interface or some other // kind of contract. Because we don't keep track of this or enforce this, to find // the "primary" character we must go through all registered pairs of // (character, element) to find the character -- O(n). // // This ends up being just fine, because in any given context there shouldn't be // so many elements registered that this cost is at all noticable. // ///////////////////////////////////////////////////////////////////////////////// /// /// The primary access key element for an element. This is stored as a WeakReference. /// private static readonly DependencyProperty AccessKeyElementProperty = DependencyProperty.RegisterAttached("AccessKeyElement", typeof(WeakReference), typeof(AccessKeyManager)); #endregion #region Private Methods for UIAutomation internal static string InternalGetAccessKeyCharacter(DependencyObject d) { return Current.GetAccessKeyCharacter(d); } private string GetAccessKeyCharacter(DependencyObject d) { // See what the local value for AccessKeyElement is first and start with that. WeakReference cachedElementWeakRef = (WeakReference)d.GetValue(AccessKeyElementProperty); IInputElement accessKeyElement = (cachedElementWeakRef != null) ? (IInputElement)cachedElementWeakRef.Target : null; if (accessKeyElement != null) { // First figure out if the target of accessKeyElement is still "d", then go find // the "primary" character for the accessKeyElement. AccessKeyPressedEventArgs accessKeyPressedEventArgs = new AccessKeyPressedEventArgs(); accessKeyElement.RaiseEvent(accessKeyPressedEventArgs); if (accessKeyPressedEventArgs.Target == d) { // Because there is no way to get at the access key element's character from the // element (there is no interface or anything) we have to go through all registered // access keys and see if this access key element is still registered and what its // "primary" character is. foreach (DictionaryEntry entry in Current._keyToElements) { ArrayList elements = (ArrayList)entry.Value; for (int i = 0; i < elements.Count; i++) { // If this element matches accessKeyElement, then return the current character WeakReference currentElementWeakRef = (WeakReference)elements[i]; if (currentElementWeakRef.Target == accessKeyElement) { return (string)entry.Key; } } } } } // There was no access key stored or it no longer matched. Clear out the cache and figure it out again. d.ClearValue(AccessKeyElementProperty); foreach (DictionaryEntry entry in Current._keyToElements) { ArrayList elements = (ArrayList)entry.Value; for (int i = 0; i < elements.Count; i++) { // Determine the target for this element. Cache the weak reference for the element on the target. WeakReference currentElementWeakRef = (WeakReference)elements[i]; IInputElement currentElement = (IInputElement)currentElementWeakRef.Target; if (currentElement != null) { AccessKeyPressedEventArgs accessKeyPressedEventArgs = new AccessKeyPressedEventArgs(); currentElement.RaiseEvent(accessKeyPressedEventArgs); // If the target was non-null, cache the access key element on the target. // if the target matches "d", return the current character. if (accessKeyPressedEventArgs.Target != null) { accessKeyPressedEventArgs.Target.SetValue(AccessKeyElementProperty, currentElementWeakRef); if (accessKeyPressedEventArgs.Target == d) { return (string)entry.Key; } } } } } return String.Empty; } #endregion #region Data // Map: string -> ArrayList of WeakReferences to IInputElements private Hashtable _keyToElements = new Hashtable(10); [ThreadStatic] private static AccessKeyManager _accessKeyManager; #endregion } ////// The delegate type for handling a FindScope event /// public delegate void AccessKeyPressedEventHandler(object sender, AccessKeyPressedEventArgs e); ////// The inputs to an AccessKeyPressedEventHandler /// public class AccessKeyPressedEventArgs : RoutedEventArgs { #region Constructors ////// The constructor for AccessKeyPressed event args /// public AccessKeyPressedEventArgs() { RoutedEvent = AccessKeyManager.AccessKeyPressedEvent; _key = null; } ////// Constructor for AccessKeyPressed event args /// /// public AccessKeyPressedEventArgs(string key) : this() { _key = key; } #endregion #region Public Properties ////// The scope for the element that raised this event. /// public object Scope { get { return _scope; } set { _scope = value; } } ////// Target element for the element that raised this event. /// ///public UIElement Target { get { return _target; } set { _target = value; } } /// /// Key that was pressed /// ///public string Key { get { return _key; } } #endregion #region Protected Methods /// /// /// The handler to invoke. /// The current object along the event's route. protected override void InvokeEventHandler(Delegate genericHandler, object genericTarget) { AccessKeyPressedEventHandler handler = (AccessKeyPressedEventHandler)genericHandler; handler(genericTarget, this); } #endregion #region Data private object _scope; private UIElement _target; private string _key; #endregion } ////// Information pertaining to when the access key associated with an element is pressed /// public class AccessKeyEventArgs : EventArgs { ////// /// ////// Critical - sets the userInitiated bit on a command, which is used /// for security purposes later. /// [SecurityCritical] internal AccessKeyEventArgs(string key, bool isMultiple, bool userInitiated) { _key = key; _isMultiple = isMultiple; _userInitiated = new SecurityCriticalDataForSet(userInitiated); } /// /// Critical - sets the userInitiated bit on a command, which is used /// for security purposes later. /// TreatAsSafe: Resets the user initiated bit /// [SecurityCritical,SecurityTreatAsSafe] internal void ClearUserInitiated() { _userInitiated.Value = false; } ////// The key that was pressed which invoked this access key /// ///public string Key { get { return _key; } } /// /// Were there other elements which are also invoked by this key /// ///public bool IsMultiple { get { return _isMultiple; } } internal bool UserInitiated { get { return _userInitiated.Value; } } private string _key; private bool _isMultiple; /// /// Critical - This is critical for set, setting this bool can cause the spoofing of paste /// private SecurityCriticalDataForSet_userInitiated; } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ActiveXContainer.cs
- ChooseAction.cs
- figurelength.cs
- HostingPreferredMapPath.cs
- OdbcConnectionStringbuilder.cs
- SmtpFailedRecipientsException.cs
- SoapAttributes.cs
- ObjectDataSource.cs
- PackagePart.cs
- CurrentTimeZone.cs
- CqlLexerHelpers.cs
- Thread.cs
- SrgsDocumentParser.cs
- OuterGlowBitmapEffect.cs
- AggregateNode.cs
- CacheMemory.cs
- SystemInfo.cs
- TypographyProperties.cs
- ToolStripContentPanel.cs
- ViewCellSlot.cs
- ScriptMethodAttribute.cs
- SqlClientMetaDataCollectionNames.cs
- XpsFontSerializationService.cs
- TabControlAutomationPeer.cs
- Effect.cs
- InfoCardTraceRecord.cs
- HttpCookiesSection.cs
- PictureBox.cs
- GeometryGroup.cs
- DateTimeFormatInfo.cs
- MdiWindowListItemConverter.cs
- HideDisabledControlAdapter.cs
- LiteralControl.cs
- XmlWrappingReader.cs
- ProfessionalColors.cs
- FileLoadException.cs
- BitmapImage.cs
- BindingContext.cs
- GC.cs
- ToolStripOverflowButton.cs
- MembershipPasswordException.cs
- ViewBase.cs
- Vars.cs
- TrackPointCollection.cs
- BinaryMessageEncoder.cs
- MetafileHeader.cs
- NotCondition.cs
- Mappings.cs
- DataGridViewElement.cs
- MasterPageCodeDomTreeGenerator.cs
- NativeRecognizer.cs
- DesignerActionUIService.cs
- SafeFileMapViewHandle.cs
- WorkflowRuntimeServiceElementCollection.cs
- Thread.cs
- RuleSettingsCollection.cs
- DomainLiteralReader.cs
- DataGridViewTopRowAccessibleObject.cs
- RegexCode.cs
- PrefixHandle.cs
- Vector3dCollection.cs
- BaseProcessor.cs
- CrossSiteScriptingValidation.cs
- CoreSwitches.cs
- NetCodeGroup.cs
- InertiaExpansionBehavior.cs
- TextOnlyOutput.cs
- GridViewRowEventArgs.cs
- CollectionViewSource.cs
- TextWriterTraceListener.cs
- OutputCacheProfile.cs
- OracleParameter.cs
- DesignerActionItemCollection.cs
- UInt32Storage.cs
- DataGridViewRowsRemovedEventArgs.cs
- ComponentDispatcherThread.cs
- DefaultTraceListener.cs
- SHA512Cng.cs
- Pen.cs
- CacheAxisQuery.cs
- LinkedResourceCollection.cs
- OdbcCommand.cs
- ObjectHelper.cs
- HttpProfileBase.cs
- DeviceContext2.cs
- ComponentResourceKeyConverter.cs
- KoreanCalendar.cs
- ControlPersister.cs
- CodeDomConfigurationHandler.cs
- DrawingContextWalker.cs
- InstallerTypeAttribute.cs
- CheckBoxDesigner.cs
- Select.cs
- Oci.cs
- TypeLoadException.cs
- XmlResolver.cs
- SatelliteContractVersionAttribute.cs
- RtfControlWordInfo.cs
- ProfileManager.cs
- DictionaryEntry.cs