diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/LibraryAssemblyInfo.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/LibraryAssemblyInfo.cs
new file mode 100644
index 00000000000..56d27d4d336
--- /dev/null
+++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/LibraryAssemblyInfo.cs
@@ -0,0 +1,7 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+using Microsoft.Internal;
+
+[assembly: InternalsVisibleTo($"PresentationCore.Tests, PublicKey={BuildInfo.WCP_PUBLIC_KEY_STRING}")]
diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/Accessible.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/Accessible.cs
index 20f10608ad2..f5405b26705 100644
--- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/Accessible.cs
+++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/Accessible.cs
@@ -9,6 +9,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Windows.Automation;
+using System.Windows.Automation.Provider;
using System.Windows;
using Accessibility;
using System.Runtime.InteropServices;
@@ -295,6 +296,82 @@ internal static Accessible Wrap(IAccessible acc, int idChild)
internal bool IsFocused { get { return HasState(AccessibleState.Focused); } }
internal bool IsOffScreen { get { return HasState(AccessibleState.Offscreen); } }
+ internal object GetPatternProvider(int patternId)
+ {
+ return GetPatternProvider(AccessibleExProvider, patternId);
+ }
+
+ internal object GetPropertyValue(int propertyId)
+ {
+ return GetPropertyValue(AccessibleExProvider, propertyId);
+ }
+
+ internal static object GetPatternProvider(IRawElementProviderSimple provider, int patternId)
+ {
+ if (provider == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ return provider.GetPatternProvider(patternId);
+ }
+ catch (Exception e) when (IsAccessibleExUnavailable(e))
+ {
+ return null;
+ }
+ }
+
+ internal static object GetPropertyValue(IRawElementProviderSimple provider, int propertyId)
+ {
+ if (provider == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ return provider.GetPropertyValue(propertyId);
+ }
+ catch (Exception e) when (IsAccessibleExUnavailable(e))
+ {
+ return null;
+ }
+ }
+
+ internal static IRawElementProviderSimple GetAccessibleExProvider(UnsafeNativeMethods.IServiceProvider serviceProvider, int childId)
+ {
+ if (serviceProvider == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ Guid serviceId = typeof(UnsafeNativeMethods.IAccessibleEx).GUID;
+ Guid interfaceId = serviceId;
+ UnsafeNativeMethods.IAccessibleEx accessibleEx =
+ serviceProvider.QueryService(ref serviceId, ref interfaceId) as UnsafeNativeMethods.IAccessibleEx;
+
+ if (accessibleEx == null)
+ {
+ return null;
+ }
+
+ if (childId != NativeMethods.CHILD_SELF)
+ {
+ accessibleEx = accessibleEx.GetObjectForChild(childId);
+ }
+
+ return accessibleEx as IRawElementProviderSimple;
+ }
+ catch (Exception e) when (IsAccessibleExUnavailable(e))
+ {
+ return null;
+ }
+ }
+
internal Accessible FirstChild
{
get
@@ -1391,6 +1468,19 @@ private static bool HandleIAccessibleException(Exception e)
return true;
}
+ private static bool IsAccessibleExUnavailable(Exception e)
+ {
+ COMException comException = e as COMException;
+ return e is ArgumentException
+ || e is InvalidCastException
+ || e is NotImplementedException
+ || comException != null
+ && (comException.ErrorCode == NativeMethods.E_FAIL
+ || comException.ErrorCode == NativeMethods.E_NOINTERFACE
+ || comException.ErrorCode == NativeMethods.E_NOTIMPL
+ || comException.ErrorCode == NativeMethods.E_INVALIDARG);
+ }
+
// IAccessibles that we get from Winforms apps in partial trust return failure
// code for some methods - notably accNavigate and accChild. The operation will
// succeed, however, if we first navigate up to the parent, and then back down
@@ -1476,9 +1566,25 @@ private enum NavDir
private IAccessible _acc; // a full IAccessible object or an IAccessible parent that is managing a ChildID
private int _idChild; // this is ChildID which is the ID a server gives this child (not related to child order!)
private int _accessibleChildrenIndex; // this is how many children to skip over when calling AccessibleChildren
+ private bool _accessibleExProviderInitialized;
+ private IRawElementProviderSimple _accessibleExProvider;
private IntPtr _hwnd;
+ private IRawElementProviderSimple AccessibleExProvider
+ {
+ get
+ {
+ if (!_accessibleExProviderInitialized)
+ {
+ _accessibleExProvider = GetAccessibleExProvider(_acc as UnsafeNativeMethods.IServiceProvider, _idChild);
+ _accessibleExProviderInitialized = true;
+ }
+
+ return _accessibleExProvider;
+ }
+ }
+
#endregion Private Fields
}
}
diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAAEventDispatcher.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAAEventDispatcher.cs
index 068f94a1cc6..689005d5af2 100644
--- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAAEventDispatcher.cs
+++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAAEventDispatcher.cs
@@ -24,7 +24,13 @@ internal class MSAAEventDispatcher : MSAAWinEventWrap
#region Constructors
private MSAAEventDispatcher()
- : base(NativeMethods.EVENT_OBJECT_CREATE, NativeMethods.EVENT_OBJECT_ACCELERATORCHANGE)
+ : base(
+ NativeMethods.EVENT_OBJECT_CREATE,
+ NativeMethods.EVENT_OBJECT_ACCELERATORCHANGE,
+ ExpandCollapsePattern.ExpandCollapseStateProperty.Id,
+ ExpandCollapsePattern.ExpandCollapseStateProperty.Id,
+ TogglePattern.ToggleStateProperty.Id,
+ TogglePattern.ToggleStateProperty.Id)
{ }
#endregion Constructors
@@ -179,6 +185,13 @@ internal override void WinEventProc(int eventId, IntPtr hwnd, int idObject, int
// get the 2-nd level table of events and properties we are listening for in this window
Hashtable eventTable = (Hashtable)_hwndTable[hwnd];
+ AutomationProperty property = GetPatternPropertyFromWinEvent(eventId);
+ if (property != null)
+ {
+ MaybeFirePropertyChangeEvent(null, property, eventTable, hwnd, idObject, idChild, true);
+ return;
+ }
+
switch (eventId)
{
case NativeMethods.EVENT_OBJECT_CREATE:
@@ -242,6 +255,18 @@ internal override void WinEventProc(int eventId, IntPtr hwnd, int idObject, int
// break;
}
}
+
+ }
+
+ internal static AutomationProperty GetPatternPropertyFromWinEvent(int eventId)
+ {
+ if (eventId == ExpandCollapsePattern.ExpandCollapseStateProperty.Id)
+ return ExpandCollapsePattern.ExpandCollapseStateProperty;
+
+ if (eventId == TogglePattern.ToggleStateProperty.Id)
+ return TogglePattern.ToggleStateProperty;
+
+ return null;
}
#endregion Internal Methods
diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAANativeProvider.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAANativeProvider.cs
index 01933f705a4..bc2383a7af2 100644
--- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAANativeProvider.cs
+++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAANativeProvider.cs
@@ -486,6 +486,10 @@ ProviderOptions IRawElementProviderSimple.ProviderOptions
object IRawElementProviderSimple.GetPatternProvider(int patternId)
{
+ object provider = _acc.GetPatternProvider(patternId);
+ if (provider != null)
+ return provider;
+
AutomationPattern pattern = AutomationPattern.LookupById(patternId);
//Debug.WriteLine.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0} IRawElementProviderSimple.GetPatternProvider {1}", this, pattern));
@@ -495,6 +499,10 @@ object IRawElementProviderSimple.GetPatternProvider(int patternId)
object IRawElementProviderSimple.GetPropertyValue(int propertyId)
{
+ object value = _acc.GetPropertyValue(propertyId);
+ if (value != null)
+ return value;
+
AutomationProperty idProp = AutomationProperty.LookupById(propertyId);
//Debug.WriteLine.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0} IRawElementProviderSimple.GetPropertyValue {1}", this, idProp));
@@ -669,7 +677,13 @@ IRawElementProviderSimple ISelectionItemProvider.SelectionContainer
{
//Debug.WriteLine.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0} ISelectionItemProvider.SelectionContainer", this));
- return IsRoot ? null : Parent;
+ MsaaNativeProvider parent = IsRoot ? null : Parent;
+ while (parent != null && !parent.IsPatternSupported(SelectionPattern.Pattern))
+ {
+ parent = parent.IsRoot ? null : parent.Parent;
+ }
+
+ return parent;
}
}
#endregion ISelectionItemProvider
@@ -880,6 +894,10 @@ protected virtual object GetPatternProvider(AutomationPattern pattern)
// overridable implementation of IRawElementProviderSimple.GetPropertyValue
protected virtual object GetPropertyValue(AutomationProperty idProp)
{
+ object patternValue = GetPatternPropertyValue(this, idProp);
+ if (patternValue != null)
+ return patternValue;
+
// The following UIA properties need support: AcceleratorKeyProperty, AccessKeyProperty, AutomationIdProperty,
// HasKeyboardFocusProperty, IsContentElementProperty, IsControlElementProperty, IsKeyboardFocusableProperty,
// IsPasswordProperty, IsReadOnlyProperty, NativeObjectModelAccessProperty, SiblingIdProperty, TabIndexProperty?,
@@ -907,6 +925,7 @@ protected virtual object GetPropertyValue(AutomationProperty idProp)
else
return null;
}
+
else if (idProp == AutomationElement.IsEnabledProperty)
{
return _acc.IsEnabled;
@@ -953,6 +972,24 @@ protected virtual object GetPropertyValue(AutomationProperty idProp)
return null;
}
+ internal static object GetPatternPropertyValue(IRawElementProviderSimple provider, AutomationProperty property)
+ {
+ if (property == ExpandCollapsePattern.ExpandCollapseStateProperty)
+ {
+ IExpandCollapseProvider expandCollapse =
+ provider.GetPatternProvider(ExpandCollapsePattern.Pattern.Id) as IExpandCollapseProvider;
+ return expandCollapse?.ExpandCollapseState;
+ }
+
+ if (property == TogglePattern.ToggleStateProperty)
+ {
+ IToggleProvider toggle = provider.GetPatternProvider(TogglePattern.Pattern.Id) as IToggleProvider;
+ return toggle?.ToggleState;
+ }
+
+ return null;
+ }
+
// overridable method used by value pattern to retrieve the value.
protected virtual string GetValue()
{
@@ -1309,7 +1346,9 @@ public CtrlTypePatterns(ControlType ctrlType, params AutomationPattern[] pattern
new CtrlTypePatterns(ControlType.RadioButton, SelectionItemPattern.Pattern),
// ControlType.Slider: it is impossible to tell which of RangeValue or Selection patterns to support so we're not supporting either.
// ControlType.Spinner: it is impossible to tell which of RangeValue or Selection patterns to support so we're not supporting either.
- new CtrlTypePatterns(ControlType.SplitButton, InvokePattern.Pattern)
+ new CtrlTypePatterns(ControlType.SplitButton, InvokePattern.Pattern),
+ new CtrlTypePatterns(ControlType.Tree, SelectionPattern.Pattern),
+ new CtrlTypePatterns(ControlType.TreeItem, SelectionItemPattern.Pattern)
};
private Accessible _acc; // the IAccessible we are representing. use Accessible to access.
diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAAWinEventWrap.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAAWinEventWrap.cs
index 4c315912ad1..f25d75fe9e3 100644
--- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAAWinEventWrap.cs
+++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Internal/AutomationProxies/MSAAWinEventWrap.cs
@@ -27,12 +27,14 @@ internal class MSAAWinEventWrap
#region Constructors
- // ctor that takes a range of events
- internal MSAAWinEventWrap(int eventMin, int eventMax)
+ // ctor that takes one or more event ranges as min/max pairs
+ internal MSAAWinEventWrap(params int[] eventRanges)
{
- _eventMin = eventMin;
- _eventMax = eventMax;
- _hHooks = new IntPtr[1];
+ if (eventRanges == null || eventRanges.Length == 0 || eventRanges.Length % 2 != 0)
+ throw new ArgumentException(SR.InvalidParameter);
+
+ _eventRanges = (int[])eventRanges.Clone();
+ _hHooks = new IntPtr[eventRanges.Length / 2];
Init();
}
@@ -74,12 +76,21 @@ internal void StartListening()
{
_fBusy = true;
+ for (int i = 0; i < _hHooks.Length; i++)
{
- // in a single hook, listen for a range of WinEvent types
- _hHooks[0] = Misc.SetWinEventHook(_eventMin, _eventMax, IntPtr.Zero, _winEventProc, 0, 0, _fFlags);
- if (_hHooks[0] == IntPtr.Zero)
+ int rangeIndex = i * 2;
+ _hHooks[i] = Misc.SetWinEventHook(
+ _eventRanges[rangeIndex],
+ _eventRanges[rangeIndex + 1],
+ IntPtr.Zero,
+ _winEventProc,
+ 0,
+ 0,
+ _fFlags);
+ if (_hHooks[i] == IntPtr.Zero)
{
StopListening();
+ return;
}
}
_fBusy = false;
@@ -213,8 +224,7 @@ internal WinEvent(int eventId, IntPtr hwnd, int idObject, int idChild)
}
private Queue _qEvents; // Queue of events waiting to be processed
- private int _eventMin; // minimum WinEvent type in range
- private int _eventMax; // maximium WinEventType in range
+ private int[] _eventRanges; // pairs of minimum and maximum WinEvent types
private IntPtr [] _hHooks; // the returned handles(s) from SetWinEventHook
private bool _fBusy; // Flag indicating if we're busy processing
private int _fFlags; // SetWinEventHook flags
diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Win32/NativeMethods.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Win32/NativeMethods.cs
index f627535fad4..1b257479017 100644
--- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Win32/NativeMethods.cs
+++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Win32/NativeMethods.cs
@@ -1140,6 +1140,7 @@ internal static int LODWORD(long n)
internal const int E_ACCESSDENIED = unchecked((int)0x80070005);
internal const int E_FAIL = unchecked((int)0x80004005);
+ internal const int E_NOINTERFACE = unchecked((int)0x80004002);
internal const int E_UNEXPECTED = unchecked((int)0x8000FFFF);
internal const int E_INVALIDARG = unchecked((int)0x80070057);
internal const int E_MEMBERNOTFOUND = unchecked((int)0x80020003);
diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Win32/UnsafeNativeMethods.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Win32/UnsafeNativeMethods.cs
index 02d9126dcdf..9732ba4cae8 100644
--- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Win32/UnsafeNativeMethods.cs
+++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/MS/Win32/UnsafeNativeMethods.cs
@@ -8,6 +8,7 @@
using System.Runtime.InteropServices;
using System.Text;
using System.Diagnostics;
+using System.Windows.Automation.Provider;
using NativeMethodsSetLastError = MS.Internal.UIAutomationClientSideProviders.NativeMethodsSetLastError;
namespace MS.Win32
@@ -79,6 +80,28 @@ internal static class UnsafeNativeMethods
internal static Guid IID_IDispatch = new Guid(0x00020400, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
internal static Guid IID_IAccessible = new Guid(0x618736e0, 0x3c3d, 0x11cf, 0x81, 0x0c, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
+ [ComImport, Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IServiceProvider
+ {
+ [return: MarshalAs(UnmanagedType.IUnknown)]
+ object QueryService(ref Guid service, ref Guid riid);
+ }
+
+ [ComImport, Guid("F8B80ADA-2C44-48D0-89BE-5FF23C9CD875"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IAccessibleEx
+ {
+ [return: MarshalAs(UnmanagedType.Interface)]
+ IAccessibleEx GetObjectForChild(int idChild);
+
+ void GetIAccessiblePair([MarshalAs(UnmanagedType.Interface)] out IAccessible accessible, out int childId);
+
+ [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4)]
+ int[] GetRuntimeId();
+
+ [return: MarshalAs(UnmanagedType.Interface)]
+ IAccessibleEx ConvertReturnedElement([MarshalAs(UnmanagedType.Interface)] IRawElementProviderSimple provider);
+ }
+
[DllImport("oleacc.dll", SetLastError=true)]
internal static extern IntPtr GetProcessHandleFromHwnd(IntPtr hwnd);
@@ -474,4 +497,3 @@ public struct LHITTESTINFO
}
}
}
-
diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/UIAutomationClientSideProviders.csproj b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/UIAutomationClientSideProviders.csproj
index 106e50cb575..a8ae860c8c1 100644
--- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/UIAutomationClientSideProviders.csproj
+++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClientSideProviders/UIAutomationClientSideProviders.csproj
@@ -15,6 +15,7 @@
+
Common\System\SR.cs
diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/AutomationProxies/AccessibleExTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/AutomationProxies/AccessibleExTests.cs
new file mode 100644
index 00000000000..d92d0484587
--- /dev/null
+++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/AutomationProxies/AccessibleExTests.cs
@@ -0,0 +1,503 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#nullable enable
+
+extern alias uiaProviders;
+
+using System.Runtime.InteropServices;
+using System.Windows.Automation;
+using System.Windows.Automation.Provider;
+using Accessibility;
+using Moq;
+using Accessible = uiaProviders::MS.Internal.AutomationProxies.Accessible;
+using AccessibleRole = uiaProviders::MS.Internal.AutomationProxies.AccessibleRole;
+using MSAAEventDispatcher = uiaProviders::MS.Internal.AutomationProxies.MSAAEventDispatcher;
+using MsaaNativeProvider = uiaProviders::MS.Internal.AutomationProxies.MsaaNativeProvider;
+using NativeMethods = uiaProviders::MS.Win32.NativeMethods;
+using UnsafeNativeMethods = uiaProviders::MS.Win32.UnsafeNativeMethods;
+
+namespace PresentationCore.Tests.MS.Internal.AutomationProxies;
+
+public class AccessibleExTests
+{
+ [Fact]
+ public void IsPatternSupported_OutlineItem_SupportsSelectionItem()
+ {
+ TestMsaaNativeProvider provider = new(CreateAccessible(AccessibleRole.OutlineItem));
+
+ Assert.True(provider.IsPatternSupported(SelectionItemPattern.Pattern));
+ }
+
+ [Fact]
+ public void IsPatternSupported_Outline_SupportsSelection()
+ {
+ TestMsaaNativeProvider provider = new(CreateAccessible(AccessibleRole.Outline));
+
+ Assert.True(provider.IsPatternSupported(SelectionPattern.Pattern));
+ }
+
+ [Fact]
+ public void SelectionContainer_NestedOutlineItem_ReturnsOutlineAncestor()
+ {
+ TestMsaaNativeProvider root = new(
+ CreateAccessible(AccessibleRole.Outline),
+ parent: null,
+ knownRoot: null,
+ MsaaNativeProvider.RootStatus.Root);
+ TestMsaaNativeProvider parent = new(
+ CreateAccessible(AccessibleRole.OutlineItem),
+ root,
+ root,
+ MsaaNativeProvider.RootStatus.NotRoot);
+ TestMsaaNativeProvider child = new(
+ CreateAccessible(AccessibleRole.OutlineItem),
+ parent,
+ root,
+ MsaaNativeProvider.RootStatus.NotRoot);
+
+ IRawElementProviderSimple? container =
+ ((ISelectionItemProvider)child).SelectionContainer;
+
+ Assert.Same(root, container);
+ }
+
+ [Fact]
+ public void GetPatternProvider_UnknownPattern_ReturnsNull()
+ {
+ TestMsaaNativeProvider provider = new(CreateAccessible(AccessibleRole.OutlineItem));
+
+ object? pattern =
+ ((IRawElementProviderSimple)provider).GetPatternProvider(int.MaxValue);
+
+ Assert.Null(pattern);
+ }
+
+ [Fact]
+ public void GetAccessibleExProvider_Self_ReturnsRootProvider()
+ {
+ TestAccessibleEx accessibleEx = new();
+ TestServiceProvider serviceProvider = new(accessibleEx);
+
+ IRawElementProviderSimple? provider =
+ Accessible.GetAccessibleExProvider(serviceProvider, NativeMethods.CHILD_SELF);
+
+ Assert.Same(accessibleEx, provider);
+ Assert.Equal(typeof(UnsafeNativeMethods.IAccessibleEx).GUID, serviceProvider.ServiceId);
+ Assert.Equal(typeof(UnsafeNativeMethods.IAccessibleEx).GUID, serviceProvider.InterfaceId);
+ Assert.Equal(0, accessibleEx.GetObjectForChildCallCount);
+ }
+
+ [Fact]
+ public void GetAccessibleExProvider_Child_ReturnsChildProvider()
+ {
+ TestAccessibleEx child = new();
+ TestAccessibleEx root = new() { Child = child };
+ TestServiceProvider serviceProvider = new(root);
+
+ IRawElementProviderSimple? provider =
+ Accessible.GetAccessibleExProvider(serviceProvider, childId: 42);
+
+ Assert.Same(child, provider);
+ Assert.Equal(1, root.GetObjectForChildCallCount);
+ Assert.Equal(42, root.LastChildId);
+ }
+
+ [Fact]
+ public void GetAccessibleExProvider_ServiceDoesNotImplementIAccessibleEx_ReturnsNull()
+ {
+ TestServiceProvider serviceProvider = new(new object());
+
+ IRawElementProviderSimple? provider =
+ Accessible.GetAccessibleExProvider(serviceProvider, NativeMethods.CHILD_SELF);
+
+ Assert.Null(provider);
+ }
+
+ [Theory]
+ [InlineData(NativeMethods.E_FAIL)]
+ [InlineData(NativeMethods.E_NOINTERFACE)]
+ [InlineData(NativeMethods.E_NOTIMPL)]
+ [InlineData(NativeMethods.E_INVALIDARG)]
+ public void GetAccessibleExProvider_UnsupportedService_ReturnsNull(int errorCode)
+ {
+ TestServiceProvider serviceProvider = new(GetExceptionForHR(errorCode));
+
+ IRawElementProviderSimple? provider =
+ Accessible.GetAccessibleExProvider(serviceProvider, NativeMethods.CHILD_SELF);
+
+ Assert.Null(provider);
+ }
+
+ [Fact]
+ public void GetAccessibleExProvider_UnsupportedChild_ReturnsNull()
+ {
+ TestAccessibleEx root = new()
+ {
+ GetObjectForChildException = GetExceptionForHR(NativeMethods.E_INVALIDARG)
+ };
+
+ IRawElementProviderSimple? provider =
+ Accessible.GetAccessibleExProvider(new TestServiceProvider(root), childId: 42);
+
+ Assert.Null(provider);
+ }
+
+ [Fact]
+ public void GetAccessibleExProvider_UnexpectedComFailure_Throws()
+ {
+ COMException expected = Assert.IsType(
+ GetExceptionForHR(NativeMethods.RPC_E_DISCONNECTED));
+ TestServiceProvider serviceProvider = new(expected);
+
+ COMException actual = Assert.Throws(
+ () => Accessible.GetAccessibleExProvider(serviceProvider, NativeMethods.CHILD_SELF));
+
+ Assert.Same(expected, actual);
+ }
+
+ [Fact]
+ public void GetPatternProvider_SupportedPattern_ReturnsPatternProvider()
+ {
+ object expected = new();
+ TestAccessibleEx accessibleEx = new() { PatternProvider = expected };
+
+ object? actual = Accessible.GetPatternProvider(accessibleEx, patternId: 10005);
+
+ Assert.Same(expected, actual);
+ }
+
+ [Theory]
+ [InlineData(NativeMethods.E_FAIL)]
+ [InlineData(NativeMethods.E_NOTIMPL)]
+ [InlineData(NativeMethods.E_INVALIDARG)]
+ public void GetPatternProvider_UnsupportedPattern_ReturnsNull(int errorCode)
+ {
+ TestAccessibleEx accessibleEx = new()
+ {
+ GetPatternProviderException = GetExceptionForHR(errorCode)
+ };
+
+ object? actual = Accessible.GetPatternProvider(accessibleEx, patternId: 10005);
+
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void GetPatternProvider_UnexpectedComFailure_Throws()
+ {
+ COMException expected = Assert.IsType(
+ GetExceptionForHR(NativeMethods.RPC_E_DISCONNECTED));
+ TestAccessibleEx accessibleEx = new() { GetPatternProviderException = expected };
+
+ COMException actual = Assert.Throws(
+ () => Accessible.GetPatternProvider(accessibleEx, patternId: 10005));
+
+ Assert.Same(expected, actual);
+ }
+
+ [Fact]
+ public void GetPatternProvider_ProjectedInvalidArgument_ReturnsNull()
+ {
+ Exception projected = GetExceptionForHR(NativeMethods.E_INVALIDARG);
+ Assert.IsType(projected);
+ TestAccessibleEx accessibleEx = new()
+ {
+ GetPatternProviderException = projected
+ };
+
+ object? actual = Accessible.GetPatternProvider(accessibleEx, patternId: 10005);
+
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void GetPatternProvider_ProjectedNoInterface_ReturnsNull()
+ {
+ Exception projected = GetExceptionForHR(NativeMethods.E_NOINTERFACE);
+ Assert.IsType(projected);
+ TestAccessibleEx accessibleEx = new()
+ {
+ GetPatternProviderException = projected
+ };
+
+ object? actual = Accessible.GetPatternProvider(accessibleEx, patternId: 10005);
+
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void GetPropertyValue_SupportedProperty_ReturnsValue()
+ {
+ object expected = new();
+ TestAccessibleEx accessibleEx = new() { PropertyValue = expected };
+
+ object? actual = Accessible.GetPropertyValue(accessibleEx, propertyId: 30152);
+
+ Assert.Same(expected, actual);
+ }
+
+ [Theory]
+ [InlineData(NativeMethods.E_FAIL)]
+ [InlineData(NativeMethods.E_NOINTERFACE)]
+ [InlineData(NativeMethods.E_NOTIMPL)]
+ [InlineData(NativeMethods.E_INVALIDARG)]
+ public void GetPropertyValue_UnsupportedProperty_ReturnsNull(int errorCode)
+ {
+ TestAccessibleEx accessibleEx = new()
+ {
+ GetPropertyValueException = GetExceptionForHR(errorCode)
+ };
+
+ object? actual = Accessible.GetPropertyValue(accessibleEx, propertyId: 30152);
+
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void GetPropertyValue_UnexpectedComFailure_Throws()
+ {
+ COMException expected = Assert.IsType(
+ GetExceptionForHR(NativeMethods.RPC_E_DISCONNECTED));
+ TestAccessibleEx accessibleEx = new() { GetPropertyValueException = expected };
+
+ COMException actual = Assert.Throws(
+ () => Accessible.GetPropertyValue(accessibleEx, propertyId: 30152));
+
+ Assert.Same(expected, actual);
+ }
+
+ [Fact]
+ public void GetPatternPropertyValue_ExpandCollapse_ReturnsState()
+ {
+ TestAccessibleEx provider = new()
+ {
+ PatternProvider = new TestExpandCollapseProvider(ExpandCollapseState.Expanded)
+ };
+
+ object? actual = MsaaNativeProvider.GetPatternPropertyValue(
+ provider,
+ ExpandCollapsePattern.ExpandCollapseStateProperty);
+
+ Assert.Equal(ExpandCollapseState.Expanded, actual);
+ }
+
+ [Fact]
+ public void GetPatternPropertyValue_Toggle_ReturnsState()
+ {
+ TestAccessibleEx provider = new()
+ {
+ PatternProvider = new TestToggleProvider(ToggleState.Indeterminate)
+ };
+
+ object? actual = MsaaNativeProvider.GetPatternPropertyValue(
+ provider,
+ TogglePattern.ToggleStateProperty);
+
+ Assert.Equal(ToggleState.Indeterminate, actual);
+ }
+
+ [Fact]
+ public void GetPatternPropertyValue_UnrelatedProperty_ReturnsNull()
+ {
+ object? actual = MsaaNativeProvider.GetPatternPropertyValue(
+ new TestAccessibleEx(),
+ AutomationElement.NameProperty);
+
+ Assert.Null(actual);
+ }
+
+ [Fact]
+ public void GetPatternPropertyFromWinEvent_ExpandCollapse_ReturnsProperty()
+ {
+ AutomationProperty? property = MSAAEventDispatcher.GetPatternPropertyFromWinEvent(
+ ExpandCollapsePattern.ExpandCollapseStateProperty.Id);
+
+ Assert.Same(ExpandCollapsePattern.ExpandCollapseStateProperty, property);
+ }
+
+ [Fact]
+ public void GetPatternPropertyFromWinEvent_Toggle_ReturnsProperty()
+ {
+ AutomationProperty? property = MSAAEventDispatcher.GetPatternPropertyFromWinEvent(
+ TogglePattern.ToggleStateProperty.Id);
+
+ Assert.Same(TogglePattern.ToggleStateProperty, property);
+ }
+
+ [Theory]
+ [InlineData(NativeMethods.EVENT_OBJECT_STATECHANGE)]
+ [InlineData(30000)]
+ public void GetPatternPropertyFromWinEvent_UnsupportedProperty_ReturnsNull(int eventId)
+ {
+ Assert.Null(MSAAEventDispatcher.GetPatternPropertyFromWinEvent(eventId));
+ }
+
+ private static Exception GetExceptionForHR(int errorCode)
+ {
+ return Marshal.GetExceptionForHR(errorCode)!;
+ }
+
+ private sealed class TestServiceProvider : UnsafeNativeMethods.IServiceProvider
+ {
+ private readonly object _service;
+
+ internal TestServiceProvider(object service)
+ {
+ _service = service;
+ }
+
+ internal Guid ServiceId { get; private set; }
+ internal Guid InterfaceId { get; private set; }
+
+ public object QueryService(ref Guid service, ref Guid riid)
+ {
+ ServiceId = service;
+ InterfaceId = riid;
+
+ if (_service is Exception exception)
+ {
+ throw exception;
+ }
+
+ return _service;
+ }
+ }
+
+ private sealed class TestAccessibleEx :
+ UnsafeNativeMethods.IAccessibleEx,
+ IRawElementProviderSimple
+ {
+ internal TestAccessibleEx? Child { get; set; }
+ internal Exception? GetObjectForChildException { get; set; }
+ internal Exception? GetPatternProviderException { get; set; }
+ internal Exception? GetPropertyValueException { get; set; }
+ internal object? PatternProvider { get; set; }
+ internal object? PropertyValue { get; set; }
+ internal int GetObjectForChildCallCount { get; private set; }
+ internal int LastChildId { get; private set; }
+
+ public UnsafeNativeMethods.IAccessibleEx GetObjectForChild(int idChild)
+ {
+ GetObjectForChildCallCount++;
+ LastChildId = idChild;
+
+ if (GetObjectForChildException is Exception exception)
+ {
+ throw exception;
+ }
+
+ return Child!;
+ }
+
+ public void GetIAccessiblePair(out IAccessible accessible, out int childId)
+ {
+ throw new NotSupportedException();
+ }
+
+ public int[] GetRuntimeId()
+ {
+ throw new NotSupportedException();
+ }
+
+ public UnsafeNativeMethods.IAccessibleEx ConvertReturnedElement(IRawElementProviderSimple provider)
+ {
+ throw new NotSupportedException();
+ }
+
+ ProviderOptions IRawElementProviderSimple.ProviderOptions =>
+ ProviderOptions.ServerSideProvider;
+
+ object IRawElementProviderSimple.GetPatternProvider(int patternId)
+ {
+ if (GetPatternProviderException is Exception exception)
+ {
+ throw exception;
+ }
+
+ return PatternProvider!;
+ }
+
+ object IRawElementProviderSimple.GetPropertyValue(int propertyId)
+ {
+ if (GetPropertyValueException is Exception exception)
+ {
+ throw exception;
+ }
+
+ return PropertyValue!;
+ }
+
+ IRawElementProviderSimple IRawElementProviderSimple.HostRawElementProvider => null!;
+ }
+
+ private sealed class TestExpandCollapseProvider : IExpandCollapseProvider
+ {
+ internal TestExpandCollapseProvider(ExpandCollapseState state)
+ {
+ ExpandCollapseState = state;
+ }
+
+ public ExpandCollapseState ExpandCollapseState { get; }
+
+ public void Collapse()
+ {
+ throw new NotSupportedException();
+ }
+
+ public void Expand()
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ private sealed class TestToggleProvider : IToggleProvider
+ {
+ internal TestToggleProvider(ToggleState state)
+ {
+ ToggleState = state;
+ }
+
+ public ToggleState ToggleState { get; }
+
+ public void Toggle()
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ private static Accessible CreateAccessible(AccessibleRole role)
+ {
+ Mock accessible = new(MockBehavior.Strict);
+ accessible
+ .Setup(instance => instance.get_accRole(NativeMethods.CHILD_SELF))
+ .Returns((int)role);
+ return Accessible.Wrap(accessible.Object);
+ }
+
+ private sealed class TestMsaaNativeProvider : MsaaNativeProvider
+ {
+ internal TestMsaaNativeProvider(Accessible accessible)
+ : this(
+ accessible,
+ parent: null,
+ knownRoot: null,
+ RootStatus.NotRoot)
+ {
+ }
+
+ internal TestMsaaNativeProvider(
+ Accessible accessible,
+ MsaaNativeProvider? parent,
+ MsaaNativeProvider? knownRoot,
+ RootStatus rootStatus)
+ : base(
+ accessible,
+ new IntPtr(1),
+ parent!,
+ knownRoot!,
+ rootStatus)
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/PresentationCore.Tests.csproj b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/PresentationCore.Tests.csproj
index 43d961d246a..cead242a717 100644
--- a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/PresentationCore.Tests.csproj
+++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/PresentationCore.Tests.csproj
@@ -23,9 +23,16 @@
+
+
+ uiaProviders
+
+
+
+