Skip to content

Commit

Permalink
Fix: a11y crash when an accessible link is ellipsized away (#37050)
Browse files Browse the repository at this point in the history
Summary:
If an accessible link is ellipsized out of being rendered, the AccessibilityDelegate will still attempt to populate an accessibility node for it; doing so results in an invalid request to a TextLayout API, however, causing a crash. This crash occurs as soon as the element is rendered, so long as a Screen Reader (or app using similar a11y APIs) is enabled. This change uses a technique similar to those existing to make the node "blank" in such cases, so Talkback can filter it out—and, more importantly, not crash.

## Changelog:

[Android] [Fixed] - Fix links hidden via ellipsis crashing screen readers

Pull Request resolved: #37050

Test Plan:
- Added a block to the "Accessibility Android APIs" page in the rn-tester app. Without the changes to `ReactAccessibilityDelegate`, this component crashes the app; with the changes, the component renders without problem.
- You can also see the crash "in the wild" using [this Expo Snack](https://snack.expo.dev/dhleong/2d1407) that I put together when trying to isolate this issue.

Reviewed By: rshest

Differential Revision: D46206673

Pulled By: NickGerleman

fbshipit-source-id: 0eb3e735202ee6be5f931bbb4bb92c24e7458ea6
  • Loading branch information
dhleong authored and facebook-github-bot committed Jun 4, 2023
1 parent fd9e295 commit d54f486
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -786,9 +786,18 @@ protected void onPopulateNodeForVirtualView(
return;
}

// NOTE: The span may not actually have visible bounds within its parent,
// due to line limits, etc.
final Rect bounds = getBoundsInParent(accessibleTextSpan);
if (bounds == null) {
node.setContentDescription("");
node.setBoundsInParent(new Rect(0, 0, 1, 1));
return;
}

node.setContentDescription(accessibleTextSpan.description);
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
node.setBoundsInParent(getBoundsInParent(accessibleTextSpan));
node.setBoundsInParent(bounds);
node.setRoleDescription(mView.getResources().getString(R.string.link_description));
node.setClassName(AccessibilityRole.getValue(AccessibilityRole.BUTTON));
}
Expand All @@ -805,10 +814,19 @@ private Rect getBoundsInParent(AccessibilityLinks.AccessibleLink accessibleLink)
return new Rect(0, 0, textView.getWidth(), textView.getHeight());
}

Rect rootRect = new Rect();

double startOffset = accessibleLink.start;
double endOffset = accessibleLink.end;

// Ensure the link hasn't been ellipsized away; in such cases,
// getPrimaryHorizontal will crash (and the link isn't rendered anyway).
int startOffsetLineNumber = textViewLayout.getLineForOffset((int) startOffset);
int lineEndOffset = textViewLayout.getLineEnd(startOffsetLineNumber);
if (startOffset > lineEndOffset) {
return null;
}

Rect rootRect = new Rect();

double startXCoordinates = textViewLayout.getPrimaryHorizontal((int) startOffset);

final Paint paint = new Paint();
Expand All @@ -818,7 +836,6 @@ private Rect getBoundsInParent(AccessibilityLinks.AccessibleLink accessibleLink)
paint.setTextSize(textSize);
int textWidth = (int) Math.ceil(paint.measureText(accessibleLink.description));

int startOffsetLineNumber = textViewLayout.getLineForOffset((int) startOffset);
int endOffsetLineNumber = textViewLayout.getLineForOffset((int) endOffset);
boolean isMultiline = startOffsetLineNumber != endOffsetLineNumber;
textViewLayout.getLineBounds(startOffsetLineNumber, rootRect);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ class AccessibilityAndroidExample extends React.Component<
render(): React.Node {
return (
<RNTesterPage title={'Accessibility Android APIs'}>
<RNTesterBlock title="Ellipsized Accessible Links">
<Text numberOfLines={3}>
<Text>
Bacon {this.state.count} Ipsum{'\n'}
</Text>
<Text>Dolor sit amet{'\n'}</Text>
<Text>Eggsecetur{'\n'}</Text>
<Text>{'\n'}</Text>
<Text accessibilityRole="link" onPress={this._addOne}>
http://github.com
</Text>
</Text>
</RNTesterBlock>

<RNTesterBlock title="LiveRegion">
<TouchableWithoutFeedback onPress={this._addOne}>
<View style={styles.embedded}>
Expand Down

0 comments on commit d54f486

Please sign in to comment.