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

Commit 956d16a

Browse files
moffatmanJasguerrero
authored andcommitted
Fix crash with CJK keyboard with emoji at end of text field (#42539)
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 45f6e00 commit 956d16a

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,6 +1600,7 @@ - (CGRect)firstRectForRange:(UITextRange*)range {
16001600
}
16011601

16021602
- (CGRect)caretRectForPosition:(UITextPosition*)position {
1603+
<<<<<<< HEAD
16031604
// TODO(cbracken) Implement.
16041605

16051606
// As of iOS 14.4, this call is used by iOS's
@@ -1616,6 +1617,67 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position {
16161617

16171618
- (CGRect)bounds {
16181619
return _isFloatingCursorActive ? kSpacePanBounds : super.bounds;
1620+
=======
1621+
NSInteger index = ((FlutterTextPosition*)position).index;
1622+
UITextStorageDirection affinity = ((FlutterTextPosition*)position).affinity;
1623+
// Get the selectionRect of the characters before and after the requested caret position.
1624+
NSArray<UITextSelectionRect*>* rects = [self
1625+
selectionRectsForRange:[FlutterTextRange
1626+
rangeWithNSRange:fml::RangeForCharactersInRange(
1627+
self.text,
1628+
NSMakeRange(
1629+
MAX(0, index - 1),
1630+
(index >= (NSInteger)self.text.length)
1631+
? 1
1632+
: 2))]];
1633+
if (rects.count == 0) {
1634+
return CGRectZero;
1635+
}
1636+
if (index == 0) {
1637+
// There is no character before the caret, so this will be the bounds of the character after the
1638+
// caret position.
1639+
CGRect characterAfterCaret = rects[0].rect;
1640+
// Return a zero-width rectangle along the upstream edge of the character after the caret
1641+
// position.
1642+
if ([rects[0] isKindOfClass:[FlutterTextSelectionRect class]] &&
1643+
((FlutterTextSelectionRect*)rects[0]).isRTL) {
1644+
return CGRectMake(characterAfterCaret.origin.x + characterAfterCaret.size.width,
1645+
characterAfterCaret.origin.y, 0, characterAfterCaret.size.height);
1646+
} else {
1647+
return CGRectMake(characterAfterCaret.origin.x, characterAfterCaret.origin.y, 0,
1648+
characterAfterCaret.size.height);
1649+
}
1650+
} else if (rects.count == 2 && affinity == UITextStorageDirectionForward) {
1651+
// There are characters before and after the caret, with forward direction affinity.
1652+
// It's better to use the character after the caret.
1653+
CGRect characterAfterCaret = rects[1].rect;
1654+
// Return a zero-width rectangle along the upstream edge of the character after the caret
1655+
// position.
1656+
if ([rects[1] isKindOfClass:[FlutterTextSelectionRect class]] &&
1657+
((FlutterTextSelectionRect*)rects[1]).isRTL) {
1658+
return CGRectMake(characterAfterCaret.origin.x + characterAfterCaret.size.width,
1659+
characterAfterCaret.origin.y, 0, characterAfterCaret.size.height);
1660+
} else {
1661+
return CGRectMake(characterAfterCaret.origin.x, characterAfterCaret.origin.y, 0,
1662+
characterAfterCaret.size.height);
1663+
}
1664+
}
1665+
1666+
// Covers 2 remaining cases:
1667+
// 1. there are characters before and after the caret, with backward direction affinity.
1668+
// 2. there is only 1 character before the caret (caret is at the end of text).
1669+
// For both cases, return a zero-width rectangle along the downstream edge of the character
1670+
// before the caret position.
1671+
CGRect characterBeforeCaret = rects[0].rect;
1672+
if ([rects[0] isKindOfClass:[FlutterTextSelectionRect class]] &&
1673+
((FlutterTextSelectionRect*)rects[0]).isRTL) {
1674+
return CGRectMake(characterBeforeCaret.origin.x, characterBeforeCaret.origin.y, 0,
1675+
characterBeforeCaret.size.height);
1676+
} else {
1677+
return CGRectMake(characterBeforeCaret.origin.x + characterBeforeCaret.size.width,
1678+
characterBeforeCaret.origin.y, 0, characterBeforeCaret.size.height);
1679+
}
1680+
>>>>>>> de68fba093 (Fix crash with CJK keyboard with emoji at end of text field (#42540))
16191681
}
16201682

16211683
- (UITextPosition*)closestPositionToPoint:(CGPoint)point {

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,29 @@ - (void)testClosestPositionToPointWithinRange {
14291429
1U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index);
14301430
}
14311431

1432+
- (void)testClosestPositionToPointWithPartialSelectionRects {
1433+
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1434+
[inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1435+
1436+
[inputView setSelectionRects:@[ [FlutterTextSelectionRect
1437+
selectionRectWithRect:CGRectMake(0, 0, 100, 100)
1438+
position:0U] ]];
1439+
// Asking with a position at the end of selection rects should give you the trailing edge of
1440+
// the last rect.
1441+
XCTAssertTrue(CGRectEqualToRect(
1442+
[inputView caretRectForPosition:[FlutterTextPosition
1443+
positionWithIndex:1
1444+
affinity:UITextStorageDirectionForward]],
1445+
CGRectMake(100, 0, 0, 100)));
1446+
// Asking with a position beyond the end of selection rects should return CGRectZero without
1447+
// crashing.
1448+
XCTAssertTrue(CGRectEqualToRect(
1449+
[inputView caretRectForPosition:[FlutterTextPosition
1450+
positionWithIndex:2
1451+
affinity:UITextStorageDirectionForward]],
1452+
CGRectZero));
1453+
}
1454+
14321455
#pragma mark - Floating Cursor - Tests
14331456

14341457
- (void)testFloatingCursorDoesNotThrow {

0 commit comments

Comments
 (0)