From 2a44d8b56463c71e4a1757d43e8fa9b8e762ab98 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Mar 2021 13:25:50 +0100 Subject: [PATCH 001/822] Initial implementation of UI automation. Follows WPF/UWP API as closely as possible. Limited to win32 right now. Broken in many places. --- .../AutomationElementIdentifiers.cs | 28 + .../Automation/AutomationLiveSetting.cs | 28 + .../Automation/AutomationProperties.cs | 539 ++++++++++++++++++ .../Automation/AutomationProperty.cs | 11 + .../Automation/ElementNotEnabledException.cs | 10 + .../ExpandCollapsePatternIdentifiers.cs | 15 + .../Automation/ExpandCollapseState.cs | 29 + .../Automation/IsOffscreenBehavior.cs | 26 + .../Automation/Peers/AutomationPeer.cs | 227 ++++++++ .../Automation/Peers/ButtonAutomationPeer.cs | 29 + .../Peers/CheckBoxAutomationPeer.cs | 20 + .../Peers/ComboBoxAutomationPeer.cs | 123 ++++ .../Peers/ContentControlAutomationPeer.cs | 39 ++ .../Automation/Peers/ControlAutomationPeer.cs | 196 +++++++ .../Automation/Peers/ImageAutomationPeer.cs | 20 + .../Peers/ItemsControlAutomationPeer.cs | 57 ++ .../Peers/ListItemAutomationPeer.cs | 82 +++ .../Automation/Peers/MenuAutomationPeer.cs | 22 + .../Peers/MenuItemAutomationPeer.cs | 39 ++ .../Automation/Peers/NoneAutomationPeer.cs | 28 + .../Peers/PopupRootAutomationPeer.cs | 36 ++ .../Peers/RangeBaseAutomationPeer.cs | 34 ++ .../Peers/ScrollViewerAutomationPeer.cs | 175 ++++++ .../SelectingItemsControlAutomationPeer.cs | 87 +++ .../Automation/Peers/SliderAutomationPeer.cs | 20 + .../Peers/TabControlAutomationPeer.cs | 20 + .../Automation/Peers/TabItemAutomationPeer.cs | 18 + .../Automation/Peers/TextAutomationPeer.cs | 28 + .../Automation/Peers/TextBoxAutomationPeer.cs | 21 + .../Peers/ToggleButtonAutomationPeer.cs | 39 ++ .../Peers/UnrealizedElementAutomationPeer.cs | 32 ++ .../Automation/Peers/WindowAutomationPeer.cs | 39 ++ .../Peers/WindowBaseAutomationPeer.cs | 72 +++ .../Automation/Platform/IAutomationNode.cs | 32 ++ .../Platform/IAutomationNodeFactory.cs | 18 + .../Platform/IRootAutomationNode.cs | 20 + .../Provider/IExpandCollapseProvider.cs | 24 + .../Automation/Provider/IInvokeProvider.cs | 15 + .../Provider/IRangeValueProvider.cs | 37 ++ .../Automation/Provider/IRootProvider.cs | 14 + .../Automation/Provider/IScrollProvider.cs | 71 +++ .../Provider/ISelectionItemProvider .cs | 37 ++ .../Automation/Provider/ISelectionProvider.cs | 29 + .../Automation/Provider/IToggleProvider.cs | 40 ++ .../Automation/Provider/IValueProvider.cs | 31 + .../RangeValuePatternIdentifiers.cs | 30 + .../Automation/ScrollPatternIdentifiers.cs | 45 ++ .../Automation/SelectionPatternIdentifiers.cs | 25 + src/Avalonia.Controls/Button.cs | 9 + src/Avalonia.Controls/CheckBox.cs | 6 + src/Avalonia.Controls/ComboBox.cs | 7 + src/Avalonia.Controls/Control.cs | 21 + src/Avalonia.Controls/Image.cs | 7 + src/Avalonia.Controls/ItemsControl.cs | 7 + src/Avalonia.Controls/ListBoxItem.cs | 8 +- src/Avalonia.Controls/Menu.cs | 7 + src/Avalonia.Controls/MenuItem.cs | 7 + src/Avalonia.Controls/Primitives/Popup.cs | 14 +- src/Avalonia.Controls/Primitives/PopupRoot.cs | 7 + .../Primitives/ToggleButton.cs | 7 + src/Avalonia.Controls/ScrollViewer.cs | 7 + src/Avalonia.Controls/Slider.cs | 7 + src/Avalonia.Controls/TabControl.cs | 7 + src/Avalonia.Controls/TabItem.cs | 7 + src/Avalonia.Controls/TextBlock.cs | 9 +- src/Avalonia.Controls/TextBox.cs | 7 + src/Avalonia.Controls/Window.cs | 7 + src/Avalonia.Input/KeyboardDevice.cs | 32 +- .../AutomationNode.ExpandCollapse.cs | 19 + .../Automation/AutomationNode.RangeValue.cs | 19 + .../Automation/AutomationNode.Scroll.cs | 32 ++ .../Automation/AutomationNode.Selection.cs | 38 ++ .../Automation/AutomationNode.Toggle.cs | 13 + .../Automation/AutomationNode.Value.cs | 18 + .../Automation/AutomationNode.cs | 328 +++++++++++ .../Automation/AutomationNodeFactory.cs | 20 + .../Automation/RootAutomationNode.cs | 72 +++ .../Avalonia.Win32/Avalonia.Win32.csproj | 3 + .../Interop/Automation/IDockProvider.cs | 26 + .../Automation/IExpandCollapseProvider.cs | 16 + .../Interop/Automation/IGridItemProvider.cs | 17 + .../Interop/Automation/IGridProvider.cs | 15 + .../Interop/Automation/IInvokeProvider.cs | 19 + .../Automation/IMultipleViewProvider.cs | 16 + .../Interop/Automation/IRangeValueProvider.cs | 19 + .../IRawElementProviderAdviseEvents.cs | 15 + .../Automation/IRawElementProviderFragment.cs | 34 ++ .../IRawElementProviderFragmentRoot.cs | 14 + .../Automation/IRawElementProviderSimple.cs | 285 +++++++++ .../Automation/IRawElementProviderSimple2.cs | 14 + .../Interop/Automation/IScrollItemProvider.cs | 13 + .../Interop/Automation/IScrollProvider.cs | 21 + .../Automation/ISelectionItemProvider.cs | 17 + .../Interop/Automation/ISelectionProvider.cs | 15 + .../Automation/ISynchronizedInputProvider.cs | 26 + .../Interop/Automation/ITableItemProvider.cs | 14 + .../Interop/Automation/ITableProvider.cs | 24 + .../Interop/Automation/ITextProvider.cs | 30 + .../Interop/Automation/ITextRangeProvider.cs | 48 ++ .../Interop/Automation/IToggleProvider.cs | 15 + .../Interop/Automation/ITransformProvider.cs | 18 + .../Interop/Automation/IValueProvider.cs | 17 + .../Interop/Automation/IWindowProvider.cs | 42 ++ .../Interop/Automation/UiaCoreProviderApi.cs | 91 +++ .../Interop/Automation/UiaCoreTypesApi.cs | 62 ++ .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 16 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 28 +- .../Automation/ControlAutomationPeerTests.cs | 253 ++++++++ .../Primitives/PopupTests.cs | 1 + .../KeyboardDeviceTests.cs | 25 + .../MockWindowingPlatform.cs | 7 +- 111 files changed, 4649 insertions(+), 33 deletions(-) create mode 100644 src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs create mode 100644 src/Avalonia.Controls/Automation/AutomationLiveSetting.cs create mode 100644 src/Avalonia.Controls/Automation/AutomationProperties.cs create mode 100644 src/Avalonia.Controls/Automation/AutomationProperty.cs create mode 100644 src/Avalonia.Controls/Automation/ElementNotEnabledException.cs create mode 100644 src/Avalonia.Controls/Automation/ExpandCollapsePatternIdentifiers.cs create mode 100644 src/Avalonia.Controls/Automation/ExpandCollapseState.cs create mode 100644 src/Avalonia.Controls/Automation/IsOffscreenBehavior.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/ContentControlAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/TextAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/UnrealizedElementAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/Platform/IAutomationNode.cs create mode 100644 src/Avalonia.Controls/Automation/Platform/IAutomationNodeFactory.cs create mode 100644 src/Avalonia.Controls/Automation/Platform/IRootAutomationNode.cs create mode 100644 src/Avalonia.Controls/Automation/Provider/IExpandCollapseProvider.cs create mode 100644 src/Avalonia.Controls/Automation/Provider/IInvokeProvider.cs create mode 100644 src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs create mode 100644 src/Avalonia.Controls/Automation/Provider/IRootProvider.cs create mode 100644 src/Avalonia.Controls/Automation/Provider/IScrollProvider.cs create mode 100644 src/Avalonia.Controls/Automation/Provider/ISelectionItemProvider .cs create mode 100644 src/Avalonia.Controls/Automation/Provider/ISelectionProvider.cs create mode 100644 src/Avalonia.Controls/Automation/Provider/IToggleProvider.cs create mode 100644 src/Avalonia.Controls/Automation/Provider/IValueProvider.cs create mode 100644 src/Avalonia.Controls/Automation/RangeValuePatternIdentifiers.cs create mode 100644 src/Avalonia.Controls/Automation/ScrollPatternIdentifiers.cs create mode 100644 src/Avalonia.Controls/Automation/SelectionPatternIdentifiers.cs create mode 100644 src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs create mode 100644 src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs create mode 100644 src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs create mode 100644 src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs create mode 100644 src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs create mode 100644 src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs create mode 100644 src/Windows/Avalonia.Win32/Automation/AutomationNode.cs create mode 100644 src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs create mode 100644 src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IDockProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IExpandCollapseProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IGridItemProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IGridProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IInvokeProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IMultipleViewProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IRangeValueProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderAdviseEvents.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragment.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragmentRoot.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple2.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IScrollItemProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IScrollProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/ISelectionItemProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/ISelectionProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/ISynchronizedInputProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/ITableItemProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/ITableProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/ITextProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/ITextRangeProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IToggleProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/ITransformProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IValueProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/IWindowProvider.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs create mode 100644 src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs create mode 100644 tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs diff --git a/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs b/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs new file mode 100644 index 0000000000..7f6bff11ad --- /dev/null +++ b/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs @@ -0,0 +1,28 @@ +using Avalonia.Automation.Peers; + +namespace Avalonia.Automation +{ + /// + /// Contains values used as automation property identifiers by UI Automation providers. + /// + public static class AutomationElementIdentifiers + { + /// + /// Identifies the bounding rectangle automation property. The bounding rectangle property + /// value is returned by the method. + /// + public static AutomationProperty BoundingRectangleProperty { get; } = new(); + + /// + /// Identifies the class name automation property. The class name property value is returned + /// by the method. + /// + public static AutomationProperty ClassNameProperty { get; } = new(); + + /// + /// Identifies the name automation property. The class name property value is returned + /// by the method. + /// + public static AutomationProperty NameProperty { get; } = new(); + } +} diff --git a/src/Avalonia.Controls/Automation/AutomationLiveSetting.cs b/src/Avalonia.Controls/Automation/AutomationLiveSetting.cs new file mode 100644 index 0000000000..55de657b32 --- /dev/null +++ b/src/Avalonia.Controls/Automation/AutomationLiveSetting.cs @@ -0,0 +1,28 @@ +namespace Avalonia.Automation +{ + /// + /// Describes the notification characteristics of a particular live region + /// + public enum AutomationLiveSetting + { + /// + /// The element does not send notifications if the content of the live region has changed. + /// + Off = 0, + + /// + /// The element sends non-interruptive notifications if the content of the live region has + /// changed. With this setting, UI Automation clients and assistive technologies are expected + /// to not interrupt the user to inform of changes to the live region. + /// + Polite = 1, + + /// + /// The element sends interruptive notifications if the content of the live region has changed. + /// With this setting, UI Automation clients and assistive technologies are expected to interrupt + /// the user to inform of changes to the live region. + /// + Assertive = 2, + } +} + diff --git a/src/Avalonia.Controls/Automation/AutomationProperties.cs b/src/Avalonia.Controls/Automation/AutomationProperties.cs new file mode 100644 index 0000000000..8ff1210a76 --- /dev/null +++ b/src/Avalonia.Controls/Automation/AutomationProperties.cs @@ -0,0 +1,539 @@ +using System; +using Avalonia.Controls; + +namespace Avalonia.Automation +{ + public static class AutomationProperties + { + internal const int AutomationPositionInSetDefault = -1; + internal const int AutomationSizeOfSetDefault = -1; + + /// + /// Defines the AutomationProperties.AcceleratorKey attached property. + /// + public static readonly AttachedProperty AcceleratorKeyProperty = + AvaloniaProperty.RegisterAttached( + "AcceleratorKey", + typeof(AutomationProperties)); + + /// + /// Defines the AutomationProperties.AccessKey attached property + /// + public static readonly AttachedProperty AccessKeyProperty = + AvaloniaProperty.RegisterAttached( + "AccessKey", + typeof(AutomationProperties)); + + /// + /// Defines the AutomationProperties.AutomationId attached property. + /// + public static readonly AttachedProperty AutomationIdProperty = + AvaloniaProperty.RegisterAttached( + "AutomationId", + typeof(AutomationProperties)); + + /// + /// Defines the AutomationProperties.HelpText attached property. + /// + public static readonly AttachedProperty HelpTextProperty = + AvaloniaProperty.RegisterAttached( + "HelpText", + typeof(AutomationProperties)); + + /// + /// Defines the AutomationProperties.IsColumnHeader attached property. + /// + public static readonly AttachedProperty IsColumnHeaderProperty = + AvaloniaProperty.RegisterAttached( + "IsColumnHeader", + typeof(AutomationProperties), + false); + + /// + /// Defines the AutomationProperties.IsRequiredForForm attached property. + /// + public static readonly AttachedProperty IsRequiredForFormProperty = + AvaloniaProperty.RegisterAttached( + "IsRequiredForForm", + typeof(AutomationProperties), + false); + + /// + /// Defines the AutomationProperties.IsRowHeader attached property. + /// + public static readonly AttachedProperty IsRowHeaderProperty = + AvaloniaProperty.RegisterAttached( + "IsRowHeader", + typeof(AutomationProperties), + false); + + /// + /// Defines the AutomationProperties.IsOffscreenBehavior attached property. + /// + public static readonly AttachedProperty IsOffscreenBehaviorProperty = + AvaloniaProperty.RegisterAttached( + "IsOffscreenBehavior", + typeof(AutomationProperties), + IsOffscreenBehavior.Default); + + /// + /// Defines the AutomationProperties.ItemStatus attached property. + /// + public static readonly AttachedProperty ItemStatusProperty = + AvaloniaProperty.RegisterAttached( + "ItemStatus", + typeof(AutomationProperties)); + + /// + /// Defines the AutomationProperties.ItemType attached property. + /// + public static readonly AttachedProperty ItemTypeProperty = + AvaloniaProperty.RegisterAttached( + "ItemType", + typeof(AutomationProperties)); + + /// + /// Defines the AutomationProperties.LabeledBy attached property. + /// + public static readonly AttachedProperty LabeledByProperty = + AvaloniaProperty.RegisterAttached( + "LabeledBy", + typeof(AutomationProperties)); + + /// + /// Defines the AutomationProperties.LiveSetting attached property. + /// + public static readonly AttachedProperty LiveSettingProperty = + AvaloniaProperty.RegisterAttached( + "LiveSetting", + typeof(AutomationProperties), + AutomationLiveSetting.Off); + + /// + /// Defines the AutomationProperties.Name attached attached property. + /// + public static readonly AttachedProperty NameProperty = + AvaloniaProperty.RegisterAttached( + "Name", + typeof(AutomationProperties)); + + /// + /// Defines the AutomationProperties.PositionInSet attached property. + /// + /// + /// The PositionInSet property describes the ordinal location of the element within a set + /// of elements which are considered to be siblings. PositionInSet works in coordination + /// with the SizeOfSet property to describe the ordinal location in the set. + /// + public static readonly AttachedProperty PositionInSetProperty = + AvaloniaProperty.RegisterAttached( + "PositionInSet", + typeof(AutomationProperties), + AutomationPositionInSetDefault); + + /// + /// Defines the AutomationProperties.SizeOfSet attached property. + /// + /// + /// The SizeOfSet property describes the count of automation elements in a group or set + /// that are considered to be siblings. SizeOfSet works in coordination with the PositionInSet + /// property to describe the count of items in the set. + /// + public static readonly AttachedProperty SizeOfSetProperty = + AvaloniaProperty.RegisterAttached( + "SizeOfSet", + typeof(AutomationProperties), + AutomationSizeOfSetDefault); + + /// + /// Helper for setting AcceleratorKey property on a StyledElement. + /// + public static void SetAcceleratorKey(StyledElement element, string value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(AcceleratorKeyProperty, value); + } + + /// + /// Helper for reading AcceleratorKey property from a StyledElement. + /// + public static string GetAcceleratorKey(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((string)element.GetValue(AcceleratorKeyProperty)); + } + + /// + /// Helper for setting AccessKey property on a StyledElement. + /// + public static void SetAccessKey(StyledElement element, string value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(AccessKeyProperty, value); + } + + /// + /// Helper for reading AccessKey property from a StyledElement. + /// + public static string GetAccessKey(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((string)element.GetValue(AccessKeyProperty)); + } + + /// + /// Helper for setting AutomationId property on a StyledElement. + /// + public static void SetAutomationId(StyledElement element, string value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(AutomationIdProperty, value); + } + + /// + /// Helper for reading AutomationId property from a StyledElement. + /// + public static string GetAutomationId(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return element.GetValue(AutomationIdProperty); + } + + /// + /// Helper for setting HelpText property on a StyledElement. + /// + public static void SetHelpText(StyledElement element, string value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(HelpTextProperty, value); + } + + /// + /// Helper for reading HelpText property from a StyledElement. + /// + public static string GetHelpText(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((string)element.GetValue(HelpTextProperty)); + } + + /// + /// Helper for setting IsColumnHeader property on a StyledElement. + /// + public static void SetIsColumnHeader(StyledElement element, bool value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(IsColumnHeaderProperty, value); + } + + /// + /// Helper for reading IsColumnHeader property from a StyledElement. + /// + public static bool GetIsColumnHeader(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((bool)element.GetValue(IsColumnHeaderProperty)); + } + + /// + /// Helper for setting IsRequiredForForm property on a StyledElement. + /// + public static void SetIsRequiredForForm(StyledElement element, bool value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(IsRequiredForFormProperty, value); + } + + /// + /// Helper for reading IsRequiredForForm property from a StyledElement. + /// + public static bool GetIsRequiredForForm(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((bool)element.GetValue(IsRequiredForFormProperty)); + } + + /// + /// Helper for reading IsRowHeader property from a StyledElement. + /// + public static bool GetIsRowHeader(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((bool)element.GetValue(IsRowHeaderProperty)); + } + + /// + /// Helper for setting IsRowHeader property on a StyledElement. + /// + public static void SetIsRowHeader(StyledElement element, bool value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(IsRowHeaderProperty, value); + } + + /// + /// Helper for setting IsOffscreenBehavior property on a StyledElement. + /// + public static void SetIsOffscreenBehavior(StyledElement element, IsOffscreenBehavior value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(IsOffscreenBehaviorProperty, value); + } + + /// + /// Helper for reading IsOffscreenBehavior property from a StyledElement. + /// + public static IsOffscreenBehavior GetIsOffscreenBehavior(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((IsOffscreenBehavior)element.GetValue(IsOffscreenBehaviorProperty)); + } + + /// + /// Helper for setting ItemStatus property on a StyledElement. + /// + public static void SetItemStatus(StyledElement element, string value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(ItemStatusProperty, value); + } + + /// + /// Helper for reading ItemStatus property from a StyledElement. + /// + public static string GetItemStatus(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((string)element.GetValue(ItemStatusProperty)); + } + + /// + /// Helper for setting ItemType property on a StyledElement. + /// + public static void SetItemType(StyledElement element, string value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(ItemTypeProperty, value); + } + + /// + /// Helper for reading ItemType property from a StyledElement. + /// + public static string GetItemType(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((string)element.GetValue(ItemTypeProperty)); + } + + /// + /// Helper for setting LabeledBy property on a StyledElement. + /// + public static void SetLabeledBy(StyledElement element, IControl value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(LabeledByProperty, value); + } + + /// + /// Helper for reading LabeledBy property from a StyledElement. + /// + public static IControl GetLabeledBy(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return element.GetValue(LabeledByProperty); + } + + /// + /// Helper for setting LiveSetting property on a StyledElement. + /// + public static void SetLiveSetting(StyledElement element, AutomationLiveSetting value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(LiveSettingProperty, value); + } + + /// + /// Helper for reading LiveSetting property from a StyledElement. + /// + public static AutomationLiveSetting GetLiveSetting(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((AutomationLiveSetting)element.GetValue(LiveSettingProperty)); + } + + /// + /// Helper for setting Name property on a StyledElement. + /// + public static void SetName(StyledElement element, string value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(NameProperty, value); + } + + /// + /// Helper for reading Name property from a StyledElement. + /// + public static string GetName(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((string)element.GetValue(NameProperty)); + } + + /// + /// Helper for setting PositionInSet property on a StyledElement. + /// + public static void SetPositionInSet(StyledElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(PositionInSetProperty, value); + } + + /// + /// Helper for reading PositionInSet property from a StyledElement. + /// + public static int GetPositionInSet(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((int)element.GetValue(PositionInSetProperty)); + } + + /// + /// Helper for setting SizeOfSet property on a StyledElement. + /// + public static void SetSizeOfSet(StyledElement element, int value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(SizeOfSetProperty, value); + } + + /// + /// Helper for reading SizeOfSet property from a StyledElement. + /// + public static int GetSizeOfSet(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return ((int)element.GetValue(SizeOfSetProperty)); + } + } +} + diff --git a/src/Avalonia.Controls/Automation/AutomationProperty.cs b/src/Avalonia.Controls/Automation/AutomationProperty.cs new file mode 100644 index 0000000000..16968b271d --- /dev/null +++ b/src/Avalonia.Controls/Automation/AutomationProperty.cs @@ -0,0 +1,11 @@ +namespace Avalonia.Automation +{ + /// + /// Identifies a property of or of a specific + /// control pattern. + /// + public sealed class AutomationProperty + { + internal AutomationProperty() { } + } +} diff --git a/src/Avalonia.Controls/Automation/ElementNotEnabledException.cs b/src/Avalonia.Controls/Automation/ElementNotEnabledException.cs new file mode 100644 index 0000000000..ac73d50603 --- /dev/null +++ b/src/Avalonia.Controls/Automation/ElementNotEnabledException.cs @@ -0,0 +1,10 @@ +using System; + +namespace Avalonia.Automation +{ + public class ElementNotEnabledException : Exception + { + public ElementNotEnabledException() : base("Element not enabled.") { } + public ElementNotEnabledException(string message) : base(message) { } + } +} diff --git a/src/Avalonia.Controls/Automation/ExpandCollapsePatternIdentifiers.cs b/src/Avalonia.Controls/Automation/ExpandCollapsePatternIdentifiers.cs new file mode 100644 index 0000000000..342bdaceb1 --- /dev/null +++ b/src/Avalonia.Controls/Automation/ExpandCollapsePatternIdentifiers.cs @@ -0,0 +1,15 @@ +using Avalonia.Automation.Provider; + +namespace Avalonia.Automation +{ + /// + /// Contains values used as identifiers by . + /// + public static class ExpandCollapsePatternIdentifiers + { + /// + /// Identifies automation property. + /// + public static AutomationProperty ExpandCollapseStateProperty { get; } = new(); + } +} diff --git a/src/Avalonia.Controls/Automation/ExpandCollapseState.cs b/src/Avalonia.Controls/Automation/ExpandCollapseState.cs new file mode 100644 index 0000000000..c6b4feeb50 --- /dev/null +++ b/src/Avalonia.Controls/Automation/ExpandCollapseState.cs @@ -0,0 +1,29 @@ +namespace Avalonia.Automation +{ + /// + /// Contains values that specify the of a UI Automation element. + /// + public enum ExpandCollapseState + { + /// + /// No child nodes, controls, or content of the UI Automation element are displayed. + /// + Collapsed, + + /// + /// All child nodes, controls or content of the UI Automation element are displayed. + /// + Expanded, + + /// + /// The UI Automation element has no child nodes, controls, or content to display. + /// + LeafNode, + + /// + /// Some, but not all, child nodes, controls, or content of the UI Automation element are + /// displayed. + /// + PartiallyExpanded + } +} diff --git a/src/Avalonia.Controls/Automation/IsOffscreenBehavior.cs b/src/Avalonia.Controls/Automation/IsOffscreenBehavior.cs new file mode 100644 index 0000000000..128c1e1dcc --- /dev/null +++ b/src/Avalonia.Controls/Automation/IsOffscreenBehavior.cs @@ -0,0 +1,26 @@ +namespace Avalonia.Automation +{ + /// + /// This enum offers different ways of evaluating the IsOffscreen AutomationProperty + /// + public enum IsOffscreenBehavior + { + /// + /// The AutomationProperty IsOffscreen is calculated based on IsVisible. + /// + Default, + /// + /// The AutomationProperty IsOffscreen is false. + /// + Onscreen, + /// + /// The AutomationProperty IsOffscreen if true. + /// + Offscreen, + /// + /// The AutomationProperty IsOffscreen is calculated based on clip regions. + /// + FromClip, + } +} + diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs new file mode 100644 index 0000000000..aefd1febd0 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -0,0 +1,227 @@ +using System.Collections.Generic; +using System.Globalization; +using Avalonia.Automation.Platform; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public enum AutomationControlType + { + Button, + Calendar, + CheckBox, + ComboBox, + Edit, + Hyperlink, + Image, + ListItem, + List, + Menu, + MenuBar, + MenuItem, + ProgressBar, + RadioButton, + ScrollBar, + Slider, + Spinner, + StatusBar, + Tab, + TabItem, + Text, + ToolBar, + ToolTip, + Tree, + TreeItem, + Custom, + Group, + Thumb, + DataGrid, + DataItem, + Document, + SplitButton, + Window, + Pane, + Header, + HeaderItem, + Table, + TitleBar, + Separator, + } + + /// + /// Provides a base class that exposes an element to UI Automation. + /// + public abstract class AutomationPeer + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The factory to use to create the platform automation node. + /// + protected AutomationPeer(IAutomationNodeFactory factory) + { + Node = factory.CreateNode(this); + } + + /// + /// Gets the related node in the platform UI Automation tree. + /// + public IAutomationNode Node { get; } + + /// + /// Attempts to bring the element associated with the automation peer into view. + /// + public void BringIntoView() => BringIntoViewCore(); + + /// + /// Gets the control type for the element that is associated with the UI Automation peer. + /// + public AutomationControlType GetAutomationControlType() => GetAutomationControlTypeCore(); + + /// + /// Gets the automation ID of the element that is associated with the UI Automation peer. + /// + public string? GetAutomationId() => GetAutomationIdCore(); + + /// + /// Gets the bounding rectangle of the element that is associated with the automation peer + /// in top-level coordinates. + /// + public Rect GetBoundingRectangle() => GetBoundingRectangleCore(); + + /// + /// Gets the child automation peers. + /// + public IReadOnlyList GetChildren() => GetOrCreateChildrenCore(); + + /// + /// Gets a string that describes the class of the element. + /// + public string GetClassName() => GetClassNameCore() ?? string.Empty; + + /// + /// Gets a human-readable localized string that represents the type of the control that is + /// associated with this automation peer. + /// + public string GetLocalizedControlType() => GetLocalizedControlTypeCore(); + + /// + /// Gets text that describes the element that is associated with this automation peer. + /// + public string GetName() => GetNameCore() ?? string.Empty; + + /// + /// Gets the that is the parent of this . + /// + /// + public AutomationPeer? GetParent() => GetParentCore(); + + /// + /// Gets a value that indicates whether the element that is associated with this automation + /// peer currently has keyboard focus. + /// + public bool HasKeyboardFocus() => HasKeyboardFocusCore(); + + /// + /// Gets a value that indicates whether the element that is associated with this automation + /// peer contains data that is presented to the user. + /// + public bool IsContentElement() => IsControlElement() && IsContentElementCore(); + + /// + /// Gets a value that indicates whether the element is understood by the user as + /// interactive or as contributing to the logical structure of the control in the GUI. + /// + public bool IsControlElement() => IsControlElementCore(); + + /// + /// Gets a value indicating whether the control is enabled for user interaction. + /// + public bool IsEnabled() => IsEnabledCore(); + + /// + /// Gets a value that indicates whether the element can accept keyboard focus. + /// + /// + public bool IsKeyboardFocusable() => IsKeyboardFocusableCore(); + + /// + /// Sets the keyboard focus on the element that is associated with this automation peer. + /// + public void SetFocus() => SetFocusCore(); + + /// + /// Shows the context menu for the element that is associated with this automation peer. + /// + /// true if a context menu is present for the element; otherwise false. + public bool ShowContextMenu() => ShowContextMenuCore(); + + /// + /// Raises an event to notify the automation client of a changed property value. + /// + /// The property that changed. + /// The previous value of the property. + /// The new value of the property. + public void RaisePropertyChangedEvent( + AutomationProperty automationProperty, + object? oldValue, + object? newValue) + { + Node.PropertyChanged(automationProperty, oldValue, newValue); + } + + protected virtual string GetLocalizedControlTypeCore() + { + var controlType = GetAutomationControlType(); + + return controlType switch + { + AutomationControlType.CheckBox => "check box", + AutomationControlType.ComboBox => "combo box", + AutomationControlType.ListItem => "list item", + AutomationControlType.MenuBar => "menu bar", + AutomationControlType.MenuItem => "menu item", + AutomationControlType.ProgressBar => "progress bar", + AutomationControlType.RadioButton => "radio button", + AutomationControlType.ScrollBar => "scroll bar", + AutomationControlType.StatusBar => "status bar", + AutomationControlType.TabItem => "tab item", + AutomationControlType.ToolBar => "toolbar", + AutomationControlType.ToolTip => "tooltip", + AutomationControlType.TreeItem => "tree item", + AutomationControlType.Custom => "custom", + AutomationControlType.DataGrid => "data grid", + AutomationControlType.DataItem => "data item", + AutomationControlType.SplitButton => "split button", + AutomationControlType.HeaderItem => "header item", + AutomationControlType.TitleBar => "title bar", + _ => controlType.ToString().ToLowerInvariant(), + }; + } + + protected abstract void BringIntoViewCore(); + protected abstract AutomationControlType GetAutomationControlTypeCore(); + protected abstract string? GetAutomationIdCore(); + protected abstract Rect GetBoundingRectangleCore(); + protected abstract IReadOnlyList GetOrCreateChildrenCore(); + protected abstract string GetClassNameCore(); + protected abstract string? GetNameCore(); + protected abstract AutomationPeer? GetParentCore(); + protected abstract bool HasKeyboardFocusCore(); + protected abstract bool IsContentElementCore(); + protected abstract bool IsControlElementCore(); + protected abstract bool IsEnabledCore(); + protected abstract bool IsKeyboardFocusableCore(); + protected abstract void SetFocusCore(); + protected abstract bool ShowContextMenuCore(); + protected internal abstract bool TrySetParent(AutomationPeer? parent); + + protected void EnsureEnabled() + { + if (!IsEnabled()) + throw new ElementNotEnabledException(); + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs new file mode 100644 index 0000000000..ded27f14bd --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs @@ -0,0 +1,29 @@ +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class ButtonAutomationPeer : ContentControlAutomationPeer, + IInvokeProvider + { + public ButtonAutomationPeer(IAutomationNodeFactory factory, Button owner) + : base(factory, owner) + { + } + + public void Invoke() + { + EnsureEnabled(); + (Owner as Button)?.PerformClick(); + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Button; + } + } +} + diff --git a/src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs new file mode 100644 index 0000000000..4e98cc7746 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs @@ -0,0 +1,20 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class CheckBoxAutomationPeer : ToggleButtonAutomationPeer + { + public CheckBoxAutomationPeer(IAutomationNodeFactory factory, CheckBox owner) + : base(factory, owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.CheckBox; + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs new file mode 100644 index 0000000000..698bfd1460 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class ComboBoxAutomationPeer : SelectingItemsControlAutomationPeer, + IExpandCollapseProvider + { + private UnrealizedSelectionPeer[]? _selection; + + public ComboBoxAutomationPeer(IAutomationNodeFactory factory, ComboBox owner) + : base(factory, owner) + { + } + + public new ComboBox Owner => (ComboBox)base.Owner; + + public ExpandCollapseState ExpandCollapseState => ToState(Owner.IsDropDownOpen); + public void Collapse() => Owner.IsDropDownOpen = false; + public void Expand() => Owner.IsDropDownOpen = true; + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.ComboBox; + } + + protected override IReadOnlyList? GetSelectionCore() + { + if (ExpandCollapseState == ExpandCollapseState.Expanded) + return base.GetSelectionCore(); + + // If the combo box is not open then we won't have an ItemsPresenter so the default + // GetSelectionCore implementation won't work. For this case we create a separate + // peer to represent the unrealized item. + if (Owner.SelectedItem is object selection) + { + _selection ??= new[] { new UnrealizedSelectionPeer(Node.Factory, this) }; + _selection[0].Item = selection; + return _selection; + } + + return null; + } + + protected override void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + base.OwnerPropertyChanged(sender, e); + + if (e.Property == ComboBox.IsDropDownOpenProperty) + { + RaisePropertyChangedEvent( + ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, + ToState((bool)e.OldValue!), + ToState((bool)e.NewValue!)); + } + } + + private ExpandCollapseState ToState(bool value) + { + return value ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed; + } + + private class UnrealizedSelectionPeer : UnrealizedElementAutomationPeer + { + private readonly ComboBoxAutomationPeer _owner; + private object? _item; + + public UnrealizedSelectionPeer(IAutomationNodeFactory factory, ComboBoxAutomationPeer owner) + : base(factory) + { + _owner = owner; + } + + public object? Item + { + get => _item; + set + { + if (_item != value) + { + var oldValue = GetNameCore(); + _item = value; + RaisePropertyChangedEvent( + AutomationElementIdentifiers.NameProperty, + oldValue, + GetNameCore()); + } + } + } + + protected override string? GetAutomationIdCore() => null; + protected override string GetClassNameCore() => typeof(ComboBoxItem).Name; + protected override AutomationPeer? GetParentCore() => _owner; + protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.ListItem; + + protected override string? GetNameCore() + { + if (_item is Control c) + { + var result = AutomationProperties.GetName(c); + + if (result is null && c is ContentControl cc && cc.Presenter?.Child is TextBlock text) + { + result = text.Text; + } + + if (result is null) + { + result = c.GetValue(ContentControl.ContentProperty)?.ToString(); + } + + return result; + } + + return _item?.ToString(); + } + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/ContentControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ContentControlAutomationPeer.cs new file mode 100644 index 0000000000..db1c7e1aa7 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ContentControlAutomationPeer.cs @@ -0,0 +1,39 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class ContentControlAutomationPeer : ControlAutomationPeer + { + protected ContentControlAutomationPeer(IAutomationNodeFactory factory, ContentControl owner) + : base(factory, owner) + { + } + + public new ContentControl Owner => (ContentControl)base.Owner; + + protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Pane; + + protected override string? GetNameCore() + { + var result = base.GetNameCore(); + + if (result is null && Owner.Presenter?.Child is TextBlock text) + { + result = text.Text; + } + + if (result is null) + { + result = Owner.Content?.ToString(); + } + + return result; + } + + protected override bool IsContentElementCore() => false; + protected override bool IsControlElementCore() => false; + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs new file mode 100644 index 0000000000..7989986653 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Automation.Platform; +using Avalonia.Controls; +using Avalonia.VisualTree; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + /// + /// An automation peer which represents a element. + /// + public class ControlAutomationPeer : AutomationPeer + { + private IReadOnlyList? _children; + private bool _childrenValid; + private AutomationPeer? _parent; + private bool _parentValid; + + public ControlAutomationPeer(IAutomationNodeFactory factory, Control owner) + : base(factory) + { + Owner = owner ?? throw new ArgumentNullException("owner"); + + owner.PropertyChanged += OwnerPropertyChanged; + var visualChildren = ((IVisual)owner).VisualChildren; + visualChildren.CollectionChanged += VisualChildrenChanged; + } + + public Control Owner { get; } + + public static AutomationPeer GetOrCreatePeer(IAutomationNodeFactory factory, Control element) + { + element = element ?? throw new ArgumentNullException("element"); + return element.GetOrCreateAutomationPeer(factory); + } + + public AutomationPeer GetOrCreatePeer(Control element) + { + return element == Owner ? this : GetOrCreatePeer(Node.Factory, element); + } + + protected override void BringIntoViewCore() => Owner.BringIntoView(); + protected override string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner); + protected override Rect GetBoundingRectangleCore() => GetBounds(Owner.TransformedBounds); + + protected virtual IReadOnlyList? GetChildrenCore() + { + var children = ((IVisual)Owner).VisualChildren; + + if (children.Count == 0) + return null; + + var result = new List(); + + foreach (var child in children) + { + if (child is Control c && c.IsVisible) + { + result.Add(GetOrCreatePeer(c)); + } + } + + return result; + } + + protected override AutomationPeer? GetParentCore() + { + EnsureConnected(); + return _parent; + } + + /// + /// Invalidates the peer's children and causes a re-read from . + /// + protected void InvalidateChildren() + { + _childrenValid = false; + Node!.ChildrenChanged(); + } + + /// + /// Invalidates the peer's parent. + /// + protected void InvalidateParent() + { + _parent = null; + _parentValid = false; + } + + protected override IReadOnlyList GetOrCreateChildrenCore() + { + var children = _children ?? Array.Empty(); + + if (_childrenValid) + return children; + + var newChildren = GetChildrenCore() ?? Array.Empty(); + + foreach (var peer in children.Except(newChildren)) + peer.TrySetParent(null); + foreach (var peer in newChildren) + peer.TrySetParent(this); + + _childrenValid = true; + return _children = newChildren; + } + + protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Custom; + protected override string GetClassNameCore() => Owner.GetType().Name; + protected override string? GetNameCore() => AutomationProperties.GetName(Owner); + protected override bool HasKeyboardFocusCore() => Owner.IsFocused; + protected override bool IsContentElementCore() => true; + protected override bool IsControlElementCore() => true; + protected override bool IsEnabledCore() => Owner.IsEnabled; + protected override bool IsKeyboardFocusableCore() => Owner.Focusable; + protected override void SetFocusCore() => Owner.Focus(); + + protected override bool ShowContextMenuCore() + { + var c = Owner; + + while (c is object) + { + if (c.ContextMenu is object) + { + c.ContextMenu.Open(c); + return true; + } + + c = c.Parent as Control; + } + + return false; + } + + protected internal override bool TrySetParent(AutomationPeer? parent) + { + _parent = parent; + return true; + } + + private Rect GetBounds(TransformedBounds? bounds) + { + return bounds?.Bounds.TransformToAABB(bounds!.Value.Transform) ?? default; + } + + private void VisualChildrenChanged(object sender, EventArgs e) => InvalidateChildren(); + + private void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == Visual.IsVisibleProperty) + { + var parent = Owner.GetVisualParent(); + if (parent is Control c) + (GetOrCreatePeer(c) as ControlAutomationPeer)?.InvalidateChildren(); + } + else if (e.Property == Visual.TransformedBoundsProperty) + { + RaisePropertyChangedEvent( + AutomationElementIdentifiers.BoundingRectangleProperty, + GetBounds((TransformedBounds?)e.OldValue), + GetBounds((TransformedBounds?)e.NewValue)); + } + else if (e.Property == Visual.VisualParentProperty) + { + InvalidateParent(); + } + } + + + private void EnsureConnected() + { + if (!_parentValid) + { + var parent = Owner.GetVisualParent(); + + while (parent is object) + { + if (parent is Control c) + { + var parentPeer = GetOrCreatePeer(c); + parentPeer.GetChildren(); + } + + parent = parent.GetVisualParent(); + } + + _parentValid = true; + } + } + } +} + diff --git a/src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs new file mode 100644 index 0000000000..ad88941299 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs @@ -0,0 +1,20 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class ImageAutomationPeer : ControlAutomationPeer + { + public ImageAutomationPeer(IAutomationNodeFactory factory, Control owner) + : base(factory, owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Image; + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs new file mode 100644 index 0000000000..6eed22e6cf --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs @@ -0,0 +1,57 @@ +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class ItemsControlAutomationPeer : ControlAutomationPeer, IScrollProvider + { + private bool _searchedForScrollable; + private IScrollProvider? _scroller; + + public ItemsControlAutomationPeer(IAutomationNodeFactory factory, ItemsControl owner) + : base(factory, owner) + { + } + + public new ItemsControl Owner => (ItemsControl)base.Owner; + public bool HorizontallyScrollable => _scroller?.HorizontallyScrollable ?? false; + public double HorizontalScrollPercent => _scroller?.HorizontalScrollPercent ?? -1; + public double HorizontalViewSize => _scroller?.HorizontalViewSize ?? 0; + public bool VerticallyScrollable => _scroller?.VerticallyScrollable ?? false; + public double VerticalScrollPercent => _scroller?.VerticalScrollPercent ?? -1; + public double VerticalViewSize => _scroller?.VerticalViewSize ?? 0; + + protected virtual IScrollProvider? Scroller + { + get + { + if (!_searchedForScrollable) + { + if (Owner.GetValue(ListBox.ScrollProperty) is Control scrollable) + _scroller = GetOrCreatePeer(scrollable) as IScrollProvider; + _searchedForScrollable = true; + } + + return _scroller; + } + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.List; + } + + public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount) + { + _scroller?.Scroll(horizontalAmount, verticalAmount); + } + + public void SetScrollPercent(double horizontalPercent, double verticalPercent) + { + _scroller?.SetScrollPercent(horizontalPercent, verticalPercent); + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs new file mode 100644 index 0000000000..a9e5089e43 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs @@ -0,0 +1,82 @@ +using System; +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Selection; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class ListItemAutomationPeer : ContentControlAutomationPeer, + ISelectionItemProvider + { + public ListItemAutomationPeer(IAutomationNodeFactory factory, ContentControl owner) + : base(factory, owner) + { + } + + public bool IsSelected => Owner.GetValue(ListBoxItem.IsSelectedProperty); + + public ISelectionProvider? SelectionContainer + { + get + { + if (Owner.Parent is Control parent) + { + var parentPeer = GetOrCreatePeer(parent); + return parentPeer as ISelectionProvider; + } + + return null; + } + } + + public void Select() + { + EnsureEnabled(); + + if (Owner.Parent is SelectingItemsControl parent) + { + var index = parent.ItemContainerGenerator.IndexFromContainer(Owner); + + if (index != -1) + parent.SelectedIndex = index; + } + } + + void ISelectionItemProvider.AddToSelection() + { + EnsureEnabled(); + + if (Owner.Parent is ItemsControl parent && + parent.GetValue(ListBox.SelectionProperty) is ISelectionModel selectionModel) + { + var index = parent.ItemContainerGenerator.IndexFromContainer(Owner); + + if (index != -1) + selectionModel.Select(index); + } + } + + void ISelectionItemProvider.RemoveFromSelection() + { + EnsureEnabled(); + + if (Owner.Parent is ItemsControl parent && + parent.GetValue(ListBox.SelectionProperty) is ISelectionModel selectionModel) + { + var index = parent.ItemContainerGenerator.IndexFromContainer(Owner); + + if (index != -1) + selectionModel.Deselect(index); + } + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.ListItem; + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs new file mode 100644 index 0000000000..38cd7a8d41 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs @@ -0,0 +1,22 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class MenuAutomationPeer : ControlAutomationPeer + { + public MenuAutomationPeer(IAutomationNodeFactory factory, Menu owner) + : base(factory, owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Menu; + } + + protected override bool IsContentElementCore() => false; + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs new file mode 100644 index 0000000000..b1f7774fc9 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs @@ -0,0 +1,39 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class MenuItemAutomationPeer : ControlAutomationPeer + { + public MenuItemAutomationPeer(IAutomationNodeFactory factory, MenuItem owner) + : base(factory, owner) + { + } + + public new MenuItem Owner => (MenuItem)base.Owner; + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.MenuItem; + } + + protected override string? GetNameCore() + { + var result = base.GetNameCore(); + + if (result is null && Owner.HeaderPresenter.Child is TextBlock text) + { + result = text.Text; + } + + if (result is null) + { + result = Owner.Header?.ToString(); + } + + return result; + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs new file mode 100644 index 0000000000..894234e502 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs @@ -0,0 +1,28 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + /// + /// An automation peer which represents an element that is exposed to automation as non- + /// interactive or as not contributing to the logical structure of the application. + /// + public class NoneAutomationPeer : ControlAutomationPeer + { + public NoneAutomationPeer(IAutomationNodeFactory factory, Control owner) + : base(factory, owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Pane; + } + + protected override bool IsContentElementCore() => false; + protected override bool IsControlElementCore() => false; + } +} + diff --git a/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs new file mode 100644 index 0000000000..ed0e8f6d44 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs @@ -0,0 +1,36 @@ +using System; +using Avalonia.Automation.Platform; +using Avalonia.Controls.Primitives; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class PopupRootAutomationPeer : WindowBaseAutomationPeer + { + public PopupRootAutomationPeer(IAutomationNodeFactory factory, PopupRoot owner) + : base(factory, owner) + { + if (owner.IsVisible) + StartTrackingFocus(); + else + owner.Opened += OnOpened; + owner.Closed += OnClosed; + } + + protected override bool IsContentElementCore() => false; + protected override bool IsControlElementCore() => false; + + private void OnOpened(object sender, EventArgs e) + { + ((PopupRoot)Owner).Opened -= OnOpened; + StartTrackingFocus(); + } + + private void OnClosed(object sender, EventArgs e) + { + ((PopupRoot)Owner).Closed -= OnClosed; + StopTrackingFocus(); + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs new file mode 100644 index 0000000000..bafc4c14fc --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs @@ -0,0 +1,34 @@ +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls.Primitives; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public abstract class RangeBaseAutomationPeer : ControlAutomationPeer, IRangeValueProvider + { + public RangeBaseAutomationPeer(IAutomationNodeFactory factory, RangeBase owner) + : base(factory, owner) + { + owner.PropertyChanged += OwnerPropertyChanged; + } + + public new RangeBase Owner => (RangeBase)base.Owner; + public virtual bool IsReadOnly => false; + public double Maximum => Owner.Maximum; + public double Minimum => Owner.Minimum; + public double Value => Owner.Value; + public void SetValue(double value) => Owner.Value = value; + + protected virtual void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == RangeBase.MinimumProperty) + RaisePropertyChangedEvent(RangeValuePatternIdentifiers.MinimumProperty, e.OldValue, e.NewValue); + else if (e.Property == RangeBase.MaximumProperty) + RaisePropertyChangedEvent(RangeValuePatternIdentifiers.MaximumProperty, e.OldValue, e.NewValue); + else if (e.Property == RangeBase.ValueProperty) + RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty, e.OldValue, e.NewValue); + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs new file mode 100644 index 0000000000..165df6c032 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs @@ -0,0 +1,175 @@ +using System; +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls; +using Avalonia.Utilities; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class ScrollViewerAutomationPeer : ControlAutomationPeer, IScrollProvider + { + public ScrollViewerAutomationPeer(IAutomationNodeFactory factory, ScrollViewer owner) + : base(factory, owner) + { + } + + public new ScrollViewer Owner => (ScrollViewer)base.Owner; + + public bool HorizontallyScrollable + { + get => MathUtilities.GreaterThan(Owner.Extent.Width, Owner.Viewport.Width); + } + + public double HorizontalScrollPercent + { + get + { + if (!HorizontallyScrollable) + return ScrollPatternIdentifiers.NoScroll; + return (double)(Owner.Offset.X * 100.0 / (Owner.Extent.Width - Owner.Viewport.Width)); + } + } + + public double HorizontalViewSize + { + get + { + if (MathUtilities.IsZero(Owner.Extent.Width)) + return 100; + return Math.Min(100, Owner.Viewport.Width * 100.0 / Owner.Extent.Width); + } + } + + public bool VerticallyScrollable + { + get => MathUtilities.GreaterThan(Owner.Extent.Height, Owner.Viewport.Height); + } + + public double VerticalScrollPercent + { + get + { + if (!VerticallyScrollable) + return ScrollPatternIdentifiers.NoScroll; + return (double)(Owner.Offset.Y * 100.0 / (Owner.Extent.Height - Owner.Viewport.Height)); + } + } + + public double VerticalViewSize + { + get + { + if (MathUtilities.IsZero(Owner.Extent.Height)) + return 100; + return Math.Min(100, Owner.Viewport.Height * 100.0 / Owner.Extent.Height); + } + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Pane; + } + + protected override bool IsContentElementCore() => false; + + protected override bool IsControlElementCore() + { + // Return false if the control is part of a control template. + return Owner.TemplatedParent is null && base.IsControlElementCore(); + } + + public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount) + { + if (!IsEnabled()) + throw new ElementNotEnabledException(); + + var scrollHorizontally = horizontalAmount != ScrollAmount.NoAmount; + var scrollVertically = verticalAmount != ScrollAmount.NoAmount; + + if (scrollHorizontally && !HorizontallyScrollable || scrollVertically && !VerticallyScrollable) + { + throw new InvalidOperationException("Operation cannot be performed"); + } + + switch (horizontalAmount) + { + case ScrollAmount.LargeDecrement: + Owner.PageLeft(); + break; + case ScrollAmount.SmallDecrement: + Owner.LineLeft(); + break; + case ScrollAmount.SmallIncrement: + Owner.LineRight(); + break; + case ScrollAmount.LargeIncrement: + Owner.PageRight(); + break; + case ScrollAmount.NoAmount: + break; + default: + throw new InvalidOperationException("Operation cannot be performed"); + } + + switch (verticalAmount) + { + case ScrollAmount.LargeDecrement: + Owner.PageUp(); + break; + case ScrollAmount.SmallDecrement: + Owner.LineUp(); + break; + case ScrollAmount.SmallIncrement: + Owner.LineDown(); + break; + case ScrollAmount.LargeIncrement: + Owner.PageDown(); + break; + case ScrollAmount.NoAmount: + break; + default: + throw new InvalidOperationException("Operation cannot be performed"); + } + } + + public void SetScrollPercent(double horizontalPercent, double verticalPercent) + { + if (!IsEnabled()) + throw new ElementNotEnabledException(); + + var scrollHorizontally = horizontalPercent != ScrollPatternIdentifiers.NoScroll; + var scrollVertically = verticalPercent != ScrollPatternIdentifiers.NoScroll; + + if (scrollHorizontally && !HorizontallyScrollable || scrollVertically && !VerticallyScrollable) + { + throw new InvalidOperationException("Operation cannot be performed"); + } + + if (scrollHorizontally && (horizontalPercent < 0.0) || (horizontalPercent > 100.0)) + { + throw new ArgumentOutOfRangeException("horizontalPercent"); + } + + if (scrollVertically && (verticalPercent < 0.0) || (verticalPercent > 100.0)) + { + throw new ArgumentOutOfRangeException("verticalPercent"); + } + + var offset = Owner.Offset; + + if (scrollHorizontally) + { + offset = offset.WithX((Owner.Extent.Width - Owner.Viewport.Width) * horizontalPercent * 0.01); + } + + if (scrollVertically) + { + offset = offset.WithY((Owner.Extent.Height - Owner.Viewport.Height) * verticalPercent * 0.01); + } + + Owner.Offset = offset; + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs new file mode 100644 index 0000000000..b55f653a5d --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Selection; +using Avalonia.VisualTree; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public abstract class SelectingItemsControlAutomationPeer : ItemsControlAutomationPeer, + ISelectionProvider + { + private ISelectionModel _selection; + + protected SelectingItemsControlAutomationPeer(IAutomationNodeFactory factory, SelectingItemsControl owner) + : base(factory, owner) + { + _selection = owner.GetValue(ListBox.SelectionProperty); + _selection.SelectionChanged += OwnerSelectionChanged; + owner.PropertyChanged += OwnerPropertyChanged; + } + + public bool CanSelectMultiple => GetSelectionModeCore().HasFlagCustom(SelectionMode.Multiple); + public bool IsSelectionRequired => GetSelectionModeCore().HasFlagCustom(SelectionMode.AlwaysSelected); + public IReadOnlyList GetSelection() => GetSelectionCore() ?? Array.Empty(); + + protected virtual IReadOnlyList? GetSelectionCore() + { + List? result = null; + + if (Owner is SelectingItemsControl owner) + { + var selection = Owner.GetValue(ListBox.SelectionProperty); + + foreach (var i in selection.SelectedIndexes) + { + var container = owner.ItemContainerGenerator.ContainerFromIndex(i); + + if (container is Control c && ((IVisual)c).IsAttachedToVisualTree) + { + var peer = GetOrCreatePeer(c); + + if (peer is object) + { + result ??= new List(); + result.Add(peer); + } + } + } + + return result; + } + + return result; + } + + protected virtual SelectionMode GetSelectionModeCore() + { + return (Owner as SelectingItemsControl)?.GetValue(ListBox.SelectionModeProperty) ?? SelectionMode.Single; + } + + protected virtual void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == ListBox.SelectionProperty) + { + _selection.SelectionChanged -= OwnerSelectionChanged; + _selection = Owner.GetValue(ListBox.SelectionProperty); + _selection.SelectionChanged += OwnerSelectionChanged; + RaiseSelectionChanged(); + } + } + + protected virtual void OwnerSelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e) + { + RaiseSelectionChanged(); + } + + private void RaiseSelectionChanged() + { + RaisePropertyChangedEvent(SelectionPatternIdentifiers.SelectionProperty, null, null); + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs new file mode 100644 index 0000000000..172b9a1b54 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs @@ -0,0 +1,20 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class SliderAutomationPeer : RangeBaseAutomationPeer + { + public SliderAutomationPeer(IAutomationNodeFactory factory, Slider owner) + : base(factory, owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Slider; + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs new file mode 100644 index 0000000000..d615be43f3 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs @@ -0,0 +1,20 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class TabControlAutomationPeer : SelectingItemsControlAutomationPeer + { + public TabControlAutomationPeer(IAutomationNodeFactory factory, TabControl owner) + : base(factory, owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Tab; + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs new file mode 100644 index 0000000000..20021b5b96 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs @@ -0,0 +1,18 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +namespace Avalonia.Automation.Peers +{ + public class TabItemAutomationPeer : ListItemAutomationPeer + { + public TabItemAutomationPeer(IAutomationNodeFactory factory, TabItem owner) + : base(factory, owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.TabItem; + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/TextAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TextAutomationPeer.cs new file mode 100644 index 0000000000..c523feb0f8 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/TextAutomationPeer.cs @@ -0,0 +1,28 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class TextAutomationPeer : ControlAutomationPeer + { + public TextAutomationPeer(IAutomationNodeFactory factory, Control owner) + : base(factory, owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Text; + } + + protected override string? GetNameCore() => Owner.GetValue(TextBlock.TextProperty); + + protected override bool IsControlElementCore() + { + // Return false if the control is part of a control template. + return Owner.TemplatedParent is null && base.IsControlElementCore(); + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs new file mode 100644 index 0000000000..8ee1aacb52 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs @@ -0,0 +1,21 @@ +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class TextBoxAutomationPeer : TextAutomationPeer, IValueProvider + { + public TextBoxAutomationPeer(IAutomationNodeFactory factory, TextBox owner) + : base(factory, owner) + { + } + + public new TextBox Owner => (TextBox)base.Owner; + public bool IsReadOnly => Owner.IsReadOnly; + public string? Value => Owner.Text; + public void SetValue(string? value) => Owner.Text = value; + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs new file mode 100644 index 0000000000..b103df3165 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs @@ -0,0 +1,39 @@ +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls.Primitives; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class ToggleButtonAutomationPeer : ContentControlAutomationPeer, IToggleProvider + { + public ToggleButtonAutomationPeer(IAutomationNodeFactory factory, ToggleButton owner) + : base(factory, owner) + { + } + + public new ToggleButton Owner => (ToggleButton)base.Owner; + + ToggleState IToggleProvider.ToggleState + { + get => Owner.IsChecked switch + { + true => ToggleState.On, + false => ToggleState.Off, + null => ToggleState.Indeterminate, + }; + } + + void IToggleProvider.Toggle() + { + EnsureEnabled(); + Owner.PerformClick(); + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Button; + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/UnrealizedElementAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/UnrealizedElementAutomationPeer.cs new file mode 100644 index 0000000000..5832b04dd7 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/UnrealizedElementAutomationPeer.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using Avalonia.Automation.Platform; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + /// + /// An automation peer which represents an unrealized element + /// + public abstract class UnrealizedElementAutomationPeer : AutomationPeer + { + protected UnrealizedElementAutomationPeer(IAutomationNodeFactory factory) + : base(factory) + { + } + + public void SetParent(AutomationPeer? parent) => TrySetParent(parent); + protected override void BringIntoViewCore() => GetParent()?.BringIntoView(); + protected override Rect GetBoundingRectangleCore() => GetParent()?.GetBoundingRectangle() ?? default; + protected override IReadOnlyList GetOrCreateChildrenCore() => Array.Empty(); + protected override bool HasKeyboardFocusCore() => false; + protected override bool IsContentElementCore() => false; + protected override bool IsControlElementCore() => false; + protected override bool IsEnabledCore() => true; + protected override bool IsKeyboardFocusableCore() => false; + protected override void SetFocusCore() { } + protected override bool ShowContextMenuCore() => false; + protected internal override bool TrySetParent(AutomationPeer? parent) => false; + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs new file mode 100644 index 0000000000..41778457e8 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs @@ -0,0 +1,39 @@ +using System; +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class WindowAutomationPeer : WindowBaseAutomationPeer + { + public WindowAutomationPeer(IAutomationNodeFactory factory, Window owner) + : base(factory, owner) + { + if (owner.IsVisible) + StartTrackingFocus(); + else + owner.Opened += OnOpened; + owner.Closed += OnClosed; + } + + public new Window Owner => (Window)base.Owner; + + protected override string GetNameCore() => Owner.Title; + + private void OnOpened(object sender, EventArgs e) + { + Owner.Opened -= OnOpened; + StartTrackingFocus(); + } + + private void OnClosed(object sender, EventArgs e) + { + Owner.Closed -= OnClosed; + StopTrackingFocus(); + } + } +} + + diff --git a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs new file mode 100644 index 0000000000..ac235b3898 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs @@ -0,0 +1,72 @@ +using System.ComponentModel; +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Platform; +using Avalonia.VisualTree; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class WindowBaseAutomationPeer : ControlAutomationPeer, IRootProvider + { + private Control? _focus; + + public WindowBaseAutomationPeer(IAutomationNodeFactory factory, WindowBase owner) + : base(factory, owner) + { + } + + public new WindowBase Owner => (WindowBase)base.Owner; + public ITopLevelImpl PlatformImpl => Owner.PlatformImpl; + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Window; + } + + public AutomationPeer? GetFocus() => _focus is object ? GetOrCreatePeer(_focus) : null; + + public AutomationPeer? GetPeerFromPoint(Point p) + { + var hit = Owner.GetVisualAt(p)?.FindAncestorOfType(includeSelf: true); + return hit is object ? GetOrCreatePeer(hit) : null; + } + + protected void StartTrackingFocus() + { + KeyboardDevice.Instance.PropertyChanged += KeyboardDevicePropertyChanged; + OnFocusChanged(KeyboardDevice.Instance.FocusedElement); + } + + protected void StopTrackingFocus() + { + KeyboardDevice.Instance.PropertyChanged -= KeyboardDevicePropertyChanged; + } + + private void OnFocusChanged(IInputElement? focus) + { + var oldFocus = _focus; + + _focus = focus?.VisualRoot == Owner ? focus as Control : null; + + if (_focus != oldFocus) + { + var peer = _focus is object ? GetOrCreatePeer(_focus) : null; + ((IRootAutomationNode)Node).FocusChanged(peer); + } + } + + private void KeyboardDevicePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(KeyboardDevice.FocusedElement)) + { + OnFocusChanged(KeyboardDevice.Instance.FocusedElement); + } + } + } +} + + diff --git a/src/Avalonia.Controls/Automation/Platform/IAutomationNode.cs b/src/Avalonia.Controls/Automation/Platform/IAutomationNode.cs new file mode 100644 index 0000000000..cae3504a4a --- /dev/null +++ b/src/Avalonia.Controls/Automation/Platform/IAutomationNode.cs @@ -0,0 +1,32 @@ +using System; +using Avalonia.Automation.Peers; + +#nullable enable + +namespace Avalonia.Automation.Platform +{ + /// + /// Represents a platform implementation of a node in the UI Automation tree. + /// + public interface IAutomationNode + { + /// + /// Gets a factory which can be used to create child nodes. + /// + IAutomationNodeFactory Factory { get; } + + /// + /// Called by the when the children of the peer change. + /// + void ChildrenChanged(); + + /// + /// Called by the when a property other than the parent, + /// children or root changes. + /// + /// The property that changed. + /// The previous value of the property. + /// The new value of the property. + void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue); + } +} diff --git a/src/Avalonia.Controls/Automation/Platform/IAutomationNodeFactory.cs b/src/Avalonia.Controls/Automation/Platform/IAutomationNodeFactory.cs new file mode 100644 index 0000000000..da16611b9e --- /dev/null +++ b/src/Avalonia.Controls/Automation/Platform/IAutomationNodeFactory.cs @@ -0,0 +1,18 @@ +using Avalonia.Automation.Peers; + +#nullable enable + +namespace Avalonia.Automation.Platform +{ + /// + /// Creates nodes in the UI Automation tree of the underlying platform. + /// + public interface IAutomationNodeFactory + { + /// + /// Creates an automation node for a peer. + /// + /// The peer. + IAutomationNode CreateNode(AutomationPeer peer); + } +} diff --git a/src/Avalonia.Controls/Automation/Platform/IRootAutomationNode.cs b/src/Avalonia.Controls/Automation/Platform/IRootAutomationNode.cs new file mode 100644 index 0000000000..8346a908c2 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Platform/IRootAutomationNode.cs @@ -0,0 +1,20 @@ +using Avalonia.Automation.Peers; + +#nullable enable + +namespace Avalonia.Automation.Platform +{ + /// + /// Represents a platform implementation of a root node in the UI Automation tree. + /// + public interface IRootAutomationNode : IAutomationNode + { + /// + /// Called by the when its focus changes. + /// + /// + /// The automation peer for the newly focused control or null if no control is focused. + /// + void FocusChanged(AutomationPeer? focus); + } +} diff --git a/src/Avalonia.Controls/Automation/Provider/IExpandCollapseProvider.cs b/src/Avalonia.Controls/Automation/Provider/IExpandCollapseProvider.cs new file mode 100644 index 0000000000..f0308c226b --- /dev/null +++ b/src/Avalonia.Controls/Automation/Provider/IExpandCollapseProvider.cs @@ -0,0 +1,24 @@ +namespace Avalonia.Automation.Provider +{ + /// + /// Exposes methods and properties to support UI Automation client access to controls that + /// visually expand to display content and collapse to hide content. + /// + public interface IExpandCollapseProvider + { + /// + /// Gets the state, expanded or collapsed, of the control. + /// + ExpandCollapseState ExpandCollapseState { get; } + + /// + /// Displays all child nodes, controls, or content of the control. + /// + void Expand(); + + /// + /// Hides all nodes, controls, or content that are descendants of the control. + /// + void Collapse(); + } +} diff --git a/src/Avalonia.Controls/Automation/Provider/IInvokeProvider.cs b/src/Avalonia.Controls/Automation/Provider/IInvokeProvider.cs new file mode 100644 index 0000000000..47d7211c92 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Provider/IInvokeProvider.cs @@ -0,0 +1,15 @@ +namespace Avalonia.Automation.Provider +{ + /// + /// Exposes methods and properties to support UI Automation client access to controls that + /// initiate or perform a single, unambiguous action and do not maintain state when + /// activated. + /// + public interface IInvokeProvider + { + /// + /// Sends a request to activate a control and initiate its single, unambiguous action. + /// + void Invoke(); + } +} diff --git a/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs b/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs new file mode 100644 index 0000000000..d4cd35fcf9 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs @@ -0,0 +1,37 @@ +#nullable enable + +namespace Avalonia.Automation.Provider +{ + /// + /// Exposes methods and properties to support access by a UI Automation client to controls + /// that can be set to a value within a range. + /// + public interface IRangeValueProvider + { + /// + /// Gets a value that indicates whether the value of a control is read-only. + /// + bool IsReadOnly { get; } + + /// + /// Gets the minimum range value that is supported by the control. + /// + double Minimum { get; } + + /// + /// Gets the maximum range value that is supported by the control. + /// + double Maximum { get; } + + /// + /// Gets the value of the control. + /// + double Value { get; } + + /// + /// Sets the value of the control. + /// + /// The value to set. + public void SetValue(double value); + } +} diff --git a/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs b/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs new file mode 100644 index 0000000000..698b4f26b9 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs @@ -0,0 +1,14 @@ +using Avalonia.Automation.Peers; +using Avalonia.Platform; + +#nullable enable + +namespace Avalonia.Automation.Provider +{ + public interface IRootProvider + { + ITopLevelImpl? PlatformImpl { get; } + AutomationPeer? GetFocus(); + AutomationPeer? GetPeerFromPoint(Point p); + } +} diff --git a/src/Avalonia.Controls/Automation/Provider/IScrollProvider.cs b/src/Avalonia.Controls/Automation/Provider/IScrollProvider.cs new file mode 100644 index 0000000000..1055a2f1e1 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Provider/IScrollProvider.cs @@ -0,0 +1,71 @@ +namespace Avalonia.Automation.Provider +{ + public enum ScrollAmount + { + LargeDecrement, + SmallDecrement, + NoAmount, + LargeIncrement, + SmallIncrement, + } + + /// + /// Exposes methods and properties to support access by a UI Automation client to a control + /// that acts as a scrollable container for a collection of child objects. + /// + public interface IScrollProvider + { + /// + /// Gets a value that indicates whether the control can scroll horizontally. + /// + bool HorizontallyScrollable { get; } + + /// + /// Gets the current horizontal scroll position. + /// + double HorizontalScrollPercent { get; } + + /// + /// Gets the current horizontal view size. + /// + double HorizontalViewSize { get; } + + /// + /// Gets a value that indicates whether the control can scroll vertically. + /// + bool VerticallyScrollable { get; } + + /// + /// Gets the current vertical scroll position. + /// + double VerticalScrollPercent { get; } + + /// + /// Gets the vertical view size. + /// + double VerticalViewSize { get; } + + /// + /// Scrolls the visible region of the content area horizontally and vertically. + /// + /// The horizontal increment specific to the control. + /// The vertical increment specific to the control. + void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount); + + /// + /// Sets the horizontal and vertical scroll position as a percentage of the total content + /// area within the control. + /// + /// + /// The horizontal position as a percentage of the content area's total range. + /// should be passed in if the control + /// cannot be scrolled in this direction. + /// + /// + /// The vertical position as a percentage of the content area's total range. + /// should be passed in if the control + /// cannot be scrolled in this direction. + /// + void SetScrollPercent(double horizontalPercent, double verticalPercent); + } +} diff --git a/src/Avalonia.Controls/Automation/Provider/ISelectionItemProvider .cs b/src/Avalonia.Controls/Automation/Provider/ISelectionItemProvider .cs new file mode 100644 index 0000000000..767d6bd7a2 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Provider/ISelectionItemProvider .cs @@ -0,0 +1,37 @@ +#nullable enable + +namespace Avalonia.Automation.Provider +{ + /// + /// Exposes methods and properties to support access by a UI Automation client to individual, + /// selectable child controls of containers that implement . + /// + public interface ISelectionItemProvider + { + /// + /// Gets a value that indicates whether an item is selected. + /// + bool IsSelected { get; } + + /// + /// Gets the UI Automation provider that implements and + /// acts as the container for the calling object. + /// + ISelectionProvider? SelectionContainer { get; } + + /// + /// Adds the current element to the collection of selected items. + /// + void AddToSelection(); + + /// + /// Removes the current element from the collection of selected items. + /// + void RemoveFromSelection(); + + /// + /// Clears any existing selection and then selects the current element. + /// + void Select(); + } +} diff --git a/src/Avalonia.Controls/Automation/Provider/ISelectionProvider.cs b/src/Avalonia.Controls/Automation/Provider/ISelectionProvider.cs new file mode 100644 index 0000000000..bf21c0151f --- /dev/null +++ b/src/Avalonia.Controls/Automation/Provider/ISelectionProvider.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Avalonia.Automation.Peers; + +namespace Avalonia.Automation.Provider +{ + /// + /// Exposes methods and properties to support access by a UI Automation client to controls + /// that act as containers for a collection of individual, selectable child items. + /// + public interface ISelectionProvider + { + /// + /// Gets a value that indicates whether the provider allows more than one child element + /// to be selected concurrently. + /// + bool CanSelectMultiple { get; } + + /// + /// Gets a value that indicates whether the provider requires at least one child element + /// to be selected. + /// + bool IsSelectionRequired { get; } + + /// + /// Retrieves a provider for each child element that is selected. + /// + IReadOnlyList GetSelection(); + } +} diff --git a/src/Avalonia.Controls/Automation/Provider/IToggleProvider.cs b/src/Avalonia.Controls/Automation/Provider/IToggleProvider.cs new file mode 100644 index 0000000000..67913e3204 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Provider/IToggleProvider.cs @@ -0,0 +1,40 @@ +namespace Avalonia.Automation.Provider +{ + /// + /// Contains values that specify the toggle state of a UI Automation element. + /// + public enum ToggleState + { + /// + /// The UI Automation element isn't selected, checked, marked, or otherwise activated. + /// + Off, + + /// + /// The UI Automation element is selected, checked, marked, or otherwise activated. + /// + On, + + /// + /// The UI Automation element is in an indeterminate state. + /// + Indeterminate, + } + + /// + /// Exposes methods and properties to support UI Automation client access to controls that can + /// cycle through a set of states and maintain a particular state. + /// + public interface IToggleProvider + { + /// + /// Gets the toggle state of the control. + /// + ToggleState ToggleState { get; } + + /// + /// Cycles through the toggle states of a control. + /// + void Toggle(); + } +} diff --git a/src/Avalonia.Controls/Automation/Provider/IValueProvider.cs b/src/Avalonia.Controls/Automation/Provider/IValueProvider.cs new file mode 100644 index 0000000000..83dbde4ec2 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Provider/IValueProvider.cs @@ -0,0 +1,31 @@ +#nullable enable + +namespace Avalonia.Automation.Provider +{ + /// + /// Exposes methods and properties to support access by a UI Automation client to controls + /// that have an intrinsic value that does not span a range and that can be represented as + /// a string. + /// + public interface IValueProvider + { + /// + /// Gets a value that indicates whether the value of a control is read-only. + /// + bool IsReadOnly { get; } + + /// + /// Gets the value of the control. + /// + public string? Value { get; } + + /// + /// Sets the value of a control. + /// + /// + /// The value to set. The provider is responsible for converting the value to the + /// appropriate data type. + /// + public void SetValue(string? value); + } +} diff --git a/src/Avalonia.Controls/Automation/RangeValuePatternIdentifiers.cs b/src/Avalonia.Controls/Automation/RangeValuePatternIdentifiers.cs new file mode 100644 index 0000000000..e3fa744cc0 --- /dev/null +++ b/src/Avalonia.Controls/Automation/RangeValuePatternIdentifiers.cs @@ -0,0 +1,30 @@ +using Avalonia.Automation.Provider; + +namespace Avalonia.Automation +{ + /// + /// Contains values used as identifiers by . + /// + public static class RangeValuePatternIdentifiers + { + /// + /// Identifies automation property. + /// + public static AutomationProperty IsReadOnlyProperty { get; } = new(); + + /// + /// Identifies automation property. + /// + public static AutomationProperty MinimumProperty { get; } = new(); + + /// + /// Identifies automation property. + /// + public static AutomationProperty MaximumProperty { get; } = new(); + + /// + /// Identifies automation property. + /// + public static AutomationProperty ValueProperty { get; } = new(); + } +} diff --git a/src/Avalonia.Controls/Automation/ScrollPatternIdentifiers.cs b/src/Avalonia.Controls/Automation/ScrollPatternIdentifiers.cs new file mode 100644 index 0000000000..ad10b96e17 --- /dev/null +++ b/src/Avalonia.Controls/Automation/ScrollPatternIdentifiers.cs @@ -0,0 +1,45 @@ +using Avalonia.Automation.Provider; + +namespace Avalonia.Automation +{ + /// + /// Contains values used as identifiers by . + /// + public static class ScrollPatternIdentifiers + { + /// + /// Specifies that scrolling should not be performed. + /// + public const double NoScroll = -1; + + /// + /// Identifies automation property. + /// + public static AutomationProperty HorizontallyScrollableProperty { get; } = new(); + + /// + /// Identifies automation property. + /// + public static AutomationProperty HorizontalScrollPercentProperty { get; } = new(); + + /// + /// Identifies automation property. + /// + public static AutomationProperty HorizontalViewSizeProperty { get; } = new(); + + /// + /// Identifies automation property. + /// + public static AutomationProperty VerticallyScrollableProperty { get; } = new(); + + /// + /// Identifies automation property. + /// + public static AutomationProperty VerticalScrollPercentProperty { get; } = new(); + + /// + /// Identifies automation property. + /// + public static AutomationProperty VerticalViewSizeProperty { get; } = new(); + } +} diff --git a/src/Avalonia.Controls/Automation/SelectionPatternIdentifiers.cs b/src/Avalonia.Controls/Automation/SelectionPatternIdentifiers.cs new file mode 100644 index 0000000000..ce4fdda739 --- /dev/null +++ b/src/Avalonia.Controls/Automation/SelectionPatternIdentifiers.cs @@ -0,0 +1,25 @@ +using Avalonia.Automation.Provider; + +namespace Avalonia.Automation +{ + /// + /// Contains values used as identifiers by . + /// + public static class SelectionPatternIdentifiers + { + /// + /// Identifies automation property. + /// + public static AutomationProperty CanSelectMultipleProperty { get; } = new(); + + /// + /// Identifies automation property. + /// + public static AutomationProperty IsSelectionRequiredProperty { get; } = new(); + + /// + /// Identifies the property that gets the selected items in a container. + /// + public static AutomationProperty SelectionProperty { get; } = new(); + } +} diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index c779e4b0cb..c8720f7cee 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using System.Windows.Input; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Metadata; using Avalonia.Data; using Avalonia.Input; @@ -338,6 +340,11 @@ namespace Avalonia.Controls } } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new ButtonAutomationPeer(factory, this); + } + protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) { base.UpdateDataValidation(property, value); @@ -354,6 +361,8 @@ namespace Avalonia.Controls } } + internal void PerformClick() => OnClick(); + /// /// Called when the property changes. /// diff --git a/src/Avalonia.Controls/CheckBox.cs b/src/Avalonia.Controls/CheckBox.cs index 05d49a44b1..374ab33338 100644 --- a/src/Avalonia.Controls/CheckBox.cs +++ b/src/Avalonia.Controls/CheckBox.cs @@ -1,3 +1,5 @@ +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Primitives; namespace Avalonia.Controls @@ -7,5 +9,9 @@ namespace Avalonia.Controls /// public class CheckBox : ToggleButton { + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new CheckBoxAutomationPeer(factory, this); + } } } diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index c5af5ffa7a..83b97cc3d6 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Generators; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; @@ -335,6 +337,11 @@ namespace Avalonia.Controls _popup.Opened += PopupOpened; } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new ComboBoxAutomationPeer(factory, this); + } + internal void ItemFocused(ComboBoxItem dropDownItem) { if (IsDropDownOpen && dropDownItem.IsFocused && dropDownItem.IsArrangeValid) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 4aab92c428..6b63ac14cc 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -1,5 +1,7 @@ using System; using System.ComponentModel; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; @@ -48,6 +50,7 @@ namespace Avalonia.Controls private DataTemplates? _dataTemplates; private IControl? _focusAdorner; + private AutomationPeer? _automationPeer; /// /// Gets or sets the control's focus adorner. @@ -188,5 +191,23 @@ namespace Avalonia.Controls _focusAdorner = null; } } + + protected virtual AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new NoneAutomationPeer(factory, this); + } + + internal AutomationPeer GetOrCreateAutomationPeer(IAutomationNodeFactory factory) + { + VerifyAccess(); + + if (_automationPeer is object) + { + return _automationPeer; + } + + _automationPeer = OnCreateAutomationPeer(factory); + return _automationPeer; + } } } diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 5fc7d8b6b6..35bb8316f6 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -1,3 +1,5 @@ +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Media; using Avalonia.Media.Imaging; @@ -122,5 +124,10 @@ namespace Avalonia.Controls return new Size(); } } + + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new ImageAutomationPeer(factory, this); + } } } diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 4dc8aec6f3..b6c2e8656d 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -3,6 +3,8 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using Avalonia.Collections; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Generators; using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; @@ -323,6 +325,11 @@ namespace Avalonia.Controls base.OnKeyDown(e); } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new ItemsControlAutomationPeer(factory, this); + } + /// /// Called when the property changes. /// diff --git a/src/Avalonia.Controls/ListBoxItem.cs b/src/Avalonia.Controls/ListBoxItem.cs index 4fe5f4de40..5599a89b62 100644 --- a/src/Avalonia.Controls/ListBoxItem.cs +++ b/src/Avalonia.Controls/ListBoxItem.cs @@ -1,6 +1,7 @@ +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; -using Avalonia.Input; namespace Avalonia.Controls { @@ -34,5 +35,10 @@ namespace Avalonia.Controls get { return GetValue(IsSelectedProperty); } set { SetValue(IsSelectedProperty, value); } } + + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new ListItemAutomationPeer(factory, this); + } } } diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs index 4da044fec1..5260561393 100644 --- a/src/Avalonia.Controls/Menu.cs +++ b/src/Avalonia.Controls/Menu.cs @@ -1,3 +1,5 @@ +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -92,5 +94,10 @@ namespace Avalonia.Controls inputRoot.AccessKeyHandler.MainMenu = this; } } + + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new MenuAutomationPeer(factory, this); + } } } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 94099a970e..71bc08890e 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; using System.Windows.Input; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Generators; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; @@ -477,6 +479,11 @@ namespace Avalonia.Controls } } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new MenuItemAutomationPeer(factory, this); + } + protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) { base.UpdateDataValidation(property, value); diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index b445de0472..1837b90c5d 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using System.Reactive.Disposables; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Input; @@ -596,7 +598,17 @@ namespace Avalonia.Controls.Primitives { if (PlacementTarget != null) { - FocusManager.Instance?.Focus(PlacementTarget); + var e = (IControl?)PlacementTarget; + + while (e is object && (!e.Focusable || !e.IsEffectivelyEnabled || !e.IsVisible)) + { + e = e.Parent; + } + + if (e is object) + { + FocusManager.Instance?.Focus(e); + } } else { diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index da7352b77f..73dd68e7cb 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Reactive.Disposables; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Interactivity; using Avalonia.Media; @@ -168,5 +170,10 @@ namespace Avalonia.Controls.Primitives return ClientSize; } } + + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new PopupRootAutomationPeer(factory, this); + } } } diff --git a/src/Avalonia.Controls/Primitives/ToggleButton.cs b/src/Avalonia.Controls/Primitives/ToggleButton.cs index 6b2c566422..434d34928f 100644 --- a/src/Avalonia.Controls/Primitives/ToggleButton.cs +++ b/src/Avalonia.Controls/Primitives/ToggleButton.cs @@ -1,4 +1,6 @@ using System; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Metadata; using Avalonia.Data; using Avalonia.Interactivity; @@ -169,6 +171,11 @@ namespace Avalonia.Controls.Primitives RaiseEvent(e); } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new ToggleButtonAutomationPeer(factory, this); + } + private void OnIsCheckedChanged(AvaloniaPropertyChangedEventArgs e) { var newValue = (bool?)e.NewValue; diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 6b75149d62..fe6e03e7a4 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -1,5 +1,7 @@ using System; using System.Reactive.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Input; @@ -688,6 +690,11 @@ namespace Avalonia.Controls _scrollBarExpandSubscription = SubscribeToScrollBars(e); } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new ScrollViewerAutomationPeer(factory, this); + } + private IDisposable SubscribeToScrollBars(TemplateAppliedEventArgs e) { static IObservable GetExpandedObservable(ScrollBar scrollBar) diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index e02efc2bd2..a3d18d73a1 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -1,5 +1,7 @@ using System; using Avalonia.Collections; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; @@ -209,6 +211,11 @@ namespace Avalonia.Controls _pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel); } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new SliderAutomationPeer(factory, this); + } + protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 306a9d3e6a..63c68b783c 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -1,6 +1,8 @@ using System.ComponentModel; using System.Linq; using Avalonia.Collections; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Generators; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; @@ -230,5 +232,10 @@ namespace Avalonia.Controls } } } + + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new TabControlAutomationPeer(factory, this); + } } } diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 593643a1eb..4d50ef961d 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -1,3 +1,5 @@ +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; @@ -80,5 +82,10 @@ namespace Avalonia.Controls } } } + + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new ListItemAutomationPeer(factory, this); + } } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 31517ba59d..eb23878c3d 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -1,9 +1,11 @@ using System.Reactive.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; +using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.Metadata; -using Avalonia.Layout; namespace Avalonia.Controls { @@ -532,6 +534,11 @@ namespace Avalonia.Controls InvalidateMeasure(); } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new TextAutomationPeer(factory, this); + } + private static bool IsValidMaxLines(int maxLines) => maxLines >= 0; private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0; diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 54d3af9b59..e91d3dc44d 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -14,6 +14,8 @@ using Avalonia.Data; using Avalonia.Layout; using Avalonia.Utilities; using Avalonia.Controls.Metadata; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; namespace Avalonia.Controls { @@ -904,6 +906,11 @@ namespace Avalonia.Controls } } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new TextBoxAutomationPeer(factory, this); + } + protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) { if (property == TextProperty) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 98f4cadc13..7648162493 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -4,6 +4,8 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Interactivity; @@ -965,5 +967,10 @@ namespace Avalonia.Controls } } } + + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new WindowAutomationPeer(factory, this); + } } } diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 5899824c29..79cda87001 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -24,30 +24,7 @@ namespace Avalonia.Input // the source of truth about the input focus is in KeyboardDevice private readonly TextInputMethodManager _textInputManager = new TextInputMethodManager(); - public IInputElement? FocusedElement - { - get - { - return _focusedElement; - } - - private set - { - _focusedElement = value; - - if (_focusedElement != null && _focusedElement.IsAttachedToVisualTree) - { - _focusedRoot = _focusedElement.VisualRoot as IInputRoot; - } - else - { - _focusedRoot = null; - } - - RaisePropertyChanged(); - _textInputManager.SetFocusedElement(value); - } - } + public IInputElement? FocusedElement => _focusedElement; private void ClearFocusWithinAncestors(IInputElement? element) { @@ -162,8 +139,8 @@ namespace Avalonia.Input } SetIsFocusWithin(FocusedElement, element); - - FocusedElement = element; + _focusedElement = element; + _focusedRoot = _focusedElement?.VisualRoot as IInputRoot; interactive?.RaiseEvent(new RoutedEventArgs { @@ -178,6 +155,9 @@ namespace Avalonia.Input NavigationMethod = method, KeyModifiers = keyModifiers, }); + + _textInputManager.SetFocusedElement(element); + RaisePropertyChanged(nameof(FocusedElement)); } } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs new file mode 100644 index 0000000000..34ae969afe --- /dev/null +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs @@ -0,0 +1,19 @@ +using Avalonia.Automation; +using Avalonia.Automation.Provider; +using UIA = Avalonia.Win32.Interop.Automation; + +#nullable enable + +namespace Avalonia.Win32.Automation +{ + internal partial class AutomationNode : UIA.IExpandCollapseProvider + { + public ExpandCollapseState ExpandCollapseState + { + get => InvokeSync(x => x.ExpandCollapseState); + } + + public void Expand() => InvokeSync(x => x.Expand()); + public void Collapse() => InvokeSync(x => x.Collapse()); + } +} diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs new file mode 100644 index 0000000000..d7e97cb30c --- /dev/null +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs @@ -0,0 +1,19 @@ +using Avalonia.Automation.Provider; +using UIA = Avalonia.Win32.Interop.Automation; + +#nullable enable + +namespace Avalonia.Win32.Automation +{ + internal partial class AutomationNode : UIA.IRangeValueProvider + { + double UIA.IRangeValueProvider.Value => InvokeSync(x => x.Value); + public bool IsReadOnly => InvokeSync(x => x.IsReadOnly); + public double Maximum => InvokeSync(x => x.Maximum); + public double Minimum => InvokeSync(x => x.Minimum); + public double LargeChange => 1; + public double SmallChange => 1; + + public void SetValue(double value) => InvokeSync(x => x.SetValue(value)); + } +} diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs new file mode 100644 index 0000000000..55f1aba71c --- /dev/null +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs @@ -0,0 +1,32 @@ +using Avalonia.Automation.Provider; +using UIA = Avalonia.Win32.Interop.Automation; + +#nullable enable + +namespace Avalonia.Win32.Automation +{ + internal partial class AutomationNode : UIA.IScrollProvider, UIA.IScrollItemProvider + { + public bool HorizontallyScrollable => InvokeSync(x => x.HorizontallyScrollable); + public double HorizontalScrollPercent => InvokeSync(x => x.HorizontalScrollPercent); + public double HorizontalViewSize => InvokeSync(x => x.HorizontalViewSize); + public bool VerticallyScrollable => InvokeSync(x => x.VerticallyScrollable); + public double VerticalScrollPercent => InvokeSync(x => x.VerticalScrollPercent); + public double VerticalViewSize => InvokeSync(x => x.VerticalViewSize); + + public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount) + { + InvokeSync(x => x.Scroll(horizontalAmount, verticalAmount)); + } + + public void SetScrollPercent(double horizontalPercent, double verticalPercent) + { + InvokeSync(x => x.SetScrollPercent(horizontalPercent, verticalPercent)); + } + + public void ScrollIntoView() + { + InvokeSync(() => Peer.BringIntoView()); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs new file mode 100644 index 0000000000..17634ae805 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Provider; +using UIA = Avalonia.Win32.Interop.Automation; + +#nullable enable + +namespace Avalonia.Win32.Automation +{ + internal partial class AutomationNode : UIA.ISelectionProvider, UIA.ISelectionItemProvider + { + public bool CanSelectMultiple => InvokeSync(x => x.CanSelectMultiple); + public bool IsSelectionRequired => InvokeSync(x => x.IsSelectionRequired); + public bool IsSelected => InvokeSync(x => x.IsSelected); + + public UIA.IRawElementProviderSimple? SelectionContainer + { + get + { + var peer = InvokeSync(x => x.SelectionContainer); + return (peer as AutomationPeer)?.Node as AutomationNode; + } + } + + public UIA.IRawElementProviderSimple[] GetSelection() + { + var peers = InvokeSync>(x => x.GetSelection()); + return peers?.Select(x => (UIA.IRawElementProviderSimple)x.Node).ToArray() ?? + Array.Empty(); + } + + public void AddToSelection() => InvokeSync(x => x.AddToSelection()); + public void RemoveFromSelection() => InvokeSync(x => x.RemoveFromSelection()); + public void Select() => InvokeSync(x => x.Select()); + } +} diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs new file mode 100644 index 0000000000..9047555785 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs @@ -0,0 +1,13 @@ +using Avalonia.Automation.Provider; +using UIA = Avalonia.Win32.Interop.Automation; + +#nullable enable + +namespace Avalonia.Win32.Automation +{ + internal partial class AutomationNode : UIA.IToggleProvider + { + public ToggleState ToggleState => InvokeSync(x => x.ToggleState); + public void Toggle() => InvokeSync(x => x.Toggle()); + } +} diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs new file mode 100644 index 0000000000..06e6708663 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; +using Avalonia.Automation.Provider; +using UIA = Avalonia.Win32.Interop.Automation; + +#nullable enable + +namespace Avalonia.Win32.Automation +{ + internal partial class AutomationNode : UIA.IValueProvider + { + public string? Value => InvokeSync(x => x.Value); + + public void SetValue([MarshalAs(UnmanagedType.LPWStr)] string? value) + { + InvokeSync(x => x.SetValue(value)); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs new file mode 100644 index 0000000000..ca237567c1 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Runtime.InteropServices; +using Avalonia.Automation; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; +using Avalonia.Threading; +using Avalonia.Win32.Interop.Automation; +using AAP = Avalonia.Automation.Provider; + +#nullable enable + +namespace Avalonia.Win32.Automation +{ + [ComVisible(true)] + internal partial class AutomationNode : MarshalByRefObject, + IAutomationNode, + IRawElementProviderSimple, + IRawElementProviderSimple2, + IRawElementProviderFragment, + IRawElementProviderAdviseEvents, + IInvokeProvider + { + private static Dictionary s_propertyMap = new() + { + { AutomationElementIdentifiers.BoundingRectangleProperty, UiaPropertyId.BoundingRectangle }, + { AutomationElementIdentifiers.ClassNameProperty, UiaPropertyId.ClassName }, + { AutomationElementIdentifiers.NameProperty, UiaPropertyId.Name }, + { ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, UiaPropertyId.ExpandCollapseExpandCollapseState }, + { RangeValuePatternIdentifiers.IsReadOnlyProperty, UiaPropertyId.RangeValueIsReadOnly}, + { RangeValuePatternIdentifiers.MaximumProperty, UiaPropertyId.RangeValueMaximum }, + { RangeValuePatternIdentifiers.MinimumProperty, UiaPropertyId.RangeValueMinimum }, + { RangeValuePatternIdentifiers.ValueProperty, UiaPropertyId.RangeValueValue }, + { ScrollPatternIdentifiers.HorizontallyScrollableProperty, UiaPropertyId.ScrollHorizontallyScrollable }, + { ScrollPatternIdentifiers.HorizontalScrollPercentProperty, UiaPropertyId.ScrollHorizontalScrollPercent }, + { ScrollPatternIdentifiers.HorizontalViewSizeProperty, UiaPropertyId.ScrollHorizontalViewSize }, + { ScrollPatternIdentifiers.VerticallyScrollableProperty, UiaPropertyId.ScrollVerticallyScrollable }, + { ScrollPatternIdentifiers.VerticalScrollPercentProperty, UiaPropertyId.ScrollVerticalScrollPercent }, + { ScrollPatternIdentifiers.VerticalViewSizeProperty, UiaPropertyId.ScrollVerticalViewSize }, + { SelectionPatternIdentifiers.CanSelectMultipleProperty, UiaPropertyId.SelectionCanSelectMultiple }, + { SelectionPatternIdentifiers.IsSelectionRequiredProperty, UiaPropertyId.SelectionIsSelectionRequired }, + { SelectionPatternIdentifiers.SelectionProperty, UiaPropertyId.SelectionSelection }, + }; + + private readonly int[] _runtimeId; + private int _raiseFocusChanged; + private int _raisePropertyChanged; + + public AutomationNode(AutomationPeer peer) + { + _runtimeId = new int[] { 3, GetHashCode() }; + Peer = peer; + } + + public AutomationPeer Peer { get; } + public IAutomationNodeFactory Factory => AutomationNodeFactory.Instance; + + public Rect BoundingRectangle + { + get => InvokeSync(() => + { + if (GetRoot()?.Node is RootAutomationNode root) + return root.ToScreen(Peer.GetBoundingRectangle()); + return default; + }); + } + + public virtual IRawElementProviderFragmentRoot? FragmentRoot + { + get => InvokeSync(() => GetRoot())?.Node as IRawElementProviderFragmentRoot; + } + + public virtual IRawElementProviderSimple? HostRawElementProvider => null; + public ProviderOptions ProviderOptions => ProviderOptions.ServerSideProvider; + + public void ChildrenChanged() + { + UiaCoreProviderApi.UiaRaiseStructureChangedEvent( + this, + StructureChangeType.ChildrenInvalidated, + _runtimeId, + _runtimeId.Length); + } + + public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue) + { + if (_raisePropertyChanged > 0 && s_propertyMap.TryGetValue(property, out var id)) + { + UiaCoreProviderApi.UiaRaiseAutomationPropertyChangedEvent(this, (int)id, oldValue, newValue); + } + } + + [return: MarshalAs(UnmanagedType.IUnknown)] + public virtual object? GetPatternProvider(int patternId) + { + return (UiaPatternId)patternId switch + { + UiaPatternId.ExpandCollapse => Peer is IExpandCollapseProvider ? this : null, + UiaPatternId.Invoke => Peer is AAP.IInvokeProvider ? this : null, + UiaPatternId.RangeValue => Peer is AAP.IRangeValueProvider ? this : null, + UiaPatternId.Scroll => Peer is AAP.IScrollProvider ? this : null, + UiaPatternId.ScrollItem => this, + UiaPatternId.Selection => Peer is AAP.ISelectionProvider ? this : null, + UiaPatternId.SelectionItem => Peer is AAP.ISelectionItemProvider ? this : null, + UiaPatternId.Toggle => Peer is AAP.IToggleProvider ? this : null, + UiaPatternId.Value => Peer is AAP.IValueProvider ? this : null, + _ => null, + }; + } + + public virtual object? GetPropertyValue(int propertyId) + { + return (UiaPropertyId)propertyId switch + { + UiaPropertyId.AutomationId => InvokeSync(() => Peer.GetAutomationId()), + UiaPropertyId.ClassName => InvokeSync(() => Peer.GetClassName()), + UiaPropertyId.ClickablePoint => new[] { BoundingRectangle.Center.X, BoundingRectangle.Center.Y }, + UiaPropertyId.ControlType => InvokeSync(() => ToUiaControlType(Peer.GetAutomationControlType())), + UiaPropertyId.Culture => CultureInfo.CurrentCulture.LCID, + UiaPropertyId.FrameworkId => "Avalonia", + UiaPropertyId.HasKeyboardFocus => InvokeSync(() => Peer.HasKeyboardFocus()), + UiaPropertyId.IsContentElement => InvokeSync(() => Peer.IsContentElement()), + UiaPropertyId.IsControlElement => InvokeSync(() => Peer.IsControlElement()), + UiaPropertyId.IsEnabled => InvokeSync(() => Peer.IsEnabled()), + UiaPropertyId.IsKeyboardFocusable => InvokeSync(() => Peer.IsKeyboardFocusable()), + UiaPropertyId.LocalizedControlType => InvokeSync(() => Peer.GetLocalizedControlType()), + UiaPropertyId.Name => InvokeSync(() => Peer.GetName()), + UiaPropertyId.ProcessId => Process.GetCurrentProcess().Id, + UiaPropertyId.RuntimeId => _runtimeId, + _ => null, + }; + } + + public int[]? GetRuntimeId() => _runtimeId; + + public virtual IRawElementProviderFragment? Navigate(NavigateDirection direction) + { + IAutomationNode? GetSibling(int direction) + { + var children = Peer.GetParent()?.GetChildren(); + + for (var i = 0; i < (children?.Count ?? 0); ++i) + { + if (ReferenceEquals(children![i], Peer)) + { + var j = i + direction; + if (j >= 0 && j < children.Count) + return children[j].Node; + } + } + + return null; + } + + return InvokeSync(() => + { + return direction switch + { + NavigateDirection.Parent => Peer.GetParent()?.Node, + NavigateDirection.NextSibling => GetSibling(1), + NavigateDirection.PreviousSibling => GetSibling(-1), + NavigateDirection.FirstChild => Peer.GetChildren().FirstOrDefault()?.Node, + NavigateDirection.LastChild => Peer.GetChildren().LastOrDefault()?.Node, + _ => null, + }; + }) as IRawElementProviderFragment; + } + + public void SetFocus() => InvokeSync(() => Peer.SetFocus()); + + IRawElementProviderSimple[]? IRawElementProviderFragment.GetEmbeddedFragmentRoots() => null; + void IRawElementProviderSimple2.ShowContextMenu() => InvokeSync(() => Peer.ShowContextMenu()); + void IInvokeProvider.Invoke() => InvokeSync((AAP.IInvokeProvider x) => x.Invoke()); + + void IRawElementProviderAdviseEvents.AdviseEventAdded(int eventId, int[] properties) + { + switch ((UiaEventId)eventId) + { + case UiaEventId.AutomationPropertyChanged: + ++_raisePropertyChanged; + break; + case UiaEventId.AutomationFocusChanged: + ++_raiseFocusChanged; + break; + } + } + + void IRawElementProviderAdviseEvents.AdviseEventRemoved(int eventId, int[] properties) + { + switch ((UiaEventId)eventId) + { + case UiaEventId.AutomationPropertyChanged: + --_raisePropertyChanged; + break; + case UiaEventId.AutomationFocusChanged: + --_raiseFocusChanged; + break; + } + } + + protected void InvokeSync(Action action) + { + if (Dispatcher.UIThread.CheckAccess()) + action(); + else + Dispatcher.UIThread.InvokeAsync(action).Wait(); + } + + [return: MaybeNull] + protected T InvokeSync(Func func) + { + if (Dispatcher.UIThread.CheckAccess()) + return func(); + else + return Dispatcher.UIThread.InvokeAsync(func).Result; + } + + protected void InvokeSync(Action action) + { + if (Peer is TInterface i) + { + try + { + InvokeSync(() => action(i)); + } + catch (AggregateException e) when (e.InnerException is ElementNotEnabledException) + { + throw new COMException(e.Message, UiaCoreProviderApi.UIA_E_ELEMENTNOTENABLED); + } + } + } + + [return: MaybeNull] + protected TResult InvokeSync(Func func) + { + if (Peer is TInterface i) + { + try + { + return InvokeSync(() => func(i)); + } + catch (AggregateException e) when (e.InnerException is ElementNotEnabledException) + { + throw new COMException(e.Message, UiaCoreProviderApi.UIA_E_ELEMENTNOTENABLED); + } + } + + return default; + } + + protected void RaiseFocusChanged(AutomationNode? focused) + { + if (_raiseFocusChanged > 0) + { + UiaCoreProviderApi.UiaRaiseAutomationEvent( + focused, + (int)UiaEventId.AutomationFocusChanged); + } + } + + private AutomationPeer GetRoot() + { + Dispatcher.UIThread.VerifyAccess(); + + var peer = Peer; + var parent = peer.GetParent(); + + while (parent is object) + { + peer = parent; + parent = peer.GetParent(); + } + + return peer; + } + + private static UiaControlTypeId ToUiaControlType(AutomationControlType role) + { + return role switch + { + AutomationControlType.Button => UiaControlTypeId.Button, + AutomationControlType.Calendar => UiaControlTypeId.Calendar, + AutomationControlType.CheckBox => UiaControlTypeId.CheckBox, + AutomationControlType.ComboBox => UiaControlTypeId.ComboBox, + AutomationControlType.Edit => UiaControlTypeId.Edit, + AutomationControlType.Hyperlink => UiaControlTypeId.Hyperlink, + AutomationControlType.Image => UiaControlTypeId.Image, + AutomationControlType.ListItem => UiaControlTypeId.ListItem, + AutomationControlType.List => UiaControlTypeId.List, + AutomationControlType.Menu => UiaControlTypeId.Menu, + AutomationControlType.MenuBar => UiaControlTypeId.MenuBar, + AutomationControlType.MenuItem => UiaControlTypeId.MenuItem, + AutomationControlType.ProgressBar => UiaControlTypeId.ProgressBar, + AutomationControlType.RadioButton => UiaControlTypeId.RadioButton, + AutomationControlType.ScrollBar => UiaControlTypeId.ScrollBar, + AutomationControlType.Slider => UiaControlTypeId.Slider, + AutomationControlType.Spinner => UiaControlTypeId.Spinner, + AutomationControlType.StatusBar => UiaControlTypeId.StatusBar, + AutomationControlType.Tab => UiaControlTypeId.Tab, + AutomationControlType.TabItem => UiaControlTypeId.TabItem, + AutomationControlType.Text => UiaControlTypeId.Text, + AutomationControlType.ToolBar => UiaControlTypeId.ToolBar, + AutomationControlType.ToolTip => UiaControlTypeId.ToolTip, + AutomationControlType.Tree => UiaControlTypeId.Tree, + AutomationControlType.TreeItem => UiaControlTypeId.TreeItem, + AutomationControlType.Custom => UiaControlTypeId.Custom, + AutomationControlType.Group => UiaControlTypeId.Group, + AutomationControlType.Thumb => UiaControlTypeId.Thumb, + AutomationControlType.DataGrid => UiaControlTypeId.DataGrid, + AutomationControlType.DataItem => UiaControlTypeId.DataItem, + AutomationControlType.Document => UiaControlTypeId.Document, + AutomationControlType.SplitButton => UiaControlTypeId.SplitButton, + AutomationControlType.Window => UiaControlTypeId.Window, + AutomationControlType.Pane => UiaControlTypeId.Pane, + AutomationControlType.Header => UiaControlTypeId.Header, + AutomationControlType.HeaderItem => UiaControlTypeId.HeaderItem, + AutomationControlType.Table => UiaControlTypeId.Table, + AutomationControlType.TitleBar => UiaControlTypeId.TitleBar, + AutomationControlType.Separator => UiaControlTypeId.Separator, + _ => UiaControlTypeId.Custom, + }; + } + } +} diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs new file mode 100644 index 0000000000..776b65adc6 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs @@ -0,0 +1,20 @@ +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Threading; + +#nullable enable + +namespace Avalonia.Win32.Automation +{ + internal class AutomationNodeFactory : IAutomationNodeFactory + { + public static readonly AutomationNodeFactory Instance = new AutomationNodeFactory(); + + public IAutomationNode CreateNode(AutomationPeer peer) + { + Dispatcher.UIThread.VerifyAccess(); + return peer is IRootProvider ? new RootAutomationNode(peer) : new AutomationNode(peer); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs new file mode 100644 index 0000000000..cb8cfae90e --- /dev/null +++ b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs @@ -0,0 +1,72 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Win32.Interop.Automation; + +#nullable enable + +namespace Avalonia.Win32.Automation +{ + internal class RootAutomationNode : AutomationNode, + IRawElementProviderFragmentRoot, + IRootAutomationNode + { + public RootAutomationNode(AutomationPeer peer) + : base(peer) + { + } + + public override IRawElementProviderFragmentRoot? FragmentRoot => this; + public new IRootProvider Peer => (IRootProvider)base.Peer; + public WindowImpl? WindowImpl => Peer.PlatformImpl as WindowImpl; + + public IRawElementProviderFragment? ElementProviderFromPoint(double x, double y) + { + if (WindowImpl is null) + return null; + + var p = WindowImpl.PointToClient(new PixelPoint((int)x, (int)y)); + var peer = (WindowBaseAutomationPeer)Peer; + var found = InvokeSync(() => peer.GetPeerFromPoint(p)); + var result = found?.Node as IRawElementProviderFragment; + return result; + } + + public IRawElementProviderFragment? GetFocus() + { + var focus = InvokeSync(() => Peer.GetFocus()); + return (AutomationNode?)focus?.Node; + } + + public void FocusChanged(AutomationPeer? focus) + { + var node = focus?.Node as AutomationNode; + RaiseFocusChanged(node); + } + + public Rect ToScreen(Rect rect) + { + if (WindowImpl is null) + return default; + return new PixelRect( + WindowImpl.PointToScreen(rect.TopLeft), + WindowImpl.PointToScreen(rect.BottomRight)) + .ToRect(1); + } + + public override IRawElementProviderSimple? HostRawElementProvider + { + get + { + var handle = WindowImpl?.Handle.Handle ?? IntPtr.Zero; + if (handle == IntPtr.Zero) + return null; + var hr = UiaCoreProviderApi.UiaHostProviderFromHwnd(handle, out var result); + Marshal.ThrowExceptionForHR(hr); + return result; + } + } + } +} diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index fe5f806fbe..94d7ed6651 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -6,6 +6,9 @@ + + + diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IDockProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IDockProvider.cs new file mode 100644 index 0000000000..2787434d26 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IDockProvider.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("70d46e77-e3a8-449d-913c-e30eb2afecdb")] + public enum DockPosition + { + Top, + Left, + Bottom, + Right, + Fill, + None + } + + [ComVisible(true)] + [Guid("159bc72c-4ad3-485e-9637-d7052edf0146")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IDockProvider + { + void SetDockPosition(DockPosition dockPosition); + DockPosition DockPosition { get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IExpandCollapseProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IExpandCollapseProvider.cs new file mode 100644 index 0000000000..67be1e6c71 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IExpandCollapseProvider.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Automation; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("d847d3a5-cab0-4a98-8c32-ecb45c59ad24")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IExpandCollapseProvider + { + void Expand(); + void Collapse(); + ExpandCollapseState ExpandCollapseState { get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IGridItemProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IGridItemProvider.cs new file mode 100644 index 0000000000..f911c38472 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IGridItemProvider.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("d02541f1-fb81-4d64-ae32-f520f8a6dbd1")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IGridItemProvider + { + int Row { get; } + int Column { get; } + int RowSpan { get; } + int ColumnSpan { get; } + IRawElementProviderSimple ContainingGrid { get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IGridProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IGridProvider.cs new file mode 100644 index 0000000000..a8caf26524 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IGridProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("b17d6187-0907-464b-a168-0ef17a1572b1")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IGridProvider + { + IRawElementProviderSimple GetItem(int row, int column); + int RowCount { get; } + int ColumnCount { get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IInvokeProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IInvokeProvider.cs new file mode 100644 index 0000000000..f35646d456 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IInvokeProvider.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Description: Invoke pattern provider interface + +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("54fcb24b-e18e-47a2-b4d3-eccbe77599a2")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IInvokeProvider + { + void Invoke(); + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IMultipleViewProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IMultipleViewProvider.cs new file mode 100644 index 0000000000..c487a0f5df --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IMultipleViewProvider.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("6278cab1-b556-4a1a-b4e0-418acc523201")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IMultipleViewProvider + { + string GetViewName(int viewId); + void SetCurrentView(int viewId); + int CurrentView { get; } + int[] GetSupportedViews(); + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IRangeValueProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IRangeValueProvider.cs new file mode 100644 index 0000000000..558f38a2cc --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IRangeValueProvider.cs @@ -0,0 +1,19 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("36dc7aef-33e6-4691-afe1-2be7274b3d33")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IRangeValueProvider + { + void SetValue(double value); + double Value { get; } + bool IsReadOnly { [return: MarshalAs(UnmanagedType.Bool)] get; } + double Maximum { get; } + double Minimum { get; } + double LargeChange { get; } + double SmallChange { get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderAdviseEvents.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderAdviseEvents.cs new file mode 100644 index 0000000000..1e799e05a2 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderAdviseEvents.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.InteropServices; + + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("a407b27b-0f6d-4427-9292-473c7bf93258")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IRawElementProviderAdviseEvents : IRawElementProviderSimple + { + void AdviseEventAdded(int eventId, int [] properties); + void AdviseEventRemoved(int eventId, int [] properties); + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragment.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragment.cs new file mode 100644 index 0000000000..a62aa842cb --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragment.cs @@ -0,0 +1,34 @@ +using System; +using System.Runtime.InteropServices; + +#nullable enable + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("670c3006-bf4c-428b-8534-e1848f645122")] + public enum NavigateDirection + { + Parent, + NextSibling, + PreviousSibling, + FirstChild, + LastChild, + } + + // NOTE: This interface needs to be public otherwise Navigate is never called. I have no idea + // why given that IRawElementProviderSimple and IRawElementProviderFragmentRoot seem to get + // called fine when they're internal, but I lost a couple of days to this. + [ComVisible(true)] + [Guid("f7063da8-8359-439c-9297-bbc5299a7d87")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IRawElementProviderFragment : IRawElementProviderSimple + { + IRawElementProviderFragment? Navigate(NavigateDirection direction); + int[]? GetRuntimeId(); + Rect BoundingRectangle { get; } + IRawElementProviderSimple[]? GetEmbeddedFragmentRoots(); + void SetFocus(); + IRawElementProviderFragmentRoot? FragmentRoot { get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragmentRoot.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragmentRoot.cs new file mode 100644 index 0000000000..71d1bdce60 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderFragmentRoot.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("620ce2a5-ab8f-40a9-86cb-de3c75599b58")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IRawElementProviderFragmentRoot : IRawElementProviderFragment + { + IRawElementProviderFragment ElementProviderFromPoint(double x, double y); + IRawElementProviderFragment GetFocus(); + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple.cs new file mode 100644 index 0000000000..439036290e --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple.cs @@ -0,0 +1,285 @@ +using System; +using System.Runtime.InteropServices; + +#nullable enable + +namespace Avalonia.Win32.Interop.Automation +{ + [Flags] + public enum ProviderOptions + { + ClientSideProvider = 0x0001, + ServerSideProvider = 0x0002, + NonClientAreaProvider = 0x0004, + OverrideProvider = 0x0008, + ProviderOwnsSetFocus = 0x0010, + UseComThreading = 0x0020 + } + + internal enum UiaPropertyId + { + RuntimeId = 30000, + BoundingRectangle, + ProcessId, + ControlType, + LocalizedControlType, + Name, + AcceleratorKey, + AccessKey, + HasKeyboardFocus, + IsKeyboardFocusable, + IsEnabled, + AutomationId, + ClassName, + HelpText, + ClickablePoint, + Culture, + IsControlElement, + IsContentElement, + LabeledBy, + IsPassword, + NativeWindowHandle, + ItemType, + IsOffscreen, + Orientation, + FrameworkId, + IsRequiredForForm, + ItemStatus, + IsDockPatternAvailable, + IsExpandCollapsePatternAvailable, + IsGridItemPatternAvailable, + IsGridPatternAvailable, + IsInvokePatternAvailable, + IsMultipleViewPatternAvailable, + IsRangeValuePatternAvailable, + IsScrollPatternAvailable, + IsScrollItemPatternAvailable, + IsSelectionItemPatternAvailable, + IsSelectionPatternAvailable, + IsTablePatternAvailable, + IsTableItemPatternAvailable, + IsTextPatternAvailable, + IsTogglePatternAvailable, + IsTransformPatternAvailable, + IsValuePatternAvailable, + IsWindowPatternAvailable, + ValueValue, + ValueIsReadOnly, + RangeValueValue, + RangeValueIsReadOnly, + RangeValueMinimum, + RangeValueMaximum, + RangeValueLargeChange, + RangeValueSmallChange, + ScrollHorizontalScrollPercent, + ScrollHorizontalViewSize, + ScrollVerticalScrollPercent, + ScrollVerticalViewSize, + ScrollHorizontallyScrollable, + ScrollVerticallyScrollable, + SelectionSelection, + SelectionCanSelectMultiple, + SelectionIsSelectionRequired, + GridRowCount, + GridColumnCount, + GridItemRow, + GridItemColumn, + GridItemRowSpan, + GridItemColumnSpan, + GridItemContainingGrid, + DockDockPosition, + ExpandCollapseExpandCollapseState, + MultipleViewCurrentView, + MultipleViewSupportedViews, + WindowCanMaximize, + WindowCanMinimize, + WindowWindowVisualState, + WindowWindowInteractionState, + WindowIsModal, + WindowIsTopmost, + SelectionItemIsSelected, + SelectionItemSelectionContainer, + TableRowHeaders, + TableColumnHeaders, + TableRowOrColumnMajor, + TableItemRowHeaderItems, + TableItemColumnHeaderItems, + ToggleToggleState, + TransformCanMove, + TransformCanResize, + TransformCanRotate, + IsLegacyIAccessiblePatternAvailable, + LegacyIAccessibleChildId, + LegacyIAccessibleName, + LegacyIAccessibleValue, + LegacyIAccessibleDescription, + LegacyIAccessibleRole, + LegacyIAccessibleState, + LegacyIAccessibleHelp, + LegacyIAccessibleKeyboardShortcut, + LegacyIAccessibleSelection, + LegacyIAccessibleDefaultAction, + AriaRole, + AriaProperties, + IsDataValidForForm, + ControllerFor, + DescribedBy, + FlowsTo, + ProviderDescription, + IsItemContainerPatternAvailable, + IsVirtualizedItemPatternAvailable, + IsSynchronizedInputPatternAvailable, + OptimizeForVisualContent, + IsObjectModelPatternAvailable, + AnnotationAnnotationTypeId, + AnnotationAnnotationTypeName, + AnnotationAuthor, + AnnotationDateTime, + AnnotationTarget, + IsAnnotationPatternAvailable, + IsTextPattern2Available, + StylesStyleId, + StylesStyleName, + StylesFillColor, + StylesFillPatternStyle, + StylesShape, + StylesFillPatternColor, + StylesExtendedProperties, + IsStylesPatternAvailable, + IsSpreadsheetPatternAvailable, + SpreadsheetItemFormula, + SpreadsheetItemAnnotationObjects, + SpreadsheetItemAnnotationTypes, + IsSpreadsheetItemPatternAvailable, + Transform2CanZoom, + IsTransformPattern2Available, + LiveSetting, + IsTextChildPatternAvailable, + IsDragPatternAvailable, + DragIsGrabbed, + DragDropEffect, + DragDropEffects, + IsDropTargetPatternAvailable, + DropTargetDropTargetEffect, + DropTargetDropTargetEffects, + DragGrabbedItems, + Transform2ZoomLevel, + Transform2ZoomMinimum, + Transform2ZoomMaximum, + FlowsFrom, + IsTextEditPatternAvailable, + IsPeripheral, + IsCustomNavigationPatternAvailable, + PositionInSet, + SizeOfSet, + Level, + AnnotationTypes, + AnnotationObjects, + LandmarkType, + LocalizedLandmarkType, + FullDescription, + FillColor, + OutlineColor, + FillType, + VisualEffects, + OutlineThickness, + CenterPoint, + Rotatation, + Size + } + + internal enum UiaPatternId + { + Invoke = 10000, + Selection, + Value, + RangeValue, + Scroll, + ExpandCollapse, + Grid, + GridItem, + MultipleView, + Window, + SelectionItem, + Dock, + Table, + TableItem, + Text, + Toggle, + Transform, + ScrollItem, + LegacyIAccessible, + ItemContainer, + VirtualizedItem, + SynchronizedInput, + ObjectModel, + Annotation, + Text2, + Styles, + Spreadsheet, + SpreadsheetItem, + Transform2, + TextChild, + Drag, + DropTarget, + TextEdit, + CustomNavigation + }; + + internal enum UiaControlTypeId + { + Button = 50000, + Calendar, + CheckBox, + ComboBox, + Edit, + Hyperlink, + Image, + ListItem, + List, + Menu, + MenuBar, + MenuItem, + ProgressBar, + RadioButton, + ScrollBar, + Slider, + Spinner, + StatusBar, + Tab, + TabItem, + Text, + ToolBar, + ToolTip, + Tree, + TreeItem, + Custom, + Group, + Thumb, + DataGrid, + DataItem, + Document, + SplitButton, + Window, + Pane, + Header, + HeaderItem, + Table, + TitleBar, + Separator, + SemanticZoom, + AppBar + }; + + [ComVisible(true)] + [Guid("d6dd68d1-86fd-4332-8666-9abedea2d24c")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IRawElementProviderSimple + { + ProviderOptions ProviderOptions { get; } + [return: MarshalAs(UnmanagedType.IUnknown)] + object? GetPatternProvider(int patternId); + object? GetPropertyValue(int propertyId); + IRawElementProviderSimple? HostRawElementProvider { get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple2.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple2.cs new file mode 100644 index 0000000000..f3504b8d77 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IRawElementProviderSimple2.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; + +#nullable enable + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("a0a839a9-8da1-4a82-806a-8e0d44e79f56")] + public interface IRawElementProviderSimple2 + { + void ShowContextMenu(); + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IScrollItemProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IScrollItemProvider.cs new file mode 100644 index 0000000000..c34c8667ef --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IScrollItemProvider.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("2360c714-4bf1-4b26-ba65-9b21316127eb")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IScrollItemProvider + { + void ScrollIntoView(); + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IScrollProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IScrollProvider.cs new file mode 100644 index 0000000000..154d52c6af --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IScrollProvider.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Automation.Provider; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("b38b8077-1fc3-42a5-8cae-d40c2215055a")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IScrollProvider + { + void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount); + void SetScrollPercent(double horizontalPercent, double verticalPercent); + double HorizontalScrollPercent { get; } + double VerticalScrollPercent { get; } + double HorizontalViewSize { get; } + double VerticalViewSize { get; } + bool HorizontallyScrollable { [return: MarshalAs(UnmanagedType.Bool)] get; } + bool VerticallyScrollable { [return: MarshalAs(UnmanagedType.Bool)] get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/ISelectionItemProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/ISelectionItemProvider.cs new file mode 100644 index 0000000000..1de0cf0f9b --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/ISelectionItemProvider.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("2acad808-b2d4-452d-a407-91ff1ad167b2")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface ISelectionItemProvider + { + void Select(); + void AddToSelection(); + void RemoveFromSelection(); + bool IsSelected { [return: MarshalAs(UnmanagedType.Bool)] get; } + IRawElementProviderSimple? SelectionContainer { get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/ISelectionProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/ISelectionProvider.cs new file mode 100644 index 0000000000..8a5924126d --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/ISelectionProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("fb8b03af-3bdf-48d4-bd36-1a65793be168")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface ISelectionProvider + { + IRawElementProviderSimple [] GetSelection(); + bool CanSelectMultiple { [return: MarshalAs(UnmanagedType.Bool)] get; } + bool IsSelectionRequired { [return: MarshalAs(UnmanagedType.Bool)] get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/ISynchronizedInputProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/ISynchronizedInputProvider.cs new file mode 100644 index 0000000000..def1bbd4b9 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/ISynchronizedInputProvider.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("fdc8f176-aed2-477a-8c89-5604c66f278d")] + public enum SynchronizedInputType + { + KeyUp = 0x01, + KeyDown = 0x02, + MouseLeftButtonUp = 0x04, + MouseLeftButtonDown = 0x08, + MouseRightButtonUp = 0x10, + MouseRightButtonDown = 0x20 + } + + [ComVisible(true)] + [Guid("29db1a06-02ce-4cf7-9b42-565d4fab20ee")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface ISynchronizedInputProvider + { + void StartListening(SynchronizedInputType inputType); + void Cancel(); + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/ITableItemProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/ITableItemProvider.cs new file mode 100644 index 0000000000..36751122d1 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/ITableItemProvider.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("b9734fa6-771f-4d78-9c90-2517999349cd")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface ITableItemProvider : IGridItemProvider + { + IRawElementProviderSimple [] GetRowHeaderItems(); + IRawElementProviderSimple [] GetColumnHeaderItems(); + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/ITableProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/ITableProvider.cs new file mode 100644 index 0000000000..e82bda3272 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/ITableProvider.cs @@ -0,0 +1,24 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("15fdf2e2-9847-41cd-95dd-510612a025ea")] + public enum RowOrColumnMajor + { + RowMajor, + ColumnMajor, + Indeterminate, + } + + [ComVisible(true)] + [Guid("9c860395-97b3-490a-b52a-858cc22af166")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface ITableProvider : IGridProvider + { + IRawElementProviderSimple [] GetRowHeaders(); + IRawElementProviderSimple [] GetColumnHeaders(); + RowOrColumnMajor RowOrColumnMajor { get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/ITextProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/ITextProvider.cs new file mode 100644 index 0000000000..3f8fbc80c7 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/ITextProvider.cs @@ -0,0 +1,30 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [Flags] + [ComVisible(true)] + [Guid("3d9e3d8f-bfb0-484f-84ab-93ff4280cbc4")] + public enum SupportedTextSelection + { + None, + Single, + Multiple, + } + + [ComVisible(true)] + [Guid("3589c92c-63f3-4367-99bb-ada653b77cf2")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface ITextProvider + { + ITextRangeProvider [] GetSelection(); + ITextRangeProvider [] GetVisibleRanges(); + ITextRangeProvider RangeFromChild(IRawElementProviderSimple childElement); + ITextRangeProvider RangeFromPoint(Point screenLocation); + ITextRangeProvider DocumentRange { get; } + SupportedTextSelection SupportedTextSelection { get; } + } +} + + diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/ITextRangeProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/ITextRangeProvider.cs new file mode 100644 index 0000000000..9ebb4c9f49 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/ITextRangeProvider.cs @@ -0,0 +1,48 @@ +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + public enum TextPatternRangeEndpoint + { + Start = 0, + End = 1, + } + + public enum TextUnit + { + Character = 0, + Format = 1, + Word = 2, + Line = 3, + Paragraph = 4, + Page = 5, + Document = 6, + } + + [ComVisible(true)] + [Guid("5347ad7b-c355-46f8-aff5-909033582f63")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface ITextRangeProvider + + { + ITextRangeProvider Clone(); + [return: MarshalAs(UnmanagedType.Bool)] + bool Compare(ITextRangeProvider range); + int CompareEndpoints(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint); + void ExpandToEnclosingUnit(TextUnit unit); + ITextRangeProvider FindAttribute(int attribute, object value, [MarshalAs(UnmanagedType.Bool)] bool backward); + ITextRangeProvider FindText(string text, [MarshalAs(UnmanagedType.Bool)] bool backward, [MarshalAs(UnmanagedType.Bool)] bool ignoreCase); + object GetAttributeValue(int attribute); + double [] GetBoundingRectangles(); + IRawElementProviderSimple GetEnclosingElement(); + string GetText(int maxLength); + int Move(TextUnit unit, int count); + int MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count); + void MoveEndpointByRange(TextPatternRangeEndpoint endpoint, ITextRangeProvider targetRange, TextPatternRangeEndpoint targetEndpoint); + void Select(); + void AddToSelection(); + void RemoveFromSelection(); + void ScrollIntoView([MarshalAs(UnmanagedType.Bool)] bool alignToTop); + IRawElementProviderSimple[] GetChildren(); + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IToggleProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IToggleProvider.cs new file mode 100644 index 0000000000..e4072a1250 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IToggleProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Automation.Provider; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("56d00bd0-c4f4-433c-a836-1a52a57e0892")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IToggleProvider + { + void Toggle( ); + ToggleState ToggleState { get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/ITransformProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/ITransformProvider.cs new file mode 100644 index 0000000000..4859f2d078 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/ITransformProvider.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("6829ddc4-4f91-4ffa-b86f-bd3e2987cb4c")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface ITransformProvider + { + void Move( double x, double y ); + void Resize( double width, double height ); + void Rotate( double degrees ); + bool CanMove { [return: MarshalAs(UnmanagedType.Bool)] get; } + bool CanResize { [return: MarshalAs(UnmanagedType.Bool)] get; } + bool CanRotate { [return: MarshalAs(UnmanagedType.Bool)] get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IValueProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IValueProvider.cs new file mode 100644 index 0000000000..919be647f8 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IValueProvider.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +#nullable enable + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("c7935180-6fb3-4201-b174-7df73adbf64a")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IValueProvider + { + void SetValue([MarshalAs(UnmanagedType.LPWStr)] string? value); + string? Value { get; } + bool IsReadOnly { [return: MarshalAs(UnmanagedType.Bool)] get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/IWindowProvider.cs b/src/Windows/Avalonia.Win32/Interop/Automation/IWindowProvider.cs new file mode 100644 index 0000000000..d915beb601 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/IWindowProvider.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("fdc8f176-aed2-477a-8c89-ea04cc5f278d")] + public enum WindowVisualState + { + Normal, + Maximized, + Minimized + } + + [ComVisible(true)] + [Guid("65101cc7-7904-408e-87a7-8c6dbd83a18b")] + public enum WindowInteractionState + { + Running, + Closing, + ReadyForUserInteraction, + BlockedByModalWindow, + NotResponding + } + + [ComVisible(true)] + [Guid("987df77b-db06-4d77-8f8a-86a9c3bb90b9")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IWindowProvider + { + void SetVisualState(WindowVisualState state); + void Close(); + [return: MarshalAs(UnmanagedType.Bool)] + bool WaitForInputIdle(int milliseconds); + bool Maximizable { [return: MarshalAs(UnmanagedType.Bool)] get; } + bool Minimizable { [return: MarshalAs(UnmanagedType.Bool)] get; } + bool IsModal { [return: MarshalAs(UnmanagedType.Bool)] get; } + WindowVisualState VisualState { get; } + WindowInteractionState InteractionState { get; } + bool IsTopmost { [return: MarshalAs(UnmanagedType.Bool)] get; } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs new file mode 100644 index 0000000000..4ba7a710d4 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreProviderApi.cs @@ -0,0 +1,91 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + [ComVisible(true)] + [Guid("d8e55844-7043-4edc-979d-593cc6b4775e")] + internal enum AsyncContentLoadedState + { + Beginning, + Progress, + Completed, + } + + [ComVisible(true)] + [Guid("e4cfef41-071d-472c-a65c-c14f59ea81eb")] + internal enum StructureChangeType + { + ChildAdded, + ChildRemoved, + ChildrenInvalidated, + ChildrenBulkAdded, + ChildrenBulkRemoved, + ChildrenReordered, + } + + internal enum UiaEventId + { + ToolTipOpened = 20000, + ToolTipClosed, + StructureChanged, + MenuOpened, + AutomationPropertyChanged, + AutomationFocusChanged, + AsyncContentLoaded, + MenuClosed, + LayoutInvalidated, + Invoke_Invoked, + SelectionItem_ElementAddedToSelection, + SelectionItem_ElementRemovedFromSelection, + SelectionItem_ElementSelected, + Selection_Invalidated, + Text_TextSelectionChanged, + Text_TextChanged, + Window_WindowOpened, + Window_WindowClosed, + MenuModeStart, + MenuModeEnd, + InputReachedTarget, + InputReachedOtherElement, + InputDiscarded, + SystemAlert, + LiveRegionChanged, + HostedFragmentRootsInvalidated, + Drag_DragStart, + Drag_DragCancel, + Drag_DragComplete, + DropTarget_DragEnter, + DropTarget_DragLeave, + DropTarget_Dropped, + TextEdit_TextChanged, + TextEdit_ConversionTargetChanged, + Changes + }; + + internal static class UiaCoreProviderApi + { + public const int UIA_E_ELEMENTNOTENABLED = unchecked((int)0x80040200); + + [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] + public static extern bool UiaClientsAreListening(); + + [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] + public static extern IntPtr UiaReturnRawElementProvider(IntPtr hwnd, IntPtr wParam, IntPtr lParam, IRawElementProviderSimple el); + + [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] + public static extern int UiaHostProviderFromHwnd(IntPtr hwnd, [MarshalAs(UnmanagedType.Interface)] out IRawElementProviderSimple provider); + + [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] + public static extern int UiaRaiseAutomationEvent(IRawElementProviderSimple provider, int id); + + [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] + public static extern int UiaRaiseAutomationPropertyChangedEvent(IRawElementProviderSimple provider, int id, object oldValue, object newValue); + + [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] + public static extern int UiaRaiseStructureChangedEvent(IRawElementProviderSimple provider, StructureChangeType structureChangeType, int[] runtimeId, int runtimeIdLen); + + [DllImport("UIAutomationCore.dll", CharSet = CharSet.Unicode)] + public static extern int UiaDisconnectProvider(IRawElementProviderSimple provider); + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs new file mode 100644 index 0000000000..4375b2fde1 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs @@ -0,0 +1,62 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Win32.Interop.Automation +{ + internal static class UiaCoreTypesApi + { + private const string StartListeningExportName = "SynchronizedInputPattern_StartListening"; + + internal enum AutomationIdType + { + Property, + Pattern, + Event, + ControlType, + TextAttribute + } + + internal const int UIA_E_ELEMENTNOTENABLED = unchecked((int)0x80040200); + internal const int UIA_E_ELEMENTNOTAVAILABLE = unchecked((int)0x80040201); + internal const int UIA_E_NOCLICKABLEPOINT = unchecked((int)0x80040202); + internal const int UIA_E_PROXYASSEMBLYNOTLOADED = unchecked((int)0x80040203); + + internal static int UiaLookupId(AutomationIdType type, ref Guid guid) + { + return RawUiaLookupId( type, ref guid ); + } + + internal static object UiaGetReservedNotSupportedValue() + { + object notSupportedValue; + CheckError(RawUiaGetReservedNotSupportedValue(out notSupportedValue)); + return notSupportedValue; + } + + internal static object UiaGetReservedMixedAttributeValue() + { + object mixedAttributeValue; + CheckError(RawUiaGetReservedMixedAttributeValue(out mixedAttributeValue)); + return mixedAttributeValue; + } + + private static void CheckError(int hr) + { + if (hr >= 0) + { + return; + } + + Marshal.ThrowExceptionForHR(hr, (IntPtr)(-1)); + } + + [DllImport("UIAutomationCore.dll", EntryPoint = "UiaLookupId", CharSet = CharSet.Unicode)] + private static extern int RawUiaLookupId(AutomationIdType type, ref Guid guid); + + [DllImport("UIAutomationCore.dll", EntryPoint = "UiaGetReservedNotSupportedValue", CharSet = CharSet.Unicode)] + private static extern int RawUiaGetReservedNotSupportedValue([MarshalAs(UnmanagedType.IUnknown)] out object notSupportedValue); + + [DllImport("UIAutomationCore.dll", EntryPoint = "UiaGetReservedMixedAttributeValue", CharSet = CharSet.Unicode)] + private static extern int RawUiaGetReservedMixedAttributeValue([MarshalAs(UnmanagedType.IUnknown)] out object mixedAttributeValue); + } +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index e7d16f731c..b0d41b72eb 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -2,10 +2,10 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using Avalonia.Controls; -using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Win32.Input; +using Avalonia.Win32.Interop.Automation; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia.Win32 @@ -17,6 +17,7 @@ namespace Avalonia.Win32 protected virtual unsafe IntPtr AppWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { const double wheelDelta = 120.0; + const long UiaRootObjectId = -25; uint timestamp = unchecked((uint)GetMessageTime()); RawInputEventArgs e = null; @@ -453,6 +454,19 @@ namespace Avalonia.Win32 case WindowsMessage.WM_KILLFOCUS: LostFocus?.Invoke(); break; + + case WindowsMessage.WM_GETOBJECT: + if ((long)lParam == UiaRootObjectId) + { + var provider = GetOrCreateAutomationProvider(); + + if (provider is object) + { + var r = UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, provider); + return r; + } + } + break; } #if USE_MANAGED_DRAG diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 1ddec2e763..59927fd488 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; using Avalonia.Controls; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Raw; @@ -12,6 +14,7 @@ using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Win32.Automation; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; using Avalonia.Win32.OpenGl; @@ -24,7 +27,8 @@ namespace Avalonia.Win32 /// /// Window implementation for Win32 platform. /// - public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo, + public partial class WindowImpl : IWindowImpl, + EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithNativeControlHost { private static readonly List s_instances = new List(); @@ -83,6 +87,7 @@ namespace Avalonia.Win32 private POINT _maxTrackSize; private WindowImpl _parent; private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; + private AutomationNode _automationProvider; private bool _isCloseRequested; public WindowImpl() @@ -727,7 +732,7 @@ namespace Avalonia.Win32 throw new Win32Exception(); } - Handle = new PlatformHandle(_hwnd, PlatformConstants.WindowHandleType); + Handle = new WindowImplPlatformHandle(this); _multitouch = Win32Platform.Options.EnableMultitouch ?? true; @@ -1250,6 +1255,17 @@ namespace Avalonia.Win32 /// public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0); + internal AutomationNode GetOrCreateAutomationProvider() + { + if (_automationProvider is null) + { + var peer = ControlAutomationPeer.GetOrCreatePeer(AutomationNodeFactory.Instance, (Control)_owner); + _automationProvider = peer.Node as AutomationNode; + } + + return _automationProvider; + } + private struct SavedWindowInfo { public WindowStyles Style { get; set; } @@ -1264,5 +1280,13 @@ namespace Avalonia.Win32 public SystemDecorations Decorations; public bool IsFullScreen; } + + private class WindowImplPlatformHandle : IPlatformHandle + { + private readonly WindowImpl _owner; + public WindowImplPlatformHandle(WindowImpl owner) => _owner = owner; + public IntPtr Handle => _owner.Hwnd; + public string HandleDescriptor => PlatformConstants.WindowHandleType; + } } } diff --git a/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs b/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs new file mode 100644 index 0000000000..250cad4e25 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs @@ -0,0 +1,253 @@ +using System.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Platform; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Moq; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Automation +{ + public class ControlAutomationPeerTests + { + private static Mock _factory; + + public ControlAutomationPeerTests() + { + _factory = new Mock(); + _factory.Setup(x => x.CreateNode(It.IsAny())) + .Returns(() => Mock.Of(x => x.Factory == _factory)); + } + + public class Children + { + [Fact] + public void Creates_Children_For_Controls_In_Visual_Tree() + { + var panel = new Panel + { + Children = + { + new Border(), + new Border(), + }, + }; + + var factory = CreateFactory(); + var target = CreatePeer(factory, panel); + + Assert.Equal( + panel.GetVisualChildren(), + target.GetChildren().Cast().Select(x => x.Owner)); + } + + [Fact] + public void Creates_Children_when_Controls_Attached_To_Visual_Tree() + { + var contentControl = new ContentControl + { + Template = new FuncControlTemplate((o, ns) => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = o[!ContentControl.ContentProperty], + }), + Content = new Border(), + }; + + var factory = CreateFactory(); + var target = CreatePeer(factory, contentControl); + + Assert.Empty(target.GetChildren()); + + contentControl.Measure(Size.Infinity); + + Assert.Equal(1, target.GetChildren().Count); + } + + [Fact] + public void Updates_Children_When_VisualChildren_Added() + { + var panel = new Panel + { + Children = + { + new Border(), + new Border(), + }, + }; + + var factory = CreateFactory(); + var target = CreatePeer(factory, panel); + var children = target.GetChildren(); + + Assert.Equal(2, children.Count); + + panel.Children.Add(new Decorator()); + + children = target.GetChildren(); + Assert.Equal(3, children.Count); + } + + [Fact] + public void Updates_Children_When_VisualChildren_Removed() + { + var panel = new Panel + { + Children = + { + new Border(), + new Border(), + }, + }; + + var factory = CreateFactory(); + var target = CreatePeer(factory, panel); + var children = target.GetChildren(); + + Assert.Equal(2, children.Count); + + panel.Children.RemoveAt(1); + + children = target.GetChildren(); + Assert.Equal(1, children.Count); + } + + [Fact] + public void Updates_Children_When_Visibility_Changes() + { + var panel = new Panel + { + Children = + { + new Border(), + new Border(), + }, + }; + + var factory = CreateFactory(); + var target = CreatePeer(factory, panel); + var children = target.GetChildren(); + + Assert.Equal(2, children.Count); + + panel.Children[1].IsVisible = false; + children = target.GetChildren(); + Assert.Equal(1, children.Count); + + panel.Children[1].IsVisible = true; + children = target.GetChildren(); + Assert.Equal(2, children.Count); + } + } + + public class Parent + { + [Fact] + public void Connects_Peer_To_Tree_When_GetParent_Called() + { + var border = new Border(); + var tree = new Decorator + { + Child = new Decorator + { + Child = border, + } + }; + + var factory = CreateFactory(); + + // We're accessing Border directly without going via its ancestors. Because the tree + // is built lazily, ensure that calling GetParent causes the ancestor tree to be built. + var target = CreatePeer(factory, border); + + var parentPeer = Assert.IsAssignableFrom(target.GetParent()); + Assert.Same(border.GetVisualParent(), parentPeer.Owner); + } + + [Fact] + public void Parent_Updated_When_Moved_To_Separate_Visual_Tree() + { + var border = new Border(); + var root1 = new Decorator { Child = border }; + var root2 = new Decorator(); + var factory = CreateFactory(); + var target = CreatePeer(factory, border); + + var parentPeer = Assert.IsAssignableFrom(target.GetParent()); + Assert.Same(root1, parentPeer.Owner); + + root1.Child = null; + + Assert.Null(target.GetParent()); + + root2.Child = border; + + parentPeer = Assert.IsAssignableFrom(target.GetParent()); + Assert.Same(root2, parentPeer.Owner); + } + } + + private static IAutomationNodeFactory CreateFactory() + { + var factory = new Mock(); + factory.Setup(x => x.CreateNode(It.IsAny())) + .Returns(() => Mock.Of(x => x.Factory == factory.Object)); + return factory.Object; + } + + private static AutomationPeer CreatePeer(IAutomationNodeFactory factory, Control control) + { + return ControlAutomationPeer.GetOrCreatePeer(factory, control); + } + + private class TestControl : Control + { + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new TestAutomationPeer(factory, this); + } + } + + private class AutomationTestRoot : TestRoot + { + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new TestRootAutomationPeer(factory, this); + } + } + + private class TestAutomationPeer : ControlAutomationPeer + { + public TestAutomationPeer(IAutomationNodeFactory factory, Control owner) + : base(factory, owner) + { + } + } + + private class TestRootAutomationPeer : ControlAutomationPeer, IRootProvider + { + public TestRootAutomationPeer(IAutomationNodeFactory factory, Control owner) + : base(factory, owner) + { + } + + public ITopLevelImpl PlatformImpl => throw new System.NotImplementedException(); + + public AutomationPeer GetFocus() + { + throw new System.NotImplementedException(); + } + + public AutomationPeer GetPeerFromPoint(Point p) + { + throw new System.NotImplementedException(); + } + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 9223b08c81..697179af74 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -491,6 +491,7 @@ namespace Avalonia.Controls.UnitTests.Primitives using (CreateServicesWithFocus()) { var window = PreparedWindow(); + window.Focusable = true; var tb = new TextBox(); var p = new Popup diff --git a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs index df0a077c7f..382bd762d5 100644 --- a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs @@ -86,5 +86,30 @@ namespace Avalonia.Input.UnitTests focused.Verify(x => x.RaiseEvent(It.IsAny())); } + + [Fact] + public void Control_Focus_Should_Be_Set_Before_FocusedElement_Raises_PropertyChanged() + { + var target = new KeyboardDevice(); + var focused = new Mock(); + var root = Mock.Of(); + var raised = 0; + + target.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(target.FocusedElement)) + { + focused.Verify(x => x.RaiseEvent(It.IsAny())); + ++raised; + } + }; + + target.SetFocusedElement( + focused.Object, + NavigationMethod.Unspecified, + KeyModifiers.None); + + Assert.Equal(1, raised); + } } } diff --git a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs index 8a24a8366f..983f48e5f0 100644 --- a/tests/Avalonia.UnitTests/MockWindowingPlatform.cs +++ b/tests/Avalonia.UnitTests/MockWindowingPlatform.cs @@ -85,7 +85,12 @@ namespace Avalonia.UnitTests popupImpl.Setup(x => x.MaxAutoSizeHint).Returns(s_screenSize); popupImpl.Setup(x => x.RenderScaling).Returns(1); popupImpl.Setup(x => x.PopupPositioner).Returns(positioner); - + + popupImpl.Setup(x => x.Dispose()).Callback(() => + { + popupImpl.Object.Closed?.Invoke(); + }); + SetupToplevel(popupImpl); return popupImpl; From c1dfc1f9634e1004486d2c3f9ba0eace3a248a4a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Mar 2021 16:40:08 +0100 Subject: [PATCH 002/822] Started adding integration tests. --- ...a.IntegrationTests.Win32.v3.ncrunchproject | 5 ++ .ncrunch/IntegrationTestApp.v3.ncrunchproject | 5 ++ Avalonia.sln | 60 ++++++++++++++++++- samples/IntegrationTestApp/App.axaml | 7 +++ samples/IntegrationTestApp/App.axaml.cs | 24 ++++++++ .../IntegrationTestApp.csproj | 10 ++++ samples/IntegrationTestApp/MainWindow.axaml | 22 +++++++ .../IntegrationTestApp/MainWindow.axaml.cs | 19 ++++++ samples/IntegrationTestApp/Program.cs | 22 +++++++ samples/IntegrationTestApp/nuget.config | 11 ++++ .../Automation/Peers/ButtonAutomationPeer.cs | 3 + .../Properties/AssemblyInfo.cs | 1 + .../Avalonia.IntegrationTests.Win32.csproj | 13 ++++ .../ButtonTests.cs | 40 +++++++++++++ .../Properties/AssemblyInfo.cs | 4 ++ .../TestAppCollection.cs | 9 +++ .../TestAppFixture.cs | 28 +++++++++ .../xunit.runner.json | 3 + 18 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 .ncrunch/Avalonia.IntegrationTests.Win32.v3.ncrunchproject create mode 100644 .ncrunch/IntegrationTestApp.v3.ncrunchproject create mode 100644 samples/IntegrationTestApp/App.axaml create mode 100644 samples/IntegrationTestApp/App.axaml.cs create mode 100644 samples/IntegrationTestApp/IntegrationTestApp.csproj create mode 100644 samples/IntegrationTestApp/MainWindow.axaml create mode 100644 samples/IntegrationTestApp/MainWindow.axaml.cs create mode 100644 samples/IntegrationTestApp/Program.cs create mode 100644 samples/IntegrationTestApp/nuget.config create mode 100644 tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj create mode 100644 tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs create mode 100644 tests/Avalonia.IntegrationTests.Win32/Properties/AssemblyInfo.cs create mode 100644 tests/Avalonia.IntegrationTests.Win32/TestAppCollection.cs create mode 100644 tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs create mode 100644 tests/Avalonia.IntegrationTests.Win32/xunit.runner.json diff --git a/.ncrunch/Avalonia.IntegrationTests.Win32.v3.ncrunchproject b/.ncrunch/Avalonia.IntegrationTests.Win32.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.IntegrationTests.Win32.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/IntegrationTestApp.v3.ncrunchproject b/.ncrunch/IntegrationTestApp.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/IntegrationTestApp.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/Avalonia.sln b/Avalonia.sln index 75f1dd8407..96d35e1bb9 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -226,11 +226,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.Events" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroComGenerator", "src\tools\MicroComGenerator\MicroComGenerator.csproj", "{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroComGenerator", "src\tools\MicroComGenerator\MicroComGenerator.csproj", "{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestApp", "samples\IntegrationTestApp\IntegrationTestApp.csproj", "{676D6BFD-029D-4E43-BFC7-3892265CE251}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.IntegrationTests.Win32", "tests\Avalonia.IntegrationTests.Win32\Avalonia.IntegrationTests.Win32.csproj", "{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -2142,6 +2146,54 @@ Global {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhone.Build.0 = Release|Any CPU {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {BC594FD5-4AF2-409E-A1E6-04123F54D7C5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.AppStore|iPhone.Build.0 = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Debug|Any CPU.Build.0 = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Debug|iPhone.Build.0 = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Release|Any CPU.ActiveCfg = Release|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Release|Any CPU.Build.0 = Release|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Release|iPhone.ActiveCfg = Release|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Release|iPhone.Build.0 = Release|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {676D6BFD-029D-4E43-BFC7-3892265CE251}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.AppStore|iPhone.Build.0 = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Debug|iPhone.Build.0 = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Release|Any CPU.Build.0 = Release|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Release|iPhone.ActiveCfg = Release|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Release|iPhone.Build.0 = Release|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2203,6 +2255,8 @@ Global {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {676D6BFD-029D-4E43-BFC7-3892265CE251} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {F2CE566B-E7F6-447A-AB1A-3F574A6FE43A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/samples/IntegrationTestApp/App.axaml b/samples/IntegrationTestApp/App.axaml new file mode 100644 index 0000000000..a833e096df --- /dev/null +++ b/samples/IntegrationTestApp/App.axaml @@ -0,0 +1,7 @@ + + + + + diff --git a/samples/IntegrationTestApp/App.axaml.cs b/samples/IntegrationTestApp/App.axaml.cs new file mode 100644 index 0000000000..022931366d --- /dev/null +++ b/samples/IntegrationTestApp/App.axaml.cs @@ -0,0 +1,24 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +namespace IntegrationTestApp +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } + } +} diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj new file mode 100644 index 0000000000..89e793837a --- /dev/null +++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj @@ -0,0 +1,10 @@ + + + WinExe + netcoreapp3.1 + enable + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml new file mode 100644 index 0000000000..5daa7c5c18 --- /dev/null +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs new file mode 100644 index 0000000000..5a14d67aa5 --- /dev/null +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace IntegrationTestApp +{ + public class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/IntegrationTestApp/Program.cs b/samples/IntegrationTestApp/Program.cs new file mode 100644 index 0000000000..c09b249cfa --- /dev/null +++ b/samples/IntegrationTestApp/Program.cs @@ -0,0 +1,22 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; + +namespace IntegrationTestApp +{ + class Program + { + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .LogToTrace(); + } +} diff --git a/samples/IntegrationTestApp/nuget.config b/samples/IntegrationTestApp/nuget.config new file mode 100644 index 0000000000..6c273ab3d9 --- /dev/null +++ b/samples/IntegrationTestApp/nuget.config @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs index ded27f14bd..89c80e1144 100644 --- a/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs @@ -24,6 +24,9 @@ namespace Avalonia.Automation.Peers { return AutomationControlType.Button; } + + protected override bool IsContentElementCore() => true; + protected override bool IsControlElementCore() => true; } } diff --git a/src/Avalonia.Controls/Properties/AssemblyInfo.cs b/src/Avalonia.Controls/Properties/AssemblyInfo.cs index 5c9017f127..5ccafb725b 100644 --- a/src/Avalonia.Controls/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Controls/Properties/AssemblyInfo.cs @@ -9,6 +9,7 @@ using Avalonia.Metadata; [assembly: InternalsVisibleTo("Avalonia.DesignerSupport")] #endif [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Automation")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Embedding")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Presenters")] diff --git a/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj b/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj new file mode 100644 index 0000000000..0967944e19 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj @@ -0,0 +1,13 @@ + + + + netcoreapp3.1 + enable + + + + + + + + diff --git a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs new file mode 100644 index 0000000000..03a156a9e2 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs @@ -0,0 +1,40 @@ +using OpenQA.Selenium.Appium.Windows; +using Xunit; + +namespace Avalonia.IntegrationTests.Win32 +{ + [Collection("IntegrationTestApp collection")] + public class ButtonTests + { + private WindowsDriver _session; + public ButtonTests(TestAppFixture fixture) => _session = fixture.Session; + + [Fact] + public void BasicButton() + { + SelectButtonTab(); + + var button = _session.FindElementByAccessibilityId("BasicButton"); + + Assert.Equal("Basic Button", button.Text); + } + + [Fact] + public void ButtonWithTextBlock() + { + SelectButtonTab(); + + var button = _session.FindElementByAccessibilityId("ButtonWithTextBlock"); + + Assert.Equal("Button with TextBlock", button.Text); + } + + private WindowsElement SelectButtonTab() + { + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var buttonTab = tabs.FindElementByName("Button"); + buttonTab.Click(); + return (WindowsElement)buttonTab; + } + } +} diff --git a/tests/Avalonia.IntegrationTests.Win32/Properties/AssemblyInfo.cs b/tests/Avalonia.IntegrationTests.Win32/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..f9248a3152 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using Xunit; + +// Don't run tests in parallel. +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/Avalonia.IntegrationTests.Win32/TestAppCollection.cs b/tests/Avalonia.IntegrationTests.Win32/TestAppCollection.cs new file mode 100644 index 0000000000..6461e14596 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/TestAppCollection.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace Avalonia.IntegrationTests.Win32 +{ + [CollectionDefinition("IntegrationTestApp collection")] + public class TestAppCollection : ICollectionFixture + { + } +} diff --git a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs new file mode 100644 index 0000000000..6337ff1c6f --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Appium.Windows; + +namespace Avalonia.IntegrationTests.Win32 +{ + public class TestAppFixture : IDisposable + { + private const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723"; + private const string TestAppPath = @"..\..\..\..\..\samples\IntegrationTestApp\bin\Debug\netcoreapp3.1\IntegrationTestApp.exe"; + + public TestAppFixture() + { + var opts = new AppiumOptions(); + var path = Path.GetFullPath(TestAppPath); + opts.AddAdditionalCapability("app", path); + opts.AddAdditionalCapability("deviceName", "WindowsPC"); + Session = new WindowsDriver( + new Uri(WindowsApplicationDriverUrl), + opts); + } + + public WindowsDriver Session { get; } + + public void Dispose() => Session.Close(); + } +} diff --git a/tests/Avalonia.IntegrationTests.Win32/xunit.runner.json b/tests/Avalonia.IntegrationTests.Win32/xunit.runner.json new file mode 100644 index 0000000000..f78bc2f0c6 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json" +} \ No newline at end of file From e8801c2acaba1e1bd171d717747f286962f5693d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Mar 2021 22:46:12 +0100 Subject: [PATCH 003/822] Started adding ComboBox integration tests. And fixed some issues with ComboBox/popups. --- .../IntegrationTestApp.csproj | 3 + samples/IntegrationTestApp/MainWindow.axaml | 17 ++++++ .../IntegrationTestApp/MainWindow.axaml.cs | 1 + .../Peers/ListItemAutomationPeer.cs | 3 + .../Automation/Peers/PopupAutomationPeer.cs | 52 ++++++++++++++++++ .../Peers/PopupRootAutomationPeer.cs | 7 +++ src/Avalonia.Controls/Primitives/Popup.cs | 5 ++ .../Automation/AutomationNode.cs | 11 +++- .../ButtonTests.cs | 24 ++++++-- .../ComboBoxTests.cs | 55 +++++++++++++++++++ 10 files changed, 169 insertions(+), 9 deletions(-) create mode 100644 src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs create mode 100644 tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj index 89e793837a..423b428181 100644 --- a/samples/IntegrationTestApp/IntegrationTestApp.csproj +++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj @@ -7,4 +7,7 @@ + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 5daa7c5c18..f70e3ab040 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -8,6 +8,9 @@ + @@ -17,6 +20,20 @@ + + + Foo + Bar + + + Foo + Bar + + + Foo + Bar + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 5a14d67aa5..1d3ca28432 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -9,6 +9,7 @@ namespace IntegrationTestApp public MainWindow() { InitializeComponent(); + this.AttachDevTools(); } private void InitializeComponent() diff --git a/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs index a9e5089e43..1b9a8354a1 100644 --- a/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs @@ -78,5 +78,8 @@ namespace Avalonia.Automation.Peers { return AutomationControlType.ListItem; } + + protected override bool IsContentElementCore() => true; + protected override bool IsControlElementCore() => true; } } diff --git a/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs new file mode 100644 index 0000000000..4bad8fd108 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using Avalonia.Automation.Platform; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.VisualTree; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class PopupAutomationPeer : ControlAutomationPeer + { + public PopupAutomationPeer(IAutomationNodeFactory factory, Popup owner) + : base(factory, owner) + { + owner.Opened += PopupOpenedClosed; + owner.Closed += PopupOpenedClosed; + } + + protected override IReadOnlyList? GetChildrenCore() + { + var host = (IVisualTreeHost)Owner; + System.Diagnostics.Debug.WriteLine($"Popup children='{host}'"); + return host.Root is Control c ? new[] { GetOrCreatePeer(c) } : null; + } + + protected override bool IsContentElementCore() => false; + protected override bool IsControlElementCore() => false; + + private void PopupOpenedClosed(object sender, EventArgs e) + { + // This is golden. We're following WPF's automation peer API here where the + // parent of a peer is set when another peer returns it as a child. We want to + // add the popup root as a child of the popup, so we need to return it as a + // child right? Yeah except invalidating children doesn't automatically cause + // UIA to re-read the children meaning that the parent doesn't get set. So the + // MAIN MECHANISM FOR PARENTING CONTROLS IS BROKEN WITH THE ONLY AUTOMATION API + // IT WAS WRITTEN FOR. Luckily WPF provides an escape-hatch by exposing the + // TrySetParent API internally to work around this. We're exposing it publicly + // to shame whoever came up with this abomination of an API. + GetPopupRoot()?.TrySetParent(this); + InvalidateChildren(); + } + + private AutomationPeer? GetPopupRoot() + { + var popupRoot = ((IVisualTreeHost)Owner).Root as Control; + return popupRoot is object ? GetOrCreatePeer(popupRoot) : null; + } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs index ed0e8f6d44..b54a938a7f 100644 --- a/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs @@ -21,6 +21,13 @@ namespace Avalonia.Automation.Peers protected override bool IsContentElementCore() => false; protected override bool IsControlElementCore() => false; + + protected override AutomationPeer? GetParentCore() + { + var parent = base.GetParentCore(); + return parent; + } + private void OnOpened(object sender, EventArgs e) { ((PopupRoot)Owner).Opened -= OnOpened; diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 1837b90c5d..0aa27dd100 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -516,6 +516,11 @@ namespace Avalonia.Controls.Primitives Close(); } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new PopupAutomationPeer(factory, this); + } + private static IDisposable SubscribeToEventHandler(T target, TEventHandler handler, Action subscribe, Action unsubscribe) { subscribe(target, handler); diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index ca237567c1..3757958cca 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -82,8 +82,8 @@ namespace Avalonia.Win32.Automation UiaCoreProviderApi.UiaRaiseStructureChangedEvent( this, StructureChangeType.ChildrenInvalidated, - _runtimeId, - _runtimeId.Length); + null, + 0); } public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue) @@ -156,6 +156,11 @@ namespace Avalonia.Win32.Automation return null; } + if (Peer.GetType().Name == "PopupAutomationPeer") + { + System.Diagnostics.Debug.WriteLine("Popup automation node navigate " + direction); + } + return InvokeSync(() => { return direction switch @@ -269,7 +274,7 @@ namespace Avalonia.Win32.Automation var peer = Peer; var parent = peer.GetParent(); - while (parent is object) + while (peer is not AAP.IRootProvider && parent is object) { peer = parent; parent = peer.GetParent(); diff --git a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs index 03a156a9e2..e8548430f3 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs @@ -9,32 +9,44 @@ namespace Avalonia.IntegrationTests.Win32 private WindowsDriver _session; public ButtonTests(TestAppFixture fixture) => _session = fixture.Session; + [Fact] + public void DisabledButton() + { + SelectTab(); + + var button = _session.FindElementByAccessibilityId("DisabledButton"); + + Assert.Equal("Disabled Button", button.Text); + Assert.False(button.Enabled); + } + [Fact] public void BasicButton() { - SelectButtonTab(); + SelectTab(); var button = _session.FindElementByAccessibilityId("BasicButton"); Assert.Equal("Basic Button", button.Text); + Assert.True(button.Enabled); } [Fact] public void ButtonWithTextBlock() { - SelectButtonTab(); + SelectTab(); var button = _session.FindElementByAccessibilityId("ButtonWithTextBlock"); Assert.Equal("Button with TextBlock", button.Text); } - private WindowsElement SelectButtonTab() + private WindowsElement SelectTab() { var tabs = _session.FindElementByAccessibilityId("MainTabs"); - var buttonTab = tabs.FindElementByName("Button"); - buttonTab.Click(); - return (WindowsElement)buttonTab; + var tab = tabs.FindElementByName("Button"); + tab.Click(); + return (WindowsElement)tab; } } } diff --git a/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs new file mode 100644 index 0000000000..95ca1ed14a --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs @@ -0,0 +1,55 @@ +using OpenQA.Selenium.Appium.Windows; +using Xunit; + +namespace Avalonia.IntegrationTests.Win32 +{ + [Collection("IntegrationTestApp collection")] + public class ComboBoxTests + { + private WindowsDriver _session; + public ComboBoxTests(TestAppFixture fixture) => _session = fixture.Session; + + [Fact] + public void UnselectedComboBox() + { + SelectTab(); + + var comboBox = _session.FindElementByAccessibilityId("UnselectedComboBox"); + + Assert.Equal(string.Empty, comboBox.Text); + + comboBox.Click(); + comboBox.FindElementByName("Bar").Click(); + + Assert.Equal("Bar", comboBox.Text); + } + + [Fact] + public void SelectedIndex0ComboBox() + { + SelectTab(); + + var comboBox = _session.FindElementByAccessibilityId("SelectedIndex0ComboBox"); + + Assert.Equal("Foo", comboBox.Text); + } + + [Fact] + public void SelectedIndex1ComboBox() + { + SelectTab(); + + var comboBox = _session.FindElementByAccessibilityId("SelectedIndex1ComboBox"); + + Assert.Equal("Bar", comboBox.Text); + } + + private WindowsElement SelectTab() + { + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("ComboBox"); + tab.Click(); + return (WindowsElement)tab; + } + } +} From 5f551a2dd9f178b5272c54d0b29af18d55a43f77 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 9 Mar 2021 23:07:34 +0100 Subject: [PATCH 004/822] Make integration tests ordered. --- build/XUnit.props | 18 ++++++------- .../Avalonia.IntegrationTests.Win32.csproj | 1 + .../ButtonTests.cs | 26 +++++++------------ .../ComboBoxTests.cs | 26 +++++++------------ .../DefaultCollection.cs | 9 +++++++ .../TestAppCollection.cs | 9 ------- 6 files changed, 39 insertions(+), 50 deletions(-) create mode 100644 tests/Avalonia.IntegrationTests.Win32/DefaultCollection.cs delete mode 100644 tests/Avalonia.IntegrationTests.Win32/TestAppCollection.cs diff --git a/build/XUnit.props b/build/XUnit.props index a75e1bac86..17ead91aa3 100644 --- a/build/XUnit.props +++ b/build/XUnit.props @@ -1,14 +1,14 @@  - - - - - - - - - + + + + + + + + + diff --git a/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj b/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj index 0967944e19..f38f8b0ce1 100644 --- a/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj +++ b/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj @@ -7,6 +7,7 @@ + diff --git a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs index e8548430f3..b0d300e9fe 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs @@ -3,17 +3,23 @@ using Xunit; namespace Avalonia.IntegrationTests.Win32 { - [Collection("IntegrationTestApp collection")] + [Collection("Default")] public class ButtonTests { private WindowsDriver _session; - public ButtonTests(TestAppFixture fixture) => _session = fixture.Session; + + public ButtonTests(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("Button"); + tab.Click(); + } [Fact] public void DisabledButton() { - SelectTab(); - var button = _session.FindElementByAccessibilityId("DisabledButton"); Assert.Equal("Disabled Button", button.Text); @@ -23,8 +29,6 @@ namespace Avalonia.IntegrationTests.Win32 [Fact] public void BasicButton() { - SelectTab(); - var button = _session.FindElementByAccessibilityId("BasicButton"); Assert.Equal("Basic Button", button.Text); @@ -34,19 +38,9 @@ namespace Avalonia.IntegrationTests.Win32 [Fact] public void ButtonWithTextBlock() { - SelectTab(); - var button = _session.FindElementByAccessibilityId("ButtonWithTextBlock"); Assert.Equal("Button with TextBlock", button.Text); } - - private WindowsElement SelectTab() - { - var tabs = _session.FindElementByAccessibilityId("MainTabs"); - var tab = tabs.FindElementByName("Button"); - tab.Click(); - return (WindowsElement)tab; - } } } diff --git a/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs index 95ca1ed14a..6764343c02 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs @@ -3,17 +3,23 @@ using Xunit; namespace Avalonia.IntegrationTests.Win32 { - [Collection("IntegrationTestApp collection")] + [Collection("Default")] public class ComboBoxTests { private WindowsDriver _session; - public ComboBoxTests(TestAppFixture fixture) => _session = fixture.Session; + + public ComboBoxTests(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("ComboBox"); + tab.Click(); + } [Fact] public void UnselectedComboBox() { - SelectTab(); - var comboBox = _session.FindElementByAccessibilityId("UnselectedComboBox"); Assert.Equal(string.Empty, comboBox.Text); @@ -27,8 +33,6 @@ namespace Avalonia.IntegrationTests.Win32 [Fact] public void SelectedIndex0ComboBox() { - SelectTab(); - var comboBox = _session.FindElementByAccessibilityId("SelectedIndex0ComboBox"); Assert.Equal("Foo", comboBox.Text); @@ -37,19 +41,9 @@ namespace Avalonia.IntegrationTests.Win32 [Fact] public void SelectedIndex1ComboBox() { - SelectTab(); - var comboBox = _session.FindElementByAccessibilityId("SelectedIndex1ComboBox"); Assert.Equal("Bar", comboBox.Text); } - - private WindowsElement SelectTab() - { - var tabs = _session.FindElementByAccessibilityId("MainTabs"); - var tab = tabs.FindElementByName("ComboBox"); - tab.Click(); - return (WindowsElement)tab; - } } } diff --git a/tests/Avalonia.IntegrationTests.Win32/DefaultCollection.cs b/tests/Avalonia.IntegrationTests.Win32/DefaultCollection.cs new file mode 100644 index 0000000000..1c8f09a430 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/DefaultCollection.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace Avalonia.IntegrationTests.Win32 +{ + [CollectionDefinition("Default")] + public class DefaultCollection : ICollectionFixture + { + } +} diff --git a/tests/Avalonia.IntegrationTests.Win32/TestAppCollection.cs b/tests/Avalonia.IntegrationTests.Win32/TestAppCollection.cs deleted file mode 100644 index 6461e14596..0000000000 --- a/tests/Avalonia.IntegrationTests.Win32/TestAppCollection.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Xunit; - -namespace Avalonia.IntegrationTests.Win32 -{ - [CollectionDefinition("IntegrationTestApp collection")] - public class TestAppCollection : ICollectionFixture - { - } -} From 2e2333399690d41797624631e9f781d01b9f1cc2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 10 Mar 2021 22:37:08 +0100 Subject: [PATCH 005/822] Add CheckBox tests. --- .editorconfig | 2 +- Avalonia.sln | 3 +- samples/IntegrationTestApp/MainWindow.axaml | 43 ++++++----- .../Peers/ToggleButtonAutomationPeer.cs | 3 + .../CheckBoxTests.cs | 72 +++++++++++++++++++ .../TestAppFixture.cs | 11 +++ 6 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs diff --git a/.editorconfig b/.editorconfig index c7a381b730..25e0135725 100644 --- a/.editorconfig +++ b/.editorconfig @@ -137,7 +137,7 @@ space_within_single_line_array_initializer_braces = true csharp_wrap_before_ternary_opsigns = false # Xaml files -[*.xaml] +[*.{xaml,axaml}] indent_size = 2 # Xml project files diff --git a/Avalonia.sln b/Avalonia.sln index 96d35e1bb9..0dae88db96 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -234,7 +234,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvv EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestApp", "samples\IntegrationTestApp\IntegrationTestApp.csproj", "{676D6BFD-029D-4E43-BFC7-3892265CE251}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.IntegrationTests.Win32", "tests\Avalonia.IntegrationTests.Win32\Avalonia.IntegrationTests.Win32.csproj", "{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.IntegrationTests.Win32", "tests\Avalonia.IntegrationTests.Win32\Avalonia.IntegrationTests.Win32.csproj", "{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution @@ -2235,6 +2235,7 @@ Global {29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098} {7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} = {A689DEF5-D50F-4975-8B72-124C9EB54066} {854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888} diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index f70e3ab040..098b4dc3aa 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -9,31 +9,40 @@ + + + + Unchecked + Checked + ThreeState + + + - - - Foo - Bar - - - Foo - Bar - - - Foo - Bar - - + + + Foo + Bar + + + Foo + Bar + + + Foo + Bar + + diff --git a/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs index b103df3165..4c410d8654 100644 --- a/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs @@ -35,5 +35,8 @@ namespace Avalonia.Automation.Peers { return AutomationControlType.Button; } + + protected override bool IsContentElementCore() => true; + protected override bool IsControlElementCore() => true; } } diff --git a/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs b/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs new file mode 100644 index 0000000000..ebf7408eab --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs @@ -0,0 +1,72 @@ +using OpenQA.Selenium.Appium.Windows; +using Xunit; + +namespace Avalonia.IntegrationTests.Win32 +{ + [Collection("Default")] + public class CheckBoxTests + { + private WindowsDriver _session; + + public CheckBoxTests(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("CheckBox"); + tab.Click(); + } + + [Fact] + public void UncheckedCheckBox() + { + var checkBox = _session.FindElementByAccessibilityId("UncheckedCheckBox"); + + Assert.Equal("Unchecked", checkBox.Text); + Assert.False(checkBox.Selected); + Assert.Equal("0", checkBox.GetAttribute("Toggle.ToggleState")); + + checkBox.Click(); + + Assert.True(checkBox.Selected); + Assert.Equal("1", checkBox.GetAttribute("Toggle.ToggleState")); + } + + [Fact] + public void CheckedCheckBox() + { + var checkBox = _session.FindElementByAccessibilityId("CheckedCheckBox"); + + Assert.Equal("Checked", checkBox.Text); + Assert.True(checkBox.Selected); + Assert.Equal("1", checkBox.GetAttribute("Toggle.ToggleState")); + + checkBox.Click(); + + Assert.False(checkBox.Selected); + Assert.Equal("0", checkBox.GetAttribute("Toggle.ToggleState")); + } + + [Fact] + public void ThreeStateCheckBox() + { + var checkBox = _session.FindElementByAccessibilityId("ThreeStateCheckBox"); + + Assert.Equal("ThreeState", checkBox.Text); + Assert.Equal("2", checkBox.GetAttribute("Toggle.ToggleState")); + + checkBox.Click(); + + Assert.False(checkBox.Selected); + Assert.Equal("0", checkBox.GetAttribute("Toggle.ToggleState")); + + checkBox.Click(); + + Assert.True(checkBox.Selected); + Assert.Equal("1", checkBox.GetAttribute("Toggle.ToggleState")); + + checkBox.Click(); + Assert.Equal("2", checkBox.GetAttribute("Toggle.ToggleState")); + } + } +} diff --git a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs index 6337ff1c6f..fe2daa3cd0 100644 --- a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs +++ b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs @@ -1,5 +1,7 @@ using System; +using System.Globalization; using System.IO; +using System.Runtime.InteropServices; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Windows; @@ -19,10 +21,19 @@ namespace Avalonia.IntegrationTests.Win32 Session = new WindowsDriver( new Uri(WindowsApplicationDriverUrl), opts); + + // https://github.com/microsoft/WinAppDriver/issues/1025 + SetForegroundWindow(new IntPtr(int.Parse( + Session.WindowHandles[0].Substring(2), + NumberStyles.AllowHexSpecifier))); } public WindowsDriver Session { get; } public void Dispose() => Session.Close(); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetForegroundWindow(IntPtr hWnd); } } From 651106aed34ee8c610bbc40f6752fdc88eac6e96 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 11 Mar 2021 15:29:41 +0100 Subject: [PATCH 006/822] Use Name for AutomationId. --- samples/IntegrationTestApp/MainWindow.axaml | 30 ++++++++++++------- .../Automation/Peers/ControlAutomationPeer.cs | 2 +- .../AutomationTests.cs | 29 ++++++++++++++++++ 3 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 098b4dc3aa..bd9f2a0d32 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -5,16 +5,24 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="IntegrationTestApp.MainWindow" Title="IntegrationTestApp"> - + + + + TextBlockWithName + + TextBlockWithNameAndAutomationId + + + - - - @@ -22,23 +30,23 @@ - Unchecked - Checked - ThreeState + Unchecked + Checked + ThreeState - + - + Foo Bar - + Foo Bar - + Foo Bar diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index 7989986653..a6e7a920e3 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -43,7 +43,7 @@ namespace Avalonia.Automation.Peers } protected override void BringIntoViewCore() => Owner.BringIntoView(); - protected override string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner); + protected override string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner) ?? Owner.Name; protected override Rect GetBoundingRectangleCore() => GetBounds(Owner.TransformedBounds); protected virtual IReadOnlyList? GetChildrenCore() diff --git a/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs b/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs new file mode 100644 index 0000000000..3c6d9172c4 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs @@ -0,0 +1,29 @@ +using OpenQA.Selenium.Appium.Windows; +using Xunit; + +namespace Avalonia.IntegrationTests.Win32 +{ + [Collection("Default")] + public class AutomationTests + { + private WindowsDriver _session; + + public AutomationTests(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("Automation"); + tab.Click(); + } + + [Fact] + public void AutomationId() + { + // AutomationID can be specified by the Name or AutomationProperties.AutomationId + // properties, with the latter taking precedence. + var byName = _session.FindElementByAccessibilityId("TextBlockWithName"); + var byAutomationId = _session.FindElementByAccessibilityId("TextBlockWithNameAndAutomationId"); + } + } +} From 8b642a91417a6a8862d6ea0500f298930c0798c7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 11 Mar 2021 16:28:48 +0100 Subject: [PATCH 007/822] Implemented LabeledBy. --- samples/IntegrationTestApp/MainWindow.axaml | 4 + .../Automation/Peers/AutomationPeer.cs | 7 ++ .../Peers/ComboBoxAutomationPeer.cs | 1 + .../Automation/Peers/ControlAutomationPeer.cs | 77 +++++++++++-------- ...tionPeer.cs => TextBlockAutomationPeer.cs} | 8 +- .../Automation/Peers/TextBoxAutomationPeer.cs | 7 +- src/Avalonia.Controls/TextBlock.cs | 2 +- .../AutomationTests.cs | 10 +++ 8 files changed, 81 insertions(+), 35 deletions(-) rename src/Avalonia.Controls/Automation/Peers/{TextAutomationPeer.cs => TextBlockAutomationPeer.cs} (67%) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index bd9f2a0d32..0ccca5d4cc 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -12,6 +12,10 @@ TextBlockWithNameAndAutomationId + Label for TextBox + + Foo + diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index aefd1febd0..8a30661359 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -101,6 +101,12 @@ namespace Avalonia.Automation.Peers /// public string GetClassName() => GetClassNameCore() ?? string.Empty; + /// + /// Gets the automation peer for the label that is targeted to the element. + /// + /// + public AutomationPeer? GetLabeledBy() => GetLabeledByCore(); + /// /// Gets a human-readable localized string that represents the type of the control that is /// associated with this automation peer. @@ -207,6 +213,7 @@ namespace Avalonia.Automation.Peers protected abstract Rect GetBoundingRectangleCore(); protected abstract IReadOnlyList GetOrCreateChildrenCore(); protected abstract string GetClassNameCore(); + protected abstract AutomationPeer? GetLabeledByCore(); protected abstract string? GetNameCore(); protected abstract AutomationPeer? GetParentCore(); protected abstract bool HasKeyboardFocusCore(); diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs index 698bfd1460..721272c83e 100644 --- a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs @@ -94,6 +94,7 @@ namespace Avalonia.Automation.Peers protected override string? GetAutomationIdCore() => null; protected override string GetClassNameCore() => typeof(ComboBoxItem).Name; + protected override AutomationPeer? GetLabeledByCore() => null; protected override AutomationPeer? GetParentCore() => _owner; protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.ListItem; diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index a6e7a920e3..e68bf2ea47 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -43,8 +43,24 @@ namespace Avalonia.Automation.Peers } protected override void BringIntoViewCore() => Owner.BringIntoView(); - protected override string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner) ?? Owner.Name; - protected override Rect GetBoundingRectangleCore() => GetBounds(Owner.TransformedBounds); + + protected override IReadOnlyList GetOrCreateChildrenCore() + { + var children = _children ?? Array.Empty(); + + if (_childrenValid) + return children; + + var newChildren = GetChildrenCore() ?? Array.Empty(); + + foreach (var peer in children.Except(newChildren)) + peer.TrySetParent(null); + foreach (var peer in newChildren) + peer.TrySetParent(this); + + _childrenValid = true; + return _children = newChildren; + } protected virtual IReadOnlyList? GetChildrenCore() { @@ -66,6 +82,24 @@ namespace Avalonia.Automation.Peers return result; } + protected override AutomationPeer? GetLabeledByCore() + { + var label = AutomationProperties.GetLabeledBy(Owner); + return label is Control c ? GetOrCreatePeer(c) : null; + } + + protected override string? GetNameCore() + { + var result = AutomationProperties.GetName(Owner); + + if (string.IsNullOrWhiteSpace(result) && GetLabeledBy() is AutomationPeer labeledBy) + { + return labeledBy.GetName(); + } + + return null; + } + protected override AutomationPeer? GetParentCore() { EnsureConnected(); @@ -90,34 +124,6 @@ namespace Avalonia.Automation.Peers _parentValid = false; } - protected override IReadOnlyList GetOrCreateChildrenCore() - { - var children = _children ?? Array.Empty(); - - if (_childrenValid) - return children; - - var newChildren = GetChildrenCore() ?? Array.Empty(); - - foreach (var peer in children.Except(newChildren)) - peer.TrySetParent(null); - foreach (var peer in newChildren) - peer.TrySetParent(this); - - _childrenValid = true; - return _children = newChildren; - } - - protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Custom; - protected override string GetClassNameCore() => Owner.GetType().Name; - protected override string? GetNameCore() => AutomationProperties.GetName(Owner); - protected override bool HasKeyboardFocusCore() => Owner.IsFocused; - protected override bool IsContentElementCore() => true; - protected override bool IsControlElementCore() => true; - protected override bool IsEnabledCore() => Owner.IsEnabled; - protected override bool IsKeyboardFocusableCore() => Owner.Focusable; - protected override void SetFocusCore() => Owner.Focus(); - protected override bool ShowContextMenuCore() { var c = Owner; @@ -142,6 +148,17 @@ namespace Avalonia.Automation.Peers return true; } + protected override string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner) ?? Owner.Name; + protected override Rect GetBoundingRectangleCore() => GetBounds(Owner.TransformedBounds); + protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Custom; + protected override string GetClassNameCore() => Owner.GetType().Name; + protected override bool HasKeyboardFocusCore() => Owner.IsFocused; + protected override bool IsContentElementCore() => true; + protected override bool IsControlElementCore() => true; + protected override bool IsEnabledCore() => Owner.IsEnabled; + protected override bool IsKeyboardFocusableCore() => Owner.Focusable; + protected override void SetFocusCore() => Owner.Focus(); + private Rect GetBounds(TransformedBounds? bounds) { return bounds?.Bounds.TransformToAABB(bounds!.Value.Transform) ?? default; diff --git a/src/Avalonia.Controls/Automation/Peers/TextAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs similarity index 67% rename from src/Avalonia.Controls/Automation/Peers/TextAutomationPeer.cs rename to src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs index c523feb0f8..a2ddedffc6 100644 --- a/src/Avalonia.Controls/Automation/Peers/TextAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs @@ -5,19 +5,21 @@ using Avalonia.Controls; namespace Avalonia.Automation.Peers { - public class TextAutomationPeer : ControlAutomationPeer + public class TextBlockAutomationPeer : ControlAutomationPeer { - public TextAutomationPeer(IAutomationNodeFactory factory, Control owner) + public TextBlockAutomationPeer(IAutomationNodeFactory factory, TextBlock owner) : base(factory, owner) { } + public new TextBlock Owner => (TextBlock)base.Owner; + protected override AutomationControlType GetAutomationControlTypeCore() { return AutomationControlType.Text; } - protected override string? GetNameCore() => Owner.GetValue(TextBlock.TextProperty); + protected override string? GetNameCore() => Owner.Text; protected override bool IsControlElementCore() { diff --git a/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs index 8ee1aacb52..99f50ddabb 100644 --- a/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs @@ -6,7 +6,7 @@ using Avalonia.Controls; namespace Avalonia.Automation.Peers { - public class TextBoxAutomationPeer : TextAutomationPeer, IValueProvider + public class TextBoxAutomationPeer : ControlAutomationPeer, IValueProvider { public TextBoxAutomationPeer(IAutomationNodeFactory factory, TextBox owner) : base(factory, owner) @@ -17,5 +17,10 @@ namespace Avalonia.Automation.Peers public bool IsReadOnly => Owner.IsReadOnly; public string? Value => Owner.Text; public void SetValue(string? value) => Owner.Text = value; + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Edit; + } } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index eb23878c3d..a80606988b 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -536,7 +536,7 @@ namespace Avalonia.Controls protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) { - return new TextAutomationPeer(factory, this); + return new TextBlockAutomationPeer(factory, this); } private static bool IsValidMaxLines(int maxLines) => maxLines >= 0; diff --git a/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs b/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs index 3c6d9172c4..045a4f08d1 100644 --- a/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs @@ -25,5 +25,15 @@ namespace Avalonia.IntegrationTests.Win32 var byName = _session.FindElementByAccessibilityId("TextBlockWithName"); var byAutomationId = _session.FindElementByAccessibilityId("TextBlockWithNameAndAutomationId"); } + + [Fact] + public void LabeledBy() + { + var label = _session.FindElementByAccessibilityId("TextBlockAsLabel"); + var labeledTextBox = _session.FindElementByAccessibilityId("LabeledByTextBox"); + + Assert.Equal("Label for TextBox", label.Text); + Assert.Equal("Label for TextBox", labeledTextBox.GetAttribute("Name")); + } } } From 06ed8963176c0958f1612957ffcdfcc3e62dc643 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 11 Mar 2021 21:27:48 +0100 Subject: [PATCH 008/822] Added access/accelerator key support. And start adding integration tests for menu items. --- samples/IntegrationTestApp/MainWindow.axaml | 108 ++++++++++-------- .../Automation/Peers/AutomationPeer.cs | 16 ++- .../Automation/Peers/ButtonAutomationPeer.cs | 16 ++- .../Peers/ComboBoxAutomationPeer.cs | 2 + .../Automation/Peers/ControlAutomationPeer.cs | 2 + .../Peers/MenuItemAutomationPeer.cs | 37 ++++-- .../Primitives/AccessText.cs | 38 ++++++ .../Automation/AutomationNode.cs | 2 + .../ButtonTests.cs | 8 ++ .../MenuTests.cs | 31 +++++ 10 files changed, 201 insertions(+), 59 deletions(-) create mode 100644 tests/Avalonia.IntegrationTests.Win32/MenuTests.cs diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 0ccca5d4cc..c72d45783c 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -5,56 +5,64 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="IntegrationTestApp.MainWindow" Title="IntegrationTestApp"> - - - - TextBlockWithName - - TextBlockWithNameAndAutomationId - - Label for TextBox - - Foo - - - - - - - - - - + + + + + + + + + + TextBlockWithName + + TextBlockWithNameAndAutomationId + + Label for TextBox + + Foo + + + + + + + + + + + - - - Unchecked - Checked - ThreeState - - + + + Unchecked + Checked + ThreeState + + - - - - Foo - Bar - - - Foo - Bar - - - Foo - Bar - - - - + + + + Foo + Bar + + + Foo + Bar + + + Foo + Bar + + + + + diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index 8a30661359..4c517fcd71 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; using Avalonia.Automation.Platform; @@ -75,6 +76,17 @@ namespace Avalonia.Automation.Peers /// public void BringIntoView() => BringIntoViewCore(); + /// + /// Gets the accelerator key combinations for the element that is associated with the UI + /// Automation peer. + /// + public string? GetAcceleratorKey() => GetAcceleratorKeyCore(); + + /// + /// Gets the access key for the element that is associated with the automation peer. + /// + public string? GetAccessKey() => GetAccessKeyCore(); + /// /// Gets the control type for the element that is associated with the UI Automation peer. /// @@ -208,6 +220,8 @@ namespace Avalonia.Automation.Peers } protected abstract void BringIntoViewCore(); + protected abstract string? GetAcceleratorKeyCore(); + protected abstract string? GetAccessKeyCore(); protected abstract AutomationControlType GetAutomationControlTypeCore(); protected abstract string? GetAutomationIdCore(); protected abstract Rect GetBoundingRectangleCore(); diff --git a/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs index 89c80e1144..66e5bd7b49 100644 --- a/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs @@ -13,13 +13,27 @@ namespace Avalonia.Automation.Peers : base(factory, owner) { } - + + public new Button Owner => (Button)base.Owner; + public void Invoke() { EnsureEnabled(); (Owner as Button)?.PerformClick(); } + protected override string? GetAcceleratorKeyCore() + { + var result = base.GetAcceleratorKeyCore(); + + if (string.IsNullOrWhiteSpace(result)) + { + result = Owner.HotKey?.ToString(); + } + + return result; + } + protected override AutomationControlType GetAutomationControlTypeCore() { return AutomationControlType.Button; diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs index 721272c83e..d11946b27e 100644 --- a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs @@ -92,6 +92,8 @@ namespace Avalonia.Automation.Peers } } + protected override string? GetAcceleratorKeyCore() => null; + protected override string? GetAccessKeyCore() => null; protected override string? GetAutomationIdCore() => null; protected override string GetClassNameCore() => typeof(ComboBoxItem).Name; protected override AutomationPeer? GetLabeledByCore() => null; diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index e68bf2ea47..cba5b4d79c 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -148,6 +148,8 @@ namespace Avalonia.Automation.Peers return true; } + protected override string? GetAcceleratorKeyCore() => AutomationProperties.GetAcceleratorKey(Owner); + protected override string? GetAccessKeyCore() => AutomationProperties.GetAccessKey(Owner); protected override string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner) ?? Owner.Name; protected override Rect GetBoundingRectangleCore() => GetBounds(Owner.TransformedBounds); protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Custom; diff --git a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs index b1f7774fc9..b9f032ef0d 100644 --- a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs @@ -1,5 +1,6 @@ using Avalonia.Automation.Platform; using Avalonia.Controls; +using Avalonia.Controls.Primitives; #nullable enable @@ -14,6 +15,33 @@ namespace Avalonia.Automation.Peers public new MenuItem Owner => (MenuItem)base.Owner; + protected override string? GetAccessKeyCore() + { + var result = base.GetAccessKeyCore(); + + if (string.IsNullOrWhiteSpace(result)) + { + if (Owner.HeaderPresenter.Child is AccessText accessText) + { + result = accessText.AccessKey.ToString(); + } + } + + return result; + } + + protected override string? GetAcceleratorKeyCore() + { + var result = base.GetAcceleratorKeyCore(); + + if (string.IsNullOrWhiteSpace(result)) + { + result = Owner.InputGesture?.ToString(); + } + + return result; + } + protected override AutomationControlType GetAutomationControlTypeCore() { return AutomationControlType.MenuItem; @@ -23,14 +51,9 @@ namespace Avalonia.Automation.Peers { var result = base.GetNameCore(); - if (result is null && Owner.HeaderPresenter.Child is TextBlock text) - { - result = text.Text; - } - - if (result is null) + if (result is null && Owner.Header is string header) { - result = Owner.Header?.ToString(); + result = AccessText.RemoveAccessKeyMarker(header); } return result; diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index 7a5e6ce426..061fd359f1 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -1,4 +1,6 @@ using System; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Input; using Avalonia.Media; using Avalonia.Media.TextFormatting; @@ -76,6 +78,37 @@ namespace Avalonia.Controls.Primitives } } + internal static string RemoveAccessKeyMarker(string text) + { + if (!string.IsNullOrEmpty(text)) + { + var accessKeyMarker = "_"; + var doubleAccessKeyMarker = accessKeyMarker + accessKeyMarker; + int index = FindAccessKeyMarker(text); + if (index >= 0 && index < text.Length - 1) + text = text.Remove(index, 1); + text = text.Replace(doubleAccessKeyMarker, accessKeyMarker); + } + return text; + } + + private static int FindAccessKeyMarker(string text) + { + var length = text.Length; + var startIndex = 0; + while (startIndex < length) + { + int index = text.IndexOf('_', startIndex); + if (index == -1) + return -1; + if (index + 1 < length && text[index + 1] != '_') + return index; + startIndex = index + 2; + } + + return -1; + } + /// /// Get the pixel location relative to the top-left of the layout box given the text position. /// @@ -180,6 +213,11 @@ namespace Avalonia.Controls.Primitives } } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new NoneAutomationPeer(factory, this); + } + /// /// Returns a string with the first underscore stripped. /// diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 3757958cca..8a5488b1f1 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -116,6 +116,8 @@ namespace Avalonia.Win32.Automation { return (UiaPropertyId)propertyId switch { + UiaPropertyId.AcceleratorKey => InvokeSync(() => Peer.GetAcceleratorKey()), + UiaPropertyId.AccessKey => InvokeSync(() => Peer.GetAccessKey()), UiaPropertyId.AutomationId => InvokeSync(() => Peer.GetAutomationId()), UiaPropertyId.ClassName => InvokeSync(() => Peer.GetClassName()), UiaPropertyId.ClickablePoint => new[] { BoundingRectangle.Center.X, BoundingRectangle.Center.Y }, diff --git a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs index b0d300e9fe..21c2b1a7e3 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs @@ -42,5 +42,13 @@ namespace Avalonia.IntegrationTests.Win32 Assert.Equal("Button with TextBlock", button.Text); } + + [Fact] + public void ButtonWithAcceleratorKey() + { + var button = _session.FindElementByAccessibilityId("ButtonWithAcceleratorKey"); + + Assert.Equal("Ctrl+B", button.GetAttribute("AcceleratorKey")); + } } } diff --git a/tests/Avalonia.IntegrationTests.Win32/MenuTests.cs b/tests/Avalonia.IntegrationTests.Win32/MenuTests.cs new file mode 100644 index 0000000000..3d93afec12 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/MenuTests.cs @@ -0,0 +1,31 @@ +using OpenQA.Selenium.Appium.Windows; +using Xunit; + +namespace Avalonia.IntegrationTests.Win32 +{ + [Collection("Default")] + public class MenuTests + { + private WindowsDriver _session; + + public MenuTests(TestAppFixture fixture) => _session = fixture.Session; + + [Fact] + public void File() + { + var fileMenu = _session.FindElementByAccessibilityId("FileMenu"); + + Assert.Equal("File", fileMenu.Text); + } + + [Fact] + public void Open() + { + var fileMenu = _session.FindElementByAccessibilityId("FileMenu"); + fileMenu.Click(); + + var openMenu = fileMenu.FindElementByAccessibilityId("OpenMenu"); + Assert.Equal("Ctrl+O", openMenu.GetAttribute("AcceleratorKey")); + } + } +} From 3fcfe4d9de1e42e66eec1eccafe0f0279d875ae6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 11 Mar 2021 22:55:33 +0100 Subject: [PATCH 009/822] Notify UIA that a window has been destroyed. --- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index b0d41b72eb..8d3a17bcc5 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -76,6 +76,9 @@ namespace Avalonia.Win32 case WindowsMessage.WM_DESTROY: { + if (_automationProvider is object) + UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, IntPtr.Zero, IntPtr.Zero, null); + //Window doesn't exist anymore _hwnd = IntPtr.Zero; //Remove root reference to this class, so unmanaged delegate can be collected From bd0d97309294b275648d50130eddf7c74e8cae27 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 11 Mar 2021 23:19:09 +0100 Subject: [PATCH 010/822] Use Group as generic control type. Seems to be what best fits, and what e.g. chrome uses. --- src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs index 894234e502..fa5e6c175e 100644 --- a/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs @@ -18,7 +18,7 @@ namespace Avalonia.Automation.Peers protected override AutomationControlType GetAutomationControlTypeCore() { - return AutomationControlType.Pane; + return AutomationControlType.Group; } protected override bool IsContentElementCore() => false; From acc40a7e21641ec5eb42ba6fa1c562a178813e92 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 11 Mar 2021 23:29:02 +0100 Subject: [PATCH 011/822] Add context menu automation peer. And fixed NRE in MenuItemAutomationPeer. --- .../Peers/ContextMenuAutomationPeer.cs | 22 +++++++++++++++++++ .../Peers/MenuItemAutomationPeer.cs | 2 +- src/Avalonia.Controls/ContextMenu.cs | 7 ++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs diff --git a/src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs new file mode 100644 index 0000000000..5631fcf581 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs @@ -0,0 +1,22 @@ +using Avalonia.Automation.Platform; +using Avalonia.Controls; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class ContextMenuAutomationPeer : ControlAutomationPeer + { + public ContextMenuAutomationPeer(IAutomationNodeFactory factory, ContextMenu owner) + : base(factory, owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Menu; + } + + protected override bool IsContentElementCore() => false; + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs index b9f032ef0d..5ed1c5e579 100644 --- a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs @@ -21,7 +21,7 @@ namespace Avalonia.Automation.Peers if (string.IsNullOrWhiteSpace(result)) { - if (Owner.HeaderPresenter.Child is AccessText accessText) + if (Owner.HeaderPresenter?.Child is AccessText accessText) { result = accessText.AccessKey.ToString(); } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 57e4909e39..c190e98f54 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Generators; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; @@ -304,6 +306,11 @@ namespace Avalonia.Controls return new MenuItemContainerGenerator(this); } + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new ContextMenuAutomationPeer(factory, this); + } + private void Open(Control control, Control placementTarget) { if (IsOpen) From 7f4222997b30c00fb5da6720a13bd90c8bdb5744 Mon Sep 17 00:00:00 2001 From: grokys Date: Fri, 12 Mar 2021 15:24:56 +0100 Subject: [PATCH 012/822] Don't use C#9 features. Causing the build to fail on OSX/Linux --- .../Automation/AutomationElementIdentifiers.cs | 6 +++--- .../Automation/ExpandCollapsePatternIdentifiers.cs | 2 +- .../Automation/RangeValuePatternIdentifiers.cs | 8 ++++---- .../Automation/ScrollPatternIdentifiers.cs | 12 ++++++------ .../Automation/SelectionPatternIdentifiers.cs | 6 +++--- .../Avalonia.Win32/Automation/AutomationNode.cs | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs b/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs index 7f6bff11ad..4566cd9db5 100644 --- a/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs +++ b/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs @@ -11,18 +11,18 @@ namespace Avalonia.Automation /// Identifies the bounding rectangle automation property. The bounding rectangle property /// value is returned by the method. /// - public static AutomationProperty BoundingRectangleProperty { get; } = new(); + public static AutomationProperty BoundingRectangleProperty { get; } = new AutomationProperty(); /// /// Identifies the class name automation property. The class name property value is returned /// by the method. /// - public static AutomationProperty ClassNameProperty { get; } = new(); + public static AutomationProperty ClassNameProperty { get; } = new AutomationProperty(); /// /// Identifies the name automation property. The class name property value is returned /// by the method. /// - public static AutomationProperty NameProperty { get; } = new(); + public static AutomationProperty NameProperty { get; } = new AutomationProperty(); } } diff --git a/src/Avalonia.Controls/Automation/ExpandCollapsePatternIdentifiers.cs b/src/Avalonia.Controls/Automation/ExpandCollapsePatternIdentifiers.cs index 342bdaceb1..e2b6782162 100644 --- a/src/Avalonia.Controls/Automation/ExpandCollapsePatternIdentifiers.cs +++ b/src/Avalonia.Controls/Automation/ExpandCollapsePatternIdentifiers.cs @@ -10,6 +10,6 @@ namespace Avalonia.Automation /// /// Identifies automation property. /// - public static AutomationProperty ExpandCollapseStateProperty { get; } = new(); + public static AutomationProperty ExpandCollapseStateProperty { get; } = new AutomationProperty(); } } diff --git a/src/Avalonia.Controls/Automation/RangeValuePatternIdentifiers.cs b/src/Avalonia.Controls/Automation/RangeValuePatternIdentifiers.cs index e3fa744cc0..625b37d001 100644 --- a/src/Avalonia.Controls/Automation/RangeValuePatternIdentifiers.cs +++ b/src/Avalonia.Controls/Automation/RangeValuePatternIdentifiers.cs @@ -10,21 +10,21 @@ namespace Avalonia.Automation /// /// Identifies automation property. /// - public static AutomationProperty IsReadOnlyProperty { get; } = new(); + public static AutomationProperty IsReadOnlyProperty { get; } = new AutomationProperty(); /// /// Identifies automation property. /// - public static AutomationProperty MinimumProperty { get; } = new(); + public static AutomationProperty MinimumProperty { get; } = new AutomationProperty(); /// /// Identifies automation property. /// - public static AutomationProperty MaximumProperty { get; } = new(); + public static AutomationProperty MaximumProperty { get; } = new AutomationProperty(); /// /// Identifies automation property. /// - public static AutomationProperty ValueProperty { get; } = new(); + public static AutomationProperty ValueProperty { get; } = new AutomationProperty(); } } diff --git a/src/Avalonia.Controls/Automation/ScrollPatternIdentifiers.cs b/src/Avalonia.Controls/Automation/ScrollPatternIdentifiers.cs index ad10b96e17..d9e843e75a 100644 --- a/src/Avalonia.Controls/Automation/ScrollPatternIdentifiers.cs +++ b/src/Avalonia.Controls/Automation/ScrollPatternIdentifiers.cs @@ -15,31 +15,31 @@ namespace Avalonia.Automation /// /// Identifies automation property. /// - public static AutomationProperty HorizontallyScrollableProperty { get; } = new(); + public static AutomationProperty HorizontallyScrollableProperty { get; } = new AutomationProperty(); /// /// Identifies automation property. /// - public static AutomationProperty HorizontalScrollPercentProperty { get; } = new(); + public static AutomationProperty HorizontalScrollPercentProperty { get; } = new AutomationProperty(); /// /// Identifies automation property. /// - public static AutomationProperty HorizontalViewSizeProperty { get; } = new(); + public static AutomationProperty HorizontalViewSizeProperty { get; } = new AutomationProperty(); /// /// Identifies automation property. /// - public static AutomationProperty VerticallyScrollableProperty { get; } = new(); + public static AutomationProperty VerticallyScrollableProperty { get; } = new AutomationProperty(); /// /// Identifies automation property. /// - public static AutomationProperty VerticalScrollPercentProperty { get; } = new(); + public static AutomationProperty VerticalScrollPercentProperty { get; } = new AutomationProperty(); /// /// Identifies automation property. /// - public static AutomationProperty VerticalViewSizeProperty { get; } = new(); + public static AutomationProperty VerticalViewSizeProperty { get; } = new AutomationProperty(); } } diff --git a/src/Avalonia.Controls/Automation/SelectionPatternIdentifiers.cs b/src/Avalonia.Controls/Automation/SelectionPatternIdentifiers.cs index ce4fdda739..c3669528cd 100644 --- a/src/Avalonia.Controls/Automation/SelectionPatternIdentifiers.cs +++ b/src/Avalonia.Controls/Automation/SelectionPatternIdentifiers.cs @@ -10,16 +10,16 @@ namespace Avalonia.Automation /// /// Identifies automation property. /// - public static AutomationProperty CanSelectMultipleProperty { get; } = new(); + public static AutomationProperty CanSelectMultipleProperty { get; } = new AutomationProperty(); /// /// Identifies automation property. /// - public static AutomationProperty IsSelectionRequiredProperty { get; } = new(); + public static AutomationProperty IsSelectionRequiredProperty { get; } = new AutomationProperty(); /// /// Identifies the property that gets the selected items in a container. /// - public static AutomationProperty SelectionProperty { get; } = new(); + public static AutomationProperty SelectionProperty { get; } = new AutomationProperty(); } } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 8a5488b1f1..c7021e6d53 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -25,7 +25,7 @@ namespace Avalonia.Win32.Automation IRawElementProviderAdviseEvents, IInvokeProvider { - private static Dictionary s_propertyMap = new() + private static Dictionary s_propertyMap = new Dictionary() { { AutomationElementIdentifiers.BoundingRectangleProperty, UiaPropertyId.BoundingRectangle }, { AutomationElementIdentifiers.ClassNameProperty, UiaPropertyId.ClassName }, @@ -276,7 +276,7 @@ namespace Avalonia.Win32.Automation var peer = Peer; var parent = peer.GetParent(); - while (peer is not AAP.IRootProvider && parent is object) + while (!(peer is AAP.IRootProvider) && parent is object) { peer = parent; parent = peer.GetParent(); From 8add2371c6da37a69d3fe4d3ca56785b12b5e630 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Mar 2021 22:55:39 +0100 Subject: [PATCH 013/822] Make root automation node come from OS. This will be needed for OSX. --- .../Automation/Peers/AutomationPeer.cs | 11 ++++++++++ .../Automation/Peers/ControlAutomationPeer.cs | 19 +++++++++++++---- .../Peers/PopupRootAutomationPeer.cs | 4 ++-- .../Automation/Peers/WindowAutomationPeer.cs | 4 ++-- .../Peers/WindowBaseAutomationPeer.cs | 4 ++-- src/Avalonia.Controls/Control.cs | 7 +++++++ .../Platform/IWindowBaseImpl.cs | 7 +++++++ src/Avalonia.Controls/Primitives/PopupRoot.cs | 4 ++-- src/Avalonia.Controls/TopLevel.cs | 2 ++ src/Avalonia.Controls/Window.cs | 4 ++-- src/Avalonia.Controls/WindowBase.cs | 21 +++++++++++++++++++ .../Remote/PreviewerWindowImpl.cs | 3 +++ src/Avalonia.DesignerSupport/Remote/Stubs.cs | 3 +++ src/Avalonia.Headless/HeadlessWindowImpl.cs | 3 +++ src/Avalonia.Native/WindowImplBase.cs | 3 +++ src/Avalonia.X11/X11Window.cs | 3 +++ .../Automation/AutomationNode.cs | 8 ++++++- .../Automation/AutomationNodeFactory.cs | 3 +-- .../Automation/RootAutomationNode.cs | 4 ++-- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 12 +++++++---- src/Windows/Avalonia.Win32/WindowImpl.cs | 14 ++----------- 21 files changed, 108 insertions(+), 35 deletions(-) diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index 4c517fcd71..25af340014 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -66,6 +66,17 @@ namespace Avalonia.Automation.Peers Node = factory.CreateNode(this); } + /// + /// Initializes a new instance of the class. + /// + /// + /// The platform automation node. + /// + protected AutomationPeer(IAutomationNode node) + { + Node = node; + } + /// /// Gets the related node in the platform UI Automation tree. /// diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index cba5b4d79c..bd4e9dfcf4 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -23,10 +23,14 @@ namespace Avalonia.Automation.Peers : base(factory) { Owner = owner ?? throw new ArgumentNullException("owner"); + Initialize(); + } - owner.PropertyChanged += OwnerPropertyChanged; - var visualChildren = ((IVisual)owner).VisualChildren; - visualChildren.CollectionChanged += VisualChildrenChanged; + protected ControlAutomationPeer(IAutomationNode node, Control owner) + : base(node) + { + Owner = owner ?? throw new ArgumentNullException("owner"); + Initialize(); } public Control Owner { get; } @@ -161,11 +165,18 @@ namespace Avalonia.Automation.Peers protected override bool IsKeyboardFocusableCore() => Owner.Focusable; protected override void SetFocusCore() => Owner.Focus(); - private Rect GetBounds(TransformedBounds? bounds) + private static Rect GetBounds(TransformedBounds? bounds) { return bounds?.Bounds.TransformToAABB(bounds!.Value.Transform) ?? default; } + private void Initialize() + { + Owner.PropertyChanged += OwnerPropertyChanged; + var visualChildren = ((IVisual)Owner).VisualChildren; + visualChildren.CollectionChanged += VisualChildrenChanged; + } + private void VisualChildrenChanged(object sender, EventArgs e) => InvalidateChildren(); private void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) diff --git a/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs index b54a938a7f..f7e06e83eb 100644 --- a/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs @@ -8,8 +8,8 @@ namespace Avalonia.Automation.Peers { public class PopupRootAutomationPeer : WindowBaseAutomationPeer { - public PopupRootAutomationPeer(IAutomationNodeFactory factory, PopupRoot owner) - : base(factory, owner) + public PopupRootAutomationPeer(IAutomationNode node, PopupRoot owner) + : base(node, owner) { if (owner.IsVisible) StartTrackingFocus(); diff --git a/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs index 41778457e8..9629ae0294 100644 --- a/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs @@ -8,8 +8,8 @@ namespace Avalonia.Automation.Peers { public class WindowAutomationPeer : WindowBaseAutomationPeer { - public WindowAutomationPeer(IAutomationNodeFactory factory, Window owner) - : base(factory, owner) + public WindowAutomationPeer(IAutomationNode node, Window owner) + : base(node, owner) { if (owner.IsVisible) StartTrackingFocus(); diff --git a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs index ac235b3898..f97d13c766 100644 --- a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs @@ -14,8 +14,8 @@ namespace Avalonia.Automation.Peers { private Control? _focus; - public WindowBaseAutomationPeer(IAutomationNodeFactory factory, WindowBase owner) - : base(factory, owner) + public WindowBaseAutomationPeer(IAutomationNode node, WindowBase owner) + : base(node, owner) { } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 6b63ac14cc..ac95743e30 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -209,5 +209,12 @@ namespace Avalonia.Controls _automationPeer = OnCreateAutomationPeer(factory); return _automationPeer; } + + internal void SetAutomationPeer(AutomationPeer peer) + { + if (_automationPeer is object) + throw new InvalidOperationException("Automation peer is already set."); + _automationPeer = peer ?? throw new ArgumentNullException(nameof(peer)); + } } } diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 0d303a6666..8d18b925f8 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -1,4 +1,6 @@ using System; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; namespace Avalonia.Platform { @@ -44,6 +46,11 @@ namespace Avalonia.Platform /// Action Activated { get; set; } + /// + /// Gets or sets a method called when automation is started on the window. + /// + Func AutomationStarted { get; set; } + /// /// Gets the platform window handle. /// diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 73dd68e7cb..96ceb2afcf 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -171,9 +171,9 @@ namespace Avalonia.Controls.Primitives } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNode node) { - return new PopupRootAutomationPeer(factory, this); + return new PopupRootAutomationPeer(node, this); } } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 7a92836ddf..cbb9506627 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -1,5 +1,7 @@ using System; using System.Reactive.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 7648162493..9d54f3beee 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -968,9 +968,9 @@ namespace Avalonia.Controls } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNode node) { - return new WindowAutomationPeer(factory, this); + return new WindowAutomationPeer(node, this); } } } diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index cdcb499e98..4f31dfeada 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -3,6 +3,8 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Layout; @@ -64,6 +66,7 @@ namespace Avalonia.Controls impl.Activated = HandleActivated; impl.Deactivated = HandleDeactivated; impl.PositionChanged = HandlePositionChanged; + impl.AutomationStarted = HandleAutomationStarted; } /// @@ -269,6 +272,17 @@ namespace Avalonia.Controls /// The actual size of the window. protected virtual Size ArrangeSetBounds(Size size) => size; + protected sealed override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + throw new NotSupportedException( + "Automation peer for window controls must be created by the operating system."); + } + + protected virtual AutomationPeer OnCreateAutomationPeer(IAutomationNode node) + { + throw new NotImplementedException("OnCreateAutomationPeer must be implemented in a derived class."); + } + /// /// Handles a window position change notification from /// . @@ -306,6 +320,13 @@ namespace Avalonia.Controls Deactivated?.Invoke(this, EventArgs.Empty); } + private AutomationPeer HandleAutomationStarted(IAutomationNode node) + { + var peer = OnCreateAutomationPeer(node); + SetAutomationPeer(peer); + return peer; + } + private void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e) { if (!_ignoreVisibilityChange) diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 787f44887f..1919503151 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -1,5 +1,7 @@ using System; using System.Reactive.Disposables; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Remote.Server; using Avalonia.Input; @@ -45,6 +47,7 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } + public Func AutomationStarted { get; set; } public Size MaxAutoSizeHint { get; } = new Size(4096, 4096); protected override void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index eedfc52d9d..090d93362b 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Reactive.Disposables; using System.Threading.Tasks; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives.PopupPositioning; @@ -38,6 +40,7 @@ namespace Avalonia.DesignerSupport.Remote public Action PositionChanged { get; set; } public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } + public Func AutomationStarted { get; set; } public Action TransparencyLevelChanged { get; set; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index af522f3e36..fe3064b415 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Primitives.PopupPositioning; @@ -142,6 +144,7 @@ namespace Avalonia.Headless public IScreenImpl Screen { get; } = new HeadlessScreensStub(); public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } + public Func AutomationStarted { get; set; } public void SetTitle(string title) { diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 71359f733d..65b1ae2120 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Platform.Surfaces; @@ -466,5 +468,6 @@ namespace Avalonia.Native public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0); public IPlatformHandle Handle { get; private set; } + public Func AutomationStarted { get; set; } } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 8f3f412578..3650bf1c79 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -6,6 +6,8 @@ using System.Linq; using System.Reactive.Disposables; using System.Text; using System.Threading.Tasks; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives.PopupPositioning; @@ -1128,5 +1130,6 @@ namespace Avalonia.X11 public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8); public bool NeedsManagedDecorations => false; + public Func AutomationStarted { get; set; } } } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index c7021e6d53..3963d0cb6c 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -56,7 +56,13 @@ namespace Avalonia.Win32.Automation Peer = peer; } - public AutomationPeer Peer { get; } + protected AutomationNode(Func peerGetter) + { + _runtimeId = new int[] { 3, GetHashCode() }; + Peer = peerGetter(this); + } + + public AutomationPeer Peer { get; protected set; } public IAutomationNodeFactory Factory => AutomationNodeFactory.Instance; public Rect BoundingRectangle diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs index 776b65adc6..a7ee0e192f 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs @@ -1,6 +1,5 @@ using Avalonia.Automation.Peers; using Avalonia.Automation.Platform; -using Avalonia.Automation.Provider; using Avalonia.Threading; #nullable enable @@ -14,7 +13,7 @@ namespace Avalonia.Win32.Automation public IAutomationNode CreateNode(AutomationPeer peer) { Dispatcher.UIThread.VerifyAccess(); - return peer is IRootProvider ? new RootAutomationNode(peer) : new AutomationNode(peer); + return new AutomationNode(peer); } } } diff --git a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs index cb8cfae90e..dd2665a624 100644 --- a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs @@ -13,8 +13,8 @@ namespace Avalonia.Win32.Automation IRawElementProviderFragmentRoot, IRootAutomationNode { - public RootAutomationNode(AutomationPeer peer) - : base(peer) + public RootAutomationNode(Func peerGetter) + : base(peerGetter) { } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 8d3a17bcc5..31afcc368c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Win32.Automation; using Avalonia.Win32.Input; using Avalonia.Win32.Interop.Automation; using static Avalonia.Win32.Interop.UnmanagedMethods; @@ -76,7 +77,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_DESTROY: { - if (_automationProvider is object) + if (_automationNode is object) UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, IntPtr.Zero, IntPtr.Zero, null); //Window doesn't exist anymore @@ -461,11 +462,14 @@ namespace Avalonia.Win32 case WindowsMessage.WM_GETOBJECT: if ((long)lParam == UiaRootObjectId) { - var provider = GetOrCreateAutomationProvider(); + if (_automationNode is null && AutomationStarted is object) + { + _automationNode = new RootAutomationNode(AutomationStarted); + } - if (provider is object) + if (_automationNode is object) { - var r = UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, provider); + var r = UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, _automationNode); return r; } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 02fbb7a143..fa3d2665ed 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -87,7 +87,7 @@ namespace Avalonia.Win32 private POINT _maxTrackSize; private WindowImpl _parent; private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; - private AutomationNode _automationProvider; + private AutomationNode _automationNode; private bool _isCloseRequested; private bool _shown; @@ -175,6 +175,7 @@ namespace Avalonia.Win32 public Action LostFocus { get; set; } public Action TransparencyLevelChanged { get; set; } + public Func AutomationStarted { get; set; } public Thickness BorderThickness { @@ -1269,17 +1270,6 @@ namespace Avalonia.Win32 /// public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0); - internal AutomationNode GetOrCreateAutomationProvider() - { - if (_automationProvider is null) - { - var peer = ControlAutomationPeer.GetOrCreatePeer(AutomationNodeFactory.Instance, (Control)_owner); - _automationProvider = peer.Node as AutomationNode; - } - - return _automationProvider; - } - private struct SavedWindowInfo { public WindowStyles Style { get; set; } From 1c603747b56d92c674583c0ecb6d7c5b12c5e3dd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 Mar 2021 20:52:20 +0100 Subject: [PATCH 014/822] Use correct peer for TabItems. --- src/Avalonia.Controls/TabItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 4d50ef961d..90817c655d 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -85,7 +85,7 @@ namespace Avalonia.Controls protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) { - return new ListItemAutomationPeer(factory, this); + return new TabItemAutomationPeer(factory, this); } } } From 7bb26473d13b5352ee6b8f30afa5d2c7bbd1a867 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 31 May 2021 13:47:20 +0200 Subject: [PATCH 015/822] Fix AccessText after merge. --- .../Primitives/AccessText.cs | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index 8a269287b2..15c44e8c12 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -76,26 +76,12 @@ namespace Avalonia.Controls.Primitives rect.BottomLeft + offset, rect.BottomRight + offset); } - - internal static string RemoveAccessKeyMarker(string text) - { - if (!string.IsNullOrEmpty(text)) - { - var accessKeyMarker = "_"; - var doubleAccessKeyMarker = accessKeyMarker + accessKeyMarker; - int index = FindAccessKeyMarker(text); - if (index >= 0 && index < text.Length - 1) - text = text.Remove(index, 1); - text = text.Replace(doubleAccessKeyMarker, accessKeyMarker); - } - return text; - } } /// protected override TextLayout CreateTextLayout(Size constraint, string text) { - return base.CreateTextLayout(constraint, StripAccessKey(text)); + return base.CreateTextLayout(constraint, RemoveAccessKeyMarker(text)); } /// @@ -127,23 +113,35 @@ namespace Avalonia.Controls.Primitives return new NoneAutomationPeer(factory, this); } - /// - /// Returns a string with the first underscore stripped. - /// - /// The text. - /// The text with the first underscore stripped. - private string StripAccessKey(string text) + internal static string RemoveAccessKeyMarker(string text) { - var position = text.IndexOf('_'); - - if (position == -1) + if (!string.IsNullOrEmpty(text)) { - return text; + var accessKeyMarker = "_"; + var doubleAccessKeyMarker = accessKeyMarker + accessKeyMarker; + int index = FindAccessKeyMarker(text); + if (index >= 0 && index < text.Length - 1) + text = text.Remove(index, 1); + text = text.Replace(doubleAccessKeyMarker, accessKeyMarker); } - else + return text; + } + + private static int FindAccessKeyMarker(string text) + { + var length = text.Length; + var startIndex = 0; + while (startIndex < length) { - return text.Substring(0, position) + text.Substring(position + 1); + int index = text.IndexOf('_', startIndex); + if (index == -1) + return -1; + if (index + 1 < length && text[index + 1] != '_') + return index; + startIndex = index + 2; } + + return -1; } /// From d6d583a16ea3ae68cebd02a12160dfdc48189cc2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 Mar 2021 18:31:51 +0100 Subject: [PATCH 016/822] Initial implementation of OSX automation. --- .../project.pbxproj | 8 + native/Avalonia.Native/src/OSX/AvnString.h | 1 + native/Avalonia.Native/src/OSX/AvnString.mm | 17 ++ native/Avalonia.Native/src/OSX/automation.h | 16 ++ native/Avalonia.Native/src/OSX/automation.mm | 220 ++++++++++++++++++ native/Avalonia.Native/src/OSX/common.h | 4 + native/Avalonia.Native/src/OSX/main.mm | 16 ++ native/Avalonia.Native/src/OSX/window.h | 1 + native/Avalonia.Native/src/OSX/window.mm | 83 ++++++- src/Avalonia.Native/AutomationNode.cs | 36 +++ src/Avalonia.Native/AutomationNodeFactory.cs | 24 ++ src/Avalonia.Native/AvnAutomationPeer.cs | 97 ++++++++ src/Avalonia.Native/AvnString.cs | 48 ++++ src/Avalonia.Native/Helpers.cs | 11 + src/Avalonia.Native/PopupImpl.cs | 4 +- src/Avalonia.Native/WindowImpl.cs | 4 +- src/Avalonia.Native/WindowImplBase.cs | 15 +- src/Avalonia.Native/avn.idl | 88 +++++++ 18 files changed, 684 insertions(+), 9 deletions(-) create mode 100644 native/Avalonia.Native/src/OSX/automation.h create mode 100644 native/Avalonia.Native/src/OSX/automation.mm create mode 100644 src/Avalonia.Native/AutomationNode.cs create mode 100644 src/Avalonia.Native/AutomationNodeFactory.cs create mode 100644 src/Avalonia.Native/AvnAutomationPeer.cs diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index dba3ee6d31..2a3f7d7e7d 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj @@ -29,6 +29,8 @@ AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; }; AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; }; AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; }; + BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; }; + BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -61,6 +63,8 @@ AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = ""; }; AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libAvalonia.Native.OSX.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = ""; }; + BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = ""; }; + BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -94,6 +98,8 @@ AB7A61E62147C814003C5833 = { isa = PBXGroup; children = ( + BC11A5BC2608D58F0017BAD0 /* automation.h */, + BC11A5BD2608D58F0017BAD0 /* automation.mm */, 1A1852DB23E05814008F0DED /* deadlock.mm */, 1A002B9D232135EE00021753 /* app.mm */, 37DDA9B121933371002E132B /* AvnString.h */, @@ -138,6 +144,7 @@ buildActionMask = 2147483647; files = ( 37155CE4233C00EB0034DCE9 /* menu.h in Headers */, + BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -207,6 +214,7 @@ AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */, 1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */, 1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */, + BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */, 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */, 520624B322973F4100C4DCEF /* menu.mm in Sources */, 37A517B32159597E00FBA241 /* Screens.mm in Sources */, diff --git a/native/Avalonia.Native/src/OSX/AvnString.h b/native/Avalonia.Native/src/OSX/AvnString.h index 3ce83d370a..3b750b11db 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.h +++ b/native/Avalonia.Native/src/OSX/AvnString.h @@ -14,4 +14,5 @@ extern IAvnStringArray* CreateAvnStringArray(NSArray* array); extern IAvnStringArray* CreateAvnStringArray(NSArray* array); extern IAvnStringArray* CreateAvnStringArray(NSString* string); extern IAvnString* CreateByteArray(void* data, int len); +extern NSString* GetNSStringAndRelease(IAvnString* s); #endif /* AvnString_h */ diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 001cf151d8..df11ad8715 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -141,3 +141,20 @@ IAvnString* CreateByteArray(void* data, int len) { return new AvnStringImpl(data, len); } + +NSString* GetNSStringAndRelease(IAvnString* s) +{ + if (s != nullptr) + { + char* p; + + if (s->Pointer((void**)&p) == S_OK) + { + return [NSString stringWithUTF8String:p]; + } + + s->Release(); + } + + return nullptr; +} diff --git a/native/Avalonia.Native/src/OSX/automation.h b/native/Avalonia.Native/src/OSX/automation.h new file mode 100644 index 0000000000..65e1153248 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/automation.h @@ -0,0 +1,16 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +class IAvnAutomationPeer; + +@interface AvnAutomationNode : NSAccessibilityElement +- (AvnAutomationNode *)initWithPeer:(IAvnAutomationPeer *)peer; +@end + +struct INSAccessibilityHolder +{ + virtual NSObject* _Nonnull GetNSAccessibility () = 0; +}; + +NS_ASSUME_NONNULL_END diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm new file mode 100644 index 0000000000..064b414094 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -0,0 +1,220 @@ +#import "automation.h" +#include "common.h" +#include "AvnString.h" +#include "window.h" + +class AutomationNode : public ComSingleObject, + public INSAccessibilityHolder +{ +private: + NSAccessibilityElement* _node; +public: + FORWARD_IUNKNOWN() + + AutomationNode(NSAccessibilityElement* node) + { + _node = node; + } + + AutomationNode(IAvnAutomationPeer* peer) + { + _node = [[AvnAutomationNode alloc] initWithPeer: peer]; + } + + virtual NSObject* GetNSAccessibility() override + { + return _node; + } +}; + +@implementation AvnAutomationNode +{ + IAvnAutomationPeer* _peer; + NSMutableArray* _children; +} + +- (AvnAutomationNode *)initWithPeer:(IAvnAutomationPeer *)peer +{ + self = [super init]; + _peer = peer; + return self; +} + +- (BOOL)isAccessibilityElement +{ + return _peer->IsControlElement(); +} + +- (NSAccessibilityRole)accessibilityRole +{ + auto controlType = _peer->GetAutomationControlType(); + + switch (controlType) { + case AutomationButton: + return NSAccessibilityButtonRole; + case AutomationCheckBox: + return NSAccessibilityCheckBoxRole; + case AutomationComboBox: + return NSAccessibilityPopUpButtonRole; + case AutomationGroup: + case AutomationPane: + return NSAccessibilityGroupRole; + case AutomationSlider: + return NSAccessibilitySliderRole; + case AutomationTab: + return NSAccessibilityTabGroupRole; + case AutomationTabItem: + return NSAccessibilityRadioButtonRole; + case AutomationWindow: + return NSAccessibilityWindowRole; + default: + return NSAccessibilityUnknownRole; + } +} + +- (NSString *)accessibilityIdentifier +{ + return GetNSStringAndRelease(_peer->GetAutomationId()); +} + +- (NSString *)accessibilityTitle +{ + return GetNSStringAndRelease(_peer->GetName()); +} + +- (NSArray *)accessibilityChildren +{ + if (_children == nullptr && _peer != nullptr) + { + auto childPeers = _peer->GetChildren(); + auto childCount = childPeers != nullptr ? childPeers->GetCount() : 0; + + if (childCount > 0) + { + _children = [[NSMutableArray alloc] initWithCapacity:childCount]; + + for (int i = 0; i < childCount; ++i) + { + IAvnAutomationPeer* child; + + if (childPeers->Get(i, &child) == S_OK) + { + NSObject* element = ::GetAccessibilityElement(child->GetNode()); + [_children addObject:element]; + } + } + } + } + + return _children; +} + +- (NSRect)accessibilityFrame +{ + auto view = [self getAvnView]; + auto window = [self getAvnWindow]; + + if (view != nullptr) + { + auto bounds = ToNSRect(_peer->GetBoundingRectangle()); + auto windowBounds = [view convertRect:bounds toView:nil]; + auto screenBounds = [window convertRectToScreen:windowBounds]; + return screenBounds; + } + + return NSRect(); +} + +- (id)accessibilityParent +{ + auto parentPeer = _peer->GetParent(); + + if (parentPeer != nullptr) + { + return GetAccessibilityElement(parentPeer); + } + + return [NSApplication sharedApplication]; +} + +- (id)accessibilityTopLevelUIElement +{ + return GetAccessibilityElement([self getRootNode]); +} + +- (id)accessibilityWindow +{ + return [self accessibilityTopLevelUIElement]; +} + +- (BOOL)accessibilityPerformPress +{ + _peer->InvokeProvider_Invoke(); + return YES; +} + +- (BOOL)isAccessibilitySelectorAllowed:(SEL)selector +{ + if (selector == @selector(accessibilityPerformPress)) + { + return _peer->IsInvokeProvider(); + } + + return [super isAccessibilitySelectorAllowed:selector]; +} + +- (IAvnAutomationNode*)getRootNode +{ + auto rootPeer = _peer->GetRootPeer(); + return rootPeer != nullptr ? rootPeer->GetNode() : nullptr; +} + +- (IAvnWindowBase*)getWindow +{ + auto rootNode = [self getRootNode]; + + if (rootNode != nullptr) + { + IAvnWindowBase* window; + if (rootNode->QueryInterface(&IID_IAvnWindow, (void**)&window) == S_OK) + { + return window; + } + } + + return nullptr; +} + +- (AvnWindow*) getAvnWindow +{ + auto window = [self getWindow]; + return dynamic_cast(window)->GetNSWindow(); +} + +- (AvnView*) getAvnView +{ + auto window = [self getWindow]; + return dynamic_cast(window)->GetNSView(); +} + +@end + +extern IAvnAutomationNode* CreateAutomationNode(IAvnAutomationPeer* peer) +{ + @autoreleasepool + { + return new AutomationNode(peer); + } +} + +extern NSObject* GetAccessibilityElement(IAvnAutomationPeer* peer) +{ + auto node = peer != nullptr ? peer->GetNode() : nullptr; + return GetAccessibilityElement(node); +} + +extern NSObject* GetAccessibilityElement(IAvnAutomationNode* node) +{ + auto holder = dynamic_cast(node); + return holder != nullptr ? holder->GetNSAccessibility() : nil; +} diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index c082003ccf..7c00ebc469 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -30,10 +30,14 @@ extern IAvnMenu* GetAppMenu (); extern NSMenuItem* GetAppMenuItem (); extern void SetAutoGenerateDefaultAppMenuItems (bool enabled); extern bool GetAutoGenerateDefaultAppMenuItems (); +extern IAvnAutomationNode* CreateAutomationNode(IAvnAutomationPeer* peer); +extern NSObject* GetAccessibilityElement(IAvnAutomationPeer* peer); +extern NSObject* GetAccessibilityElement(IAvnAutomationNode* node); extern void InitializeAvnApp(IAvnApplicationEvents* events); extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; extern NSPoint ToNSPoint (AvnPoint p); +extern NSRect ToNSRect (AvnRect r); extern AvnPoint ToAvnPoint (NSPoint p); extern AvnPoint ConvertPointY (AvnPoint p); extern CGFloat PrimaryDisplayHeight(); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index aaaf381b26..f231ff9a71 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -1,6 +1,7 @@ //This file will contain actual IID structures #define COM_GUIDS_MATERIALIZE #include "common.h" +#include "window.h" static bool s_generateDefaultAppMenuItems = true; static NSString* s_appTitle = @"Avalonia"; @@ -259,6 +260,12 @@ public: return S_OK; } + virtual HRESULT CreateAutomationNode (IAvnAutomationPeer* peer, IAvnAutomationNode** ppv) override + { + *ppv = ::CreateAutomationNode(peer); + return S_OK; + } + virtual HRESULT SetAppMenu (IAvnMenu* appMenu) override { ::SetAppMenu(s_appTitle, appMenu); @@ -295,6 +302,15 @@ NSPoint ToNSPoint (AvnPoint p) return result; } +NSRect ToNSRect (AvnRect r) +{ + return NSRect + { + NSPoint { r.X, r.Y }, + NSSize { r.Width, r.Height } + }; +} + AvnPoint ToAvnPoint (NSPoint p) { AvnPoint result; diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index b1f64bca88..200b442fcd 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -39,6 +39,7 @@ class WindowBaseImpl; struct INSWindowHolder { virtual AvnWindow* _Nonnull GetNSWindow () = 0; + virtual AvnView* _Nonnull GetNSView () = 0; }; struct IWindowStateChanged diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 870345e543..dc009dc5a0 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -5,14 +5,25 @@ #include "menu.h" #include #include "rendertarget.h" +#include "AvnString.h" +#include "automation.h" -class WindowBaseImpl : public virtual ComSingleObject, public INSWindowHolder +class WindowBaseImpl : public virtual ComObject, + public virtual IAvnWindowBase, + public virtual IAvnAutomationNode, + public INSWindowHolder, + public INSAccessibilityHolder { private: NSCursor* cursor; public: FORWARD_IUNKNOWN() + BEGIN_INTERFACE_MAP() + INTERFACE_MAP_ENTRY(IAvnWindowBase, IID_IAvnWindowBase) + INTERFACE_MAP_ENTRY(IAvnAutomationNode, IID_IAvnAutomationNode) + END_INTERFACE_MAP() + virtual ~WindowBaseImpl() { View = NULL; @@ -104,6 +115,16 @@ public: { return Window; } + + virtual AvnView* GetNSView() override + { + return View; + } + + virtual NSObject* GetNSAccessibility() override + { + return Window; + } virtual HRESULT Show(bool activate) override { @@ -1846,6 +1867,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent bool _isExtended; AvnMenu* _menu; double _lastScaling; + IAvnAutomationPeer* _automationPeer; + NSMutableArray* _automationChildren; } -(void) setIsExtended:(bool)value; @@ -2218,6 +2241,64 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _parent->BaseEvents->PositionChanged(position); } } + +- (BOOL)isAccessibilityElement +{ + [self getAutomationPeer]; + return YES; +} + +- (NSString *)accessibilityIdentifier +{ + auto peer = [self getAutomationPeer]; + return GetNSStringAndRelease(peer->GetAutomationId()); +} + +- (NSArray *)accessibilityChildren +{ + auto peer = [self getAutomationPeer]; + + if (_automationChildren == nullptr) + { + _automationChildren = (NSMutableArray*)[super accessibilityChildren]; + + auto childPeers = peer->GetChildren(); + auto childCount = childPeers != nullptr ? childPeers->GetCount() : 0; + + if (childCount > 0) + { + for (int i = 0; i < childCount; ++i) + { + IAvnAutomationPeer* child; + + if (childPeers->Get(i, &child) == S_OK) + { + auto element = GetAccessibilityElement(child); + [_automationChildren addObject:element]; + } + } + } + } + + return _automationChildren; +} + +- (id)accessibilityHitTest:(NSPoint)point +{ + point = [self convertPointFromScreen:point]; + auto p = [_parent->View translateLocalPoint:ToAvnPoint(point)]; + auto peer = [self getAutomationPeer]; + auto hit = peer->RootProvider_GetPeerFromPoint(p); + return GetAccessibilityElement(hit); +} + +- (IAvnAutomationPeer* _Nonnull) getAutomationPeer +{ + if (_automationPeer == nullptr) + _automationPeer = _parent->BaseEvents->AutomationStarted(_parent); + return _automationPeer; +} + @end class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup diff --git a/src/Avalonia.Native/AutomationNode.cs b/src/Avalonia.Native/AutomationNode.cs new file mode 100644 index 0000000000..c45ade520a --- /dev/null +++ b/src/Avalonia.Native/AutomationNode.cs @@ -0,0 +1,36 @@ +using Avalonia.Automation; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; +using Avalonia.Native.Interop; + +#nullable enable + +namespace Avalonia.Native +{ + internal class AutomationNode : IRootAutomationNode + { + public AutomationNode(AutomationNodeFactory factory, IAvnAutomationNode native) + { + Native = native; + Factory = factory; + } + + public IAvnAutomationNode Native { get; } + public IAutomationNodeFactory Factory { get; } + + public void ChildrenChanged() + { + // TODO + } + + public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue) + { + // TODO + } + + public void FocusChanged(AutomationPeer? focus) + { + // TODO + } + } +} diff --git a/src/Avalonia.Native/AutomationNodeFactory.cs b/src/Avalonia.Native/AutomationNodeFactory.cs new file mode 100644 index 0000000000..d40009a855 --- /dev/null +++ b/src/Avalonia.Native/AutomationNodeFactory.cs @@ -0,0 +1,24 @@ +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; +using Avalonia.Native.Interop; + +namespace Avalonia.Native +{ + internal class AutomationNodeFactory : IAutomationNodeFactory + { + private static AutomationNodeFactory _instance; + private readonly IAvaloniaNativeFactory _native; + + public static AutomationNodeFactory GetInstance(IAvaloniaNativeFactory native) + { + return _instance ??= new AutomationNodeFactory(native); + } + + private AutomationNodeFactory(IAvaloniaNativeFactory native) => _native = native; + + public IAutomationNode CreateNode(AutomationPeer peer) + { + return new AutomationNode(this, _native.CreateAutomationNode(new AvnAutomationPeer(peer))); + } + } +} diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs new file mode 100644 index 0000000000..a1519d8d26 --- /dev/null +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Provider; +using Avalonia.Native.Interop; + +#nullable enable + +namespace Avalonia.Native +{ + internal class AvnAutomationPeer : CallbackBase, IAvnAutomationPeer + { + private readonly AutomationPeer _inner; + + public AvnAutomationPeer(AutomationPeer inner) => _inner = inner; + + public IAvnAutomationNode Node => ((AutomationNode)_inner.Node).Native; + public IAvnString? AcceleratorKey => _inner.GetAcceleratorKey().ToAvnString(); + public IAvnString? AccessKey => _inner.GetAccessKey().ToAvnString(); + public AvnAutomationControlType AutomationControlType => (AvnAutomationControlType)_inner.GetAutomationControlType(); + public IAvnString? AutomationId => _inner.GetAutomationId().ToAvnString(); + public AvnRect BoundingRectangle => _inner.GetBoundingRectangle().ToAvnRect(); + public IAvnAutomationPeerArray Children => new AvnAutomationPeerArray(_inner.GetChildren()); + public IAvnString ClassName => _inner.GetClassName().ToAvnString(); + public IAvnAutomationPeer? LabeledBy => Wrap(_inner.GetLabeledBy()); + public IAvnString Name => _inner.GetName().ToAvnString(); + public IAvnAutomationPeer? Parent => Wrap(_inner.GetParent()); + + public int HasKeyboardFocus() => _inner.HasKeyboardFocus().AsComBool(); + public int IsContentElement() => _inner.IsContentElement().AsComBool(); + public int IsControlElement() => _inner.IsControlElement().AsComBool(); + public int IsEnabled() => _inner.IsEnabled().AsComBool(); + public int IsKeyboardFocusable() => _inner.IsKeyboardFocusable().AsComBool(); + public void SetFocus() => _inner.SetFocus(); + public int ShowContextMenu() => _inner.ShowContextMenu().AsComBool(); + + public IAvnAutomationPeer? RootPeer + { + get + { + var peer = _inner; + var parent = peer.GetParent(); + + while (!(peer is IRootProvider) && parent is object) + { + peer = parent; + parent = peer.GetParent(); + } + + return new AvnAutomationPeer(peer); + } + } + + public int IsRootProvider() => (_inner is IRootProvider).AsComBool(); + + public IAvnAutomationPeer? RootProvider_GetPeerFromPoint(AvnPoint point) + { + var result = ((IRootProvider)_inner).GetPeerFromPoint(point.ToAvaloniaPoint()); + + if (result is null) + return null; + + // The OSX accessibility APIs expect non-ignored elements when hit-testing. + while (!result.IsControlElement()) + { + var parent = result.GetParent(); + + if (parent is object) + result = parent; + else + break; + } + + return Wrap(result); + } + + public int IsInvokeProvider() => (_inner is IInvokeProvider).AsComBool(); + + public void InvokeProvider_Invoke() => ((IInvokeProvider)_inner).Invoke(); + + public static AvnAutomationPeer? Wrap(AutomationPeer? peer) => + peer != null ? new AvnAutomationPeer(peer) : null; + } + + internal class AvnAutomationPeerArray : CallbackBase, IAvnAutomationPeerArray + { + private readonly AvnAutomationPeer[] _items; + + public AvnAutomationPeerArray(IReadOnlyList items) + { + _items = items.Select(x => new AvnAutomationPeer(x)).ToArray(); + } + + public uint Count => (uint)_items.Length; + public IAvnAutomationPeer Get(uint index) => _items[index]; + } +} diff --git a/src/Avalonia.Native/AvnString.cs b/src/Avalonia.Native/AvnString.cs index dcd473bae3..bcaa16c5b2 100644 --- a/src/Avalonia.Native/AvnString.cs +++ b/src/Avalonia.Native/AvnString.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.Text; namespace Avalonia.Native.Interop { @@ -13,6 +14,53 @@ namespace Avalonia.Native.Interop { string[] ToStringArray(); } + + internal class AvnString : CallbackBase, IAvnString + { + private IntPtr _native; + private int _nativeLen; + + public AvnString(string s) => String = s; + + public string String { get; } + public byte[] Bytes => Encoding.UTF8.GetBytes(String); + + public unsafe void* Pointer() + { + EnsureNative(); + return _native.ToPointer(); + } + + public int Length() + { + EnsureNative(); + return _nativeLen; + } + + protected override void Destroyed() + { + if (_native != IntPtr.Zero) + { + Marshal.FreeHGlobal(_native); + _native = IntPtr.Zero; + } + } + + private unsafe void EnsureNative() + { + if (string.IsNullOrEmpty(String)) + return; + if (_native == IntPtr.Zero) + { + _nativeLen = Encoding.UTF8.GetByteCount(String); + _native = Marshal.AllocHGlobal(_nativeLen + 1); + var ptr = (byte*)_native.ToPointer(); + fixed (char* chars = String) + Encoding.UTF8.GetBytes(chars, String.Length, ptr, _nativeLen); + ptr[_nativeLen] = 0; + } + } + } } namespace Avalonia.Native.Interop.Impl { diff --git a/src/Avalonia.Native/Helpers.cs b/src/Avalonia.Native/Helpers.cs index 564434a04c..764ff789dc 100644 --- a/src/Avalonia.Native/Helpers.cs +++ b/src/Avalonia.Native/Helpers.cs @@ -1,4 +1,5 @@ using Avalonia.Native.Interop; +using JetBrains.Annotations; namespace Avalonia.Native { @@ -24,11 +25,21 @@ namespace Avalonia.Native return new AvnPoint { X = pt.X, Y = pt.Y }; } + public static AvnRect ToAvnRect (this Rect rect) + { + return new AvnRect() { X = rect.X, Y= rect.Y, Height = rect.Height, Width = rect.Width }; + } + public static AvnSize ToAvnSize (this Size size) { return new AvnSize { Height = size.Height, Width = size.Width }; } + public static IAvnString ToAvnString(this string s) + { + return s != null ? new AvnString(s) : null; + } + public static Size ToAvaloniaSize (this AvnSize size) { return new Size(size.Width, size.Height); diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index c36675afcd..83f2cc2f82 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -7,7 +7,6 @@ namespace Avalonia.Native { class PopupImpl : WindowBaseImpl, IPopupImpl { - private readonly IAvaloniaNativeFactory _factory; private readonly AvaloniaNativePlatformOptions _opts; private readonly AvaloniaNativePlatformOpenGlInterface _glFeature; private readonly IWindowBaseImpl _parent; @@ -15,9 +14,8 @@ namespace Avalonia.Native public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOpenGlInterface glFeature, - IWindowBaseImpl parent) : base(opts, glFeature) + IWindowBaseImpl parent) : base(factory, opts, glFeature) { - _factory = factory; _opts = opts; _glFeature = glFeature; _parent = parent; diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index f3b60f07be..05cfb5b2ed 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -13,7 +13,6 @@ namespace Avalonia.Native { internal class WindowImpl : WindowBaseImpl, IWindowImpl, ITopLevelImplWithNativeMenuExporter { - private readonly IAvaloniaNativeFactory _factory; private readonly AvaloniaNativePlatformOptions _opts; private readonly AvaloniaNativePlatformOpenGlInterface _glFeature; IAvnWindow _native; @@ -22,9 +21,8 @@ namespace Avalonia.Native internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, - AvaloniaNativePlatformOpenGlInterface glFeature) : base(opts, glFeature) + AvaloniaNativePlatformOpenGlInterface glFeature) : base(factory, opts, glFeature) { - _factory = factory; _opts = opts; _glFeature = glFeature; _doubleClickHelper = new DoubleClickHelper(); diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index b45a0787b9..1e0d7ca0f4 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -48,6 +48,7 @@ namespace Avalonia.Native internal abstract class WindowBaseImpl : IWindowBaseImpl, IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost { + protected readonly IAvaloniaNativeFactory _factory; protected IInputRoot _inputRoot; IAvnWindowBase _native; private object _syncRoot = new object(); @@ -63,8 +64,10 @@ namespace Avalonia.Native private NativeControlHostImpl _nativeControlHost; private IGlContext _glContext; - internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOpenGlInterface glFeature) + internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, + AvaloniaNativePlatformOpenGlInterface glFeature) { + _factory = factory; _gpu = opts.UseGpu && glFeature != null; _deferredRendering = opts.UseDeferredRendering; @@ -92,6 +95,8 @@ namespace Avalonia.Native Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d)); } + public IAvnWindowBase Native => _native; + public Size ClientSize { get @@ -244,8 +249,14 @@ namespace Avalonia.Native return (AvnDragDropEffects)args.Effects; } } - } + public IAvnAutomationPeer AutomationStarted(IAvnAutomationNode node) + { + var factory = AutomationNodeFactory.GetInstance(_parent._factory); + return new AvnAutomationPeer(_parent.AutomationStarted(new AutomationNode(factory, node))); + } + } + public void Activate() { _native.Activate(); diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index adcbeb2d3a..8357742f55 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -400,6 +400,49 @@ enum AvnExtendClientAreaChromeHints AvnDefaultChrome = AvnPreferSystemChrome, } +enum AvnAutomationControlType +{ + AutomationButton, + AutomationCalendar, + AutomationCheckBox, + AutomationComboBox, + AutomationEdit, + AutomationHyperlink, + AutomationImage, + AutomationListItem, + AutomationList, + AutomationMenu, + AutomationMenuBar, + AutomationMenuItem, + AutomationProgressBar, + AutomationRadioButton, + AutomationScrollBar, + AutomationSlider, + AutomationSpinner, + AutomationStatusBar, + AutomationTab, + AutomationTabItem, + AutomationText, + AutomationToolBar, + AutomationToolTip, + AutomationTree, + AutomationTreeItem, + AutomationCustom, + AutomationGroup, + AutomationThumb, + AutomationDataGrid, + AutomationDataItem, + AutomationDocument, + AutomationSplitButton, + AutomationWindow, + AutomationPane, + AutomationHeader, + AutomationHeaderItem, + AutomationTable, + AutomationTitleBar, + AutomationSeparator, +} + [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] interface IAvaloniaNativeFactory : IUnknown { @@ -418,6 +461,7 @@ interface IAvaloniaNativeFactory : IUnknown HRESULT CreateMenu(IAvnMenuEvents* cb, IAvnMenu** ppv); HRESULT CreateMenuItem(IAvnMenuItem** ppv); HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv); + HRESULT CreateAutomationNode(IAvnAutomationPeer* peer, IAvnAutomationNode** ppv); } [uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)] @@ -506,6 +550,7 @@ interface IAvnWindowBaseEvents : IUnknown AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position, AvnInputModifiers modifiers, AvnDragDropEffects effects, IAvnClipboard* clipboard, [intptr]void* dataObjectHandle); + IAvnAutomationPeer* AutomationStarted(IAvnAutomationNode* node); } [uuid(1ae178ee-1fcc-447f-b6dd-b7bb727f934c)] @@ -733,3 +778,46 @@ interface IAvnApplicationEvents : IUnknown { void FilesOpened (IAvnStringArray* urls); } + +[uuid(b87016f3-7eec-41de-b385-07844c268dc4)] +interface IAvnAutomationPeer : IUnknown +{ + IAvnAutomationNode* GetNode(); + IAvnString* GetAcceleratorKey(); + IAvnString* GetAccessKey(); + AvnAutomationControlType GetAutomationControlType(); + IAvnString* GetAutomationId(); + AvnRect GetBoundingRectangle(); + IAvnAutomationPeerArray* GetChildren(); + IAvnString* GetClassName(); + IAvnAutomationPeer* GetLabeledBy(); + IAvnString* GetName(); + IAvnAutomationPeer* GetParent(); + bool HasKeyboardFocus(); + bool IsContentElement(); + bool IsControlElement(); + bool IsEnabled(); + bool IsKeyboardFocusable(); + void SetFocus(); + bool ShowContextMenu(); + + IAvnAutomationPeer* GetRootPeer(); + + bool IsRootProvider(); + IAvnAutomationPeer* RootProvider_GetPeerFromPoint(AvnPoint point); + + bool IsInvokeProvider(); + void InvokeProvider_Invoke(); +} + +[uuid(b00af5da-78af-4b33-bfff-4ce13a6239a9)] +interface IAvnAutomationPeerArray : IUnknown +{ + uint GetCount(); + HRESULT Get(uint index, IAvnAutomationPeer**ppv); +} + +[uuid(004dc40b-e435-49dc-bac5-6272ee35382a)] +interface IAvnAutomationNode : IUnknown +{ +} From bcc8876e8e434210b3c725ce88a6525ed9ab359f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 Mar 2021 22:16:30 +0100 Subject: [PATCH 017/822] Handle children changing. And map all the automation control types to roles. --- native/Avalonia.Native/src/OSX/automation.mm | 68 ++++++++++++++------ native/Avalonia.Native/src/OSX/window.mm | 5 ++ src/Avalonia.Native/AutomationNode.cs | 5 +- src/Avalonia.Native/avn.idl | 1 + 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 064b414094..32230ff5b7 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -21,6 +21,11 @@ public: _node = [[AvnAutomationNode alloc] initWithPeer: peer]; } + virtual void ChildrenChanged() override + { + NSAccessibilityPostNotification(_node, NSAccessibilityLayoutChangedNotification); + } + virtual NSObject* GetNSAccessibility() override { return _node; @@ -50,25 +55,46 @@ public: auto controlType = _peer->GetAutomationControlType(); switch (controlType) { - case AutomationButton: - return NSAccessibilityButtonRole; - case AutomationCheckBox: - return NSAccessibilityCheckBoxRole; - case AutomationComboBox: - return NSAccessibilityPopUpButtonRole; - case AutomationGroup: - case AutomationPane: - return NSAccessibilityGroupRole; - case AutomationSlider: - return NSAccessibilitySliderRole; - case AutomationTab: - return NSAccessibilityTabGroupRole; - case AutomationTabItem: - return NSAccessibilityRadioButtonRole; - case AutomationWindow: - return NSAccessibilityWindowRole; - default: - return NSAccessibilityUnknownRole; + case AutomationButton: return NSAccessibilityButtonRole; + case AutomationCalendar: return NSAccessibilityGridRole; + case AutomationCheckBox: return NSAccessibilityCheckBoxRole; + case AutomationComboBox: return NSAccessibilityPopUpButtonRole; + case AutomationEdit: return NSAccessibilityTextFieldRole; + case AutomationHyperlink: return NSAccessibilityLinkRole; + case AutomationImage: return NSAccessibilityImageRole; + case AutomationListItem: return NSAccessibilityRowRole; + case AutomationList: return NSAccessibilityTableRole; + case AutomationMenu: return NSAccessibilityMenuBarRole; + case AutomationMenuBar: return NSAccessibilityMenuBarRole; + case AutomationMenuItem: return NSAccessibilityMenuItemRole; + case AutomationProgressBar: return NSAccessibilityProgressIndicatorRole; + case AutomationRadioButton: return NSAccessibilityRadioButtonRole; + case AutomationScrollBar: return NSAccessibilityScrollBarRole; + case AutomationSlider: return NSAccessibilitySliderRole; + case AutomationSpinner: return NSAccessibilityIncrementorRole; + case AutomationStatusBar: return NSAccessibilityTableRole; + case AutomationTab: return NSAccessibilityTabGroupRole; + case AutomationTabItem: return NSAccessibilityRadioButtonRole; + case AutomationText: return NSAccessibilityTextFieldRole; + case AutomationToolBar: return NSAccessibilityToolbarRole; + case AutomationToolTip: return NSAccessibilityPopoverRole; + case AutomationTree: return NSAccessibilityOutlineRole; + case AutomationTreeItem: return NSAccessibilityOutlineRowSubrole; + case AutomationCustom: return NSAccessibilityUnknownRole; + case AutomationGroup: return NSAccessibilityGroupRole; + case AutomationThumb: return NSAccessibilityHandleRole; + case AutomationDataGrid: return NSAccessibilityGridRole; + case AutomationDataItem: return NSAccessibilityCellRole; + case AutomationDocument: return NSAccessibilityStaticTextRole; + case AutomationSplitButton: return NSAccessibilityPopUpButtonRole; + case AutomationWindow: return NSAccessibilityWindowRole; + case AutomationPane: return NSAccessibilityGroupRole; + case AutomationHeader: return NSAccessibilityGroupRole; + case AutomationHeaderItem: return NSAccessibilityButtonRole; + case AutomationTable: return NSAccessibilityTableRole; + case AutomationTitleBar: return NSAccessibilityGroupRole; + case AutomationSeparator: return NSAccessibilityUnknownRole; + default: return NSAccessibilityUnknownRole; } } @@ -188,13 +214,13 @@ public: - (AvnWindow*) getAvnWindow { auto window = [self getWindow]; - return dynamic_cast(window)->GetNSWindow(); + return window != nullptr ? dynamic_cast(window)->GetNSWindow() : nullptr; } - (AvnView*) getAvnView { auto window = [self getWindow]; - return dynamic_cast(window)->GetNSView(); + return window != nullptr ? dynamic_cast(window)->GetNSView() : nullptr; } @end diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index dc009dc5a0..456a2db3ee 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -509,6 +509,11 @@ public: return S_OK; } + virtual void ChildrenChanged() override + { + NSAccessibilityPostNotification(Window, NSAccessibilityLayoutChangedNotification); + } + protected: virtual NSWindowStyleMask GetStyle() { diff --git a/src/Avalonia.Native/AutomationNode.cs b/src/Avalonia.Native/AutomationNode.cs index c45ade520a..c0e46b52b9 100644 --- a/src/Avalonia.Native/AutomationNode.cs +++ b/src/Avalonia.Native/AutomationNode.cs @@ -18,10 +18,7 @@ namespace Avalonia.Native public IAvnAutomationNode Native { get; } public IAutomationNodeFactory Factory { get; } - public void ChildrenChanged() - { - // TODO - } + public void ChildrenChanged() => Native.ChildrenChanged(); public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue) { diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 8357742f55..5697665545 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -820,4 +820,5 @@ interface IAvnAutomationPeerArray : IUnknown [uuid(004dc40b-e435-49dc-bac5-6272ee35382a)] interface IAvnAutomationNode : IUnknown { + void ChildrenChanged(); } From 7bbbfa414f3407ffc7710230613829bc35dabef2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 27 Mar 2021 00:07:15 +0100 Subject: [PATCH 018/822] More OSX accessibility implementation. --- native/Avalonia.Native/src/OSX/automation.mm | 74 ++++++++++++++++++- native/Avalonia.Native/src/OSX/window.mm | 4 + .../Peers/RangeBaseAutomationPeer.cs | 3 + .../Provider/IRangeValueProvider.cs | 12 +++ src/Avalonia.Native/AutomationNode.cs | 15 +++- src/Avalonia.Native/AvnAutomationPeer.cs | 15 +++- src/Avalonia.Native/avn.idl | 23 ++++++ 7 files changed, 142 insertions(+), 4 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 32230ff5b7..b400785a13 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -26,6 +26,17 @@ public: NSAccessibilityPostNotification(_node, NSAccessibilityLayoutChangedNotification); } + virtual void PropertyChanged(AvnAutomationProperty property) override + { + switch (property) { + case RangeValueProvider_Value: + NSAccessibilityPostNotification(_node, NSAccessibilityValueChangedNotification); + break; + default: + break; + } + } + virtual NSObject* GetNSAccessibility() override { return _node; @@ -75,7 +86,7 @@ public: case AutomationStatusBar: return NSAccessibilityTableRole; case AutomationTab: return NSAccessibilityTabGroupRole; case AutomationTabItem: return NSAccessibilityRadioButtonRole; - case AutomationText: return NSAccessibilityTextFieldRole; + case AutomationText: return NSAccessibilityStaticTextRole; case AutomationToolBar: return NSAccessibilityToolbarRole; case AutomationToolTip: return NSAccessibilityPopoverRole; case AutomationTree: return NSAccessibilityOutlineRole; @@ -105,7 +116,39 @@ public: - (NSString *)accessibilityTitle { - return GetNSStringAndRelease(_peer->GetName()); + // StaticText exposes its text via the value property. + if (_peer->GetAutomationControlType() != AutomationText) + { + return GetNSStringAndRelease(_peer->GetName()); + } + + return [super accessibilityTitle]; +} + +- (id)accessibilityValue +{ + if (_peer->IsRangeValueProvider()) + { + return [NSNumber numberWithDouble:_peer->RangeValueProvider_GetValue()]; + } + else if (_peer->IsToggleProvider()) + { + switch (_peer->ToggleProvider_GetToggleState()) { + case 0: return [NSNumber numberWithBool:NO]; + case 1: return [NSNumber numberWithBool:YES]; + default: return [NSNumber numberWithInt:-1]; + } + } + else if (_peer->IsValueProvider()) + { + return GetNSStringAndRelease(_peer->ValueProvider_GetValue()); + } + else if (_peer->GetAutomationControlType() == AutomationText) + { + return GetNSStringAndRelease(_peer->GetName()); + } + + return [super accessibilityValue]; } - (NSArray *)accessibilityChildren @@ -175,16 +218,43 @@ public: - (BOOL)accessibilityPerformPress { + if (!_peer->IsInvokeProvider()) + return NO; _peer->InvokeProvider_Invoke(); return YES; } +- (BOOL)accessibilityPerformIncrement +{ + if (!_peer->IsRangeValueProvider()) + return NO; + auto value = _peer->RangeValueProvider_GetValue(); + value += _peer->RangeValueProvider_GetSmallChange(); + _peer->RangeValueProvider_SetValue(value); + return YES; +} + +- (BOOL)accessibilityPerformDecrement +{ + if (!_peer->IsRangeValueProvider()) + return NO; + auto value = _peer->RangeValueProvider_GetValue(); + value -= _peer->RangeValueProvider_GetSmallChange(); + _peer->RangeValueProvider_SetValue(value); + return YES; +} + - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector { if (selector == @selector(accessibilityPerformPress)) { return _peer->IsInvokeProvider(); } + else if (selector == @selector(accessibilityPerformIncrement) || + selector == @selector(accessibilityPerformDecrement)) + { + return _peer->IsRangeValueProvider(); + } return [super isAccessibilitySelectorAllowed:selector]; } diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 456a2db3ee..5d69fc8e75 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -514,6 +514,10 @@ public: NSAccessibilityPostNotification(Window, NSAccessibilityLayoutChangedNotification); } + virtual void PropertyChanged(AvnAutomationProperty property) override + { + } + protected: virtual NSWindowStyleMask GetStyle() { diff --git a/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs index bafc4c14fc..31a2b7e7af 100644 --- a/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs @@ -19,6 +19,9 @@ namespace Avalonia.Automation.Peers public double Maximum => Owner.Maximum; public double Minimum => Owner.Minimum; public double Value => Owner.Value; + public double SmallChange => Owner.SmallChange; + public double LargeChange => Owner.LargeChange; + public void SetValue(double value) => Owner.Value = value; protected virtual void OwnerPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) diff --git a/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs b/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs index d4cd35fcf9..d494e068f7 100644 --- a/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs +++ b/src/Avalonia.Controls/Automation/Provider/IRangeValueProvider.cs @@ -28,6 +28,18 @@ namespace Avalonia.Automation.Provider /// double Value { get; } + /// + /// Gets the value that is added to or subtracted from the Value property when a large + /// change is made, such as with the PAGE DOWN key. + /// + double LargeChange { get; } + + /// + /// Gets the value that is added to or subtracted from the Value property when a small + /// change is made, such as with an arrow key. + /// + double SmallChange { get; } + /// /// Sets the value of the control. /// diff --git a/src/Avalonia.Native/AutomationNode.cs b/src/Avalonia.Native/AutomationNode.cs index c0e46b52b9..2601b2f239 100644 --- a/src/Avalonia.Native/AutomationNode.cs +++ b/src/Avalonia.Native/AutomationNode.cs @@ -22,7 +22,20 @@ namespace Avalonia.Native public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue) { - // TODO + AvnAutomationProperty p; + + if (property == AutomationElementIdentifiers.BoundingRectangleProperty) + p = AvnAutomationProperty.AutomationPeer_BoundingRectangle; + else if (property == AutomationElementIdentifiers.ClassNameProperty) + p = AvnAutomationProperty.AutomationPeer_ClassName; + else if (property == AutomationElementIdentifiers.NameProperty) + p = AvnAutomationProperty.AutomationPeer_Name; + else if (property == RangeValuePatternIdentifiers.ValueProperty) + p = AvnAutomationProperty.RangeValueProvider_Value; + else + return; + + Native.PropertyChanged(p); } public void FocusChanged(AutomationPeer? focus) diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index a1519d8d26..0c4ad58b9d 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -75,8 +75,21 @@ namespace Avalonia.Native } public int IsInvokeProvider() => (_inner is IInvokeProvider).AsComBool(); - public void InvokeProvider_Invoke() => ((IInvokeProvider)_inner).Invoke(); + + public int IsRangeValueProvider() => (_inner is IRangeValueProvider).AsComBool(); + public double RangeValueProvider_GetValue() => ((IRangeValueProvider)_inner).Value; + public double RangeValueProvider_GetSmallChange() => ((IRangeValueProvider)_inner).SmallChange; + public double RangeValueProvider_GetLargeChange() => ((IRangeValueProvider)_inner).LargeChange; + public void RangeValueProvider_SetValue(double value) => ((IRangeValueProvider)_inner).SetValue(value); + + public int IsToggleProvider() => (_inner is IToggleProvider).AsComBool(); + public int ToggleProvider_GetToggleState() => (int)((IToggleProvider)_inner).ToggleState; + public void ToggleProvider_Toggle() => ((IToggleProvider)_inner).Toggle(); + + public int IsValueProvider() => (_inner is IValueProvider).AsComBool(); + public IAvnString ValueProvider_GetValue() => ((IValueProvider)_inner).Value.ToAvnString(); + public void ValueProvider_SetValue(string value) => ((IValueProvider)_inner).SetValue(value); public static AvnAutomationPeer? Wrap(AutomationPeer? peer) => peer != null ? new AvnAutomationPeer(peer) : null; diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 5697665545..88c9225419 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -218,6 +218,14 @@ enum SystemDecorations { SystemDecorationsFull = 2, } +enum AvnAutomationProperty +{ + AutomationPeer_BoundingRectangle, + AutomationPeer_ClassName, + AutomationPeer_Name, + RangeValueProvider_Value, +} + struct AvnSize { double Width, Height; @@ -808,6 +816,20 @@ interface IAvnAutomationPeer : IUnknown bool IsInvokeProvider(); void InvokeProvider_Invoke(); + + bool IsRangeValueProvider(); + double RangeValueProvider_GetValue(); + double RangeValueProvider_GetSmallChange(); + double RangeValueProvider_GetLargeChange(); + void RangeValueProvider_SetValue(double value); + + bool IsToggleProvider(); + int ToggleProvider_GetToggleState(); + void ToggleProvider_Toggle(); + + bool IsValueProvider(); + IAvnString* ValueProvider_GetValue(); + void ValueProvider_SetValue(char* value); } [uuid(b00af5da-78af-4b33-bfff-4ce13a6239a9)] @@ -821,4 +843,5 @@ interface IAvnAutomationPeerArray : IUnknown interface IAvnAutomationNode : IUnknown { void ChildrenChanged(); + void PropertyChanged(AvnAutomationProperty property); } From 0ab5e4dbfecc424397c27fbcca8683c7d4a949ea Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 27 Mar 2021 14:40:59 +0100 Subject: [PATCH 019/822] Implemented a11y focus on OSX. --- native/Avalonia.Native/src/OSX/automation.mm | 7 ++++++- native/Avalonia.Native/src/OSX/window.mm | 17 +++++++++++++++++ src/Avalonia.Native/AutomationNode.cs | 5 +---- src/Avalonia.Native/AvnAutomationPeer.cs | 1 + src/Avalonia.Native/avn.idl | 2 ++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index b400785a13..ff72cc2a20 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -36,7 +36,12 @@ public: break; } } - + + virtual void FocusChanged(IAvnAutomationPeer* peer) override + { + // Only implemented in top-level nodes, i.e. AvnWindow. + } + virtual NSObject* GetNSAccessibility() override { return _node; diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 5d69fc8e75..62ca8e32df 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -518,6 +518,11 @@ public: { } + virtual void FocusChanged(IAvnAutomationPeer* peer) override + { + NSAccessibilityPostNotification(Window, NSAccessibilityFocusedUIElementChangedNotification); + } + protected: virtual NSWindowStyleMask GetStyle() { @@ -2301,6 +2306,18 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent return GetAccessibilityElement(hit); } +- (id)accessibilityFocusedUIElement +{ + auto peer = [self getAutomationPeer]; + + if (peer->IsRootProvider()) + { + return GetAccessibilityElement(peer->RootProvider_GetFocus()); + } + + return [super accessibilityFocusedUIElement]; +} + - (IAvnAutomationPeer* _Nonnull) getAutomationPeer { if (_automationPeer == nullptr) diff --git a/src/Avalonia.Native/AutomationNode.cs b/src/Avalonia.Native/AutomationNode.cs index 2601b2f239..f85813a6fb 100644 --- a/src/Avalonia.Native/AutomationNode.cs +++ b/src/Avalonia.Native/AutomationNode.cs @@ -38,9 +38,6 @@ namespace Avalonia.Native Native.PropertyChanged(p); } - public void FocusChanged(AutomationPeer? focus) - { - // TODO - } + public void FocusChanged(AutomationPeer? focus) => Native.FocusChanged(AvnAutomationPeer.Wrap(focus)); } } diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index 0c4ad58b9d..fb3759b77e 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -52,6 +52,7 @@ namespace Avalonia.Native } public int IsRootProvider() => (_inner is IRootProvider).AsComBool(); + public IAvnAutomationPeer? RootProvider_GetFocus() => Wrap(((IRootProvider)_inner).GetFocus()); public IAvnAutomationPeer? RootProvider_GetPeerFromPoint(AvnPoint point) { diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 88c9225419..369031b72c 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -812,6 +812,7 @@ interface IAvnAutomationPeer : IUnknown IAvnAutomationPeer* GetRootPeer(); bool IsRootProvider(); + IAvnAutomationPeer* RootProvider_GetFocus(); IAvnAutomationPeer* RootProvider_GetPeerFromPoint(AvnPoint point); bool IsInvokeProvider(); @@ -844,4 +845,5 @@ interface IAvnAutomationNode : IUnknown { void ChildrenChanged(); void PropertyChanged(AvnAutomationProperty property); + void FocusChanged(IAvnAutomationPeer* peer); } From 1553b89bd78d6766a3a141b5fb23a22d4814e98b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 28 Mar 2021 00:09:26 +0100 Subject: [PATCH 020/822] Fix a11y for popup windows. - Don't show when creating a popup window with a11y enabled - Raise focus changed notification on element that got focus instead of the window --- native/Avalonia.Native/src/OSX/window.mm | 9 +++-- src/Avalonia.Native/WindowImplBase.cs | 44 ++++++++++++++++++++---- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 62ca8e32df..ba563c051a 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -520,7 +520,12 @@ public: virtual void FocusChanged(IAvnAutomationPeer* peer) override { - NSAccessibilityPostNotification(Window, NSAccessibilityFocusedUIElementChangedNotification); + auto element = GetAccessibilityElement(peer); + + if (element != nullptr) + { + NSAccessibilityPostNotification(element, NSAccessibilityFocusedUIElementChangedNotification); + } } protected: @@ -2318,7 +2323,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent return [super accessibilityFocusedUIElement]; } -- (IAvnAutomationPeer* _Nonnull) getAutomationPeer +- (IAvnAutomationPeer*) getAutomationPeer { if (_automationPeer == nullptr) _automationPeer = _parent->BaseEvents->AutomationStarted(_parent); diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 1e0d7ca0f4..92576578fa 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -63,6 +63,9 @@ namespace Avalonia.Native private GlPlatformSurface _glSurface; private NativeControlHostImpl _nativeControlHost; private IGlContext _glContext; + private IAutomationNode _automationNode; + private AvnAutomationPeer _automationPeer; + private Func _automationStarted; internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOpenGlInterface glFeature) @@ -209,7 +212,6 @@ namespace Avalonia.Native return _parent.RawTextInputEvent(timeStamp, text).AsComBool(); } - void IAvnWindowBaseEvents.ScalingChanged(double scaling) { _parent._savedScaling = scaling; @@ -250,11 +252,7 @@ namespace Avalonia.Native } } - public IAvnAutomationPeer AutomationStarted(IAvnAutomationNode node) - { - var factory = AutomationNodeFactory.GetInstance(_parent._factory); - return new AvnAutomationPeer(_parent.AutomationStarted(new AutomationNode(factory, node))); - } + public IAvnAutomationPeer AutomationStarted(IAvnAutomationNode node) => _parent.HandleAutomationStarted(node); } public void Activate() @@ -477,6 +475,38 @@ namespace Avalonia.Native public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0); public IPlatformHandle Handle { get; private set; } - public Func AutomationStarted { get; set; } + + public Func AutomationStarted + { + get => _automationStarted; + set + { + _automationStarted = value; + + // We've already received an AutomationStarted event, but the Window/PopupRoot wasn't initialized. + // Now it is, so notify it and store the automation peer for the next time the OS invokes an a11y + // query. + if (value is object && _automationNode is object) + _automationPeer = new AvnAutomationPeer(_automationStarted.Invoke(_automationNode)); + } + } + + private AvnAutomationPeer HandleAutomationStarted(IAvnAutomationNode node) + { + if (_automationPeer is object) + return _automationPeer; + + var factory = AutomationNodeFactory.GetInstance(_factory); + _automationNode = new AutomationNode(factory, node); + + // If automation is started during platform window creation we don't yet have a Window/PopupRoot + // control to notify. In this case we'll notify them when AutomationStarted gets set. We can safely + // return null here because the peer isn't actually needed at this point and will be re-queried the next + // time it's needed. + if (AutomationStarted is null) + return null; + + return _automationPeer = new AvnAutomationPeer(AutomationStarted(_automationNode)); + } } } From 6096cab3b97c4faa3b288832871bff9f4a9fd5cd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 28 Mar 2021 00:47:27 +0100 Subject: [PATCH 021/822] Use correct role for combobox items on OSX. --- native/Avalonia.Native/src/OSX/automation.mm | 1 + .../Automation/Peers/AutomationPeer.cs | 1 + .../Peers/ComboBoxItemAutomationPeer.cs | 24 +++++++++++++++++++ src/Avalonia.Controls/ComboBoxItem.cs | 7 ++++++ src/Avalonia.Native/avn.idl | 1 + .../Automation/AutomationNode.cs | 1 + 6 files changed, 35 insertions(+) create mode 100644 src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index ff72cc2a20..cff142a912 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -75,6 +75,7 @@ public: case AutomationCalendar: return NSAccessibilityGridRole; case AutomationCheckBox: return NSAccessibilityCheckBoxRole; case AutomationComboBox: return NSAccessibilityPopUpButtonRole; + case AutomationComboBoxItem: return NSAccessibilityMenuItemRole; case AutomationEdit: return NSAccessibilityTextFieldRole; case AutomationHyperlink: return NSAccessibilityLinkRole; case AutomationImage: return NSAccessibilityImageRole; diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index 25af340014..85a0e665b2 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -13,6 +13,7 @@ namespace Avalonia.Automation.Peers Calendar, CheckBox, ComboBox, + ComboBoxItem, Edit, Hyperlink, Image, diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs new file mode 100644 index 0000000000..06141ae835 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs @@ -0,0 +1,24 @@ +using System; +using Avalonia.Automation.Platform; +using Avalonia.Automation.Provider; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Selection; + +#nullable enable + +namespace Avalonia.Automation.Peers +{ + public class ComboBoxItemAutomationPeer : ListItemAutomationPeer + { + public ComboBoxItemAutomationPeer(IAutomationNodeFactory factory, ComboBoxItem owner) + : base(factory, owner) + { + } + + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.ComboBoxItem; + } + } +} diff --git a/src/Avalonia.Controls/ComboBoxItem.cs b/src/Avalonia.Controls/ComboBoxItem.cs index a0a1f2a4aa..0d5846f270 100644 --- a/src/Avalonia.Controls/ComboBoxItem.cs +++ b/src/Avalonia.Controls/ComboBoxItem.cs @@ -1,5 +1,7 @@ using System; using System.Reactive.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Platform; namespace Avalonia.Controls { @@ -13,5 +15,10 @@ namespace Avalonia.Controls this.GetObservable(ComboBoxItem.IsFocusedProperty).Where(focused => focused) .Subscribe(_ => (Parent as ComboBox)?.ItemFocused(this)); } + + protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + { + return new ComboBoxItemAutomationPeer(factory, this); + } } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 369031b72c..9b74eef6e2 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -414,6 +414,7 @@ enum AvnAutomationControlType AutomationCalendar, AutomationCheckBox, AutomationComboBox, + AutomationComboBoxItem, AutomationEdit, AutomationHyperlink, AutomationImage, diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 3963d0cb6c..79ef5bc719 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -299,6 +299,7 @@ namespace Avalonia.Win32.Automation AutomationControlType.Calendar => UiaControlTypeId.Calendar, AutomationControlType.CheckBox => UiaControlTypeId.CheckBox, AutomationControlType.ComboBox => UiaControlTypeId.ComboBox, + AutomationControlType.ComboBoxItem => UiaControlTypeId.ListItem, AutomationControlType.Edit => UiaControlTypeId.Edit, AutomationControlType.Hyperlink => UiaControlTypeId.Hyperlink, AutomationControlType.Image => UiaControlTypeId.Image, From 8ded356614b6bef5d1c69a00da978efbacd795b1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 30 Mar 2021 14:59:54 +0200 Subject: [PATCH 022/822] More work on ComboBox OSX a11y. Implement "Show Menu" and "Perform Press" actions. --- native/Avalonia.Native/src/OSX/automation.mm | 48 +++++++++++++++++-- .../Peers/ComboBoxAutomationPeer.cs | 1 + .../Provider/IExpandCollapseProvider.cs | 9 ++++ src/Avalonia.Native/AvnAutomationPeer.cs | 14 ++++++ src/Avalonia.Native/avn.idl | 6 +++ 5 files changed, 73 insertions(+), 5 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index cff142a912..c3ca182b91 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -222,11 +222,33 @@ public: return [self accessibilityTopLevelUIElement]; } -- (BOOL)accessibilityPerformPress +- (BOOL)isAccessibilityExpanded { - if (!_peer->IsInvokeProvider()) + if (!_peer->IsExpandCollapseProvider()) return NO; - _peer->InvokeProvider_Invoke(); + return _peer->ExpandCollapseProvider_IsExpanded(); +} + +- (void)setAccessibilityExpanded:(BOOL)accessibilityExpanded +{ + if (!_peer->IsExpandCollapseProvider()) + return; + if (accessibilityExpanded) + _peer->ExpandCollapseProvider_Expand(); + else + _peer->ExpandCollapseProvider_Collapse(); +} + +- (BOOL)accessibilityPerformPress +{ + if (_peer->IsInvokeProvider()) + { + _peer->InvokeProvider_Invoke(); + } + else if (_peer->IsExpandCollapseProvider()) + { + _peer->ExpandCollapseProvider_Expand(); + } return YES; } @@ -250,11 +272,27 @@ public: return YES; } +- (BOOL)accessibilityPerformShowMenu +{ + if (!_peer->IsExpandCollapseProvider()) + return NO; + _peer->ExpandCollapseProvider_Expand(); + return YES; +} + - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector { - if (selector == @selector(accessibilityPerformPress)) + if (selector == @selector(accessibilityPerformShowMenu)) + { + return _peer->IsExpandCollapseProvider() && _peer->ExpandCollapseProvider_ShowsMenu(); + } + else if (selector == @selector(isAccessibilityExpanded)) + { + return _peer->IsExpandCollapseProvider(); + } + else if (selector == @selector(accessibilityPerformPress)) { - return _peer->IsInvokeProvider(); + return _peer->IsInvokeProvider() || _peer->IsExpandCollapseProvider(); } else if (selector == @selector(accessibilityPerformIncrement) || selector == @selector(accessibilityPerformDecrement)) diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs index d11946b27e..d8225dbc40 100644 --- a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs @@ -20,6 +20,7 @@ namespace Avalonia.Automation.Peers public new ComboBox Owner => (ComboBox)base.Owner; public ExpandCollapseState ExpandCollapseState => ToState(Owner.IsDropDownOpen); + public bool ShowsMenu => true; public void Collapse() => Owner.IsDropDownOpen = false; public void Expand() => Owner.IsDropDownOpen = true; diff --git a/src/Avalonia.Controls/Automation/Provider/IExpandCollapseProvider.cs b/src/Avalonia.Controls/Automation/Provider/IExpandCollapseProvider.cs index f0308c226b..a4691180a3 100644 --- a/src/Avalonia.Controls/Automation/Provider/IExpandCollapseProvider.cs +++ b/src/Avalonia.Controls/Automation/Provider/IExpandCollapseProvider.cs @@ -11,6 +11,15 @@ /// ExpandCollapseState ExpandCollapseState { get; } + /// + /// Gets a value indicating whether expanding the element shows a menu of items to the user, + /// such as drop-down list. + /// + /// + /// Used in OSX to enable the "Show Menu" action on the element. + /// + bool ShowsMenu { get; } + /// /// Displays all child nodes, controls, or content of the control. /// diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index fb3759b77e..81ed94bda9 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Avalonia.Automation; using Avalonia.Automation.Peers; using Avalonia.Automation.Provider; using Avalonia.Native.Interop; @@ -75,6 +76,19 @@ namespace Avalonia.Native return Wrap(result); } + public int IsExpandCollapseProvider() => (_inner is IExpandCollapseProvider).AsComBool(); + + public int ExpandCollapseProvider_IsExpanded() => ((IExpandCollapseProvider)_inner).ExpandCollapseState switch + { + ExpandCollapseState.Expanded => 1, + ExpandCollapseState.PartiallyExpanded => 1, + _ => 0, + }; + + public int ExpandCollapseProvider_ShowsMenu() => ((IExpandCollapseProvider)_inner).ShowsMenu.AsComBool(); + public void ExpandCollapseProvider_Expand() => ((IExpandCollapseProvider)_inner).Expand(); + public void ExpandCollapseProvider_Collapse() => ((IExpandCollapseProvider)_inner).Collapse(); + public int IsInvokeProvider() => (_inner is IInvokeProvider).AsComBool(); public void InvokeProvider_Invoke() => ((IInvokeProvider)_inner).Invoke(); diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 9b74eef6e2..8585ee63f2 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -816,6 +816,12 @@ interface IAvnAutomationPeer : IUnknown IAvnAutomationPeer* RootProvider_GetFocus(); IAvnAutomationPeer* RootProvider_GetPeerFromPoint(AvnPoint point); + bool IsExpandCollapseProvider(); + bool ExpandCollapseProvider_IsExpanded(); + bool ExpandCollapseProvider_ShowsMenu(); + void ExpandCollapseProvider_Expand(); + void ExpandCollapseProvider_Collapse(); + bool IsInvokeProvider(); void InvokeProvider_Invoke(); From 2df1e32aae1d2fbda00a8fef7374e460c5244efd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 30 Mar 2021 18:32:56 +0200 Subject: [PATCH 023/822] Expose max/min for slider controls etc. --- native/Avalonia.Native/src/OSX/automation.mm | 29 ++++++++++++++++++-- src/Avalonia.Native/AvnAutomationPeer.cs | 6 ++-- src/Avalonia.Native/avn.idl | 6 ++-- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index c3ca182b91..b1a2b9e94e 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -157,6 +157,27 @@ public: return [super accessibilityValue]; } +- (id)accessibilityMinValue +{ + if (_peer->IsRangeValueProvider()) + { + return [NSNumber numberWithDouble:_peer->RangeValueProvider_GetMinimum()]; + } + + return [super accessibilityMinValue]; +} + +- (id)accessibilityMaxValue +{ + if (_peer->IsRangeValueProvider()) + { + return [NSNumber numberWithDouble:_peer->RangeValueProvider_GetMaximum()]; + } + + return [super accessibilityMaxValue]; +} + + - (NSArray *)accessibilityChildren { if (_children == nullptr && _peer != nullptr) @@ -226,7 +247,7 @@ public: { if (!_peer->IsExpandCollapseProvider()) return NO; - return _peer->ExpandCollapseProvider_IsExpanded(); + return _peer->ExpandCollapseProvider_GetIsExpanded(); } - (void)setAccessibilityExpanded:(BOOL)accessibilityExpanded @@ -284,7 +305,7 @@ public: { if (selector == @selector(accessibilityPerformShowMenu)) { - return _peer->IsExpandCollapseProvider() && _peer->ExpandCollapseProvider_ShowsMenu(); + return _peer->IsExpandCollapseProvider() && _peer->ExpandCollapseProvider_GetShowsMenu(); } else if (selector == @selector(isAccessibilityExpanded)) { @@ -295,7 +316,9 @@ public: return _peer->IsInvokeProvider() || _peer->IsExpandCollapseProvider(); } else if (selector == @selector(accessibilityPerformIncrement) || - selector == @selector(accessibilityPerformDecrement)) + selector == @selector(accessibilityPerformDecrement) || + selector == @selector(accessibilityMinValue) || + selector == @selector(accessibilityMaxValue)) { return _peer->IsRangeValueProvider(); } diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index 81ed94bda9..dee7658d33 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -78,14 +78,14 @@ namespace Avalonia.Native public int IsExpandCollapseProvider() => (_inner is IExpandCollapseProvider).AsComBool(); - public int ExpandCollapseProvider_IsExpanded() => ((IExpandCollapseProvider)_inner).ExpandCollapseState switch + public int ExpandCollapseProvider_GetIsExpanded() => ((IExpandCollapseProvider)_inner).ExpandCollapseState switch { ExpandCollapseState.Expanded => 1, ExpandCollapseState.PartiallyExpanded => 1, _ => 0, }; - public int ExpandCollapseProvider_ShowsMenu() => ((IExpandCollapseProvider)_inner).ShowsMenu.AsComBool(); + public int ExpandCollapseProvider_GetShowsMenu() => ((IExpandCollapseProvider)_inner).ShowsMenu.AsComBool(); public void ExpandCollapseProvider_Expand() => ((IExpandCollapseProvider)_inner).Expand(); public void ExpandCollapseProvider_Collapse() => ((IExpandCollapseProvider)_inner).Collapse(); @@ -94,6 +94,8 @@ namespace Avalonia.Native public int IsRangeValueProvider() => (_inner is IRangeValueProvider).AsComBool(); public double RangeValueProvider_GetValue() => ((IRangeValueProvider)_inner).Value; + public double RangeValueProvider_GetMinimum() => ((IRangeValueProvider)_inner).Minimum; + public double RangeValueProvider_GetMaximum() => ((IRangeValueProvider)_inner).Maximum; public double RangeValueProvider_GetSmallChange() => ((IRangeValueProvider)_inner).SmallChange; public double RangeValueProvider_GetLargeChange() => ((IRangeValueProvider)_inner).LargeChange; public void RangeValueProvider_SetValue(double value) => ((IRangeValueProvider)_inner).SetValue(value); diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 8585ee63f2..73494d3e43 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -817,8 +817,8 @@ interface IAvnAutomationPeer : IUnknown IAvnAutomationPeer* RootProvider_GetPeerFromPoint(AvnPoint point); bool IsExpandCollapseProvider(); - bool ExpandCollapseProvider_IsExpanded(); - bool ExpandCollapseProvider_ShowsMenu(); + bool ExpandCollapseProvider_GetIsExpanded(); + bool ExpandCollapseProvider_GetShowsMenu(); void ExpandCollapseProvider_Expand(); void ExpandCollapseProvider_Collapse(); @@ -827,6 +827,8 @@ interface IAvnAutomationPeer : IUnknown bool IsRangeValueProvider(); double RangeValueProvider_GetValue(); + double RangeValueProvider_GetMinimum(); + double RangeValueProvider_GetMaximum(); double RangeValueProvider_GetSmallChange(); double RangeValueProvider_GetLargeChange(); void RangeValueProvider_SetValue(double value); From cb9f1bc9bd2b709e080f72fcd3517ae51799270b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 15:26:40 +0200 Subject: [PATCH 024/822] Add Transform property for Brush class --- src/Avalonia.Visuals/Media/Brush.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index cf7f5f531c..fe0dc63297 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -18,13 +18,19 @@ namespace Avalonia.Media public static readonly StyledProperty OpacityProperty = AvaloniaProperty.Register(nameof(Opacity), 1.0); + /// + /// Defines the property. + /// + public static readonly StyledProperty TransformProperty = + AvaloniaProperty.Register(nameof(Transform)); + /// public event EventHandler Invalidated; static Brush() { Animation.Animation.RegisterAnimator(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType)); - AffectsRender(OpacityProperty); + AffectsRender(OpacityProperty, TransformProperty); } /// @@ -36,6 +42,15 @@ namespace Avalonia.Media set { SetValue(OpacityProperty, value); } } + /// + /// Gets or sets the transform of the brush. + /// + public Transform Transform + { + get { return GetValue(TransformProperty); } + set { SetValue(TransformProperty, value); } + } + /// /// Parses a brush string. /// From 4cea7a703fec2ed746e0007cadf0aa4ebdb73fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 18:11:48 +0200 Subject: [PATCH 025/822] Add Transform property to IBrush interface --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 4 +- src/Avalonia.Visuals/Media/IBrush.cs | 5 ++ .../Media/Immutable/ImmutableGradientBrush.cs | 5 ++ .../Immutable/ImmutableSolidColorBrush.cs | 9 ++- .../Media/Immutable/ImmutableTileBrush.cs | 5 ++ src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 67 ++++++++++++++++--- 6 files changed, 81 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 39a4c3004c..7d2151a485 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -5,6 +5,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract. MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.Transform Avalonia.Media.IBrush.Transform' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.Transform Avalonia.Media.IBrush.Transform.get()' is present in the implementation but not in the contract. TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract. @@ -74,4 +76,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. -Total Issues: 75 +Total Issues: 77 diff --git a/src/Avalonia.Visuals/Media/IBrush.cs b/src/Avalonia.Visuals/Media/IBrush.cs index 15b7681be4..f481bbff54 100644 --- a/src/Avalonia.Visuals/Media/IBrush.cs +++ b/src/Avalonia.Visuals/Media/IBrush.cs @@ -12,5 +12,10 @@ namespace Avalonia.Media /// Gets the opacity of the brush. /// double Opacity { get; } + + /// + /// Gets the transform of the brush. + /// + Transform Transform { get; } } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs index 1f6e3bbcfd..6ae1bfe88a 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs @@ -39,6 +39,11 @@ namespace Avalonia.Media.Immutable /// public double Opacity { get; } + /// + /// Gets the transform of the brush. + /// + public Transform Transform { get; } + /// public GradientSpreadMethod SpreadMethod { get; } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs index 8e93ac580e..1e48d91f05 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs @@ -46,11 +46,16 @@ namespace Avalonia.Media.Immutable /// public double Opacity { get; } + /// + /// Gets the transform of the brush. + /// + public Transform Transform { get; } + public bool Equals(ImmutableSolidColorBrush other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Color.Equals(other.Color) && Opacity.Equals(other.Opacity); + return Color.Equals(other.Color) && Opacity.Equals(other.Opacity) && (Transform == null && other.Transform == null ? true : Transform.Equals(other.Transform)); } public override bool Equals(object obj) @@ -62,7 +67,7 @@ namespace Avalonia.Media.Immutable { unchecked { - return (Color.GetHashCode() * 397) ^ Opacity.GetHashCode(); + return (Color.GetHashCode() * 397) ^ Opacity.GetHashCode() ^ (Transform is null ? 0 : Transform.GetHashCode()); } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs index fd4d921516..0557f5c1d6 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs @@ -69,6 +69,11 @@ namespace Avalonia.Media.Immutable /// public double Opacity { get; } + /// + /// Gets the transform of the brush. + /// + public Transform Transform { get; } + /// public RelativeRect SourceRect { get; } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2352b8b076..5bade6c5e5 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -608,10 +608,21 @@ namespace Avalonia.Skia var end = position + linearGradient.EndPoint.ToPixels(targetRect.Size).ToSKPoint(); // would be nice to cache these shaders possibly? - using (var shader = - SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode)) + if (linearGradient.Transform is null) { - paintWrapper.Paint.Shader = shader; + using (var shader = + SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode)) + { + paintWrapper.Paint.Shader = shader; + } + } + else + { + using (var shader = + SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode, linearGradient.Transform.Value.ToSKMatrix())) + { + paintWrapper.Paint.Shader = shader; + } } break; @@ -626,10 +637,21 @@ namespace Avalonia.Skia if (origin.Equals(center)) { // when the origin is the same as the center the Skia RadialGradient acts the same as D2D - using (var shader = - SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode)) + if (radialGradient.Transform is null) { - paintWrapper.Paint.Shader = shader; + using (var shader = + SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode)) + { + paintWrapper.Paint.Shader = shader; + } + } + else + { + using (var shader = + SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode, radialGradient.Transform.Value.ToSKMatrix())) + { + paintWrapper.Paint.Shader = shader; + } } } else @@ -653,12 +675,25 @@ namespace Avalonia.Skia } // compose with a background colour of the final stop to match D2D's behaviour of filling with the final color - using (var shader = SKShader.CreateCompose( - SKShader.CreateColor(reversedColors[0]), - SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode) - )) + if (radialGradient.Transform is null) { - paintWrapper.Paint.Shader = shader; + using (var shader = SKShader.CreateCompose( + SKShader.CreateColor(reversedColors[0]), + SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode) + )) + { + paintWrapper.Paint.Shader = shader; + } + } + else + { + using (var shader = SKShader.CreateCompose( + SKShader.CreateColor(reversedColors[0]), + SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode, radialGradient.Transform.Value.ToSKMatrix()) + )) + { + paintWrapper.Paint.Shader = shader; + } } } @@ -673,6 +708,11 @@ namespace Avalonia.Skia var angle = (float)(conicGradient.Angle - 90); var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y); + if (conicGradient.Transform is not null) + { + rotation = rotation.PreConcat(conicGradient.Transform.Value.ToSKMatrix()); + } + using (var shader = SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation)) { @@ -740,6 +780,11 @@ namespace Avalonia.Skia var paintTransform = default(SKMatrix); + if (tileBrush.Transform is not null) + { + paintTransform = paintTransform.PreConcat(tileBrush.Transform.Value.ToSKMatrix()); + } + SKMatrix.Concat( ref paintTransform, tileTransform, From 4a443f691675bd82a50a768c8d21102c2a2f6884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 19:10:26 +0200 Subject: [PATCH 026/822] Move transform --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 5bade6c5e5..c282b4c24d 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -780,16 +780,16 @@ namespace Avalonia.Skia var paintTransform = default(SKMatrix); - if (tileBrush.Transform is not null) - { - paintTransform = paintTransform.PreConcat(tileBrush.Transform.Value.ToSKMatrix()); - } - SKMatrix.Concat( ref paintTransform, tileTransform, SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y))); + if (tileBrush.Transform is not null) + { + paintTransform = paintTransform.PreConcat(tileBrush.Transform.Value.ToSKMatrix()); + } + using (var shader = image.ToShader(tileX, tileY, paintTransform)) { paintWrapper.Paint.Shader = shader; From d47c47935e354fc24a076b0c3383d233b3b84792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 19:26:27 +0200 Subject: [PATCH 027/822] Do not use not pattern --- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index c282b4c24d..4e47953863 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -708,7 +708,7 @@ namespace Avalonia.Skia var angle = (float)(conicGradient.Angle - 90); var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y); - if (conicGradient.Transform is not null) + if (conicGradient.Transform is { }) { rotation = rotation.PreConcat(conicGradient.Transform.Value.ToSKMatrix()); } @@ -785,7 +785,7 @@ namespace Avalonia.Skia tileTransform, SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y))); - if (tileBrush.Transform is not null) + if (tileBrush.Transform is { }) { paintTransform = paintTransform.PreConcat(tileBrush.Transform.Value.ToSKMatrix()); } From 13f137c7abbbbb0fec8eec756c170b734e8e97d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 20:36:48 +0200 Subject: [PATCH 028/822] Fix deferred rendering by using immutable transform --- .../Animators/GradientBrushAnimator.cs | 6 +++++ src/Avalonia.Visuals/ApiCompatBaseline.txt | 12 ++++++--- src/Avalonia.Visuals/Media/Brush.cs | 6 ++--- src/Avalonia.Visuals/Media/IBrush.cs | 2 +- .../Media/IMutableTransform.cs | 6 +++++ .../Media/Immutable/IImmutableTransform.cs | 21 ++++++++++++++++ .../Immutable/ImmutableConicGradientBrush.cs | 4 ++- .../Media/Immutable/ImmutableGradientBrush.cs | 7 ++++-- .../Media/Immutable/ImmutableImageBrush.cs | 3 +++ .../Immutable/ImmutableLinearGradientBrush.cs | 4 ++- .../Immutable/ImmutableRadialGradientBrush.cs | 4 ++- .../Immutable/ImmutableSolidColorBrush.cs | 2 +- .../Media/Immutable/ImmutableTileBrush.cs | 6 ++++- .../Media/Immutable/ImmutableVisualBrush.cs | 3 +++ src/Avalonia.Visuals/Media/Transform.cs | 7 ++++++ .../Media/TransformExtensions.cs | 25 +++++++++++++++++++ 16 files changed, 104 insertions(+), 14 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/Immutable/IImmutableTransform.cs create mode 100644 src/Avalonia.Visuals/Media/TransformExtensions.cs diff --git a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs index 864e12413f..bb8457de9a 100644 --- a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs @@ -30,6 +30,7 @@ namespace Avalonia.Animation.Animators return new ImmutableRadialGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), + oldValue.Transform, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center), s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin), @@ -39,6 +40,7 @@ namespace Avalonia.Animation.Animators return new ImmutableConicGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), + oldValue.Transform, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center), s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle)); @@ -47,6 +49,7 @@ namespace Avalonia.Animation.Animators return new ImmutableLinearGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), + oldValue.Transform, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint), s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint)); @@ -93,16 +96,19 @@ namespace Avalonia.Animation.Animators case IRadialGradientBrush oldRadial: return new ImmutableRadialGradientBrush( CreateStopsFromSolidColorBrush(solidColorBrush, oldRadial.GradientStops), solidColorBrush.Opacity, + oldRadial.Transform, oldRadial.SpreadMethod, oldRadial.Center, oldRadial.GradientOrigin, oldRadial.Radius); case IConicGradientBrush oldConic: return new ImmutableConicGradientBrush( CreateStopsFromSolidColorBrush(solidColorBrush, oldConic.GradientStops), solidColorBrush.Opacity, + oldConic.Transform, oldConic.SpreadMethod, oldConic.Center, oldConic.Angle); case ILinearGradientBrush oldLinear: return new ImmutableLinearGradientBrush( CreateStopsFromSolidColorBrush(solidColorBrush, oldLinear.GradientStops), solidColorBrush.Opacity, + oldLinear.Transform, oldLinear.SpreadMethod, oldLinear.StartPoint, oldLinear.EndPoint); default: diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 7d2151a485..41985dc078 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -5,9 +5,15 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract. MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.Transform Avalonia.Media.IBrush.Transform' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.Transform Avalonia.Media.IBrush.Transform.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.ITransform Avalonia.Media.IBrush.Transform' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.ITransform Avalonia.Media.IBrush.Transform.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.ITransform Avalonia.Media.IMutableTransform.ToImmutable()' is present in the implementation but not in the contract. +MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableConicGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable, System.Double)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Media.Immutable.ImmutableGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableLinearGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable, System.Nullable)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableRadialGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable, System.Nullable, System.Double)' does not exist in the implementation but it does exist in the contract. TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract. +MembersMustExist : Member 'protected void Avalonia.Media.Immutable.ImmutableTileBrush..ctor(Avalonia.Media.AlignmentX, Avalonia.Media.AlignmentY, Avalonia.RelativeRect, System.Double, Avalonia.RelativeRect, Avalonia.Media.Stretch, Avalonia.Media.TileMode, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract. CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. @@ -76,4 +82,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. -Total Issues: 77 +Total Issues: 83 diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index fe0dc63297..101b28a376 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -21,8 +21,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty TransformProperty = - AvaloniaProperty.Register(nameof(Transform)); + public static readonly StyledProperty TransformProperty = + AvaloniaProperty.Register(nameof(Transform)); /// public event EventHandler Invalidated; @@ -45,7 +45,7 @@ namespace Avalonia.Media /// /// Gets or sets the transform of the brush. /// - public Transform Transform + public ITransform Transform { get { return GetValue(TransformProperty); } set { SetValue(TransformProperty, value); } diff --git a/src/Avalonia.Visuals/Media/IBrush.cs b/src/Avalonia.Visuals/Media/IBrush.cs index f481bbff54..c3d03fb35b 100644 --- a/src/Avalonia.Visuals/Media/IBrush.cs +++ b/src/Avalonia.Visuals/Media/IBrush.cs @@ -16,6 +16,6 @@ namespace Avalonia.Media /// /// Gets the transform of the brush. /// - Transform Transform { get; } + ITransform Transform { get; } } } diff --git a/src/Avalonia.Visuals/Media/IMutableTransform.cs b/src/Avalonia.Visuals/Media/IMutableTransform.cs index 2033c434c0..d7a106b22b 100644 --- a/src/Avalonia.Visuals/Media/IMutableTransform.cs +++ b/src/Avalonia.Visuals/Media/IMutableTransform.cs @@ -8,5 +8,11 @@ namespace Avalonia.Media /// Raised when the transform changes. /// event EventHandler Changed; + + /// + /// Converts a transform to an immutable transform. + /// + /// The immutable transform + ITransform ToImmutable(); } } diff --git a/src/Avalonia.Visuals/Media/Immutable/IImmutableTransform.cs b/src/Avalonia.Visuals/Media/Immutable/IImmutableTransform.cs new file mode 100644 index 0000000000..d5ff2b8317 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Immutable/IImmutableTransform.cs @@ -0,0 +1,21 @@ +using Avalonia.VisualTree; + +namespace Avalonia.Media.Immutable +{ + /// + /// Represents a transform on an . + /// + public class ImmutableTransform : ITransform + { + public Matrix Value { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The transform matrix. + public ImmutableTransform(Matrix matrix) + { + Value = matrix; + } + } +} diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs index d3c80dfcad..dc9360d162 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs @@ -12,16 +12,18 @@ namespace Avalonia.Media.Immutable /// /// The gradient stops. /// The opacity of the brush. + /// The transform of the brush. /// The spread method. /// The center point for the gradient. /// The starting angle for the gradient. public ImmutableConicGradientBrush( IReadOnlyList gradientStops, double opacity = 1, + ITransform transform = null, GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, RelativePoint? center = null, double angle = 0) - : base(gradientStops, opacity, spreadMethod) + : base(gradientStops, opacity, transform, spreadMethod) { Center = center ?? RelativePoint.Center; Angle = angle; diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs index 6ae1bfe88a..12e1a39635 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs @@ -12,14 +12,17 @@ namespace Avalonia.Media.Immutable /// /// The gradient stops. /// The opacity of the brush. + /// The transform of the brush. /// The spread method. protected ImmutableGradientBrush( IReadOnlyList gradientStops, double opacity, + ITransform transform, GradientSpreadMethod spreadMethod) { GradientStops = gradientStops; Opacity = opacity; + Transform = transform; SpreadMethod = spreadMethod; } @@ -28,7 +31,7 @@ namespace Avalonia.Media.Immutable /// /// The brush from which this brush's properties should be copied. protected ImmutableGradientBrush(GradientBrush source) - : this(source.GradientStops.ToImmutable(), source.Opacity, source.SpreadMethod) + : this(source.GradientStops.ToImmutable(), source.Opacity, source.Transform.ToImmutable(), source.SpreadMethod) { } @@ -42,7 +45,7 @@ namespace Avalonia.Media.Immutable /// /// Gets the transform of the brush. /// - public Transform Transform { get; } + public ITransform Transform { get; } /// public GradientSpreadMethod SpreadMethod { get; } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs index 15da8f8b43..43b157faa9 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs @@ -16,6 +16,7 @@ namespace Avalonia.Media.Immutable /// The vertical alignment of a tile in the destination. /// The rectangle on the destination in which to paint a tile. /// The opacity of the brush. + /// The transform of the brush. /// The rectangle of the source image that will be displayed. /// /// How the source rectangle will be stretched to fill the destination rect. @@ -28,6 +29,7 @@ namespace Avalonia.Media.Immutable AlignmentY alignmentY = AlignmentY.Center, RelativeRect? destinationRect = null, double opacity = 1, + ITransform transform = null, RelativeRect? sourceRect = null, Stretch stretch = Stretch.Uniform, TileMode tileMode = TileMode.None, @@ -37,6 +39,7 @@ namespace Avalonia.Media.Immutable alignmentY, destinationRect ?? RelativeRect.Fill, opacity, + transform, sourceRect ?? RelativeRect.Fill, stretch, tileMode, diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs index 912d77d763..2270a5e855 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs @@ -12,16 +12,18 @@ namespace Avalonia.Media.Immutable /// /// The gradient stops. /// The opacity of the brush. + /// The transform of the brush. /// The spread method. /// The start point for the gradient. /// The end point for the gradient. public ImmutableLinearGradientBrush( IReadOnlyList gradientStops, double opacity = 1, + ITransform transform = null, GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, RelativePoint? startPoint = null, RelativePoint? endPoint = null) - : base(gradientStops, opacity, spreadMethod) + : base(gradientStops, opacity, transform, spreadMethod) { StartPoint = startPoint ?? RelativePoint.TopLeft; EndPoint = endPoint ?? RelativePoint.BottomRight; diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs index e26fbab5f5..f621d83213 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs @@ -12,6 +12,7 @@ namespace Avalonia.Media.Immutable /// /// The gradient stops. /// The opacity of the brush. + /// The transform of the brush. /// The spread method. /// The start point for the gradient. /// @@ -23,11 +24,12 @@ namespace Avalonia.Media.Immutable public ImmutableRadialGradientBrush( IReadOnlyList gradientStops, double opacity = 1, + ITransform transform = null, GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, RelativePoint? center = null, RelativePoint? gradientOrigin = null, double radius = 0.5) - : base(gradientStops, opacity, spreadMethod) + : base(gradientStops, opacity, transform, spreadMethod) { Center = center ?? RelativePoint.Center; GradientOrigin = gradientOrigin ?? RelativePoint.Center; diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs index 1e48d91f05..daf73f8974 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs @@ -49,7 +49,7 @@ namespace Avalonia.Media.Immutable /// /// Gets the transform of the brush. /// - public Transform Transform { get; } + public ITransform Transform { get; } public bool Equals(ImmutableSolidColorBrush other) { diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs index 0557f5c1d6..e96ef026af 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs @@ -14,6 +14,7 @@ namespace Avalonia.Media.Immutable /// The vertical alignment of a tile in the destination. /// The rectangle on the destination in which to paint a tile. /// The opacity of the brush. + /// The transform of the brush. /// The rectangle of the source image that will be displayed. /// /// How the source rectangle will be stretched to fill the destination rect. @@ -25,6 +26,7 @@ namespace Avalonia.Media.Immutable AlignmentY alignmentY, RelativeRect destinationRect, double opacity, + ITransform transform, RelativeRect sourceRect, Stretch stretch, TileMode tileMode, @@ -34,6 +36,7 @@ namespace Avalonia.Media.Immutable AlignmentY = alignmentY; DestinationRect = destinationRect; Opacity = opacity; + Transform = transform; SourceRect = sourceRect; Stretch = stretch; TileMode = tileMode; @@ -50,6 +53,7 @@ namespace Avalonia.Media.Immutable source.AlignmentY, source.DestinationRect, source.Opacity, + source.Transform.ToImmutable(), source.SourceRect, source.Stretch, source.TileMode, @@ -72,7 +76,7 @@ namespace Avalonia.Media.Immutable /// /// Gets the transform of the brush. /// - public Transform Transform { get; } + public ITransform Transform { get; } /// public RelativeRect SourceRect { get; } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs index 4c6aae08ab..ccc0b64f98 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs @@ -16,6 +16,7 @@ namespace Avalonia.Media.Immutable /// The vertical alignment of a tile in the destination. /// The rectangle on the destination in which to paint a tile. /// The opacity of the brush. + /// The transform of the brush. /// The rectangle of the source image that will be displayed. /// /// How the source rectangle will be stretched to fill the destination rect. @@ -28,6 +29,7 @@ namespace Avalonia.Media.Immutable AlignmentY alignmentY = AlignmentY.Center, RelativeRect? destinationRect = null, double opacity = 1, + Transform transform = null, RelativeRect? sourceRect = null, Stretch stretch = Stretch.Uniform, TileMode tileMode = TileMode.None, @@ -37,6 +39,7 @@ namespace Avalonia.Media.Immutable alignmentY, destinationRect ?? RelativeRect.Fill, opacity, + transform, sourceRect ?? RelativeRect.Fill, stretch, tileMode, diff --git a/src/Avalonia.Visuals/Media/Transform.cs b/src/Avalonia.Visuals/Media/Transform.cs index 7cf1b35ada..2df0c7cc27 100644 --- a/src/Avalonia.Visuals/Media/Transform.cs +++ b/src/Avalonia.Visuals/Media/Transform.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Animation; using Avalonia.Animation.Animators; +using Avalonia.Media.Immutable; using Avalonia.VisualTree; namespace Avalonia.Media @@ -44,6 +45,12 @@ namespace Avalonia.Media Changed?.Invoke(this, EventArgs.Empty); } + /// + public ITransform ToImmutable() + { + return new ImmutableTransform(this.Value); + } + /// /// Returns a String representing this transform matrix instance. /// diff --git a/src/Avalonia.Visuals/Media/TransformExtensions.cs b/src/Avalonia.Visuals/Media/TransformExtensions.cs new file mode 100644 index 0000000000..2f473fcadb --- /dev/null +++ b/src/Avalonia.Visuals/Media/TransformExtensions.cs @@ -0,0 +1,25 @@ +using System; + +namespace Avalonia.Media +{ + /// + /// Extension methods for transform classes. + /// + public static class TransformExtensions + { + /// + /// Converts a transform to an immutable transform. + /// + /// The transform. + /// + /// The result of calling if the transform is mutable, + /// otherwise . + /// + public static ITransform ToImmutable(this ITransform transform) + { + Contract.Requires(transform != null); + + return (transform as IMutableTransform)?.ToImmutable() ?? transform; + } + } +} From 90a027aef82e1fcddfbc4a8dcb6f40009328acf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 20:39:22 +0200 Subject: [PATCH 029/822] Rename --- .../Immutable/{IImmutableTransform.cs => ImmutableTransform.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Avalonia.Visuals/Media/Immutable/{IImmutableTransform.cs => ImmutableTransform.cs} (100%) diff --git a/src/Avalonia.Visuals/Media/Immutable/IImmutableTransform.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableTransform.cs similarity index 100% rename from src/Avalonia.Visuals/Media/Immutable/IImmutableTransform.cs rename to src/Avalonia.Visuals/Media/Immutable/ImmutableTransform.cs From e3bfa12909d1f9d8840a0114512bd4d587a8942f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 21:40:19 +0200 Subject: [PATCH 030/822] Use ImmutableTransform --- .../Animation/Animators/GradientBrushAnimator.cs | 13 +++++++------ .../Animation/Animators/TransformAnimator.cs | 7 +++++++ src/Avalonia.Visuals/Media/IMutableTransform.cs | 6 ------ .../Media/Immutable/ImmutableConicGradientBrush.cs | 2 +- .../Media/Immutable/ImmutableGradientBrush.cs | 2 +- .../Media/Immutable/ImmutableImageBrush.cs | 2 +- .../Media/Immutable/ImmutableLinearGradientBrush.cs | 2 +- .../Media/Immutable/ImmutableRadialGradientBrush.cs | 2 +- .../Media/Immutable/ImmutableVisualBrush.cs | 2 +- src/Avalonia.Visuals/Media/Transform.cs | 7 +++++-- src/Avalonia.Visuals/Media/TransformExtensions.cs | 7 ++++--- 11 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs index bb8457de9a..5d206ad9e1 100644 --- a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs @@ -16,6 +16,7 @@ namespace Avalonia.Animation.Animators { private static readonly RelativePointAnimator s_relativePointAnimator = new RelativePointAnimator(); private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator(); + private static readonly TransformAnimator s_transformAnimator = new TransformAnimator(); public override IGradientBrush? Interpolate(double progress, IGradientBrush? oldValue, IGradientBrush? newValue) { @@ -30,7 +31,7 @@ namespace Avalonia.Animation.Animators return new ImmutableRadialGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - oldValue.Transform, + s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform), oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center), s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin), @@ -40,7 +41,7 @@ namespace Avalonia.Animation.Animators return new ImmutableConicGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - oldValue.Transform, + s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform), oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center), s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle)); @@ -49,7 +50,7 @@ namespace Avalonia.Animation.Animators return new ImmutableLinearGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - oldValue.Transform, + s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform), oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint), s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint)); @@ -96,19 +97,19 @@ namespace Avalonia.Animation.Animators case IRadialGradientBrush oldRadial: return new ImmutableRadialGradientBrush( CreateStopsFromSolidColorBrush(solidColorBrush, oldRadial.GradientStops), solidColorBrush.Opacity, - oldRadial.Transform, + new ImmutableTransform(oldRadial.Transform.Value), oldRadial.SpreadMethod, oldRadial.Center, oldRadial.GradientOrigin, oldRadial.Radius); case IConicGradientBrush oldConic: return new ImmutableConicGradientBrush( CreateStopsFromSolidColorBrush(solidColorBrush, oldConic.GradientStops), solidColorBrush.Opacity, - oldConic.Transform, + new ImmutableTransform(oldConic.Transform.Value), oldConic.SpreadMethod, oldConic.Center, oldConic.Angle); case ILinearGradientBrush oldLinear: return new ImmutableLinearGradientBrush( CreateStopsFromSolidColorBrush(solidColorBrush, oldLinear.GradientStops), solidColorBrush.Opacity, - oldLinear.Transform, + new ImmutableTransform(oldLinear.Transform.Value), oldLinear.SpreadMethod, oldLinear.StartPoint, oldLinear.EndPoint); default: diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index 1b2142f6c9..0299cba0e5 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -2,6 +2,7 @@ using System.Reactive.Disposables; using Avalonia.Logging; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Media.Transformation; namespace Avalonia.Animation.Animators @@ -86,6 +87,12 @@ namespace Avalonia.Animation.Animators return null; } + internal ImmutableTransform InterpolateTransform(double progress, ITransform oldValue, ITransform newValue) + { + // TODO: + return new ImmutableTransform(newValue.Value); + } + /// public override double Interpolate(double p, double o, double n) => 0; } diff --git a/src/Avalonia.Visuals/Media/IMutableTransform.cs b/src/Avalonia.Visuals/Media/IMutableTransform.cs index d7a106b22b..2033c434c0 100644 --- a/src/Avalonia.Visuals/Media/IMutableTransform.cs +++ b/src/Avalonia.Visuals/Media/IMutableTransform.cs @@ -8,11 +8,5 @@ namespace Avalonia.Media /// Raised when the transform changes. /// event EventHandler Changed; - - /// - /// Converts a transform to an immutable transform. - /// - /// The immutable transform - ITransform ToImmutable(); } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs index dc9360d162..869262c7a4 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs @@ -19,7 +19,7 @@ namespace Avalonia.Media.Immutable public ImmutableConicGradientBrush( IReadOnlyList gradientStops, double opacity = 1, - ITransform transform = null, + ImmutableTransform transform = null, GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, RelativePoint? center = null, double angle = 0) diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs index 12e1a39635..87ada91cbd 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs @@ -17,7 +17,7 @@ namespace Avalonia.Media.Immutable protected ImmutableGradientBrush( IReadOnlyList gradientStops, double opacity, - ITransform transform, + ImmutableTransform transform, GradientSpreadMethod spreadMethod) { GradientStops = gradientStops; diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs index 43b157faa9..41456b7f92 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs @@ -29,7 +29,7 @@ namespace Avalonia.Media.Immutable AlignmentY alignmentY = AlignmentY.Center, RelativeRect? destinationRect = null, double opacity = 1, - ITransform transform = null, + ImmutableTransform transform = null, RelativeRect? sourceRect = null, Stretch stretch = Stretch.Uniform, TileMode tileMode = TileMode.None, diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs index 2270a5e855..1c905c166e 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs @@ -19,7 +19,7 @@ namespace Avalonia.Media.Immutable public ImmutableLinearGradientBrush( IReadOnlyList gradientStops, double opacity = 1, - ITransform transform = null, + ImmutableTransform transform = null, GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, RelativePoint? startPoint = null, RelativePoint? endPoint = null) diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs index f621d83213..910e14faf7 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs @@ -24,7 +24,7 @@ namespace Avalonia.Media.Immutable public ImmutableRadialGradientBrush( IReadOnlyList gradientStops, double opacity = 1, - ITransform transform = null, + ImmutableTransform transform = null, GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad, RelativePoint? center = null, RelativePoint? gradientOrigin = null, diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs index ccc0b64f98..abec170419 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs @@ -29,7 +29,7 @@ namespace Avalonia.Media.Immutable AlignmentY alignmentY = AlignmentY.Center, RelativeRect? destinationRect = null, double opacity = 1, - Transform transform = null, + ImmutableTransform transform = null, RelativeRect? sourceRect = null, Stretch stretch = Stretch.Uniform, TileMode tileMode = TileMode.None, diff --git a/src/Avalonia.Visuals/Media/Transform.cs b/src/Avalonia.Visuals/Media/Transform.cs index 2df0c7cc27..5a757b44c6 100644 --- a/src/Avalonia.Visuals/Media/Transform.cs +++ b/src/Avalonia.Visuals/Media/Transform.cs @@ -45,8 +45,11 @@ namespace Avalonia.Media Changed?.Invoke(this, EventArgs.Empty); } - /// - public ITransform ToImmutable() + /// + /// Converts a transform to an immutable transform. + /// + /// The immutable transform + public ImmutableTransform ToImmutable() { return new ImmutableTransform(this.Value); } diff --git a/src/Avalonia.Visuals/Media/TransformExtensions.cs b/src/Avalonia.Visuals/Media/TransformExtensions.cs index 2f473fcadb..c264d483cd 100644 --- a/src/Avalonia.Visuals/Media/TransformExtensions.cs +++ b/src/Avalonia.Visuals/Media/TransformExtensions.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Media.Immutable; namespace Avalonia.Media { @@ -12,14 +13,14 @@ namespace Avalonia.Media /// /// The transform. /// - /// The result of calling if the transform is mutable, + /// The result of calling if the transform is mutable, /// otherwise . /// - public static ITransform ToImmutable(this ITransform transform) + public static ImmutableTransform ToImmutable(this ITransform transform) { Contract.Requires(transform != null); - return (transform as IMutableTransform)?.ToImmutable() ?? transform; + return (transform as Transform)?.ToImmutable() ?? new ImmutableTransform(transform.Value); } } } From 4f90ab46f0a7869d0f6294a93c61d7dad748f423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 21:44:54 +0200 Subject: [PATCH 031/822] Check for null --- .../Animation/Animators/GradientBrushAnimator.cs | 12 ++++++------ .../Media/Immutable/ImmutableGradientBrush.cs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs index 5d206ad9e1..817c53698e 100644 --- a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs @@ -31,7 +31,7 @@ namespace Avalonia.Animation.Animators return new ImmutableRadialGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform), + oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform) : null, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center), s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin), @@ -41,7 +41,7 @@ namespace Avalonia.Animation.Animators return new ImmutableConicGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform), + oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform) : null, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center), s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle)); @@ -50,7 +50,7 @@ namespace Avalonia.Animation.Animators return new ImmutableLinearGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform), + oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform) : null, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint), s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint)); @@ -97,19 +97,19 @@ namespace Avalonia.Animation.Animators case IRadialGradientBrush oldRadial: return new ImmutableRadialGradientBrush( CreateStopsFromSolidColorBrush(solidColorBrush, oldRadial.GradientStops), solidColorBrush.Opacity, - new ImmutableTransform(oldRadial.Transform.Value), + oldRadial.Transform is { } ? new ImmutableTransform(oldRadial.Transform.Value) : null, oldRadial.SpreadMethod, oldRadial.Center, oldRadial.GradientOrigin, oldRadial.Radius); case IConicGradientBrush oldConic: return new ImmutableConicGradientBrush( CreateStopsFromSolidColorBrush(solidColorBrush, oldConic.GradientStops), solidColorBrush.Opacity, - new ImmutableTransform(oldConic.Transform.Value), + oldConic.Transform is { } ? new ImmutableTransform(oldConic.Transform.Value) : null, oldConic.SpreadMethod, oldConic.Center, oldConic.Angle); case ILinearGradientBrush oldLinear: return new ImmutableLinearGradientBrush( CreateStopsFromSolidColorBrush(solidColorBrush, oldLinear.GradientStops), solidColorBrush.Opacity, - new ImmutableTransform(oldLinear.Transform.Value), + oldLinear.Transform is { } ? new ImmutableTransform(oldLinear.Transform.Value) : null, oldLinear.SpreadMethod, oldLinear.StartPoint, oldLinear.EndPoint); default: diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs index 87ada91cbd..78e46f52d0 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs @@ -31,7 +31,7 @@ namespace Avalonia.Media.Immutable /// /// The brush from which this brush's properties should be copied. protected ImmutableGradientBrush(GradientBrush source) - : this(source.GradientStops.ToImmutable(), source.Opacity, source.Transform.ToImmutable(), source.SpreadMethod) + : this(source.GradientStops.ToImmutable(), source.Opacity, source.Transform?.ToImmutable(), source.SpreadMethod) { } From e361b827eb6ad7edf0cf08831f8de8002a25a72d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 21:51:01 +0200 Subject: [PATCH 032/822] Add transform --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 2 +- .../Media/Immutable/ImmutableSolidColorBrush.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index 41985dc078..18b8f4c5a8 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -7,12 +7,12 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.ITransform Avalonia.Media.IBrush.Transform' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.ITransform Avalonia.Media.IBrush.Transform.get()' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.ITransform Avalonia.Media.IMutableTransform.ToImmutable()' is present in the implementation but not in the contract. MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableConicGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable, System.Double)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Media.Immutable.ImmutableGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableLinearGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable, System.Nullable)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableRadialGradientBrush..ctor(System.Collections.Generic.IReadOnlyList, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable, System.Nullable, System.Double)' does not exist in the implementation but it does exist in the contract. TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract. +MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableSolidColorBrush..ctor(Avalonia.Media.Color, System.Double)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'protected void Avalonia.Media.Immutable.ImmutableTileBrush..ctor(Avalonia.Media.AlignmentX, Avalonia.Media.AlignmentY, Avalonia.RelativeRect, System.Double, Avalonia.RelativeRect, Avalonia.Media.Stretch, Avalonia.Media.TileMode, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract. diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs index daf73f8974..ac7d336f30 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs @@ -12,10 +12,12 @@ namespace Avalonia.Media.Immutable /// /// The color to use. /// The opacity of the brush. - public ImmutableSolidColorBrush(Color color, double opacity = 1) + /// The transform of the brush. + public ImmutableSolidColorBrush(Color color, double opacity = 1, ImmutableTransform transform = null) { Color = color; Opacity = opacity; + Transform = null; } /// @@ -32,7 +34,7 @@ namespace Avalonia.Media.Immutable /// /// The brush from which this brush's properties should be copied. public ImmutableSolidColorBrush(ISolidColorBrush source) - : this(source.Color, source.Opacity) + : this(source.Color, source.Opacity, source.Transform?.ToImmutable()) { } From 167ad6c003ad58ea5739a8574ba25102b19c1d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 21:51:08 +0200 Subject: [PATCH 033/822] Use ImmutableTransform --- src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs index e96ef026af..1eaf3bce67 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs @@ -26,7 +26,7 @@ namespace Avalonia.Media.Immutable AlignmentY alignmentY, RelativeRect destinationRect, double opacity, - ITransform transform, + ImmutableTransform transform, RelativeRect sourceRect, Stretch stretch, TileMode tileMode, From 81daa4f03b50c53cae15f2a770daba79eb613d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 22:10:51 +0200 Subject: [PATCH 034/822] Add Matrix interpolation --- .../Animators/GradientBrushAnimator.cs | 6 +++--- .../Animation/Animators/TransformAnimator.cs | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs index 817c53698e..ce0cfa2ce3 100644 --- a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs @@ -31,7 +31,7 @@ namespace Avalonia.Animation.Animators return new ImmutableRadialGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform) : null, + oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateMatrix(progress, oldValue.Transform, newValue.Transform) : null, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center), s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin), @@ -41,7 +41,7 @@ namespace Avalonia.Animation.Animators return new ImmutableConicGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform) : null, + oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateMatrix(progress, oldValue.Transform, newValue.Transform) : null, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center), s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle)); @@ -50,7 +50,7 @@ namespace Avalonia.Animation.Animators return new ImmutableLinearGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateTransform(progress, oldValue.Transform, newValue.Transform) : null, + oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateMatrix(progress, oldValue.Transform, newValue.Transform) : null, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint), s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint)); diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index 0299cba0e5..102e0f83a8 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -87,10 +87,19 @@ namespace Avalonia.Animation.Animators return null; } - internal ImmutableTransform InterpolateTransform(double progress, ITransform oldValue, ITransform newValue) - { - // TODO: - return new ImmutableTransform(newValue.Value); + internal ImmutableTransform InterpolateMatrix(double progress, ITransform oldValue, ITransform newValue) + { + var from = oldValue.Value; + var to = newValue.Value; + var matrix = new Matrix( + ((to.M11 - from.M11) * progress) + from.M11, + ((to.M12 - from.M12) * progress) + from.M12, + ((to.M21 - from.M21) * progress) + from.M21, + ((to.M22 - from.M22) * progress) + from.M22, + ((to.M31 - from.M31) * progress) + from.M31, + ((to.M32 - from.M32) * progress) + from.M32); + + return new ImmutableTransform(matrix); } /// From cca7b962287be0b209de79181e0ef6a8375d822f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 22:57:27 +0200 Subject: [PATCH 035/822] Mark field as private --- src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index 102e0f83a8..271430ee42 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -12,7 +12,7 @@ namespace Avalonia.Animation.Animators /// public class TransformAnimator : Animator { - DoubleAnimator _doubleAnimator; + private DoubleAnimator _doubleAnimator; /// public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable obsMatch, Action onComplete) From 97332822c368ae1250b854c8370337a52566d459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sun, 1 Aug 2021 22:57:40 +0200 Subject: [PATCH 036/822] Add MatrixAnimator --- .../Animators/GradientBrushAnimator.cs | 12 ++++++++--- .../Animation/Animators/MatrixAnimator.cs | 20 +++++++++++++++++++ .../Animation/Animators/TransformAnimator.cs | 15 -------------- .../Animation/Transitions/MatrixTransition.cs | 11 ++++++++++ src/Avalonia.Visuals/Matrix.cs | 10 ++++++++++ 5 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 src/Avalonia.Visuals/Animation/Animators/MatrixAnimator.cs create mode 100644 src/Avalonia.Visuals/Animation/Transitions/MatrixTransition.cs diff --git a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs index ce0cfa2ce3..3c13752992 100644 --- a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs @@ -17,6 +17,7 @@ namespace Avalonia.Animation.Animators private static readonly RelativePointAnimator s_relativePointAnimator = new RelativePointAnimator(); private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator(); private static readonly TransformAnimator s_transformAnimator = new TransformAnimator(); + private static readonly MatrixAnimator _matrixAnimator = new MatrixAnimator(); public override IGradientBrush? Interpolate(double progress, IGradientBrush? oldValue, IGradientBrush? newValue) { @@ -31,7 +32,7 @@ namespace Avalonia.Animation.Animators return new ImmutableRadialGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateMatrix(progress, oldValue.Transform, newValue.Transform) : null, + oldValue.Transform is { } && newValue.Transform is { } ? InterpolateTransform(progress, oldValue.Transform, newValue.Transform) : null, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center), s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin), @@ -41,7 +42,7 @@ namespace Avalonia.Animation.Animators return new ImmutableConicGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateMatrix(progress, oldValue.Transform, newValue.Transform) : null, + oldValue.Transform is { } && newValue.Transform is { } ? InterpolateTransform(progress, oldValue.Transform, newValue.Transform) : null, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center), s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle)); @@ -50,7 +51,7 @@ namespace Avalonia.Animation.Animators return new ImmutableLinearGradientBrush( InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops), s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity), - oldValue.Transform is { } && newValue.Transform is { } ? s_transformAnimator.InterpolateMatrix(progress, oldValue.Transform, newValue.Transform) : null, + oldValue.Transform is { } && newValue.Transform is { } ? InterpolateTransform(progress, oldValue.Transform, newValue.Transform) : null, oldValue.SpreadMethod, s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint), s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint)); @@ -90,6 +91,11 @@ namespace Avalonia.Animation.Animators return stops; } + private ImmutableTransform InterpolateTransform(double progress, ITransform oldValue, ITransform newValue) + { + return new ImmutableTransform(_matrixAnimator.Interpolate(progress, oldValue.Value, newValue.Value)); + } + internal static IGradientBrush ConvertSolidColorBrushToGradient(IGradientBrush gradientBrush, ISolidColorBrush solidColorBrush) { switch (gradientBrush) diff --git a/src/Avalonia.Visuals/Animation/Animators/MatrixAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/MatrixAnimator.cs new file mode 100644 index 0000000000..d5b6267a3c --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Animators/MatrixAnimator.cs @@ -0,0 +1,20 @@ +namespace Avalonia.Animation.Animators +{ + /// + /// Animator that handles properties. + /// + public class MatrixAnimator : Animator + { + /// + public override Matrix Interpolate(double progress, Matrix oldValue, Matrix newValue) + { + return new Matrix( + ((newValue.M11 - oldValue.M11) * progress) + oldValue.M11, + ((newValue.M12 - oldValue.M12) * progress) + oldValue.M12, + ((newValue.M21 - oldValue.M21) * progress) + oldValue.M21, + ((newValue.M22 - oldValue.M22) * progress) + oldValue.M22, + ((newValue.M31 - oldValue.M31) * progress) + oldValue.M31, + ((newValue.M32 - oldValue.M32) * progress) + oldValue.M32); + } + } +} diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index 271430ee42..1c5f6fa786 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -87,21 +87,6 @@ namespace Avalonia.Animation.Animators return null; } - internal ImmutableTransform InterpolateMatrix(double progress, ITransform oldValue, ITransform newValue) - { - var from = oldValue.Value; - var to = newValue.Value; - var matrix = new Matrix( - ((to.M11 - from.M11) * progress) + from.M11, - ((to.M12 - from.M12) * progress) + from.M12, - ((to.M21 - from.M21) * progress) + from.M21, - ((to.M22 - from.M22) * progress) + from.M22, - ((to.M31 - from.M31) * progress) + from.M31, - ((to.M32 - from.M32) * progress) + from.M32); - - return new ImmutableTransform(matrix); - } - /// public override double Interpolate(double p, double o, double n) => 0; } diff --git a/src/Avalonia.Visuals/Animation/Transitions/MatrixTransition.cs b/src/Avalonia.Visuals/Animation/Transitions/MatrixTransition.cs new file mode 100644 index 0000000000..59bf06ca26 --- /dev/null +++ b/src/Avalonia.Visuals/Animation/Transitions/MatrixTransition.cs @@ -0,0 +1,11 @@ +using Avalonia.Animation.Animators; + +namespace Avalonia.Animation +{ + /// + /// Transition class that handles with type. + /// + public class MatrixTransition : AnimatorDrivenTransition + { + } +} diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 8136f843df..7a56aa6fe2 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -1,5 +1,8 @@ using System; using System.Globalization; +#if !BUILDTASK +using Avalonia.Animation.Animators; +#endif using Avalonia.Utilities; namespace Avalonia @@ -12,6 +15,13 @@ namespace Avalonia #endif readonly struct Matrix : IEquatable { + static Matrix() + { +#if !BUILDTASK + Animation.Animation.RegisterAnimator(prop => typeof(Matrix).IsAssignableFrom(prop.PropertyType)); +#endif + } + private readonly double _m11; private readonly double _m12; private readonly double _m21; From 0beaa1c642a71fa40b3e03264834b47c6a4548ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Mon, 2 Aug 2021 08:34:10 +0200 Subject: [PATCH 037/822] Fix tests --- src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs index 1eaf3bce67..ed722275f5 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs @@ -53,7 +53,7 @@ namespace Avalonia.Media.Immutable source.AlignmentY, source.DestinationRect, source.Opacity, - source.Transform.ToImmutable(), + source.Transform?.ToImmutable(), source.SourceRect, source.Stretch, source.TileMode, From 7ca502ee10c0c99b47a34ae49c5f2e3e180ce761 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 28 Sep 2021 22:13:30 +0200 Subject: [PATCH 038/822] Update ApiCompatBaseline.txt --- src/Avalonia.Controls/ApiCompatBaseline.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index fac5923db5..6e895179e5 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -49,10 +49,13 @@ MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.Resized.s InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Func Avalonia.Platform.IWindowBaseImpl.AutomationStarted' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Func Avalonia.Platform.IWindowBaseImpl.AutomationStarted.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.AutomationStarted.set(System.Func)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean, System.Boolean)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. -Total Issues: 56 +Total Issues: 59 From c34a825bf5507a2ea87c9afd9e6cea3ff53c7594 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 2 Oct 2021 11:39:00 +0200 Subject: [PATCH 039/822] Try to fix problem with ComboBox. Only the OS can create the root automation peer, so we need to take care to not accidentally try to create it from `PopupAutomationPeer`. --- .../Automation/Peers/PopupAutomationPeer.cs | 11 ++++++----- src/Avalonia.Controls/Control.cs | 6 ++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs index 4bad8fd108..0f534e6aeb 100644 --- a/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Avalonia.Automation.Platform; using Avalonia.Controls; +using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.VisualTree; @@ -20,9 +21,9 @@ namespace Avalonia.Automation.Peers protected override IReadOnlyList? GetChildrenCore() { - var host = (IVisualTreeHost)Owner; - System.Diagnostics.Debug.WriteLine($"Popup children='{host}'"); - return host.Root is Control c ? new[] { GetOrCreatePeer(c) } : null; + var popupHost = ((IPopupHostProvider)Owner)?.PopupHost as Control; + var hostPeer = popupHost?.GetAutomationPeer(); + return hostPeer is object ? new[] { hostPeer } : null; } protected override bool IsContentElementCore() => false; @@ -45,8 +46,8 @@ namespace Avalonia.Automation.Peers private AutomationPeer? GetPopupRoot() { - var popupRoot = ((IVisualTreeHost)Owner).Root as Control; - return popupRoot is object ? GetOrCreatePeer(popupRoot) : null; + var popupRoot = ((IPopupHostProvider)Owner).PopupHost as Control; + return popupRoot?.GetAutomationPeer(); } } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 60acaf82db..f2e2cd1d8f 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -235,6 +235,12 @@ namespace Avalonia.Controls return new NoneAutomationPeer(factory, this); } + internal AutomationPeer? GetAutomationPeer() + { + VerifyAccess(); + return _automationPeer; + } + internal AutomationPeer GetOrCreateAutomationPeer(IAutomationNodeFactory factory) { VerifyAccess(); From acb49d66e992f9d1aa5d79d9d8c4dd0294c8edeb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 2 Oct 2021 14:37:19 +0200 Subject: [PATCH 040/822] Revert "Try to fix problem with ComboBox." This reverts commit c34a825bf5507a2ea87c9afd9e6cea3ff53c7594. --- .../Automation/Peers/PopupAutomationPeer.cs | 11 +++++------ src/Avalonia.Controls/Control.cs | 6 ------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs index 0f534e6aeb..4bad8fd108 100644 --- a/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using Avalonia.Automation.Platform; using Avalonia.Controls; -using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.VisualTree; @@ -21,9 +20,9 @@ namespace Avalonia.Automation.Peers protected override IReadOnlyList? GetChildrenCore() { - var popupHost = ((IPopupHostProvider)Owner)?.PopupHost as Control; - var hostPeer = popupHost?.GetAutomationPeer(); - return hostPeer is object ? new[] { hostPeer } : null; + var host = (IVisualTreeHost)Owner; + System.Diagnostics.Debug.WriteLine($"Popup children='{host}'"); + return host.Root is Control c ? new[] { GetOrCreatePeer(c) } : null; } protected override bool IsContentElementCore() => false; @@ -46,8 +45,8 @@ namespace Avalonia.Automation.Peers private AutomationPeer? GetPopupRoot() { - var popupRoot = ((IPopupHostProvider)Owner).PopupHost as Control; - return popupRoot?.GetAutomationPeer(); + var popupRoot = ((IVisualTreeHost)Owner).Root as Control; + return popupRoot is object ? GetOrCreatePeer(popupRoot) : null; } } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index f2e2cd1d8f..60acaf82db 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -235,12 +235,6 @@ namespace Avalonia.Controls return new NoneAutomationPeer(factory, this); } - internal AutomationPeer? GetAutomationPeer() - { - VerifyAccess(); - return _automationPeer; - } - internal AutomationPeer GetOrCreateAutomationPeer(IAutomationNodeFactory factory) { VerifyAccess(); From 11c60b42944c08e3559808caf7c5afc38e6c5b14 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 2 Oct 2021 16:20:19 +0200 Subject: [PATCH 041/822] Decouple automation peers from platform nodes. Now peers are entirely unaware of the platform implementation. --- .../Resources/Resource.Designer.cs | 2 +- .../Resources/Resource.Designer.cs | 2 +- .../AutomationPropertyChangedEventArgs.cs | 23 + .../Automation/Peers/AutomationPeer.cs | 37 +- .../Automation/Peers/ButtonAutomationPeer.cs | 5 +- .../Peers/CheckBoxAutomationPeer.cs | 7 +- .../Peers/ComboBoxAutomationPeer.cs | 10 +- .../Peers/ComboBoxItemAutomationPeer.cs | 5 +- .../Peers/ContentControlAutomationPeer.cs | 7 +- .../Peers/ContextMenuAutomationPeer.cs | 7 +- .../Automation/Peers/ControlAutomationPeer.cs | 34 +- .../Automation/Peers/ImageAutomationPeer.cs | 7 +- .../Peers/ItemsControlAutomationPeer.cs | 9 +- .../Peers/ListItemAutomationPeer.cs | 7 +- .../Automation/Peers/MenuAutomationPeer.cs | 7 +- .../Peers/MenuItemAutomationPeer.cs | 7 +- .../Automation/Peers/NoneAutomationPeer.cs | 5 +- .../Automation/Peers/PopupAutomationPeer.cs | 9 +- .../Peers/PopupRootAutomationPeer.cs | 5 +- .../Peers/RangeBaseAutomationPeer.cs | 5 +- .../Peers/ScrollViewerAutomationPeer.cs | 5 +- .../SelectingItemsControlAutomationPeer.cs | 11 +- .../Automation/Peers/SliderAutomationPeer.cs | 5 +- .../Peers/TabControlAutomationPeer.cs | 7 +- .../Automation/Peers/TabItemAutomationPeer.cs | 7 +- .../Peers/TextBlockAutomationPeer.cs | 7 +- .../Automation/Peers/TextBoxAutomationPeer.cs | 7 +- .../Peers/ToggleButtonAutomationPeer.cs | 7 +- .../Peers/UnrealizedElementAutomationPeer.cs | 6 - .../Automation/Peers/WindowAutomationPeer.cs | 5 +- .../Peers/WindowBaseAutomationPeer.cs | 18 +- .../Automation/Platform/IAutomationNode.cs | 32 -- .../Platform/IAutomationNodeFactory.cs | 18 - .../Platform/IRootAutomationNode.cs | 20 - .../Automation/Provider/IRootProvider.cs | 4 +- src/Avalonia.Controls/Button.cs | 6 +- src/Avalonia.Controls/CheckBox.cs | 5 +- src/Avalonia.Controls/ComboBox.cs | 5 +- src/Avalonia.Controls/ComboBoxItem.cs | 5 +- src/Avalonia.Controls/ContextMenu.cs | 5 +- src/Avalonia.Controls/Control.cs | 16 +- src/Avalonia.Controls/Image.cs | 5 +- src/Avalonia.Controls/ItemsControl.cs | 5 +- src/Avalonia.Controls/ListBoxItem.cs | 5 +- src/Avalonia.Controls/Menu.cs | 5 +- src/Avalonia.Controls/MenuItem.cs | 5 +- .../Platform/IWindowBaseImpl.cs | 6 - .../Primitives/AccessText.cs | 5 +- src/Avalonia.Controls/Primitives/Popup.cs | 5 +- src/Avalonia.Controls/Primitives/PopupRoot.cs | 5 +- .../Primitives/ToggleButton.cs | 5 +- src/Avalonia.Controls/ScrollViewer.cs | 5 +- src/Avalonia.Controls/Slider.cs | 5 +- src/Avalonia.Controls/TabControl.cs | 5 +- src/Avalonia.Controls/TabItem.cs | 5 +- src/Avalonia.Controls/TextBlock.cs | 5 +- src/Avalonia.Controls/TextBox.cs | 5 +- src/Avalonia.Controls/TopLevel.cs | 2 - src/Avalonia.Controls/Window.cs | 5 +- src/Avalonia.Controls/WindowBase.cs | 20 - .../Remote/PreviewerWindowImpl.cs | 4 - src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 - src/Avalonia.Headless/HeadlessWindowImpl.cs | 2 - src/Avalonia.Native/AutomationNode.cs | 7 +- src/Avalonia.Native/AutomationNodeFactory.cs | 24 - src/Avalonia.Native/AvnAutomationPeer.cs | 3 +- src/Avalonia.Native/WindowImplBase.cs | 38 -- src/Avalonia.Native/avn.idl | 1 - src/Avalonia.X11/X11Window.cs | 2 - .../Automation/AutomationNode.Selection.cs | 4 +- .../Automation/AutomationNode.cs | 39 +- .../Automation/AutomationNodeFactory.cs | 19 - .../Automation/RootAutomationNode.cs | 18 +- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 13 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 - .../Automation/ControlAutomationPeerTests.cs | 506 +++++++++--------- 76 files changed, 466 insertions(+), 727 deletions(-) create mode 100644 src/Avalonia.Controls/Automation/AutomationPropertyChangedEventArgs.cs delete mode 100644 src/Avalonia.Controls/Automation/Platform/IAutomationNode.cs delete mode 100644 src/Avalonia.Controls/Automation/Platform/IAutomationNodeFactory.cs delete mode 100644 src/Avalonia.Controls/Automation/Platform/IRootAutomationNode.cs delete mode 100644 src/Avalonia.Native/AutomationNodeFactory.cs delete mode 100644 src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs diff --git a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs index b1ca548e2c..6ac6673986 100644 --- a/samples/ControlCatalog.Android/Resources/Resource.Designer.cs +++ b/samples/ControlCatalog.Android/Resources/Resource.Designer.cs @@ -14,7 +14,7 @@ namespace ControlCatalog.Android { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.0.99.19")] public partial class Resource { diff --git a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs index 83db67fcee..4046e60161 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs +++ b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs @@ -14,7 +14,7 @@ namespace Avalonia.AndroidTestApplication { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.0.99.19")] public partial class Resource { diff --git a/src/Avalonia.Controls/Automation/AutomationPropertyChangedEventArgs.cs b/src/Avalonia.Controls/Automation/AutomationPropertyChangedEventArgs.cs new file mode 100644 index 0000000000..b8018613f8 --- /dev/null +++ b/src/Avalonia.Controls/Automation/AutomationPropertyChangedEventArgs.cs @@ -0,0 +1,23 @@ +using System; + +#nullable enable + +namespace Avalonia.Automation +{ + public class AutomationPropertyChangedEventArgs : EventArgs + { + public AutomationPropertyChangedEventArgs( + AutomationProperty property, + object? oldValue, + object? newValue) + { + Property = property; + OldValue = oldValue; + NewValue = newValue; + } + + public AutomationProperty Property { get; } + public object? OldValue { get; } + public object? NewValue { get; } + } +} diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index 85a0e665b2..757fe9e158 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; -using Avalonia.Automation.Platform; #nullable enable @@ -56,33 +54,6 @@ namespace Avalonia.Automation.Peers /// public abstract class AutomationPeer { - /// - /// Initializes a new instance of the class. - /// - /// - /// The factory to use to create the platform automation node. - /// - protected AutomationPeer(IAutomationNodeFactory factory) - { - Node = factory.CreateNode(this); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The platform automation node. - /// - protected AutomationPeer(IAutomationNode node) - { - Node = node; - } - - /// - /// Gets the related node in the platform UI Automation tree. - /// - public IAutomationNode Node { get; } - /// /// Attempts to bring the element associated with the automation peer into view. /// @@ -188,18 +159,20 @@ namespace Avalonia.Automation.Peers /// true if a context menu is present for the element; otherwise false. public bool ShowContextMenu() => ShowContextMenuCore(); + public event EventHandler? PropertyChanged; + /// /// Raises an event to notify the automation client of a changed property value. /// - /// The property that changed. + /// The property that changed. /// The previous value of the property. /// The new value of the property. public void RaisePropertyChangedEvent( - AutomationProperty automationProperty, + AutomationProperty property, object? oldValue, object? newValue) { - Node.PropertyChanged(automationProperty, oldValue, newValue); + PropertyChanged?.Invoke(this, new AutomationPropertyChangedEventArgs(property, oldValue, newValue)); } protected virtual string GetLocalizedControlTypeCore() diff --git a/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs index 66e5bd7b49..f5d6dce039 100644 --- a/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ButtonAutomationPeer.cs @@ -1,4 +1,3 @@ -using Avalonia.Automation.Platform; using Avalonia.Automation.Provider; using Avalonia.Controls; @@ -9,8 +8,8 @@ namespace Avalonia.Automation.Peers public class ButtonAutomationPeer : ContentControlAutomationPeer, IInvokeProvider { - public ButtonAutomationPeer(IAutomationNodeFactory factory, Button owner) - : base(factory, owner) + public ButtonAutomationPeer(Button owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs index 4e98cc7746..7f4e492935 100644 --- a/src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Controls; +using Avalonia.Controls; #nullable enable @@ -7,8 +6,8 @@ namespace Avalonia.Automation.Peers { public class CheckBoxAutomationPeer : ToggleButtonAutomationPeer { - public CheckBoxAutomationPeer(IAutomationNodeFactory factory, CheckBox owner) - : base(factory, owner) + public CheckBoxAutomationPeer(CheckBox owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs index d8225dbc40..c582c3d372 100644 --- a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Avalonia.Automation.Platform; using Avalonia.Automation.Provider; using Avalonia.Controls; @@ -12,8 +11,8 @@ namespace Avalonia.Automation.Peers { private UnrealizedSelectionPeer[]? _selection; - public ComboBoxAutomationPeer(IAutomationNodeFactory factory, ComboBox owner) - : base(factory, owner) + public ComboBoxAutomationPeer(ComboBox owner) + : base(owner) { } @@ -39,7 +38,7 @@ namespace Avalonia.Automation.Peers // peer to represent the unrealized item. if (Owner.SelectedItem is object selection) { - _selection ??= new[] { new UnrealizedSelectionPeer(Node.Factory, this) }; + _selection ??= new[] { new UnrealizedSelectionPeer(this) }; _selection[0].Item = selection; return _selection; } @@ -70,8 +69,7 @@ namespace Avalonia.Automation.Peers private readonly ComboBoxAutomationPeer _owner; private object? _item; - public UnrealizedSelectionPeer(IAutomationNodeFactory factory, ComboBoxAutomationPeer owner) - : base(factory) + public UnrealizedSelectionPeer(ComboBoxAutomationPeer owner) { _owner = owner; } diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs index 06141ae835..70d29dbc87 100644 --- a/src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.Automation.Platform; using Avalonia.Automation.Provider; using Avalonia.Controls; using Avalonia.Controls.Primitives; @@ -11,8 +10,8 @@ namespace Avalonia.Automation.Peers { public class ComboBoxItemAutomationPeer : ListItemAutomationPeer { - public ComboBoxItemAutomationPeer(IAutomationNodeFactory factory, ComboBoxItem owner) - : base(factory, owner) + public ComboBoxItemAutomationPeer(ComboBoxItem owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/ContentControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ContentControlAutomationPeer.cs index db1c7e1aa7..08e4f2a926 100644 --- a/src/Avalonia.Controls/Automation/Peers/ContentControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ContentControlAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Controls; +using Avalonia.Controls; #nullable enable @@ -7,8 +6,8 @@ namespace Avalonia.Automation.Peers { public class ContentControlAutomationPeer : ControlAutomationPeer { - protected ContentControlAutomationPeer(IAutomationNodeFactory factory, ContentControl owner) - : base(factory, owner) + protected ContentControlAutomationPeer(ContentControl owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs index 5631fcf581..3230f33506 100644 --- a/src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Controls; +using Avalonia.Controls; #nullable enable @@ -7,8 +6,8 @@ namespace Avalonia.Automation.Peers { public class ContextMenuAutomationPeer : ControlAutomationPeer { - public ContextMenuAutomationPeer(IAutomationNodeFactory factory, ContextMenu owner) - : base(factory, owner) + public ContextMenuAutomationPeer(ContextMenu owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index bd4e9dfcf4..947c88a190 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.VisualTree; @@ -19,15 +18,7 @@ namespace Avalonia.Automation.Peers private AutomationPeer? _parent; private bool _parentValid; - public ControlAutomationPeer(IAutomationNodeFactory factory, Control owner) - : base(factory) - { - Owner = owner ?? throw new ArgumentNullException("owner"); - Initialize(); - } - - protected ControlAutomationPeer(IAutomationNode node, Control owner) - : base(node) + public ControlAutomationPeer(Control owner) { Owner = owner ?? throw new ArgumentNullException("owner"); Initialize(); @@ -35,15 +26,18 @@ namespace Avalonia.Automation.Peers public Control Owner { get; } - public static AutomationPeer GetOrCreatePeer(IAutomationNodeFactory factory, Control element) + public event EventHandler? ChildrenChanged; + + public AutomationPeer GetOrCreate(Control element) { - element = element ?? throw new ArgumentNullException("element"); - return element.GetOrCreateAutomationPeer(factory); + if (element == Owner) + return this; + return CreatePeerForElement(element); } - public AutomationPeer GetOrCreatePeer(Control element) + public static AutomationPeer CreatePeerForElement(Control element) { - return element == Owner ? this : GetOrCreatePeer(Node.Factory, element); + return element.GetOrCreateAutomationPeer(); } protected override void BringIntoViewCore() => Owner.BringIntoView(); @@ -79,7 +73,7 @@ namespace Avalonia.Automation.Peers { if (child is Control c && c.IsVisible) { - result.Add(GetOrCreatePeer(c)); + result.Add(GetOrCreate(c)); } } @@ -89,7 +83,7 @@ namespace Avalonia.Automation.Peers protected override AutomationPeer? GetLabeledByCore() { var label = AutomationProperties.GetLabeledBy(Owner); - return label is Control c ? GetOrCreatePeer(c) : null; + return label is Control c ? GetOrCreate(c) : null; } protected override string? GetNameCore() @@ -116,7 +110,7 @@ namespace Avalonia.Automation.Peers protected void InvalidateChildren() { _childrenValid = false; - Node!.ChildrenChanged(); + ChildrenChanged?.Invoke(this, EventArgs.Empty); } /// @@ -185,7 +179,7 @@ namespace Avalonia.Automation.Peers { var parent = Owner.GetVisualParent(); if (parent is Control c) - (GetOrCreatePeer(c) as ControlAutomationPeer)?.InvalidateChildren(); + (GetOrCreate(c) as ControlAutomationPeer)?.InvalidateChildren(); } else if (e.Property == Visual.TransformedBoundsProperty) { @@ -211,7 +205,7 @@ namespace Avalonia.Automation.Peers { if (parent is Control c) { - var parentPeer = GetOrCreatePeer(c); + var parentPeer = GetOrCreate(c); parentPeer.GetChildren(); } diff --git a/src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs index ad88941299..4548341487 100644 --- a/src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Controls; +using Avalonia.Controls; #nullable enable @@ -7,8 +6,8 @@ namespace Avalonia.Automation.Peers { public class ImageAutomationPeer : ControlAutomationPeer { - public ImageAutomationPeer(IAutomationNodeFactory factory, Control owner) - : base(factory, owner) + public ImageAutomationPeer(Control owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs index 6eed22e6cf..0d35920e19 100644 --- a/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ItemsControlAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Automation.Provider; +using Avalonia.Automation.Provider; using Avalonia.Controls; #nullable enable @@ -11,8 +10,8 @@ namespace Avalonia.Automation.Peers private bool _searchedForScrollable; private IScrollProvider? _scroller; - public ItemsControlAutomationPeer(IAutomationNodeFactory factory, ItemsControl owner) - : base(factory, owner) + public ItemsControlAutomationPeer(ItemsControl owner) + : base(owner) { } @@ -31,7 +30,7 @@ namespace Avalonia.Automation.Peers if (!_searchedForScrollable) { if (Owner.GetValue(ListBox.ScrollProperty) is Control scrollable) - _scroller = GetOrCreatePeer(scrollable) as IScrollProvider; + _scroller = GetOrCreate(scrollable) as IScrollProvider; _searchedForScrollable = true; } diff --git a/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs index 1b9a8354a1..0621e81c1a 100644 --- a/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.Automation.Platform; using Avalonia.Automation.Provider; using Avalonia.Controls; using Avalonia.Controls.Primitives; @@ -12,8 +11,8 @@ namespace Avalonia.Automation.Peers public class ListItemAutomationPeer : ContentControlAutomationPeer, ISelectionItemProvider { - public ListItemAutomationPeer(IAutomationNodeFactory factory, ContentControl owner) - : base(factory, owner) + public ListItemAutomationPeer(ContentControl owner) + : base(owner) { } @@ -25,7 +24,7 @@ namespace Avalonia.Automation.Peers { if (Owner.Parent is Control parent) { - var parentPeer = GetOrCreatePeer(parent); + var parentPeer = GetOrCreate(parent); return parentPeer as ISelectionProvider; } diff --git a/src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs index 38cd7a8d41..e223a0864f 100644 --- a/src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Controls; +using Avalonia.Controls; #nullable enable @@ -7,8 +6,8 @@ namespace Avalonia.Automation.Peers { public class MenuAutomationPeer : ControlAutomationPeer { - public MenuAutomationPeer(IAutomationNodeFactory factory, Menu owner) - : base(factory, owner) + public MenuAutomationPeer(Menu owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs index 5ed1c5e579..1994f004d6 100644 --- a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Controls; +using Avalonia.Controls; using Avalonia.Controls.Primitives; #nullable enable @@ -8,8 +7,8 @@ namespace Avalonia.Automation.Peers { public class MenuItemAutomationPeer : ControlAutomationPeer { - public MenuItemAutomationPeer(IAutomationNodeFactory factory, MenuItem owner) - : base(factory, owner) + public MenuItemAutomationPeer(MenuItem owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs index fa5e6c175e..2963144ebf 100644 --- a/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs @@ -1,4 +1,3 @@ -using Avalonia.Automation.Platform; using Avalonia.Controls; #nullable enable @@ -11,8 +10,8 @@ namespace Avalonia.Automation.Peers /// public class NoneAutomationPeer : ControlAutomationPeer { - public NoneAutomationPeer(IAutomationNodeFactory factory, Control owner) - : base(factory, owner) + public NoneAutomationPeer(Control owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs index 4bad8fd108..e6f827b942 100644 --- a/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.VisualTree; @@ -11,8 +10,8 @@ namespace Avalonia.Automation.Peers { public class PopupAutomationPeer : ControlAutomationPeer { - public PopupAutomationPeer(IAutomationNodeFactory factory, Popup owner) - : base(factory, owner) + public PopupAutomationPeer(Popup owner) + : base(owner) { owner.Opened += PopupOpenedClosed; owner.Closed += PopupOpenedClosed; @@ -22,7 +21,7 @@ namespace Avalonia.Automation.Peers { var host = (IVisualTreeHost)Owner; System.Diagnostics.Debug.WriteLine($"Popup children='{host}'"); - return host.Root is Control c ? new[] { GetOrCreatePeer(c) } : null; + return host.Root is Control c ? new[] { GetOrCreate(c) } : null; } protected override bool IsContentElementCore() => false; @@ -46,7 +45,7 @@ namespace Avalonia.Automation.Peers private AutomationPeer? GetPopupRoot() { var popupRoot = ((IVisualTreeHost)Owner).Root as Control; - return popupRoot is object ? GetOrCreatePeer(popupRoot) : null; + return popupRoot is object ? GetOrCreate(popupRoot) : null; } } } diff --git a/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs index f7e06e83eb..fb717ef98b 100644 --- a/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/PopupRootAutomationPeer.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.Automation.Platform; using Avalonia.Controls.Primitives; #nullable enable @@ -8,8 +7,8 @@ namespace Avalonia.Automation.Peers { public class PopupRootAutomationPeer : WindowBaseAutomationPeer { - public PopupRootAutomationPeer(IAutomationNode node, PopupRoot owner) - : base(node, owner) + public PopupRootAutomationPeer(PopupRoot owner) + : base(owner) { if (owner.IsVisible) StartTrackingFocus(); diff --git a/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs index 31a2b7e7af..1bb487b161 100644 --- a/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/RangeBaseAutomationPeer.cs @@ -1,4 +1,3 @@ -using Avalonia.Automation.Platform; using Avalonia.Automation.Provider; using Avalonia.Controls.Primitives; @@ -8,8 +7,8 @@ namespace Avalonia.Automation.Peers { public abstract class RangeBaseAutomationPeer : ControlAutomationPeer, IRangeValueProvider { - public RangeBaseAutomationPeer(IAutomationNodeFactory factory, RangeBase owner) - : base(factory, owner) + public RangeBaseAutomationPeer(RangeBase owner) + : base(owner) { owner.PropertyChanged += OwnerPropertyChanged; } diff --git a/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs index 165df6c032..c2474bb9b8 100644 --- a/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.Automation.Platform; using Avalonia.Automation.Provider; using Avalonia.Controls; using Avalonia.Utilities; @@ -10,8 +9,8 @@ namespace Avalonia.Automation.Peers { public class ScrollViewerAutomationPeer : ControlAutomationPeer, IScrollProvider { - public ScrollViewerAutomationPeer(IAutomationNodeFactory factory, ScrollViewer owner) - : base(factory, owner) + public ScrollViewerAutomationPeer(ScrollViewer owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs index b55f653a5d..f372e3b781 100644 --- a/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/SelectingItemsControlAutomationPeer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Avalonia.Automation.Platform; using Avalonia.Automation.Provider; using Avalonia.Controls; using Avalonia.Controls.Primitives; @@ -16,16 +15,16 @@ namespace Avalonia.Automation.Peers { private ISelectionModel _selection; - protected SelectingItemsControlAutomationPeer(IAutomationNodeFactory factory, SelectingItemsControl owner) - : base(factory, owner) + protected SelectingItemsControlAutomationPeer(SelectingItemsControl owner) + : base(owner) { _selection = owner.GetValue(ListBox.SelectionProperty); _selection.SelectionChanged += OwnerSelectionChanged; owner.PropertyChanged += OwnerPropertyChanged; } - public bool CanSelectMultiple => GetSelectionModeCore().HasFlagCustom(SelectionMode.Multiple); - public bool IsSelectionRequired => GetSelectionModeCore().HasFlagCustom(SelectionMode.AlwaysSelected); + public bool CanSelectMultiple => GetSelectionModeCore().HasAllFlags(SelectionMode.Multiple); + public bool IsSelectionRequired => GetSelectionModeCore().HasAllFlags(SelectionMode.AlwaysSelected); public IReadOnlyList GetSelection() => GetSelectionCore() ?? Array.Empty(); protected virtual IReadOnlyList? GetSelectionCore() @@ -42,7 +41,7 @@ namespace Avalonia.Automation.Peers if (container is Control c && ((IVisual)c).IsAttachedToVisualTree) { - var peer = GetOrCreatePeer(c); + var peer = GetOrCreate(c); if (peer is object) { diff --git a/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs index 172b9a1b54..907e779046 100644 --- a/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs @@ -1,4 +1,3 @@ -using Avalonia.Automation.Platform; using Avalonia.Controls; #nullable enable @@ -7,8 +6,8 @@ namespace Avalonia.Automation.Peers { public class SliderAutomationPeer : RangeBaseAutomationPeer { - public SliderAutomationPeer(IAutomationNodeFactory factory, Slider owner) - : base(factory, owner) + public SliderAutomationPeer(Slider owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs index d615be43f3..e14e61a6e4 100644 --- a/src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Controls; +using Avalonia.Controls; #nullable enable @@ -7,8 +6,8 @@ namespace Avalonia.Automation.Peers { public class TabControlAutomationPeer : SelectingItemsControlAutomationPeer { - public TabControlAutomationPeer(IAutomationNodeFactory factory, TabControl owner) - : base(factory, owner) + public TabControlAutomationPeer(TabControl owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs index 20021b5b96..dc794da915 100644 --- a/src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs @@ -1,12 +1,11 @@ -using Avalonia.Automation.Platform; -using Avalonia.Controls; +using Avalonia.Controls; namespace Avalonia.Automation.Peers { public class TabItemAutomationPeer : ListItemAutomationPeer { - public TabItemAutomationPeer(IAutomationNodeFactory factory, TabItem owner) - : base(factory, owner) + public TabItemAutomationPeer(TabItem owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs index a2ddedffc6..37eaf6f7c0 100644 --- a/src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/TextBlockAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Controls; +using Avalonia.Controls; #nullable enable @@ -7,8 +6,8 @@ namespace Avalonia.Automation.Peers { public class TextBlockAutomationPeer : ControlAutomationPeer { - public TextBlockAutomationPeer(IAutomationNodeFactory factory, TextBlock owner) - : base(factory, owner) + public TextBlockAutomationPeer(TextBlock owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs index 99f50ddabb..33b2ba58b6 100644 --- a/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/TextBoxAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Automation.Provider; +using Avalonia.Automation.Provider; using Avalonia.Controls; #nullable enable @@ -8,8 +7,8 @@ namespace Avalonia.Automation.Peers { public class TextBoxAutomationPeer : ControlAutomationPeer, IValueProvider { - public TextBoxAutomationPeer(IAutomationNodeFactory factory, TextBox owner) - : base(factory, owner) + public TextBoxAutomationPeer(TextBox owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs index 4c410d8654..dd0c506d29 100644 --- a/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ToggleButtonAutomationPeer.cs @@ -1,5 +1,4 @@ -using Avalonia.Automation.Platform; -using Avalonia.Automation.Provider; +using Avalonia.Automation.Provider; using Avalonia.Controls.Primitives; #nullable enable @@ -8,8 +7,8 @@ namespace Avalonia.Automation.Peers { public class ToggleButtonAutomationPeer : ContentControlAutomationPeer, IToggleProvider { - public ToggleButtonAutomationPeer(IAutomationNodeFactory factory, ToggleButton owner) - : base(factory, owner) + public ToggleButtonAutomationPeer(ToggleButton owner) + : base(owner) { } diff --git a/src/Avalonia.Controls/Automation/Peers/UnrealizedElementAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/UnrealizedElementAutomationPeer.cs index 5832b04dd7..b388f21a17 100644 --- a/src/Avalonia.Controls/Automation/Peers/UnrealizedElementAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/UnrealizedElementAutomationPeer.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Avalonia.Automation.Platform; #nullable enable @@ -11,11 +10,6 @@ namespace Avalonia.Automation.Peers /// public abstract class UnrealizedElementAutomationPeer : AutomationPeer { - protected UnrealizedElementAutomationPeer(IAutomationNodeFactory factory) - : base(factory) - { - } - public void SetParent(AutomationPeer? parent) => TrySetParent(parent); protected override void BringIntoViewCore() => GetParent()?.BringIntoView(); protected override Rect GetBoundingRectangleCore() => GetParent()?.GetBoundingRectangle() ?? default; diff --git a/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs index 9629ae0294..fbc6e9d4f4 100644 --- a/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/WindowAutomationPeer.cs @@ -1,5 +1,4 @@ using System; -using Avalonia.Automation.Platform; using Avalonia.Controls; #nullable enable @@ -8,8 +7,8 @@ namespace Avalonia.Automation.Peers { public class WindowAutomationPeer : WindowBaseAutomationPeer { - public WindowAutomationPeer(IAutomationNode node, Window owner) - : base(node, owner) + public WindowAutomationPeer(Window owner) + : base(owner) { if (owner.IsVisible) StartTrackingFocus(); diff --git a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs index f97d13c766..b24685929a 100644 --- a/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/WindowBaseAutomationPeer.cs @@ -1,5 +1,5 @@ +using System; using System.ComponentModel; -using Avalonia.Automation.Platform; using Avalonia.Automation.Provider; using Avalonia.Controls; using Avalonia.Input; @@ -14,25 +14,27 @@ namespace Avalonia.Automation.Peers { private Control? _focus; - public WindowBaseAutomationPeer(IAutomationNode node, WindowBase owner) - : base(node, owner) + public WindowBaseAutomationPeer(WindowBase owner) + : base(owner) { } public new WindowBase Owner => (WindowBase)base.Owner; public ITopLevelImpl PlatformImpl => Owner.PlatformImpl; + public event EventHandler? FocusChanged; + protected override AutomationControlType GetAutomationControlTypeCore() { return AutomationControlType.Window; } - public AutomationPeer? GetFocus() => _focus is object ? GetOrCreatePeer(_focus) : null; + public AutomationPeer? GetFocus() => _focus is object ? GetOrCreate(_focus) : null; public AutomationPeer? GetPeerFromPoint(Point p) { var hit = Owner.GetVisualAt(p)?.FindAncestorOfType(includeSelf: true); - return hit is object ? GetOrCreatePeer(hit) : null; + return hit is object ? GetOrCreate(hit) : null; } protected void StartTrackingFocus() @@ -54,8 +56,10 @@ namespace Avalonia.Automation.Peers if (_focus != oldFocus) { - var peer = _focus is object ? GetOrCreatePeer(_focus) : null; - ((IRootAutomationNode)Node).FocusChanged(peer); + var peer = _focus is object ? + _focus == Owner ? this : + GetOrCreate(_focus) : null; + FocusChanged?.Invoke(this, EventArgs.Empty); } } diff --git a/src/Avalonia.Controls/Automation/Platform/IAutomationNode.cs b/src/Avalonia.Controls/Automation/Platform/IAutomationNode.cs deleted file mode 100644 index cae3504a4a..0000000000 --- a/src/Avalonia.Controls/Automation/Platform/IAutomationNode.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Avalonia.Automation.Peers; - -#nullable enable - -namespace Avalonia.Automation.Platform -{ - /// - /// Represents a platform implementation of a node in the UI Automation tree. - /// - public interface IAutomationNode - { - /// - /// Gets a factory which can be used to create child nodes. - /// - IAutomationNodeFactory Factory { get; } - - /// - /// Called by the when the children of the peer change. - /// - void ChildrenChanged(); - - /// - /// Called by the when a property other than the parent, - /// children or root changes. - /// - /// The property that changed. - /// The previous value of the property. - /// The new value of the property. - void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue); - } -} diff --git a/src/Avalonia.Controls/Automation/Platform/IAutomationNodeFactory.cs b/src/Avalonia.Controls/Automation/Platform/IAutomationNodeFactory.cs deleted file mode 100644 index da16611b9e..0000000000 --- a/src/Avalonia.Controls/Automation/Platform/IAutomationNodeFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Avalonia.Automation.Peers; - -#nullable enable - -namespace Avalonia.Automation.Platform -{ - /// - /// Creates nodes in the UI Automation tree of the underlying platform. - /// - public interface IAutomationNodeFactory - { - /// - /// Creates an automation node for a peer. - /// - /// The peer. - IAutomationNode CreateNode(AutomationPeer peer); - } -} diff --git a/src/Avalonia.Controls/Automation/Platform/IRootAutomationNode.cs b/src/Avalonia.Controls/Automation/Platform/IRootAutomationNode.cs deleted file mode 100644 index 8346a908c2..0000000000 --- a/src/Avalonia.Controls/Automation/Platform/IRootAutomationNode.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Avalonia.Automation.Peers; - -#nullable enable - -namespace Avalonia.Automation.Platform -{ - /// - /// Represents a platform implementation of a root node in the UI Automation tree. - /// - public interface IRootAutomationNode : IAutomationNode - { - /// - /// Called by the when its focus changes. - /// - /// - /// The automation peer for the newly focused control or null if no control is focused. - /// - void FocusChanged(AutomationPeer? focus); - } -} diff --git a/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs b/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs index 698b4f26b9..8ea53863eb 100644 --- a/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs +++ b/src/Avalonia.Controls/Automation/Provider/IRootProvider.cs @@ -1,4 +1,5 @@ -using Avalonia.Automation.Peers; +using System; +using Avalonia.Automation.Peers; using Avalonia.Platform; #nullable enable @@ -10,5 +11,6 @@ namespace Avalonia.Automation.Provider ITopLevelImpl? PlatformImpl { get; } AutomationPeer? GetFocus(); AutomationPeer? GetPeerFromPoint(Point p); + event EventHandler? FocusChanged; } } diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 645e697d03..740f715fe8 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using System.Windows.Input; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Data; @@ -380,10 +379,7 @@ namespace Avalonia.Controls } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) - { - return new ButtonAutomationPeer(factory, this); - } + protected override AutomationPeer OnCreateAutomationPeer() => new ButtonAutomationPeer(this); protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) { diff --git a/src/Avalonia.Controls/CheckBox.cs b/src/Avalonia.Controls/CheckBox.cs index 374ab33338..f7b0dcfdc2 100644 --- a/src/Avalonia.Controls/CheckBox.cs +++ b/src/Avalonia.Controls/CheckBox.cs @@ -1,5 +1,4 @@ using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Primitives; namespace Avalonia.Controls @@ -9,9 +8,9 @@ namespace Avalonia.Controls /// public class CheckBox : ToggleButton { - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new CheckBoxAutomationPeer(factory, this); + return new CheckBoxAutomationPeer(this); } } } diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index a50132270b..70fb62dc69 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using System.Reactive.Disposables; using Avalonia.Controls.Generators; using Avalonia.Controls.Mixins; @@ -298,9 +297,9 @@ namespace Avalonia.Controls _popup.Closed += PopupClosed; } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new ComboBoxAutomationPeer(factory, this); + return new ComboBoxAutomationPeer(this); } internal void ItemFocused(ComboBoxItem dropDownItem) diff --git a/src/Avalonia.Controls/ComboBoxItem.cs b/src/Avalonia.Controls/ComboBoxItem.cs index 0d5846f270..42ec6e43b9 100644 --- a/src/Avalonia.Controls/ComboBoxItem.cs +++ b/src/Avalonia.Controls/ComboBoxItem.cs @@ -1,7 +1,6 @@ using System; using System.Reactive.Linq; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; namespace Avalonia.Controls { @@ -16,9 +15,9 @@ namespace Avalonia.Controls .Subscribe(_ => (Parent as ComboBox)?.ItemFocused(this)); } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new ComboBoxItemAutomationPeer(factory, this); + return new ComboBoxItemAutomationPeer(this); } } } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index ade23308d0..0a4b518f57 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using System.Linq; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Generators; @@ -320,9 +319,9 @@ namespace Avalonia.Controls return new MenuItemContainerGenerator(this); } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new ContextMenuAutomationPeer(factory, this); + return new ContextMenuAutomationPeer(this); } private void Open(Control control, Control placementTarget, bool requestedByPointer) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 60acaf82db..a6b16a5b27 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -1,7 +1,6 @@ using System; using System.ComponentModel; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; @@ -230,12 +229,12 @@ namespace Avalonia.Controls } } - protected virtual AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected virtual AutomationPeer OnCreateAutomationPeer() { - return new NoneAutomationPeer(factory, this); + return new NoneAutomationPeer(this); } - internal AutomationPeer GetOrCreateAutomationPeer(IAutomationNodeFactory factory) + internal AutomationPeer GetOrCreateAutomationPeer() { VerifyAccess(); @@ -244,17 +243,10 @@ namespace Avalonia.Controls return _automationPeer; } - _automationPeer = OnCreateAutomationPeer(factory); + _automationPeer = OnCreateAutomationPeer(); return _automationPeer; } - internal void SetAutomationPeer(AutomationPeer peer) - { - if (_automationPeer is object) - throw new InvalidOperationException("Automation peer is already set."); - _automationPeer = peer ?? throw new ArgumentNullException(nameof(peer)); - } - protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index ad5455ba06..aaf93cac26 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -1,5 +1,4 @@ using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Metadata; @@ -127,9 +126,9 @@ namespace Avalonia.Controls } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new ImageAutomationPeer(factory, this); + return new ImageAutomationPeer(this); } } } diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index dbf57b712d..e3bb7abaad 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -5,7 +5,6 @@ using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Generators; using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; @@ -326,9 +325,9 @@ namespace Avalonia.Controls base.OnKeyDown(e); } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new ItemsControlAutomationPeer(factory, this); + return new ItemsControlAutomationPeer(this); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) diff --git a/src/Avalonia.Controls/ListBoxItem.cs b/src/Avalonia.Controls/ListBoxItem.cs index 5599a89b62..66a46cab4a 100644 --- a/src/Avalonia.Controls/ListBoxItem.cs +++ b/src/Avalonia.Controls/ListBoxItem.cs @@ -1,5 +1,4 @@ using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; @@ -36,9 +35,9 @@ namespace Avalonia.Controls set { SetValue(IsSelectedProperty, value); } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new ListItemAutomationPeer(factory, this); + return new ListItemAutomationPeer(this); } } } diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs index d6a0dc6f12..ed70316a53 100644 --- a/src/Avalonia.Controls/Menu.cs +++ b/src/Avalonia.Controls/Menu.cs @@ -1,5 +1,4 @@ using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -94,9 +93,9 @@ namespace Avalonia.Controls } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new MenuAutomationPeer(factory, this); + return new MenuAutomationPeer(this); } } } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index f773f8fe2a..8213cf29dc 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reactive.Linq; using System.Windows.Input; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Generators; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; @@ -495,9 +494,9 @@ namespace Avalonia.Controls } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new MenuItemAutomationPeer(factory, this); + return new MenuItemAutomationPeer(this); } protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 82b6f6666a..5a9e99106b 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -1,6 +1,5 @@ using System; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; namespace Avalonia.Platform { @@ -48,11 +47,6 @@ namespace Avalonia.Platform /// Action Activated { get; set; } - /// - /// Gets or sets a method called when automation is started on the window. - /// - Func AutomationStarted { get; set; } - /// /// Gets the platform window handle. /// diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index 15c44e8c12..39f8cd938c 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -1,6 +1,5 @@ using System; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Input; using Avalonia.Media; using Avalonia.Media.TextFormatting; @@ -108,9 +107,9 @@ namespace Avalonia.Controls.Primitives } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new NoneAutomationPeer(factory, this); + return new NoneAutomationPeer(this); } internal static string RemoveAccessKeyMarker(string text) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index ae72ccfee1..0bfbbf948b 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -3,7 +3,6 @@ using System.ComponentModel; using System.Linq; using System.Reactive.Disposables; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Mixins; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Presenters; @@ -543,9 +542,9 @@ namespace Avalonia.Controls.Primitives } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new PopupAutomationPeer(factory, this); + return new PopupAutomationPeer(this); } private static IDisposable SubscribeToEventHandler(T target, TEventHandler handler, Action subscribe, Action unsubscribe) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 0a0c0f30ad..f7281c107a 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Reactive.Disposables; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Interactivity; using Avalonia.Media; @@ -168,9 +167,9 @@ namespace Avalonia.Controls.Primitives return ClientSize; } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNode node) + protected override AutomationPeer OnCreateAutomationPeer() { - return new PopupRootAutomationPeer(node, this); + return new PopupRootAutomationPeer(this); } } } diff --git a/src/Avalonia.Controls/Primitives/ToggleButton.cs b/src/Avalonia.Controls/Primitives/ToggleButton.cs index 434d34928f..6c21302132 100644 --- a/src/Avalonia.Controls/Primitives/ToggleButton.cs +++ b/src/Avalonia.Controls/Primitives/ToggleButton.cs @@ -1,6 +1,5 @@ using System; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Metadata; using Avalonia.Data; using Avalonia.Interactivity; @@ -171,9 +170,9 @@ namespace Avalonia.Controls.Primitives RaiseEvent(e); } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new ToggleButtonAutomationPeer(factory, this); + return new ToggleButtonAutomationPeer(this); } private void OnIsCheckedChanged(AvaloniaPropertyChangedEventArgs e) diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 7d9675706c..eaa9ba43c3 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -1,7 +1,6 @@ using System; using System.Reactive.Linq; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Input; @@ -712,9 +711,9 @@ namespace Avalonia.Controls _scrollBarExpandSubscription = SubscribeToScrollBars(e); } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new ScrollViewerAutomationPeer(factory, this); + return new ScrollViewerAutomationPeer(this); } private IDisposable SubscribeToScrollBars(TemplateAppliedEventArgs e) diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 698e1734bb..227c387ffb 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -1,7 +1,6 @@ using System; using Avalonia.Collections; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; @@ -211,9 +210,9 @@ namespace Avalonia.Controls _pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel); } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new SliderAutomationPeer(factory, this); + return new SliderAutomationPeer(this); } protected override void OnKeyDown(KeyEventArgs e) diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 63c68b783c..cc9dab986a 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -2,7 +2,6 @@ using System.ComponentModel; using System.Linq; using Avalonia.Collections; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Generators; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; @@ -233,9 +232,9 @@ namespace Avalonia.Controls } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new TabControlAutomationPeer(factory, this); + return new TabControlAutomationPeer(this); } } } diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 90817c655d..846010ce77 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -1,5 +1,4 @@ using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; @@ -83,9 +82,9 @@ namespace Avalonia.Controls } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new TabItemAutomationPeer(factory, this); + return new TabItemAutomationPeer(this); } } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 8b4af15bc8..27db48e3f4 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -1,6 +1,5 @@ using System.Reactive.Linq; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Media; @@ -513,9 +512,9 @@ namespace Avalonia.Controls InvalidateMeasure(); } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new TextBlockAutomationPeer(factory, this); + return new TextBlockAutomationPeer(this); } private static bool IsValidMaxLines(int maxLines) => maxLines >= 0; diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index f28d792dc2..5b334d2029 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -15,7 +15,6 @@ using Avalonia.Layout; using Avalonia.Utilities; using Avalonia.Controls.Metadata; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; namespace Avalonia.Controls { @@ -1055,9 +1054,9 @@ namespace Avalonia.Controls } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) + protected override AutomationPeer OnCreateAutomationPeer() { - return new TextBoxAutomationPeer(factory, this); + return new TextBoxAutomationPeer(this); } protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index a8e76a394b..5d9a0c8eed 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -1,7 +1,5 @@ using System; using System.Reactive.Linq; -using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index bbb0c16450..50227445df 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Interactivity; @@ -1014,9 +1013,9 @@ namespace Avalonia.Controls } } - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNode node) + protected override AutomationPeer OnCreateAutomationPeer() { - return new WindowAutomationPeer(node, this); + return new WindowAutomationPeer(this); } } } diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 905a6b552f..a3fd584447 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Layout; @@ -65,7 +64,6 @@ namespace Avalonia.Controls impl.Activated = HandleActivated; impl.Deactivated = HandleDeactivated; impl.PositionChanged = HandlePositionChanged; - impl.AutomationStarted = HandleAutomationStarted; } /// @@ -263,17 +261,6 @@ namespace Avalonia.Controls /// The actual size of the window. protected virtual Size ArrangeSetBounds(Size size) => size; - protected sealed override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) - { - throw new NotSupportedException( - "Automation peer for window controls must be created by the operating system."); - } - - protected virtual AutomationPeer OnCreateAutomationPeer(IAutomationNode node) - { - throw new NotImplementedException("OnCreateAutomationPeer must be implemented in a derived class."); - } - /// /// Handles a window position change notification from /// . @@ -311,13 +298,6 @@ namespace Avalonia.Controls Deactivated?.Invoke(this, EventArgs.Empty); } - private AutomationPeer HandleAutomationStarted(IAutomationNode node) - { - var peer = OnCreateAutomationPeer(node); - SetAutomationPeer(peer); - return peer; - } - private void IsVisibleChanged(AvaloniaPropertyChangedEventArgs e) { if (!_ignoreVisibilityChange) diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index ec685191d1..12af602f54 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -1,7 +1,4 @@ using System; -using System.Reactive.Disposables; -using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Remote.Server; using Avalonia.Input; @@ -47,7 +44,6 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } - public Func AutomationStarted { get; set; } public Size MaxAutoSizeHint { get; } = new Size(4096, 4096); protected override void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj) diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index a8e0ddad52..8fb2c456b2 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -4,7 +4,6 @@ using System.IO; using System.Reactive.Disposables; using System.Threading.Tasks; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives.PopupPositioning; @@ -41,7 +40,6 @@ namespace Avalonia.DesignerSupport.Remote public Action PositionChanged { get; set; } public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } - public Func AutomationStarted { get; set; } public Action TransparencyLevelChanged { get; set; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 55a1476933..3bf7db34f6 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Primitives.PopupPositioning; @@ -145,7 +144,6 @@ namespace Avalonia.Headless public IScreenImpl Screen { get; } = new HeadlessScreensStub(); public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } - public Func AutomationStarted { get; set; } public void SetTitle(string title) { diff --git a/src/Avalonia.Native/AutomationNode.cs b/src/Avalonia.Native/AutomationNode.cs index f85813a6fb..251b7156f1 100644 --- a/src/Avalonia.Native/AutomationNode.cs +++ b/src/Avalonia.Native/AutomationNode.cs @@ -1,22 +1,19 @@ using Avalonia.Automation; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Native.Interop; #nullable enable namespace Avalonia.Native { - internal class AutomationNode : IRootAutomationNode + internal class AutomationNode { - public AutomationNode(AutomationNodeFactory factory, IAvnAutomationNode native) + public AutomationNode(IAvnAutomationNode native) { Native = native; - Factory = factory; } public IAvnAutomationNode Native { get; } - public IAutomationNodeFactory Factory { get; } public void ChildrenChanged() => Native.ChildrenChanged(); diff --git a/src/Avalonia.Native/AutomationNodeFactory.cs b/src/Avalonia.Native/AutomationNodeFactory.cs deleted file mode 100644 index d40009a855..0000000000 --- a/src/Avalonia.Native/AutomationNodeFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; -using Avalonia.Native.Interop; - -namespace Avalonia.Native -{ - internal class AutomationNodeFactory : IAutomationNodeFactory - { - private static AutomationNodeFactory _instance; - private readonly IAvaloniaNativeFactory _native; - - public static AutomationNodeFactory GetInstance(IAvaloniaNativeFactory native) - { - return _instance ??= new AutomationNodeFactory(native); - } - - private AutomationNodeFactory(IAvaloniaNativeFactory native) => _native = native; - - public IAutomationNode CreateNode(AutomationPeer peer) - { - return new AutomationNode(this, _native.CreateAutomationNode(new AvnAutomationPeer(peer))); - } - } -} diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index dee7658d33..b50b0d5111 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Avalonia.Automation; @@ -15,7 +16,7 @@ namespace Avalonia.Native public AvnAutomationPeer(AutomationPeer inner) => _inner = inner; - public IAvnAutomationNode Node => ((AutomationNode)_inner.Node).Native; + public IAvnAutomationNode Node => throw new NotImplementedException(); public IAvnString? AcceleratorKey => _inner.GetAcceleratorKey().ToAvnString(); public IAvnString? AccessKey => _inner.GetAccessKey().ToAvnString(); public AvnAutomationControlType AutomationControlType => (AvnAutomationControlType)_inner.GetAutomationControlType(); diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 529e90ab49..d055e4a1c4 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Platform.Surfaces; @@ -63,9 +62,7 @@ namespace Avalonia.Native private GlPlatformSurface _glSurface; private NativeControlHostImpl _nativeControlHost; private IGlContext _glContext; - private IAutomationNode _automationNode; private AvnAutomationPeer _automationPeer; - private Func _automationStarted; internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOpenGlInterface glFeature) @@ -265,8 +262,6 @@ namespace Avalonia.Native return (AvnDragDropEffects)args.Effects; } } - - public IAvnAutomationPeer AutomationStarted(IAvnAutomationNode node) => _parent.HandleAutomationStarted(node); } public void Activate() @@ -489,38 +484,5 @@ namespace Avalonia.Native public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0); public IPlatformHandle Handle { get; private set; } - - public Func AutomationStarted - { - get => _automationStarted; - set - { - _automationStarted = value; - - // We've already received an AutomationStarted event, but the Window/PopupRoot wasn't initialized. - // Now it is, so notify it and store the automation peer for the next time the OS invokes an a11y - // query. - if (value is object && _automationNode is object) - _automationPeer = new AvnAutomationPeer(_automationStarted.Invoke(_automationNode)); - } - } - - private AvnAutomationPeer HandleAutomationStarted(IAvnAutomationNode node) - { - if (_automationPeer is object) - return _automationPeer; - - var factory = AutomationNodeFactory.GetInstance(_factory); - _automationNode = new AutomationNode(factory, node); - - // If automation is started during platform window creation we don't yet have a Window/PopupRoot - // control to notify. In this case we'll notify them when AutomationStarted gets set. We can safely - // return null here because the peer isn't actually needed at this point and will be re-queried the next - // time it's needed. - if (AutomationStarted is null) - return null; - - return _automationPeer = new AvnAutomationPeer(AutomationStarted(_automationNode)); - } } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index da1bd7decd..3aec9d493c 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -569,7 +569,6 @@ interface IAvnWindowBaseEvents : IUnknown AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position, AvnInputModifiers modifiers, AvnDragDropEffects effects, IAvnClipboard* clipboard, [intptr]void* dataObjectHandle); - IAvnAutomationPeer* AutomationStarted(IAvnAutomationNode* node); } [uuid(1ae178ee-1fcc-447f-b6dd-b7bb727f934c)] diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 6783eada1f..938a7f9b5d 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -7,7 +7,6 @@ using System.Reactive.Disposables; using System.Text; using System.Threading.Tasks; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives.PopupPositioning; @@ -1163,6 +1162,5 @@ namespace Avalonia.X11 public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8); public bool NeedsManagedDecorations => false; - public Func AutomationStarted { get; set; } } } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs index 17634ae805..c41ace4aa8 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs @@ -20,14 +20,14 @@ namespace Avalonia.Win32.Automation get { var peer = InvokeSync(x => x.SelectionContainer); - return (peer as AutomationPeer)?.Node as AutomationNode; + return GetOrCreate(peer as AutomationPeer); } } public UIA.IRawElementProviderSimple[] GetSelection() { var peers = InvokeSync>(x => x.GetSelection()); - return peers?.Select(x => (UIA.IRawElementProviderSimple)x.Node).ToArray() ?? + return peers?.Select(x => (UIA.IRawElementProviderSimple)GetOrCreate(x)!).ToArray() ?? Array.Empty(); } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 79ef5bc719..ebaf096b78 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -4,10 +4,10 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Avalonia.Automation; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Threading; using Avalonia.Win32.Interop.Automation; using AAP = Avalonia.Automation.Provider; @@ -18,7 +18,6 @@ namespace Avalonia.Win32.Automation { [ComVisible(true)] internal partial class AutomationNode : MarshalByRefObject, - IAutomationNode, IRawElementProviderSimple, IRawElementProviderSimple2, IRawElementProviderFragment, @@ -46,6 +45,9 @@ namespace Avalonia.Win32.Automation { SelectionPatternIdentifiers.SelectionProperty, UiaPropertyId.SelectionSelection }, }; + private static ConditionalWeakTable s_nodes = + new ConditionalWeakTable(); + private readonly int[] _runtimeId; private int _raiseFocusChanged; private int _raisePropertyChanged; @@ -54,22 +56,16 @@ namespace Avalonia.Win32.Automation { _runtimeId = new int[] { 3, GetHashCode() }; Peer = peer; - } - - protected AutomationNode(Func peerGetter) - { - _runtimeId = new int[] { 3, GetHashCode() }; - Peer = peerGetter(this); + s_nodes.Add(peer, this); } public AutomationPeer Peer { get; protected set; } - public IAutomationNodeFactory Factory => AutomationNodeFactory.Instance; public Rect BoundingRectangle { get => InvokeSync(() => { - if (GetRoot()?.Node is RootAutomationNode root) + if (GetRoot() is RootAutomationNode root) return root.ToScreen(Peer.GetBoundingRectangle()); return default; }); @@ -77,7 +73,7 @@ namespace Avalonia.Win32.Automation public virtual IRawElementProviderFragmentRoot? FragmentRoot { - get => InvokeSync(() => GetRoot())?.Node as IRawElementProviderFragmentRoot; + get => InvokeSync(() => GetRoot()) as IRawElementProviderFragmentRoot; } public virtual IRawElementProviderSimple? HostRawElementProvider => null; @@ -147,7 +143,7 @@ namespace Avalonia.Win32.Automation public virtual IRawElementProviderFragment? Navigate(NavigateDirection direction) { - IAutomationNode? GetSibling(int direction) + AutomationNode? GetSibling(int direction) { var children = Peer.GetParent()?.GetChildren(); @@ -157,7 +153,7 @@ namespace Avalonia.Win32.Automation { var j = i + direction; if (j >= 0 && j < children.Count) - return children[j].Node; + return GetOrCreate(children[j]); } } @@ -173,11 +169,11 @@ namespace Avalonia.Win32.Automation { return direction switch { - NavigateDirection.Parent => Peer.GetParent()?.Node, + NavigateDirection.Parent => GetOrCreate(Peer.GetParent()), NavigateDirection.NextSibling => GetSibling(1), NavigateDirection.PreviousSibling => GetSibling(-1), - NavigateDirection.FirstChild => Peer.GetChildren().FirstOrDefault()?.Node, - NavigateDirection.LastChild => Peer.GetChildren().LastOrDefault()?.Node, + NavigateDirection.FirstChild => GetOrCreate(Peer.GetChildren().FirstOrDefault()), + NavigateDirection.LastChild => GetOrCreate(Peer.GetChildren().LastOrDefault()), _ => null, }; }) as IRawElementProviderFragment; @@ -185,6 +181,13 @@ namespace Avalonia.Win32.Automation public void SetFocus() => InvokeSync(() => Peer.SetFocus()); + public static AutomationNode? GetOrCreate(AutomationPeer? peer) + { + if (peer is null) + return null; + return s_nodes.GetValue(peer, x => new AutomationNode(x)); + } + IRawElementProviderSimple[]? IRawElementProviderFragment.GetEmbeddedFragmentRoots() => null; void IRawElementProviderSimple2.ShowContextMenu() => InvokeSync(() => Peer.ShowContextMenu()); void IInvokeProvider.Invoke() => InvokeSync((AAP.IInvokeProvider x) => x.Invoke()); @@ -275,7 +278,7 @@ namespace Avalonia.Win32.Automation } } - private AutomationPeer GetRoot() + private AutomationNode? GetRoot() { Dispatcher.UIThread.VerifyAccess(); @@ -288,7 +291,7 @@ namespace Avalonia.Win32.Automation parent = peer.GetParent(); } - return peer; + return peer is object ? GetOrCreate(peer) : null; } private static UiaControlTypeId ToUiaControlType(AutomationControlType role) diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs deleted file mode 100644 index a7ee0e192f..0000000000 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNodeFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; -using Avalonia.Threading; - -#nullable enable - -namespace Avalonia.Win32.Automation -{ - internal class AutomationNodeFactory : IAutomationNodeFactory - { - public static readonly AutomationNodeFactory Instance = new AutomationNodeFactory(); - - public IAutomationNode CreateNode(AutomationPeer peer) - { - Dispatcher.UIThread.VerifyAccess(); - return new AutomationNode(peer); - } - } -} diff --git a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs index dd2665a624..6728aa4283 100644 --- a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs @@ -1,7 +1,6 @@ using System; using System.Runtime.InteropServices; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Automation.Provider; using Avalonia.Win32.Interop.Automation; @@ -10,12 +9,12 @@ using Avalonia.Win32.Interop.Automation; namespace Avalonia.Win32.Automation { internal class RootAutomationNode : AutomationNode, - IRawElementProviderFragmentRoot, - IRootAutomationNode + IRawElementProviderFragmentRoot { - public RootAutomationNode(Func peerGetter) - : base(peerGetter) + public RootAutomationNode(AutomationPeer peer) + : base(peer) { + ((IRootProvider)peer).FocusChanged += FocusChanged; } public override IRawElementProviderFragmentRoot? FragmentRoot => this; @@ -30,20 +29,19 @@ namespace Avalonia.Win32.Automation var p = WindowImpl.PointToClient(new PixelPoint((int)x, (int)y)); var peer = (WindowBaseAutomationPeer)Peer; var found = InvokeSync(() => peer.GetPeerFromPoint(p)); - var result = found?.Node as IRawElementProviderFragment; + var result = GetOrCreate(found) as IRawElementProviderFragment; return result; } public IRawElementProviderFragment? GetFocus() { var focus = InvokeSync(() => Peer.GetFocus()); - return (AutomationNode?)focus?.Node; + return GetOrCreate(focus); } - public void FocusChanged(AutomationPeer? focus) + public void FocusChanged(object sender, EventArgs e) { - var node = focus?.Node as AutomationNode; - RaiseFocusChanged(node); + RaiseFocusChanged(GetOrCreate(Peer.GetFocus())); } public Rect ToScreen(Rect rect) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 8803901bc5..fcc0ac1e32 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; +using Avalonia.Automation.Peers; using Avalonia.Controls; using Avalonia.Controls.Remote; using Avalonia.Input; @@ -483,16 +484,14 @@ namespace Avalonia.Win32 case WindowsMessage.WM_GETOBJECT: if ((long)lParam == UiaRootObjectId) { - if (_automationNode is null && AutomationStarted is object) + if (_automationNode is null) { - _automationNode = new RootAutomationNode(AutomationStarted); + var peer = ControlAutomationPeer.CreatePeerForElement((Control)_owner); + _automationNode = new RootAutomationNode(peer); } - if (_automationNode is object) - { - var r = UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, _automationNode); - return r; - } + var r = UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, _automationNode); + return r; } break; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 24bf409f58..ffad00cf95 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -4,7 +4,6 @@ using System.ComponentModel; using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Raw; @@ -177,7 +176,6 @@ namespace Avalonia.Win32 public Action LostFocus { get; set; } public Action TransparencyLevelChanged { get; set; } - public Func AutomationStarted { get; set; } public Thickness BorderThickness { diff --git a/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs b/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs index 250cad4e25..3a4bbd9928 100644 --- a/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs @@ -1,253 +1,253 @@ -using System.Linq; -using Avalonia.Automation.Peers; -using Avalonia.Automation.Platform; -using Avalonia.Automation.Provider; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Templates; -using Avalonia.Platform; -using Avalonia.UnitTests; -using Avalonia.VisualTree; -using Moq; -using Xunit; - -namespace Avalonia.Controls.UnitTests.Automation -{ - public class ControlAutomationPeerTests - { - private static Mock _factory; - - public ControlAutomationPeerTests() - { - _factory = new Mock(); - _factory.Setup(x => x.CreateNode(It.IsAny())) - .Returns(() => Mock.Of(x => x.Factory == _factory)); - } - - public class Children - { - [Fact] - public void Creates_Children_For_Controls_In_Visual_Tree() - { - var panel = new Panel - { - Children = - { - new Border(), - new Border(), - }, - }; - - var factory = CreateFactory(); - var target = CreatePeer(factory, panel); - - Assert.Equal( - panel.GetVisualChildren(), - target.GetChildren().Cast().Select(x => x.Owner)); - } - - [Fact] - public void Creates_Children_when_Controls_Attached_To_Visual_Tree() - { - var contentControl = new ContentControl - { - Template = new FuncControlTemplate((o, ns) => - new ContentPresenter - { - Name = "PART_ContentPresenter", - [!ContentPresenter.ContentProperty] = o[!ContentControl.ContentProperty], - }), - Content = new Border(), - }; - - var factory = CreateFactory(); - var target = CreatePeer(factory, contentControl); - - Assert.Empty(target.GetChildren()); - - contentControl.Measure(Size.Infinity); - - Assert.Equal(1, target.GetChildren().Count); - } - - [Fact] - public void Updates_Children_When_VisualChildren_Added() - { - var panel = new Panel - { - Children = - { - new Border(), - new Border(), - }, - }; - - var factory = CreateFactory(); - var target = CreatePeer(factory, panel); - var children = target.GetChildren(); - - Assert.Equal(2, children.Count); - - panel.Children.Add(new Decorator()); - - children = target.GetChildren(); - Assert.Equal(3, children.Count); - } - - [Fact] - public void Updates_Children_When_VisualChildren_Removed() - { - var panel = new Panel - { - Children = - { - new Border(), - new Border(), - }, - }; - - var factory = CreateFactory(); - var target = CreatePeer(factory, panel); - var children = target.GetChildren(); - - Assert.Equal(2, children.Count); - - panel.Children.RemoveAt(1); - - children = target.GetChildren(); - Assert.Equal(1, children.Count); - } - - [Fact] - public void Updates_Children_When_Visibility_Changes() - { - var panel = new Panel - { - Children = - { - new Border(), - new Border(), - }, - }; - - var factory = CreateFactory(); - var target = CreatePeer(factory, panel); - var children = target.GetChildren(); - - Assert.Equal(2, children.Count); - - panel.Children[1].IsVisible = false; - children = target.GetChildren(); - Assert.Equal(1, children.Count); - - panel.Children[1].IsVisible = true; - children = target.GetChildren(); - Assert.Equal(2, children.Count); - } - } - - public class Parent - { - [Fact] - public void Connects_Peer_To_Tree_When_GetParent_Called() - { - var border = new Border(); - var tree = new Decorator - { - Child = new Decorator - { - Child = border, - } - }; - - var factory = CreateFactory(); - - // We're accessing Border directly without going via its ancestors. Because the tree - // is built lazily, ensure that calling GetParent causes the ancestor tree to be built. - var target = CreatePeer(factory, border); - - var parentPeer = Assert.IsAssignableFrom(target.GetParent()); - Assert.Same(border.GetVisualParent(), parentPeer.Owner); - } - - [Fact] - public void Parent_Updated_When_Moved_To_Separate_Visual_Tree() - { - var border = new Border(); - var root1 = new Decorator { Child = border }; - var root2 = new Decorator(); - var factory = CreateFactory(); - var target = CreatePeer(factory, border); - - var parentPeer = Assert.IsAssignableFrom(target.GetParent()); - Assert.Same(root1, parentPeer.Owner); - - root1.Child = null; - - Assert.Null(target.GetParent()); - - root2.Child = border; - - parentPeer = Assert.IsAssignableFrom(target.GetParent()); - Assert.Same(root2, parentPeer.Owner); - } - } - - private static IAutomationNodeFactory CreateFactory() - { - var factory = new Mock(); - factory.Setup(x => x.CreateNode(It.IsAny())) - .Returns(() => Mock.Of(x => x.Factory == factory.Object)); - return factory.Object; - } - - private static AutomationPeer CreatePeer(IAutomationNodeFactory factory, Control control) - { - return ControlAutomationPeer.GetOrCreatePeer(factory, control); - } - - private class TestControl : Control - { - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) - { - return new TestAutomationPeer(factory, this); - } - } - - private class AutomationTestRoot : TestRoot - { - protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) - { - return new TestRootAutomationPeer(factory, this); - } - } - - private class TestAutomationPeer : ControlAutomationPeer - { - public TestAutomationPeer(IAutomationNodeFactory factory, Control owner) - : base(factory, owner) - { - } - } - - private class TestRootAutomationPeer : ControlAutomationPeer, IRootProvider - { - public TestRootAutomationPeer(IAutomationNodeFactory factory, Control owner) - : base(factory, owner) - { - } - - public ITopLevelImpl PlatformImpl => throw new System.NotImplementedException(); - - public AutomationPeer GetFocus() - { - throw new System.NotImplementedException(); - } - - public AutomationPeer GetPeerFromPoint(Point p) - { - throw new System.NotImplementedException(); - } - } - } -} +////using System.Linq; +////using Avalonia.Automation.Peers; +////using Avalonia.Automation.Platform; +////using Avalonia.Automation.Provider; +////using Avalonia.Controls.Presenters; +////using Avalonia.Controls.Primitives; +////using Avalonia.Controls.Templates; +////using Avalonia.Platform; +////using Avalonia.UnitTests; +////using Avalonia.VisualTree; +////using Moq; +////using Xunit; + +////namespace Avalonia.Controls.UnitTests.Automation +////{ +//// public class ControlAutomationPeerTests +//// { +//// private static Mock _factory; + +//// public ControlAutomationPeerTests() +//// { +//// _factory = new Mock(); +//// _factory.Setup(x => x.CreateNode(It.IsAny())) +//// .Returns(() => Mock.Of(x => x.Factory == _factory)); +//// } + +//// public class Children +//// { +//// [Fact] +//// public void Creates_Children_For_Controls_In_Visual_Tree() +//// { +//// var panel = new Panel +//// { +//// Children = +//// { +//// new Border(), +//// new Border(), +//// }, +//// }; + +//// var factory = CreateFactory(); +//// var target = CreatePeer(factory, panel); + +//// Assert.Equal( +//// panel.GetVisualChildren(), +//// target.GetChildren().Cast().Select(x => x.Owner)); +//// } + +//// [Fact] +//// public void Creates_Children_when_Controls_Attached_To_Visual_Tree() +//// { +//// var contentControl = new ContentControl +//// { +//// Template = new FuncControlTemplate((o, ns) => +//// new ContentPresenter +//// { +//// Name = "PART_ContentPresenter", +//// [!ContentPresenter.ContentProperty] = o[!ContentControl.ContentProperty], +//// }), +//// Content = new Border(), +//// }; + +//// var factory = CreateFactory(); +//// var target = CreatePeer(factory, contentControl); + +//// Assert.Empty(target.GetChildren()); + +//// contentControl.Measure(Size.Infinity); + +//// Assert.Equal(1, target.GetChildren().Count); +//// } + +//// [Fact] +//// public void Updates_Children_When_VisualChildren_Added() +//// { +//// var panel = new Panel +//// { +//// Children = +//// { +//// new Border(), +//// new Border(), +//// }, +//// }; + +//// var factory = CreateFactory(); +//// var target = CreatePeer(factory, panel); +//// var children = target.GetChildren(); + +//// Assert.Equal(2, children.Count); + +//// panel.Children.Add(new Decorator()); + +//// children = target.GetChildren(); +//// Assert.Equal(3, children.Count); +//// } + +//// [Fact] +//// public void Updates_Children_When_VisualChildren_Removed() +//// { +//// var panel = new Panel +//// { +//// Children = +//// { +//// new Border(), +//// new Border(), +//// }, +//// }; + +//// var factory = CreateFactory(); +//// var target = CreatePeer(factory, panel); +//// var children = target.GetChildren(); + +//// Assert.Equal(2, children.Count); + +//// panel.Children.RemoveAt(1); + +//// children = target.GetChildren(); +//// Assert.Equal(1, children.Count); +//// } + +//// [Fact] +//// public void Updates_Children_When_Visibility_Changes() +//// { +//// var panel = new Panel +//// { +//// Children = +//// { +//// new Border(), +//// new Border(), +//// }, +//// }; + +//// var factory = CreateFactory(); +//// var target = CreatePeer(factory, panel); +//// var children = target.GetChildren(); + +//// Assert.Equal(2, children.Count); + +//// panel.Children[1].IsVisible = false; +//// children = target.GetChildren(); +//// Assert.Equal(1, children.Count); + +//// panel.Children[1].IsVisible = true; +//// children = target.GetChildren(); +//// Assert.Equal(2, children.Count); +//// } +//// } + +//// public class Parent +//// { +//// [Fact] +//// public void Connects_Peer_To_Tree_When_GetParent_Called() +//// { +//// var border = new Border(); +//// var tree = new Decorator +//// { +//// Child = new Decorator +//// { +//// Child = border, +//// } +//// }; + +//// var factory = CreateFactory(); + +//// // We're accessing Border directly without going via its ancestors. Because the tree +//// // is built lazily, ensure that calling GetParent causes the ancestor tree to be built. +//// var target = CreatePeer(factory, border); + +//// var parentPeer = Assert.IsAssignableFrom(target.GetParent()); +//// Assert.Same(border.GetVisualParent(), parentPeer.Owner); +//// } + +//// [Fact] +//// public void Parent_Updated_When_Moved_To_Separate_Visual_Tree() +//// { +//// var border = new Border(); +//// var root1 = new Decorator { Child = border }; +//// var root2 = new Decorator(); +//// var factory = CreateFactory(); +//// var target = CreatePeer(factory, border); + +//// var parentPeer = Assert.IsAssignableFrom(target.GetParent()); +//// Assert.Same(root1, parentPeer.Owner); + +//// root1.Child = null; + +//// Assert.Null(target.GetParent()); + +//// root2.Child = border; + +//// parentPeer = Assert.IsAssignableFrom(target.GetParent()); +//// Assert.Same(root2, parentPeer.Owner); +//// } +//// } + +//// private static IAutomationNodeFactory CreateFactory() +//// { +//// var factory = new Mock(); +//// factory.Setup(x => x.CreateNode(It.IsAny())) +//// .Returns(() => Mock.Of(x => x.Factory == factory.Object)); +//// return factory.Object; +//// } + +//// private static AutomationPeer CreatePeer(IAutomationNodeFactory factory, Control control) +//// { +//// return ControlAutomationPeer.GetOrCreatePeer(factory, control); +//// } + +//// private class TestControl : Control +//// { +//// protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) +//// { +//// return new TestAutomationPeer(factory, this); +//// } +//// } + +//// private class AutomationTestRoot : TestRoot +//// { +//// protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) +//// { +//// return new TestRootAutomationPeer(factory, this); +//// } +//// } + +//// private class TestAutomationPeer : ControlAutomationPeer +//// { +//// public TestAutomationPeer(IAutomationNodeFactory factory, Control owner) +//// : base(factory, owner) +//// { +//// } +//// } + +//// private class TestRootAutomationPeer : ControlAutomationPeer, IRootProvider +//// { +//// public TestRootAutomationPeer(IAutomationNodeFactory factory, Control owner) +//// : base(factory, owner) +//// { +//// } + +//// public ITopLevelImpl PlatformImpl => throw new System.NotImplementedException(); + +//// public AutomationPeer GetFocus() +//// { +//// throw new System.NotImplementedException(); +//// } + +//// public AutomationPeer GetPeerFromPoint(Point p) +//// { +//// throw new System.NotImplementedException(); +//// } +//// } +//// } +////} From 8ffbbfb7fb59278c590fe0227cae336239b0c426 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 2 Oct 2021 22:40:22 +0200 Subject: [PATCH 042/822] Create root automation nodes like others. --- .../Avalonia.Win32/Automation/AutomationNode.cs | 16 ++++++++-------- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 14 ++++---------- src/Windows/Avalonia.Win32/WindowImpl.cs | 1 - 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index ebaf096b78..1b37664e4b 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -160,11 +160,6 @@ namespace Avalonia.Win32.Automation return null; } - if (Peer.GetType().Name == "PopupAutomationPeer") - { - System.Diagnostics.Debug.WriteLine("Popup automation node navigate " + direction); - } - return InvokeSync(() => { return direction switch @@ -183,11 +178,11 @@ namespace Avalonia.Win32.Automation public static AutomationNode? GetOrCreate(AutomationPeer? peer) { - if (peer is null) - return null; - return s_nodes.GetValue(peer, x => new AutomationNode(x)); + return peer is null ? null : s_nodes.GetValue(peer, Create); } + public static void Release(AutomationPeer peer) => s_nodes.Remove(peer); + IRawElementProviderSimple[]? IRawElementProviderFragment.GetEmbeddedFragmentRoots() => null; void IRawElementProviderSimple2.ShowContextMenu() => InvokeSync(() => Peer.ShowContextMenu()); void IInvokeProvider.Invoke() => InvokeSync((AAP.IInvokeProvider x) => x.Invoke()); @@ -294,6 +289,11 @@ namespace Avalonia.Win32.Automation return peer is object ? GetOrCreate(peer) : null; } + private static AutomationNode Create(AutomationPeer peer) + { + return peer is AAP.IRootProvider ? new RootAutomationNode(peer) : new AutomationNode(peer); + } + private static UiaControlTypeId ToUiaControlType(AutomationControlType role) { return role switch diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index fcc0ac1e32..67c4aabafe 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -79,8 +79,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_DESTROY: { - if (_automationNode is object) - UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, IntPtr.Zero, IntPtr.Zero, null); + UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, IntPtr.Zero, IntPtr.Zero, null); //Window doesn't exist anymore _hwnd = IntPtr.Zero; @@ -484,14 +483,9 @@ namespace Avalonia.Win32 case WindowsMessage.WM_GETOBJECT: if ((long)lParam == UiaRootObjectId) { - if (_automationNode is null) - { - var peer = ControlAutomationPeer.CreatePeerForElement((Control)_owner); - _automationNode = new RootAutomationNode(peer); - } - - var r = UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, _automationNode); - return r; + var peer = ControlAutomationPeer.CreatePeerForElement((Control)_owner); + var node = AutomationNode.GetOrCreate(peer); + return UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, node); } break; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ffad00cf95..f8c6c29fa1 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -87,7 +87,6 @@ namespace Avalonia.Win32 private POINT _maxTrackSize; private WindowImpl _parent; private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default; - private AutomationNode _automationNode; private bool _isCloseRequested; private bool _shown; private bool _hiddenWindowIsParent; From ae8905776710351c361851bb89a202c0498d4bd6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 2 Oct 2021 22:44:39 +0200 Subject: [PATCH 043/822] Use non-obsolete interface. And remove debug code. --- .../Automation/Peers/PopupAutomationPeer.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs index e6f827b942..9c24f855f8 100644 --- a/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/PopupAutomationPeer.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using Avalonia.Controls; +using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; -using Avalonia.VisualTree; #nullable enable @@ -19,9 +19,8 @@ namespace Avalonia.Automation.Peers protected override IReadOnlyList? GetChildrenCore() { - var host = (IVisualTreeHost)Owner; - System.Diagnostics.Debug.WriteLine($"Popup children='{host}'"); - return host.Root is Control c ? new[] { GetOrCreate(c) } : null; + var host = (IPopupHostProvider)Owner; + return host.PopupHost is Control c ? new[] { GetOrCreate(c) } : null; } protected override bool IsContentElementCore() => false; @@ -44,7 +43,7 @@ namespace Avalonia.Automation.Peers private AutomationPeer? GetPopupRoot() { - var popupRoot = ((IVisualTreeHost)Owner).Root as Control; + var popupRoot = ((IPopupHostProvider)Owner).PopupHost as Control; return popupRoot is object ? GetOrCreate(popupRoot) : null; } } From 26419fc345a85daad143451b346fdbbf2517a326 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 3 Oct 2021 21:13:47 +0200 Subject: [PATCH 044/822] Added AutomationControlType.None. --- src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs | 1 + src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs | 2 +- src/Windows/Avalonia.Win32/Automation/AutomationNode.cs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index 757fe9e158..f1276c68da 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -7,6 +7,7 @@ namespace Avalonia.Automation.Peers { public enum AutomationControlType { + None, Button, Calendar, CheckBox, diff --git a/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs index 2963144ebf..a995886179 100644 --- a/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/NoneAutomationPeer.cs @@ -17,7 +17,7 @@ namespace Avalonia.Automation.Peers protected override AutomationControlType GetAutomationControlTypeCore() { - return AutomationControlType.Group; + return AutomationControlType.None; } protected override bool IsContentElementCore() => false; diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 1b37664e4b..6630657257 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -298,6 +298,7 @@ namespace Avalonia.Win32.Automation { return role switch { + AutomationControlType.None => UiaControlTypeId.Group, AutomationControlType.Button => UiaControlTypeId.Button, AutomationControlType.Calendar => UiaControlTypeId.Calendar, AutomationControlType.CheckBox => UiaControlTypeId.CheckBox, From a5c28de7976cb7a460592fbfce444b3092fc0c56 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 3 Oct 2021 21:17:59 +0200 Subject: [PATCH 045/822] Reinstate ControlAutomationPeerTests. --- .../Automation/ControlAutomationPeerTests.cs | 482 +++++++++--------- 1 file changed, 229 insertions(+), 253 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs b/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs index 3a4bbd9928..b128e6d83a 100644 --- a/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Automation/ControlAutomationPeerTests.cs @@ -1,253 +1,229 @@ -////using System.Linq; -////using Avalonia.Automation.Peers; -////using Avalonia.Automation.Platform; -////using Avalonia.Automation.Provider; -////using Avalonia.Controls.Presenters; -////using Avalonia.Controls.Primitives; -////using Avalonia.Controls.Templates; -////using Avalonia.Platform; -////using Avalonia.UnitTests; -////using Avalonia.VisualTree; -////using Moq; -////using Xunit; - -////namespace Avalonia.Controls.UnitTests.Automation -////{ -//// public class ControlAutomationPeerTests -//// { -//// private static Mock _factory; - -//// public ControlAutomationPeerTests() -//// { -//// _factory = new Mock(); -//// _factory.Setup(x => x.CreateNode(It.IsAny())) -//// .Returns(() => Mock.Of(x => x.Factory == _factory)); -//// } - -//// public class Children -//// { -//// [Fact] -//// public void Creates_Children_For_Controls_In_Visual_Tree() -//// { -//// var panel = new Panel -//// { -//// Children = -//// { -//// new Border(), -//// new Border(), -//// }, -//// }; - -//// var factory = CreateFactory(); -//// var target = CreatePeer(factory, panel); - -//// Assert.Equal( -//// panel.GetVisualChildren(), -//// target.GetChildren().Cast().Select(x => x.Owner)); -//// } - -//// [Fact] -//// public void Creates_Children_when_Controls_Attached_To_Visual_Tree() -//// { -//// var contentControl = new ContentControl -//// { -//// Template = new FuncControlTemplate((o, ns) => -//// new ContentPresenter -//// { -//// Name = "PART_ContentPresenter", -//// [!ContentPresenter.ContentProperty] = o[!ContentControl.ContentProperty], -//// }), -//// Content = new Border(), -//// }; - -//// var factory = CreateFactory(); -//// var target = CreatePeer(factory, contentControl); - -//// Assert.Empty(target.GetChildren()); - -//// contentControl.Measure(Size.Infinity); - -//// Assert.Equal(1, target.GetChildren().Count); -//// } - -//// [Fact] -//// public void Updates_Children_When_VisualChildren_Added() -//// { -//// var panel = new Panel -//// { -//// Children = -//// { -//// new Border(), -//// new Border(), -//// }, -//// }; - -//// var factory = CreateFactory(); -//// var target = CreatePeer(factory, panel); -//// var children = target.GetChildren(); - -//// Assert.Equal(2, children.Count); - -//// panel.Children.Add(new Decorator()); - -//// children = target.GetChildren(); -//// Assert.Equal(3, children.Count); -//// } - -//// [Fact] -//// public void Updates_Children_When_VisualChildren_Removed() -//// { -//// var panel = new Panel -//// { -//// Children = -//// { -//// new Border(), -//// new Border(), -//// }, -//// }; - -//// var factory = CreateFactory(); -//// var target = CreatePeer(factory, panel); -//// var children = target.GetChildren(); - -//// Assert.Equal(2, children.Count); - -//// panel.Children.RemoveAt(1); - -//// children = target.GetChildren(); -//// Assert.Equal(1, children.Count); -//// } - -//// [Fact] -//// public void Updates_Children_When_Visibility_Changes() -//// { -//// var panel = new Panel -//// { -//// Children = -//// { -//// new Border(), -//// new Border(), -//// }, -//// }; - -//// var factory = CreateFactory(); -//// var target = CreatePeer(factory, panel); -//// var children = target.GetChildren(); - -//// Assert.Equal(2, children.Count); - -//// panel.Children[1].IsVisible = false; -//// children = target.GetChildren(); -//// Assert.Equal(1, children.Count); - -//// panel.Children[1].IsVisible = true; -//// children = target.GetChildren(); -//// Assert.Equal(2, children.Count); -//// } -//// } - -//// public class Parent -//// { -//// [Fact] -//// public void Connects_Peer_To_Tree_When_GetParent_Called() -//// { -//// var border = new Border(); -//// var tree = new Decorator -//// { -//// Child = new Decorator -//// { -//// Child = border, -//// } -//// }; - -//// var factory = CreateFactory(); - -//// // We're accessing Border directly without going via its ancestors. Because the tree -//// // is built lazily, ensure that calling GetParent causes the ancestor tree to be built. -//// var target = CreatePeer(factory, border); - -//// var parentPeer = Assert.IsAssignableFrom(target.GetParent()); -//// Assert.Same(border.GetVisualParent(), parentPeer.Owner); -//// } - -//// [Fact] -//// public void Parent_Updated_When_Moved_To_Separate_Visual_Tree() -//// { -//// var border = new Border(); -//// var root1 = new Decorator { Child = border }; -//// var root2 = new Decorator(); -//// var factory = CreateFactory(); -//// var target = CreatePeer(factory, border); - -//// var parentPeer = Assert.IsAssignableFrom(target.GetParent()); -//// Assert.Same(root1, parentPeer.Owner); - -//// root1.Child = null; - -//// Assert.Null(target.GetParent()); - -//// root2.Child = border; - -//// parentPeer = Assert.IsAssignableFrom(target.GetParent()); -//// Assert.Same(root2, parentPeer.Owner); -//// } -//// } - -//// private static IAutomationNodeFactory CreateFactory() -//// { -//// var factory = new Mock(); -//// factory.Setup(x => x.CreateNode(It.IsAny())) -//// .Returns(() => Mock.Of(x => x.Factory == factory.Object)); -//// return factory.Object; -//// } - -//// private static AutomationPeer CreatePeer(IAutomationNodeFactory factory, Control control) -//// { -//// return ControlAutomationPeer.GetOrCreatePeer(factory, control); -//// } - -//// private class TestControl : Control -//// { -//// protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) -//// { -//// return new TestAutomationPeer(factory, this); -//// } -//// } - -//// private class AutomationTestRoot : TestRoot -//// { -//// protected override AutomationPeer OnCreateAutomationPeer(IAutomationNodeFactory factory) -//// { -//// return new TestRootAutomationPeer(factory, this); -//// } -//// } - -//// private class TestAutomationPeer : ControlAutomationPeer -//// { -//// public TestAutomationPeer(IAutomationNodeFactory factory, Control owner) -//// : base(factory, owner) -//// { -//// } -//// } - -//// private class TestRootAutomationPeer : ControlAutomationPeer, IRootProvider -//// { -//// public TestRootAutomationPeer(IAutomationNodeFactory factory, Control owner) -//// : base(factory, owner) -//// { -//// } - -//// public ITopLevelImpl PlatformImpl => throw new System.NotImplementedException(); - -//// public AutomationPeer GetFocus() -//// { -//// throw new System.NotImplementedException(); -//// } - -//// public AutomationPeer GetPeerFromPoint(Point p) -//// { -//// throw new System.NotImplementedException(); -//// } -//// } -//// } -////} +using System; +using System.Linq; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Provider; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Platform; +using Avalonia.UnitTests; +using Avalonia.VisualTree; +using Xunit; + +#nullable enable + +namespace Avalonia.Controls.UnitTests.Automation +{ + public class ControlAutomationPeerTests + { + public class Children + { + [Fact] + public void Creates_Children_For_Controls_In_Visual_Tree() + { + var panel = new Panel + { + Children = + { + new Border(), + new Border(), + }, + }; + + var target = CreatePeer(panel); + + Assert.Equal( + panel.GetVisualChildren(), + target.GetChildren().Cast().Select(x => x.Owner)); + } + + [Fact] + public void Creates_Children_when_Controls_Attached_To_Visual_Tree() + { + var contentControl = new ContentControl + { + Template = new FuncControlTemplate((o, ns) => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = o[!ContentControl.ContentProperty], + }), + Content = new Border(), + }; + + var target = CreatePeer(contentControl); + + Assert.Empty(target.GetChildren()); + + contentControl.Measure(Size.Infinity); + + Assert.Equal(1, target.GetChildren().Count); + } + + [Fact] + public void Updates_Children_When_VisualChildren_Added() + { + var panel = new Panel + { + Children = + { + new Border(), + new Border(), + }, + }; + + var target = CreatePeer(panel); + var children = target.GetChildren(); + + Assert.Equal(2, children.Count); + + panel.Children.Add(new Decorator()); + + children = target.GetChildren(); + Assert.Equal(3, children.Count); + } + + [Fact] + public void Updates_Children_When_VisualChildren_Removed() + { + var panel = new Panel + { + Children = + { + new Border(), + new Border(), + }, + }; + + var target = CreatePeer(panel); + var children = target.GetChildren(); + + Assert.Equal(2, children.Count); + + panel.Children.RemoveAt(1); + + children = target.GetChildren(); + Assert.Equal(1, children.Count); + } + + [Fact] + public void Updates_Children_When_Visibility_Changes() + { + var panel = new Panel + { + Children = + { + new Border(), + new Border(), + }, + }; + + var target = CreatePeer(panel); + var children = target.GetChildren(); + + Assert.Equal(2, children.Count); + + panel.Children[1].IsVisible = false; + children = target.GetChildren(); + Assert.Equal(1, children.Count); + + panel.Children[1].IsVisible = true; + children = target.GetChildren(); + Assert.Equal(2, children.Count); + } + } + + public class Parent + { + [Fact] + public void Connects_Peer_To_Tree_When_GetParent_Called() + { + var border = new Border(); + var tree = new Decorator + { + Child = new Decorator + { + Child = border, + } + }; + + // We're accessing Border directly without going via its ancestors. Because the tree + // is built lazily, ensure that calling GetParent causes the ancestor tree to be built. + var target = CreatePeer(border); + + var parentPeer = Assert.IsAssignableFrom(target.GetParent()); + Assert.Same(border.GetVisualParent(), parentPeer.Owner); + } + + [Fact] + public void Parent_Updated_When_Moved_To_Separate_Visual_Tree() + { + var border = new Border(); + var root1 = new Decorator { Child = border }; + var root2 = new Decorator(); + var target = CreatePeer(border); + + var parentPeer = Assert.IsAssignableFrom(target.GetParent()); + Assert.Same(root1, parentPeer.Owner); + + root1.Child = null; + + Assert.Null(target.GetParent()); + + root2.Child = border; + + parentPeer = Assert.IsAssignableFrom(target.GetParent()); + Assert.Same(root2, parentPeer.Owner); + } + } + + private static AutomationPeer CreatePeer(Control control) + { + return ControlAutomationPeer.CreatePeerForElement(control); + } + + private class TestControl : Control + { + protected override AutomationPeer OnCreateAutomationPeer() + { + return new TestAutomationPeer(this); + } + } + + private class AutomationTestRoot : TestRoot + { + protected override AutomationPeer OnCreateAutomationPeer() + { + return new TestRootAutomationPeer(this); + } + } + + private class TestAutomationPeer : ControlAutomationPeer + { + public TestAutomationPeer( Control owner) + : base(owner) + { + } + } + + private class TestRootAutomationPeer : ControlAutomationPeer, IRootProvider + { + public TestRootAutomationPeer(Control owner) + : base(owner) + { + } + + public ITopLevelImpl PlatformImpl => throw new System.NotImplementedException(); + public event EventHandler? FocusChanged; + + public AutomationPeer GetFocus() + { + throw new System.NotImplementedException(); + } + + public AutomationPeer GetPeerFromPoint(Point p) + { + throw new System.NotImplementedException(); + } + } + } +} From 54d9ba7639f6a52817b385bc6a94ce178ec73a11 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 4 Oct 2021 17:19:53 +0200 Subject: [PATCH 046/822] Allow peers to dynamically expose implemented interfaces. --- .../Automation/Peers/AutomationPeer.cs | 13 +++++++++ .../Automation/AutomationNode.cs | 28 +++++++++++-------- .../Automation/RootAutomationNode.cs | 6 ++-- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index f1276c68da..c3276db058 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -160,6 +160,13 @@ namespace Avalonia.Automation.Peers /// true if a context menu is present for the element; otherwise false. public bool ShowContextMenu() => ShowContextMenuCore(); + /// + /// Tries to get a provider of the specified type from the peer. + /// + /// The provider type. + /// The provider, or null if not implemented on this peer. + public T? GetProvider() => (T?)GetProviderCore(typeof(T)); + public event EventHandler? PropertyChanged; /// @@ -223,6 +230,12 @@ namespace Avalonia.Automation.Peers protected abstract bool IsKeyboardFocusableCore(); protected abstract void SetFocusCore(); protected abstract bool ShowContextMenuCore(); + + protected virtual object? GetProviderCore(Type providerType) + { + return providerType.IsAssignableFrom(this.GetType()) ? this : null; + } + protected internal abstract bool TrySetParent(AutomationPeer? parent); protected void EnsureEnabled() diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 6630657257..7a8aa7b0fe 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -99,17 +99,19 @@ namespace Avalonia.Win32.Automation [return: MarshalAs(UnmanagedType.IUnknown)] public virtual object? GetPatternProvider(int patternId) { + AutomationNode? ThisIfPeerImplementsProvider() => Peer.GetProvider() is object ? this : null; + return (UiaPatternId)patternId switch { - UiaPatternId.ExpandCollapse => Peer is IExpandCollapseProvider ? this : null, - UiaPatternId.Invoke => Peer is AAP.IInvokeProvider ? this : null, - UiaPatternId.RangeValue => Peer is AAP.IRangeValueProvider ? this : null, - UiaPatternId.Scroll => Peer is AAP.IScrollProvider ? this : null, + UiaPatternId.ExpandCollapse => ThisIfPeerImplementsProvider(), + UiaPatternId.Invoke => ThisIfPeerImplementsProvider(), + UiaPatternId.RangeValue => ThisIfPeerImplementsProvider(), + UiaPatternId.Scroll => ThisIfPeerImplementsProvider(), UiaPatternId.ScrollItem => this, - UiaPatternId.Selection => Peer is AAP.ISelectionProvider ? this : null, - UiaPatternId.SelectionItem => Peer is AAP.ISelectionItemProvider ? this : null, - UiaPatternId.Toggle => Peer is AAP.IToggleProvider ? this : null, - UiaPatternId.Value => Peer is AAP.IValueProvider ? this : null, + UiaPatternId.Selection => ThisIfPeerImplementsProvider(), + UiaPatternId.SelectionItem => ThisIfPeerImplementsProvider(), + UiaPatternId.Toggle => ThisIfPeerImplementsProvider(), + UiaPatternId.Value => ThisIfPeerImplementsProvider(), _ => null, }; } @@ -232,7 +234,7 @@ namespace Avalonia.Win32.Automation protected void InvokeSync(Action action) { - if (Peer is TInterface i) + if (Peer.GetProvider() is TInterface i) { try { @@ -248,7 +250,7 @@ namespace Avalonia.Win32.Automation [return: MaybeNull] protected TResult InvokeSync(Func func) { - if (Peer is TInterface i) + if (Peer.GetProvider() is TInterface i) { try { @@ -280,7 +282,7 @@ namespace Avalonia.Win32.Automation var peer = Peer; var parent = peer.GetParent(); - while (!(peer is AAP.IRootProvider) && parent is object) + while (peer.GetProvider() is null && parent is object) { peer = parent; parent = peer.GetParent(); @@ -291,7 +293,9 @@ namespace Avalonia.Win32.Automation private static AutomationNode Create(AutomationPeer peer) { - return peer is AAP.IRootProvider ? new RootAutomationNode(peer) : new AutomationNode(peer); + return peer.GetProvider() is object ? + new RootAutomationNode(peer) : + new AutomationNode(peer); } private static UiaControlTypeId ToUiaControlType(AutomationControlType role) diff --git a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs index 6728aa4283..435e0671df 100644 --- a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs @@ -14,11 +14,13 @@ namespace Avalonia.Win32.Automation public RootAutomationNode(AutomationPeer peer) : base(peer) { - ((IRootProvider)peer).FocusChanged += FocusChanged; + Peer = base.Peer.GetProvider() ?? throw new AvaloniaInternalException( + "Attempt to create RootAutomationNode from peer which does not implement IRootProvider."); + Peer.FocusChanged += FocusChanged; } public override IRawElementProviderFragmentRoot? FragmentRoot => this; - public new IRootProvider Peer => (IRootProvider)base.Peer; + public new IRootProvider Peer { get; } public WindowImpl? WindowImpl => Peer.PlatformImpl as WindowImpl; public IRawElementProviderFragment? ElementProviderFromPoint(double x, double y) From 49ef4b3f280e4fdab2396f13da7d0a3ff4693e42 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 4 Oct 2021 22:51:39 +0200 Subject: [PATCH 047/822] Added AccessibilityView and ControlType. - `AutomationProperties.AccessibilityView`: from UWP - control's element's visibility in automation tree - `ControlTypeOverride`: from UWP proposal - overrides the automation control type specified in peer --- .../Automation/AutomationProperties.cs | 91 +++++++++++++++++++ .../Automation/Peers/AutomationPeer.cs | 9 +- .../Automation/Peers/ControlAutomationPeer.cs | 11 ++- 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Automation/AutomationProperties.cs b/src/Avalonia.Controls/Automation/AutomationProperties.cs index 8ff1210a76..c20af148b8 100644 --- a/src/Avalonia.Controls/Automation/AutomationProperties.cs +++ b/src/Avalonia.Controls/Automation/AutomationProperties.cs @@ -1,8 +1,30 @@ using System; +using Avalonia.Automation.Peers; using Avalonia.Controls; namespace Avalonia.Automation { + /// + /// Declares how a control should included in different views of the automation tree. + /// + public enum AccessibilityView + { + /// + /// The control is included in the Raw view of the automation tree. + /// + Raw, + + /// + /// The control is included in the Control view of the automation tree. + /// + Control, + + /// + /// The control is included in the Content view of the automation tree. + /// + Content, + } + public static class AutomationProperties { internal const int AutomationPositionInSetDefault = -1; @@ -16,6 +38,15 @@ namespace Avalonia.Automation "AcceleratorKey", typeof(AutomationProperties)); + /// + /// Defines the AutomationProperties.AccessibilityView attached property. + /// + public static readonly AttachedProperty AccessibilityViewProperty = + AvaloniaProperty.RegisterAttached( + "AccessibilityView", + typeof(AutomationProperties), + defaultValue: AccessibilityView.Content); + /// /// Defines the AutomationProperties.AccessKey attached property /// @@ -32,6 +63,14 @@ namespace Avalonia.Automation "AutomationId", typeof(AutomationProperties)); + /// + /// Defines the AutomationProperties.ControlTypeOverride attached property. + /// + public static readonly AttachedProperty ControlTypeOverrideProperty = + AvaloniaProperty.RegisterAttached( + "ControlTypeOverride", + typeof(AutomationProperties)); + /// /// Defines the AutomationProperties.HelpText attached property. /// @@ -171,6 +210,32 @@ namespace Avalonia.Automation return ((string)element.GetValue(AcceleratorKeyProperty)); } + /// + /// Helper for setting AccessibilityView property on a StyledElement. + /// + public static void SetAccessibilityView(StyledElement element, AccessibilityView value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(AccessibilityViewProperty, value); + } + + /// + /// Helper for reading AccessibilityView property from a StyledElement. + /// + public static AccessibilityView GetAccessibilityView(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return element.GetValue(AccessibilityViewProperty); + } + /// /// Helper for setting AccessKey property on a StyledElement. /// @@ -223,6 +288,32 @@ namespace Avalonia.Automation return element.GetValue(AutomationIdProperty); } + /// + /// Helper for setting ControlTypeOverride property on a StyledElement. + /// + public static void SetControlTypeOverride(StyledElement element, AutomationControlType? value) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + element.SetValue(ControlTypeOverrideProperty, value); + } + + /// + /// Helper for reading ControlTypeOverride property from a StyledElement. + /// + public static AutomationControlType? GetControlTypeOverride(StyledElement element) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + return element.GetValue(ControlTypeOverrideProperty); + } + /// /// Helper for setting HelpText property on a StyledElement. /// diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index c3276db058..fbca22031a 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -74,7 +74,7 @@ namespace Avalonia.Automation.Peers /// /// Gets the control type for the element that is associated with the UI Automation peer. /// - public AutomationControlType GetAutomationControlType() => GetAutomationControlTypeCore(); + public AutomationControlType GetAutomationControlType() => GetControlTypeOverrideCore(); /// /// Gets the automation ID of the element that is associated with the UI Automation peer. @@ -230,7 +230,12 @@ namespace Avalonia.Automation.Peers protected abstract bool IsKeyboardFocusableCore(); protected abstract void SetFocusCore(); protected abstract bool ShowContextMenuCore(); - + + protected virtual AutomationControlType GetControlTypeOverrideCore() + { + return GetAutomationControlTypeCore(); + } + protected virtual object? GetProviderCore(Type providerType) { return providerType.IsAssignableFrom(this.GetType()) ? this : null; diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index 947c88a190..3cbcdcf348 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -148,17 +148,22 @@ namespace Avalonia.Automation.Peers protected override string? GetAcceleratorKeyCore() => AutomationProperties.GetAcceleratorKey(Owner); protected override string? GetAccessKeyCore() => AutomationProperties.GetAccessKey(Owner); + protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Custom; protected override string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner) ?? Owner.Name; protected override Rect GetBoundingRectangleCore() => GetBounds(Owner.TransformedBounds); - protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Custom; protected override string GetClassNameCore() => Owner.GetType().Name; protected override bool HasKeyboardFocusCore() => Owner.IsFocused; - protected override bool IsContentElementCore() => true; - protected override bool IsControlElementCore() => true; + protected override bool IsContentElementCore() => AutomationProperties.GetAccessibilityView(Owner) >= AccessibilityView.Content; + protected override bool IsControlElementCore() => AutomationProperties.GetAccessibilityView(Owner) >= AccessibilityView.Control; protected override bool IsEnabledCore() => Owner.IsEnabled; protected override bool IsKeyboardFocusableCore() => Owner.Focusable; protected override void SetFocusCore() => Owner.Focus(); + protected override AutomationControlType GetControlTypeOverrideCore() + { + return AutomationProperties.GetControlTypeOverride(Owner) ?? GetAutomationControlTypeCore(); + } + private static Rect GetBounds(TransformedBounds? bounds) { return bounds?.Bounds.TransformToAABB(bounds!.Value.Transform) ?? default; From 0756c1b3be4c8ebdf4d60ed568de896af3624f8f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 4 Oct 2021 23:27:33 +0200 Subject: [PATCH 048/822] Use new properties to remove some peers. Simple peers which only change the accessibility view or control type can now use the new `AutomationProperties`. --- .../Peers/CheckBoxAutomationPeer.cs | 19 --------------- .../Peers/ComboBoxItemAutomationPeer.cs | 23 ------------------- .../Peers/ContextMenuAutomationPeer.cs | 21 ----------------- .../Automation/Peers/ImageAutomationPeer.cs | 19 --------------- .../Automation/Peers/MenuAutomationPeer.cs | 21 ----------------- .../Automation/Peers/SliderAutomationPeer.cs | 19 --------------- .../Peers/TabControlAutomationPeer.cs | 19 --------------- .../Automation/Peers/TabItemAutomationPeer.cs | 17 -------------- src/Avalonia.Controls/CheckBox.cs | 5 ++-- src/Avalonia.Controls/ComboBoxItem.cs | 5 ++-- src/Avalonia.Controls/ContextMenu.cs | 8 +++---- src/Avalonia.Controls/Image.cs | 7 ++---- src/Avalonia.Controls/Menu.cs | 8 +++---- src/Avalonia.Controls/Slider.cs | 7 ++---- src/Avalonia.Controls/TabControl.cs | 7 ++---- src/Avalonia.Controls/TabItem.cs | 7 ++---- 16 files changed, 20 insertions(+), 192 deletions(-) delete mode 100644 src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs delete mode 100644 src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs delete mode 100644 src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs delete mode 100644 src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs delete mode 100644 src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs delete mode 100644 src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs delete mode 100644 src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs delete mode 100644 src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs diff --git a/src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs deleted file mode 100644 index 7f4e492935..0000000000 --- a/src/Avalonia.Controls/Automation/Peers/CheckBoxAutomationPeer.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Avalonia.Controls; - -#nullable enable - -namespace Avalonia.Automation.Peers -{ - public class CheckBoxAutomationPeer : ToggleButtonAutomationPeer - { - public CheckBoxAutomationPeer(CheckBox owner) - : base(owner) - { - } - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.CheckBox; - } - } -} diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs deleted file mode 100644 index 70d29dbc87..0000000000 --- a/src/Avalonia.Controls/Automation/Peers/ComboBoxItemAutomationPeer.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Avalonia.Automation.Provider; -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Selection; - -#nullable enable - -namespace Avalonia.Automation.Peers -{ - public class ComboBoxItemAutomationPeer : ListItemAutomationPeer - { - public ComboBoxItemAutomationPeer(ComboBoxItem owner) - : base(owner) - { - } - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.ComboBoxItem; - } - } -} diff --git a/src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs deleted file mode 100644 index 3230f33506..0000000000 --- a/src/Avalonia.Controls/Automation/Peers/ContextMenuAutomationPeer.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Avalonia.Controls; - -#nullable enable - -namespace Avalonia.Automation.Peers -{ - public class ContextMenuAutomationPeer : ControlAutomationPeer - { - public ContextMenuAutomationPeer(ContextMenu owner) - : base(owner) - { - } - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.Menu; - } - - protected override bool IsContentElementCore() => false; - } -} diff --git a/src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs deleted file mode 100644 index 4548341487..0000000000 --- a/src/Avalonia.Controls/Automation/Peers/ImageAutomationPeer.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Avalonia.Controls; - -#nullable enable - -namespace Avalonia.Automation.Peers -{ - public class ImageAutomationPeer : ControlAutomationPeer - { - public ImageAutomationPeer(Control owner) - : base(owner) - { - } - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.Image; - } - } -} diff --git a/src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs deleted file mode 100644 index e223a0864f..0000000000 --- a/src/Avalonia.Controls/Automation/Peers/MenuAutomationPeer.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Avalonia.Controls; - -#nullable enable - -namespace Avalonia.Automation.Peers -{ - public class MenuAutomationPeer : ControlAutomationPeer - { - public MenuAutomationPeer(Menu owner) - : base(owner) - { - } - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.Menu; - } - - protected override bool IsContentElementCore() => false; - } -} diff --git a/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs deleted file mode 100644 index 907e779046..0000000000 --- a/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Avalonia.Controls; - -#nullable enable - -namespace Avalonia.Automation.Peers -{ - public class SliderAutomationPeer : RangeBaseAutomationPeer - { - public SliderAutomationPeer(Slider owner) - : base(owner) - { - } - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.Slider; - } - } -} diff --git a/src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs deleted file mode 100644 index e14e61a6e4..0000000000 --- a/src/Avalonia.Controls/Automation/Peers/TabControlAutomationPeer.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Avalonia.Controls; - -#nullable enable - -namespace Avalonia.Automation.Peers -{ - public class TabControlAutomationPeer : SelectingItemsControlAutomationPeer - { - public TabControlAutomationPeer(TabControl owner) - : base(owner) - { - } - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.Tab; - } - } -} diff --git a/src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs deleted file mode 100644 index dc794da915..0000000000 --- a/src/Avalonia.Controls/Automation/Peers/TabItemAutomationPeer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Avalonia.Controls; - -namespace Avalonia.Automation.Peers -{ - public class TabItemAutomationPeer : ListItemAutomationPeer - { - public TabItemAutomationPeer(TabItem owner) - : base(owner) - { - } - - protected override AutomationControlType GetAutomationControlTypeCore() - { - return AutomationControlType.TabItem; - } - } -} diff --git a/src/Avalonia.Controls/CheckBox.cs b/src/Avalonia.Controls/CheckBox.cs index f7b0dcfdc2..238a21393f 100644 --- a/src/Avalonia.Controls/CheckBox.cs +++ b/src/Avalonia.Controls/CheckBox.cs @@ -1,3 +1,4 @@ +using Avalonia.Automation; using Avalonia.Automation.Peers; using Avalonia.Controls.Primitives; @@ -8,9 +9,9 @@ namespace Avalonia.Controls /// public class CheckBox : ToggleButton { - protected override AutomationPeer OnCreateAutomationPeer() + static CheckBox() { - return new CheckBoxAutomationPeer(this); + AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.CheckBox); } } } diff --git a/src/Avalonia.Controls/ComboBoxItem.cs b/src/Avalonia.Controls/ComboBoxItem.cs index 42ec6e43b9..83057d139f 100644 --- a/src/Avalonia.Controls/ComboBoxItem.cs +++ b/src/Avalonia.Controls/ComboBoxItem.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Linq; +using Avalonia.Automation; using Avalonia.Automation.Peers; namespace Avalonia.Controls @@ -15,9 +16,9 @@ namespace Avalonia.Controls .Subscribe(_ => (Parent as ComboBox)?.ItemFocused(this)); } - protected override AutomationPeer OnCreateAutomationPeer() + static ComboBoxItem() { - return new ComboBoxItemAutomationPeer(this); + AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.ComboBoxItem); } } } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 0a4b518f57..90c61aaed9 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -14,6 +14,7 @@ using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Styling; +using Avalonia.Automation; #nullable enable @@ -110,6 +111,8 @@ namespace Avalonia.Controls ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); PlacementModeProperty.OverrideDefaultValue(PlacementMode.Pointer); ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); + AutomationProperties.AccessibilityViewProperty.OverrideDefaultValue(AccessibilityView.Control); + AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.Menu); } /// @@ -319,11 +322,6 @@ namespace Avalonia.Controls return new MenuItemContainerGenerator(this); } - protected override AutomationPeer OnCreateAutomationPeer() - { - return new ContextMenuAutomationPeer(this); - } - private void Open(Control control, Control placementTarget, bool requestedByPointer) { if (IsOpen) diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index aaf93cac26..3d67880638 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -1,3 +1,4 @@ +using Avalonia.Automation; using Avalonia.Automation.Peers; using Avalonia.Media; using Avalonia.Media.Imaging; @@ -34,6 +35,7 @@ namespace Avalonia.Controls { AffectsRender(SourceProperty, StretchProperty, StretchDirectionProperty); AffectsMeasure(SourceProperty, StretchProperty, StretchDirectionProperty); + AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.Image); } /// @@ -125,10 +127,5 @@ namespace Avalonia.Controls return new Size(); } } - - protected override AutomationPeer OnCreateAutomationPeer() - { - return new ImageAutomationPeer(this); - } } } diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs index ed70316a53..4e71c99b02 100644 --- a/src/Avalonia.Controls/Menu.cs +++ b/src/Avalonia.Controls/Menu.cs @@ -1,3 +1,4 @@ +using Avalonia.Automation; using Avalonia.Automation.Peers; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; @@ -38,6 +39,8 @@ namespace Avalonia.Controls static Menu() { ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel); + AutomationProperties.AccessibilityViewProperty.OverrideDefaultValue(AccessibilityView.Control); + AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.Menu); } /// @@ -92,10 +95,5 @@ namespace Avalonia.Controls inputRoot.AccessKeyHandler.MainMenu = this; } } - - protected override AutomationPeer OnCreateAutomationPeer() - { - return new MenuAutomationPeer(this); - } } } diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 227c387ffb..dc8e27c3e2 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -9,6 +9,7 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Utilities; +using Avalonia.Automation; namespace Avalonia.Controls { @@ -106,6 +107,7 @@ namespace Avalonia.Controls RoutingStrategies.Bubble); ValueProperty.OverrideMetadata(new DirectPropertyMetadata(enableDataValidation: true)); + AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.Slider); } /// @@ -210,11 +212,6 @@ namespace Avalonia.Controls _pointerMovedDispose = this.AddDisposableHandler(PointerMovedEvent, TrackMoved, RoutingStrategies.Tunnel); } - protected override AutomationPeer OnCreateAutomationPeer() - { - return new SliderAutomationPeer(this); - } - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index cc9dab986a..da204ffce8 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -10,6 +10,7 @@ using Avalonia.Input; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.VisualTree; +using Avalonia.Automation; namespace Avalonia.Controls { @@ -69,6 +70,7 @@ namespace Avalonia.Controls ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); AffectsMeasure(TabStripPlacementProperty); SelectedItemProperty.Changed.AddClassHandler((x, e) => x.UpdateSelectedContent()); + AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.Tab); } /// @@ -231,10 +233,5 @@ namespace Avalonia.Controls } } } - - protected override AutomationPeer OnCreateAutomationPeer() - { - return new TabControlAutomationPeer(this); - } } } diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 846010ce77..7f3df0ed8a 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -1,3 +1,4 @@ +using Avalonia.Automation; using Avalonia.Automation.Peers; using Avalonia.Controls.Metadata; using Avalonia.Controls.Mixins; @@ -32,6 +33,7 @@ namespace Avalonia.Controls PressedMixin.Attach(); FocusableProperty.OverrideDefaultValue(typeof(TabItem), true); DataContextProperty.Changed.AddClassHandler((x, e) => x.UpdateHeader(e)); + AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(AutomationControlType.TabItem); } /// @@ -81,10 +83,5 @@ namespace Avalonia.Controls } } } - - protected override AutomationPeer OnCreateAutomationPeer() - { - return new TabItemAutomationPeer(this); - } } } From eebcacac9a0bda61722a17997e8e52ad2645ac77 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 4 Oct 2021 23:38:57 +0200 Subject: [PATCH 049/822] Query correct interface. --- src/Windows/Avalonia.Win32/Automation/AutomationNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 7a8aa7b0fe..f2ba875945 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -108,7 +108,7 @@ namespace Avalonia.Win32.Automation UiaPatternId.RangeValue => ThisIfPeerImplementsProvider(), UiaPatternId.Scroll => ThisIfPeerImplementsProvider(), UiaPatternId.ScrollItem => this, - UiaPatternId.Selection => ThisIfPeerImplementsProvider(), + UiaPatternId.Selection => ThisIfPeerImplementsProvider(), UiaPatternId.SelectionItem => ThisIfPeerImplementsProvider(), UiaPatternId.Toggle => ThisIfPeerImplementsProvider(), UiaPatternId.Value => ThisIfPeerImplementsProvider(), From f4f478c910ca1bda40f9b8ef29b2038854896340 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Oct 2021 14:50:50 +0200 Subject: [PATCH 050/822] Move ChildrenChanged to AutomationPeer. --- .../Automation/Peers/AutomationPeer.cs | 13 +++++++++++++ .../Automation/Peers/ControlAutomationPeer.cs | 4 +--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index fbca22031a..54b2fcc7fa 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -167,8 +167,21 @@ namespace Avalonia.Automation.Peers /// The provider, or null if not implemented on this peer. public T? GetProvider() => (T?)GetProviderCore(typeof(T)); + /// + /// Occurs when the children of the automation peer have changed. + /// + public event EventHandler? ChildrenChanged; + + /// + /// Occurs when a property value of the automation peer has changed. + /// public event EventHandler? PropertyChanged; + /// + /// Raises an event to notify the automation client the the children of the peer have changed. + /// + protected void RaiseChildrenChangedEvent() => ChildrenChanged?.Invoke(this, EventArgs.Empty); + /// /// Raises an event to notify the automation client of a changed property value. /// diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index 3cbcdcf348..f7a993e16b 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -26,8 +26,6 @@ namespace Avalonia.Automation.Peers public Control Owner { get; } - public event EventHandler? ChildrenChanged; - public AutomationPeer GetOrCreate(Control element) { if (element == Owner) @@ -110,7 +108,7 @@ namespace Avalonia.Automation.Peers protected void InvalidateChildren() { _childrenValid = false; - ChildrenChanged?.Invoke(this, EventArgs.Empty); + RaiseChildrenChangedEvent(); } /// From bc128676c456d3040f5c34b7040ce9d635aec185 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 10 Nov 2021 23:48:54 +0100 Subject: [PATCH 051/822] Make OSX a11y work again. Pretty major refactor of that code. --- native/Avalonia.Native/src/OSX/AvnString.mm | 2 +- native/Avalonia.Native/src/OSX/automation.h | 10 +- native/Avalonia.Native/src/OSX/automation.mm | 234 ++++++++++++------- native/Avalonia.Native/src/OSX/common.h | 3 - native/Avalonia.Native/src/OSX/main.mm | 8 +- native/Avalonia.Native/src/OSX/window.mm | 130 +++-------- src/Avalonia.Native/AutomationNode.cs | 40 ---- src/Avalonia.Native/Avalonia.Native.csproj | 4 + src/Avalonia.Native/AvnAutomationPeer.cs | 47 +++- src/Avalonia.Native/WindowImplBase.cs | 11 +- src/Avalonia.Native/avn.idl | 9 +- 11 files changed, 247 insertions(+), 251 deletions(-) delete mode 100644 src/Avalonia.Native/AutomationNode.cs diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index c69cef5c87..e0266a127c 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -160,7 +160,7 @@ NSString* GetNSStringAndRelease(IAvnString* s) { char* p; - if (s->Pointer((void**)&p) == S_OK) + if (s->Pointer((void**)&p) == S_OK && p != nullptr) { return [NSString stringWithUTF8String:p]; } diff --git a/native/Avalonia.Native/src/OSX/automation.h b/native/Avalonia.Native/src/OSX/automation.h index 65e1153248..4a12a965fd 100644 --- a/native/Avalonia.Native/src/OSX/automation.h +++ b/native/Avalonia.Native/src/OSX/automation.h @@ -1,16 +1,12 @@ #import +#include "window.h" NS_ASSUME_NONNULL_BEGIN class IAvnAutomationPeer; -@interface AvnAutomationNode : NSAccessibilityElement -- (AvnAutomationNode *)initWithPeer:(IAvnAutomationPeer *)peer; +@interface AvnAccessibilityElement : NSAccessibilityElement ++ (AvnAccessibilityElement *) acquire:(IAvnAutomationPeer *) peer; @end -struct INSAccessibilityHolder -{ - virtual NSObject* _Nonnull GetNSAccessibility () = 0; -}; - NS_ASSUME_NONNULL_END diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index b1a2b9e94e..ca845d0ec8 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -1,66 +1,108 @@ -#import "automation.h" #include "common.h" +#include "automation.h" #include "AvnString.h" #include "window.h" -class AutomationNode : public ComSingleObject, - public INSAccessibilityHolder +@interface AvnAccessibilityElement (Events) +- (void) raiseChildrenChanged; +@end + +@interface AvnRootAccessibilityElement : AvnAccessibilityElement +- (AvnView *) ownerView; +- (AvnRootAccessibilityElement *) initWithPeer:(IAvnAutomationPeer *) peer owner:(AvnView*) owner; +- (void) raiseFocusChanged; +@end + +class AutomationNode : public ComSingleObject { -private: - NSAccessibilityElement* _node; public: FORWARD_IUNKNOWN() - AutomationNode(NSAccessibilityElement* node) + AutomationNode(AvnAccessibilityElement* owner) { - _node = node; + _owner = owner; } - - AutomationNode(IAvnAutomationPeer* peer) + + AvnAccessibilityElement* GetOwner() { - _node = [[AvnAutomationNode alloc] initWithPeer: peer]; + return _owner; } - virtual void ChildrenChanged() override + virtual void Dispose() override { - NSAccessibilityPostNotification(_node, NSAccessibilityLayoutChangedNotification); } - virtual void PropertyChanged(AvnAutomationProperty property) override + virtual void ChildrenChanged () override { - switch (property) { - case RangeValueProvider_Value: - NSAccessibilityPostNotification(_node, NSAccessibilityValueChangedNotification); - break; - default: - break; - } + [_owner raiseChildrenChanged]; } - - virtual void FocusChanged(IAvnAutomationPeer* peer) override + + virtual void PropertyChanged (AvnAutomationProperty property) override { - // Only implemented in top-level nodes, i.e. AvnWindow. + } - - virtual NSObject* GetNSAccessibility() override + + virtual void FocusChanged () override { - return _node; + [(AvnRootAccessibilityElement*)_owner raiseFocusChanged]; } + +private: + AvnAccessibilityElement* _owner; }; -@implementation AvnAutomationNode +@implementation AvnAccessibilityElement { IAvnAutomationPeer* _peer; + AutomationNode* _node; NSMutableArray* _children; } -- (AvnAutomationNode *)initWithPeer:(IAvnAutomationPeer *)peer ++ (AvnAccessibilityElement *)acquire:(IAvnAutomationPeer *)peer +{ + if (peer == nullptr) + return nil; + + auto instance = peer->GetNode(); + + if (instance != nullptr) + return dynamic_cast(instance)->GetOwner(); + + if (peer->IsRootProvider()) + { + auto window = peer->RootProvider_GetWindow(); + auto holder = dynamic_cast(window); + auto view = holder->GetNSView(); + return [[AvnRootAccessibilityElement alloc] initWithPeer:peer owner:view]; + } + else + { + return [[AvnAccessibilityElement alloc] initWithPeer:peer]; + } +} + +- (AvnAccessibilityElement *)initWithPeer:(IAvnAutomationPeer *)peer { self = [super init]; _peer = peer; + _node = new AutomationNode(self); + _peer->SetNode(_node); return self; } +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@ '%@' (%p)", + GetNSStringAndRelease(_peer->GetClassName()), + GetNSStringAndRelease(_peer->GetName()), + _peer]; +} + +- (IAvnAutomationPeer *)peer +{ + return _peer; +} + - (BOOL)isAccessibilityElement { return _peer->IsControlElement(); @@ -96,7 +138,7 @@ public: case AutomationToolBar: return NSAccessibilityToolbarRole; case AutomationToolTip: return NSAccessibilityPopoverRole; case AutomationTree: return NSAccessibilityOutlineRole; - case AutomationTreeItem: return NSAccessibilityOutlineRowSubrole; + case AutomationTreeItem: return NSAccessibilityCellRole; case AutomationCustom: return NSAccessibilityUnknownRole; case AutomationGroup: return NSAccessibilityGroupRole; case AutomationThumb: return NSAccessibilityHandleRole; @@ -110,8 +152,10 @@ public: case AutomationHeaderItem: return NSAccessibilityButtonRole; case AutomationTable: return NSAccessibilityTableRole; case AutomationTitleBar: return NSAccessibilityGroupRole; - case AutomationSeparator: return NSAccessibilityUnknownRole; - default: return NSAccessibilityUnknownRole; + // Treat unknown roles as generic group container items. Returning + // NSAccessibilityUnknownRole is also possible but makes the screen + // reader focus on the item instead of passing focus to child items. + default: return NSAccessibilityGroupRole; } } @@ -177,6 +221,15 @@ public: return [super accessibilityMaxValue]; } +- (BOOL)isAccessibilityEnabled +{ + return _peer->IsEnabled(); +} + +- (BOOL)isAccessibilityFocused +{ + return _peer->HasKeyboardFocus(); +} - (NSArray *)accessibilityChildren { @@ -195,52 +248,55 @@ public: if (childPeers->Get(i, &child) == S_OK) { - NSObject* element = ::GetAccessibilityElement(child->GetNode()); + auto element = [AvnAccessibilityElement acquire:child]; [_children addObject:element]; } } } } - + return _children; } - (NSRect)accessibilityFrame { - auto view = [self getAvnView]; - auto window = [self getAvnWindow]; + id topLevel = [self accessibilityTopLevelUIElement]; + auto result = NSZeroRect; - if (view != nullptr) + if ([topLevel isKindOfClass:[AvnRootAccessibilityElement class]]) { - auto bounds = ToNSRect(_peer->GetBoundingRectangle()); - auto windowBounds = [view convertRect:bounds toView:nil]; - auto screenBounds = [window convertRectToScreen:windowBounds]; - return screenBounds; + auto root = (AvnRootAccessibilityElement*)topLevel; + auto view = [root ownerView]; + + if (view) + { + auto window = [view window]; + auto bounds = ToNSRect(_peer->GetBoundingRectangle()); + auto windowBounds = [view convertRect:bounds toView:nil]; + auto screenBounds = [window convertRectToScreen:windowBounds]; + result = screenBounds; + } } - - return NSRect(); + + return result; } - (id)accessibilityParent { auto parentPeer = _peer->GetParent(); - - if (parentPeer != nullptr) - { - return GetAccessibilityElement(parentPeer); - } - - return [NSApplication sharedApplication]; + return parentPeer ? [AvnAccessibilityElement acquire:parentPeer] : [NSApplication sharedApplication]; } - (id)accessibilityTopLevelUIElement { - return GetAccessibilityElement([self getRootNode]); + auto rootPeer = _peer->GetRootPeer(); + return [AvnAccessibilityElement acquire:rootPeer]; } - (id)accessibilityWindow { - return [self accessibilityTopLevelUIElement]; + id topLevel = [self accessibilityTopLevelUIElement]; + return [topLevel isKindOfClass:[NSWindow class]] ? topLevel : nil; } - (BOOL)isAccessibilityExpanded @@ -326,58 +382,68 @@ public: return [super isAccessibilitySelectorAllowed:selector]; } -- (IAvnAutomationNode*)getRootNode +- (void)raiseChildrenChanged { - auto rootPeer = _peer->GetRootPeer(); - return rootPeer != nullptr ? rootPeer->GetNode() : nullptr; + NSAccessibilityPostNotification(self, NSAccessibilityLayoutChangedNotification); } -- (IAvnWindowBase*)getWindow +- (void)raisePropertyChanged { - auto rootNode = [self getRootNode]; +} - if (rootNode != nullptr) - { - IAvnWindowBase* window; - if (rootNode->QueryInterface(&IID_IAvnWindow, (void**)&window) == S_OK) - { - return window; - } - } - - return nullptr; +- (void)setAccessibilityFocused:(BOOL)accessibilityFocused +{ + if (accessibilityFocused) + _peer->SetFocus(); } -- (AvnWindow*) getAvnWindow +@end + +@implementation AvnRootAccessibilityElement { - auto window = [self getWindow]; - return window != nullptr ? dynamic_cast(window)->GetNSWindow() : nullptr; + AvnView* _owner; } -- (AvnView*) getAvnView +- (AvnRootAccessibilityElement *)initWithPeer:(IAvnAutomationPeer *)peer owner:(AvnView *)owner { - auto window = [self getWindow]; - return window != nullptr ? dynamic_cast(window)->GetNSView() : nullptr; + self = [super initWithPeer:peer]; + _owner = owner; + return self; } -@end +- (AvnView *)ownerView +{ + return _owner; +} -extern IAvnAutomationNode* CreateAutomationNode(IAvnAutomationPeer* peer) +- (id)accessibilityFocusedUIElement { - @autoreleasepool - { - return new AutomationNode(peer); - } + auto focusedPeer = [self peer]->RootProvider_GetFocus(); + return [AvnAccessibilityElement acquire:focusedPeer]; } -extern NSObject* GetAccessibilityElement(IAvnAutomationPeer* peer) +- (id)accessibilityHitTest:(NSPoint)point { - auto node = peer != nullptr ? peer->GetNode() : nullptr; - return GetAccessibilityElement(node); + auto clientPoint = [[_owner window] convertPointFromScreen:point]; + auto localPoint = [_owner translateLocalPoint:ToAvnPoint(clientPoint)]; + auto hit = [self peer]->RootProvider_GetPeerFromPoint(localPoint); + return [AvnAccessibilityElement acquire:hit]; } -extern NSObject* GetAccessibilityElement(IAvnAutomationNode* node) +- (id)accessibilityParent { - auto holder = dynamic_cast(node); - return holder != nullptr ? holder->GetNSAccessibility() : nil; + return _owner; } + +- (void)raiseFocusChanged +{ + id focused = [self accessibilityFocusedUIElement]; + NSAccessibilityPostNotification(focused, NSAccessibilityFocusedUIElementChangedNotification); +} + +- (void)accessibilityPerformAction:(NSAccessibilityActionName)action +{ + [_owner accessibilityPerformAction:action]; +} + +@end diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 53ff46cf32..091856fcf7 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -31,9 +31,6 @@ extern IAvnMenu* GetAppMenu (); extern NSMenuItem* GetAppMenuItem (); extern void SetAutoGenerateDefaultAppMenuItems (bool enabled); extern bool GetAutoGenerateDefaultAppMenuItems (); -extern IAvnAutomationNode* CreateAutomationNode(IAvnAutomationPeer* peer); -extern NSObject* GetAccessibilityElement(IAvnAutomationPeer* peer); -extern NSObject* GetAccessibilityElement(IAvnAutomationNode* node); extern void InitializeAvnApp(IAvnApplicationEvents* events); extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 68afb51f40..9dc9da1cd1 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -347,13 +347,7 @@ public: return S_OK; } } - - virtual HRESULT CreateAutomationNode (IAvnAutomationPeer* peer, IAvnAutomationNode** ppv) override - { - *ppv = ::CreateAutomationNode(peer); - return S_OK; - } - + virtual HRESULT SetAppMenu (IAvnMenu* appMenu) override { START_COM_CALL; diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 47d605a311..5b8a7a1c0e 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -10,9 +10,7 @@ class WindowBaseImpl : public virtual ComObject, public virtual IAvnWindowBase, - public virtual IAvnAutomationNode, - public INSWindowHolder, - public INSAccessibilityHolder + public INSWindowHolder { private: NSCursor* cursor; @@ -21,7 +19,6 @@ public: FORWARD_IUNKNOWN() BEGIN_INTERFACE_MAP() INTERFACE_MAP_ENTRY(IAvnWindowBase, IID_IAvnWindowBase) - INTERFACE_MAP_ENTRY(IAvnAutomationNode, IID_IAvnAutomationNode) END_INTERFACE_MAP() virtual ~WindowBaseImpl() @@ -131,11 +128,6 @@ public: return View; } - virtual NSObject* GetNSAccessibility() override - { - return Window; - } - virtual HRESULT Show(bool activate, bool isDialog) override { START_COM_CALL; @@ -603,25 +595,6 @@ public: return S_OK; } - virtual void ChildrenChanged() override - { - NSAccessibilityPostNotification(Window, NSAccessibilityLayoutChangedNotification); - } - - virtual void PropertyChanged(AvnAutomationProperty property) override - { - } - - virtual void FocusChanged(IAvnAutomationPeer* peer) override - { - auto element = GetAccessibilityElement(peer); - - if (element != nullptr) - { - NSAccessibilityPostNotification(element, NSAccessibilityFocusedUIElementChangedNotification); - } - } - virtual bool IsDialog() { return false; @@ -1432,6 +1405,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent AvnPixelSize _lastPixelSize; NSObject* _renderTarget; AvnPlatformResizeReason _resizeReason; + AvnAccessibilityElement* _accessibilityChild; } - (void)onClosed @@ -2055,6 +2029,37 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _resizeReason = reason; } +- (AvnAccessibilityElement *) accessibilityChild +{ + if (_accessibilityChild == nil) + { + auto peer = _parent->BaseEvents->GetAutomationPeer(); + + if (peer == nil) + return nil; + + _accessibilityChild = [AvnAccessibilityElement acquire:peer]; + } + + return _accessibilityChild; +} + +- (NSArray *)accessibilityChildren +{ + auto child = [self accessibilityChild]; + return NSAccessibilityUnignoredChildrenForOnlyChild(child); +} + +- (id)accessibilityHitTest:(NSPoint)point +{ + return [[self accessibilityChild] accessibilityHitTest:point]; +} + +- (id)accessibilityFocusedUIElement +{ + return [[self accessibilityChild] accessibilityFocusedUIElement]; +} + @end @@ -2466,75 +2471,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent } } -- (BOOL)isAccessibilityElement -{ - [self getAutomationPeer]; - return YES; -} - -- (NSString *)accessibilityIdentifier -{ - auto peer = [self getAutomationPeer]; - return GetNSStringAndRelease(peer->GetAutomationId()); -} - -- (NSArray *)accessibilityChildren -{ - auto peer = [self getAutomationPeer]; - - if (_automationChildren == nullptr) - { - _automationChildren = (NSMutableArray*)[super accessibilityChildren]; - - auto childPeers = peer->GetChildren(); - auto childCount = childPeers != nullptr ? childPeers->GetCount() : 0; - - if (childCount > 0) - { - for (int i = 0; i < childCount; ++i) - { - IAvnAutomationPeer* child; - - if (childPeers->Get(i, &child) == S_OK) - { - auto element = GetAccessibilityElement(child); - [_automationChildren addObject:element]; - } - } - } - } - - return _automationChildren; -} - -- (id)accessibilityHitTest:(NSPoint)point -{ - point = [self convertPointFromScreen:point]; - auto p = [_parent->View translateLocalPoint:ToAvnPoint(point)]; - auto peer = [self getAutomationPeer]; - auto hit = peer->RootProvider_GetPeerFromPoint(p); - return GetAccessibilityElement(hit); -} - -- (id)accessibilityFocusedUIElement -{ - auto peer = [self getAutomationPeer]; - - if (peer->IsRootProvider()) - { - return GetAccessibilityElement(peer->RootProvider_GetFocus()); - } - - return [super accessibilityFocusedUIElement]; -} - -- (IAvnAutomationPeer*) getAutomationPeer -{ - if (_automationPeer == nullptr) - _automationPeer = _parent->BaseEvents->AutomationStarted(_parent); - return _automationPeer; -} - @end class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup diff --git a/src/Avalonia.Native/AutomationNode.cs b/src/Avalonia.Native/AutomationNode.cs deleted file mode 100644 index 251b7156f1..0000000000 --- a/src/Avalonia.Native/AutomationNode.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Avalonia.Automation; -using Avalonia.Automation.Peers; -using Avalonia.Native.Interop; - -#nullable enable - -namespace Avalonia.Native -{ - internal class AutomationNode - { - public AutomationNode(IAvnAutomationNode native) - { - Native = native; - } - - public IAvnAutomationNode Native { get; } - - public void ChildrenChanged() => Native.ChildrenChanged(); - - public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue) - { - AvnAutomationProperty p; - - if (property == AutomationElementIdentifiers.BoundingRectangleProperty) - p = AvnAutomationProperty.AutomationPeer_BoundingRectangle; - else if (property == AutomationElementIdentifiers.ClassNameProperty) - p = AvnAutomationProperty.AutomationPeer_ClassName; - else if (property == AutomationElementIdentifiers.NameProperty) - p = AvnAutomationProperty.AutomationPeer_Name; - else if (property == RangeValuePatternIdentifiers.ValueProperty) - p = AvnAutomationProperty.RangeValueProvider_Value; - else - return; - - Native.PropertyChanged(p); - } - - public void FocusChanged(AutomationPeer? focus) => Native.FocusChanged(AvnAutomationPeer.Wrap(focus)); - } -} diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 3913484431..b430483419 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -16,6 +16,10 @@ PreserveNewest + + + + diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index b50b0d5111..bcb3d54fcf 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Runtime.CompilerServices; using Avalonia.Automation; using Avalonia.Automation.Peers; using Avalonia.Automation.Provider; +using Avalonia.Controls; using Avalonia.Native.Interop; #nullable enable @@ -12,11 +15,20 @@ namespace Avalonia.Native { internal class AvnAutomationPeer : CallbackBase, IAvnAutomationPeer { + private static readonly ConditionalWeakTable s_wrappers = new(); private readonly AutomationPeer _inner; - public AvnAutomationPeer(AutomationPeer inner) => _inner = inner; + private AvnAutomationPeer(AutomationPeer inner) + { + _inner = inner; + _inner.ChildrenChanged += (_, _) => Node?.ChildrenChanged(); + if (inner is WindowBaseAutomationPeer window) + window.FocusChanged += (_, _) => Node?.FocusChanged(); + } - public IAvnAutomationNode Node => throw new NotImplementedException(); + ~AvnAutomationPeer() => Node?.Dispose(); + + public IAvnAutomationNode? Node { get; private set; } public IAvnString? AcceleratorKey => _inner.GetAcceleratorKey().ToAvnString(); public IAvnString? AccessKey => _inner.GetAccessKey().ToAvnString(); public AvnAutomationControlType AutomationControlType => (AvnAutomationControlType)_inner.GetAutomationControlType(); @@ -43,17 +55,31 @@ namespace Avalonia.Native var peer = _inner; var parent = peer.GetParent(); - while (!(peer is IRootProvider) && parent is object) + while (peer is not IRootProvider && parent is not null) { peer = parent; parent = peer.GetParent(); } - return new AvnAutomationPeer(peer); + return Wrap(peer); } } + public void SetNode(IAvnAutomationNode node) + { + if (Node is not null) + throw new InvalidOperationException("The AvnAutomationPeer already has a node."); + Node = node; + } + public int IsRootProvider() => (_inner is IRootProvider).AsComBool(); + + public IAvnWindowBase RootProvider_GetWindow() + { + var window = (WindowBase)((ControlAutomationPeer)_inner).Owner; + return ((WindowBaseImpl)window.PlatformImpl!).Native; + } + public IAvnAutomationPeer? RootProvider_GetFocus() => Wrap(((IRootProvider)_inner).GetFocus()); public IAvnAutomationPeer? RootProvider_GetPeerFromPoint(AvnPoint point) @@ -68,7 +94,7 @@ namespace Avalonia.Native { var parent = result.GetParent(); - if (parent is object) + if (parent is not null) result = parent; else break; @@ -108,9 +134,12 @@ namespace Avalonia.Native public int IsValueProvider() => (_inner is IValueProvider).AsComBool(); public IAvnString ValueProvider_GetValue() => ((IValueProvider)_inner).Value.ToAvnString(); public void ValueProvider_SetValue(string value) => ((IValueProvider)_inner).SetValue(value); - - public static AvnAutomationPeer? Wrap(AutomationPeer? peer) => - peer != null ? new AvnAutomationPeer(peer) : null; + + [return: NotNullIfNotNull("peer")] + public static AvnAutomationPeer? Wrap(AutomationPeer? peer) + { + return peer is null ? null : s_wrappers.GetValue(peer, x => new(peer)); + } } internal class AvnAutomationPeerArray : CallbackBase, IAvnAutomationPeerArray @@ -119,7 +148,7 @@ namespace Avalonia.Native public AvnAutomationPeerArray(IReadOnlyList items) { - _items = items.Select(x => new AvnAutomationPeer(x)).ToArray(); + _items = items.Select(x => AvnAutomationPeer.Wrap(x)).ToArray(); } public uint Count => (uint)_items.Length; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index d055e4a1c4..fab72dfe47 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -62,7 +62,6 @@ namespace Avalonia.Native private GlPlatformSurface _glSurface; private NativeControlHostImpl _nativeControlHost; private IGlContext _glContext; - private AvnAutomationPeer _automationPeer; internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOpenGlInterface glFeature) @@ -158,6 +157,11 @@ namespace Avalonia.Native public IMouseDevice MouseDevice => _mouse; public abstract IPopupImpl CreatePopup(); + public AutomationPeer GetAutomationPeer() + { + return _inputRoot is Control c ? ControlAutomationPeer.CreatePeerForElement(c) : null; + } + protected unsafe class WindowBaseEvents : CallbackBase, IAvnWindowBaseEvents { private readonly WindowBaseImpl _parent; @@ -262,6 +266,11 @@ namespace Avalonia.Native return (AvnDragDropEffects)args.Effects; } } + + IAvnAutomationPeer IAvnWindowBaseEvents.AutomationPeer + { + get => AvnAutomationPeer.Wrap(_parent.GetAutomationPeer()); + } } public void Activate() diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index e16b5174b0..954aeb3fb4 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -419,6 +419,7 @@ enum AvnPlatformResizeReason enum AvnAutomationControlType { + AutomationNone, AutomationButton, AutomationCalendar, AutomationCheckBox, @@ -480,7 +481,6 @@ interface IAvaloniaNativeFactory : IUnknown HRESULT CreateMenuItem(IAvnMenuItem** ppv); HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv); HRESULT CreateTrayIcon(IAvnTrayIcon** ppv); - HRESULT CreateAutomationNode(IAvnAutomationPeer* peer, IAvnAutomationNode** ppv); } [uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)] @@ -570,6 +570,7 @@ interface IAvnWindowBaseEvents : IUnknown AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position, AvnInputModifiers modifiers, AvnDragDropEffects effects, IAvnClipboard* clipboard, [intptr]void* dataObjectHandle); + IAvnAutomationPeer* GetAutomationPeer(); } [uuid(1ae178ee-1fcc-447f-b6dd-b7bb727f934c)] @@ -811,6 +812,8 @@ interface IAvnApplicationEvents : IUnknown interface IAvnAutomationPeer : IUnknown { IAvnAutomationNode* GetNode(); + void SetNode(IAvnAutomationNode* node); + IAvnString* GetAcceleratorKey(); IAvnString* GetAccessKey(); AvnAutomationControlType GetAutomationControlType(); @@ -832,6 +835,7 @@ interface IAvnAutomationPeer : IUnknown IAvnAutomationPeer* GetRootPeer(); bool IsRootProvider(); + IAvnWindowBase* RootProvider_GetWindow(); IAvnAutomationPeer* RootProvider_GetFocus(); IAvnAutomationPeer* RootProvider_GetPeerFromPoint(AvnPoint point); @@ -871,7 +875,8 @@ interface IAvnAutomationPeerArray : IUnknown [uuid(004dc40b-e435-49dc-bac5-6272ee35382a)] interface IAvnAutomationNode : IUnknown { + void Dispose(); void ChildrenChanged(); void PropertyChanged(AvnAutomationProperty property); - void FocusChanged(IAvnAutomationPeer* peer); + void FocusChanged(); } From ab15b8e882fa2867c86943e19184c077a0d295dc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 11 Nov 2021 00:14:28 +0100 Subject: [PATCH 052/822] Try to prevent leaks. --- native/Avalonia.Native/src/OSX/automation.mm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index ca845d0ec8..4fb4d736a6 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -30,6 +30,7 @@ public: virtual void Dispose() override { + _owner = nil; } virtual void ChildrenChanged () override @@ -48,7 +49,7 @@ public: } private: - AvnAccessibilityElement* _owner; + __strong AvnAccessibilityElement* _owner; }; @implementation AvnAccessibilityElement @@ -90,6 +91,13 @@ private: return self; } +- (void)dealloc +{ + if (_node) + delete _node; + _node = nullptr; +} + - (NSString *)description { return [NSString stringWithFormat:@"%@ '%@' (%p)", From af588cc193f17b2adc8b7365946a76ec43bfe735 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 11 Nov 2021 00:18:22 +0100 Subject: [PATCH 053/822] Stop warning. --- native/Avalonia.Native/src/OSX/automation.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 4fb4d736a6..4a11f85a5a 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -449,9 +449,13 @@ private: NSAccessibilityPostNotification(focused, NSAccessibilityFocusedUIElementChangedNotification); } +// Although this method is marked as deprecated we get runtime warnings if we don't handle it. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-implementations" - (void)accessibilityPerformAction:(NSAccessibilityActionName)action { [_owner accessibilityPerformAction:action]; } +#pragma clang diagnostic pop @end From e0d37998872740f2b3f4e0d126ef2d8e92dd477e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 12 Nov 2021 14:30:52 +0100 Subject: [PATCH 054/822] Update IntegrationTestApp to net6.0. --- samples/IntegrationTestApp/IntegrationTestApp.csproj | 2 +- tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj index 423b428181..748a61bb7e 100644 --- a/samples/IntegrationTestApp/IntegrationTestApp.csproj +++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj @@ -1,7 +1,7 @@ WinExe - netcoreapp3.1 + net6.0 enable diff --git a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs index fe2daa3cd0..b179aea6a9 100644 --- a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs +++ b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs @@ -10,7 +10,7 @@ namespace Avalonia.IntegrationTests.Win32 public class TestAppFixture : IDisposable { private const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723"; - private const string TestAppPath = @"..\..\..\..\..\samples\IntegrationTestApp\bin\Debug\netcoreapp3.1\IntegrationTestApp.exe"; + private const string TestAppPath = @"..\..\..\..\..\samples\IntegrationTestApp\bin\Debug\net6.0\IntegrationTestApp.exe"; public TestAppFixture() { From 77f535f49d6bff0ccbfd431c87ca796a249788ef Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 12 Nov 2021 15:34:34 +0100 Subject: [PATCH 055/822] Trying to use a shared project for win32/mac integration tests. --- .../AutomationTests.cs | 4 +- .../Avalonia.IntegrationTests.Win32.csproj | 2 +- .../ButtonTests.cs | 4 +- .../CheckBoxTests.cs | 4 +- .../ComboBoxTests.cs | 4 +- .../MenuTests.cs | 4 +- .../TestAppFixture.cs | 47 ++++++++++++++----- 7 files changed, 45 insertions(+), 24 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs b/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs index 045a4f08d1..735214426f 100644 --- a/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs @@ -1,4 +1,4 @@ -using OpenQA.Selenium.Appium.Windows; +using OpenQA.Selenium.Appium; using Xunit; namespace Avalonia.IntegrationTests.Win32 @@ -6,7 +6,7 @@ namespace Avalonia.IntegrationTests.Win32 [Collection("Default")] public class AutomationTests { - private WindowsDriver _session; + private AppiumDriver _session; public AutomationTests(TestAppFixture fixture) { diff --git a/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj b/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj index f38f8b0ce1..095f0e63e0 100644 --- a/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj +++ b/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net6.0 enable diff --git a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs index 21c2b1a7e3..d44d1db1a2 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs @@ -1,4 +1,4 @@ -using OpenQA.Selenium.Appium.Windows; +using OpenQA.Selenium.Appium; using Xunit; namespace Avalonia.IntegrationTests.Win32 @@ -6,7 +6,7 @@ namespace Avalonia.IntegrationTests.Win32 [Collection("Default")] public class ButtonTests { - private WindowsDriver _session; + private AppiumDriver _session; public ButtonTests(TestAppFixture fixture) { diff --git a/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs b/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs index ebf7408eab..fdc3f10415 100644 --- a/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs @@ -1,4 +1,4 @@ -using OpenQA.Selenium.Appium.Windows; +using OpenQA.Selenium.Appium; using Xunit; namespace Avalonia.IntegrationTests.Win32 @@ -6,7 +6,7 @@ namespace Avalonia.IntegrationTests.Win32 [Collection("Default")] public class CheckBoxTests { - private WindowsDriver _session; + private AppiumDriver _session; public CheckBoxTests(TestAppFixture fixture) { diff --git a/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs index 6764343c02..b920974b5c 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs @@ -1,4 +1,4 @@ -using OpenQA.Selenium.Appium.Windows; +using OpenQA.Selenium.Appium; using Xunit; namespace Avalonia.IntegrationTests.Win32 @@ -6,7 +6,7 @@ namespace Avalonia.IntegrationTests.Win32 [Collection("Default")] public class ComboBoxTests { - private WindowsDriver _session; + private AppiumDriver _session; public ComboBoxTests(TestAppFixture fixture) { diff --git a/tests/Avalonia.IntegrationTests.Win32/MenuTests.cs b/tests/Avalonia.IntegrationTests.Win32/MenuTests.cs index 3d93afec12..c95bc405e8 100644 --- a/tests/Avalonia.IntegrationTests.Win32/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/MenuTests.cs @@ -1,4 +1,4 @@ -using OpenQA.Selenium.Appium.Windows; +using OpenQA.Selenium.Appium; using Xunit; namespace Avalonia.IntegrationTests.Win32 @@ -6,7 +6,7 @@ namespace Avalonia.IntegrationTests.Win32 [Collection("Default")] public class MenuTests { - private WindowsDriver _session; + private AppiumDriver _session; public MenuTests(TestAppFixture fixture) => _session = fixture.Session; diff --git a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs index b179aea6a9..6fdc7acd96 100644 --- a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs +++ b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs @@ -3,32 +3,53 @@ using System.Globalization; using System.IO; using System.Runtime.InteropServices; using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Appium.Enums; +using OpenQA.Selenium.Appium.Mac; using OpenQA.Selenium.Appium.Windows; namespace Avalonia.IntegrationTests.Win32 { public class TestAppFixture : IDisposable { - private const string WindowsApplicationDriverUrl = "http://127.0.0.1:4723"; - private const string TestAppPath = @"..\..\..\..\..\samples\IntegrationTestApp\bin\Debug\net6.0\IntegrationTestApp.exe"; + private const string TestAppPath = @"..\..\..\..\..\samples\IntegrationTestApp\bin\Debug\net6.0\IntegrationTestApp"; public TestAppFixture() { var opts = new AppiumOptions(); var path = Path.GetFullPath(TestAppPath); - opts.AddAdditionalCapability("app", path); - opts.AddAdditionalCapability("deviceName", "WindowsPC"); - Session = new WindowsDriver( - new Uri(WindowsApplicationDriverUrl), - opts); - - // https://github.com/microsoft/WinAppDriver/issues/1025 - SetForegroundWindow(new IntPtr(int.Parse( - Session.WindowHandles[0].Substring(2), - NumberStyles.AllowHexSpecifier))); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + opts.AddAdditionalCapability(MobileCapabilityType.App, path + ".exe"); + opts.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.Windows); + opts.AddAdditionalCapability(MobileCapabilityType.DeviceName, "WindowsPC"); + + Session = new WindowsDriver( + new Uri("http://127.0.0.1:4723"), + opts); + + // https://github.com/microsoft/WinAppDriver/issues/1025 + SetForegroundWindow(new IntPtr(int.Parse( + Session.WindowHandles[0].Substring(2), + NumberStyles.AllowHexSpecifier))); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + opts.AddAdditionalCapability(MobileCapabilityType.App, path + ".exe"); + opts.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.MacOS); + opts.AddAdditionalCapability(MobileCapabilityType.AutomationName, "mac2"); + + Session = new MacDriver( + new Uri("http://127.0.0.1:4723/wd/hub"), + opts); + } + else + { + throw new NotSupportedException("Unsupported platform."); + } } - public WindowsDriver Session { get; } + public AppiumDriver Session { get; } public void Dispose() => Session.Close(); From 32e76d335031d3c0b2ea16859b3a46be30083a76 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 12 Nov 2021 16:32:08 +0100 Subject: [PATCH 056/822] WIP: Bundle IntegrationTestApp. --- .../IntegrationTestApp.csproj | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj index 748a61bb7e..e8338adae6 100644 --- a/samples/IntegrationTestApp/IntegrationTestApp.csproj +++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj @@ -4,10 +4,24 @@ net6.0 enable - - - + + + IntegrationTestApp + net.avaloniaui.avalonia.integrationtestapp + true + 1.0.0 + + + + + + + + + + + From 9b9abb28ca1588d97d04286c31f538c2aabfaa92 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 12 Nov 2021 23:20:51 +0100 Subject: [PATCH 057/822] Added script to create bundle. --- samples/IntegrationTestApp/bundle.sh | 5 +++++ 1 file changed, 5 insertions(+) create mode 100755 samples/IntegrationTestApp/bundle.sh diff --git a/samples/IntegrationTestApp/bundle.sh b/samples/IntegrationTestApp/bundle.sh new file mode 100755 index 0000000000..505991582e --- /dev/null +++ b/samples/IntegrationTestApp/bundle.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +cd $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) +dotnet restore -r osx-arm64 +dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-arm64 -p:_AvaloniaUseExternalMSBuild=false \ No newline at end of file From cab21b608430771eac07d4fdf87fca4a136fbe32 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 12 Nov 2021 23:21:56 +0100 Subject: [PATCH 058/822] Start making automation tests work on macOS. --- .../AutomationTests.cs | 2 +- .../ElementExtensions.cs | 16 ++++++++++++++++ .../TestAppFixture.cs | 7 ++++--- 3 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs diff --git a/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs b/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs index 735214426f..42fc019e72 100644 --- a/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs @@ -33,7 +33,7 @@ namespace Avalonia.IntegrationTests.Win32 var labeledTextBox = _session.FindElementByAccessibilityId("LabeledByTextBox"); Assert.Equal("Label for TextBox", label.Text); - Assert.Equal("Label for TextBox", labeledTextBox.GetAttribute("Name")); + Assert.Equal("Label for TextBox", labeledTextBox.GetName()); } } } diff --git a/tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs new file mode 100644 index 0000000000..545a2741bf --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.InteropServices; +using OpenQA.Selenium.Appium; + +namespace Avalonia.IntegrationTests.Win32 +{ + internal static class ElementExtensions + { + public static string GetName(this AppiumWebElement element) => GetAttribute(element, "Name", "title"); + + public static string GetAttribute(AppiumWebElement element, string windows, string macOS) + { + return element.GetAttribute(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? windows : macOS); + } + } +} diff --git a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs index 6fdc7acd96..e8f73ec82d 100644 --- a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs +++ b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs @@ -11,7 +11,8 @@ namespace Avalonia.IntegrationTests.Win32 { public class TestAppFixture : IDisposable { - private const string TestAppPath = @"..\..\..\..\..\samples\IntegrationTestApp\bin\Debug\net6.0\IntegrationTestApp"; + private const string TestAppPath = @"..\..\..\..\..\samples\IntegrationTestApp\bin\Debug\net6.0\IntegrationTestApp.exe"; + private const string TestAppBundleId = "net.avaloniaui.avalonia.integrationtestapp"; public TestAppFixture() { @@ -20,7 +21,7 @@ namespace Avalonia.IntegrationTests.Win32 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - opts.AddAdditionalCapability(MobileCapabilityType.App, path + ".exe"); + opts.AddAdditionalCapability(MobileCapabilityType.App, path); opts.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.Windows); opts.AddAdditionalCapability(MobileCapabilityType.DeviceName, "WindowsPC"); @@ -35,7 +36,7 @@ namespace Avalonia.IntegrationTests.Win32 } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - opts.AddAdditionalCapability(MobileCapabilityType.App, path + ".exe"); + opts.AddAdditionalCapability("appium:bundleId", TestAppBundleId); opts.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.MacOS); opts.AddAdditionalCapability(MobileCapabilityType.AutomationName, "mac2"); From 71b84b3664d6bf057f830419c84d085d0d08ca38 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 13 Nov 2021 18:22:41 +0100 Subject: [PATCH 059/822] Correctly raise layout changed notification (?) --- native/Avalonia.Native/src/OSX/automation.mm | 60 +++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 4a11f85a5a..4e79d8af6e 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -242,27 +242,7 @@ private: - (NSArray *)accessibilityChildren { if (_children == nullptr && _peer != nullptr) - { - auto childPeers = _peer->GetChildren(); - auto childCount = childPeers != nullptr ? childPeers->GetCount() : 0; - - if (childCount > 0) - { - _children = [[NSMutableArray alloc] initWithCapacity:childCount]; - - for (int i = 0; i < childCount; ++i) - { - IAvnAutomationPeer* child; - - if (childPeers->Get(i, &child) == S_OK) - { - auto element = [AvnAccessibilityElement acquire:child]; - [_children addObject:element]; - } - } - } - } - + [self recalculateChildren]; return _children; } @@ -392,7 +372,17 @@ private: - (void)raiseChildrenChanged { - NSAccessibilityPostNotification(self, NSAccessibilityLayoutChangedNotification); + auto changed = _children ? [NSMutableSet setWithArray:_children] : [NSMutableSet set]; + + [self recalculateChildren]; + + if (_children) + [changed addObjectsFromArray:_children]; + + NSAccessibilityPostNotificationWithUserInfo( + self, + NSAccessibilityLayoutChangedNotification, + @{ NSAccessibilityUIElementsKey: [changed allObjects]}); } - (void)raisePropertyChanged @@ -405,6 +395,32 @@ private: _peer->SetFocus(); } +- (void)recalculateChildren +{ + auto childPeers = _peer->GetChildren(); + auto childCount = childPeers != nullptr ? childPeers->GetCount() : 0; + + if (childCount > 0) + { + _children = [[NSMutableArray alloc] initWithCapacity:childCount]; + + for (int i = 0; i < childCount; ++i) + { + IAvnAutomationPeer* child; + + if (childPeers->Get(i, &child) == S_OK) + { + auto element = [AvnAccessibilityElement acquire:child]; + [_children addObject:element]; + } + } + } + else + { + _children = nil; + } +} + @end @implementation AvnRootAccessibilityElement From 812bf2665ed76281445ceba0f8866af9be0ca33a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 14 Nov 2021 00:50:14 +0100 Subject: [PATCH 060/822] Get CheckBox automation tests passing on macOS. --- native/Avalonia.Native/src/OSX/automation.mm | 8 +++-- .../CheckBoxTests.cs | 34 +++++++------------ .../ElementExtensions.cs | 9 +++++ .../TestAppFixture.cs | 12 ++++++- 4 files changed, 38 insertions(+), 25 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 4e79d8af6e..46515380a5 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -194,7 +194,7 @@ private: switch (_peer->ToggleProvider_GetToggleState()) { case 0: return [NSNumber numberWithBool:NO]; case 1: return [NSNumber numberWithBool:YES]; - default: return [NSNumber numberWithInt:-1]; + default: return [NSNumber numberWithInt:2]; } } else if (_peer->IsValueProvider()) @@ -314,6 +314,10 @@ private: { _peer->ExpandCollapseProvider_Expand(); } + else if (_peer->IsToggleProvider()) + { + _peer->ToggleProvider_Toggle(); + } return YES; } @@ -357,7 +361,7 @@ private: } else if (selector == @selector(accessibilityPerformPress)) { - return _peer->IsInvokeProvider() || _peer->IsExpandCollapseProvider(); + return _peer->IsInvokeProvider() || _peer->IsExpandCollapseProvider() || _peer->IsToggleProvider(); } else if (selector == @selector(accessibilityPerformIncrement) || selector == @selector(accessibilityPerformDecrement) || diff --git a/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs b/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs index fdc3f10415..71be603dc9 100644 --- a/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs @@ -6,7 +6,7 @@ namespace Avalonia.IntegrationTests.Win32 [Collection("Default")] public class CheckBoxTests { - private AppiumDriver _session; + private readonly AppiumDriver _session; public CheckBoxTests(TestAppFixture fixture) { @@ -22,14 +22,11 @@ namespace Avalonia.IntegrationTests.Win32 { var checkBox = _session.FindElementByAccessibilityId("UncheckedCheckBox"); - Assert.Equal("Unchecked", checkBox.Text); - Assert.False(checkBox.Selected); - Assert.Equal("0", checkBox.GetAttribute("Toggle.ToggleState")); + Assert.Equal("Unchecked", checkBox.GetName()); + Assert.Equal(false, checkBox.GetIsChecked()); checkBox.Click(); - - Assert.True(checkBox.Selected); - Assert.Equal("1", checkBox.GetAttribute("Toggle.ToggleState")); + Assert.Equal(true, checkBox.GetIsChecked()); } [Fact] @@ -37,14 +34,11 @@ namespace Avalonia.IntegrationTests.Win32 { var checkBox = _session.FindElementByAccessibilityId("CheckedCheckBox"); - Assert.Equal("Checked", checkBox.Text); - Assert.True(checkBox.Selected); - Assert.Equal("1", checkBox.GetAttribute("Toggle.ToggleState")); + Assert.Equal("Checked", checkBox.GetName()); + Assert.Equal(true, checkBox.GetIsChecked()); checkBox.Click(); - - Assert.False(checkBox.Selected); - Assert.Equal("0", checkBox.GetAttribute("Toggle.ToggleState")); + Assert.Equal(false, checkBox.GetIsChecked()); } [Fact] @@ -52,21 +46,17 @@ namespace Avalonia.IntegrationTests.Win32 { var checkBox = _session.FindElementByAccessibilityId("ThreeStateCheckBox"); - Assert.Equal("ThreeState", checkBox.Text); - Assert.Equal("2", checkBox.GetAttribute("Toggle.ToggleState")); + Assert.Equal("ThreeState", checkBox.GetName()); + Assert.Null(checkBox.GetIsChecked()); checkBox.Click(); - - Assert.False(checkBox.Selected); - Assert.Equal("0", checkBox.GetAttribute("Toggle.ToggleState")); + Assert.Equal(false, checkBox.GetIsChecked()); checkBox.Click(); - - Assert.True(checkBox.Selected); - Assert.Equal("1", checkBox.GetAttribute("Toggle.ToggleState")); + Assert.Equal(true, checkBox.GetIsChecked()); checkBox.Click(); - Assert.Equal("2", checkBox.GetAttribute("Toggle.ToggleState")); + Assert.Null(checkBox.GetIsChecked()); } } } diff --git a/tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs index 545a2741bf..ac92cd0c5d 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs @@ -8,6 +8,15 @@ namespace Avalonia.IntegrationTests.Win32 { public static string GetName(this AppiumWebElement element) => GetAttribute(element, "Name", "title"); + public static bool? GetIsChecked(this AppiumWebElement element) => + GetAttribute(element, "Toggle.ToggleState", "value") switch + { + "0" => false, + "1" => true, + "2" => null, + _ => throw new ArgumentOutOfRangeException($"Unexpected IsChecked value.") + }; + public static string GetAttribute(AppiumWebElement element, string windows, string macOS) { return element.GetAttribute(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? windows : macOS); diff --git a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs index e8f73ec82d..761787872f 100644 --- a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs +++ b/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs @@ -52,7 +52,17 @@ namespace Avalonia.IntegrationTests.Win32 public AppiumDriver Session { get; } - public void Dispose() => Session.Close(); + public void Dispose() + { + try + { + Session.Close(); + } + catch + { + // Closing the session currently seems to crash the mac2 driver. + } + } [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] From 8250131e10a855d5ee20050d8e5f0d0a2e68eadc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 14 Nov 2021 00:52:26 +0100 Subject: [PATCH 061/822] Rename integration tests now they're not win32-only. --- Avalonia.sln | 2 +- .../AutomationTests.cs | 2 +- .../Avalonia.IntegrationTests.Appium.csproj} | 0 .../ButtonTests.cs | 2 +- .../CheckBoxTests.cs | 2 +- .../ComboBoxTests.cs | 2 +- .../DefaultCollection.cs | 2 +- .../ElementExtensions.cs | 2 +- .../MenuTests.cs | 2 +- .../Properties/AssemblyInfo.cs | 0 .../TestAppFixture.cs | 2 +- .../xunit.runner.json | 0 12 files changed, 9 insertions(+), 9 deletions(-) rename tests/{Avalonia.IntegrationTests.Win32 => Avalonia.IntegrationTests.Appium}/AutomationTests.cs (96%) rename tests/{Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj => Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj} (100%) rename tests/{Avalonia.IntegrationTests.Win32 => Avalonia.IntegrationTests.Appium}/ButtonTests.cs (97%) rename tests/{Avalonia.IntegrationTests.Win32 => Avalonia.IntegrationTests.Appium}/CheckBoxTests.cs (97%) rename tests/{Avalonia.IntegrationTests.Win32 => Avalonia.IntegrationTests.Appium}/ComboBoxTests.cs (96%) rename tests/{Avalonia.IntegrationTests.Win32 => Avalonia.IntegrationTests.Appium}/DefaultCollection.cs (76%) rename tests/{Avalonia.IntegrationTests.Win32 => Avalonia.IntegrationTests.Appium}/ElementExtensions.cs (95%) rename tests/{Avalonia.IntegrationTests.Win32 => Avalonia.IntegrationTests.Appium}/MenuTests.cs (94%) rename tests/{Avalonia.IntegrationTests.Win32 => Avalonia.IntegrationTests.Appium}/Properties/AssemblyInfo.cs (100%) rename tests/{Avalonia.IntegrationTests.Win32 => Avalonia.IntegrationTests.Appium}/TestAppFixture.cs (98%) rename tests/{Avalonia.IntegrationTests.Win32 => Avalonia.IntegrationTests.Appium}/xunit.runner.json (100%) diff --git a/Avalonia.sln b/Avalonia.sln index d8f202d38e..f0c4ecb832 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -232,7 +232,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvv EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestApp", "samples\IntegrationTestApp\IntegrationTestApp.csproj", "{676D6BFD-029D-4E43-BFC7-3892265CE251}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.IntegrationTests.Win32", "tests\Avalonia.IntegrationTests.Win32\Avalonia.IntegrationTests.Win32.csproj", "{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.IntegrationTests.Appium", "tests\Avalonia.IntegrationTests.Appium\Avalonia.IntegrationTests.Appium.csproj", "{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}" EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution diff --git a/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs b/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs similarity index 96% rename from tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs rename to tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs index 42fc019e72..38e4a70e88 100644 --- a/tests/Avalonia.IntegrationTests.Win32/AutomationTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs @@ -1,7 +1,7 @@ using OpenQA.Selenium.Appium; using Xunit; -namespace Avalonia.IntegrationTests.Win32 +namespace Avalonia.IntegrationTests.Appium { [Collection("Default")] public class AutomationTests diff --git a/tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj similarity index 100% rename from tests/Avalonia.IntegrationTests.Win32/Avalonia.IntegrationTests.Win32.csproj rename to tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj diff --git a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs similarity index 97% rename from tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs rename to tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs index d44d1db1a2..1bae5b1069 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ButtonTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs @@ -1,7 +1,7 @@ using OpenQA.Selenium.Appium; using Xunit; -namespace Avalonia.IntegrationTests.Win32 +namespace Avalonia.IntegrationTests.Appium { [Collection("Default")] public class ButtonTests diff --git a/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs similarity index 97% rename from tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs rename to tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs index 71be603dc9..02e7ac60c4 100644 --- a/tests/Avalonia.IntegrationTests.Win32/CheckBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/CheckBoxTests.cs @@ -1,7 +1,7 @@ using OpenQA.Selenium.Appium; using Xunit; -namespace Avalonia.IntegrationTests.Win32 +namespace Avalonia.IntegrationTests.Appium { [Collection("Default")] public class CheckBoxTests diff --git a/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs similarity index 96% rename from tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs rename to tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index b920974b5c..c5ddb14d4b 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -1,7 +1,7 @@ using OpenQA.Selenium.Appium; using Xunit; -namespace Avalonia.IntegrationTests.Win32 +namespace Avalonia.IntegrationTests.Appium { [Collection("Default")] public class ComboBoxTests diff --git a/tests/Avalonia.IntegrationTests.Win32/DefaultCollection.cs b/tests/Avalonia.IntegrationTests.Appium/DefaultCollection.cs similarity index 76% rename from tests/Avalonia.IntegrationTests.Win32/DefaultCollection.cs rename to tests/Avalonia.IntegrationTests.Appium/DefaultCollection.cs index 1c8f09a430..bb2dd1fbec 100644 --- a/tests/Avalonia.IntegrationTests.Win32/DefaultCollection.cs +++ b/tests/Avalonia.IntegrationTests.Appium/DefaultCollection.cs @@ -1,6 +1,6 @@ using Xunit; -namespace Avalonia.IntegrationTests.Win32 +namespace Avalonia.IntegrationTests.Appium { [CollectionDefinition("Default")] public class DefaultCollection : ICollectionFixture diff --git a/tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs similarity index 95% rename from tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs rename to tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs index ac92cd0c5d..8949aa9208 100644 --- a/tests/Avalonia.IntegrationTests.Win32/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs @@ -2,7 +2,7 @@ using System; using System.Runtime.InteropServices; using OpenQA.Selenium.Appium; -namespace Avalonia.IntegrationTests.Win32 +namespace Avalonia.IntegrationTests.Appium { internal static class ElementExtensions { diff --git a/tests/Avalonia.IntegrationTests.Win32/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs similarity index 94% rename from tests/Avalonia.IntegrationTests.Win32/MenuTests.cs rename to tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index c95bc405e8..1e43330ae2 100644 --- a/tests/Avalonia.IntegrationTests.Win32/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs @@ -1,7 +1,7 @@ using OpenQA.Selenium.Appium; using Xunit; -namespace Avalonia.IntegrationTests.Win32 +namespace Avalonia.IntegrationTests.Appium { [Collection("Default")] public class MenuTests diff --git a/tests/Avalonia.IntegrationTests.Win32/Properties/AssemblyInfo.cs b/tests/Avalonia.IntegrationTests.Appium/Properties/AssemblyInfo.cs similarity index 100% rename from tests/Avalonia.IntegrationTests.Win32/Properties/AssemblyInfo.cs rename to tests/Avalonia.IntegrationTests.Appium/Properties/AssemblyInfo.cs diff --git a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs b/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs similarity index 98% rename from tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs rename to tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs index 761787872f..f1b8d5773b 100644 --- a/tests/Avalonia.IntegrationTests.Win32/TestAppFixture.cs +++ b/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs @@ -7,7 +7,7 @@ using OpenQA.Selenium.Appium.Enums; using OpenQA.Selenium.Appium.Mac; using OpenQA.Selenium.Appium.Windows; -namespace Avalonia.IntegrationTests.Win32 +namespace Avalonia.IntegrationTests.Appium { public class TestAppFixture : IDisposable { diff --git a/tests/Avalonia.IntegrationTests.Win32/xunit.runner.json b/tests/Avalonia.IntegrationTests.Appium/xunit.runner.json similarity index 100% rename from tests/Avalonia.IntegrationTests.Win32/xunit.runner.json rename to tests/Avalonia.IntegrationTests.Appium/xunit.runner.json From ab06e45488b94f2d767def229460283a50281e83 Mon Sep 17 00:00:00 2001 From: RMBGAME Date: Fri, 19 Nov 2021 20:08:06 +0800 Subject: [PATCH 062/822] TrayIcon should be re-added when the Explorer is restarted --- .../Interop/UnmanagedMethods.cs | 85 ++++++++++--------- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 12 ++- 2 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 938f4222e0..9e514461d7 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -903,9 +903,9 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData); - + public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData); - + [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr GetDC(IntPtr hWnd); @@ -996,7 +996,7 @@ namespace Avalonia.Win32.Interop public static uint GetWindowLong(IntPtr hWnd, int nIndex) { - if(IntPtr.Size == 4) + if (IntPtr.Size == 4) { return GetWindowLong32b(hWnd, nIndex); } @@ -1023,7 +1023,7 @@ namespace Avalonia.Win32.Interop return (uint)SetWindowLong64b(hWnd, nIndex, new IntPtr((uint)value)).ToInt32(); } } - + public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr handle) { if (IntPtr.Size == 4) @@ -1057,14 +1057,14 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool InvalidateRect(IntPtr hWnd, RECT* lpRect, bool bErase); - - + + [DllImport("user32.dll")] public static extern bool ValidateRect(IntPtr hWnd, IntPtr lpRect); [DllImport("user32.dll")] public static extern bool IsWindow(IntPtr hWnd); - + [DllImport("user32.dll")] public static extern bool IsWindowEnabled(IntPtr hWnd); @@ -1091,22 +1091,25 @@ namespace Avalonia.Win32.Interop [DllImport("user32")] public static extern IntPtr GetMessageExtraInfo(); - + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterClassExW")] public static extern ushort RegisterClassEx(ref WNDCLASSEX lpwcx); [DllImport("user32.dll")] public static extern void RegisterTouchWindow(IntPtr hWnd, int flags); - + [DllImport("user32.dll")] public static extern bool ReleaseCapture(); + [DllImport("user32.dll", SetLastError = true)] + public static extern uint RegisterWindowMessage(string lpString); + [DllImport("user32.dll")] public static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr GetActiveWindow(); - + [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr SetActiveWindow(IntPtr hWnd); @@ -1282,7 +1285,7 @@ namespace Avalonia.Win32.Interop [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr LoadLibrary(string fileName); - + [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr LoadLibraryEx(string fileName, IntPtr hFile, int flags); @@ -1326,7 +1329,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern IntPtr MonitorFromWindow(IntPtr hwnd, MONITOR dwFlags); - + [DllImport("user32", EntryPoint = "GetMonitorInfoW", ExactSpelling = true, CharSet = CharSet.Unicode)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi); @@ -1334,14 +1337,14 @@ namespace Avalonia.Win32.Interop [DllImport("user32")] public static extern unsafe bool GetTouchInputInfo( IntPtr hTouchInput, - uint cInputs, + uint cInputs, TOUCHINPUT* pInputs, - int cbSize + int cbSize ); - + [DllImport("user32")] public static extern bool CloseTouchInputHandle(IntPtr hTouchInput); - + [return: MarshalAs(UnmanagedType.Bool)] [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); @@ -1350,7 +1353,7 @@ namespace Avalonia.Win32.Interop public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest, uint dwWidth, uint dwHeight, int XSrc, int YSrc, uint uStartScan, uint cScanLines, IntPtr lpvBits, [In] ref BITMAPINFOHEADER lpbmi, uint fuColorUse); - + [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr hObject); @@ -1365,27 +1368,27 @@ namespace Avalonia.Win32.Interop [DllImport("gdi32.dll")] public static extern int ChoosePixelFormat(IntPtr hdc, ref PixelFormatDescriptor pfd); - + [DllImport("gdi32.dll")] public static extern int DescribePixelFormat(IntPtr hdc, ref PixelFormatDescriptor pfd); [DllImport("gdi32.dll")] public static extern int SetPixelFormat(IntPtr hdc, int iPixelFormat, ref PixelFormatDescriptor pfd); - - + + [DllImport("gdi32.dll")] public static extern int DescribePixelFormat(IntPtr hdc, int iPixelFormat, int bytes, ref PixelFormatDescriptor pfd); - + [DllImport("gdi32.dll")] public static extern bool SwapBuffers(IntPtr hdc); [DllImport("opengl32.dll")] public static extern IntPtr wglCreateContext(IntPtr hdc); - + [DllImport("opengl32.dll")] public static extern bool wglDeleteContext(IntPtr context); - + [DllImport("opengl32.dll")] public static extern bool wglMakeCurrent(IntPtr hdc, IntPtr context); @@ -1406,9 +1409,9 @@ namespace Avalonia.Win32.Interop uint dwMaximumSizeLow, string lpName); - [DllImport("msvcrt.dll", EntryPoint="memcpy", SetLastError = false, CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count); - + [DllImport("msvcrt.dll", EntryPoint = "memcpy", SetLastError = false, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count); + [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern HRESULT RegisterDragDrop(IntPtr hwnd, IDropTarget target); @@ -1447,10 +1450,10 @@ namespace Avalonia.Win32.Interop [DllImport("dwmapi.dll")] public static extern void DwmFlush(); - + [DllImport("dwmapi.dll")] public static extern bool DwmDefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref IntPtr plResult); - + [DllImport("dwmapi.dll")] public static extern void DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind); @@ -1507,8 +1510,8 @@ namespace Avalonia.Win32.Interop throw new Exception("RtlGetVersion failed!"); } } - - [DllImport("kernel32", EntryPoint="WaitForMultipleObjectsEx", SetLastError = true, CharSet = CharSet.Auto)] + + [DllImport("kernel32", EntryPoint = "WaitForMultipleObjectsEx", SetLastError = true, CharSet = CharSet.Auto)] private static extern int IntWaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable); public const int WAIT_FAILED = unchecked((int)0xFFFFFFFF); @@ -1516,7 +1519,7 @@ namespace Avalonia.Win32.Interop internal static int WaitForMultipleObjectsEx(int nCount, IntPtr[] pHandles, bool bWaitAll, int dwMilliseconds, bool bAlertable) { int result = IntWaitForMultipleObjectsEx(nCount, pHandles, bWaitAll, dwMilliseconds, bAlertable); - if(result == WAIT_FAILED) + if (result == WAIT_FAILED) { throw new Win32Exception(); } @@ -1558,7 +1561,7 @@ namespace Avalonia.Win32.Interop DrawLeftBorder = 0x20, DrawTopBorder = 0x40, DrawRightBorder = 0x80, - DrawBottomBorder = 0x100, + DrawBottomBorder = 0x100, } [StructLayout(LayoutKind.Sequential)] @@ -1626,9 +1629,9 @@ namespace Avalonia.Win32.Interop MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, MDT_DEFAULT = MDT_EFFECTIVE_DPI - } + } - public enum ClipboardFormat + public enum ClipboardFormat { /// /// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data. Use this format for ANSI text. @@ -1679,7 +1682,7 @@ namespace Avalonia.Win32.Interop public int X; public int Y; } - + public struct SIZE { public int X; @@ -1880,7 +1883,7 @@ namespace Avalonia.Win32.Interop OFN_NOREADONLYRETURN = 0x00008000, OFN_OVERWRITEPROMPT = 0x00000002 } - + public enum HRESULT : uint { S_FALSE = 0x0001, @@ -2198,13 +2201,13 @@ namespace Avalonia.Win32.Interop internal interface IDropTarget { [PreserveSig] - UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)][In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)][In] int grfKeyState, [MarshalAs(UnmanagedType.U8)][In] long pt, [In][Out] ref DropEffect pdwEffect); [PreserveSig] - UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)][In] int grfKeyState, [MarshalAs(UnmanagedType.U8)][In] long pt, [In][Out] ref DropEffect pdwEffect); [PreserveSig] UnmanagedMethods.HRESULT DragLeave(); [PreserveSig] - UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)][In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)][In] int grfKeyState, [MarshalAs(UnmanagedType.U8)][In] long pt, [In][Out] ref DropEffect pdwEffect); } [ComImport] @@ -2213,9 +2216,9 @@ namespace Avalonia.Win32.Interop internal interface IDropSource { [PreserveSig] - int QueryContinueDrag(int fEscapePressed, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState); + int QueryContinueDrag(int fEscapePressed, [MarshalAs(UnmanagedType.U4)][In] int grfKeyState); [PreserveSig] - int GiveFeedback([MarshalAs(UnmanagedType.U4)] [In] int dwEffect); + int GiveFeedback([MarshalAs(UnmanagedType.U4)][In] int dwEffect); } diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index 23395dd9b5..c28ec94fe8 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -24,6 +24,7 @@ namespace Avalonia.Win32 private readonly Win32NativeToManagedMenuExporter _exporter; private static readonly Dictionary s_trayIcons = new Dictionary(); private bool _disposedValue; + private static readonly uint WM_TASKBARCREATED = UnmanagedMethods.RegisterWindowMessage("TaskbarCreated"); public TrayIconImpl() { @@ -44,6 +45,15 @@ namespace Avalonia.Win32 { s_trayIcons[wParam.ToInt32()].WndProc(hWnd, msg, wParam, lParam); } + + if (msg == WM_TASKBARCREATED) + { + foreach (var tray in s_trayIcons.Values) + { + tray.UpdateIcon(true); + tray.UpdateIcon(); + } + } } public void SetIcon(IWindowIconImpl? icon) @@ -145,7 +155,7 @@ namespace Avalonia.Win32 private enum CustomWindowsMessage : uint { WM_TRAYICON = WindowsMessage.WM_APP + 1024, - WM_TRAYMOUSE = WindowsMessage.WM_USER + 1024 + WM_TRAYMOUSE = WindowsMessage.WM_USER + 1024, } private class TrayIconMenuFlyoutPresenter : MenuFlyoutPresenter, IStyleable From 6bb6c0e5c92716521aac4915904f509450966397 Mon Sep 17 00:00:00 2001 From: RMBGAME Date: Fri, 19 Nov 2021 21:55:35 +0800 Subject: [PATCH 063/822] Only re-add visible TrayIcon --- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index c28ec94fe8..86732539f1 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -50,8 +50,11 @@ namespace Avalonia.Win32 { foreach (var tray in s_trayIcons.Values) { - tray.UpdateIcon(true); - tray.UpdateIcon(); + if (tray._iconAdded) + { + tray.UpdateIcon(true); + tray.UpdateIcon(); + } } } } From 66923cdb240454fe56bcc226f03e207b6e65d2d7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 20 Nov 2021 22:38:54 +0100 Subject: [PATCH 064/822] Skip accelerator key test on OSX. OSX doesn't support accelerator keys. --- .../ButtonTests.cs | 5 ++-- .../PlatformFactAttribute.cs | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs diff --git a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs index 1bae5b1069..8856d7f58d 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs @@ -1,4 +1,5 @@ -using OpenQA.Selenium.Appium; +using System.Runtime.InteropServices; +using OpenQA.Selenium.Appium; using Xunit; namespace Avalonia.IntegrationTests.Appium @@ -43,7 +44,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Button with TextBlock", button.Text); } - [Fact] + [PlatformFact(SkipOnOSX = true)] public void ButtonWithAcceleratorKey() { var button = _session.FindElementByAccessibilityId("ButtonWithAcceleratorKey"); diff --git a/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs b/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs new file mode 100644 index 0000000000..53b9a98347 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using Xunit; + +namespace Avalonia.IntegrationTests.Appium +{ + internal class PlatformFactAttribute : FactAttribute + { + public override string? Skip + { + get + { + if (SkipOnWindows && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return "Ignored on Windows"; + if (SkipOnOSX && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return "Ignored on Windows"; + return null; + } + set => throw new NotSupportedException(); + } + public bool SkipOnOSX { get; set; } + public bool SkipOnWindows { get; set; } + } +} From 6b177078126827b2093330a59073eee3709eac76 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 20 Nov 2021 23:31:47 +0100 Subject: [PATCH 065/822] Raise focus changed on new window if it has focus. --- native/Avalonia.Native/src/OSX/automation.mm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 46515380a5..556cf209af 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -436,6 +436,14 @@ private: { self = [super initWithPeer:peer]; _owner = owner; + + // Seems we need to raise a focus changed notification here if we have focus + auto focusedPeer = [self peer]->RootProvider_GetFocus(); + id focused = [AvnAccessibilityElement acquire:focusedPeer]; + + if (focused) + NSAccessibilityPostNotification(focused, NSAccessibilityFocusedUIElementChangedNotification); + return self; } From f4d9d4cc131ba0d14122fad6f413eeee5644dc0f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 21 Nov 2021 15:33:33 +0100 Subject: [PATCH 066/822] Make integration tests pass on MacOS. --- .../Peers/ComboBoxAutomationPeer.cs | 20 ++++++++++++++++--- .../ComboBoxTests.cs | 5 +++-- .../ElementExtensions.cs | 19 ++++++++++++++++++ .../MenuTests.cs | 2 +- .../PlatformFactAttribute.cs | 2 +- 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs index c582c3d372..0122527950 100644 --- a/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ComboBoxAutomationPeer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Automation.Provider; using Avalonia.Controls; @@ -7,12 +8,13 @@ using Avalonia.Controls; namespace Avalonia.Automation.Peers { public class ComboBoxAutomationPeer : SelectingItemsControlAutomationPeer, - IExpandCollapseProvider + IExpandCollapseProvider, + IValueProvider { private UnrealizedSelectionPeer[]? _selection; public ComboBoxAutomationPeer(ComboBox owner) - : base(owner) + : base(owner) { } @@ -22,7 +24,19 @@ namespace Avalonia.Automation.Peers public bool ShowsMenu => true; public void Collapse() => Owner.IsDropDownOpen = false; public void Expand() => Owner.IsDropDownOpen = true; + bool IValueProvider.IsReadOnly => true; + string? IValueProvider.Value + { + get + { + var selection = GetSelection(); + return selection.Count == 1 ? selection[0].GetName() : null; + } + } + + void IValueProvider.SetValue(string? value) => throw new NotSupportedException(); + protected override AutomationControlType GetAutomationControlTypeCore() { return AutomationControlType.ComboBox; diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index c5ddb14d4b..f8920e6290 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -1,4 +1,5 @@ using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Appium.Mac; using Xunit; namespace Avalonia.IntegrationTests.Appium @@ -24,8 +25,8 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(string.Empty, comboBox.Text); - comboBox.Click(); - comboBox.FindElementByName("Bar").Click(); + ((MacElement)comboBox).Click(); + _session.FindElementByName("Bar").ClickListItem(); Assert.Equal("Bar", comboBox.Text); } diff --git a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs index 8949aa9208..9e91422c38 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs @@ -1,6 +1,8 @@ using System; using System.Runtime.InteropServices; using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Appium.MultiTouch; +using OpenQA.Selenium.Interactions; namespace Avalonia.IntegrationTests.Appium { @@ -17,6 +19,23 @@ namespace Avalonia.IntegrationTests.Appium _ => throw new ArgumentOutOfRangeException($"Unexpected IsChecked value.") }; + public static void ClickListItem(this AppiumWebElement element) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + element.Click(); + } + else + { + // List items don't respond to performClick on MacOS, so instead send a physical click as VoiceOver + // does. + var action = new Actions(element.WrappedDriver); + action.MoveToElement(element); + action.Click(); + action.Perform(); + } + } + public static string GetAttribute(AppiumWebElement element, string windows, string macOS) { return element.GetAttribute(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? windows : macOS); diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index 1e43330ae2..a2c3ee0e44 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs @@ -18,7 +18,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("File", fileMenu.Text); } - [Fact] + [PlatformFact(SkipOnOSX = true)] public void Open() { var fileMenu = _session.FindElementByAccessibilityId("FileMenu"); diff --git a/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs b/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs index 53b9a98347..60338b92c2 100644 --- a/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs +++ b/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs @@ -14,7 +14,7 @@ namespace Avalonia.IntegrationTests.Appium if (SkipOnWindows && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return "Ignored on Windows"; if (SkipOnOSX && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return "Ignored on Windows"; + return "Ignored on MacOS"; return null; } set => throw new NotSupportedException(); From dc83f8e788f7b70681b306dfdccf94679ebddd7f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 21 Nov 2021 16:49:43 +0100 Subject: [PATCH 067/822] Use ListItemAutomationPeer for TabItem. --- src/Avalonia.Controls/TabItem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 7f3df0ed8a..f68db4743b 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -56,6 +56,8 @@ namespace Avalonia.Controls set { SetValue(IsSelectedProperty, value); } } + protected override AutomationPeer OnCreateAutomationPeer() => new ListItemAutomationPeer(this); + private void UpdateHeader(AvaloniaPropertyChangedEventArgs obj) { if (Header == null) From 5534e66bcddb8aa12a2d57c3cd698f6e6ec77105 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 21 Nov 2021 16:50:47 +0100 Subject: [PATCH 068/822] Implement isAccessibilitySelected. --- native/Avalonia.Native/src/OSX/automation.mm | 7 +++++++ src/Avalonia.Native/AvnAutomationPeer.cs | 3 +++ src/Avalonia.Native/avn.idl | 3 +++ 3 files changed, 13 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 556cf209af..7d697140c2 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -349,6 +349,13 @@ private: return YES; } +- (BOOL)isAccessibilitySelected +{ + if (_peer->IsSelectionItemProvider()) + return _peer->SelectionItemProvider_IsSelected(); + return NO; +} + - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector { if (selector == @selector(accessibilityPerformShowMenu)) diff --git a/src/Avalonia.Native/AvnAutomationPeer.cs b/src/Avalonia.Native/AvnAutomationPeer.cs index bcb3d54fcf..4c1e69aa16 100644 --- a/src/Avalonia.Native/AvnAutomationPeer.cs +++ b/src/Avalonia.Native/AvnAutomationPeer.cs @@ -126,6 +126,9 @@ namespace Avalonia.Native public double RangeValueProvider_GetSmallChange() => ((IRangeValueProvider)_inner).SmallChange; public double RangeValueProvider_GetLargeChange() => ((IRangeValueProvider)_inner).LargeChange; public void RangeValueProvider_SetValue(double value) => ((IRangeValueProvider)_inner).SetValue(value); + + public int IsSelectionItemProvider() => (_inner is ISelectionItemProvider).AsComBool(); + public int SelectionItemProvider_IsSelected() => ((ISelectionItemProvider)_inner).IsSelected.AsComBool(); public int IsToggleProvider() => (_inner is IToggleProvider).AsComBool(); public int ToggleProvider_GetToggleState() => (int)((IToggleProvider)_inner).ToggleState; diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 954aeb3fb4..6bb6f69a6f 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -856,6 +856,9 @@ interface IAvnAutomationPeer : IUnknown double RangeValueProvider_GetLargeChange(); void RangeValueProvider_SetValue(double value); + bool IsSelectionItemProvider(); + bool SelectionItemProvider_IsSelected(); + bool IsToggleProvider(); int ToggleProvider_GetToggleState(); void ToggleProvider_Toggle(); From 610ef6f467cf2b7fa0c7c590c26cc074f37cce60 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 21 Nov 2021 16:52:13 +0100 Subject: [PATCH 069/822] Added a NativeMenu test for macOS. --- samples/IntegrationTestApp/MainWindow.axaml | 18 ++++++--- .../IntegrationTestApp/MainWindow.axaml.cs | 21 +++++++++++ .../AutomationTests.cs | 2 +- .../ButtonTests.cs | 2 +- .../ComboBoxTests.cs | 2 +- .../MenuTests.cs | 4 +- .../NativeMenuTests.cs | 37 +++++++++++++++++++ 7 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index c72d45783c..57f8971aab 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -5,12 +5,20 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="IntegrationTestApp.MainWindow" Title="IntegrationTestApp"> + + + + + + + + + + + + - - - - - + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 1d3ca28432..5cff7ccdbc 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -1,3 +1,4 @@ +using System; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; @@ -9,6 +10,7 @@ namespace IntegrationTestApp public MainWindow() { InitializeComponent(); + InitializeViewMenu(); this.AttachDevTools(); } @@ -16,5 +18,24 @@ namespace IntegrationTestApp { AvaloniaXamlLoader.Load(this); } + + private void InitializeViewMenu() + { + var mainTabs = this.FindControl("MainTabs"); + var viewMenu = (NativeMenuItem)NativeMenu.GetMenu(this).Items[1]; + + foreach (TabItem tabItem in mainTabs.Items) + { + var menuItem = new NativeMenuItem + { + Header = (string)tabItem.Header!, + IsChecked = tabItem.IsSelected, + ToggleType = NativeMenuItemToggleType.Radio, + }; + + menuItem.Click += (s, e) => tabItem.IsSelected = true; + viewMenu.Menu.Items.Add(menuItem); + } + } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs b/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs index 38e4a70e88..bad015506f 100644 --- a/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/AutomationTests.cs @@ -6,7 +6,7 @@ namespace Avalonia.IntegrationTests.Appium [Collection("Default")] public class AutomationTests { - private AppiumDriver _session; + private readonly AppiumDriver _session; public AutomationTests(TestAppFixture fixture) { diff --git a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs index 8856d7f58d..2ac859e091 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs @@ -7,7 +7,7 @@ namespace Avalonia.IntegrationTests.Appium [Collection("Default")] public class ButtonTests { - private AppiumDriver _session; + private readonly AppiumDriver _session; public ButtonTests(TestAppFixture fixture) { diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index f8920e6290..746a919162 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -7,7 +7,7 @@ namespace Avalonia.IntegrationTests.Appium [Collection("Default")] public class ComboBoxTests { - private AppiumDriver _session; + private readonly AppiumDriver _session; public ComboBoxTests(TestAppFixture fixture) { diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index a2c3ee0e44..4a0e5d76ba 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs @@ -6,7 +6,7 @@ namespace Avalonia.IntegrationTests.Appium [Collection("Default")] public class MenuTests { - private AppiumDriver _session; + private readonly AppiumDriver _session; public MenuTests(TestAppFixture fixture) => _session = fixture.Session; @@ -19,7 +19,7 @@ namespace Avalonia.IntegrationTests.Appium } [PlatformFact(SkipOnOSX = true)] - public void Open() + public void OpenMenu_AcceleratorKey() { var fileMenu = _session.FindElementByAccessibilityId("FileMenu"); fileMenu.Click(); diff --git a/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs new file mode 100644 index 0000000000..fde01f0e41 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs @@ -0,0 +1,37 @@ +using OpenQA.Selenium.Appium; +using Xunit; + +namespace Avalonia.IntegrationTests.Appium +{ + [Collection("Default")] + public class NativeMenuTests + { + private readonly AppiumDriver _session; + + public NativeMenuTests(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("Automation"); + tab.Click(); + } + + [PlatformFact(SkipOnWindows = true)] + public void View_Menu_Select_Button_Tab() + { + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var buttonTab = tabs.FindElementByName("Button"); + var menuBar = _session.FindElementByXPath("/XCUIElementTypeApplication/XCUIElementTypeMenuBar"); + var viewMenu = menuBar.FindElementByName("View"); + + Assert.False(buttonTab.Selected); + + viewMenu.Click(); + var buttonMenu = viewMenu.FindElementByName("Button"); + buttonMenu.Click(); + + Assert.True(buttonTab.Selected); + } + } +} From f6e06a6d15bb8c18a005920197f9f89add29a3c0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 21 Nov 2021 17:38:40 +0100 Subject: [PATCH 070/822] Refactored Menu integration tests. --- samples/IntegrationTestApp/MainWindow.axaml | 14 ++++++ .../IntegrationTestApp/MainWindow.axaml.cs | 7 +++ .../ComboBoxTests.cs | 2 +- .../ElementExtensions.cs | 5 +- .../MenuTests.cs | 50 +++++++++++++++---- 5 files changed, 66 insertions(+), 12 deletions(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 57f8971aab..b33b2c21e0 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -71,6 +71,20 @@ + + + + + 0 + + + + + + + None + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 5cff7ccdbc..8079472ad8 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -1,6 +1,7 @@ using System; using Avalonia; using Avalonia.Controls; +using Avalonia.Interactivity; using Avalonia.Markup.Xaml; namespace IntegrationTestApp @@ -37,5 +38,11 @@ namespace IntegrationTestApp viewMenu.Menu.Items.Add(menuItem); } } + + private void MenuClicked(object? sender, RoutedEventArgs e) + { + var clickedMenuItemTextBlock = this.FindControl("ClickedMenuItem"); + clickedMenuItemTextBlock.Text = ((MenuItem)sender!).Header.ToString(); + } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index 746a919162..cc72c5ce88 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -26,7 +26,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(string.Empty, comboBox.Text); ((MacElement)comboBox).Click(); - _session.FindElementByName("Bar").ClickListItem(); + _session.FindElementByName("Bar").SendClick(); Assert.Equal("Bar", comboBox.Text); } diff --git a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs index 9e91422c38..0d31e1de27 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs @@ -19,7 +19,7 @@ namespace Avalonia.IntegrationTests.Appium _ => throw new ArgumentOutOfRangeException($"Unexpected IsChecked value.") }; - public static void ClickListItem(this AppiumWebElement element) + public static void SendClick(this AppiumWebElement element) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -27,7 +27,8 @@ namespace Avalonia.IntegrationTests.Appium } else { - // List items don't respond to performClick on MacOS, so instead send a physical click as VoiceOver + // The Click() method seems to correspond to accessibilityPerformPress on macOS but certain controls + // such as list items don't support this action, so instead simulate a physical click as VoiceOver // does. var action = new Actions(element.WrappedDriver); action.MoveToElement(element); diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index 4a0e5d76ba..e9a433b975 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs @@ -8,24 +8,56 @@ namespace Avalonia.IntegrationTests.Appium { private readonly AppiumDriver _session; - public MenuTests(TestAppFixture fixture) => _session = fixture.Session; + public MenuTests(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("Menu"); + tab.Click(); + } + + [Fact] + public void Click_Child() + { + var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem"); + + rootMenuItem.SendClick(); + + var childMenuItem = _session.FindElementByAccessibilityId("Child1MenuItem"); + childMenuItem.SendClick(); + + var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem"); + Assert.Equal("_Child 1", clickedMenuItem.Text); + } [Fact] - public void File() + public void Click_Grandchild() { - var fileMenu = _session.FindElementByAccessibilityId("FileMenu"); + var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem"); + + rootMenuItem.SendClick(); - Assert.Equal("File", fileMenu.Text); + var childMenuItem = _session.FindElementByAccessibilityId("Child2MenuItem"); + childMenuItem.SendClick(); + + var grandchildMenuItem = _session.FindElementByAccessibilityId("GrandchildMenuItem"); + grandchildMenuItem.SendClick(); + + var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem"); + Assert.Equal("_Grandchild", clickedMenuItem.Text); } [PlatformFact(SkipOnOSX = true)] - public void OpenMenu_AcceleratorKey() + public void Child_AcceleratorKey() { - var fileMenu = _session.FindElementByAccessibilityId("FileMenu"); - fileMenu.Click(); + var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem"); + + rootMenuItem.SendClick(); + + var childMenuItem = _session.FindElementByAccessibilityId("Child1MenuItem"); - var openMenu = fileMenu.FindElementByAccessibilityId("OpenMenu"); - Assert.Equal("Ctrl+O", openMenu.GetAttribute("AcceleratorKey")); + Assert.Equal("Ctrl+O", childMenuItem.GetAttribute("AcceleratorKey")); } } } From 742d9608746f7f554035613c3ef011f400ccb48e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 21 Nov 2021 22:13:38 +0100 Subject: [PATCH 071/822] Remove rogue character. --- samples/IntegrationTestApp/MainWindow.axaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index b33b2c21e0..035e0e211b 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -75,7 +75,7 @@ - 0 + From 33ca12c48c411c2c929274170a0a18ea7df74e19 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 21 Nov 2021 22:17:19 +0100 Subject: [PATCH 072/822] Remove faulty cast. --- tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index cc72c5ce88..26cbd1127f 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -25,7 +25,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(string.Empty, comboBox.Text); - ((MacElement)comboBox).Click(); + comboBox.Click(); _session.FindElementByName("Bar").SendClick(); Assert.Equal("Bar", comboBox.Text); From 11c1543b5a53301cc6d6ff9ae2c74085242566a5 Mon Sep 17 00:00:00 2001 From: Lubomir Tetak Date: Mon, 22 Nov 2021 10:53:25 +0100 Subject: [PATCH 073/822] Handle default actions to prevent beeping sounds --- samples/ControlCatalog/Pages/DialogsPage.xaml.cs | 3 ++- src/Avalonia.Controls/Button.cs | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 49921fb7f6..cc1b8e4f5a 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -128,7 +128,8 @@ namespace ControlCatalog.Pages (button = new Button { HorizontalAlignment = HorizontalAlignment.Center, - Content = "Click to close" + Content = "Click to close", + IsDefault = true }) } }, diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 8b22cdd4ec..34180225e0 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -547,6 +547,7 @@ namespace Avalonia.Controls if (e.Key == Key.Enter && IsVisible && IsEnabled) { OnClick(); + e.Handled = true; } } @@ -560,6 +561,7 @@ namespace Avalonia.Controls if (e.Key == Key.Escape && IsVisible && IsEnabled) { OnClick(); + e.Handled = true; } } From ddb6ab977e796c402f1535e141d41b369029da44 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 22 Nov 2021 14:25:37 +0100 Subject: [PATCH 074/822] Refactored ComboBox integration tests. --- samples/IntegrationTestApp/MainWindow.axaml | 18 ++--- .../IntegrationTestApp/MainWindow.axaml.cs | 11 +++ .../ComboBoxTests.cs | 74 ++++++++++++++++--- 3 files changed, 81 insertions(+), 22 deletions(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 035e0e211b..b0483cba32 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -19,6 +19,7 @@ + @@ -32,6 +33,7 @@ + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 8079472ad8..e61341c159 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -13,6 +13,7 @@ namespace IntegrationTestApp InitializeComponent(); InitializeViewMenu(); this.AttachDevTools(); + AddHandler(Button.ClickEvent, OnButtonClick); } private void InitializeComponent() @@ -44,5 +45,15 @@ namespace IntegrationTestApp var clickedMenuItemTextBlock = this.FindControl("ClickedMenuItem"); clickedMenuItemTextBlock.Text = ((MenuItem)sender!).Header.ToString(); } + + private void OnButtonClick(object? sender, RoutedEventArgs e) + { + var source = e.Source as Button; + + if (source?.Name == "ComboBoxSelectionClear") + this.FindControl("ComboBox").SelectedIndex = -1; + if (source?.Name == "ComboBoxSelectFirst") + this.FindControl("ComboBox").SelectedIndex = 0; + } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index 26cbd1127f..9c458059cf 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -1,4 +1,6 @@ -using OpenQA.Selenium.Appium; +using System.Threading; +using OpenQA.Selenium; +using OpenQA.Selenium.Appium; using OpenQA.Selenium.Appium.Mac; using Xunit; @@ -19,32 +21,82 @@ namespace Avalonia.IntegrationTests.Appium } [Fact] - public void UnselectedComboBox() + public void Can_Change_Selection_Using_Mouse() { - var comboBox = _session.FindElementByAccessibilityId("UnselectedComboBox"); + var comboBox = _session.FindElementByAccessibilityId("ComboBox"); + _session.FindElementByAccessibilityId("ComboBoxSelectFirst").Click(); + Assert.Equal("Item 0", comboBox.Text); + + comboBox.Click(); + _session.FindElementByName("Item 1").SendClick(); + + Assert.Equal("Item 1", comboBox.Text); + } + + [Fact] + public void Can_Change_Selection_From_Unselected_Using_Mouse() + { + var comboBox = _session.FindElementByAccessibilityId("ComboBox"); + + _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click(); Assert.Equal(string.Empty, comboBox.Text); comboBox.Click(); - _session.FindElementByName("Bar").SendClick(); + _session.FindElementByName("Item 0").SendClick(); - Assert.Equal("Bar", comboBox.Text); + Assert.Equal("Item 0", comboBox.Text); } [Fact] - public void SelectedIndex0ComboBox() + public void Can_Change_Selection_With_Keyboard() { - var comboBox = _session.FindElementByAccessibilityId("SelectedIndex0ComboBox"); + var comboBox = _session.FindElementByAccessibilityId("ComboBox"); + + _session.FindElementByAccessibilityId("ComboBoxSelectFirst").Click(); + Assert.Equal("Item 0", comboBox.Text); + + comboBox.SendKeys(Keys.LeftAlt + Keys.ArrowDown); + comboBox.SendKeys(Keys.ArrowDown); - Assert.Equal("Foo", comboBox.Text); + var item = _session.FindElementByName("Item 1"); + item.SendKeys(Keys.Enter); + + Assert.Equal("Item 1", comboBox.Text); } [Fact] - public void SelectedIndex1ComboBox() + public void Can_Change_Selection_With_Keyboard_From_Unselected() { - var comboBox = _session.FindElementByAccessibilityId("SelectedIndex1ComboBox"); + var comboBox = _session.FindElementByAccessibilityId("ComboBox"); + + _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click(); + Assert.Equal(string.Empty, comboBox.Text); - Assert.Equal("Bar", comboBox.Text); + comboBox.SendKeys(Keys.LeftAlt + Keys.ArrowDown); + comboBox.SendKeys(Keys.ArrowDown); + + var item = _session.FindElementByName("Item 0"); + item.SendKeys(Keys.Enter); + + Assert.Equal("Item 0", comboBox.Text); + } + + [Fact] + public void Can_Cancel_Keyboard_Selection_With_Escape() + { + var comboBox = _session.FindElementByAccessibilityId("ComboBox"); + + _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click(); + Assert.Equal(string.Empty, comboBox.Text); + + comboBox.SendKeys(Keys.LeftAlt + Keys.ArrowDown); + comboBox.SendKeys(Keys.ArrowDown); + + var item = _session.FindElementByName("Item 0"); + item.SendKeys(Keys.Escape); + + Assert.Equal(string.Empty, comboBox.Text); } } } From 26137caacaa2753f6f6ce4593967ea020ced208d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 22 Nov 2021 14:59:41 +0100 Subject: [PATCH 075/822] Trying to get stuff working on macOS. Man, appium for mac sucks. --- samples/IntegrationTestApp/MainWindow.axaml | 2 +- .../IntegrationTestApp/MainWindow.axaml.cs | 4 +-- .../ComboBoxTests.cs | 30 +++++++++---------- .../ElementExtensions.cs | 7 +++++ .../TestAppFixture.cs | 1 + 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index b0483cba32..90407294b5 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -59,7 +59,7 @@ - + Item 0 Item 1 diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index e61341c159..74bfb6b579 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -51,9 +51,9 @@ namespace IntegrationTestApp var source = e.Source as Button; if (source?.Name == "ComboBoxSelectionClear") - this.FindControl("ComboBox").SelectedIndex = -1; + this.FindControl("BasicComboBox").SelectedIndex = -1; if (source?.Name == "ComboBoxSelectFirst") - this.FindControl("ComboBox").SelectedIndex = 0; + this.FindControl("BasicComboBox").SelectedIndex = 0; } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index 9c458059cf..677ba02b95 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -23,38 +23,38 @@ namespace Avalonia.IntegrationTests.Appium [Fact] public void Can_Change_Selection_Using_Mouse() { - var comboBox = _session.FindElementByAccessibilityId("ComboBox"); + var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); _session.FindElementByAccessibilityId("ComboBoxSelectFirst").Click(); - Assert.Equal("Item 0", comboBox.Text); + Assert.Equal("Item 0", comboBox.GetComboBoxValue()); comboBox.Click(); _session.FindElementByName("Item 1").SendClick(); - Assert.Equal("Item 1", comboBox.Text); + Assert.Equal("Item 1", comboBox.GetComboBoxValue()); } [Fact] public void Can_Change_Selection_From_Unselected_Using_Mouse() { - var comboBox = _session.FindElementByAccessibilityId("ComboBox"); + var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click(); - Assert.Equal(string.Empty, comboBox.Text); + Assert.Equal(string.Empty, comboBox.GetComboBoxValue()); comboBox.Click(); _session.FindElementByName("Item 0").SendClick(); - Assert.Equal("Item 0", comboBox.Text); + Assert.Equal("Item 0", comboBox.GetComboBoxValue()); } [Fact] public void Can_Change_Selection_With_Keyboard() { - var comboBox = _session.FindElementByAccessibilityId("ComboBox"); + var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); _session.FindElementByAccessibilityId("ComboBoxSelectFirst").Click(); - Assert.Equal("Item 0", comboBox.Text); + Assert.Equal("Item 0", comboBox.GetComboBoxValue()); comboBox.SendKeys(Keys.LeftAlt + Keys.ArrowDown); comboBox.SendKeys(Keys.ArrowDown); @@ -62,16 +62,16 @@ namespace Avalonia.IntegrationTests.Appium var item = _session.FindElementByName("Item 1"); item.SendKeys(Keys.Enter); - Assert.Equal("Item 1", comboBox.Text); + Assert.Equal("Item 1", comboBox.GetComboBoxValue()); } [Fact] public void Can_Change_Selection_With_Keyboard_From_Unselected() { - var comboBox = _session.FindElementByAccessibilityId("ComboBox"); + var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click(); - Assert.Equal(string.Empty, comboBox.Text); + Assert.Equal(string.Empty, comboBox.GetComboBoxValue()); comboBox.SendKeys(Keys.LeftAlt + Keys.ArrowDown); comboBox.SendKeys(Keys.ArrowDown); @@ -79,16 +79,16 @@ namespace Avalonia.IntegrationTests.Appium var item = _session.FindElementByName("Item 0"); item.SendKeys(Keys.Enter); - Assert.Equal("Item 0", comboBox.Text); + Assert.Equal("Item 0", comboBox.GetComboBoxValue()); } [Fact] public void Can_Cancel_Keyboard_Selection_With_Escape() { - var comboBox = _session.FindElementByAccessibilityId("ComboBox"); + var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); _session.FindElementByAccessibilityId("ComboBoxSelectionClear").Click(); - Assert.Equal(string.Empty, comboBox.Text); + Assert.Equal(string.Empty, comboBox.GetComboBoxValue()); comboBox.SendKeys(Keys.LeftAlt + Keys.ArrowDown); comboBox.SendKeys(Keys.ArrowDown); @@ -96,7 +96,7 @@ namespace Avalonia.IntegrationTests.Appium var item = _session.FindElementByName("Item 0"); item.SendKeys(Keys.Escape); - Assert.Equal(string.Empty, comboBox.Text); + Assert.Equal(string.Empty, comboBox.GetComboBoxValue()); } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs index 0d31e1de27..81d85be861 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs @@ -8,6 +8,13 @@ namespace Avalonia.IntegrationTests.Appium { internal static class ElementExtensions { + public static string GetComboBoxValue(this AppiumWebElement element) + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? + element.Text : + element.GetAttribute("value"); + } + public static string GetName(this AppiumWebElement element) => GetAttribute(element, "Name", "title"); public static bool? GetIsChecked(this AppiumWebElement element) => diff --git a/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs b/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs index f1b8d5773b..b3385d8ee7 100644 --- a/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs +++ b/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs @@ -39,6 +39,7 @@ namespace Avalonia.IntegrationTests.Appium opts.AddAdditionalCapability("appium:bundleId", TestAppBundleId); opts.AddAdditionalCapability(MobileCapabilityType.PlatformName, MobilePlatform.MacOS); opts.AddAdditionalCapability(MobileCapabilityType.AutomationName, "mac2"); + opts.AddAdditionalCapability("appium:showServerLogs", true); Session = new MacDriver( new Uri("http://127.0.0.1:4723/wd/hub"), From b4a183bd9746a33c5ab12a37c5cda3bc0d12c924 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 22 Nov 2021 15:12:36 +0100 Subject: [PATCH 076/822] Skip keyboard interaction tests on macOS. They send appium for mac crazy. --- tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index 677ba02b95..4f407e5964 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -48,7 +48,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Item 0", comboBox.GetComboBoxValue()); } - [Fact] + [PlatformFact(SkipOnOSX = true)] public void Can_Change_Selection_With_Keyboard() { var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); @@ -65,7 +65,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Item 1", comboBox.GetComboBoxValue()); } - [Fact] + [PlatformFact(SkipOnOSX = true)] public void Can_Change_Selection_With_Keyboard_From_Unselected() { var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); @@ -82,7 +82,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Item 0", comboBox.GetComboBoxValue()); } - [Fact] + [PlatformFact(SkipOnOSX = true)] public void Can_Cancel_Keyboard_Selection_With_Escape() { var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); From 261f46ea742b4e3bcff716550397d9cd360a5002 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Nov 2021 10:42:35 +0100 Subject: [PATCH 077/822] Added a few ListBox tests. --- samples/IntegrationTestApp/MainWindow.axaml | 9 ++ .../IntegrationTestApp/MainWindow.axaml.cs | 8 ++ .../ComboBoxTests.cs | 4 +- .../ElementExtensions.cs | 10 +- .../ListBoxTests.cs | 103 ++++++++++++++++++ 5 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 90407294b5..fe1487a7f2 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -67,6 +67,15 @@ + + + + + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 74bfb6b579..b9e631a312 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; @@ -14,8 +16,12 @@ namespace IntegrationTestApp InitializeViewMenu(); this.AttachDevTools(); AddHandler(Button.ClickEvent, OnButtonClick); + ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList(); + DataContext = this; } + public List ListBoxItems { get; } + private void InitializeComponent() { AvaloniaXamlLoader.Load(this); @@ -54,6 +60,8 @@ namespace IntegrationTestApp this.FindControl("BasicComboBox").SelectedIndex = -1; if (source?.Name == "ComboBoxSelectFirst") this.FindControl("BasicComboBox").SelectedIndex = 0; + if (source?.Name == "ListBoxSelectionClear") + this.FindControl("BasicListBox").SelectedIndex = -1; } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index 4f407e5964..fad3e1eb9f 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -1,7 +1,5 @@ -using System.Threading; -using OpenQA.Selenium; +using OpenQA.Selenium; using OpenQA.Selenium.Appium; -using OpenQA.Selenium.Appium.Mac; using Xunit; namespace Avalonia.IntegrationTests.Appium diff --git a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs index 81d85be861..15e22f4424 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs @@ -1,13 +1,16 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using OpenQA.Selenium.Appium; -using OpenQA.Selenium.Appium.MultiTouch; using OpenQA.Selenium.Interactions; namespace Avalonia.IntegrationTests.Appium { internal static class ElementExtensions { + public static IReadOnlyList GetChildren(this AppiumWebElement element) => + element.FindElementsByXPath("*/*"); + public static string GetComboBoxValue(this AppiumWebElement element) { return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @@ -37,10 +40,7 @@ namespace Avalonia.IntegrationTests.Appium // The Click() method seems to correspond to accessibilityPerformPress on macOS but certain controls // such as list items don't support this action, so instead simulate a physical click as VoiceOver // does. - var action = new Actions(element.WrappedDriver); - action.MoveToElement(element); - action.Click(); - action.Perform(); + new Actions(element.WrappedDriver).MoveToElement(element).Click().Perform(); } } diff --git a/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs new file mode 100644 index 0000000000..9c2252e99f --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs @@ -0,0 +1,103 @@ +using System.Threading; +using OpenQA.Selenium; +using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Interactions; +using Xunit; + +namespace Avalonia.IntegrationTests.Appium +{ + [Collection("Default")] + public class ListBoxTests + { + private readonly AppiumDriver _session; + + public ListBoxTests(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("ListBox"); + tab.Click(); + } + + [Fact] + public void Can_Select_Item_By_Clicking() + { + var listBox = GetTarget(); + var item2 = listBox.FindElementByName("Item 2"); + var item4 = listBox.FindElementByName("Item 4"); + + Assert.False(item2.Selected); + Assert.False(item4.Selected); + + item2.SendClick(); + Assert.True(item2.Selected); + Assert.False(item4.Selected); + + item4.SendClick(); + Assert.False(item2.Selected); + Assert.True(item4.Selected); + } + + // WinAppDriver seems unable to consistently send a Ctrl key. + [PlatformFact(SkipOnWindows = true)] + public void Can_Select_Items_By_Ctrl_Clicking() + { + var listBox = GetTarget(); + var item2 = listBox.FindElementByName("Item 2"); + var item4 = listBox.FindElementByName("Item 4"); + + Assert.False(item2.Selected); + Assert.False(item4.Selected); + + new Actions(_session) + .Click(item2) + .KeyDown(Keys.Control) + .Click(item4) + .KeyUp(Keys.Control) + .Perform(); + + Assert.True(item2.Selected); + Assert.True(item4.Selected); + } + + [Fact] + public void Can_Select_Range_By_Shift_Clicking() + { + var listBox = GetTarget(); + var item2 = listBox.FindElementByName("Item 2"); + var item3 = listBox.FindElementByName("Item 3"); + var item4 = listBox.FindElementByName("Item 4"); + + Assert.False(item2.Selected); + Assert.False(item3.Selected); + Assert.False(item4.Selected); + + new Actions(_session) + .Click(item2) + .KeyDown(Keys.Shift) + .Click(item4) + .KeyUp(Keys.Shift) + .Perform(); + + Assert.True(item2.Selected); + Assert.True(item3.Selected); + Assert.True(item4.Selected); + } + + [Fact] + public void Is_Virtualized() + { + var listBox = GetTarget(); + var children = listBox.GetChildren(); + + Assert.True(children.Count < 100); + } + + private AppiumWebElement GetTarget() + { + _session.FindElementByAccessibilityId("ListBoxSelectionClear").Click(); + return _session.FindElementByAccessibilityId("BasicListBox"); + } + } +} From 9f807c9737e2c51bf78637c9d083b77cc730c711 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Nov 2021 10:53:37 +0100 Subject: [PATCH 078/822] Sigh. Appium sucks. --- tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs index 9c2252e99f..625742ac20 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs @@ -39,8 +39,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.True(item4.Selected); } - // WinAppDriver seems unable to consistently send a Ctrl key. - [PlatformFact(SkipOnWindows = true)] + [Fact(Skip = "WinAppDriver seems unable to consistently send a Ctrl key and appium-mac2-driver just hangs")] public void Can_Select_Items_By_Ctrl_Clicking() { var listBox = GetTarget(); @@ -61,7 +60,8 @@ namespace Avalonia.IntegrationTests.Appium Assert.True(item4.Selected); } - [Fact] + // appium-mac2-driver just hangs + [PlatformFact(SkipOnOSX = true)] public void Can_Select_Range_By_Shift_Clicking() { var listBox = GetTarget(); From 6da59f9eee2da945ca99ce929990583eef1652fb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Nov 2021 16:57:27 +0100 Subject: [PATCH 079/822] Added integration tests readme. --- .../readme.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/Avalonia.IntegrationTests.Appium/readme.md diff --git a/tests/Avalonia.IntegrationTests.Appium/readme.md b/tests/Avalonia.IntegrationTests.Appium/readme.md new file mode 100644 index 0000000000..d824294fad --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Appium/readme.md @@ -0,0 +1,29 @@ +# Running Integration Tests + +## Windows + +### Prerequisites + +- Install WinAppDriver: https://github.com/microsoft/WinAppDriver + +### Running + +- Run WinAppDriver (it gets installed to the start menu) +- Run the tests in this project + +## MacOS + +### Prerequisites + +- Install Appium: https://appium.io/ +- `cd samples/IntegrationTestApp` then `./bundle.sh` to create an app bundle for `IntegrationTestApp` +- Register the app bundle by running `open -n ./bin/Debug/net6.0/osx-arm64/publish/IntegrationTestApp.app` + +### Running + +- Run `appium` +- Run the tests in this project + +Each time you make a change to Avalonia or `IntegrationTestApp`, re-run the `bundle.sh` script (registration only needs to be done once). + + From e89bd580d82d6e164d346d1e2bed4822804d1003 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Nov 2021 18:54:48 +0100 Subject: [PATCH 080/822] Add additional macOS prerequisite. --- tests/Avalonia.IntegrationTests.Appium/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Avalonia.IntegrationTests.Appium/readme.md b/tests/Avalonia.IntegrationTests.Appium/readme.md index d824294fad..2a8c3068ba 100644 --- a/tests/Avalonia.IntegrationTests.Appium/readme.md +++ b/tests/Avalonia.IntegrationTests.Appium/readme.md @@ -16,6 +16,7 @@ ### Prerequisites - Install Appium: https://appium.io/ +- Give [XCode helper the required permissions](https://apple.stackexchange.com/questions/334008) - `cd samples/IntegrationTestApp` then `./bundle.sh` to create an app bundle for `IntegrationTestApp` - Register the app bundle by running `open -n ./bin/Debug/net6.0/osx-arm64/publish/IntegrationTestApp.app` From 7f8624e8a6f12309060471fc650689d11b23bbd9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Nov 2021 10:48:34 +0100 Subject: [PATCH 081/822] Updated ApiCompat. --- src/Avalonia.Controls/ApiCompatBaseline.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 509e11b857..dd41c30e85 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -50,9 +50,6 @@ MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.Resized.s InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.Func Avalonia.Platform.IWindowBaseImpl.AutomationStarted' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public System.Func Avalonia.Platform.IWindowBaseImpl.AutomationStarted.get()' is present in the implementation but not in the contract. -InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.AutomationStarted.set(System.Func)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean, System.Boolean)' is present in the implementation but not in the contract. @@ -60,4 +57,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract. -Total Issues: 61 +Total Issues: 58 From cc4cfa67ad9d07ef7c91e3a8239a52fc2463e97e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 30 Nov 2021 09:49:49 +0100 Subject: [PATCH 082/822] Throw when interface not supported. --- src/Windows/Avalonia.Win32/Automation/AutomationNode.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index f2ba875945..70e415aff1 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; @@ -223,7 +222,6 @@ namespace Avalonia.Win32.Automation Dispatcher.UIThread.InvokeAsync(action).Wait(); } - [return: MaybeNull] protected T InvokeSync(Func func) { if (Dispatcher.UIThread.CheckAccess()) @@ -245,9 +243,12 @@ namespace Avalonia.Win32.Automation throw new COMException(e.Message, UiaCoreProviderApi.UIA_E_ELEMENTNOTENABLED); } } + else + { + throw new NotSupportedException(); + } } - [return: MaybeNull] protected TResult InvokeSync(Func func) { if (Peer.GetProvider() is TInterface i) @@ -262,7 +263,7 @@ namespace Avalonia.Win32.Automation } } - return default; + throw new NotSupportedException(); } protected void RaiseFocusChanged(AutomationNode? focused) From 98a070e1af5e7788c9a04497327f98bfc6932555 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 30 Nov 2021 09:50:32 +0100 Subject: [PATCH 083/822] Implement provider members as explicit interface members. Previous change showed up a problem where incorrect interface was getting called for `IsReadOnly`. --- .../AutomationNode.ExpandCollapse.cs | 6 +++--- .../Automation/AutomationNode.RangeValue.cs | 10 +++++----- .../Automation/AutomationNode.Scroll.cs | 18 +++++++++--------- .../Automation/AutomationNode.Selection.cs | 16 ++++++++-------- .../Automation/AutomationNode.Toggle.cs | 4 ++-- .../Automation/AutomationNode.Value.cs | 5 +++-- 6 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs index 34ae969afe..5f3f863493 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.ExpandCollapse.cs @@ -8,12 +8,12 @@ namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.IExpandCollapseProvider { - public ExpandCollapseState ExpandCollapseState + ExpandCollapseState UIA.IExpandCollapseProvider.ExpandCollapseState { get => InvokeSync(x => x.ExpandCollapseState); } - public void Expand() => InvokeSync(x => x.Expand()); - public void Collapse() => InvokeSync(x => x.Collapse()); + void UIA.IExpandCollapseProvider.Expand() => InvokeSync(x => x.Expand()); + void UIA.IExpandCollapseProvider.Collapse() => InvokeSync(x => x.Collapse()); } } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs index d7e97cb30c..b91cb76888 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.RangeValue.cs @@ -8,11 +8,11 @@ namespace Avalonia.Win32.Automation internal partial class AutomationNode : UIA.IRangeValueProvider { double UIA.IRangeValueProvider.Value => InvokeSync(x => x.Value); - public bool IsReadOnly => InvokeSync(x => x.IsReadOnly); - public double Maximum => InvokeSync(x => x.Maximum); - public double Minimum => InvokeSync(x => x.Minimum); - public double LargeChange => 1; - public double SmallChange => 1; + bool UIA.IRangeValueProvider.IsReadOnly => InvokeSync(x => x.IsReadOnly); + double UIA.IRangeValueProvider.Maximum => InvokeSync(x => x.Maximum); + double UIA.IRangeValueProvider.Minimum => InvokeSync(x => x.Minimum); + double UIA.IRangeValueProvider.LargeChange => 1; + double UIA.IRangeValueProvider.SmallChange => 1; public void SetValue(double value) => InvokeSync(x => x.SetValue(value)); } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs index 55f1aba71c..4f2d4ae269 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Scroll.cs @@ -7,24 +7,24 @@ namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.IScrollProvider, UIA.IScrollItemProvider { - public bool HorizontallyScrollable => InvokeSync(x => x.HorizontallyScrollable); - public double HorizontalScrollPercent => InvokeSync(x => x.HorizontalScrollPercent); - public double HorizontalViewSize => InvokeSync(x => x.HorizontalViewSize); - public bool VerticallyScrollable => InvokeSync(x => x.VerticallyScrollable); - public double VerticalScrollPercent => InvokeSync(x => x.VerticalScrollPercent); - public double VerticalViewSize => InvokeSync(x => x.VerticalViewSize); + bool UIA.IScrollProvider.HorizontallyScrollable => InvokeSync(x => x.HorizontallyScrollable); + double UIA.IScrollProvider.HorizontalScrollPercent => InvokeSync(x => x.HorizontalScrollPercent); + double UIA.IScrollProvider.HorizontalViewSize => InvokeSync(x => x.HorizontalViewSize); + bool UIA.IScrollProvider.VerticallyScrollable => InvokeSync(x => x.VerticallyScrollable); + double UIA.IScrollProvider.VerticalScrollPercent => InvokeSync(x => x.VerticalScrollPercent); + double UIA.IScrollProvider.VerticalViewSize => InvokeSync(x => x.VerticalViewSize); - public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount) + void UIA.IScrollProvider.Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount) { InvokeSync(x => x.Scroll(horizontalAmount, verticalAmount)); } - public void SetScrollPercent(double horizontalPercent, double verticalPercent) + void UIA.IScrollProvider.SetScrollPercent(double horizontalPercent, double verticalPercent) { InvokeSync(x => x.SetScrollPercent(horizontalPercent, verticalPercent)); } - public void ScrollIntoView() + void UIA.IScrollItemProvider.ScrollIntoView() { InvokeSync(() => Peer.BringIntoView()); } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs index c41ace4aa8..61903ab5b0 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Selection.cs @@ -11,11 +11,11 @@ namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.ISelectionProvider, UIA.ISelectionItemProvider { - public bool CanSelectMultiple => InvokeSync(x => x.CanSelectMultiple); - public bool IsSelectionRequired => InvokeSync(x => x.IsSelectionRequired); - public bool IsSelected => InvokeSync(x => x.IsSelected); + bool UIA.ISelectionProvider.CanSelectMultiple => InvokeSync(x => x.CanSelectMultiple); + bool UIA.ISelectionProvider.IsSelectionRequired => InvokeSync(x => x.IsSelectionRequired); + bool UIA.ISelectionItemProvider.IsSelected => InvokeSync(x => x.IsSelected); - public UIA.IRawElementProviderSimple? SelectionContainer + UIA.IRawElementProviderSimple? UIA.ISelectionItemProvider.SelectionContainer { get { @@ -24,15 +24,15 @@ namespace Avalonia.Win32.Automation } } - public UIA.IRawElementProviderSimple[] GetSelection() + UIA.IRawElementProviderSimple[] UIA.ISelectionProvider.GetSelection() { var peers = InvokeSync>(x => x.GetSelection()); return peers?.Select(x => (UIA.IRawElementProviderSimple)GetOrCreate(x)!).ToArray() ?? Array.Empty(); } - public void AddToSelection() => InvokeSync(x => x.AddToSelection()); - public void RemoveFromSelection() => InvokeSync(x => x.RemoveFromSelection()); - public void Select() => InvokeSync(x => x.Select()); + void UIA.ISelectionItemProvider.AddToSelection() => InvokeSync(x => x.AddToSelection()); + void UIA.ISelectionItemProvider.RemoveFromSelection() => InvokeSync(x => x.RemoveFromSelection()); + void UIA.ISelectionItemProvider.Select() => InvokeSync(x => x.Select()); } } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs index 9047555785..38f4d80946 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Toggle.cs @@ -7,7 +7,7 @@ namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.IToggleProvider { - public ToggleState ToggleState => InvokeSync(x => x.ToggleState); - public void Toggle() => InvokeSync(x => x.Toggle()); + ToggleState UIA.IToggleProvider.ToggleState => InvokeSync(x => x.ToggleState); + void UIA.IToggleProvider.Toggle() => InvokeSync(x => x.Toggle()); } } diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs index 06e6708663..34f5dfe0b9 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.Value.cs @@ -8,9 +8,10 @@ namespace Avalonia.Win32.Automation { internal partial class AutomationNode : UIA.IValueProvider { - public string? Value => InvokeSync(x => x.Value); + bool UIA.IValueProvider.IsReadOnly => InvokeSync(x => x.IsReadOnly); + string? UIA.IValueProvider.Value => InvokeSync(x => x.Value); - public void SetValue([MarshalAs(UnmanagedType.LPWStr)] string? value) + void UIA.IValueProvider.SetValue([MarshalAs(UnmanagedType.LPWStr)] string? value) { InvokeSync(x => x.SetValue(value)); } From 1d665c55f96dc541b06e350b017484ef56aeb385 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 3 Dec 2021 16:02:03 +0100 Subject: [PATCH 084/822] Fix leak in GetNSStringAndRelease. --- native/Avalonia.Native/src/OSX/AvnString.mm | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index e0266a127c..5e50068c51 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -156,17 +156,16 @@ IAvnString* CreateByteArray(void* data, int len) NSString* GetNSStringAndRelease(IAvnString* s) { + NSString* result = nil; + if (s != nullptr) { char* p; - if (s->Pointer((void**)&p) == S_OK && p != nullptr) - { - return [NSString stringWithUTF8String:p]; - } + result = [NSString stringWithUTF8String:p]; s->Release(); } - return nullptr; + return result; } From bff461679596182d07195b7179e1f57310d867d7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 5 Dec 2021 16:34:22 +0100 Subject: [PATCH 085/822] Don't run integration tests under ncrunch. --- .ncrunch/Avalonia.IntegrationTests.Appium.v3.ncrunchproject | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .ncrunch/Avalonia.IntegrationTests.Appium.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.IntegrationTests.Appium.v3.ncrunchproject b/.ncrunch/Avalonia.IntegrationTests.Appium.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.IntegrationTests.Appium.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file From 394a5be4026afcf41202d876fb29dcf4beb8ab7c Mon Sep 17 00:00:00 2001 From: Lubomir Tetak Date: Fri, 17 Dec 2021 14:52:22 +0100 Subject: [PATCH 086/822] OSX handle CMD+key up combinations in Avalonia --- native/Avalonia.Native/src/OSX/app.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm index 79175d9ff1..05b129baca 100644 --- a/native/Avalonia.Native/src/OSX/app.mm +++ b/native/Avalonia.Native/src/OSX/app.mm @@ -73,6 +73,11 @@ ComPtr _events; _isHandlingSendEvent = true; @try { [super sendEvent: event]; + if ([event type] == NSEventTypeKeyUp && ([event modifierFlags] & NSEventModifierFlagCommand)) + { + [[self keyWindow] sendEvent:event]; + } + } @finally { _isHandlingSendEvent = oldHandling; } From e7afc15ca7e40229e5bbacabeb27c601a5db585f Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 18 Dec 2021 11:36:52 -0600 Subject: [PATCH 087/822] Implement the runtime side of the "method->Delegate" binding conversion for compiled bindings. --- .../Data/Core/Plugins/MethodAccessorPlugin.cs | 26 ++----- .../Avalonia.Markup.Xaml.csproj | 1 + .../CompiledBindings/CompiledBindingPath.cs | 23 ++++++ .../CompiledBindings/MethodAccessorPlugin.cs | 74 +++++++++++++++++++ 4 files changed, 106 insertions(+), 18 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index 160c7301f5..f4e5ab3dbc 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -22,19 +22,9 @@ namespace Avalonia.Data.Core.Plugins var method = GetFirstMethodWithName(instance.GetType(), methodName); - if (method != null) + if (method is not null) { - var parameters = method.GetParameters(); - - if (parameters.Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8) - { - var exception = new ArgumentException( - "Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.", - nameof(methodName)); - return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); - } - - return new Accessor(reference, method, parameters); + return new Accessor(reference, method); } else { @@ -82,7 +72,7 @@ namespace Avalonia.Data.Core.Plugins private sealed class Accessor : PropertyAccessorBase { - public Accessor(WeakReference reference, MethodInfo method, ParameterInfo[] parameters) + public Accessor(WeakReference reference, MethodInfo method) { _ = reference ?? throw new ArgumentNullException(nameof(reference)); _ = method ?? throw new ArgumentNullException(nameof(method)); @@ -90,10 +80,13 @@ namespace Avalonia.Data.Core.Plugins var returnType = method.ReturnType; bool hasReturn = returnType != typeof(void); + var parameters = method.GetParameters(); + var signatureTypeCount = (hasReturn ? 1 : 0) + parameters.Length; var paramTypes = new Type[signatureTypeCount]; + for (var i = 0; i < parameters.Length; i++) { ParameterInfo parameter = parameters[i]; @@ -105,13 +98,10 @@ namespace Avalonia.Data.Core.Plugins { paramTypes[paramTypes.Length - 1] = returnType; - PropertyType = Expression.GetFuncType(paramTypes); - } - else - { - PropertyType = Expression.GetActionType(paramTypes); } + PropertyType = Expression.GetDelegateType(paramTypes); + if (method.IsStatic) { Value = method.CreateDelegate(PropertyType); diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index ac6aced8cb..d44a067882 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index 11489c39aa..3e468d8c8e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Avalonia.Controls; using Avalonia.Data.Core; using Avalonia.Data.Core.Plugins; @@ -36,6 +37,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings case PropertyElement prop: node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory)); break; + case MethodAsDelegateElement methodAsDelegate: + node = new PropertyAccessorNode(methodAsDelegate.Method.Name, enableValidation, new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType)); + break; case ArrayElementPathElement arr: node = new PropertyAccessorNode(CommonPropertyNames.IndexerName, enableValidation, new ArrayElementPlugin(arr.Indices, arr.ElementType)); break; @@ -92,6 +96,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings return this; } + public CompiledBindingPathBuilder Method(RuntimeMethodHandle handle, Type delegateType) + { + _elements.Add(new MethodAsDelegateElement(handle, delegateType)); + return this; + } + public CompiledBindingPathBuilder StreamTask() { _elements.Add(new TaskStreamPathElement()); @@ -178,6 +188,19 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings => _isFirstElement ? Property.Name : $".{Property.Name}"; } + internal class MethodAsDelegateElement : ICompiledBindingPathElement + { + public MethodAsDelegateElement(RuntimeMethodHandle method, Type delegateType) + { + Method = (MethodInfo)MethodBase.GetMethodFromHandle(method); + DelegateType = delegateType; + } + + public MethodInfo Method { get; } + + public Type DelegateType { get; } + } + internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement { IStreamPlugin CreatePlugin(); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs new file mode 100644 index 0000000000..45ad45e658 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using Avalonia.Data; +using Avalonia.Data.Core.Plugins; + +#nullable enable + +namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings +{ + internal class MethodAccessorPlugin : IPropertyAccessorPlugin + { + private MethodInfo _method; + private readonly Type _delegateType; + + public MethodAccessorPlugin(MethodInfo method, Type delegateType) + { + _method = method; + _delegateType = delegateType; + } + + public bool Match(object obj, string propertyName) + { + throw new InvalidOperationException("The MethodAccessorPlugin does not support dynamic matching"); + } + + public IPropertyAccessor Start(WeakReference reference, string propertyName) + { + Debug.Assert(_method.Name == propertyName); + return new Accessor(reference, _method, _delegateType); + } + + private sealed class Accessor : PropertyAccessorBase + { + public Accessor(WeakReference reference, MethodInfo method, Type delegateType) + { + _ = reference ?? throw new ArgumentNullException(nameof(reference)); + _ = method ?? throw new ArgumentNullException(nameof(method)); + + PropertyType = delegateType; + + if (method.IsStatic) + { + Value = method.CreateDelegate(PropertyType); + } + else if (reference.TryGetTarget(out var target)) + { + Value = method.CreateDelegate(PropertyType, target); + } + } + + public override Type? PropertyType { get; } + + public override object? Value { get; } + + public override bool SetValue(object? value, BindingPriority priority) => false; + + protected override void SubscribeCore() + { + try + { + PublishValue(Value); + } + catch { } + } + + protected override void UnsubscribeCore() + { + } + } + } +} From 12adf764c0fc3db1362eb7af2b827a6a31bd2dce Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sat, 18 Dec 2021 12:31:07 -0600 Subject: [PATCH 088/822] Implement method->delegate mapping in the XAML compiler for well-known delegate types --- .../XamlIlBindingPathHelper.cs | 71 +++++++++++++++++-- .../CompiledBindings/CompiledBindingPath.cs | 6 +- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index 1974dfe3bc..c847bf54bc 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -119,16 +119,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe, XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo))); } - else + else if (GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName) is IXamlProperty clrProperty) { - var clrProperty = GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName); - - if (clrProperty is null) - { - throw new XamlX.XamlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo); - } nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty)); } + else if (GetAllDefinedMethods(targetType).FirstOrDefault(m => m.Name == propName.PropertyName) is IXamlMethod method) + { + nodes.Add(new XamlIlClrMethodPathElementNode(method, context.Configuration.WellKnownTypes.Delegate)); + } + else + { + throw new XamlX.XamlParseException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo); + } break; case BindingExpressionGrammar.IndexerNode indexer: { @@ -275,6 +277,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } + static IEnumerable GetAllDefinedMethods(IXamlType type) + { + foreach (var t in TraverseTypeHierarchy(type)) + { + foreach (var m in t.Methods) + { + yield return m; + } + } + } + static IEnumerable TraverseTypeHierarchy(IXamlType type) { if (type.IsInterface) @@ -529,6 +542,50 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0]; } + class XamlIlClrMethodPathElementNode : IXamlIlBindingPathElementNode + { + private readonly IXamlMethod _method; + + public XamlIlClrMethodPathElementNode(IXamlMethod method, IXamlType systemDelegateType) + { + _method = method; + Type = systemDelegateType; + } + + public IXamlType Type { get; } + + public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen) + { + IXamlType specificDelegateType; + if (_method.ReturnType == context.Configuration.WellKnownTypes.Void && _method.Parameters.Count <= 8) + { + specificDelegateType = context.Configuration.TypeSystem + .GetType($"System.Action`{_method.Parameters.Count}") + .MakeGenericType(_method.Parameters); + } + else if (_method.Parameters.Count <= 7) + { + List genericParameters = new(); + genericParameters.AddRange(_method.Parameters); + genericParameters.Add(_method.ReturnType); + specificDelegateType = context.Configuration.TypeSystem + .GetType($"System.Func`{_method.Parameters.Count}") + .MakeGenericType(genericParameters); + } + else + { + // In this case, we need to emit our own delegate type. + specificDelegateType = null; + } + + codeGen + .Ldtoken(_method) + .Ldtoken(specificDelegateType) + .EmitCall(context.GetAvaloniaTypes() + .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Method")); + } + } + class XamlIlClrIndexerPathElementNode : IXamlIlBindingPathElementNode { private readonly IXamlProperty _property; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index 3e468d8c8e..31c9535e5f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -96,7 +96,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings return this; } - public CompiledBindingPathBuilder Method(RuntimeMethodHandle handle, Type delegateType) + public CompiledBindingPathBuilder Method(RuntimeMethodHandle handle, RuntimeTypeHandle delegateType) { _elements.Add(new MethodAsDelegateElement(handle, delegateType)); return this; @@ -190,10 +190,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings internal class MethodAsDelegateElement : ICompiledBindingPathElement { - public MethodAsDelegateElement(RuntimeMethodHandle method, Type delegateType) + public MethodAsDelegateElement(RuntimeMethodHandle method, RuntimeTypeHandle delegateType) { Method = (MethodInfo)MethodBase.GetMethodFromHandle(method); - DelegateType = delegateType; + DelegateType = Type.GetTypeFromHandle(delegateType); } public MethodInfo Method { get; } From 0576baf5b7309f85446b681b588f11d89478bea8 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sun, 19 Dec 2021 17:10:02 -0600 Subject: [PATCH 089/822] Add tests for full-fidelity method->delegate conversion in compiled bindings. --- .../XamlCompilerTaskExecutor.cs | 2 + .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 2 +- .../AvaloniaXamlIlSelectorTransformer.cs | 2 +- .../XamlIlBindingPathHelper.cs | 17 ++++++-- .../Avalonia.Markup.Xaml.Loader/xamlil.github | 2 +- .../CompiledBindingExtensionTests.cs | 43 +++++++++++++++++++ 6 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 593d79471e..d2f7102b48 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -255,6 +255,8 @@ namespace Avalonia.Build.Tasks true), (closureName, closureBaseType) => populateBuilder.DefineSubType(closureBaseType, closureName, false), + (closureName, returnType, parameterTypes) => + populateBuilder.CreateDelegateSubType(closureName, false, returnType, parameterTypes), res.Uri, res ); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 4592b9c8b4..b622971d38 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -201,7 +201,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions if (type.Equals(types.Classes)) { var classes = text.Split(' '); - var classNodes = classes.Select(c => new XamlAstTextNode(node, c, types.XamlIlTypes.String)).ToArray(); + var classNodes = classes.Select(c => new XamlAstTextNode(node, c, type: types.XamlIlTypes.String)).ToArray(); result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, types.Classes, types.XamlIlTypes.String, classNodes); return true; diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs index dfabd66d17..652d9628ac 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -76,7 +76,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers throw new XamlParseException($"Cannot find '{property.Property}' on '{type}", node); if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, - new XamlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String), + new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String), targetProperty.PropertyType, out var typedValue)) throw new XamlParseException( $"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}", diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index c847bf54bc..f35d45e1e6 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -556,26 +556,33 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen) { + IXamlTypeBuilder newDelegateTypeBuilder = null; IXamlType specificDelegateType; - if (_method.ReturnType == context.Configuration.WellKnownTypes.Void && _method.Parameters.Count <= 8) + if (_method.ReturnType == context.Configuration.WellKnownTypes.Void && _method.Parameters.Count == 0) + { + specificDelegateType = context.Configuration.TypeSystem + .GetType("System.Action"); + } + else if (_method.ReturnType == context.Configuration.WellKnownTypes.Void && _method.Parameters.Count <= 16) { specificDelegateType = context.Configuration.TypeSystem .GetType($"System.Action`{_method.Parameters.Count}") .MakeGenericType(_method.Parameters); } - else if (_method.Parameters.Count <= 7) + else if (_method.Parameters.Count <= 16) { List genericParameters = new(); genericParameters.AddRange(_method.Parameters); genericParameters.Add(_method.ReturnType); specificDelegateType = context.Configuration.TypeSystem - .GetType($"System.Func`{_method.Parameters.Count}") + .GetType($"System.Func`{_method.Parameters.Count + 1}") .MakeGenericType(genericParameters); } else { // In this case, we need to emit our own delegate type. - specificDelegateType = null; + string delegateTypeName = context.Configuration.IdentifierGenerator.GenerateIdentifierPart(); + specificDelegateType = newDelegateTypeBuilder = context.DefineDelegateSubType(delegateTypeName, _method.ReturnType, _method.Parameters); } codeGen @@ -583,6 +590,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions .Ldtoken(specificDelegateType) .EmitCall(context.GetAvaloniaTypes() .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Method")); + + newDelegateTypeBuilder?.CreateType(); } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index 8e20d65eb5..daaac590e0 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit 8e20d65eb5f1efbae08e49b18f39bfdce32df7b3 +Subproject commit daaac590e078967b78045f74c38ef046d00d8582 diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 8a61458030..f18955c0b0 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -1042,6 +1042,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void SupportsMethodBindingAsDelegate() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + + + + + + + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + window.DataContext = new MethodDataContext(); + + Assert.IsAssignableFrom(typeof(Action), window.FindControl("action").Content); + Assert.IsAssignableFrom(typeof(Func), window.FindControl("func").Content); + Assert.IsAssignableFrom(typeof(Action), window.FindControl("action16").Content); + Assert.IsAssignableFrom(typeof(Func), window.FindControl("func16").Content); + Assert.True(typeof(Delegate).IsAssignableFrom(window.FindControl("customvoid").Content.GetType())); + Assert.True(typeof(Delegate).IsAssignableFrom(window.FindControl("customint").Content.GetType())); + } + } + void Throws(string type, Action cb) { try @@ -1131,4 +1162,16 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } } + + public class MethodDataContext + { + public void Action() { } + + public int Func() => 1; + + public void Action16(int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15, int i16) { } + public int Func16(int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15, int i16) => i; + public void CustomDelegateTypeVoid(int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15, int i16, int i17) { } + public int CustomDelegateTypeInt(int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15, int i16, int i17) => i; + } } From 2647c5cc73aca06bb44ab719d48b896ab4cf486c Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sun, 19 Dec 2021 18:38:47 -0600 Subject: [PATCH 090/822] Implement support for method->command direct binding (not hooked up yet) --- .../HtmlTransport/webapp/package-lock.json | 10692 +++++++++++++++- .../XamlIlBindingPathHelper.cs | 74 + .../Avalonia.Markup.Xaml.csproj | 1 + .../CompiledBindings/CommandAccessorPlugin.cs | 197 + .../CompiledBindings/CompiledBindingPath.cs | 60 + 5 files changed, 11000 insertions(+), 24 deletions(-) create mode 100644 src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json index 028027a974..aac30523d9 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json @@ -1,8 +1,10579 @@ { "name": "simple", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "simple", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@types/react": "^16.3.14", + "@types/react-dom": "^16.0.5", + "mobx": "4.3.0", + "mobx-react": "^5.1.2", + "react": "^16.3.2", + "react-dom": "^16.3.2" + }, + "devDependencies": { + "awesome-typescript-loader": "^5.0.0", + "clean-webpack-plugin": "^0.1.19", + "compression-webpack-plugin": "^2.0.0", + "copy-webpack-plugin": "^4.6.0", + "cross-env": "^5.1.6", + "css-loader": "^1.0.0", + "file-loader": "^1.1.11", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^0.4.1", + "source-map-loader": "^0.2.3", + "style-loader": "^0.21.0", + "to-string-loader": "^1.1.5", + "tsconfig-paths-webpack-plugin": "^3.2.0", + "typescript": "^2.9.2", + "url-loader": "^1.0.1", + "webpack": "~4.16.3", + "webpack-cli": "~2.1.3", + "webpack-livereload-plugin": "~2.1.1" + } + }, + "node_modules/@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@samverschueren/stream-to-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", + "integrity": "sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==", + "dev": true, + "dependencies": { + "any-observable": "^0.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + }, + "node_modules/@types/react": { + "version": "16.9.11", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.11.tgz", + "integrity": "sha512-UBT4GZ3PokTXSWmdgC/GeCGEJXE5ofWyibCcecRLUVN2ZBpXQGVgQGtG2foS7CrTKFKlQVVswLvf7Js6XA/CVQ==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "node_modules/@types/react-dom": { + "version": "16.9.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.4.tgz", + "integrity": "sha512-fya9xteU/n90tda0s+FtN5Ym4tbgxpq/hb/Af24dvs6uYnYn+fspaxw5USlw0R8apDNwxsqumdRoCoKitckQqw==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz", + "integrity": "sha512-49nwvW/Hx9i+OYHg+mRhKZfAlqThr11Dqz8TsrvqGKMhdI2ijy3KBJOun2Z4770TPjrIJhR6KxChQIDaz8clDA==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-module-context": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/wast-parser": "1.5.13", + "debug": "^3.1.0", + "mamacro": "^0.0.3" + } + }, + "node_modules/@webassemblyjs/ast/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/ast/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz", + "integrity": "sha512-vrvvB18Kh4uyghSKb0NTv+2WZx871WL2NzwMj61jcq2bXkyhRC+8Q0oD7JGVf0+5i/fKQYQSBCNMMsDMRVAMqA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz", + "integrity": "sha512-dBh2CWYqjaDlvMmRP/kudxpdh30uXjIbpkLj9HQe+qtYlwvYjPRjdQXrq1cTAAOUSMTtzqbXIxEdEZmyKfcwsg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz", + "integrity": "sha512-v7igWf1mHcpJNbn4m7e77XOAWXCDT76Xe7Is1VQFXc4K5jRcFrl9D0NrqM4XifQ0bXiuTSkTKMYqDxu5MhNljA==", + "dev": true, + "dependencies": { + "debug": "^3.1.0" + } + }, + "node_modules/@webassemblyjs/helper-buffer/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/helper-buffer/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-code-frame": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz", + "integrity": "sha512-yN6ScQQDFCiAXnVctdVO/J5NQRbwyTbQzsGzEgXsAnrxhjp0xihh+nNHQTMrq5UhOqTb5LykpJAvEv9AT0jnAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/wast-printer": "1.5.13" + } + }, + "node_modules/@webassemblyjs/helper-fsm": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz", + "integrity": "sha512-hSIKzbXjVMRvy3Jzhgu+vDd/aswJ+UMEnLRCkZDdknZO3Z9e6rp1DAs0tdLItjCFqkz9+0BeOPK/mk3eYvVzZg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-module-context": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz", + "integrity": "sha512-zxJXULGPLB7r+k+wIlvGlXpT4CYppRz8fLUM/xobGHc9Z3T6qlmJD9ySJ2jknuktuuiR9AjnNpKYDECyaiX+QQ==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "mamacro": "^0.0.3" + } + }, + "node_modules/@webassemblyjs/helper-module-context/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/helper-module-context/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz", + "integrity": "sha512-0n3SoNGLvbJIZPhtMFq0XmmnA/YmQBXaZKQZcW8maGKwLpVcgjNrxpFZHEOLKjXJYVN5Il8vSfG7nRX50Zn+aw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz", + "integrity": "sha512-IJ/goicOZ5TT1axZFSnlAtz4m8KEjYr12BNOANAwGFPKXM4byEDaMNXYowHMG0yKV9a397eU/NlibFaLwr1fbw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "debug": "^3.1.0" + } + }, + "node_modules/@webassemblyjs/helper-wasm-section/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/helper-wasm-section/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz", + "integrity": "sha512-TseswvXEPpG5TCBKoLx9tT7+/GMACjC1ruo09j46ULRZWYm8XHpDWaosOjTnI7kr4SRJFzA6MWoUkAB+YCGKKg==", + "dev": true, + "dependencies": { + "ieee754": "^1.1.11" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.5.13.tgz", + "integrity": "sha512-0NRMxrL+GG3eISGZBmLBLAVjphbN8Si15s7jzThaw1UE9e5BY1oH49/+MA1xBzxpf1OW5sf9OrPDOclk9wj2yg==", + "dev": true, + "dependencies": { + "long": "4.0.0" + } + }, + "node_modules/@webassemblyjs/leb128/node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.5.13.tgz", + "integrity": "sha512-Ve1ilU2N48Ew0lVGB8FqY7V7hXjaC4+PeZM+vDYxEd+R2iQ0q+Wb3Rw8v0Ri0+rxhoz6gVGsnQNb4FjRiEH/Ng==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz", + "integrity": "sha512-X7ZNW4+Hga4f2NmqENnHke2V/mGYK/xnybJSIXImt1ulxbCOEs/A+ZK/Km2jgihjyVxp/0z0hwIcxC6PrkWtgw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/helper-wasm-section": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "@webassemblyjs/wasm-opt": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "@webassemblyjs/wast-printer": "1.5.13", + "debug": "^3.1.0" + } + }, + "node_modules/@webassemblyjs/wasm-edit/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/wasm-edit/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz", + "integrity": "sha512-yfv94Se8R73zmr8GAYzezFHc3lDwE/lBXQddSiIZEKZFuqy7yWtm3KMwA1uGbv5G1WphimJxboXHR80IgX1hQA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/ieee754": "1.5.13", + "@webassemblyjs/leb128": "1.5.13", + "@webassemblyjs/utf8": "1.5.13" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz", + "integrity": "sha512-IkXSkgzVhQ0QYAdIayuCWMmXSYx0dHGU8Ah/AxJf1gBvstMWVnzJnBwLsXLyD87VSBIcsqkmZ28dVb0mOC3oBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-buffer": "1.5.13", + "@webassemblyjs/wasm-gen": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "debug": "^3.1.0" + } + }, + "node_modules/@webassemblyjs/wasm-opt/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz", + "integrity": "sha512-XnYoIcu2iqq8/LrtmdnN3T+bRjqYFjRHqWbqK3osD/0r/Fcv4d9ecRzjVtC29ENEuNTK4mQ9yyxCBCbK8S/cpg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-api-error": "1.5.13", + "@webassemblyjs/helper-wasm-bytecode": "1.5.13", + "@webassemblyjs/ieee754": "1.5.13", + "@webassemblyjs/leb128": "1.5.13", + "@webassemblyjs/utf8": "1.5.13" + } + }, + "node_modules/@webassemblyjs/wast-parser": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz", + "integrity": "sha512-Lbz65T0LQ1LgzKiUytl34CwuhMNhaCLgrh0JW4rJBN6INnBB8NMwUfQM+FxTnLY9qJ+lHJL/gCM5xYhB9oWi4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/floating-point-hex-parser": "1.5.13", + "@webassemblyjs/helper-api-error": "1.5.13", + "@webassemblyjs/helper-code-frame": "1.5.13", + "@webassemblyjs/helper-fsm": "1.5.13", + "long": "^3.2.0", + "mamacro": "^0.0.3" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz", + "integrity": "sha512-QcwogrdqcBh8Z+eUF8SG+ag5iwQSXxQJELBEHmLkk790wgQgnIMmntT2sMAMw53GiFNckArf5X0bsCA44j3lWQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/wast-parser": "1.5.13", + "long": "^3.2.0" + } + }, + "node_modules/acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-dynamic-import": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", + "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", + "dev": true, + "dependencies": { + "acorn": "^5.0.0" + } + }, + "node_modules/ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "node_modules/ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true, + "peerDependencies": { + "ajv": ">=5.0.0" + } + }, + "node_modules/ajv-keywords": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", + "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-observable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz", + "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1", + "util": "0.10.3" + } + }, + "node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-types": { + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.5.tgz", + "integrity": "sha512-oJjo+5e7/vEc2FBK8gUalV0pba4L3VdBIs2EKhOLHLcOd2FgQIVQN9xb0eZ9IjEWyAL7vq6fGJxOvVvdCHNyMw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/awesome-typescript-loader": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/awesome-typescript-loader/-/awesome-typescript-loader-5.2.1.tgz", + "integrity": "sha512-slv66OAJB8orL+UUaTI3pKlLorwIvS4ARZzYR9iJJyGsEgOqueMfOMdKySWzZ73vIkEe3fcwFgsKMg4d8zyb1g==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^1.1.0", + "lodash": "^4.17.5", + "micromatch": "^3.1.9", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.3", + "webpack-log": "^1.2.0" + }, + "peerDependencies": { + "typescript": "^2.7 || ^3" + } + }, + "node_modules/babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "node_modules/babel-code-frame/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/babel-code-frame/node_modules/js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "node_modules/babel-code-frame/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + } + }, + "node_modules/babel-core/node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "bin": { + "babylon": "bin/babylon.js" + } + }, + "node_modules/babel-core/node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "dependencies": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "node_modules/babel-generator/node_modules/jsesc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", + "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/babel-helper-bindify-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz", + "integrity": "sha1-FMGeXxQte0fxmlJDHlKxzLxAozA=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", + "integrity": "sha1-zORReto1b0IgvK6KAsKzRvmlZmQ=", + "dev": true, + "dependencies": { + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-call-delegate": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz", + "integrity": "sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340=", + "dev": true, + "dependencies": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-define-map": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", + "dev": true, + "dependencies": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz", + "integrity": "sha1-8luCz33BBDPFX3BZLVdGQArCLKo=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-explode-class": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz", + "integrity": "sha1-fcKjkQ3uAHBW4eMdZAztPVTqqes=", + "dev": true, + "dependencies": { + "babel-helper-bindify-decorators": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", + "integrity": "sha1-00dbjAPtmCQqJbSDUasYOZ01gKk=", + "dev": true, + "dependencies": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-get-function-arity": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz", + "integrity": "sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-hoist-variables": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz", + "integrity": "sha1-HssnaJydJVE+rbyZFKc/VAi+enY=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-optimise-call-expression": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz", + "integrity": "sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-regex": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz", + "integrity": "sha1-XsWBgnrXI/7N04HxySg5BnbkVRs=", + "dev": true, + "dependencies": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helper-replace-supers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz", + "integrity": "sha1-v22/5Dk40XNpohPKiov3S2qQqxo=", + "dev": true, + "dependencies": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-helpers": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", + "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", + "integrity": "sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", + "integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=", + "dev": true + }, + "node_modules/babel-plugin-syntax-async-generators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz", + "integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=", + "dev": true + }, + "node_modules/babel-plugin-syntax-class-constructor-call": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz", + "integrity": "sha1-nLnTn+Q8hgC+yBRkVt3L1OGnZBY=", + "dev": true + }, + "node_modules/babel-plugin-syntax-class-properties": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", + "integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=", + "dev": true + }, + "node_modules/babel-plugin-syntax-decorators": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz", + "integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=", + "dev": true + }, + "node_modules/babel-plugin-syntax-dynamic-import": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz", + "integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=", + "dev": true + }, + "node_modules/babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", + "integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=", + "dev": true + }, + "node_modules/babel-plugin-syntax-export-extensions": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz", + "integrity": "sha1-cKFITw+QiaToStRLrDU8lbmxJyE=", + "dev": true + }, + "node_modules/babel-plugin-syntax-flow": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", + "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=", + "dev": true + }, + "node_modules/babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", + "dev": true + }, + "node_modules/babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz", + "integrity": "sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM=", + "dev": true + }, + "node_modules/babel-plugin-transform-async-generator-functions": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz", + "integrity": "sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds=", + "dev": true, + "dependencies": { + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-generators": "^6.5.0", + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz", + "integrity": "sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E=", + "dev": true, + "dependencies": { + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-class-constructor-call": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz", + "integrity": "sha1-gNwoVQWsBn3LjWxl4vbxGrd2Xvk=", + "dev": true, + "dependencies": { + "babel-plugin-syntax-class-constructor-call": "^6.18.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-class-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz", + "integrity": "sha1-anl2PqYdM9NvN7YRqp3vgagbRqw=", + "dev": true, + "dependencies": { + "babel-helper-function-name": "^6.24.1", + "babel-plugin-syntax-class-properties": "^6.8.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-decorators": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz", + "integrity": "sha1-eIAT2PjGtSIr33s0Q5Df13Vp4k0=", + "dev": true, + "dependencies": { + "babel-helper-explode-class": "^6.24.1", + "babel-plugin-syntax-decorators": "^6.13.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz", + "integrity": "sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz", + "integrity": "sha1-u8UbSflk1wy42OC5ToICRs46YUE=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz", + "integrity": "sha1-WkxYpQyclGHlZLSyo7+ryXolhNs=", + "dev": true, + "dependencies": { + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz", + "integrity": "sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz", + "integrity": "sha1-mXux8auWf2gtKwh2/jWNYOdlxW0=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz", + "integrity": "sha1-c+s9MQypaePvnskcU3QabxV2Qj4=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz", + "integrity": "sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz", + "integrity": "sha1-g0yJhTvDaxrw86TF26qU/Y6sqos=", + "dev": true, + "dependencies": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz", + "integrity": "sha1-T1SgLWzWbPkVKAAZox0xklN3yi4=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz", + "integrity": "sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ=", + "dev": true, + "dependencies": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", + "dev": true, + "dependencies": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + } + }, + "node_modules/babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz", + "integrity": "sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM=", + "dev": true, + "dependencies": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz", + "integrity": "sha1-rJl+YoXNGO1hdq22B9YCNErThGg=", + "dev": true, + "dependencies": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz", + "integrity": "sha1-JM72muIcuDp/hgPa0CH1cusnj40=", + "dev": true, + "dependencies": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz", + "integrity": "sha1-V6w1GrScrxSpfNE7CfZv3wpiXys=", + "dev": true, + "dependencies": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz", + "integrity": "sha1-JPh11nIch2YbvZmkYi5R8U3jiqA=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz", + "integrity": "sha1-1taKmfia7cRTbIGlQujdnxdG+NE=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz", + "integrity": "sha1-AMHNsaynERLN8M9hJsLta0V8zbw=", + "dev": true, + "dependencies": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz", + "integrity": "sha1-qEs0UPfp+PH2g51taH2oS7EjbY0=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz", + "integrity": "sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz", + "integrity": "sha1-04sS9C6nMj9yk4fxinxa4frrNek=", + "dev": true, + "dependencies": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "node_modules/babel-plugin-transform-es2015-unicode-regex/node_modules/regexpu-core": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-2.0.0.tgz", + "integrity": "sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA=", + "dev": true, + "dependencies": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "node_modules/babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", + "integrity": "sha1-KrDJx/MJj6SJB3cruBP+QejeOg4=", + "dev": true, + "dependencies": { + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-export-extensions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz", + "integrity": "sha1-U3OLR+deghhYnuqUbLvTkQm75lM=", + "dev": true, + "dependencies": { + "babel-plugin-syntax-export-extensions": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-flow-strip-types": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", + "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", + "dev": true, + "dependencies": { + "babel-plugin-syntax-flow": "^6.18.0", + "babel-runtime": "^6.22.0" + } + }, + "node_modules/babel-plugin-transform-object-rest-spread": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz", + "integrity": "sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY=", + "dev": true, + "dependencies": { + "babel-plugin-syntax-object-rest-spread": "^6.8.0", + "babel-runtime": "^6.26.0" + } + }, + "node_modules/babel-plugin-transform-regenerator": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", + "dev": true, + "dependencies": { + "regenerator-transform": "^0.10.0" + } + }, + "node_modules/babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", + "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "node_modules/babel-preset-es2015": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", + "integrity": "sha1-1EBQ1rwsn+6nAqrzjXJ6AhBTiTk=", + "deprecated": "🙌 Thanks for using Babel: we recommend using babel-preset-env now: please read https://babeljs.io/env to update!", + "dev": true, + "dependencies": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.24.1", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-computed-properties": "^6.24.1", + "babel-plugin-transform-es2015-destructuring": "^6.22.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", + "babel-plugin-transform-es2015-for-of": "^6.22.0", + "babel-plugin-transform-es2015-function-name": "^6.24.1", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-umd": "^6.24.1", + "babel-plugin-transform-es2015-object-super": "^6.24.1", + "babel-plugin-transform-es2015-parameters": "^6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", + "babel-plugin-transform-regenerator": "^6.24.1" + } + }, + "node_modules/babel-preset-stage-1": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz", + "integrity": "sha1-dpLNfc1oSZB+auSgqFWJz7niv7A=", + "dev": true, + "dependencies": { + "babel-plugin-transform-class-constructor-call": "^6.24.1", + "babel-plugin-transform-export-extensions": "^6.22.0", + "babel-preset-stage-2": "^6.24.1" + } + }, + "node_modules/babel-preset-stage-2": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz", + "integrity": "sha1-2eKWD7PXEYfw5k7sYrwHdnIZvcE=", + "dev": true, + "dependencies": { + "babel-plugin-syntax-dynamic-import": "^6.18.0", + "babel-plugin-transform-class-properties": "^6.24.1", + "babel-plugin-transform-decorators": "^6.24.1", + "babel-preset-stage-3": "^6.24.1" + } + }, + "node_modules/babel-preset-stage-3": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz", + "integrity": "sha1-g2raCp56f6N8sTj7kyb4eTSkg5U=", + "dev": true, + "dependencies": { + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-generator-functions": "^6.24.1", + "babel-plugin-transform-async-to-generator": "^6.24.1", + "babel-plugin-transform-exponentiation-operator": "^6.24.1", + "babel-plugin-transform-object-rest-spread": "^6.22.0" + } + }, + "node_modules/babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "dependencies": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + } + }, + "node_modules/babel-register/node_modules/source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "dependencies": { + "source-map": "^0.5.6" + } + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-template/node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "bin": { + "babylon": "bin/babylon.js" + } + }, + "node_modules/babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "node_modules/babel-traverse/node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "bin": { + "babylon": "bin/babylon.js" + } + }, + "node_modules/babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "dependencies": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "node_modules/babylon": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", + "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", + "dev": true, + "bin": { + "babylon": "bin/babylon.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/binaryextensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.2.0.tgz", + "integrity": "sha512-bHhs98rj/7i/RZpCSJ3uk55pLXOItjIrh2sRQZSM6OoktScX+LxJzvlU+FELp9j3TdcddTmmYArLSGptCTwjuw==", + "dev": true, + "engines": { + "node": ">=0.8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bluebird": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", + "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", + "dev": true + }, + "node_modules/bn.js": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", + "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==", + "dev": true + }, + "node_modules/body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "dev": true, + "dependencies": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz", + "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=", + "dev": true, + "dependencies": { + "bn.js": "^4.1.1", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.2", + "elliptic": "^6.0.0", + "inherits": "^2.0.1", + "parse-asn1": "^5.0.0" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "node_modules/bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true + }, + "node_modules/cacache": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz", + "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==", + "dev": true, + "dependencies": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", + "dev": true, + "dependencies": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 will break on node v14+. Upgrade to chokidar 3 with 15x less dependencies.", + "dev": true, + "dependencies": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "optionalDependencies": { + "fsevents": "^1.2.7" + } + }, + "node_modules/chownr": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", + "dev": true + }, + "node_modules/chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-webpack-plugin": { + "version": "0.1.19", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-0.1.19.tgz", + "integrity": "sha512-M1Li5yLHECcN2MahoreuODul5LkjohJGFxLPTjl3j1ttKrF5rgjZET1SJduuqxLAuT1gAPOdkhg03qcaaU1KeA==", + "dev": true, + "dependencies": { + "rimraf": "^2.6.1" + } + }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "dev": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-table/node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/cli-truncate": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", + "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", + "dev": true, + "dependencies": { + "slice-ansi": "0.0.4", + "string-width": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "node_modules/cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "dependencies": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui/node_modules/wrap-ansi/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cliui/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/compression-webpack-plugin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-2.0.0.tgz", + "integrity": "sha512-bDgd7oTUZC8EkRx8j0sjyCfeiO+e5sFcfgaFcjVhfQf5lLya7oY2BczxcJ7IUuVjz5m6fy8IECFmVFew3xLk8Q==", + "dev": true, + "dependencies": { + "cacache": "^11.2.0", + "find-cache-dir": "^2.0.0", + "neo-async": "^2.5.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.4.0", + "webpack-sources": "^1.0.1" + }, + "engines": { + "node": ">= 6.9.0 <7.0.0 || >= 8.9.0" + }, + "peerDependencies": { + "webpack": "^4.3.0" + } + }, + "node_modules/compression-webpack-plugin/node_modules/cacache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", + "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "dev": true, + "dependencies": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "node_modules/compression-webpack-plugin/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/compression-webpack-plugin/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/compression-webpack-plugin/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/compression-webpack-plugin/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/compression-webpack-plugin/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/compression-webpack-plugin/node_modules/mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "dependencies": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/compression-webpack-plugin/node_modules/p-limit": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz", + "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/compression-webpack-plugin/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/compression-webpack-plugin/node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/compression-webpack-plugin/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/compression-webpack-plugin/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/compression-webpack-plugin/node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/compression-webpack-plugin/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/compression-webpack-plugin/node_modules/ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "dependencies": { + "figgy-pudding": "^3.5.1" + } + }, + "node_modules/compression-webpack-plugin/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "node_modules/continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "dependencies": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.6.0.tgz", + "integrity": "sha512-Y+SQCF+0NoWQryez2zXn5J5knmr9z/9qSQt7fbL78u83rxmigOy8X5+BFn8CFSuX+nKT8gpYwJX68ekqtQt6ZA==", + "dev": true, + "dependencies": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "globby": "^7.1.1", + "is-glob": "^4.0.0", + "loader-utils": "^1.1.0", + "minimatch": "^3.0.4", + "p-limit": "^1.0.0", + "serialize-javascript": "^1.4.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/core-js": { + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", + "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==", + "deprecated": "core-js@<3.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/create-ecdh": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", + "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.0.0" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-env": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz", + "integrity": "sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.5" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/css-loader": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.1.tgz", + "integrity": "sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw==", + "dev": true, + "dependencies": { + "babel-code-frame": "^6.26.0", + "css-selector-tokenizer": "^0.7.0", + "icss-utils": "^2.1.0", + "loader-utils": "^1.0.2", + "lodash": "^4.17.11", + "postcss": "^6.0.23", + "postcss-modules-extract-imports": "^1.2.0", + "postcss-modules-local-by-default": "^1.2.0", + "postcss-modules-scope": "^1.1.0", + "postcss-modules-values": "^1.3.0", + "postcss-value-parser": "^3.3.0", + "source-list-map": "^2.0.0" + }, + "engines": { + "node": ">= 6.9.0 <7.0.0 || >= 8.9.0" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, + "node_modules/css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "dependencies": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "node_modules/css-selector-tokenizer": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.1.tgz", + "integrity": "sha512-xYL0AMZJ4gFzJQsHUKa5jiWWi2vH77WVNg7JYRyewwj6oPh4yb/y6Y9ZCw9dsj/9UauMhtuxR+ogQd//EdEVNA==", + "dev": true, + "dependencies": { + "cssesc": "^0.1.0", + "fastparse": "^1.1.1", + "regexpu-core": "^1.0.0" + } + }, + "node_modules/css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/cssesc": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz", + "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + } + }, + "node_modules/csstype": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.7.tgz", + "integrity": "sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ==" + }, + "node_modules/cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", + "dev": true + }, + "node_modules/d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "dependencies": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "node_modules/dargs": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-5.1.0.tgz", + "integrity": "sha1-7H6lDHhWTNNsnV7Bj2Yyn63ieCk=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==", + "dev": true + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/detect-conflict": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/detect-conflict/-/detect-conflict-1.0.1.tgz", + "integrity": "sha1-CIZXpmqWHAUBnbfEIwiDsca0F24=", + "dev": true + }, + "node_modules/detect-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", + "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", + "dev": true, + "dependencies": { + "repeating": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "dependencies": { + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + }, + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true, + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/editions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-2.2.0.tgz", + "integrity": "sha512-RYg3iEA2BDLCNVe8PUkD+ox5vAKxB9XS/mAhx1bdxGCF0CpX077C0pyTA9t5D6idCYA3avl5/XDHKPsHFrygfw==", + "dev": true, + "dependencies": { + "errlop": "^1.1.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/editions/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/ejs": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.2.tgz", + "integrity": "sha512-rHGwtpl67oih3xAHbZlpw5rQAt+YV1mSCu2fUZ9XNrfaGEhom7E+AUiMci+ByP4aSfuAWx7hE0BPuJLMrpXwOw==", + "dev": true, + "hasInstallScript": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/elegant-spinner": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", + "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/elliptic": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", + "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==", + "dev": true, + "dependencies": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "node_modules/emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + }, + "node_modules/envinfo": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-5.12.1.tgz", + "integrity": "sha512-pwdo0/G3CIkQ0y6PCXq4RdkvId2elvtPCJMG0konqlrfkWQbf1DWeH9K2b/cvu2YgGvPPTOnonZxXM1gikFu1w==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/errlop": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/errlop/-/errlop-1.3.0.tgz", + "integrity": "sha512-qbb3CRis7c/XouHw41umhSIt4e0KFcESXDrWvgFwIuD/neT1WBO9lKFj7sriqPgF8xEY0Vz6Ws5hMbjN4q/hsQ==", + "dev": true, + "dependencies": { + "editions": "^2.2.0" + }, + "engines": { + "node": ">=0.8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "dev": true, + "dependencies": { + "string-template": "~0.2.1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.0", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es5-ext": { + "version": "0.10.52", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.52.tgz", + "integrity": "sha512-bWCbE9fbpYQY4CU6hJbJ1vSz70EClMlDgJ7BmwI+zEJhxrwjesZRPglGJlsZhu0334U3hI+gaspwksH9IGD6ag==", + "dev": true, + "dependencies": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.2", + "next-tick": "~1.0.0" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "dependencies": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "dependencies": { + "estraverse": "^4.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa/node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "dependencies": { + "fill-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "dependencies": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-range/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.2.0.tgz", + "integrity": "sha512-0ccUQK/9e3NreLFg6K6np8aPyRgwycx+oFGtfx1dSp7Wj00Ozw9r05FgBRlzjf2XBM7LAzwgLyDscRrtSU91hA==", + "dev": true, + "dependencies": { + "type": "^2.0.0" + } + }, + "node_modules/ext/node_modules/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "dev": true + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dev": true, + "dependencies": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "node_modules/fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "dev": true, + "dependencies": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "node_modules/fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "node_modules/faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "dev": true + }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-loader": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz", + "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==", + "dev": true, + "dependencies": { + "loader-utils": "^1.0.2", + "schema-utils": "^0.4.5" + }, + "engines": { + "node": ">= 4.3 < 5.0.0 || >= 5.10" + }, + "peerDependencies": { + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-cache-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/first-chunk-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", + "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", + "dev": true, + "dependencies": { + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/flow-parser": { + "version": "0.112.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.112.0.tgz", + "integrity": "sha512-sxjnwhR76B/fUN6n/XerYzn8R1HvtVo3SM8Il3WiZ4nkAlb2BBzKe1TSVKGSyZgD6FW9Bsxom/57ktkqrqmXGA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "bundleDependencies": [ + "node-pre-gyp" + ], + "deprecated": "fsevents 1 will break on node v14+ and could be using insecure binaries. Upgrade to fsevents 2.", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/fsevents/node_modules/abbrev": { + "version": "1.1.1", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/ansi-regex": { + "version": "2.1.1", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/aproba": { + "version": "1.2.0", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/are-we-there-yet": { + "version": "1.1.5", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "node_modules/fsevents/node_modules/balanced-match": { + "version": "1.0.0", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/brace-expansion": { + "version": "1.1.11", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/fsevents/node_modules/chownr": { + "version": "1.1.1", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/code-point-at": { + "version": "1.1.0", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/concat-map": { + "version": "0.0.1", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/console-control-strings": { + "version": "1.1.0", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/core-util-is": { + "version": "1.0.2", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/debug": { + "version": "4.1.1", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/fsevents/node_modules/deep-extend": { + "version": "0.6.0", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/fsevents/node_modules/delegates": { + "version": "1.0.0", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/detect-libc": { + "version": "1.0.3", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, + "inBundle": true, + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/fsevents/node_modules/fs-minipass": { + "version": "1.2.5", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minipass": "^2.2.1" + } + }, + "node_modules/fsevents/node_modules/fs.realpath": { + "version": "1.0.0", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/gauge": { + "version": "2.7.4", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/fsevents/node_modules/glob": { + "version": "7.1.3", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/has-unicode": { + "version": "2.0.1", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/iconv-lite": { + "version": "0.4.24", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/ignore-walk": { + "version": "3.0.1", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/fsevents/node_modules/inflight": { + "version": "1.0.6", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/fsevents/node_modules/inherits": { + "version": "2.0.3", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/ini": { + "version": "1.3.5", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/isarray": { + "version": "1.0.0", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/minimatch": { + "version": "3.0.4", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fsevents/node_modules/minimist": { + "version": "0.0.8", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/minipass": { + "version": "2.3.5", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/fsevents/node_modules/minizlib": { + "version": "1.2.1", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minipass": "^2.2.1" + } + }, + "node_modules/fsevents/node_modules/mkdirp": { + "version": "0.5.1", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/fsevents/node_modules/ms": { + "version": "2.1.1", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/needle": { + "version": "2.3.0", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/fsevents/node_modules/node-pre-gyp": { + "version": "0.12.0", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", + "deprecated": "Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/fsevents/node_modules/nopt": { + "version": "4.0.1", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/fsevents/node_modules/npm-bundled": { + "version": "1.0.6", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/npm-packlist": { + "version": "1.4.1", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "node_modules/fsevents/node_modules/npmlog": { + "version": "4.1.2", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/fsevents/node_modules/number-is-nan": { + "version": "1.0.1", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/object-assign": { + "version": "4.1.1", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/once": { + "version": "1.4.0", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/fsevents/node_modules/os-homedir": { + "version": "1.0.2", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/os-tmpdir": { + "version": "1.0.2", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/osenv": { + "version": "0.1.5", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "node_modules/fsevents/node_modules/path-is-absolute": { + "version": "1.0.1", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/process-nextick-args": { + "version": "2.0.0", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/rc": { + "version": "1.2.8", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/fsevents/node_modules/rc/node_modules/minimist": { + "version": "1.2.0", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/readable-stream": { + "version": "2.3.6", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/fsevents/node_modules/rimraf": { + "version": "2.6.3", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/fsevents/node_modules/safe-buffer": { + "version": "5.1.2", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/safer-buffer": { + "version": "2.1.2", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/sax": { + "version": "1.2.4", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/semver": { + "version": "5.7.0", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true, + "inBundle": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/fsevents/node_modules/set-blocking": { + "version": "2.0.0", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/signal-exit": { + "version": "3.0.2", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/string_decoder": { + "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fsevents/node_modules/string-width": { + "version": "1.0.2", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/strip-ansi": { + "version": "3.0.1", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/strip-json-comments": { + "version": "2.0.1", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "inBundle": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fsevents/node_modules/tar": { + "version": "4.4.8", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, + "engines": { + "node": ">=4.5" + } + }, + "node_modules/fsevents/node_modules/util-deprecate": { + "version": "1.0.2", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/wide-align": { + "version": "1.1.3", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "inBundle": true, + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/fsevents/node_modules/wrappy": { + "version": "1.0.2", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/fsevents/node_modules/yallist": { + "version": "3.0.3", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true, + "inBundle": true, + "optional": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-got": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-6.0.0.tgz", + "integrity": "sha512-F/mS+fsWQMo1zfgG9MD8KWvTWPPzzhuVwY++fhQ5Ggd+0P+CAMHtzMZhNxG+TqGfHDChJKsbh6otfMGqO2AKBw==", + "dev": true, + "dependencies": { + "got": "^7.0.0", + "is-plain-obj": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gh-got/node_modules/got": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", + "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", + "dev": true, + "dependencies": { + "decompress-response": "^3.2.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-plain-obj": "^1.1.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "p-cancelable": "^0.3.0", + "p-timeout": "^1.1.1", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "url-parse-lax": "^1.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gh-got/node_modules/p-cancelable": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", + "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/gh-got/node_modules/p-timeout": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", + "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", + "dev": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/gh-got/node_modules/prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-got/node_modules/url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "dependencies": { + "prepend-http": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/github-username": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/github-username/-/github-username-4.1.0.tgz", + "integrity": "sha1-y+KABBiDIG2kISrp5LXxacML9Bc=", + "dev": true, + "dependencies": { + "gh-got": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-all": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-all/-/glob-all-3.1.0.tgz", + "integrity": "sha1-iRPd+17hrHgSZWJBsD1SF8ZLAqs=", + "dev": true, + "dependencies": { + "glob": "^7.0.5", + "yargs": "~1.2.6" + }, + "bin": { + "glob-all": "bin/glob-all" + } + }, + "node_modules/glob-all/node_modules/minimist": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz", + "integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4=", + "dev": true + }, + "node_modules/glob-all/node_modules/yargs": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-1.2.6.tgz", + "integrity": "sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=", + "dev": true, + "dependencies": { + "minimist": "^0.1.0" + } + }, + "node_modules/glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "dependencies": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-base/node_modules/glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "dependencies": { + "is-glob": "^2.0.0" + } + }, + "node_modules/glob-base/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-base/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "dev": true + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "node_modules/grouped-queue": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-0.3.3.tgz", + "integrity": "sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=", + "dev": true, + "dependencies": { + "lodash": "^4.17.2" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "dev": true, + "dependencies": { + "has-symbol-support-x": "^1.4.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-wbg3bpgA/ZqWrZuMOeJi8+SKMhr7X9TesL/rXMjTzh0p0JUBo3II8DHboYbuIXWRlttrUFxwcu/5kygrCw8fJw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/home-or-tmp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", + "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "dev": true + }, + "node_modules/html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "dependencies": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + }, + "bin": { + "html-minifier": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/html-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", + "deprecated": "3.x is no longer supported", + "dev": true, + "dependencies": { + "html-minifier": "^3.2.3", + "loader-utils": "^0.2.16", + "lodash": "^4.17.3", + "pretty-error": "^2.0.2", + "tapable": "^1.0.0", + "toposort": "^1.0.0", + "util.promisify": "1.0.0" + }, + "engines": { + "node": ">=6.9" + }, + "peerDependencies": { + "webpack": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/html-webpack-plugin/node_modules/big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/html-webpack-plugin/node_modules/json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/html-webpack-plugin/node_modules/loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "dependencies": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "node_modules/htmlparser2/node_modules/readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "node_modules/http-parser-js": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz", + "integrity": "sha1-ksnBN0w1CF912zWexWzCV8u5P6Q=", + "dev": true + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-replace-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", + "dev": true + }, + "node_modules/icss-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-2.1.0.tgz", + "integrity": "sha1-g/Cg7DeL8yRheLbCrZE28TWxyWI=", + "dev": true, + "dependencies": { + "postcss": "^6.0.1" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "node_modules/iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "node_modules/ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "node_modules/import-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", + "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "dev": true, + "dependencies": { + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "deprecated": "Please update to ini >=1.3.6 to avoid a prototype pollution issue", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/inquirer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", + "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.1.0", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^5.5.2", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", + "dev": true, + "dependencies": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "dependencies": { + "is-primitive": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "dev": true + }, + "node_modules/is-observable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz", + "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==", + "dev": true, + "dependencies": { + "symbol-observable": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-observable/node_modules/symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "dependencies": { + "has": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-scoped": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-1.0.0.tgz", + "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", + "dev": true, + "dependencies": { + "scoped-regex": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", + "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", + "dev": true, + "dependencies": { + "buffer-alloc": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istextorbinary": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.6.0.tgz", + "integrity": "sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==", + "dev": true, + "dependencies": { + "binaryextensions": "^2.1.2", + "editions": "^2.2.0", + "textextensions": "^2.5.0" + }, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "dev": true, + "dependencies": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jscodeshift": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.5.1.tgz", + "integrity": "sha512-sRMollbhbmSDrR79JMAnhEjyZJlQQVozeeY9A6/KNuV26DNcuB3mGSCWXp0hks9dcwRNOELbNOiwraZaXXRk5Q==", + "dev": true, + "dependencies": { + "babel-plugin-transform-flow-strip-types": "^6.8.0", + "babel-preset-es2015": "^6.9.0", + "babel-preset-stage-1": "^6.5.0", + "babel-register": "^6.9.0", + "babylon": "^7.0.0-beta.47", + "colors": "^1.1.2", + "flow-parser": "^0.*", + "lodash": "^4.13.1", + "micromatch": "^2.3.7", + "neo-async": "^2.5.0", + "node-dir": "0.1.8", + "nomnom": "^1.8.1", + "recast": "^0.15.0", + "temp": "^0.8.1", + "write-file-atomic": "^1.2.0" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.sh" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jscodeshift/node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jscodeshift/node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jscodeshift/node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jscodeshift/node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jscodeshift/node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jscodeshift/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jscodeshift/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jscodeshift/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jscodeshift/node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jscodeshift/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "dependencies": { + "invert-kv": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/listr": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz", + "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==", + "dev": true, + "dependencies": { + "@samverschueren/stream-to-observable": "^0.3.0", + "is-observable": "^1.1.0", + "is-promise": "^2.1.0", + "is-stream": "^1.1.0", + "listr-silent-renderer": "^1.1.1", + "listr-update-renderer": "^0.5.0", + "listr-verbose-renderer": "^0.5.0", + "p-map": "^2.0.0", + "rxjs": "^6.3.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/listr-silent-renderer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", + "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/listr-update-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz", + "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "cli-truncate": "^0.2.1", + "elegant-spinner": "^1.0.1", + "figures": "^1.7.0", + "indent-string": "^3.0.0", + "log-symbols": "^1.0.2", + "log-update": "^2.3.0", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "listr": "^0.14.2" + } + }, + "node_modules/listr-update-renderer/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/listr-update-renderer/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/listr-update-renderer/node_modules/figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/listr-update-renderer/node_modules/log-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", + "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", + "dev": true, + "dependencies": { + "chalk": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/listr-update-renderer/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/listr-verbose-renderer": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz", + "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "cli-cursor": "^2.1.0", + "date-fns": "^1.27.2", + "figures": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/listr/node_modules/rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/livereload-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-update": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", + "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.0.0", + "cli-cursor": "^2.0.0", + "wrap-ansi": "^3.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/loglevelnext": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.5.tgz", + "integrity": "sha512-V/73qkPuJmx4BcBF19xPBr+0ZRVBhc4POxvZTZdMeXpJ4NItXSJ/MSwuFT0kQJlCbXvdlZoQQ/418bS1y9Jh6A==", + "dev": true, + "dependencies": { + "es6-symbol": "^3.1.1", + "object.assign": "^4.1.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/long": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", + "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, + "node_modules/map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "dependencies": { + "p-defer": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", + "dev": true + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "dependencies": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/mem-fs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.1.3.tgz", + "integrity": "sha1-uK6NLj/Lb10/kWXBLUVRoGXZicw=", + "dev": true, + "dependencies": { + "through2": "^2.0.0", + "vinyl": "^1.1.0", + "vinyl-file": "^2.0.0" + } + }, + "node_modules/mem-fs-editor": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-4.0.3.tgz", + "integrity": "sha512-tgWmwI/+6vwu6POan82dTjxEpwAoaj0NAFnghtVo/FcLK2/7IhPUtFUUYlwou4MOY6OtjTUJtwpfH1h+eSUziw==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "deep-extend": "^0.6.0", + "ejs": "^2.5.9", + "glob": "^7.0.3", + "globby": "^7.1.1", + "isbinaryfile": "^3.0.2", + "mkdirp": "^0.5.0", + "multimatch": "^2.0.0", + "rimraf": "^2.2.8", + "through2": "^2.0.0", + "vinyl": "^2.0.1" + } + }, + "node_modules/mem-fs-editor/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/mem-fs-editor/node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "node_modules/mem-fs-editor/node_modules/replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/mem-fs-editor/node_modules/vinyl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "dev": true, + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/mem/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mem/node_modules/p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/merge2": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.5.tgz", + "integrity": "sha512-dqBanNfktnp2hwL2YguV9Jh91PFX7gu7nRLs4TGsbAfAG6WOtlynFRYzwDwmmeSb5uIwHo9nx1ta0f7vAZVp2w==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "engines": { + "node": ">= 6.9.0 <7.0.0 || >= 8.9.0" + }, + "peerDependencies": { + "webpack": "^4.4.0" + } + }, + "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "node_modules/mississippi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz", + "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==", + "dev": true, + "dependencies": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp/node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "node_modules/mobx": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-4.3.0.tgz", + "integrity": "sha1-UKXmP9AZeoPGgWb/g62kW2XIWfg=" + }, + "node_modules/mobx-react": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.4.4.tgz", + "integrity": "sha512-2mTzpyEjVB/RGk2i6KbcmP4HWcAUFox5ZRCrGvSyz49w20I4C4qql63grPpYrS9E9GKwgydBHQlA4y665LuRCQ==", + "dependencies": { + "hoist-non-react-statics": "^3.0.0", + "react-lifecycles-compat": "^3.0.2" + }, + "peerDependencies": { + "mobx": "^4.0.0 || ^5.0.0", + "react": "^0.13.0 || ^0.14.0 || ^15.0.0 || ^16.0.0" + } + }, + "node_modules/move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "dependencies": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "node_modules/multimatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", + "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", + "dev": true, + "dependencies": { + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "node_modules/nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "node_modules/next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, + "node_modules/node-dir": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.8.tgz", + "integrity": "sha1-VfuN62mQcHB/tn+RpGDwRIKUx30=", + "dev": true, + "engines": { + "node": ">= 0.10.5" + } + }, + "node_modules/node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + } + }, + "node_modules/node-libs-browser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "node_modules/nomnom": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", + "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", + "deprecated": "Package no longer supported. Contact support@npmjs.com for more info.", + "dev": true, + "dependencies": { + "chalk": "~0.4.0", + "underscore": "~1.6.0" + } + }, + "node_modules/nomnom/node_modules/ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nomnom/node_modules/chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dev": true, + "dependencies": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/nomnom/node_modules/strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", + "dev": true, + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "dependencies": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "dependencies": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, + "dependencies": { + "p-reduce": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-lazy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-lazy/-/p-lazy-1.0.0.tgz", + "integrity": "sha1-7FPIAvLuOsKPFmzILQsrAt4nqDU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "dev": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pako": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", + "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "dev": true + }, + "node_modules/parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, + "dependencies": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "dev": true, + "dependencies": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "dependencies": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-glob/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-glob/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pbkdf2": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "dependencies": { + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz", + "integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==", + "dev": true, + "dependencies": { + "postcss": "^6.0.1" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz", + "integrity": "sha1-99gMOYxaOT+nlkRmvRlQCn1hwGk=", + "dev": true, + "dependencies": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "node_modules/postcss-modules-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz", + "integrity": "sha1-1upkmUx5+XtipytCb75gVqGUu5A=", + "dev": true, + "dependencies": { + "css-selector-tokenizer": "^0.7.0", + "postcss": "^6.0.1" + } + }, + "node_modules/postcss-modules-values": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz", + "integrity": "sha1-7P+p1+GSUYOJ9CrQ6D9yrsRW6iA=", + "dev": true, + "dependencies": { + "icss-replace-symbols": "^1.1.0", + "postcss": "^6.0.1" + } + }, + "node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "node_modules/postcss/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-bytes": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", + "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "dependencies": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "node_modules/private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "node_modules/prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==", + "dev": true, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "dependencies": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "dev": true, + "dependencies": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/randomatic/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "dev": true, + "dependencies": { + "bytes": "1", + "string_decoder": "0.10" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/raw-body/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "node_modules/react": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", + "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz", + "integrity": "sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.18.0" + }, + "peerDependencies": { + "react": "^16.0.0" + } + }, + "node_modules/react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/read-chunk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz", + "integrity": "sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU=", + "dev": true, + "dependencies": { + "pify": "^3.0.0", + "safe-buffer": "^5.1.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/recast": { + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.15.5.tgz", + "integrity": "sha512-nkAYNqarh73cMWRKFiPQ8I9dOLFvFk6SnG8u/LUlOYfArDOD/EjsVRAs860TlBLrpxqAXHGET/AUAVjdEymL5w==", + "dev": true, + "dependencies": { + "ast-types": "0.11.5", + "esprima": "~4.0.0", + "private": "~0.1.5", + "source-map": "~0.6.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regenerate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", + "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "dev": true + }, + "node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", + "dev": true, + "dependencies": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "node_modules/regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "dependencies": { + "is-equal-shallow": "^0.1.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regexpu-core": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz", + "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=", + "dev": true, + "dependencies": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "node_modules/regjsgen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz", + "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz", + "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/renderkid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", + "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "dev": true, + "dependencies": { + "css-select": "^1.1.0", + "dom-converter": "^0.2", + "htmlparser2": "^3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + } + }, + "node_modules/repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "dependencies": { + "is-finite": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "node_modules/resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + } + }, + "node_modules/resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "dependencies": { + "is-promise": "^2.1.0" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "dependencies": { + "aproba": "^1.1.1" + } + }, + "node_modules/rxjs": { + "version": "5.5.12", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", + "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", + "dev": true, + "dependencies": { + "symbol-observable": "1.0.1" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", + "dev": true + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/scheduler": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", + "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/scoped-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", + "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "dev": true + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "node_modules/slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "dependencies": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "dependencies": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssri": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz", + "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.1" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "node_modules/strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-bom-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", + "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", + "dev": true, + "dependencies": { + "first-chunk-stream": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-bom-stream/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-loader": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz", + "integrity": "sha512-T+UNsAcl3Yg+BsPKs1vd22Fr8sVT+CJMtzqc6LEw9bbJZb43lm9GoeIfUcDEefBSWC0BhYbcdupV1GtI4DGzxg==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "schema-utils": "^0.4.5" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/temp": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "dev": true, + "dependencies": { + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/textextensions": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", + "integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==", + "dev": true, + "engines": { + "node": ">=0.8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/timers-browserify": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "dependencies": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" + } + }, + "node_modules/tiny-lr/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/tiny-lr/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-string-loader": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/to-string-loader/-/to-string-loader-1.1.6.tgz", + "integrity": "sha512-VNg62//PS1WfNwrK3n7t6wtK5Vdtx/qeYLLEioW46VMlYUwAYT6wnfB+OwS2FMTCalIHu0tk79D3RXX8ttmZTQ==", + "dev": true, + "dependencies": { + "loader-utils": "^1.0.0" + } + }, + "node_modules/toposort": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz", + "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", + "dev": true + }, + "node_modules/trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-S/gOOPOkV8rIL4LurZ1vUdYCVgo15iX9ZMJ6wx6w2OgcpT/G4wMyHB6WM+xheSqGMrWKuxFul+aXpCju3wmj/g==", + "dev": true, + "dependencies": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "tsconfig-paths": "^3.4.0" + } + }, + "node_modules/tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, + "node_modules/tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "node_modules/type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "node_modules/typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "dependencies": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uglify-js/node_modules/commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "node_modules/uglify-js/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglifyjs-webpack-plugin": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", + "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==", + "dev": true, + "dependencies": { + "cacache": "^10.0.4", + "find-cache-dir": "^1.0.0", + "schema-utils": "^0.4.5", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "uglify-es": "^3.3.4", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "engines": { + "node": ">= 4.8 < 5.0.0 || >= 5.10" + }, + "peerDependencies": { + "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/commander": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", + "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "dev": true + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglifyjs-webpack-plugin/node_modules/uglify-es": { + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", + "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", + "deprecated": "support for ECMAScript is superseded by `uglify-js` as of v3.13.0", + "dev": true, + "dependencies": { + "commander": "~2.13.0", + "source-map": "~0.6.1" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/untildify": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", + "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url-loader": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", + "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", + "dev": true, + "dependencies": { + "loader-utils": "^1.1.0", + "mime": "^2.0.3", + "schema-utils": "^1.0.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "webpack": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "node_modules/uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "dependencies": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + }, + "engines": { + "node": ">= 0.9" + } + }, + "node_modules/vinyl-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", + "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "pify": "^2.3.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0", + "strip-bom-stream": "^2.0.0", + "vinyl": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl-file/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl-file/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "node_modules/watchpack": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", + "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==", + "dev": true, + "dependencies": { + "chokidar": "^2.0.2", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + } + }, + "node_modules/webpack": { + "version": "4.16.5", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.16.5.tgz", + "integrity": "sha512-i5cHYHonzSc1zBuwB5MSzW4v9cScZFbprkHK8ZgzPDCRkQXGGpYzPmJhbus5bOrZ0tXTcQp+xyImRSvKb0b+Kw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.5.13", + "@webassemblyjs/helper-module-context": "1.5.13", + "@webassemblyjs/wasm-edit": "1.5.13", + "@webassemblyjs/wasm-opt": "1.5.13", + "@webassemblyjs/wasm-parser": "1.5.13", + "acorn": "^5.6.2", + "acorn-dynamic-import": "^3.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^0.4.4", + "tapable": "^1.0.0", + "uglifyjs-webpack-plugin": "^1.2.4", + "watchpack": "^1.5.0", + "webpack-sources": "^1.0.1" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/webpack-addons": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/webpack-addons/-/webpack-addons-1.1.5.tgz", + "integrity": "sha512-MGO0nVniCLFAQz1qv22zM02QPjcpAoJdy7ED0i3Zy7SY1IecgXCm460ib7H/Wq7e9oL5VL6S2BxaObxwIcag0g==", + "dev": true, + "dependencies": { + "jscodeshift": "^0.4.0" + } + }, + "node_modules/webpack-addons/node_modules/arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "dependencies": { + "arr-flatten": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-addons/node_modules/array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-addons/node_modules/ast-types": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.10.1.tgz", + "integrity": "sha512-UY7+9DPzlJ9VM8eY0b2TUZcZvF+1pO0hzMtAyjBYKhOmnvRlqYNYnWdtsMj0V16CGaMlpL0G1jnLbLo4AyotuQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-addons/node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "node_modules/webpack-addons/node_modules/babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true, + "bin": { + "babylon": "bin/babylon.js" + } + }, + "node_modules/webpack-addons/node_modules/braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "dependencies": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-addons/node_modules/expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "dependencies": { + "is-posix-bracket": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-addons/node_modules/extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-addons/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-addons/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-addons/node_modules/jscodeshift": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.4.1.tgz", + "integrity": "sha512-iOX6If+hsw0q99V3n31t4f5VlD1TQZddH08xbT65ZqA7T4Vkx68emrDZMUOLVvCEAJ6NpAk7DECe3fjC/t52AQ==", + "dev": true, + "dependencies": { + "async": "^1.5.0", + "babel-plugin-transform-flow-strip-types": "^6.8.0", + "babel-preset-es2015": "^6.9.0", + "babel-preset-stage-1": "^6.5.0", + "babel-register": "^6.9.0", + "babylon": "^6.17.3", + "colors": "^1.1.2", + "flow-parser": "^0.*", + "lodash": "^4.13.1", + "micromatch": "^2.3.7", + "node-dir": "0.1.8", + "nomnom": "^1.8.1", + "recast": "^0.12.5", + "temp": "^0.8.1", + "write-file-atomic": "^1.2.0" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.sh" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-addons/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-addons/node_modules/micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "dependencies": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-addons/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-addons/node_modules/recast": { + "version": "0.12.9", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.12.9.tgz", + "integrity": "sha512-y7ANxCWmMW8xLOaiopiRDlyjQ9ajKRENBH+2wjntIbk3A6ZR1+BLQttkmSHMY7Arl+AAZFwJ10grg2T6f1WI8A==", + "dev": true, + "dependencies": { + "ast-types": "0.10.1", + "core-js": "^2.4.1", + "esprima": "~4.0.0", + "private": "~0.1.5", + "source-map": "~0.6.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-addons/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-2.1.5.tgz", + "integrity": "sha512-CiWQR+1JS77rmyiO6y1q8Kt/O+e8nUUC9YfJ25JtSmzDwbqJV7vIsh3+QKRHVTbTCa0DaVh8iY1LBiagUIDB3g==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "diff": "^3.5.0", + "enhanced-resolve": "^4.0.0", + "envinfo": "^5.7.0", + "glob-all": "^3.1.0", + "global-modules": "^1.0.0", + "got": "^8.3.1", + "import-local": "^1.0.0", + "inquirer": "^5.2.0", + "interpret": "^1.1.0", + "jscodeshift": "^0.5.0", + "listr": "^0.14.1", + "loader-utils": "^1.1.0", + "lodash": "^4.17.10", + "log-symbols": "^2.2.0", + "mkdirp": "^0.5.1", + "p-each-series": "^1.0.0", + "p-lazy": "^1.0.0", + "prettier": "^1.12.1", + "supports-color": "^5.4.0", + "v8-compile-cache": "^2.0.0", + "webpack-addons": "^1.1.5", + "yargs": "^11.1.0", + "yeoman-environment": "^2.1.1", + "yeoman-generator": "^2.0.5" + }, + "bin": { + "webpack-cli": "bin/webpack.js" + }, + "engines": { + "node": ">=6.11.5" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, + "node_modules/webpack-livereload-plugin": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/webpack-livereload-plugin/-/webpack-livereload-plugin-2.1.1.tgz", + "integrity": "sha512-W7Q55QbPvVJotpIZSjjwzmqQ22333ExYxWM3WFlHKkbPStQqVRSmJkjntUqXF9jtpdeXi8r8HLkA1RVnAP0SQA==", + "dev": true, + "dependencies": { + "tiny-lr": "^1.1.1" + } + }, + "node_modules/webpack-log": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-1.2.0.tgz", + "integrity": "sha512-U9AnICnu50HXtiqiDxuli5gLB5PGBo7VvcHx36jRZHwK4vzOYLbImqT4lwWwoMHdQWwEKw736fCHEekokTEKHA==", + "dev": true, + "dependencies": { + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "loglevelnext": "^1.0.1", + "uuid": "^3.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/webpack-sources/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.3.tgz", + "integrity": "sha512-bpxWlvbbB459Mlipc5GBzzZwhoZgGEZLuqPaR0INBGnPAY1vdBX6hPnoFXiw+3yWxDuHyQjO2oXTMyS8A5haFg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.4.0 <0.4.11", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "dependencies": { + "errno": "~0.1.7" + } + }, + "node_modules/wrap-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", + "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=", + "dev": true, + "dependencies": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-1.3.4.tgz", + "integrity": "sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "node_modules/yargs": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", + "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", + "dev": true, + "dependencies": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + } + }, + "node_modules/yargs-parser": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0" + } + }, + "node_modules/yargs/node_modules/y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "node_modules/yeoman-environment": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.6.0.tgz", + "integrity": "sha512-Hl0LBs9/mKun8XyJ6gFiUNhZwjN/eezn+E9IFWz6KtXg/3wsnztF2lgtE8eIjfhWYtvY4yMq9iizi1Ei5JJ+7A==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "debug": "^3.1.0", + "diff": "^3.5.0", + "escape-string-regexp": "^1.0.2", + "globby": "^8.0.1", + "grouped-queue": "^0.3.3", + "inquirer": "^6.0.0", + "is-scoped": "^1.0.0", + "lodash": "^4.17.10", + "log-symbols": "^2.2.0", + "mem-fs": "^1.1.0", + "strip-ansi": "^4.0.0", + "text-table": "^0.2.0", + "untildify": "^3.0.3" + } + }, + "node_modules/yeoman-environment/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yeoman-environment/node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/yeoman-environment/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/yeoman-environment/node_modules/dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "dev": true, + "dependencies": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/yeoman-environment/node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/yeoman-environment/node_modules/globby": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", + "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "dir-glob": "2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/yeoman-environment/node_modules/inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/yeoman-environment/node_modules/inquirer/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yeoman-environment/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/yeoman-environment/node_modules/rxjs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/yeoman-environment/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/yeoman-environment/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/yeoman-generator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-2.0.5.tgz", + "integrity": "sha512-rV6tJ8oYzm4mmdF2T3wjY+Q42jKF2YiiD0VKfJ8/0ZYwmhCKC9Xs2346HVLPj/xE13i68psnFJv7iS6gWRkeAg==", + "dev": true, + "dependencies": { + "async": "^2.6.0", + "chalk": "^2.3.0", + "cli-table": "^0.3.1", + "cross-spawn": "^6.0.5", + "dargs": "^5.1.0", + "dateformat": "^3.0.3", + "debug": "^3.1.0", + "detect-conflict": "^1.0.0", + "error": "^7.0.2", + "find-up": "^2.1.0", + "github-username": "^4.0.0", + "istextorbinary": "^2.2.1", + "lodash": "^4.17.10", + "make-dir": "^1.1.0", + "mem-fs-editor": "^4.0.0", + "minimist": "^1.2.0", + "pretty-bytes": "^4.0.2", + "read-chunk": "^2.1.0", + "read-pkg-up": "^3.0.0", + "rimraf": "^2.6.2", + "run-async": "^2.0.0", + "shelljs": "^0.8.0", + "text-table": "^0.2.0", + "through2": "^2.0.0", + "yeoman-environment": "^2.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/yeoman-generator/node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/yeoman-generator/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + }, "dependencies": { "@mrmlnc/readdir-enhanced": { "version": "2.2.1", @@ -389,13 +10960,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv-keywords": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-escapes": { "version": "3.2.0", @@ -3536,23 +14109,28 @@ "dependencies": { "abbrev": { "version": "1.1.1", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "bundled": true, "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "bundled": true, "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "bundled": true, "dev": true, "optional": true, @@ -3563,12 +14141,14 @@ }, "balanced-match": { "version": "1.0.0", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "bundled": true, "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "bundled": true, "dev": true, "optional": true, @@ -3579,36 +14159,42 @@ }, "chownr": { "version": "1.1.1", + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "bundled": true, "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "bundled": true, "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "bundled": true, "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "bundled": true, "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "bundled": true, "dev": true, "optional": true }, "debug": { "version": "4.1.1", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "bundled": true, "dev": true, "optional": true, @@ -3618,24 +14204,28 @@ }, "deep-extend": { "version": "0.6.0", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "bundled": true, "dev": true, "optional": true }, "delegates": { "version": "1.0.0", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "bundled": true, "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "bundled": true, "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "bundled": true, "dev": true, "optional": true, @@ -3645,12 +14235,14 @@ }, "fs.realpath": { "version": "1.0.0", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "bundled": true, "dev": true, "optional": true }, "gauge": { "version": "2.7.4", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "bundled": true, "dev": true, "optional": true, @@ -3667,6 +14259,7 @@ }, "glob": { "version": "7.1.3", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "bundled": true, "dev": true, "optional": true, @@ -3681,12 +14274,14 @@ }, "has-unicode": { "version": "2.0.1", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "bundled": true, "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "bundled": true, "dev": true, "optional": true, @@ -3696,6 +14291,7 @@ }, "ignore-walk": { "version": "3.0.1", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "bundled": true, "dev": true, "optional": true, @@ -3705,6 +14301,7 @@ }, "inflight": { "version": "1.0.6", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "bundled": true, "dev": true, "optional": true, @@ -3715,18 +14312,21 @@ }, "inherits": { "version": "2.0.3", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "bundled": true, "dev": true, "optional": true }, "ini": { "version": "1.3.5", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "bundled": true, "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "bundled": true, "dev": true, "optional": true, @@ -3736,12 +14336,14 @@ }, "isarray": { "version": "1.0.0", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "bundled": true, "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "bundled": true, "dev": true, "optional": true, @@ -3751,12 +14353,14 @@ }, "minimist": { "version": "0.0.8", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "bundled": true, "dev": true, "optional": true }, "minipass": { "version": "2.3.5", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "bundled": true, "dev": true, "optional": true, @@ -3767,6 +14371,7 @@ }, "minizlib": { "version": "1.2.1", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "bundled": true, "dev": true, "optional": true, @@ -3776,6 +14381,7 @@ }, "mkdirp": { "version": "0.5.1", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "bundled": true, "dev": true, "optional": true, @@ -3785,12 +14391,14 @@ }, "ms": { "version": "2.1.1", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "bundled": true, "dev": true, "optional": true }, "needle": { "version": "2.3.0", + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "bundled": true, "dev": true, "optional": true, @@ -3802,6 +14410,7 @@ }, "node-pre-gyp": { "version": "0.12.0", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "bundled": true, "dev": true, "optional": true, @@ -3820,6 +14429,7 @@ }, "nopt": { "version": "4.0.1", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "bundled": true, "dev": true, "optional": true, @@ -3830,12 +14440,14 @@ }, "npm-bundled": { "version": "1.0.6", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "bundled": true, "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "bundled": true, "dev": true, "optional": true, @@ -3846,6 +14458,7 @@ }, "npmlog": { "version": "4.1.2", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "bundled": true, "dev": true, "optional": true, @@ -3858,18 +14471,21 @@ }, "number-is-nan": { "version": "1.0.1", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "bundled": true, "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "bundled": true, "dev": true, "optional": true }, "once": { "version": "1.4.0", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "bundled": true, "dev": true, "optional": true, @@ -3879,18 +14495,21 @@ }, "os-homedir": { "version": "1.0.2", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "bundled": true, "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "bundled": true, "dev": true, "optional": true }, "osenv": { "version": "0.1.5", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "bundled": true, "dev": true, "optional": true, @@ -3901,18 +14520,21 @@ }, "path-is-absolute": { "version": "1.0.1", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "bundled": true, "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "bundled": true, "dev": true, "optional": true }, "rc": { "version": "1.2.8", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "bundled": true, "dev": true, "optional": true, @@ -3925,6 +14547,7 @@ "dependencies": { "minimist": { "version": "1.2.0", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "bundled": true, "dev": true, "optional": true @@ -3933,6 +14556,7 @@ }, "readable-stream": { "version": "2.3.6", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "bundled": true, "dev": true, "optional": true, @@ -3948,6 +14572,7 @@ }, "rimraf": { "version": "2.6.3", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "bundled": true, "dev": true, "optional": true, @@ -3957,75 +14582,88 @@ }, "safe-buffer": { "version": "5.1.2", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "bundled": true, "dev": true, "optional": true }, "sax": { "version": "1.2.4", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "bundled": true, "dev": true, "optional": true }, "semver": { "version": "5.7.0", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "bundled": true, "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "bundled": true, "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "bundled": true, "dev": true, "optional": true }, - "string-width": { - "version": "1.0.2", + "string_decoder": { + "version": "1.1.1", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "bundled": true, "dev": true, "optional": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "safe-buffer": "~5.1.0" } }, - "string_decoder": { - "version": "1.1.1", + "string-width": { + "version": "1.0.2", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "bundled": true, "dev": true, "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "strip-ansi": { "version": "3.0.1", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "bundled": true, "dev": true, "optional": true }, "tar": { "version": "4.4.8", + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "bundled": true, "dev": true, "optional": true, @@ -4041,12 +14679,14 @@ }, "util-deprecate": { "version": "1.0.2", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "bundled": true, "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "bundled": true, "dev": true, "optional": true, @@ -4056,13 +14696,17 @@ }, "wrappy": { "version": "1.0.2", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -7543,6 +18187,15 @@ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "string-template": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", @@ -7596,15 +18249,6 @@ "function-bind": "^1.1.1" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index f35d45e1e6..21eea95a85 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -595,6 +595,80 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } + class XamlIlClrMethodAsCommandPathElementNode : IXamlIlBindingPathElementNode + { + private readonly IXamlMethod _executeMethod; + private readonly IXamlMethod _canExecuteMethod; + private readonly IReadOnlyList _dependsOnProperties; + + public XamlIlClrMethodAsCommandPathElementNode(IXamlType iCommandType, IXamlMethod executeMethod, IXamlMethod canExecuteMethod, IReadOnlyList dependsOnProperties) + { + Type = iCommandType; + _executeMethod = executeMethod; + _canExecuteMethod = canExecuteMethod; + _dependsOnProperties = dependsOnProperties; + } + + + public IXamlType Type { get; } + + public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen) + { + codeGen + .Ldtoken(_executeMethod); + + if (_canExecuteMethod is not null) + { + codeGen.Ldtoken(_canExecuteMethod); + } + else + { + using var canExecuteMethodHandle = codeGen.LocalsPool.GetLocal(context.Configuration.TypeSystem.GetType("System.Reflection.RuntimeMethodHandle")); + codeGen + .Ldloca(canExecuteMethodHandle.Local) + .Emit(OpCodes.Initobj) + .Ldloc(canExecuteMethodHandle.Local); + } + + if (_dependsOnProperties is { Count:> 0 }) + { + using var dependsOnProperties = codeGen.LocalsPool.GetLocal(context.Configuration.WellKnownTypes.String.MakeArrayType(1)); + codeGen + .Ldc_I4(_dependsOnProperties.Count) + .Newarr(context.Configuration.WellKnownTypes.String) + .Stloc(dependsOnProperties.Local); + + for (var i = 0; i < _dependsOnProperties.Count; i++) + { + var prop = _dependsOnProperties[i]; + codeGen + .Ldloc(dependsOnProperties.Local) + .Ldc_I4(i) + .Ldstr(prop) + .Stelem_ref(); + } + codeGen.Ldloc(dependsOnProperties.Local); + } + else + { + codeGen.Ldnull(); + } + + if (_executeMethod.Parameters.Count != 0) + { + codeGen.EmitCall( + context.GetAvaloniaTypes() + .CompiledBindingPathBuilder.FindMethod(m => m.Name == "CommandWithParameter")); + } + else + { + codeGen.EmitCall( + context.GetAvaloniaTypes() + .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Command")); + } + } + } + class XamlIlClrIndexerPathElementNode : IXamlIlBindingPathElementNode { private readonly IXamlProperty _property; diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index d44a067882..bb81174ef3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs new file mode 100644 index 0000000000..beb76c8100 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Text; +using System.Windows.Input; +using Avalonia.Data; +using Avalonia.Data.Core.Plugins; +using Avalonia.Utilities; + +namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings +{ + internal class CommandAccessorPlugin : IPropertyAccessorPlugin + { + private readonly Func, IPropertyAccessor> _commandAccessorFactory; + + public CommandAccessorPlugin(Func, IPropertyAccessor> commandAccessorFactory) + { + _commandAccessorFactory = commandAccessorFactory; + } + + public bool Match(object obj, string propertyName) + { + throw new InvalidOperationException("The CommandAccessorPlugin does not support dynamic matching"); + } + + public IPropertyAccessor Start(WeakReference reference, string propertyName) + { + return _commandAccessorFactory(reference); + } + + internal abstract class CommandAccessorBase : PropertyAccessorBase + { + private readonly WeakReference _reference; + private readonly ISet _dependsOnProperties; + + public CommandAccessorBase(WeakReference reference, ISet dependsOnProperties) + { + Contract.Requires(reference != null); + + _reference = reference; + _dependsOnProperties = dependsOnProperties; + } + + public override Type PropertyType => typeof(ICommand); + + protected abstract void RaiseCanExecuteChanged(); + + public override bool SetValue(object value, BindingPriority priority) + { + return false; + } + + void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (string.IsNullOrEmpty(e.PropertyName) || _dependsOnProperties.Contains(e.PropertyName)) + { + SendCurrentValue(); + } + } + + protected override void SubscribeCore() + { + SendCurrentValue(); + SubscribeToChanges(); + } + + protected override void UnsubscribeCore() + { + if (_dependsOnProperties is { Count: > 0 } && _reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc) + { + WeakEventHandlerManager.Unsubscribe( + inpc, + nameof(INotifyPropertyChanged.PropertyChanged), + OnNotifyPropertyChanged); + } + } + + private void SendCurrentValue() + { + try + { + var value = Value; + PublishValue(value); + } + catch { } + } + + private void SubscribeToChanges() + { + if (_dependsOnProperties is { Count:>0 } && _reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc) + { + WeakEventHandlerManager.Subscribe( + inpc, + nameof(INotifyPropertyChanged.PropertyChanged), + OnNotifyPropertyChanged); + } + } + } + + internal sealed class CommandWithParameterAccessor : CommandAccessorBase + { + private Command _command; + + public CommandWithParameterAccessor(WeakReference target, ISet dependsOnProperties, Action execute, Func canExecute) + : base(target, dependsOnProperties) + { + _command = new Command(execute, canExecute); + } + + public override object Value => _command; + + protected override void RaiseCanExecuteChanged() + { + _command.RaiseCanExecuteChanged(); + } + + private sealed class Command : ICommand + { + private readonly Action _execute; + private readonly Func _canExecute; + + public event EventHandler CanExecuteChanged; + + public Command(Action execute, Func canExecute) + { + _execute = execute; + _canExecute = canExecute; + } + + public void RaiseCanExecuteChanged() + { + Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty) + , Threading.DispatcherPriority.Input); + } + + public bool CanExecute(object parameter) + { + return _canExecute(parameter); + } + + public void Execute(object parameter) + { + _execute((T)parameter); + } + } + } + + internal sealed class CommandWithoutParameterAccessor : CommandAccessorBase + { + private Command _command; + + public CommandWithoutParameterAccessor(WeakReference target, ISet dependsOnProperties, Action execute, Func canExecute) + : base(target, dependsOnProperties) + { + _command = new Command(execute, canExecute); + } + + public override object Value => _command; + + protected override void RaiseCanExecuteChanged() + { + _command.RaiseCanExecuteChanged(); + } + + private sealed class Command : ICommand + { + private readonly Action _execute; + private readonly Func _canExecute; + + public event EventHandler CanExecuteChanged; + + public Command(Action execute, Func canExecute) + { + _execute = execute; + _canExecute = canExecute; + } + + public void RaiseCanExecuteChanged() + { + Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty) + , Threading.DispatcherPriority.Input); + } + + public bool CanExecute(object parameter) + { + return _canExecute(parameter); + } + + public void Execute(object parameter) + { + _execute(); + } + } + } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index 31c9535e5f..b5ab24a74e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -37,6 +37,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings case PropertyElement prop: node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory)); break; + case IMethodAsCommandElement methodAsCommand: + node = new PropertyAccessorNode(methodAsCommand.ExecuteMethod.Name, enableValidation, new CommandAccessorPlugin(methodAsCommand.CreateAccessor)); + break; case MethodAsDelegateElement methodAsDelegate: node = new PropertyAccessorNode(methodAsDelegate.Method.Name, enableValidation, new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType)); break; @@ -102,6 +105,18 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings return this; } + public CompiledBindingPathBuilder Command(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnProperties) + { + _elements.Add(new MethodAsCommandElement(executeMethod, canExecuteMethod, dependsOnProperties)); + return this; + } + + public CompiledBindingPathBuilder CommandWithParameter(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnProperties) + { + _elements.Add(new MethodAsCommandElement(executeMethod, canExecuteMethod, dependsOnProperties)); + return this; + } + public CompiledBindingPathBuilder StreamTask() { _elements.Add(new TaskStreamPathElement()); @@ -201,6 +216,51 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public Type DelegateType { get; } } + internal interface IMethodAsCommandElement + { + MethodInfo ExecuteMethod { get; } + IPropertyAccessor CreateAccessor(WeakReference obj); + } + + internal class MethodAsCommandElement : ICompiledBindingPathElement, IMethodAsCommandElement + { + public MethodAsCommandElement(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnElements) + { + ExecuteMethod = (MethodInfo)MethodBase.GetMethodFromHandle(executeMethod); + CanExecuteMethod = canExecuteMethod != default ? (MethodInfo)MethodBase.GetMethodFromHandle(canExecuteMethod) : null; + DependsOnProperties = new HashSet(dependsOnElements); + } + + public MethodInfo ExecuteMethod { get; } + public MethodInfo CanExecuteMethod { get; } + public HashSet DependsOnProperties { get; } + + public IPropertyAccessor CreateAccessor(WeakReference obj) + { + obj.TryGetTarget(out object target); + return new CommandAccessorPlugin.CommandWithoutParameterAccessor(obj, DependsOnProperties, (Action)ExecuteMethod.CreateDelegate(typeof(Action), target), (Func)CanExecuteMethod?.CreateDelegate(typeof(Func), target)); + } + } + internal class MethodAsCommandElement : ICompiledBindingPathElement, IMethodAsCommandElement + { + public MethodAsCommandElement(RuntimeMethodHandle executeMethod, RuntimeMethodHandle canExecuteMethod, string[] dependsOnElements) + { + ExecuteMethod = (MethodInfo)MethodBase.GetMethodFromHandle(executeMethod); + CanExecuteMethod = canExecuteMethod != default ? (MethodInfo)MethodBase.GetMethodFromHandle(canExecuteMethod) : null; + DependsOnProperties = new HashSet(dependsOnElements); + } + + public MethodInfo ExecuteMethod { get; } + public MethodInfo CanExecuteMethod { get; } + public HashSet DependsOnProperties { get; } + + public IPropertyAccessor CreateAccessor(WeakReference obj) + { + obj.TryGetTarget(out object target); + return new CommandAccessorPlugin.CommandWithParameterAccessor(obj, DependsOnProperties, (Action)ExecuteMethod.CreateDelegate(typeof(Action), target), (Func)CanExecuteMethod?.CreateDelegate(typeof(Func), target)); + } + } + internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement { IStreamPlugin CreatePlugin(); From a383c77650cc584cd4864a295decc0849e59abd6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sun, 19 Dec 2021 18:57:55 -0600 Subject: [PATCH 091/822] Hook up target-typing for ICommand. --- .../AvaloniaXamlIlWellKnownTypes.cs | 4 ++ .../XamlIlBindingPathHelper.cs | 70 +++++++++++++------ 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 6dd3521183..7a8e95df13 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -19,10 +19,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlMethod AvaloniaObjectBindMethod { get; } public IXamlMethod AvaloniaObjectSetValueMethod { get; } public IXamlType IDisposable { get; } + public IXamlType ICommand { get; } public XamlTypeWellKnownTypes XamlIlTypes { get; } public XamlLanguageTypeMappings XamlIlMappings { get; } public IXamlType Transitions { get; } public IXamlType AssignBindingAttribute { get; } + public IXamlType DependsOnAttribute { get; } public IXamlType UnsetValueType { get; } public IXamlType StyledElement { get; } public IXamlType IStyledElement { get; } @@ -96,8 +98,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers BindingPriority = cfg.TypeSystem.GetType("Avalonia.Data.BindingPriority"); IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding"); IDisposable = cfg.TypeSystem.GetType("System.IDisposable"); + IDisposable = cfg.TypeSystem.GetType("System.Windows.Input.ICommand"); Transitions = cfg.TypeSystem.GetType("Avalonia.Animation.Transitions"); AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute"); + DependsOnAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DependsOnAttribute"); AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject, AvaloniaProperty, IBinding, cfg.WellKnownTypes.Object); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index 028f2f5fa8..07a5a74388 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -32,6 +32,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions selfType, bindingPath.Path); + transformed = TransformForTargetTyping(transformed, context); + bindingResultType = transformed.BindingResultType; binding.Arguments[0] = transformed; } @@ -54,6 +56,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions selfType, bindingPathNode.Path); + transformed = TransformForTargetTyping(transformed, context); + bindingResultType = transformed.BindingResultType; bindingPathAssignment.Values[0] = transformed; } @@ -66,7 +70,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return bindingResultType; } - private static IXamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func startTypeResolver, IXamlType selfType, IEnumerable bindingExpression) + private static XamlIlBindingPathNode TransformForTargetTyping(XamlIlBindingPathNode transformed, AstTransformationContext context) + { + if (context.ParentNodes().OfType().FirstOrDefault().Property.Getter.ReturnType == context.GetAvaloniaTypes().ICommand + && transformed.Elements[transformed.Elements.Count - 1] is XamlIlClrMethodPathElementNode method) + { + IXamlMethod executeMethod = method.Method; + IXamlMethod canExecuteMethod = executeMethod.DeclaringType.FindMethod(new FindMethodMethodSignature($"Can{executeMethod.Name}", context.Configuration.WellKnownTypes.Boolean, context.Configuration.WellKnownTypes.Object)); + List dependsOnProperties = new(); + if (canExecuteMethod is not null) + { + foreach (var attr in canExecuteMethod.CustomAttributes) + { + if (attr.Type == context.GetAvaloniaTypes().DependsOnAttribute) + { + dependsOnProperties.Add((string)attr.Parameters[0]); + } + } + } + transformed.Elements.RemoveAt(transformed.Elements.Count - 1); + transformed.Elements.Add(new XamlIlClrMethodAsCommandPathElementNode(context.GetAvaloniaTypes().ICommand, executeMethod, canExecuteMethod, dependsOnProperties)); + } + + return transformed; + } + + private static XamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func startTypeResolver, IXamlType selfType, IEnumerable bindingExpression) { List transformNodes = new List(); List nodes = new List(); @@ -553,13 +582,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions class XamlIlClrMethodPathElementNode : IXamlIlBindingPathElementNode { - private readonly IXamlMethod _method; public XamlIlClrMethodPathElementNode(IXamlMethod method, IXamlType systemDelegateType) { - _method = method; + Method = method; Type = systemDelegateType; } + public IXamlMethod Method { get; } public IXamlType Type { get; } @@ -567,35 +596,35 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { IXamlTypeBuilder newDelegateTypeBuilder = null; IXamlType specificDelegateType; - if (_method.ReturnType == context.Configuration.WellKnownTypes.Void && _method.Parameters.Count == 0) + if (Method.ReturnType == context.Configuration.WellKnownTypes.Void && Method.Parameters.Count == 0) { specificDelegateType = context.Configuration.TypeSystem .GetType("System.Action"); } - else if (_method.ReturnType == context.Configuration.WellKnownTypes.Void && _method.Parameters.Count <= 16) + else if (Method.ReturnType == context.Configuration.WellKnownTypes.Void && Method.Parameters.Count <= 16) { specificDelegateType = context.Configuration.TypeSystem - .GetType($"System.Action`{_method.Parameters.Count}") - .MakeGenericType(_method.Parameters); + .GetType($"System.Action`{Method.Parameters.Count}") + .MakeGenericType(Method.Parameters); } - else if (_method.Parameters.Count <= 16) + else if (Method.Parameters.Count <= 16) { List genericParameters = new(); - genericParameters.AddRange(_method.Parameters); - genericParameters.Add(_method.ReturnType); + genericParameters.AddRange(Method.Parameters); + genericParameters.Add(Method.ReturnType); specificDelegateType = context.Configuration.TypeSystem - .GetType($"System.Func`{_method.Parameters.Count + 1}") + .GetType($"System.Func`{Method.Parameters.Count + 1}") .MakeGenericType(genericParameters); } else { // In this case, we need to emit our own delegate type. string delegateTypeName = context.Configuration.IdentifierGenerator.GenerateIdentifierPart(); - specificDelegateType = newDelegateTypeBuilder = context.DefineDelegateSubType(delegateTypeName, _method.ReturnType, _method.Parameters); + specificDelegateType = newDelegateTypeBuilder = context.DefineDelegateSubType(delegateTypeName, Method.ReturnType, Method.Parameters); } codeGen - .Ldtoken(_method) + .Ldtoken(Method) .Ldtoken(specificDelegateType) .EmitCall(context.GetAvaloniaTypes() .CompiledBindingPathBuilder.FindMethod(m => m.Name == "Method")); @@ -803,7 +832,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstEmitableNode { private readonly List _transformElements; - private readonly List _elements; public XamlIlBindingPathNode(IXamlLineInfo lineInfo, IXamlType bindingPathType, @@ -812,16 +840,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { Type = new XamlAstClrTypeReference(lineInfo, bindingPathType, false); _transformElements = transformElements; - _elements = elements; + Elements = elements; } public IXamlType BindingResultType => _transformElements.Count > 0 ? _transformElements[0].Type - : _elements[_elements.Count - 1].Type; + : Elements[Elements.Count - 1].Type; public IXamlAstTypeReference Type { get; } + public List Elements { get; } + public XamlILNodeEmitResult Emit(XamlIlEmitContext context, IXamlILEmitter codeGen) { var types = context.GetAvaloniaTypes(); @@ -832,7 +862,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions transform.Emit(context, codeGen); } - foreach (var element in _elements) + foreach (var element in Elements) { element.Emit(context, codeGen); } @@ -850,11 +880,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions _transformElements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor); } } - for (int i = 0; i < _elements.Count; i++) + for (int i = 0; i < Elements.Count; i++) { - if (_elements[i] is IXamlAstNode ast) + if (Elements[i] is IXamlAstNode ast) { - _elements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor); + Elements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor); } } } From cdc8c9f07871f38e9e926700f30cde4153a073ed Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Sun, 19 Dec 2021 19:19:47 -0600 Subject: [PATCH 092/822] Add some tests --- .../AvaloniaXamlIlWellKnownTypes.cs | 2 +- .../XamlIlBindingPathHelper.cs | 2 +- .../CompiledBindingExtensionTests.cs | 188 ++++++++++++++++++ 3 files changed, 190 insertions(+), 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 7a8e95df13..c178bb618e 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -98,7 +98,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers BindingPriority = cfg.TypeSystem.GetType("Avalonia.Data.BindingPriority"); IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding"); IDisposable = cfg.TypeSystem.GetType("System.IDisposable"); - IDisposable = cfg.TypeSystem.GetType("System.Windows.Input.ICommand"); + ICommand = cfg.TypeSystem.GetType("System.Windows.Input.ICommand"); Transitions = cfg.TypeSystem.GetType("Avalonia.Animation.Transitions"); AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute"); DependsOnAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DependsOnAttribute"); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index 07a5a74388..8046325ef5 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -661,7 +661,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } else { - using var canExecuteMethodHandle = codeGen.LocalsPool.GetLocal(context.Configuration.TypeSystem.GetType("System.Reflection.RuntimeMethodHandle")); + using var canExecuteMethodHandle = codeGen.LocalsPool.GetLocal(context.Configuration.TypeSystem.GetType("System.RuntimeMethodHandle")); codeGen .Ldloca(canExecuteMethodHandle.Local) .Emit(OpCodes.Initobj) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 4ce1d6f287..18c2ad663c 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Globalization; using System.Reactive.Subjects; using System.Text; @@ -9,6 +10,7 @@ using Avalonia.Controls; using Avalonia.Controls.Presenters; using Avalonia.Data.Converters; using Avalonia.Data.Core; +using Avalonia.Input; using Avalonia.Markup.Data; using Avalonia.Media; using Avalonia.UnitTests; @@ -1093,6 +1095,143 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void Binding_Method_To_Command_Works() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + +