Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,6 @@ public void Remove(Adorner adorner)
RemoveAdornerInfo(_zOrderMap, adorner, adornerInfo.ZOrder);
_children.Remove(adorner);
RemoveLogicalChild(adorner);

// If no more adorners remain for this element, unsubscribe from its LayoutUpdated
// to break the AdornerLayer/UIElement retention cycle.
if (ElementMap[adorner.AdornedElement] == null)
{
UnsubscribeFromElementLayout(adorner.AdornedElement);
}
_layoutDirty = true;
}

/// <summary>
Expand All @@ -261,7 +253,6 @@ public void Update()
}
}

_layoutDirty = true;
UpdateAdorner(null);
}

Expand All @@ -285,7 +276,6 @@ public void Update(UIElement element)
InvalidateAdorner((AdornerInfo)adornerInfos[i++]);
}

_layoutDirty = true;
UpdateAdorner(element);
}

Expand Down Expand Up @@ -612,15 +602,10 @@ internal void Add(Adorner adorner, int zOrder)

AddAdornerInfo(ElementMap, adornerInfo, adorner.AdornedElement);

// Subscribe to the adorned element's LayoutUpdated so we can arm _layoutDirty
// only when something actually changes, rather than on every layer-level fire.
SubscribeToElementLayout(adorner.AdornedElement);

AddAdornerToVisualTree(adornerInfo, zOrder);

AddLogicalChild(adorner);

_layoutDirty = true;
UpdateAdorner(adorner.AdornedElement);
}

Expand All @@ -639,18 +624,6 @@ internal static void InvalidateAdorner(AdornerInfo adornerInfo)
adornerInfo.SimpleTransform = default;
}

// TODO: regression tests for OnLayoutUpdated fast path (no DRT harness available in fork):
// 1. EmptyAdornerLayer_OnLayoutUpdated_DoesNotCallUpdateAdorner
// Create an AdornerLayer, call OnLayoutUpdated — verify UpdateAdorner was NOT called
// (mock or subclass override) and _layoutDirty ends up false.
// 2. AdornerLayer_AddAdornerAfterIdle_TriggersUpdateAdorner
// Create empty layer, fire OnLayoutUpdated (empty fast-path, _layoutDirty→false),
// Add() an adorner, fire OnLayoutUpdated again — verify UpdateAdorner IS called.
// 3. AdornerLayer_AddRemoveDuringLayoutUpdated_NoStaleDirtyFlag
// Simulate Add() inside a LayoutUpdated handler that fires concurrently with the
// layer's own handler; confirm that by the time the next pass fires, the adorner
// is walked (ElementMap.Count > 0 path) and _layoutDirty is not stranded false.

/// <summary>
/// OnLayoutUpdated event handler
/// </summary>
Expand All @@ -670,7 +643,7 @@ internal void OnLayoutUpdated(object sender, EventArgs args)
return;
}

// Non-empty layer: always run UpdateAdorner. A cached _layoutDirty flag
// Non-empty layer: always run UpdateAdorner. A cached dirty flag
// cannot comprehensively observe every coordinate-space change that
// requires re-walking adorners (ancestor RenderTransform changes,
// ContentPresenter re-templating that swaps the subtree between an
Expand All @@ -682,40 +655,6 @@ internal void OnLayoutUpdated(object sender, EventArgs args)
UpdateAdorner(null);
}

/// <summary>
/// LayoutUpdated handler subscribed per adorned element.
/// Arms the layer-level dirty bit so the next OnLayoutUpdated fires UpdateAdorner.
/// </summary>
private void OnAdornedElementLayoutUpdated(object sender, EventArgs e)
{
_layoutDirty = true;
}

/// <summary>
/// Subscribe to LayoutUpdated on the given element exactly once (tracked via
/// _subscribedElements). Called when the first adorner is registered for an element.
/// </summary>
private void SubscribeToElementLayout(UIElement element)
{
_subscribedElements ??= new HashSet<UIElement>();
if (_subscribedElements.Add(element))
{
element.LayoutUpdated += OnAdornedElementLayoutUpdated;
}
}

/// <summary>
/// Unsubscribe from LayoutUpdated on the given element.
/// Called when the last adorner for an element is removed.
/// </summary>
private void UnsubscribeFromElementLayout(UIElement element)
{
if (_subscribedElements != null && _subscribedElements.Remove(element))
{
element.LayoutUpdated -= OnAdornedElementLayoutUpdated;
}
}

/// <summary>
/// Set the zOrder on the given adorner.
/// </summary>
Expand All @@ -739,7 +678,6 @@ internal void SetAdornerZOrder(Adorner adorner, int zOrder)
adornerInfo.ZOrder = zOrder;
AddAdornerToVisualTree(adornerInfo, zOrder);
InvalidateAdorner(adornerInfo);
_layoutDirty = true;
UpdateAdorner(adorner.AdornedElement);
}

Expand Down Expand Up @@ -1264,15 +1202,6 @@ private GeneralTransform GetProposedTransform(Adorner adorner, GeneralTransform
// re-enter this same AdornerLayer cannot clobber an outer pass's snapshot.
private object[] _zOrderValuesSnapshotBuffer;

// Dirty-bit gate for OnLayoutUpdated. Set on adorner add/remove and on any
// per-element LayoutUpdated event; cleared at the top of UpdateAdorner so a
// re-entrant fire during the walk re-arms for the next pass.
// Starts true so the very first layout pass is never skipped.
private bool _layoutDirty = true;
// Set of elements for which we have a LayoutUpdated subscription.
// Maintained to ensure subscribe/unsubscribe are balanced.
private HashSet<UIElement> _subscribedElements;

#endregion Private Fields
}
}
Expand Down
Loading