-
Notifications
You must be signed in to change notification settings - Fork 24.4k
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
Add support for screen reader blur and focus (accessibility) #24642
Add support for screen reader blur and focus (accessibility) #24642
Conversation
event.putInt("target", host.getId()); | ||
|
||
if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED) { | ||
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(host.getId(), BLUR_EVENT_NAME, event); |
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 not sure how React Native handles the call to receiveEvent here, but it's worth pointing out that not all events that get passed through onInitializeAccessibilityEvent
will actually be fired. Every ancestor of the view has the chance to cancel the event before it bubbles to the top. Only if it makes it all the way to the top of the view hierarchy will it be passed off to a running accessibility service.
The AccessibilityEvent cycle goes like this:
1.) View.sendAccessibilityEvent()
(intent to send event started)
2.) View.onInitializeAccessibilityEvent()
(event populated with view info)
3.) View.dispatchPopulateAccessibilityEvent()
(event text populated with additional info)
4.) ViewParent.requestSendAccessibilityEvent()
(parent of View is free to modify or cancel the event, or pass it up to its parent)
5.) ViewRootImpl calls sendAccessibilityEvent
on the AccessibilityManager
(this is what actually passes the event to any running accessibility service).
The best way to actually handle this would probably be to override performAccessibilityAction
instead. When Talkback (or any other accessibility service) receives a focus event, it's not guaranteed to actually change focus. For example, if the already focused view sent another TYPE_VIEW_ACCESSIBILITY_FOCUSED
event, it would be ignored. If it does decide to change focus, it will fire the performAccessibilityAction
method on the View and pass in an AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
action.
Here is the code in Talkback doing that for reference:
https://github.com/google/talkback/blob/e69d4731fce02bb9e69613d0e48c29033cad4a98/talkback/src/main/java/eventprocessor/ProcessorFocusAndSingleTap.java#L871
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.
@bravalla Thanks for this detailed feedback.
I applied your suggestions, which works fine for focus event, but for the blur event it doesn't work as expected. Using performAccessibilityAction
, blur is triggered just when the focus moves to a non react-native element (my assumption). When the focus is moving around react-native elements, nothing happens.
In this video you can understand better what I say: blur just trigger when focus moves to status bar.
code: https://gist.github.com/elucaswork/242c098a78577ddcdef4d75a60d2a4a5
Do you have an idea of what can be happening?
Thank you.
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.
@elucaswork, The code here looks good to me. I've verified the approach in a non-ReactNative app to make sure that Talkback was correctly calling performAccessibilityAction on both focus and blur, and it is.
It's possible that the issue is at the ReactNative side (I commented above on a few typos), but I am definitely not an expert on that layer so I'll defer to others if thats where the issue lies.
You could verify that the Android side is working correctly by adding some logging into the performAccessibilityAction method itself to see if the expected actions are ever being passed in.
@@ -1325,6 +1331,14 @@ const TextInput = createReactClass({ | |||
} | |||
}, | |||
|
|||
_onAccessibilityBlur: function(event: BlurEvent) { | |||
this.props.oncAccessibilityBlur && this.props.oncAccessibilityBlur(event); |
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.
typo oncAccessibilityBlur -> onAccessibilityBlur
}, | ||
|
||
_onAccessibilityFocus: function(event: BlurEvent) { | ||
this.props.oncAccessibilityFocus && this.props.oncAccessibilityFocus(event); |
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.
typo oncAccessibilityFocus -> onAccessibilityFocus
@@ -1325,6 +1331,14 @@ const TextInput = createReactClass({ | |||
} | |||
}, | |||
|
|||
_onAccessibilityBlur: function(event: BlurEvent) { |
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.
Shoudln't this be AccessibilityBlurEvent not BlurEvent?
this.props.oncAccessibilityBlur && this.props.oncAccessibilityBlur(event); | ||
}, | ||
|
||
_onAccessibilityFocus: function(event: BlurEvent) { |
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.
Shoudln't this be AccessibilityFocusEvent not BlurEvent?
event.putInt("target", host.getId()); | ||
|
||
if (accessibilityEvent.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED) { | ||
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(host.getId(), BLUR_EVENT_NAME, event); |
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.
@elucaswork, The code here looks good to me. I've verified the approach in a non-ReactNative app to make sure that Talkback was correctly calling performAccessibilityAction on both focus and blur, and it is.
It's possible that the issue is at the ReactNative side (I commented above on a few typos), but I am definitely not an expert on that layer so I'll defer to others if thats where the issue lies.
You could verify that the Android side is working correctly by adding some logging into the performAccessibilityAction method itself to see if the expected actions are ever being passed in.
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 just realized there is another PR that will conflict with this. Check out Marc's changes here (#24695).
His approach allows for full custom accessibility events, as well as pre-defined events like the ones you have here. It should be pretty easy to add your events into his framework.
I'll let you two figure out which of you makes the most sense to add these in :)
@blavalla good point. It makes sense to wait for @marcmulcahy's #24695 and then add blur and focus events on top of it. I'm going to close it for now and open a new one as soon as "extended accessibility actions" gets merged. Thanks for your review! |
@marcmulcahy's 24695 PR has been merged. Is the functionality to add support for voice over focus and blur has bee added. or any active PR which is adding this functionality? Thank You estevaolucas |
@chyamuna , this functionality hasn't been added, but if you'd like to take up the mantle here and make a PR I'd be happy to review. I think the API for this is pretty clear, since 24695 added an accessibilityActions prop with a few built-in actions like "activate" and "longpress". I think "focus" and "blur" could be added as additional built-in actions that work for both iOS and Android. See the documentation here if you aren't familiar with this API - https://reactnative.dev/docs/accessibility#accessibility-actions |
Summary
Add support for screen reader blur and focus events via
UIAccessibilityFocus
for iOS andView.AccessibilityDelegate
withAccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
andTYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
for Android.When VoiceOver (iOS) or TalkBack (Android) enabled,
onAccessibilityFocus
will be fired once the element gains the screen reader focus, andonAccessibilityBlur
will be fired when it loses the screen reader focus.Changelog
[General] [Added] - Accessibility - Add support for screen reader blur and focus events
Test Plan
P.S.: To make it work for
TextInput
on iOS, this fix #24641 is necessary.