File: System\Windows\EventRoute.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using MS.Utility;
 
using SR=MS.Internal.PresentationCore.SR;
using MS.Internal;
using MS.Internal.KnownBoxes;
 
namespace System.Windows
{
    /// <summary>
    ///     Container for the route to be followed 
    ///     by a RoutedEvent when raised
    /// </summary>
    /// <remarks>
    ///     EventRoute constitues <para/>
    ///     a non-null <see cref="RoutedEvent"/>
    ///     and <para/>
    ///     an ordered list of (target object, handler list)
    ///     pairs <para/>
    ///     <para/>
    ///
    ///     It facilitates adding new entries to this list
    ///     and also allows for the handlers in the list 
    ///     to be invoked
    /// </remarks>
    public sealed class EventRoute
    {
        #region Construction
 
        /// <summary>
        ///     Constructor for <see cref="EventRoute"/> given
        ///     the associated <see cref="RoutedEvent"/>
        /// </summary>
        /// <param name="routedEvent">
        ///     Non-null <see cref="RoutedEvent"/> to be associated with 
        ///     this <see cref="EventRoute"/>
        /// </param>
        public EventRoute(RoutedEvent routedEvent)
        {
            ArgumentNullException.ThrowIfNull(routedEvent);
 
            _routedEvent = routedEvent;
 
            // Changed the initialization size to 16 
            // to achieve performance gain based 
            // on standard app behavior
            _routeItemList = new FrugalStructList<RouteItem>(16);
            _sourceItemList = new FrugalStructList<SourceItem>(16);
        }
 
        #endregion Construction
 
        #region External API
 
        /// <summary>
        ///     Adds this handler for the 
        ///     specified target to the route
        /// </summary>
        /// <remarks>
        ///     NOTE: It is not an error to add a 
        ///     handler for a particular target instance 
        ///     twice (handler will simply be called twice). 
        /// </remarks>
        /// <param name="target">
        ///     Target object whose handler is to be 
        ///     added to the route
        /// </param>
        /// <param name="handler">
        ///     Handler to be added to the route
        /// </param>
        /// <param name="handledEventsToo">
        ///     Flag indicating whether or not the listener wants to 
        ///     hear about events that have already been handled
        /// </param>
        public void Add(object target, Delegate handler, bool handledEventsToo)
        {
            ArgumentNullException.ThrowIfNull(target);
 
            ArgumentNullException.ThrowIfNull(handler);
 
            RouteItem routeItem = new RouteItem(target, new RoutedEventHandlerInfo(handler, handledEventsToo));
 
            _routeItemList.Add(routeItem);
        }
 
        /// <summary>
        ///     Invokes all the handlers that have been 
        ///     added to the route
        /// </summary>
        /// <remarks>
        ///     NOTE: If the <see cref="RoutingStrategy"/> 
        ///     of the associated <see cref="RoutedEvent"/> 
        ///     is <see cref="RoutingStrategy.Bubble"/>
        ///     the last handlers added are the 
        ///     last ones invoked <para/>
        ///     However if the <see cref="RoutingStrategy"/> 
        ///     of the associated <see cref="RoutedEvent"/> 
        ///     is <see cref="RoutingStrategy.Tunnel"/>, 
        ///     the last handlers added are the 
        ///     first ones invoked 
        /// </remarks>
        /// <param name="source">
        ///     <see cref="RoutedEventArgs.Source"/> 
        ///     that raised the RoutedEvent
        /// </param>
        /// <param name="args">
        ///     <see cref="RoutedEventArgs"/> that carry
        ///     all the details specific to this RoutedEvent
        /// </param>
        internal void InvokeHandlers(object source, RoutedEventArgs args)
        {
            InvokeHandlersImpl(source, args, false);
        }
 
        internal void ReInvokeHandlers(object source, RoutedEventArgs args)
        {
            InvokeHandlersImpl(source, args, true);
        }
 
        private void InvokeHandlersImpl(object source, RoutedEventArgs args, bool reRaised)
        {
            ArgumentNullException.ThrowIfNull(source);
 
            ArgumentNullException.ThrowIfNull(args);
 
            if (args.Source == null)
            {
                throw new ArgumentException(SR.SourceNotSet); 
            }
 
            if (args.RoutedEvent != _routedEvent)
            {
                throw new ArgumentException(SR.Mismatched_RoutedEvent);
            }
 
            // Check RoutingStrategy to know the order of invocation
            if (args.RoutedEvent.RoutingStrategy == RoutingStrategy.Bubble ||
                args.RoutedEvent.RoutingStrategy == RoutingStrategy.Direct)
            {
                int endSourceChangeIndex = 0;
                
                // If the RoutingStrategy of the associated is 
                // Bubble the handlers for the last target 
                // added are the last ones invoked
                // Invoke class listeners
                for (int i=0; i<_routeItemList.Count; i++)
                {
                    // Query for new source only if we are 
                    // past the range of the previous source change
                    if (i >= endSourceChangeIndex)
                    {
                        // Get the source at this point in the bubble route and also 
                        // the index at which this source change seizes to apply
                        object newSource = GetBubbleSource(i, out endSourceChangeIndex);
                        
                        // Set appropriate source
                        // The first call to setsource seems redundant 
                        // but is necessary because the source could have 
                        // been modified during BuildRoute call and hence 
                        // may need to be reset to the original source.
                        // Note: we skip this logic if reRaised is set, which is done when we're trying
                        //       to convert MouseDown/Up into a MouseLeft/RightButtonDown/Up
                        if(!reRaised)
                        {
                            if (newSource == null)
                                args.Source=source;
                            else
                                args.Source=newSource;
                        }
                    }
                    
                    // Invoke listeners
 
                    var traceRoutedEventIsEnabled = TraceRoutedEvent.IsEnabled;
                    if ( traceRoutedEventIsEnabled )
                    {
                        _traceArguments ??= new object[3];
                        _traceArguments[0] = _routeItemList[i].Target;
                        _traceArguments[1] = args;
                        _traceArguments[2] = BooleanBoxes.Box(args.Handled);
                        TraceRoutedEvent.Trace(
                            TraceEventType.Start,
                            TraceRoutedEvent.InvokeHandlers,
                            _traceArguments);
                    }
                    
                    _routeItemList[i].InvokeHandler(args);
 
                    if( traceRoutedEventIsEnabled )
                    {
                        _traceArguments[2] = BooleanBoxes.Box(args.Handled);
                        TraceRoutedEvent.Trace(
                            TraceEventType.Stop,
                            TraceRoutedEvent.InvokeHandlers,
                            _traceArguments);
                    }
 
 
                }
            }
            else
            {
                int startSourceChangeIndex = _routeItemList.Count;
                int endTargetIndex =_routeItemList.Count-1;
                int startTargetIndex;
                
                // If the RoutingStrategy of the associated is 
                // Tunnel the handlers for the last target 
                // added are the first ones invoked
                while (endTargetIndex >= 0)
                {
                    // For tunnel events we need to invoke handlers for the last target first. 
                    // However the handlers for that individual target must be fired in the right order. 
                    // Eg. Class Handlers must be fired before Instance Handlers.
                    object currTarget = _routeItemList[endTargetIndex].Target;
                    for (startTargetIndex=endTargetIndex; startTargetIndex>=0; startTargetIndex--)
                    {
                        if (_routeItemList[startTargetIndex].Target != currTarget)
                        {
                            break;
                        }
                    }
                    
                    for (int i=startTargetIndex+1; i<=endTargetIndex; i++)
                    {
                        // Query for new source only if we are 
                        // past the range of the previous source change
                        if (i < startSourceChangeIndex)
                        {
                            // Get the source at this point in the tunnel route and also 
                            // the index at which this source change seizes to apply
                            object newSource = GetTunnelSource(i, out startSourceChangeIndex);
                            
                            // Set appropriate source
                            // The first call to setsource seems redundant 
                            // but is necessary because the source could have 
                            // been modified during BuildRoute call and hence 
                            // may need to be reset to the original source.
                            if (newSource == null)
                                args.Source=source;                            
                            else
                                args.Source=newSource;
                        }
                        
                        
                        var traceRoutedEventIsEnabled = TraceRoutedEvent.IsEnabled;
                        if ( traceRoutedEventIsEnabled )
                        {
                            _traceArguments ??= new object[3];
                            _traceArguments[0] = _routeItemList[i].Target;
                            _traceArguments[1] = args;
                            _traceArguments[2] = BooleanBoxes.Box(args.Handled);
                            TraceRoutedEvent.Trace(
                                TraceEventType.Start,
                                TraceRoutedEvent.InvokeHandlers,
                                _traceArguments);
                        }
 
                        // Invoke listeners
                        _routeItemList[i].InvokeHandler(args);
 
                        if (traceRoutedEventIsEnabled)
                        {
                            _traceArguments[2] = BooleanBoxes.Box(args.Handled);
                            TraceRoutedEvent.Trace(
                                TraceEventType.Stop,
                                TraceRoutedEvent.InvokeHandlers,
                                _traceArguments);
                        }
 
                    }
 
                    endTargetIndex = startTargetIndex;
                }
            }            
        }
 
        /// <summary>
        ///     Pushes the given node at the top of the stack of branches.
        /// </summary>
        /// <remarks>
        ///     If a node in the tree has different visual and logical,
        ///     FrameworkElement will store the node on this stack of
        ///     branches.  If the route ever returns to the same logical
        ///     tree, the event source will be restored.
        ///     <para/>
        ///     NOTE: This method needs to be public because it is used
        ///     by FrameworkElement.
        /// </remarks>
        /// <param name="node">
        ///     The node where the visual parent is different from the logical
        ///     parent.
        /// </param>
        /// <param name="source">
        ///     The source that is currently being used, and which should be
        ///     restored when this branch is popped off the stack.
        /// </param>
        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)]
        public void PushBranchNode(object node, object source)
        {
            BranchNode branchNode = new BranchNode();
            branchNode.Node = node;
            branchNode.Source = source;
            
            (_branchNodeStack ??= new Stack<BranchNode>(1)).Push(branchNode);
        }
 
        /// <summary>
        ///     Pops the given node from the top of the stack of branches.
        /// </summary>
        /// <remarks>
        ///     If a node in the tree has different visual and logical,
        ///     FrameworkElement will store the node on this stack of
        ///     branches.  If the route ever returns to the same logical
        ///     tree, the event source will be restored.
        ///     <para/>
        ///     NOTE: This method needs to be public because it is used
        ///     by FrameworkElement.
        /// </remarks>
        /// <returns>
        ///     The node where the visual parent was different from the
        ///     logical parent.
        /// </returns>
        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)]
        public object PopBranchNode()
        {
            if (_branchNodeStack is { Count: > 0 } stack)
            {
                return stack.Pop().Node;
            }
 
            return null;
        }
 
        /// <summary>
        ///     Peeks the given node from the top of the stack of branches.
        /// </summary>
        /// <remarks>
        ///     If a node in the tree has different visual and logical,
        ///     FrameworkElement will store the node on this stack of
        ///     branches.  If the route ever returns to the same logical
        ///     tree, the event source will be restored.
        ///     <para/>
        ///     NOTE: This method needs to be public because it is used
        ///     by FrameworkElement.
        /// </remarks>
        /// <returns>
        ///     The node where the visual parent was different from the
        ///     logical parent.
        /// </returns>
        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)]
        public object PeekBranchNode()
        {
            if (_branchNodeStack is { Count: > 0 } stack)
            {
                return stack.Peek().Node;
            }
 
            return null;
        }
 
        /// <summary>
        ///     Peeks the given source from the top of the stack of branches.
        /// </summary>
        /// <remarks>
        ///     If a node in the tree has different visual and logical,
        ///     FrameworkElement will store the node on this stack of
        ///     branches.  If the route ever returns to the same logical
        ///     tree, the event source will be restored.
        ///     <para/>
        ///     NOTE: This method needs to be public because it is used
        ///     by FrameworkElement.
        /// </remarks>
        /// <returns>
        ///     The source that was stored along with the node where the
        ///     visual parent was different from the logical parent.
        /// </returns>
        [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)]
        public object PeekBranchSource()
        {
            if (_branchNodeStack is { Count: > 0 } stack)
            {
                return stack.Peek().Source;
            }
 
            return null;
        }
        
        #endregion External API
        
        #region Operations
 
        // Return associated RoutedEvent
        internal RoutedEvent RoutedEvent
        {
            get {return _routedEvent;}
            set { _routedEvent = value; }
        }
 
        // A BranchNode indicates a point in the tree where the logical and
        // visual structure might diverge.  When building a route, we store
        // this branch node for every logical link we find.  Along with the
        // node where the possible divergence occurred, we store the source
        // that the event should use.  This is so that the source of an
        // event will always be in the logical tree of the element handling
        // the event.
        private struct BranchNode
        {
            public object Node;
            public object Source;
        }
 
        // Add the given source to the source item list
        // indicating what the source will be this point 
        // onwards in the route
        internal void AddSource(object source)
        {
            int startIndex = _routeItemList.Count;
            _sourceItemList.Add(new SourceItem(startIndex, source));
        }
 
        // Determine what the RoutedEventArgs.Source should be, at this
        // point in the bubble. Also the endIndex output parameter tells 
        // you the exact index of the handlersList at which this source 
        // change ceases to apply
        private object GetBubbleSource(int index, out int endIndex)
        {
            // If the Source never changes during the route execution,
            // then we're done (just return null).
            if (_sourceItemList.Count == 0)
            {
                endIndex = _routeItemList.Count;
                return null;
            }
            
            // Similarly, if we're not to the point of the route of the first Source
            // change, simply return null.
            if (index < _sourceItemList[0].StartIndex)
            {
                endIndex = _sourceItemList[0].StartIndex;
                return null;
            }
            
            // See if we should be using one of the intermediate
            // sources
            for (int i=0; i<_sourceItemList.Count -1; i++)
            {
                if (index >= _sourceItemList[i].StartIndex &&
                    index < _sourceItemList[i+1].StartIndex)
                {
                    endIndex = _sourceItemList[i+1].StartIndex;
                    return _sourceItemList[i].Source;
                }
            }
 
            // If we get here, we're on the last one,
            // so return that.            
            endIndex = _routeItemList.Count;
            return _sourceItemList[_sourceItemList.Count -1].Source;
        }
 
        // Determine what the RoutedEventArgs.Source should be, at this
        // point in the tunnel. Also the startIndex output parameter tells 
        // you the exact index of the handlersList at which this source 
        // change starts to apply
        private object GetTunnelSource(int index, out int startIndex)
        {
            // If the Source never changes during the route execution,
            // then we're done (just return null).
            if (_sourceItemList.Count == 0)
            {
                startIndex = 0;
                return null;
            }
            
            // Similarly, if we're past the point of the route of the first Source
            // change, simply return null.
            if (index < _sourceItemList[0].StartIndex)
            {
                startIndex = 0;
                return null;
            }
            
            // See if we should be using one of the intermediate
            // sources
            for (int i=0; i<_sourceItemList.Count -1; i++)
            {
                if (index >= _sourceItemList[i].StartIndex &&
                    index < _sourceItemList[i+1].StartIndex)
                {
                    startIndex = _sourceItemList[i].StartIndex;
                    return _sourceItemList[i].Source;
                }
            }
        
            // If we get here, we're on the last one, so return that.            
            startIndex = _sourceItemList[_sourceItemList.Count -1].StartIndex;
            return _sourceItemList[_sourceItemList.Count -1].Source;
        }
 
        /// <summary>
        ///     Cleanup all the references within the data
        /// </summary>
        internal void Clear()
        {
            _routedEvent = null;
            
            _routeItemList.Clear();
 
            if (_branchNodeStack != null)
            {
                _branchNodeStack.Clear();
            }
 
            _sourceItemList.Clear();
        }
        
        #endregion Operations
        
        #region Data
 
        private RoutedEvent _routedEvent;
 
        // Stores the routed event handlers to be 
        // invoked for the associated RoutedEvent
        private FrugalStructList<RouteItem> _routeItemList;
 
        // Stores the branching nodes in the route
        // that need to be backtracked while 
        // augmenting the route
        private Stack<BranchNode> _branchNodeStack;
 
        // Stores Source Items for separated trees
        private FrugalStructList<SourceItem> _sourceItemList;
 
        // Stores arguments that are passed to TraceRoutedEvent.Trace (to reduce allocations)
        private object[] _traceArguments;
 
        #endregion Data
    }
}