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)); + } } }