Commit 68c01000 authored by Mohamed Amir Yosef's avatar Mohamed Amir Yosef Committed by Commit Bot

[Passwords] Introduce PendingBubbleController

This CL is one of many refactoring CLs that would split the
ManagePasswordsBubbleModel into different controllers one per view.

Bug: 1044034
Change-Id: I80836c2ce3bb89efcd6765108a59922a5459368c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2022672
Commit-Queue: Mohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#737312}
parent 5241d010
......@@ -1067,6 +1067,8 @@ jumbo_static_library("ui") {
"passwords/bubble_controllers/items_bubble_controller.h",
"passwords/bubble_controllers/password_bubble_controller_base.cc",
"passwords/bubble_controllers/password_bubble_controller_base.h",
"passwords/bubble_controllers/pending_bubble_controller.cc",
"passwords/bubble_controllers/pending_bubble_controller.h",
"passwords/bubble_controllers/sign_in_promo_bubble_controller.cc",
"passwords/bubble_controllers/sign_in_promo_bubble_controller.h",
"passwords/credential_leak_dialog_controller.h",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/passwords/bubble_controllers/pending_bubble_controller.h"
#include "base/metrics/field_trial_params.h"
#include "base/time/default_clock.h"
#include "chrome/browser/password_manager/password_store_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
#include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
#include "chrome/browser/ui/passwords/passwords_model_delegate.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/password_manager/core/browser/password_bubble_experiment.h"
#include "components/password_manager/core/browser/password_form_metrics_recorder.h"
#include "components/password_manager/core/browser/password_manager_metrics_util.h"
#include "components/password_manager/core/browser/password_store.h"
#include "components/password_manager/core/common/password_manager_features.h"
#include "components/password_manager/core/common/password_manager_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/sync/driver/sync_service.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#if defined(PASSWORD_STORE_SELECT_ENABLED)
#include "components/password_manager/core/browser/password_feature_manager.h"
#endif // defined(PASSWORD_STORE_SELECT_ENABLED)
namespace {
namespace metrics_util = password_manager::metrics_util;
using Store = autofill::PasswordForm::Store;
password_manager::metrics_util::UIDisplayDisposition ComputeDisplayDisposition(
ManagePasswordsBubbleModel::DisplayReason display_reason,
password_manager::ui::State state) {
if (display_reason == ManagePasswordsBubbleModel::USER_ACTION) {
switch (state) {
case password_manager::ui::PENDING_PASSWORD_STATE:
return metrics_util::MANUAL_WITH_PASSWORD_PENDING;
case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE:
return metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE;
default:
NOTREACHED();
return metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE;
}
} else {
switch (state) {
case password_manager::ui::PENDING_PASSWORD_STATE:
return metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING;
case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE:
return metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE;
default:
NOTREACHED();
return metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING;
}
}
}
void CleanStatisticsForSite(Profile* profile, const GURL& origin) {
DCHECK(profile);
password_manager::PasswordStore* password_store =
PasswordStoreFactory::GetForProfile(profile,
ServiceAccessType::IMPLICIT_ACCESS)
.get();
password_store->RemoveSiteStats(origin.GetOrigin());
}
std::vector<autofill::PasswordForm> DeepCopyForms(
const std::vector<std::unique_ptr<autofill::PasswordForm>>& forms) {
std::vector<autofill::PasswordForm> result;
result.reserve(forms.size());
std::transform(forms.begin(), forms.end(), std::back_inserter(result),
[](const std::unique_ptr<autofill::PasswordForm>& form) {
return *form;
});
return result;
}
bool IsSyncUser(Profile* profile) {
const syncer::SyncService* sync_service =
ProfileSyncServiceFactory::GetForProfile(profile);
return password_bubble_experiment::IsSmartLockUser(sync_service);
}
} // namespace
PendingBubbleController::PendingBubbleController(
base::WeakPtr<PasswordsModelDelegate> delegate,
ManagePasswordsBubbleModel::DisplayReason display_reason)
: PasswordBubbleControllerBase(
delegate,
ComputeDisplayDisposition(display_reason, delegate->GetState())),
display_disposition_(
ComputeDisplayDisposition(display_reason, delegate->GetState())),
password_revealing_requires_reauth_(false),
are_passwords_revealed_when_bubble_is_opened_(false),
enable_editing_(false),
dismissal_reason_(metrics_util::NO_DIRECT_INTERACTION),
clock_(base::DefaultClock::GetInstance()) {
state_ = delegate_->GetState();
DCHECK(state_ == password_manager::ui::PENDING_PASSWORD_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE);
origin_ = delegate_->GetOrigin();
pending_password_ = delegate_->GetPendingPassword();
local_credentials_ = DeepCopyForms(delegate_->GetCurrentForms());
if (state_ == password_manager::ui::PENDING_PASSWORD_STATE) {
interaction_stats_.origin_domain = origin_.GetOrigin();
interaction_stats_.username_value = pending_password_.username_value;
const password_manager::InteractionsStats* stats =
delegate_->GetCurrentInteractionStats();
if (stats) {
DCHECK_EQ(interaction_stats_.username_value, stats->username_value);
DCHECK_EQ(interaction_stats_.origin_domain, stats->origin_domain);
interaction_stats_.dismissal_count = stats->dismissal_count;
}
}
if (delegate_->ArePasswordsRevealedWhenBubbleIsOpened()) {
are_passwords_revealed_when_bubble_is_opened_ = true;
delegate_->OnPasswordsRevealed();
}
// The condition for the password reauth:
// If the bubble opened after reauth -> no more reauth necessary, otherwise
// If a password was autofilled -> require reauth to view it, otherwise
// Require reauth iff the user opened the bubble manually and it's not the
// manual saving state. The manual saving state as well as automatic prompt
// are temporary states, therefore, it's better for the sake of convenience
// for the user not to break the UX with the reauth prompt.
password_revealing_requires_reauth_ =
!are_passwords_revealed_when_bubble_is_opened_ &&
(pending_password_.form_has_autofilled_value ||
(!delegate_->BubbleIsManualFallbackForSaving() &&
display_reason == ManagePasswordsBubbleModel::USER_ACTION));
enable_editing_ = delegate_->GetCredentialSource() !=
password_manager::metrics_util::CredentialSourceType::
kCredentialManagementAPI;
// Compute the title.
PasswordTitleType type =
state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
? PasswordTitleType::UPDATE_PASSWORD
: (pending_password_.federation_origin.opaque()
? PasswordTitleType::SAVE_PASSWORD
: PasswordTitleType::SAVE_ACCOUNT);
GetSavePasswordDialogTitleTextAndLinkRange(GetWebContents()->GetVisibleURL(),
origin_, type, &title_);
}
PendingBubbleController::~PendingBubbleController() {
if (!interaction_reported_)
OnBubbleClosing();
}
void PendingBubbleController::OnSaveClicked() {
DCHECK(state_ == password_manager::ui::PENDING_PASSWORD_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE);
dismissal_reason_ = metrics_util::CLICKED_SAVE;
if (delegate_) {
CleanStatisticsForSite(GetProfile(), origin_);
delegate_->SavePassword(pending_password_.username_value,
pending_password_.password_value);
}
}
void PendingBubbleController::OnNopeUpdateClicked() {
DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_UPDATE_STATE, state_);
dismissal_reason_ = metrics_util::CLICKED_CANCEL;
if (delegate_)
delegate_->OnNopeUpdateClicked();
}
void PendingBubbleController::OnNeverForThisSiteClicked() {
DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_STATE, state_);
dismissal_reason_ = metrics_util::CLICKED_NEVER;
if (delegate_) {
CleanStatisticsForSite(GetProfile(), origin_);
delegate_->NeverSavePassword();
}
}
void PendingBubbleController::OnCredentialEdited(base::string16 new_username,
base::string16 new_password) {
DCHECK(state_ == password_manager::ui::PENDING_PASSWORD_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE);
pending_password_.username_value = std::move(new_username);
pending_password_.password_value = std::move(new_password);
}
bool PendingBubbleController::IsCurrentStateUpdate() const {
DCHECK(state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_STATE);
return std::any_of(local_credentials_.begin(), local_credentials_.end(),
[this](const autofill::PasswordForm& form) {
return form.username_value ==
pending_password_.username_value;
});
}
bool PendingBubbleController::ShouldShowFooter() const {
return (state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_STATE) &&
IsSyncUser(GetProfile());
}
int PendingBubbleController::GetTopIllustration(bool dark_mode) const {
if (state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_STATE) {
int image = base::GetFieldTrialParamByFeatureAsInt(
password_manager::features::kPasswordSaveIllustration, "image", 0);
switch (image) {
case 1:
return dark_mode ? IDR_SAVE_PASSWORD1_DARK : IDR_SAVE_PASSWORD1;
case 2:
return dark_mode ? IDR_SAVE_PASSWORD2_DARK : IDR_SAVE_PASSWORD2;
case 3:
return dark_mode ? IDR_SAVE_PASSWORD3_DARK : IDR_SAVE_PASSWORD3;
default:
return 0;
}
}
return 0;
}
bool PendingBubbleController::ReplaceToShowPromotionIfNeeded() {
Profile* profile = GetProfile();
if (!profile)
return false;
PrefService* prefs = profile->GetPrefs();
const syncer::SyncService* sync_service =
ProfileSyncServiceFactory::GetForProfile(profile);
// Signin promotion.
if (password_bubble_experiment::ShouldShowChromeSignInPasswordPromo(
prefs, sync_service)) {
ReportInteractions();
title_ = l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SYNC_PROMO_TITLE);
state_ = password_manager::ui::CHROME_SIGN_IN_PROMO_STATE;
int show_count = prefs->GetInteger(
password_manager::prefs::kNumberSignInPasswordPromoShown);
show_count++;
prefs->SetInteger(password_manager::prefs::kNumberSignInPasswordPromoShown,
show_count);
return true;
}
return false;
}
bool PendingBubbleController::RevealPasswords() {
bool reveal_immediately = !password_revealing_requires_reauth_ ||
(delegate_ && delegate_->AuthenticateUser());
if (reveal_immediately)
delegate_->OnPasswordsRevealed();
return reveal_immediately;
}
#if defined(PASSWORD_STORE_SELECT_ENABLED)
void PendingBubbleController::OnToggleAccountStore(bool is_checked) {
delegate_->GetPasswordFeatureManager()->SetDefaultPasswordStore(
is_checked ? Store::kAccountStore : Store::kProfileStore);
}
bool PendingBubbleController::IsUsingAccountStore() {
return delegate_->GetPasswordFeatureManager()->GetDefaultPasswordStore() ==
Store::kAccountStore;
}
#endif // defined(PASSWORD_STORE_SELECT_ENABLED)
void PendingBubbleController::ReportInteractions() {
if (state_ == password_manager::ui::CHROME_SIGN_IN_PROMO_STATE)
return;
DCHECK(state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_STATE);
if (state_ == password_manager::ui::PENDING_PASSWORD_STATE) {
// Update the statistics for the save password bubble.
Profile* profile = GetProfile();
if (profile) {
if (dismissal_reason_ == metrics_util::NO_DIRECT_INTERACTION &&
display_disposition_ ==
metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING) {
if (interaction_stats_.dismissal_count <
std::numeric_limits<decltype(
interaction_stats_.dismissal_count)>::max())
interaction_stats_.dismissal_count++;
interaction_stats_.update_time = clock_->Now();
password_manager::PasswordStore* password_store =
PasswordStoreFactory::GetForProfile(
profile, ServiceAccessType::IMPLICIT_ACCESS)
.get();
password_store->AddSiteStats(interaction_stats_);
}
}
}
// Log UMA histograms.
if (state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) {
metrics_util::LogUpdateUIDismissalReason(dismissal_reason_);
} else if (state_ == password_manager::ui::PENDING_PASSWORD_STATE) {
metrics_util::LogSaveUIDismissalReason(dismissal_reason_);
}
// Update the delegate so that it can send votes to the server.
// Send a notification if there was no interaction with the bubble.
bool no_interaction =
dismissal_reason_ == metrics_util::NO_DIRECT_INTERACTION;
if (no_interaction && delegate_) {
delegate_->OnNoInteraction();
}
// Record UKM statistics on dismissal reason.
if (metrics_recorder_)
metrics_recorder_->RecordUIDismissalReason(dismissal_reason_);
}
base::string16 PendingBubbleController::GetTitle() const {
return title_;
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UI_PASSWORDS_BUBBLE_CONTROLLERS_PENDING_BUBBLE_CONTROLLER_H_
#define CHROME_BROWSER_UI_PASSWORDS_BUBBLE_CONTROLLERS_PENDING_BUBBLE_CONTROLLER_H_
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ui/passwords/bubble_controllers/password_bubble_controller_base.h"
#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
#include "components/password_manager/core/browser/manage_passwords_referrer.h"
class PasswordsModelDelegate;
// This controller provides data and actions for the PasswordPendingView.
class PendingBubbleController : public PasswordBubbleControllerBase {
public:
explicit PendingBubbleController(
base::WeakPtr<PasswordsModelDelegate> delegate,
ManagePasswordsBubbleModel::DisplayReason display_reason);
~PendingBubbleController() override;
// Called by the view code when the save/update button is clicked by the user.
void OnSaveClicked();
// Called by the view code when the "Nope" button in clicked by the user in
// update bubble.
void OnNopeUpdateClicked();
// Called by the view code when the "Never for this site." button in clicked
// by the user.
void OnNeverForThisSiteClicked();
// Called by the view code when username or password is corrected using
// the username correction or password selection features in PendingView.
void OnCredentialEdited(base::string16 new_username,
base::string16 new_password);
// The password bubble can switch its state between "save" and "update"
// depending on the user input. |state_| only captures the correct state on
// creation. This method returns true iff the current state is "update".
bool IsCurrentStateUpdate() const;
// Returns true iff the bubble is supposed to show the footer about syncing
// to Google account.
bool ShouldShowFooter() const;
// Returns the ID of the picture to show above the title.
int GetTopIllustration(bool dark_mode) const;
// Returns true and updates the internal state iff the Save bubble should
// switch to show a promotion after the password was saved. Otherwise,
// returns false and leaves the current state.
bool ReplaceToShowPromotionIfNeeded();
// Returns true if passwords revealing is not locked or re-authentication is
// not available on the given platform. Otherwise, the method schedules
// re-authentication and bubble reopen (the current bubble will be destroyed),
// and returns false immediately. New bubble will reveal the passwords if the
// re-authentication is successful.
bool RevealPasswords();
#if defined(PASSWORD_STORE_SELECT_ENABLED)
// Called by the view when the account store checkbox is toggled.
void OnToggleAccountStore(bool is_checked);
// Returns true iff the password account store is used.
bool IsUsingAccountStore();
#endif // defined(PASSWORD_STORE_SELECT_ENABLED)
password_manager::ui::State state() const { return state_; }
const autofill::PasswordForm& pending_password() const {
return pending_password_;
}
bool are_passwords_revealed_when_bubble_is_opened() const {
return are_passwords_revealed_when_bubble_is_opened_;
}
bool enable_editing() const { return enable_editing_; }
#if defined(UNIT_TEST)
void set_clock(base::Clock* clock) { clock_ = clock; }
void allow_passwords_revealing() {
password_revealing_requires_reauth_ = false;
}
bool password_revealing_requires_reauth() const {
return password_revealing_requires_reauth_;
}
#endif
private:
// PasswordBubbleControllerBase methods:
base::string16 GetTitle() const override;
void ReportInteractions() override;
// URL of the page from where this bubble was triggered.
GURL origin_;
password_manager::ui::State state_;
base::string16 title_;
autofill::PasswordForm pending_password_;
std::vector<autofill::PasswordForm> local_credentials_;
password_manager::InteractionsStats interaction_stats_;
password_manager::metrics_util::UIDisplayDisposition display_disposition_;
// True iff password revealing should require re-auth for privacy reasons.
bool password_revealing_requires_reauth_;
// True iff bubble should pop up with revealed password value.
bool are_passwords_revealed_when_bubble_is_opened_;
// True iff username/password editing should be enabled.
bool enable_editing_;
// Dismissal reason for a password bubble.
password_manager::metrics_util::UIDismissalReason dismissal_reason_;
// Used to retrieve the current time, in base::Time units.
base::Clock* clock_;
};
#endif // CHROME_BROWSER_UI_PASSWORDS_BUBBLE_CONTROLLERS_PENDING_BUBBLE_CONTROLLER_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
#include "chrome/browser/ui/passwords/bubble_controllers/pending_bubble_controller.h"
#include <memory>
#include <string>
......@@ -62,10 +62,10 @@ constexpr char kUIDismissalReasonUpdateMetric[] =
} // namespace
class ManagePasswordsBubbleModelTest : public ::testing::Test {
class PendingBubbleControllerTest : public ::testing::Test {
public:
ManagePasswordsBubbleModelTest() = default;
~ManagePasswordsBubbleModelTest() override = default;
PendingBubbleControllerTest() = default;
~PendingBubbleControllerTest() override = default;
void SetUp() override {
test_web_contents_ =
......@@ -89,7 +89,7 @@ class ManagePasswordsBubbleModelTest : public ::testing::Test {
void TearDown() override {
// Reset the delegate first. It can happen if the user closes the tab.
mock_delegate_.reset();
model_.reset();
controller_.reset();
}
PrefService* prefs() { return profile_.GetPrefs(); }
......@@ -103,9 +103,9 @@ class ManagePasswordsBubbleModelTest : public ::testing::Test {
.get());
}
PasswordsModelDelegateMock* controller() { return mock_delegate_.get(); }
PasswordsModelDelegateMock* delegate() { return mock_delegate_.get(); }
ManagePasswordsBubbleModel* model() { return model_.get(); }
PendingBubbleController* controller() { return controller_.get(); }
autofill::PasswordForm& pending_password() { return pending_password_; }
const autofill::PasswordForm& pending_password() const {
......@@ -130,42 +130,42 @@ class ManagePasswordsBubbleModelTest : public ::testing::Test {
content::RenderViewHostTestEnabler rvh_enabler_;
TestingProfile profile_;
std::unique_ptr<content::WebContents> test_web_contents_;
std::unique_ptr<ManagePasswordsBubbleModel> model_;
std::unique_ptr<PendingBubbleController> controller_;
std::unique_ptr<PasswordsModelDelegateMock> mock_delegate_;
autofill::PasswordForm pending_password_;
};
void ManagePasswordsBubbleModelTest::SetUpWithState(
void PendingBubbleControllerTest::SetUpWithState(
password_manager::ui::State state,
ManagePasswordsBubbleModel::DisplayReason reason) {
GURL origin(kSiteOrigin);
EXPECT_CALL(*controller(), GetOrigin()).WillOnce(ReturnRef(origin));
EXPECT_CALL(*controller(), GetState()).WillOnce(Return(state));
EXPECT_CALL(*controller(), OnBubbleShown());
EXPECT_CALL(*controller(), GetWebContents())
EXPECT_CALL(*delegate(), GetOrigin()).WillOnce(ReturnRef(origin));
EXPECT_CALL(*delegate(), GetState()).WillRepeatedly(Return(state));
EXPECT_CALL(*delegate(), OnBubbleShown());
EXPECT_CALL(*delegate(), GetWebContents())
.WillRepeatedly(Return(test_web_contents_.get()));
model_.reset(
new ManagePasswordsBubbleModel(mock_delegate_->AsWeakPtr(), reason));
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(controller()));
EXPECT_CALL(*controller(), GetWebContents())
controller_.reset(
new PendingBubbleController(mock_delegate_->AsWeakPtr(), reason));
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(delegate()));
EXPECT_CALL(*delegate(), GetWebContents())
.WillRepeatedly(Return(test_web_contents_.get()));
}
void ManagePasswordsBubbleModelTest::PretendPasswordWaiting(
void PendingBubbleControllerTest::PretendPasswordWaiting(
ManagePasswordsBubbleModel::DisplayReason reason) {
EXPECT_CALL(*controller(), GetPendingPassword())
EXPECT_CALL(*delegate(), GetPendingPassword())
.WillOnce(ReturnRef(pending_password()));
password_manager::InteractionsStats stats = GetTestStats();
EXPECT_CALL(*controller(), GetCurrentInteractionStats())
EXPECT_CALL(*delegate(), GetCurrentInteractionStats())
.WillOnce(Return(&stats));
std::vector<std::unique_ptr<autofill::PasswordForm>> forms =
GetCurrentForms();
EXPECT_CALL(*controller(), GetCurrentForms()).WillOnce(ReturnRef(forms));
EXPECT_CALL(*delegate(), GetCurrentForms()).WillOnce(ReturnRef(forms));
SetUpWithState(password_manager::ui::PENDING_PASSWORD_STATE, reason);
}
void ManagePasswordsBubbleModelTest::PretendUpdatePasswordWaiting() {
EXPECT_CALL(*controller(), GetPendingPassword())
void PendingBubbleControllerTest::PretendUpdatePasswordWaiting() {
EXPECT_CALL(*delegate(), GetPendingPassword())
.WillOnce(ReturnRef(pending_password()));
std::vector<std::unique_ptr<autofill::PasswordForm>> forms =
GetCurrentForms();
......@@ -173,23 +173,23 @@ void ManagePasswordsBubbleModelTest::PretendUpdatePasswordWaiting() {
std::make_unique<autofill::PasswordForm>(pending_password());
current_form->password_value = base::ASCIIToUTF16("old_password");
forms.push_back(std::move(current_form));
EXPECT_CALL(*controller(), GetCurrentForms()).WillOnce(ReturnRef(forms));
EXPECT_CALL(*delegate(), GetCurrentForms()).WillOnce(ReturnRef(forms));
SetUpWithState(password_manager::ui::PENDING_PASSWORD_UPDATE_STATE,
ManagePasswordsBubbleModel::AUTOMATIC);
}
void ManagePasswordsBubbleModelTest::
void PendingBubbleControllerTest::
DestroyModelAndVerifyControllerExpectations() {
EXPECT_CALL(*controller(), OnBubbleHidden());
model_->OnBubbleClosing();
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(controller()));
model_.reset();
EXPECT_CALL(*delegate(), OnBubbleHidden());
controller_->OnBubbleClosing();
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(delegate()));
controller_.reset();
}
void ManagePasswordsBubbleModelTest::DestroyModelExpectReason(
void PendingBubbleControllerTest::DestroyModelExpectReason(
password_manager::metrics_util::UIDismissalReason dismissal_reason) {
base::HistogramTester histogram_tester;
password_manager::ui::State state = model_->state();
password_manager::ui::State state = controller_->state();
std::string histogram(kUIDismissalReasonGeneralMetric);
if (state == password_manager::ui::PENDING_PASSWORD_STATE)
histogram = kUIDismissalReasonSaveMetric;
......@@ -201,7 +201,7 @@ void ManagePasswordsBubbleModelTest::DestroyModelExpectReason(
// static
password_manager::InteractionsStats
ManagePasswordsBubbleModelTest::GetTestStats() {
PendingBubbleControllerTest::GetTestStats() {
password_manager::InteractionsStats result;
result.origin_domain = GURL(kSiteOrigin).GetOrigin();
result.username_value = base::ASCIIToUTF16(kUsername);
......@@ -211,7 +211,7 @@ ManagePasswordsBubbleModelTest::GetTestStats() {
}
std::vector<std::unique_ptr<autofill::PasswordForm>>
ManagePasswordsBubbleModelTest::GetCurrentForms() const {
PendingBubbleControllerTest::GetCurrentForms() const {
autofill::PasswordForm form(pending_password());
form.username_value = base::ASCIIToUTF16(kUsernameExisting);
form.password_value = base::ASCIIToUTF16("123456");
......@@ -226,180 +226,181 @@ ManagePasswordsBubbleModelTest::GetCurrentForms() const {
return forms;
}
TEST_F(ManagePasswordsBubbleModelTest, CloseWithoutInteraction) {
TEST_F(PendingBubbleControllerTest, CloseWithoutInteraction) {
PretendPasswordWaiting();
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, model()->state());
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE,
controller()->state());
base::SimpleTestClock clock;
base::Time now = base::Time::Now();
clock.SetNow(now);
model()->SetClockForTesting(&clock);
controller()->set_clock(&clock);
password_manager::InteractionsStats stats = GetTestStats();
stats.dismissal_count++;
stats.update_time = now;
EXPECT_CALL(*GetStore(), AddSiteStatsImpl(stats));
EXPECT_CALL(*controller(), OnNoInteraction());
EXPECT_CALL(*controller(), SavePassword(_, _)).Times(0);
EXPECT_CALL(*controller(), NeverSavePassword()).Times(0);
EXPECT_CALL(*delegate(), OnNoInteraction());
EXPECT_CALL(*delegate(), SavePassword(_, _)).Times(0);
EXPECT_CALL(*delegate(), NeverSavePassword()).Times(0);
DestroyModelExpectReason(
password_manager::metrics_util::NO_DIRECT_INTERACTION);
}
TEST_F(ManagePasswordsBubbleModelTest, ClickSave) {
TEST_F(PendingBubbleControllerTest, ClickSave) {
PretendPasswordWaiting();
EXPECT_TRUE(model()->enable_editing());
EXPECT_FALSE(model()->IsCurrentStateUpdate());
EXPECT_TRUE(controller()->enable_editing());
EXPECT_FALSE(controller()->IsCurrentStateUpdate());
EXPECT_CALL(*GetStore(), RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), OnPasswordsRevealed()).Times(0);
EXPECT_CALL(*controller(), SavePassword(pending_password().username_value,
EXPECT_CALL(*delegate(), OnPasswordsRevealed()).Times(0);
EXPECT_CALL(*delegate(), SavePassword(pending_password().username_value,
pending_password().password_value));
EXPECT_CALL(*controller(), NeverSavePassword()).Times(0);
EXPECT_CALL(*controller(), OnNopeUpdateClicked()).Times(0);
model()->OnSaveClicked();
EXPECT_CALL(*delegate(), NeverSavePassword()).Times(0);
EXPECT_CALL(*delegate(), OnNopeUpdateClicked()).Times(0);
controller()->OnSaveClicked();
DestroyModelExpectReason(password_manager::metrics_util::CLICKED_SAVE);
}
TEST_F(ManagePasswordsBubbleModelTest, ClickSaveInUpdateState) {
TEST_F(PendingBubbleControllerTest, ClickSaveInUpdateState) {
PretendUpdatePasswordWaiting();
// Edit username, now it's a new credential.
model()->OnCredentialEdited(base::ASCIIToUTF16(kUsernameNew),
controller()->OnCredentialEdited(base::ASCIIToUTF16(kUsernameNew),
base::ASCIIToUTF16(kPasswordEdited));
EXPECT_FALSE(model()->IsCurrentStateUpdate());
EXPECT_FALSE(controller()->IsCurrentStateUpdate());
EXPECT_CALL(*GetStore(), RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), SavePassword(base::ASCIIToUTF16(kUsernameNew),
EXPECT_CALL(*delegate(), SavePassword(base::ASCIIToUTF16(kUsernameNew),
base::ASCIIToUTF16(kPasswordEdited)));
EXPECT_CALL(*controller(), NeverSavePassword()).Times(0);
EXPECT_CALL(*controller(), OnNopeUpdateClicked()).Times(0);
model()->OnSaveClicked();
EXPECT_CALL(*delegate(), NeverSavePassword()).Times(0);
EXPECT_CALL(*delegate(), OnNopeUpdateClicked()).Times(0);
controller()->OnSaveClicked();
DestroyModelExpectReason(password_manager::metrics_util::CLICKED_SAVE);
}
TEST_F(ManagePasswordsBubbleModelTest, ClickNever) {
TEST_F(PendingBubbleControllerTest, ClickNever) {
PretendPasswordWaiting();
EXPECT_CALL(*GetStore(), RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), SavePassword(_, _)).Times(0);
EXPECT_CALL(*controller(), NeverSavePassword());
model()->OnNeverForThisSiteClicked();
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE, model()->state());
EXPECT_CALL(*delegate(), SavePassword(_, _)).Times(0);
EXPECT_CALL(*delegate(), NeverSavePassword());
controller()->OnNeverForThisSiteClicked();
EXPECT_EQ(password_manager::ui::PENDING_PASSWORD_STATE,
controller()->state());
DestroyModelExpectReason(password_manager::metrics_util::CLICKED_NEVER);
}
TEST_F(ManagePasswordsBubbleModelTest, ClickUpdate) {
TEST_F(PendingBubbleControllerTest, ClickUpdate) {
PretendUpdatePasswordWaiting();
EXPECT_TRUE(model()->enable_editing());
EXPECT_TRUE(model()->IsCurrentStateUpdate());
EXPECT_TRUE(controller()->enable_editing());
EXPECT_TRUE(controller()->IsCurrentStateUpdate());
EXPECT_CALL(*GetStore(), RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), OnPasswordsRevealed()).Times(0);
EXPECT_CALL(*controller(), SavePassword(pending_password().username_value,
EXPECT_CALL(*delegate(), OnPasswordsRevealed()).Times(0);
EXPECT_CALL(*delegate(), SavePassword(pending_password().username_value,
pending_password().password_value));
EXPECT_CALL(*controller(), NeverSavePassword()).Times(0);
EXPECT_CALL(*controller(), OnNopeUpdateClicked()).Times(0);
model()->OnSaveClicked();
EXPECT_CALL(*delegate(), NeverSavePassword()).Times(0);
EXPECT_CALL(*delegate(), OnNopeUpdateClicked()).Times(0);
controller()->OnSaveClicked();
DestroyModelExpectReason(password_manager::metrics_util::CLICKED_SAVE);
}
TEST_F(ManagePasswordsBubbleModelTest, ClickUpdateInSaveState) {
TEST_F(PendingBubbleControllerTest, ClickUpdateInSaveState) {
PretendPasswordWaiting();
// Edit username, now it's an existing credential.
model()->OnCredentialEdited(base::ASCIIToUTF16(kUsernameExisting),
controller()->OnCredentialEdited(base::ASCIIToUTF16(kUsernameExisting),
base::ASCIIToUTF16(kPasswordEdited));
EXPECT_TRUE(model()->IsCurrentStateUpdate());
EXPECT_TRUE(controller()->IsCurrentStateUpdate());
EXPECT_CALL(*GetStore(), RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), SavePassword(base::ASCIIToUTF16(kUsernameExisting),
EXPECT_CALL(*delegate(), SavePassword(base::ASCIIToUTF16(kUsernameExisting),
base::ASCIIToUTF16(kPasswordEdited)));
EXPECT_CALL(*controller(), NeverSavePassword()).Times(0);
EXPECT_CALL(*controller(), OnNopeUpdateClicked()).Times(0);
model()->OnSaveClicked();
EXPECT_CALL(*delegate(), NeverSavePassword()).Times(0);
EXPECT_CALL(*delegate(), OnNopeUpdateClicked()).Times(0);
controller()->OnSaveClicked();
DestroyModelExpectReason(password_manager::metrics_util::CLICKED_SAVE);
}
TEST_F(ManagePasswordsBubbleModelTest, GetInitialUsername_MatchedUsername) {
TEST_F(PendingBubbleControllerTest, GetInitialUsername_MatchedUsername) {
PretendUpdatePasswordWaiting();
EXPECT_EQ(base::UTF8ToUTF16(kUsername),
model()->pending_password().username_value);
controller()->pending_password().username_value);
}
TEST_F(ManagePasswordsBubbleModelTest, EditCredential) {
TEST_F(PendingBubbleControllerTest, EditCredential) {
PretendPasswordWaiting();
EXPECT_CALL(*GetStore(), RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
const base::string16 kExpectedUsername = base::UTF8ToUTF16("new_username");
const base::string16 kExpectedPassword = base::UTF8ToUTF16("new_password");
model()->OnCredentialEdited(kExpectedUsername, kExpectedPassword);
EXPECT_EQ(kExpectedUsername, model()->pending_password().username_value);
EXPECT_EQ(kExpectedPassword, model()->pending_password().password_value);
EXPECT_CALL(*controller(),
SavePassword(kExpectedUsername, kExpectedPassword));
EXPECT_CALL(*controller(), NeverSavePassword()).Times(0);
model()->OnSaveClicked();
controller()->OnCredentialEdited(kExpectedUsername, kExpectedPassword);
EXPECT_EQ(kExpectedUsername, controller()->pending_password().username_value);
EXPECT_EQ(kExpectedPassword, controller()->pending_password().password_value);
EXPECT_CALL(*delegate(), SavePassword(kExpectedUsername, kExpectedPassword));
EXPECT_CALL(*delegate(), NeverSavePassword()).Times(0);
controller()->OnSaveClicked();
DestroyModelAndVerifyControllerExpectations();
}
TEST_F(ManagePasswordsBubbleModelTest, SuppressSignInPromo) {
TEST_F(PendingBubbleControllerTest, SuppressSignInPromo) {
prefs()->SetBoolean(password_manager::prefs::kSignInPasswordPromoRevive,
true);
prefs()->SetBoolean(password_manager::prefs::kWasSignInPasswordPromoClicked,
true);
PretendPasswordWaiting();
EXPECT_CALL(*GetStore(), RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), SavePassword(pending_password().username_value,
EXPECT_CALL(*delegate(), SavePassword(pending_password().username_value,
pending_password().password_value));
model()->OnSaveClicked();
controller()->OnSaveClicked();
EXPECT_FALSE(model()->ReplaceToShowPromotionIfNeeded());
EXPECT_FALSE(controller()->ReplaceToShowPromotionIfNeeded());
DestroyModelAndVerifyControllerExpectations();
}
TEST_F(ManagePasswordsBubbleModelTest, SignInPromoOK) {
TEST_F(PendingBubbleControllerTest, SignInPromoOK) {
base::HistogramTester histogram_tester;
PretendPasswordWaiting();
EXPECT_CALL(*GetStore(), RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), SavePassword(pending_password().username_value,
EXPECT_CALL(*delegate(), SavePassword(pending_password().username_value,
pending_password().password_value));
model()->OnSaveClicked();
controller()->OnSaveClicked();
#if defined(OS_CHROMEOS)
EXPECT_FALSE(model()->ReplaceToShowPromotionIfNeeded());
EXPECT_FALSE(controller()->ReplaceToShowPromotionIfNeeded());
#else
EXPECT_TRUE(model()->ReplaceToShowPromotionIfNeeded());
EXPECT_TRUE(controller()->ReplaceToShowPromotionIfNeeded());
#endif
}
#if !defined(OS_CHROMEOS)
TEST_F(ManagePasswordsBubbleModelTest, SignInPromoCancel) {
TEST_F(PendingBubbleControllerTest, SignInPromoCancel) {
base::HistogramTester histogram_tester;
PretendPasswordWaiting();
EXPECT_CALL(*GetStore(), RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), SavePassword(pending_password().username_value,
EXPECT_CALL(*delegate(), SavePassword(pending_password().username_value,
pending_password().password_value));
model()->OnSaveClicked();
controller()->OnSaveClicked();
EXPECT_TRUE(model()->ReplaceToShowPromotionIfNeeded());
EXPECT_TRUE(controller()->ReplaceToShowPromotionIfNeeded());
DestroyModelAndVerifyControllerExpectations();
histogram_tester.ExpectUniqueSample(
kUIDismissalReasonSaveMetric,
password_manager::metrics_util::CLICKED_SAVE, 1);
}
TEST_F(ManagePasswordsBubbleModelTest, SignInPromoDismiss) {
TEST_F(PendingBubbleControllerTest, SignInPromoDismiss) {
base::HistogramTester histogram_tester;
PretendPasswordWaiting();
EXPECT_CALL(*GetStore(), RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), SavePassword(pending_password().username_value,
EXPECT_CALL(*delegate(), SavePassword(pending_password().username_value,
pending_password().password_value));
model()->OnSaveClicked();
controller()->OnSaveClicked();
EXPECT_TRUE(model()->ReplaceToShowPromotionIfNeeded());
EXPECT_TRUE(controller()->ReplaceToShowPromotionIfNeeded());
DestroyModelAndVerifyControllerExpectations();
histogram_tester.ExpectUniqueSample(
kUIDismissalReasonSaveMetric,
......@@ -410,7 +411,7 @@ TEST_F(ManagePasswordsBubbleModelTest, SignInPromoDismiss) {
#endif // !defined(OS_CHROMEOS)
// Verify that URL keyed metrics are properly recorded.
TEST_F(ManagePasswordsBubbleModelTest, RecordUKMs) {
TEST_F(PendingBubbleControllerTest, RecordUKMs) {
using BubbleDismissalReason =
password_manager::PasswordFormMetricsRecorder::BubbleDismissalReason;
using BubbleTrigger =
......@@ -439,9 +440,9 @@ TEST_F(ManagePasswordsBubbleModelTest, RecordUKMs) {
true /*is_main_frame_secure*/, kTestSourceId);
// Exercise bubble.
ON_CALL(*controller(), GetPasswordFormMetricsRecorder())
ON_CALL(*delegate(), GetPasswordFormMetricsRecorder())
.WillByDefault(Return(recorder.get()));
ON_CALL(*controller(), GetCredentialSource())
ON_CALL(*delegate(), GetCredentialSource())
.WillByDefault(
Return(credential_management_api
? CredentialSourceType::kCredentialManagementAPI
......@@ -455,37 +456,37 @@ TEST_F(ManagePasswordsBubbleModelTest, RecordUKMs) {
if (interaction == BubbleDismissalReason::kAccepted) {
EXPECT_CALL(*GetStore(),
RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(),
EXPECT_CALL(*delegate(),
SavePassword(pending_password().username_value,
pending_password().password_value));
model()->OnSaveClicked();
controller()->OnSaveClicked();
} else if (interaction == BubbleDismissalReason::kDeclined &&
update) {
EXPECT_CALL(*controller(), SavePassword(_, _)).Times(0);
model()->OnNopeUpdateClicked();
EXPECT_CALL(*delegate(), SavePassword(_, _)).Times(0);
controller()->OnNopeUpdateClicked();
} else if (interaction == BubbleDismissalReason::kDeclined &&
!update) {
EXPECT_CALL(*GetStore(),
RemoveSiteStatsImpl(GURL(kSiteOrigin).GetOrigin()));
EXPECT_CALL(*controller(), SavePassword(_, _)).Times(0);
EXPECT_CALL(*controller(), NeverSavePassword());
model()->OnNeverForThisSiteClicked();
EXPECT_CALL(*delegate(), SavePassword(_, _)).Times(0);
EXPECT_CALL(*delegate(), NeverSavePassword());
controller()->OnNeverForThisSiteClicked();
} else if (interaction == BubbleDismissalReason::kIgnored && update) {
EXPECT_CALL(*controller(), SavePassword(_, _)).Times(0);
EXPECT_CALL(*controller(), NeverSavePassword()).Times(0);
EXPECT_CALL(*delegate(), SavePassword(_, _)).Times(0);
EXPECT_CALL(*delegate(), NeverSavePassword()).Times(0);
} else if (interaction == BubbleDismissalReason::kIgnored &&
!update) {
EXPECT_CALL(*GetStore(), AddSiteStatsImpl(testing::_));
EXPECT_CALL(*controller(), OnNoInteraction());
EXPECT_CALL(*controller(), SavePassword(_, _)).Times(0);
EXPECT_CALL(*controller(), NeverSavePassword()).Times(0);
EXPECT_CALL(*delegate(), OnNoInteraction());
EXPECT_CALL(*delegate(), SavePassword(_, _)).Times(0);
EXPECT_CALL(*delegate(), NeverSavePassword()).Times(0);
} else {
NOTREACHED();
}
DestroyModelAndVerifyControllerExpectations();
}
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(controller()));
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(delegate()));
// Flush async calls on password store.
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(GetStore()));
......@@ -520,102 +521,108 @@ TEST_F(ManagePasswordsBubbleModelTest, RecordUKMs) {
}
}
TEST_F(ManagePasswordsBubbleModelTest, EyeIcon_ReauthForPasswordsRevealing) {
for (bool is_manual_fallback_for_saving : {false, true}) {
for (bool form_has_autofilled_value : {false, true}) {
for (ManagePasswordsBubbleModel::DisplayReason display_reason :
{ManagePasswordsBubbleModel::AUTOMATIC,
ManagePasswordsBubbleModel::USER_ACTION}) {
class PendingBubbleControllerPasswordRevealingTest
: public PendingBubbleControllerTest,
public testing::WithParamInterface<
std::tuple<bool /*is manual fallback*/,
bool /*form has autofilled value*/,
bool /*does os support user authentication*/,
ManagePasswordsBubbleModel::DisplayReason>> {};
TEST_P(PendingBubbleControllerPasswordRevealingTest,
EyeIcon_ReauthForPasswordsRevealing) {
bool is_manual_fallback_for_saving = std::get<0>(GetParam());
bool form_has_autofilled_value = std::get<1>(GetParam());
bool does_os_support_user_auth = std::get<2>(GetParam());
ManagePasswordsBubbleModel::DisplayReason display_reason =
std::get<3>(GetParam());
// That state is impossible.
if (is_manual_fallback_for_saving &&
(display_reason == ManagePasswordsBubbleModel::AUTOMATIC))
continue;
SUCCEED();
SCOPED_TRACE(testing::Message()
<< "is_manual_fallback_for_saving = "
<< is_manual_fallback_for_saving
<< " form_has_autofilled_value = "
<< form_has_autofilled_value << " display_reason = "
<< " form_has_autofilled_value = " << form_has_autofilled_value
<< " display_reason = "
<< (display_reason == ManagePasswordsBubbleModel::AUTOMATIC
? "AUTOMATIC"
: "USER_ACTION"));
pending_password().form_has_autofilled_value =
form_has_autofilled_value;
EXPECT_CALL(*controller(), ArePasswordsRevealedWhenBubbleIsOpened())
pending_password().form_has_autofilled_value = form_has_autofilled_value;
EXPECT_CALL(*delegate(), ArePasswordsRevealedWhenBubbleIsOpened())
.WillOnce(Return(false));
EXPECT_CALL(*controller(), BubbleIsManualFallbackForSaving())
EXPECT_CALL(*delegate(), BubbleIsManualFallbackForSaving())
.WillRepeatedly(Return(is_manual_fallback_for_saving));
PretendPasswordWaiting(display_reason);
bool reauth_expected = form_has_autofilled_value;
if (!reauth_expected) {
reauth_expected =
!is_manual_fallback_for_saving &&
reauth_expected = !is_manual_fallback_for_saving &&
display_reason == ManagePasswordsBubbleModel::USER_ACTION;
}
EXPECT_EQ(reauth_expected,
model()->password_revealing_requires_reauth());
controller()->password_revealing_requires_reauth());
if (reauth_expected) {
EXPECT_CALL(*controller(), AuthenticateUser())
.WillOnce(Return(false));
EXPECT_FALSE(model()->RevealPasswords());
// delegate()->AuthenticateUser() is called only when reauth is expected.
EXPECT_CALL(*delegate(), AuthenticateUser())
.Times(reauth_expected)
.WillOnce(Return(!does_os_support_user_auth));
EXPECT_CALL(*controller(), AuthenticateUser()).WillOnce(Return(true));
EXPECT_TRUE(model()->RevealPasswords());
if (reauth_expected) {
EXPECT_EQ(controller()->RevealPasswords(), !does_os_support_user_auth);
} else {
EXPECT_TRUE(model()->RevealPasswords());
}
if (display_reason == ManagePasswordsBubbleModel::AUTOMATIC)
EXPECT_CALL(*GetStore(), AddSiteStatsImpl(_));
DestroyModelAndVerifyControllerExpectations();
// Flush async calls on password store.
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(GetStore()));
}
}
EXPECT_TRUE(controller()->RevealPasswords());
}
}
TEST_F(ManagePasswordsBubbleModelTest, EyeIcon_BubbleReopenedAfterAuth) {
INSTANTIATE_TEST_SUITE_P(
PendingBubbleController,
PendingBubbleControllerPasswordRevealingTest,
testing::Combine(testing::Bool(),
testing::Bool(),
testing::Bool(),
testing::Values(ManagePasswordsBubbleModel::AUTOMATIC,
ManagePasswordsBubbleModel::USER_ACTION)));
TEST_F(PendingBubbleControllerTest, EyeIcon_BubbleReopenedAfterAuth) {
// Checks re-authentication is not needed if the bubble is opened right after
// successful authentication.
pending_password().form_has_autofilled_value = true;
// After successful authentication this value is set to true.
EXPECT_CALL(*controller(), ArePasswordsRevealedWhenBubbleIsOpened())
EXPECT_CALL(*delegate(), ArePasswordsRevealedWhenBubbleIsOpened())
.WillOnce(Return(true));
PretendPasswordWaiting(ManagePasswordsBubbleModel::USER_ACTION);
EXPECT_FALSE(model()->password_revealing_requires_reauth());
EXPECT_TRUE(model()->RevealPasswords());
EXPECT_FALSE(controller()->password_revealing_requires_reauth());
EXPECT_TRUE(controller()->RevealPasswords());
}
TEST_F(ManagePasswordsBubbleModelTest, PasswordsRevealedReported) {
TEST_F(PendingBubbleControllerTest, PasswordsRevealedReported) {
PretendPasswordWaiting();
EXPECT_CALL(*controller(), OnPasswordsRevealed());
EXPECT_TRUE(model()->RevealPasswords());
EXPECT_CALL(*delegate(), OnPasswordsRevealed());
EXPECT_TRUE(controller()->RevealPasswords());
}
TEST_F(ManagePasswordsBubbleModelTest, PasswordsRevealedReportedAfterReauth) {
TEST_F(PendingBubbleControllerTest, PasswordsRevealedReportedAfterReauth) {
// The bubble is opened after reauthentication and the passwords are revealed.
pending_password().form_has_autofilled_value = true;
// After successful authentication this value is set to true.
EXPECT_CALL(*controller(), ArePasswordsRevealedWhenBubbleIsOpened())
EXPECT_CALL(*delegate(), ArePasswordsRevealedWhenBubbleIsOpened())
.WillOnce(Return(true));
EXPECT_CALL(*controller(), OnPasswordsRevealed());
EXPECT_CALL(*delegate(), OnPasswordsRevealed());
PretendPasswordWaiting(ManagePasswordsBubbleModel::USER_ACTION);
}
TEST_F(ManagePasswordsBubbleModelTest, DisableEditing) {
EXPECT_CALL(*controller(), BubbleIsManualFallbackForSaving())
TEST_F(PendingBubbleControllerTest, DisableEditing) {
EXPECT_CALL(*delegate(), BubbleIsManualFallbackForSaving())
.WillRepeatedly(Return(false));
EXPECT_CALL(*controller(), GetCredentialSource())
EXPECT_CALL(*delegate(), GetCredentialSource())
.WillOnce(Return(password_manager::metrics_util::CredentialSourceType::
kCredentialManagementAPI));
PretendPasswordWaiting();
EXPECT_FALSE(model()->enable_editing());
EXPECT_FALSE(controller()->enable_editing());
}
......@@ -39,132 +39,6 @@
namespace metrics_util = password_manager::metrics_util;
namespace {
using Store = autofill::PasswordForm::Store;
void CleanStatisticsForSite(Profile* profile, const GURL& origin) {
DCHECK(profile);
password_manager::PasswordStore* password_store =
PasswordStoreFactory::GetForProfile(profile,
ServiceAccessType::IMPLICIT_ACCESS)
.get();
password_store->RemoveSiteStats(origin.GetOrigin());
}
std::vector<autofill::PasswordForm> DeepCopyForms(
const std::vector<std::unique_ptr<autofill::PasswordForm>>& forms) {
std::vector<autofill::PasswordForm> result;
result.reserve(forms.size());
std::transform(forms.begin(), forms.end(), std::back_inserter(result),
[](const std::unique_ptr<autofill::PasswordForm>& form) {
return *form;
});
return result;
}
bool IsSyncUser(Profile* profile) {
const syncer::SyncService* sync_service =
ProfileSyncServiceFactory::GetForProfile(profile);
return password_bubble_experiment::IsSmartLockUser(sync_service);
}
} // namespace
// Class responsible for collecting and reporting all the runtime interactions
// with the bubble.
class ManagePasswordsBubbleModel::InteractionKeeper {
public:
InteractionKeeper(
password_manager::InteractionsStats stats,
password_manager::metrics_util::UIDisplayDisposition display_disposition);
~InteractionKeeper() = default;
// Records UMA events, updates the interaction statistics and sends
// notifications to the delegate when the bubble is closed.
void ReportInteractions(const ManagePasswordsBubbleModel* model);
void set_dismissal_reason(
password_manager::metrics_util::UIDismissalReason reason) {
dismissal_reason_ = reason;
}
void SetClockForTesting(base::Clock* clock) { clock_ = clock; }
private:
// The way the bubble appeared.
const password_manager::metrics_util::UIDisplayDisposition
display_disposition_;
// Dismissal reason for a password bubble.
password_manager::metrics_util::UIDismissalReason dismissal_reason_;
// Current statistics for the save password bubble;
password_manager::InteractionsStats interaction_stats_;
// Used to retrieve the current time, in base::Time units.
base::Clock* clock_;
DISALLOW_COPY_AND_ASSIGN(InteractionKeeper);
};
ManagePasswordsBubbleModel::InteractionKeeper::InteractionKeeper(
password_manager::InteractionsStats stats,
password_manager::metrics_util::UIDisplayDisposition display_disposition)
: display_disposition_(display_disposition),
dismissal_reason_(metrics_util::NO_DIRECT_INTERACTION),
interaction_stats_(std::move(stats)),
clock_(base::DefaultClock::GetInstance()) {}
void ManagePasswordsBubbleModel::InteractionKeeper::ReportInteractions(
const ManagePasswordsBubbleModel* model) {
if (model->state() == password_manager::ui::PENDING_PASSWORD_STATE) {
// Update the statistics for the save password bubble.
Profile* profile = model->GetProfile();
if (profile) {
if (dismissal_reason_ == metrics_util::NO_DIRECT_INTERACTION &&
display_disposition_ ==
metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING) {
if (interaction_stats_.dismissal_count <
std::numeric_limits<decltype(
interaction_stats_.dismissal_count)>::max())
interaction_stats_.dismissal_count++;
interaction_stats_.update_time = clock_->Now();
password_manager::PasswordStore* password_store =
PasswordStoreFactory::GetForProfile(
profile, ServiceAccessType::IMPLICIT_ACCESS)
.get();
password_store->AddSiteStats(interaction_stats_);
}
}
}
// Log UMA histograms.
if (model->state() == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) {
metrics_util::LogUpdateUIDismissalReason(dismissal_reason_);
} else if (model->state() == password_manager::ui::PENDING_PASSWORD_STATE) {
metrics_util::LogSaveUIDismissalReason(dismissal_reason_);
} else {
metrics_util::LogGeneralUIDismissalReason(dismissal_reason_);
}
// Update the delegate so that it can send votes to the server.
if (model->state() == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE ||
model->state() == password_manager::ui::PENDING_PASSWORD_STATE) {
// Send a notification if there was no interaction with the bubble.
bool no_interaction =
dismissal_reason_ == metrics_util::NO_DIRECT_INTERACTION;
if (no_interaction && model->delegate_) {
model->delegate_->OnNoInteraction();
}
}
// Record UKM statistics on dismissal reason.
if (model->metrics_recorder_)
model->metrics_recorder_->RecordUIDismissalReason(dismissal_reason_);
}
ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
base::WeakPtr<PasswordsModelDelegate> delegate,
DisplayReason display_reason)
......@@ -172,58 +46,12 @@ ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
interaction_reported_(false),
are_passwords_revealed_when_bubble_is_opened_(false),
metrics_recorder_(delegate_->GetPasswordFormMetricsRecorder()) {
origin_ = delegate_->GetOrigin();
state_ = delegate_->GetState();
password_manager::InteractionsStats interaction_stats;
if (state_ == password_manager::ui::PENDING_PASSWORD_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) {
pending_password_ = delegate_->GetPendingPassword();
local_credentials_ = DeepCopyForms(delegate_->GetCurrentForms());
if (state_ == password_manager::ui::PENDING_PASSWORD_STATE) {
interaction_stats.origin_domain = origin_.GetOrigin();
interaction_stats.username_value = pending_password_.username_value;
const password_manager::InteractionsStats* stats =
delegate_->GetCurrentInteractionStats();
if (stats) {
DCHECK_EQ(interaction_stats.username_value, stats->username_value);
DCHECK_EQ(interaction_stats.origin_domain, stats->origin_domain);
interaction_stats.dismissal_count = stats->dismissal_count;
}
}
if (delegate_->ArePasswordsRevealedWhenBubbleIsOpened()) {
are_passwords_revealed_when_bubble_is_opened_ = true;
delegate_->OnPasswordsRevealed();
}
// The condition for the password reauth:
// If the bubble opened after reauth -> no more reauth necessary, otherwise
// If a password was autofilled -> require reauth to view it, otherwise
// Require reauth iff the user opened the bubble manually and it's not the
// manual saving state. The manual saving state as well as automatic prompt
// are temporary states, therefore, it's better for the sake of convinience
// for the user not to break the UX with the reauth prompt.
password_revealing_requires_reauth_ =
!are_passwords_revealed_when_bubble_is_opened_ &&
(pending_password_.form_has_autofilled_value ||
(!delegate_->BubbleIsManualFallbackForSaving() &&
display_reason == USER_ACTION));
enable_editing_ = delegate_->GetCredentialSource() !=
password_manager::metrics_util::CredentialSourceType::
kCredentialManagementAPI;
UpdatePendingStateTitle();
}
password_manager::metrics_util::UIDisplayDisposition display_disposition =
metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING;
if (display_reason == USER_ACTION) {
switch (state_) {
case password_manager::ui::PENDING_PASSWORD_STATE:
display_disposition = metrics_util::MANUAL_WITH_PASSWORD_PENDING;
break;
case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE:
display_disposition = metrics_util::MANUAL_WITH_PASSWORD_PENDING_UPDATE;
break;
case password_manager::ui::MANAGE_STATE:
case password_manager::ui::CONFIRMATION_STATE:
case password_manager::ui::CREDENTIAL_REQUEST_STATE:
......@@ -236,12 +64,7 @@ ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
} else {
switch (state_) {
case password_manager::ui::PENDING_PASSWORD_STATE:
display_disposition = metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING;
break;
case password_manager::ui::PENDING_PASSWORD_UPDATE_STATE:
display_disposition =
metrics_util::AUTOMATIC_WITH_PASSWORD_PENDING_UPDATE;
break;
case password_manager::ui::CONFIRMATION_STATE:
case password_manager::ui::AUTO_SIGNIN_STATE:
case password_manager::ui::MANAGE_STATE:
......@@ -258,8 +81,6 @@ ManagePasswordsBubbleModel::ManagePasswordsBubbleModel(
delegate_->GetCredentialSource(), display_disposition);
}
metrics_util::LogUIDisplayDisposition(display_disposition);
interaction_keeper_ = std::make_unique<InteractionKeeper>(
std::move(interaction_stats), display_disposition);
delegate_->OnBubbleShown();
}
......@@ -270,56 +91,12 @@ ManagePasswordsBubbleModel::~ManagePasswordsBubbleModel() {
}
void ManagePasswordsBubbleModel::OnBubbleClosing() {
interaction_keeper_->ReportInteractions(this);
if (delegate_)
delegate_->OnBubbleHidden();
delegate_.reset();
interaction_reported_ = true;
}
void ManagePasswordsBubbleModel::OnNopeUpdateClicked() {
DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_UPDATE_STATE, state_);
interaction_keeper_->set_dismissal_reason(metrics_util::CLICKED_CANCEL);
if (delegate_)
delegate_->OnNopeUpdateClicked();
}
void ManagePasswordsBubbleModel::OnNeverForThisSiteClicked() {
DCHECK_EQ(password_manager::ui::PENDING_PASSWORD_STATE, state_);
interaction_keeper_->set_dismissal_reason(metrics_util::CLICKED_NEVER);
if (delegate_) {
CleanStatisticsForSite(GetProfile(), origin_);
delegate_->NeverSavePassword();
}
}
void ManagePasswordsBubbleModel::OnCredentialEdited(
base::string16 new_username,
base::string16 new_password) {
DCHECK(state_ == password_manager::ui::PENDING_PASSWORD_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE);
pending_password_.username_value = std::move(new_username);
pending_password_.password_value = std::move(new_password);
}
void ManagePasswordsBubbleModel::OnSaveClicked() {
DCHECK(state_ == password_manager::ui::PENDING_PASSWORD_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE);
interaction_keeper_->set_dismissal_reason(metrics_util::CLICKED_SAVE);
if (delegate_) {
CleanStatisticsForSite(GetProfile(), origin_);
delegate_->SavePassword(pending_password_.username_value,
pending_password_.password_value);
}
}
#if defined(PASSWORD_STORE_SELECT_ENABLED)
void ManagePasswordsBubbleModel::OnToggleAccountStore(bool is_checked) {
delegate_->GetPasswordFeatureManager()->SetDefaultPasswordStore(
is_checked ? Store::kAccountStore : Store::kProfileStore);
}
#endif // defined(PASSWORD_STORE_SELECT_ENABLED)
Profile* ManagePasswordsBubbleModel::GetProfile() const {
content::WebContents* web_contents = GetWebContents();
if (!web_contents)
......@@ -331,90 +108,4 @@ content::WebContents* ManagePasswordsBubbleModel::GetWebContents() const {
return delegate_ ? delegate_->GetWebContents() : nullptr;
}
bool ManagePasswordsBubbleModel::IsCurrentStateUpdate() const {
DCHECK(state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_STATE);
return std::any_of(local_credentials_.begin(), local_credentials_.end(),
[this](const autofill::PasswordForm& form) {
return form.username_value ==
pending_password_.username_value;
});
}
bool ManagePasswordsBubbleModel::ShouldShowFooter() const {
return (state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_STATE) &&
IsSyncUser(GetProfile());
}
int ManagePasswordsBubbleModel::GetTopIllustration(bool dark_mode) const {
if (state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE ||
state_ == password_manager::ui::PENDING_PASSWORD_STATE) {
int image = base::GetFieldTrialParamByFeatureAsInt(
password_manager::features::kPasswordSaveIllustration, "image", 0);
switch (image) {
case 1:
return dark_mode ? IDR_SAVE_PASSWORD1_DARK : IDR_SAVE_PASSWORD1;
case 2:
return dark_mode ? IDR_SAVE_PASSWORD2_DARK : IDR_SAVE_PASSWORD2;
case 3:
return dark_mode ? IDR_SAVE_PASSWORD3_DARK : IDR_SAVE_PASSWORD3;
default:
return 0;
}
}
return 0;
}
bool ManagePasswordsBubbleModel::ReplaceToShowPromotionIfNeeded() {
Profile* profile = GetProfile();
if (!profile)
return false;
PrefService* prefs = profile->GetPrefs();
const syncer::SyncService* sync_service =
ProfileSyncServiceFactory::GetForProfile(profile);
// Signin promotion.
if (password_bubble_experiment::ShouldShowChromeSignInPasswordPromo(
prefs, sync_service)) {
interaction_keeper_->ReportInteractions(this);
title_ = l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SYNC_PROMO_TITLE);
state_ = password_manager::ui::CHROME_SIGN_IN_PROMO_STATE;
int show_count = prefs->GetInteger(
password_manager::prefs::kNumberSignInPasswordPromoShown);
show_count++;
prefs->SetInteger(password_manager::prefs::kNumberSignInPasswordPromoShown,
show_count);
return true;
}
return false;
}
void ManagePasswordsBubbleModel::SetClockForTesting(base::Clock* clock) {
interaction_keeper_->SetClockForTesting(clock);
}
bool ManagePasswordsBubbleModel::RevealPasswords() {
bool reveal_immediately = !password_revealing_requires_reauth_ ||
(delegate_ && delegate_->AuthenticateUser());
if (reveal_immediately)
delegate_->OnPasswordsRevealed();
return reveal_immediately;
}
#if defined(PASSWORD_STORE_SELECT_ENABLED)
bool ManagePasswordsBubbleModel::IsUsingAccountStore() {
return delegate_->GetPasswordFeatureManager()->GetDefaultPasswordStore() ==
Store::kAccountStore;
}
#endif // defined(PASSWORD_STORE_SELECT_ENABLED)
void ManagePasswordsBubbleModel::UpdatePendingStateTitle() {
PasswordTitleType type =
state_ == password_manager::ui::PENDING_PASSWORD_UPDATE_STATE
? PasswordTitleType::UPDATE_PASSWORD
: (pending_password_.federation_origin.opaque()
? PasswordTitleType::SAVE_PASSWORD
: PasswordTitleType::SAVE_ACCOUNT);
GetSavePasswordDialogTitleTextAndLinkRange(GetWebContents()->GetVisibleURL(),
origin_, type, &title_);
}
void ManagePasswordsBubbleModel::SetClockForTesting(base::Clock* clock) {}
......@@ -46,27 +46,6 @@ class ManagePasswordsBubbleModel {
// closed. Otherwise, it is called later on when the model is destroyed.
void OnBubbleClosing();
// Called by the view code when the "Nope" button in clicked by the user in
// update bubble.
void OnNopeUpdateClicked();
// Called by the view code when the "Never for this site." button in clicked
// by the user.
void OnNeverForThisSiteClicked();
// Called by the view code when username or password is corrected using
// the username correction or password selection features in PendingView.
void OnCredentialEdited(base::string16 new_username,
base::string16 new_password);
// Called by the view code when the save/update button is clicked by the user.
void OnSaveClicked();
#if defined(PASSWORD_STORE_SELECT_ENABLED)
// Called by the view when the account store checkbox is toggled.
void OnToggleAccountStore(bool is_checked);
#endif // defined(PASSWORD_STORE_SELECT_ENABLED)
password_manager::ui::State state() const { return state_; }
const base::string16& title() const { return title_; }
......@@ -78,56 +57,15 @@ class ManagePasswordsBubbleModel {
return are_passwords_revealed_when_bubble_is_opened_;
}
#if defined(UNIT_TEST)
void allow_passwords_revealing() {
password_revealing_requires_reauth_ = false;
}
bool password_revealing_requires_reauth() const {
return password_revealing_requires_reauth_;
}
#endif
bool enable_editing() const { return enable_editing_; }
Profile* GetProfile() const;
content::WebContents* GetWebContents() const;
// The password bubble can switch its state between "save" and "update"
// depending on the user input. |state_| only captures the correct state on
// creation. This method returns true iff the current state is "update".
bool IsCurrentStateUpdate() const;
// Returns true iff the bubble is supposed to show the footer about syncing
// to Google account.
bool ShouldShowFooter() const;
// Returns the ID of the picture to show above the title.
int GetTopIllustration(bool dark_mode) const;
// Returns true and updates the internal state iff the Save bubble should
// switch to show a promotion after the password was saved. Otherwise,
// returns false and leaves the current state.
bool ReplaceToShowPromotionIfNeeded();
void SetClockForTesting(base::Clock* clock);
// Returns true if passwords revealing is not locked or re-authentication is
// not available on the given platform. Otherwise, the method schedules
// re-authentication and bubble reopen (the current bubble will be destroyed),
// and returns false immediately. New bubble will reveal the passwords if the
// re-authentication is successful.
bool RevealPasswords();
#if defined(PASSWORD_STORE_SELECT_ENABLED)
// Returns true iff the password account store is used.
bool IsUsingAccountStore();
#endif // defined(PASSWORD_STORE_SELECT_ENABLED)
private:
class InteractionKeeper;
// Updates |title_| for the PENDING_PASSWORD_STATE.
void UpdatePendingStateTitle();
// URL of the page from where this bubble was triggered.
GURL origin_;
......@@ -136,9 +74,6 @@ class ManagePasswordsBubbleModel {
autofill::PasswordForm pending_password_;
std::vector<autofill::PasswordForm> local_credentials_;
// Responsible for recording all the interactions required.
std::unique_ptr<InteractionKeeper> interaction_keeper_;
// A bridge to ManagePasswordsUIController instance.
base::WeakPtr<PasswordsModelDelegate> delegate_;
......@@ -146,9 +81,6 @@ class ManagePasswordsBubbleModel {
// the bubble is closing.
bool interaction_reported_;
// True iff password revealing should require re-auth for privacy reasons.
bool password_revealing_requires_reauth_;
// True iff bubble should pop up with revealed password value.
bool are_passwords_revealed_when_bubble_is_opened_;
......
......@@ -296,7 +296,7 @@ IN_PROC_BROWSER_TEST_F(PasswordBubbleInteractiveUiTest,
// PasswordBubbleViewBase::PendingView:: ButtonPressed(), and
// simulate the OS event queue by posting a task.
auto press_button = [](PasswordBubbleViewBase* bubble, bool* ran) {
bubble->model()->OnNeverForThisSiteClicked();
bubble->Cancel();
*ran = true;
};
......
......@@ -132,7 +132,10 @@ PasswordBubbleViewBase::PasswordBubbleViewBase(
// bubble controllers.
if (delegate->GetState() != password_manager::ui::AUTO_SIGNIN_STATE &&
delegate->GetState() != password_manager::ui::CONFIRMATION_STATE &&
delegate->GetState() != password_manager::ui::MANAGE_STATE) {
delegate->GetState() != password_manager::ui::MANAGE_STATE &&
delegate->GetState() != password_manager::ui::PENDING_PASSWORD_STATE &&
delegate->GetState() !=
password_manager::ui::PENDING_PASSWORD_UPDATE_STATE) {
model_ = std::make_unique<ManagePasswordsBubbleModel>(
delegate, reason == AUTOMATIC
? ManagePasswordsBubbleModel::AUTOMATIC
......
......@@ -14,6 +14,7 @@
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
#include "chrome/browser/ui/passwords/password_dialog_prompts.h"
#include "chrome/browser/ui/passwords/passwords_model_delegate.h"
#include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
......@@ -253,18 +254,22 @@ PasswordPendingView::PasswordPendingView(content::WebContents* web_contents,
anchor_view,
reason,
/*auto_dismissable=*/false),
is_update_bubble_(model()->state() ==
controller_(PasswordsModelDelegateFromWebContents(web_contents),
reason == AUTOMATIC
? ManagePasswordsBubbleModel::AUTOMATIC
: ManagePasswordsBubbleModel::USER_ACTION),
is_update_bubble_(controller_.state() ==
password_manager::ui::PENDING_PASSWORD_UPDATE_STATE),
sign_in_promo_(nullptr),
username_dropdown_(nullptr),
password_view_button_(nullptr),
password_dropdown_(nullptr),
are_passwords_revealed_(
model()->are_passwords_revealed_when_bubble_is_opened()) {
DCHECK(model()->state() == password_manager::ui::PENDING_PASSWORD_STATE ||
model()->state() ==
controller_.are_passwords_revealed_when_bubble_is_opened()) {
DCHECK(controller_.state() == password_manager::ui::PENDING_PASSWORD_STATE ||
controller_.state() ==
password_manager::ui::PENDING_PASSWORD_UPDATE_STATE);
const autofill::PasswordForm& password_form = model()->pending_password();
const autofill::PasswordForm& password_form = controller_.pending_password();
if (password_form.IsFederatedCredential()) {
// The credential to be saved doesn't contain password but just the identity
// provider (e.g. "Sign in with Google"). Thus, the layout is different.
......@@ -274,7 +279,7 @@ PasswordPendingView::PasswordPendingView(content::WebContents* web_contents,
CredentialsItemView* credential_view = new CredentialsItemView(
this, titles.first, titles.second, &password_form,
content::BrowserContext::GetDefaultStoragePartition(
model()->GetProfile())
controller_.GetProfile())
->GetURLLoaderFactoryForBrowserProcess()
.get());
credential_view->SetEnabled(false);
......@@ -301,7 +306,7 @@ PasswordPendingView::PasswordPendingView(content::WebContents* web_contents,
std::move(password_view_button));
#if defined(PASSWORD_STORE_SELECT_ENABLED)
account_store_checkbox_ = MaybeAppendAccountCheckboxRow(
layout, model()->IsUsingAccountStore(),
layout, controller_.IsUsingAccountStore(),
password_dropdown_->GetPreferredSize().height(), this);
#endif // defined(PASSWORD_STORE_SELECT_ENABLED)
}
......@@ -317,17 +322,17 @@ views::View* PasswordPendingView::GetUsernameTextfieldForTest() const {
PasswordPendingView::~PasswordPendingView() = default;
PasswordBubbleControllerBase* PasswordPendingView::GetController() {
return nullptr;
return &controller_;
}
const PasswordBubbleControllerBase* PasswordPendingView::GetController() const {
return nullptr;
return &controller_;
}
bool PasswordPendingView::Accept() {
UpdateUsernameAndPasswordInModel();
model()->OnSaveClicked();
if (model()->ReplaceToShowPromotionIfNeeded()) {
controller_.OnSaveClicked();
if (controller_.ReplaceToShowPromotionIfNeeded()) {
ReplaceWithPromo();
return false; // Keep open.
}
......@@ -337,10 +342,10 @@ bool PasswordPendingView::Accept() {
bool PasswordPendingView::Cancel() {
UpdateUsernameAndPasswordInModel();
if (is_update_bubble_) {
model()->OnNopeUpdateClicked();
controller_.OnNopeUpdateClicked();
return true;
}
model()->OnNeverForThisSiteClicked();
controller_.OnNeverForThisSiteClicked();
return true;
}
......@@ -353,7 +358,7 @@ void PasswordPendingView::ButtonPressed(views::Button* sender,
#if defined(PASSWORD_STORE_SELECT_ENABLED)
DCHECK(sender);
if (sender == account_store_checkbox_) {
model()->OnToggleAccountStore(account_store_checkbox_->GetChecked());
controller_.OnToggleAccountStore(account_store_checkbox_->GetChecked());
return;
}
#endif // defined(PASSWORD_STORE_SELECT_ENABLED)
......@@ -363,12 +368,12 @@ void PasswordPendingView::ButtonPressed(views::Button* sender,
void PasswordPendingView::OnContentChanged(
views::EditableCombobox* editable_combobox) {
bool is_update_state_before = model()->IsCurrentStateUpdate();
bool is_update_state_before = controller_.IsCurrentStateUpdate();
bool is_ok_button_enabled_before =
IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK);
UpdateUsernameAndPasswordInModel();
// Maybe the buttons should be updated.
if (is_update_state_before != model()->IsCurrentStateUpdate() ||
if (is_update_state_before != controller_.IsCurrentStateUpdate() ||
is_ok_button_enabled_before !=
IsDialogButtonEnabled(ui::DIALOG_BUTTON_OK)) {
UpdateDialogButtons();
......@@ -400,8 +405,8 @@ views::View* PasswordPendingView::GetInitiallyFocusedView() {
bool PasswordPendingView::IsDialogButtonEnabled(ui::DialogButton button) const {
return button != ui::DIALOG_BUTTON_OK ||
model()->pending_password().IsFederatedCredential() ||
!model()->pending_password().password_value.empty();
controller_.pending_password().IsFederatedCredential() ||
!controller_.pending_password().password_value.empty();
}
gfx::ImageSkia PasswordPendingView::GetWindowIcon() {
......@@ -422,14 +427,14 @@ void PasswordPendingView::AddedToWidget() {
}
void PasswordPendingView::OnThemeChanged() {
if (int id = model()->GetTopIllustration(
if (int id = controller_.GetTopIllustration(
color_utils::IsDark(GetBubbleFrameView()->GetBackgroundColor()))) {
GetBubbleFrameView()->SetHeaderView(CreateHeaderImage(id));
}
}
void PasswordPendingView::TogglePasswordVisibility() {
if (!are_passwords_revealed_ && !model()->RevealPasswords())
if (!are_passwords_revealed_ && !controller_.RevealPasswords())
return;
are_passwords_revealed_ = !are_passwords_revealed_;
......@@ -441,15 +446,16 @@ void PasswordPendingView::TogglePasswordVisibility() {
void PasswordPendingView::UpdateUsernameAndPasswordInModel() {
if (!username_dropdown_ && !password_dropdown_)
return;
base::string16 new_username = model()->pending_password().username_value;
base::string16 new_password = model()->pending_password().password_value;
base::string16 new_username = controller_.pending_password().username_value;
base::string16 new_password = controller_.pending_password().password_value;
if (username_dropdown_) {
new_username = username_dropdown_->GetText();
base::TrimString(new_username, base::ASCIIToUTF16(" "), &new_username);
}
if (password_dropdown_)
new_password = password_dropdown_->GetText();
model()->OnCredentialEdited(std::move(new_username), std::move(new_password));
controller_.OnCredentialEdited(std::move(new_username),
std::move(new_password));
}
void PasswordPendingView::ReplaceWithPromo() {
......@@ -466,8 +472,8 @@ void PasswordPendingView::ReplaceWithPromo() {
SetLayoutManager(std::make_unique<views::FillLayout>());
set_margins(ChromeLayoutProvider::Get()->GetDialogInsetsForContentType(
views::TEXT, views::TEXT));
if (model()->state() == password_manager::ui::CHROME_SIGN_IN_PROMO_STATE) {
sign_in_promo_ = new PasswordSignInPromoView(model()->GetWebContents());
if (controller_.state() == password_manager::ui::CHROME_SIGN_IN_PROMO_STATE) {
sign_in_promo_ = new PasswordSignInPromoView(controller_.GetWebContents());
AddChildView(sign_in_promo_);
} else {
NOTREACHED();
......@@ -490,7 +496,7 @@ void PasswordPendingView::UpdateDialogButtons() {
(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL));
DialogDelegate::set_button_label(
ui::DIALOG_BUTTON_OK,
l10n_util::GetStringUTF16(model()->IsCurrentStateUpdate()
l10n_util::GetStringUTF16(controller_.IsCurrentStateUpdate()
? IDS_PASSWORD_MANAGER_UPDATE_BUTTON
: IDS_PASSWORD_MANAGER_SAVE_BUTTON));
DialogDelegate::set_button_label(
......@@ -501,7 +507,7 @@ void PasswordPendingView::UpdateDialogButtons() {
}
std::unique_ptr<views::View> PasswordPendingView::CreateFooterView() {
if (!model()->ShouldShowFooter())
if (!controller_.ShouldShowFooter())
return nullptr;
auto label = std::make_unique<views::Label>(
l10n_util::GetStringUTF16(IDS_SAVE_PASSWORD_FOOTER),
......
......@@ -5,6 +5,7 @@
#ifndef CHROME_BROWSER_UI_VIEWS_PASSWORDS_PASSWORD_PENDING_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_PASSWORDS_PASSWORD_PENDING_VIEW_H_
#include "chrome/browser/ui/passwords/bubble_controllers/pending_bubble_controller.h"
#include "chrome/browser/ui/views/passwords/password_bubble_view_base.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/editable_combobox/editable_combobox_listener.h"
......@@ -69,6 +70,8 @@ class PasswordPendingView : public PasswordBubbleViewBase,
void UpdateDialogButtons();
std::unique_ptr<views::View> CreateFooterView();
PendingBubbleController controller_;
// True iff it is an update password bubble on creation. False iff it is a
// save bubble.
const bool is_update_bubble_;
......
......@@ -4058,10 +4058,10 @@ test("unit_tests") {
"../browser/ui/passwords/bubble_controllers/auto_sign_in_bubble_controller_unittest.cc",
"../browser/ui/passwords/bubble_controllers/generation_confirmation_bubble_controller_unittest.cc",
"../browser/ui/passwords/bubble_controllers/items_bubble_controller_unittest.cc",
"../browser/ui/passwords/bubble_controllers/pending_bubble_controller_unittest.cc",
"../browser/ui/passwords/bubble_controllers/sign_in_promo_bubble_controller_unittest.cc",
"../browser/ui/passwords/credential_leak_dialog_controller_impl_unittest.cc",
"../browser/ui/passwords/credential_manager_dialog_controller_impl_unittest.cc",
"../browser/ui/passwords/manage_passwords_bubble_model_unittest.cc",
"../browser/ui/recently_audible_helper_unittest.cc",
"../browser/ui/search/ntp_user_data_logger_unittest.cc",
"../browser/ui/search/search_ipc_router_policy_unittest.cc",
......
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