Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 9af56b3

Browse files
authored
[macOS] Set textfield autofill type (#39632)
1 parent b59787f commit 9af56b3

File tree

2 files changed

+122
-5
lines changed

2 files changed

+122
-5
lines changed

shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,36 @@ typedef NS_ENUM(NSUInteger, FlutterTextAffinity) {
9191
}
9292

9393
// Returns the autofill hint content type, if specified; otherwise nil.
94-
static NSString* GetAutofillContentType(NSDictionary* autofill) {
94+
static NSString* GetAutofillHint(NSDictionary* autofill) {
9595
NSArray<NSString*>* hints = autofill[kAutofillHints];
9696
return hints.count > 0 ? hints[0] : nil;
9797
}
9898

99+
// Returns the text content type for the specified TextInputConfiguration.
100+
// NSTextContentType is only available for macOS 11.0 and later.
101+
static NSTextContentType GetTextContentType(NSDictionary* configuration)
102+
API_AVAILABLE(macos(11.0)) {
103+
// Check autofill hints.
104+
NSDictionary* autofill = configuration[kAutofillProperties];
105+
if (autofill) {
106+
NSString* hint = GetAutofillHint(autofill);
107+
if ([hint isEqualToString:@"username"]) {
108+
return NSTextContentTypeUsername;
109+
}
110+
if ([hint isEqualToString:@"password"]) {
111+
return NSTextContentTypePassword;
112+
}
113+
if ([hint isEqualToString:@"oneTimeCode"]) {
114+
return NSTextContentTypeOneTimeCode;
115+
}
116+
}
117+
// If no autofill hints, guess based on other attributes.
118+
if ([configuration[kSecureTextEntry] boolValue]) {
119+
return NSTextContentTypePassword;
120+
}
121+
return nil;
122+
}
123+
99124
// Returns YES if configuration describes a field for which autocomplete should be enabled for
100125
// the specified TextInputConfiguration. Autocomplete is enabled by default, but will be disabled
101126
// if the field is password-related, or if the configuration contains no autofill settings.
@@ -113,8 +138,8 @@ static BOOL EnableAutocompleteForTextInputConfiguration(NSDictionary* configurat
113138

114139
// Disable if autofill properties indicate a username/password.
115140
// See: https://github.com/flutter/flutter/issues/119824
116-
NSString* contentType = GetAutofillContentType(autofill);
117-
if ([contentType isEqualToString:@"password"] || [contentType isEqualToString:@"username"]) {
141+
NSString* hint = GetAutofillHint(autofill);
142+
if ([hint isEqualToString:@"password"] || [hint isEqualToString:@"username"]) {
118143
return NO;
119144
}
120145
return YES;
@@ -386,7 +411,9 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
386411
_inputType = inputTypeInfo[kTextInputTypeName];
387412
self.textAffinity = kFlutterTextAffinityUpstream;
388413
self.automaticTextCompletionEnabled = EnableAutocomplete(config);
389-
// TODO(cbracken): support text content types https://github.com/flutter/flutter/issues/120252
414+
if (@available(macOS 11.0, *)) {
415+
self.contentType = GetTextContentType(config);
416+
}
390417

391418
_activeModel = std::make_unique<flutter::TextInputModel>();
392419
}

shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,12 @@ - (bool)testAutocompleteEnabledWhenAutofillSet {
449449

450450
// Verify autocomplete is enabled.
451451
EXPECT_TRUE([plugin isAutomaticTextCompletionEnabled]);
452+
453+
// Verify content type is nil for unsupported content types.
454+
if (@available(macOS 11.0, *)) {
455+
EXPECT_EQ([plugin contentType], nil);
456+
}
457+
452458
return true;
453459
}
454460

@@ -510,7 +516,6 @@ - (bool)testAutocompleteDisabledWhenObscureTextSet {
510516
@"obscureText" : @YES,
511517
@"autofill" : @{
512518
@"uniqueIdentifier" : @"field1",
513-
@"hints" : @[ @"name" ],
514519
@"editingValue" : @{@"text" : @""},
515520
}
516521
}
@@ -555,6 +560,11 @@ - (bool)testAutocompleteDisabledWhenPasswordAutofillSet {
555560

556561
// Verify autocomplete is disabled.
557562
EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
563+
564+
// Verify content type is password.
565+
if (@available(macOS 11.0, *)) {
566+
EXPECT_EQ([plugin contentType], NSTextContentTypePassword);
567+
}
558568
return true;
559569
}
560570

@@ -608,6 +618,86 @@ - (bool)testAutocompleteDisabledWhenAutofillGroupIncludesPassword {
608618
return true;
609619
}
610620

621+
- (bool)testContentTypeWhenAutofillTypeIsUsername {
622+
// Set up FlutterTextInputPlugin.
623+
id engineMock = flutter::testing::CreateMockFlutterEngine(@"");
624+
id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
625+
OCMStub( // NOLINT(google-objc-avoid-throwing-exception)
626+
[engineMock binaryMessenger])
627+
.andReturn(binaryMessengerMock);
628+
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock
629+
nibName:@""
630+
bundle:nil];
631+
FlutterTextInputPlugin* plugin =
632+
[[FlutterTextInputPlugin alloc] initWithViewController:viewController];
633+
634+
// Set input client 1.
635+
[plugin handleMethodCall:[FlutterMethodCall
636+
methodCallWithMethodName:@"TextInput.setClient"
637+
arguments:@[
638+
@(1), @{
639+
@"inputAction" : @"action",
640+
@"inputType" : @{@"name" : @"inputName"},
641+
@"autofill" : @{
642+
@"uniqueIdentifier" : @"field1",
643+
@"hints" : @[ @"username" ],
644+
@"editingValue" : @{@"text" : @""},
645+
}
646+
}
647+
]]
648+
result:^(id){
649+
}];
650+
651+
// Verify autocomplete is disabled.
652+
EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
653+
654+
// Verify content type is username.
655+
if (@available(macOS 11.0, *)) {
656+
EXPECT_EQ([plugin contentType], NSTextContentTypeUsername);
657+
}
658+
return true;
659+
}
660+
661+
- (bool)testContentTypeWhenAutofillTypeIsOneTimeCode {
662+
// Set up FlutterTextInputPlugin.
663+
id engineMock = flutter::testing::CreateMockFlutterEngine(@"");
664+
id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
665+
OCMStub( // NOLINT(google-objc-avoid-throwing-exception)
666+
[engineMock binaryMessenger])
667+
.andReturn(binaryMessengerMock);
668+
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock
669+
nibName:@""
670+
bundle:nil];
671+
FlutterTextInputPlugin* plugin =
672+
[[FlutterTextInputPlugin alloc] initWithViewController:viewController];
673+
674+
// Set input client 1.
675+
[plugin handleMethodCall:[FlutterMethodCall
676+
methodCallWithMethodName:@"TextInput.setClient"
677+
arguments:@[
678+
@(1), @{
679+
@"inputAction" : @"action",
680+
@"inputType" : @{@"name" : @"inputName"},
681+
@"autofill" : @{
682+
@"uniqueIdentifier" : @"field1",
683+
@"hints" : @[ @"oneTimeCode" ],
684+
@"editingValue" : @{@"text" : @""},
685+
}
686+
}
687+
]]
688+
result:^(id){
689+
}];
690+
691+
// Verify autocomplete is disabled.
692+
EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
693+
694+
// Verify content type is username.
695+
if (@available(macOS 11.0, *)) {
696+
EXPECT_EQ([plugin contentType], NSTextContentTypeOneTimeCode);
697+
}
698+
return true;
699+
}
700+
611701
- (bool)testFirstRectForCharacterRange {
612702
id engineMock = flutter::testing::CreateMockFlutterEngine(@"");
613703
id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));

0 commit comments

Comments
 (0)