@@ -69,7 +69,14 @@ @interface FlutterTextInputPlugin ()
6969@property (nonatomic , readonly ) UIView* keyboardView;
7070@property (nonatomic , assign ) UIView* cachedFirstResponder;
7171@property (nonatomic , readonly ) CGRect keyboardRect;
72+ // Whether the client disconnected while an autofill context was active.
73+ // The removeFromSuperview call is delayed until triggerAutofillSave
74+ // to avoid prematurely ending the autofill session.
7275@property (nonatomic , readonly ) BOOL pendingAutofillRemoval;
76+ // Whether the client disconnected without sending a hideText message.
77+ // This can indicate that the focus is being switched to a different
78+ // text field and to prevent flickering the removeFromSuperview
79+ // call should be delayed until hideTextInput.
7380@property (nonatomic , readonly ) BOOL pendingInputViewRemoval;
7481@property (nonatomic , readonly )
7582 NSMutableDictionary <NSString*, FlutterTextInputView*>* autofillContext;
@@ -2737,6 +2744,33 @@ - (void)testAutofillContextPersistsAfterClearClient {
27372744 XCTAssertFalse (textInputPlugin.pendingAutofillRemoval );
27382745}
27392746
2747+ - (void )testSetClientResetsPendingAutofillRemoval {
2748+ // When autofill context exists and clearClient sets pendingAutofillRemoval,
2749+ // a subsequent setClient should reset the flag because a new client is
2750+ // connecting and the deferred removal is no longer needed.
2751+ NSMutableDictionary * field = self.mutablePasswordTemplateCopy ;
2752+ [field setValue: @{
2753+ @" uniqueIdentifier" : @" field1" ,
2754+ @" hints" : @[ @" password" ],
2755+ @" editingValue" : @{@" text" : @" " }
2756+ }
2757+ forKey: @" autofill" ];
2758+ [field setValue: @[ field ] forKey: @" fields" ];
2759+
2760+ // Set up autofill context.
2761+ [self setClientId: 123 configuration: field];
2762+ XCTAssertGreaterThan (textInputPlugin.autofillContext .count , 0ul );
2763+
2764+ // clearClient with autofill context sets pendingAutofillRemoval.
2765+ [self setClientClear ];
2766+ XCTAssertTrue (textInputPlugin.pendingAutofillRemoval );
2767+
2768+ // A new setClient resets the pending autofill removal flag.
2769+ [self setClientId: 456 configuration: self .mutableTemplateCopy];
2770+ XCTAssertFalse (textInputPlugin.pendingAutofillRemoval );
2771+ XCTAssertFalse (textInputPlugin.pendingInputViewRemoval );
2772+ }
2773+
27402774- (void )testPendingInputViewRemovalAfterClearClient {
27412775 // When autofillContext is empty and the view is first responder,
27422776 // clearClient should set pendingInputViewRemoval,
@@ -2785,10 +2819,10 @@ - (void)testHideBeforeClearClientRemovesViewImmediately {
27852819 XCTAssertNil (textInputPlugin.activeView .superview );
27862820}
27872821
2788- - (void )testShowTextInputResetsStalePendingInputViewRemoval {
2789- // showTextInput should reset a stale pendingInputViewRemoval flag.
2790- // This prevents unintended view removal when hide is called for a new field
2791- // without a preceding clearClient .
2822+ - (void )testSetClientResetsPendingInputViewRemoval {
2823+ // When clearClient sets pendingInputViewRemoval (no autofill, first responder),
2824+ // a subsequent setClient should reset the flag because a new client is
2825+ // connecting and the deferred removal is no longer needed .
27922826 NSDictionary * config = self.mutableTemplateCopy ;
27932827
27942828 // Field 1: setClient → show → clearClient (sets pendingInputViewRemoval).
@@ -2803,17 +2837,9 @@ - (void)testShowTextInputResetsStalePendingInputViewRemoval {
28032837 [self setClientClear ];
28042838 XCTAssertTrue (textInputPlugin.pendingInputViewRemoval );
28052839
2806- // Field 2: setClient → show (should reset stale flag) .
2840+ // Field 2: setClient resets the stale flag.
28072841 [self setClientId: 456 configuration: config];
2808- [self setTextInputShow ];
28092842 XCTAssertFalse (textInputPlugin.pendingInputViewRemoval );
2810-
2811- // hideTextInput without clearClient should only resignFirstResponder,
2812- // not removeFromSuperview.
2813- [self setTextInputHide ];
2814- XCTAssertFalse (textInputPlugin.pendingInputViewRemoval );
2815- // The active view should still be in the view hierarchy.
2816- XCTAssertNotNil (textInputPlugin.activeView .superview );
28172843}
28182844
28192845- (void )testPasswordAutofillHack {
0 commit comments