diff --git a/Microsoft.Dotnet.Wpf.sln b/Microsoft.Dotnet.Wpf.sln index 09509d6f269..1b156137dbf 100644 --- a/Microsoft.Dotnet.Wpf.sln +++ b/Microsoft.Dotnet.Wpf.sln @@ -34,6 +34,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsBase", "src\Microsoft.DotNet.Wpf\src\WindowsBase\WindowsBase.csproj", "{FA69991B-9696-42D0-A5C7-F5E73F0DEE9E}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DirectWriteForwarder", "src\Microsoft.DotNet.Wpf\src\DirectWriteForwarder\DirectWriteForwarder.vcxproj", "{50A5318F-3B9A-48B9-9615-D5FA9D6D9C3E}" + ProjectSection(ProjectDependencies) = postProject + {FA69991B-9696-42D0-A5C7-F5E73F0DEE9E} = {FA69991B-9696-42D0-A5C7-F5E73F0DEE9E} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationCore", "src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj", "{74C63A45-EAE5-407A-9F89-E0C6BC604841}" EndProject @@ -157,6 +160,13 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationFramework.Royale-ref", "src\Microsoft.DotNet.Wpf\src\Themes\PresentationFramework.Royale\ref\PresentationFramework.Royale-ref.csproj", "{598B6188-937C-448B-8E6B-925F000BC076}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "System.Printing", "src\Microsoft.DotNet.Wpf\src\System.Printing\System.Printing.vcxproj", "{765E6BBC-772B-4808-BB72-E85615E8F237}" + ProjectSection(ProjectDependencies) = postProject + {17AA6CC2-CAE3-429C-B065-B76B8E14C632} = {17AA6CC2-CAE3-429C-B065-B76B8E14C632} + {74C63A45-EAE5-407A-9F89-E0C6BC604841} = {74C63A45-EAE5-407A-9F89-E0C6BC604841} + {9AC36357-34B7-40A1-95CA-FE9F46D089A7} = {9AC36357-34B7-40A1-95CA-FE9F46D089A7} + {B0C1157E-8664-44C4-AD8E-35CD6A78C769} = {B0C1157E-8664-44C4-AD8E-35CD6A78C769} + {FA69991B-9696-42D0-A5C7-F5E73F0DEE9E} = {FA69991B-9696-42D0-A5C7-F5E73F0DEE9E} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PresentationUI", "src\Microsoft.DotNet.Wpf\src\PresentationUI\PresentationUI.csproj", "{B8E5B99C-D162-4DCA-9C4F-90CF2CDE942E}" EndProject diff --git a/src/Microsoft.DotNet.Wpf/ApiCompat/Baselines/WindowsBase-ref-Net48.baseline.txt b/src/Microsoft.DotNet.Wpf/ApiCompat/Baselines/WindowsBase-ref-Net48.baseline.txt index abc3fcc2a71..2ccd82e68d1 100644 --- a/src/Microsoft.DotNet.Wpf/ApiCompat/Baselines/WindowsBase-ref-Net48.baseline.txt +++ b/src/Microsoft.DotNet.Wpf/ApiCompat/Baselines/WindowsBase-ref-Net48.baseline.txt @@ -32,4 +32,7 @@ CannotRemoveBaseTypeOrInterface : Type 'System.Windows.Markup.XmlnsCompatibleWit CannotRemoveBaseTypeOrInterface : Type 'System.Windows.Markup.XmlnsDefinitionAttribute' does not implement interface 'System.Runtime.InteropServices._Attribute' in the implementation but it does in the contract. CannotRemoveBaseTypeOrInterface : Type 'System.Windows.Markup.XmlnsPrefixAttribute' does not implement interface 'System.Runtime.InteropServices._Attribute' in the implementation but it does in the contract. CannotRemoveBaseTypeOrInterface : Type 'System.Windows.Media.DisableDpiAwarenessAttribute' does not implement interface 'System.Runtime.InteropServices._Attribute' in the implementation but it does in the contract. -Total Issues: 33 +CannotMakeTypeAbstract : Type 'System.Windows.Threading.DispatcherOperation' is abstract in the implementation but is not abstract in the contract. +MembersMustExist : Member 'protected System.Object System.Windows.Threading.DispatcherOperation.InvokeDelegateCore()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected System.Object System.Windows.Threading.DispatcherOperation.InvokeDelegateCore()' does not exist in the implementation but it does exist in the contract. +Total Issues: 36 diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/DockProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/DockProviderWrapper.cs index b20ae8c78cd..71666d4629e 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/DockProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/DockProviderWrapper.cs @@ -1,124 +1,47 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Dock pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class DockProviderWrapper: MarshalByRefObject, IDockProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private DockProviderWrapper( AutomationPeer peer, IDockProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IDockProvider - // - //------------------------------------------------------ - - #region Interface IDockProvider - - public void SetDockPosition(DockPosition dockPosition) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( SetDockPosition ), dockPosition ); - } - - public DockPosition DockPosition - { - get - { - return (DockPosition)ElementUtil.Invoke(_peer, new DispatcherOperationCallback( GetDockPosition ), null); - } - } - - #endregion Interface IDockProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new DockProviderWrapper( peer, (IDockProvider) iface ); - } +namespace MS.Internal.Automation; - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object SetDockPosition( object arg ) - { - _iface.SetDockPosition( (DockPosition) arg ); - return null; - } - - private object GetDockPosition( object unused ) - { - return _iface.DockPosition; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class DockProviderWrapper : MarshalByRefObject, IDockProvider +{ + private readonly AutomationPeer _peer; + private readonly IDockProvider _iface; - #endregion Private Methods + private DockProviderWrapper(AutomationPeer peer, IDockProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); + _peer = peer; + _iface = iface; + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public void SetDockPosition(DockPosition dockPosition) + { + ElementUtil.Invoke(_peer, static (state, dockPosition) => state.SetDockPosition(dockPosition), _iface, dockPosition); + } - private AutomationPeer _peer; - private IDockProvider _iface; + public DockPosition DockPosition + { + get => ElementUtil.Invoke(_peer, static (state) => state.DockPosition, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new DockProviderWrapper(peer, (IDockProvider)iface); } } 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..63a99efae45 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 @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // @@ -10,10 +10,9 @@ using System.Windows; using System.Windows.Automation; -using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; +using System.Windows.Automation.Provider; using System.Windows.Input; -using System.Windows.Threading; namespace MS.Internal.Automation { @@ -25,7 +24,7 @@ namespace MS.Internal.Automation // // Currently exposes just BoundingRectangle, ClassName, IsEnabled // and IsKeyboardFocused properties. - internal class ElementProxy: IRawElementProviderFragmentRoot, IRawElementProviderAdviseEvents + internal class ElementProxy : IRawElementProviderFragmentRoot, IRawElementProviderAdviseEvents { //------------------------------------------------------ // @@ -72,149 +71,105 @@ private ElementProxy(AutomationPeer peer) // IRawElementProviderSimple methods... - public object GetPatternProvider ( int pattern ) + public object GetPatternProvider(int pattern) { - AutomationPeer peer = Peer; - if (peer == null) - { - throw new ElementNotAvailableException(); - } - return ElementUtil.Invoke(peer, new DispatcherOperationCallback( InContextGetPatternProvider ), pattern); + AutomationPeer peer = Peer ?? throw new ElementNotAvailableException(); + + return ElementUtil.Invoke(peer, static (state, pattern) => state.InContextGetPatternProvider(pattern), this, pattern); } public object GetPropertyValue(int property) { - AutomationPeer peer = Peer; - if (peer == null) - { - throw new ElementNotAvailableException(); - } - return ElementUtil.Invoke(peer, new DispatcherOperationCallback(InContextGetPropertyValue), property); + AutomationPeer peer = Peer ?? throw new ElementNotAvailableException(); + + return ElementUtil.Invoke(peer, static (state, property) => state.InContextGetPropertyValue(property), this, property); } public ProviderOptions ProviderOptions { - get + get { AutomationPeer peer = Peer; - if (peer == null) - { - return ProviderOptions.ServerSideProvider; - } - return (ProviderOptions)ElementUtil.Invoke(peer, state => ((ElementProxy)state).InContextGetProviderOptions(), this); + + return peer is null ? ProviderOptions.ServerSideProvider : ElementUtil.Invoke(peer, static (state) => state.InContextGetProviderOptions(), this); } - } + } public IRawElementProviderSimple HostRawElementProvider { get { - IRawElementProviderSimple host = null; - HostedWindowWrapper hwndWrapper = null; AutomationPeer peer = Peer; - if (peer == null) + + if (peer is null) { return null; } - hwndWrapper = (HostedWindowWrapper)ElementUtil.Invoke( - peer, - new DispatcherOperationCallback(InContextGetHostRawElementProvider), - null); - - if(hwndWrapper != null) - host = GetHostHelper(hwndWrapper); - - return host; - } - } - private IRawElementProviderSimple GetHostHelper(HostedWindowWrapper hwndWrapper) - { - return AutomationInteropProvider.HostProviderFromHandle(hwndWrapper.Handle); - } + HostedWindowWrapper hwndWrapper = ElementUtil.Invoke(peer, static (state) => state.InContextGetHostRawElementProvider(), this); - // IRawElementProviderFragment methods... + return hwndWrapper is not null ? AutomationInteropProvider.HostProviderFromHandle(hwndWrapper.Handle) : null; + } + } - public IRawElementProviderFragment Navigate( NavigateDirection direction ) + public IRawElementProviderFragment Navigate(NavigateDirection direction) { AutomationPeer peer = Peer; - if (peer == null) - { - return null; - } - return (IRawElementProviderFragment)ElementUtil.Invoke(peer, new DispatcherOperationCallback(InContextNavigate), direction); + + return peer is not null ? ElementUtil.Invoke(peer, static (state, direction) => state.InContextNavigate(direction), this, direction) : null; } - public int [ ] GetRuntimeId() + public int[] GetRuntimeId() { - AutomationPeer peer = Peer; - if (peer == null) - { - throw new ElementNotAvailableException(); - } - return (int []) ElementUtil.Invoke( peer, state => ((ElementProxy)state).InContextGetRuntimeId(), this); + AutomationPeer peer = Peer ?? throw new ElementNotAvailableException(); + + return ElementUtil.Invoke(peer, static (state) => state.InContextGetRuntimeId(), this); } public Rect BoundingRectangle { - get + get { - AutomationPeer peer = Peer; - if (peer == null) - { - throw new ElementNotAvailableException(); - } - return (Rect)ElementUtil.Invoke(peer, state => ((ElementProxy)state).InContextBoundingRectangle(), this); + AutomationPeer peer = Peer ?? throw new ElementNotAvailableException(); + + return ElementUtil.Invoke(peer, static (state) => state.InContextBoundingRectangle(), this); } } - - public IRawElementProviderSimple [] GetEmbeddedFragmentRoots() + + public IRawElementProviderSimple[] GetEmbeddedFragmentRoots() { return null; } - + public void SetFocus() { - AutomationPeer peer = Peer; - if (peer == null) - { - throw new ElementNotAvailableException(); - } - ElementUtil.Invoke(peer, state => ((ElementProxy)state).InContextSetFocus(), this); + AutomationPeer peer = Peer ?? throw new ElementNotAvailableException(); + + ElementUtil.Invoke(peer, state => state.InContextSetFocus(), this); } public IRawElementProviderFragmentRoot FragmentRoot { - get + get { AutomationPeer peer = Peer; - if (peer == null) - { - return null; - } - return (IRawElementProviderFragmentRoot) ElementUtil.Invoke( peer, state => ((ElementProxy)state).InContextFragmentRoot(), this); + + return peer is not null ? ElementUtil.Invoke(peer, static (state) => state.InContextFragmentRoot(), this) : null; } } - // IRawElementProviderFragmentRoot methods.. - public IRawElementProviderFragment ElementProviderFromPoint( double x, double y ) + public IRawElementProviderFragment ElementProviderFromPoint(double x, double y) { AutomationPeer peer = Peer; - if (peer == null) - { - return null; - } - return (IRawElementProviderFragment) ElementUtil.Invoke( peer, new DispatcherOperationCallback( InContextElementProviderFromPoint ), new Point( x, y ) ); + + return peer is not null ? ElementUtil.Invoke(peer, static (state, point) => state.InContextElementProviderFromPoint(point), this, new Point(x, y)) : null; } public IRawElementProviderFragment GetFocus() { AutomationPeer peer = Peer; - if (peer == null) - { - return null; - } - return (IRawElementProviderFragment) ElementUtil.Invoke( peer, state => ((ElementProxy)state).InContextGetFocus(), this); + + return peer is not null ? ElementUtil.Invoke(peer, static (state) => state.InContextGetFocus(), this) : null; } // Event support: EventMap is a static class and access is synchronized, so no need to access it in UI thread context. @@ -289,9 +244,9 @@ internal AutomationPeer Peer { get { - if (_peer is WeakReference) + if (_peer is WeakReference weakRef) { - AutomationPeer peer = (AutomationPeer)((WeakReference)_peer).Target; + AutomationPeer peer = (AutomationPeer)weakRef.Target; return peer; } else @@ -315,9 +270,9 @@ internal AutomationPeer Peer // The signature of most of the folling methods is "object func( object arg )", // since that's what the Conmtext.Invoke delegate requires. // Return the element at specified coords. - private object InContextElementProviderFromPoint( object arg ) + private IRawElementProviderFragment InContextElementProviderFromPoint(Point arg) { - Point point = (Point)arg; + Point point = arg; AutomationPeer peer = Peer; if (peer == null) { @@ -328,7 +283,7 @@ private object InContextElementProviderFromPoint( object arg ) } // Return proxy representing currently focused element (if any) - private object InContextGetFocus() + private IRawElementProviderFragment InContextGetFocus() { // Note: - what if a custom element - eg anchor in a text box - has focus? // won't have a UIElement there, can we even find the host? @@ -344,20 +299,16 @@ private object InContextGetFocus() } // redirect to AutomationPeer - private object InContextGetPatternProvider(object arg) + private object InContextGetPatternProvider(int arg) { - AutomationPeer peer = Peer; - if (peer == null) - { - throw new ElementNotAvailableException(); - } - return peer.GetWrappedPattern((int)arg); + AutomationPeer peer = Peer ?? throw new ElementNotAvailableException(); + + return peer.GetWrappedPattern(arg); } // Return proxy representing element in specified direction (parent/next/firstchild/etc.) - private object InContextNavigate( object arg ) + private IRawElementProviderFragment InContextNavigate(NavigateDirection navigateDirection) { - NavigateDirection direction = (NavigateDirection) arg; AutomationPeer dest; AutomationPeer peer = Peer; if (peer == null) @@ -365,7 +316,7 @@ private object InContextNavigate( object arg ) return null; } - switch( direction ) + switch (navigateDirection) { case NavigateDirection.Parent: dest = peer.GetParent(); @@ -411,7 +362,7 @@ private object InContextNavigate( object arg ) // Return value for specified property; or null if not supported - private object InContextGetProviderOptions() + private ProviderOptions InContextGetProviderOptions() { ProviderOptions options = ProviderOptions.ServerSideProvider; AutomationPeer peer = Peer; @@ -426,63 +377,47 @@ private object InContextGetProviderOptions() } // Return value for specified property; or null if not supported - private object InContextGetPropertyValue ( object arg ) + private object InContextGetPropertyValue(int property) { - AutomationPeer peer = Peer; - if (peer == null) - { - throw new ElementNotAvailableException(); - } - return peer.GetPropertyValue((int)arg); + AutomationPeer peer = Peer ?? throw new ElementNotAvailableException(); + + return peer.GetPropertyValue(property); } /// Returns whether this is the Root of the WCP tree or not - private object InContextGetHostRawElementProvider( object unused ) + private HostedWindowWrapper InContextGetHostRawElementProvider() { AutomationPeer peer = Peer; - if (peer == null) - { - return null; - } - return peer.GetHostRawElementProvider(); + + return peer?.GetHostRawElementProvider(); } // Return unique ID for this element... - private object InContextGetRuntimeId() + private int[] InContextGetRuntimeId() { - AutomationPeer peer = Peer; - if (peer == null) - { - throw new ElementNotAvailableException(); - } + AutomationPeer peer = Peer ?? throw new ElementNotAvailableException(); + return peer.GetRuntimeId(); } // Return bounding rectangle (screen coords) for this element... - private object InContextBoundingRectangle() + private Rect InContextBoundingRectangle() { - AutomationPeer peer = Peer; - if (peer == null) - { - throw new ElementNotAvailableException(); - } + AutomationPeer peer = Peer ?? throw new ElementNotAvailableException(); + return peer.GetBoundingRectangle(); } // Set focus to this element... - private object InContextSetFocus() + private void InContextSetFocus() { - AutomationPeer peer = Peer; - if (peer == null) - { - throw new ElementNotAvailableException(); - } + AutomationPeer peer = Peer ?? throw new ElementNotAvailableException(); + peer.SetFocus(); - return null; } // Return proxy representing the root of this WCP tree... - private object InContextFragmentRoot() + private IRawElementProviderFragmentRoot InContextFragmentRoot() { AutomationPeer peer = Peer; AutomationPeer root = peer; diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementUtil.ActionInfo.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementUtil.ActionInfo.cs new file mode 100644 index 00000000000..49d3f0e5abd --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementUtil.ActionInfo.cs @@ -0,0 +1,56 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace MS.Internal.Automation; + +/// +/// Utility class for working with . +/// +internal static partial class ElementUtil +{ + /// + /// Wraps the exception from an asynchronous operation, tracking whether the operation has completed or timed out. + /// + [StructLayout(LayoutKind.Auto)] + private readonly struct ActionInfo + { + /// + /// The exception that was thrown during the operation, if any. + /// + internal Exception? StoredException { get; init; } + + /// + /// Gets a value indicating whether the operation has been completed or timed out. + /// + internal bool HasCompleted { get; init; } + + /// + /// Creates a new instance of with the specified exception. + /// + /// The exception that was thrown during the operation. + /// Returns a object. + internal static ActionInfo FromException(Exception exception) + { + return new ActionInfo + { + StoredException = exception, + HasCompleted = true + }; + } + + /// + /// Returns a singleton instance of signalizing successful completion of the operation. + /// + /// Returns a object. + internal static ActionInfo Completed { get; } = new ActionInfo + { + StoredException = null, + HasCompleted = true + }; + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementUtil.ReturnInfo.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementUtil.ReturnInfo.cs new file mode 100644 index 00000000000..baa13b46f3c --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementUtil.ReturnInfo.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace MS.Internal.Automation; + +/// +/// Utility class for working with . +/// +internal static partial class ElementUtil +{ + /// + /// Wraps the return value and exception of an asynchronous operation. + /// + /// The type of the return value. + [StructLayout(LayoutKind.Auto)] + private readonly struct ReturnInfo + { + /// + /// The exception that was thrown during the operation, if any. + /// + internal Exception? StoredException { get; init; } + + /// + /// Gets a value indicating whether the operation has been completed or timed out. + /// + internal bool HasCompleted { get; init; } + + /// + /// The return value of the operation, if it completed successfully. + /// + internal TReturn Value { get; init; } + + /// + /// Creates a new instance of with the specified exception. + /// + /// The exception that was thrown during the operation. + /// Returns a object. + internal static ReturnInfo FromException(Exception exception) + { + return new ReturnInfo + { + StoredException = exception, + HasCompleted = true, + Value = default! + }; + } + + /// + /// Creates a new instance of with the specified value. + /// + /// The return value of the operation. + /// Returns a object. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ReturnInfo FromResult(TReturn value) + { + return new ReturnInfo + { + StoredException = null, + HasCompleted = true, + Value = value + }; + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementUtil.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementUtil.cs index 39a7e4bb601..d3111f958b2 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementUtil.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ElementUtil.cs @@ -1,315 +1,186 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Windows.Threading; -using System.Windows; -using System.Windows.Interop; -using System.Windows.Media; -using System.Windows.Automation; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Windows.Automation.Peers; -using MS.Win32; -using MS.Internal.Media; -using System.Runtime.InteropServices; +using System.Windows.Automation; +using System.Windows.Threading; + +#nullable enable -using MS.Internal.PresentationCore; +namespace MS.Internal.Automation; -namespace MS.Internal.Automation +/// +/// Utility class for working with . +/// +internal static partial class ElementUtil { - // static class providing utility information for working with WCP elements - internal class ElementUtil + /// + /// Provides a helper to invoke work on the UI thread, re-throwing all exceptions on the thread that invoked this execution. + /// + internal static void Invoke(AutomationPeer peer, Action work, TArg arg) { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors + // Null dispatcher likely means the visual is in bad shape + Dispatcher dispatcher = peer.Dispatcher ?? throw new ElementNotAvailableException(); - // static class, so use private ctor - private ElementUtil() { } + ActionInfo retVal = dispatcher.Invoke(ExceptionWrapper, DispatcherPriority.Send, TimeSpan.FromMinutes(3), work, arg); - #endregion Constructors - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static Visual GetParent( Visual el ) + static ActionInfo ExceptionWrapper(Action func, TArg arg) { - return VisualTreeHelper.GetParent(el) as Visual; - } - - internal static Visual GetFirstChild( Visual el ) - { - if (el == null) + try { - return null; + func(arg); + return ActionInfo.Completed; } - - return FindVisibleSibling ( el, 0, true ); - } - - internal static Visual GetLastChild( Visual el ) - { - if (el == null) + catch (Exception e) { - return null; + return ActionInfo.FromException(e); } - - return FindVisibleSibling ( el, el.InternalVisualChildrenCount - 1, false ); } - // Warning: Method is O(N). See FindVisibleSibling function for more information. - internal static Visual GetNextSibling( Visual el ) - { - // To get next/previous sibling, have to find out where we - // are in our parent's children collection (ie. our siblings) - Visual parent = VisualTreeHelper.GetParent(el) as Visual; - // If parent is null, we're at root, so have no siblings - if (parent == null) - { - return null; - } - return FindVisibleSibling ( parent, el, searchForwards: true); - } + // Either throws an exception if the operation did not complete successfully, or does nothing. + HandleActionInfo(dispatcher, retVal); + } - // Warning: Method is O(N). See FindVisibleSibling function for more information. - internal static Visual GetPreviousSibling( Visual el ) - { - // To get next/previous sibling, have to find out where we - // are in our parent's children collection (ie. our siblings) - Visual parent = VisualTreeHelper.GetParent(el) as Visual; - // If parent is null, we're at root, so have no siblings - if (parent == null) - { - return null; - } + /// + /// Provides a helper to invoke work on the UI thread, re-throwing all exceptions on the thread that invoked this execution. + /// + internal static void Invoke(AutomationPeer peer, Action work, TArg1 arg1, TArg2 arg2) + { + // Null dispatcher likely means the visual is in bad shape + Dispatcher dispatcher = peer.Dispatcher ?? throw new ElementNotAvailableException(); - return FindVisibleSibling ( parent, el, searchForwards: false); - } + ActionInfo retVal = dispatcher.Invoke(ExceptionWrapper, DispatcherPriority.Send, TimeSpan.FromMinutes(3), work, arg1, arg2); - internal static Visual GetRoot( Visual el ) + static ActionInfo ExceptionWrapper(Action func, TArg1 arg1, TArg2 arg2) { - // Keep moving up parent chain till we reach the top... - Visual scan = el; - for( ; ; ) + try { - Visual test = VisualTreeHelper.GetParent(scan) as Visual; - if( test == null ) - break; - scan = test; + func(arg1, arg2); + return ActionInfo.Completed; + } + catch (Exception e) + { + return ActionInfo.FromException(e); } - return scan; } - // Get bounding rectangle, in coords relative to root (not screen) - internal static Rect GetLocalRect( UIElement element ) - { - // Get top-most visual. - Visual parent = GetRoot( element ); - - // Get the points for the rectangle and transform them. - double height = element.RenderSize.Height; - double width = element.RenderSize.Width; - Rect rect = new Rect(0, 0, width, height); - - GeneralTransform g = element.TransformToAncestor(parent); - return g.TransformBounds(rect); - } + // Either throws an exception if the operation did not complete successfully, or does nothing. + HandleActionInfo(dispatcher, retVal); + } - // Get bounding rectangle, relative to screen - internal static Rect GetScreenRect( IntPtr hwnd, UIElement el ) - { - Rect rc = GetLocalRect( el ); - - // Map from local to screen coords... - NativeMethods.RECT rcWin32 = new NativeMethods.RECT( (int) rc.Left, (int) rc.Top, (int) rc.Right, (int) rc.Bottom ); - try + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void HandleActionInfo(Dispatcher dispatcher, ActionInfo retVal) + { + if (!retVal.HasCompleted) + { + if (dispatcher.HasShutdownStarted) { - SafeSecurityHelper.TransformLocalRectToScreen(new HandleRef(null, hwnd), ref rcWin32); + ThrowInvalidOperationException(); } - catch (System.ComponentModel.Win32Exception) + else { - return Rect.Empty; + ThrowTimeoutException(); } - - rc = new Rect( rcWin32.left, rcWin32.top, rcWin32.right - rcWin32.left, rcWin32.bottom - rcWin32.top ); - - return rc; } - // Get element at given point (screen coords) - internal static Visual GetElementFromPoint( IntPtr hwnd, Visual root, Point pointScreen ) - { - HwndSource hwndSource = HwndSource.CriticalFromHwnd(hwnd); - - if(hwndSource == null) - return null; - - Point pointClient = PointUtil.ScreenToClient( pointScreen, hwndSource ); - Point pointRoot = PointUtil.ClientToRoot(pointClient, hwndSource); - PointHitTestResult result = VisualTreeUtils.AsNearestPointHitTestResult(VisualTreeHelper.HitTest(root, pointRoot)); - Visual visual = result?.VisualHit; + if (retVal.StoredException is not null) + UnwrapException(retVal.StoredException); + } + /// + /// Provides a helper to invoke work on the UI thread, re-throwing all exceptions on the thread that invoked this execution. + /// + internal static TReturn Invoke(AutomationPeer peer, Func work, TArg arg) + { + // Null dispatcher likely means the visual is in bad shape + Dispatcher dispatcher = peer.Dispatcher ?? throw new ElementNotAvailableException(); - return visual; - } + ReturnInfo retVal = dispatcher.Invoke(ExceptionWrapper, DispatcherPriority.Send, TimeSpan.FromMinutes(3), work, arg); - // Ensures that an element is enabled; throws exception otherwise - internal static void CheckEnabled(Visual visual) + static ReturnInfo ExceptionWrapper(Func func, TArg arg) { - UIElement el = visual as UIElement; - - if( el != null && ! el.IsEnabled ) + try { - throw new ElementNotEnabledException(); + return ReturnInfo.FromResult(func(arg)); + } + catch (Exception e) + { + return ReturnInfo.FromException(e); } } - internal static object Invoke(AutomationPeer peer, DispatcherOperationCallback work, object arg) - { - Dispatcher dispatcher = peer.Dispatcher; + // Either returns the result or throws an exception if the operation did not complete successfully + return HandleReturnValue(dispatcher, in retVal); + } - // Null dispatcher likely means the visual is in bad shape! - if( dispatcher == null ) - { - throw new ElementNotAvailableException(); - } + /// + /// Provides a helper to invoke work on the UI thread, re-throwing all exceptions on the thread that invoked this execution. + /// + internal static TReturn Invoke(AutomationPeer peer, Func work, TArg1 arg1, TArg2 arg2) + { + // Null dispatcher likely means the visual is in bad shape + Dispatcher dispatcher = peer.Dispatcher ?? throw new ElementNotAvailableException(); - Exception remoteException = null; - bool completed = false; + ReturnInfo retVal = dispatcher.Invoke(ExceptionWrapper, DispatcherPriority.Send, TimeSpan.FromMinutes(3), work, arg1, arg2); - object retVal = dispatcher.Invoke( - DispatcherPriority.Send, - TimeSpan.FromMinutes(3), - (DispatcherOperationCallback) delegate(object unused) - { - try - { - return work(arg); - } - catch(Exception e) - { - remoteException = e; - return null; - } - catch //for non-CLS Compliant exceptions - { - remoteException = null; - return null; - } - finally - { - completed = true; - } -}, - null); - - if(completed) + static ReturnInfo ExceptionWrapper(Func func, TArg1 arg1, TArg2 arg2) + { + try { - if(remoteException != null) - { - throw remoteException; - } + return ReturnInfo.FromResult(func(arg1, arg2)); } - else + catch (Exception e) { - bool dispatcherInShutdown = dispatcher.HasShutdownStarted; - - if(dispatcherInShutdown) - { - throw new InvalidOperationException(SR.AutomationDispatcherShutdown); - } - else - { - throw new TimeoutException(SR.AutomationTimeout); - } + return ReturnInfo.FromException(e); } - - return retVal; -} - - #endregion Internal Methods + } - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ + // Either returns the result or throws an exception if the operation did not complete successfully + return HandleReturnValue(dispatcher, in retVal); + } - // Potential enhancement: Consider Visual3Ds in this walk? - // Does this walk need to continue through the Visual3D tree once - // we have UIElement3D? - private static Visual FindVisibleSibling ( Visual parent, int start, bool searchForwards) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TReturn HandleReturnValue(Dispatcher dispatcher, ref readonly ReturnInfo retVal) + { + if (!retVal.HasCompleted) { - int index = start; - int childrenCount = parent.InternalVisualChildrenCount; - - while ( index >= 0 && index < childrenCount ) + if (dispatcher.HasShutdownStarted) { - Visual sibling = parent.InternalGetVisualChild(index); - - // if its visible or something other than a UIElement keep it - if ( !(sibling is UIElement) || (((UIElement)sibling).Visibility == Visibility.Visible ) ) - return sibling; - - index += searchForwards ? 1 : -1; + ThrowInvalidOperationException(); } - - return null; - } - - // Potential enhancement - Consider Visual3Ds in this walk? - // Does this walk need to continue through the Visual3D tree once - // we have UIElement3D? - // - // WARNING: This method is O(N) and can therefore lead to O(N^2) algorithms. - private static Visual FindVisibleSibling(Visual parent, Visual child, bool searchForwards) - { - // - // First we figure out the index of the specified child Visual. This is why the runtime - // of this method is O(n). - - int childrenCount = parent.InternalVisualChildrenCount; - int childIndex; - - for (childIndex = 0; childIndex < childrenCount; childIndex++) + else { - Visual current = parent.InternalGetVisualChild(childIndex); - if (current == child) - { - // Found the child. - break; - } + ThrowTimeoutException(); } - - // - // Now that we have the child index, we can go and lookup the sibling. - if(searchForwards) - return FindVisibleSibling(parent, childIndex+1, searchForwards); // (FindVisibleSibling can deal with out of range indices). - else - return FindVisibleSibling(parent, childIndex-1, searchForwards); // (FindVisibleSibling can deal with out of range indices). } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + if (retVal.StoredException is not null) + UnwrapException(retVal.StoredException); - // static class, so no private fields - - #endregion Private Fields + return retVal.Value; } -} - + /// + /// Throws an indicating that the associated dispatcher has been shut down. + /// + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidOperationException() => throw new InvalidOperationException(SR.AutomationDispatcherShutdown); + + /// + /// Throws a indicating that the automation operation has timed out. + /// + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowTimeoutException() => throw new TimeoutException(SR.AutomationTimeout); + + /// + /// Unwraps the exception and throws it on the current thread. + /// + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + private static void UnwrapException(Exception exception) => throw exception; +} diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ExpandCollapseProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ExpandCollapseProviderWrapper.cs index a543c985d4c..b4b4fe00ea4 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ExpandCollapseProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ExpandCollapseProviderWrapper.cs @@ -1,135 +1,49 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Expand Collapse pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class ExpandCollapseProviderWrapper: MarshalByRefObject, IExpandCollapseProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private ExpandCollapseProviderWrapper( AutomationPeer peer, IExpandCollapseProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IExpandCollapseProvider - // - //------------------------------------------------------ - - #region Interface IExpandCollapseProvider - - public void Expand() - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( Expand ), null ); - } - - public void Collapse() - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( Collapse ), null ); - } - - public ExpandCollapseState ExpandCollapseState - { - get - { - return (ExpandCollapseState) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetExpandCollapseState ), null ); - } - } - - #endregion Interface IExpandCollapseProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new ExpandCollapseProviderWrapper( peer, (IExpandCollapseProvider) iface ); - } +namespace MS.Internal.Automation; - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object Expand( object unused ) - { - _iface.Expand(); - return null; - } - - private object Collapse( object unused ) - { - _iface.Collapse(); - return null; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class ExpandCollapseProviderWrapper : MarshalByRefObject, IExpandCollapseProvider +{ + private readonly AutomationPeer _peer; + private readonly IExpandCollapseProvider _iface; - private object GetExpandCollapseState( object unused ) - { - return _iface.ExpandCollapseState; - } + private ExpandCollapseProviderWrapper(AutomationPeer peer, IExpandCollapseProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - #endregion Private Methods + _peer = peer; + _iface = iface; + } + public void Expand() + { + ElementUtil.Invoke(_peer, static (state) => state.Expand(), _iface); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public void Collapse() + { + ElementUtil.Invoke(_peer, static (state) => state.Collapse(), _iface); + } - private AutomationPeer _peer; - private IExpandCollapseProvider _iface; + public ExpandCollapseState ExpandCollapseState + { + get => ElementUtil.Invoke(_peer, static (state) => state.ExpandCollapseState, _iface); + } - #endregion Private Fields + internal static object Wrap(AutomationPeer peer, object iface) + { + return new ExpandCollapseProviderWrapper(peer, (IExpandCollapseProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/GridItemProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/GridItemProviderWrapper.cs index e7a20f62d47..158b462447d 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/GridItemProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/GridItemProviderWrapper.cs @@ -1,164 +1,61 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Grid Item pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class GridItemProviderWrapper: MarshalByRefObject, IGridItemProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private GridItemProviderWrapper( AutomationPeer peer, IGridItemProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IGridItemProvider - // - //------------------------------------------------------ - - #region Interface IGridItemProvider - - public int Row - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetRow ), null ); - } - } - - public int Column - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetColumn ), null ); - } - } - - public int RowSpan - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetRowSpan ), null ); - } - } - - public int ColumnSpan - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetColumnSpan ), null ); - } - } - - public IRawElementProviderSimple ContainingGrid - { - get - { - return (IRawElementProviderSimple) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetContainingGrid ), null ); - } - } - - #endregion Interface IGridItemProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new GridItemProviderWrapper( peer, (IGridItemProvider) iface ); - } - - #endregion Internal Methods +namespace MS.Internal.Automation; - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object GetRow( object unused ) - { - return _iface.Row; - } - - private object GetColumn( object unused ) - { - return _iface.Column; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class GridItemProviderWrapper : MarshalByRefObject, IGridItemProvider +{ + private readonly AutomationPeer _peer; + private readonly IGridItemProvider _iface; - private object GetRowSpan( object unused ) - { - return _iface.RowSpan; - } + private GridItemProviderWrapper(AutomationPeer peer, IGridItemProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - private object GetColumnSpan( object unused ) - { - return _iface.ColumnSpan; - } + _peer = peer; + _iface = iface; + } - private object GetContainingGrid( object unused ) - { - return _iface.ContainingGrid; - } + public int Row + { + get => ElementUtil.Invoke(_peer, static (state) => state.Row, _iface); + } - #endregion Private Methods + public int Column + { + get => ElementUtil.Invoke(_peer, static (state) => state.Column, _iface); + } + public int RowSpan + { + get => ElementUtil.Invoke(_peer, static (state) => state.RowSpan, _iface); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public int ColumnSpan + { + get => ElementUtil.Invoke(_peer, static (state) => state.ColumnSpan, _iface); + } - private AutomationPeer _peer; - private IGridItemProvider _iface; + public IRawElementProviderSimple ContainingGrid + { + get => ElementUtil.Invoke(_peer, static (state) => state.ContainingGrid, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new GridItemProviderWrapper(peer, (IGridItemProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/GridProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/GridProviderWrapper.cs index 112e0192bf1..a058e34f8f3 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/GridProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/GridProviderWrapper.cs @@ -1,136 +1,51 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Grid pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class GridProviderWrapper: MarshalByRefObject, IGridProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private GridProviderWrapper( AutomationPeer peer, IGridProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IGridProvider - // - //------------------------------------------------------ - - #region Interface IGridProvider - - public IRawElementProviderSimple GetItem(int row, int column) - { - return (IRawElementProviderSimple) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetItem ), new int [ ] { row, column } ); - } - - public int RowCount - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetRowCount ), null ); - } - } - - public int ColumnCount - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetColumnCount ), null ); - } - } - - #endregion Interface IGridProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new GridProviderWrapper( peer, (IGridProvider) iface ); - } +namespace MS.Internal.Automation; - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object GetItem( object arg ) - { - int [ ] coords = (int [ ]) arg; - return _iface.GetItem( coords[ 0 ], coords[ 1 ] ); - } - - private object GetRowCount( object unused ) - { - return _iface.RowCount; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class GridProviderWrapper : MarshalByRefObject, IGridProvider +{ + private readonly AutomationPeer _peer; + private readonly IGridProvider _iface; - private object GetColumnCount( object unused ) - { - return _iface.ColumnCount; - } + private GridProviderWrapper(AutomationPeer peer, IGridProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - #endregion Private Methods + _peer = peer; + _iface = iface; + } + public IRawElementProviderSimple? GetItem(int row, int column) + { + return ElementUtil.Invoke(_peer, static (state, rowColumn) => state.GetItem(rowColumn[0], rowColumn[1]), _iface, new int[] { row, column }); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public int RowCount + { + get => ElementUtil.Invoke(_peer, static (state) => state.RowCount, _iface); + } - private AutomationPeer _peer; - private IGridProvider _iface; + public int ColumnCount + { + get => ElementUtil.Invoke(_peer, static (state) => state.ColumnCount, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new GridProviderWrapper(peer, (IGridProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/InvokeProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/InvokeProviderWrapper.cs index a16b8d814b6..39e6e2d63ae 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/InvokeProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/InvokeProviderWrapper.cs @@ -1,110 +1,41 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Invoke pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class InvokeProviderWrapper: MarshalByRefObject, IInvokeProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private InvokeProviderWrapper( AutomationPeer peer, IInvokeProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IInvokeProvider - // - //------------------------------------------------------ - - #region Interface IInvokeProvider - - public void Invoke() - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( Invoke ), null ); - } - - #endregion Interface IInvokeProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods +namespace MS.Internal.Automation; - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new InvokeProviderWrapper( peer, (IInvokeProvider) iface ); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object Invoke( object unused ) - { - _iface.Invoke(); - return null; - } - - #endregion Private Methods +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class InvokeProviderWrapper : MarshalByRefObject, IInvokeProvider +{ + private readonly AutomationPeer _peer; + private readonly IInvokeProvider _iface; + private InvokeProviderWrapper(AutomationPeer peer, IInvokeProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + _peer = peer; + _iface = iface; + } - private AutomationPeer _peer; - private IInvokeProvider _iface; + public void Invoke() + { + ElementUtil.Invoke(_peer, static (state) => state.Invoke(), _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new InvokeProviderWrapper(peer, (IInvokeProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ItemContainerProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ItemContainerProviderWrapper.cs index 85e9bb26b37..71e663310b9 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ItemContainerProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ItemContainerProviderWrapper.cs @@ -1,115 +1,53 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Item Container pattern provider wrapper for WPF -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WPF Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WPF AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class ItemContainerProviderWrapper : MarshalByRefObject, IItemContainerProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private ItemContainerProviderWrapper(AutomationPeer peer, IItemContainerProvider iface) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors +namespace MS.Internal.Automation; +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class ItemContainerProviderWrapper : MarshalByRefObject, IItemContainerProvider +{ + private readonly AutomationPeer _peer; + private readonly IItemContainerProvider _iface; - //------------------------------------------------------ - // - // Interface IItemContainerProvider - // - //------------------------------------------------------ - - #region Interface IItemContainerProvider - - public IRawElementProviderSimple FindItemByProperty(IRawElementProviderSimple startAfter, int propertyId, object value) - { - object [] args = new object[]{startAfter, propertyId, value}; - return (IRawElementProviderSimple)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(FindItemByProperty), args); - } - - #endregion Interface IItemContainerProvider - + private ItemContainerProviderWrapper(AutomationPeer peer, IItemContainerProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ + _peer = peer; + _iface = iface; + } - #region Internal Methods + public IRawElementProviderSimple? FindItemByProperty(IRawElementProviderSimple? startAfter, int propertyId, object? value) + { + object?[] args = [startAfter, propertyId, value]; - internal static object Wrap(AutomationPeer peer, object iface) + // The actual invocation method that gets called on the peer's context. + static IRawElementProviderSimple? FindItemByProperty(IItemContainerProvider state, object?[] args) { - return new ItemContainerProviderWrapper(peer, (IItemContainerProvider)iface); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods + IRawElementProviderSimple? startAfter = (IRawElementProviderSimple?)args[0]; + int propertyId = (int)args[1]!; + object? value = args[2]; - private object FindItemByProperty(object arg) - { - object[] args = (object[])arg; - IRawElementProviderSimple startAfter = (IRawElementProviderSimple)args[0]; - int propertyId = (int)args[1]; - object value = (object)args[2]; - - return _iface.FindItemByProperty(startAfter, propertyId, value); + return state.FindItemByProperty(startAfter, propertyId, value); } - #endregion Private Methods - - - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields - - private AutomationPeer _peer; - private IItemContainerProvider _iface; + return ElementUtil.Invoke(_peer, FindItemByProperty, _iface, args); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new ItemContainerProviderWrapper(peer, (IItemContainerProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/MultipleViewProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/MultipleViewProviderWrapper.cs index 3507cbba7ff..247a2f84f33 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/MultipleViewProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/MultipleViewProviderWrapper.cs @@ -1,143 +1,56 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Multiple View pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class MultipleViewProviderWrapper: MarshalByRefObject, IMultipleViewProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private MultipleViewProviderWrapper( AutomationPeer peer, IMultipleViewProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IMultipleViewProvider - // - //------------------------------------------------------ - - #region Interface IMultipleViewProvider - - public string GetViewName( int viewID ) - { - return (string) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetViewName ), viewID ); - } - - public void SetCurrentView( int viewID ) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( SetCurrentView ), viewID ); - } - - public int CurrentView - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetCurrentView ), null ); - } - } - - public int [] GetSupportedViews() - { - return (int []) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetSupportedViews ), null ); - } - - #endregion Interface IMultipleViewProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new MultipleViewProviderWrapper( peer, (IMultipleViewProvider) iface ); - } - - #endregion Internal Methods +namespace MS.Internal.Automation; - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object GetViewName( object arg ) - { - return _iface.GetViewName( (int) arg ); - } - - private object SetCurrentView( object arg ) - { - _iface.SetCurrentView( (int) arg ); - return null; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class MultipleViewProviderWrapper : MarshalByRefObject, IMultipleViewProvider +{ + private readonly AutomationPeer _peer; + private readonly IMultipleViewProvider _iface; - private object GetCurrentView( object unused ) - { - return _iface.CurrentView; - } + private MultipleViewProviderWrapper(AutomationPeer peer, IMultipleViewProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - private object GetSupportedViews( object unused ) - { - return _iface.GetSupportedViews(); - } + _peer = peer; + _iface = iface; + } - #endregion Private Methods + public string GetViewName(int viewID) + { + return ElementUtil.Invoke(_peer, static (state, viewID) => state.GetViewName(viewID), _iface, viewID); + } + public void SetCurrentView(int viewID) + { + ElementUtil.Invoke(_peer, static (state, viewID) => state.SetCurrentView(viewID), _iface, viewID); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public int CurrentView + { + get => ElementUtil.Invoke(_peer, static (state) => state.CurrentView, _iface); + } - private AutomationPeer _peer; - private IMultipleViewProvider _iface; + public int[] GetSupportedViews() + { + return ElementUtil.Invoke(_peer, static (state) => state.GetSupportedViews(), _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new MultipleViewProviderWrapper(peer, (IMultipleViewProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/RangeValueProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/RangeValueProviderWrapper.cs index 82905d1e0bb..353d2d9b3aa 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/RangeValueProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/RangeValueProviderWrapper.cs @@ -1,188 +1,71 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Range Value pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class RangeValueProviderWrapper: MarshalByRefObject, IRangeValueProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private RangeValueProviderWrapper( AutomationPeer peer, IRangeValueProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IRangeValueProvider - // - //------------------------------------------------------ - - #region Interface IRangeValueProvider - - public void SetValue( double val ) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( SetValueInternal ), val ); - } - - public double Value - { - get - { - return (double) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetValue ), null ); - } - } - - public bool IsReadOnly - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetIsReadOnly ), null ); - } - } - - public double Maximum - { - get - { - return (double) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetMaximum ), null ); - } - } - - public double Minimum - { - get - { - return (double) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetMinimum ), null ); - } - } - - public double LargeChange - { - get - { - return (double) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetLargeChange ), null ); - } - } - - public double SmallChange - { - get - { - return (double) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetSmallChange ), null ); - } - } - - #endregion Interface IRangeValueProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new RangeValueProviderWrapper( peer, (IRangeValueProvider) iface ); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods +namespace MS.Internal.Automation; - private object SetValueInternal( object arg ) - { - _iface.SetValue( (double)arg ); - return null; - } - - private object GetValue( object unused ) - { - return _iface.Value; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class RangeValueProviderWrapper : MarshalByRefObject, IRangeValueProvider +{ + private readonly AutomationPeer _peer; + private readonly IRangeValueProvider _iface; - private object GetIsReadOnly( object unused ) - { - return _iface.IsReadOnly; - } + private RangeValueProviderWrapper(AutomationPeer peer, IRangeValueProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - private object GetMaximum( object unused ) - { - return _iface.Maximum; - } + _peer = peer; + _iface = iface; + } - private object GetMinimum( object unused ) - { - return _iface.Minimum; - } + public void SetValue(double val) + { + ElementUtil.Invoke(_peer, static (state, val) => state.SetValue(val), _iface, val); + } - private object GetLargeChange( object unused ) - { - return _iface.LargeChange; - } + public double Value + { + get => ElementUtil.Invoke(_peer, static (state) => state.Value, _iface); + } - private object GetSmallChange( object unused ) - { - return _iface.SmallChange; - } + public bool IsReadOnly + { + get => ElementUtil.Invoke(_peer, static (state) => state.IsReadOnly, _iface); + } - #endregion Private Methods + public double Maximum + { + get => ElementUtil.Invoke(_peer, static (state) => state.Maximum, _iface); + } + public double Minimum + { + get => ElementUtil.Invoke(_peer, static (state) => state.Minimum, _iface); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public double LargeChange + { + get => ElementUtil.Invoke(_peer, static (state) => state.LargeChange, _iface); + } - private AutomationPeer _peer; - private IRangeValueProvider _iface; + public double SmallChange + { + get => ElementUtil.Invoke(_peer, static (state) => state.SmallChange, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new RangeValueProviderWrapper(peer, (IRangeValueProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ScrollItemProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ScrollItemProviderWrapper.cs index a1c13459f0d..8c33fb1d14c 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ScrollItemProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ScrollItemProviderWrapper.cs @@ -1,110 +1,41 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Scroll Item pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class ScrollItemProviderWrapper: MarshalByRefObject, IScrollItemProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private ScrollItemProviderWrapper(AutomationPeer peer, IScrollItemProvider iface) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IInvokeProvider - // - //------------------------------------------------------ - - #region Interface IScrollItemProvider - - public void ScrollIntoView() - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( ScrollIntoView ), null ); - } - - #endregion Interface IScrollItemProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods +namespace MS.Internal.Automation; - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new ScrollItemProviderWrapper(peer, (IScrollItemProvider)iface); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object ScrollIntoView(object unused) - { - _iface.ScrollIntoView(); - return null; - } - - #endregion Private Methods +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class ScrollItemProviderWrapper : MarshalByRefObject, IScrollItemProvider +{ + private readonly AutomationPeer _peer; + private readonly IScrollItemProvider _iface; + private ScrollItemProviderWrapper(AutomationPeer peer, IScrollItemProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + _peer = peer; + _iface = iface; + } - private AutomationPeer _peer; - private IScrollItemProvider _iface; + public void ScrollIntoView() + { + ElementUtil.Invoke(_peer, static (state) => state.ScrollIntoView(), _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new ScrollItemProviderWrapper(peer, (IScrollItemProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ScrollProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ScrollProviderWrapper.cs index 0c86bde70c4..6fc355a0762 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ScrollProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ScrollProviderWrapper.cs @@ -1,203 +1,77 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Scroll pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation +namespace MS.Internal.Automation; + +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class ScrollProviderWrapper : MarshalByRefObject, IScrollProvider { - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. + private readonly AutomationPeer _peer; + private readonly IScrollProvider _iface; - internal class ScrollProviderWrapper: MarshalByRefObject, IScrollProvider + private ScrollProviderWrapper(AutomationPeer peer, IScrollProvider iface) { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private ScrollProviderWrapper( AutomationPeer peer, IScrollProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IScrollProvider - // - //------------------------------------------------------ - - #region Interface IScrollProvider - - public void Scroll( ScrollAmount horizontalAmount, ScrollAmount verticalAmount ) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( Scroll ), new ScrollAmount [ ] { horizontalAmount, verticalAmount } ); - } - - public void SetScrollPercent( double horizontalPercent, double verticalPercent ) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( SetScrollPercent ), new double [ ] { horizontalPercent, verticalPercent } ); - } - - public double HorizontalScrollPercent - { - get - { - return (double) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetHorizontalScrollPercent ), null ); - } - } - - public double VerticalScrollPercent - { - get - { - return (double) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetVerticalScrollPercent ), null ); - } - } - - public double HorizontalViewSize - { - get - { - return (double) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetHorizontalViewSize ), null ); - } - } + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - public double VerticalViewSize - { - get - { - return (double) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetVerticalViewSize ), null ); - } - } - - public bool HorizontallyScrollable - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetHorizontallyScrollable ), null ); - } - } - - public bool VerticallyScrollable - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetVerticallyScrollable ), null ); - } - } - - #endregion Interface IScrollProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new ScrollProviderWrapper( peer, (IScrollProvider) iface ); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object Scroll( object arg ) - { - ScrollAmount [ ] args = (ScrollAmount [ ]) arg; - _iface.Scroll( args[ 0 ], args[ 1 ] ); - return null; - } + _peer = peer; + _iface = iface; + } - private object SetScrollPercent( object arg ) - { - double [ ] args = (double [ ]) arg; - _iface.SetScrollPercent( args[ 0 ], args[ 1 ] ); - return null; - } - - private object GetHorizontalScrollPercent( object unused ) - { - return _iface.HorizontalScrollPercent; - } + public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount) + { + ElementUtil.Invoke(_peer, static (state, values) => state.Scroll(values[0], values[1]), _iface, new ScrollAmount[] { horizontalAmount, verticalAmount }); + } - private object GetVerticalScrollPercent( object unused ) - { - return _iface.VerticalScrollPercent; - } + public void SetScrollPercent(double horizontalPercent, double verticalPercent) + { + ElementUtil.Invoke(_peer, static (state, values) => state.SetScrollPercent(values[0], values[1]), _iface, new double[] { horizontalPercent, verticalPercent }); + } - private object GetHorizontalViewSize( object unused ) - { - return _iface.HorizontalViewSize; - } + public double HorizontalScrollPercent + { + get => ElementUtil.Invoke(_peer, static (state) => state.HorizontalScrollPercent, _iface); + } - private object GetVerticalViewSize( object unused ) - { - return _iface.VerticalViewSize; - } - - private object GetHorizontallyScrollable( object unused ) - { - return _iface.HorizontallyScrollable; - } - - private object GetVerticallyScrollable( object unused ) - { - return _iface.VerticallyScrollable; - } + public double VerticalScrollPercent + { + get => ElementUtil.Invoke(_peer, static (state) => state.VerticalScrollPercent, _iface); + } - #endregion Private Methods + public double HorizontalViewSize + { + get => ElementUtil.Invoke(_peer, static (state) => state.HorizontalViewSize, _iface); + } + public double VerticalViewSize + { + get => ElementUtil.Invoke(_peer, static (state) => state.VerticalViewSize, _iface); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public bool HorizontallyScrollable + { + get => ElementUtil.Invoke(_peer, static (state) => state.HorizontallyScrollable, _iface); + } - private AutomationPeer _peer; - private IScrollProvider _iface; + public bool VerticallyScrollable + { + get => ElementUtil.Invoke(_peer, static (state) => state.VerticallyScrollable, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new ScrollProviderWrapper(peer, (IScrollProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SelectionItemProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SelectionItemProviderWrapper.cs index 8c2305e6f22..0bb01ba18ca 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SelectionItemProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SelectionItemProviderWrapper.cs @@ -1,159 +1,61 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Selection Item pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation +namespace MS.Internal.Automation; + +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class SelectionItemProviderWrapper : MarshalByRefObject, ISelectionItemProvider { - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. + private readonly AutomationPeer _peer; + private readonly ISelectionItemProvider _iface; - internal class SelectionItemProviderWrapper: MarshalByRefObject, ISelectionItemProvider + private SelectionItemProviderWrapper(AutomationPeer peer, ISelectionItemProvider iface) { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private SelectionItemProviderWrapper( AutomationPeer peer, ISelectionItemProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface ISelectionItemProvider - // - //------------------------------------------------------ - - #region Interface ISelectionItemProvider - - public void Select() - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( Select ), null ); - } - - public void AddToSelection() - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( AddToSelection ), null ); - } - - public void RemoveFromSelection() - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( RemoveFromSelection ), null ); - } - - public bool IsSelected - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetIsSelected ), null ); - } - } - - public IRawElementProviderSimple SelectionContainer - { - get - { - return (IRawElementProviderSimple) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetSelectionContainer ), null ); - } - } - - #endregion Interface ISelectionItemProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new SelectionItemProviderWrapper( peer, (ISelectionItemProvider) iface ); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - private object Select( object unused ) - { - _iface.Select(); - return null; - } - - private object AddToSelection( object unused ) - { - _iface.AddToSelection(); - return null; - } - - private object RemoveFromSelection( object unused ) - { - _iface.RemoveFromSelection(); - return null; - } - - private object GetIsSelected( object unused ) - { - return _iface.IsSelected; - } + _peer = peer; + _iface = iface; + } - private object GetSelectionContainer( object unused ) - { - return _iface.SelectionContainer; - } + public void Select() + { + ElementUtil.Invoke(_peer, static (state) => state.Select(), _iface); + } - #endregion Private Methods + public void AddToSelection() + { + ElementUtil.Invoke(_peer, static (state) => state.AddToSelection(), _iface); + } + public void RemoveFromSelection() + { + ElementUtil.Invoke(_peer, static (state) => state.RemoveFromSelection(), _iface); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public bool IsSelected + { + get => ElementUtil.Invoke(_peer, static (state) => state.IsSelected, _iface); + } - private AutomationPeer _peer; - private ISelectionItemProvider _iface; + public IRawElementProviderSimple SelectionContainer + { + get => ElementUtil.Invoke(_peer, static (state) => state.SelectionContainer, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new SelectionItemProviderWrapper(peer, (ISelectionItemProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SelectionProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SelectionProviderWrapper.cs index 9e19f682bac..3f136f4cb51 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SelectionProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SelectionProviderWrapper.cs @@ -1,135 +1,51 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Selection pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class SelectionProviderWrapper: MarshalByRefObject, ISelectionProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private SelectionProviderWrapper( AutomationPeer peer, ISelectionProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface ISelectionProvider - // - //------------------------------------------------------ - - #region Interface ISelectionProvider - - public IRawElementProviderSimple [] GetSelection() - { - return (IRawElementProviderSimple []) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetSelection ), null ); - } - - public bool CanSelectMultiple - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetCanSelectMultiple ), null ); - } - } - - public bool IsSelectionRequired - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetIsSelectionRequired ), null ); - } - } - - #endregion Interface ISelectionProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new SelectionProviderWrapper( peer, (ISelectionProvider) iface ); - } +namespace MS.Internal.Automation; - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object GetSelection( object unused ) - { - return _iface.GetSelection(); - } - - private object GetCanSelectMultiple( object unused ) - { - return _iface.CanSelectMultiple; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class SelectionProviderWrapper : MarshalByRefObject, ISelectionProvider +{ + private readonly AutomationPeer _peer; + private readonly ISelectionProvider _iface; - private object GetIsSelectionRequired( object unused ) - { - return _iface.IsSelectionRequired; - } + private SelectionProviderWrapper(AutomationPeer peer, ISelectionProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - #endregion Private Methods + _peer = peer; + _iface = iface; + } + public IRawElementProviderSimple[] GetSelection() + { + return ElementUtil.Invoke(_peer, static (state) => state.GetSelection(), _iface); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public bool CanSelectMultiple + { + get => ElementUtil.Invoke(_peer, static (state) => state.CanSelectMultiple, _iface); + } - private AutomationPeer _peer; - private ISelectionProvider _iface; + public bool IsSelectionRequired + { + get => ElementUtil.Invoke(_peer, static (state) => state.IsSelectionRequired, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new SelectionProviderWrapper(peer, (ISelectionProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SynchronizedInputProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SynchronizedInputProviderWrapper.cs index dbcb5d145ae..dbd0defeabd 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SynchronizedInputProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/SynchronizedInputProviderWrapper.cs @@ -1,119 +1,47 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: SynchronizedInput pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class SynchronizedInputProviderWrapper: MarshalByRefObject, ISynchronizedInputProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private SynchronizedInputProviderWrapper( AutomationPeer peer, ISynchronizedInputProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface ISynchronizedInputProvider - // - //------------------------------------------------------ - - #region Interface ISynchronizedInputProvider - - public void StartListening(SynchronizedInputType inputType) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( StartListening ), inputType ); -} - - public void Cancel() - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( Cancel ), null ); -} - #endregion Interface ISynchronizedInputProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods +namespace MS.Internal.Automation; - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new SynchronizedInputProviderWrapper( peer, (ISynchronizedInputProvider) iface ); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class SynchronizedInputProviderWrapper : MarshalByRefObject, ISynchronizedInputProvider +{ + private readonly AutomationPeer _peer; + private readonly ISynchronizedInputProvider _iface; - private object StartListening( object arg ) - { - _iface.StartListening((SynchronizedInputType)arg); - return null; -} - private object Cancel( object unused ) - { - _iface.Cancel(); - return null; -} - #endregion Private Methods + private SynchronizedInputProviderWrapper(AutomationPeer peer, ISynchronizedInputProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); + _peer = peer; + _iface = iface; + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public void StartListening(SynchronizedInputType inputType) + { + ElementUtil.Invoke(_peer, static (state, inputType) => state.StartListening(inputType), _iface, inputType); + } - private AutomationPeer _peer; - private ISynchronizedInputProvider _iface; + public void Cancel() + { + ElementUtil.Invoke(_peer, static (state) => state.Cancel(), _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new SynchronizedInputProviderWrapper(peer, (ISynchronizedInputProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TableItemProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TableItemProviderWrapper.cs index 6f894d8857d..b399e4cee06 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TableItemProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TableItemProviderWrapper.cs @@ -1,184 +1,71 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Table Item pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class TableItemProviderWrapper: MarshalByRefObject, ITableItemProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private TableItemProviderWrapper( AutomationPeer peer, ITableItemProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface ITableItemProvider - // - //------------------------------------------------------ - - #region Interface ITableItemProvider - - public int Row - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetRow ), null ); - } - } - - public int Column - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetColumn ), null ); - } - } - - public int RowSpan - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetRowSpan ), null ); - } - } - - public int ColumnSpan - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetColumnSpan ), null ); - } - } - - public IRawElementProviderSimple ContainingGrid - { - get - { - return (IRawElementProviderSimple) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetContainingGrid ), null ); - } - } - - public IRawElementProviderSimple [] GetRowHeaderItems() - { - return (IRawElementProviderSimple []) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetRowHeaderItems ), null ); - } - - public IRawElementProviderSimple [] GetColumnHeaderItems() - { - return (IRawElementProviderSimple []) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetColumnHeaderItems ), null ); - } - - #endregion Interface ITableItemProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new TableItemProviderWrapper( peer, (ITableItemProvider) iface ); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods +namespace MS.Internal.Automation; - private object GetRow( object unused ) - { - return _iface.Row; - } - - private object GetColumn( object unused ) - { - return _iface.Column; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class TableItemProviderWrapper : MarshalByRefObject, ITableItemProvider +{ + private readonly AutomationPeer _peer; + private readonly ITableItemProvider _iface; - private object GetRowSpan( object unused ) - { - return _iface.RowSpan; - } + private TableItemProviderWrapper(AutomationPeer peer, ITableItemProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - private object GetColumnSpan( object unused ) - { - return _iface.ColumnSpan; - } + _peer = peer; + _iface = iface; + } - private object GetContainingGrid( object unused ) - { - return _iface.ContainingGrid; - } + public int Row + { + get => ElementUtil.Invoke(_peer, static (state) => state.Row, _iface); + } - private object GetRowHeaderItems( object unused ) - { - return _iface.GetRowHeaderItems(); - } + public int Column + { + get => ElementUtil.Invoke(_peer, static (state) => state.Column, _iface); + } - private object GetColumnHeaderItems( object unused ) - { - return _iface.GetColumnHeaderItems(); - } + public int RowSpan + { + get => ElementUtil.Invoke(_peer, static (state) => state.RowSpan, _iface); + } - #endregion Private Methods + public int ColumnSpan + { + get => ElementUtil.Invoke(_peer, static (state) => state.ColumnSpan, _iface); + } + public IRawElementProviderSimple ContainingGrid + { + get => ElementUtil.Invoke(_peer, static (state) => state.ContainingGrid, _iface); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public IRawElementProviderSimple[] GetRowHeaderItems() + { + return ElementUtil.Invoke(_peer, static (state) => state.GetRowHeaderItems(), _iface); + } - private AutomationPeer _peer; - private ITableItemProvider _iface; + public IRawElementProviderSimple[] GetColumnHeaderItems() + { + return ElementUtil.Invoke(_peer, static (state) => state.GetColumnHeaderItems(), _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new TableItemProviderWrapper(peer, (ITableItemProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TableProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TableProviderWrapper.cs index dae898b1883..6a0232e9205 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TableProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TableProviderWrapper.cs @@ -1,170 +1,67 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Table pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class TableProviderWrapper: MarshalByRefObject, ITableProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private TableProviderWrapper( AutomationPeer peer, ITableProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface ITableProvider - // - //------------------------------------------------------ - - #region Interface ITableProvider - - public IRawElementProviderSimple GetItem(int row, int column) - { - return (IRawElementProviderSimple) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetItem ), new int [ ] { row, column } ); - } - - public int RowCount - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetRowCount ), null ); - } - } - - public int ColumnCount - { - get - { - return (int) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetColumnCount ), null ); - } - } - - public IRawElementProviderSimple [] GetRowHeaders() - { - return (IRawElementProviderSimple []) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetRowHeaders ), null ); - } - - public IRawElementProviderSimple [] GetColumnHeaders() - { - return (IRawElementProviderSimple []) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetColumnHeaders ), null ); - } - - public RowOrColumnMajor RowOrColumnMajor - { - get - { - return (RowOrColumnMajor) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetRowOrColumnMajor ), null ); - } - } - - #endregion Interface ITableProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new TableProviderWrapper( peer, (ITableProvider) iface ); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods +namespace MS.Internal.Automation; - private object GetItem( object arg ) - { - int [ ] coords = (int [ ]) arg; - return _iface.GetItem( coords[ 0 ], coords[ 1 ] ); - } - - private object GetRowCount( object unused ) - { - return _iface.RowCount; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class TableProviderWrapper : MarshalByRefObject, ITableProvider +{ + private readonly AutomationPeer _peer; + private readonly ITableProvider _iface; - private object GetColumnCount( object unused ) - { - return _iface.ColumnCount; - } + private TableProviderWrapper(AutomationPeer peer, ITableProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - private object GetRowHeaders( object unused ) - { - return _iface.GetRowHeaders(); - } + _peer = peer; + _iface = iface; + } - private object GetColumnHeaders( object unused ) - { - return _iface.GetColumnHeaders(); - } + public IRawElementProviderSimple? GetItem(int row, int column) + { + return ElementUtil.Invoke(_peer, static (state, rowColumn) => state.GetItem(rowColumn[0], rowColumn[1]), _iface, new int[] { row, column }); + } - private object GetRowOrColumnMajor( object unused ) - { - return _iface.RowOrColumnMajor; - } + public int RowCount + { + get => ElementUtil.Invoke(_peer, static (state) => state.RowCount, _iface); + } - #endregion Private Methods + public int ColumnCount + { + get => ElementUtil.Invoke(_peer, static (state) => state.ColumnCount, _iface); + } + public IRawElementProviderSimple[] GetRowHeaders() + { + return ElementUtil.Invoke(_peer, static (state) => state.GetRowHeaders(), _iface); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public IRawElementProviderSimple[] GetColumnHeaders() + { + return ElementUtil.Invoke(_peer, static (state) => state.GetColumnHeaders(), _iface); + } - private AutomationPeer _peer; - private ITableProvider _iface; + public RowOrColumnMajor RowOrColumnMajor + { + get => ElementUtil.Invoke(_peer, static (state) => state.RowOrColumnMajor, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new TableProviderWrapper(peer, (ITableProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TextProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TextProviderWrapper.cs index f8c60784e16..c673578a20f 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TextProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TextProviderWrapper.cs @@ -1,157 +1,84 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// Description: Text pattern provider wrapper for WCP +#nullable enable -using System.Windows.Threading; using System.Windows; using System.Windows.Automation; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // see comment on InvokeProviderWrapper class for explanation of purpose and organization of these wrapper classes. - internal class TextProviderWrapper : MarshalByRefObject, ITextProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private TextProviderWrapper( AutomationPeer peer, ITextProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface ITextProvider - // - //------------------------------------------------------ - - #region Interface ITextProvider - - public ITextRangeProvider [] GetSelection() - { - return (ITextRangeProvider [])ElementUtil.Invoke(_peer, new DispatcherOperationCallback(GetSelection), null); - } - - public ITextRangeProvider [] GetVisibleRanges() - { - return (ITextRangeProvider[])ElementUtil.Invoke(_peer, new DispatcherOperationCallback(GetVisibleRanges), null); - } - - public ITextRangeProvider RangeFromChild(IRawElementProviderSimple childElement) - { - if (!(childElement is ElementProxy)) - { - throw new ArgumentException(SR.Format(SR.TextProvider_InvalidChild, "childElement")); - } - - return (ITextRangeProvider)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(RangeFromChild), childElement); - } - - public ITextRangeProvider RangeFromPoint(Point screenLocation) - { - return (ITextRangeProvider)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(RangeFromPoint), screenLocation); - } - - public ITextRangeProvider DocumentRange - { - get - { - return (ITextRangeProvider)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(GetDocumentRange), null); - } - } - - public SupportedTextSelection SupportedTextSelection - { - get - { - return (SupportedTextSelection)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(GetSupportedTextSelection), null); - } - } - - #endregion Interface ITextProvider - +namespace MS.Internal.Automation; - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new TextProviderWrapper( peer, (ITextProvider) iface ); - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class TextProviderWrapper : MarshalByRefObject, ITextProvider +{ + private readonly AutomationPeer _peer; + private readonly ITextProvider _iface; - #endregion Internal Methods + private TextProviderWrapper(AutomationPeer peer, ITextProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods + _peer = peer; + _iface = iface; + } - private object GetSelection(object unused) - { - return TextRangeProviderWrapper.WrapArgument( _iface.GetSelection(), _peer ); - } + public ITextRangeProvider[] GetSelection() + { + return ElementUtil.Invoke(_peer, static (state, peer) => TextRangeProviderWrapper.WrapArgument(state.GetSelection(), peer), _iface, _peer); + } - private object GetVisibleRanges(object unused) - { - return TextRangeProviderWrapper.WrapArgument( _iface.GetVisibleRanges(), _peer ); - } + public ITextRangeProvider[] GetVisibleRanges() + { + return ElementUtil.Invoke(_peer, static (state, peer) => TextRangeProviderWrapper.WrapArgument(state.GetVisibleRanges(), peer), _iface, _peer); + } - private object RangeFromChild(object arg) - { - IRawElementProviderSimple childElement = (IRawElementProviderSimple)arg; - return TextRangeProviderWrapper.WrapArgument( _iface.RangeFromChild(childElement), _peer ); - } + public ITextRangeProvider RangeFromChild(IRawElementProviderSimple childElement) + { + if (childElement is not ElementProxy) + throw new ArgumentException(SR.Format(SR.TextProvider_InvalidChild, nameof(childElement))); - private object RangeFromPoint(object arg) + // The actual invocation method that gets called on the peer's context. + static ITextRangeProvider RangeFromChild(TextProviderWrapper state, IRawElementProviderSimple childElement) { - Point screenLocation = (Point)arg; - return TextRangeProviderWrapper.WrapArgument( _iface.RangeFromPoint(screenLocation), _peer ); + return TextRangeProviderWrapper.WrapArgument(state._iface.RangeFromChild(childElement), state._peer); } - private object GetDocumentRange(object unused) - { - return TextRangeProviderWrapper.WrapArgument( _iface.DocumentRange, _peer ); - } + return ElementUtil.Invoke(_peer, RangeFromChild, this, childElement); + } - private object GetSupportedTextSelection(object unused) + public ITextRangeProvider RangeFromPoint(Point screenLocation) + { + // The actual invocation method that gets called on the peer's context. + static ITextRangeProvider RangeFromPoint(TextProviderWrapper state, Point screenLocation) { - return _iface.SupportedTextSelection; + return TextRangeProviderWrapper.WrapArgument(state._iface.RangeFromPoint(screenLocation), state._peer); } - #endregion Private Methods + return ElementUtil.Invoke(_peer, RangeFromPoint, this, screenLocation); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public ITextRangeProvider DocumentRange + { + get => ElementUtil.Invoke(_peer, static (state, peer) => TextRangeProviderWrapper.WrapArgument(state.DocumentRange, peer), _iface, _peer); + } - private AutomationPeer _peer; - private ITextProvider _iface; + public SupportedTextSelection SupportedTextSelection + { + get => ElementUtil.Invoke(_peer, static (state) => state.SupportedTextSelection, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new TextProviderWrapper(peer, (ITextProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TextRangeProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TextRangeProviderWrapper.cs index 2af9bd62450..7b5aa5096b4 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TextRangeProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TextRangeProviderWrapper.cs @@ -1,361 +1,241 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// Description: TextRange provider wrapper for WCP +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Text; using System.Windows.Automation.Peers; +using System.Diagnostics.CodeAnalysis; -namespace MS.Internal.Automation +namespace MS.Internal.Automation; + +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class TextRangeProviderWrapper : MarshalByRefObject, ITextRangeProvider { - // see comment on InvokeProviderWrapper class for explanation of purpose and organization of these wrapper classes. - internal class TextRangeProviderWrapper: MarshalByRefObject, ITextRangeProvider + private readonly AutomationPeer _peer; + private readonly ITextRangeProvider _iface; + + private TextRangeProviderWrapper(AutomationPeer peer, ITextRangeProvider iface) { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - internal TextRangeProviderWrapper( AutomationPeer peer, ITextRangeProvider iface ) - { - _peer = peer; - _iface = iface; - } + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - #endregion Constructors + _peer = peer; + _iface = iface; + } + public ITextRangeProvider Clone() + { + return ElementUtil.Invoke(_peer, static (state, peer) => WrapArgument(state.Clone(), peer), _iface, _peer); + } - //------------------------------------------------------ - // - // Interface ITextRangeProvider - // - //------------------------------------------------------ - - #region Interface ITextRangeProvider + public bool Compare(ITextRangeProvider range) + { + if (range is not TextRangeProviderWrapper) + throw new ArgumentException(SR.Format(SR.TextRangeProvider_InvalidRangeProvider, nameof(range))); - public ITextRangeProvider Clone() - { - return (ITextRangeProvider)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(Clone), null); - } + // Note: We always need to unwrap the range argument here. + return ElementUtil.Invoke(_peer, static (state, range) => state.Compare(UnwrapArgument(range)), _iface, range); + } - public bool Compare(ITextRangeProvider range) - { - if (!(range is TextRangeProviderWrapper)) - { - throw new ArgumentException(SR.Format(SR.TextRangeProvider_InvalidRangeProvider, "range")); - } + public int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint) + { + if (targetRange is not TextRangeProviderWrapper) + throw new ArgumentException(SR.Format(SR.TextRangeProvider_InvalidRangeProvider, nameof(targetRange))); - return (bool)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(Compare), range); - } + object[] args = [endpoint, targetRange, targetEndpoint]; - public int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint) + // The actual invocation method that gets called on the peer's context. + static int CompareEndpoints(ITextRangeProvider state, object[] args) { - if (!(targetRange is TextRangeProviderWrapper)) - { - throw new ArgumentException(SR.Format(SR.TextRangeProvider_InvalidRangeProvider, "targetRange")); - } + TextPatternRangeEndpoint endpoint = (TextPatternRangeEndpoint)args[0]; + ITextRangeProvider targetRange = (ITextRangeProvider)args[1]; + TextPatternRangeEndpoint targetEndpoint = (TextPatternRangeEndpoint)args[2]; - object[] args = new object[] { endpoint, targetRange, targetEndpoint }; - return (int)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(CompareEndpoints), args); + return state.CompareEndpoints(endpoint, UnwrapArgument(targetRange), targetEndpoint); } - public void ExpandToEnclosingUnit(TextUnit unit) - { - object[] args = new object[] { unit }; - ElementUtil.Invoke(_peer, new DispatcherOperationCallback(ExpandToEnclosingUnit), args); - } + return ElementUtil.Invoke(_peer, CompareEndpoints, _iface, args); + } - public ITextRangeProvider FindAttribute(int attribute, object val, bool backward) - { - object[] args = new object[] { attribute, val, backward }; - return (ITextRangeProvider)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(FindAttribute), args); - } + public void ExpandToEnclosingUnit(TextUnit unit) + { + ElementUtil.Invoke(_peer, static (state, unit) => state.ExpandToEnclosingUnit(unit), _iface, unit); + } - public ITextRangeProvider FindText(string text, bool backward, bool ignoreCase) - { - object[] args = new object[] { text, backward, ignoreCase }; - return (ITextRangeProvider)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(FindText), args); - } + public ITextRangeProvider? FindAttribute(int attribute, object val, bool backward) + { + object[] args = [attribute, val, backward]; - public object GetAttributeValue(int attribute) + // The actual invocation method that gets called on the peer's context. + static ITextRangeProvider? FindAttribute(TextRangeProviderWrapper state, object[] args) { - object[] args = new object[] { attribute }; - return ElementUtil.Invoke(_peer, new DispatcherOperationCallback(GetAttributeValue), args); - } + int attribute = (int)args[0]; + object val = args[1]; + bool backward = (bool)args[2]; - public double [] GetBoundingRectangles() - { - return (double [])ElementUtil.Invoke(_peer, new DispatcherOperationCallback(GetBoundingRectangles), null); + return WrapArgument(state._iface.FindAttribute(attribute, val, backward), state._peer); } - public IRawElementProviderSimple GetEnclosingElement() - { - return (IRawElementProviderSimple)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(GetEnclosingElement), null); - } + return ElementUtil.Invoke(_peer, FindAttribute, this, args); + } - public string GetText(int maxLength) - { - object[] args = new object[] {maxLength}; - return (string)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(GetText), args); - } + public ITextRangeProvider? FindText(string text, bool backward, bool ignoreCase) + { + object[] args = [text, backward, ignoreCase]; - public int Move(TextUnit unit, int count) + // The actual invocation method that gets called on the peer's context. + static ITextRangeProvider? FindText(TextRangeProviderWrapper state, object[] args) { - object[] args = new object[] { unit, count }; - return (int)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(Move), args); - } + string text = (string)args[0]; + bool backward = (bool)args[1]; + bool ignoreCase = (bool)args[2]; - public int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count) - { - object[] args = new object[] { endpoint, unit, count }; - return (int)ElementUtil.Invoke(_peer, new DispatcherOperationCallback(MoveEndpointByUnit), args); + return WrapArgument(state._iface.FindText(text, backward, ignoreCase), state._peer); } - public void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint) - { - if (!(targetRange is TextRangeProviderWrapper)) - { - throw new ArgumentException(SR.Format(SR.TextRangeProvider_InvalidRangeProvider, "targetRange")); - } + return ElementUtil.Invoke(_peer, FindText, this, args); + } - object[] args = new object[] { endpoint, targetRange, targetEndpoint }; - ElementUtil.Invoke(_peer, new DispatcherOperationCallback(MoveEndpointByRange), args); - } + public object GetAttributeValue(int attribute) + { + // Note: If an attribute value is ever a range then we'll need to wrap/unwrap it appropriately here. + return ElementUtil.Invoke(_peer, static (state, attribute) => state.GetAttributeValue(attribute), _iface, attribute); + } - public void Select() - { - ElementUtil.Invoke(_peer, new DispatcherOperationCallback(Select), null); - } + public double[] GetBoundingRectangles() + { + return ElementUtil.Invoke(_peer, static (state) => state.GetBoundingRectangles(), _iface); + } - public void AddToSelection() - { - ElementUtil.Invoke(_peer, new DispatcherOperationCallback(AddToSelection), null); - } + public IRawElementProviderSimple GetEnclosingElement() + { + return ElementUtil.Invoke(_peer, static (state) => state.GetEnclosingElement(), _iface); + } - public void RemoveFromSelection() - { - ElementUtil.Invoke(_peer, new DispatcherOperationCallback(RemoveFromSelection), null); - } + public string GetText(int maxLength) + { + return ElementUtil.Invoke(_peer, static (state, maxLength) => state.GetText(maxLength), _iface, maxLength); + } - public void ScrollIntoView(bool alignToTop) - { - ElementUtil.Invoke(_peer, new DispatcherOperationCallback(ScrollIntoView), alignToTop); - } + public int Move(TextUnit unit, int count) + { + object[] args = [unit, count]; - public IRawElementProviderSimple[] GetChildren() + // The actual invocation method that gets called on the peer's context. + static int Move(ITextRangeProvider state, object[] args) { - return (IRawElementProviderSimple[])ElementUtil.Invoke(_peer, new DispatcherOperationCallback(GetChildren), null); - } - + TextUnit unit = (TextUnit)args[0]; + int count = (int)args[1]; - #endregion Interface ITextRangeProvider + return state.Move(unit, count); + } + return ElementUtil.Invoke(_peer, Move, _iface, args); + } - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods + public int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count) + { + object[] args = [endpoint, unit, count]; - // Wrap arguments that are being returned, assuming they're not null or already wrapped. - internal static ITextRangeProvider WrapArgument(ITextRangeProvider argument, AutomationPeer peer) + // The actual invocation method that gets called on the peer's context. + static int MoveEndpointByUnit(ITextRangeProvider state, object[] args) { - if (argument == null) - return null; - - if (argument is TextRangeProviderWrapper) - return argument; - - return new TextRangeProviderWrapper(peer, argument); - } + TextPatternRangeEndpoint endpoint = (TextPatternRangeEndpoint)args[0]; + TextUnit unit = (TextUnit)args[1]; + int count = (int)args[2]; - internal static ITextRangeProvider [] WrapArgument(ITextRangeProvider [] argument, AutomationPeer peer) - { - if (argument == null) - return null; - - if (argument is TextRangeProviderWrapper []) - return argument; - - ITextRangeProvider[] outArray = new ITextRangeProvider[argument.Length]; - for (int i = 0; i < argument.Length; i++) - { - outArray[i] = WrapArgument(argument[i], peer); - } - return outArray; + return state.MoveEndpointByUnit(endpoint, unit, count); } - // Remove the wrapper from the argument if a wrapper exists - internal static ITextRangeProvider UnwrapArgument(ITextRangeProvider argument) - { - if (argument is TextRangeProviderWrapper) - { - return ((TextRangeProviderWrapper)argument)._iface; - } - - return argument; - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods + return ElementUtil.Invoke(_peer, MoveEndpointByUnit, _iface, args); + } - private object Clone(object unused) - { - return TextRangeProviderWrapper.WrapArgument( _iface.Clone(), _peer ); - } + public void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint) + { + if (targetRange is not TextRangeProviderWrapper) + throw new ArgumentException(SR.Format(SR.TextRangeProvider_InvalidRangeProvider, nameof(targetRange))); - private object Compare(object arg) - { - ITextRangeProvider range = (ITextRangeProvider)arg; - return _iface.Compare( TextRangeProviderWrapper.UnwrapArgument( range ) ); - } + object[] args = [endpoint, targetRange, targetEndpoint]; - private object CompareEndpoints(object arg) + // The actual invocation method that gets called on the peer's context. + static void MoveEndpointByRange(ITextRangeProvider state, object[] args) { - object[] args = (object[])arg; TextPatternRangeEndpoint endpoint = (TextPatternRangeEndpoint)args[0]; ITextRangeProvider targetRange = (ITextRangeProvider)args[1]; TextPatternRangeEndpoint targetEndpoint = (TextPatternRangeEndpoint)args[2]; - return _iface.CompareEndpoints(endpoint, TextRangeProviderWrapper.UnwrapArgument( targetRange ), targetEndpoint); - } - - private object ExpandToEnclosingUnit(object arg) - { - object[] args = (object[])arg; - TextUnit unit = (TextUnit)args[0]; - _iface.ExpandToEnclosingUnit(unit); - return null; - } - - private object FindAttribute(object arg) - { - object[] args = (object[])arg; - int attribute = (int)args[0]; - object val = args[1]; - bool backward = (bool)args[2]; - return TextRangeProviderWrapper.WrapArgument( _iface.FindAttribute(attribute, val, backward), _peer ); - } - private object FindText(object arg) - { - object[] args = (object[])arg; - string text = (string)args[0]; - bool backward = (bool)args[1]; - bool ignoreCase = (bool)args[2]; - return TextRangeProviderWrapper.WrapArgument( _iface.FindText(text, backward, ignoreCase), _peer ); + state.MoveEndpointByRange(endpoint, UnwrapArgument(targetRange), targetEndpoint); } - private object GetAttributeValue(object arg) - { - object[] args = (object[])arg; - int attribute = (int)args[0]; - return _iface.GetAttributeValue(attribute); - // note: if an attribute value is ever a range then we'll need to wrap/unwrap it appropriately here. - } + ElementUtil.Invoke(_peer, MoveEndpointByRange, _iface, args); + } - private object GetBoundingRectangles(object unused) - { - return _iface.GetBoundingRectangles(); - } + public void Select() + { + ElementUtil.Invoke(_peer, static (state) => state.Select(), _iface); + } - private object GetEnclosingElement(object unused) - { - return _iface.GetEnclosingElement(); - } + public void AddToSelection() + { + ElementUtil.Invoke(_peer, static (state) => state.AddToSelection(), _iface); + } - private object GetText(object arg) - { - object[] args = (object[])arg; - int maxLength = (int)args[0]; - return _iface.GetText(maxLength); - } + public void RemoveFromSelection() + { + ElementUtil.Invoke(_peer, static (state) => state.RemoveFromSelection(), _iface); + } - private object Move(object arg) - { - object[] args = (object[])arg; - TextUnit unit = (TextUnit)args[0]; - int count = (int)args[1]; - return _iface.Move(unit, count); - } + public void ScrollIntoView(bool alignToTop) + { + ElementUtil.Invoke(_peer, static (state, alignToTop) => state.ScrollIntoView(alignToTop), _iface, alignToTop); + } - private object MoveEndpointByUnit(object arg) - { - object[] args = (object[])arg; - TextPatternRangeEndpoint endpoint = (TextPatternRangeEndpoint)args[0]; - TextUnit unit = (TextUnit)args[1]; - int count = (int)args[2]; - return _iface.MoveEndpointByUnit(endpoint, unit, count); - } + public IRawElementProviderSimple[]? GetChildren() + { + return ElementUtil.Invoke(_peer, static (state) => state.GetChildren(), _iface); + } - private object MoveEndpointByRange(object arg) - { - object[] args = (object[])arg; - TextPatternRangeEndpoint endpoint = (TextPatternRangeEndpoint)args[0]; - ITextRangeProvider targetRange = (ITextRangeProvider)args[1]; - TextPatternRangeEndpoint targetEndpoint = (TextPatternRangeEndpoint)args[2]; - _iface.MoveEndpointByRange(endpoint, TextRangeProviderWrapper.UnwrapArgument( targetRange ), targetEndpoint); + // Wrap arguments that are being returned, assuming they're not null or already wrapped. + [return: NotNullIfNotNull(nameof(argument))] + internal static ITextRangeProvider? WrapArgument(ITextRangeProvider? argument, AutomationPeer peer) + { + if (argument == null) return null; - } - private object Select(object unused) - { - _iface.Select(); - return null; - } + if (argument is TextRangeProviderWrapper) + return argument; - private object AddToSelection(object unused) - { - _iface.AddToSelection(); - return null; - } + return new TextRangeProviderWrapper(peer, argument); + } - private object RemoveFromSelection(object unused) - { - _iface.RemoveFromSelection(); + [return: NotNullIfNotNull(nameof(argument))] + internal static ITextRangeProvider[]? WrapArgument(ITextRangeProvider[]? argument, AutomationPeer peer) + { + if (argument == null) return null; - } - private object ScrollIntoView(object arg) - { - bool alignTop = (bool)arg; - _iface.ScrollIntoView(alignTop); - return null; - } + if (argument is TextRangeProviderWrapper[]) + return argument; - private object GetChildren(object unused) + ITextRangeProvider[] outArray = new ITextRangeProvider[argument.Length]; + for (int i = 0; i < argument.Length; i++) { - return _iface.GetChildren(); + outArray[i] = WrapArgument(argument[i], peer); } + return outArray; + } - #endregion Private Methods - - - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields - - private AutomationPeer _peer; - private ITextRangeProvider _iface; - - #endregion Private Fields + /// + /// Removes the wrapper from the argument if a wrapper exists, otherwise returns the argument as is. + /// + internal static ITextRangeProvider UnwrapArgument(ITextRangeProvider argument) + { + return argument is TextRangeProviderWrapper wrapper ? wrapper._iface : argument; } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ToggleProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ToggleProviderWrapper.cs index 60162ee3a05..61908c41e17 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ToggleProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ToggleProviderWrapper.cs @@ -1,124 +1,47 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Toggle pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class ToggleProviderWrapper: MarshalByRefObject, IToggleProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private ToggleProviderWrapper( AutomationPeer peer, IToggleProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IValueProvider - // - //------------------------------------------------------ - - #region Interface IToggleProvider - - public void Toggle( ) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( ToggleInternal ), null ); - } - - public ToggleState ToggleState - { - get - { - return (ToggleState) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetToggleState ), null ); - } - } - - #endregion Interface IToggleProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new ToggleProviderWrapper( peer, (IToggleProvider) iface ); - } +namespace MS.Internal.Automation; - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object ToggleInternal( object unused ) - { - _iface.Toggle(); - return null; - } - - private object GetToggleState( object unused ) - { - return _iface.ToggleState; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class ToggleProviderWrapper : MarshalByRefObject, IToggleProvider +{ + private readonly AutomationPeer _peer; + private readonly IToggleProvider _iface; - #endregion Private Methods + private ToggleProviderWrapper(AutomationPeer peer, IToggleProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); + _peer = peer; + _iface = iface; + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public void Toggle() + { + ElementUtil.Invoke(_peer, static (state) => state.Toggle(), _iface); + } - private AutomationPeer _peer; - private IToggleProvider _iface; + public ToggleState ToggleState + { + get => ElementUtil.Invoke(_peer, static (state) => state.ToggleState, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new ToggleProviderWrapper(peer, (IToggleProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TransformProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TransformProviderWrapper.cs index 2ab80ad29fa..95a5f8240ea 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TransformProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/TransformProviderWrapper.cs @@ -1,174 +1,66 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Transform pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class TransformProviderWrapper: MarshalByRefObject, ITransformProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private TransformProviderWrapper( AutomationPeer peer, ITransformProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IWindowProvider - // - //------------------------------------------------------ - - #region Interface ITransformProvider - - - public void Move( double x, double y ) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( Move ), new double [ ] { x, y } ); - } +namespace MS.Internal.Automation; - public void Resize( double width, double height ) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( Resize ), new double [ ] { width, height } ); - } - - public void Rotate( double degrees ) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( Rotate ), degrees ); - } - - public bool CanMove - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetCanMove ), null ); - } - } - - public bool CanResize - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetCanResize ), null ); - } - } - - public bool CanRotate - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetCanRotate ), null ); - } - } - - #endregion Interface ITransformProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new TransformProviderWrapper( peer, (ITransformProvider) iface ); - } - - #endregion Internal Methods +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class TransformProviderWrapper : MarshalByRefObject, ITransformProvider +{ + private readonly AutomationPeer _peer; + private readonly ITransformProvider _iface; - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods + private TransformProviderWrapper(AutomationPeer peer, ITransformProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - private object Move( object arg ) - { - double [ ] args = (double [ ]) arg; - _iface.Move( args[ 0 ], args[ 1 ] ); - return null; - } + _peer = peer; + _iface = iface; + } - private object Resize( object arg ) - { - double [ ] args = (double [ ]) arg; - _iface.Resize( args[ 0 ], args[ 1 ] ); - return null; - } + public void Move(double x, double y) + { + ElementUtil.Invoke(_peer, static (state, coordinates) => state.Move(coordinates[0], coordinates[1]), _iface, new double[] { x, y }); + } - private object Rotate( object arg ) - { - _iface.Rotate( (double)arg ); - return null; - } + public void Resize(double width, double height) + { + ElementUtil.Invoke(_peer, static (state, dimensions) => state.Resize(dimensions[0], dimensions[1]), _iface, new double[] { width, height }); + } - private object GetCanMove( object unused ) - { - return _iface.CanMove; - } - - private object GetCanResize( object unused ) - { - return _iface.CanResize; - } - - private object GetCanRotate( object unused ) - { - return _iface.CanRotate; - } - - #endregion Private Methods + public void Rotate(double degrees) + { + ElementUtil.Invoke(_peer, static (state, degrees) => state.Rotate(degrees), _iface, degrees); + } + public bool CanMove + { + get => ElementUtil.Invoke(_peer, static (state) => state.CanMove, _iface); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public bool CanResize + { + get => ElementUtil.Invoke(_peer, static (state) => state.CanResize, _iface); + } - private AutomationPeer _peer; - private ITransformProvider _iface; + public bool CanRotate + { + get => ElementUtil.Invoke(_peer, static (state) => state.CanRotate, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new TransformProviderWrapper(peer, (ITransformProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ValueProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ValueProviderWrapper.cs index c6807172d67..fe2f2474565 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ValueProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/ValueProviderWrapper.cs @@ -1,136 +1,51 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Value pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class ValueProviderWrapper: MarshalByRefObject, IValueProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private ValueProviderWrapper( AutomationPeer peer, IValueProvider iface ) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IValueProvider - // - //------------------------------------------------------ - - #region Interface IValueProvider - - public void SetValue( string val ) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( SetValueInternal ), val ); - } - - public string Value - { - get - { - return (string) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetValue ), null ); - } - } - - public bool IsReadOnly - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetIsReadOnly ), null ); - } - } - - #endregion Interface IValueProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface ) - { - return new ValueProviderWrapper( peer, (IValueProvider) iface ); - } +namespace MS.Internal.Automation; - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object SetValueInternal( object arg ) - { - _iface.SetValue( (string)arg ); - return null; - } - - private object GetValue( object unused ) - { - return _iface.Value; - } +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class ValueProviderWrapper : MarshalByRefObject, IValueProvider +{ + private readonly AutomationPeer _peer; + private readonly IValueProvider _iface; - private object GetIsReadOnly( object unused ) - { - return _iface.IsReadOnly; - } + private ValueProviderWrapper(AutomationPeer peer, IValueProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - #endregion Private Methods + _peer = peer; + _iface = iface; + } + public void SetValue(string val) + { + ElementUtil.Invoke(_peer, static (state, value) => state.SetValue(value), _iface, val); + } - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields + public string Value + { + get => ElementUtil.Invoke(_peer, static (state) => state.Value, _iface); + } - private AutomationPeer _peer; - private IValueProvider _iface; + public bool IsReadOnly + { + get => ElementUtil.Invoke(_peer, static (state) => state.IsReadOnly, _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new ValueProviderWrapper(peer, (IValueProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/VirtualizedItemProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/VirtualizedItemProviderWrapper.cs index a90545ff6eb..8c9b56678d5 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/VirtualizedItemProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/VirtualizedItemProviderWrapper.cs @@ -1,111 +1,41 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Virtualized Item pattern provider wrapper for WPF -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation -{ - // Automation/WPF Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WPF AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class VirtualizedItemProviderWrapper : MarshalByRefObject, IVirtualizedItemProvider - { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private VirtualizedItemProviderWrapper(AutomationPeer peer, IVirtualizedItemProvider iface) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IVirtualizedItemProvider - // - //------------------------------------------------------ - - #region Interface IVirtualizedItemProvider - - public void Realize() - { - ElementUtil.Invoke(_peer, new DispatcherOperationCallback(Realize), null); - } - - #endregion Interface IVirtualizedItemProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap(AutomationPeer peer, object iface) - { - return new VirtualizedItemProviderWrapper(peer, (IVirtualizedItemProvider)iface); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object Realize(object unused) - { - _iface.Realize(); - return null; - } - - - #endregion Private Methods +namespace MS.Internal.Automation; +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class VirtualizedItemProviderWrapper : MarshalByRefObject, IVirtualizedItemProvider +{ + private readonly AutomationPeer _peer; + private readonly IVirtualizedItemProvider _iface; - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ + private VirtualizedItemProviderWrapper(AutomationPeer peer, IVirtualizedItemProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); - #region Private Fields + _peer = peer; + _iface = iface; + } - private AutomationPeer _peer; - private IVirtualizedItemProvider _iface; + public void Realize() + { + ElementUtil.Invoke(_peer, static (state) => state.Realize(), _iface); + } - #endregion Private Fields + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) + { + return new VirtualizedItemProviderWrapper(peer, (IVirtualizedItemProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/WindowProviderWrapper.cs b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/WindowProviderWrapper.cs index cb92daf6d23..71feca97e3d 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/WindowProviderWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/Automation/WindowProviderWrapper.cs @@ -1,210 +1,82 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -// -// -// -// Description: Window pattern provider wrapper for WCP -// -// +#nullable enable -using System.Windows.Threading; using System.Windows.Automation; using System.Windows.Automation.Provider; using System.Windows.Automation.Peers; -namespace MS.Internal.Automation +namespace MS.Internal.Automation; + +/// +/// Wrapper class for the interface, calls through to the managed +/// that implements it. The calls are made on the peer's context to ensure that the correct synchronization context is used. +/// +internal sealed class WindowProviderWrapper : MarshalByRefObject, IWindowProvider { - // Automation/WCP Wrapper class: Implements that UIAutomation I...Provider - // interface, and calls through to a WCP AutomationPeer which implements the corresponding - // I...Provider inteface. Marshalls the call from the RPC thread onto the - // target AutomationPeer's context. - // - // Class has two major parts to it: - // * Implementation of the I...Provider, which uses Dispatcher.Invoke - // to call a private method (lives in second half of the class) via a delegate, - // if necessary, packages any params into an object param. Return type of Invoke - // must be cast from object to appropriate type. - // * private methods - one for each interface entry point - which get called back - // on the right context. These call through to the peer that's actually - // implenting the I...Provider version of the interface. - internal class WindowProviderWrapper: MarshalByRefObject, IWindowProvider + private readonly AutomationPeer _peer; + private readonly IWindowProvider _iface; + + private WindowProviderWrapper(AutomationPeer peer, IWindowProvider iface) + { + Debug.Assert(peer is not null); + Debug.Assert(iface is not null); + + _peer = peer; + _iface = iface; + } + + public void SetVisualState(WindowVisualState state) + { + ElementUtil.Invoke(_peer, static (state, visualState) => state.SetVisualState(visualState), _iface, state); + } + + public void Close() + { + ElementUtil.Invoke(_peer, static (state) => state.Close(), _iface); + } + + public bool WaitForInputIdle(int milliseconds) + { + return ElementUtil.Invoke(_peer, static (state, milliseconds) => state.WaitForInputIdle(milliseconds), _iface, milliseconds); + } + + public bool Maximizable + { + get => ElementUtil.Invoke(_peer, static (state) => state.Maximizable, _iface); + } + + public bool Minimizable + { + get => ElementUtil.Invoke(_peer, static (state) => state.Minimizable, _iface); + } + + public bool IsModal + { + get => ElementUtil.Invoke(_peer, static (state) => state.IsModal, _iface); + } + + public WindowVisualState VisualState + { + get => ElementUtil.Invoke(_peer, static (state) => state.VisualState, _iface); + } + + public WindowInteractionState InteractionState + { + get => ElementUtil.Invoke(_peer, static (state) => state.InteractionState, _iface); + } + + public bool IsTopmost + { + get => ElementUtil.Invoke(_peer, static (state) => state.IsTopmost, _iface); + } + + /// + /// Creates a wrapper for the given and interface. + /// + internal static object Wrap(AutomationPeer peer, object iface) { - //------------------------------------------------------ - // - // Constructors - // - //------------------------------------------------------ - - #region Constructors - - private WindowProviderWrapper( AutomationPeer peer, IWindowProvider iface) - { - _peer = peer; - _iface = iface; - } - - #endregion Constructors - - - //------------------------------------------------------ - // - // Interface IWindowProvider - // - //------------------------------------------------------ - - #region Interface IWindowProvider - - public void SetVisualState( WindowVisualState state ) - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( SetVisualState ), state ); - } - - public void Close() - { - ElementUtil.Invoke( _peer, new DispatcherOperationCallback( Close ), null ); - } - - public bool WaitForInputIdle( int milliseconds ) - { - return (bool)ElementUtil.Invoke( _peer, new DispatcherOperationCallback( WaitForInputIdle ), milliseconds ); - } - - public bool Maximizable - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetMaximizable ), null ); - } - } - - public bool Minimizable - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetMinimizable ), null ); - } - } - - public bool IsModal - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetIsModal ), null ); - } - } - - public WindowVisualState VisualState - { - get - { - return (WindowVisualState) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetVisualState ), null ); - } - } - - public WindowInteractionState InteractionState - { - get - { - return (WindowInteractionState) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetInteractionState ), null ); - } - } - - public bool IsTopmost - { - get - { - return (bool) ElementUtil.Invoke( _peer, new DispatcherOperationCallback( GetIsTopmost ), null ); - } - } - - #endregion Interface IWindowProvider - - - //------------------------------------------------------ - // - // Internal Methods - // - //------------------------------------------------------ - - #region Internal Methods - - internal static object Wrap( AutomationPeer peer, object iface) - { - return new WindowProviderWrapper( peer, (IWindowProvider) iface ); - } - - #endregion Internal Methods - - //------------------------------------------------------ - // - // Private Methods - // - //------------------------------------------------------ - - #region Private Methods - - private object SetVisualState( object arg ) - { - _iface.SetVisualState( (WindowVisualState) arg ); - return null; - } - - private object WaitForInputIdle( object arg ) - { - return _iface.WaitForInputIdle( (int) arg ); - } - - private object Close( object unused ) - { - _iface.Close(); - return null; - } - - private object GetMaximizable( object unused ) - { - return _iface.Maximizable; - } - - private object GetMinimizable( object unused ) - { - return _iface.Minimizable; - } - - private object GetIsModal( object unused ) - { - return _iface.IsModal; - } - - private object GetVisualState( object unused ) - { - return _iface.VisualState; - } - - private object GetInteractionState( object unused ) - { - return _iface.InteractionState; - } - - private object GetIsTopmost( object unused ) - { - return _iface.IsTopmost; - } - - #endregion Private Methods - - - //------------------------------------------------------ - // - // Private Fields - // - //------------------------------------------------------ - - #region Private Fields - - private AutomationPeer _peer; - private IWindowProvider _iface; - - #endregion Private Fields + return new WindowProviderWrapper(peer, (IWindowProvider)iface); } } diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationCore/PresentationCore.csproj b/src/Microsoft.DotNet.Wpf/src/PresentationCore/PresentationCore.csproj index da1d32be5fa..9c93a7b2871 100644 --- a/src/Microsoft.DotNet.Wpf/src/PresentationCore/PresentationCore.csproj +++ b/src/Microsoft.DotNet.Wpf/src/PresentationCore/PresentationCore.csproj @@ -24,6 +24,7 @@ + @@ -137,6 +138,7 @@ + diff --git a/src/Microsoft.DotNet.Wpf/src/Shared/RefAssemblyAttrs.cs b/src/Microsoft.DotNet.Wpf/src/Shared/RefAssemblyAttrs.cs index 0e2505fbb3e..243113550b3 100644 --- a/src/Microsoft.DotNet.Wpf/src/Shared/RefAssemblyAttrs.cs +++ b/src/Microsoft.DotNet.Wpf/src/Shared/RefAssemblyAttrs.cs @@ -57,6 +57,7 @@ internal static class BuildInfo internal const string SystemPrinting = $"System.Printing, PublicKey={WCP_PUBLIC_KEY_STRING}"; internal const string SystemXaml = $"System.Xaml, PublicKey={DEVDIV_PUBLIC_KEY_STRING}"; internal const string WindowsFormsIntegration = $"WindowsFormsIntegration, PublicKey={WCP_PUBLIC_KEY_STRING}"; + internal const string WindowsBaseTests = $"WindowsBase.Tests, PublicKey={WCP_PUBLIC_KEY_STRING}"; // Make internal visible to the 3.5 dll, System.Windows.Presentation.dll. // we hard code the key here because the 3.5 dll is built in the devdiv depot using the CLR key. diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IGridProvider.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IGridProvider.cs index 5d7adf75ecc..ce2cf681bef 100644 --- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IGridProvider.cs +++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IGridProvider.cs @@ -1,8 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Description: Grid pattern provider interface +#nullable enable + using System.Runtime.InteropServices; namespace System.Windows.Automation.Provider @@ -24,7 +26,7 @@ public interface IGridProvider /// /// Row of cell to get /// Column of cell to get - IRawElementProviderSimple GetItem(int row, int column); + IRawElementProviderSimple? GetItem(int row, int column); /// /// number of rows in the grid diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IItemContainerProvider.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IItemContainerProvider.cs index 578297ea47b..b2dfe04687e 100644 --- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IItemContainerProvider.cs +++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IItemContainerProvider.cs @@ -3,6 +3,8 @@ // Description: Item Container pattern provider interface +#nullable enable + using System.Runtime.InteropServices; namespace System.Windows.Automation.Provider @@ -10,7 +12,7 @@ namespace System.Windows.Automation.Provider /// /// /// Exposes a container control's ability to search over the items it contains. - /// This pattern must be implemented by containers which suppots virtualization and have + /// This pattern must be implemented by containers which supports virtualization and have /// no other means to find the virtualized element though it's orthogonal to virtualization /// and can be implemented by any containers which has items in it. /// @@ -53,7 +55,7 @@ public interface IItemContainerProvider /// corresponds to property for whose value it want to search over. /// value to be searched for, for specified property /// The first item which matches the searched criterion, if no item matches, it returns null - IRawElementProviderSimple FindItemByProperty(IRawElementProviderSimple startAfter, int propertyId, object value); + IRawElementProviderSimple? FindItemByProperty(IRawElementProviderSimple? startAfter, int propertyId, object? value); } } diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IMultipleViewProvider.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IMultipleViewProvider.cs index 3b5bfe73127..92a994583e8 100644 --- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IMultipleViewProvider.cs +++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationProvider/System/Windows/Automation/Provider/IMultipleViewProvider.cs @@ -1,8 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Description: Multiple View pattern provider interface +#nullable enable + using System.Runtime.InteropServices; namespace System.Windows.Automation.Provider @@ -30,20 +32,17 @@ public interface IMultipleViewProvider /// be the same across instances. /// /// Return a localized, human readable string in the application's current UI language. - string GetViewName( int viewId ); + string GetViewName(int viewId); /// /// Change the current view using an ID returned from SupportedViews property /// - void SetCurrentView( int viewId ); + void SetCurrentView(int viewId); /// The view ID corresponding to the control's current state. This ID is control-specific - int CurrentView - { - get; - } + int CurrentView { get; } /// Returns an array of ints representing the full set of views available in this control. - int [] GetSupportedViews(); + int[] GetSupportedViews(); } } diff --git a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationTypes/System/Windows/Automation/Provider/ITextRangeProvider.cs b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationTypes/System/Windows/Automation/Provider/ITextRangeProvider.cs index b5bc9f12786..4466a6617ec 100644 --- a/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationTypes/System/Windows/Automation/Provider/ITextRangeProvider.cs +++ b/src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationTypes/System/Windows/Automation/Provider/ITextRangeProvider.cs @@ -1,8 +1,10 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Description: Interface exposes a control's ability to manipulate text ranges +#nullable enable + using System.Windows.Automation.Text; using System.Runtime.InteropServices; @@ -20,14 +22,6 @@ internal interface ITextRangeProvider public interface ITextRangeProvider #endif { - //------------------------------------------------------ - // - // Public Methods - // - //------------------------------------------------------ - - #region Public Methods - /// /// Retrieves a new range covering an identical span of text. The new range can be manipulated independently from the original. /// @@ -71,7 +65,7 @@ public interface ITextRangeProvider /// The value of the specified attribute to search for. /// true if the last occurring range should be returned instead of the first. /// A subrange with the specified attribute, or null if no such subrange exists. - ITextRangeProvider FindAttribute(int attribute, object value, [MarshalAs(UnmanagedType.Bool)] bool backward); + ITextRangeProvider? FindAttribute(int attribute, object value, [MarshalAs(UnmanagedType.Bool)] bool backward); /// /// Searches for an occurrence of text within the range. @@ -80,7 +74,7 @@ public interface ITextRangeProvider /// true if the last occurring range should be returned instead of the first. /// true if case should be ignored for the purposes of comparison. /// A subrange with the specified text, or null if no such subrange exists. - ITextRangeProvider FindText(string text, [MarshalAs(UnmanagedType.Bool)] bool backward, [MarshalAs(UnmanagedType.Bool)] bool ignoreCase); + ITextRangeProvider? FindText(string text, [MarshalAs(UnmanagedType.Bool)] bool backward, [MarshalAs(UnmanagedType.Bool)] bool ignoreCase); /// /// Retrieves the value of a text attribute over the entire range. @@ -100,7 +94,7 @@ public interface ITextRangeProvider /// /// /// - double [] GetBoundingRectangles(); + double[] GetBoundingRectangles(); /// /// Retrieves the innermost element that encloses this range. @@ -185,17 +179,6 @@ public interface ITextRangeProvider /// true if the provider should be scrolled so the range is flush with the top of the viewport. /// false if the provider should be scrolled so the range is flush with the bottom. void ScrollIntoView([MarshalAs(UnmanagedType.Bool)] bool alignToTop); - - #endregion Public Methods - - - //------------------------------------------------------ - // - // Public Properties - // - //------------------------------------------------------ - - #region Public Properties /// /// Retrieves a collection of all of the children that fall within the range. @@ -204,8 +187,7 @@ public interface ITextRangeProvider /// that overlap with the range but are not entirely enclosed by it will /// also be included in the collection. If there are no children then /// this can return either null or an empty enumeration. - IRawElementProviderSimple[] GetChildren(); + IRawElementProviderSimple[]? GetChildren(); - #endregion Public Properties } } diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/LibraryAssemblyInfo.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/LibraryAssemblyInfo.cs index 043e9b01263..b4e5fdad7fd 100644 --- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/LibraryAssemblyInfo.cs +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/LibraryAssemblyInfo.cs @@ -2,5 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Runtime.CompilerServices; +using MS.Internal.WindowsBase; -[assembly: InternalsVisibleTo("WindowsBase.Tests, PublicKey=00000000000000000400000000000000")] +[assembly: InternalsVisibleTo(BuildInfo.WindowsBaseTests)] diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/Threading/ExceptionFilterHelper.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/Threading/ExceptionFilterHelper.cs deleted file mode 100644 index 31c3cb75850..00000000000 --- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/Threading/ExceptionFilterHelper.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -// Description: Internal class replicating the functionality of the -// former VB class of the same name. It's no longer used internally, but -// exists solely for compat - in case anyone used private reflection. - -namespace MS.Internal.Threading -{ - internal delegate object InternalRealCallDelegate(Delegate method, object args, int numArgs); - internal delegate bool FilterExceptionDelegate(object source, Exception e); - internal delegate bool CatchExceptionDelegate(object source, Exception e, Delegate catchHandler); - - /// - /// Class for Filtering and Catching Exceptions - /// - internal sealed class ExceptionFilterHelper - { - internal ExceptionFilterHelper( InternalRealCallDelegate internalRealCall, - FilterExceptionDelegate filterException, - CatchExceptionDelegate catchException) - { - _internalRealCall = internalRealCall; - _filterException = filterException; - _catchException = catchException; - } - - internal object TryCatchWhen( object source, - Delegate method, - object args, - int numArgs, - Delegate catchHandler) - { - object result = null; - - try - { - result = _internalRealCall.Invoke(method, args, numArgs); - } - catch (Exception e) when (_filterException.Invoke(source, e)) - { - if (!_catchException.Invoke(source, e, catchHandler)) - { - throw; - } - } - - return result; - } - - private InternalRealCallDelegate _internalRealCall; - private FilterExceptionDelegate _filterException; - private CatchExceptionDelegate _catchException; - } -} diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/Threading/ExceptionWrapper.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/Dispatcher.ExceptionWrapper.cs similarity index 61% rename from src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/Threading/ExceptionWrapper.cs rename to src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/Dispatcher.ExceptionWrapper.cs index a1d6d84a2dd..66af1cf2dfc 100644 --- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/MS/Internal/Threading/ExceptionWrapper.cs +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/Dispatcher.ExceptionWrapper.cs @@ -1,21 +1,25 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Threading; -namespace System.Windows.Threading +namespace System.Windows.Threading; + +public sealed partial class Dispatcher { /// - /// Class for Filtering and Catching Exceptions + /// Helper class for Filtering and Catching Exceptions. /// - internal class ExceptionWrapper + /// + /// This is mostly legacy interface as newer (e.g. async) methods don't catch/filter the exceptions. + /// + internal static class ExceptionWrapper { - internal ExceptionWrapper() - { - } - - // Helper for exception filtering: - public object TryCatchWhen(object source, Delegate callback, object args, int numArgs, Delegate catchHandler) + /// + /// Calls the delegate and catches exceptions that are filtered by the dispatcher, raising the UnhandledException event if necessary. + /// + /// Result of the delegate method call in case it has succeeded. + internal static object TryCatchWhen(Dispatcher dispatcher, Delegate callback, object args, int numArgs, Delegate catchCallback) { object result = null; @@ -23,9 +27,10 @@ public object TryCatchWhen(object source, Delegate callback, object args, int nu { result = InternalRealCall(callback, args, numArgs); } - catch (Exception e) when (FilterException(source, e)) + catch (Exception e) when (FilterException(dispatcher, e)) { - if (!CatchException(source, e, catchHandler)) + // Determine whether we should catch or throw the exception, executing the catchCallback beforehand + if (!CatchException(dispatcher, e, catchCallback)) { throw; } @@ -34,7 +39,7 @@ public object TryCatchWhen(object source, Delegate callback, object args, int nu return result; } - private object InternalRealCall(Delegate callback, object args, int numArgs) + private static object InternalRealCall(Delegate callback, object args, int numArgs) { object result = null; @@ -46,14 +51,14 @@ private object InternalRealCall(Delegate callback, object args, int numArgs) // of an arbitrary "params object[]" is passed. int numArgsEx = numArgs; object singleArg = args; - if(numArgs == -1) + if (numArgs == -1) { object[] argsArr = (object[])args; if (argsArr == null || argsArr.Length == 0) { numArgsEx = 0; } - else if(argsArr.Length == 1) + else if (argsArr.Length == 1) { numArgsEx = 1; singleArg = argsArr[0]; @@ -62,7 +67,7 @@ private object InternalRealCall(Delegate callback, object args, int numArgs) // Special-case delegates that we know about to avoid the // expensive DynamicInvoke call. - if(numArgsEx == 0) + if (numArgsEx == 0) { if (callback is Action action) { @@ -70,7 +75,7 @@ private object InternalRealCall(Delegate callback, object args, int numArgs) } else { - if (callback is Dispatcher.ShutdownCallback shutdownCallback) + if (callback is ShutdownCallback shutdownCallback) { shutdownCallback(); } @@ -81,7 +86,7 @@ private object InternalRealCall(Delegate callback, object args, int numArgs) } } } - else if(numArgsEx == 1) + else if (numArgsEx == 1) { if (callback is DispatcherOperationCallback dispatcherOperationCallback) { @@ -121,27 +126,22 @@ private object InternalRealCall(Delegate callback, object args, int numArgs) return result; } - private bool FilterException(object source, Exception e) + /// + /// Exception filter returns if exception should be caught. + /// + private static bool FilterException(Dispatcher dispatcher, Exception e) { - // If we have a Catch handler we should catch the exception - // unless the Filter handler says we shouldn't. - bool shouldCatch = (null != Catch); - if(null != Filter) - { - shouldCatch = Filter(source, e); - } - return shouldCatch; + // This will raise Dispatcher.UnhandledException event if registered. + return dispatcher.ExceptionFilter(e); } - // This returns false when caller should rethrow the exception. - // true means Exception is "handled" and things just continue on. - private bool CatchException(object source, Exception e, Delegate catchHandler) + private static bool CatchException(Dispatcher dispatcher, Exception e, Delegate catchHandler) { - if (catchHandler != null) + if (catchHandler is not null) { - if(catchHandler is DispatcherOperationCallback) + if (catchHandler is DispatcherOperationCallback catchCallback) { - ((DispatcherOperationCallback)catchHandler)(null); + catchCallback(null); } else { @@ -149,29 +149,9 @@ private bool CatchException(object source, Exception e, Delegate catchHandler) } } - if(null != Catch) - return Catch(source, e); - - return false; + // This returns false when caller should rethrow the exception. + // true means Exception is "handled" and things just continue on. + return dispatcher.CatchException(e); } - - /// - /// Exception Catch Handler Delegate - /// Returns true if the exception is "handled" - /// Returns false if the caller should rethow the exception. - /// - public delegate bool CatchHandler(object source, Exception e); - - /// - /// Exception Catch Handler - /// Returns true if the exception is "handled" - /// Returns false if the caller should rethow the exception. - /// - public event CatchHandler Catch; - - public delegate bool FilterHandler(object source, Exception e); - public event FilterHandler Filter; } } - - diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/Dispatcher.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/Dispatcher.cs index 9b6b1dd8d90..a07f1787282 100644 --- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/Dispatcher.cs +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/Dispatcher.cs @@ -11,13 +11,14 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Globalization; namespace System.Windows.Threading { /// /// Provides UI services for a thread. /// - public sealed class Dispatcher + public sealed partial class Dispatcher { static Dispatcher() { @@ -25,9 +26,6 @@ static Dispatcher() _globalLock = new object(); _dispatchers = new List(); _possibleDispatcher = new WeakReference(null); - _exceptionWrapper = new ExceptionWrapper(); - _exceptionWrapper.Catch += new ExceptionWrapper.CatchHandler(CatchExceptionStatic); - _exceptionWrapper.Filter += new ExceptionWrapper.FilterHandler(ExceptionFilterStatic); } /// @@ -189,7 +187,7 @@ public Thread Thread /// /// True if the calling thread has access to this object. /// - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [EditorBrowsable(EditorBrowsableState.Never)] public bool CheckAccess() { return Thread == Thread.CurrentThread; @@ -204,7 +202,7 @@ public bool CheckAccess() /// This method is public so that derived classes can probe to /// see if the calling thread has access to itself. /// - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] + [EditorBrowsable(EditorBrowsableState.Never)] public void VerifyAccess() { if(!CheckAccess()) @@ -298,7 +296,7 @@ public static void PushFrame(DispatcherFrame frame) { ArgumentNullException.ThrowIfNull(frame); - Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + Dispatcher dispatcher = CurrentDispatcher; if(dispatcher._hasShutdownFinished) // Dispatcher thread - no lock needed for read { throw new InvalidOperationException(SR.DispatcherHasShutdown); @@ -323,7 +321,7 @@ public static void PushFrame(DispatcherFrame frame) public static void ExitAllFrames() { - Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + Dispatcher dispatcher = CurrentDispatcher; if(dispatcher._frameDepth > 0) { dispatcher._exitAllFrames = true; @@ -580,22 +578,8 @@ public void Invoke(Action callback, DispatcherPriority priority, CancellationTok try { - DispatcherSynchronizationContext newSynchronizationContext; - if(BaseCompatibilityPreferences.GetReuseDispatcherSynchronizationContextInstance()) - { - newSynchronizationContext = _defaultDispatcherSynchronizationContext; - } - else - { - if(BaseCompatibilityPreferences.GetFlowDispatcherSynchronizationContextPriority()) - { - newSynchronizationContext = new DispatcherSynchronizationContext(this, priority); - } - else - { - newSynchronizationContext = new DispatcherSynchronizationContext(this, DispatcherPriority.Normal); - } - } + DispatcherSynchronizationContext newSynchronizationContext = DispatcherUtils.GetOrCreateContext(this, priority); + SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); callback(); @@ -608,7 +592,7 @@ public void Invoke(Action callback, DispatcherPriority priority, CancellationTok } // Slow-Path: go through the queue. - DispatcherOperation operation = new DispatcherOperation(this, priority, callback); + DispatcherOperation operation = new DispatcherOperationAction(this, priority, callback); InvokeImpl(operation, cancellationToken, timeout); } @@ -676,6 +660,288 @@ public TResult Invoke(Func callback, DispatcherPriority priori return Invoke(callback, priority, cancellationToken, TimeSpan.FromMilliseconds(-1)); } + /// + /// Executes the specified synchronously on the + /// thread that the Dispatcher was created on. + /// + /// + /// An delegate to invoke through the dispatcher. + /// + /// + /// The priority that determines in what order the specified + /// callback is invoked relative to the other pending operations + /// in the Dispatcher. + /// + /// + /// The minimum amount of time to wait for the operation to start. + /// Once the operation has started, it will complete before this method + /// returns. + /// + internal void Invoke(Action callback, DispatcherPriority priority, CancellationToken cancellationToken, TimeSpan timeout, TArg arg) + { + Debug.Assert(callback is not null, "Callback cannot be null"); + Debug.Assert(timeout.TotalMilliseconds >= 0 || timeout == TimeSpan.FromMilliseconds(-1)); + + // Fast-Path: if on the same thread, and invoking at Send priority, and the cancellation + // token is not already canceled, then just call the callback directly. + if (!cancellationToken.IsCancellationRequested && priority is DispatcherPriority.Send && CheckAccess()) + { + SynchronizationContext oldSynchronizationContext = SynchronizationContext.Current; + + try + { + DispatcherSynchronizationContext newSynchronizationContext = DispatcherUtils.GetOrCreateContext(this, priority); + + SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); + + callback(arg); + return; + } + finally + { + SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext); + } + } + + // Slow-Path: go through the queue. + DispatcherOperation operation = new DispatcherOperationAction(this, priority, callback, arg); + InvokeImpl(operation, cancellationToken, timeout); + } + + /// + /// Executes the specified synchronously on the + /// thread that the Dispatcher was created on. + /// + /// + /// A delegate to invoke through the dispatcher. + /// + /// + /// The priority that determines in what order the specified + /// callback is invoked relative to the other pending operations + /// in the Dispatcher. + /// + /// + /// The minimum amount of time to wait for the operation to start. + /// Once the operation has started, it will complete before this method + /// returns. + /// + /// + /// The return value from the delegate being invoked. + /// + internal TResult Invoke(Func callback, DispatcherPriority priority, TimeSpan timeout, TArg1 arg1, TArg2 arg2) + { + Debug.Assert(callback is not null, "Callback cannot be null"); + Debug.Assert(timeout.TotalMilliseconds >= 0 || timeout == TimeSpan.FromMilliseconds(-1)); + + // Fast-Path: if on the same thread, and invoking at Send priority, then just call the callback directly. + if (priority is DispatcherPriority.Send && CheckAccess()) + { + SynchronizationContext oldSynchronizationContext = SynchronizationContext.Current; + + try + { + DispatcherSynchronizationContext newSynchronizationContext = DispatcherUtils.GetOrCreateContext(this, priority); + + SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); + + return callback(arg1, arg2); + } + finally + { + SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext); + } + } + + // Slow-Path: go through the queue. + DispatcherOperation operation = new(this, priority, callback, arg1, arg2); + return InvokeImpl(operation, CancellationToken.None, timeout); + } + + /// + /// Executes the specified synchronously on the + /// thread that the Dispatcher was created on. + /// + /// + /// A delegate to invoke through the dispatcher. + /// + /// + /// The priority that determines in what order the specified + /// callback is invoked relative to the other pending operations + /// in the Dispatcher. + /// + /// + /// The minimum amount of time to wait for the operation to start. + /// Once the operation has started, it will complete before this method + /// returns. + /// + /// + /// The return value from the delegate being invoked. + /// + internal TResult Invoke(Func callback, DispatcherPriority priority, TimeSpan timeout, TArg1 arg1, TArg2 arg2, TArg3 arg3) + { + Debug.Assert(callback is not null, "Callback cannot be null"); + Debug.Assert(timeout.TotalMilliseconds >= 0 || timeout == TimeSpan.FromMilliseconds(-1)); + + // Fast-Path: if on the same thread, and invoking at Send priority, then just call the callback directly. + if (priority is DispatcherPriority.Send && CheckAccess()) + { + SynchronizationContext oldSynchronizationContext = SynchronizationContext.Current; + + try + { + DispatcherSynchronizationContext newSynchronizationContext = DispatcherUtils.GetOrCreateContext(this, priority); + + SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); + + return callback(arg1, arg2, arg3); + } + finally + { + SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext); + } + } + + // Slow-Path: go through the queue. + DispatcherOperation operation = new(this, priority, callback, arg1, arg2, arg3); + return InvokeImpl(operation, CancellationToken.None, timeout); + } + + /// + /// Executes the specified synchronously on the + /// thread that the Dispatcher was created on. + /// + /// + /// A delegate to invoke through the dispatcher. + /// + /// + /// The priority that determines in what order the specified + /// callback is invoked relative to the other pending operations + /// in the Dispatcher. + /// + /// + /// The minimum amount of time to wait for the operation to start. + /// Once the operation has started, it will complete before this method + /// returns. + /// + /// + /// The return value from the delegate being invoked. + /// + internal TResult Invoke(Func callback, DispatcherPriority priority, TimeSpan timeout, TArg arg) + { + Debug.Assert(callback is not null, "Callback cannot be null"); + Debug.Assert(timeout.TotalMilliseconds >= 0 || timeout == TimeSpan.FromMilliseconds(-1)); + + // Fast-Path: if on the same thread, and invoking at Send priority, then just call the callback directly. + if (priority is DispatcherPriority.Send && CheckAccess()) + { + SynchronizationContext oldSynchronizationContext = SynchronizationContext.Current; + + try + { + DispatcherSynchronizationContext newSynchronizationContext = DispatcherUtils.GetOrCreateContext(this, priority); + + SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); + + return callback(arg); + } + finally + { + SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext); + } + } + + // Slow-Path: go through the queue. + DispatcherOperation operation = new(this, priority, callback, arg); + return InvokeImpl(operation, CancellationToken.None, timeout); + } + + private TResult InvokeImpl(DispatcherOperation operation, CancellationToken cancellationToken, TimeSpan timeout) + { + TResult result = default; + + Debug.Assert(timeout.TotalMilliseconds >= 0 || timeout == TimeSpan.FromMilliseconds(-1)); + Debug.Assert(operation.Priority != DispatcherPriority.Send || !CheckAccess()); // should be handled by caller + + if (!cancellationToken.IsCancellationRequested) + { + // This operation must be queued since it was invoked either to + // another thread, or at a priority other than Send. + InvokeAsyncImpl(operation, cancellationToken); + + CancellationToken ctTimeout = CancellationToken.None; + CancellationTokenRegistration ctTimeoutRegistration = new CancellationTokenRegistration(); + CancellationTokenSource ctsTimeout = null; + + if (timeout.TotalMilliseconds >= 0) + { + // Create a CancellationTokenSource that will abort the + // operation after the timeout. Note that this does not + // cancel the operation, just abort it if it is still pending. + ctsTimeout = new CancellationTokenSource(timeout); + ctTimeout = ctsTimeout.Token; + ctTimeoutRegistration = ctTimeout.Register(s => ((DispatcherOperation)s).Abort(), operation); + } + + + // We have already registered with the cancellation tokens + // (both provided by the user, and one for the timeout) to + // abort the operation when they are canceled. If the + // operation has already started when the timeout expires, + // we still wait for it to complete. This is different + // than simply waiting on the operation with a timeout + // because we are the ones queueing the dispatcher + // operation, not the caller. We can't leave the operation + // in a state that it might execute if we return that it did not + // invoke. + try + { + operation.Wait(); + + Debug.Assert(operation.Status == DispatcherOperationStatus.Completed || + operation.Status == DispatcherOperationStatus.Aborted); + + // Old async semantics return from Wait without + // throwing an exception if the operation was aborted. + // There is no need to test the timout condition, since + // the old async semantics would just return the result, + // which would be null. + + // This should not block because either the operation + // is using the old async sematics, or the operation + // completed successfully. + result = operation.Result; + } + catch (OperationCanceledException) + { + Debug.Assert(operation.Status == DispatcherOperationStatus.Aborted); + + // New async semantics will throw an exception if the + // operation was aborted. Here we convert that + // exception into a timeout exception if the timeout + // has expired (admittedly a weak relationship + // assuming causality). + if (ctTimeout.IsCancellationRequested) + { + // The operation was canceled because of the + // timeout, throw a TimeoutException instead. + throw new TimeoutException(); + } + else + { + // The operation was canceled from some other reason. + throw; + } + } + finally + { + ctTimeoutRegistration.Dispose(); + ctsTimeout?.Dispose(); + } + } + + return result; + } + /// /// Executes the specified Func synchronously on the /// thread that the Dispatcher was created on. @@ -722,22 +988,8 @@ public TResult Invoke(Func callback, DispatcherPriority priori try { - DispatcherSynchronizationContext newSynchronizationContext; - if(BaseCompatibilityPreferences.GetReuseDispatcherSynchronizationContextInstance()) - { - newSynchronizationContext = _defaultDispatcherSynchronizationContext; - } - else - { - if(BaseCompatibilityPreferences.GetFlowDispatcherSynchronizationContextPriority()) - { - newSynchronizationContext = new DispatcherSynchronizationContext(this, priority); - } - else - { - newSynchronizationContext = new DispatcherSynchronizationContext(this, DispatcherPriority.Normal); - } - } + DispatcherSynchronizationContext newSynchronizationContext = DispatcherUtils.GetOrCreateContext(this, priority); + SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); return callback(); @@ -750,7 +1002,7 @@ public TResult Invoke(Func callback, DispatcherPriority priori // Slow-Path: go through the queue. DispatcherOperation operation = new DispatcherOperation(this, priority, callback); - return (TResult) InvokeImpl(operation, cancellationToken, timeout); + return InvokeImpl(operation, cancellationToken, timeout); } /// @@ -820,7 +1072,7 @@ public DispatcherOperation InvokeAsync(Action callback, DispatcherPriority prior ArgumentNullException.ThrowIfNull(callback); ValidatePriority(priority, "priority"); - DispatcherOperation operation = new DispatcherOperation(this, priority, callback); + DispatcherOperation operation = new DispatcherOperationAction(this, priority, callback); InvokeAsyncImpl(operation, cancellationToken); return operation; @@ -901,7 +1153,7 @@ private DispatcherOperation LegacyBeginInvokeImpl(DispatcherPriority priority, D ValidatePriority(priority, "priority"); ArgumentNullException.ThrowIfNull(method); - DispatcherOperation operation = new DispatcherOperation(this, method, priority, args, numArgs); + DispatcherOperation operation = new DispatcherOperationLegacy(this, method, priority, args, numArgs); InvokeAsyncImpl(operation, CancellationToken.None); return operation; @@ -1278,22 +1530,8 @@ internal object LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, try { - DispatcherSynchronizationContext newSynchronizationContext; - if(BaseCompatibilityPreferences.GetReuseDispatcherSynchronizationContextInstance()) - { - newSynchronizationContext = _defaultDispatcherSynchronizationContext; - } - else - { - if(BaseCompatibilityPreferences.GetFlowDispatcherSynchronizationContextPriority()) - { - newSynchronizationContext = new DispatcherSynchronizationContext(this, priority); - } - else - { - newSynchronizationContext = new DispatcherSynchronizationContext(this, DispatcherPriority.Normal); - } - } + DispatcherSynchronizationContext newSynchronizationContext = DispatcherUtils.GetOrCreateContext(this, priority); + SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); return WrappedInvoke(method, args, numArgs, null); @@ -1305,7 +1543,7 @@ internal object LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, } // Slow-Path: go through the queue. - DispatcherOperation operation = new DispatcherOperation(this, method, priority, args, numArgs); + DispatcherOperation operation = new DispatcherOperationLegacy(this, method, priority, args, numArgs); return InvokeImpl(operation, CancellationToken.None, timeout); } @@ -1333,7 +1571,7 @@ private object InvokeImpl(DispatcherOperation operation, CancellationToken cance // cancel the operation, just abort it if it is still pending. ctsTimeout = new CancellationTokenSource(timeout); ctTimeout = ctsTimeout.Token; - ctTimeoutRegistration = ctTimeout.Register(s => ((DispatcherOperation)s).Abort(), operation); + ctTimeoutRegistration = ctTimeout.Register(static s => ((DispatcherOperation)s).Abort(), operation); } @@ -1420,103 +1658,6 @@ public DispatcherProcessingDisabled DisableProcessing() return dpd; } -/* - /// - /// Reports the range of priorities that are considered - /// as foreground priorities. - /// - /// - /// A foreground priority is processed before input. - /// - public static PriorityRange ForegroundPriorityRange - { - get - { - return _foregroundPriorityRange; - } - } - - /// - /// Reports the range of priorities that are considered - /// as background priorities. - /// - /// - /// A background priority is processed after input. - /// - public static PriorityRange BackgroundPriorityRange - { - get - { - return _backgroundPriorityRange; - } - } - - /// - /// Reports the range of priorities that are considered - /// as idle priorities. - /// - /// - /// An idle priority is processed periodically after background - /// priorities have been processed. - /// - public static PriorityRange IdlePriorityRange - { - get - { - return _idlePriorityRange; - } - } - - /// - /// Represents a convenient foreground priority. - /// - /// - /// A foreground priority is processed before input. In general - /// you should define your own foreground priority to allow for - /// more fine-grained ordering of queued items. - /// - public static Priority ForegroundPriority - { - get - { - return _foregroundPriority; - } - } - - /// - /// Represents a convenient background priority. - /// - /// - /// A background priority is processed after input. In general you - /// should define your own background priority to allow for more - /// fine-grained ordering of queued items. - /// - public static Priority BackgroundPriority - { - get - { - return _backgroundPriority; - } - } - - /// - /// Represents a convenient idle priority. - /// - /// - /// An idle priority is processed periodically after background - /// priorities have been processed. In general you should define - /// your own idle priority to allow for more fine-grained ordering - /// of queued items. - /// - public static Priority IdlePriority - { - get - { - return _idlePriority; - } - } -*/ - /// /// Validates that a priority is suitable for use by the dispatcher. /// @@ -1528,20 +1669,22 @@ public static Priority IdlePriority /// that is raised if the priority is not suitable for use by /// the dispatcher. /// - public static void ValidatePriority(DispatcherPriority priority, string parameterName) // NOTE: should be Priority + public static void ValidatePriority(DispatcherPriority priority, string parameterName) { - // First make sure the Priority is valid. - // Priority.ValidatePriority(priority, paramName); + // Make sure the priority is in a range recognized by the dispatcher. + if (!_foregroundPriorityRange.Contains(priority) && + !_backgroundPriorityRange.Contains(priority) && + !_idlePriorityRange.Contains(priority) && + priority is not DispatcherPriority.Inactive) + { + ThrowInvalidEnumArgumentException(parameterName, (int)priority); + } - // Second, make sure the priority is in a range recognized by - // the dispatcher. - if(!_foregroundPriorityRange.Contains(priority) && - !_backgroundPriorityRange.Contains(priority) && - !_idlePriorityRange.Contains(priority) && - DispatcherPriority.Inactive != priority) // NOTE: should be Priority.Min + [DoesNotReturn] + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowInvalidEnumArgumentException(string argumentName, int value) { - // If we move to a Priority class, this exception will have to change too. - throw new System.ComponentModel.InvalidEnumArgumentException(parameterName, (int)priority, typeof(DispatcherPriority)); + throw new InvalidEnumArgumentException(argumentName, value, typeof(DispatcherPriority)); } } @@ -1557,7 +1700,7 @@ public static void ValidatePriority(DispatcherPriority priority, string paramete /// /// True if the calling thread has access to this object. /// - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)] + [EditorBrowsable(EditorBrowsableState.Advanced)] public DispatcherHooks Hooks { get @@ -2459,7 +2602,7 @@ private void OnRequestProcessingFailure(string methodName) if (_reservedPtsCache is Tuple> tuple) { List list = tuple.Item2; - list.Add(String.Format(System.Globalization.CultureInfo.InvariantCulture, + list.Add(String.Format(CultureInfo.InvariantCulture, "{0:O} {1} failed", DateTime.Now, methodName)); // keep the list from growing too large @@ -2673,13 +2816,6 @@ private void KillWin32Timer() } } - // Exception filter returns true if exception should be caught. - private static bool ExceptionFilterStatic(object source, Exception e) - { - Dispatcher d = (Dispatcher)source; - return d.ExceptionFilter(e); - } - private bool ExceptionFilter(Exception e) { // see whether this dispatcher has already seen the exception. @@ -2730,14 +2866,6 @@ private bool ExceptionFilter(Exception e) return requestCatch; } - // This returns false when caller should rethrow the exception. - // true means Exception is "handled" and things just continue on. - private static bool CatchExceptionStatic(object source, Exception e) - { - Dispatcher dispatcher = (Dispatcher)source; - return dispatcher.CatchException(e); - } - // The exception filter called for catching an unhandled exception. private bool CatchException(Exception e) { @@ -2770,12 +2898,20 @@ private bool HasUnhandledExceptionHandler get { return (UnhandledException != null); } } - internal object WrappedInvoke(Delegate callback, object args, int numArgs, Delegate catchHandler) + /// + /// Calls the delegate and catches exceptions that are filtered by the dispatcher, raising the UnhandledException event if necessary. + /// + /// Result of the delegate method call in case it has succeeded. + internal object WrappedInvoke(Delegate callback, object args, int numArgs, Delegate catchCallback) { - return _exceptionWrapper.TryCatchWhen(this, callback, args, numArgs, catchHandler); + return ExceptionWrapper.TryCatchWhen(this, callback, args, numArgs, catchCallback); } - private object[] CombineParameters(object arg, object[] args) + /// + /// Combines the first argument with the rest of the arguments into an array. + /// + /// NOTE: This is a legacy invoke arguments handling. + private static object[] CombineParameters(object arg, object[] args) { object[] parameters = new object[1 + (args == null ? 1 : args.Length)]; parameters[0] = arg; @@ -2827,7 +2963,6 @@ private object[] CombineParameters(object arg, object[] args) private int _postedProcessingType; private static WindowMessage _msgProcessQueue; - private static ExceptionWrapper _exceptionWrapper; private static readonly object ExceptionDataKey = new object(); // Preallocated arguments for exception handling. @@ -2849,7 +2984,7 @@ private object[] CombineParameters(object arg, object[] args) internal DispatcherSynchronizationContext _defaultDispatcherSynchronizationContext; - internal object _instanceLock = new object(); // Also used by DispatcherOperation + internal Lock _instanceLock = new Lock(); // Also used by DispatcherOperation private PriorityQueue _queue; private List _timers = new List(); private long _timersVersion; diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Action.0.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Action.0.cs new file mode 100644 index 00000000000..b7e71d41535 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Action.0.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; +using MS.Internal; + +namespace System.Windows.Threading; + +/// +/// DispatcherOperation represents a delegate that has been posted to the queue. +/// +internal class DispatcherOperationAction : DispatcherOperation +{ + internal DispatcherOperationAction(Dispatcher dispatcher, DispatcherPriority priority, Delegate method) : base( + dispatcher: dispatcher, + method: method, + priority: priority, + taskSource: new DispatcherOperationTaskSource(), + useAsyncSemantics: true) + { + } + + private protected sealed override object OperationResult + { + get + { + // New semantics require waiting for the operation to + // complete. + // + // Use DispatcherOperation.Wait instead of Task.Wait to handle + // waiting on the same thread. + Wait(); + + if (_status is DispatcherOperationStatus.Completed or DispatcherOperationStatus.Aborted) + { + // We know the operation has completed, and the + // _taskSource has been completed, so it safe to ask + // the task for the Awaiter, and the awaiter for the result. + // We don't actually care about the result, but this gives the + // Task the chance to throw any captured exceptions. + Task.GetAwaiter().GetResult(); + } + + return null; + } + } + + internal sealed override void InvokeCompletions() + { + switch (_status) + { + case DispatcherOperationStatus.Aborted: + _taskSource.SetCanceled(); + break; + case DispatcherOperationStatus.Completed: + if (_exception is not null) + { + _taskSource.SetException(_exception); + } + else + { + _taskSource.SetResult(null); + } + break; + + default: + Invariant.Assert(false, "Operation should be either Aborted or Completed!"); + break; + } + } + + private protected sealed override void InvokeImpl() + { + SynchronizationContext oldSynchronizationContext = SynchronizationContext.Current; + + try + { + // We are executing under the "foreign" execution context, but the SynchronizationContext + // must be for the correct dispatcher and the correct priority (since NETFX 4.5). + DispatcherSynchronizationContext newSynchronizationContext = DispatcherUtils.GetOrCreateContext(_dispatcher, _priority); + + SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); + + // Win32 considers timers to be low priority. Avalon does not, since different timers are + // associated with different priorities. So we promote the timers before we invoke any work items. + _dispatcher.PromoteTimers(Environment.TickCount); + + try + { + InvokeDelegateCore(); + } + catch (Exception e) + { + // Remember this for the later call to InvokeCompletions. + _exception = e; + } + } + finally + { + SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext); + } + } + + private protected virtual void InvokeDelegateCore() + { + Action action = (Action)_method; + action(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Action.1.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Action.1.cs new file mode 100644 index 00000000000..4f045728e76 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Action.1.cs @@ -0,0 +1,28 @@ +// 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; + +namespace System.Windows.Threading; + +/// +/// DispatcherOperation represents a delegate that has been posted to the queue. +/// +internal sealed class DispatcherOperationAction : DispatcherOperationAction +{ + private readonly TArg _arg; + + internal DispatcherOperationAction(Dispatcher dispatcher, DispatcherPriority priority, Action method, TArg arg) : base( + dispatcher: dispatcher, + method: method, + priority: priority) + { + _arg = arg; + } + + private protected sealed override void InvokeDelegateCore() + { + Action action = Unsafe.As>(_method); + action(_arg); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Event.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Event.cs new file mode 100644 index 00000000000..14189fb3233 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Event.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; + +namespace System.Windows.Threading; + +/// +/// DispatcherOperation represents a delegate that has been posted to the queue. +/// +public abstract partial class DispatcherOperation +{ + private protected sealed class DispatcherOperationEvent + { + private readonly DispatcherOperation _operation; + private readonly ManualResetEvent _event; + private readonly TimeSpan _timeout; + private bool _eventClosed; + + private Lock DispatcherLock + { + get => _operation.DispatcherLock; + } + + public DispatcherOperationEvent(DispatcherOperation op, TimeSpan timeout) + { + _operation = op; + _timeout = timeout; + _event = new ManualResetEvent(false); + _eventClosed = false; + + lock (DispatcherLock) + { + // We will set our event once the operation is completed or aborted. + _operation.Aborted += new EventHandler(OnCompletedOrAborted); + _operation.Completed += new EventHandler(OnCompletedOrAborted); + + // Since some other thread is dispatching this operation, it could + // have been dispatched while we were setting up the handlers. + // We check the state again and set the event ourselves if this + // happened. + if (_operation._status is not DispatcherOperationStatus.Pending and not DispatcherOperationStatus.Executing) + { + _event.Set(); + } + } + } + + private void OnCompletedOrAborted(object sender, EventArgs e) + { + lock (DispatcherLock) + { + if (!_eventClosed) + { + _event.Set(); + } + } + } + + public void WaitOne() + { + _event.WaitOne(_timeout, false); + + lock (DispatcherLock) + { + if (!_eventClosed) + { + // Cleanup the events. + _operation.Aborted -= new EventHandler(OnCompletedOrAborted); + _operation.Completed -= new EventHandler(OnCompletedOrAborted); + + // Close the event immediately instead of waiting for a GC + // because the Dispatcher is a a high-activity component and + // we could run out of events. + _event.Close(); + + _eventClosed = true; + } + } + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Frame.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Frame.cs new file mode 100644 index 00000000000..8cf0d053380 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.Frame.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; + +namespace System.Windows.Threading; + +/// +/// DispatcherOperation represents a delegate that has been posted to the queue. +/// +public abstract partial class DispatcherOperation +{ + private protected sealed class DispatcherOperationFrame : DispatcherFrame + { + private readonly DispatcherOperation _operation; + private readonly Timer _waitTimer; + + // Note: we pass "exitWhenRequested=false" to the base + // DispatcherFrame constructor because we do not want to exit + // this frame if the dispatcher is shutting down. This is + // because we may need to invoke operations during the shutdown process. + public DispatcherOperationFrame(DispatcherOperation op, TimeSpan timeout) : base(false) + { + _operation = op; + + // We will exit this frame once the operation is completed or aborted. + _operation.Aborted += new EventHandler(OnCompletedOrAborted); + _operation.Completed += new EventHandler(OnCompletedOrAborted); + + // We will exit the frame if the operation is not completed within + // the requested timeout. + if (timeout.TotalMilliseconds > 0) + { + _waitTimer = new Timer(new TimerCallback(OnTimeout), + null, + timeout, + TimeSpan.FromMilliseconds(-1)); + } + + // Some other thread could have aborted the operation while we were + // setting up the handlers. We check the state again and mark the + // frame as "should not continue" if this happened. + if (_operation._status != DispatcherOperationStatus.Pending) + { + Exit(); + } + } + + private void OnCompletedOrAborted(object sender, EventArgs e) + { + Exit(); + } + + private void OnTimeout(object arg) + { + Exit(); + } + + private void Exit() + { + Continue = false; + + _waitTimer?.Dispose(); + + _operation.Aborted -= new EventHandler(OnCompletedOrAborted); + _operation.Completed -= new EventHandler(OnCompletedOrAborted); + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.0.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.0.cs new file mode 100644 index 00000000000..25464a54bda --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.0.cs @@ -0,0 +1,145 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using MS.Internal; + +namespace System.Windows.Threading; + +/// +/// DispatcherOperation represents a delegate that has been posted to the queue. +/// +public class DispatcherOperation : DispatcherOperation +{ + private TResult _result; + + internal DispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority, Delegate func) : base( + dispatcher: dispatcher, + method: func, + priority: priority, + taskSource: new DispatcherOperationTaskSource(), + useAsyncSemantics: true) + { + } + + /// + /// Returns a Task representing the operation. + /// + public new Task Task + { + get + { + // Just upcast the base Task to what it really is. + return (Task)((DispatcherOperation)this).Task; + } + } + + /// + /// Returns an awaiter for awaiting the completion of the operation. + /// + /// + /// This method is intended to be used by compilers. + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public new TaskAwaiter GetAwaiter() + { + return Task.GetAwaiter(); + } + + /// + /// Returns the result of the operation if it has completed. + /// + public new TResult Result + { + get + { + // New semantics require waiting for the operation to complete. + // + // Use DispatcherOperation.Wait instead of Task.Wait to handle waiting on the same thread. + Wait(); + + if (_status is DispatcherOperationStatus.Completed or DispatcherOperationStatus.Aborted) + { + // We know the operation has completed, and the + // _taskSource has been completed, so it safe to ask + // the task for the Awaiter, and the awaiter for the result. + // We don't actually care about the result, but this gives the + // Task the chance to throw any captured exceptions. + Task.GetAwaiter().GetResult(); + } + + return _result; + } + } + + private protected sealed override object OperationResult + { + get => Result; + } + + internal sealed override void InvokeCompletions() + { + switch (_status) + { + case DispatcherOperationStatus.Aborted: + _taskSource.SetCanceled(); + break; + case DispatcherOperationStatus.Completed: + if (_exception is not null) + { + _taskSource.SetException(_exception); + } + else + { + // Make sure we don't box at this stage + ((DispatcherOperationTaskSource)_taskSource).SetResult(_result); + } + break; + + default: + Invariant.Assert(false, "Operation should be either Aborted or Completed!"); + break; + } + } + + private protected sealed override void InvokeImpl() + { + SynchronizationContext oldSynchronizationContext = SynchronizationContext.Current; + + try + { + // We are executing under the "foreign" execution context, but the SynchronizationContext + // must be for the correct dispatcher and the correct priority (since NETFX 4.5). + DispatcherSynchronizationContext newSynchronizationContext = DispatcherUtils.GetOrCreateContext(_dispatcher, _priority); + + SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); + + // Win32 considers timers to be low priority. Avalon does not, since different timers are + // associated with different priorities. So we promote the timers before we invoke any work items. + _dispatcher.PromoteTimers(Environment.TickCount); + + try + { + _result = InvokeDelegateCore(); + } + catch (Exception e) + { + // Remember this for the later call to InvokeCompletions. + _exception = e; + } + } + finally + { + SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext); + } + } + + private protected virtual TResult InvokeDelegateCore() + { + Func func = (Func)_method; + return func(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.1.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.1.cs new file mode 100644 index 00000000000..4fa2cc90690 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.1.cs @@ -0,0 +1,27 @@ +// 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; + +namespace System.Windows.Threading; + +/// +/// DispatcherOperation represents a delegate that has been posted to the queue. +/// +internal sealed class DispatcherOperation : DispatcherOperation +{ + private readonly TArg _arg; + + internal DispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority, + Func func, TArg arg) : base(dispatcher, priority, func) + { + _arg = arg; + } + + private protected sealed override TResult InvokeDelegateCore() + { + Func func = Unsafe.As>(_method); + return func(_arg); + } +} + diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.2.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.2.cs new file mode 100644 index 00000000000..4411722d453 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.2.cs @@ -0,0 +1,29 @@ +// 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; + +namespace System.Windows.Threading; + +/// +/// DispatcherOperation represents a delegate that has been posted to the queue. +/// +internal sealed class DispatcherOperation : DispatcherOperation +{ + private readonly TArg1 _arg1; + private readonly TArg2 _arg2; + + internal DispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority, Func func, + TArg1 arg1, TArg2 arg2) : base(dispatcher, priority, func) + { + _arg1 = arg1; + _arg2 = arg2; + } + + private protected sealed override TResult InvokeDelegateCore() + { + Func func = Unsafe.As>(_method); + return func(_arg1, _arg2); + } +} + diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.3.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.3.cs new file mode 100644 index 00000000000..2ef7e351094 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.TResult.3.cs @@ -0,0 +1,31 @@ +// 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; + +namespace System.Windows.Threading; + +/// +/// DispatcherOperation represents a delegate that has been posted to the queue. +/// +internal sealed class DispatcherOperation : DispatcherOperation +{ + private readonly TArg1 _arg1; + private readonly TArg2 _arg2; + private readonly TArg3 _arg3; + + internal DispatcherOperation(Dispatcher dispatcher, DispatcherPriority priority, Func func, + TArg1 arg1, TArg2 arg2, TArg3 arg3) : base(dispatcher, priority, func) + { + _arg1 = arg1; + _arg2 = arg2; + _arg3 = arg3; + } + + private protected sealed override TResult InvokeDelegateCore() + { + Func func = Unsafe.As>(_method); + return func(_arg1, _arg2, _arg3); + } +} + diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.cs index 826b9b6aaa6..be13a360ae2 100644 --- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.cs +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperation.cs @@ -1,103 +1,76 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Threading; using System.ComponentModel; -using System.Threading.Tasks; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; using MS.Internal; namespace System.Windows.Threading { /// - /// DispatcherOperation represents a delegate that has been - /// posted to the Dispatcher queue. + /// DispatcherOperation represents a delegate that has been posted to the queue. /// - public class DispatcherOperation + public abstract partial class DispatcherOperation { - static DispatcherOperation() - { - _invokeInSecurityContext = new ContextCallback(InvokeInSecurityContext); - } + private CulturePreservingExecutionContext _executionContext; + + private protected readonly Dispatcher _dispatcher; + private protected readonly Delegate _method; - internal DispatcherOperation( - Dispatcher dispatcher, - Delegate method, - DispatcherPriority priority, - object args, - int numArgs, - DispatcherOperationTaskSource taskSource, - bool useAsyncSemantics) + private protected readonly bool _useAsyncSemantics; + + private protected DispatcherPriority _priority; + private protected Exception _exception; + + internal PriorityItem _item; // The Dispatcher sets this when it enqueues/deques the item. + internal DispatcherOperationStatus _status; // set from Dispatcher + + private EventHandler _aborted; + private EventHandler _completed; + + internal readonly DispatcherOperationTaskSource _taskSource; // also used from Dispatcher + + internal DispatcherOperation(Dispatcher dispatcher, Delegate method, DispatcherPriority priority, + DispatcherOperationTaskSource taskSource, bool useAsyncSemantics) { _dispatcher = dispatcher; _method = method; _priority = priority; - _numArgs = numArgs; - _args = args; _executionContext = CulturePreservingExecutionContext.Capture(); _taskSource = taskSource; _taskSource.Initialize(this); - + _useAsyncSemantics = useAsyncSemantics; } - internal DispatcherOperation( - Dispatcher dispatcher, - Delegate method, - DispatcherPriority priority, - object args, - int numArgs) : this( - dispatcher, - method, - priority, - args, - numArgs, - new DispatcherOperationTaskSource(), - false) - { - } + /// + /// Returns the Dispatcher that this operation was posted to. + /// + public Dispatcher Dispatcher => _dispatcher; - internal DispatcherOperation( - Dispatcher dispatcher, - DispatcherPriority priority, - Action action) : this( - dispatcher, - action, - priority, - null, - 0, - new DispatcherOperationTaskSource(), - true) - { - } - - internal DispatcherOperation( - Dispatcher dispatcher, - DispatcherPriority priority, - Delegate method, - object[] args) : this( - dispatcher, - method, - priority, - args, - -1, - new DispatcherOperationTaskSource(), - true) - { - } + private Lock DispatcherLock => _dispatcher._instanceLock; /// - /// Returns the Dispatcher that this operation was posted to. + /// The status of this operation. /// - public Dispatcher Dispatcher - { - get - { - return _dispatcher; - } - } + public DispatcherOperationStatus Status => _status; + + /// + /// Returns a Task representing the operation. + /// + public Task Task => _taskSource.GetTask(); + + /// + /// Name of this operation. + /// + /// + /// Returns a string representation of the operation to be invoked. + /// + internal string Name => $"{_method.Method.DeclaringType}.{_method.Method.Name}"; /// /// Gets or sets the priority of this operation within the @@ -105,44 +78,18 @@ public Dispatcher Dispatcher /// public DispatcherPriority Priority // NOTE: should be Priority { - get - { - return _priority; - } - + get => _priority; set { Dispatcher.ValidatePriority(value, "value"); - - if(value != _priority && _dispatcher.SetPriority(this, value)) + + if (value != _priority && _dispatcher.SetPriority(this, value)) { _priority = value; } } } - /// - /// The status of this operation. - /// - public DispatcherOperationStatus Status - { - get - { - return _status; - } - } - - /// - /// Returns a Task representing the operation. - /// - public Task Task - { - get - { - return _taskSource.GetTask(); - } - } - /// /// Returns an awaiter for awaiting the completion of the operation. /// @@ -179,12 +126,11 @@ public DispatcherOperationStatus Wait() /// public DispatcherOperationStatus Wait(TimeSpan timeout) { - if((_status == DispatcherOperationStatus.Pending || _status == DispatcherOperationStatus.Executing) && - timeout.TotalMilliseconds != 0) + if ((_status is DispatcherOperationStatus.Pending or DispatcherOperationStatus.Executing) && timeout.TotalMilliseconds != 0) { - if(_dispatcher.Thread == Thread.CurrentThread) + if (_dispatcher.Thread == Thread.CurrentThread) { - if(_status == DispatcherOperationStatus.Executing) + if (_status == DispatcherOperationStatus.Executing) { // We are the dispatching thread, and the current operation state is // executing, which means that the operation is in the middle of @@ -193,7 +139,7 @@ public DispatcherOperationStatus Wait(TimeSpan timeout) // we throw an exception instead. throw new InvalidOperationException(SR.ThreadMayNotWaitOnOperationsAlreadyExecutingOnTheSameThread); } - + // We are the dispatching thread for this operation, so // we can't block. We will push a frame instead. DispatcherOperationFrame frame = new DispatcherOperationFrame(this, timeout); @@ -215,10 +161,9 @@ public DispatcherOperationStatus Wait(TimeSpan timeout) } } - if(_useAsyncSemantics) + if (_useAsyncSemantics) { - if(_status == DispatcherOperationStatus.Completed || - _status == DispatcherOperationStatus.Aborted) + if (_status is DispatcherOperationStatus.Completed or DispatcherOperationStatus.Aborted) { // We know the operation has completed, so it safe to ask // the task for the Awaiter, and the awaiter for the result. @@ -227,10 +172,10 @@ public DispatcherOperationStatus Wait(TimeSpan timeout) Task.GetAwaiter().GetResult(); } } - + return _status; } - + /// /// Aborts this operation. /// @@ -257,31 +202,13 @@ public bool Abort() _taskSource.SetCanceled(); // Raise the aborted event. - EventHandler aborted = _aborted; - if (aborted != null) - { - aborted(this, EventArgs.Empty); - } + _aborted?.Invoke(this, EventArgs.Empty); } } return removed; } - /// - /// Name of this operation. - /// - /// - /// Returns a string representation of the operation to be invoked. - /// - internal String Name - { - get - { - return _method.Method.DeclaringType + "." + _method.Method.Name; - } - } - /// /// ID of this operation. /// @@ -299,47 +226,30 @@ internal long Id unsafe { // we need a non-readonly field of a pointer-compatible type (using _priority) - fixed (DispatcherPriority* pb = &this._priority) + fixed (DispatcherPriority* pb = &_priority) { - addr = (long) pb; + addr = (long)pb; } } return addr; } } - + /// /// Returns the result of the operation if it has completed. /// - public object Result + public object Result { - get - { - if(_useAsyncSemantics) - { - // New semantics require waiting for the operation to - // complete. - // - // Use DispatcherOperation.Wait instead of Task.Wait to handle - // waiting on the same thread. - Wait(); - - if(_status == DispatcherOperationStatus.Completed || - _status == DispatcherOperationStatus.Aborted) - { - // We know the operation has completed, and the - // _taskSource has been completed, so it safe to ask - // the task for the Awaiter, and the awaiter for the result. - // We don't actually care about the result, but this gives the - // Task the chance to throw any captured exceptions. - Task.GetAwaiter().GetResult(); - } - } - - return _result; - } + // As we cannot change the property to be abstract in the base class due to callvirt, + // we use a private protected property to allow derived classes to implement it. + get => OperationResult; } + /// + /// Gets the underlying result of the operation as an object. + /// + private protected abstract object OperationResult { get; } + /// /// An event that is raised when the operation is aborted or canceled. /// @@ -349,15 +259,14 @@ public event EventHandler Aborted { lock (DispatcherLock) { - _aborted = (EventHandler) Delegate.Combine(_aborted, value); + _aborted = (EventHandler)Delegate.Combine(_aborted, value); } } - remove { - lock(DispatcherLock) + lock (DispatcherLock) { - _aborted = (EventHandler) Delegate.Remove(_aborted, value); + _aborted = (EventHandler)Delegate.Remove(_aborted, value); } } } @@ -376,21 +285,26 @@ public event EventHandler Completed { lock (DispatcherLock) { - _completed = (EventHandler) Delegate.Combine(_completed, value); + _completed = (EventHandler)Delegate.Combine(_completed, value); } } - remove { - lock(DispatcherLock) + lock (DispatcherLock) { - _completed = (EventHandler) Delegate.Remove(_completed, value); + _completed = (EventHandler)Delegate.Remove(_completed, value); } } } - + + // Note: this is called by the Dispatcher to actually invoke the completions for the operation. + internal abstract void InvokeCompletions(); + + // Invoke --> InvokeImpl + private protected abstract void InvokeImpl(); + // Note: this is called by the Dispatcher to actually invoke the operation. - // Invoke --> InvokeInSecurityContext --> InvokeImpl + // Invoke --> InvokeImpl internal void Invoke() { // Mark this operation as executing. @@ -398,9 +312,9 @@ internal void Invoke() // Invoke the operation under the execution context that was // current when the operation was created. - if(_executionContext != null) + if (_executionContext != null) { - CulturePreservingExecutionContext.Run(_executionContext, _invokeInSecurityContext, this); + CulturePreservingExecutionContext.Run(_executionContext, static (state) => ((DispatcherOperation)state).InvokeImpl(), this); // Release any resources held by the execution context. _executionContext.Dispose(); @@ -409,17 +323,15 @@ internal void Invoke() else { // _executionContext can be null if someone called - // ExecutionContext.SupressFlow before calling BeginInvoke/Invoke. - // In this case we'll just call the invokation directly. - // SupressFlow is a privileged operation, so this is not a - // security hole. - _invokeInSecurityContext(this); + // ExecutionContext.SuppressFlow before calling BeginInvoke/Invoke. + // In this case we'll just call the invocation directly. + InvokeImpl(); } EventHandler handler; // either completed or aborted - lock(DispatcherLock) + lock (DispatcherLock) { - if(_exception is OperationCanceledException) + if (_exception is OperationCanceledException) { // A new way to abort/cancel an operation is to raise an // OperationCanceledException exception. This only works @@ -435,338 +347,14 @@ internal void Invoke() else { // The operation either completed, or a new version threw an - // exception and we caught it. There is no seperate event + // exception and we caught it. There is no separate event // for this, so we raise the same Completed event for both. handler = _completed; _status = DispatcherOperationStatus.Completed; } } - - if(handler != null) - { - handler(this, EventArgs.Empty); - } - } - - // Note: this is called by the Dispatcher to actually invoke the completions for the operation. - internal void InvokeCompletions() - { - switch(_status) - { - case DispatcherOperationStatus.Aborted: - _taskSource.SetCanceled(); - break; - - case DispatcherOperationStatus.Completed: - if(_exception != null) - { - _taskSource.SetException(_exception); - } - else - { - _taskSource.SetResult(_result); - } - break; - - default: - Invariant.Assert(false, "Operation should be either Aborted or Completed!"); - break; - } - } - - // Invoke --> InvokeInSecurityContext --> InvokeImpl - private static void InvokeInSecurityContext(Object state) - { - DispatcherOperation operation = (DispatcherOperation) state; - operation.InvokeImpl(); + handler?.Invoke(this, EventArgs.Empty); } - - // Invoke --> InvokeInSecurityContext --> InvokeImpl - private void InvokeImpl() - { - SynchronizationContext oldSynchronizationContext = SynchronizationContext.Current; - - try - { - // We are executing under the "foreign" execution context, but the - // SynchronizationContext must be for the correct dispatcher and - // priority. - DispatcherSynchronizationContext newSynchronizationContext; - if(BaseCompatibilityPreferences.GetReuseDispatcherSynchronizationContextInstance()) - { - newSynchronizationContext = Dispatcher._defaultDispatcherSynchronizationContext; - } - else - { - if(BaseCompatibilityPreferences.GetFlowDispatcherSynchronizationContextPriority()) - { - newSynchronizationContext = new DispatcherSynchronizationContext(_dispatcher, _priority); - } - else - { - newSynchronizationContext = new DispatcherSynchronizationContext(_dispatcher, DispatcherPriority.Normal); - } - } - SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); - - - // Win32 considers timers to be low priority. Avalon does not, since different timers - // are associated with different priorities. So we promote the timers before we - // invoke any work items. - _dispatcher.PromoteTimers(Environment.TickCount); - - if(_useAsyncSemantics) - { - try - { - _result = InvokeDelegateCore(); - } - catch(Exception e) - { - // Remember this for the later call to InvokeCompletions. - _exception = e; - } - } - else - { - // Invoke the delegate and route exceptions through the dispatcher events. - _result = _dispatcher.WrappedInvoke(_method, _args, _numArgs, null); - - // Note: we do not catch exceptions, they flow out the the Dispatcher.UnhandledException handling. - } - } - finally - { - SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext); - } - } - - protected virtual object InvokeDelegateCore() - { - Action action = (Action) _method; - action(); - return null; - } - - private class DispatcherOperationFrame : DispatcherFrame - { - // Note: we pass "exitWhenRequested=false" to the base - // DispatcherFrame construsctor because we do not want to exit - // this frame if the dispatcher is shutting down. This is - // because we may need to invoke operations during the shutdown process. - public DispatcherOperationFrame(DispatcherOperation op, TimeSpan timeout) : base(false) - { - _operation = op; - - // We will exit this frame once the operation is completed or aborted. - _operation.Aborted += new EventHandler(OnCompletedOrAborted); - _operation.Completed += new EventHandler(OnCompletedOrAborted); - - // We will exit the frame if the operation is not completed within - // the requested timeout. - if(timeout.TotalMilliseconds > 0) - { - _waitTimer = new Timer(new TimerCallback(OnTimeout), - null, - timeout, - TimeSpan.FromMilliseconds(-1)); - } - - // Some other thread could have aborted the operation while we were - // setting up the handlers. We check the state again and mark the - // frame as "should not continue" if this happened. - if(_operation._status != DispatcherOperationStatus.Pending) - { - Exit(); - } -} - - private void OnCompletedOrAborted(object sender, EventArgs e) - { - Exit(); - } - - private void OnTimeout(object arg) - { - Exit(); - } - - private void Exit() - { - Continue = false; - - _waitTimer?.Dispose(); - - _operation.Aborted -= new EventHandler(OnCompletedOrAborted); - _operation.Completed -= new EventHandler(OnCompletedOrAborted); - } - - private DispatcherOperation _operation; - private Timer _waitTimer; - } - - private class DispatcherOperationEvent - { - public DispatcherOperationEvent(DispatcherOperation op, TimeSpan timeout) - { - _operation = op; - _timeout = timeout; - _event = new ManualResetEvent(false); - _eventClosed = false; - - lock(DispatcherLock) - { - // We will set our event once the operation is completed or aborted. - _operation.Aborted += new EventHandler(OnCompletedOrAborted); - _operation.Completed += new EventHandler(OnCompletedOrAborted); - - // Since some other thread is dispatching this operation, it could - // have been dispatched while we were setting up the handlers. - // We check the state again and set the event ourselves if this - // happened. - if(_operation._status != DispatcherOperationStatus.Pending && _operation._status != DispatcherOperationStatus.Executing) - { - _event.Set(); - } - } - } - - private void OnCompletedOrAborted(object sender, EventArgs e) - { - lock(DispatcherLock) - { - if(!_eventClosed) - { - _event.Set(); - } - } - } - - public void WaitOne() - { - _event.WaitOne(_timeout, false); - - lock(DispatcherLock) - { - if(!_eventClosed) - { - // Cleanup the events. - _operation.Aborted -= new EventHandler(OnCompletedOrAborted); - _operation.Completed -= new EventHandler(OnCompletedOrAborted); - - // Close the event immediately instead of waiting for a GC - // because the Dispatcher is a a high-activity component and - // we could run out of events. - _event.Close(); - - _eventClosed = true; - } - } - } - - private object DispatcherLock - { - get { return _operation.DispatcherLock; } - } - - private DispatcherOperation _operation; - private TimeSpan _timeout; - private ManualResetEvent _event; - private bool _eventClosed; - } - - private object DispatcherLock - { - get { return _dispatcher._instanceLock; } - } - - private CulturePreservingExecutionContext _executionContext; - private static readonly ContextCallback _invokeInSecurityContext; - - private readonly Dispatcher _dispatcher; - private DispatcherPriority _priority; - internal readonly Delegate _method; - private readonly object _args; - private readonly int _numArgs; - - internal DispatcherOperationStatus _status; // set from Dispatcher - private object _result; - private Exception _exception; - - internal PriorityItem _item; // The Dispatcher sets this when it enques/deques the item. - - private EventHandler _aborted; - private EventHandler _completed; - - internal readonly DispatcherOperationTaskSource _taskSource; // also used from Dispatcher - private readonly bool _useAsyncSemantics; } - - /// - /// DispatcherOperation represents a delegate that has been - /// posted to the Dispatcher queue. - /// - public class DispatcherOperation : DispatcherOperation - { - internal DispatcherOperation( - Dispatcher dispatcher, - DispatcherPriority priority, - Func func) : base( - dispatcher, - func, - priority, - null, - 0, - new DispatcherOperationTaskSource(), - true) - { - } - - /// - /// Returns a Task representing the operation. - /// - public new Task Task - { - get - { - // Just upcast the base Task to what it really is. - return (Task)((DispatcherOperation)this).Task; - } - } - - /// - /// Returns an awaiter for awaiting the completion of the operation. - /// - /// - /// This method is intended to be used by compilers. - /// - [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] - public new TaskAwaiter GetAwaiter() - { - return Task.GetAwaiter(); - } - - /// - /// Returns the result of the operation if it has completed. - /// - public new TResult Result - { - get - { - return (TResult) ((DispatcherOperation)this).Result; - } - } - - protected override object InvokeDelegateCore() - { - Func func = (Func) _method; - return func(); - } -} - - /// - /// A convenient delegate to use for dispatcher operations. - /// - public delegate object DispatcherOperationCallback(object arg); } diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperationCallback.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperationCallback.cs new file mode 100644 index 00000000000..0e510947dd4 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperationCallback.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Windows.Threading; + +/// +/// A convenient delegate to use for dispatcher operations. +/// +public delegate object DispatcherOperationCallback(object arg); diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperationLegacy.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperationLegacy.cs new file mode 100644 index 00000000000..d179da613e0 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperationLegacy.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; +using MS.Internal; + +namespace System.Windows.Threading; + +/// +/// DispatcherOperation represents a delegate that has been posted to the queue. +/// +internal sealed class DispatcherOperationLegacy : DispatcherOperation +{ + private readonly object _args; + private readonly int _numArgs; + private object _result; + + private DispatcherOperationLegacy(Dispatcher dispatcher, Delegate method, DispatcherPriority priority, object args, int numArgs, + DispatcherOperationTaskSource taskSource, bool useAsyncSemantics) : base(dispatcher, method, priority, taskSource, useAsyncSemantics) + { + _numArgs = numArgs; + _args = args; + } + + internal DispatcherOperationLegacy(Dispatcher dispatcher, Delegate method, DispatcherPriority priority, object args, int numArgs) : this( + dispatcher, method, priority, args, numArgs, new DispatcherOperationTaskSource(), useAsyncSemantics: false) + { + } + + private protected sealed override object OperationResult + { + get => _result; + } + + internal override void InvokeCompletions() + { + switch (_status) + { + case DispatcherOperationStatus.Aborted: + _taskSource.SetCanceled(); + break; + case DispatcherOperationStatus.Completed: + if (_exception is not null) + { + _taskSource.SetException(_exception); + } + else + { + _taskSource.SetResult(_result); + } + break; + + default: + Invariant.Assert(false, "Operation should be either Aborted or Completed!"); + break; + } + } + + private protected sealed override void InvokeImpl() + { + SynchronizationContext oldSynchronizationContext = SynchronizationContext.Current; + + try + { + // We are executing under the "foreign" execution context, but the SynchronizationContext + // must be for the correct dispatcher and the correct priority (since NETFX 4.5). + DispatcherSynchronizationContext newSynchronizationContext = DispatcherUtils.GetOrCreateContext(_dispatcher, _priority); + + SynchronizationContext.SetSynchronizationContext(newSynchronizationContext); + + // Win32 considers timers to be low priority. Avalon does not, since different timers are + // associated with different priorities. So we promote the timers before we invoke any work items. + _dispatcher.PromoteTimers(Environment.TickCount); + + // Invoke the delegate and route exceptions through the dispatcher events. + _result = _dispatcher.WrappedInvoke(_method, _args, _numArgs, null); + + // Note: we do not catch exceptions, they flow out the the Dispatcher.UnhandledException handling. + } + finally + { + SynchronizationContext.SetSynchronizationContext(oldSynchronizationContext); + } + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperationTaskSource.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperationTaskSource.cs index c6f3d616597..cfef9b4cbd0 100644 --- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperationTaskSource.cs +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherOperationTaskSource.cs @@ -1,75 +1,70 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Threading.Tasks; -namespace System.Windows.Threading +namespace System.Windows.Threading; + +/// +/// DispatcherOperation uses this class to access a TaskCompletionSource without being a generic itself. +/// +internal abstract class DispatcherOperationTaskSource +{ + internal abstract void Initialize(DispatcherOperation operation); + internal abstract Task GetTask(); + internal abstract void SetCanceled(); + internal abstract void SetResult(object result); + internal abstract void SetException(Exception exception); +} + +internal sealed class DispatcherOperationTaskSource : DispatcherOperationTaskSource { - // DispatcherOperation uses this class to access a TaskCompletionSource - // without being a generic iteself. - internal abstract class DispatcherOperationTaskSource + private TaskCompletionSource _taskCompletionSource; + + /// + /// Create the underlying TaskCompletionSource and set the DispatcherOperation as the Task's AsyncState. + /// + /// + internal override void Initialize(DispatcherOperation operation) + { + Debug.Assert(_taskCompletionSource is null); + + _taskCompletionSource = new TaskCompletionSource(new DispatcherOperationTaskMapping(operation)); + } + + internal override Task GetTask() { - public abstract void Initialize(DispatcherOperation operation); - public abstract Task GetTask(); - public abstract void SetCanceled(); - public abstract void SetResult(object result); - public abstract void SetException(Exception exception); + Debug.Assert(_taskCompletionSource is not null); + + return _taskCompletionSource.Task; } - internal class DispatcherOperationTaskSource : DispatcherOperationTaskSource + internal override void SetCanceled() { - // Create the underlying TaskCompletionSource and set the - // DispatcherOperation as the Task's AsyncState. - public override void Initialize(DispatcherOperation operation) - { - if(_taskCompletionSource != null) - { - throw new InvalidOperationException(); - } - - _taskCompletionSource = new TaskCompletionSource(new DispatcherOperationTaskMapping(operation)); - } - - public override Task GetTask() - { - if(_taskCompletionSource == null) - { - throw new InvalidOperationException(); - } - - return _taskCompletionSource.Task; - } - - public override void SetCanceled() - { - if(_taskCompletionSource == null) - { - throw new InvalidOperationException(); - } - - _taskCompletionSource.SetCanceled(); - } - - public override void SetResult(object result) - { - if(_taskCompletionSource == null) - { - throw new InvalidOperationException(); - } - - _taskCompletionSource.SetResult((TResult)result); - } - - public override void SetException(Exception exception) - { - if(_taskCompletionSource == null) - { - throw new InvalidOperationException(); - } - - _taskCompletionSource.SetException(exception); - } - - private TaskCompletionSource _taskCompletionSource; + Debug.Assert(_taskCompletionSource is not null); + + _taskCompletionSource.SetCanceled(); + } + + internal override void SetResult(object result) + { + Debug.Assert(_taskCompletionSource is not null); + + _taskCompletionSource.SetResult((TResult)result); + } + + internal void SetResult(TResult result) + { + Debug.Assert(_taskCompletionSource is not null); + + _taskCompletionSource.SetResult(result); + } + + internal override void SetException(Exception exception) + { + Debug.Assert(_taskCompletionSource is not null); + Debug.Assert(exception is not null); + + _taskCompletionSource.SetException(exception); } } diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherUtils.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherUtils.cs new file mode 100644 index 00000000000..a55e3df81c7 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Threading/DispatcherUtils.cs @@ -0,0 +1,32 @@ +// 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; + +namespace System.Windows.Threading; + +internal static class DispatcherUtils +{ + /// + /// Either returns the default for the given dispatcher, or creates a new one with the specified priority. + /// + /// The behaviour is modified based on several . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static DispatcherSynchronizationContext GetOrCreateContext(Dispatcher dispatcher, DispatcherPriority priority) + { + // Not used since NETFX 4.5 + if (BaseCompatibilityPreferences.GetReuseDispatcherSynchronizationContextInstance()) + { + return dispatcher._defaultDispatcherSynchronizationContext; + } + + // This is our general path without any special app context switches + if (BaseCompatibilityPreferences.GetFlowDispatcherSynchronizationContextPriority()) + { + return new DispatcherSynchronizationContext(dispatcher, priority); + } + + // Compatibility path + return new DispatcherSynchronizationContext(dispatcher, DispatcherPriority.Normal); + } +} diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/WindowsBase.csproj b/src/Microsoft.DotNet.Wpf/src/WindowsBase/WindowsBase.csproj index 38aa9a81d81..5d45a323eb8 100644 --- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/WindowsBase.csproj +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/WindowsBase.csproj @@ -148,7 +148,6 @@ - @@ -278,15 +277,25 @@ - - + + + + + + + + + + + + diff --git a/src/Microsoft.DotNet.Wpf/src/WindowsBase/ref/WindowsBase.cs b/src/Microsoft.DotNet.Wpf/src/WindowsBase/ref/WindowsBase.cs index 6673a43b0d6..6afc1bda418 100644 --- a/src/Microsoft.DotNet.Wpf/src/WindowsBase/ref/WindowsBase.cs +++ b/src/Microsoft.DotNet.Wpf/src/WindowsBase/ref/WindowsBase.cs @@ -1809,7 +1809,7 @@ protected DispatcherObject() { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public void VerifyAccess() { } } - public partial class DispatcherOperation + public abstract partial class DispatcherOperation { internal DispatcherOperation() { } public System.Windows.Threading.Dispatcher Dispatcher { get { throw null; } } @@ -1823,7 +1823,6 @@ public event System.EventHandler Completed { add { } remove { } } [System.ComponentModel.BrowsableAttribute(false)] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public System.Runtime.CompilerServices.TaskAwaiter GetAwaiter() { throw null; } - protected virtual object InvokeDelegateCore() { throw null; } public System.Windows.Threading.DispatcherOperationStatus Wait() { throw null; } public System.Windows.Threading.DispatcherOperationStatus Wait(System.TimeSpan timeout) { throw null; } } @@ -1843,7 +1842,6 @@ internal DispatcherOperation() { } [System.ComponentModel.BrowsableAttribute(false)] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public new System.Runtime.CompilerServices.TaskAwaiter GetAwaiter() { throw null; } - protected override object InvokeDelegateCore() { throw null; } } public enum DispatcherPriority { diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ElementUtil.Tests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ElementUtil.Tests.cs new file mode 100644 index 00000000000..caedff46e12 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ElementUtil.Tests.cs @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Automation.Peers; +using System.Windows.Threading; + +namespace MS.Internal.Automation; + +public sealed class ElementUtilTests +{ + [WpfFact] + public void Invoke_TArg_Success() + { + UIElementAutomationPeer peer = new UIElementAutomationPeer(new UIElement()); + + ElementUtil.Invoke(peer, static (arg) => Assert.Equal("InvocationTest", arg), "InvocationTest"); + } + + [WpfFact(Skip = "This test is ignored because the default timeout is 3 minutes.")] + public void Invoke_TArg_CrossThreadInvocation_TimesOut() + { + UIElementAutomationPeer peer = new UIElementAutomationPeer(new UIElement()); + TimeoutException? expectedEx = null; + + Thread thread = new Thread(() => + { + // Wait for 5 seconds, set the timeout on ElementUtil sooner than that + expectedEx = Assert.Throws(() => ElementUtil.Invoke(peer, static (delay) => + { + while (true) + { + Thread.Sleep(delay); + } + }, 333)); + }); + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + // Pump the Dispatcher queue and block until it times out + thread.Join(); + + Assert.NotNull(expectedEx); + Assert.IsType(expectedEx); + } + + [WpfFact] + public async Task Invoke_TArg_CrossThreadInvocation_PropagatesException() + { + UIElementAutomationPeer peer = new UIElementAutomationPeer(new UIElement()); + InvalidOperationException? expectedEx = null; + + Thread thread = new Thread(() => + { + // Even when invoking on a different thread, the exception should propagate back to the calling thread + expectedEx = Assert.Throws(() => ElementUtil.Invoke(peer, static (text) => throw new InvalidOperationException(text), "Throw me")); + }); + + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + + // We cannot Join here as that would block the queued operation on the Dispatcher thread + await peer.Dispatcher.InvokeAsync(async () => + { + while (expectedEx is null) + { + // Wait until the exception is thrown in the other thread + await Task.Delay(100).ConfigureAwait(false); + } + + }, DispatcherPriority.Background); + + // Ensure the thread has completed execution + thread.Join(); + + Assert.NotNull(expectedEx); + Assert.IsType(expectedEx); + } + + [WpfFact] + public void Invoke_TArg1_TArg2_Success() + { + UIElementAutomationPeer peer = new UIElementAutomationPeer(new UIElement()); + + static void ActionWithTwoArgs(string arg1, string arg2) + { + Assert.Equal("InvocationTest", arg1); + Assert.Equal("AdditionalArgument", arg2); + } + + ElementUtil.Invoke(peer, ActionWithTwoArgs, "InvocationTest", "AdditionalArgument"); + } + + [WpfFact] + public void Invoke_TReturn_TArg_Success() + { + UIElementAutomationPeer peer = new UIElementAutomationPeer(new UIElement()); + + Assert.Equal(0xFF_FF, ElementUtil.Invoke(peer, static (arg) => 0xFF | arg, 0xFF_FF)); + } + + [WpfFact] + public void Invoke_TReturn_TArg1_TArg2_Success() + { + UIElementAutomationPeer peer = new UIElementAutomationPeer(new UIElement()); + + Assert.Equal(66 + 420, ElementUtil.Invoke(peer, static (arg1, arg2) => arg1 + arg2, 66, 420)); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ExpandCollapseProviderWrapper.Tests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ExpandCollapseProviderWrapper.Tests.cs new file mode 100644 index 00000000000..0fece4f7d03 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ExpandCollapseProviderWrapper.Tests.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Windows.Automation; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace MS.Internal.Automation; + +public sealed class ExpandCollapseProviderWrapperTests +{ + [WpfFact] + public void ComboBoxAutomationPeer_Wrap_Properties_Methods_ReturnsExpected() + { + ComboBox comboBox = new(); + ComboBoxAutomationPeer? peer = comboBox.CreateAutomationPeer() as ComboBoxAutomationPeer; + + Assert.NotNull(peer); + + ExpandCollapseProviderWrapper? wrapper = peer.GetWrappedPattern(ExpandCollapsePatternIdentifiers.Pattern.Id) as ExpandCollapseProviderWrapper; + Assert.NotNull(wrapper); + + Assert.Equal(ExpandCollapseState.Collapsed, wrapper.ExpandCollapseState); + + // TODO: To check the state after Expand and Collapse methods, we'd require valid ComboBox in the visual tree. + wrapper.Expand(); + wrapper.Collapse(); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/RangeValueProviderWrapper.Tests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/RangeValueProviderWrapper.Tests.cs new file mode 100644 index 00000000000..2170c632ec4 --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/RangeValueProviderWrapper.Tests.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Windows.Automation; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace MS.Internal.Automation; + +public sealed class RangleValueProviderWrapperTests +{ + [WpfFact] + public void SliderAutomationPeer_Wrap_Properties_Methods_ReturnsExpected() + { + Slider slider = new() { Minimum = 10, Maximum = 100, Value = 50, LargeChange = 25, SmallChange = 10 }; + SliderAutomationPeer? peer = slider.CreateAutomationPeer() as SliderAutomationPeer; + + Assert.NotNull(peer); + + RangeValueProviderWrapper? wrapper = peer.GetWrappedPattern(RangeValuePatternIdentifiers.Pattern.Id) as RangeValueProviderWrapper; + Assert.NotNull(wrapper); + + Assert.Equal(50, wrapper.Value); + Assert.Equal(10, wrapper.Minimum); + Assert.Equal(100, wrapper.Maximum); + Assert.Equal(25, wrapper.LargeChange); + Assert.Equal(10, wrapper.SmallChange); + + Assert.False(wrapper.IsReadOnly); + + // We currently do not require Slider to be a part of visual tree for RangeValuePattern to be available, so we can set value without any issues + wrapper.SetValue(75); + Assert.Equal(75, wrapper.Value); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ToggleProviderWrapper.Tests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ToggleProviderWrapper.Tests.cs new file mode 100644 index 00000000000..d1756bbbb9d --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ToggleProviderWrapper.Tests.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Windows.Automation; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace MS.Internal.Automation; + +public sealed class ToggleProviderWrapperTests +{ + [WpfFact] + public void CheckBoxAutomationPeer_Wrap_Properties_Methods_ReturnsExpected() + { + CheckBox checkBox = new(); + CheckBoxAutomationPeer? peer = checkBox.CreateAutomationPeer() as CheckBoxAutomationPeer; + + Assert.NotNull(peer); + + ToggleProviderWrapper? wrapper = peer.GetWrappedPattern(TogglePatternIdentifiers.Pattern.Id) as ToggleProviderWrapper; + Assert.NotNull(wrapper); + + Assert.Equal(ToggleState.Off, wrapper.ToggleState); + + // We currently do not require CheckBox to be a part of visual tree for TogglePattern to be available, so we can toggle it without any issues + wrapper.Toggle(); + Assert.Equal(ToggleState.On, wrapper.ToggleState); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/TransformProviderWrapper.Tests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/TransformProviderWrapper.Tests.cs new file mode 100644 index 00000000000..a115793a96e --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/TransformProviderWrapper.Tests.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Windows.Automation; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace MS.Internal.Automation; + +public sealed class TransformProviderWrapperTests +{ + [WpfFact] + public void GridSplitterAutomationPeer_Wrap_Properties_Methods_ReturnsExpected() + { + GridSplitter gridSplitter = new(); + GridSplitterAutomationPeer? peer = gridSplitter.CreateAutomationPeer() as GridSplitterAutomationPeer; + + Assert.NotNull(peer); + + TransformProviderWrapper? wrapper = peer.GetWrappedPattern(TransformPatternIdentifiers.Pattern.Id) as TransformProviderWrapper; + Assert.NotNull(wrapper); + + Assert.True(wrapper.CanMove); + + // GridSplitter does not support resizing or rotating, so these properties should return false + Assert.False(wrapper.CanResize); + Assert.False(wrapper.CanRotate); + + // Attempting to call Move should not throw an exception + wrapper.Move(10, 10); + + // Unsupported operations should throw exceptions + Assert.Throws(() => wrapper.Resize(100, 100)); + Assert.Throws(() => wrapper.Rotate(45)); + } +} diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ValueProviderWrapper.Tests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ValueProviderWrapper.Tests.cs new file mode 100644 index 00000000000..2e75cca5f2e --- /dev/null +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/PresentationCore.Tests/MS/Internal/Automation/ValueProviderWrapper.Tests.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Windows.Automation; +using System.Windows.Automation.Peers; +using System.Windows.Controls; + +namespace MS.Internal.Automation; + +public sealed class ValueProviderWrapperTests +{ + [WpfFact] + public void TextBoxAutomationPeer_Wrap_Properties_Methods_ReturnsExpected() + { + TextBox textBox = new(); + TextBoxAutomationPeer? peer = textBox.CreateAutomationPeer() as TextBoxAutomationPeer; + + Assert.NotNull(peer); + + ValueProviderWrapper? wrapper = peer.GetWrappedPattern(ValuePatternIdentifiers.Pattern.Id) as ValueProviderWrapper; + Assert.NotNull(wrapper); + + // Default TextProperty value for TextBox is string.Empty + Assert.Equal(string.Empty, wrapper.Value); + Assert.False(wrapper.IsReadOnly); + + // We currently do not require TextBox to be a part of visual tree for ValuePattern to be available, so we can interact with it without any issues + wrapper.SetValue("New Value"); + Assert.Equal("New Value", wrapper.Value); + + // Set the TextBox to be read-only and verify the wrapper reflects that change + textBox.IsReadOnly = true; + Assert.True(wrapper.IsReadOnly); + } +} 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 70dd2604032..0dd04cb8984 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 @@ -18,6 +18,9 @@ TargetFramework;TargetFrameworks + + + diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/InternalTypeHelperTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/InternalTypeHelperTests.cs index d8cbae67266..d99f3a60507 100644 --- a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/InternalTypeHelperTests.cs +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Markup/InternalTypeHelperTests.cs @@ -17,19 +17,19 @@ public void Ctor_Default() private class SubInternalTypeHelper : InternalTypeHelper { - protected override void AddEventHandler(EventInfo eventInfo, object target, Delegate handler) + protected internal override void AddEventHandler(EventInfo eventInfo, object target, Delegate handler) => throw new NotImplementedException(); - protected override Delegate CreateDelegate(Type delegateType, object target, string handler) + protected internal override Delegate CreateDelegate(Type delegateType, object target, string handler) => throw new NotImplementedException(); - protected override object CreateInstance(Type type, CultureInfo culture) + protected internal override object CreateInstance(Type type, CultureInfo culture) => throw new NotImplementedException(); - protected override object GetPropertyValue(PropertyInfo propertyInfo, object target, CultureInfo culture) + protected internal override object GetPropertyValue(PropertyInfo propertyInfo, object target, CultureInfo culture) => throw new NotImplementedException(); - protected override void SetPropertyValue(PropertyInfo propertyInfo, object target, object value, CultureInfo culture) + protected internal override void SetPropertyValue(PropertyInfo propertyInfo, object target, object value, CultureInfo culture) => throw new NotImplementedException(); } } diff --git a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherTests.cs b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherTests.cs index 2d6c992000a..19947d6f287 100644 --- a/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherTests.cs +++ b/src/Microsoft.DotNet.Wpf/tests/UnitTests/WindowsBase.Tests/System/Windows/Threading/DispatcherTests.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Threading; +using System.Threading.Tasks; namespace System.Windows.Threading.Tests; @@ -134,6 +135,264 @@ public void BeginInvoke_InvokeDelegateObjectArray_Success() Assert.NotNull(operation.Task); } + [WpfFact] + public void BeginInvoke_SameThread_DispatcherOperation_Return_Result_Block_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static object action() => 5; + DispatcherOperation operation = dispatcher.BeginInvoke(DispatcherPriority.Send, action); + + Assert.Equal(DispatcherOperationStatus.Completed, operation.Wait()); + Assert.Equal(5, (int)operation.Result); + } + + [WpfFact] + public async Task BeginInvoke_SameThread_DispatcherOperation_Result_Await_SuccessAsync() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static object action() => 5; + DispatcherOperation operation = dispatcher.BeginInvoke(DispatcherPriority.Send, action); + await operation; + + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Equal(5, (int)operation.Result); + } + + [WpfFact] + public void InvokeAsync_SameThread_DispatcherOperation_TReturn_Result_Block_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static int action() => 5; + DispatcherOperation operation = dispatcher.InvokeAsync(action, DispatcherPriority.Send, CancellationToken.None); + DispatcherOperation parentOperation = operation; + + Assert.Equal(5, operation.Result); + Assert.Equal(5, (int)parentOperation.Result); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Equal(DispatcherOperationStatus.Completed, parentOperation.Status); + } + + [WpfFact] + public async Task InvokeAsync_SameThread_DispatcherOperation_TReturn_Await_SuccessAsync() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static int action() => 5; + DispatcherOperation operation = dispatcher.InvokeAsync(action, DispatcherPriority.Send, CancellationToken.None); + DispatcherOperation parentOperation = operation; + + Assert.Equal(5, await operation); + Assert.Equal(5, operation.Result); + Assert.Equal(5, (int)parentOperation.Result); + Assert.Equal(DispatcherOperationStatus.Completed, operation.Status); + Assert.Equal(DispatcherOperationStatus.Completed, parentOperation.Status); + } + + [WpfFact] + public void Invoke_SameThread_TReturn_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static int action() => 5; + // Send + current thread forces direct callback invocation + int result = dispatcher.Invoke(action); + + Assert.Equal(5, result); + } + + [WpfFact] + public void Invoke_SameThread_TReturn_PropagatesException() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static double action() => throw new InvalidOperationException("Throw this"); + // Send + current thread forces direct callback invocation + Assert.Throws(() => dispatcher.Invoke(action, DispatcherPriority.Send, CancellationToken.None, TimeSpan.FromSeconds(3))); + } + + [WpfFact] + public void Invoke_SameThread_TArg_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + int result = 5; + // Send + current thread forces direct callback invocation + dispatcher.Invoke((param) => result += param, DispatcherPriority.Send, CancellationToken.None, TimeSpan.FromSeconds(3), 4); + + Assert.Equal(9, result); + } + + [WpfFact] + public void Invoke_SameThread_TArg_PropagatesException() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static void action(double action) => throw new InvalidOperationException("Throw this"); + // Send + current thread forces direct callback invocation + Assert.Throws(() => dispatcher.Invoke(action, DispatcherPriority.Send, CancellationToken.None, TimeSpan.FromSeconds(3), 7.0)); + } + + [WpfFact] + public void Invoke_SameThread_TReturn_TArg_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static int action(int parameter) => parameter + 5; + // Send + current thread forces direct callback invocation + int result = dispatcher.Invoke(action, DispatcherPriority.Send, TimeSpan.FromSeconds(3), 4); + + Assert.Equal(9, result); + } + + [WpfFact] + public void Invoke_SameThread_DispatcherOperation_TReturn_TArg_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static int action(int parameter) => parameter + 5; + // Anything different than Send + current thread is DispatcherOperation allocation and queue pass + int result = dispatcher.Invoke(action, DispatcherPriority.Background, TimeSpan.FromSeconds(3), 4); + + Assert.Equal(9, result); + } + + [WpfFact] + public void Invoke_SameThread_TReturn_TArg1_TArg2_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static double action(int parameter, double doubleParameter) => parameter + doubleParameter + 5.5; + // Send + current thread forces direct callback invocation + double result = dispatcher.Invoke(action, DispatcherPriority.Send, TimeSpan.FromSeconds(3), 4, 14.5); + + Assert.Equal(24.0, result); + } + + [WpfFact] + public void Invoke_SameThread_DispatcherOperation_TReturn_TArg1_TArg2_Success() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static double action(int parameter, double doubleParameter) => parameter + doubleParameter + 5.5; + // Anything different than Send + current thread is DispatcherOperation allocation and queue pass + double result = dispatcher.Invoke(action, DispatcherPriority.Background, TimeSpan.FromSeconds(3), 4, 14.5); + + Assert.Equal(24.0, result); + } + + [WpfFact] + public void Invoke_SameThread_TReturn_TArg1_TArg2_PropagatesException() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static double action(int parameter, double doubleParameter) => throw new InvalidOperationException("Throw this"); + // Send + current thread forces direct callback invocation + Assert.Throws(() => dispatcher.Invoke(action, DispatcherPriority.Send, TimeSpan.FromSeconds(3), 4, 14.5)); + } + + [WpfFact] + public void Invoke_SameThread_DispatcherOperation_TReturn_TArg1_TArg2_PropagatesException() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static double action(int parameter, double doubleParameter) => throw new InvalidOperationException("Throw this"); + // Anything different than Send + current thread is DispatcherOperation allocation and queue pass + Assert.Throws(() => dispatcher.Invoke(action, DispatcherPriority.Background, TimeSpan.FromSeconds(3), 4, 14.5)); + } + + [WpfFact] + public void Invoke_SameThread_Legacy_PropagatesException() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static object action(object exceptionText) => throw new InvalidOperationException((string)exceptionText); + // Send + current thread forces direct callback invocation + Assert.Throws(() => dispatcher.Invoke(DispatcherPriority.Send, TimeSpan.FromSeconds(3), (DispatcherOperationCallback)action, "Throw this")); + } + + [WpfFact] + public void Invoke_SameThread_Legacy_UnhandledException_Handled_DoesNotThrow() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + dispatcher.UnhandledException += unhandledException; + + // Legacy Invoke with unhandled exception handler will not propagate the exception + static void unhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) => e.Handled = true; + static object action(object exceptionText) => throw new InvalidOperationException((string)exceptionText); + + // Send + current thread forces direct callback invocation + dispatcher.Invoke(DispatcherPriority.Send, TimeSpan.FromSeconds(3), (DispatcherOperationCallback)action, "Throw this"); + + dispatcher.UnhandledException -= unhandledException; + } + + [WpfFact] + public void Invoke_SameThread_Legacy_UnhandledExceptionFilter_RequestCatch_PropagatesException() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + dispatcher.UnhandledException += unhandledException; + dispatcher.UnhandledExceptionFilter += unhandledExceptionFilter; + + // Legacy Invoke with unhandled exception handler will not propagate the exception unless the filter requests it + static void unhandledExceptionFilter(object sender, DispatcherUnhandledExceptionFilterEventArgs e) => e.RequestCatch = false; + static void unhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) => e.Handled = true; + static object action(object exceptionText) => throw new InvalidOperationException((string)exceptionText); + + // Send + current thread forces direct callback invocation + Assert.Throws(() => dispatcher.Invoke(DispatcherPriority.Send, TimeSpan.FromSeconds(3), (DispatcherOperationCallback)action, "Throw this")); + + dispatcher.UnhandledException -= unhandledException; + dispatcher.UnhandledExceptionFilter -= unhandledExceptionFilter; + } + + [WpfFact] + public void Invoke_SameThread_DispatcherOperation_Legacy_PropagatesException() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + + static object action(object exceptionText) => throw new InvalidOperationException((string)exceptionText); + // Anything different than Send + current thread is DispatcherOperation allocation and queue pass + Assert.Throws(() => dispatcher.Invoke(DispatcherPriority.Background, TimeSpan.FromSeconds(3), (DispatcherOperationCallback)action, "Throw this")); + } + + [WpfFact] + public void Invoke_SameThread_DispatcherOperation_Legacy_UnhandledException_Handled_DoesNotThrow() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + dispatcher.UnhandledException += unhandledException; + + // Legacy Invoke with unhandled exception handler will not propagate the exception + static void unhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) => e.Handled = true; + static object action(object exceptionText) => throw new InvalidOperationException((string)exceptionText); + + // Anything different than Send + current thread is DispatcherOperation allocation and queue pass + dispatcher.Invoke(DispatcherPriority.Background, TimeSpan.FromSeconds(3), (DispatcherOperationCallback)action, "Throw this"); + + dispatcher.UnhandledException -= unhandledException; + } + + [WpfFact] + public void Invoke_SameThread_DispatcherOperation_Legacy_UnhandledExceptionFilter_RequestCatch_PropagatesException() + { + Dispatcher dispatcher = Dispatcher.CurrentDispatcher; + dispatcher.UnhandledException += unhandledException; + dispatcher.UnhandledExceptionFilter += unhandledExceptionFilter; + + // Legacy Invoke with unhandled exception handler will not propagate the exception unless the filter requests it + static void unhandledExceptionFilter(object sender, DispatcherUnhandledExceptionFilterEventArgs e) => e.RequestCatch = false; + static void unhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) => e.Handled = true; + static object action(object exceptionText) => throw new InvalidOperationException((string)exceptionText); + + // Anything different than Send + current thread is DispatcherOperation allocation and queue pass + Assert.Throws(() => dispatcher.Invoke(DispatcherPriority.Background, TimeSpan.FromSeconds(3), (DispatcherOperationCallback)action, "Throw this")); + + dispatcher.UnhandledException -= unhandledException; + dispatcher.UnhandledExceptionFilter -= unhandledExceptionFilter; + } + [WpfTheory] [InlineData(DispatcherPriority.Invalid)] [InlineData(DispatcherPriority.Invalid - 1)] @@ -200,4 +459,4 @@ public void VerifyAccess_InvokeOnDifferentThread_ThrowsInvalidOperationException thread.Join(); Assert.True(threwInvalidOperationException); } -} \ No newline at end of file +}