From 6c1d6cc31b178e92c1f8dd301e845a3900b1e5b6 Mon Sep 17 00:00:00 2001 From: Yang Chao Date: Fri, 8 Jul 2022 16:37:17 +0800 Subject: [PATCH 1/6] fix a problem that deleting an emoji crashes the app --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 0e2c656a32fe5..a42212ce2ad24 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1895,7 +1895,8 @@ - (void)deleteBackward { UITextRange* oldSelectedRange = _selectedTextRange; NSRange oldRange = ((FlutterTextRange*)oldSelectedRange).range; if (oldRange.location > 0) { - NSRange newRange = NSMakeRange(oldRange.location - 1, 1); + NSRange charRange = fml::RangeForCharacterAtIndex(self.text, oldRange.location - 1); + NSRange newRange = NSMakeRange(charRange.location, oldRange.location - charRange.location); _selectedTextRange = [[FlutterTextRange rangeWithNSRange:newRange] copy]; [oldSelectedRange release]; } From 6f604cb300076afde3a02787933bb289a2856a28 Mon Sep 17 00:00:00 2001 From: Yang Chao Date: Fri, 8 Jul 2022 16:37:17 +0800 Subject: [PATCH 2/6] Add tests --- .../Source/FlutterTextInputPluginTest.mm | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index bd656cb6cc3aa..e7cf3a36fb72d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -406,6 +406,23 @@ - (void)testStandardEditActions { XCTAssertEqualObjects(substring, @"bbbbaaaabbbbaaaa"); } +- (void)testDeletingBackwardWorksWithEmoji { + NSDictionary* config = self.mutableTemplateCopy; + [self setClientId:123 configuration:config]; + NSArray* inputFields = self.installedInputViews; + FlutterTextInputView* inputView = inputFields[0]; + + [inputView insertText:@"πŸ˜€πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³"]; + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"πŸ˜€πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"πŸ˜€πŸ₯°"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"πŸ˜€"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @""); +} + - (void)testPastingNonTextDisallowed { NSDictionary* config = self.mutableTemplateCopy; [self setClientId:123 configuration:config]; From e8c4a370778d90e8fab80615b1e53ff86ac043ff Mon Sep 17 00:00:00 2001 From: Yang Chao <6036532+Yeatse@users.noreply.github.com> Date: Sat, 9 Jul 2022 18:29:16 +0800 Subject: [PATCH 3/6] Update shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm Co-authored-by: Jenn Magder --- .../Source/FlutterTextInputPluginTest.mm | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index e7cf3a36fb72d..cf159987b14a8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -406,18 +406,34 @@ - (void)testStandardEditActions { XCTAssertEqualObjects(substring, @"bbbbaaaabbbbaaaa"); } -- (void)testDeletingBackwardWorksWithEmoji { +- (void)testDeletingBackward { NSDictionary* config = self.mutableTemplateCopy; [self setClientId:123 configuration:config]; NSArray* inputFields = self.installedInputViews; FlutterTextInputView* inputView = inputFields[0]; - [inputView insertText:@"πŸ˜€πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³"]; + [inputView insertText:@"πŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³ΰΈ”ΰΈ΅ "]; [inputView deleteBackward]; - XCTAssertEqualObjects(inputView.text, @"πŸ˜€πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦"); [inputView deleteBackward]; - XCTAssertEqualObjects(inputView.text, @"πŸ˜€πŸ₯°"); + + // Thai vowel is removed. + XCTAssertEqualObjects(inputView.text, @"πŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³ΰΈ”"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"πŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"πŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"πŸ˜€ text πŸ₯°"); + [inputView deleteBackward]; + + XCTAssertEqualObjects(inputView.text, @"πŸ˜€ text "); [inputView deleteBackward]; + [inputView deleteBackward]; + [inputView deleteBackward]; + [inputView deleteBackward]; + [inputView deleteBackward]; + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"πŸ˜€"); [inputView deleteBackward]; XCTAssertEqualObjects(inputView.text, @""); From a28afd0d344bb2fe24c23ffb7ae86cf6dbd5c14e Mon Sep 17 00:00:00 2001 From: Yang Chao Date: Sat, 9 Jul 2022 18:31:42 +0800 Subject: [PATCH 4/6] remove composed characters only if they are emojis --- .../framework/Source/FlutterTextInputPlugin.mm | 17 ++++++++++++++++- .../xcschemes/IosUnitTests.xcscheme | 6 ++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index a42212ce2ad24..96a296d5fd923 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -7,6 +7,8 @@ #import #import +#include "unicode/uchar.h" + #include "flutter/fml/logging.h" #include "flutter/fml/platform/darwin/string_range_sanitization.h" @@ -1895,8 +1897,21 @@ - (void)deleteBackward { UITextRange* oldSelectedRange = _selectedTextRange; NSRange oldRange = ((FlutterTextRange*)oldSelectedRange).range; if (oldRange.location > 0) { + NSRange newRange = NSMakeRange(oldRange.location - 1, 1); + NSRange charRange = fml::RangeForCharacterAtIndex(self.text, oldRange.location - 1); - NSRange newRange = NSMakeRange(charRange.location, oldRange.location - charRange.location); + UChar32 codePoint; + BOOL gotCodePoint = [self.text getBytes:&codePoint + maxLength:sizeof(codePoint) + usedLength:NULL + encoding:NSUTF32StringEncoding + options:kNilOptions + range:charRange + remainingRange:NULL]; + if (gotCodePoint && u_hasBinaryProperty(codePoint, UCHAR_EMOJI)) { + // We should make sure the entire emoji is deleted. + newRange = NSMakeRange(charRange.location, oldRange.location - charRange.location); + } _selectedTextRange = [[FlutterTextRange rangeWithNSRange:newRange] copy]; [oldSelectedRange release]; } diff --git a/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/xcshareddata/xcschemes/IosUnitTests.xcscheme b/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/xcshareddata/xcschemes/IosUnitTests.xcscheme index 48aa2903c3a41..b1341fc8d5c2a 100644 --- a/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/xcshareddata/xcschemes/IosUnitTests.xcscheme +++ b/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/xcshareddata/xcschemes/IosUnitTests.xcscheme @@ -78,6 +78,12 @@ ReferencedContainer = "container:IosUnitTests.xcodeproj"> + + + + Date: Sat, 9 Jul 2022 18:55:15 +0800 Subject: [PATCH 5/6] update comments --- .../darwin/ios/framework/Source/FlutterTextInputPlugin.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 96a296d5fd923..2d33fb9d6d22d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1899,6 +1899,8 @@ - (void)deleteBackward { if (oldRange.location > 0) { NSRange newRange = NSMakeRange(oldRange.location - 1, 1); + // We should check if the last character is a part of emoji. + // If so, we must delete the entire emoji to prevent the text from being malformed. NSRange charRange = fml::RangeForCharacterAtIndex(self.text, oldRange.location - 1); UChar32 codePoint; BOOL gotCodePoint = [self.text getBytes:&codePoint @@ -1909,9 +1911,9 @@ - (void)deleteBackward { range:charRange remainingRange:NULL]; if (gotCodePoint && u_hasBinaryProperty(codePoint, UCHAR_EMOJI)) { - // We should make sure the entire emoji is deleted. newRange = NSMakeRange(charRange.location, oldRange.location - charRange.location); } + _selectedTextRange = [[FlutterTextRange rangeWithNSRange:newRange] copy]; [oldSelectedRange release]; } From 1382921af2171d32a46c0756a0ad16b0e70b8668 Mon Sep 17 00:00:00 2001 From: Yang Chao Date: Sun, 10 Jul 2022 11:40:33 +0800 Subject: [PATCH 6/6] update test case to cover the leading thai vowel --- .../Source/FlutterTextInputPluginTest.mm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index cf159987b14a8..bec7028df6354 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -412,21 +412,21 @@ - (void)testDeletingBackward { NSArray* inputFields = self.installedInputViews; FlutterTextInputView* inputView = inputFields[0]; - [inputView insertText:@"πŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³ΰΈ”ΰΈ΅ "]; + [inputView insertText:@"αžΉπŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³ΰΈ”ΰΈ΅ "]; [inputView deleteBackward]; [inputView deleteBackward]; // Thai vowel is removed. - XCTAssertEqualObjects(inputView.text, @"πŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³ΰΈ”"); + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³ΰΈ”"); [inputView deleteBackward]; - XCTAssertEqualObjects(inputView.text, @"πŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³"); + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³"); [inputView deleteBackward]; - XCTAssertEqualObjects(inputView.text, @"πŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦"); + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦"); [inputView deleteBackward]; - XCTAssertEqualObjects(inputView.text, @"πŸ˜€ text πŸ₯°"); + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€ text πŸ₯°"); [inputView deleteBackward]; - XCTAssertEqualObjects(inputView.text, @"πŸ˜€ text "); + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€ text "); [inputView deleteBackward]; [inputView deleteBackward]; [inputView deleteBackward]; @@ -434,7 +434,9 @@ - (void)testDeletingBackward { [inputView deleteBackward]; [inputView deleteBackward]; - XCTAssertEqualObjects(inputView.text, @"πŸ˜€"); + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"ឹ"); [inputView deleteBackward]; XCTAssertEqualObjects(inputView.text, @""); }