Skip to content

Commit a2291f3

Browse files
committed
Reset pending removal flags in text input plugin to prevent flicker during client transitions
1 parent 14165a3 commit a2291f3

2 files changed

Lines changed: 44 additions & 18 deletions

File tree

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2824,11 +2824,6 @@ - (void)startLiveTextInput {
28242824
- (void)showTextInput {
28252825
_activeView.viewResponder = _viewResponder;
28262826
[self addToInputParentViewIfNeeded:_activeView];
2827-
2828-
// Reset pending removal flag to prevent stale flags from a previous
2829-
// clearTextInputClient call from causing unintended view removal.
2830-
_pendingInputViewRemoval = NO;
2831-
28322827
[_activeView becomeFirstResponder];
28332828
}
28342829

@@ -2891,6 +2886,11 @@ - (void)setPlatformViewTextInputClient {
28912886

28922887
- (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configuration {
28932888
[self resetAllClientIds];
2889+
2890+
// Reset pending removal flags set by the previous clearTextInputClient call.
2891+
_pendingAutofillRemoval = NO;
2892+
_pendingInputViewRemoval = NO;
2893+
28942894
// Hide all input views from autofill, only make those in the new configuration visible
28952895
// to autofill.
28962896
[self changeInputViewsAutofillVisibility:NO];

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)