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

[CP] Fix crash with CJK keyboard with emoji at end of text field (#42539) #42924

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1600,22 +1600,65 @@ - (CGRect)firstRectForRange:(UITextRange*)range {
}

- (CGRect)caretRectForPosition:(UITextPosition*)position {
// TODO(cbracken) Implement.

// As of iOS 14.4, this call is used by iOS's
// _UIKeyboardTextSelectionController to determine the position
// of the floating cursor when the user force touches the space
// bar to initiate floating cursor.
//
// It is recommended to return a value that's roughly the
// center of kSpacePanBounds to make sure the floating cursor
// has ample space in all directions and does not hit kSpacePanBounds.
// See the comments in beginFloatingCursorAtPoint.
return CGRectZero;
}
NSInteger index = ((FlutterTextPosition*)position).index;
UITextStorageDirection affinity = ((FlutterTextPosition*)position).affinity;
// Get the selectionRect of the characters before and after the requested caret position.
NSArray<UITextSelectionRect*>* rects = [self
selectionRectsForRange:[FlutterTextRange
rangeWithNSRange:fml::RangeForCharactersInRange(
self.text,
NSMakeRange(
MAX(0, index - 1),
(index >= (NSInteger)self.text.length)
? 1
: 2))]];
if (rects.count == 0) {
return CGRectZero;
}
if (index == 0) {
// There is no character before the caret, so this will be the bounds of the character after the
// caret position.
CGRect characterAfterCaret = rects[0].rect;
// Return a zero-width rectangle along the upstream edge of the character after the caret
// position.
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 {
return CGRectMake(characterAfterCaret.origin.x, characterAfterCaret.origin.y, 0,
characterAfterCaret.size.height);
}
} else if (rects.count == 2 && affinity == UITextStorageDirectionForward) {
// There are characters before and after the caret, with forward direction affinity.
// It's better to use the character after the caret.
CGRect characterAfterCaret = rects[1].rect;
// Return a zero-width rectangle along the upstream edge of the character after the caret
// position.
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 {
return CGRectMake(characterAfterCaret.origin.x, characterAfterCaret.origin.y, 0,
characterAfterCaret.size.height);
}
}

- (CGRect)bounds {
return _isFloatingCursorActive ? kSpacePanBounds : super.bounds;
// Covers 2 remaining cases:
// 1. there are characters before and after the caret, with backward direction affinity.
// 2. there is only 1 character before the caret (caret is at the end of text).
// 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 ([rects[0] isKindOfClass:[FlutterTextSelectionRect class]] &&
((FlutterTextSelectionRect*)rects[0]).isRTL) {
return CGRectMake(characterBeforeCaret.origin.x, characterBeforeCaret.origin.y, 0,
characterBeforeCaret.size.height);
} else {
return CGRectMake(characterBeforeCaret.origin.x + characterBeforeCaret.size.width,
characterBeforeCaret.origin.y, 0, characterBeforeCaret.size.height);
}
}

- (UITextPosition*)closestPositionToPoint:(CGPoint)point {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,29 @@ - (void)testClosestPositionToPointWithinRange {
1U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index);
}

- (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 {
Expand Down