Browse Source
Fix macOS AXLayoutChanged notifications dropped for template-internal peers (#21595 )
* Post macOS AXLayoutChanged on the changed peer's templated parent
The changed peer is usually a template-internal layout container that AppKit will not deliver notifications for; post on its templated parent (the owning control), falling back to the nearest exposed ancestor when there is none.
* Update with comment
pull/21602/head
Jumar Macato
3 months ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with
30 additions and
1 deletions
native/Avalonia.Native/src/OSX/automation.mm
src/Avalonia.Native/AvnAutomationPeer.cs
src/Avalonia.Native/avn.idl
@ -516,8 +516,31 @@
if (_children)
[changed addObjectsFromArray:_children];
/*
For future reference, upon testing with a sample SwiftUI app:
1) Containers vanish. VStack/HStack don't appear in the accessibility tree at all,
only real elements (Text, Button, List) do, parented to one root AXHostingView.
2) Changes post on the nearest real element, not the container.
Toggling a child two VStack/HStack levels deep fired AXLayoutChanged on the AXHostingView
(the nearest real element), never on the hidden containers.
3) Real controls get their own notification. The List (AXOutline) posted AXRowCountChanged on itself.
Apple never posts a structural notification on a non-accessibility container.
It targets the nearest element that's actually in the tree.
That's exactly what the code below does (templated parent, else walk to the nearest exposed ancestor).
*/
id target = [AvnAccessibilityElement acquire:_peer->GetTemplatedParent()];
if (target == nil)
target = self;
while ([target isKindOfClass:[AvnAccessibilityElement class]] && ![(AvnAccessibilityElement*)target isAccessibilityElement])
target = [(AvnAccessibilityElement*)target accessibilityParent];
NSAccessibilityPostNotificationWithUserInfo(
self,
target ,
NSAccessibilityLayoutChangedNotification,
@{ NSAccessibilityUIElementsKey: [changed allObjects]});
}
@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
using Avalonia.Automation ;
using Avalonia.Automation.Peers ;
using Avalonia.Automation.Provider ;
using Avalonia.Controls ;
using Avalonia.Controls.Automation.Peers ;
using Avalonia.Native.Interop ;
@ -56,6 +57,10 @@ namespace Avalonia.Native
public AvnLandmarkType LandmarkType = > ( AvnLandmarkType ? ) _ inner . GetLandmarkType ( ) ? ? AvnLandmarkType . LandmarkNone ;
public int HeadingLevel = > _ inner . GetHeadingLevel ( ) ;
public IAvnAutomationPeer ? Parent = > Wrap ( _ inner . GetParent ( ) ) ;
public IAvnAutomationPeer ? TemplatedParent = >
_ inner is ControlAutomationPeer { Owner . TemplatedParent : Control templatedParent } ?
Wrap ( ControlAutomationPeer . CreatePeerForElement ( templatedParent ) )
: null ;
public IAvnAutomationPeer ? VisualRoot = > Wrap ( _ inner . GetAutomationRoot ( ) ) ;
public AvnLiveSetting LiveSetting = > ( AvnLiveSetting ) _ inner . GetLiveSetting ( ) ;
@ -1274,6 +1274,7 @@ interface IAvnAutomationPeer : IUnknown
IAvnAutomationPeer* GetLabeledBy();
IAvnString* GetName();
IAvnAutomationPeer* GetParent();
IAvnAutomationPeer* GetTemplatedParent();
IAvnAutomationPeer* GetVisualRoot();
bool HasKeyboardFocus();
bool IsContentElement();