Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit de68fba

Browse files
authored
Fix crash with CJK keyboard with emoji at end of text field (#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 flutter/flutter#128031
1 parent e6f9712 commit de68fba

File tree

2 files changed

+29
-22
lines changed

2 files changed

+29
-22
lines changed

shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,25 +1658,6 @@ - (CGRect)firstRectForRange:(UITextRange*)range {
16581658
return CGRectZero;
16591659
}
16601660

1661-
- (BOOL)isRTLAtPosition:(NSUInteger)position {
1662-
// _selectionRects is sorted by position already.
1663-
// We can use binary search.
1664-
NSInteger min = 0;
1665-
NSInteger max = [_selectionRects count];
1666-
while (min <= max) {
1667-
const NSUInteger mid = min + (max - min) / 2;
1668-
FlutterTextSelectionRect* rect = _selectionRects[mid];
1669-
if (rect.position > position) {
1670-
max = mid - 1;
1671-
} else if (rect.position == position) {
1672-
return rect.isRTL;
1673-
} else {
1674-
min = mid + 1;
1675-
}
1676-
}
1677-
return NO;
1678-
}
1679-
16801661
- (CGRect)caretRectForPosition:(UITextPosition*)position {
16811662
NSInteger index = ((FlutterTextPosition*)position).index;
16821663
UITextStorageDirection affinity = ((FlutterTextPosition*)position).affinity;
@@ -1699,7 +1680,8 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position {
16991680
CGRect characterAfterCaret = rects[0].rect;
17001681
// Return a zero-width rectangle along the upstream edge of the character after the caret
17011682
// position.
1702-
if ([self isRTLAtPosition:index]) {
1683+
if ([rects[0] isKindOfClass:[FlutterTextSelectionRect class]] &&
1684+
((FlutterTextSelectionRect*)rects[0]).isRTL) {
17031685
return CGRectMake(characterAfterCaret.origin.x + characterAfterCaret.size.width,
17041686
characterAfterCaret.origin.y, 0, characterAfterCaret.size.height);
17051687
} else {
@@ -1712,7 +1694,8 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position {
17121694
CGRect characterAfterCaret = rects[1].rect;
17131695
// Return a zero-width rectangle along the upstream edge of the character after the caret
17141696
// position.
1715-
if ([self isRTLAtPosition:index]) {
1697+
if ([rects[1] isKindOfClass:[FlutterTextSelectionRect class]] &&
1698+
((FlutterTextSelectionRect*)rects[1]).isRTL) {
17161699
return CGRectMake(characterAfterCaret.origin.x + characterAfterCaret.size.width,
17171700
characterAfterCaret.origin.y, 0, characterAfterCaret.size.height);
17181701
} else {
@@ -1727,7 +1710,8 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position {
17271710
// For both cases, return a zero-width rectangle along the downstream edge of the character
17281711
// before the caret position.
17291712
CGRect characterBeforeCaret = rects[0].rect;
1730-
if ([self isRTLAtPosition:index - 1]) {
1713+
if ([rects[0] isKindOfClass:[FlutterTextSelectionRect class]] &&
1714+
((FlutterTextSelectionRect*)rects[0]).isRTL) {
17311715
return CGRectMake(characterBeforeCaret.origin.x, characterBeforeCaret.origin.y, 0,
17321716
characterBeforeCaret.size.height);
17331717
} else {

shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1558,6 +1558,29 @@ - (void)testClosestPositionToPointWithinRange {
15581558
((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).affinity);
15591559
}
15601560

1561+
- (void)testClosestPositionToPointWithPartialSelectionRects {
1562+
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1563+
[inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1564+
1565+
[inputView setSelectionRects:@[ [FlutterTextSelectionRect
1566+
selectionRectWithRect:CGRectMake(0, 0, 100, 100)
1567+
position:0U] ]];
1568+
// Asking with a position at the end of selection rects should give you the trailing edge of
1569+
// the last rect.
1570+
XCTAssertTrue(CGRectEqualToRect(
1571+
[inputView caretRectForPosition:[FlutterTextPosition
1572+
positionWithIndex:1
1573+
affinity:UITextStorageDirectionForward]],
1574+
CGRectMake(100, 0, 0, 100)));
1575+
// Asking with a position beyond the end of selection rects should return CGRectZero without
1576+
// crashing.
1577+
XCTAssertTrue(CGRectEqualToRect(
1578+
[inputView caretRectForPosition:[FlutterTextPosition
1579+
positionWithIndex:2
1580+
affinity:UITextStorageDirectionForward]],
1581+
CGRectZero));
1582+
}
1583+
15611584
#pragma mark - Floating Cursor - Tests
15621585

15631586
- (void)testFloatingCursorDoesNotThrow {

0 commit comments

Comments
 (0)