From 2e45cd0ea97b9925614e9422ce1da3245adef127 Mon Sep 17 00:00:00 2001 From: AndreyPolovodov Date: Tue, 26 May 2026 19:23:19 +0300 Subject: [PATCH] Fix: UIA tree not visible for Avalonia content embedded via WinFormsAvaloniaControlHost (#21422) * [Automation] Add EmbeddableControlRootAutomationPeer tests * [Automation] Implement IRootProvider on EmbeddableControlRootAutomationPeer Without IRootProvider, AutomationNode.Create falls back to the plain AutomationNode which doesn't implement IRawElementProviderFragmentRoot and returns null from GetHostRawElementProvider. As a result, UiaReturnRawElementProvider responds with E_FAIL for the WM_GETOBJECT sent to the embedded HWND, and the Avalonia automation tree is invisible to UIA clients (Inspect.exe, Narrator, FlaUI etc.) when the control is hosted via WinFormsAvaloniaControlHost. --- .../EmbeddableControlRootAutomationPeer.cs | 5 +- ...mbeddableControlRootAutomationPeerTests.cs | 47 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Controls.UnitTests/Automation/EmbeddableControlRootAutomationPeerTests.cs diff --git a/src/Avalonia.Controls/Automation/Peers/EmbeddableControlRootAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/EmbeddableControlRootAutomationPeer.cs index 1aeeeec3d1..f15b18b7d3 100644 --- a/src/Avalonia.Controls/Automation/Peers/EmbeddableControlRootAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/EmbeddableControlRootAutomationPeer.cs @@ -5,11 +5,12 @@ using Avalonia.Automation.Peers; using Avalonia.Automation.Provider; using Avalonia.Controls.Embedding; using Avalonia.Input; +using Avalonia.Platform; using Avalonia.VisualTree; namespace Avalonia.Controls.Automation.Peers { - public class EmbeddableControlRootAutomationPeer : ContentControlAutomationPeer, IEmbeddedRootProvider + public class EmbeddableControlRootAutomationPeer : ContentControlAutomationPeer, IEmbeddedRootProvider, IRootProvider { private Control? _focus; @@ -24,6 +25,8 @@ namespace Avalonia.Controls.Automation.Peers public new EmbeddableControlRoot Owner => (EmbeddableControlRoot)base.Owner; + ITopLevelImpl? IRootProvider.PlatformImpl => Owner.PlatformImpl; + public event EventHandler? FocusChanged; public AutomationPeer? GetFocus() => _focus is object ? GetOrCreate(_focus) : null; diff --git a/tests/Avalonia.Controls.UnitTests/Automation/EmbeddableControlRootAutomationPeerTests.cs b/tests/Avalonia.Controls.UnitTests/Automation/EmbeddableControlRootAutomationPeerTests.cs new file mode 100644 index 0000000000..55fa04618e --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Automation/EmbeddableControlRootAutomationPeerTests.cs @@ -0,0 +1,47 @@ +using Avalonia.Automation.Peers; +using Avalonia.Automation.Provider; +using Avalonia.UnitTests; +using Xunit; + +#nullable enable + +namespace Avalonia.Controls.UnitTests.Automation +{ + public class EmbeddableControlRootAutomationPeerTests : ScopedTestBase + { + [Fact] + public void Peer_Provides_IRootProvider() + { + using var services = new CompositorTestServices(); + var peer = ControlAutomationPeer.CreatePeerForElement(services.TopLevel); + + var rootProvider = peer.GetProvider(); + + Assert.NotNull(rootProvider); + Assert.Same(peer, rootProvider); + } + + [Fact] + public void Peer_Still_Provides_IEmbeddedRootProvider() + { + using var services = new CompositorTestServices(); + var peer = ControlAutomationPeer.CreatePeerForElement(services.TopLevel); + + var embeddedRootProvider = peer.GetProvider(); + + Assert.NotNull(embeddedRootProvider); + } + + [Fact] + public void IRootProvider_PlatformImpl_Returns_Owner_PlatformImpl() + { + using var services = new CompositorTestServices(); + var peer = ControlAutomationPeer.CreatePeerForElement(services.TopLevel); + + var rootProvider = peer.GetProvider(); + + Assert.NotNull(rootProvider); + Assert.Same(services.TopLevel.PlatformImpl, rootProvider!.PlatformImpl); + } + } +}