From 69cbb2bbb834065d8c37419328b802d49efb8566 Mon Sep 17 00:00:00 2001 From: Justin McCandless Date: Fri, 2 Oct 2020 13:51:50 -0700 Subject: [PATCH] iOS Text Editing Infinite Loop (#20160) Fixes an infinite loop by eliminating an unnecessary engine/framework message. --- .../Source/FlutterTextInputPlugin.mm | 12 +-- .../Source/FlutterTextInputPluginTest.m | 82 +++++++------------ 2 files changed, 30 insertions(+), 64 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 71f5354c4b6..507b9cae843 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -537,10 +537,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { _textInputClient = client; } -// Return true if the new input state needs to be synced back to the framework. -// TODO(LongCatIsLooong): setTextInputState should never call updateEditingState. Sending the -// editing value back may overwrite the framework's updated editing value. -- (BOOL)setTextInputState:(NSDictionary*)state { +- (void)setTextInputState:(NSDictionary*)state { NSString* newText = state[@"text"]; BOOL textChanged = ![self.text isEqualToString:newText]; if (textChanged) { @@ -575,9 +572,6 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { if (textChanged) { [self.inputDelegate textDidChange:self]; } - - // For consistency with Android behavior, send an update to the framework if the text changed. - return textChanged; } // Extracts the selection information from the editing state dictionary. @@ -1423,9 +1417,7 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { } - (void)setTextInputEditingState:(NSDictionary*)state { - if ([_activeView setTextInputState:state]) { - [_activeView updateEditingState]; - } + [_activeView setTextInputState:state]; } - (void)clearTextInputClient { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index 432ce718bf3..88683abb087 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -16,7 +16,7 @@ FLUTTER_ASSERT_ARC @property(nonatomic, copy) NSString* autofillId; - (void)setEditableTransform:(NSArray*)matrix; -- (BOOL)setTextInputState:(NSDictionary*)state; +- (void)setTextInputState:(NSDictionary*)state; - (void)setMarkedRect:(CGRect)markedRect; - (void)updateEditingState; - (BOOL)isVisibleToAutofill; @@ -211,71 +211,45 @@ FLUTTER_ASSERT_ARC XCTAssertEqual(updateCount, 6); } -- (void)testTextChangesTriggerUpdateEditingClient { +- (void)testTextChangesDoNotTriggerUpdateEditingClient { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; inputView.textInputDelegate = engine; + __block int updateCount = 0; + OCMStub([engine updateEditingClient:0 withState:[OCMArg isNotNil]]) + .andDo(^(NSInvocation* invocation) { + updateCount++; + }); + [inputView.text setString:@"BEFORE"]; + XCTAssertEqual(updateCount, 0); + inputView.markedTextRange = nil; inputView.selectedTextRange = nil; + XCTAssertEqual(updateCount, 1); - // Text changes trigger update. - XCTAssertTrue([inputView setTextInputState:@{@"text" : @"AFTER"}]); + // Text changes don't trigger an update. + XCTAssertEqual(updateCount, 1); + [inputView setTextInputState:@{@"text" : @"AFTER"}]; + XCTAssertEqual(updateCount, 1); + [inputView setTextInputState:@{@"text" : @"AFTER"}]; + XCTAssertEqual(updateCount, 1); - // Don't send anything if there's nothing new. - XCTAssertFalse([inputView setTextInputState:@{@"text" : @"AFTER"}]); -} - -- (void)testSelectionChangeDoesNotTriggerUpdateEditingClient { - FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; - inputView.textInputDelegate = engine; - - [inputView.text setString:@"SELECTION"]; - inputView.markedTextRange = nil; - inputView.selectedTextRange = nil; - - BOOL shouldUpdate = [inputView + // Selection changes don't trigger an update. + [inputView setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @0, @"selectionExtent" : @3}]; - XCTAssertFalse(shouldUpdate); - - shouldUpdate = [inputView + XCTAssertEqual(updateCount, 1); + [inputView setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @3}]; - XCTAssertFalse(shouldUpdate); + XCTAssertEqual(updateCount, 1); - shouldUpdate = [inputView - setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @2}]; - XCTAssertFalse(shouldUpdate); - - // Don't send anything if there's nothing new. - shouldUpdate = [inputView - setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @2}]; - XCTAssertFalse(shouldUpdate); -} - -- (void)testComposingChangeDoesNotTriggerUpdateEditingClient { - FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init]; - inputView.textInputDelegate = engine; - - // Reset to test marked text. - [inputView.text setString:@"COMPOSING"]; - inputView.markedTextRange = nil; - inputView.selectedTextRange = nil; - - BOOL shouldUpdate = [inputView - setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @0, @"composingExtent" : @3}]; - XCTAssertFalse(shouldUpdate); - - shouldUpdate = [inputView + // Composing region changes don't trigger an update. + [inputView + setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}]; + XCTAssertEqual(updateCount, 1); + [inputView setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @3}]; - XCTAssertFalse(shouldUpdate); - - shouldUpdate = [inputView - setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}]; - XCTAssertFalse(shouldUpdate); - - shouldUpdate = [inputView - setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}]; - XCTAssertFalse(shouldUpdate); + XCTAssertEqual(updateCount, 1); } - (void)testUITextInputAvoidUnnecessaryUndateEditingClientCalls {