-
Notifications
You must be signed in to change notification settings - Fork 6k
Fix crash with CJK keyboard with emoji at end of text field #42540
Conversation
It had a bug in the binary search causing a crash. But aside from that it isn't actually needed.
@@ -1699,7 +1680,8 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position { | |||
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]] && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The old implementation doesn't seem to have this check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also you might have to special case it when characterAfterCaret
is a newline character, iirc SkParagraph reports it as an empty textbox at the end of the current line (instead of at the beginning of the next line).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we use selectionRectsForRange
instead of directly looking at the array. I put a check, because I guess if someone subclasses and reimplemented selectionRectsForRange
, this would crash if it didn't return specifically FlutterTextSelectionRect
. Not sure if this is really a concern...
Not sure what you mean about the special case. But for end-of-line, it does work correctly because I have added affinity
into the FlutterTextPosition
. So it will go to the characterBeforeCaret
section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the document is "{newline}A" (so 2 characters), placing the cursor at (1, upstream)
should point to the start of the second line. I think it would fall into the "remaining cases" category (around line 1718) and returns
return CGRectMake(characterBeforeCaret.origin.x + characterBeforeCaret.size.width,
characterBeforeCaret.origin.y, 0, characterBeforeCaret.size.height);
which is going to be the right edge of the zero-width TextBox skparagraph returns for the newline character at the first line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I understand upstream/downstream, it only comes into play where you have word wrap breaking lines. When you have a hard line-break, we have distinct indices at the end of line 1 and the beginning of line 2. And so most of the time the affinity is downstream, exccept at the end of a word-wrapped line.
I tried out the "\n
A" scenario and it seems to work as expected in all 3 possible cursor locations. In the specific one you highlighted (cursor at index 1), it used the middle branch (rects.count == 2 && affinity == UITextStorageDirectionForward
). I think this isn't a concern as you will never actually get that affinity (1, upstream)
using either keyboard or touch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you will never actually get that affinity (1, upstream) using either keyboard or touch?
Ah that's probably true.
But I think newlines are still special (sorry my example was bad). I tried getting the text boxes for "A\n":
final TextPainter painter = TextPainter(
textDirection: TextDirection.ltr,
text: const TextSpan(text: 'A\n'),
)..layout();
for (int i = 0; i < 2; i++) {
print(painter.getBoxesForSelection(TextSelection(baseOffset: i, extentOffset: i + 1)));
}
the output:
[TextBox.fromLTRBD(0.0, 0.0, 14.0, 14.0, TextDirection.ltr)]
[TextBox.fromLTRBD(14.0, 0.0, 14.0, 14.0, TextDirection.ltr)]
when index == 1
and the affinity is UITextStorageDirectionForward
this should place the cursor at the start of the second line. But if you look at the output both TextBox
es are on the first line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I get it now. But I don't know if we can fix that here, if we don't even have any text box on the second line, we can't even make a guess of the correct position? It needs to be fixed upstream in skparagraph. And there isn't any regression in this PR. I hope we can ignore this, and merge it to avoid the hard crash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM. Probably not a SkParagraph issue I think, we should probably add a bit more information to rects the framework sends.
@@ -1727,7 +1710,8 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position { | |||
// For both cases, return a zero-width rectangle along the downstream edge of the character |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we only send the rects for visible characters in a multiline text field, so technically this also includes the case when the selection reaches the end of the visible text? Would this prevent the user from scrolling the text field using force touch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This API is used by UIKit to figure out where to initially position its internal tracking of the selection gesture. So it would only be called on visible characters. And the logic still holds even if there are characters after current position that don't have a rect. As long as we have the 1 visible character before the cursor.
And yeah, to implement full scrolling we would need to send rects for all characters. A matter for another PR in framework.
@@ -1727,7 +1710,8 @@ - (CGRect)caretRectForPosition:(UITextPosition*)position { | |||
// 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]] && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can assert the class type here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what you mean. I am checking the class type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like:
FlutterTextSelectionRect *rect = (FlutterTextSelectionRect *)rects[0];
if (rect...)
Because the rect must be the correct type right? There is no way isKindOfClass
returns false, am I right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically it's possible, this method signature is not FlutterTextSelectionRect*
, but UITextSelectionRect*
.
…128721) flutter/engine@33e0693...de68fba 2023-06-12 [email protected] Fix crash with CJK keyboard with emoji at end of text field (flutter/engine#42540) 2023-06-12 [email protected] Roll Skia from 658b1d366758 to 6bdb0ef30cb6 (2 revisions) (flutter/engine#42778) 2023-06-12 [email protected] [Impeller] Correct attachment description for offscreen MSAA resolve. (flutter/engine#42753) 2023-06-12 [email protected] Remove dependency on memfs (flutter/engine#42773) 2023-06-12 [email protected] Roll Skia from 0f974a0f8c10 to 658b1d366758 (1 revision) (flutter/engine#42776) 2023-06-12 [email protected] Roll ANGLE from 3abbc4f99970 to 43ef50f389e9 (1 revision) (flutter/engine#42775) 2023-06-12 [email protected] Remove unnecessary #include of SkPromiseImageTexture (flutter/engine#42770) 2023-06-12 [email protected] Roll Skia from 951123096e55 to 0f974a0f8c10 (5 revisions) (flutter/engine#42771) 2023-06-12 [email protected] [Impeller] opt all vertex shader position/uvs into highp (flutter/engine#42746) 2023-06-12 [email protected] [Impeller] added debug info to frame debuggers like AGI (flutter/engine#42717) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-engine-flutter-autoroll Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
…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 flutter#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
The
isRTLAtPosition
method had a bug, it usedNSInteger max = [_selectionRects count]
instead ofNSInteger 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 usecaretRectForPosition
to calculate something about the composing rect.Fixes flutter/flutter#128031
Pre-launch Checklist
///
).