Browse Source

macOS: fix accessibility read-only, selection, and expand/collapse actions (#21599)

* Gate macOS setAccessibilityValue on value read-only state

A read-only value provider must not advertise the setAccessibilityValue selector, so AX clients see the value as not settable.

* Gate macOS read-only range value mutations

Report a read-only IRangeValueProvider (ProgressBar, read-only NumericUpDown) as not settable, and stop advertising setAccessibilityValue/increment/decrement for it; keep min/max exposed.

* Support deselect and toggle expand/collapse in macOS AX

setAccessibilitySelected:NO now removes from selection, and AXPress toggles an expand/collapse provider instead of always expanding.

* Harden macOS AX select, value-set, and disabled-control actions

setAccessibilitySelected:YES now adds to the selection instead of replacing it, the value setter respects read-only, and mutating actions are gated on IsEnabled.
pull/21602/head
Jumar Macato 4 months ago
committed by GitHub
parent
commit
59e7353a45
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 41
      native/Avalonia.Native/src/OSX/automation.mm
  2. 3
      src/Avalonia.Native/AvnAutomationPeer.cs
  3. 5
      src/Avalonia.Native/avn.idl

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

@ -260,14 +260,16 @@
- (void)setAccessibilityValue:(id)newValue - (void)setAccessibilityValue:(id)newValue
{ {
if (_peer->IsValueProvider()) if (!_peer->IsEnabled())
return;
if (_peer->IsValueProvider() && !_peer->ValueProvider_IsReadOnly())
{ {
if (newValue == nil) if (newValue == nil)
_peer->ValueProvider_SetValue(nil); _peer->ValueProvider_SetValue(nil);
else if ([newValue isKindOfClass:[NSString class]]) else if ([newValue isKindOfClass:[NSString class]])
_peer->ValueProvider_SetValue([(NSString*)newValue UTF8String]); _peer->ValueProvider_SetValue([(NSString*)newValue UTF8String]);
} }
else if (_peer->IsRangeValueProvider()) else if (_peer->IsRangeValueProvider() && !_peer->RangeValueProvider_IsReadOnly())
{ {
if ([newValue isKindOfClass:[NSNumber class]]) if ([newValue isKindOfClass:[NSNumber class]])
_peer->RangeValueProvider_SetValue([(NSNumber*)newValue doubleValue]); _peer->RangeValueProvider_SetValue([(NSNumber*)newValue doubleValue]);
@ -281,7 +283,7 @@
if (_peer->IsValueProvider()) if (_peer->IsValueProvider())
return !_peer->ValueProvider_IsReadOnly(); return !_peer->ValueProvider_IsReadOnly();
if (_peer->IsRangeValueProvider()) if (_peer->IsRangeValueProvider())
return YES; return !_peer->RangeValueProvider_IsReadOnly();
return NO; return NO;
} }
@ -395,7 +397,7 @@
- (void)setAccessibilityExpanded:(BOOL)accessibilityExpanded - (void)setAccessibilityExpanded:(BOOL)accessibilityExpanded
{ {
if (!_peer->IsExpandCollapseProvider()) if (!_peer->IsExpandCollapseProvider() || !_peer->IsEnabled())
return; return;
if (accessibilityExpanded) if (accessibilityExpanded)
_peer->ExpandCollapseProvider_Expand(); _peer->ExpandCollapseProvider_Expand();
@ -405,13 +407,18 @@
- (BOOL)accessibilityPerformPress - (BOOL)accessibilityPerformPress
{ {
if (!_peer->IsEnabled())
return NO;
if (_peer->IsInvokeProvider()) if (_peer->IsInvokeProvider())
{ {
_peer->InvokeProvider_Invoke(); _peer->InvokeProvider_Invoke();
} }
else if (_peer->IsExpandCollapseProvider()) else if (_peer->IsExpandCollapseProvider())
{ {
_peer->ExpandCollapseProvider_Expand(); if (_peer->ExpandCollapseProvider_GetIsExpanded())
_peer->ExpandCollapseProvider_Collapse();
else
_peer->ExpandCollapseProvider_Expand();
} }
else if (_peer->IsToggleProvider()) else if (_peer->IsToggleProvider())
{ {
@ -422,7 +429,7 @@
- (BOOL)accessibilityPerformIncrement - (BOOL)accessibilityPerformIncrement
{ {
if (!_peer->IsRangeValueProvider()) if (!_peer->IsRangeValueProvider() || _peer->RangeValueProvider_IsReadOnly() || !_peer->IsEnabled())
return NO; return NO;
auto value = _peer->RangeValueProvider_GetValue(); auto value = _peer->RangeValueProvider_GetValue();
value += _peer->RangeValueProvider_GetSmallChange(); value += _peer->RangeValueProvider_GetSmallChange();
@ -432,7 +439,7 @@
- (BOOL)accessibilityPerformDecrement - (BOOL)accessibilityPerformDecrement
{ {
if (!_peer->IsRangeValueProvider()) if (!_peer->IsRangeValueProvider() || _peer->RangeValueProvider_IsReadOnly() || !_peer->IsEnabled())
return NO; return NO;
auto value = _peer->RangeValueProvider_GetValue(); auto value = _peer->RangeValueProvider_GetValue();
value -= _peer->RangeValueProvider_GetSmallChange(); value -= _peer->RangeValueProvider_GetSmallChange();
@ -442,7 +449,7 @@
- (BOOL)accessibilityPerformShowMenu - (BOOL)accessibilityPerformShowMenu
{ {
if (!_peer->IsExpandCollapseProvider()) if (!_peer->IsExpandCollapseProvider() || !_peer->IsEnabled())
return NO; return NO;
_peer->ExpandCollapseProvider_Expand(); _peer->ExpandCollapseProvider_Expand();
return YES; return YES;
@ -476,15 +483,20 @@
- (void)setAccessibilitySelected:(BOOL)accessibilitySelected - (void)setAccessibilitySelected:(BOOL)accessibilitySelected
{ {
if (accessibilitySelected && _peer->IsSelectionItemProvider()) if (!_peer->IsSelectionItemProvider() || !_peer->IsEnabled())
_peer->SelectionItemProvider_Select(); return;
if (accessibilitySelected)
_peer->SelectionItemProvider_AddToSelection();
else
_peer->SelectionItemProvider_RemoveFromSelection();
} }
- (BOOL)isAccessibilitySelectorAllowed:(SEL)selector - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector
{ {
if (selector == @selector(setAccessibilityValue:)) if (selector == @selector(setAccessibilityValue:))
{ {
return _peer->IsValueProvider() || _peer->IsRangeValueProvider(); return (_peer->IsValueProvider() && !_peer->ValueProvider_IsReadOnly()) ||
(_peer->IsRangeValueProvider() && !_peer->RangeValueProvider_IsReadOnly());
} }
else if (selector == @selector(accessibilityPerformShowMenu)) else if (selector == @selector(accessibilityPerformShowMenu))
{ {
@ -503,8 +515,11 @@
return _peer->IsSelectionItemProvider(); return _peer->IsSelectionItemProvider();
} }
else if (selector == @selector(accessibilityPerformIncrement) || else if (selector == @selector(accessibilityPerformIncrement) ||
selector == @selector(accessibilityPerformDecrement) || selector == @selector(accessibilityPerformDecrement))
selector == @selector(accessibilityMinValue) || {
return _peer->IsRangeValueProvider() && !_peer->RangeValueProvider_IsReadOnly();
}
else if (selector == @selector(accessibilityMinValue) ||
selector == @selector(accessibilityMaxValue)) selector == @selector(accessibilityMaxValue))
{ {
return _peer->IsRangeValueProvider(); return _peer->IsRangeValueProvider();

3
src/Avalonia.Native/AvnAutomationPeer.cs

@ -184,10 +184,13 @@ namespace Avalonia.Native
public double RangeValueProvider_GetSmallChange() => RangeValueProvider.SmallChange; public double RangeValueProvider_GetSmallChange() => RangeValueProvider.SmallChange;
public double RangeValueProvider_GetLargeChange() => RangeValueProvider.LargeChange; public double RangeValueProvider_GetLargeChange() => RangeValueProvider.LargeChange;
public void RangeValueProvider_SetValue(double value) => RangeValueProvider.SetValue(value); public void RangeValueProvider_SetValue(double value) => RangeValueProvider.SetValue(value);
public int RangeValueProvider_IsReadOnly() => RangeValueProvider.IsReadOnly.AsComBool();
public int IsSelectionItemProvider() => IsProvider<ISelectionItemProvider>(); public int IsSelectionItemProvider() => IsProvider<ISelectionItemProvider>();
public int SelectionItemProvider_IsSelected() => SelectionItemProvider.IsSelected.AsComBool(); public int SelectionItemProvider_IsSelected() => SelectionItemProvider.IsSelected.AsComBool();
public void SelectionItemProvider_Select() => SelectionItemProvider.Select(); public void SelectionItemProvider_Select() => SelectionItemProvider.Select();
public void SelectionItemProvider_AddToSelection() => SelectionItemProvider.AddToSelection();
public void SelectionItemProvider_RemoveFromSelection() => SelectionItemProvider.RemoveFromSelection();
public IAvnAutomationPeer? ScrollProvider_GetHorizontalScrollBar() public IAvnAutomationPeer? ScrollProvider_GetHorizontalScrollBar()
=> _inner is ScrollViewerAutomationPeer scrollViewer ? Wrap(scrollViewer.GetHorizontalScrollBarPeer()) : null; => _inner is ScrollViewerAutomationPeer scrollViewer ? Wrap(scrollViewer.GetHorizontalScrollBarPeer()) : null;

5
src/Avalonia.Native/avn.idl

@ -1315,10 +1315,13 @@ interface IAvnAutomationPeer : IUnknown
double RangeValueProvider_GetSmallChange(); double RangeValueProvider_GetSmallChange();
double RangeValueProvider_GetLargeChange(); double RangeValueProvider_GetLargeChange();
void RangeValueProvider_SetValue(double value); void RangeValueProvider_SetValue(double value);
bool RangeValueProvider_IsReadOnly();
bool IsSelectionItemProvider(); bool IsSelectionItemProvider();
bool SelectionItemProvider_IsSelected(); bool SelectionItemProvider_IsSelected();
void SelectionItemProvider_Select(); void SelectionItemProvider_Select();
void SelectionItemProvider_AddToSelection();
void SelectionItemProvider_RemoveFromSelection();
IAvnAutomationPeer* ScrollProvider_GetHorizontalScrollBar(); IAvnAutomationPeer* ScrollProvider_GetHorizontalScrollBar();
IAvnAutomationPeer* ScrollProvider_GetVerticalScrollBar(); IAvnAutomationPeer* ScrollProvider_GetVerticalScrollBar();

Loading…
Cancel
Save