From d2b8e103798cd32700a478356964ec71418878db Mon Sep 17 00:00:00 2001 From: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Mon, 14 Jun 2021 15:34:02 -0700 Subject: [PATCH] [iOS TextInputPlugin] fix autofill assert (flutter/engine#26711) --- .../Source/FlutterTextInputPlugin.mm | 11 ++++-- .../Source/FlutterTextInputPluginTest.m | 35 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index d009cec8069..038a40d378c 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1371,8 +1371,11 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { _autofillContext = [[NSMutableDictionary alloc] init]; _inputHider = [[FlutterTextInputViewAccessibilityHider alloc] init]; // Initialize activeView with a dummy view to keep tests - // passing. + // passing. This dummy view needs to be replace once the + // framework initializes an input connection, and thus + // should never have access to the textInputDelegate. _activeView = [[FlutterTextInputView alloc] init]; + [_activeView decommission]; } return self; @@ -1383,6 +1386,9 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { _activeView.textInputDelegate = nil; [_activeView release]; [_inputHider release]; + for (FlutterTextInputView* autofillView in _autofillContext.allValues) { + autofillView.textInputDelegate = nil; + } [_autofillContext release]; [super dealloc]; } @@ -1477,13 +1483,12 @@ static FlutterAutofillType autofillTypeOf(NSDictionary* configuration) { [self removeEnableFlutterTextInputViewAccessibilityTimer]; _activeView.accessibilityEnabled = NO; [_activeView resignFirstResponder]; - [_activeView decommission]; [_activeView removeFromSuperview]; [_inputHider removeFromSuperview]; } - (void)triggerAutofillSave:(BOOL)saveEntries { - [self hideTextInput]; + [_activeView resignFirstResponder]; if (saveEntries) { // Make all the input fields in the autofill context visible, diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index 9cb0f836c42..d52359ecb38 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -80,6 +80,9 @@ FLUTTER_ASSERT_ARC } - (void)tearDown { + for (FlutterTextInputView* autofillView in textInputPlugin.autofillContext.allValues) { + autofillView.textInputDelegate = nil; + } [textInputPlugin.autofillContext removeAllObjects]; [textInputPlugin cleanUpViewHierarchy:YES clearText:YES delayRemoval:NO]; [[[[textInputPlugin textInputView] superview] subviews] @@ -896,6 +899,38 @@ FLUTTER_ASSERT_ARC [self commitAutofillContextAndVerify]; } +- (void)testDecommissionedViewAreNotReusedByAutofill { + // Regression test for https://github.com/flutter/flutter/issues/84407. + NSMutableDictionary* configuration = self.mutableTemplateCopy; + [configuration setValue:@{ + @"uniqueIdentifier" : @"field1", + @"hints" : @[ UITextContentTypePassword ], + @"editingValue" : @{@"text" : @""} + } + forKey:@"autofill"]; + [configuration setValue:@[ [configuration copy] ] forKey:@"fields"]; + + [self setClientId:123 configuration:configuration]; + + [self setTextInputHide]; + UIView* previousActiveView = textInputPlugin.activeView; + + [self setClientId:124 configuration:configuration]; + + // Make sure the autofillable view is reused. + XCTAssertEqual(previousActiveView, textInputPlugin.activeView); + XCTAssertNotNil(previousActiveView); + // Does not crash. +} + +- (void)testInitialActiveViewCantAccessTextInputDelegate { + textInputPlugin.activeView.textInputDelegate = engine; + // Before the framework sends the first text input configuration, + // the dummy "activeView" we use should never have access to + // its textInputDelegate. + XCTAssertNil(textInputPlugin.activeView.textInputDelegate); +} + #pragma mark - Accessibility - Tests - (void)testUITextInputAccessibilityNotHiddenWhenShowed {