-
Notifications
You must be signed in to change notification settings - Fork 162
VoiceOver accessibility issues in RN .62 #603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…iew." This reverts commit 5a67ae0.
|
|
This will presumably have to go in to the 0.62-stable branch as well |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice one, looks great 👌
Some of my comments are more about me trying to learn about the MS objc style, feel free to ignore those. I’ll defer to @HeyImChris for how much of a requirement these objc guide changes are.
| } | ||
| #if TARGET_OS_OSX // [TODO(macOS ISS#2323203) | ||
| // calling super.accessibilityLabel above on macOS causes the return value of this accessor to be ignored by VoiceOver. | ||
| // Calling the super's setAccessibilityLabel with nil ensures that the return value of this accessor is used by VoiceOver. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the nil setting break things on iOS or can we remove the platform checks and upstream it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It probably is fine to nil it on iOS too -- but AX is such a touchy area with lots of undocumented behavior in VoiceOver that I'd rather not risk any deltas to the iOS path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m going to unhide this one, just in case I ever want to come back to this while unforking.
| } | ||
| } | ||
| return nil; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for my understanding, what else can these values be that they can be ignored?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The accessibilityMinValue and accessibilityMaxValue are used for sliders and steppers roles. When the appropriate role, like slider, is set, VoiceOver will automatically compute percentages and announce them. i.e. if accessibilityMinValue=0, accessibilityMaxValue=100, and accessibilityValue = 50, voiceover will say "50%"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unhiding for posterity.
| } else if (_onClick) { | ||
| _onClick(nil); | ||
| return YES; | ||
| #endif // ]TODO(macOS ISS#2323203) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you imagine that at some point we might be able to upstream things like this and just have it not do anything on platforms that don’t have the concept of clicks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a weird one. On iOS returning NO from accessibilityActivate can make the system simulate a touch on the view instead which ends up indirectly causing an onPress on a touchable get called. On macOS this doesn't happen. There are a number of cases where a View or Touchable does not have an onAccessiblityAction handler but does have on onPress and expects a VoiceOver double tap to call it. It's an important case to support and this was the most obvious way to simulate a click -- we do it in other places such as performKeyEquivalent:.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know, thanks for the update! (I’ll actually unresolve this thread, as it’s just good general info for posterity.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW, I think I found how mac VoiceOver can simulate a click on VO activate: if the accessibilityActivationPoint prop is set https://developer.apple.com/documentation/appkit/nsaccessibility/1535149-accessibilityactivationpoint?language=objc. Might be worth further investigation in the future, but don't want to add risk to this change by introducing another property.
tido64
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Only a few nitpicks.
| // | ||
|
|
||
| // UIAccessibility.h/NSAccessibility.h | ||
| @compatibility_alias UIAccessibilityCustomAction NSAccessibilityCustomAction; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tom-un When we move RCTUIKit upstream at some point, do you think it makes sense to rename these symbols to be platform agnostic? E.g. in this case RCTAccessibilityCustomAction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, they should all be normalized to a consistent convention if/when we contribute upstream. I'm partial to things like RCTUIView -> UIView/NSView, because there are already several UIKit subclass upstream that use just the RCT prefix, like RCTView.
| if ([self accessibilityRole] == NSAccessibilityCheckBoxRole) { | ||
| for (NSString *state in [self accessibilityState]) { | ||
| id val = [self accessibilityState][state]; | ||
| if (val != nil) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the if statement below can be combined into one
| #endif // ]TODO(macOS ISS#2323203) | ||
| { | ||
| #if TARGET_OS_OSX // [TODO(macOS ISS#2323203) | ||
| if ([self isAccessibilityEnabled] == NO) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is plagued by early returns. Would be awesome if we could fix those up instead of adding one more to it :)
…angleRole. Make accessibilityValue return "checked" state for checkbox, radiobutton, and disclosure roles.
…anary builds set the version to 0.0.0-<commithash> via `node scripts/bump-oss-version.js --nigh tly` which causes the "Verify react-native-macos-init MacDebug" CI test to fail bec ause `pod install` fails due to `.podspec` versions that are 0.0.0. Add a `--testi ng` switch to `bump-oss-version.js` that changes the version to 1000.0.0 and use th at switch in `react-native-macos-init.yml`.
VoiceOver accessibility issues in RN .62 (microsoft#603) * Update scripts to publish react-native-macos-init * Clean up merge markers * Restored ios:macos RNTester parity except for InputAccessoryView. * Revert "Restored ios:macos RNTester parity except for InputAccessoryView." This reverts commit 5a67ae0. * Remove unnecessary android builds and tar file upload. * RN .62 accessiblity fixes. * Fix regression in recursive accessiblityLabel text. * Changed per PR feedback. * Add "disclosure" role for mac mapping to NSAccessibilityDisclosureTriangleRole. Make accessibilityValue return "checked" state for checkbox, radiobutton, and disclosure roles. * CocoaPods does not like podspec version numbers that are 0.0.0. The canary builds set the version to 0.0.0-<commithash> via `node scripts/bump-oss-version.js --nigh tly` which causes the "Verify react-native-macos-init MacDebug" CI test to fail bec ause `pod install` fails due to `.podspec` versions that are 0.0.0. Add a `--testi ng` switch to `bump-oss-version.js` that changes the version to 1000.0.0 and use th at switch in `react-native-macos-init.yml`. Co-authored-by: React-Native Bot <53619745+rnbot@users.noreply.github.com>
VoiceOver accessibility issues in RN .62 (#603) * Update scripts to publish react-native-macos-init * Clean up merge markers * Restored ios:macos RNTester parity except for InputAccessoryView. * Revert "Restored ios:macos RNTester parity except for InputAccessoryView." This reverts commit 5a67ae0. * Remove unnecessary android builds and tar file upload. * RN .62 accessiblity fixes. * Fix regression in recursive accessiblityLabel text. * Changed per PR feedback. * Add "disclosure" role for mac mapping to NSAccessibilityDisclosureTriangleRole. Make accessibilityValue return "checked" state for checkbox, radiobutton, and disclosure roles. * CocoaPods does not like podspec version numbers that are 0.0.0. The canary builds set the version to 0.0.0-<commithash> via `node scripts/bump-oss-version.js --nigh tly` which causes the "Verify react-native-macos-init MacDebug" CI test to fail bec ause `pod install` fails due to `.podspec` versions that are 0.0.0. Add a `--testi ng` switch to `bump-oss-version.js` that changes the version to 1000.0.0 and use th at switch in `react-native-macos-init.yml`. Co-authored-by: React-Native Bot <53619745+rnbot@users.noreply.github.com> Co-authored-by: React-Native Bot <53619745+rnbot@users.noreply.github.com>
Please select one of the following
Summary
There were a number of VoiceOver accessibility issues in react-native-macos since merging down .62.
accessibilityRole
When react-native-macos was first ported from iOS to macOS, there was no
accessibilityRoleprop. There was a now deprecatedaccessibilityTraitsprop, which corresponded to the-[UIAccessibility accessibilityTraits]property on iOS. On macOS there is no such property, but there is-[NSAccessibility accessibilityRole]property, so for react-native-macos theaccessibilityTraitsprop implementation was made to map to-[NSAccessibility accessibilityRole]. The iOS traitsbutton,image,link, andtextwere mapped to the macOS rolesNSAccessibilityButtonRole,NSAccessibilityImageRole,NSAccessibilityLinkRole, andNSAccessibilityStaticTextRole. Also, theaccessibilityTraitsprop was extended to acceptgroupandlistwhich mapped toNSAccessibilityGroupRoleandNSAccessibilityListRoleand are used in the macOS implementation ofVirtualList,SectionList,FlatList, etc.In later RN versions an
accessibilityRoleprop was added to replaceaccessibilityTraits. It contains a list of platform agnostic role names that map to traits on iOS and roles on Android. This change extends the macOS support for the full set of these RNaccessibilityRolevalues, and extends that list to includegroupandlist.accessibilityLabel
The native
accessibilityLabelproperty in react-native is supposed to return the JSaccessibilityLabelprop value if one was set, or return a concatenation of the child views including any text views. This recursive ability was broken in .62 such that even simple<Button>'s were not having their label's announced. This change restores it.accessibilityStates
RN used to have an
accessibilityStatesprop (plural) but it was deprecated and replaced byaccessibilityState. This change adds macOS support for settings states such as checked/unchecked, expanded/collapsed, disabled, and selected. These are all working, however for selected the correspondingisAccessibilitySelectedproperty on macOS only is announced when very particularaccessibilityRoleandaccessibilitySubRolecombinations are set such as those for tables and outline views.onAccessibilityAction / accessibilityActions
The
onAccessibilityAction / accessibilityActionsprops allow the app to respond to gestures such as swipe up/down to adjust sliders, double tap to activate, and set custom actions in the rotor. react-native-macos has never had proper support for these. Although the custom rotor actions now work in macOS, there is no announcement like on iOS saying "actions available" -- it will be up to the app developer to add an appropriateaccessibilityHintprop (which maps to the nativeaccessibilityHelpon mac) saying custom actions are available.Changelog
[Fix] [macOS] - VoiceOver accessibility issues in RN .62
Test Plan
Every case in the Accessibility test page in macOS RNTester was tested and compared against the functionality in the iOS RNTester. The follow list is a comparison of the iOS results versus the macOS results with notes where behavior differs.
TextView without label
[x] iOS: reads label
[x] macOS: reads label
TextView with label
[x] iOS: read accessibilityLabel instead of label
[x] macOS: read accessibilityLabel instead of label
Non-accessible view with TextView
[x] iOS: green and blue text separately reachable and reads each.
[x] macOS: green and blue text separately reachable and reads each.
Accessible view with TextViews without label
[x] iOS: green and blue reachable as one, reads both as one.
[x] macOS: green and blue reachable as one, reads both as one.
Accessible view with TextView with label
[x] iOS: green and blue reachable as one, reads accessibilityLabel instead of visible text
[x] macOS: green and blue reachable as one, reads accessibilityLabel instead of visible text
Accessible view with TextViews with hint
[x] iOS: green and blue reachable as one, reads both as one, reads accessibilityHint
[x] macOS: green and blue reachable as one, reads both as one, reads accessibilityHint
Accessible view TextViews with label and hint
[x] iOS: green and blue reachable as one, reads accessibilityLabel and accessibilityHint
[x] macOS: green and blue reachable as one, reads accessibilityLabel and accessibilityHint
Text with accessibilityRole = header
[x] iOS: "This is a title, heading"
[x] macOS: "This is a title" ... "you are currently on a text element inside a scroll area"
Touchable with accessibilityRole = link
[x] iOS: "Click me, link", click: Alert shown
[x] macOS: "link, Click me", click: Alert shown
Touchable with accessibilityRole = button
[x] iOS: "Click me, button", click: Alert shown
[x] macOS: "Click me, button", click: Alert shown
Disabled Touchable with role
[x] iOS: "I am disabled..., dimmed, button", click: nothing
[x] macOS: "I am disabled..., dimmed, button", click: nothing
View with multiple states
[x] iOS: "selected, This view is selected..., dimmed"
[-] macOS: "This view is selected..., dimmed" selected not announced, but it IS shown as Selected in Accessibility Inspector -- only certain roles can announce selection
View with label, hint, role, and state
[x] iOS: "selected, accessibilityLabel, button, ... accessibilityHint"
[-] macOS: "accessibilityLabel, button, ... accessibilityHint" selected not announced, but it IS shown as Selected in Accessibility Inspector -- only certain roles can announce selection
New accessibility roles and states
Alert example
[x] iOS: "element 1, alert"
[-] macOS: "element 1" no exact match on macos
Checkbox example
[x] iOS: "element 2, checkbox, checked, click be to change state"
[x] macOS: "element 2, checked, checkbox
Combobox example
[x] iOS: "element 3, combo box"
[x] macOS: "element 3, combo box"
Menu example
[x] iOS: "element 4, menu"
[x] macOS: "element 4, menu"
Menu bar example
[x] iOS: "element 5, menu bar"
[x] macOS: "element 5, menu bar"
Menu item example
[x] iOS: "element 6, menu item"
[x] macOS: "element 6, ... you are currently on a menu item"
Progress bar example
[x] iOS: "element 7, progress bar"
[x] macOS: "element 7, progress bar"
Radio button example
[x] iOS: "element 8, radio button"
[x] macOS: "element 8, radio button"
Radio button group
[x] iOS: "element 9, radio group"
[x] macOS: "element 9, radio group"
Scrollbar example
[x] iOS: "element 10, scrollbar"
[ ] macOS: cannot focus on this element
Spin button example
[x] iOS: "element 11, spin button"
[x] macOS: "element 11, stepper"
Switch example
[x] iOS: "element 12, switch button, on, ... double tap to toggle setting"
[x] macOS: "element 12, checked, checkbox"
Tab example
[x] iOS: "element 13, tab"
[-] macOS: "element 13, button" no exact match on macos
Tab list example
[x] iOS: "element 14, tab list"
[x] macOS: "element 14, tab group"
Timer example
[x] iOS: "element 15, timer"
[-] macOS: "element 15" no exact match on macos
Toolbar example
[x] iOS: "element 16, toolbar"
[x] macOS: "element 16, toolbar item palette"
State busy example
[x] iOS: "element 17, busy"
[-] macOS: "element 17" no exact match on macos
Expandable element example
[x] iOS: "element 18, collapsed, ... click me to change state", double tap toggles expand/collapse
[x] macOS: "element 18, collapsed, ... click me to change state", Ctrl+Opt+space toggles expand/collapse
Selectable element example
[x] iOS: "selected, element 19, dimmed, ... use the button on the right to enable selection". Clicking Enable selection then changes element 19 to say: "selected, element 19, click me to unselect". Double tap: "element 19"
[-] macOS: "element 19, dimmed" selected not announced, but it IS shown as Selected in Accessibility Inspector -- only certain roles can announce selection
Nested checkbox with delayed state changed
[x] iOS: "Meat, checkbox, not checked, ... state changes in two seconds after clicking"
[x] macOS: "Meat, unchecked, checkbox"
Accessibility action examples
non-touchable with activate action
[x] iOS: "click me", click: shows alert
[x] macOS: "click me", click: shows alert
View with multiple actions
[x] iOS: "this view support many actions ... actions available". Rotor to Active "this view supports many actions ... swipe up and down to select a custom action, then double tap to activate". switch up/down: "cut label", "copy label", "paste label", "activate ... default"
[-] macOS: "this view support many actions", no actions available announced, Ctrl+Opt+Cmd+Space shows Action Menu with "cut label", "copy label", "paste label"
Adjustable with increment/decrement actions
[x] iOS: "Slider, adjustable, swipe up or down with one finger to adjust the value", Swipe up or down: Alert
[x] macOS: "slider, Slider", Ctrl+Opt+Space show Alert that increment has happened. Ctrl+Opt+Cmd+Space shows Action menu with "increment", "decrement"
Button with custom accessibility actions
[x] iOS: "click me, button, ... actions available". Rotor to Active "this view supports many actions ... swipe up and down to select a custom action, then double tap to activate", switch up/down: "cut label", "copy label", "paste label", "activate ... default"
[-] macOS: "click me, button", no actions available announced, Ctrl+Opt+Cmd+Space shows Action Menu with "cut label", "copy label", "paste label"
Fake Slider Example
Fake Slider
[x] iOS: "Fake slider, 50%, adjustable, swipe up or down with one finger to adjust the value", Swipe: "52%"
[x] macOS: "50%, Fake slider, slider", Ctrl+Opt+Space: "Increment 52%"
Equalizer
[x] iOS: "equalizer, center, adjustable, swipe up or down with one finger to adjust the value", Swipe: "right"
[x] macOS: "center, equalizer, slider", Ctrl+Opt+Space: "Increment right"
Check if the screen reader is enabled
[x] iOS: The screen read is enabled/disabled
[x] macOS: The screen read is enabled/disabled
Check if the display options are enabled
[x] iOS: Invert colors is disabled/enabled
[x] macOS: Invert colors is disabled/enabled
[x] iOS: Reduce motion is disabled/enabled
[x] macOS: Reduce motion is disabled/enabled
[x] iOS: Reduce transparency is disabled/enabled
[x] macOS: Reduce transparency is disabled/enabled
Check if the screen reader announces
[x] iOS: "Announce for Accessibility, button", double tapping says "Announce for Accessibility"
[x] macOS: "Announce for Accessibility, button", Ctrl+Opt+Space: "Announcement Test"
Check if the screen reader focus sets
[x] iOS: "Set Accessibility Focus, button", double tapping moves to label below
[x] macOS: "Set Accessibility Focus, button", Ctrl+Opt+Space: moves to label below
Microsoft Reviewers: Open in CodeFlow