Commit a546ed3d authored by vasilii's avatar vasilii Committed by Commit Bot

Implement "Show all passwords" in the keyboard accessory on iOS.

The new button appears when a password field is focused or when other password suggestions are displayed. The latter may happen when a username field is focused.
Click on the button opens the list of passwords. User can find the relevant one there and copy it.

Bug: 782287
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: I199851bd28c76ac4b626f8a1ff81a86e8479a51e
Reviewed-on: https://chromium-review.googlesource.com/816479
Commit-Queue: Vasilii Sukhanov <vasilii@chromium.org>
Reviewed-by: default avatarMark Cogan <marq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537679}
parent 33b9ef7d
......@@ -1635,6 +1635,22 @@ void MainControllerAuthenticationServiceDelegate::ClearBrowsingData(
completion:nil];
}
// TODO(crbug.com/779791) : Remove show settings commands from MainController.
- (void)showSavedPasswordsSettingsFromViewController:
(UIViewController*)baseViewController {
if (_settingsNavigationController) {
[_settingsNavigationController
showSavedPasswordsSettingsFromViewController:baseViewController];
return;
}
_settingsNavigationController =
[SettingsNavigationController newSavePasswordsController:_mainBrowserState
delegate:self];
[baseViewController presentViewController:_settingsNavigationController
animated:YES
completion:nil];
}
#pragma mark - chromeExecuteCommand
- (IBAction)chromeExecuteCommand:(id)sender {
......
......@@ -33,6 +33,9 @@ class PasswordManagerDriver;
- (BOOL)displaySignInNotification:(UIViewController*)viewController
fromTabId:(NSString*)tabId;
// Opens the list of saved passwords in the settings.
- (void)displaySavedPasswordList;
@end
// Per-tab password controller. Handles password autofill and saving.
......
......@@ -45,10 +45,12 @@
#include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h"
#import "ios/chrome/browser/ui/commands/application_commands.h"
#include "ios/chrome/browser/web/tab_id_tab_helper.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/web/public/origin_util.h"
#include "ios/web/public/url_scheme_util.h"
#import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
#import "ios/web/public/web_state/web_state.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
......@@ -162,7 +164,7 @@ namespace {
// Constructs an array of FormSuggestions, each corresponding to a username/
// password pair in |AccountSelectFillData|, such that |typedValue| is a prefix
// of the username of each suggestion.
// of the username of each suggestion. "Show all" item is appended.
NSArray* BuildSuggestions(const AccountSelectFillData& fillData,
NSString* formName,
NSString* fieldName,
......@@ -172,24 +174,34 @@ NSArray* BuildSuggestions(const AccountSelectFillData& fillData,
base::string16 typed_value = base::SysNSStringToUTF16(typedValue);
NSMutableArray* suggestions = [NSMutableArray array];
std::vector<password_manager::UsernameAndRealm> username_and_realms_ =
fillData.RetrieveSuggestions(form_name, field_name, typed_value);
if (username_and_realms_.empty())
return suggestions;
for (const auto& username_and_realm : username_and_realms_) {
NSString* username = base::SysUTF16ToNSString(username_and_realm.username);
NSString* origin = username_and_realm.realm.empty()
? nil
: base::SysUTF8ToNSString(username_and_realm.realm);
[suggestions addObject:[FormSuggestion suggestionWithValue:username
displayDescription:origin
icon:nil
identifier:0]];
if (fillData.IsSuggestionsAvailable(form_name, field_name)) {
std::vector<password_manager::UsernameAndRealm> username_and_realms_ =
fillData.RetrieveSuggestions(form_name, field_name, typed_value);
// Add credentials.
for (const auto& username_and_realm : username_and_realms_) {
NSString* username =
base::SysUTF16ToNSString(username_and_realm.username);
NSString* origin =
username_and_realm.realm.empty()
? nil
: base::SysUTF8ToNSString(username_and_realm.realm);
[suggestions addObject:[FormSuggestion suggestionWithValue:username
displayDescription:origin
icon:nil
identifier:0]];
}
}
return suggestions;
// Add "Show all".
NSString* showAll = l10n_util::GetNSString(IDS_IOS_SHOW_ALL_PASSWORDS);
[suggestions addObject:[FormSuggestion suggestionWithValue:showAll
displayDescription:nil
icon:nil
identifier:1]];
return [suggestions copy];
}
// Removes URL components not essential for matching the URL to
......@@ -674,19 +686,21 @@ bool GetPageURLAndCheckTrustLevel(web::WebState* web_state, GURL* page_url) {
webState:(web::WebState*)webState
completionHandler:
(SuggestionsAvailableCompletion)completion {
if (!sentRequestToStore_ && [type isEqual:@"focus"]) {
[self findPasswordFormsAndSendThemToPasswordStore];
if (!GetPageURLAndCheckTrustLevel(webState, nullptr)) {
completion(NO);
return;
}
if (fillData_.Empty() || !GetPageURLAndCheckTrustLevel(webState, nullptr)) {
completion(NO);
if (!sentRequestToStore_ && [type isEqual:@"focus"])
[self findPasswordFormsAndSendThemToPasswordStore];
if ([fieldType isEqual:@"password"]) {
// Always dispay "Show all" on the password field.
completion(YES);
return;
}
// Suggestions are available for the username field of the password form.
completion(fillData_.IsSuggestionsAvailable(
base::SysNSStringToUTF16(formName), base::SysNSStringToUTF16(fieldName)));
completion(!fillData_.Empty() && fillData_.IsSuggestionsAvailable(
base::SysNSStringToUTF16(formName),
base::SysNSStringToUTF16(fieldName)));
}
- (void)retrieveSuggestionsForForm:(NSString*)formName
......@@ -697,10 +711,7 @@ bool GetPageURLAndCheckTrustLevel(web::WebState* web_state, GURL* page_url) {
webState:(web::WebState*)webState
completionHandler:(SuggestionsReadyCompletion)completion {
DCHECK(GetPageURLAndCheckTrustLevel(webState, nullptr));
if (fillData_.Empty()) {
completion(@[], nil);
return;
}
completion(BuildSuggestions(fillData_, formName, fieldName, typedValue),
self);
}
......@@ -709,6 +720,12 @@ bool GetPageURLAndCheckTrustLevel(web::WebState* web_state, GURL* page_url) {
forField:(NSString*)fieldName
form:(NSString*)formName
completionHandler:(SuggestionHandledCompletion)completion {
if (suggestion.identifier == 1) {
// Navigate to the settings list.
[self.delegate displaySavedPasswordList];
completion();
return;
}
const base::string16 username = base::SysNSStringToUTF16(suggestion.value);
std::unique_ptr<FillData> fillData = fillData_.GetFillData(username);
......
......@@ -187,14 +187,12 @@ class PasswordControllerTest : public web::WebTestWithWebState {
// YES on success, NO otherwise.
BOOL BasicFormFill(NSString* html);
// Retrieve the current suggestions from suggestionController_ sorted in
// alphabetical order according to their value properties.
NSArray* GetSortedSuggestionValues() {
// Retrieve the current suggestions from suggestionController_.
NSArray* GetSuggestionValues() {
NSMutableArray* suggestion_values = [NSMutableArray array];
for (FormSuggestion* suggestion in [suggestionController_ suggestions])
[suggestion_values addObject:suggestion.value];
return [suggestion_values
sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
return [suggestion_values copy];
}
// Returns an identifier for the |form_number|th form in the page.
......@@ -1115,6 +1113,7 @@ TEST_F(PasswordControllerTest, SuggestionUpdateTests) {
EXPECT_NSEQ(@"[]=, onkeyup=false, onchange=false",
ExecuteJavaScript(kUsernamePasswordVerificationScript));
NSString* showAll = @"Show All\u2026";
// clang-format off
SuggestionTestData test_data[] = {
{
......@@ -1123,16 +1122,16 @@ TEST_F(PasswordControllerTest, SuggestionUpdateTests) {
"evt.initEvent('focus', true, true, window, 1);"
"username_.dispatchEvent(evt);"),
@""],
@[@"abc", @"user0"],
@[@"user0", @"abc", showAll],
@"[]=, onkeyup=false, onchange=false"
},
{
"Should not show suggestions when focusing password field",
"Should not show password suggestions when focusing password field",
@[(@"var evt = document.createEvent('Events');"
"evt.initEvent('focus', true, true, window, 1);"
"password_.dispatchEvent(evt);"),
@""],
@[],
@[showAll],
@"[]=, onkeyup=false, onchange=false"
},
{
......@@ -1142,7 +1141,7 @@ TEST_F(PasswordControllerTest, SuggestionUpdateTests) {
"evt.initEvent('focus', true, true, window, 1);"
"username_.dispatchEvent(evt);"),
@""],
@[@"abc"],
@[@"abc", showAll],
@"ab[]=, onkeyup=false, onchange=false"
},
{
......@@ -1156,7 +1155,7 @@ TEST_F(PasswordControllerTest, SuggestionUpdateTests) {
"evt.keyCode = 98;"
"username_.dispatchEvent(evt);"),
@""],
@[@"abc"],
@[@"abc", showAll],
@"ab[]=, onkeyup=true, onchange=false"
},
{
......@@ -1175,7 +1174,7 @@ TEST_F(PasswordControllerTest, SuggestionUpdateTests) {
"evt.keyCode = 8;"
"username_.dispatchEvent(evt);"),
@""],
@[@"abc", @"user0"],
@[@"user0", @"abc", showAll],
@"[]=, onkeyup=true, onchange=false"
},
};
......@@ -1198,7 +1197,7 @@ TEST_F(PasswordControllerTest, SuggestionUpdateTests) {
WaitForBackgroundTasks();
}
EXPECT_NSEQ(data.expected_suggestions, GetSortedSuggestionValues());
EXPECT_NSEQ(data.expected_suggestions, GetSuggestionValues());
EXPECT_NSEQ(data.expected_result,
ExecuteJavaScript(kUsernamePasswordVerificationScript));
// Clear all suggestions.
......@@ -1274,7 +1273,7 @@ TEST_F(PasswordControllerTest, SelectingSuggestionShouldFillPasswordForm) {
webState:web_state()
completionHandler:^(NSArray* suggestions,
id<FormSuggestionProvider> provider) {
EXPECT_EQ(1u, [suggestions count]);
EXPECT_EQ(2u, [suggestions count]);
block_was_called = YES;
}];
EXPECT_TRUE(block_was_called);
......
......@@ -3274,6 +3274,10 @@ bubblePresenterForFeature:(const base::Feature&)feature
}
}
- (void)displaySavedPasswordList {
[self.dispatcher showSavedPasswordsSettingsFromViewController:self];
}
#pragma mark - CRWWebStateDelegate methods.
- (web::WebState*)webState:(web::WebState*)webState
......
......@@ -32,6 +32,10 @@
- (void)showSyncPassphraseSettingsFromViewController:
(UIViewController*)baseViewController;
// Shows the list of saved passwords in the settings.
- (void)showSavedPasswordsSettingsFromViewController:
(UIViewController*)baseViewController;
@end
// Protocol for commands that will generally be handled by the application,
......
......@@ -512,6 +512,16 @@ initWithRootViewController:(UIViewController*)rootViewController
[self pushViewController:controller animated:YES];
}
// TODO(crbug.com/779791) : Do not pass |baseViewController| through dispatcher.
- (void)showSavedPasswordsSettingsFromViewController:
(UIViewController*)baseViewController {
SavePasswordsCollectionViewController* controller =
[[SavePasswordsCollectionViewController alloc]
initWithBrowserState:mainBrowserState_];
controller.dispatcher = [delegate_ dispatcherForSettings];
[self pushViewController:controller animated:YES];
}
#pragma mark - Profile
- (ios::ChromeBrowserState*)mainBrowserState {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment