File: System\Windows\LayoutManager.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.Windows.Threading;
using System.Windows.Media;
using System.Windows.Automation.Peers;
using MS.Utility;
 
namespace System.Windows
{
    /// <summary>
    /// Top-level ContextLayoutManager object. Manages the layout update and layout dirty state.
    /// </summary>
    internal sealed class ContextLayoutManager : DispatcherObject
    {
        internal ContextLayoutManager()
        {
            _shutdownHandler = new EventHandler(this.OnDispatcherShutdown);
            Dispatcher.ShutdownFinished += _shutdownHandler;
        }
 
        void OnDispatcherShutdown(object sender, EventArgs e)
        {
            if(_shutdownHandler != null)
                Dispatcher.ShutdownFinished -= _shutdownHandler;
 
            _shutdownHandler = null;
            _layoutEvents = null;
            _measureQueue = null;
            _arrangeQueue = null;
            _sizeChangedChain = null;
            _isDead = true;
        }
 
 
        /// <summary>
        /// The way to obtain ContextLayoutManager associated with particular Dispatcher.
        /// </summary>
        /// <param name="dispatcher">A dispatcher for which ContextLayoutManager is queried.
        /// There is only one ContextLayoutManager associuated with all elements in a single context</param>
        /// <returns>ContextLayoutManager</returns>
        internal static ContextLayoutManager From(Dispatcher dispatcher)
        {
            ContextLayoutManager lm = dispatcher.Reserved3 as ContextLayoutManager;
            if(lm == null)
            {
                if(Dispatcher.CurrentDispatcher != dispatcher)
                {
                    throw new InvalidOperationException();
                }
 
                lm = new ContextLayoutManager();
                dispatcher.Reserved3 = lm;
            }
            return lm;
        }
 
        private void setForceLayout(UIElement e)
        {
            _forceLayoutElement = e;
        }
 
        private void markTreeDirty(UIElement e)
        {
            //walk up until we are the topmost UIElement in the tree.
            while(true)
            {
                UIElement p = e.GetUIParentNo3DTraversal() as UIElement;
                if(p == null) break;
                e = p;
            }
 
            markTreeDirtyHelper(e);
            MeasureQueue.Add(e);
            ArrangeQueue.Add(e);
        }
 
        private void markTreeDirtyHelper(Visual v)
        {
            //now walk down and mark all UIElements dirty
            if(v != null)
            {
                if(v.CheckFlagsAnd(VisualFlags.IsUIElement))
                {
                    UIElement uie = ((UIElement)v);
                    uie.InvalidateMeasureInternal();
                    uie.InvalidateArrangeInternal();
                }
 
                //walk children doing the same, don't stop if they are already dirty since there can
                //be insulated dirty islands below
                int cnt = v.InternalVisualChildrenCount;
 
                for(int i=0; i<cnt; i++)
                {
                    Visual child = v.InternalGetVisualChild(i);
                    if (child != null) markTreeDirtyHelper(child);
                }
            }
        }
 
        // posts a layout update
        private void NeedsRecalc()
        {
            if(!_layoutRequestPosted && !_isUpdating)
            {
                MediaContext.From(Dispatcher).BeginInvokeOnRender(_updateCallback, this);
                _layoutRequestPosted = true;
            }
        }
 
        private static object UpdateLayoutBackground(object arg)
        {
            ((ContextLayoutManager)arg).NeedsRecalc();
            return null;
        }
 
        private bool hasDirtiness
        {
            get
            {
                return (!MeasureQueue.IsEmpty) || (!ArrangeQueue.IsEmpty);
            }
        }
 
        internal void EnterMeasure()
        {
            Dispatcher._disableProcessingCount++;
            _lastExceptionElement = null;
            _measuresOnStack++;
            if(_measuresOnStack > s_LayoutRecursionLimit)
                throw new InvalidOperationException(SR.Format(SR.LayoutManager_DeepRecursion, s_LayoutRecursionLimit));
 
            _firePostLayoutEvents = true;
        }
 
        internal void ExitMeasure()
        {
            _measuresOnStack--;
            Dispatcher._disableProcessingCount--;
        }
 
        internal void EnterArrange()
        {
            Dispatcher._disableProcessingCount++;
            _lastExceptionElement = null;
            _arrangesOnStack++;
            if(_arrangesOnStack > s_LayoutRecursionLimit)
                throw new InvalidOperationException(SR.Format(SR.LayoutManager_DeepRecursion, s_LayoutRecursionLimit));
 
            _firePostLayoutEvents = true;
        }
 
        internal void ExitArrange()
        {
            _arrangesOnStack--;
            Dispatcher._disableProcessingCount--;
        }
 
        /// <summary>
        /// Tells ContextLayoutManager to finalize possibly async update.
        /// Used before accessing services off Visual.
        /// </summary>
        internal void UpdateLayout()
        {
            VerifyAccess();
 
            //make UpdateLayout to be a NOP if called during UpdateLayout.
            if (   _isInUpdateLayout
                || _measuresOnStack > 0
                || _arrangesOnStack > 0
                || _isDead) return;
 
#if DEBUG_CLR_MEM
            bool clrTracingEnabled = false;
 
            // Start over with the Measure and arrange counters for this layout pass
            int measureCLRPass = 0;
            int arrangeCLRPass = 0;
 
            if (CLRProfilerControl.ProcessIsUnderCLRProfiler)
            {
                clrTracingEnabled = true;
                if (CLRProfilerControl.CLRLoggingLevel >= CLRProfilerControl.CLRLogState.Performance)
                {
                    ++_layoutCLRPass;
                    CLRProfilerControl.CLRLogWriteLine("Begin_Layout_{0}", _layoutCLRPass);
                }
            }
#endif // DEBUG_CLR_MEM
 
            bool etwTracingEnabled = false;
            long perfElementID = 0;
            const EventTrace.Keyword etwKeywords = EventTrace.Keyword.KeywordLayout | EventTrace.Keyword.KeywordPerf;
            if (!_isUpdating && EventTrace.IsEnabled(etwKeywords, EventTrace.Level.Info))
            {
                etwTracingEnabled = true;
                perfElementID = PerfService.GetPerfElementID(this);
                EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientLayoutBegin, etwKeywords, EventTrace.Level.Info,
                        perfElementID, EventTrace.LayoutSource.LayoutManager);
            }
 
            int cnt = 0;
            bool gotException = true;
            UIElement currentElement = null;
 
            try
            {
                invalidateTreeIfRecovering();
 
 
                while(hasDirtiness || _firePostLayoutEvents)
                {
                    if(++cnt > 153)
                    {
                        //loop detected. Lets go over to background to let input/user to correct the situation.
                        //most frequently, we get such a loop as a result of input detecting a mouse in the "bad spot"
                        //and some event handler oscillating a layout-affecting property depending on hittest result
                        //of the mouse. Going over to background will not break the loopp but will allow user to
                        //move the mouse so that it goes out of the "bad spot".
                        Dispatcher.BeginInvoke(DispatcherPriority.Background, _updateLayoutBackground, this);
                        currentElement = null;
                        gotException = false;
                        if (etwTracingEnabled)
                        {
                            EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientLayoutAbort, etwKeywords, EventTrace.Level.Info, 0, cnt);
                        }
                        return;
                    }
 
 
                    //this flag stops posting update requests to MediaContext - we are already in one
                    //note that _isInUpdateLayout is close but different - _isInUpdateLayout is reset
                    //before firing LayoutUpdated so that event handlers could call UpdateLayout but
                    //still could not cause posting of MediaContext work item. Posting MediaContext workitem
                    //causes infinite loop in MediaContext.
                    _isUpdating = true;
                    _isInUpdateLayout = true;
 
#if DEBUG_CLR_MEM
                    if (clrTracingEnabled && (CLRProfilerControl.CLRLoggingLevel >= CLRProfilerControl.CLRLogState.Verbose))
                    {
                        ++measureCLRPass;
                        CLRProfilerControl.CLRLogWriteLine("Begin_Measure_{0}_{1}", _layoutCLRPass, measureCLRPass);
                    }
#endif // DEBUG_CLR_MEM
 
                    if (etwTracingEnabled)
                    {
                        EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientMeasureBegin, etwKeywords, EventTrace.Level.Info, perfElementID);
                    }
 
                    // Disable processing of the queue during blocking operations to prevent unrelated reentrancy.
                    using(Dispatcher.DisableProcessing())
                    {
                        //loop for Measure
                        //We limit the number of loops here by time - normally, all layout
                        //calculations should be done by this time, this limit is here for
                        //emergency, "infinite loop" scenarios - yielding in this case will
                        //provide user with ability to continue to interact with the app, even though
                        //it will be sluggish. If we don't yield here, the loop is goign to be a deadly one
                        //and it will be impossible to save results or even close the window.
                        int loopCounter = 0;
                        DateTime loopStartTime = new DateTime(0);
                        while(true)
                        {
                            if(++loopCounter > 153)
                            {
                                loopCounter = 0;
                                //first bunch of iterations is free, then we start count time
                                //this way, we don't call DateTime.Now in most layout updates
                                if(loopStartTime.Ticks == 0)
                                {
                                    loopStartTime = DateTime.UtcNow;
                                }
                                else
                                {
                                    TimeSpan loopDuration = (DateTime.UtcNow - loopStartTime);
                                    if(loopDuration.Milliseconds > 153*2) // 153*2 = magic*science
                                    {
                                        //loop detected. Lets go over to background to let input work.
                                        Dispatcher.BeginInvoke(DispatcherPriority.Background, _updateLayoutBackground, this);
                                        currentElement = null;
                                        gotException = false;
                                        if (etwTracingEnabled)
                                        {
                                            EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientMeasureAbort, etwKeywords, EventTrace.Level.Info,
                                                   loopDuration.Milliseconds, loopCounter);
                                        }
                                        return;
                                    }
                                }
                            }
 
                            currentElement = MeasureQueue.GetTopMost();
 
                            if(currentElement == null) break; //exit if no more Measure candidates
                            
                            currentElement.Measure(currentElement.PreviousConstraint);
							//not clear why this is needed, remove for now
							//if the parent was just computed, the chidlren should be clean. If they are not clean and in the queue
							//that means that there is cross-tree dependency and they most likely shodul be updated by themselves.
							//                            MeasureQueue.RemoveOrphans(currentElement);
                        }
 
                        if (etwTracingEnabled)
                        {
                            EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientMeasureEnd, etwKeywords, EventTrace.Level.Info, loopCounter);
                            EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientArrangeBegin, etwKeywords, EventTrace.Level.Info, perfElementID);
                        }
 
 
#if DEBUG_CLR_MEM
                        if (clrTracingEnabled && (CLRProfilerControl.CLRLoggingLevel >= CLRProfilerControl.CLRLogState.Verbose))
                        {
                            CLRProfilerControl.CLRLogWriteLine("End_Measure_{0}_{1}", _layoutCLRPass, measureCLRPass);
                            ++arrangeCLRPass;
                            CLRProfilerControl.CLRLogWriteLine("Begin_Arrange_{0}_{1}", _layoutCLRPass, arrangeCLRPass);
                        }
#endif // DEBUG_CLR_MEM
 
                        //loop for Arrange
                        //if Arrange dirtied the tree go clean it again
 
                        //We limit the number of loops here by time - normally, all layout
                        //calculations should be done by this time, this limit is here for
                        //emergency, "infinite loop" scenarios - yielding in this case will
                        //provide user with ability to continue to interact with the app, even though
                        //it will be sluggish. If we don't yield here, the loop is goign to be a deadly one
                        //and it will be impossible to save results or even close the window.
                        loopCounter = 0;
                        loopStartTime = new DateTime(0);
                        while(MeasureQueue.IsEmpty)
                        {
                            if(++loopCounter > 153)
                            {
                                loopCounter = 0;
                                //first bunch of iterations is free, then we start count time
                                //this way, we don't call DateTime.Now in most layout updates
                                if(loopStartTime.Ticks == 0)
                                {
                                    loopStartTime = DateTime.UtcNow;
                                }
                                else
                                {
                                    TimeSpan loopDuration = (DateTime.UtcNow - loopStartTime);
                                    if(loopDuration.Milliseconds > 153*2) // 153*2 = magic*science
                                    {
                                        //loop detected. Lets go over to background to let input work.
                                        Dispatcher.BeginInvoke(DispatcherPriority.Background, _updateLayoutBackground, this);
                                        currentElement = null;
                                        gotException = false;
                                        if (etwTracingEnabled)
                                        {
                                            EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientArrangeAbort, etwKeywords, EventTrace.Level.Info,
                                                   loopDuration.Milliseconds, loopCounter);
                                        }
                                        return;
                                    }
                                }
                            }
 
                            currentElement = ArrangeQueue.GetTopMost();
 
                            if(currentElement == null) break; //exit if no more Measure candidates
 
                            Rect finalRect = getProperArrangeRect(currentElement);
 
                            currentElement.Arrange(finalRect);
							//not clear why this is needed, remove for now
							//if the parent was just computed, the chidlren should be clean. If they are not clean and in the queue
							//that means that there is cross-tree dependency and they most likely shodul be updated by themselves.
							//                            ArrangeQueue.RemoveOrphans(currentElement);
                        }
 
                        if (etwTracingEnabled)
                        {
                            EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientArrangeEnd, etwKeywords, EventTrace.Level.Info, loopCounter);
                        }
 
#if DEBUG_CLR_MEM
                        if (clrTracingEnabled && (CLRProfilerControl.CLRLoggingLevel >= CLRProfilerControl.CLRLogState.Verbose))
                        {
                            CLRProfilerControl.CLRLogWriteLine("End_Arrange_{0}_{1}", _layoutCLRPass, arrangeCLRPass);
                        }
#endif // DEBUG_CLR_MEM
 
                        //if Arrange dirtied the tree go clean it again
                        //it is not neccesary to check ArrangeQueue sicnce we just exited from Arrange loop
                        if(!MeasureQueue.IsEmpty) continue;
 
                        //let LayoutUpdated handlers to call UpdateLayout
                        //note that it means we can get reentrancy into UpdateLayout past this point,
                        //if any of event handlers call UpdateLayout sync. Need to protect from reentrancy
                        //in the firing methods below.
                        _isInUpdateLayout = false;
}
 
                    fireSizeChangedEvents();
                    if(hasDirtiness) continue;
                    fireLayoutUpdateEvent();
                    if(hasDirtiness) continue;
                    fireAutomationEvents();
                    if(hasDirtiness) continue;
                    fireSizeChangedEvents(); // if nothing is dirty, one last chance for any size changes to announce.
                }
 
                currentElement = null;
                gotException = false;
            }
            finally
            {
                _isUpdating = false;
                _layoutRequestPosted = false;
                _isInUpdateLayout = false;
 
                if(gotException)
                {
                    if (etwTracingEnabled)
                    {
                        EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientLayoutException, etwKeywords, EventTrace.Level.Info, PerfService.GetPerfElementID(currentElement));
                    }
 
                    //set indicator
                    _gotException = true;
                    _forceLayoutElement = currentElement;
 
                    //make attempt to request the subsequent layout calc
                    //some exception handler schemas use Idle priorities to
                    //wait until dust settles. Then they correct the issue noted in the exception handler.
                    //We don't want to attempt to re-do the operation on the priority higher then that.
                    Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, _updateLayoutBackground, this);
                }
            }
 
            MS.Internal.Text.TextInterface.Font.ResetFontFaceCache();
            MS.Internal.FontCache.BufferCache.Reset();
 
            if (etwTracingEnabled)
            {
                EventTrace.EventProvider.TraceEvent(EventTrace.Event.WClientLayoutEnd, etwKeywords, EventTrace.Level.Info);
            }
 
#if DEBUG_CLR_MEM
            if (clrTracingEnabled && (CLRProfilerControl.CLRLoggingLevel >= CLRProfilerControl.CLRLogState.Performance))
            {
                CLRProfilerControl.CLRLogWriteLine("End_Layout_{0}", _layoutCLRPass);
            }
#endif // DEBUG_CLR_MEM
        }
 
        private Rect getProperArrangeRect(UIElement element)
        {
            Rect arrangeRect = element.PreviousArrangeRect;
 
            // ELements without a parent (top level) get Arrange at DesiredSize
            // if they were measured "to content" (as infinity indicates).
            // If we arrange the element that is temporarily disconnected
            // so it is not a top-level one, the assumption is that it will be
            // layout-invalidated and/or recomputed by the parent when reconnected.
            if (element.GetUIParentNo3DTraversal() == null)
            {
                arrangeRect.X = arrangeRect.Y = 0;
 
                if (double.IsPositiveInfinity(element.PreviousConstraint.Width))
                    arrangeRect.Width = element.DesiredSize.Width;
 
                if (double.IsPositiveInfinity(element.PreviousConstraint.Height))
                    arrangeRect.Height = element.DesiredSize.Height;
            }
 
            return arrangeRect;
        }
 
        private void invalidateTreeIfRecovering()
        {
            if((_forceLayoutElement != null) || _gotException)
            {
                if(_forceLayoutElement != null)
                {
                    markTreeDirty(_forceLayoutElement);
                }
 
                _forceLayoutElement = null;
                _gotException = false;
            }
        }
 
        internal LayoutQueue MeasureQueue
        {
            get
            {
                if(_measureQueue == null)
                    _measureQueue = new InternalMeasureQueue();
                return _measureQueue;
            }
        }
 
        internal LayoutQueue ArrangeQueue
        {
            get
            {
                if(_arrangeQueue == null)
                    _arrangeQueue = new InternalArrangeQueue();
                return _arrangeQueue;
            }
        }
 
        internal class InternalMeasureQueue: LayoutQueue
        {
            internal override void setRequest(UIElement e, Request r)
            {
                e.MeasureRequest = r;
            }
 
            internal override Request getRequest(UIElement e)
            {
                return e.MeasureRequest;
            }
 
            internal override bool canRelyOnParentRecalc(UIElement parent)
            {
                return !parent.IsMeasureValid
                    && !parent.MeasureInProgress; //if parent's measure is in progress, we might have passed this child already
            }
 
            internal override void invalidate(UIElement e)
            {
                e.InvalidateMeasureInternal();
            }
}
 
 
        internal class InternalArrangeQueue: LayoutQueue
        {
            internal override void setRequest(UIElement e, Request r)
            {
                e.ArrangeRequest = r;
            }
 
            internal override Request getRequest(UIElement e)
            {
                return e.ArrangeRequest;
            }
 
            internal override bool canRelyOnParentRecalc(UIElement parent)
            {
                return !parent.IsArrangeValid
                    && !parent.ArrangeInProgress; //if parent's arrange is in progress, we might have passed this child already
            }
 
            internal override void invalidate(UIElement e)
            {
                e.InvalidateArrangeInternal();
            }
}
 
        // delegate for dispatcher - keep it static so we don't allocate new ones.
        private static DispatcherOperationCallback _updateCallback = new DispatcherOperationCallback(UpdateLayoutCallback);
        private static object UpdateLayoutCallback(object arg)
        {
            ContextLayoutManager ContextLayoutManager = arg as ContextLayoutManager;
            if(ContextLayoutManager != null)
                ContextLayoutManager.UpdateLayout();
            return null;
        }
 
        //walks the list, fires events to alive handlers and removes dead ones
        private void fireLayoutUpdateEvent()
        {
            //no reentrancy. It may happen if one of handlers calls UpdateLayout synchronously
            if(_inFireLayoutUpdated) return;
 
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordLayout, EventTrace.Level.Verbose, EventTrace.Event.WClientLayoutFireLayoutUpdatedBegin);
            try
            {
                _inFireLayoutUpdated = true;
 
                LayoutEventList.ListItem [] copy = LayoutEvents.CopyToArray();
 
                for(int i=0; i<copy.Length; i++)
                {
                    LayoutEventList.ListItem item = copy[i];
                    //store handler here in case if thread gets pre-empted between check for IsAlive and invocation
                    //and GC can run making something that was alive not callable.
                    EventHandler e = null;
                    try
                    {
                        // this will return null if element is already GC'ed
                        e = (EventHandler)(item.Target);
                    }
                    catch(InvalidOperationException) //this will happen if element is being resurrected after finalization
                    {
                        e = null;
                    }
 
                    if(e != null)
                    {
                        e(null, EventArgs.Empty);
                        // if handler dirtied the tree, go clean it again before calling other handlers
                        if(hasDirtiness) break;
                    }
                    else
                    {
                        LayoutEvents.Remove(item);
                    }
                }
             }
            finally
            {
                _inFireLayoutUpdated = false;
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordLayout, EventTrace.Level.Verbose, EventTrace.Event.WClientLayoutFireLayoutUpdatedEnd);
            }
        }
 
 
        private LayoutEventList _layoutEvents;
 
        internal LayoutEventList LayoutEvents
        {
            get
            {
                if(_layoutEvents == null)
                    _layoutEvents = new LayoutEventList();
                return _layoutEvents;
            }
        }
 
        internal void AddToSizeChangedChain(SizeChangedInfo info)
        {
            //this typically will cause firing of SizeChanged from top to down. However, this order is not
            //specified for any users and is subject to change without notice.
            info.Next = _sizeChangedChain;
            _sizeChangedChain = info;
        }
 
 
 
 
        private void fireSizeChangedEvents()
        {
            //no reentrancy. It may happen if one of handlers calls UpdateLayout synchronously
            if(_inFireSizeChanged) return;
 
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordLayout, EventTrace.Level.Verbose, EventTrace.Event.WClientLayoutFireSizeChangedBegin);
            try
            {
                _inFireSizeChanged = true;
 
                //loop for SizeChanged
                while(_sizeChangedChain != null)
                {
                    SizeChangedInfo info = _sizeChangedChain;
                    _sizeChangedChain = info.Next;
 
                    info.Element.sizeChangedInfo = null;
 
                    info.Element.OnRenderSizeChanged(info);
 
                    //if callout dirtified the tree, return to cleaning
                    if(hasDirtiness) break;
                }
            }
            finally
            {
                _inFireSizeChanged = false;
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordLayout, EventTrace.Level.Verbose, EventTrace.Event.WClientLayoutFireSizeChangedEnd);
            }
        }
 
        private void fireAutomationEvents()
        {
            //no reentrancy. It may happen if one of handlers calls UpdateLayout synchronously
            if(_inFireAutomationEvents) return;
 
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordLayout, EventTrace.Level.Verbose, EventTrace.Event.WClientLayoutFireAutomationEventsBegin);
            try
            {
                _inFireAutomationEvents = true;
                _firePostLayoutEvents = false;
 
                LayoutEventList.ListItem [] copy = AutomationEvents.CopyToArray();
 
                for(int i=0; i<copy.Length; i++)
                {
                    LayoutEventList.ListItem item = copy[i];
                    //store peer here in case if thread gets pre-empted between check for IsAlive and invocation
                    //and GC can run making something that was alive not callable.
                    AutomationPeer peer = null;
                    try
                    {
                        // this will return null if element is already GC'ed
                        peer = (AutomationPeer)(item.Target);
                    }
                    catch(InvalidOperationException) //this will happen if element is being resurrected after finalization
                    {
                        peer = null;
                    }
 
                    if(peer != null)
                    {
                        peer.FireAutomationEvents();
                        // if handler dirtied the tree, go clean it again before calling other handlers
                        if(hasDirtiness) break;
                    }
                    else
                    {
                        AutomationEvents.Remove(item);
                    }
                }
            }
            finally
            {
                _inFireAutomationEvents = false;
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordLayout, EventTrace.Level.Verbose, EventTrace.Event.WClientLayoutFireAutomationEventsEnd);
            }
        }
 
        private LayoutEventList _automationEvents;
 
        internal LayoutEventList AutomationEvents
        {
            get
            {
                if(_automationEvents == null)
                    _automationEvents = new LayoutEventList();
                return _automationEvents;
            }
        }
 
        internal AutomationPeer[] GetAutomationRoots()
        {
            LayoutEventList.ListItem [] copy = AutomationEvents.CopyToArray();
 
            AutomationPeer[] peers = new AutomationPeer[copy.Length];
            int freeSlot = 0;
 
            for(int i=0; i<copy.Length; i++)
            {
                LayoutEventList.ListItem item = copy[i];
                //store peer here in case if thread gets pre-empted between check for IsAlive and invocation
                //and GC can run making something that was alive not callable.
                AutomationPeer peer = null;
                try
                {
                    // this will return null if element is already GC'ed
                    peer = (AutomationPeer)(item.Target);
                }
                catch(InvalidOperationException) //this will happen if element is being resurrected after finalization
                {
                    peer = null;
                }
 
                if(peer != null)
                {
                    peers[freeSlot++] = peer;
                }
            }
 
            return peers;
        }
 
        //this is used to prevent using automation roots in AutomationPeer when there are
        //sync updates of AutomationPeers on the stack. It is here because LayoutManager is
        //a Dispatcher-wide object and sync updates are per-dispatcher. Basically,
        //it is here to avoid creating AutomationManager to track Dispatcher scope.
        internal int AutomationSyncUpdateCounter
        {
            get
            {
                return _automationSyncUpdateCounter;
            }
            set
            {
                _automationSyncUpdateCounter = value;
            }
        }
 
        //Debuggability support - see LayoutInformation class in Framework
        internal UIElement GetLastExceptionElement()
        {
            return _lastExceptionElement;
        }
 
        internal void SetLastExceptionElement(UIElement e)
        {
            _lastExceptionElement = e;
        }
 
       ///// DATA //////
 
        private UIElement _forceLayoutElement; //set in extreme situations, forces the update of the whole tree containing the element
        private UIElement _lastExceptionElement; //set on exception in Measure or Arrange.
 
        private InternalMeasureQueue _measureQueue;
        private InternalArrangeQueue _arrangeQueue;
        private SizeChangedInfo      _sizeChangedChain;
 
        private static DispatcherOperationCallback _updateLayoutBackground = new DispatcherOperationCallback(UpdateLayoutBackground);
        private EventHandler _shutdownHandler;
 
        internal static int s_LayoutRecursionLimit = UIElement.MAX_ELEMENTS_IN_ROUTE; //to keep these two constants in sync
        private int _arrangesOnStack;
        private int _measuresOnStack;
        private int _automationSyncUpdateCounter;
 
        private bool      _isDead;
        private bool      _isUpdating;
        private bool      _isInUpdateLayout;
        private bool      _gotException; //true if UpdateLayout exited with exception
        private bool      _layoutRequestPosted;
        private bool      _inFireLayoutUpdated;
        private bool      _inFireSizeChanged;
        private bool      _firePostLayoutEvents;
        private bool      _inFireAutomationEvents;
 
 
#if DEBUG_CLR_MEM
        // Used for CLRProfiler comments
        private static int _layoutCLRPass = 0;
#endif
 
        internal abstract class LayoutQueue
        {
            //size of the pre-allocated free list
            private const int PocketCapacity = 153;
            //when this many elements remain in the free list,
            //queue will switch to invalidating up and adding only the root
            private const int PocketReserve = 8;
 
            internal abstract Request getRequest(UIElement e);
            internal abstract void setRequest(UIElement e, Request r);
            internal abstract bool canRelyOnParentRecalc(UIElement parent);
            internal abstract void invalidate(UIElement e);
 
            internal class Request
            {
                internal UIElement Target;
                internal Request Next;
                internal Request Prev;
            }
 
            internal LayoutQueue()
            {
                Request r;
                for(int i=0; i<PocketCapacity; i++)
                {
                    r = new Request();
                    r.Next = _pocket;
                    _pocket = r;
                }
                _pocketSize = PocketCapacity;
            }
 
            private void _addRequest(UIElement e)
            {
                Request r = _getNewRequest(e);
 
                if(r != null)
                {
                    r.Next = _head;
                    if(_head != null) _head.Prev = r;
                    _head = r;
 
                    setRequest(e, r);
                }
            }
 
            internal void Add(UIElement e)
            {
                if(getRequest(e) != null) return;
                if(e.CheckFlagsAnd(VisualFlags.IsLayoutSuspended)) return;
 
                RemoveOrphans(e);
 
                UIElement parent = e.GetUIParentWithinLayoutIsland();
                if(parent != null && canRelyOnParentRecalc(parent)) return;
 
                ContextLayoutManager layoutManager = ContextLayoutManager.From(e.Dispatcher);
 
                if(layoutManager._isDead) return;
 
                //10 is arbitrary number here, simply indicates the queue is
                //about to be filled. If not queue is not almost full, simply add
                //the element to it. If it is almost full, start conserve entries
                //by escalating invalidation to all the ancestors until the top of
                //the visual tree, and only add root of visula tree to the queue.
                if(_pocketSize > PocketReserve)
                {
                    _addRequest(e);
                }
                else
                {
                    //walk up until we are the topmost UIElement in the tree.
                    //on each step, mark the parent dirty and remove it from the queues
                    //only leave a single node in the queue - the root of visual tree
                    while(e != null)
                    {
                        UIElement p = e.GetUIParentWithinLayoutIsland();
 
                        invalidate(e); //invalidate in any case
 
                        if (p != null && p.Visibility != Visibility.Collapsed) //not yet a root or a collapsed node
                        {
                            Remove(e);
                        }
                        else //root of visual tree or a collapsed node
                        {
                            if (getRequest(e) == null)
                            {
                                RemoveOrphans(e);
                                _addRequest(e);
                            }
                        }
                        e = p;
                    }
                }
 
                layoutManager.NeedsRecalc();
            }
 
            internal void Remove(UIElement e)
            {
                Request r = getRequest(e);
                if(r == null) return;
                _removeRequest(r);
                setRequest(e, null);
            }
 
            internal void RemoveOrphans(UIElement parent)
            {
                Request r = _head;
                while(r != null)
                {
                    UIElement child = r.Target;
                    Request next = r.Next;
                    ulong parentTreeLevel = parent.TreeLevel;
 
                    if(   (child.TreeLevel == parentTreeLevel + 1)
                       && (child.GetUIParentWithinLayoutIsland() == parent))
                    {
                        _removeRequest(getRequest(child));
                        setRequest(child, null);
                    }
 
                    r = next;
                }
            }
 
            internal bool IsEmpty { get { return (_head == null); }}
 
            internal UIElement GetTopMost()
            {
                UIElement found = null;
                ulong treeLevel = ulong.MaxValue;
 
                for(Request r = _head; r != null; r = r.Next)
                {
                    UIElement t = r.Target;
                    ulong l = t.TreeLevel;
 
                    if(l < treeLevel)
                    {
                        treeLevel = l;
                        found = r.Target;
                    }
                }
 
                return found;
            }
 
            private void _removeRequest(Request entry)
            {
                if(entry.Prev == null) _head = entry.Next;
                else entry.Prev.Next = entry.Next;
 
                if(entry.Next != null) entry.Next.Prev = entry.Prev;
 
                ReuseRequest(entry);
            }
 
            private Request _getNewRequest(UIElement e)
            {
                Request r;
                if(_pocket != null)
                {
                    r = _pocket;
                    _pocket = r.Next;
                    _pocketSize--;
                    r.Next = r.Prev = null;
                }
                else
                {
                    ContextLayoutManager lm = ContextLayoutManager.From(e.Dispatcher);
                    try
                    {
                        r = new Request();
                    }
                    catch(System.OutOfMemoryException)
                    {
                        if(lm != null)
                            lm.setForceLayout(e);
                        throw;
                    }
                }
 
                r.Target = e;
                return r;
            }
 
            private void ReuseRequest(Request r)
            {
                r.Target = null; //let target die
 
                if (_pocketSize < PocketCapacity)
                {
                    r.Next = _pocket;
                    _pocket = r;
                    _pocketSize++;
                }
            }
 
            private Request _head;
            private Request _pocket;
            private int     _pocketSize;
        }
    }
 
    internal class LayoutEventList
    {
        //size of the pre-allocated free list
        private const int PocketCapacity = 153;
 
        internal class ListItem: WeakReference
        {
            internal ListItem() : base(null) {}
            internal ListItem Next;
            internal ListItem Prev;
            internal bool     InUse;
        }
 
        internal LayoutEventList()
        {
            ListItem t;
            for(int i=0; i<PocketCapacity; i++)
            {
                t = new ListItem();
                t.Next = _pocket;
                _pocket = t;
            }
            _pocketSize = PocketCapacity;
        }
 
        internal ListItem Add(object target)
        {
            ListItem t = getNewListItem(target);
 
            t.Next = _head;
            if(_head != null) _head.Prev = t;
            _head = t;
 
           _count++;
            return t;
        }
 
        internal void Remove(ListItem t)
        {
            //already removed item can be passed again
            //(once removed by handler and then by firing code)
            if(!t.InUse) return;
 
            if(t.Prev == null) _head = t.Next;
            else t.Prev.Next = t.Next;
 
            if(t.Next != null) t.Next.Prev = t.Prev;
 
            reuseListItem(t);
            _count--;
        }
 
        private ListItem getNewListItem(object target)
        {
            ListItem t;
            if(_pocket != null)
            {
                t = _pocket;
                _pocket = t.Next;
                _pocketSize--;
                t.Next = t.Prev = null;
            }
            else
            {
                t = new ListItem();
            }
 
            t.Target = target;
            t.InUse = true;
            return t;
        }
 
        private void reuseListItem(ListItem t)
        {
            t.Target = null; //let target die
            t.Next = t.Prev = null;
            t.InUse = false;
 
            if (_pocketSize < PocketCapacity)
            {
                t.Next = _pocket;
                _pocket = t;
                _pocketSize++;
            }
        }
 
        internal ListItem[] CopyToArray()
        {
            ListItem [] copy = new ListItem[_count];
            ListItem t = _head;
            int cnt = 0;
            while(t != null)
            {
                copy[cnt++] = t;
                t = t.Next;
            }
            return copy;
        }
 
        internal int Count
        {
            get
            {
                return _count;
            }
        }
 
        private ListItem _head;
        private ListItem _pocket;
        private int      _pocketSize;
        private int      _count;
    }
}