Skip to content

Commit

Permalink
macOS: Don't include two windows in a11y tree. (AvaloniaUI#15899)
Browse files Browse the repository at this point in the history
* Don't include two windows in a11y tree.

`AvnRootAccessibilityElement` has been removed and now `AvnWindow` handles the accessibility protocol itself, exposing its children via the `AvnView`.

* Remove hack now that issue is fixed.

* Fix build errors after merge.
  • Loading branch information
grokys authored and Gillibald committed Jul 29, 2024
1 parent 8a6aedd commit f24d57c
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
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 */; };
BC7C33822C066DBF00945A48 /* AvnAutomationNode.h in Headers */ = {isa = PBXBuildFile; fileRef = BC7C33812C066DBF00945A48 /* AvnAutomationNode.h */; };
ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; };
ED754D262A97306B0078B4DF /* PlatformRenderTimer.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED754D252A97306B0078B4DF /* PlatformRenderTimer.mm */; };
EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */; };
Expand Down Expand Up @@ -122,6 +123,8 @@
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = "<group>"; };
BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = "<group>"; };
BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = "<group>"; };
BC7C33812C066DBF00945A48 /* AvnAutomationNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvnAutomationNode.h; sourceTree = "<group>"; };
BC7C33832C066F1100945A48 /* AvnAccessibility.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnAccessibility.h; sourceTree = "<group>"; };
ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
ED754D252A97306B0078B4DF /* PlatformRenderTimer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformRenderTimer.mm; sourceTree = "<group>"; };
EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformSettings.mm; sourceTree = "<group>"; };
Expand Down Expand Up @@ -167,6 +170,8 @@
isa = PBXGroup;
children = (
F10084852BFF1FB40024303E /* TopLevelImpl.mm */,
BC7C33832C066F1100945A48 /* AvnAccessibility.h */,
BC7C33812C066DBF00945A48 /* AvnAutomationNode.h */,
ED754D252A97306B0078B4DF /* PlatformRenderTimer.mm */,
855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */,
8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */,
Expand Down Expand Up @@ -245,6 +250,7 @@
183916173528EC2737DBE5E1 /* WindowBaseImpl.h in Headers */,
1839171DCC651B0638603AC4 /* INSWindowHolder.h in Headers */,
183919D91DB9AAB5D700C2EA /* WindowImpl.h in Headers */,
BC7C33822C066DBF00945A48 /* AvnAutomationNode.h in Headers */,
18391CF07316F819E76B617C /* IWindowStateChanged.h in Headers */,
8D300D65292D0A6800320C49 /* AvnTextInputMethod.h in Headers */,
8D2F3512292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h in Headers */,
Expand Down
13 changes: 13 additions & 0 deletions native/Avalonia.Native/src/OSX/AvnAccessibility.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#pragma once
#import <Cocoa/Cocoa.h>
#import "avalonia-native.h"

// Defines the interface between AvnAutomationNode and objects which implement
// NSAccessibility such as AvnAccessibilityElement or AvnWindow.
@protocol AvnAccessibility <NSAccessibility>
@required
- (void) raiseChildrenChanged;
@optional
- (void) raiseFocusChanged;
- (void) raisePropertyChanged:(AvnAutomationProperty)property;
@end
18 changes: 18 additions & 0 deletions native/Avalonia.Native/src/OSX/AvnAutomationNode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once
#include "avalonia-native.h"
#include "AvnAccessibility.h"

// Defines a means for managed code to raise accessibility events.
class AvnAutomationNode : public ComSingleObject<IAvnAutomationNode, &IID_IAvnAutomationNode>
{
public:
FORWARD_IUNKNOWN()
AvnAutomationNode(id <AvnAccessibility> owner) { _owner = owner; }
AvnAccessibilityElement* GetOwner() { return _owner; }
virtual void Dispose() override { _owner = nil; }
virtual void ChildrenChanged () override { [_owner raiseChildrenChanged]; }
virtual void PropertyChanged (AvnAutomationProperty property) override { [_owner raisePropertyChanged:property]; }
virtual void FocusChanged () override { [_owner raiseFocusChanged]; }
private:
__strong id <AvnAccessibility> _owner;
};
1 change: 1 addition & 0 deletions native/Avalonia.Native/src/OSX/AvnView.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
-(AvnPlatformResizeReason) getResizeReason;
-(void) setResizeReason:(AvnPlatformResizeReason)reason;
-(void) setRenderTarget:(NSObject<IRenderTarget>* _Nonnull)target;
-(void) raiseAccessibilityChildrenChanged;
+ (AvnPoint)toAvnPoint:(CGPoint)p;
@end
77 changes: 58 additions & 19 deletions native/Avalonia.Native/src/OSX/AvnView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ @implementation AvnView
AvnPixelSize _lastPixelSize;
NSObject<IRenderTarget>* _currentRenderTarget;
AvnPlatformResizeReason _resizeReason;
AvnAccessibilityElement* _accessibilityChild;
NSRect _cursorRect;
NSMutableAttributedString* _text;
NSRange _selectedRange;
NSRange _markedRange;
NSEvent* _lastKeyDownEvent;
NSMutableArray* _accessibilityChildren;
}

- (void)onClosed
Expand Down Expand Up @@ -801,35 +801,74 @@ - (void)setResizeReason:(AvnPlatformResizeReason)reason
_resizeReason = reason;
}

- (AvnAccessibilityElement *) accessibilityChild
- (NSArray *)accessibilityChildren
{
if (_accessibilityChild == nil)
{
auto peer = _parent->TopLevelEvents->GetAutomationPeer();
if (_accessibilityChildren == nil)
[self recalculateAccessibiltyChildren];
return _accessibilityChildren;
}

if (peer == nil)
return nil;
- (id _Nullable) accessibilityHitTest:(NSPoint)point
{
if (![[self window] isKindOfClass:[AvnWindow class]])
return self;

_accessibilityChild = [AvnAccessibilityElement acquire:peer];
}
auto window = (AvnWindow*)[self window];
auto peer = [window automationPeer];

return _accessibilityChild;
}
if (!peer->IsRootProvider())
return nil;

- (NSArray *)accessibilityChildren
{
auto child = [self accessibilityChild];
return NSAccessibilityUnignoredChildrenForOnlyChild(child);
auto clientPoint = [window convertPointFromScreen:point];
auto localPoint = [self translateLocalPoint:ToAvnPoint(clientPoint)];
auto hit = peer->RootProvider_GetPeerFromPoint(localPoint);
return [AvnAccessibilityElement acquire:hit];
}

- (id)accessibilityHitTest:(NSPoint)point
- (void)raiseAccessibilityChildrenChanged
{
return [[self accessibilityChild] accessibilityHitTest:point];
auto changed = _accessibilityChildren ? [NSMutableSet setWithArray:_accessibilityChildren] : [NSMutableSet set];

[self recalculateAccessibiltyChildren];

if (_accessibilityChildren)
[changed addObjectsFromArray:_accessibilityChildren];

NSAccessibilityPostNotificationWithUserInfo(
self,
NSAccessibilityLayoutChangedNotification,
@{ NSAccessibilityUIElementsKey: [changed allObjects]});
}

- (id)accessibilityFocusedUIElement
- (void)recalculateAccessibiltyChildren
{
return [[self accessibilityChild] accessibilityFocusedUIElement];
_accessibilityChildren = [[NSMutableArray alloc] init];

if (![[self window] isKindOfClass:[AvnWindow class]])
{
return;
}

// The accessibility children of the Window are exposed as children
// of the AvnView.
auto window = (AvnWindow*)[self window];
auto peer = [window automationPeer];
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)
{
id element = [AvnAccessibilityElement acquire:child];
[_accessibilityChildren addObject:element];
}
}
}
}

- (void) setText:(NSString *)text{
Expand Down
47 changes: 46 additions & 1 deletion native/Avalonia.Native/src/OSX/AvnWindow.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#include "WindowImpl.h"
#include "AvnView.h"
#include "WindowInterfaces.h"
#include "AvnAutomationNode.h"
#include "AvnString.h"

@implementation CLASS_NAME
{
Expand All @@ -34,6 +36,13 @@ @implementation CLASS_NAME
bool _isExtended;
bool _isTransitioningToFullScreen;
AvnMenu* _menu;
IAvnAutomationPeer* _automationPeer;
AvnAutomationNode* _automationNode;
}

-(AvnView* _Nullable) view
{
return _parent->View;
}

-(void) setIsExtended:(bool)value;
Expand Down Expand Up @@ -208,7 +217,7 @@ - (void)windowWillClose:(NSNotification *_Nonnull)notification
ComPtr<WindowBaseImpl> parent = _parent;
_parent = NULL;

auto window = dynamic_cast<WindowImpl*>(parent.getRaw());
auto window = dynamic_cast<WindowImpl*>(parent.getRaw());

if(window != nullptr)
{
Expand Down Expand Up @@ -489,5 +498,41 @@ - (void)disconnectParent {
_parent = nullptr;
}

- (id _Nullable) accessibilityFocusedUIElement
{
if (![self automationPeer]->IsRootProvider())
return nil;
auto focusedPeer = [self automationPeer]->RootProvider_GetFocus();
return [AvnAccessibilityElement acquire:focusedPeer];
}

- (NSString * _Nullable) accessibilityIdentifier
{
return GetNSStringAndRelease([self automationPeer]->GetAutomationId());
}

- (IAvnAutomationPeer* _Nonnull) automationPeer
{
if (_automationPeer == nullptr)
{
_automationPeer = _parent->BaseEvents->GetAutomationPeer();
_automationNode = new AvnAutomationNode(self);
_automationPeer->SetNode(_automationNode);
}

return _automationPeer;
}

- (void)raiseChildrenChanged
{
[_parent->View raiseAccessibilityChildrenChanged];
}

- (void)raiseFocusChanged
{
id focused = [self accessibilityFocusedUIElement];
NSAccessibilityPostNotification(focused, NSAccessibilityFocusedUIElementChangedNotification);
}

@end

8 changes: 5 additions & 3 deletions native/Avalonia.Native/src/OSX/WindowInterfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
#import <AppKit/AppKit.h>
#include "WindowProtocol.h"
#include "WindowBaseImpl.h"
#include "AvnAccessibility.h"

@interface AvnWindow : NSWindow <AvnWindowProtocol, NSWindowDelegate>
@interface AvnWindow : NSWindow <AvnWindowProtocol, NSWindowDelegate, AvnAccessibility>
-(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
-(AvnView* _Nullable) view;
@end

@interface AvnPanel : NSPanel <AvnWindowProtocol, NSWindowDelegate>
@interface AvnPanel : NSPanel <AvnWindowProtocol, NSWindowDelegate, AvnAccessibility>
-(AvnPanel* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
@end
@end
2 changes: 2 additions & 0 deletions native/Avalonia.Native/src/OSX/WindowProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#import <AppKit/AppKit.h>

@class AvnMenu;
struct IAvnAutomationPeer;

@protocol AvnWindowProtocol
-(void) pollModalSession: (NSModalSession _Nonnull) session;
Expand All @@ -16,6 +17,7 @@
-(void) showAppMenuOnly;
-(void) showWindowMenuWithAppMenu;
-(void) applyMenu:(AvnMenu* _Nullable)menu;
-(IAvnAutomationPeer* _Nonnull) automationPeer;

-(double) getExtendedTitleBarHeight;
-(void) setIsExtended:(bool)value;
Expand Down
5 changes: 3 additions & 2 deletions native/Avalonia.Native/src/OSX/automation.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#pragma once

#import <Cocoa/Cocoa.h>
#include "AvnAccessibility.h"
NS_ASSUME_NONNULL_BEGIN

class IAvnAutomationPeer;

@interface AvnAccessibilityElement : NSAccessibilityElement
+ (AvnAccessibilityElement *) acquire:(IAvnAutomationPeer *) peer;
@interface AvnAccessibilityElement : NSAccessibilityElement <AvnAccessibility>
+ (id _Nullable) acquire:(IAvnAutomationPeer *) peer;
@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit f24d57c

Please sign in to comment.