Commit d52c1f13 authored by Friedrich Horschig's avatar Friedrich Horschig Committed by Commit Bot

Update autofill popup when account suggestions get unlocked

With this CL, the password_autofill_manager will push updates to the
popup once the "unlock passwords" button is clicked.
First, the clicked button will disappear (later: show a throbber).
Second, the popup will show all passwords once they arrive (later:
an empty state message if no additional passwords were found).

To do that, the manager will pin the popup view so that it waits for
updates and remove the unlock button.

Bug: 1043963
Change-Id: I8a8db6a1bdc6d25b6c097202ab2a93914cddd9cf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2020966Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Commit-Queue: Friedrich [CET] <fhorschig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#736770}
parent 002ecf37
......@@ -215,6 +215,27 @@ bool ContainsOtherThanManagePasswords(
});
}
bool AreSuggestionForPasswordField(
base::span<const autofill::Suggestion> suggestions) {
return std::any_of(suggestions.begin(), suggestions.end(),
[](const autofill::Suggestion& suggestion) {
return suggestion.frontend_id ==
autofill::POPUP_ITEM_ID_PASSWORD_ENTRY;
});
}
std::vector<autofill::Suggestion> CopyWithoutUnlockButton(
base::span<const autofill::Suggestion> suggestions) {
std::vector<autofill::Suggestion> new_suggestions;
std::copy_if(suggestions.begin(), suggestions.end(),
std::back_inserter(new_suggestions),
[](const autofill::Suggestion& suggestion) {
return suggestion.frontend_id !=
autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN;
});
return new_suggestions;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
......@@ -277,7 +298,12 @@ void PasswordAutofillManager::DidAcceptSuggestion(const base::string16& value,
}
} else if (identifier ==
autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN) {
// TODO(https://crbug.com/1043963): Add loading spinner.
UpdatePopup(
CopyWithoutUnlockButton(autofill_client_->GetPopupSuggestions()));
autofill_client_->PinPopupViewUntilUpdate();
password_client_->GetPasswordFeatureManager()->SetAccountStorageOptIn(true);
return; // Do not hide the popup while loading data.
} else {
metrics_util::LogPasswordDropdownItemSelected(
PasswordDropdownSelectedOption::kPassword,
......@@ -336,6 +362,15 @@ void PasswordAutofillManager::OnAddPasswordFillData(
fill_data_ = std::make_unique<autofill::PasswordFormFillData>(fill_data);
RequestFavicon(fill_data.origin);
if (!autofill_client_ || autofill_client_->GetPopupSuggestions().empty())
return;
// TODO(https://crbug.com/1043963): Add empty state.
UpdatePopup(BuildSuggestions(ShowAllPasswords(true),
ForPasswordField(AreSuggestionForPasswordField(
autofill_client_->GetPopupSuggestions())),
base::string16(), OffersGeneration(false),
ShowPasswordSuggestions(true)));
}
void PasswordAutofillManager::DeleteFillData() {
......@@ -475,6 +510,18 @@ bool PasswordAutofillManager::ShowPopup(
return true;
}
void PasswordAutofillManager::UpdatePopup(
const std::vector<autofill::Suggestion>& suggestions) {
if (!password_manager_driver_->CanShowAutofillUi())
return;
if (!ContainsOtherThanManagePasswords(suggestions)) {
autofill_client_->HideAutofillPopup(
autofill::PopupHidingReason::kNoSuggestions);
return;
}
autofill_client_->UpdatePopup(suggestions, autofill::PopupType::kPasswords);
}
bool PasswordAutofillManager::FillSuggestion(const base::string16& username) {
autofill::PasswordAndMetadata password_and_meta_data;
if (fill_data_ && GetPasswordAndMetadataForUsername(
......
......@@ -129,6 +129,9 @@ class PasswordAutofillManager : public autofill::AutofillPopupDelegate {
base::i18n::TextDirection text_direction,
const std::vector<autofill::Suggestion>& suggestions);
// Validates and forwards the given objects to the autofill client.
void UpdatePopup(const std::vector<autofill::Suggestion>& suggestions);
// Attempts to fill the password associated with user name |username|, and
// returns true if it was successful.
bool FillSuggestion(const base::string16& username);
......
......@@ -134,6 +134,9 @@ class MockAutofillClient : public autofill::TestAutofillClient {
bool autoselect_first_suggestion,
PopupType popup_type,
base::WeakPtr<autofill::AutofillPopupDelegate> delegate));
MOCK_METHOD0(PinPopupViewUntilUpdate, void());
MOCK_CONST_METHOD0(GetPopupSuggestions,
base::span<const autofill::Suggestion>());
MOCK_METHOD2(UpdatePopup,
void(const std::vector<autofill::Suggestion>&, PopupType));
MOCK_METHOD1(HideAutofillPopup, void(autofill::PopupHidingReason));
......@@ -173,6 +176,35 @@ RespondWithTestIcon(Unused, FaviconImageCallback callback, Unused) {
return 1;
}
std::vector<autofill::Suggestion> CreateTestSuggestions(bool has_opt_in) {
std::vector<Suggestion> suggestions;
suggestions.push_back(
Suggestion(/*label=*/"User1", /*value=*/"PW1", /*icon=*/"",
/*fronend_id=*/autofill::POPUP_ITEM_ID_PASSWORD_ENTRY));
if (!IsPreLollipopAndroid()) {
suggestions.push_back(Suggestion(
/*label=*/"Show all pwds", /*value=*/"", /*icon=*/"",
/*fronend_id=*/autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY));
}
if (has_opt_in) {
suggestions.push_back(Suggestion(
/*label=*/"Unlock passwords", /*value=*/"", /*icon=*/"",
/*fronend_id=*/autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN));
}
return suggestions;
}
std::vector<autofill::PopupItemId> RemoveShowAllBeforeLollipop(
std::vector<autofill::PopupItemId> ids) {
if (IsPreLollipopAndroid()) {
ids.erase(
std::remove_if(ids.begin(), ids.end(), [](autofill::PopupItemId id) {
return id == autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY;
}));
}
return ids;
}
} // namespace
class PasswordAutofillManagerTest : public testing::Test {
......@@ -290,22 +322,18 @@ TEST_F(PasswordAutofillManagerTest, ExternalDelegatePasswordSuggestions) {
.WillOnce(Invoke(RespondWithTestIcon));
password_autofill_manager_->OnAddPasswordFillData(data);
// Assemble IDs we expect: an entry and show all passwords button.
std::vector<autofill::PopupItemId> ids = {
is_suggestion_on_password_field
? autofill::POPUP_ITEM_ID_PASSWORD_ENTRY
: autofill::POPUP_ITEM_ID_USERNAME_ENTRY};
if (!IsPreLollipopAndroid()) {
ids.push_back(autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY);
}
// Show the popup and verify the suggestions.
std::vector<Suggestion> suggestions;
EXPECT_CALL(
*autofill_client,
ShowAutofillPopup(_, _, SuggestionVectorIdsAre(ElementsAreArray(ids)),
/*autoselect_first_suggestion=*/false,
PopupType::kPasswords, _))
ShowAutofillPopup(
_, _,
SuggestionVectorIdsAre(ElementsAreArray(RemoveShowAllBeforeLollipop(
{is_suggestion_on_password_field
? autofill::POPUP_ITEM_ID_PASSWORD_ENTRY
: autofill::POPUP_ITEM_ID_USERNAME_ENTRY,
autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY}))),
/*autoselect_first_suggestion=*/false, PopupType::kPasswords, _))
.WillOnce(testing::SaveArg<2>(&suggestions));
int show_suggestion_options =
......@@ -343,25 +371,75 @@ TEST_F(PasswordAutofillManagerTest, ShowUnlockButton) {
InitializePasswordAutofillManager(client.get(), autofill_client.get());
client->SetAccountStorageOptIn(false);
// Assemble IDs we expect: an entry, show all passwords and unlock button.
std::vector<autofill::PopupItemId> ids = {
autofill::POPUP_ITEM_ID_PASSWORD_ENTRY};
if (!IsPreLollipopAndroid()) {
ids.push_back(autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY);
}
ids.push_back(autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN);
// Show the popup and verify the suggestions.
EXPECT_CALL(
*autofill_client,
ShowAutofillPopup(_, _, SuggestionVectorIdsAre(ElementsAreArray(ids)),
/*autoselect_first_suggestion=*/false,
PopupType::kPasswords, _));
ShowAutofillPopup(
_, _,
SuggestionVectorIdsAre(ElementsAreArray(RemoveShowAllBeforeLollipop(
{autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY,
autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN}))),
/*autoselect_first_suggestion=*/false, PopupType::kPasswords, _));
password_autofill_manager_->OnShowPasswordSuggestions(
base::i18n::RIGHT_TO_LEFT, base::string16(),
autofill::SHOW_ALL | autofill::IS_PASSWORD_FIELD, gfx::RectF());
}
// Test that the popup is updated once remote suggestions are unlocked.
TEST_F(PasswordAutofillManagerTest, ClickOnUnlockPutsPopupInWaitingState) {
auto client = std::make_unique<TestPasswordManagerClient>();
auto autofill_client = std::make_unique<NiceMock<MockAutofillClient>>();
InitializePasswordAutofillManager(client.get(), autofill_client.get());
client->SetAccountStorageOptIn(false);
testing::Mock::VerifyAndClearExpectations(autofill_client.get());
// Accepting a suggestion should trigger a call to update the popup. The first
// update removes the unlock button
EXPECT_CALL(
*autofill_client,
UpdatePopup(
SuggestionVectorIdsAre(ElementsAreArray(RemoveShowAllBeforeLollipop(
{autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY}))),
PopupType::kPasswords));
EXPECT_CALL(*autofill_client, PinPopupViewUntilUpdate);
EXPECT_CALL(*autofill_client, GetPopupSuggestions())
.WillOnce(Return(CreateTestSuggestions(/*has_opt_in=*/true)));
password_autofill_manager_->DidAcceptSuggestion(
test_username_, autofill::POPUP_ITEM_ID_PASSWORD_ACCOUNT_STORAGE_OPTIN,
1);
}
// Test that the popup is updated once remote suggestions are unlocked.
TEST_F(PasswordAutofillManagerTest, AddOnFillDataAfterUnlockPopuplatesPopup) {
auto client = std::make_unique<TestPasswordManagerClient>();
auto autofill_client = std::make_unique<NiceMock<MockAutofillClient>>();
InitializePasswordAutofillManager(client.get(), autofill_client.get());
client->SetAccountStorageOptIn(true);
testing::Mock::VerifyAndClearExpectations(autofill_client.get());
// Once the data is loaded, an update fills the new passwords:
autofill::PasswordFormFillData new_data = CreateTestFormFillData();
new_data.uses_account_store = true;
autofill::PasswordAndMetadata additional;
additional.realm = "https://foobarrealm.org";
base::string16 additional_username(base::ASCIIToUTF16("bar.foo@example.com"));
new_data.additional_logins[additional_username] = additional;
EXPECT_CALL(*autofill_client, GetPopupSuggestions())
.Times(2)
.WillRepeatedly(Return(CreateTestSuggestions(/*has_opt_in=*/false)));
EXPECT_CALL(
*autofill_client,
UpdatePopup(
SuggestionVectorIdsAre(ElementsAreArray(RemoveShowAllBeforeLollipop(
{autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
autofill::POPUP_ITEM_ID_PASSWORD_ENTRY,
autofill::POPUP_ITEM_ID_ALL_SAVED_PASSWORDS_ENTRY}))),
PopupType::kPasswords));
password_autofill_manager_->OnAddPasswordFillData(new_data);
}
// Test that OnShowPasswordSuggestions correctly matches the given FormFieldData
// to the known PasswordFormFillData, and extracts the right suggestions.
TEST_F(PasswordAutofillManagerTest, ExtractSuggestions) {
......
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