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
parent
commit
376a6e2f0d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 25
      native/Avalonia.Native/src/OSX/automation.mm
  2. 5
      src/Avalonia.Native/AvnAutomationPeer.cs
  3. 1
      src/Avalonia.Native/avn.idl

25
native/Avalonia.Native/src/OSX/automation.mm

@ -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]});
}

5
src/Avalonia.Native/AvnAutomationPeer.cs

@ -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();

1
src/Avalonia.Native/avn.idl

@ -1274,6 +1274,7 @@ interface IAvnAutomationPeer : IUnknown
IAvnAutomationPeer* GetLabeledBy();
IAvnString* GetName();
IAvnAutomationPeer* GetParent();
IAvnAutomationPeer* GetTemplatedParent();
IAvnAutomationPeer* GetVisualRoot();
bool HasKeyboardFocus();
bool IsContentElement();

Loading…
Cancel
Save