Fix crash with CJK keyboard with emoji at end of text field (flutter/engine#42540)

The `isRTLAtPosition` method had a bug, it used `NSInteger max = [_selectionRects count]` instead of `NSInteger max = [_selectionRects count] - 1`. But I realized we don't even need the function any more, it was used in a few places in previous iterations of #36643, but in the only place remaining, we actually already have the selection rect and don't need to search for it by position.

Btw as an explanation of the crash, I guess there is some mismatch between code point and character count somewhere. UIKit was asking for `caretRectForPosition:2` when we only had 1 character. This could have only crashed when floating cursor selection was used, but actually when switching to CJK keyboard, UIKit turns out to use `caretRectForPosition` to calculate something about the composing rect.

Fixes https://github.com/flutter/flutter/issues/128031
This commit is contained in:
Callum Moffat 2023-06-12 14:13:03 -04:00 committed by GitHub
parent ed72d25fef
commit 641003fc6d
2 changed files with 29 additions and 22 deletions

View File

@ -1658,25 +1658,6 @@ static BOOL IsSelectionRectBoundaryCloserToPoint(CGPoint point,
return CGRectZero;
}
- (BOOL)isRTLAtPosition:(NSUInteger)position {
// _selectionRects is sorted by position already.
// We can use binary search.
NSInteger min = 0;
NSInteger max = [_selectionRects count];
while (min <= max) {
const NSUInteger mid = min + (max - min) / 2;
FlutterTextSelectionRect* rect = _selectionRects[mid];
if (rect.position > position) {
max = mid - 1;
} else if (rect.position == position) {
return rect.isRTL;
} else {
min = mid + 1;
}
}
return NO;
}
- (CGRect)caretRectForPosition:(UITextPosition*)position {
NSInteger index = ((FlutterTextPosition*)position).index;
UITextStorageDirection affinity = ((FlutterTextPosition*)position).affinity;
@ -1699,7 +1680,8 @@ static BOOL IsSelectionRectBoundaryCloserToPoint(CGPoint point,
CGRect characterAfterCaret = rects[0].rect;
// Return a zero-width rectangle along the upstream edge of the character after the caret
// position.
if ([self isRTLAtPosition:index]) {
if ([rects[0] isKindOfClass:[FlutterTextSelectionRect class]] &&
((FlutterTextSelectionRect*)rects[0]).isRTL) {
return CGRectMake(characterAfterCaret.origin.x + characterAfterCaret.size.width,
characterAfterCaret.origin.y, 0, characterAfterCaret.size.height);
} else {
@ -1712,7 +1694,8 @@ static BOOL IsSelectionRectBoundaryCloserToPoint(CGPoint point,
CGRect characterAfterCaret = rects[1].rect;
// Return a zero-width rectangle along the upstream edge of the character after the caret
// position.
if ([self isRTLAtPosition:index]) {
if ([rects[1] isKindOfClass:[FlutterTextSelectionRect class]] &&
((FlutterTextSelectionRect*)rects[1]).isRTL) {
return CGRectMake(characterAfterCaret.origin.x + characterAfterCaret.size.width,
characterAfterCaret.origin.y, 0, characterAfterCaret.size.height);
} else {
@ -1727,7 +1710,8 @@ static BOOL IsSelectionRectBoundaryCloserToPoint(CGPoint point,
// For both cases, return a zero-width rectangle along the downstream edge of the character
// before the caret position.
CGRect characterBeforeCaret = rects[0].rect;
if ([self isRTLAtPosition:index - 1]) {
if ([rects[0] isKindOfClass:[FlutterTextSelectionRect class]] &&
((FlutterTextSelectionRect*)rects[0]).isRTL) {
return CGRectMake(characterBeforeCaret.origin.x, characterBeforeCaret.origin.y, 0,
characterBeforeCaret.size.height);
} else {

View File

@ -1558,6 +1558,29 @@ FLUTTER_ASSERT_ARC
((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).affinity);
}
- (void)testClosestPositionToPointWithPartialSelectionRects {
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
[inputView setTextInputState:@{@"text" : @"COMPOSING"}];
[inputView setSelectionRects:@[ [FlutterTextSelectionRect
selectionRectWithRect:CGRectMake(0, 0, 100, 100)
position:0U] ]];
// Asking with a position at the end of selection rects should give you the trailing edge of
// the last rect.
XCTAssertTrue(CGRectEqualToRect(
[inputView caretRectForPosition:[FlutterTextPosition
positionWithIndex:1
affinity:UITextStorageDirectionForward]],
CGRectMake(100, 0, 0, 100)));
// Asking with a position beyond the end of selection rects should return CGRectZero without
// crashing.
XCTAssertTrue(CGRectEqualToRect(
[inputView caretRectForPosition:[FlutterTextPosition
positionWithIndex:2
affinity:UITextStorageDirectionForward]],
CGRectZero));
}
#pragma mark - Floating Cursor - Tests
- (void)testFloatingCursorDoesNotThrow {