From a2358a6a2cff5f3748f3883f38ddf055866836cc Mon Sep 17 00:00:00 2001 From: "Aleksandar Marinov (INFRAGISTICS INC)" Date: Wed, 13 May 2026 14:50:51 +0300 Subject: [PATCH 1/2] Implement opt-in weak reference handling for ElementProxy in automation peers to mitigate memory leaks in virtualized ItemsControls --- .../MS/internal/Automation/ElementProxy.cs | 3 ++- .../Windows/Automation/Peers/AutomationPeer.cs | 7 +++++++ .../MS/Internal/FrameworkAppContextSwitches.cs | 16 +++++++++++++++- .../Automation/Peers/ItemAutomationPeer.cs | 8 ++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementProxy.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementProxy.cs index cbc1e67b909..096169bd56f 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementProxy.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementProxy.cs @@ -39,7 +39,8 @@ internal class ElementProxy: IRawElementProviderFragmentRoot, IRawElementProvide private ElementProxy(AutomationPeer peer) { if ((AutomationInteropReferenceType == ReferenceType.Weak) && - (peer is UIElementAutomationPeer || peer is ContentElementAutomationPeer || peer is UIElement3DAutomationPeer)) + (peer is UIElementAutomationPeer || peer is ContentElementAutomationPeer || peer is UIElement3DAutomationPeer + || peer.ShouldUseWeakReferenceFromElementProxy)) { _peer = new WeakReference(peer); } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Automation/Peers/AutomationPeer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Automation/Peers/AutomationPeer.cs index 5d4ce668f6c..200843793ff 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Automation/Peers/AutomationPeer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Automation/Peers/AutomationPeer.cs @@ -523,6 +523,13 @@ internal virtual bool IsDataItemAutomationPeer() return false; } + // When true, ElementProxy holds this peer via WeakReference. Opt-in fix for the + // UIA-retained-ElementProxy leak in virtualized ItemsControls (see ItemAutomationPeer). + internal virtual bool ShouldUseWeakReferenceFromElementProxy + { + get { return false; } + } + // UpdatePeer is called asynchronously. Between the time the call is // posted (InvalidatePeer) and the time the call is executed (UpdatePeer), // changes to the visual tree and/or automation tree may have eliminated diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/FrameworkAppContextSwitches.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/FrameworkAppContextSwitches.cs index ed5caf9a8be..29c300554e7 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/FrameworkAppContextSwitches.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/FrameworkAppContextSwitches.cs @@ -121,7 +121,21 @@ public static bool ItemAutomationPeerKeepsItsItemAlive return LocalAppContext.GetCachedSwitchValue(ItemAutomationPeerKeepsItsItemAliveSwitchName, ref _ItemAutomationPeerKeepsItsItemAlive); } } - + + // Opt-in: when true, ElementProxy holds ItemAutomationPeer (and subclasses) via WeakReference, + // letting the GC reclaim peers and their data-item/container chain when UIA Core retains the + // ElementProxy aggressively (heap growth in virtualized ItemsControls, e.g. DataGrid). + // Re-discovery: parent ItemsControlAutomationPeer's WeakRefElementProxyStorage. + internal const string UseWeakReferenceFromElementProxyToItemPeerSwitchName = "Switch.System.Windows.Automation.Peers.UseWeakReferenceFromElementProxyToItemPeer"; + private static int _UseWeakReferenceFromElementProxyToItemPeer; + public static bool UseWeakReferenceFromElementProxyToItemPeer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return LocalAppContext.GetCachedSwitchValue(UseWeakReferenceFromElementProxyToItemPeerSwitchName, ref _UseWeakReferenceFromElementProxyToItemPeer); + } + } // Switch to opt-out Fluent theme Window Backdrop feature internal const string DisableFluentThemeWindowBackdropSwitchName = "Switch.System.Windows.Appearance.DisableFluentThemeWindowBackdrop"; diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Automation/Peers/ItemAutomationPeer.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Automation/Peers/ItemAutomationPeer.cs index 99475559aed..f7078169da3 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Automation/Peers/ItemAutomationPeer.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Automation/Peers/ItemAutomationPeer.cs @@ -155,6 +155,14 @@ internal override bool IsDataItemAutomationPeer() return true; } + // Gated by FrameworkAppContextSwitches.UseWeakReferenceFromElementProxyToItemPeer. When on, + // a long-lived UIA ElementProxy no longer pins the data item, container, or parent + // ItemsControlAutomationPeer caches; re-discovery goes via WeakRefElementProxyStorage on the parent. + internal override bool ShouldUseWeakReferenceFromElementProxy + { + get { return FrameworkAppContextSwitches.UseWeakReferenceFromElementProxyToItemPeer; } + } + internal override void AddToParentProxyWeakRefCache() { ItemsControlAutomationPeer itemsControlAutomationPeer = ItemsControlAutomationPeer; From 8b651e5f5cabe10f2a2710652d7bf702d312190d Mon Sep 17 00:00:00 2001 From: Alexander Marinov Date: Wed, 13 May 2026 19:48:00 +0300 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../MS/Internal/FrameworkAppContextSwitches.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/FrameworkAppContextSwitches.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/FrameworkAppContextSwitches.cs index 29c300554e7..3df85bacc9f 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/FrameworkAppContextSwitches.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/FrameworkAppContextSwitches.cs @@ -125,7 +125,9 @@ public static bool ItemAutomationPeerKeepsItsItemAlive // Opt-in: when true, ElementProxy holds ItemAutomationPeer (and subclasses) via WeakReference, // letting the GC reclaim peers and their data-item/container chain when UIA Core retains the // ElementProxy aggressively (heap growth in virtualized ItemsControls, e.g. DataGrid). - // Re-discovery: parent ItemsControlAutomationPeer's WeakRefElementProxyStorage. + // The parent ItemsControlAutomationPeer's WeakRefElementProxyStorage may reuse still-live + // ElementProxy/peer pairs, but it does not guarantee peer re-discovery after GC; callers may + // need to handle ElementNotAvailableException and re-walk when the peer has been collected. internal const string UseWeakReferenceFromElementProxyToItemPeerSwitchName = "Switch.System.Windows.Automation.Peers.UseWeakReferenceFromElementProxyToItemPeer"; private static int _UseWeakReferenceFromElementProxyToItemPeer; public static bool UseWeakReferenceFromElementProxyToItemPeer